asciify-engine 1.0.22 → 1.0.24

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.js CHANGED
@@ -1,17 +1,29 @@
1
1
  import { parseGIF, decompressFrames } from 'gifuct-js';
2
2
 
3
3
  // src/types.ts
4
+ var PALETTE_THEMES = {
5
+ dracula: { name: "Dracula", accent: "#bd93f9", bg: "#282a36", fg: "#f8f8f2" },
6
+ monokai: { name: "Monokai", accent: "#a6e22e", bg: "#272822", fg: "#f8f8f2" },
7
+ nord: { name: "Nord", accent: "#88c0d0", bg: "#2e3440", fg: "#eceff4" },
8
+ catppuccin: { name: "Catppuccin", accent: "#cba6f7", bg: "#1e1e2e", fg: "#cdd6f4" },
9
+ solarized: { name: "Solarized", accent: "#268bd2", bg: "#002b36", fg: "#839496" },
10
+ gruvbox: { name: "Gruvbox", accent: "#b8bb26", bg: "#282828", fg: "#ebdbb2" }
11
+ };
4
12
  var CHARSETS = {
5
13
  standard: " .:-=+*#%@",
6
14
  blocks: " \u2591\u2592\u2593\u2588",
7
15
  minimal: " .:+",
8
- dense: " .'`^\",:;Il!i><~+_-?][}{1)(|\\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$",
16
+ dense: " .'`^\",:;Il!i><~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$",
9
17
  binary: "01",
10
18
  dots: " \u2801\u2803\u2807\u2847\u28C7\u28E7\u28F7\u28FF",
11
19
  letters: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
12
20
  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",
13
21
  box: " \u25AA\u25FE\u25FC\u25A0\u2588",
14
- lines: " \u02D7\u2010\u2013\u2014\u2015\u2501"
22
+ lines: " \u02D7\u2010\u2013\u2014\u2015\u2501",
23
+ 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",
24
+ 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",
25
+ musical: " \u2669\u266A\u266B\u266C\u266D\u266E\u266F",
26
+ emoji: " \u2B1B\u{1F7EB}\u{1F7E5}\u{1F7E7}\u{1F7E8}\u{1F7E9}\u{1F7E6}\u{1F7EA}\u2B1C"
15
27
  };
16
28
  var ART_STYLE_PRESETS = {
17
29
  classic: {
@@ -54,6 +66,27 @@ var ART_STYLE_PRESETS = {
54
66
  renderMode: "ascii",
55
67
  charset: CHARSETS.lines,
56
68
  colorMode: "fullcolor"
69
+ },
70
+ braille: {
71
+ renderMode: "ascii",
72
+ charset: CHARSETS.braille,
73
+ colorMode: "fullcolor"
74
+ },
75
+ katakana: {
76
+ renderMode: "ascii",
77
+ charset: CHARSETS.katakana,
78
+ colorMode: "matrix"
79
+ },
80
+ musical: {
81
+ renderMode: "ascii",
82
+ charset: CHARSETS.musical,
83
+ colorMode: "accent",
84
+ accentColor: "#e040fb"
85
+ },
86
+ emoji: {
87
+ renderMode: "ascii",
88
+ charset: CHARSETS.emoji,
89
+ colorMode: "fullcolor"
57
90
  }
58
91
  };
59
92
  var DEFAULT_OPTIONS = {
@@ -109,6 +142,18 @@ var HOVER_PRESETS = {
109
142
  ice: {
110
143
  label: "Ice",
111
144
  options: { hoverStrength: 0.5, hoverEffect: "glow", hoverRadius: 0.15, hoverColor: "#60d5f7" }
145
+ },
146
+ gravity: {
147
+ label: "Gravity",
148
+ options: { hoverStrength: 0.7, hoverEffect: "attract", hoverRadius: 0.18, hoverColor: "#a5d6ff" }
149
+ },
150
+ shatter: {
151
+ label: "Shatter",
152
+ options: { hoverStrength: 0.8, hoverEffect: "shatter", hoverRadius: 0.14, hoverColor: "#ff6090" }
153
+ },
154
+ ghost: {
155
+ label: "Ghost",
156
+ options: { hoverStrength: 0.55, hoverEffect: "trail", hoverRadius: 0.2, hoverColor: "#b39ddb" }
112
157
  }
113
158
  };
114
159
 
@@ -128,14 +173,16 @@ function adjustLuminance(lum, brightness, contrast) {
128
173
  }
129
174
  function luminanceToChar(lum, charset, invert) {
130
175
  const normalized = invert ? 1 - lum / 255 : lum / 255;
131
- const index = Math.floor(normalized * (charset.length - 1));
132
- return charset[Math.max(0, Math.min(charset.length - 1, index))];
176
+ const chars = [...charset];
177
+ const index = Math.floor(normalized * (chars.length - 1));
178
+ return chars[Math.max(0, Math.min(chars.length - 1, index))];
133
179
  }
134
180
  function customTextToChar(lum, text, x, y, cols, invert) {
135
181
  const normalized = invert ? 1 - lum / 255 : lum / 255;
136
182
  if (normalized < 0.12) return " ";
183
+ const chars = [...text];
137
184
  const pos = y * cols + x;
138
- return text[pos % text.length];
185
+ return chars[pos % chars.length];
139
186
  }
140
187
  var BAYER_4X4 = [
141
188
  [0, 8, 2, 10],
@@ -283,6 +330,44 @@ function getAnimationMultiplier(x, y, cols, rows, time, style, speed) {
283
330
  }
284
331
  case "waveField":
285
332
  return 1;
333
+ case "ripple": {
334
+ const cx2 = cols / 2;
335
+ const cy2 = rows / 2;
336
+ const dx2 = x - cx2;
337
+ const dy2 = y - cy2;
338
+ const dist2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
339
+ const maxDist2 = Math.sqrt(cx2 * cx2 + cy2 * cy2);
340
+ const ripple = Math.sin(dist2 / maxDist2 * Math.PI * 10 - t * 5) * 0.5 + 0.5;
341
+ const fadeEdge = 1 - Math.min(1, dist2 / (maxDist2 * 1.1));
342
+ return 0.1 + 0.9 * ripple * (0.4 + fadeEdge * 0.6);
343
+ }
344
+ case "melt": {
345
+ const lagPhase = y / rows * Math.PI;
346
+ const drip = Math.sin(lagPhase - t * 1.8 + x * 0.15) * 0.5 + 0.5;
347
+ const gravity = Math.max(0, y / rows - 0.1) / 0.9;
348
+ return 0.05 + 0.95 * (drip * (1 - gravity * 0.6));
349
+ }
350
+ case "orbit": {
351
+ const ocx = cols / 2;
352
+ const ocy = rows / 2;
353
+ const odx = x - ocx;
354
+ const ody = y - ocy;
355
+ const oAngle = Math.atan2(ody, odx);
356
+ const oDist = Math.sqrt(odx * odx + ody * ody) / Math.sqrt(ocx * ocx + ocy * ocy);
357
+ const orbit = Math.sin(oAngle * 2 + oDist * 6 - t * 2.5) * 0.5 + 0.5;
358
+ return 0.1 + 0.9 * orbit;
359
+ }
360
+ case "cellular": {
361
+ const tick = Math.floor(t * 4);
362
+ const alive = (cx, cy) => {
363
+ const h = Math.sin(cx * 127.1 + cy * 311.7 + tick * 43758.5453) * 43758.5453;
364
+ return h - Math.floor(h) > 0.38 ? 1 : 0;
365
+ };
366
+ const self = alive(x, y);
367
+ 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);
368
+ const nextAlive = self === 1 ? neighbours === 2 || neighbours === 3 ? 1 : 0 : neighbours === 3 ? 1 : 0;
369
+ return nextAlive === 1 ? 1 : 0.05;
370
+ }
286
371
  default:
287
372
  return 1;
288
373
  }
@@ -342,6 +427,30 @@ function computeHoverEffect(nx, ny, hoverX, hoverY, hoverIntensity, strength, ce
342
427
  glow = eased * strength * 0.2;
343
428
  colorBlend = eased * strength * 0.7;
344
429
  break;
430
+ case "attract": {
431
+ const angle3 = Math.atan2(dy, dx);
432
+ const pull = eased * eased * strength * 1;
433
+ offsetX = -Math.cos(angle3) * pull * cellW;
434
+ offsetY = -Math.sin(angle3) * pull * cellH;
435
+ glow = eased * strength * 0.3;
436
+ break;
437
+ }
438
+ case "shatter": {
439
+ const angle4 = Math.atan2(dy, dx);
440
+ const jitter = Math.sin(dx * 43.7 + dy * 29.3) * 0.5;
441
+ const scatter = eased * strength * 1.4 * (0.7 + jitter * 0.3);
442
+ offsetX = Math.cos(angle4 + jitter) * scatter * cellW;
443
+ offsetY = Math.sin(angle4 + jitter) * scatter * cellH;
444
+ scale = Math.max(0.1, 1 - eased * strength * 0.6);
445
+ glow = eased * strength * 0.25;
446
+ break;
447
+ }
448
+ case "trail": {
449
+ colorBlend = eased * strength * 0.9;
450
+ glow = eased * strength * 0.6;
451
+ scale = 1 + eased * strength * 0.15;
452
+ break;
453
+ }
345
454
  }
346
455
  _hoverResult.scale = scale;
347
456
  _hoverResult.offsetX = offsetX;
@@ -396,7 +505,7 @@ function renderWaveBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
396
505
  lineHeightRatio = 1.4,
397
506
  chars = " .:-=+*#%@",
398
507
  baseColor = null,
399
- accentColor = "#d4ff00",
508
+ accentColor = void 0,
400
509
  accentThreshold = 0.52,
401
510
  mouseInfluence = 0.55,
402
511
  mouseFalloff = 2.8,
@@ -406,6 +515,7 @@ function renderWaveBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
406
515
  breathe = true,
407
516
  lightMode = false
408
517
  } = options;
