asciify-engine 1.0.71 → 1.0.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -2859
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -2820
- package/dist/index.js.map +1 -1
- package/package.json +60 -2
package/dist/index.cjs
CHANGED
|
@@ -1,2859 +1 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var gifuctJs = require('gifuct-js');
|
|
4
|
-
|
|
5
|
-
// src/types.ts
|
|
6
|
-
var PALETTE_THEMES = {
|
|
7
|
-
dracula: { name: "Dracula", accent: "#bd93f9", bg: "#282a36", fg: "#f8f8f2" },
|
|
8
|
-
monokai: { name: "Monokai", accent: "#a6e22e", bg: "#272822", fg: "#f8f8f2" },
|
|
9
|
-
nord: { name: "Nord", accent: "#88c0d0", bg: "#2e3440", fg: "#eceff4" },
|
|
10
|
-
catppuccin: { name: "Catppuccin", accent: "#cba6f7", bg: "#1e1e2e", fg: "#cdd6f4" },
|
|
11
|
-
solarized: { name: "Solarized", accent: "#268bd2", bg: "#002b36", fg: "#839496" },
|
|
12
|
-
gruvbox: { name: "Gruvbox", accent: "#b8bb26", bg: "#282828", fg: "#ebdbb2" }
|
|
13
|
-
};
|
|
14
|
-
var CHARSETS = {
|
|
15
|
-
standard: " .:-=+*#%@",
|
|
16
|
-
blocks: " \u2591\u2592\u2593\u2588",
|
|
17
|
-
minimal: " .:+",
|
|
18
|
-
dense: " .'`^\",:;Il!i><~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$",
|
|
19
|
-
binary: "01",
|
|
20
|
-
dots: " \u2801\u2803\u2807\u2847\u28C7\u28E7\u28F7\u28FF",
|
|
21
|
-
letters: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
|
22
|
-
claudeCode: " \u2554\u2557\u255A\u255D\u2551\u2550\u2560\u2563\u2566\u2569\u256C\u2591\u2592\u2593\u2588\u2502\u2500\u250C\u2510\u2514\u2518\u251C\u2524\u252C\u2534\u253C",
|
|
23
|
-
box: " \u25AA\u25FE\u25FC\u25A0\u2588",
|
|
24
|
-
lines: " \u02D7\u2010\u2013\u2014\u2015\u2501",
|
|
25
|
-
braille: " \u2801\u2802\u2803\u2804\u2805\u2806\u2807\u2808\u2809\u280A\u280B\u280C\u280D\u280E\u280F\u2810\u2811\u2812\u2813\u2814\u2815\u2816\u2817\u2818\u2819\u281A\u281B\u281C\u281D\u281E\u281F\u2820\u2821\u2822\u2823\u2824\u2825\u2826\u2827\u2828\u2829\u282A\u282B\u282C\u282D\u282E\u282F\u2830\u2831\u2832\u2833\u2834\u2835\u2836\u2837\u2838\u2839\u283A\u283B\u283C\u283D\u283E\u283F\u2840\u2841\u2842\u2843\u2844\u2845\u2846\u2847\u28C0\u28C1\u28C2\u28C3\u28C4\u28C5\u28C6\u28C7\u28C8\u28C9\u28CA\u28CB\u28CC\u28CD\u28CE\u28CF\u28D0\u28D1\u28D2\u28D3\u28D4\u28D5\u28D6\u28D7\u28D8\u28D9\u28DA\u28DB\u28DC\u28DD\u28DE\u28DF\u28E0\u28E1\u28E2\u28E3\u28E4\u28E5\u28E6\u28E7\u28E8\u28E9\u28EA\u28EB\u28EC\u28ED\u28EE\u28EF\u28F0\u28F1\u28F2\u28F3\u28F4\u28F5\u28F6\u28F7\u28F8\u28F9\u28FA\u28FB\u28FC\u28FD\u28FE\u28FF",
|
|
26
|
-
katakana: " \uFF66\uFF67\uFF68\uFF69\uFF6A\uFF6B\uFF6C\uFF6D\uFF6E\uFF6F\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\uFF9D",
|
|
27
|
-
musical: " \u2669\u266A\u266B\u266C\u266D\u266E\u266F",
|
|
28
|
-
emoji: " \u2B1B\u{1F7EB}\u{1F7E5}\u{1F7E7}\u{1F7E8}\u{1F7E9}\u{1F7E6}\u{1F7EA}\u2B1C",
|
|
29
|
-
circles: " .\xB7:\u2218\u25CB\u25E6\xB0\u2022\u2219",
|
|
30
|
-
shadows: " \xB7\u2218\u25E6\u25CB\u25CE\u2299\u25CF\u25C9",
|
|
31
|
-
starfield: " \u02D9\xB7\u2218\u2217\u2726\u2727\u2605\u25C6\u25CF",
|
|
32
|
-
geometric: " \xB7\u25B3\u25B7\u25C7\u25C8\u25C6\u25A3\u25A0\u2588",
|
|
33
|
-
pipes: " \u2576\u2500\u2510\u2514\u251C\u2524\u252C\u2534\u253C\u256C\u2592\u2593\u2588",
|
|
34
|
-
waves: " \u02DC\u223C\u2248\u3030\u224B\u223F\u223E\u222D\u222B",
|
|
35
|
-
shards: " \u2571\u2572\u2573\u25E4\u25E5\u25E3\u25E2\u25B3\u25B2\u25C6\u25FC\u2588",
|
|
36
|
-
smoke: " \xB7\u02D9\u205A\u2056\u2236\u2237\u22EE\u22F0\u22F1\u2234\u2235"
|
|
37
|
-
};
|
|
38
|
-
var CHARSET_SEQUENCES = {
|
|
39
|
-
/** Stars → softcircles → orbs — dreamy space feel */
|
|
40
|
-
cosmic: [CHARSETS.starfield, CHARSETS.circles, CHARSETS.shadows],
|
|
41
|
-
/** Katakana → braille dots → binary — hacker rain */
|
|
42
|
-
rain: [CHARSETS.katakana, CHARSETS.braille, CHARSETS.binary],
|
|
43
|
-
/** Box pipes → Claude glyphs → classic — terminal morph */
|
|
44
|
-
terminal: [CHARSETS.pipes, CHARSETS.claudeCode, CHARSETS.standard],
|
|
45
|
-
/** Shards → blocks → squares — shattering crystal */
|
|
46
|
-
crystal: [CHARSETS.shards, CHARSETS.geometric, CHARSETS.blocks],
|
|
47
|
-
/** Wave glyphs → smoke dots → circles — fluid / organic */
|
|
48
|
-
fluid: [CHARSETS.waves, CHARSETS.smoke, CHARSETS.circles],
|
|
49
|
-
/** Dense classic → art → blocks — maximum detail pulse */
|
|
50
|
-
pulse: [CHARSETS.dense, CHARSETS.standard, CHARSETS.blocks],
|
|
51
|
-
/** Braille → shadows → smoke — ethereal / dream-like */
|
|
52
|
-
dream: [CHARSETS.braille, CHARSETS.shadows, CHARSETS.smoke],
|
|
53
|
-
/** Geometric shapes → shards → starfield — sci-fi angular */
|
|
54
|
-
angular: [CHARSETS.geometric, CHARSETS.shards, CHARSETS.starfield]
|
|
55
|
-
};
|
|
56
|
-
var ART_STYLE_PRESETS = {
|
|
57
|
-
classic: {
|
|
58
|
-
renderMode: "ascii",
|
|
59
|
-
charset: CHARSETS.standard,
|
|
60
|
-
colorMode: "grayscale"
|
|
61
|
-
},
|
|
62
|
-
particles: {
|
|
63
|
-
renderMode: "dots",
|
|
64
|
-
colorMode: "fullcolor",
|
|
65
|
-
dotSizeRatio: 0.8
|
|
66
|
-
},
|
|
67
|
-
letters: {
|
|
68
|
-
renderMode: "ascii",
|
|
69
|
-
charset: CHARSETS.letters,
|
|
70
|
-
colorMode: "fullcolor"
|
|
71
|
-
},
|
|
72
|
-
claudeCode: {
|
|
73
|
-
renderMode: "ascii",
|
|
74
|
-
charset: CHARSETS.claudeCode,
|
|
75
|
-
colorMode: "accent",
|
|
76
|
-
accentColor: "#f97316"
|
|
77
|
-
},
|
|
78
|
-
art: {
|
|
79
|
-
renderMode: "ascii",
|
|
80
|
-
charset: CHARSETS.dense,
|
|
81
|
-
colorMode: "fullcolor"
|
|
82
|
-
},
|
|
83
|
-
terminal: {
|
|
84
|
-
renderMode: "ascii",
|
|
85
|
-
charset: CHARSETS.standard,
|
|
86
|
-
colorMode: "matrix"
|
|
87
|
-
},
|
|
88
|
-
box: {
|
|
89
|
-
renderMode: "ascii",
|
|
90
|
-
charset: CHARSETS.box,
|
|
91
|
-
colorMode: "grayscale"
|
|
92
|
-
},
|
|
93
|
-
lines: {
|
|
94
|
-
renderMode: "ascii",
|
|
95
|
-
charset: CHARSETS.lines,
|
|
96
|
-
colorMode: "fullcolor"
|
|
97
|
-
},
|
|
98
|
-
braille: {
|
|
99
|
-
renderMode: "ascii",
|
|
100
|
-
charset: CHARSETS.braille,
|
|
101
|
-
colorMode: "fullcolor"
|
|
102
|
-
},
|
|
103
|
-
katakana: {
|
|
104
|
-
renderMode: "ascii",
|
|
105
|
-
charset: CHARSETS.katakana,
|
|
106
|
-
colorMode: "matrix"
|
|
107
|
-
},
|
|
108
|
-
musical: {
|
|
109
|
-
renderMode: "ascii",
|
|
110
|
-
charset: CHARSETS.musical,
|
|
111
|
-
colorMode: "accent",
|
|
112
|
-
accentColor: "#e040fb"
|
|
113
|
-
},
|
|
114
|
-
emoji: {
|
|
115
|
-
renderMode: "ascii",
|
|
116
|
-
charset: CHARSETS.emoji,
|
|
117
|
-
colorMode: "fullcolor"
|
|
118
|
-
},
|
|
119
|
-
circles: {
|
|
120
|
-
renderMode: "ascii",
|
|
121
|
-
charset: CHARSETS.circles,
|
|
122
|
-
colorMode: "accent",
|
|
123
|
-
accentColor: "#d4ff00"
|
|
124
|
-
},
|
|
125
|
-
shadows: {
|
|
126
|
-
renderMode: "ascii",
|
|
127
|
-
charset: CHARSETS.shadows,
|
|
128
|
-
colorMode: "accent",
|
|
129
|
-
accentColor: "#50a0ff"
|
|
130
|
-
},
|
|
131
|
-
starfield: {
|
|
132
|
-
renderMode: "ascii",
|
|
133
|
-
charset: CHARSETS.starfield,
|
|
134
|
-
colorMode: "fullcolor"
|
|
135
|
-
},
|
|
136
|
-
geometric: {
|
|
137
|
-
renderMode: "ascii",
|
|
138
|
-
charset: CHARSETS.geometric,
|
|
139
|
-
colorMode: "grayscale"
|
|
140
|
-
},
|
|
141
|
-
pipes: {
|
|
142
|
-
renderMode: "ascii",
|
|
143
|
-
charset: CHARSETS.pipes,
|
|
144
|
-
colorMode: "accent",
|
|
145
|
-
accentColor: "#00ff88"
|
|
146
|
-
},
|
|
147
|
-
waves: {
|
|
148
|
-
renderMode: "ascii",
|
|
149
|
-
charset: CHARSETS.waves,
|
|
150
|
-
colorMode: "fullcolor"
|
|
151
|
-
},
|
|
152
|
-
shards: {
|
|
153
|
-
renderMode: "ascii",
|
|
154
|
-
charset: CHARSETS.shards,
|
|
155
|
-
colorMode: "grayscale"
|
|
156
|
-
},
|
|
157
|
-
smoke: {
|
|
158
|
-
renderMode: "ascii",
|
|
159
|
-
charset: CHARSETS.smoke,
|
|
160
|
-
colorMode: "accent",
|
|
161
|
-
accentColor: "#c850ff"
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
var DEFAULT_OPTIONS = {
|
|
165
|
-
fontSize: 10,
|
|
166
|
-
charSpacing: 1,
|
|
167
|
-
brightness: 0,
|
|
168
|
-
contrast: 0,
|
|
169
|
-
charset: CHARSETS.standard,
|
|
170
|
-
colorMode: "grayscale",
|
|
171
|
-
accentColor: "#d4ff00",
|
|
172
|
-
invert: false,
|
|
173
|
-
renderMode: "ascii",
|
|
174
|
-
animationStyle: "none",
|
|
175
|
-
animationSpeed: 1,
|
|
176
|
-
dotSizeRatio: 0.8,
|
|
177
|
-
ditherStrength: 0,
|
|
178
|
-
charAspect: 0.55,
|
|
179
|
-
normalize: false,
|
|
180
|
-
hoverStrength: 0,
|
|
181
|
-
hoverRadius: 0.2,
|
|
182
|
-
hoverEffect: "spotlight",
|
|
183
|
-
hoverColor: "#ffffff",
|
|
184
|
-
artStyle: "classic",
|
|
185
|
-
customText: "",
|
|
186
|
-
chromaKey: null,
|
|
187
|
-
chromaKeyTolerance: 60
|
|
188
|
-
};
|
|
189
|
-
var HOVER_PRESETS = {
|
|
190
|
-
none: {
|
|
191
|
-
label: "Off",
|
|
192
|
-
options: { hoverStrength: 0, hoverEffect: "spotlight", hoverRadius: 0.2, hoverColor: "#ffffff" }
|
|
193
|
-
},
|
|
194
|
-
subtle: {
|
|
195
|
-
label: "Subtle",
|
|
196
|
-
options: { hoverStrength: 0.25, hoverEffect: "glow", hoverRadius: 0.12, hoverColor: "#ffffff" }
|
|
197
|
-
},
|
|
198
|
-
flashlight: {
|
|
199
|
-
label: "Flashlight",
|
|
200
|
-
options: { hoverStrength: 0.6, hoverEffect: "spotlight", hoverRadius: 0.15, hoverColor: "#fffbe6" }
|
|
201
|
-
},
|
|
202
|
-
magnifier: {
|
|
203
|
-
label: "Magnifier",
|
|
204
|
-
options: { hoverStrength: 0.7, hoverEffect: "magnify", hoverRadius: 0.12, hoverColor: "#ffffff" }
|
|
205
|
-
},
|
|
206
|
-
forceField: {
|
|
207
|
-
label: "Force Field",
|
|
208
|
-
options: { hoverStrength: 0.7, hoverEffect: "repel", hoverRadius: 0.15, hoverColor: "#a0e8ff" }
|
|
209
|
-
},
|
|
210
|
-
neon: {
|
|
211
|
-
label: "Neon",
|
|
212
|
-
options: { hoverStrength: 0.6, hoverEffect: "colorShift", hoverRadius: 0.15, hoverColor: "#d946ef" }
|
|
213
|
-
},
|
|
214
|
-
fire: {
|
|
215
|
-
label: "Fire",
|
|
216
|
-
options: { hoverStrength: 0.7, hoverEffect: "spotlight", hoverRadius: 0.15, hoverColor: "#ff6b2b" }
|
|
217
|
-
},
|
|
218
|
-
ice: {
|
|
219
|
-
label: "Ice",
|
|
220
|
-
options: { hoverStrength: 0.5, hoverEffect: "glow", hoverRadius: 0.15, hoverColor: "#60d5f7" }
|
|
221
|
-
},
|
|
222
|
-
gravity: {
|
|
223
|
-
label: "Gravity",
|
|
224
|
-
options: { hoverStrength: 0.7, hoverEffect: "attract", hoverRadius: 0.18, hoverColor: "#a5d6ff" }
|
|
225
|
-
},
|
|
226
|
-
shatter: {
|
|
227
|
-
label: "Shatter",
|
|
228
|
-
options: { hoverStrength: 0.8, hoverEffect: "shatter", hoverRadius: 0.14, hoverColor: "#ff6090" }
|
|
229
|
-
},
|
|
230
|
-
ghost: {
|
|
231
|
-
label: "Ghost",
|
|
232
|
-
options: { hoverStrength: 0.55, hoverEffect: "trail", hoverRadius: 0.2, hoverColor: "#b39ddb" }
|
|
233
|
-
}
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
// src/core/utils.ts
|
|
237
|
-
function createOffscreenCanvas(width, height) {
|
|
238
|
-
const canvas = document.createElement("canvas");
|
|
239
|
-
canvas.width = width;
|
|
240
|
-
canvas.height = height;
|
|
241
|
-
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
|
242
|
-
return { canvas, ctx };
|
|
243
|
-
}
|
|
244
|
-
function adjustLuminance(lum, brightness, contrast) {
|
|
245
|
-
let adjusted = lum + brightness * 255;
|
|
246
|
-
const factor = 259 * (contrast * 255 + 255) / (255 * (259 - contrast * 255));
|
|
247
|
-
adjusted = factor * (adjusted - 128) + 128;
|
|
248
|
-
return Math.max(0, Math.min(255, adjusted));
|
|
249
|
-
}
|
|
250
|
-
function luminanceToChar(lum, charset, invert) {
|
|
251
|
-
const normalized = invert ? 1 - lum / 255 : lum / 255;
|
|
252
|
-
const chars = [...charset];
|
|
253
|
-
const index = Math.floor(normalized * (chars.length - 1));
|
|
254
|
-
return chars[Math.max(0, Math.min(chars.length - 1, index))];
|
|
255
|
-
}
|
|
256
|
-
function customTextToChar(lum, text, x, y, cols, invert) {
|
|
257
|
-
const normalized = invert ? 1 - lum / 255 : lum / 255;
|
|
258
|
-
if (normalized < 0.12) return " ";
|
|
259
|
-
const chars = [...text];
|
|
260
|
-
const pos = y * cols + x;
|
|
261
|
-
return chars[pos % chars.length];
|
|
262
|
-
}
|
|
263
|
-
var BAYER_4X4 = [
|
|
264
|
-
[0, 8, 2, 10],
|
|
265
|
-
[12, 4, 14, 6],
|
|
266
|
-
[3, 11, 1, 9],
|
|
267
|
-
[15, 7, 13, 5]
|
|
268
|
-
];
|
|
269
|
-
function applyDither(lum, x, y, strength) {
|
|
270
|
-
if (strength <= 0) return lum;
|
|
271
|
-
const threshold = (BAYER_4X4[y % 4][x % 4] / 16 - 0.5) * strength * 128;
|
|
272
|
-
return Math.max(0, Math.min(255, lum + threshold));
|
|
273
|
-
}
|
|
274
|
-
var GRAY_LUT = new Array(256);
|
|
275
|
-
var GREEN_LUT = new Array(256);
|
|
276
|
-
for (let _i = 0; _i < 256; _i++) {
|
|
277
|
-
GRAY_LUT[_i] = `rgb(${_i},${_i},${_i})`;
|
|
278
|
-
GREEN_LUT[_i] = `rgb(0,${_i},0)`;
|
|
279
|
-
}
|
|
280
|
-
function getCellColorStr(cell, colorMode, acR, acG, acB, _isInverted = false) {
|
|
281
|
-
switch (colorMode) {
|
|
282
|
-
case "fullcolor":
|
|
283
|
-
return `rgb(${cell.r},${cell.g},${cell.b})`;
|
|
284
|
-
case "matrix":
|
|
285
|
-
return GREEN_LUT[0.299 * cell.r + 0.587 * cell.g + 0.114 * cell.b | 0];
|
|
286
|
-
case "accent": {
|
|
287
|
-
return `rgb(${acR},${acG},${acB})`;
|
|
288
|
-
}
|
|
289
|
-
default: {
|
|
290
|
-
const gray = 0.299 * cell.r + 0.587 * cell.g + 0.114 * cell.b | 0;
|
|
291
|
-
return GRAY_LUT[gray];
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
var _colorRGB = [0, 0, 0];
|
|
296
|
-
function getCellColorRGB(cell, colorMode, acR, acG, acB, _isInverted = false) {
|
|
297
|
-
switch (colorMode) {
|
|
298
|
-
case "fullcolor":
|
|
299
|
-
_colorRGB[0] = cell.r;
|
|
300
|
-
_colorRGB[1] = cell.g;
|
|
301
|
-
_colorRGB[2] = cell.b;
|
|
302
|
-
break;
|
|
303
|
-
case "matrix": {
|
|
304
|
-
const mb = 0.299 * cell.r + 0.587 * cell.g + 0.114 * cell.b | 0;
|
|
305
|
-
_colorRGB[0] = 0;
|
|
306
|
-
_colorRGB[1] = mb;
|
|
307
|
-
_colorRGB[2] = 0;
|
|
308
|
-
break;
|
|
309
|
-
}
|
|
310
|
-
case "accent": {
|
|
311
|
-
_colorRGB[0] = acR;
|
|
312
|
-
_colorRGB[1] = acG;
|
|
313
|
-
_colorRGB[2] = acB;
|
|
314
|
-
break;
|
|
315
|
-
}
|
|
316
|
-
default: {
|
|
317
|
-
const gray = 0.299 * cell.r + 0.587 * cell.g + 0.114 * cell.b | 0;
|
|
318
|
-
_colorRGB[0] = gray;
|
|
319
|
-
_colorRGB[1] = gray;
|
|
320
|
-
_colorRGB[2] = gray;
|
|
321
|
-
break;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
return _colorRGB;
|
|
325
|
-
}
|
|
326
|
-
function parseChromaKeyColor(color) {
|
|
327
|
-
if (typeof color !== "string") return color;
|
|
328
|
-
const canvas = document.createElement("canvas");
|
|
329
|
-
canvas.width = 1;
|
|
330
|
-
canvas.height = 1;
|
|
331
|
-
const ctx = canvas.getContext("2d");
|
|
332
|
-
ctx.fillStyle = color;
|
|
333
|
-
ctx.fillRect(0, 0, 1, 1);
|
|
334
|
-
const d = ctx.getImageData(0, 0, 1, 1).data;
|
|
335
|
-
return { r: d[0], g: d[1], b: d[2] };
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// src/core/animation.ts
|
|
339
|
-
function smoothstep(t) {
|
|
340
|
-
return t * t * (3 - 2 * t);
|
|
341
|
-
}
|
|
342
|
-
function getAnimationMultiplier(x, y, cols, rows, time, style, speed) {
|
|
343
|
-
if (style === "none") return 1;
|
|
344
|
-
const t = time * speed;
|
|
345
|
-
switch (style) {
|
|
346
|
-
case "wave": {
|
|
347
|
-
const wave = Math.sin(x / cols * Math.PI * 4 + t * 3) * 0.5 + 0.5;
|
|
348
|
-
const wave2 = Math.sin(y / rows * Math.PI * 3 + t * 2) * 0.5 + 0.5;
|
|
349
|
-
return 0.3 + 0.7 * (wave * 0.6 + wave2 * 0.4);
|
|
350
|
-
}
|
|
351
|
-
case "pulse": {
|
|
352
|
-
const cx = cols / 2;
|
|
353
|
-
const cy = rows / 2;
|
|
354
|
-
const dist = Math.sqrt((x - cx) ** 2 + (y - cy) ** 2);
|
|
355
|
-
const maxDist = Math.sqrt(cx ** 2 + cy ** 2);
|
|
356
|
-
const ring = Math.sin(dist / maxDist * Math.PI * 6 - t * 4) * 0.5 + 0.5;
|
|
357
|
-
return 0.2 + 0.8 * ring;
|
|
358
|
-
}
|
|
359
|
-
case "rain": {
|
|
360
|
-
const drop = Math.sin(y / rows * Math.PI * 8 - t * 5 + x * 0.3) * 0.5 + 0.5;
|
|
361
|
-
const fade2 = Math.sin(x / cols * Math.PI * 2 + t) * 0.3 + 0.7;
|
|
362
|
-
return 0.1 + 0.9 * drop * fade2;
|
|
363
|
-
}
|
|
364
|
-
case "breathe": {
|
|
365
|
-
const breathe = Math.sin(t * 2) * 0.3 + 0.7;
|
|
366
|
-
const subtle = Math.sin((x + y) * 0.1 + t) * 0.1;
|
|
367
|
-
return Math.max(0.1, Math.min(1, breathe + subtle));
|
|
368
|
-
}
|
|
369
|
-
case "sparkle": {
|
|
370
|
-
const hash = Math.sin(x * 127.1 + y * 311.7 + Math.floor(t * 8) * 43758.5453) * 43758.5453;
|
|
371
|
-
const sparkle = hash - Math.floor(hash);
|
|
372
|
-
return sparkle > 0.7 ? 1 : 0.15 + sparkle * 0.4;
|
|
373
|
-
}
|
|
374
|
-
case "glitch": {
|
|
375
|
-
const band = Math.floor(y / (rows * 0.05));
|
|
376
|
-
const glitchHash = Math.sin(band * 43.23 + Math.floor(t * 6) * 17.89) * 43758.5453;
|
|
377
|
-
const glitchVal = glitchHash - Math.floor(glitchHash);
|
|
378
|
-
if (glitchVal > 0.85) {
|
|
379
|
-
const flicker = Math.sin(t * 30 + band) * 0.5 + 0.5;
|
|
380
|
-
return flicker < 0.3 ? 0 : flicker;
|
|
381
|
-
}
|
|
382
|
-
return 1;
|
|
383
|
-
}
|
|
384
|
-
case "spiral": {
|
|
385
|
-
const cx = cols / 2;
|
|
386
|
-
const cy = rows / 2;
|
|
387
|
-
const dx = x - cx;
|
|
388
|
-
const dy = y - cy;
|
|
389
|
-
const angle = Math.atan2(dy, dx);
|
|
390
|
-
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
391
|
-
const maxDist = Math.sqrt(cx * cx + cy * cy);
|
|
392
|
-
const spiral = Math.sin(angle * 3 + dist / maxDist * Math.PI * 8 - t * 3) * 0.5 + 0.5;
|
|
393
|
-
return 0.15 + 0.85 * spiral;
|
|
394
|
-
}
|
|
395
|
-
case "typewriter": {
|
|
396
|
-
const totalCells = cols * rows;
|
|
397
|
-
const cellIndex = y * cols + x;
|
|
398
|
-
const progress = t * 0.5 % 1;
|
|
399
|
-
const revealPoint = progress * totalCells * 1.3;
|
|
400
|
-
const dist = cellIndex - revealPoint;
|
|
401
|
-
if (dist > 0) return 0;
|
|
402
|
-
const fade2 = Math.max(0, 1 + dist / (totalCells * 0.15));
|
|
403
|
-
return Math.min(1, fade2);
|
|
404
|
-
}
|
|
405
|
-
case "scatter": {
|
|
406
|
-
const scx = cols / 2;
|
|
407
|
-
const scy = rows / 2;
|
|
408
|
-
const sdx = x - scx;
|
|
409
|
-
const sdy = y - scy;
|
|
410
|
-
const sdist = Math.sqrt(sdx * sdx + sdy * sdy) / Math.sqrt(scx * scx + scy * scy);
|
|
411
|
-
const phase = Math.sin(t * 1.5) * 0.5 + 0.5;
|
|
412
|
-
const threshold = phase;
|
|
413
|
-
if (sdist > threshold) {
|
|
414
|
-
return Math.max(0, 1 - (sdist - threshold) * 3);
|
|
415
|
-
}
|
|
416
|
-
return 0.7 + 0.3 * Math.sin(sdist * 10 - t * 2);
|
|
417
|
-
}
|
|
418
|
-
case "waveField":
|
|
419
|
-
return 1;
|
|
420
|
-
case "ripple": {
|
|
421
|
-
const cx2 = cols / 2;
|
|
422
|
-
const cy2 = rows / 2;
|
|
423
|
-
const dx2 = x - cx2;
|
|
424
|
-
const dy2 = y - cy2;
|
|
425
|
-
const dist2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
|
|
426
|
-
const maxDist2 = Math.sqrt(cx2 * cx2 + cy2 * cy2);
|
|
427
|
-
const ripple = Math.sin(dist2 / maxDist2 * Math.PI * 10 - t * 5) * 0.5 + 0.5;
|
|
428
|
-
const fadeEdge = 1 - Math.min(1, dist2 / (maxDist2 * 1.1));
|
|
429
|
-
return 0.1 + 0.9 * ripple * (0.4 + fadeEdge * 0.6);
|
|
430
|
-
}
|
|
431
|
-
case "melt": {
|
|
432
|
-
const lagPhase = y / rows * Math.PI;
|
|
433
|
-
const drip = Math.sin(lagPhase - t * 1.8 + x * 0.15) * 0.5 + 0.5;
|
|
434
|
-
const gravity = Math.max(0, y / rows - 0.1) / 0.9;
|
|
435
|
-
return 0.05 + 0.95 * (drip * (1 - gravity * 0.6));
|
|
436
|
-
}
|
|
437
|
-
case "orbit": {
|
|
438
|
-
const ocx = cols / 2;
|
|
439
|
-
const ocy = rows / 2;
|
|
440
|
-
const odx = x - ocx;
|
|
441
|
-
const ody = y - ocy;
|
|
442
|
-
const oAngle = Math.atan2(ody, odx);
|
|
443
|
-
const oDist = Math.sqrt(odx * odx + ody * ody) / Math.sqrt(ocx * ocx + ocy * ocy);
|
|
444
|
-
const orbit = Math.sin(oAngle * 2 + oDist * 6 - t * 2.5) * 0.5 + 0.5;
|
|
445
|
-
return 0.1 + 0.9 * orbit;
|
|
446
|
-
}
|
|
447
|
-
case "cellular": {
|
|
448
|
-
const tick = Math.floor(t * 4);
|
|
449
|
-
const alive = (cx, cy) => {
|
|
450
|
-
const h = Math.sin(cx * 127.1 + cy * 311.7 + tick * 43758.5453) * 43758.5453;
|
|
451
|
-
return h - Math.floor(h) > 0.38 ? 1 : 0;
|
|
452
|
-
};
|
|
453
|
-
const self = alive(x, y);
|
|
454
|
-
const neighbours = alive(x - 1, y) + alive(x + 1, y) + alive(x, y - 1) + alive(x, y + 1) + alive(x - 1, y - 1) + alive(x + 1, y - 1) + alive(x - 1, y + 1) + alive(x + 1, y + 1);
|
|
455
|
-
const nextAlive = self === 1 ? neighbours === 2 || neighbours === 3 ? 1 : 0 : neighbours === 3 ? 1 : 0;
|
|
456
|
-
return nextAlive === 1 ? 1 : 0.05;
|
|
457
|
-
}
|
|
458
|
-
default:
|
|
459
|
-
return 1;
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
var _hoverResult = { scale: 1, offsetX: 0, offsetY: 0, glow: 0, colorBlend: 0, proximity: 0 };
|
|
463
|
-
function computeHoverEffect(nx, ny, hoverX, hoverY, hoverIntensity, strength, cellW, cellH, effect = "spotlight", radiusFactor = 0.5) {
|
|
464
|
-
const dx = nx - hoverX;
|
|
465
|
-
const dy = ny - hoverY;
|
|
466
|
-
const distSq = dx * dx + dy * dy;
|
|
467
|
-
const radius = 0.08 + radiusFactor * 0.35 + strength * 0.04;
|
|
468
|
-
if (distSq >= radius * radius) {
|
|
469
|
-
_hoverResult.scale = 1;
|
|
470
|
-
_hoverResult.offsetX = 0;
|
|
471
|
-
_hoverResult.offsetY = 0;
|
|
472
|
-
_hoverResult.glow = 0;
|
|
473
|
-
_hoverResult.colorBlend = 0;
|
|
474
|
-
_hoverResult.proximity = 0;
|
|
475
|
-
return _hoverResult;
|
|
476
|
-
}
|
|
477
|
-
const dist = Math.sqrt(distSq);
|
|
478
|
-
const t = 1 - dist / radius;
|
|
479
|
-
const eased = smoothstep(t) * hoverIntensity;
|
|
480
|
-
let scale = 1;
|
|
481
|
-
let offsetX = 0;
|
|
482
|
-
let offsetY = 0;
|
|
483
|
-
let glow = 0;
|
|
484
|
-
let colorBlend = 0;
|
|
485
|
-
switch (effect) {
|
|
486
|
-
case "spotlight": {
|
|
487
|
-
scale = 1 + eased * strength * 1.8;
|
|
488
|
-
const angle = Math.atan2(dy, dx);
|
|
489
|
-
const pushForce = eased * eased * strength * 0.6;
|
|
490
|
-
offsetX = Math.cos(angle) * pushForce * cellW;
|
|
491
|
-
offsetY = Math.sin(angle) * pushForce * cellH;
|
|
492
|
-
glow = eased * strength * 0.4;
|
|
493
|
-
colorBlend = eased * eased * strength * 0.25;
|
|
494
|
-
break;
|
|
495
|
-
}
|
|
496
|
-
case "magnify":
|
|
497
|
-
scale = 1 + eased * strength * 2.5;
|
|
498
|
-
glow = eased * strength * 0.15;
|
|
499
|
-
break;
|
|
500
|
-
case "repel": {
|
|
501
|
-
scale = 1 + eased * strength * 0.3;
|
|
502
|
-
const angle2 = Math.atan2(dy, dx);
|
|
503
|
-
const push = eased * eased * strength * 1.2;
|
|
504
|
-
offsetX = Math.cos(angle2) * push * cellW;
|
|
505
|
-
offsetY = Math.sin(angle2) * push * cellH;
|
|
506
|
-
break;
|
|
507
|
-
}
|
|
508
|
-
case "glow":
|
|
509
|
-
glow = eased * strength * 0.8;
|
|
510
|
-
colorBlend = eased * strength * 0.4;
|
|
511
|
-
break;
|
|
512
|
-
case "colorShift":
|
|
513
|
-
scale = 1 + eased * strength * 0.4;
|
|
514
|
-
glow = eased * strength * 0.2;
|
|
515
|
-
colorBlend = eased * strength * 0.7;
|
|
516
|
-
break;
|
|
517
|
-
case "attract": {
|
|
518
|
-
const angle3 = Math.atan2(dy, dx);
|
|
519
|
-
const pull = eased * eased * strength * 1;
|
|
520
|
-
offsetX = -Math.cos(angle3) * pull * cellW;
|
|
521
|
-
offsetY = -Math.sin(angle3) * pull * cellH;
|
|
522
|
-
glow = eased * strength * 0.3;
|
|
523
|
-
break;
|
|
524
|
-
}
|
|
525
|
-
case "shatter": {
|
|
526
|
-
const angle4 = Math.atan2(dy, dx);
|
|
527
|
-
const jitter = Math.sin(dx * 43.7 + dy * 29.3) * 0.5;
|
|
528
|
-
const scatter = eased * strength * 1.4 * (0.7 + jitter * 0.3);
|
|
529
|
-
offsetX = Math.cos(angle4 + jitter) * scatter * cellW;
|
|
530
|
-
offsetY = Math.sin(angle4 + jitter) * scatter * cellH;
|
|
531
|
-
scale = Math.max(0.1, 1 - eased * strength * 0.6);
|
|
532
|
-
glow = eased * strength * 0.25;
|
|
533
|
-
break;
|
|
534
|
-
}
|
|
535
|
-
case "trail": {
|
|
536
|
-
colorBlend = eased * strength * 0.9;
|
|
537
|
-
glow = eased * strength * 0.6;
|
|
538
|
-
scale = 1 + eased * strength * 0.15;
|
|
539
|
-
break;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
_hoverResult.scale = scale;
|
|
543
|
-
_hoverResult.offsetX = offsetX;
|
|
544
|
-
_hoverResult.offsetY = offsetY;
|
|
545
|
-
_hoverResult.glow = glow;
|
|
546
|
-
_hoverResult.colorBlend = colorBlend;
|
|
547
|
-
_hoverResult.proximity = eased;
|
|
548
|
-
return _hoverResult;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
// src/backgrounds/_shared.ts
|
|
552
|
-
function parseColor(c) {
|
|
553
|
-
const hex = c.match(/^#([0-9a-f]{3,8})$/i)?.[1];
|
|
554
|
-
if (hex) {
|
|
555
|
-
const h = hex.length <= 4 ? hex.split("").map((x) => parseInt(x + x, 16)) : [parseInt(hex.slice(0, 2), 16), parseInt(hex.slice(2, 4), 16), parseInt(hex.slice(4, 6), 16)];
|
|
556
|
-
return { r: h[0], g: h[1], b: h[2] };
|
|
557
|
-
}
|
|
558
|
-
const rgb = c.match(/rgba?\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)/i);
|
|
559
|
-
if (rgb) return { r: +rgb[1], g: +rgb[2], b: +rgb[3] };
|
|
560
|
-
return null;
|
|
561
|
-
}
|
|
562
|
-
function fade(t) {
|
|
563
|
-
return t * t * t * (t * (t * 6 - 15) + 10);
|
|
564
|
-
}
|
|
565
|
-
function lerp(a, b, t) {
|
|
566
|
-
return a + (b - a) * t;
|
|
567
|
-
}
|
|
568
|
-
function hash2(ix, iy) {
|
|
569
|
-
let n = ix * 127 + iy * 311;
|
|
570
|
-
n = n >> 13 ^ n;
|
|
571
|
-
return (n * (n * n * 15731 + 789221) + 1376312589 & 2147483647) / 2147483647;
|
|
572
|
-
}
|
|
573
|
-
function vnoise(x, y) {
|
|
574
|
-
const ix = Math.floor(x), iy = Math.floor(y);
|
|
575
|
-
const fx = x - ix, fy = y - iy;
|
|
576
|
-
const ux = fade(fx), uy = fade(fy);
|
|
577
|
-
const v00 = hash2(ix, iy);
|
|
578
|
-
const v10 = hash2(ix + 1, iy);
|
|
579
|
-
const v01 = hash2(ix, iy + 1);
|
|
580
|
-
const v11 = hash2(ix + 1, iy + 1);
|
|
581
|
-
return lerp(lerp(v00, v10, ux), lerp(v01, v11, ux), uy);
|
|
582
|
-
}
|
|
583
|
-
function fbm(x, y) {
|
|
584
|
-
return (vnoise(x, y) * 0.5 + vnoise(x * 2.1, y * 2.1) * 0.25 + vnoise(x * 4.3, y * 4.3) * 0.125) / 0.875;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
// src/backgrounds/wave.ts
|
|
588
|
-
function renderWaveBackground(ctx, width, height, time, mousePos = { x: 0.5, y: 0.5 }, options = {}) {
|
|
589
|
-
const {
|
|
590
|
-
fontSize = 13,
|
|
591
|
-
charAspect = 0.62,
|
|
592
|
-
lineHeightRatio = 1.4,
|
|
593
|
-
chars = " .:-=+*#%@",
|
|
594
|
-
baseColor = null,
|
|
595
|
-
accentColor = void 0,
|
|
596
|
-
accentThreshold = 0.52,
|
|
597
|
-
mouseInfluence = 0.55,
|
|
598
|
-
mouseFalloff = 2.8,
|
|
599
|
-
speed = 1,
|
|
600
|
-
vortex = true,
|
|
601
|
-
sparkles = true,
|
|
602
|
-
breathe = true,
|
|
603
|
-
lightMode = false
|
|
604
|
-
} = options;
|
|
605
|
-
const resolvedAccent = accentColor ?? (lightMode ? "#6b8700" : "#d4ff00");
|
|
606
|
-
const charW = fontSize * charAspect;
|
|
607
|
-
const lineH = fontSize * lineHeightRatio;
|
|
608
|
-
const cols = Math.ceil(width / charW);
|
|
609
|
-
const rows = Math.ceil(height / lineH);
|
|
610
|
-
const mx = mousePos.x;
|
|
611
|
-
const my = mousePos.y;
|
|
612
|
-
let acR = 212, acG = 255, acB = 0;
|
|
613
|
-
{
|
|
614
|
-
const hex = resolvedAccent.replace("#", "");
|
|
615
|
-
if (hex.length === 6) {
|
|
616
|
-
acR = parseInt(hex.slice(0, 2), 16);
|
|
617
|
-
acG = parseInt(hex.slice(2, 4), 16);
|
|
618
|
-
acB = parseInt(hex.slice(4, 6), 16);
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
ctx.clearRect(0, 0, width, height);
|
|
622
|
-
ctx.font = `${fontSize}px "JetBrains Mono", monospace`;
|
|
623
|
-
ctx.textBaseline = "top";
|
|
624
|
-
const t = time * speed;
|
|
625
|
-
const breatheAmp = breathe ? (Math.sin(t * 0.22) * 0.5 + 0.5) * 0.12 : 0;
|
|
626
|
-
for (let row = 0; row < rows; row++) {
|
|
627
|
-
for (let col = 0; col < cols; col++) {
|
|
628
|
-
const nx = col / cols;
|
|
629
|
-
const ny = row / rows;
|
|
630
|
-
const w1 = Math.sin(col * 0.08 + row * 0.05 + t * 0.6) * 0.5 + 0.5;
|
|
631
|
-
const w2 = Math.sin(col * 0.03 - row * 0.07 + t * 0.4) * 0.5 + 0.5;
|
|
632
|
-
const w3 = Math.sin(col * 0.05 + row * 0.03 + t * 0.8) * 0.5 + 0.5;
|
|
633
|
-
const sinePart = (w1 + w2 + w3) / 3;
|
|
634
|
-
const noiseScale = 0.045;
|
|
635
|
-
const noiseShift = t * 0.08;
|
|
636
|
-
const noisePart = fbm(col * noiseScale + noiseShift, row * noiseScale * 1.4 - noiseShift * 0.7) * 0.5 + 0.5;
|
|
637
|
-
const driftFreq = 0.06;
|
|
638
|
-
const driftPart = Math.sin((col + row * 0.65) * driftFreq + t * 1.1) * 0.5 + 0.5;
|
|
639
|
-
const wavePart = sinePart * 0.45 + noisePart * 0.35 + driftPart * 0.2 + breatheAmp;
|
|
640
|
-
const dxRaw = nx - mx;
|
|
641
|
-
const dyRaw = ny - my;
|
|
642
|
-
const distRaw = Math.sqrt(dxRaw * dxRaw + dyRaw * dyRaw);
|
|
643
|
-
let vortexBump = 0;
|
|
644
|
-
if (vortex && distRaw < 0.35) {
|
|
645
|
-
const angle = Math.atan2(dyRaw, dxRaw);
|
|
646
|
-
const swirl = Math.sin(angle * 4 + t * 2.2 - distRaw * 14);
|
|
647
|
-
const falloff = Math.max(0, 1 - distRaw / 0.35);
|
|
648
|
-
vortexBump = swirl * falloff * falloff * 0.22;
|
|
649
|
-
}
|
|
650
|
-
const proximity = Math.max(0, 1 - distRaw * mouseFalloff);
|
|
651
|
-
const intensity = wavePart * (1 - mouseInfluence) + (proximity + vortexBump * 0.5) * mouseInfluence + vortexBump * 0.15;
|
|
652
|
-
const clamped = Math.min(1, Math.max(0, intensity));
|
|
653
|
-
let finalIntensity = clamped;
|
|
654
|
-
if (sparkles && clamped > 0.72) {
|
|
655
|
-
const bucket = Math.floor(t * 8);
|
|
656
|
-
const sparkleSeed = hash2(col * 7 + bucket * 3, row * 11 + bucket);
|
|
657
|
-
if (sparkleSeed > 0.88) {
|
|
658
|
-
finalIntensity = Math.min(1, clamped + (sparkleSeed - 0.88) * 4);
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
const charIdx = Math.floor(finalIntensity * (chars.length - 1));
|
|
662
|
-
if (chars[charIdx] === " ") continue;
|
|
663
|
-
const alpha = 0.015 + finalIntensity * 0.07;
|
|
664
|
-
const isAccent = finalIntensity > accentThreshold;
|
|
665
|
-
if (isAccent) {
|
|
666
|
-
const accentAlpha = Math.min(lightMode ? 0.9 : 0.28, alpha * (lightMode ? 14 : 2.8));
|
|
667
|
-
ctx.fillStyle = `rgba(${acR},${acG},${acB},${accentAlpha})`;
|
|
668
|
-
} else if (baseColor) {
|
|
669
|
-
ctx.fillStyle = baseColor.replace("{a}", String(alpha));
|
|
670
|
-
} else if (lightMode) {
|
|
671
|
-
ctx.fillStyle = `rgba(55,55,55,${alpha * 7})`;
|
|
672
|
-
} else {
|
|
673
|
-
ctx.fillStyle = `rgba(255,255,255,${alpha})`;
|
|
674
|
-
}
|
|
675
|
-
ctx.fillText(chars[charIdx], col * charW, row * lineH);
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
// src/core/renderer.ts
|
|
681
|
-
function resolveInvert(invert) {
|
|
682
|
-
if (invert !== "auto") return invert;
|
|
683
|
-
return typeof window !== "undefined" && !window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
684
|
-
}
|
|
685
|
-
function cssValueToHex(val) {
|
|
686
|
-
const hexMatch = val.match(/^#([0-9a-fA-F]{3,6})$/);
|
|
687
|
-
if (hexMatch) {
|
|
688
|
-
let h = hexMatch[1];
|
|
689
|
-
if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
|
|
690
|
-
if (h.length === 6) return h;
|
|
691
|
-
}
|
|
692
|
-
const rgbMatch = val.match(/rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/);
|
|
693
|
-
if (rgbMatch) {
|
|
694
|
-
return [rgbMatch[1], rgbMatch[2], rgbMatch[3]].map((n) => parseInt(n).toString(16).padStart(2, "0")).join("");
|
|
695
|
-
}
|
|
696
|
-
return null;
|
|
697
|
-
}
|
|
698
|
-
function resolveAccentHex(accentColor) {
|
|
699
|
-
const v = accentColor || "auto";
|
|
700
|
-
if (v !== "auto") return v.replace("#", "");
|
|
701
|
-
if (typeof document !== "undefined") {
|
|
702
|
-
const rootStyle = getComputedStyle(document.documentElement);
|
|
703
|
-
for (const prop of ["--accent-color", "--color-accent", "--accent", "--color-primary", "--primary", "--brand-color"]) {
|
|
704
|
-
const hex = cssValueToHex(rootStyle.getPropertyValue(prop).trim());
|
|
705
|
-
if (hex) return hex;
|
|
706
|
-
}
|
|
707
|
-
const native = cssValueToHex(getComputedStyle(document.body).accentColor ?? "");
|
|
708
|
-
if (native) return native;
|
|
709
|
-
}
|
|
710
|
-
const isDark = typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
711
|
-
return isDark ? "faf9f7" : "0d0d0d";
|
|
712
|
-
}
|
|
713
|
-
function imageToAsciiFrame(source, options, targetWidth, targetHeight) {
|
|
714
|
-
const srcWidth = source instanceof HTMLVideoElement ? source.videoWidth : source.width;
|
|
715
|
-
const srcHeight = source instanceof HTMLVideoElement ? source.videoHeight : source.height;
|
|
716
|
-
if (srcWidth === 0 || srcHeight === 0) {
|
|
717
|
-
return { frame: [], cols: 0, rows: 0 };
|
|
718
|
-
}
|
|
719
|
-
const charAspect = options.charAspect;
|
|
720
|
-
const cellW = options.fontSize * options.charSpacing;
|
|
721
|
-
const cellH = options.fontSize / charAspect * options.charSpacing;
|
|
722
|
-
const renderW = targetWidth || srcWidth;
|
|
723
|
-
const renderH = targetHeight || srcHeight;
|
|
724
|
-
const cols = Math.floor(renderW / cellW);
|
|
725
|
-
const rows = Math.floor(renderH / cellH);
|
|
726
|
-
if (cols <= 0 || rows <= 0) {
|
|
727
|
-
return { frame: [], cols: 0, rows: 0 };
|
|
728
|
-
}
|
|
729
|
-
const maxDim = 2048;
|
|
730
|
-
const ssX = Math.max(1, Math.min(Math.floor(maxDim / cols), Math.floor(srcWidth / cols)));
|
|
731
|
-
const ssY = Math.max(1, Math.min(Math.floor(maxDim / rows), Math.floor(srcHeight / rows)));
|
|
732
|
-
const sampleW = cols * ssX;
|
|
733
|
-
const sampleH = rows * ssY;
|
|
734
|
-
const { ctx } = createOffscreenCanvas(sampleW, sampleH);
|
|
735
|
-
ctx.drawImage(source, 0, 0, sampleW, sampleH);
|
|
736
|
-
const imageData = ctx.getImageData(0, 0, sampleW, sampleH);
|
|
737
|
-
const pixels = imageData.data;
|
|
738
|
-
const ck = options.chromaKey;
|
|
739
|
-
const ckEnabled = ck != null && ck !== false;
|
|
740
|
-
const ckHeuristicGreen = ck === true;
|
|
741
|
-
const ckHeuristicBlue = ck === "blue-screen";
|
|
742
|
-
let ckRGB = null;
|
|
743
|
-
let ckTolSq = 0;
|
|
744
|
-
if (ckEnabled && !ckHeuristicGreen && !ckHeuristicBlue) {
|
|
745
|
-
ckRGB = parseChromaKeyColor(ck);
|
|
746
|
-
ckTolSq = (options.chromaKeyTolerance ?? 60) ** 2;
|
|
747
|
-
}
|
|
748
|
-
let normMin = 0;
|
|
749
|
-
let normRange = 255;
|
|
750
|
-
if (options.normalize) {
|
|
751
|
-
let lo = 255, hi = 0;
|
|
752
|
-
for (let k = 0; k < pixels.length; k += 4) {
|
|
753
|
-
if (ckEnabled) {
|
|
754
|
-
const pr = pixels[k], pg = pixels[k + 1], pb = pixels[k + 2];
|
|
755
|
-
let keyed = false;
|
|
756
|
-
if (ckHeuristicGreen) {
|
|
757
|
-
keyed = pg > pr * 1.4 && pg > pb * 1.4 && pg > 80;
|
|
758
|
-
} else if (ckHeuristicBlue) {
|
|
759
|
-
keyed = pb > pr * 1.4 && pb > pg * 1.4 && pb > 80;
|
|
760
|
-
} else if (ckRGB !== null) {
|
|
761
|
-
const dr = pr - ckRGB.r, dg = pg - ckRGB.g, db = pb - ckRGB.b;
|
|
762
|
-
keyed = dr * dr + dg * dg + db * db <= ckTolSq;
|
|
763
|
-
}
|
|
764
|
-
if (keyed) continue;
|
|
765
|
-
}
|
|
766
|
-
const l = 0.299 * pixels[k] + 0.587 * pixels[k + 1] + 0.114 * pixels[k + 2];
|
|
767
|
-
if (l < lo) lo = l;
|
|
768
|
-
if (l > hi) hi = l;
|
|
769
|
-
}
|
|
770
|
-
normMin = lo;
|
|
771
|
-
normRange = hi > lo ? hi - lo : 255;
|
|
772
|
-
}
|
|
773
|
-
const frame = [];
|
|
774
|
-
const invertVal = resolveInvert(options.invert);
|
|
775
|
-
const effectiveCharset = ckEnabled ? options.charset.replace(/ /g, "") || options.charset : options.charset;
|
|
776
|
-
const ssCount = ssX * ssY;
|
|
777
|
-
for (let y = 0; y < rows; y++) {
|
|
778
|
-
const row = [];
|
|
779
|
-
for (let x = 0; x < cols; x++) {
|
|
780
|
-
let sumR = 0, sumG = 0, sumB = 0, sumA = 0;
|
|
781
|
-
let keyedCount = 0;
|
|
782
|
-
for (let sy = 0; sy < ssY; sy++) {
|
|
783
|
-
const rowOff = (y * ssY + sy) * sampleW;
|
|
784
|
-
for (let sx = 0; sx < ssX; sx++) {
|
|
785
|
-
const i = (rowOff + x * ssX + sx) * 4;
|
|
786
|
-
const pr = pixels[i], pg = pixels[i + 1], pb = pixels[i + 2], pa = pixels[i + 3];
|
|
787
|
-
if (ckEnabled) {
|
|
788
|
-
let keyed = false;
|
|
789
|
-
if (ckHeuristicGreen) {
|
|
790
|
-
keyed = pg > pr * 1.4 && pg > pb * 1.4 && pg > 80;
|
|
791
|
-
} else if (ckHeuristicBlue) {
|
|
792
|
-
keyed = pb > pr * 1.4 && pb > pg * 1.4 && pb > 80;
|
|
793
|
-
} else if (ckRGB !== null) {
|
|
794
|
-
const dr = pr - ckRGB.r, dg = pg - ckRGB.g, db = pb - ckRGB.b;
|
|
795
|
-
keyed = dr * dr + dg * dg + db * db <= ckTolSq;
|
|
796
|
-
}
|
|
797
|
-
if (keyed) {
|
|
798
|
-
keyedCount++;
|
|
799
|
-
continue;
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
sumR += pr;
|
|
803
|
-
sumG += pg;
|
|
804
|
-
sumB += pb;
|
|
805
|
-
sumA += pa;
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
if (ckEnabled && keyedCount > ssCount / 2) {
|
|
809
|
-
row.push({ char: " ", r: 0, g: 0, b: 0, a: 0 });
|
|
810
|
-
continue;
|
|
811
|
-
}
|
|
812
|
-
const nonKeyed = ssCount - keyedCount;
|
|
813
|
-
const r = nonKeyed > 0 ? sumR / nonKeyed : 0;
|
|
814
|
-
const g = nonKeyed > 0 ? sumG / nonKeyed : 0;
|
|
815
|
-
const b = nonKeyed > 0 ? sumB / nonKeyed : 0;
|
|
816
|
-
const a = nonKeyed > 0 ? sumA / nonKeyed : 0;
|
|
817
|
-
const rawLum = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
818
|
-
const lum = options.normalize ? (rawLum - normMin) / normRange * 255 : rawLum;
|
|
819
|
-
const adjustedLum = adjustLuminance(lum, options.brightness, options.contrast);
|
|
820
|
-
const ditheredLum = applyDither(adjustedLum, x, y, options.ditherStrength);
|
|
821
|
-
const char = options.customText ? customTextToChar(ditheredLum, options.customText, x, y, cols, invertVal) : luminanceToChar(ditheredLum, effectiveCharset, invertVal);
|
|
822
|
-
row.push({ char, r, g, b, a, lum: ditheredLum });
|
|
823
|
-
}
|
|
824
|
-
frame.push(row);
|
|
825
|
-
}
|
|
826
|
-
return { frame, cols, rows };
|
|
827
|
-
}
|
|
828
|
-
async function videoToAsciiFrames(video, options, targetWidth, targetHeight, targetFps = 12, maxDuration = 10, onProgress, startTime = 0) {
|
|
829
|
-
const duration = Math.min(video.duration - startTime, maxDuration);
|
|
830
|
-
const totalFrames = Math.ceil(duration * targetFps);
|
|
831
|
-
const frames = [];
|
|
832
|
-
let cols = 0;
|
|
833
|
-
let rows = 0;
|
|
834
|
-
for (let i = 0; i < totalFrames; i++) {
|
|
835
|
-
const time = startTime + i / targetFps;
|
|
836
|
-
if (time > startTime + duration) break;
|
|
837
|
-
video.currentTime = time;
|
|
838
|
-
await new Promise((resolve) => {
|
|
839
|
-
const handler = () => {
|
|
840
|
-
video.removeEventListener("seeked", handler);
|
|
841
|
-
resolve();
|
|
842
|
-
};
|
|
843
|
-
video.addEventListener("seeked", handler);
|
|
844
|
-
});
|
|
845
|
-
const result = imageToAsciiFrame(video, options, targetWidth, targetHeight);
|
|
846
|
-
frames.push(result.frame);
|
|
847
|
-
cols = result.cols;
|
|
848
|
-
rows = result.rows;
|
|
849
|
-
onProgress?.((i + 1) / totalFrames);
|
|
850
|
-
}
|
|
851
|
-
return { frames, cols, rows, fps: targetFps };
|
|
852
|
-
}
|
|
853
|
-
async function gifToAsciiFrames(buffer, options, targetWidth, targetHeight, onProgress) {
|
|
854
|
-
const gif = gifuctJs.parseGIF(buffer);
|
|
855
|
-
const rawFrames = gifuctJs.decompressFrames(gif, true);
|
|
856
|
-
if (rawFrames.length === 0) {
|
|
857
|
-
return { frames: [], cols: 0, rows: 0, fps: 10 };
|
|
858
|
-
}
|
|
859
|
-
const gifW = rawFrames[0].dims.width;
|
|
860
|
-
const gifH = rawFrames[0].dims.height;
|
|
861
|
-
const logicalW = gif.lsd?.width || gifW;
|
|
862
|
-
const logicalH = gif.lsd?.height || gifH;
|
|
863
|
-
const compCanvas = document.createElement("canvas");
|
|
864
|
-
compCanvas.width = logicalW;
|
|
865
|
-
compCanvas.height = logicalH;
|
|
866
|
-
const compCtx = compCanvas.getContext("2d");
|
|
867
|
-
const prevCanvas = document.createElement("canvas");
|
|
868
|
-
prevCanvas.width = logicalW;
|
|
869
|
-
prevCanvas.height = logicalH;
|
|
870
|
-
const prevCtx = prevCanvas.getContext("2d");
|
|
871
|
-
const frames = [];
|
|
872
|
-
let cols = 0;
|
|
873
|
-
let rows = 0;
|
|
874
|
-
let totalDelay = 0;
|
|
875
|
-
for (const f of rawFrames) {
|
|
876
|
-
totalDelay += f.delay || 100;
|
|
877
|
-
}
|
|
878
|
-
const avgDelay = totalDelay / rawFrames.length;
|
|
879
|
-
const fps = Math.round(Math.min(30, Math.max(5, 1e3 / avgDelay)));
|
|
880
|
-
const maxFrames = Math.min(rawFrames.length, 300);
|
|
881
|
-
for (let i = 0; i < maxFrames; i++) {
|
|
882
|
-
const f = rawFrames[i];
|
|
883
|
-
const { dims, patch, disposalType } = f;
|
|
884
|
-
if (disposalType === 3) {
|
|
885
|
-
prevCtx.clearRect(0, 0, logicalW, logicalH);
|
|
886
|
-
prevCtx.drawImage(compCanvas, 0, 0);
|
|
887
|
-
}
|
|
888
|
-
const frameImageData = new ImageData(new Uint8ClampedArray(patch.buffer), dims.width, dims.height);
|
|
889
|
-
const tempCanvas = document.createElement("canvas");
|
|
890
|
-
tempCanvas.width = dims.width;
|
|
891
|
-
tempCanvas.height = dims.height;
|
|
892
|
-
const tempCtx = tempCanvas.getContext("2d");
|
|
893
|
-
tempCtx.putImageData(frameImageData, 0, 0);
|
|
894
|
-
compCtx.drawImage(tempCanvas, dims.left || 0, dims.top || 0);
|
|
895
|
-
const result = imageToAsciiFrame(compCanvas, options, targetWidth, targetHeight);
|
|
896
|
-
frames.push(result.frame);
|
|
897
|
-
cols = result.cols;
|
|
898
|
-
rows = result.rows;
|
|
899
|
-
if (disposalType === 2) {
|
|
900
|
-
compCtx.clearRect(dims.left || 0, dims.top || 0, dims.width, dims.height);
|
|
901
|
-
} else if (disposalType === 3) {
|
|
902
|
-
compCtx.clearRect(0, 0, logicalW, logicalH);
|
|
903
|
-
compCtx.drawImage(prevCanvas, 0, 0);
|
|
904
|
-
}
|
|
905
|
-
onProgress?.((i + 1) / maxFrames);
|
|
906
|
-
}
|
|
907
|
-
return { frames, cols, rows, fps };
|
|
908
|
-
}
|
|
909
|
-
function renderFrameToCanvas(ctx, frame, options, canvasWidth, canvasHeight, time = 0, hoverPos) {
|
|
910
|
-
if (options.animationStyle === "waveField") {
|
|
911
|
-
const mouseNorm = hoverPos ? { x: hoverPos.x, y: hoverPos.y } : { x: 0.5, y: 0.5 };
|
|
912
|
-
const acHexWF = options.accentColor ? resolveAccentHex(options.accentColor) : "d4ff00";
|
|
913
|
-
renderWaveBackground(ctx, canvasWidth, canvasHeight, time, mouseNorm, {
|
|
914
|
-
accentColor: `#${acHexWF}`,
|
|
915
|
-
accentThreshold: 0.52,
|
|
916
|
-
mouseInfluence: options.hoverStrength > 0 ? Math.min(1, 0.3 + options.hoverStrength * 0.5) : 0.55,
|
|
917
|
-
mouseFalloff: 2.8,
|
|
918
|
-
speed: options.animationSpeed,
|
|
919
|
-
vortex: options.hoverStrength > 0,
|
|
920
|
-
sparkles: true,
|
|
921
|
-
breathe: true
|
|
922
|
-
});
|
|
923
|
-
return;
|
|
924
|
-
}
|
|
925
|
-
const rows = frame.length;
|
|
926
|
-
if (rows === 0) return;
|
|
927
|
-
const cols = frame[0].length;
|
|
928
|
-
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
|
929
|
-
let hasTransparency = false;
|
|
930
|
-
const sampleStepY = Math.max(1, rows >> 2);
|
|
931
|
-
const sampleStepX = Math.max(1, cols >> 2);
|
|
932
|
-
outer:
|
|
933
|
-
for (let sampleY = 0; sampleY < rows; sampleY += sampleStepY) {
|
|
934
|
-
const row = frame[sampleY];
|
|
935
|
-
for (let sampleX = 0; sampleX < cols; sampleX += sampleStepX) {
|
|
936
|
-
if (row[sampleX].a < 200) {
|
|
937
|
-
hasTransparency = true;
|
|
938
|
-
break outer;
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
if (!hasTransparency) {
|
|
943
|
-
const isDarkScheme = typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
944
|
-
ctx.fillStyle = isDarkScheme ? "#0a0a0a" : "#faf9f7";
|
|
945
|
-
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
|
|
946
|
-
}
|
|
947
|
-
const cellW = canvasWidth / cols;
|
|
948
|
-
const cellH = canvasHeight / rows;
|
|
949
|
-
const totalCells = rows * cols;
|
|
950
|
-
const hoverIntensity = hoverPos?.intensity ?? 1;
|
|
951
|
-
const animationActive = options.animationStyle !== "none";
|
|
952
|
-
const suppressHover = animationActive && totalCells > 5e3;
|
|
953
|
-
const hoverActive = !suppressHover && !!(hoverPos && options.hoverStrength > 0 && hoverIntensity > 5e-3);
|
|
954
|
-
const hc = options.hoverColor || "#ffffff";
|
|
955
|
-
const hcR = parseInt(hc.slice(1, 3), 16) || 255;
|
|
956
|
-
const hcG = parseInt(hc.slice(3, 5), 16) || 255;
|
|
957
|
-
const hcB = parseInt(hc.slice(5, 7), 16) || 255;
|
|
958
|
-
const acHex = resolveAccentHex(options.accentColor);
|
|
959
|
-
const acR = parseInt(acHex.substring(0, 2), 16) || 255;
|
|
960
|
-
const acG = parseInt(acHex.substring(2, 4), 16) || 255;
|
|
961
|
-
const acB = parseInt(acHex.substring(4, 6), 16) || 255;
|
|
962
|
-
const radiusScale = totalCells > 3e4 ? 0.25 : totalCells > 15e3 ? 0.4 : totalCells > 5e3 ? 0.6 : 1;
|
|
963
|
-
const effectiveHoverRadius = options.hoverRadius * radiusScale;
|
|
964
|
-
let hoverMinCol = 0, hoverMaxCol = cols, hoverMinRow = 0, hoverMaxRow = rows;
|
|
965
|
-
let hoverPosX = 0, hoverPosY = 0;
|
|
966
|
-
if (hoverActive && hoverPos) {
|
|
967
|
-
hoverPosX = hoverPos.x;
|
|
968
|
-
hoverPosY = hoverPos.y;
|
|
969
|
-
const hoverNormRadius = 0.08 + effectiveHoverRadius * 0.35 + options.hoverStrength * 0.04;
|
|
970
|
-
hoverMinCol = Math.max(0, Math.floor((hoverPosX - hoverNormRadius) * cols) - 1);
|
|
971
|
-
hoverMaxCol = Math.min(cols, Math.ceil((hoverPosX + hoverNormRadius) * cols) + 1);
|
|
972
|
-
hoverMinRow = Math.max(0, Math.floor((hoverPosY - hoverNormRadius) * rows) - 1);
|
|
973
|
-
hoverMaxRow = Math.min(rows, Math.ceil((hoverPosY + hoverNormRadius) * rows) + 1);
|
|
974
|
-
}
|
|
975
|
-
const animStyle = options.animationStyle;
|
|
976
|
-
const animSpeed = options.animationSpeed;
|
|
977
|
-
const noAnimation = animStyle === "none";
|
|
978
|
-
const hoverStrength = options.hoverStrength;
|
|
979
|
-
const hoverEffect = options.hoverEffect;
|
|
980
|
-
const hoverRadiusFactor = effectiveHoverRadius;
|
|
981
|
-
const isInverted = resolveInvert(options.invert);
|
|
982
|
-
const colorMode = options.colorMode;
|
|
983
|
-
const TWO_PI = Math.PI * 2;
|
|
984
|
-
const invCols = 1 / cols;
|
|
985
|
-
const invRows = 1 / rows;
|
|
986
|
-
let lastFillStyle = "";
|
|
987
|
-
let lastAlpha = -1;
|
|
988
|
-
if (options.renderMode === "dots") {
|
|
989
|
-
const maxRadius = Math.min(cellW, cellH) * 0.5 * options.dotSizeRatio;
|
|
990
|
-
for (let y = 0; y < rows; y++) {
|
|
991
|
-
const rowData = frame[y];
|
|
992
|
-
for (let x = 0; x < cols; x++) {
|
|
993
|
-
const cell = rowData[x];
|
|
994
|
-
if (cell.a < 10) continue;
|
|
995
|
-
const lum = (0.299 * cell.r + 0.587 * cell.g + 0.114 * cell.b) * 0.00392156863;
|
|
996
|
-
const intensity = isInverted ? 1 - lum : lum;
|
|
997
|
-
if (intensity < 0.02) continue;
|
|
998
|
-
const animMul = noAnimation ? 1 : getAnimationMultiplier(x, y, cols, rows, time, animStyle, animSpeed);
|
|
999
|
-
let hoverMul = 1;
|
|
1000
|
-
let hoverOffX = 0;
|
|
1001
|
-
let hoverOffY = 0;
|
|
1002
|
-
let hoverGlow = 0;
|
|
1003
|
-
let hoverBlend = 0;
|
|
1004
|
-
if (hoverActive && x >= hoverMinCol && x <= hoverMaxCol && y >= hoverMinRow && y <= hoverMaxRow) {
|
|
1005
|
-
const fx = computeHoverEffect(
|
|
1006
|
-
x * invCols,
|
|
1007
|
-
y * invRows,
|
|
1008
|
-
hoverPosX,
|
|
1009
|
-
hoverPosY,
|
|
1010
|
-
hoverIntensity,
|
|
1011
|
-
hoverStrength,
|
|
1012
|
-
cellW,
|
|
1013
|
-
cellH,
|
|
1014
|
-
hoverEffect,
|
|
1015
|
-
hoverRadiusFactor
|
|
1016
|
-
);
|
|
1017
|
-
hoverMul = fx.scale;
|
|
1018
|
-
hoverOffX = fx.offsetX;
|
|
1019
|
-
hoverOffY = fx.offsetY;
|
|
1020
|
-
hoverGlow = fx.glow;
|
|
1021
|
-
hoverBlend = fx.colorBlend;
|
|
1022
|
-
}
|
|
1023
|
-
const radius = maxRadius * intensity * animMul * hoverMul;
|
|
1024
|
-
if (radius < 0.3) continue;
|
|
1025
|
-
const px = x * cellW + cellW * 0.5 + hoverOffX;
|
|
1026
|
-
const py = y * cellH + cellH * 0.5 + hoverOffY;
|
|
1027
|
-
let color;
|
|
1028
|
-
if (hoverBlend > 0) {
|
|
1029
|
-
const rgb = getCellColorRGB(cell, colorMode, acR, acG, acB, isInverted);
|
|
1030
|
-
const cr = Math.min(255, rgb[0] + (hcR - rgb[0]) * hoverBlend | 0);
|
|
1031
|
-
const cg = Math.min(255, rgb[1] + (hcG - rgb[1]) * hoverBlend | 0);
|
|
1032
|
-
const cb = Math.min(255, rgb[2] + (hcB - rgb[2]) * hoverBlend | 0);
|
|
1033
|
-
color = `rgb(${cr},${cg},${cb})`;
|
|
1034
|
-
} else {
|
|
1035
|
-
color = getCellColorStr(cell, colorMode, acR, acG, acB, isInverted);
|
|
1036
|
-
}
|
|
1037
|
-
const alpha = Math.min(1, cell.a * 0.00392156863 * animMul * (1 + hoverGlow));
|
|
1038
|
-
if (alpha !== lastAlpha) {
|
|
1039
|
-
ctx.globalAlpha = alpha;
|
|
1040
|
-
lastAlpha = alpha;
|
|
1041
|
-
}
|
|
1042
|
-
if (color !== lastFillStyle) {
|
|
1043
|
-
ctx.fillStyle = color;
|
|
1044
|
-
lastFillStyle = color;
|
|
1045
|
-
}
|
|
1046
|
-
if (radius <= 3) {
|
|
1047
|
-
const d = radius * 2;
|
|
1048
|
-
ctx.fillRect(px - radius, py - radius, d, d);
|
|
1049
|
-
} else {
|
|
1050
|
-
ctx.beginPath();
|
|
1051
|
-
ctx.arc(px, py, radius, 0, TWO_PI);
|
|
1052
|
-
ctx.fill();
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
} else {
|
|
1057
|
-
const charAspect = 0.55;
|
|
1058
|
-
const fontSize = Math.min(cellW / charAspect, cellH) * 0.9;
|
|
1059
|
-
const useFastRect = fontSize < 6;
|
|
1060
|
-
if (!useFastRect) {
|
|
1061
|
-
const isEmoji = options.artStyle === "emoji";
|
|
1062
|
-
ctx.font = isEmoji ? `${fontSize}px "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla", sans-serif` : `${fontSize}px "JetBrains Mono", monospace`;
|
|
1063
|
-
ctx.textAlign = "center";
|
|
1064
|
-
ctx.textBaseline = "middle";
|
|
1065
|
-
}
|
|
1066
|
-
let charWeights = null;
|
|
1067
|
-
const dynFrms = options.charsetFrames;
|
|
1068
|
-
const hasDyn = !!dynFrms?.length;
|
|
1069
|
-
const dynCharset = hasDyn ? dynFrms[Math.floor(Math.max(0, time) * (options.charsetFps ?? 2)) % dynFrms.length] : options.charset;
|
|
1070
|
-
if (useFastRect) {
|
|
1071
|
-
charWeights = {};
|
|
1072
|
-
const csChars = [...dynCharset];
|
|
1073
|
-
const csLen = csChars.length;
|
|
1074
|
-
for (let i = 0; i < csLen; i++) {
|
|
1075
|
-
charWeights[csChars[i]] = Math.max(0.1, (i + 0.3) / csLen);
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
const baseTransform = !useFastRect ? ctx.getTransform() : null;
|
|
1079
|
-
for (let y = 0; y < rows; y++) {
|
|
1080
|
-
const rowData = frame[y];
|
|
1081
|
-
for (let x = 0; x < cols; x++) {
|
|
1082
|
-
const cell = rowData[x];
|
|
1083
|
-
if (cell.a < 10) continue;
|
|
1084
|
-
const drawChar = hasDyn && cell.lum != null ? luminanceToChar(cell.lum, dynCharset, isInverted) : cell.char;
|
|
1085
|
-
if (drawChar === " ") continue;
|
|
1086
|
-
const animMul = noAnimation ? 1 : getAnimationMultiplier(x, y, cols, rows, time, animStyle, animSpeed);
|
|
1087
|
-
if (animMul < 0.05) continue;
|
|
1088
|
-
let hoverScale = 1;
|
|
1089
|
-
let hoverOffX = 0;
|
|
1090
|
-
let hoverOffY = 0;
|
|
1091
|
-
let hoverGlow = 0;
|
|
1092
|
-
let hoverBlend = 0;
|
|
1093
|
-
if (hoverActive && x >= hoverMinCol && x <= hoverMaxCol && y >= hoverMinRow && y <= hoverMaxRow) {
|
|
1094
|
-
const fx = computeHoverEffect(
|
|
1095
|
-
x * invCols,
|
|
1096
|
-
y * invRows,
|
|
1097
|
-
hoverPosX,
|
|
1098
|
-
hoverPosY,
|
|
1099
|
-
hoverIntensity,
|
|
1100
|
-
hoverStrength,
|
|
1101
|
-
cellW,
|
|
1102
|
-
cellH,
|
|
1103
|
-
hoverEffect,
|
|
1104
|
-
hoverRadiusFactor
|
|
1105
|
-
);
|
|
1106
|
-
hoverScale = fx.scale;
|
|
1107
|
-
hoverOffX = fx.offsetX;
|
|
1108
|
-
hoverOffY = fx.offsetY;
|
|
1109
|
-
hoverGlow = fx.glow;
|
|
1110
|
-
hoverBlend = fx.colorBlend;
|
|
1111
|
-
}
|
|
1112
|
-
const px = x * cellW + cellW * 0.5 + hoverOffX;
|
|
1113
|
-
const py = y * cellH + cellH * 0.5 + hoverOffY;
|
|
1114
|
-
let color;
|
|
1115
|
-
if (hoverBlend > 0) {
|
|
1116
|
-
const rgb = getCellColorRGB(cell, colorMode, acR, acG, acB, isInverted);
|
|
1117
|
-
const cr = Math.min(255, rgb[0] + (hcR - rgb[0]) * hoverBlend | 0);
|
|
1118
|
-
const cg = Math.min(255, rgb[1] + (hcG - rgb[1]) * hoverBlend | 0);
|
|
1119
|
-
const cb = Math.min(255, rgb[2] + (hcB - rgb[2]) * hoverBlend | 0);
|
|
1120
|
-
color = `rgb(${cr},${cg},${cb})`;
|
|
1121
|
-
} else {
|
|
1122
|
-
color = getCellColorStr(cell, colorMode, acR, acG, acB, isInverted);
|
|
1123
|
-
}
|
|
1124
|
-
if (useFastRect) {
|
|
1125
|
-
const weight = charWeights[drawChar] ?? 0.5;
|
|
1126
|
-
const effAlpha = Math.min(1, cell.a * 0.00392156863 * animMul * (1 + hoverGlow)) * weight;
|
|
1127
|
-
if (effAlpha < 0.02) continue;
|
|
1128
|
-
if (effAlpha !== lastAlpha) {
|
|
1129
|
-
ctx.globalAlpha = effAlpha;
|
|
1130
|
-
lastAlpha = effAlpha;
|
|
1131
|
-
}
|
|
1132
|
-
if (color !== lastFillStyle) {
|
|
1133
|
-
ctx.fillStyle = color;
|
|
1134
|
-
lastFillStyle = color;
|
|
1135
|
-
}
|
|
1136
|
-
const rw = cellW * hoverScale;
|
|
1137
|
-
const rh = cellH * hoverScale;
|
|
1138
|
-
ctx.fillRect(px - rw * 0.5, py - rh * 0.5, rw, rh);
|
|
1139
|
-
} else {
|
|
1140
|
-
const alpha = Math.min(1, cell.a * 0.00392156863 * animMul * (1 + hoverGlow));
|
|
1141
|
-
if (alpha !== lastAlpha) {
|
|
1142
|
-
ctx.globalAlpha = alpha;
|
|
1143
|
-
lastAlpha = alpha;
|
|
1144
|
-
}
|
|
1145
|
-
if (color !== lastFillStyle) {
|
|
1146
|
-
ctx.fillStyle = color;
|
|
1147
|
-
lastFillStyle = color;
|
|
1148
|
-
}
|
|
1149
|
-
if (hoverScale !== 1) {
|
|
1150
|
-
ctx.translate(px, py);
|
|
1151
|
-
ctx.scale(hoverScale, hoverScale);
|
|
1152
|
-
ctx.fillText(drawChar, 0, 0);
|
|
1153
|
-
ctx.setTransform(baseTransform);
|
|
1154
|
-
} else {
|
|
1155
|
-
ctx.fillText(drawChar, px, py);
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
ctx.globalAlpha = 1;
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
// src/core/simple-api.ts
|
|
1165
|
-
function getSourceDims(el) {
|
|
1166
|
-
if (el instanceof HTMLVideoElement) return { w: el.videoWidth, h: el.videoHeight };
|
|
1167
|
-
if (el instanceof HTMLImageElement) return { w: el.naturalWidth || el.width, h: el.naturalHeight || el.height };
|
|
1168
|
-
return { w: el.width, h: el.height };
|
|
1169
|
-
}
|
|
1170
|
-
function computeRenderDims(srcW, srcH) {
|
|
1171
|
-
const MAX = 2048;
|
|
1172
|
-
const scale = Math.min(1, MAX / Math.max(srcW, srcH));
|
|
1173
|
-
return { renderW: Math.round(srcW * scale), renderH: Math.round(srcH * scale) };
|
|
1174
|
-
}
|
|
1175
|
-
function sizeCanvasToContainer(canvas, container, aspect, srcW, srcH) {
|
|
1176
|
-
const { width, height } = container.getBoundingClientRect();
|
|
1177
|
-
if (!width || !height) return { renderW: 0, renderH: 0, dpr: 1 };
|
|
1178
|
-
let cssW = width, cssH = cssW / aspect;
|
|
1179
|
-
if (cssH > height) {
|
|
1180
|
-
cssH = height;
|
|
1181
|
-
cssW = cssH * aspect;
|
|
1182
|
-
}
|
|
1183
|
-
cssW = Math.round(cssW);
|
|
1184
|
-
cssH = Math.round(cssH);
|
|
1185
|
-
let renderW, renderH;
|
|
1186
|
-
if (srcW && srcH) {
|
|
1187
|
-
({ renderW, renderH } = computeRenderDims(srcW, srcH));
|
|
1188
|
-
} else {
|
|
1189
|
-
renderW = cssW;
|
|
1190
|
-
renderH = cssH;
|
|
1191
|
-
}
|
|
1192
|
-
const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
|
|
1193
|
-
const MAX_PX = 8e6;
|
|
1194
|
-
const cappedDpr = renderW * dpr * renderH * dpr > MAX_PX ? Math.sqrt(MAX_PX / (renderW * renderH)) : dpr;
|
|
1195
|
-
canvas.width = Math.round(renderW * cappedDpr);
|
|
1196
|
-
canvas.height = Math.round(renderH * cappedDpr);
|
|
1197
|
-
canvas.style.width = cssW + "px";
|
|
1198
|
-
canvas.style.height = cssH + "px";
|
|
1199
|
-
return { renderW, renderH, dpr: cappedDpr };
|
|
1200
|
-
}
|
|
1201
|
-
async function asciify(source, canvas, { fontSize, artStyle = "classic", options = {} } = {}) {
|
|
1202
|
-
let el;
|
|
1203
|
-
if (typeof source === "string") {
|
|
1204
|
-
const img = new Image();
|
|
1205
|
-
img.crossOrigin = "anonymous";
|
|
1206
|
-
await new Promise((resolve, reject) => {
|
|
1207
|
-
img.onload = () => resolve();
|
|
1208
|
-
img.onerror = () => reject(new Error(`Failed to load image: ${source}`));
|
|
1209
|
-
img.src = source;
|
|
1210
|
-
});
|
|
1211
|
-
el = img;
|
|
1212
|
-
} else if (source instanceof HTMLImageElement && !source.complete) {
|
|
1213
|
-
await new Promise((resolve, reject) => {
|
|
1214
|
-
source.onload = () => resolve();
|
|
1215
|
-
source.onerror = () => reject(new Error("Image failed to load"));
|
|
1216
|
-
});
|
|
1217
|
-
el = source;
|
|
1218
|
-
} else {
|
|
1219
|
-
el = source;
|
|
1220
|
-
}
|
|
1221
|
-
const preset = ART_STYLE_PRESETS[artStyle];
|
|
1222
|
-
const resolvedFontSize = fontSize ?? options.fontSize ?? 10;
|
|
1223
|
-
const merged = { ...DEFAULT_OPTIONS, ...preset, ...options, fontSize: resolvedFontSize };
|
|
1224
|
-
const ctx = canvas.getContext("2d");
|
|
1225
|
-
if (!ctx) throw new Error("Could not get 2d context from canvas");
|
|
1226
|
-
const { w: srcW, h: srcH } = getSourceDims(el);
|
|
1227
|
-
const { renderW, renderH } = computeRenderDims(srcW, srcH);
|
|
1228
|
-
const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
|
|
1229
|
-
const MAX_PX = 8e6;
|
|
1230
|
-
const cappedDpr = renderW * dpr * renderH * dpr > MAX_PX ? Math.sqrt(MAX_PX / (renderW * renderH)) : dpr;
|
|
1231
|
-
if (canvas.width < renderW || canvas.height < renderH) {
|
|
1232
|
-
canvas.width = Math.round(renderW * cappedDpr);
|
|
1233
|
-
canvas.height = Math.round(renderH * cappedDpr);
|
|
1234
|
-
}
|
|
1235
|
-
const { frame } = imageToAsciiFrame(el, merged, renderW, renderH);
|
|
1236
|
-
ctx.save();
|
|
1237
|
-
ctx.setTransform(cappedDpr, 0, 0, cappedDpr, 0, 0);
|
|
1238
|
-
renderFrameToCanvas(ctx, frame, merged, renderW, renderH);
|
|
1239
|
-
ctx.restore();
|
|
1240
|
-
}
|
|
1241
|
-
async function asciifyGif(source, canvas, { fontSize, artStyle = "classic", options = {} } = {}) {
|
|
1242
|
-
const buffer = typeof source === "string" ? await fetch(source).then((r) => r.arrayBuffer()) : source;
|
|
1243
|
-
const resolvedFontSize = fontSize ?? options.fontSize ?? 10;
|
|
1244
|
-
const merged = { ...DEFAULT_OPTIONS, ...ART_STYLE_PRESETS[artStyle], ...options, fontSize: resolvedFontSize };
|
|
1245
|
-
const ctx = canvas.getContext("2d");
|
|
1246
|
-
if (!ctx) throw new Error("Could not get 2d context from canvas");
|
|
1247
|
-
const { frames, fps } = await gifToAsciiFrames(buffer, merged, canvas.width, canvas.height);
|
|
1248
|
-
let cancelled = false;
|
|
1249
|
-
let animId;
|
|
1250
|
-
let i = 0;
|
|
1251
|
-
let last = performance.now();
|
|
1252
|
-
const interval = 1e3 / fps;
|
|
1253
|
-
const tick = (now) => {
|
|
1254
|
-
if (cancelled) return;
|
|
1255
|
-
if (now - last >= interval) {
|
|
1256
|
-
renderFrameToCanvas(ctx, frames[i], merged, canvas.width, canvas.height);
|
|
1257
|
-
i = (i + 1) % frames.length;
|
|
1258
|
-
last = now;
|
|
1259
|
-
}
|
|
1260
|
-
animId = requestAnimationFrame(tick);
|
|
1261
|
-
};
|
|
1262
|
-
animId = requestAnimationFrame(tick);
|
|
1263
|
-
return () => {
|
|
1264
|
-
cancelled = true;
|
|
1265
|
-
cancelAnimationFrame(animId);
|
|
1266
|
-
};
|
|
1267
|
-
}
|
|
1268
|
-
async function asciifyVideo(source, canvas, { fontSize, artStyle = "classic", options = {}, fitTo, preExtract = false, trim, onReady, onFrame } = {}) {
|
|
1269
|
-
const trimStart = trim?.start ?? 0;
|
|
1270
|
-
const trimEnd = trim?.end;
|
|
1271
|
-
const resolvedFontSize = fontSize ?? options.fontSize ?? 10;
|
|
1272
|
-
const merged = { ...DEFAULT_OPTIONS, ...ART_STYLE_PRESETS[artStyle], ...options, fontSize: resolvedFontSize };
|
|
1273
|
-
const ctx = canvas.getContext("2d");
|
|
1274
|
-
if (!ctx) throw new Error("asciifyVideo: could not get 2d context from canvas.");
|
|
1275
|
-
const container = typeof fitTo === "string" ? document.querySelector(fitTo) : fitTo instanceof HTMLElement ? fitTo : null;
|
|
1276
|
-
if (preExtract) {
|
|
1277
|
-
let video2;
|
|
1278
|
-
if (typeof source === "string") {
|
|
1279
|
-
video2 = document.createElement("video");
|
|
1280
|
-
video2.crossOrigin = "anonymous";
|
|
1281
|
-
video2.src = source;
|
|
1282
|
-
if (video2.readyState < 2) {
|
|
1283
|
-
await new Promise((resolve, reject) => {
|
|
1284
|
-
video2.onloadeddata = () => resolve();
|
|
1285
|
-
video2.onerror = () => reject(new Error(`asciifyVideo: failed to load "${source}"`));
|
|
1286
|
-
});
|
|
1287
|
-
}
|
|
1288
|
-
} else {
|
|
1289
|
-
video2 = source;
|
|
1290
|
-
}
|
|
1291
|
-
if (container) sizeCanvasToContainer(canvas, container, video2.videoWidth / video2.videoHeight, video2.videoWidth, video2.videoHeight);
|
|
1292
|
-
const { renderW: renderW2, renderH: renderH2 } = computeRenderDims(video2.videoWidth, video2.videoHeight);
|
|
1293
|
-
const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
|
|
1294
|
-
const MAX_PX = 8e6;
|
|
1295
|
-
const cappedDpr = renderW2 * dpr * renderH2 * dpr > MAX_PX ? Math.sqrt(MAX_PX / (renderW2 * renderH2)) : dpr;
|
|
1296
|
-
if (canvas.width < Math.round(renderW2 * cappedDpr)) {
|
|
1297
|
-
canvas.width = Math.round(renderW2 * cappedDpr);
|
|
1298
|
-
canvas.height = Math.round(renderH2 * cappedDpr);
|
|
1299
|
-
}
|
|
1300
|
-
const maxDur = trimEnd !== void 0 ? trimEnd - trimStart : 10;
|
|
1301
|
-
const { frames, fps } = await videoToAsciiFrames(video2, merged, renderW2, renderH2, void 0, maxDur, void 0, trimStart);
|
|
1302
|
-
let cancelled2 = false, animId2, i = 0, last = performance.now();
|
|
1303
|
-
let firstFrame2 = true;
|
|
1304
|
-
const interval = 1e3 / fps;
|
|
1305
|
-
const tick2 = (now) => {
|
|
1306
|
-
if (cancelled2) return;
|
|
1307
|
-
if (now - last >= interval) {
|
|
1308
|
-
ctx.save();
|
|
1309
|
-
ctx.setTransform(cappedDpr, 0, 0, cappedDpr, 0, 0);
|
|
1310
|
-
renderFrameToCanvas(ctx, frames[i], merged, renderW2, renderH2);
|
|
1311
|
-
ctx.restore();
|
|
1312
|
-
i = (i + 1) % frames.length;
|
|
1313
|
-
last = now;
|
|
1314
|
-
if (firstFrame2) {
|
|
1315
|
-
firstFrame2 = false;
|
|
1316
|
-
onReady?.(video2);
|
|
1317
|
-
}
|
|
1318
|
-
onFrame?.();
|
|
1319
|
-
}
|
|
1320
|
-
animId2 = requestAnimationFrame(tick2);
|
|
1321
|
-
};
|
|
1322
|
-
animId2 = requestAnimationFrame(tick2);
|
|
1323
|
-
return () => {
|
|
1324
|
-
cancelled2 = true;
|
|
1325
|
-
cancelAnimationFrame(animId2);
|
|
1326
|
-
};
|
|
1327
|
-
}
|
|
1328
|
-
let video;
|
|
1329
|
-
let ownedVideo = false;
|
|
1330
|
-
if (typeof source === "string") {
|
|
1331
|
-
video = document.createElement("video");
|
|
1332
|
-
video.src = source;
|
|
1333
|
-
video.muted = true;
|
|
1334
|
-
video.loop = true;
|
|
1335
|
-
video.playsInline = true;
|
|
1336
|
-
video.setAttribute("playsinline", "");
|
|
1337
|
-
Object.assign(video.style, {
|
|
1338
|
-
position: "fixed",
|
|
1339
|
-
top: "0",
|
|
1340
|
-
left: "0",
|
|
1341
|
-
width: "1px",
|
|
1342
|
-
height: "1px",
|
|
1343
|
-
opacity: "0",
|
|
1344
|
-
pointerEvents: "none",
|
|
1345
|
-
zIndex: "-1"
|
|
1346
|
-
});
|
|
1347
|
-
document.body.appendChild(video);
|
|
1348
|
-
ownedVideo = true;
|
|
1349
|
-
await new Promise((resolve, reject) => {
|
|
1350
|
-
video.onloadedmetadata = () => resolve();
|
|
1351
|
-
video.onerror = () => reject(new Error(`asciifyVideo: failed to load "${source}"`));
|
|
1352
|
-
});
|
|
1353
|
-
await video.play().catch(() => {
|
|
1354
|
-
});
|
|
1355
|
-
} else {
|
|
1356
|
-
video = source;
|
|
1357
|
-
if (video.paused) await video.play().catch(() => {
|
|
1358
|
-
});
|
|
1359
|
-
}
|
|
1360
|
-
if (trimStart > 0) {
|
|
1361
|
-
video.currentTime = trimStart;
|
|
1362
|
-
await new Promise((resolve) => {
|
|
1363
|
-
const h = () => {
|
|
1364
|
-
video.removeEventListener("seeked", h);
|
|
1365
|
-
resolve();
|
|
1366
|
-
};
|
|
1367
|
-
video.addEventListener("seeked", h);
|
|
1368
|
-
});
|
|
1369
|
-
}
|
|
1370
|
-
let timeupdateHandler = null;
|
|
1371
|
-
if (trimStart > 0 || trimEnd !== void 0) {
|
|
1372
|
-
timeupdateHandler = () => {
|
|
1373
|
-
if (trimEnd !== void 0 && video.currentTime >= trimEnd) {
|
|
1374
|
-
video.currentTime = trimStart;
|
|
1375
|
-
} else if (trimStart > 0 && video.currentTime < trimStart) {
|
|
1376
|
-
video.currentTime = trimStart;
|
|
1377
|
-
}
|
|
1378
|
-
};
|
|
1379
|
-
video.addEventListener("timeupdate", timeupdateHandler);
|
|
1380
|
-
}
|
|
1381
|
-
let ro = null;
|
|
1382
|
-
const { renderW, renderH } = computeRenderDims(video.videoWidth, video.videoHeight);
|
|
1383
|
-
if (container) {
|
|
1384
|
-
const aspect = video.videoWidth / video.videoHeight;
|
|
1385
|
-
const vw = video.videoWidth, vh = video.videoHeight;
|
|
1386
|
-
const sizing = sizeCanvasToContainer(canvas, container, aspect, vw, vh);
|
|
1387
|
-
const sCtx = canvas.getContext("2d");
|
|
1388
|
-
if (sCtx) sCtx.setTransform(sizing.dpr, 0, 0, sizing.dpr, 0, 0);
|
|
1389
|
-
ro = new ResizeObserver(() => {
|
|
1390
|
-
const s = sizeCanvasToContainer(canvas, container, aspect, vw, vh);
|
|
1391
|
-
const rCtx = canvas.getContext("2d");
|
|
1392
|
-
if (rCtx) rCtx.setTransform(s.dpr, 0, 0, s.dpr, 0, 0);
|
|
1393
|
-
});
|
|
1394
|
-
ro.observe(container);
|
|
1395
|
-
} else {
|
|
1396
|
-
const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
|
|
1397
|
-
const MAX_PX = 8e6;
|
|
1398
|
-
const cappedDpr = renderW * dpr * renderH * dpr > MAX_PX ? Math.sqrt(MAX_PX / (renderW * renderH)) : dpr;
|
|
1399
|
-
if (canvas.width < Math.round(renderW * cappedDpr)) {
|
|
1400
|
-
canvas.width = Math.round(renderW * cappedDpr);
|
|
1401
|
-
canvas.height = Math.round(renderH * cappedDpr);
|
|
1402
|
-
}
|
|
1403
|
-
ctx.setTransform(cappedDpr, 0, 0, cappedDpr, 0, 0);
|
|
1404
|
-
}
|
|
1405
|
-
let cancelled = false;
|
|
1406
|
-
let animId;
|
|
1407
|
-
let firstFrame = true;
|
|
1408
|
-
const tick = () => {
|
|
1409
|
-
if (cancelled) return;
|
|
1410
|
-
animId = requestAnimationFrame(tick);
|
|
1411
|
-
if (video.readyState < 2 || canvas.width === 0 || canvas.height === 0) return;
|
|
1412
|
-
if (trimStart > 0 && video.currentTime < trimStart) return;
|
|
1413
|
-
if (trimEnd !== void 0 && video.currentTime >= trimEnd) return;
|
|
1414
|
-
const { frame } = imageToAsciiFrame(video, merged, renderW, renderH);
|
|
1415
|
-
if (frame.length > 0) {
|
|
1416
|
-
renderFrameToCanvas(ctx, frame, merged, renderW, renderH, 0, null);
|
|
1417
|
-
if (firstFrame) {
|
|
1418
|
-
firstFrame = false;
|
|
1419
|
-
onReady?.(video);
|
|
1420
|
-
}
|
|
1421
|
-
onFrame?.();
|
|
1422
|
-
}
|
|
1423
|
-
};
|
|
1424
|
-
animId = requestAnimationFrame(tick);
|
|
1425
|
-
return () => {
|
|
1426
|
-
cancelled = true;
|
|
1427
|
-
cancelAnimationFrame(animId);
|
|
1428
|
-
ro?.disconnect();
|
|
1429
|
-
if (timeupdateHandler) video.removeEventListener("timeupdate", timeupdateHandler);
|
|
1430
|
-
if (ownedVideo) {
|
|
1431
|
-
video.pause();
|
|
1432
|
-
video.src = "";
|
|
1433
|
-
document.body.removeChild(video);
|
|
1434
|
-
}
|
|
1435
|
-
};
|
|
1436
|
-
}
|
|
1437
|
-
function asciifyLiveVideo(source, canvas, opts) {
|
|
1438
|
-
return asciifyVideo(source, canvas, opts);
|
|
1439
|
-
}
|
|
1440
|
-
|
|
1441
|
-
// src/backgrounds/rain.ts
|
|
1442
|
-
function renderRainBackground(ctx, width, height, time, options = {}) {
|
|
1443
|
-
const {
|
|
1444
|
-
fontSize = 13,
|
|
1445
|
-
chars = "0123456789ABCDEF@#$&*+=/<>",
|
|
1446
|
-
accentColor = void 0,
|
|
1447
|
-
color,
|
|
1448
|
-
speed = 1,
|
|
1449
|
-
density = 0.55,
|
|
1450
|
-
tailLength = 14,
|
|
1451
|
-
lightMode = false
|
|
1452
|
-
} = options;
|
|
1453
|
-
const resolvedAccent = accentColor ?? (lightMode ? "#6b8700" : "#d4ff00");
|
|
1454
|
-
const charW = fontSize * 0.62;
|
|
1455
|
-
const lineH = fontSize * 1.4;
|
|
1456
|
-
const cols = Math.ceil(width / charW);
|
|
1457
|
-
const rows = Math.ceil(height / lineH);
|
|
1458
|
-
ctx.clearRect(0, 0, width, height);
|
|
1459
|
-
ctx.font = `${fontSize}px monospace`;
|
|
1460
|
-
ctx.textBaseline = "top";
|
|
1461
|
-
let br = 255, bg = 255, bb = 255;
|
|
1462
|
-
if (lightMode) {
|
|
1463
|
-
br = 55;
|
|
1464
|
-
bg = 55;
|
|
1465
|
-
bb = 55;
|
|
1466
|
-
}
|
|
1467
|
-
if (color) {
|
|
1468
|
-
const p = parseColor(color);
|
|
1469
|
-
if (p) {
|
|
1470
|
-
br = p.r;
|
|
1471
|
-
bg = p.g;
|
|
1472
|
-
bb = p.b;
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
let acR = 212, acG = 255, acB = 0;
|
|
1476
|
-
const ap = parseColor(resolvedAccent);
|
|
1477
|
-
if (ap) {
|
|
1478
|
-
acR = ap.r;
|
|
1479
|
-
acG = ap.g;
|
|
1480
|
-
acB = ap.b;
|
|
1481
|
-
}
|
|
1482
|
-
const period = rows + tailLength;
|
|
1483
|
-
for (let c = 0; c < cols; c++) {
|
|
1484
|
-
if (hash2(c * 17, 3) > density) continue;
|
|
1485
|
-
const colSpeed = (0.5 + hash2(c * 31, 7) * 1.5) * speed;
|
|
1486
|
-
const phase = hash2(c * 13, 11) * period;
|
|
1487
|
-
const headRow = Math.floor((time * colSpeed * 7 + phase) % period);
|
|
1488
|
-
const x = c * charW;
|
|
1489
|
-
for (let k = 0; k <= tailLength; k++) {
|
|
1490
|
-
const row = headRow - (tailLength - k);
|
|
1491
|
-
if (row < 0 || row >= rows) continue;
|
|
1492
|
-
const y = row * lineH;
|
|
1493
|
-
const charSeed = hash2(c * 53 + Math.floor(time * 5 + k), row * 7);
|
|
1494
|
-
const ch = chars[Math.floor(charSeed * chars.length)];
|
|
1495
|
-
const tRatio = k / tailLength;
|
|
1496
|
-
if (k === tailLength) {
|
|
1497
|
-
ctx.fillStyle = `rgba(${acR},${acG},${acB},${lightMode ? 0.7 : 0.85})`;
|
|
1498
|
-
} else {
|
|
1499
|
-
const alpha = lightMode ? tRatio * 0.85 : tRatio * 0.15;
|
|
1500
|
-
ctx.fillStyle = `rgba(${br},${bg},${bb},${alpha})`;
|
|
1501
|
-
}
|
|
1502
|
-
ctx.fillText(ch, x, y);
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
// src/backgrounds/stars.ts
|
|
1508
|
-
function renderStarsBackground(ctx, width, height, time, mousePos = { x: 0.5, y: 0.5 }, options = {}) {
|
|
1509
|
-
const {
|
|
1510
|
-
fontSize = 14,
|
|
1511
|
-
chars = " . \xB7 * + \xB0 \u2605",
|
|
1512
|
-
accentColor = void 0,
|
|
1513
|
-
color,
|
|
1514
|
-
speed = 1,
|
|
1515
|
-
count = 180,
|
|
1516
|
-
lightMode = false
|
|
1517
|
-
} = options;
|
|
1518
|
-
const resolvedAccent = accentColor ?? (lightMode ? "#6b8700" : "#d4ff00");
|
|
1519
|
-
ctx.clearRect(0, 0, width, height);
|
|
1520
|
-
ctx.textBaseline = "middle";
|
|
1521
|
-
ctx.textAlign = "center";
|
|
1522
|
-
const cx = width * (0.2 + mousePos.x * 0.6);
|
|
1523
|
-
const cy = height * (0.2 + mousePos.y * 0.6);
|
|
1524
|
-
const maxR = Math.sqrt(width * width + height * height) * 0.65;
|
|
1525
|
-
let br = 255, bg = 255, bb = 255;
|
|
1526
|
-
if (lightMode) {
|
|
1527
|
-
br = 55;
|
|
1528
|
-
bg = 55;
|
|
1529
|
-
bb = 55;
|
|
1530
|
-
}
|
|
1531
|
-
if (color) {
|
|
1532
|
-
const p = parseColor(color);
|
|
1533
|
-
if (p) {
|
|
1534
|
-
br = p.r;
|
|
1535
|
-
bg = p.g;
|
|
1536
|
-
bb = p.b;
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
let acR = 212, acG = 255, acB = 0;
|
|
1540
|
-
const ap = parseColor(resolvedAccent);
|
|
1541
|
-
if (ap) {
|
|
1542
|
-
acR = ap.r;
|
|
1543
|
-
acG = ap.g;
|
|
1544
|
-
acB = ap.b;
|
|
1545
|
-
}
|
|
1546
|
-
const charArr = chars.replace(/ /g, "").split("");
|
|
1547
|
-
if (charArr.length === 0) return;
|
|
1548
|
-
for (let i = 0; i < count; i++) {
|
|
1549
|
-
const angle = hash2(i * 17, 3) * Math.PI * 2;
|
|
1550
|
-
const baseSpd = 0.15 + hash2(i * 31, 7) * 0.85;
|
|
1551
|
-
const phase = hash2(i * 13, 11);
|
|
1552
|
-
const r = (time * baseSpd * speed * 0.22 + phase) % 1;
|
|
1553
|
-
const x = cx + Math.cos(angle) * r * maxR;
|
|
1554
|
-
const y = cy + Math.sin(angle) * r * maxR;
|
|
1555
|
-
if (x < -20 || x > width + 20 || y < -20 || y > height + 20) continue;
|
|
1556
|
-
const sz = Math.max(6, fontSize * (0.4 + r * 0.9));
|
|
1557
|
-
ctx.font = `${sz}px monospace`;
|
|
1558
|
-
const charIdx = Math.min(charArr.length - 1, Math.floor(r * charArr.length));
|
|
1559
|
-
const ch = charArr[charIdx];
|
|
1560
|
-
const isAccent = r > 0.72;
|
|
1561
|
-
const alpha = lightMode ? r * 0.85 : r * 0.2;
|
|
1562
|
-
ctx.fillStyle = isAccent ? `rgba(${acR},${acG},${acB},${Math.min(lightMode ? 0.92 : 0.32, alpha * 2.2)})` : `rgba(${br},${bg},${bb},${alpha})`;
|
|
1563
|
-
ctx.fillText(ch, x, y);
|
|
1564
|
-
}
|
|
1565
|
-
ctx.textAlign = "left";
|
|
1566
|
-
}
|
|
1567
|
-
|
|
1568
|
-
// src/backgrounds/pulse.ts
|
|
1569
|
-
function renderPulseBackground(ctx, width, height, time, mousePos = { x: 0.5, y: 0.5 }, options = {}) {
|
|
1570
|
-
const {
|
|
1571
|
-
fontSize = 14,
|
|
1572
|
-
chars = ". \xB7 \u25CB \u25CE \u25CF",
|
|
1573
|
-
accentColor = void 0,
|
|
1574
|
-
color,
|
|
1575
|
-
rings = 5,
|
|
1576
|
-
speed = 1,
|
|
1577
|
-
sharpness = 4,
|
|
1578
|
-
lightMode = false
|
|
1579
|
-
} = options;
|
|
1580
|
-
const resolvedAccent = accentColor ?? (lightMode ? "#007a5e" : "#00ffcc");
|
|
1581
|
-
ctx.clearRect(0, 0, width, height);
|
|
1582
|
-
ctx.textBaseline = "middle";
|
|
1583
|
-
ctx.textAlign = "center";
|
|
1584
|
-
const cx = width * mousePos.x;
|
|
1585
|
-
const cy = height * mousePos.y;
|
|
1586
|
-
const maxDist = Math.sqrt(cx * cx + cy * cy) * 1.6 + Math.sqrt(width * width + height * height) * 0.2;
|
|
1587
|
-
let br = 255, bg = 255, bb = 255;
|
|
1588
|
-
if (lightMode) {
|
|
1589
|
-
br = 55;
|
|
1590
|
-
bg = 55;
|
|
1591
|
-
bb = 55;
|
|
1592
|
-
}
|
|
1593
|
-
if (color) {
|
|
1594
|
-
const p = parseColor(color);
|
|
1595
|
-
if (p) {
|
|
1596
|
-
br = p.r;
|
|
1597
|
-
bg = p.g;
|
|
1598
|
-
bb = p.b;
|
|
1599
|
-
}
|
|
1600
|
-
}
|
|
1601
|
-
let acR = 0, acG = 255, acB = 204;
|
|
1602
|
-
const ap = parseColor(resolvedAccent);
|
|
1603
|
-
if (ap) {
|
|
1604
|
-
acR = ap.r;
|
|
1605
|
-
acG = ap.g;
|
|
1606
|
-
acB = ap.b;
|
|
1607
|
-
}
|
|
1608
|
-
const charArr = chars.replace(/ /g, "").split("");
|
|
1609
|
-
if (charArr.length === 0) return;
|
|
1610
|
-
const cols = Math.ceil(width / fontSize);
|
|
1611
|
-
const rows = Math.ceil(height / fontSize);
|
|
1612
|
-
for (let row = 0; row < rows; row++) {
|
|
1613
|
-
for (let col = 0; col < cols; col++) {
|
|
1614
|
-
const px = col * fontSize + fontSize * 0.5;
|
|
1615
|
-
const py = row * fontSize + fontSize * 0.5;
|
|
1616
|
-
const dx = px - cx;
|
|
1617
|
-
const dy = py - cy;
|
|
1618
|
-
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
1619
|
-
const norm = dist / maxDist;
|
|
1620
|
-
let totalIntensity = 0;
|
|
1621
|
-
for (let r = 0; r < rings; r++) {
|
|
1622
|
-
const phase = r / rings;
|
|
1623
|
-
const t = (time * speed * 0.38 + phase) % 1;
|
|
1624
|
-
const ringDist = Math.abs(norm - t);
|
|
1625
|
-
const ringNorm = Math.max(0, 1 - ringDist * maxDist / (fontSize * (12 - sharpness)));
|
|
1626
|
-
totalIntensity += Math.cos(ringNorm * Math.PI * 0.5) * ringNorm;
|
|
1627
|
-
}
|
|
1628
|
-
totalIntensity = Math.min(1, totalIntensity);
|
|
1629
|
-
if (totalIntensity < 0.02) continue;
|
|
1630
|
-
const isAccent = totalIntensity > 0.6;
|
|
1631
|
-
ctx.font = `${fontSize}px monospace`;
|
|
1632
|
-
const charIdx = Math.floor(totalIntensity * (charArr.length - 1));
|
|
1633
|
-
const ch = charArr[Math.min(charIdx, charArr.length - 1)];
|
|
1634
|
-
const alpha = lightMode ? totalIntensity * 0.88 : totalIntensity * 0.22;
|
|
1635
|
-
ctx.fillStyle = isAccent ? `rgba(${acR},${acG},${acB},${Math.min(lightMode ? 0.95 : 0.4, totalIntensity * 0.55)})` : `rgba(${br},${bg},${bb},${alpha})`;
|
|
1636
|
-
ctx.fillText(ch, px, py);
|
|
1637
|
-
}
|
|
1638
|
-
}
|
|
1639
|
-
ctx.textAlign = "left";
|
|
1640
|
-
}
|
|
1641
|
-
|
|
1642
|
-
// src/backgrounds/noise.ts
|
|
1643
|
-
function renderNoiseBackground(ctx, width, height, time, mousePos = { x: 0.5, y: 0.5 }, options = {}) {
|
|
1644
|
-
const {
|
|
1645
|
-
fontSize = 14,
|
|
1646
|
-
chars = " .\xB7:;=+*#%@\u2591\u2592\u2593",
|
|
1647
|
-
accentColor = void 0,
|
|
1648
|
-
color,
|
|
1649
|
-
octaves = 4,
|
|
1650
|
-
speed = 1,
|
|
1651
|
-
scale = 1,
|
|
1652
|
-
accentThreshold = 0.78,
|
|
1653
|
-
mouseWarp = 0.3,
|
|
1654
|
-
lightMode = false
|
|
1655
|
-
} = options;
|
|
1656
|
-
const resolvedAccent = accentColor ?? (lightMode ? "#6b8700" : "#d4ff00");
|
|
1657
|
-
const charW = fontSize * 0.62;
|
|
1658
|
-
const lineH = fontSize * 1.4;
|
|
1659
|
-
const cols = Math.ceil(width / charW);
|
|
1660
|
-
const rows = Math.ceil(height / lineH);
|
|
1661
|
-
ctx.clearRect(0, 0, width, height);
|
|
1662
|
-
ctx.font = `${fontSize}px monospace`;
|
|
1663
|
-
ctx.textBaseline = "top";
|
|
1664
|
-
let br = 255, bgc = 255, bb = 255;
|
|
1665
|
-
if (lightMode) {
|
|
1666
|
-
br = 55;
|
|
1667
|
-
bgc = 55;
|
|
1668
|
-
bb = 55;
|
|
1669
|
-
}
|
|
1670
|
-
if (color) {
|
|
1671
|
-
const p = parseColor(color);
|
|
1672
|
-
if (p) {
|
|
1673
|
-
br = p.r;
|
|
1674
|
-
bgc = p.g;
|
|
1675
|
-
bb = p.b;
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
let acR = 212, acG = 255, acB = 0;
|
|
1679
|
-
const ap = parseColor(resolvedAccent);
|
|
1680
|
-
if (ap) {
|
|
1681
|
-
acR = ap.r;
|
|
1682
|
-
acG = ap.g;
|
|
1683
|
-
acB = ap.b;
|
|
1684
|
-
}
|
|
1685
|
-
const noiseScale = 0.035 * scale;
|
|
1686
|
-
const t = time * speed;
|
|
1687
|
-
const oct = Math.min(6, Math.max(1, octaves));
|
|
1688
|
-
const fbmN = (x, y) => {
|
|
1689
|
-
let v = 0, amp = 0.5, freq = 1, norm = 0;
|
|
1690
|
-
for (let o = 0; o < oct; o++) {
|
|
1691
|
-
v += vnoise(x * freq, y * freq) * amp;
|
|
1692
|
-
norm += amp;
|
|
1693
|
-
amp *= 0.5;
|
|
1694
|
-
freq *= 2.1;
|
|
1695
|
-
}
|
|
1696
|
-
return v / norm;
|
|
1697
|
-
};
|
|
1698
|
-
for (let row = 0; row < rows; row++) {
|
|
1699
|
-
for (let col = 0; col < cols; col++) {
|
|
1700
|
-
const nx = col * noiseScale + t * 0.06;
|
|
1701
|
-
const ny = row * noiseScale * 1.3 - t * 0.04;
|
|
1702
|
-
const dx = col / cols - mousePos.x;
|
|
1703
|
-
const dy = row / rows - mousePos.y;
|
|
1704
|
-
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
1705
|
-
const warp = mouseWarp > 0 ? Math.max(0, 1 - dist / mouseWarp) * 0.12 : 0;
|
|
1706
|
-
const wx = nx + warp * Math.sin(t * 1.3 + dy * 8);
|
|
1707
|
-
const wy = ny + warp * Math.cos(t * 0.9 + dx * 8);
|
|
1708
|
-
const raw = fbmN(wx, wy);
|
|
1709
|
-
const norm2 = raw * 0.5 + 0.5;
|
|
1710
|
-
if (norm2 < 0.12) continue;
|
|
1711
|
-
const charIdx = Math.floor(norm2 * (chars.length - 1));
|
|
1712
|
-
const ch = chars[charIdx];
|
|
1713
|
-
if (ch === " ") continue;
|
|
1714
|
-
const isAccent = norm2 > accentThreshold;
|
|
1715
|
-
const alpha = lightMode ? norm2 * 0.82 : norm2 * 0.13;
|
|
1716
|
-
ctx.fillStyle = isAccent ? `rgba(${acR},${acG},${acB},${lightMode ? 0.92 : 0.28})` : `rgba(${br},${bgc},${bb},${alpha})`;
|
|
1717
|
-
ctx.fillText(ch, col * charW, row * lineH);
|
|
1718
|
-
}
|
|
1719
|
-
}
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
|
-
// src/backgrounds/grid.ts
|
|
1723
|
-
function renderGridBackground(ctx, width, height, time, mousePos = { x: 0.5, y: 0.5 }, options = {}) {
|
|
1724
|
-
const {
|
|
1725
|
-
fontSize = 12,
|
|
1726
|
-
chars = "\xB7-=+|/",
|
|
1727
|
-
accentColor = void 0,
|
|
1728
|
-
color,
|
|
1729
|
-
bands = 3,
|
|
1730
|
-
speed = 1,
|
|
1731
|
-
bandWidth = 0.12,
|
|
1732
|
-
glitch = true,
|
|
1733
|
-
lightMode = false
|
|
1734
|
-
} = options;
|
|
1735
|
-
const resolvedAccent = accentColor ?? (lightMode ? "#6b8700" : "#d4ff00");
|
|
1736
|
-
const charW = fontSize * 0.62;
|
|
1737
|
-
const lineH = fontSize * 1.4;
|
|
1738
|
-
const cols = Math.ceil(width / charW);
|
|
1739
|
-
const rows = Math.ceil(height / lineH);
|
|
1740
|
-
ctx.clearRect(0, 0, width, height);
|
|
1741
|
-
ctx.font = `${fontSize}px monospace`;
|
|
1742
|
-
ctx.textBaseline = "top";
|
|
1743
|
-
let br = 255, bgv = 255, bb = 255;
|
|
1744
|
-
if (lightMode) {
|
|
1745
|
-
br = 55;
|
|
1746
|
-
bgv = 55;
|
|
1747
|
-
bb = 55;
|
|
1748
|
-
}
|
|
1749
|
-
if (color) {
|
|
1750
|
-
const p = parseColor(color);
|
|
1751
|
-
if (p) {
|
|
1752
|
-
br = p.r;
|
|
1753
|
-
bgv = p.g;
|
|
1754
|
-
bb = p.b;
|
|
1755
|
-
}
|
|
1756
|
-
}
|
|
1757
|
-
let acR = 212, acG = 255, acB = 0;
|
|
1758
|
-
const ap = parseColor(resolvedAccent);
|
|
1759
|
-
if (ap) {
|
|
1760
|
-
acR = ap.r;
|
|
1761
|
-
acG = ap.g;
|
|
1762
|
-
acB = ap.b;
|
|
1763
|
-
}
|
|
1764
|
-
const t = time * speed;
|
|
1765
|
-
for (let row = 0; row < rows; row++) {
|
|
1766
|
-
for (let col = 0; col < cols; col++) {
|
|
1767
|
-
const ny = row / rows;
|
|
1768
|
-
const scanPhase = ((ny * bands - t * 0.5) % 1 + 1) % 1;
|
|
1769
|
-
const bandIntensity = Math.max(0, 1 - scanPhase / bandWidth);
|
|
1770
|
-
const gridSeed = hash2(col * 3, row * 7);
|
|
1771
|
-
const gridBase = (gridSeed * 0.5 + 0.5) * 0.35;
|
|
1772
|
-
let glitchBump = 0;
|
|
1773
|
-
if (glitch) {
|
|
1774
|
-
const dx = col / cols - mousePos.x;
|
|
1775
|
-
const dy = ny - mousePos.y;
|
|
1776
|
-
const d = Math.sqrt(dx * dx + dy * dy);
|
|
1777
|
-
if (d < 0.18) {
|
|
1778
|
-
const g = hash2(col * 11 + Math.floor(t * 12), row * 5);
|
|
1779
|
-
glitchBump = Math.max(0, 1 - d / 0.18) * (g > 0.5 ? g - 0.3 : 0);
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
|
-
const intensity = Math.min(1, gridBase + bandIntensity * 0.8 + glitchBump * 0.6);
|
|
1783
|
-
if (intensity < 0.04) continue;
|
|
1784
|
-
const charIdx = Math.floor(intensity * (chars.length - 1));
|
|
1785
|
-
const ch = chars[charIdx];
|
|
1786
|
-
const isAccent = bandIntensity > 0.55;
|
|
1787
|
-
const alpha = lightMode ? intensity * 0.82 : intensity * 0.12;
|
|
1788
|
-
ctx.fillStyle = isAccent ? `rgba(${acR},${acG},${acB},${lightMode ? 0.92 : 0.28})` : `rgba(${br},${bgv},${bb},${alpha})`;
|
|
1789
|
-
ctx.fillText(ch, col * charW, row * lineH);
|
|
1790
|
-
}
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
|
-
|
|
1794
|
-
// src/backgrounds/aurora.ts
|
|
1795
|
-
function renderAuroraBackground(ctx, width, height, time, mousePos = { x: 0.5, y: 0.5 }, options = {}) {
|
|
1796
|
-
const {
|
|
1797
|
-
fontSize = 14,
|
|
1798
|
-
chars = " \xB7\u2219\u2022:;+=\u2261\u2263#@",
|
|
1799
|
-
color,
|
|
1800
|
-
accentColor = void 0,
|
|
1801
|
-
speed = 1,
|
|
1802
|
-
layers = 5,
|
|
1803
|
-
softness = 1.2,
|
|
1804
|
-
mouseRipple = 0.2,
|
|
1805
|
-
lightMode = false
|
|
1806
|
-
} = options;
|
|
1807
|
-
const resolvedAccent = accentColor ?? (lightMode ? "#6b8700" : "#d4ff00");
|
|
1808
|
-
const charW = fontSize * 0.62;
|
|
1809
|
-
const lineH = fontSize * 1.4;
|
|
1810
|
-
const cols = Math.ceil(width / charW);
|
|
1811
|
-
const rows = Math.ceil(height / lineH);
|
|
1812
|
-
ctx.clearRect(0, 0, width, height);
|
|
1813
|
-
ctx.font = `${fontSize}px monospace`;
|
|
1814
|
-
ctx.textBaseline = "top";
|
|
1815
|
-
let cr = 255, cg = 255, cb = 255;
|
|
1816
|
-
if (lightMode) {
|
|
1817
|
-
cr = 55;
|
|
1818
|
-
cg = 55;
|
|
1819
|
-
cb = 55;
|
|
1820
|
-
}
|
|
1821
|
-
if (color) {
|
|
1822
|
-
const p = parseColor(color);
|
|
1823
|
-
if (p) {
|
|
1824
|
-
cr = p.r;
|
|
1825
|
-
cg = p.g;
|
|
1826
|
-
cb = p.b;
|
|
1827
|
-
}
|
|
1828
|
-
}
|
|
1829
|
-
let acR = 212, acG = 255, acB = 0;
|
|
1830
|
-
const ap = parseColor(resolvedAccent);
|
|
1831
|
-
if (ap) {
|
|
1832
|
-
acR = ap.r;
|
|
1833
|
-
acG = ap.g;
|
|
1834
|
-
acB = ap.b;
|
|
1835
|
-
}
|
|
1836
|
-
const t = time * speed;
|
|
1837
|
-
const layerParams = [];
|
|
1838
|
-
for (let l = 0; l < layers; l++) {
|
|
1839
|
-
const seed = hash2(l * 17, l * 31 + 7);
|
|
1840
|
-
const seed2 = hash2(l * 23 + 5, l * 11);
|
|
1841
|
-
layerParams.push({
|
|
1842
|
-
fx: 0.8 + seed * 2.2,
|
|
1843
|
-
fy: 1.2 + seed2 * 1.8,
|
|
1844
|
-
phase: seed * Math.PI * 4,
|
|
1845
|
-
dt: (0.3 + hash2(l * 7, l * 13 + 3) * 0.5) * (l % 2 === 0 ? 1 : -1),
|
|
1846
|
-
amp: 0.55 + hash2(l * 29, l * 3) * 0.45
|
|
1847
|
-
});
|
|
1848
|
-
}
|
|
1849
|
-
for (let row = 0; row < rows; row++) {
|
|
1850
|
-
const ny = row / rows;
|
|
1851
|
-
for (let col = 0; col < cols; col++) {
|
|
1852
|
-
const nx = col / cols;
|
|
1853
|
-
const mdx = nx - mousePos.x;
|
|
1854
|
-
const mdy = ny - mousePos.y;
|
|
1855
|
-
const md = Math.sqrt(mdx * mdx + mdy * mdy);
|
|
1856
|
-
const warp = mouseRipple * Math.exp(-md * md / 0.06);
|
|
1857
|
-
const wx = nx + mdx * warp;
|
|
1858
|
-
const wy = ny + mdy * warp;
|
|
1859
|
-
let sum = 0;
|
|
1860
|
-
let totalAmp = 0;
|
|
1861
|
-
for (let l = 0; l < layers; l++) {
|
|
1862
|
-
const { fx, fy, phase, dt, amp } = layerParams[l];
|
|
1863
|
-
const wave = Math.sin(wx * fx * Math.PI * 2 + t * dt + phase) * Math.cos(wy * fy * Math.PI * 2 + t * dt * 0.7 + phase * 1.3);
|
|
1864
|
-
sum += wave * amp;
|
|
1865
|
-
totalAmp += amp;
|
|
1866
|
-
}
|
|
1867
|
-
const rawVal = sum / totalAmp;
|
|
1868
|
-
const curved = 0.5 + 0.5 * Math.tanh(rawVal * softness * 2.2);
|
|
1869
|
-
if (curved < 0.12) continue;
|
|
1870
|
-
const normalized = (curved - 0.12) / 0.88;
|
|
1871
|
-
const charIdx = Math.min(chars.length - 1, Math.floor(normalized * chars.length));
|
|
1872
|
-
const ch = chars[charIdx];
|
|
1873
|
-
const isAccent = curved > 0.82;
|
|
1874
|
-
const alpha = lightMode ? curved * 0.82 : curved * 0.14;
|
|
1875
|
-
ctx.fillStyle = isAccent ? `rgba(${acR},${acG},${acB},${lightMode ? 0.92 : 0.32})` : `rgba(${cr},${cg},${cb},${alpha})`;
|
|
1876
|
-
ctx.fillText(ch, col * charW, row * lineH);
|
|
1877
|
-
}
|
|
1878
|
-
}
|
|
1879
|
-
}
|
|
1880
|
-
|
|
1881
|
-
// src/backgrounds/silk.ts
|
|
1882
|
-
function renderSilkBackground(ctx, width, height, time, options = {}) {
|
|
1883
|
-
const {
|
|
1884
|
-
fontSize = 13,
|
|
1885
|
-
color,
|
|
1886
|
-
accentColor = void 0,
|
|
1887
|
-
speed = 0.4,
|
|
1888
|
-
layers = 4,
|
|
1889
|
-
turbulence = 0.8,
|
|
1890
|
-
lightMode = false
|
|
1891
|
-
} = options;
|
|
1892
|
-
const resolvedAccent = accentColor ?? (lightMode ? "#6b8700" : "#d4ff00");
|
|
1893
|
-
const charW = fontSize * 0.62;
|
|
1894
|
-
const lineH = fontSize * 1.4;
|
|
1895
|
-
const cols = Math.ceil(width / charW);
|
|
1896
|
-
const rows = Math.ceil(height / lineH);
|
|
1897
|
-
ctx.clearRect(0, 0, width, height);
|
|
1898
|
-
ctx.font = `${fontSize}px monospace`;
|
|
1899
|
-
ctx.textBaseline = "top";
|
|
1900
|
-
let cr = 255, cg = 255, cb = 255;
|
|
1901
|
-
if (lightMode) {
|
|
1902
|
-
cr = 55;
|
|
1903
|
-
cg = 55;
|
|
1904
|
-
cb = 55;
|
|
1905
|
-
}
|
|
1906
|
-
if (color) {
|
|
1907
|
-
const p = parseColor(color);
|
|
1908
|
-
if (p) {
|
|
1909
|
-
cr = p.r;
|
|
1910
|
-
cg = p.g;
|
|
1911
|
-
cb = p.b;
|
|
1912
|
-
}
|
|
1913
|
-
}
|
|
1914
|
-
let acR = 212, acG = 255, acB = 0;
|
|
1915
|
-
const ap = parseColor(resolvedAccent);
|
|
1916
|
-
if (ap) {
|
|
1917
|
-
acR = ap.r;
|
|
1918
|
-
acG = ap.g;
|
|
1919
|
-
acB = ap.b;
|
|
1920
|
-
}
|
|
1921
|
-
const t = time * speed;
|
|
1922
|
-
const dirChars = ["\u2500", "\u2500", "\u254C", "\xB7", "\u254C", "\u2500", "\u2500", "\u254C", "\xB7"];
|
|
1923
|
-
for (let row = 0; row < rows; row++) {
|
|
1924
|
-
const ny = row / rows;
|
|
1925
|
-
for (let col = 0; col < cols; col++) {
|
|
1926
|
-
const nx = col / cols;
|
|
1927
|
-
let angleSum = 0;
|
|
1928
|
-
let intensitySum = 0;
|
|
1929
|
-
for (let l = 0; l < layers; l++) {
|
|
1930
|
-
const ls = hash2(l * 13, l * 7 + 3);
|
|
1931
|
-
const ls2 = hash2(l * 29, l * 11 + 1);
|
|
1932
|
-
const fx = 1.1 + ls * 2.4;
|
|
1933
|
-
const fy = 0.9 + ls2 * 2;
|
|
1934
|
-
const ph = ls * Math.PI * 6;
|
|
1935
|
-
const dr = (0.2 + hash2(l * 41, l * 17) * 0.5) * (l % 2 === 0 ? 1 : -1.3);
|
|
1936
|
-
const u = Math.sin(nx * fx * Math.PI * 2 + t * dr + ph);
|
|
1937
|
-
const v = Math.cos(ny * fy * Math.PI * 2 + t * dr * 0.6 + ph * 1.7);
|
|
1938
|
-
const cross = Math.sin(nx * fy * Math.PI * turbulence + ny * fx * Math.PI * turbulence + t * dr * 0.4);
|
|
1939
|
-
angleSum += Math.atan2(v + cross * 0.3, u);
|
|
1940
|
-
intensitySum += (u * v + 1) * 0.5;
|
|
1941
|
-
}
|
|
1942
|
-
const angle = angleSum / layers;
|
|
1943
|
-
const intensity = Math.min(1, intensitySum / layers);
|
|
1944
|
-
if (intensity < 0.1) continue;
|
|
1945
|
-
const angleNorm = (angle + Math.PI) / (Math.PI * 2);
|
|
1946
|
-
const charIdx = Math.floor(angleNorm * dirChars.length) % dirChars.length;
|
|
1947
|
-
const ch = dirChars[charIdx];
|
|
1948
|
-
const isAccent = intensity > 0.8;
|
|
1949
|
-
const alpha = lightMode ? intensity * 0.8 : intensity * 0.13;
|
|
1950
|
-
ctx.fillStyle = isAccent ? `rgba(${acR},${acG},${acB},${lightMode ? 0.9 : 0.26})` : `rgba(${cr},${cg},${cb},${alpha})`;
|
|
1951
|
-
ctx.fillText(ch, col * charW, row * lineH);
|
|
1952
|
-
}
|
|
1953
|
-
}
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
|
-
// src/backgrounds/void.ts
|
|
1957
|
-
function renderVoidBackground(ctx, width, height, time, mousePos = { x: 0.5, y: 0.5 }, options = {}) {
|
|
1958
|
-
const {
|
|
1959
|
-
fontSize = 13,
|
|
1960
|
-
chars = " \xB7:;=+*#%@",
|
|
1961
|
-
color,
|
|
1962
|
-
accentColor = void 0,
|
|
1963
|
-
speed = 1,
|
|
1964
|
-
radius = 0.38,
|
|
1965
|
-
swirl = 3,
|
|
1966
|
-
lightMode = false
|
|
1967
|
-
} = options;
|
|
1968
|
-
const resolvedAccent = accentColor ?? (lightMode ? "#6b8700" : "#d4ff00");
|
|
1969
|
-
const charW = fontSize * 0.62;
|
|
1970
|
-
const lineH = fontSize * 1.4;
|
|
1971
|
-
const cols = Math.ceil(width / charW);
|
|
1972
|
-
const rows = Math.ceil(height / lineH);
|
|
1973
|
-
const aspect = width / height;
|
|
1974
|
-
ctx.clearRect(0, 0, width, height);
|
|
1975
|
-
ctx.font = `${fontSize}px monospace`;
|
|
1976
|
-
ctx.textBaseline = "top";
|
|
1977
|
-
let cr = 255, cg = 255, cb = 255;
|
|
1978
|
-
if (lightMode) {
|
|
1979
|
-
cr = 55;
|
|
1980
|
-
cg = 55;
|
|
1981
|
-
cb = 55;
|
|
1982
|
-
}
|
|
1983
|
-
if (color) {
|
|
1984
|
-
const p = parseColor(color);
|
|
1985
|
-
if (p) {
|
|
1986
|
-
cr = p.r;
|
|
1987
|
-
cg = p.g;
|
|
1988
|
-
cb = p.b;
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
let acR = 212, acG = 255, acB = 0;
|
|
1992
|
-
const ap = parseColor(resolvedAccent);
|
|
1993
|
-
if (ap) {
|
|
1994
|
-
acR = ap.r;
|
|
1995
|
-
acG = ap.g;
|
|
1996
|
-
acB = ap.b;
|
|
1997
|
-
}
|
|
1998
|
-
const t = time * speed;
|
|
1999
|
-
for (let row = 0; row < rows; row++) {
|
|
2000
|
-
const ny = row / rows;
|
|
2001
|
-
for (let col = 0; col < cols; col++) {
|
|
2002
|
-
const nx = col / cols;
|
|
2003
|
-
const dx = (nx - mousePos.x) * aspect;
|
|
2004
|
-
const dy = ny - mousePos.y;
|
|
2005
|
-
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
2006
|
-
const r = dist / radius;
|
|
2007
|
-
if (r > 1) {
|
|
2008
|
-
const outerNoise = hash2(col * 3, row * 7) * Math.max(0, 1 - (r - 1) * 3);
|
|
2009
|
-
if (outerNoise < 0.62) continue;
|
|
2010
|
-
const alpha2 = outerNoise * (lightMode ? 0.28 : 0.04);
|
|
2011
|
-
ctx.fillStyle = `rgba(${cr},${cg},${cb},${alpha2})`;
|
|
2012
|
-
ctx.fillText(chars[1], col * charW, row * lineH);
|
|
2013
|
-
continue;
|
|
2014
|
-
}
|
|
2015
|
-
const pulseRing = Math.max(0, 1 - Math.abs(r - (0.15 + 0.12 * Math.sin(t * 1.1))) / 0.07);
|
|
2016
|
-
const gravity = Math.pow(1 - r, 2.2);
|
|
2017
|
-
const intensity = Math.min(1, gravity + pulseRing * 0.6);
|
|
2018
|
-
if (intensity < 0.06) continue;
|
|
2019
|
-
const densityI = Math.floor(intensity * (chars.length - 1));
|
|
2020
|
-
const charIdx = Math.min(chars.length - 1, densityI);
|
|
2021
|
-
const ch = chars[charIdx];
|
|
2022
|
-
const isAccent = pulseRing > 0.35 || r < 0.08;
|
|
2023
|
-
const alpha = lightMode ? intensity * 0.85 : intensity * 0.18;
|
|
2024
|
-
ctx.fillStyle = isAccent ? `rgba(${acR},${acG},${acB},${lightMode ? 0.95 : 0.38})` : `rgba(${cr},${cg},${cb},${alpha})`;
|
|
2025
|
-
ctx.fillText(ch, col * charW, row * lineH);
|
|
2026
|
-
}
|
|
2027
|
-
}
|
|
2028
|
-
}
|
|
2029
|
-
|
|
2030
|
-
// src/backgrounds/morph.ts
|
|
2031
|
-
function renderMorphBackground(ctx, width, height, time, options = {}) {
|
|
2032
|
-
const {
|
|
2033
|
-
fontSize = 14,
|
|
2034
|
-
chars = " \xB7\u2219\u2022:-=+*#",
|
|
2035
|
-
color,
|
|
2036
|
-
accentColor = void 0,
|
|
2037
|
-
speed = 0.5,
|
|
2038
|
-
harmonics = 3,
|
|
2039
|
-
lightMode = false
|
|
2040
|
-
} = options;
|
|
2041
|
-
const resolvedAccent = accentColor ?? (lightMode ? "#6b8700" : "#d4ff00");
|
|
2042
|
-
const charW = fontSize * 0.62;
|
|
2043
|
-
const lineH = fontSize * 1.4;
|
|
2044
|
-
const cols = Math.ceil(width / charW);
|
|
2045
|
-
const rows = Math.ceil(height / lineH);
|
|
2046
|
-
ctx.clearRect(0, 0, width, height);
|
|
2047
|
-
ctx.font = `${fontSize}px monospace`;
|
|
2048
|
-
ctx.textBaseline = "top";
|
|
2049
|
-
let cr = 255, cg = 255, cb = 255;
|
|
2050
|
-
if (lightMode) {
|
|
2051
|
-
cr = 55;
|
|
2052
|
-
cg = 55;
|
|
2053
|
-
cb = 55;
|
|
2054
|
-
}
|
|
2055
|
-
if (color) {
|
|
2056
|
-
const p = parseColor(color);
|
|
2057
|
-
if (p) {
|
|
2058
|
-
cr = p.r;
|
|
2059
|
-
cg = p.g;
|
|
2060
|
-
cb = p.b;
|
|
2061
|
-
}
|
|
2062
|
-
}
|
|
2063
|
-
let acR = 212, acG = 255, acB = 0;
|
|
2064
|
-
const ap = parseColor(resolvedAccent);
|
|
2065
|
-
if (ap) {
|
|
2066
|
-
acR = ap.r;
|
|
2067
|
-
acG = ap.g;
|
|
2068
|
-
acB = ap.b;
|
|
2069
|
-
}
|
|
2070
|
-
const t = time * speed;
|
|
2071
|
-
const maxV = Array.from({ length: harmonics }, (_, h) => 1 / (h + 1)).reduce((a, b) => a + b, 0);
|
|
2072
|
-
for (let row = 0; row < rows; row++) {
|
|
2073
|
-
for (let col = 0; col < cols; col++) {
|
|
2074
|
-
let v = 0;
|
|
2075
|
-
for (let h = 0; h < harmonics; h++) {
|
|
2076
|
-
const fBase = hash2(col * (h + 3) + 7, row * (h + 5) + 11);
|
|
2077
|
-
const fineF = 0.18 + fBase * 1.4;
|
|
2078
|
-
const phase = hash2(col * (h + 7), row * (h + 9) + 3) * Math.PI * 2;
|
|
2079
|
-
const weight = 1 / (h + 1);
|
|
2080
|
-
v += Math.sin(t * fineF + phase) * weight;
|
|
2081
|
-
}
|
|
2082
|
-
const norm = (v / maxV + 1) * 0.5;
|
|
2083
|
-
if (norm < 0.28) continue;
|
|
2084
|
-
const remapped = (norm - 0.28) / 0.72;
|
|
2085
|
-
const charIdx = Math.min(chars.length - 1, Math.floor(remapped * chars.length));
|
|
2086
|
-
const ch = chars[charIdx];
|
|
2087
|
-
const isAccent = norm > 0.88;
|
|
2088
|
-
const alpha = lightMode ? remapped * 0.82 : remapped * 0.13;
|
|
2089
|
-
ctx.fillStyle = isAccent ? `rgba(${acR},${acG},${acB},${lightMode ? 0.92 : 0.28})` : `rgba(${cr},${cg},${cb},${alpha})`;
|
|
2090
|
-
ctx.fillText(ch, col * charW, row * lineH);
|
|
2091
|
-
}
|
|
2092
|
-
}
|
|
2093
|
-
}
|
|
2094
|
-
|
|
2095
|
-
// src/backgrounds/fire.ts
|
|
2096
|
-
function renderFireBackground(ctx, width, height, time, options = {}) {
|
|
2097
|
-
const {
|
|
2098
|
-
fontSize = 13,
|
|
2099
|
-
chars = " .,:;i+xX#&@",
|
|
2100
|
-
color = "#ff4500",
|
|
2101
|
-
hotColor = "#ffe066",
|
|
2102
|
-
intensity = 0.85,
|
|
2103
|
-
wind = 0,
|
|
2104
|
-
speed = 1,
|
|
2105
|
-
lightMode = false
|
|
2106
|
-
} = options;
|
|
2107
|
-
const charW = fontSize * 0.62;
|
|
2108
|
-
const lineH = fontSize * 1.4;
|
|
2109
|
-
const cols = Math.ceil(width / charW);
|
|
2110
|
-
const rows = Math.ceil(height / lineH);
|
|
2111
|
-
const len = cols * rows;
|
|
2112
|
-
const key = "__fire_heat__";
|
|
2113
|
-
const canvasAny = ctx.canvas;
|
|
2114
|
-
let heat = canvasAny[key];
|
|
2115
|
-
if (!heat || heat.length !== len) {
|
|
2116
|
-
heat = new Float32Array(len);
|
|
2117
|
-
canvasAny[key] = heat;
|
|
2118
|
-
}
|
|
2119
|
-
const dt = 0.016 * speed;
|
|
2120
|
-
const coolingRate = 0.18 * dt;
|
|
2121
|
-
const windShift = wind * speed * 0.8;
|
|
2122
|
-
const baseRow = rows - 1;
|
|
2123
|
-
const t = time * speed;
|
|
2124
|
-
for (let c = 0; c < cols; c++) {
|
|
2125
|
-
const flicker = Math.sin(c * 0.31 + t * 4.1) * 0.5 + 0.5;
|
|
2126
|
-
const flicker2 = Math.sin(c * 0.73 - t * 2.7) * 0.5 + 0.5;
|
|
2127
|
-
const seed = (flicker * 0.6 + flicker2 * 0.4) * intensity;
|
|
2128
|
-
heat[baseRow * cols + c] = Math.min(1, seed + Math.random() * 0.15 * intensity);
|
|
2129
|
-
if (baseRow > 0) heat[(baseRow - 1) * cols + c] = Math.min(1, seed * 0.85 + Math.random() * 0.1 * intensity);
|
|
2130
|
-
}
|
|
2131
|
-
const newHeat = new Float32Array(len);
|
|
2132
|
-
for (let r = 0; r < rows - 2; r++) {
|
|
2133
|
-
for (let c = 0; c < cols; c++) {
|
|
2134
|
-
const below = heat[(r + 1) * cols + c];
|
|
2135
|
-
const below2 = heat[(r + 2) * cols + Math.max(0, Math.min(cols - 1, c + Math.round(windShift)))];
|
|
2136
|
-
const left = heat[(r + 1) * cols + Math.max(0, c - 1)];
|
|
2137
|
-
const right = heat[(r + 1) * cols + Math.min(cols - 1, c + 1)];
|
|
2138
|
-
const avg = below * 0.4 + below2 * 0.25 + left * 0.175 + right * 0.175;
|
|
2139
|
-
newHeat[r * cols + c] = Math.max(0, avg - coolingRate - Math.random() * 0.02 * speed);
|
|
2140
|
-
}
|
|
2141
|
-
}
|
|
2142
|
-
for (let c = 0; c < cols; c++) {
|
|
2143
|
-
newHeat[(rows - 1) * cols + c] = heat[(rows - 1) * cols + c];
|
|
2144
|
-
if (rows > 1) newHeat[(rows - 2) * cols + c] = heat[(rows - 2) * cols + c];
|
|
2145
|
-
}
|
|
2146
|
-
canvasAny[key] = newHeat;
|
|
2147
|
-
const cp = parseColor(color) ?? { r: 255, g: 69, b: 0 };
|
|
2148
|
-
const hp = parseColor(hotColor) ?? { r: 255, g: 224, b: 102 };
|
|
2149
|
-
ctx.clearRect(0, 0, width, height);
|
|
2150
|
-
ctx.font = `${fontSize}px "JetBrains Mono", monospace`;
|
|
2151
|
-
ctx.textBaseline = "top";
|
|
2152
|
-
for (let r = 0; r < rows; r++) {
|
|
2153
|
-
for (let c = 0; c < cols; c++) {
|
|
2154
|
-
const v = newHeat[r * cols + c];
|
|
2155
|
-
if (v < 0.03) continue;
|
|
2156
|
-
const charIdx = Math.min(chars.length - 1, Math.floor(v * chars.length));
|
|
2157
|
-
const ch = chars[charIdx];
|
|
2158
|
-
if (ch === " ") continue;
|
|
2159
|
-
const blend = Math.min(1, v * 1.2);
|
|
2160
|
-
const r2 = cp.r + (hp.r - cp.r) * blend | 0;
|
|
2161
|
-
const g2 = cp.g + (hp.g - cp.g) * blend | 0;
|
|
2162
|
-
const b2 = cp.b + (hp.b - cp.b) * blend | 0;
|
|
2163
|
-
const alpha = lightMode ? 1 - v * 0.3 : Math.min(1, v + 0.15);
|
|
2164
|
-
ctx.globalAlpha = alpha;
|
|
2165
|
-
ctx.fillStyle = `rgb(${r2},${g2},${b2})`;
|
|
2166
|
-
ctx.fillText(ch, c * charW, r * lineH);
|
|
2167
|
-
}
|
|
2168
|
-
}
|
|
2169
|
-
ctx.globalAlpha = 1;
|
|
2170
|
-
}
|
|
2171
|
-
|
|
2172
|
-
// src/backgrounds/dna.ts
|
|
2173
|
-
function renderDnaBackground(ctx, width, height, time, options = {}) {
|
|
2174
|
-
const {
|
|
2175
|
-
fontSize = 13,
|
|
2176
|
-
baseChars = "ATCG",
|
|
2177
|
-
bridgeChars = "-=\u2261",
|
|
2178
|
-
color = "#00e5ff",
|
|
2179
|
-
color2 = "#ff4081",
|
|
2180
|
-
bridgeColor = "#88ffcc",
|
|
2181
|
-
speed = 1,
|
|
2182
|
-
helixCount,
|
|
2183
|
-
lightMode = false
|
|
2184
|
-
} = options;
|
|
2185
|
-
const charW = fontSize * 0.62;
|
|
2186
|
-
const lineH = fontSize * 1.4;
|
|
2187
|
-
const cols = Math.ceil(width / charW);
|
|
2188
|
-
const rows = Math.ceil(height / lineH);
|
|
2189
|
-
const numHelix = helixCount ?? Math.max(1, Math.floor(width / 80));
|
|
2190
|
-
const sectionW = cols / numHelix;
|
|
2191
|
-
const cp = parseColor(color) ?? { r: 0, g: 229, b: 255 };
|
|
2192
|
-
const cp2 = parseColor(color2) ?? { r: 255, g: 64, b: 129 };
|
|
2193
|
-
const bp = parseColor(bridgeColor) ?? { r: 136, g: 255, b: 204 };
|
|
2194
|
-
ctx.clearRect(0, 0, width, height);
|
|
2195
|
-
ctx.font = `${fontSize}px "JetBrains Mono", monospace`;
|
|
2196
|
-
ctx.textBaseline = "top";
|
|
2197
|
-
const t = time * speed;
|
|
2198
|
-
const amplitude = sectionW * 0.35;
|
|
2199
|
-
for (let h = 0; h < numHelix; h++) {
|
|
2200
|
-
const centerCol = sectionW * (h + 0.5);
|
|
2201
|
-
for (let r = 0; r < rows; r++) {
|
|
2202
|
-
const phase = r / rows * Math.PI * 6 - t * 1.8;
|
|
2203
|
-
const strand1ColF = centerCol + Math.sin(phase) * amplitude;
|
|
2204
|
-
const strand2ColF = centerCol + Math.sin(phase + Math.PI) * amplitude;
|
|
2205
|
-
const strand1Col = Math.round(strand1ColF);
|
|
2206
|
-
const strand2Col = Math.round(strand2ColF);
|
|
2207
|
-
if (strand1Col < 0 || strand1Col >= cols) continue;
|
|
2208
|
-
const baseSeed1 = hash2(h * 31 + r * 7, 3);
|
|
2209
|
-
const ch1 = baseChars[Math.floor(baseSeed1 * baseChars.length)];
|
|
2210
|
-
const depth1 = (Math.sin(phase) + 1) * 0.5;
|
|
2211
|
-
const depth2 = (Math.sin(phase + Math.PI) + 1) * 0.5;
|
|
2212
|
-
ctx.globalAlpha = 0.35 + depth1 * 0.65;
|
|
2213
|
-
ctx.fillStyle = `rgb(${cp.r},${cp.g},${cp.b})`;
|
|
2214
|
-
ctx.fillText(ch1, strand1Col * charW, r * lineH);
|
|
2215
|
-
if (strand2Col >= 0 && strand2Col < cols) {
|
|
2216
|
-
const baseSeed2 = hash2(h * 53 + r * 11, 7);
|
|
2217
|
-
const ch2 = baseChars[Math.floor(baseSeed2 * baseChars.length)];
|
|
2218
|
-
ctx.globalAlpha = 0.35 + depth2 * 0.65;
|
|
2219
|
-
ctx.fillStyle = `rgb(${cp2.r},${cp2.g},${cp2.b})`;
|
|
2220
|
-
ctx.fillText(ch2, strand2Col * charW, r * lineH);
|
|
2221
|
-
}
|
|
2222
|
-
const bridgeInterval = 3;
|
|
2223
|
-
if (r % bridgeInterval === 0) {
|
|
2224
|
-
const minC = Math.min(strand1Col, strand2Col);
|
|
2225
|
-
const maxC = Math.max(strand1Col, strand2Col);
|
|
2226
|
-
const bridgeLen = maxC - minC;
|
|
2227
|
-
if (bridgeLen > 1) {
|
|
2228
|
-
const bSeed = hash2(r * 17 + h * 43, 5);
|
|
2229
|
-
const bCh = bridgeChars[Math.floor(bSeed * bridgeChars.length)];
|
|
2230
|
-
const midBridgeAlpha = (depth1 + depth2) * 0.25 + 0.2;
|
|
2231
|
-
ctx.globalAlpha = midBridgeAlpha;
|
|
2232
|
-
ctx.fillStyle = `rgb(${bp.r},${bp.g},${bp.b})`;
|
|
2233
|
-
for (let bc = minC + 1; bc < maxC; bc++) {
|
|
2234
|
-
ctx.fillText(bCh, bc * charW, r * lineH);
|
|
2235
|
-
}
|
|
2236
|
-
}
|
|
2237
|
-
}
|
|
2238
|
-
}
|
|
2239
|
-
}
|
|
2240
|
-
ctx.globalAlpha = 1;
|
|
2241
|
-
}
|
|
2242
|
-
|
|
2243
|
-
// src/backgrounds/terrain.ts
|
|
2244
|
-
function renderTerrainBackground(ctx, width, height, time, options = {}) {
|
|
2245
|
-
const {
|
|
2246
|
-
fontSize = 13,
|
|
2247
|
-
chars = " .,:;+*#@",
|
|
2248
|
-
color = "#4caf50",
|
|
2249
|
-
skyColor = "#1a237e",
|
|
2250
|
-
peakColor = "#e0e0e0",
|
|
2251
|
-
speed = 1,
|
|
2252
|
-
roughness = 0.55,
|
|
2253
|
-
heightScale = 0.55,
|
|
2254
|
-
stars = true,
|
|
2255
|
-
lightMode = false
|
|
2256
|
-
} = options;
|
|
2257
|
-
const charW = fontSize * 0.62;
|
|
2258
|
-
const lineH = fontSize * 1.4;
|
|
2259
|
-
const cols = Math.ceil(width / charW);
|
|
2260
|
-
const rows = Math.ceil(height / lineH);
|
|
2261
|
-
const cp = parseColor(color) ?? { r: 76, g: 175, b: 80 };
|
|
2262
|
-
const sky = parseColor(skyColor) ?? { r: 26, g: 35, b: 126 };
|
|
2263
|
-
const peak = parseColor(peakColor) ?? { r: 224, g: 224, b: 224 };
|
|
2264
|
-
ctx.clearRect(0, 0, width, height);
|
|
2265
|
-
ctx.font = `${fontSize}px "JetBrains Mono", monospace`;
|
|
2266
|
-
ctx.textBaseline = "top";
|
|
2267
|
-
const scroll = time * speed * 0.4;
|
|
2268
|
-
const terrainRow = new Array(cols);
|
|
2269
|
-
for (let c = 0; c < cols; c++) {
|
|
2270
|
-
const nx = (c / cols + scroll) * roughness * 3;
|
|
2271
|
-
const h = (fbm(nx, 0.5) * 0.5 + 0.5) * heightScale;
|
|
2272
|
-
terrainRow[c] = Math.floor(h * rows);
|
|
2273
|
-
}
|
|
2274
|
-
for (let r = 0; r < rows; r++) {
|
|
2275
|
-
for (let c = 0; c < cols; c++) {
|
|
2276
|
-
const terrainStart = rows - 1 - terrainRow[c];
|
|
2277
|
-
const isGround = r >= terrainStart;
|
|
2278
|
-
const isNearPeak = r === terrainStart;
|
|
2279
|
-
if (!isGround) {
|
|
2280
|
-
if (stars) {
|
|
2281
|
-
const starSeed = hash2(c * 7 + Math.floor(scroll * 0.3), r * 13);
|
|
2282
|
-
if (starSeed > 0.97) {
|
|
2283
|
-
const twinkle = Math.sin(time * 2 + starSeed * 100) * 0.3 + 0.7;
|
|
2284
|
-
ctx.globalAlpha = twinkle * 0.5;
|
|
2285
|
-
ctx.fillStyle = `rgb(${sky.r + 60},${sky.g + 60},${sky.b + 80})`;
|
|
2286
|
-
ctx.fillText("\xB7", c * charW, r * lineH);
|
|
2287
|
-
}
|
|
2288
|
-
}
|
|
2289
|
-
continue;
|
|
2290
|
-
}
|
|
2291
|
-
const depth = (r - terrainStart) / Math.max(1, terrainRow[c]);
|
|
2292
|
-
const charIdx = Math.min(chars.length - 1, Math.floor(depth * chars.length));
|
|
2293
|
-
const ch = chars[charIdx];
|
|
2294
|
-
if (ch === " " && !isNearPeak) continue;
|
|
2295
|
-
const blendPeak = isNearPeak ? 1 : Math.max(0, 1 - depth * 4);
|
|
2296
|
-
const r2 = cp.r + (peak.r - cp.r) * blendPeak | 0;
|
|
2297
|
-
const g2 = cp.g + (peak.g - cp.g) * blendPeak | 0;
|
|
2298
|
-
const b2 = cp.b + (peak.b - cp.b) * blendPeak | 0;
|
|
2299
|
-
ctx.globalAlpha = 0.5 + depth * 0.5;
|
|
2300
|
-
ctx.fillStyle = `rgb(${r2},${g2},${b2})`;
|
|
2301
|
-
ctx.fillText(isNearPeak ? chars[chars.length - 1] : ch, c * charW, r * lineH);
|
|
2302
|
-
}
|
|
2303
|
-
}
|
|
2304
|
-
ctx.globalAlpha = 1;
|
|
2305
|
-
}
|
|
2306
|
-
|
|
2307
|
-
// src/backgrounds/circuit.ts
|
|
2308
|
-
var EAST = 1;
|
|
2309
|
-
var WEST = 2;
|
|
2310
|
-
var NORTH = 4;
|
|
2311
|
-
var SOUTH = 8;
|
|
2312
|
-
var DIR_CHARS = {
|
|
2313
|
-
[EAST | WEST]: "\u2500",
|
|
2314
|
-
[NORTH | SOUTH]: "\u2502",
|
|
2315
|
-
[EAST | SOUTH]: "\u250C",
|
|
2316
|
-
[WEST | SOUTH]: "\u2510",
|
|
2317
|
-
[EAST | NORTH]: "\u2514",
|
|
2318
|
-
[WEST | NORTH]: "\u2518",
|
|
2319
|
-
[EAST | WEST | SOUTH]: "\u252C",
|
|
2320
|
-
[EAST | WEST | NORTH]: "\u2534",
|
|
2321
|
-
[NORTH | SOUTH | EAST]: "\u251C",
|
|
2322
|
-
[NORTH | SOUTH | WEST]: "\u2524",
|
|
2323
|
-
[EAST | WEST | NORTH | SOUTH]: "\u253C",
|
|
2324
|
-
[EAST]: "\u2576",
|
|
2325
|
-
[WEST]: "\u2574",
|
|
2326
|
-
[NORTH]: "\u2575",
|
|
2327
|
-
[SOUTH]: "\u2577"
|
|
2328
|
-
};
|
|
2329
|
-
function renderCircuitBackground(ctx, width, height, time, options = {}) {
|
|
2330
|
-
const {
|
|
2331
|
-
fontSize = 13,
|
|
2332
|
-
pulseColor = "#ffffff",
|
|
2333
|
-
color = "#00ff88",
|
|
2334
|
-
density = 0.38,
|
|
2335
|
-
speed = 1,
|
|
2336
|
-
lightMode = false
|
|
2337
|
-
} = options;
|
|
2338
|
-
const charW = fontSize * 0.62;
|
|
2339
|
-
const lineH = fontSize * 1.4;
|
|
2340
|
-
const cols = Math.ceil(width / charW);
|
|
2341
|
-
const rows = Math.ceil(height / lineH);
|
|
2342
|
-
const cp = parseColor(color) ?? { r: 0, g: 255, b: 136 };
|
|
2343
|
-
const pp = parseColor(pulseColor) ?? { r: 255, g: 255, b: 255 };
|
|
2344
|
-
const getConnections = (c, r) => {
|
|
2345
|
-
if (hash2(c * 17 + 1, r * 7 + 2) > density) return 0;
|
|
2346
|
-
let mask = 0;
|
|
2347
|
-
if (c + 1 < cols && hash2(c * 17 + 1, r * 7 + 2) > 0.15) mask |= EAST;
|
|
2348
|
-
if (c - 1 >= 0 && hash2((c - 1) * 17 + 1, r * 7 + 2) > 0.15) mask |= WEST;
|
|
2349
|
-
if (r + 1 < rows && hash2(c * 17 + 1, (r + 1) * 7 + 2) > 0.15) mask |= SOUTH;
|
|
2350
|
-
if (r - 1 >= 0 && hash2(c * 17 + 1, (r - 1) * 7 + 2) > 0.15) mask |= NORTH;
|
|
2351
|
-
return mask;
|
|
2352
|
-
};
|
|
2353
|
-
ctx.clearRect(0, 0, width, height);
|
|
2354
|
-
ctx.font = `${fontSize}px "JetBrains Mono", monospace`;
|
|
2355
|
-
ctx.textBaseline = "top";
|
|
2356
|
-
const t = time * speed;
|
|
2357
|
-
for (let r = 0; r < rows; r++) {
|
|
2358
|
-
for (let c = 0; c < cols; c++) {
|
|
2359
|
-
const mask = getConnections(c, r);
|
|
2360
|
-
if (mask === 0) continue;
|
|
2361
|
-
const ch = DIR_CHARS[mask] ?? "\xB7";
|
|
2362
|
-
const pulsePosH = (t * 8 + hash2(c, r * 23) * 40) % cols;
|
|
2363
|
-
const pulsePosV = (t * 6 + hash2(r * 17, c * 11) * 40) % rows;
|
|
2364
|
-
const nearH = (mask & EAST || mask & WEST) && Math.abs(c - pulsePosH) < 1.5;
|
|
2365
|
-
const nearV = (mask & NORTH || mask & SOUTH) && Math.abs(r - pulsePosV) < 1.5;
|
|
2366
|
-
const isPulse = nearH || nearV;
|
|
2367
|
-
const baseAlpha = 0.25 + hash2(c * 3, r * 5) * 0.35;
|
|
2368
|
-
if (isPulse) {
|
|
2369
|
-
ctx.globalAlpha = 0.95;
|
|
2370
|
-
ctx.fillStyle = `rgb(${pp.r},${pp.g},${pp.b})`;
|
|
2371
|
-
} else {
|
|
2372
|
-
ctx.globalAlpha = baseAlpha;
|
|
2373
|
-
ctx.fillStyle = `rgb(${cp.r},${cp.g},${cp.b})`;
|
|
2374
|
-
}
|
|
2375
|
-
ctx.fillText(ch, c * charW, r * lineH);
|
|
2376
|
-
}
|
|
2377
|
-
}
|
|
2378
|
-
ctx.globalAlpha = 1;
|
|
2379
|
-
}
|
|
2380
|
-
|
|
2381
|
-
// src/backgrounds/index.ts
|
|
2382
|
-
function _parseColor(c) {
|
|
2383
|
-
const hex = c.match(/^#([0-9a-f]{3,8})$/i)?.[1];
|
|
2384
|
-
if (hex) {
|
|
2385
|
-
const h = hex.length <= 4 ? hex.split("").map((x) => parseInt(x + x, 16)) : [parseInt(hex.slice(0, 2), 16), parseInt(hex.slice(2, 4), 16), parseInt(hex.slice(4, 6), 16)];
|
|
2386
|
-
return { r: h[0], g: h[1], b: h[2] };
|
|
2387
|
-
}
|
|
2388
|
-
const rgb = c.match(/rgba?\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)/i);
|
|
2389
|
-
if (rgb) return { r: +rgb[1], g: +rgb[2], b: +rgb[3] };
|
|
2390
|
-
return null;
|
|
2391
|
-
}
|
|
2392
|
-
var BACKGROUND_TYPES = [
|
|
2393
|
-
"wave",
|
|
2394
|
-
"rain",
|
|
2395
|
-
"stars",
|
|
2396
|
-
"pulse",
|
|
2397
|
-
"noise",
|
|
2398
|
-
"grid",
|
|
2399
|
-
"aurora",
|
|
2400
|
-
"silk",
|
|
2401
|
-
"void",
|
|
2402
|
-
"morph",
|
|
2403
|
-
"fire",
|
|
2404
|
-
"dna",
|
|
2405
|
-
"terrain",
|
|
2406
|
-
"circuit"
|
|
2407
|
-
];
|
|
2408
|
-
function asciiBackground(target, options = {}) {
|
|
2409
|
-
const {
|
|
2410
|
-
type = "wave",
|
|
2411
|
-
opacity = 0.2,
|
|
2412
|
-
className,
|
|
2413
|
-
zIndex = 0,
|
|
2414
|
-
colorScheme = "auto",
|
|
2415
|
-
color,
|
|
2416
|
-
...renderOpts
|
|
2417
|
-
} = options;
|
|
2418
|
-
const container = typeof target === "string" ? document.querySelector(target) : target;
|
|
2419
|
-
if (!container) {
|
|
2420
|
-
console.warn("[asciify] asciiBackground: target not found", target);
|
|
2421
|
-
return { destroy: () => {
|
|
2422
|
-
} };
|
|
2423
|
-
}
|
|
2424
|
-
const prevPosition = container.style.position;
|
|
2425
|
-
if (getComputedStyle(container).position === "static") {
|
|
2426
|
-
container.style.position = "relative";
|
|
2427
|
-
}
|
|
2428
|
-
const canvas = document.createElement("canvas");
|
|
2429
|
-
canvas.style.cssText = [
|
|
2430
|
-
"position:absolute",
|
|
2431
|
-
"inset:0",
|
|
2432
|
-
"width:100%",
|
|
2433
|
-
"height:100%",
|
|
2434
|
-
`opacity:${opacity}`,
|
|
2435
|
-
"pointer-events:none",
|
|
2436
|
-
`z-index:${zIndex}`
|
|
2437
|
-
].join(";");
|
|
2438
|
-
if (className) canvas.className = className;
|
|
2439
|
-
container.prepend(canvas);
|
|
2440
|
-
const ctx = canvas.getContext("2d");
|
|
2441
|
-
const dpr = window.devicePixelRatio || 1;
|
|
2442
|
-
const mouse = { x: 0.5, y: 0.5 };
|
|
2443
|
-
const smoothMouse = { x: 0.5, y: 0.5 };
|
|
2444
|
-
const mq = window.matchMedia("(prefers-color-scheme: light)");
|
|
2445
|
-
const isLight = () => {
|
|
2446
|
-
if (colorScheme === "light") return true;
|
|
2447
|
-
if (colorScheme === "dark") return false;
|
|
2448
|
-
return mq.matches;
|
|
2449
|
-
};
|
|
2450
|
-
const parsedColor = color ? _parseColor(color) : null;
|
|
2451
|
-
const buildWaveOpts = () => ({
|
|
2452
|
-
...renderOpts,
|
|
2453
|
-
lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight(),
|
|
2454
|
-
baseColor: parsedColor ? `rgba(${parsedColor.r},${parsedColor.g},${parsedColor.b},{a})` : renderOpts.baseColor
|
|
2455
|
-
});
|
|
2456
|
-
const buildRainOpts = () => ({
|
|
2457
|
-
...renderOpts,
|
|
2458
|
-
lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight(),
|
|
2459
|
-
color: color ?? renderOpts.color
|
|
2460
|
-
});
|
|
2461
|
-
const buildStarsOpts = () => ({
|
|
2462
|
-
...renderOpts,
|
|
2463
|
-
lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight(),
|
|
2464
|
-
color: color ?? renderOpts.color
|
|
2465
|
-
});
|
|
2466
|
-
const buildPulseOpts = () => ({
|
|
2467
|
-
...renderOpts,
|
|
2468
|
-
lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight(),
|
|
2469
|
-
color: color ?? renderOpts.color
|
|
2470
|
-
});
|
|
2471
|
-
const buildNoiseOpts = () => ({
|
|
2472
|
-
...renderOpts,
|
|
2473
|
-
lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight(),
|
|
2474
|
-
color: color ?? renderOpts.color
|
|
2475
|
-
});
|
|
2476
|
-
const buildGridOpts = () => ({
|
|
2477
|
-
...renderOpts,
|
|
2478
|
-
lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight(),
|
|
2479
|
-
color: color ?? renderOpts.color
|
|
2480
|
-
});
|
|
2481
|
-
const buildAuroraOpts = () => ({
|
|
2482
|
-
...renderOpts,
|
|
2483
|
-
lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight(),
|
|
2484
|
-
color: color ?? renderOpts.color
|
|
2485
|
-
});
|
|
2486
|
-
const buildSilkOpts = () => ({
|
|
2487
|
-
...renderOpts,
|
|
2488
|
-
lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight(),
|
|
2489
|
-
color: color ?? renderOpts.color
|
|
2490
|
-
});
|
|
2491
|
-
const buildVoidOpts = () => ({
|
|
2492
|
-
...renderOpts,
|
|
2493
|
-
lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight(),
|
|
2494
|
-
color: color ?? renderOpts.color
|
|
2495
|
-
});
|
|
2496
|
-
const buildMorphOpts = () => ({
|
|
2497
|
-
...renderOpts,
|
|
2498
|
-
lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight(),
|
|
2499
|
-
color: color ?? renderOpts.color
|
|
2500
|
-
});
|
|
2501
|
-
const buildFireOpts = () => ({
|
|
2502
|
-
...renderOpts,
|
|
2503
|
-
color: color ?? renderOpts.color
|
|
2504
|
-
});
|
|
2505
|
-
const buildDnaOpts = () => ({
|
|
2506
|
-
...renderOpts,
|
|
2507
|
-
color: color ?? renderOpts.color
|
|
2508
|
-
});
|
|
2509
|
-
const buildTerrainOpts = () => ({
|
|
2510
|
-
...renderOpts,
|
|
2511
|
-
color: color ?? renderOpts.color,
|
|
2512
|
-
lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight()
|
|
2513
|
-
});
|
|
2514
|
-
const buildCircuitOpts = () => ({
|
|
2515
|
-
...renderOpts,
|
|
2516
|
-
color: color ?? renderOpts.color,
|
|
2517
|
-
lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight()
|
|
2518
|
-
});
|
|
2519
|
-
const optsRef = { current: buildWaveOpts() };
|
|
2520
|
-
const rebuildOpts = () => {
|
|
2521
|
-
if (type === "rain") optsRef.current = buildRainOpts();
|
|
2522
|
-
else if (type === "stars") optsRef.current = buildStarsOpts();
|
|
2523
|
-
else if (type === "pulse") optsRef.current = buildPulseOpts();
|
|
2524
|
-
else if (type === "noise") optsRef.current = buildNoiseOpts();
|
|
2525
|
-
else if (type === "grid") optsRef.current = buildGridOpts();
|
|
2526
|
-
else if (type === "aurora") optsRef.current = buildAuroraOpts();
|
|
2527
|
-
else if (type === "silk") optsRef.current = buildSilkOpts();
|
|
2528
|
-
else if (type === "void") optsRef.current = buildVoidOpts();
|
|
2529
|
-
else if (type === "morph") optsRef.current = buildMorphOpts();
|
|
2530
|
-
else if (type === "fire") optsRef.current = buildFireOpts();
|
|
2531
|
-
else if (type === "dna") optsRef.current = buildDnaOpts();
|
|
2532
|
-
else if (type === "terrain") optsRef.current = buildTerrainOpts();
|
|
2533
|
-
else if (type === "circuit") optsRef.current = buildCircuitOpts();
|
|
2534
|
-
else optsRef.current = buildWaveOpts();
|
|
2535
|
-
};
|
|
2536
|
-
rebuildOpts();
|
|
2537
|
-
const onSchemeChange = () => {
|
|
2538
|
-
rebuildOpts();
|
|
2539
|
-
};
|
|
2540
|
-
if (colorScheme === "auto") mq.addEventListener("change", onSchemeChange);
|
|
2541
|
-
const resize = () => {
|
|
2542
|
-
const r = container.getBoundingClientRect();
|
|
2543
|
-
canvas.width = r.width * dpr;
|
|
2544
|
-
canvas.height = r.height * dpr;
|
|
2545
|
-
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
2546
|
-
};
|
|
2547
|
-
resize();
|
|
2548
|
-
const onMouseMove = (e) => {
|
|
2549
|
-
const r = container.getBoundingClientRect();
|
|
2550
|
-
mouse.x = (e.clientX - r.left) / r.width;
|
|
2551
|
-
mouse.y = (e.clientY - r.top) / r.height;
|
|
2552
|
-
};
|
|
2553
|
-
const ro = new ResizeObserver(resize);
|
|
2554
|
-
ro.observe(container);
|
|
2555
|
-
window.addEventListener("mousemove", onMouseMove);
|
|
2556
|
-
let time = 0;
|
|
2557
|
-
let raf = 0;
|
|
2558
|
-
const tick = () => {
|
|
2559
|
-
smoothMouse.x += (mouse.x - smoothMouse.x) * 0.07;
|
|
2560
|
-
smoothMouse.y += (mouse.y - smoothMouse.y) * 0.07;
|
|
2561
|
-
const r = container.getBoundingClientRect();
|
|
2562
|
-
if (type === "rain") {
|
|
2563
|
-
renderRainBackground(ctx, r.width, r.height, time, optsRef.current);
|
|
2564
|
-
} else if (type === "stars") {
|
|
2565
|
-
renderStarsBackground(ctx, r.width, r.height, time, smoothMouse, optsRef.current);
|
|
2566
|
-
} else if (type === "pulse") {
|
|
2567
|
-
renderPulseBackground(ctx, r.width, r.height, time, smoothMouse, optsRef.current);
|
|
2568
|
-
} else if (type === "noise") {
|
|
2569
|
-
renderNoiseBackground(ctx, r.width, r.height, time, smoothMouse, optsRef.current);
|
|
2570
|
-
} else if (type === "grid") {
|
|
2571
|
-
renderGridBackground(ctx, r.width, r.height, time, smoothMouse, optsRef.current);
|
|
2572
|
-
} else if (type === "aurora") {
|
|
2573
|
-
renderAuroraBackground(ctx, r.width, r.height, time, smoothMouse, optsRef.current);
|
|
2574
|
-
} else if (type === "silk") {
|
|
2575
|
-
renderSilkBackground(ctx, r.width, r.height, time, optsRef.current);
|
|
2576
|
-
} else if (type === "void") {
|
|
2577
|
-
renderVoidBackground(ctx, r.width, r.height, time, smoothMouse, optsRef.current);
|
|
2578
|
-
} else if (type === "morph") {
|
|
2579
|
-
renderMorphBackground(ctx, r.width, r.height, time, optsRef.current);
|
|
2580
|
-
} else if (type === "fire") {
|
|
2581
|
-
renderFireBackground(ctx, r.width, r.height, time, optsRef.current);
|
|
2582
|
-
} else if (type === "dna") {
|
|
2583
|
-
renderDnaBackground(ctx, r.width, r.height, time, optsRef.current);
|
|
2584
|
-
} else if (type === "terrain") {
|
|
2585
|
-
renderTerrainBackground(ctx, r.width, r.height, time, optsRef.current);
|
|
2586
|
-
} else if (type === "circuit") {
|
|
2587
|
-
renderCircuitBackground(ctx, r.width, r.height, time, optsRef.current);
|
|
2588
|
-
} else {
|
|
2589
|
-
renderWaveBackground(ctx, r.width, r.height, time, smoothMouse, optsRef.current);
|
|
2590
|
-
}
|
|
2591
|
-
time += 0.016;
|
|
2592
|
-
raf = requestAnimationFrame(tick);
|
|
2593
|
-
};
|
|
2594
|
-
raf = requestAnimationFrame(tick);
|
|
2595
|
-
return {
|
|
2596
|
-
destroy: () => {
|
|
2597
|
-
cancelAnimationFrame(raf);
|
|
2598
|
-
ro.disconnect();
|
|
2599
|
-
if (colorScheme === "auto") mq.removeEventListener("change", onSchemeChange);
|
|
2600
|
-
window.removeEventListener("mousemove", onMouseMove);
|
|
2601
|
-
canvas.remove();
|
|
2602
|
-
container.style.position = prevPosition;
|
|
2603
|
-
}
|
|
2604
|
-
};
|
|
2605
|
-
}
|
|
2606
|
-
var mountWaveBackground = asciiBackground;
|
|
2607
|
-
|
|
2608
|
-
// src/core/ascii-text.ts
|
|
2609
|
-
function asciiText(source, options = {}, targetWidth, targetHeight) {
|
|
2610
|
-
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
2611
|
-
const { frame, cols } = imageToAsciiFrame(source, opts, targetWidth, targetHeight);
|
|
2612
|
-
if (!frame.length || cols === 0) return "";
|
|
2613
|
-
const lines = [];
|
|
2614
|
-
for (const row of frame) {
|
|
2615
|
-
lines.push(row.map((cell) => cell.char).join(""));
|
|
2616
|
-
}
|
|
2617
|
-
return lines.join("\n");
|
|
2618
|
-
}
|
|
2619
|
-
function asciiTextAnsi(source, options = {}, targetWidth, targetHeight) {
|
|
2620
|
-
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
2621
|
-
const { frame, cols } = imageToAsciiFrame(source, opts, targetWidth, targetHeight);
|
|
2622
|
-
if (!frame.length || cols === 0) return "";
|
|
2623
|
-
const RESET = "\x1B[0m";
|
|
2624
|
-
const lines = [];
|
|
2625
|
-
for (const row of frame) {
|
|
2626
|
-
let line = "";
|
|
2627
|
-
for (const cell of row) {
|
|
2628
|
-
if (cell.char === " " || cell.a < 10) {
|
|
2629
|
-
line += " ";
|
|
2630
|
-
} else {
|
|
2631
|
-
line += `\x1B[38;2;${cell.r};${cell.g};${cell.b}m${cell.char}${RESET}`;
|
|
2632
|
-
}
|
|
2633
|
-
}
|
|
2634
|
-
lines.push(line);
|
|
2635
|
-
}
|
|
2636
|
-
return lines.join("\n");
|
|
2637
|
-
}
|
|
2638
|
-
|
|
2639
|
-
// src/core/text-frame.ts
|
|
2640
|
-
function buildTextFrame(text, cols, rows, color = "#505050", opacity = 100) {
|
|
2641
|
-
if (!text || cols <= 0 || rows <= 0) return [];
|
|
2642
|
-
const parsed = parseColor(color) ?? { r: 80, g: 80, b: 80 };
|
|
2643
|
-
const pattern = text;
|
|
2644
|
-
const len = pattern.length;
|
|
2645
|
-
return Array.from(
|
|
2646
|
-
{ length: rows },
|
|
2647
|
-
(_, row) => Array.from({ length: cols }, (_2, col) => ({
|
|
2648
|
-
char: pattern[(row * cols + col) % len],
|
|
2649
|
-
r: parsed.r,
|
|
2650
|
-
g: parsed.g,
|
|
2651
|
-
b: parsed.b,
|
|
2652
|
-
a: opacity
|
|
2653
|
-
}))
|
|
2654
|
-
);
|
|
2655
|
-
}
|
|
2656
|
-
function renderTextBackground(ctx, width, height, text, options = {}, hoverPos) {
|
|
2657
|
-
const {
|
|
2658
|
-
fontSize = 10,
|
|
2659
|
-
lineHeight = 1.6,
|
|
2660
|
-
color = "#505050",
|
|
2661
|
-
opacity = 100,
|
|
2662
|
-
hoverEffect = "spotlight",
|
|
2663
|
-
hoverStrength = 0.85,
|
|
2664
|
-
hoverRadius = 0.18,
|
|
2665
|
-
hoverColor = "#d4ff00"
|
|
2666
|
-
} = options;
|
|
2667
|
-
const cols = Math.max(1, Math.floor(width / fontSize));
|
|
2668
|
-
const rows = Math.max(1, Math.floor(height / (fontSize * lineHeight)));
|
|
2669
|
-
const frame = buildTextFrame(text, cols, rows, color, opacity);
|
|
2670
|
-
const renderOpts = {
|
|
2671
|
-
...DEFAULT_OPTIONS,
|
|
2672
|
-
hoverEffect,
|
|
2673
|
-
hoverStrength,
|
|
2674
|
-
hoverRadius,
|
|
2675
|
-
hoverColor
|
|
2676
|
-
};
|
|
2677
|
-
renderFrameToCanvas(ctx, frame, renderOpts, width, height, 0, hoverPos ?? null);
|
|
2678
|
-
}
|
|
2679
|
-
|
|
2680
|
-
// src/core/record.ts
|
|
2681
|
-
function captureSnapshot(canvas, { format = "png", quality = 0.92, scale = 1 } = {}) {
|
|
2682
|
-
return new Promise((resolve, reject) => {
|
|
2683
|
-
let src = canvas;
|
|
2684
|
-
if (scale !== 1) {
|
|
2685
|
-
const off = document.createElement("canvas");
|
|
2686
|
-
off.width = Math.round(canvas.width * scale);
|
|
2687
|
-
off.height = Math.round(canvas.height * scale);
|
|
2688
|
-
const offCtx = off.getContext("2d");
|
|
2689
|
-
if (!offCtx) {
|
|
2690
|
-
reject(new Error("captureSnapshot: could not get 2d context"));
|
|
2691
|
-
return;
|
|
2692
|
-
}
|
|
2693
|
-
offCtx.drawImage(canvas, 0, 0, off.width, off.height);
|
|
2694
|
-
src = off;
|
|
2695
|
-
}
|
|
2696
|
-
src.toBlob(
|
|
2697
|
-
(blob) => blob ? resolve(blob) : reject(new Error("captureSnapshot: toBlob returned null")),
|
|
2698
|
-
`image/${format}`,
|
|
2699
|
-
quality
|
|
2700
|
-
);
|
|
2701
|
-
});
|
|
2702
|
-
}
|
|
2703
|
-
async function snapshotAndDownload(canvas, options = {}) {
|
|
2704
|
-
const { filename = "asciify-snapshot", format = "png", ...snapOpts } = options;
|
|
2705
|
-
const blob = await captureSnapshot(canvas, { format, ...snapOpts });
|
|
2706
|
-
const ext = format === "jpeg" ? "jpg" : format;
|
|
2707
|
-
const a = document.createElement("a");
|
|
2708
|
-
a.href = URL.createObjectURL(blob);
|
|
2709
|
-
a.download = `${filename}.${ext}`;
|
|
2710
|
-
a.click();
|
|
2711
|
-
setTimeout(() => URL.revokeObjectURL(a.href), 1e4);
|
|
2712
|
-
}
|
|
2713
|
-
|
|
2714
|
-
// src/core/webcam.ts
|
|
2715
|
-
async function asciifyWebcam(canvas, {
|
|
2716
|
-
fontSize = 10,
|
|
2717
|
-
style = "classic",
|
|
2718
|
-
options = {},
|
|
2719
|
-
liveOptions,
|
|
2720
|
-
mirror = true,
|
|
2721
|
-
constraints = { facingMode: "user" },
|
|
2722
|
-
dpr: dprOverride
|
|
2723
|
-
} = {}) {
|
|
2724
|
-
if (!navigator.mediaDevices?.getUserMedia) {
|
|
2725
|
-
throw new Error("asciifyWebcam: getUserMedia is not supported in this browser.");
|
|
2726
|
-
}
|
|
2727
|
-
const stream = await navigator.mediaDevices.getUserMedia({ video: constraints });
|
|
2728
|
-
const video = document.createElement("video");
|
|
2729
|
-
video.srcObject = stream;
|
|
2730
|
-
video.muted = true;
|
|
2731
|
-
video.playsInline = true;
|
|
2732
|
-
await new Promise((resolve, reject) => {
|
|
2733
|
-
video.onloadedmetadata = () => resolve();
|
|
2734
|
-
video.onerror = () => reject(new Error("asciifyWebcam: video stream failed to load."));
|
|
2735
|
-
video.play().catch(reject);
|
|
2736
|
-
});
|
|
2737
|
-
const merged = {
|
|
2738
|
-
...DEFAULT_OPTIONS,
|
|
2739
|
-
...ART_STYLE_PRESETS[style],
|
|
2740
|
-
...options,
|
|
2741
|
-
fontSize
|
|
2742
|
-
};
|
|
2743
|
-
const ctx = canvas.getContext("2d");
|
|
2744
|
-
if (!ctx) throw new Error("asciifyWebcam: could not get 2d context from canvas.");
|
|
2745
|
-
const deviceRatio = dprOverride ?? (typeof window !== "undefined" ? window.devicePixelRatio : 1) ?? 1;
|
|
2746
|
-
if (deviceRatio !== 1) {
|
|
2747
|
-
ctx.scale(deviceRatio, deviceRatio);
|
|
2748
|
-
}
|
|
2749
|
-
let hoverPos = null;
|
|
2750
|
-
const smoothHover = { x: 0.5, y: 0.5, intensity: 0 };
|
|
2751
|
-
const onMouseMove = (e) => {
|
|
2752
|
-
const rect = canvas.getBoundingClientRect();
|
|
2753
|
-
hoverPos = {
|
|
2754
|
-
x: (e.clientX - rect.left) / rect.width,
|
|
2755
|
-
y: (e.clientY - rect.top) / rect.height
|
|
2756
|
-
};
|
|
2757
|
-
};
|
|
2758
|
-
const onMouseLeave = () => {
|
|
2759
|
-
hoverPos = null;
|
|
2760
|
-
};
|
|
2761
|
-
if (merged.hoverStrength > 0) {
|
|
2762
|
-
canvas.addEventListener("mousemove", onMouseMove);
|
|
2763
|
-
canvas.addEventListener("mouseleave", onMouseLeave);
|
|
2764
|
-
}
|
|
2765
|
-
let cancelled = false;
|
|
2766
|
-
let animId;
|
|
2767
|
-
const startTime = performance.now();
|
|
2768
|
-
const tick = (timestamp) => {
|
|
2769
|
-
if (cancelled) return;
|
|
2770
|
-
if (video.readyState >= video.HAVE_CURRENT_DATA) {
|
|
2771
|
-
const displayW = canvas.width / deviceRatio;
|
|
2772
|
-
const displayH = canvas.height / deviceRatio;
|
|
2773
|
-
const elapsed = (timestamp - startTime) / 1e3;
|
|
2774
|
-
const frameOptions = liveOptions ? { ...merged, ...liveOptions() } : merged;
|
|
2775
|
-
const wantsHover = frameOptions.hoverStrength > 0;
|
|
2776
|
-
if (wantsHover) {
|
|
2777
|
-
canvas.addEventListener("mousemove", onMouseMove);
|
|
2778
|
-
canvas.addEventListener("mouseleave", onMouseLeave);
|
|
2779
|
-
} else {
|
|
2780
|
-
canvas.removeEventListener("mousemove", onMouseMove);
|
|
2781
|
-
canvas.removeEventListener("mouseleave", onMouseLeave);
|
|
2782
|
-
}
|
|
2783
|
-
const { frame } = imageToAsciiFrame(video, frameOptions, displayW, displayH);
|
|
2784
|
-
if (hoverPos) {
|
|
2785
|
-
const dx = hoverPos.x - smoothHover.x;
|
|
2786
|
-
const dy = hoverPos.y - smoothHover.y;
|
|
2787
|
-
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
2788
|
-
const speed = Math.min(0.25, 0.06 + dist * 0.8);
|
|
2789
|
-
smoothHover.x += dx * speed;
|
|
2790
|
-
smoothHover.y += dy * speed;
|
|
2791
|
-
smoothHover.intensity += (1 - smoothHover.intensity) * 0.12;
|
|
2792
|
-
} else {
|
|
2793
|
-
smoothHover.intensity *= 0.965;
|
|
2794
|
-
if (smoothHover.intensity < 3e-3) smoothHover.intensity = 0;
|
|
2795
|
-
}
|
|
2796
|
-
const hoverArg = smoothHover.intensity > 3e-3 ? { x: smoothHover.x, y: smoothHover.y, intensity: smoothHover.intensity } : null;
|
|
2797
|
-
if (mirror) {
|
|
2798
|
-
ctx.save();
|
|
2799
|
-
ctx.scale(-1, 1);
|
|
2800
|
-
ctx.translate(-displayW, 0);
|
|
2801
|
-
renderFrameToCanvas(ctx, frame, frameOptions, displayW, displayH, elapsed, hoverArg);
|
|
2802
|
-
ctx.restore();
|
|
2803
|
-
} else {
|
|
2804
|
-
renderFrameToCanvas(ctx, frame, frameOptions, displayW, displayH, elapsed, hoverArg);
|
|
2805
|
-
}
|
|
2806
|
-
}
|
|
2807
|
-
animId = requestAnimationFrame(tick);
|
|
2808
|
-
};
|
|
2809
|
-
animId = requestAnimationFrame(tick);
|
|
2810
|
-
return () => {
|
|
2811
|
-
cancelled = true;
|
|
2812
|
-
cancelAnimationFrame(animId);
|
|
2813
|
-
canvas.removeEventListener("mousemove", onMouseMove);
|
|
2814
|
-
canvas.removeEventListener("mouseleave", onMouseLeave);
|
|
2815
|
-
stream.getTracks().forEach((t) => t.stop());
|
|
2816
|
-
video.srcObject = null;
|
|
2817
|
-
};
|
|
2818
|
-
}
|
|
2819
|
-
|
|
2820
|
-
exports.ART_STYLE_PRESETS = ART_STYLE_PRESETS;
|
|
2821
|
-
exports.BACKGROUND_TYPES = BACKGROUND_TYPES;
|
|
2822
|
-
exports.CHARSETS = CHARSETS;
|
|
2823
|
-
exports.CHARSET_SEQUENCES = CHARSET_SEQUENCES;
|
|
2824
|
-
exports.DEFAULT_OPTIONS = DEFAULT_OPTIONS;
|
|
2825
|
-
exports.HOVER_PRESETS = HOVER_PRESETS;
|
|
2826
|
-
exports.PALETTE_THEMES = PALETTE_THEMES;
|
|
2827
|
-
exports.asciiBackground = asciiBackground;
|
|
2828
|
-
exports.asciiText = asciiText;
|
|
2829
|
-
exports.asciiTextAnsi = asciiTextAnsi;
|
|
2830
|
-
exports.asciify = asciify;
|
|
2831
|
-
exports.asciifyGif = asciifyGif;
|
|
2832
|
-
exports.asciifyLiveVideo = asciifyLiveVideo;
|
|
2833
|
-
exports.asciifyVideo = asciifyVideo;
|
|
2834
|
-
exports.asciifyWebcam = asciifyWebcam;
|
|
2835
|
-
exports.buildTextFrame = buildTextFrame;
|
|
2836
|
-
exports.captureSnapshot = captureSnapshot;
|
|
2837
|
-
exports.gifToAsciiFrames = gifToAsciiFrames;
|
|
2838
|
-
exports.imageToAsciiFrame = imageToAsciiFrame;
|
|
2839
|
-
exports.mountWaveBackground = mountWaveBackground;
|
|
2840
|
-
exports.renderAuroraBackground = renderAuroraBackground;
|
|
2841
|
-
exports.renderCircuitBackground = renderCircuitBackground;
|
|
2842
|
-
exports.renderDnaBackground = renderDnaBackground;
|
|
2843
|
-
exports.renderFireBackground = renderFireBackground;
|
|
2844
|
-
exports.renderFrameToCanvas = renderFrameToCanvas;
|
|
2845
|
-
exports.renderGridBackground = renderGridBackground;
|
|
2846
|
-
exports.renderMorphBackground = renderMorphBackground;
|
|
2847
|
-
exports.renderNoiseBackground = renderNoiseBackground;
|
|
2848
|
-
exports.renderPulseBackground = renderPulseBackground;
|
|
2849
|
-
exports.renderRainBackground = renderRainBackground;
|
|
2850
|
-
exports.renderSilkBackground = renderSilkBackground;
|
|
2851
|
-
exports.renderStarsBackground = renderStarsBackground;
|
|
2852
|
-
exports.renderTerrainBackground = renderTerrainBackground;
|
|
2853
|
-
exports.renderTextBackground = renderTextBackground;
|
|
2854
|
-
exports.renderVoidBackground = renderVoidBackground;
|
|
2855
|
-
exports.renderWaveBackground = renderWaveBackground;
|
|
2856
|
-
exports.snapshotAndDownload = snapshotAndDownload;
|
|
2857
|
-
exports.videoToAsciiFrames = videoToAsciiFrames;
|
|
2858
|
-
//# sourceMappingURL=index.cjs.map
|
|
2859
|
-
//# sourceMappingURL=index.cjs.map
|
|
1
|
+
"use strict";var t=require("gifuct-js"),e={standard:" .:-=+*#%@",blocks:" ░▒▓█",minimal:" .:+",dense:" .'`^\",:;Il!i><~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$",binary:"01",dots:" ⠁⠃⠇⡇⣇⣧⣷⣿",letters:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",claudeCode:" ╔╗╚╝║═╠╣╦╩╬░▒▓█│─┌┐└┘├┤┬┴┼",box:" ▪◾◼■█",lines:" ˗‐–—―━",braille:" ⠁⠂⠃⠄⠅⠆⠇⠈⠉⠊⠋⠌⠍⠎⠏⠐⠑⠒⠓⠔⠕⠖⠗⠘⠙⠚⠛⠜⠝⠞⠟⠠⠡⠢⠣⠤⠥⠦⠧⠨⠩⠪⠫⠬⠭⠮⠯⠰⠱⠲⠳⠴⠵⠶⠷⠸⠹⠺⠻⠼⠽⠾⠿⡀⡁⡂⡃⡄⡅⡆⡇⣀⣁⣂⣃⣄⣅⣆⣇⣈⣉⣊⣋⣌⣍⣎⣏⣐⣑⣒⣓⣔⣕⣖⣗⣘⣙⣚⣛⣜⣝⣞⣟⣠⣡⣢⣣⣤⣥⣦⣧⣨⣩⣪⣫⣬⣭⣮⣯⣰⣱⣲⣳⣴⣵⣶⣷⣸⣹⣺⣻⣼⣽⣾⣿",katakana:" ヲァィゥェォャュョッアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン",musical:" ♩♪♫♬♭♮♯",emoji:" ⬛🟫🟥🟧🟨🟩🟦🟪⬜",circles:" .·:∘○◦°•∙",shadows:" ·∘◦○◎⊙●◉",starfield:" ˙·∘∗✦✧★◆●",geometric:" ·△▷◇◈◆▣■█",pipes:" ╶─┐└├┤┬┴┼╬▒▓█",waves:" ˜∼≈〰≋∿∾∭∫",shards:" ╱╲╳◤◥◣◢△▲◆◼█",smoke:" ·˙⁚⁖∶∷⋮⋰⋱∴∵"},o={cosmic:[e.starfield,e.circles,e.shadows],rain:[e.katakana,e.braille,e.binary],terminal:[e.pipes,e.claudeCode,e.standard],crystal:[e.shards,e.geometric,e.blocks],fluid:[e.waves,e.smoke,e.circles],pulse:[e.dense,e.standard,e.blocks],dream:[e.braille,e.shadows,e.smoke],angular:[e.geometric,e.shards,e.starfield]},n={classic:{renderMode:"ascii",charset:e.standard,colorMode:"grayscale"},particles:{renderMode:"dots",colorMode:"fullcolor",dotSizeRatio:.8},letters:{renderMode:"ascii",charset:e.letters,colorMode:"fullcolor"},claudeCode:{renderMode:"ascii",charset:e.claudeCode,colorMode:"accent",accentColor:"#f97316"},art:{renderMode:"ascii",charset:e.dense,colorMode:"fullcolor"},terminal:{renderMode:"ascii",charset:e.standard,colorMode:"matrix"},box:{renderMode:"ascii",charset:e.box,colorMode:"grayscale"},lines:{renderMode:"ascii",charset:e.lines,colorMode:"fullcolor"},braille:{renderMode:"ascii",charset:e.braille,colorMode:"fullcolor"},katakana:{renderMode:"ascii",charset:e.katakana,colorMode:"matrix"},musical:{renderMode:"ascii",charset:e.musical,colorMode:"accent",accentColor:"#e040fb"},emoji:{renderMode:"ascii",charset:e.emoji,colorMode:"fullcolor"},circles:{renderMode:"ascii",charset:e.circles,colorMode:"accent",accentColor:"#d4ff00"},shadows:{renderMode:"ascii",charset:e.shadows,colorMode:"accent",accentColor:"#50a0ff"},starfield:{renderMode:"ascii",charset:e.starfield,colorMode:"fullcolor"},geometric:{renderMode:"ascii",charset:e.geometric,colorMode:"grayscale"},pipes:{renderMode:"ascii",charset:e.pipes,colorMode:"accent",accentColor:"#00ff88"},waves:{renderMode:"ascii",charset:e.waves,colorMode:"fullcolor"},shards:{renderMode:"ascii",charset:e.shards,colorMode:"grayscale"},smoke:{renderMode:"ascii",charset:e.smoke,colorMode:"accent",accentColor:"#c850ff"}},r={fontSize:10,charSpacing:1,brightness:0,contrast:0,charset:e.standard,colorMode:"grayscale",accentColor:"#d4ff00",invert:!1,renderMode:"ascii",animationStyle:"none",animationSpeed:1,dotSizeRatio:.8,ditherStrength:0,charAspect:.55,normalize:!1,hoverStrength:0,hoverRadius:.2,hoverEffect:"spotlight",hoverColor:"#ffffff",artStyle:"classic",customText:"",chromaKey:null,chromaKeyTolerance:60};function a(t,e,o){let n=t+255*e;return n=259*(255*o+255)/(255*(259-255*o))*(n-128)+128,Math.max(0,Math.min(255,n))}function i(t,e,o){const n=o?1-t/255:t/255,r=[...e],a=Math.floor(n*(r.length-1));return r[Math.max(0,Math.min(r.length-1,a))]}function s(t,e,o,n,r,a){if((a?1-t/255:t/255)<.12)return" ";const i=[...e];return i[(n*r+o)%i.length]}var c=[[0,8,2,10],[12,4,14,6],[3,11,1,9],[15,7,13,5]];function l(t,e,o,n){if(n<=0)return t;const r=(c[o%4][e%4]/16-.5)*n*128;return Math.max(0,Math.min(255,t+r))}var h=new Array(256),f=new Array(256);for(let t=0;t<256;t++)h[t]=`rgb(${t},${t},${t})`,f[t]=`rgb(0,${t},0)`;function d(t,e,o,n,r,a=!1){switch(e){case"fullcolor":return`rgb(${t.r},${t.g},${t.b})`;case"matrix":return f[.299*t.r+.587*t.g+.114*t.b|0];case"accent":return`rgb(${o},${n},${r})`;default:{const e=.299*t.r+.587*t.g+.114*t.b|0;return h[e]}}}var g=[0,0,0];function u(t,e,o,n,r,a=!1){switch(e){case"fullcolor":g[0]=t.r,g[1]=t.g,g[2]=t.b;break;case"matrix":{const e=.299*t.r+.587*t.g+.114*t.b|0;g[0]=0,g[1]=e,g[2]=0;break}case"accent":g[0]=o,g[1]=n,g[2]=r;break;default:{const e=.299*t.r+.587*t.g+.114*t.b|0;g[0]=e,g[1]=e,g[2]=e;break}}return g}function m(t,e,o,n,r,a,i){if("none"===a)return 1;const s=r*i;switch(a){case"wave":return.3+.7*(.6*(.5*Math.sin(t/o*Math.PI*4+3*s)+.5)+.4*(.5*Math.sin(e/n*Math.PI*3+2*s)+.5));case"pulse":{const r=o/2,a=n/2,i=Math.sqrt((t-r)**2+(e-a)**2),c=Math.sqrt(r**2+a**2);return.2+.8*(.5*Math.sin(i/c*Math.PI*6-4*s)+.5)}case"rain":return.1+.9*(.5*Math.sin(e/n*Math.PI*8-5*s+.3*t)+.5)*(.3*Math.sin(t/o*Math.PI*2+s)+.7);case"breathe":{const o=.3*Math.sin(2*s)+.7,n=.1*Math.sin(.1*(t+e)+s);return Math.max(.1,Math.min(1,o+n))}case"sparkle":{const o=43758.5453*Math.sin(127.1*t+311.7*e+43758.5453*Math.floor(8*s)),n=o-Math.floor(o);return n>.7?1:.15+.4*n}case"glitch":{const t=Math.floor(e/(.05*n)),o=43758.5453*Math.sin(43.23*t+17.89*Math.floor(6*s));if(o-Math.floor(o)>.85){const e=.5*Math.sin(30*s+t)+.5;return e<.3?0:e}return 1}case"spiral":{const r=o/2,a=n/2,i=t-r,c=e-a,l=Math.atan2(c,i),h=Math.sqrt(i*i+c*c),f=Math.sqrt(r*r+a*a);return.15+.85*(.5*Math.sin(3*l+h/f*Math.PI*8-3*s)+.5)}case"typewriter":{const r=o*n,a=e*o+t-.5*s%1*r*1.3;if(a>0)return 0;const i=Math.max(0,1+a/(.15*r));return Math.min(1,i)}case"scatter":{const r=o/2,a=n/2,i=t-r,c=e-a,l=Math.sqrt(i*i+c*c)/Math.sqrt(r*r+a*a),h=.5*Math.sin(1.5*s)+.5;return l>h?Math.max(0,1-3*(l-h)):.7+.3*Math.sin(10*l-2*s)}case"waveField":default:return 1;case"ripple":{const r=o/2,a=n/2,i=t-r,c=e-a,l=Math.sqrt(i*i+c*c),h=Math.sqrt(r*r+a*a);return.1+.9*(.5*Math.sin(l/h*Math.PI*10-5*s)+.5)*(.4+.6*(1-Math.min(1,l/(1.1*h))))}case"melt":{const o=e/n*Math.PI;return.05+(.5*Math.sin(o-1.8*s+.15*t)+.5)*(1-.6*(Math.max(0,e/n-.1)/.9))*.95}case"orbit":{const r=o/2,a=n/2,i=t-r,c=e-a,l=Math.atan2(c,i),h=Math.sqrt(i*i+c*c)/Math.sqrt(r*r+a*a);return.1+.9*(.5*Math.sin(2*l+6*h-2.5*s)+.5)}case"cellular":{const o=Math.floor(4*s),n=(t,e)=>{const n=43758.5453*Math.sin(127.1*t+311.7*e+43758.5453*o);return n-Math.floor(n)>.38?1:0},r=n(t,e),a=n(t-1,e)+n(t+1,e)+n(t,e-1)+n(t,e+1)+n(t-1,e-1)+n(t+1,e-1)+n(t-1,e+1)+n(t+1,e+1);return 1===(1===r?2===a||3===a?1:0:3===a?1:0)?1:.05}}}var M={scale:1,offsetX:0,offsetY:0,glow:0,colorBlend:0,proximity:0};function p(t,e,o,n,r,a,i,s,c="spotlight",l=.5){const h=t-o,f=e-n,d=h*h+f*f,g=.08+.35*l+.04*a;if(d>=g*g)return M.scale=1,M.offsetX=0,M.offsetY=0,M.glow=0,M.colorBlend=0,M.proximity=0,M;const u=function(t){return t*t*(3-2*t)}(1-Math.sqrt(d)/g)*r;let m=1,p=0,b=0,x=0,v=0;switch(c){case"spotlight":{m=1+u*a*1.8;const t=Math.atan2(f,h),e=u*u*a*.6;p=Math.cos(t)*e*i,b=Math.sin(t)*e*s,x=u*a*.4,v=u*u*a*.25;break}case"magnify":m=1+u*a*2.5,x=u*a*.15;break;case"repel":{m=1+u*a*.3;const t=Math.atan2(f,h),e=u*u*a*1.2;p=Math.cos(t)*e*i,b=Math.sin(t)*e*s;break}case"glow":x=u*a*.8,v=u*a*.4;break;case"colorShift":m=1+u*a*.4,x=u*a*.2,v=u*a*.7;break;case"attract":{const t=Math.atan2(f,h),e=u*u*a*1;p=-Math.cos(t)*e*i,b=-Math.sin(t)*e*s,x=u*a*.3;break}case"shatter":{const t=Math.atan2(f,h),e=.5*Math.sin(43.7*h+29.3*f),o=u*a*1.4*(.7+.3*e);p=Math.cos(t+e)*o*i,b=Math.sin(t+e)*o*s,m=Math.max(.1,1-u*a*.6),x=u*a*.25;break}case"trail":v=u*a*.9,x=u*a*.6,m=1+u*a*.15}return M.scale=m,M.offsetX=p,M.offsetY=b,M.glow=x,M.colorBlend=v,M.proximity=u,M}function b(t){const e=t.match(/^#([0-9a-f]{3,8})$/i)?.[1];if(e){const t=e.length<=4?e.split("").map(t=>parseInt(t+t,16)):[parseInt(e.slice(0,2),16),parseInt(e.slice(2,4),16),parseInt(e.slice(4,6),16)];return{r:t[0],g:t[1],b:t[2]}}const o=t.match(/rgba?\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)/i);return o?{r:+o[1],g:+o[2],b:+o[3]}:null}function x(t){return t*t*t*(t*(6*t-15)+10)}function v(t,e,o){return t+(e-t)*o}function w(t,e){let o=127*t+311*e;return o^=o>>13,(o*(o*o*15731+789221)+1376312589&2147483647)/2147483647}function y(t,e){const o=Math.floor(t),n=Math.floor(e),r=e-n,a=x(t-o),i=x(r),s=w(o,n),c=w(o+1,n),l=w(o,n+1),h=w(o+1,n+1);return v(v(s,c,a),v(l,h,a),i)}function $(t,e){return(.5*y(t,e)+.25*y(2.1*t,2.1*e)+.125*y(4.3*t,4.3*e))/.875}function S(t,e,o,n,r={x:.5,y:.5},a={}){const{fontSize:i=13,charAspect:s=.62,lineHeightRatio:c=1.4,chars:l=" .:-=+*#%@",baseColor:h=null,accentColor:f,accentThreshold:d=.52,mouseInfluence:g=.55,mouseFalloff:u=2.8,speed:m=1,vortex:M=!0,sparkles:p=!0,breathe:b=!0,lightMode:x=!1}=a,v=f??(x?"#6b8700":"#d4ff00"),y=i*s,S=i*c,C=Math.ceil(e/y),E=Math.ceil(o/S),T=r.x,R=r.y;let k=212,A=255,I=0;{const t=v.replace("#","");6===t.length&&(k=parseInt(t.slice(0,2),16),A=parseInt(t.slice(2,4),16),I=parseInt(t.slice(4,6),16))}t.clearRect(0,0,e,o),t.font=`${i}px "JetBrains Mono", monospace`,t.textBaseline="top";const B=n*m,P=b?.12*(.5*Math.sin(.22*B)+.5):0;for(let e=0;e<E;e++)for(let o=0;o<C;o++){const n=o/C,r=e/E,a=.045,i=.08*B,s=.06,c=.45*((.5*Math.sin(.08*o+.05*e+.6*B)+.5+(.5*Math.sin(.03*o-.07*e+.4*B)+.5)+(.5*Math.sin(.05*o+.03*e+.8*B)+.5))/3)+.35*(.5*$(o*a+i,e*a*1.4-.7*i)+.5)+.2*(.5*Math.sin((o+.65*e)*s+1.1*B)+.5)+P,f=n-T,m=r-R,b=Math.sqrt(f*f+m*m);let v=0;if(M&&b<.35){const t=Math.atan2(m,f),e=Math.sin(4*t+2.2*B-14*b),o=Math.max(0,1-b/.35);v=e*o*o*.22}const z=c*(1-g)+(Math.max(0,1-b*u)+.5*v)*g+.15*v,F=Math.min(1,Math.max(0,z));let q=F;if(p&&F>.72){const t=Math.floor(8*B),n=w(7*o+3*t,11*e+t);n>.88&&(q=Math.min(1,F+4*(n-.88)))}const L=Math.floor(q*(l.length-1));if(" "===l[L])continue;const H=.015+.07*q;if(q>d){const e=Math.min(x?.9:.28,H*(x?14:2.8));t.fillStyle=`rgba(${k},${A},${I},${e})`}else t.fillStyle=h?h.replace("{a}",String(H)):x?`rgba(55,55,55,${7*H})`:`rgba(255,255,255,${H})`;t.fillText(l[L],o*y,e*S)}}function C(t){return"auto"!==t?t:"undefined"!=typeof window&&!window.matchMedia("(prefers-color-scheme: dark)").matches}function E(t){const e=t.match(/^#([0-9a-fA-F]{3,6})$/);if(e){let t=e[1];if(3===t.length&&(t=t[0]+t[0]+t[1]+t[1]+t[2]+t[2]),6===t.length)return t}const o=t.match(/rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/);return o?[o[1],o[2],o[3]].map(t=>parseInt(t).toString(16).padStart(2,"0")).join(""):null}function T(t){const e=t||"auto";if("auto"!==e)return e.replace("#","");if("undefined"!=typeof document){const t=getComputedStyle(document.documentElement);for(const e of["--accent-color","--color-accent","--accent","--color-primary","--primary","--brand-color"]){const o=E(t.getPropertyValue(e).trim());if(o)return o}const e=E(getComputedStyle(document.body).accentColor??"");if(e)return e}return"undefined"!=typeof window&&window.matchMedia("(prefers-color-scheme: dark)").matches?"faf9f7":"0d0d0d"}function R(t,e,o,n){const r=t instanceof HTMLVideoElement?t.videoWidth:t.width,c=t instanceof HTMLVideoElement?t.videoHeight:t.height;if(0===r||0===c)return{frame:[],cols:0,rows:0};const h=e.charAspect,f=e.fontSize*e.charSpacing,d=e.fontSize/h*e.charSpacing,g=o||r,u=n||c,m=Math.floor(g/f),M=Math.floor(u/d);if(m<=0||M<=0)return{frame:[],cols:0,rows:0};const p=Math.max(1,Math.min(Math.floor(2048/m),Math.floor(r/m))),b=Math.max(1,Math.min(Math.floor(2048/M),Math.floor(c/M))),x=m*p,v=M*b,{ctx:w}=function(t,e){const o=document.createElement("canvas");o.width=t,o.height=e;const n=o.getContext("2d",{willReadFrequently:!0});return{canvas:o,ctx:n}}(x,v);w.drawImage(t,0,0,x,v);const y=w.getImageData(0,0,x,v).data,$=e.chromaKey,S=null!=$&&!1!==$,E=!0===$,T="blue-screen"===$;let R=null,k=0;!S||E||T||(R=function(t){if("string"!=typeof t)return t;const e=document.createElement("canvas");e.width=1,e.height=1;const o=e.getContext("2d");o.fillStyle=t,o.fillRect(0,0,1,1);const n=o.getImageData(0,0,1,1).data;return{r:n[0],g:n[1],b:n[2]}}($),k=(e.chromaKeyTolerance??60)**2);let A=0,I=255;if(e.normalize){let t=255,e=0;for(let o=0;o<y.length;o+=4){if(S){const t=y[o],e=y[o+1],n=y[o+2];let r=!1;if(E)r=e>1.4*t&&e>1.4*n&&e>80;else if(T)r=n>1.4*t&&n>1.4*e&&n>80;else if(null!==R){const o=t-R.r,a=e-R.g,i=n-R.b;r=o*o+a*a+i*i<=k}if(r)continue}const n=.299*y[o]+.587*y[o+1]+.114*y[o+2];n<t&&(t=n),n>e&&(e=n)}A=t,I=e>t?e-t:255}const B=[],P=C(e.invert),z=S&&e.charset.replace(/ /g,"")||e.charset,F=p*b;for(let t=0;t<M;t++){const o=[];for(let n=0;n<m;n++){let r=0,c=0,h=0,f=0,d=0;for(let e=0;e<b;e++){const o=(t*b+e)*x;for(let t=0;t<p;t++){const e=4*(o+n*p+t),a=y[e],i=y[e+1],s=y[e+2],l=y[e+3];if(S){let t=!1;if(E)t=i>1.4*a&&i>1.4*s&&i>80;else if(T)t=s>1.4*a&&s>1.4*i&&s>80;else if(null!==R){const e=a-R.r,o=i-R.g,n=s-R.b;t=e*e+o*o+n*n<=k}if(t){d++;continue}}r+=a,c+=i,h+=s,f+=l}}if(S&&d>F/2){o.push({char:" ",r:0,g:0,b:0,a:0});continue}const g=F-d,u=g>0?r/g:0,M=g>0?c/g:0,v=g>0?h/g:0,w=g>0?f/g:0,$=.299*u+.587*M+.114*v,C=l(a(e.normalize?($-A)/I*255:$,e.brightness,e.contrast),n,t,e.ditherStrength),B=e.customText?s(C,e.customText,n,t,m,P):i(C,z,P);o.push({char:B,r:u,g:M,b:v,a:w,lum:C})}B.push(o)}return{frame:B,cols:m,rows:M}}async function k(t,e,o,n,r=12,a=10,i,s=0){const c=Math.min(t.duration-s,a),l=Math.ceil(c*r),h=[];let f=0,d=0;for(let a=0;a<l;a++){const g=s+a/r;if(g>s+c)break;t.currentTime=g,await new Promise(e=>{const o=()=>{t.removeEventListener("seeked",o),e()};t.addEventListener("seeked",o)});const u=R(t,e,o,n);h.push(u.frame),f=u.cols,d=u.rows,i?.((a+1)/l)}return{frames:h,cols:f,rows:d,fps:r}}async function A(e,o,n,r,a){const i=t.parseGIF(e),s=t.decompressFrames(i,!0);if(0===s.length)return{frames:[],cols:0,rows:0,fps:10};const c=s[0].dims.width,l=s[0].dims.height,h=i.lsd?.width||c,f=i.lsd?.height||l,d=document.createElement("canvas");d.width=h,d.height=f;const g=d.getContext("2d"),u=document.createElement("canvas");u.width=h,u.height=f;const m=u.getContext("2d"),M=[];let p=0,b=0,x=0;for(const t of s)x+=t.delay||100;const v=x/s.length,w=Math.round(Math.min(30,Math.max(5,1e3/v))),y=Math.min(s.length,300);for(let t=0;t<y;t++){const e=s[t],{dims:i,patch:c,disposalType:l}=e;3===l&&(m.clearRect(0,0,h,f),m.drawImage(d,0,0));const x=new ImageData(new Uint8ClampedArray(c.buffer),i.width,i.height),v=document.createElement("canvas");v.width=i.width,v.height=i.height;v.getContext("2d").putImageData(x,0,0),g.drawImage(v,i.left||0,i.top||0);const w=R(d,o,n,r);M.push(w.frame),p=w.cols,b=w.rows,2===l?g.clearRect(i.left||0,i.top||0,i.width,i.height):3===l&&(g.clearRect(0,0,h,f),g.drawImage(u,0,0)),a?.((t+1)/y)}return{frames:M,cols:p,rows:b,fps:w}}function I(t,e,o,n,r,a=0,s){if("waveField"===o.animationStyle){return void S(t,n,r,a,s?{x:s.x,y:s.y}:{x:.5,y:.5},{accentColor:`#${o.accentColor?T(o.accentColor):"d4ff00"}`,accentThreshold:.52,mouseInfluence:o.hoverStrength>0?Math.min(1,.3+.5*o.hoverStrength):.55,mouseFalloff:2.8,speed:o.animationSpeed,vortex:o.hoverStrength>0,sparkles:!0,breathe:!0})}const c=e.length;if(0===c)return;const l=e[0].length;t.clearRect(0,0,n,r);let h=!1;const f=Math.max(1,c>>2),g=Math.max(1,l>>2);t:for(let t=0;t<c;t+=f){const o=e[t];for(let t=0;t<l;t+=g)if(o[t].a<200){h=!0;break t}}if(!h){const e="undefined"!=typeof window&&window.matchMedia("(prefers-color-scheme: dark)").matches;t.fillStyle=e?"#0a0a0a":"#faf9f7",t.fillRect(0,0,n,r)}const M=n/l,b=r/c,x=c*l,v=s?.intensity??1,w=!("none"!==o.animationStyle&&x>5e3)&&!!(s&&o.hoverStrength>0&&v>.005),y=o.hoverColor||"#ffffff",$=parseInt(y.slice(1,3),16)||255,E=parseInt(y.slice(3,5),16)||255,R=parseInt(y.slice(5,7),16)||255,k=T(o.accentColor),A=parseInt(k.substring(0,2),16)||255,I=parseInt(k.substring(2,4),16)||255,B=parseInt(k.substring(4,6),16)||255,P=x>3e4?.25:x>15e3?.4:x>5e3?.6:1,z=o.hoverRadius*P;let F=0,q=l,L=0,H=c,W=0,j=0;if(w&&s){W=s.x,j=s.y;const t=.08+.35*z+.04*o.hoverStrength;F=Math.max(0,Math.floor((W-t)*l)-1),q=Math.min(l,Math.ceil((W+t)*l)+1),L=Math.max(0,Math.floor((j-t)*c)-1),H=Math.min(c,Math.ceil((j+t)*c)+1)}const O=o.animationStyle,U=o.animationSpeed,_="none"===O,D=o.hoverStrength,V=o.hoverEffect,N=z,Y=C(o.invert),X=o.colorMode,G=2*Math.PI,J=1/l,K=1/c;let Q="",Z=-1;if("dots"===o.renderMode){const n=.5*Math.min(M,b)*o.dotSizeRatio;for(let o=0;o<c;o++){const r=e[o];for(let e=0;e<l;e++){const i=r[e];if(i.a<10)continue;const s=.00392156863*(.299*i.r+.587*i.g+.114*i.b),h=Y?1-s:s;if(h<.02)continue;const f=_?1:m(e,o,l,c,a,O,U);let g=1,x=0,y=0,S=0,C=0;if(w&&e>=F&&e<=q&&o>=L&&o<=H){const t=p(e*J,o*K,W,j,v,D,M,b,V,N);g=t.scale,x=t.offsetX,y=t.offsetY,S=t.glow,C=t.colorBlend}const T=n*h*f*g;if(T<.3)continue;const k=e*M+.5*M+x,P=o*b+.5*b+y;let z;if(C>0){const t=u(i,X,A,I,B,Y);z=`rgb(${Math.min(255,t[0]+($-t[0])*C|0)},${Math.min(255,t[1]+(E-t[1])*C|0)},${Math.min(255,t[2]+(R-t[2])*C|0)})`}else z=d(i,X,A,I,B,Y);const tt=Math.min(1,.00392156863*i.a*f*(1+S));if(tt!==Z&&(t.globalAlpha=tt,Z=tt),z!==Q&&(t.fillStyle=z,Q=z),T<=3){const e=2*T;t.fillRect(k-T,P-T,e,e)}else t.beginPath(),t.arc(k,P,T,0,G),t.fill()}}}else{const n=.55,r=.9*Math.min(M/n,b),s=r<6;if(!s){const e="emoji"===o.artStyle;t.font=e?`${r}px "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla", sans-serif`:`${r}px "JetBrains Mono", monospace`,t.textAlign="center",t.textBaseline="middle"}let h=null;const f=o.charsetFrames,g=!!f?.length,x=g?f[Math.floor(Math.max(0,a)*(o.charsetFps??2))%f.length]:o.charset;if(s){h={};const t=[...x],e=t.length;for(let o=0;o<e;o++)h[t[o]]=Math.max(.1,(o+.3)/e)}const y=s?null:t.getTransform();for(let o=0;o<c;o++){const n=e[o];for(let e=0;e<l;e++){const r=n[e];if(r.a<10)continue;const f=g&&null!=r.lum?i(r.lum,x,Y):r.char;if(" "===f)continue;const S=_?1:m(e,o,l,c,a,O,U);if(S<.05)continue;let C=1,T=0,k=0,P=0,z=0;if(w&&e>=F&&e<=q&&o>=L&&o<=H){const t=p(e*J,o*K,W,j,v,D,M,b,V,N);C=t.scale,T=t.offsetX,k=t.offsetY,P=t.glow,z=t.colorBlend}const G=e*M+.5*M+T,tt=o*b+.5*b+k;let et;if(z>0){const t=u(r,X,A,I,B,Y);et=`rgb(${Math.min(255,t[0]+($-t[0])*z|0)},${Math.min(255,t[1]+(E-t[1])*z|0)},${Math.min(255,t[2]+(R-t[2])*z|0)})`}else et=d(r,X,A,I,B,Y);if(s){const e=h[f]??.5,o=Math.min(1,.00392156863*r.a*S*(1+P))*e;if(o<.02)continue;o!==Z&&(t.globalAlpha=o,Z=o),et!==Q&&(t.fillStyle=et,Q=et);const n=M*C,a=b*C;t.fillRect(G-.5*n,tt-.5*a,n,a)}else{const e=Math.min(1,.00392156863*r.a*S*(1+P));e!==Z&&(t.globalAlpha=e,Z=e),et!==Q&&(t.fillStyle=et,Q=et),1!==C?(t.translate(G,tt),t.scale(C,C),t.fillText(f,0,0),t.setTransform(y)):t.fillText(f,G,tt)}}}}t.globalAlpha=1}function B(t,e){const o=Math.min(1,2048/Math.max(t,e));return{renderW:Math.round(t*o),renderH:Math.round(e*o)}}function P(t,e,o,n,r){const{width:a,height:i}=e.getBoundingClientRect();if(!a||!i)return{renderW:0,renderH:0,dpr:1};let s,c,l=a,h=l/o;h>i&&(h=i,l=h*o),l=Math.round(l),h=Math.round(h),n&&r?({renderW:s,renderH:c}=B(n,r)):(s=l,c=h);const f="undefined"!=typeof window&&window.devicePixelRatio||1,d=8e6,g=s*f*c*f>d?Math.sqrt(d/(s*c)):f;return t.width=Math.round(s*g),t.height=Math.round(c*g),t.style.width=l+"px",t.style.height=h+"px",{renderW:s,renderH:c,dpr:g}}async function z(t,e,{fontSize:o,artStyle:a="classic",options:i={},fitTo:s,preExtract:c=!1,trim:l,onReady:h,onFrame:f}={}){const d=l?.start??0,g=l?.end,u=o??i.fontSize??10,m={...r,...n[a],...i,fontSize:u},M=e.getContext("2d");if(!M)throw new Error("asciifyVideo: could not get 2d context from canvas.");const p="string"==typeof s?document.querySelector(s):s instanceof HTMLElement?s:null;if(c){let o;"string"==typeof t?(o=document.createElement("video"),o.crossOrigin="anonymous",o.src=t,o.readyState<2&&await new Promise((e,n)=>{o.onloadeddata=()=>e(),o.onerror=()=>n(new Error(`asciifyVideo: failed to load "${t}"`))})):o=t,p&&P(e,p,o.videoWidth/o.videoHeight,o.videoWidth,o.videoHeight);const{renderW:n,renderH:r}=B(o.videoWidth,o.videoHeight),a="undefined"!=typeof window&&window.devicePixelRatio||1,i=8e6,s=n*a*r*a>i?Math.sqrt(i/(n*r)):a;e.width<Math.round(n*s)&&(e.width=Math.round(n*s),e.height=Math.round(r*s));const c=void 0!==g?g-d:10,{frames:l,fps:u}=await k(o,m,n,r,void 0,c,void 0,d);let b,x=!1,v=0,w=performance.now(),y=!0;const $=1e3/u,S=t=>{x||(t-w>=$&&(M.save(),M.setTransform(s,0,0,s,0,0),I(M,l[v],m,n,r),M.restore(),v=(v+1)%l.length,w=t,y&&(y=!1,h?.(o)),f?.()),b=requestAnimationFrame(S))};return b=requestAnimationFrame(S),()=>{x=!0,cancelAnimationFrame(b)}}let b,x=!1;"string"==typeof t?(b=document.createElement("video"),b.src=t,b.muted=!0,b.loop=!0,b.playsInline=!0,b.setAttribute("playsinline",""),Object.assign(b.style,{position:"fixed",top:"0",left:"0",width:"1px",height:"1px",opacity:"0",pointerEvents:"none",zIndex:"-1"}),document.body.appendChild(b),x=!0,await new Promise((e,o)=>{b.onloadedmetadata=()=>e(),b.onerror=()=>o(new Error(`asciifyVideo: failed to load "${t}"`))}),await b.play().catch(()=>{})):(b=t,b.paused&&await b.play().catch(()=>{})),d>0&&(b.currentTime=d,await new Promise(t=>{const e=()=>{b.removeEventListener("seeked",e),t()};b.addEventListener("seeked",e)}));let v=null;(d>0||void 0!==g)&&(v=()=>{(void 0!==g&&b.currentTime>=g||d>0&&b.currentTime<d)&&(b.currentTime=d)},b.addEventListener("timeupdate",v));let w=null;const{renderW:y,renderH:$}=B(b.videoWidth,b.videoHeight);if(p){const t=b.videoWidth/b.videoHeight,o=b.videoWidth,n=b.videoHeight,r=P(e,p,t,o,n),a=e.getContext("2d");a&&a.setTransform(r.dpr,0,0,r.dpr,0,0),w=new ResizeObserver(()=>{const r=P(e,p,t,o,n),a=e.getContext("2d");a&&a.setTransform(r.dpr,0,0,r.dpr,0,0)}),w.observe(p)}else{const t="undefined"!=typeof window&&window.devicePixelRatio||1,o=8e6,n=y*t*$*t>o?Math.sqrt(o/(y*$)):t;e.width<Math.round(y*n)&&(e.width=Math.round(y*n),e.height=Math.round($*n)),M.setTransform(n,0,0,n,0,0)}let S,C=!1,E=!0;const T=()=>{if(C)return;if(S=requestAnimationFrame(T),b.readyState<2||0===e.width||0===e.height)return;if(d>0&&b.currentTime<d)return;if(void 0!==g&&b.currentTime>=g)return;const{frame:t}=R(b,m,y,$);t.length>0&&(I(M,t,m,y,$,0,null),E&&(E=!1,h?.(b)),f?.())};return S=requestAnimationFrame(T),()=>{C=!0,cancelAnimationFrame(S),w?.disconnect(),v&&b.removeEventListener("timeupdate",v),x&&(b.pause(),b.src="",document.body.removeChild(b))}}function F(t,e,o,n,r={}){const{fontSize:a=13,chars:i="0123456789ABCDEF@#$&*+=/<>",accentColor:s,color:c,speed:l=1,density:h=.55,tailLength:f=14,lightMode:d=!1}=r,g=s??(d?"#6b8700":"#d4ff00"),u=.62*a,m=1.4*a,M=Math.ceil(e/u),p=Math.ceil(o/m);t.clearRect(0,0,e,o),t.font=`${a}px monospace`,t.textBaseline="top";let x=255,v=255,y=255;if(d&&(x=55,v=55,y=55),c){const t=b(c);t&&(x=t.r,v=t.g,y=t.b)}let $=212,S=255,C=0;const E=b(g);E&&($=E.r,S=E.g,C=E.b);const T=p+f;for(let e=0;e<M;e++){if(w(17*e,3)>h)continue;const o=(.5+1.5*w(31*e,7))*l,r=w(13*e,11)*T,a=Math.floor((n*o*7+r)%T),s=e*u;for(let o=0;o<=f;o++){const r=a-(f-o);if(r<0||r>=p)continue;const c=r*m,l=w(53*e+Math.floor(5*n+o),7*r),h=i[Math.floor(l*i.length)],g=o/f;if(o===f)t.fillStyle=`rgba(${$},${S},${C},${d?.7:.85})`;else{const e=d?.85*g:.15*g;t.fillStyle=`rgba(${x},${v},${y},${e})`}t.fillText(h,s,c)}}}function q(t,e,o,n,r={x:.5,y:.5},a={}){const{fontSize:i=14,chars:s=" . · * + ° ★",accentColor:c,color:l,speed:h=1,count:f=180,lightMode:d=!1}=a,g=c??(d?"#6b8700":"#d4ff00");t.clearRect(0,0,e,o),t.textBaseline="middle",t.textAlign="center";const u=e*(.2+.6*r.x),m=o*(.2+.6*r.y),M=.65*Math.sqrt(e*e+o*o);let p=255,x=255,v=255;if(d&&(p=55,x=55,v=55),l){const t=b(l);t&&(p=t.r,x=t.g,v=t.b)}let y=212,$=255,S=0;const C=b(g);C&&(y=C.r,$=C.g,S=C.b);const E=s.replace(/ /g,"").split("");if(0!==E.length){for(let r=0;r<f;r++){const a=w(17*r,3)*Math.PI*2,s=(n*(.15+.85*w(31*r,7))*h*.22+w(13*r,11))%1,c=u+Math.cos(a)*s*M,l=m+Math.sin(a)*s*M;if(c<-20||c>e+20||l<-20||l>o+20)continue;const f=Math.max(6,i*(.4+.9*s));t.font=`${f}px monospace`;const g=E[Math.min(E.length-1,Math.floor(s*E.length))],b=s>.72,C=d?.85*s:.2*s;t.fillStyle=b?`rgba(${y},${$},${S},${Math.min(d?.92:.32,2.2*C)})`:`rgba(${p},${x},${v},${C})`,t.fillText(g,c,l)}t.textAlign="left"}}function L(t,e,o,n,r={x:.5,y:.5},a={}){const{fontSize:i=14,chars:s=". · ○ ◎ ●",accentColor:c,color:l,rings:h=5,speed:f=1,sharpness:d=4,lightMode:g=!1}=a,u=c??(g?"#007a5e":"#00ffcc");t.clearRect(0,0,e,o),t.textBaseline="middle",t.textAlign="center";const m=e*r.x,M=o*r.y,p=1.6*Math.sqrt(m*m+M*M)+.2*Math.sqrt(e*e+o*o);let x=255,v=255,w=255;if(g&&(x=55,v=55,w=55),l){const t=b(l);t&&(x=t.r,v=t.g,w=t.b)}let y=0,$=255,S=204;const C=b(u);C&&(y=C.r,$=C.g,S=C.b);const E=s.replace(/ /g,"").split("");if(0===E.length)return;const T=Math.ceil(e/i),R=Math.ceil(o/i);for(let e=0;e<R;e++)for(let o=0;o<T;o++){const r=o*i+.5*i,a=e*i+.5*i,s=r-m,c=a-M,l=Math.sqrt(s*s+c*c)/p;let u=0;for(let t=0;t<h;t++){const e=(n*f*.38+t/h)%1,o=Math.abs(l-e),r=Math.max(0,1-o*p/(i*(12-d)));u+=Math.cos(r*Math.PI*.5)*r}if(u=Math.min(1,u),u<.02)continue;const b=u>.6;t.font=`${i}px monospace`;const C=Math.floor(u*(E.length-1)),T=E[Math.min(C,E.length-1)],R=g?.88*u:.22*u;t.fillStyle=b?`rgba(${y},${$},${S},${Math.min(g?.95:.4,.55*u)})`:`rgba(${x},${v},${w},${R})`,t.fillText(T,r,a)}t.textAlign="left"}function H(t,e,o,n,r={x:.5,y:.5},a={}){const{fontSize:i=14,chars:s=" .·:;=+*#%@░▒▓",accentColor:c,color:l,octaves:h=4,speed:f=1,scale:d=1,accentThreshold:g=.78,mouseWarp:u=.3,lightMode:m=!1}=a,M=c??(m?"#6b8700":"#d4ff00"),p=.62*i,x=1.4*i,v=Math.ceil(e/p),w=Math.ceil(o/x);t.clearRect(0,0,e,o),t.font=`${i}px monospace`,t.textBaseline="top";let $=255,S=255,C=255;if(m&&($=55,S=55,C=55),l){const t=b(l);t&&($=t.r,S=t.g,C=t.b)}let E=212,T=255,R=0;const k=b(M);k&&(E=k.r,T=k.g,R=k.b);const A=.035*d,I=n*f,B=Math.min(6,Math.max(1,h)),P=(t,e)=>{let o=0,n=.5,r=1,a=0;for(let i=0;i<B;i++)o+=y(t*r,e*r)*n,a+=n,n*=.5,r*=2.1;return o/a};for(let e=0;e<w;e++)for(let o=0;o<v;o++){const n=o*A+.06*I,a=e*A*1.3-.04*I,i=o/v-r.x,c=e/w-r.y,l=Math.sqrt(i*i+c*c),h=u>0?.12*Math.max(0,1-l/u):0,f=.5*P(n+h*Math.sin(1.3*I+8*c),a+h*Math.cos(.9*I+8*i))+.5;if(f<.12)continue;const d=s[Math.floor(f*(s.length-1))];if(" "===d)continue;const M=f>g,b=m?.82*f:.13*f;t.fillStyle=M?`rgba(${E},${T},${R},${m?.92:.28})`:`rgba(${$},${S},${C},${b})`,t.fillText(d,o*p,e*x)}}function W(t,e,o,n,r={x:.5,y:.5},a={}){const{fontSize:i=12,chars:s="·-=+|/",accentColor:c,color:l,bands:h=3,speed:f=1,bandWidth:d=.12,glitch:g=!0,lightMode:u=!1}=a,m=c??(u?"#6b8700":"#d4ff00"),M=.62*i,p=1.4*i,x=Math.ceil(e/M),v=Math.ceil(o/p);t.clearRect(0,0,e,o),t.font=`${i}px monospace`,t.textBaseline="top";let y=255,$=255,S=255;if(u&&(y=55,$=55,S=55),l){const t=b(l);t&&(y=t.r,$=t.g,S=t.b)}let C=212,E=255,T=0;const R=b(m);R&&(C=R.r,E=R.g,T=R.b);const k=n*f;for(let e=0;e<v;e++)for(let o=0;o<x;o++){const n=e/v,a=((n*h-.5*k)%1+1)%1,i=Math.max(0,1-a/d),c=.35*(.5*w(3*o,7*e)+.5);let l=0;if(g){const t=o/x-r.x,a=n-r.y,i=Math.sqrt(t*t+a*a);if(i<.18){const t=w(11*o+Math.floor(12*k),5*e);l=Math.max(0,1-i/.18)*(t>.5?t-.3:0)}}const f=Math.min(1,c+.8*i+.6*l);if(f<.04)continue;const m=s[Math.floor(f*(s.length-1))],b=i>.55,R=u?.82*f:.12*f;t.fillStyle=b?`rgba(${C},${E},${T},${u?.92:.28})`:`rgba(${y},${$},${S},${R})`,t.fillText(m,o*M,e*p)}}function j(t,e,o,n,r={x:.5,y:.5},a={}){const{fontSize:i=14,chars:s=" ·∙•:;+=≡≣#@",color:c,accentColor:l,speed:h=1,layers:f=5,softness:d=1.2,mouseRipple:g=.2,lightMode:u=!1}=a,m=l??(u?"#6b8700":"#d4ff00"),M=.62*i,p=1.4*i,x=Math.ceil(e/M),v=Math.ceil(o/p);t.clearRect(0,0,e,o),t.font=`${i}px monospace`,t.textBaseline="top";let y=255,$=255,S=255;if(u&&(y=55,$=55,S=55),c){const t=b(c);t&&(y=t.r,$=t.g,S=t.b)}let C=212,E=255,T=0;const R=b(m);R&&(C=R.r,E=R.g,T=R.b);const k=n*h,A=[];for(let t=0;t<f;t++){const e=w(17*t,31*t+7),o=w(23*t+5,11*t);A.push({fx:.8+2.2*e,fy:1.2+1.8*o,phase:e*Math.PI*4,dt:(.3+.5*w(7*t,13*t+3))*(t%2==0?1:-1),amp:.55+.45*w(29*t,3*t)})}for(let e=0;e<v;e++){const o=e/v;for(let n=0;n<x;n++){const a=n/x,i=a-r.x,c=o-r.y,l=Math.sqrt(i*i+c*c),h=g*Math.exp(-l*l/.06),m=a+i*h,b=o+c*h;let v=0,w=0;for(let t=0;t<f;t++){const{fx:e,fy:o,phase:n,dt:r,amp:a}=A[t];v+=Math.sin(m*e*Math.PI*2+k*r+n)*Math.cos(b*o*Math.PI*2+k*r*.7+1.3*n)*a,w+=a}const R=v/w,I=.5+.5*Math.tanh(R*d*2.2);if(I<.12)continue;const B=(I-.12)/.88,P=s[Math.min(s.length-1,Math.floor(B*s.length))],z=I>.82,F=u?.82*I:.14*I;t.fillStyle=z?`rgba(${C},${E},${T},${u?.92:.32})`:`rgba(${y},${$},${S},${F})`,t.fillText(P,n*M,e*p)}}}function O(t,e,o,n,r={}){const{fontSize:a=13,color:i,accentColor:s,speed:c=.4,layers:l=4,turbulence:h=.8,lightMode:f=!1}=r,d=s??(f?"#6b8700":"#d4ff00"),g=.62*a,u=1.4*a,m=Math.ceil(e/g),M=Math.ceil(o/u);t.clearRect(0,0,e,o),t.font=`${a}px monospace`,t.textBaseline="top";let p=255,x=255,v=255;if(f&&(p=55,x=55,v=55),i){const t=b(i);t&&(p=t.r,x=t.g,v=t.b)}let y=212,$=255,S=0;const C=b(d);C&&(y=C.r,$=C.g,S=C.b);const E=n*c,T=["─","─","╌","·","╌","─","─","╌","·"];for(let e=0;e<M;e++){const o=e/M;for(let n=0;n<m;n++){const r=n/m;let a=0,i=0;for(let t=0;t<l;t++){const e=w(13*t,7*t+3),n=1.1+2.4*e,s=.9+2*w(29*t,11*t+1),c=e*Math.PI*6,l=(.2+.5*w(41*t,17*t))*(t%2==0?1:-1.3),f=Math.sin(r*n*Math.PI*2+E*l+c),d=Math.cos(o*s*Math.PI*2+E*l*.6+1.7*c),g=Math.sin(r*s*Math.PI*h+o*n*Math.PI*h+E*l*.4);a+=Math.atan2(d+.3*g,f),i+=.5*(f*d+1)}const s=a/l,c=Math.min(1,i/l);if(c<.1)continue;const d=(s+Math.PI)/(2*Math.PI),M=T[Math.floor(d*T.length)%T.length],b=c>.8,C=f?.8*c:.13*c;t.fillStyle=b?`rgba(${y},${$},${S},${f?.9:.26})`:`rgba(${p},${x},${v},${C})`,t.fillText(M,n*g,e*u)}}}function U(t,e,o,n,r={x:.5,y:.5},a={}){const{fontSize:i=13,chars:s=" ·:;=+*#%@",color:c,accentColor:l,speed:h=1,radius:f=.38,swirl:d=3,lightMode:g=!1}=a,u=l??(g?"#6b8700":"#d4ff00"),m=.62*i,M=1.4*i,p=Math.ceil(e/m),x=Math.ceil(o/M),v=e/o;t.clearRect(0,0,e,o),t.font=`${i}px monospace`,t.textBaseline="top";let y=255,$=255,S=255;if(g&&(y=55,$=55,S=55),c){const t=b(c);t&&(y=t.r,$=t.g,S=t.b)}let C=212,E=255,T=0;const R=b(u);R&&(C=R.r,E=R.g,T=R.b);const k=n*h;for(let e=0;e<x;e++){const o=e/x;for(let n=0;n<p;n++){const a=(n/p-r.x)*v,i=o-r.y,c=Math.sqrt(a*a+i*i)/f;if(c>1){const o=w(3*n,7*e)*Math.max(0,1-3*(c-1));if(o<.62)continue;const r=o*(g?.28:.04);t.fillStyle=`rgba(${y},${$},${S},${r})`,t.fillText(s[1],n*m,e*M);continue}const l=Math.max(0,1-Math.abs(c-(.15+.12*Math.sin(1.1*k)))/.07),h=Math.pow(1-c,2.2),d=Math.min(1,h+.6*l);if(d<.06)continue;const u=Math.floor(d*(s.length-1)),b=s[Math.min(s.length-1,u)],x=l>.35||c<.08,R=g?.85*d:.18*d;t.fillStyle=x?`rgba(${C},${E},${T},${g?.95:.38})`:`rgba(${y},${$},${S},${R})`,t.fillText(b,n*m,e*M)}}}function _(t,e,o,n,r={}){const{fontSize:a=14,chars:i=" ·∙•:-=+*#",color:s,accentColor:c,speed:l=.5,harmonics:h=3,lightMode:f=!1}=r,d=c??(f?"#6b8700":"#d4ff00"),g=.62*a,u=1.4*a,m=Math.ceil(e/g),M=Math.ceil(o/u);t.clearRect(0,0,e,o),t.font=`${a}px monospace`,t.textBaseline="top";let p=255,x=255,v=255;if(f&&(p=55,x=55,v=55),s){const t=b(s);t&&(p=t.r,x=t.g,v=t.b)}let y=212,$=255,S=0;const C=b(d);C&&(y=C.r,$=C.g,S=C.b);const E=n*l,T=Array.from({length:h},(t,e)=>1/(e+1)).reduce((t,e)=>t+e,0);for(let e=0;e<M;e++)for(let o=0;o<m;o++){let n=0;for(let t=0;t<h;t++){const r=.18+1.4*w(o*(t+3)+7,e*(t+5)+11),a=w(o*(t+7),e*(t+9)+3)*Math.PI*2,i=1/(t+1);n+=Math.sin(E*r+a)*i}const r=.5*(n/T+1);if(r<.28)continue;const a=(r-.28)/.72,s=i[Math.min(i.length-1,Math.floor(a*i.length))],c=r>.88,l=f?.82*a:.13*a;t.fillStyle=c?`rgba(${y},${$},${S},${f?.92:.28})`:`rgba(${p},${x},${v},${l})`,t.fillText(s,o*g,e*u)}}function D(t,e,o,n,r={}){const{fontSize:a=13,chars:i=" .,:;i+xX#&@",color:s="#ff4500",hotColor:c="#ffe066",intensity:l=.85,wind:h=0,speed:f=1,lightMode:d=!1}=r,g=.62*a,u=1.4*a,m=Math.ceil(e/g),M=Math.ceil(o/u),p=m*M,x="__fire_heat__",v=t.canvas;let w=v[x];w&&w.length===p||(w=new Float32Array(p),v[x]=w);const y=.18*(.016*f),$=h*f*.8,S=M-1,C=n*f;for(let t=0;t<m;t++){const e=(.6*(.5*Math.sin(.31*t+4.1*C)+.5)+.4*(.5*Math.sin(.73*t-2.7*C)+.5))*l;w[S*m+t]=Math.min(1,e+.15*Math.random()*l),S>0&&(w[(S-1)*m+t]=Math.min(1,.85*e+.1*Math.random()*l))}const E=new Float32Array(p);for(let t=0;t<M-2;t++)for(let e=0;e<m;e++){const o=.4*w[(t+1)*m+e]+.25*w[(t+2)*m+Math.max(0,Math.min(m-1,e+Math.round($)))]+.175*w[(t+1)*m+Math.max(0,e-1)]+.175*w[(t+1)*m+Math.min(m-1,e+1)];E[t*m+e]=Math.max(0,o-y-.02*Math.random()*f)}for(let t=0;t<m;t++)E[(M-1)*m+t]=w[(M-1)*m+t],M>1&&(E[(M-2)*m+t]=w[(M-2)*m+t]);v[x]=E;const T=b(s)??{r:255,g:69,b:0},R=b(c)??{r:255,g:224,b:102};t.clearRect(0,0,e,o),t.font=`${a}px "JetBrains Mono", monospace`,t.textBaseline="top";for(let e=0;e<M;e++)for(let o=0;o<m;o++){const n=E[e*m+o];if(n<.03)continue;const r=i[Math.min(i.length-1,Math.floor(n*i.length))];if(" "===r)continue;const a=Math.min(1,1.2*n),s=T.r+(R.r-T.r)*a|0,c=T.g+(R.g-T.g)*a|0,l=T.b+(R.b-T.b)*a|0,h=d?1-.3*n:Math.min(1,n+.15);t.globalAlpha=h,t.fillStyle=`rgb(${s},${c},${l})`,t.fillText(r,o*g,e*u)}t.globalAlpha=1}function V(t,e,o,n,r={}){const{fontSize:a=13,baseChars:i="ATCG",bridgeChars:s="-=≡",color:c="#00e5ff",color2:l="#ff4081",bridgeColor:h="#88ffcc",speed:f=1,helixCount:d,lightMode:g=!1}=r,u=.62*a,m=1.4*a,M=Math.ceil(e/u),p=Math.ceil(o/m),x=d??Math.max(1,Math.floor(e/80)),v=M/x,y=b(c)??{r:0,g:229,b:255},$=b(l)??{r:255,g:64,b:129},S=b(h)??{r:136,g:255,b:204};t.clearRect(0,0,e,o),t.font=`${a}px "JetBrains Mono", monospace`,t.textBaseline="top";const C=n*f,E=.35*v;for(let e=0;e<x;e++){const o=v*(e+.5);for(let n=0;n<p;n++){const r=n/p*Math.PI*6-1.8*C,a=o+Math.sin(r)*E,c=o+Math.sin(r+Math.PI)*E,l=Math.round(a),h=Math.round(c);if(l<0||l>=M)continue;const f=w(31*e+7*n,3),d=i[Math.floor(f*i.length)],g=.5*(Math.sin(r)+1),b=.5*(Math.sin(r+Math.PI)+1);if(t.globalAlpha=.35+.65*g,t.fillStyle=`rgb(${y.r},${y.g},${y.b})`,t.fillText(d,l*u,n*m),h>=0&&h<M){const o=w(53*e+11*n,7),r=i[Math.floor(o*i.length)];t.globalAlpha=.35+.65*b,t.fillStyle=`rgb(${$.r},${$.g},${$.b})`,t.fillText(r,h*u,n*m)}if(n%3===0){const o=Math.min(l,h),r=Math.max(l,h);if(r-o>1){const a=w(17*n+43*e,5),i=s[Math.floor(a*s.length)],c=.25*(g+b)+.2;t.globalAlpha=c,t.fillStyle=`rgb(${S.r},${S.g},${S.b})`;for(let e=o+1;e<r;e++)t.fillText(i,e*u,n*m)}}}}t.globalAlpha=1}function N(t,e,o,n,r={}){const{fontSize:a=13,chars:i=" .,:;+*#@",color:s="#4caf50",skyColor:c="#1a237e",peakColor:l="#e0e0e0",speed:h=1,roughness:f=.55,heightScale:d=.55,stars:g=!0,lightMode:u=!1}=r,m=.62*a,M=1.4*a,p=Math.ceil(e/m),x=Math.ceil(o/M),v=b(s)??{r:76,g:175,b:80},y=b(c)??{r:26,g:35,b:126},S=b(l)??{r:224,g:224,b:224};t.clearRect(0,0,e,o),t.font=`${a}px "JetBrains Mono", monospace`,t.textBaseline="top";const C=n*h*.4,E=new Array(p);for(let t=0;t<p;t++){const e=(.5*$((t/p+C)*f*3,.5)+.5)*d;E[t]=Math.floor(e*x)}for(let e=0;e<x;e++)for(let o=0;o<p;o++){const r=x-1-E[o],a=e===r;if(!(e>=r)){if(g){const r=w(7*o+Math.floor(.3*C),13*e);if(r>.97){const a=.3*Math.sin(2*n+100*r)+.7;t.globalAlpha=.5*a,t.fillStyle=`rgb(${y.r+60},${y.g+60},${y.b+80})`,t.fillText("·",o*m,e*M)}}continue}const s=(e-r)/Math.max(1,E[o]),c=i[Math.min(i.length-1,Math.floor(s*i.length))];if(" "===c&&!a)continue;const l=a?1:Math.max(0,1-4*s),h=v.r+(S.r-v.r)*l|0,f=v.g+(S.g-v.g)*l|0,d=v.b+(S.b-v.b)*l|0;t.globalAlpha=.5+.5*s,t.fillStyle=`rgb(${h},${f},${d})`,t.fillText(a?i[i.length-1]:c,o*m,e*M)}t.globalAlpha=1}var Y=1,X=2,G=4,J=8,K={3:"─",12:"│",9:"┌",10:"┐",5:"└",6:"┘",11:"┬",7:"┴",13:"├",14:"┤",15:"┼",[Y]:"╶",[X]:"╴",[G]:"╵",[J]:"╷"};function Q(t,e,o,n,r={}){const{fontSize:a=13,pulseColor:i="#ffffff",color:s="#00ff88",density:c=.38,speed:l=1,lightMode:h=!1}=r,f=.62*a,d=1.4*a,g=Math.ceil(e/f),u=Math.ceil(o/d),m=b(s)??{r:0,g:255,b:136},M=b(i)??{r:255,g:255,b:255},p=(t,e)=>{if(w(17*t+1,7*e+2)>c)return 0;let o=0;return t+1<g&&w(17*t+1,7*e+2)>.15&&(o|=1),t-1>=0&&w(17*(t-1)+1,7*e+2)>.15&&(o|=2),e+1<u&&w(17*t+1,7*(e+1)+2)>.15&&(o|=8),e-1>=0&&w(17*t+1,7*(e-1)+2)>.15&&(o|=4),o};t.clearRect(0,0,e,o),t.font=`${a}px "JetBrains Mono", monospace`,t.textBaseline="top";const x=n*l;for(let e=0;e<u;e++)for(let o=0;o<g;o++){const n=p(o,e);if(0===n)continue;const r=K[n]??"·",a=(8*x+40*w(o,23*e))%g,i=(6*x+40*w(17*e,11*o))%u,s=(1&n||2&n)&&Math.abs(o-a)<1.5,c=(4&n||8&n)&&Math.abs(e-i)<1.5,l=s||c,h=.25+.35*w(3*o,5*e);l?(t.globalAlpha=.95,t.fillStyle=`rgb(${M.r},${M.g},${M.b})`):(t.globalAlpha=h,t.fillStyle=`rgb(${m.r},${m.g},${m.b})`),t.fillText(r,o*f,e*d)}t.globalAlpha=1}function Z(t,e={}){const{type:o="wave",opacity:n=.2,className:r,zIndex:a=0,colorScheme:i="auto",color:s,...c}=e,l="string"==typeof t?document.querySelector(t):t;if(!l)return console.warn("[asciify] asciiBackground: target not found",t),{destroy:()=>{}};const h=l.style.position;"static"===getComputedStyle(l).position&&(l.style.position="relative");const f=document.createElement("canvas");f.style.cssText=["position:absolute","inset:0","width:100%","height:100%",`opacity:${n}`,"pointer-events:none",`z-index:${a}`].join(";"),r&&(f.className=r),l.prepend(f);const d=f.getContext("2d"),g=window.devicePixelRatio||1,u={x:.5,y:.5},m={x:.5,y:.5},M=window.matchMedia("(prefers-color-scheme: light)"),p=()=>"light"===i||"dark"!==i&&M.matches,b=s?function(t){const e=t.match(/^#([0-9a-f]{3,8})$/i)?.[1];if(e){const t=e.length<=4?e.split("").map(t=>parseInt(t+t,16)):[parseInt(e.slice(0,2),16),parseInt(e.slice(2,4),16),parseInt(e.slice(4,6),16)];return{r:t[0],g:t[1],b:t[2]}}const o=t.match(/rgba?\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)/i);return o?{r:+o[1],g:+o[2],b:+o[3]}:null}(s):null,x=()=>({...c,lightMode:void 0!==c.lightMode?c.lightMode:p(),baseColor:b?`rgba(${b.r},${b.g},${b.b},{a})`:c.baseColor}),v={current:x()},w=()=>{v.current="rain"===o||"stars"===o||"pulse"===o||"noise"===o||"grid"===o||"aurora"===o||"silk"===o||"void"===o||"morph"===o?{...c,lightMode:void 0!==c.lightMode?c.lightMode:p(),color:s??c.color}:"fire"===o||"dna"===o?{...c,color:s??c.color}:"terrain"===o||"circuit"===o?{...c,color:s??c.color,lightMode:void 0!==c.lightMode?c.lightMode:p()}:x()};w();const y=()=>{w()};"auto"===i&&M.addEventListener("change",y);const $=()=>{const t=l.getBoundingClientRect();f.width=t.width*g,f.height=t.height*g,d.setTransform(g,0,0,g,0,0)};$();const C=t=>{const e=l.getBoundingClientRect();u.x=(t.clientX-e.left)/e.width,u.y=(t.clientY-e.top)/e.height},E=new ResizeObserver($);E.observe(l),window.addEventListener("mousemove",C);let T=0,R=0;const k=()=>{m.x+=.07*(u.x-m.x),m.y+=.07*(u.y-m.y);const t=l.getBoundingClientRect();"rain"===o?F(d,t.width,t.height,T,v.current):"stars"===o?q(d,t.width,t.height,T,m,v.current):"pulse"===o?L(d,t.width,t.height,T,m,v.current):"noise"===o?H(d,t.width,t.height,T,m,v.current):"grid"===o?W(d,t.width,t.height,T,m,v.current):"aurora"===o?j(d,t.width,t.height,T,m,v.current):"silk"===o?O(d,t.width,t.height,T,v.current):"void"===o?U(d,t.width,t.height,T,m,v.current):"morph"===o?_(d,t.width,t.height,T,v.current):"fire"===o?D(d,t.width,t.height,T,v.current):"dna"===o?V(d,t.width,t.height,T,v.current):"terrain"===o?N(d,t.width,t.height,T,v.current):"circuit"===o?Q(d,t.width,t.height,T,v.current):S(d,t.width,t.height,T,m,v.current),T+=.016,R=requestAnimationFrame(k)};return R=requestAnimationFrame(k),{destroy:()=>{cancelAnimationFrame(R),E.disconnect(),"auto"===i&&M.removeEventListener("change",y),window.removeEventListener("mousemove",C),f.remove(),l.style.position=h}}}var tt=Z;function et(t,e,o,n="#505050",r=100){if(!t||e<=0||o<=0)return[];const a=b(n)??{r:80,g:80,b:80},i=t,s=i.length;return Array.from({length:o},(t,o)=>Array.from({length:e},(t,n)=>({char:i[(o*e+n)%s],r:a.r,g:a.g,b:a.b,a:r})))}function ot(t,{format:e="png",quality:o=.92,scale:n=1}={}){return new Promise((r,a)=>{let i=t;if(1!==n){const e=document.createElement("canvas");e.width=Math.round(t.width*n),e.height=Math.round(t.height*n);const o=e.getContext("2d");if(!o)return void a(new Error("captureSnapshot: could not get 2d context"));o.drawImage(t,0,0,e.width,e.height),i=e}i.toBlob(t=>t?r(t):a(new Error("captureSnapshot: toBlob returned null")),`image/${e}`,o)})}exports.ART_STYLE_PRESETS=n,exports.BACKGROUND_TYPES=["wave","rain","stars","pulse","noise","grid","aurora","silk","void","morph","fire","dna","terrain","circuit"],exports.CHARSETS=e,exports.CHARSET_SEQUENCES=o,exports.DEFAULT_OPTIONS=r,exports.HOVER_PRESETS={none:{label:"Off",options:{hoverStrength:0,hoverEffect:"spotlight",hoverRadius:.2,hoverColor:"#ffffff"}},subtle:{label:"Subtle",options:{hoverStrength:.25,hoverEffect:"glow",hoverRadius:.12,hoverColor:"#ffffff"}},flashlight:{label:"Flashlight",options:{hoverStrength:.6,hoverEffect:"spotlight",hoverRadius:.15,hoverColor:"#fffbe6"}},magnifier:{label:"Magnifier",options:{hoverStrength:.7,hoverEffect:"magnify",hoverRadius:.12,hoverColor:"#ffffff"}},forceField:{label:"Force Field",options:{hoverStrength:.7,hoverEffect:"repel",hoverRadius:.15,hoverColor:"#a0e8ff"}},neon:{label:"Neon",options:{hoverStrength:.6,hoverEffect:"colorShift",hoverRadius:.15,hoverColor:"#d946ef"}},fire:{label:"Fire",options:{hoverStrength:.7,hoverEffect:"spotlight",hoverRadius:.15,hoverColor:"#ff6b2b"}},ice:{label:"Ice",options:{hoverStrength:.5,hoverEffect:"glow",hoverRadius:.15,hoverColor:"#60d5f7"}},gravity:{label:"Gravity",options:{hoverStrength:.7,hoverEffect:"attract",hoverRadius:.18,hoverColor:"#a5d6ff"}},shatter:{label:"Shatter",options:{hoverStrength:.8,hoverEffect:"shatter",hoverRadius:.14,hoverColor:"#ff6090"}},ghost:{label:"Ghost",options:{hoverStrength:.55,hoverEffect:"trail",hoverRadius:.2,hoverColor:"#b39ddb"}}},exports.PALETTE_THEMES={dracula:{name:"Dracula",accent:"#bd93f9",bg:"#282a36",fg:"#f8f8f2"},monokai:{name:"Monokai",accent:"#a6e22e",bg:"#272822",fg:"#f8f8f2"},nord:{name:"Nord",accent:"#88c0d0",bg:"#2e3440",fg:"#eceff4"},catppuccin:{name:"Catppuccin",accent:"#cba6f7",bg:"#1e1e2e",fg:"#cdd6f4"},solarized:{name:"Solarized",accent:"#268bd2",bg:"#002b36",fg:"#839496"},gruvbox:{name:"Gruvbox",accent:"#b8bb26",bg:"#282828",fg:"#ebdbb2"}},exports.asciiBackground=Z,exports.asciiText=function(t,e={},o,n){const a={...r,...e},{frame:i,cols:s}=R(t,a,o,n);if(!i.length||0===s)return"";const c=[];for(const t of i)c.push(t.map(t=>t.char).join(""));return c.join("\n")},exports.asciiTextAnsi=function(t,e={},o,n){const a={...r,...e},{frame:i,cols:s}=R(t,a,o,n);if(!i.length||0===s)return"";const c=[];for(const t of i){let e="";for(const o of t)" "===o.char||o.a<10?e+=" ":e+=`[38;2;${o.r};${o.g};${o.b}m${o.char}[0m`;c.push(e)}return c.join("\n")},exports.asciify=async function(t,e,{fontSize:o,artStyle:a="classic",options:i={}}={}){let s;if("string"==typeof t){const e=new Image;e.crossOrigin="anonymous",await new Promise((o,n)=>{e.onload=()=>o(),e.onerror=()=>n(new Error(`Failed to load image: ${t}`)),e.src=t}),s=e}else t instanceof HTMLImageElement&&!t.complete?(await new Promise((e,o)=>{t.onload=()=>e(),t.onerror=()=>o(new Error("Image failed to load"))}),s=t):s=t;const c=n[a],l=o??i.fontSize??10,h={...r,...c,...i,fontSize:l},f=e.getContext("2d");if(!f)throw new Error("Could not get 2d context from canvas");const{w:d,h:g}=function(t){return t instanceof HTMLVideoElement?{w:t.videoWidth,h:t.videoHeight}:t instanceof HTMLImageElement?{w:t.naturalWidth||t.width,h:t.naturalHeight||t.height}:{w:t.width,h:t.height}}(s),{renderW:u,renderH:m}=B(d,g),M="undefined"!=typeof window&&window.devicePixelRatio||1,p=8e6,b=u*M*m*M>p?Math.sqrt(p/(u*m)):M;(e.width<u||e.height<m)&&(e.width=Math.round(u*b),e.height=Math.round(m*b));const{frame:x}=R(s,h,u,m);f.save(),f.setTransform(b,0,0,b,0,0),I(f,x,h,u,m),f.restore()},exports.asciifyGif=async function(t,e,{fontSize:o,artStyle:a="classic",options:i={}}={}){const s="string"==typeof t?await fetch(t).then(t=>t.arrayBuffer()):t,c=o??i.fontSize??10,l={...r,...n[a],...i,fontSize:c},h=e.getContext("2d");if(!h)throw new Error("Could not get 2d context from canvas");const{frames:f,fps:d}=await A(s,l,e.width,e.height);let g,u=!1,m=0,M=performance.now();const p=1e3/d,b=t=>{u||(t-M>=p&&(I(h,f[m],l,e.width,e.height),m=(m+1)%f.length,M=t),g=requestAnimationFrame(b))};return g=requestAnimationFrame(b),()=>{u=!0,cancelAnimationFrame(g)}},exports.asciifyLiveVideo=function(t,e,o){return z(t,e,o)},exports.asciifyVideo=z,exports.asciifyWebcam=async function(t,{fontSize:e=10,style:o="classic",options:a={},liveOptions:i,mirror:s=!0,constraints:c={facingMode:"user"},dpr:l}={}){if(!navigator.mediaDevices?.getUserMedia)throw new Error("asciifyWebcam: getUserMedia is not supported in this browser.");const h=await navigator.mediaDevices.getUserMedia({video:c}),f=document.createElement("video");f.srcObject=h,f.muted=!0,f.playsInline=!0,await new Promise((t,e)=>{f.onloadedmetadata=()=>t(),f.onerror=()=>e(new Error("asciifyWebcam: video stream failed to load.")),f.play().catch(e)});const d={...r,...n[o],...a,fontSize:e},g=t.getContext("2d");if(!g)throw new Error("asciifyWebcam: could not get 2d context from canvas.");const u=l??("undefined"!=typeof window?window.devicePixelRatio:1)??1;1!==u&&g.scale(u,u);let m=null;const M={x:.5,y:.5,intensity:0},p=e=>{const o=t.getBoundingClientRect();m={x:(e.clientX-o.left)/o.width,y:(e.clientY-o.top)/o.height}},b=()=>{m=null};d.hoverStrength>0&&(t.addEventListener("mousemove",p),t.addEventListener("mouseleave",b));let x,v=!1;const w=performance.now(),y=e=>{if(!v){if(f.readyState>=f.HAVE_CURRENT_DATA){const o=t.width/u,n=t.height/u,r=(e-w)/1e3,a=i?{...d,...i()}:d;a.hoverStrength>0?(t.addEventListener("mousemove",p),t.addEventListener("mouseleave",b)):(t.removeEventListener("mousemove",p),t.removeEventListener("mouseleave",b));const{frame:c}=R(f,a,o,n);if(m){const t=m.x-M.x,e=m.y-M.y,o=Math.sqrt(t*t+e*e),n=Math.min(.25,.06+.8*o);M.x+=t*n,M.y+=e*n,M.intensity+=.12*(1-M.intensity)}else M.intensity*=.965,M.intensity<.003&&(M.intensity=0);const l=M.intensity>.003?{x:M.x,y:M.y,intensity:M.intensity}:null;s?(g.save(),g.scale(-1,1),g.translate(-o,0),I(g,c,a,o,n,r,l),g.restore()):I(g,c,a,o,n,r,l)}x=requestAnimationFrame(y)}};return x=requestAnimationFrame(y),()=>{v=!0,cancelAnimationFrame(x),t.removeEventListener("mousemove",p),t.removeEventListener("mouseleave",b),h.getTracks().forEach(t=>t.stop()),f.srcObject=null}},exports.buildTextFrame=et,exports.captureSnapshot=ot,exports.gifToAsciiFrames=A,exports.imageToAsciiFrame=R,exports.mountWaveBackground=tt,exports.renderAuroraBackground=j,exports.renderCircuitBackground=Q,exports.renderDnaBackground=V,exports.renderFireBackground=D,exports.renderFrameToCanvas=I,exports.renderGridBackground=W,exports.renderMorphBackground=_,exports.renderNoiseBackground=H,exports.renderPulseBackground=L,exports.renderRainBackground=F,exports.renderSilkBackground=O,exports.renderStarsBackground=q,exports.renderTerrainBackground=N,exports.renderTextBackground=function(t,e,o,n,a={},i){const{fontSize:s=10,lineHeight:c=1.6,color:l="#505050",opacity:h=100,hoverEffect:f="spotlight",hoverStrength:d=.85,hoverRadius:g=.18,hoverColor:u="#d4ff00"}=a;I(t,et(n,Math.max(1,Math.floor(e/s)),Math.max(1,Math.floor(o/(s*c))),l,h),{...r,hoverEffect:f,hoverStrength:d,hoverRadius:g,hoverColor:u},e,o,0,i??null)},exports.renderVoidBackground=U,exports.renderWaveBackground=S,exports.snapshotAndDownload=async function(t,e={}){const{filename:o="asciify-snapshot",format:n="png",...r}=e,a=await ot(t,{format:n,...r}),i="jpeg"===n?"jpg":n,s=document.createElement("a");s.href=URL.createObjectURL(a),s.download=`${o}.${i}`,s.click(),setTimeout(()=>URL.revokeObjectURL(s.href),1e4)},exports.videoToAsciiFrames=k;//# sourceMappingURL=index.cjs.map
|