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