518
+ const resolvedAccent = accentColor ?? (lightMode ? "#6b8700" : "#d4ff00");
409
519
  const charW = fontSize * charAspect;
410
520
  const lineH = fontSize * lineHeightRatio;
411
521
  const cols = Math.ceil(width / charW);
@@ -413,8 +523,8 @@ function renderWaveBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
413
523
  const mx = mousePos.x;
414
524
  const my = mousePos.y;
415
525
  let acR = 212, acG = 255, acB = 0;
416
- if (accentColor) {
417
- const hex = accentColor.replace("#", "");
526
+ {
527
+ const hex = resolvedAccent.replace("#", "");
418
528
  if (hex.length === 6) {
419
529
  acR = parseInt(hex.slice(0, 2), 16);
420
530
  acG = parseInt(hex.slice(2, 4), 16);
@@ -752,17 +862,18 @@ function renderFrameToCanvas(ctx, frame, options, canvasWidth, canvasHeight, tim
752
862
  const fontSize = Math.min(cellW / charAspect, cellH) * 0.9;
753
863
  const useFastRect = fontSize < 6;
754
864
  if (!useFastRect) {
755
- ctx.font = `${fontSize}px "JetBrains Mono", monospace`;
865
+ const isEmoji = options.artStyle === "emoji";
866
+ ctx.font = isEmoji ? `${fontSize}px "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla", sans-serif` : `${fontSize}px "JetBrains Mono", monospace`;
756
867
  ctx.textAlign = "center";
757
868
  ctx.textBaseline = "middle";
758
869
  }
759
870
  let charWeights = null;
760
871
  if (useFastRect) {
761
872
  charWeights = {};
762
- const cs = options.charset;
763
- const csLen = cs.length;
873
+ const csChars = [...options.charset];
874
+ const csLen = csChars.length;
764
875
  for (let i = 0; i < csLen; i++) {
765
- charWeights[cs[i]] = Math.max(0.1, (i + 0.3) / csLen);
876
+ charWeights[csChars[i]] = Math.max(0.1, (i + 0.3) / csLen);
766
877
  }
767
878
  }
768
879
  const baseTransform = !useFastRect ? ctx.getTransform() : null;
@@ -1024,13 +1135,14 @@ function renderRainBackground(ctx, width, height, time, options = {}) {
1024
1135
  const {
1025
1136
  fontSize = 13,
1026
1137
  chars = "0123456789ABCDEF@#$&*+=/<>",
1027
- accentColor = "#d4ff00",
1138
+ accentColor = void 0,
1028
1139
  color,
1029
1140
  speed = 1,
1030
1141
  density = 0.55,
1031
1142
  tailLength = 14,
1032
1143
  lightMode = false
1033
1144
  } = options;
1145
+ const resolvedAccent = accentColor ?? (lightMode ? "#6b8700" : "#d4ff00");
1034
1146
  const charW = fontSize * 0.62;
1035
1147
  const lineH = fontSize * 1.4;
1036
1148
  const cols = Math.ceil(width / charW);
@@ -1053,7 +1165,7 @@ function renderRainBackground(ctx, width, height, time, options = {}) {
1053
1165
  }
1054
1166
  }
1055
1167
  let acR = 212, acG = 255, acB = 0;
1056
- const ap = parseColor(accentColor);
1168
+ const ap = parseColor(resolvedAccent);
1057
1169
  if (ap) {
1058
1170
  acR = ap.r;
1059
1171
  acG = ap.g;
@@ -1089,12 +1201,13 @@ function renderStarsBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1089
1201
  const {
1090
1202
  fontSize = 14,
1091
1203
  chars = " . \xB7 * + \xB0 \u2605",
1092
- accentColor = "#d4ff00",
1204
+ accentColor = void 0,
1093
1205
  color,
1094
1206
  speed = 1,
1095
1207
  count = 180,
1096
1208
  lightMode = false
1097
1209
  } = options;
1210
+ const resolvedAccent = accentColor ?? (lightMode ? "#6b8700" : "#d4ff00");
1098
1211
  ctx.clearRect(0, 0, width, height);
1099
1212
  ctx.textBaseline = "middle";
1100
1213
  ctx.textAlign = "center";
@@ -1116,7 +1229,7 @@ function renderStarsBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1116
1229
  }
1117
1230
  }
1118
1231
  let acR = 212, acG = 255, acB = 0;
1119
- const ap = parseColor(accentColor);
1232
+ const ap = parseColor(resolvedAccent);
1120
1233
  if (ap) {
1121
1234
  acR = ap.r;
1122
1235
  acG = ap.g;
@@ -1149,13 +1262,14 @@ function renderPulseBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1149
1262
  const {
1150
1263
  fontSize = 14,
1151
1264
  chars = ". \xB7 \u25CB \u25CE \u25CF",
1152
- accentColor = "#00ffcc",
1265
+ accentColor = void 0,
1153
1266
  color,
1154
1267
  rings = 5,
1155
1268
  speed = 1,
1156
1269
  sharpness = 4,
1157
1270
  lightMode = false
1158
1271
  } = options;
1272
+ const resolvedAccent = accentColor ?? (lightMode ? "#007a5e" : "#00ffcc");
1159
1273
  ctx.clearRect(0, 0, width, height);
1160
1274
  ctx.textBaseline = "middle";
1161
1275
  ctx.textAlign = "center";
@@ -1177,7 +1291,7 @@ function renderPulseBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1177
1291
  }
1178
1292
  }
1179
1293
  let acR = 0, acG = 255, acB = 204;
1180
- const ap = parseColor(accentColor);
1294
+ const ap = parseColor(resolvedAccent);
1181
1295
  if (ap) {
1182
1296
  acR = ap.r;
1183
1297
  acG = ap.g;
@@ -1222,7 +1336,7 @@ function renderNoiseBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1222
1336
  const {
1223
1337
  fontSize = 14,
1224
1338
  chars = " .\xB7:;=+*#%@\u2591\u2592\u2593",
1225
- accentColor = "#d4ff00",
1339
+ accentColor = void 0,
1226
1340
  color,
1227
1341
  octaves = 4,
1228
1342
  speed = 1,
@@ -1231,6 +1345,7 @@ function renderNoiseBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1231
1345
  mouseWarp = 0.3,
1232
1346
  lightMode = false
1233
1347
  } = options;
1348
+ const resolvedAccent = accentColor ?? (lightMode ? "#6b8700" : "#d4ff00");
1234
1349
  const charW = fontSize * 0.62;
1235
1350
  const lineH = fontSize * 1.4;
1236
1351
  const cols = Math.ceil(width / charW);
@@ -1253,7 +1368,7 @@ function renderNoiseBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1253
1368
  }
1254
1369
  }
1255
1370
  let acR = 212, acG = 255, acB = 0;
1256
- const ap = parseColor(accentColor);
1371
+ const ap = parseColor(resolvedAccent);
1257
1372
  if (ap) {
1258
1373
  acR = ap.r;
1259
1374
  acG = ap.g;
@@ -1301,7 +1416,7 @@ function renderGridBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1301
1416
  const {
1302
1417
  fontSize = 12,
1303
1418
  chars = "\xB7-=+|/",
1304
- accentColor = "#d4ff00",
1419
+ accentColor = void 0,
1305
1420
  color,
1306
1421
  bands = 3,
1307
1422
  speed = 1,
@@ -1309,6 +1424,7 @@ function renderGridBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1309
1424
  glitch = true,
1310
1425
  lightMode = false
1311
1426
  } = options;
1427
+ const resolvedAccent = accentColor ?? (lightMode ? "#6b8700" : "#d4ff00");
1312
1428
  const charW = fontSize * 0.62;
1313
1429
  const lineH = fontSize * 1.4;
1314
1430
  const cols = Math.ceil(width / charW);
@@ -1331,7 +1447,7 @@ function renderGridBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1331
1447
  }
1332
1448
  }
1333
1449
  let acR = 212, acG = 255, acB = 0;
1334
- const ap = parseColor(accentColor);
1450
+ const ap = parseColor(resolvedAccent);
1335
1451
  if (ap) {
1336
1452
  acR = ap.r;
1337
1453
  acG = ap.g;
@@ -1373,13 +1489,14 @@ function renderAuroraBackground(ctx, width, height, time, mousePos = { x: 0.5, y
1373
1489
  fontSize = 14,
1374
1490
  chars = " \xB7\u2219\u2022:;+=\u2261\u2263#@",
1375
1491
  color,
1376
- accentColor = "#d4ff00",
1492
+ accentColor = void 0,
1377
1493
  speed = 1,
1378
1494
  layers = 5,
1379
1495
  softness = 1.2,
1380
1496
  mouseRipple = 0.2,
1381
1497
  lightMode = false
1382
1498
  } = options;
1499
+ const resolvedAccent = accentColor ?? (lightMode ? "#6b8700" : "#d4ff00");
1383
1500
  const charW = fontSize * 0.62;
1384
1501
  const lineH = fontSize * 1.4;
1385
1502
  const cols = Math.ceil(width / charW);
@@ -1402,7 +1519,7 @@ function renderAuroraBackground(ctx, width, height, time, mousePos = { x: 0.5, y
1402
1519
  }
1403
1520
  }
1404
1521
  let acR = 212, acG = 255, acB = 0;
1405
- const ap = parseColor(accentColor);
1522
+ const ap = parseColor(resolvedAccent);
1406
1523
  if (ap) {
1407
1524
  acR = ap.r;
1408
1525
  acG = ap.g;
@@ -1458,12 +1575,13 @@ function renderSilkBackground(ctx, width, height, time, options = {}) {
1458
1575
  const {
1459
1576
  fontSize = 13,
1460
1577
  color,
1461
- accentColor = "#d4ff00",
1578
+ accentColor = void 0,
1462
1579
  speed = 0.4,
1463
1580
  layers = 4,
1464
1581
  turbulence = 0.8,
1465
1582
  lightMode = false
1466
1583
  } = options;
1584
+ const resolvedAccent = accentColor ?? (lightMode ? "#6b8700" : "#d4ff00");
1467
1585
  const charW = fontSize * 0.62;
1468
1586
  const lineH = fontSize * 1.4;
1469
1587
  const cols = Math.ceil(width / charW);
@@ -1486,7 +1604,7 @@ function renderSilkBackground(ctx, width, height, time, options = {}) {
1486
1604
  }
1487
1605
  }
1488
1606
  let acR = 212, acG = 255, acB = 0;
1489
- const ap = parseColor(accentColor);
1607
+ const ap = parseColor(resolvedAccent);
1490
1608
  if (ap) {
1491
1609
  acR = ap.r;
1492
1610
  acG = ap.g;
@@ -1533,12 +1651,13 @@ function renderVoidBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1533
1651
  fontSize = 13,
1534
1652
  chars = " \xB7:;=+*#%@",
1535
1653
  color,
1536
- accentColor = "#d4ff00",
1654
+ accentColor = void 0,
1537
1655
  speed = 1,
1538
1656
  radius = 0.38,
1539
1657
  swirl = 3,
1540
1658
  lightMode = false
1541
1659
  } = options;
1660
+ const resolvedAccent = accentColor ?? (lightMode ? "#6b8700" : "#d4ff00");
1542
1661
  const charW = fontSize * 0.62;
1543
1662
  const lineH = fontSize * 1.4;
1544
1663
  const cols = Math.ceil(width / charW);
@@ -1562,7 +1681,7 @@ function renderVoidBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1562
1681
  }
1563
1682
  }
1564
1683
  let acR = 212, acG = 255, acB = 0;
1565
- const ap = parseColor(accentColor);
1684
+ const ap = parseColor(resolvedAccent);
1566
1685
  if (ap) {
1567
1686
  acR = ap.r;
1568
1687
  acG = ap.g;
@@ -1606,11 +1725,12 @@ function renderMorphBackground(ctx, width, height, time, options = {}) {
1606
1725
  fontSize = 14,
1607
1726
  chars = " \xB7\u2219\u2022:-=+*#",
1608
1727
  color,
1609
- accentColor = "#d4ff00",
1728
+ accentColor = void 0,
1610
1729
  speed = 0.5,
1611
1730
  harmonics = 3,
1612
1731
  lightMode = false
1613
1732
  } = options;
1733
+ const resolvedAccent = accentColor ?? (lightMode ? "#6b8700" : "#d4ff00");
1614
1734
  const charW = fontSize * 0.62;
1615
1735
  const lineH = fontSize * 1.4;
1616
1736
  const cols = Math.ceil(width / charW);
@@ -1633,7 +1753,7 @@ function renderMorphBackground(ctx, width, height, time, options = {}) {
1633
1753
  }
1634
1754
  }
1635
1755
  let acR = 212, acG = 255, acB = 0;
1636
- const ap = parseColor(accentColor);
1756
+ const ap = parseColor(resolvedAccent);
1637
1757
  if (ap) {
1638
1758
  acR = ap.r;
1639
1759
  acG = ap.g;
@@ -1664,6 +1784,292 @@ function renderMorphBackground(ctx, width, height, time, options = {}) {
1664
1784
  }
1665
1785
  }
1666
1786
 
1787
+ // src/backgrounds/fire.ts
1788
+ function renderFireBackground(ctx, width, height, time, options = {}) {
1789
+ const {
1790
+ fontSize = 13,
1791
+ chars = " .,:;i+xX#&@",
1792
+ color = "#ff4500",
1793
+ hotColor = "#ffe066",
1794
+ intensity = 0.85,
1795
+ wind = 0,
1796
+ speed = 1,
1797
+ lightMode = false
1798
+ } = options;
1799
+ const charW = fontSize * 0.62;
1800
+ const lineH = fontSize * 1.4;
1801
+ const cols = Math.ceil(width / charW);
1802
+ const rows = Math.ceil(height / lineH);
1803
+ const len = cols * rows;
1804
+ const key = "__fire_heat__";
1805
+ const canvasAny = ctx.canvas;
1806
+ let heat = canvasAny[key];
1807
+ if (!heat || heat.length !== len) {
1808
+ heat = new Float32Array(len);
1809
+ canvasAny[key] = heat;
1810
+ }
1811
+ const dt = 0.016 * speed;
1812
+ const coolingRate = 0.18 * dt;
1813
+ const windShift = wind * speed * 0.8;
1814
+ const baseRow = rows - 1;
1815
+ const t = time * speed;
1816
+ for (let c = 0; c < cols; c++) {
1817
+ const flicker = Math.sin(c * 0.31 + t * 4.1) * 0.5 + 0.5;
1818
+ const flicker2 = Math.sin(c * 0.73 - t * 2.7) * 0.5 + 0.5;
1819
+ const seed = (flicker * 0.6 + flicker2 * 0.4) * intensity;
1820
+ heat[baseRow * cols + c] = Math.min(1, seed + Math.random() * 0.15 * intensity);
1821
+ if (baseRow > 0) heat[(baseRow - 1) * cols + c] = Math.min(1, seed * 0.85 + Math.random() * 0.1 * intensity);
1822
+ }
1823
+ const newHeat = new Float32Array(len);
1824
+ for (let r = 0; r < rows - 2; r++) {
1825
+ for (let c = 0; c < cols; c++) {
1826
+ const below = heat[(r + 1) * cols + c];
1827
+ const below2 = heat[(r + 2) * cols + Math.max(0, Math.min(cols - 1, c + Math.round(windShift)))];
1828
+ const left = heat[(r + 1) * cols + Math.max(0, c - 1)];
1829
+ const right = heat[(r + 1) * cols + Math.min(cols - 1, c + 1)];
1830
+ const avg = below * 0.4 + below2 * 0.25 + left * 0.175 + right * 0.175;
1831
+ newHeat[r * cols + c] = Math.max(0, avg - coolingRate - Math.random() * 0.02 * speed);
1832
+ }
1833
+ }
1834
+ for (let c = 0; c < cols; c++) {
1835
+ newHeat[(rows - 1) * cols + c] = heat[(rows - 1) * cols + c];
1836
+ if (rows > 1) newHeat[(rows - 2) * cols + c] = heat[(rows - 2) * cols + c];
1837
+ }
1838
+ canvasAny[key] = newHeat;
1839
+ const cp = parseColor(color) ?? { r: 255, g: 69, b: 0 };
1840
+ const hp = parseColor(hotColor) ?? { r: 255, g: 224, b: 102 };
1841
+ ctx.clearRect(0, 0, width, height);
1842
+ ctx.font = `${fontSize}px "JetBrains Mono", monospace`;
1843
+ ctx.textBaseline = "top";
1844
+ for (let r = 0; r < rows; r++) {
1845
+ for (let c = 0; c < cols; c++) {
1846
+ const v = newHeat[r * cols + c];
1847
+ if (v < 0.03) continue;
1848
+ const charIdx = Math.min(chars.length - 1, Math.floor(v * chars.length));
1849
+ const ch = chars[charIdx];
1850
+ if (ch === " ") continue;
1851
+ const blend = Math.min(1, v * 1.2);
1852
+ const r2 = cp.r + (hp.r - cp.r) * blend | 0;
1853
+ const g2 = cp.g + (hp.g - cp.g) * blend | 0;
1854
+ const b2 = cp.b + (hp.b - cp.b) * blend | 0;
1855
+ const alpha = lightMode ? 1 - v * 0.3 : Math.min(1, v + 0.15);
1856
+ ctx.globalAlpha = alpha;
1857
+ ctx.fillStyle = `rgb(${r2},${g2},${b2})`;
1858
+ ctx.fillText(ch, c * charW, r * lineH);
1859
+ }
1860
+ }
1861
+ ctx.globalAlpha = 1;
1862
+ }
1863
+
1864
+ // src/backgrounds/dna.ts
1865
+ function renderDnaBackground(ctx, width, height, time, options = {}) {
1866
+ const {
1867
+ fontSize = 13,
1868
+ baseChars = "ATCG",
1869
+ bridgeChars = "-=\u2261",
1870
+ color = "#00e5ff",
1871
+ color2 = "#ff4081",
1872
+ bridgeColor = "#88ffcc",
1873
+ speed = 1,
1874
+ helixCount,
1875
+ lightMode = false
1876
+ } = options;
1877
+ const charW = fontSize * 0.62;
1878
+ const lineH = fontSize * 1.4;
1879
+ const cols = Math.ceil(width / charW);
1880
+ const rows = Math.ceil(height / lineH);
1881
+ const numHelix = helixCount ?? Math.max(1, Math.floor(width / 80));
1882
+ const sectionW = cols / numHelix;
1883
+ const cp = parseColor(color) ?? { r: 0, g: 229, b: 255 };
1884
+ const cp2 = parseColor(color2) ?? { r: 255, g: 64, b: 129 };
1885
+ const bp = parseColor(bridgeColor) ?? { r: 136, g: 255, b: 204 };
1886
+ ctx.clearRect(0, 0, width, height);
1887
+ ctx.font = `${fontSize}px "JetBrains Mono", monospace`;
1888
+ ctx.textBaseline = "top";
1889
+ const t = time * speed;
1890
+ const amplitude = sectionW * 0.35;
1891
+ for (let h = 0; h < numHelix; h++) {
1892
+ const centerCol = sectionW * (h + 0.5);
1893
+ for (let r = 0; r < rows; r++) {
1894
+ const phase = r / rows * Math.PI * 6 - t * 1.8;
1895
+ const strand1ColF = centerCol + Math.sin(phase) * amplitude;
1896
+ const strand2ColF = centerCol + Math.sin(phase + Math.PI) * amplitude;
1897
+ const strand1Col = Math.round(strand1ColF);
1898
+ const strand2Col = Math.round(strand2ColF);
1899
+ if (strand1Col < 0 || strand1Col >= cols) continue;
1900
+ const baseSeed1 = hash2(h * 31 + r * 7, 3);
1901
+ const ch1 = baseChars[Math.floor(baseSeed1 * baseChars.length)];
1902
+ const depth1 = (Math.sin(phase) + 1) * 0.5;
1903
+ const depth2 = (Math.sin(phase + Math.PI) + 1) * 0.5;
1904
+ ctx.globalAlpha = 0.35 + depth1 * 0.65;
1905
+ ctx.fillStyle = `rgb(${cp.r},${cp.g},${cp.b})`;
1906
+ ctx.fillText(ch1, strand1Col * charW, r * lineH);
1907
+ if (strand2Col >= 0 && strand2Col < cols) {
1908
+ const baseSeed2 = hash2(h * 53 + r * 11, 7);
1909
+ const ch2 = baseChars[Math.floor(baseSeed2 * baseChars.length)];
1910
+ ctx.globalAlpha = 0.35 + depth2 * 0.65;
1911
+ ctx.fillStyle = `rgb(${cp2.r},${cp2.g},${cp2.b})`;
1912
+ ctx.fillText(ch2, strand2Col * charW, r * lineH);
1913
+ }
1914
+ const bridgeInterval = 3;
1915
+ if (r % bridgeInterval === 0) {
1916
+ const minC = Math.min(strand1Col, strand2Col);
1917
+ const maxC = Math.max(strand1Col, strand2Col);
1918
+ const bridgeLen = maxC - minC;
1919
+ if (bridgeLen > 1) {
1920
+ const bSeed = hash2(r * 17 + h * 43, 5);
1921
+ const bCh = bridgeChars[Math.floor(bSeed * bridgeChars.length)];
1922
+ const midBridgeAlpha = (depth1 + depth2) * 0.25 + 0.2;
1923
+ ctx.globalAlpha = midBridgeAlpha;
1924
+ ctx.fillStyle = `rgb(${bp.r},${bp.g},${bp.b})`;
1925
+ for (let bc = minC + 1; bc < maxC; bc++) {
1926
+ ctx.fillText(bCh, bc * charW, r * lineH);
1927
+ }
1928
+ }
1929
+ }
1930
+ }
1931
+ }
1932
+ ctx.globalAlpha = 1;
1933
+ }
1934
+
1935
+ // src/backgrounds/terrain.ts
1936
+ function renderTerrainBackground(ctx, width, height, time, options = {}) {
1937
+ const {
1938
+ fontSize = 13,
1939
+ chars = " .,:;+*#@",
1940
+ color = "#4caf50",
1941
+ skyColor = "#1a237e",
1942
+ peakColor = "#e0e0e0",
1943
+ speed = 1,
1944
+ roughness = 0.55,
1945
+ heightScale = 0.55,
1946
+ stars = true,
1947
+ lightMode = false
1948
+ } = options;
1949
+ const charW = fontSize * 0.62;
1950
+ const lineH = fontSize * 1.4;
1951
+ const cols = Math.ceil(width / charW);
1952
+ const rows = Math.ceil(height / lineH);
1953
+ const cp = parseColor(color) ?? { r: 76, g: 175, b: 80 };
1954
+ const sky = parseColor(skyColor) ?? { r: 26, g: 35, b: 126 };
1955
+ const peak = parseColor(peakColor) ?? { r: 224, g: 224, b: 224 };
1956
+ ctx.clearRect(0, 0, width, height);
1957
+ ctx.font = `${fontSize}px "JetBrains Mono", monospace`;
1958
+ ctx.textBaseline = "top";
1959
+ const scroll = time * speed * 0.4;
1960
+ const terrainRow = new Array(cols);
1961
+ for (let c = 0; c < cols; c++) {
1962
+ const nx = (c / cols + scroll) * roughness * 3;
1963
+ const h = (fbm(nx, 0.5) * 0.5 + 0.5) * heightScale;
1964
+ terrainRow[c] = Math.floor(h * rows);
1965
+ }
1966
+ for (let r = 0; r < rows; r++) {
1967
+ for (let c = 0; c < cols; c++) {
1968
+ const terrainStart = rows - 1 - terrainRow[c];
1969
+ const isGround = r >= terrainStart;
1970
+ const isNearPeak = r === terrainStart;
1971
+ if (!isGround) {
1972
+ if (stars) {
1973
+ const starSeed = hash2(c * 7 + Math.floor(scroll * 0.3), r * 13);
1974
+ if (starSeed > 0.97) {
1975
+ const twinkle = Math.sin(time * 2 + starSeed * 100) * 0.3 + 0.7;
1976
+ ctx.globalAlpha = twinkle * 0.5;
1977
+ ctx.fillStyle = `rgb(${sky.r + 60},${sky.g + 60},${sky.b + 80})`;
1978
+ ctx.fillText("\xB7", c * charW, r * lineH);
1979
+ }
1980
+ }
1981
+ continue;
1982
+ }
1983
+ const depth = (r - terrainStart) / Math.max(1, terrainRow[c]);
1984
+ const charIdx = Math.min(chars.length - 1, Math.floor(depth * chars.length));
1985
+ const ch = chars[charIdx];
1986
+ if (ch === " " && !isNearPeak) continue;
1987
+ const blendPeak = isNearPeak ? 1 : Math.max(0, 1 - depth * 4);
1988
+ const r2 = cp.r + (peak.r - cp.r) * blendPeak | 0;
1989
+ const g2 = cp.g + (peak.g - cp.g) * blendPeak | 0;
1990
+ const b2 = cp.b + (peak.b - cp.b) * blendPeak | 0;
1991
+ ctx.globalAlpha = 0.5 + depth * 0.5;
1992
+ ctx.fillStyle = `rgb(${r2},${g2},${b2})`;
1993
+ ctx.fillText(isNearPeak ? chars[chars.length - 1] : ch, c * charW, r * lineH);
1994
+ }
1995
+ }
1996
+ ctx.globalAlpha = 1;
1997
+ }
1998
+
1999
+ // src/backgrounds/circuit.ts
2000
+ var EAST = 1;
2001
+ var WEST = 2;
2002
+ var NORTH = 4;
2003
+ var SOUTH = 8;
2004
+ var DIR_CHARS = {
2005
+ [EAST | WEST]: "\u2500",
2006
+ [NORTH | SOUTH]: "\u2502",
2007
+ [EAST | SOUTH]: "\u250C",
2008
+ [WEST | SOUTH]: "\u2510",
2009
+ [EAST | NORTH]: "\u2514",
2010
+ [WEST | NORTH]: "\u2518",
2011
+ [EAST | WEST | SOUTH]: "\u252C",
2012
+ [EAST | WEST | NORTH]: "\u2534",
2013
+ [NORTH | SOUTH | EAST]: "\u251C",
2014
+ [NORTH | SOUTH | WEST]: "\u2524",
2015
+ [EAST | WEST | NORTH | SOUTH]: "\u253C",
2016
+ [EAST]: "\u2576",
2017
+ [WEST]: "\u2574",
2018
+ [NORTH]: "\u2575",
2019
+ [SOUTH]: "\u2577"
2020
+ };
2021
+ function renderCircuitBackground(ctx, width, height, time, options = {}) {
2022
+ const {
2023
+ fontSize = 13,
2024
+ pulseColor = "#ffffff",
2025
+ color = "#00ff88",
2026
+ density = 0.38,
2027
+ speed = 1,
2028
+ lightMode = false
2029
+ } = options;
2030
+ const charW = fontSize * 0.62;
2031
+ const lineH = fontSize * 1.4;
2032
+ const cols = Math.ceil(width / charW);
2033
+ const rows = Math.ceil(height / lineH);
2034
+ const cp = parseColor(color) ?? { r: 0, g: 255, b: 136 };
2035
+ const pp = parseColor(pulseColor) ?? { r: 255, g: 255, b: 255 };
2036
+ const getConnections = (c, r) => {
2037
+ if (hash2(c * 17 + 1, r * 7 + 2) > density) return 0;
2038
+ let mask = 0;
2039
+ if (c + 1 < cols && hash2(c * 17 + 1, r * 7 + 2) > 0.15) mask |= EAST;
2040
+ if (c - 1 >= 0 && hash2((c - 1) * 17 + 1, r * 7 + 2) > 0.15) mask |= WEST;
2041
+ if (r + 1 < rows && hash2(c * 17 + 1, (r + 1) * 7 + 2) > 0.15) mask |= SOUTH;
2042
+ if (r - 1 >= 0 && hash2(c * 17 + 1, (r - 1) * 7 + 2) > 0.15) mask |= NORTH;
2043
+ return mask;
2044
+ };
2045
+ ctx.clearRect(0, 0, width, height);
2046
+ ctx.font = `${fontSize}px "JetBrains Mono", monospace`;
2047
+ ctx.textBaseline = "top";
2048
+ const t = time * speed;
2049
+ for (let r = 0; r < rows; r++) {
2050
+ for (let c = 0; c < cols; c++) {
2051
+ const mask = getConnections(c, r);
2052
+ if (mask === 0) continue;
2053
+ const ch = DIR_CHARS[mask] ?? "\xB7";
2054
+ const pulsePosH = (t * 8 + hash2(c, r * 23) * 40) % cols;
2055
+ const pulsePosV = (t * 6 + hash2(r * 17, c * 11) * 40) % rows;
2056
+ const nearH = (mask & EAST || mask & WEST) && Math.abs(c - pulsePosH) < 1.5;
2057
+ const nearV = (mask & NORTH || mask & SOUTH) && Math.abs(r - pulsePosV) < 1.5;
2058
+ const isPulse = nearH || nearV;
2059
+ const baseAlpha = 0.25 + hash2(c * 3, r * 5) * 0.35;
2060
+ if (isPulse) {
2061
+ ctx.globalAlpha = 0.95;
2062
+ ctx.fillStyle = `rgb(${pp.r},${pp.g},${pp.b})`;
2063
+ } else {
2064
+ ctx.globalAlpha = baseAlpha;
2065
+ ctx.fillStyle = `rgb(${cp.r},${cp.g},${cp.b})`;
2066
+ }
2067
+ ctx.fillText(ch, c * charW, r * lineH);
2068
+ }
2069
+ }
2070
+ ctx.globalAlpha = 1;
2071
+ }
2072
+
1667
2073
  // src/backgrounds/index.ts
1668
2074
  function _parseColor(c) {
1669
2075
  const hex = c.match(/^#([0-9a-f]{3,8})$/i)?.[1];
@@ -1768,6 +2174,24 @@ function asciiBackground(target, options = {}) {
1768
2174
  lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight(),
1769
2175
  color: color ?? renderOpts.color
1770
2176
  });
2177
+ const buildFireOpts = () => ({
2178
+ ...renderOpts,
2179
+ color: color ?? renderOpts.color
2180
+ });
2181
+ const buildDnaOpts = () => ({
2182
+ ...renderOpts,
2183
+ color: color ?? renderOpts.color
2184
+ });
2185
+ const buildTerrainOpts = () => ({
2186
+ ...renderOpts,
2187
+ color: color ?? renderOpts.color,
2188
+ lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight()
2189
+ });
2190
+ const buildCircuitOpts = () => ({
2191
+ ...renderOpts,
2192
+ color: color ?? renderOpts.color,
2193
+ lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight()
2194
+ });
1771
2195
  const optsRef = { current: buildWaveOpts() };
1772
2196
  const rebuildOpts = () => {
1773
2197
  if (type === "rain") optsRef.current = buildRainOpts();
@@ -1779,6 +2203,10 @@ function asciiBackground(target, options = {}) {
1779
2203
  else if (type === "silk") optsRef.current = buildSilkOpts();
1780
2204
  else if (type === "void") optsRef.current = buildVoidOpts();
1781
2205
  else if (type === "morph") optsRef.current = buildMorphOpts();
2206
+ else if (type === "fire") optsRef.current = buildFireOpts();
2207
+ else if (type === "dna") optsRef.current = buildDnaOpts();
2208
+ else if (type === "terrain") optsRef.current = buildTerrainOpts();
2209
+ else if (type === "circuit") optsRef.current = buildCircuitOpts();
1782
2210
  else optsRef.current = buildWaveOpts();
1783
2211
  };
1784
2212
  rebuildOpts();
@@ -1825,6 +2253,14 @@ function asciiBackground(target, options = {}) {
1825
2253
  renderVoidBackground(ctx, r.width, r.height, time, smoothMouse, optsRef.current);
1826
2254
  } else if (type === "morph") {
1827
2255
  renderMorphBackground(ctx, r.width, r.height, time, optsRef.current);
2256
+ } else if (type === "fire") {
2257
+ renderFireBackground(ctx, r.width, r.height, time, optsRef.current);
2258
+ } else if (type === "dna") {
2259
+ renderDnaBackground(ctx, r.width, r.height, time, optsRef.current);
2260
+ } else if (type === "terrain") {
2261
+ renderTerrainBackground(ctx, r.width, r.height, time, optsRef.current);
2262
+ } else if (type === "circuit") {
2263
+ renderCircuitBackground(ctx, r.width, r.height, time, optsRef.current);
1828
2264
  } else {
1829
2265
  renderWaveBackground(ctx, r.width, r.height, time, smoothMouse, optsRef.current);
1830
2266
  }
@@ -1845,6 +2281,178 @@ function asciiBackground(target, options = {}) {
1845
2281
  }
1846
2282
  var mountWaveBackground = asciiBackground;
1847
2283
 
2284
+ // src/core/ascii-text.ts
2285
+ function asciiText(source, options = {}, targetWidth, targetHeight) {
2286
+ const opts = { ...DEFAULT_OPTIONS, ...options };
2287
+ const { frame, cols } = imageToAsciiFrame(source, opts, targetWidth, targetHeight);
2288
+ if (!frame.length || cols === 0) return "";
2289
+ const lines = [];
2290
+ for (const row of frame) {
2291
+ lines.push(row.map((cell) => cell.char).join(""));
2292
+ }
2293
+ return lines.join("\n");
2294
+ }
2295
+ function asciiTextAnsi(source, options = {}, targetWidth, targetHeight) {
2296
+ const opts = { ...DEFAULT_OPTIONS, ...options };
2297
+ const { frame, cols } = imageToAsciiFrame(source, opts, targetWidth, targetHeight);
2298
+ if (!frame.length || cols === 0) return "";
2299
+ const RESET = "\x1B[0m";
2300
+ const lines = [];
2301
+ for (const row of frame) {
2302
+ let line = "";
2303
+ for (const cell of row) {
2304
+ if (cell.char === " " || cell.a < 10) {
2305
+ line += " ";
2306
+ } else {
2307
+ line += `\x1B[38;2;${cell.r};${cell.g};${cell.b}m${cell.char}${RESET}`;
2308
+ }
2309
+ }
2310
+ lines.push(line);
2311
+ }
2312
+ return lines.join("\n");
2313
+ }
2314
+
2315
+ // src/core/record.ts
2316
+ function createRecorder(canvas, options = {}) {
2317
+ const {
2318
+ fps = 15,
2319
+ maxFrames = 120,
2320
+ format = "gif",
2321
+ quality = 10,
2322
+ scale = 1
2323
+ } = options;
2324
+ const interval = 1e3 / fps;
2325
+ let recording = false;
2326
+ let timerId = -1;
2327
+ const blobs = [];
2328
+ const captureFrame = () => {
2329
+ if (!recording || blobs.length >= maxFrames) return;
2330
+ let src = canvas;
2331
+ if (scale !== 1) {
2332
+ const off = document.createElement("canvas");
2333
+ off.width = Math.round(canvas.width * scale);
2334
+ off.height = Math.round(canvas.height * scale);
2335
+ const offCtx = off.getContext("2d");
2336
+ offCtx.drawImage(canvas, 0, 0, off.width, off.height);
2337
+ src = off;
2338
+ }
2339
+ blobs.push(src.toDataURL("image/png"));
2340
+ };
2341
+ const encodeGif = async (frames) => {
2342
+ return new Promise((resolve, reject) => {
2343
+ if (typeof GIF === "undefined") {
2344
+ reject(new Error('[asciify recorder] gif.js not found. Add <script src="/gif.worker.js"> to your page.'));
2345
+ return;
2346
+ }
2347
+ const gif = new GIF({
2348
+ workers: 2,
2349
+ quality,
2350
+ workerScript: "/gif.worker.js"
2351
+ });
2352
+ let loaded = 0;
2353
+ const total = frames.length;
2354
+ frames.forEach((dataUrl) => {
2355
+ const img = new Image();
2356
+ img.onload = () => {
2357
+ gif.addFrame(img, { delay: interval, copy: true });
2358
+ loaded++;
2359
+ if (loaded === total) gif.render();
2360
+ };
2361
+ img.src = dataUrl;
2362
+ });
2363
+ gif.on("finished", (blob) => {
2364
+ const reader = new FileReader();
2365
+ reader.onload = () => resolve(reader.result);
2366
+ reader.readAsDataURL(blob);
2367
+ });
2368
+ gif.on("error", reject);
2369
+ });
2370
+ };
2371
+ const encodeWebP = async (frames, _fps) => {
2372
+ if (typeof MediaRecorder === "undefined") {
2373
+ throw new Error("[asciify recorder] MediaRecorder not available in this browser.");
2374
+ }
2375
+ const off = document.createElement("canvas");
2376
+ if (frames.length === 0) return "";
2377
+ const probe = new Image();
2378
+ await new Promise((res) => {
2379
+ probe.onload = () => res();
2380
+ probe.src = frames[0];
2381
+ });
2382
+ off.width = probe.naturalWidth;
2383
+ off.height = probe.naturalHeight;
2384
+ const offCtx = off.getContext("2d");
2385
+ const stream = off.captureStream(_fps);
2386
+ const recorder = new MediaRecorder(stream, { mimeType: "video/webm;codecs=vp9" });
2387
+ const chunks = [];
2388
+ recorder.ondataavailable = (e) => chunks.push(e.data);
2389
+ return new Promise((resolve, reject) => {
2390
+ recorder.onstop = () => {
2391
+ const blob = new Blob(chunks, { type: "video/webm" });
2392
+ const reader = new FileReader();
2393
+ reader.onload = () => resolve(reader.result);
2394
+ reader.readAsDataURL(blob);
2395
+ };
2396
+ recorder.onerror = reject;
2397
+ recorder.start();
2398
+ let idx = 0;
2399
+ const drawNext = () => {
2400
+ if (idx >= frames.length) {
2401
+ recorder.stop();
2402
+ return;
2403
+ }
2404
+ const img = new Image();
2405
+ img.onload = () => {
2406
+ offCtx.drawImage(img, 0, 0);
2407
+ idx++;
2408
+ setTimeout(drawNext, interval);
2409
+ };
2410
+ img.src = frames[idx];
2411
+ };
2412
+ drawNext();
2413
+ });
2414
+ };
2415
+ return {
2416
+ get isRecording() {
2417
+ return recording;
2418
+ },
2419
+ get frameCount() {
2420
+ return blobs.length;
2421
+ },
2422
+ start() {
2423
+ if (recording) return;
2424
+ blobs.length = 0;
2425
+ recording = true;
2426
+ timerId = window.setInterval(captureFrame, interval);
2427
+ },
2428
+ async stop() {
2429
+ if (!recording) return "";
2430
+ recording = false;
2431
+ clearInterval(timerId);
2432
+ const frames = blobs.slice();
2433
+ if (format === "png-sequence") {
2434
+ return JSON.stringify(frames);
2435
+ }
2436
+ if (format === "webp") {
2437
+ return encodeWebP(frames, fps);
2438
+ }
2439
+ return encodeGif(frames);
2440
+ }
2441
+ };
2442
+ }
2443
+ async function recordAndDownload(canvas, durationMs, options = {}) {
2444
+ const { filename = "asciify-recording", ...recOpts } = options;
2445
+ const recorder = createRecorder(canvas, recOpts);
2446
+ recorder.start();
2447
+ await new Promise((res) => setTimeout(res, durationMs));
2448
+ const dataUrl = await recorder.stop();
2449
+ const ext = options.format === "webp" ? "webm" : options.format === "png-sequence" ? "json" : "gif";
2450
+ const a = document.createElement("a");
2451
+ a.href = dataUrl;
2452
+ a.download = `${filename}.${ext}`;
2453
+ a.click();
2454
+ }
2455
+
1848
2456
  // src/webgl-engine.ts
1849
2457
  var VERT_SRC = (
1850
2458
  /* glsl */
@@ -2107,8 +2715,8 @@ function makeTexture(gl, filter = gl.NEAREST) {
2107
2715
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
2108
2716
  return t;
2109
2717
  }
2110
- var ANIM_STYLES = ["none", "wave", "pulse", "rain", "breathe", "sparkle", "glitch", "spiral", "typewriter", "scatter", "waveField"];
2111
- var HOVER_EFFECTS = ["spotlight", "magnify", "repel", "glow", "colorShift"];
2718
+ var ANIM_STYLES = ["none", "wave", "pulse", "rain", "breathe", "sparkle", "glitch", "spiral", "typewriter", "scatter", "waveField", "ripple", "melt", "orbit", "cellular"];
2719
+ var HOVER_EFFECTS = ["spotlight", "magnify", "repel", "glow", "colorShift", "attract", "shatter", "trail"];
2112
2720
  var COLOR_MODES = ["grayscale", "fullcolor", "matrix", "accent"];
2113
2721
  function tryCreateWebGLRenderer(canvas) {
2114
2722
  const glOrNull = canvas.getContext("webgl2", {
@@ -2293,6 +2901,6 @@ function tryCreateWebGLRenderer(canvas) {
2293
2901
  }
2294
2902
  }
2295
2903
 
2296
- export { ART_STYLE_PRESETS, CHARSETS, DEFAULT_OPTIONS, HOVER_PRESETS, asciiBackground, asciify, asciifyGif, asciifyVideo, generateAnimatedEmbedCode, generateEmbedCode, gifToAsciiFrames, imageToAsciiFrame, mountWaveBackground, renderAuroraBackground, renderFrameToCanvas, renderGridBackground, renderMorphBackground, renderNoiseBackground, renderPulseBackground, renderRainBackground, renderSilkBackground, renderStarsBackground, renderVoidBackground, renderWaveBackground, tryCreateWebGLRenderer, videoToAsciiFrames };
2904
+ export { ART_STYLE_PRESETS, CHARSETS, DEFAULT_OPTIONS, HOVER_PRESETS, PALETTE_THEMES, asciiBackground, asciiText, asciiTextAnsi, asciify, asciifyGif, asciifyVideo, createRecorder, generateAnimatedEmbedCode, generateEmbedCode, gifToAsciiFrames, imageToAsciiFrame, mountWaveBackground, recordAndDownload, renderAuroraBackground, renderCircuitBackground, renderDnaBackground, renderFireBackground, renderFrameToCanvas, renderGridBackground, renderMorphBackground, renderNoiseBackground, renderPulseBackground, renderRainBackground, renderSilkBackground, renderStarsBackground, renderTerrainBackground, renderVoidBackground, renderWaveBackground, tryCreateWebGLRenderer, videoToAsciiFrames };
2297
2905
  //# sourceMappingURL=index.js.map
2298
2906
  //# sourceMappingURL=index.js.map