asciify-engine 1.0.23 → 1.0.25

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;
@@ -472,7 +581,7 @@ function renderWaveBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
472
581
  } else if (baseColor) {
473
582
  ctx.fillStyle = baseColor.replace("{a}", String(alpha));
474
583
  } else if (lightMode) {
475
- ctx.fillStyle = `rgba(0,0,0,${alpha * 7})`;
584
+ ctx.fillStyle = `rgba(55,55,55,${alpha * 7})`;
476
585
  } else {
477
586
  ctx.fillStyle = `rgba(255,255,255,${alpha})`;
478
587
  }
@@ -753,17 +862,18 @@ function renderFrameToCanvas(ctx, frame, options, canvasWidth, canvasHeight, tim
753
862
  const fontSize = Math.min(cellW / charAspect, cellH) * 0.9;
754
863
  const useFastRect = fontSize < 6;
755
864
  if (!useFastRect) {
756
- 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`;
757
867
  ctx.textAlign = "center";
758
868
  ctx.textBaseline = "middle";
759
869
  }
760
870
  let charWeights = null;
761
871
  if (useFastRect) {
762
872
  charWeights = {};
763
- const cs = options.charset;
764
- const csLen = cs.length;
873
+ const csChars = [...options.charset];
874
+ const csLen = csChars.length;
765
875
  for (let i = 0; i < csLen; i++) {
766
- charWeights[cs[i]] = Math.max(0.1, (i + 0.3) / csLen);
876
+ charWeights[csChars[i]] = Math.max(0.1, (i + 0.3) / csLen);
767
877
  }
768
878
  }
769
879
  const baseTransform = !useFastRect ? ctx.getTransform() : null;
@@ -1042,9 +1152,9 @@ function renderRainBackground(ctx, width, height, time, options = {}) {
1042
1152
  ctx.textBaseline = "top";
1043
1153
  let br = 255, bg = 255, bb = 255;
1044
1154
  if (lightMode) {
1045
- br = 0;
1046
- bg = 0;
1047
- bb = 0;
1155
+ br = 55;
1156
+ bg = 55;
1157
+ bb = 55;
1048
1158
  }
1049
1159
  if (color) {
1050
1160
  const p = parseColor(color);
@@ -1106,9 +1216,9 @@ function renderStarsBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1106
1216
  const maxR = Math.sqrt(width * width + height * height) * 0.65;
1107
1217
  let br = 255, bg = 255, bb = 255;
1108
1218
  if (lightMode) {
1109
- br = 0;
1110
- bg = 0;
1111
- bb = 0;
1219
+ br = 55;
1220
+ bg = 55;
1221
+ bb = 55;
1112
1222
  }
1113
1223
  if (color) {
1114
1224
  const p = parseColor(color);
@@ -1168,9 +1278,9 @@ function renderPulseBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1168
1278
  const maxDist = Math.sqrt(cx * cx + cy * cy) * 1.6 + Math.sqrt(width * width + height * height) * 0.2;
1169
1279
  let br = 255, bg = 255, bb = 255;
1170
1280
  if (lightMode) {
1171
- br = 0;
1172
- bg = 0;
1173
- bb = 0;
1281
+ br = 55;
1282
+ bg = 55;
1283
+ bb = 55;
1174
1284
  }
1175
1285
  if (color) {
1176
1286
  const p = parseColor(color);
@@ -1245,9 +1355,9 @@ function renderNoiseBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1245
1355
  ctx.textBaseline = "top";
1246
1356
  let br = 255, bgc = 255, bb = 255;
1247
1357
  if (lightMode) {
1248
- br = 0;
1249
- bgc = 0;
1250
- bb = 0;
1358
+ br = 55;
1359
+ bgc = 55;
1360
+ bb = 55;
1251
1361
  }
1252
1362
  if (color) {
1253
1363
  const p = parseColor(color);
@@ -1324,9 +1434,9 @@ function renderGridBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1324
1434
  ctx.textBaseline = "top";
1325
1435
  let br = 255, bgv = 255, bb = 255;
1326
1436
  if (lightMode) {
1327
- br = 0;
1328
- bgv = 0;
1329
- bb = 0;
1437
+ br = 55;
1438
+ bgv = 55;
1439
+ bb = 55;
1330
1440
  }
1331
1441
  if (color) {
1332
1442
  const p = parseColor(color);
@@ -1396,9 +1506,9 @@ function renderAuroraBackground(ctx, width, height, time, mousePos = { x: 0.5, y
1396
1506
  ctx.textBaseline = "top";
1397
1507
  let cr = 255, cg = 255, cb = 255;
1398
1508
  if (lightMode) {
1399
- cr = 0;
1400
- cg = 0;
1401
- cb = 0;
1509
+ cr = 55;
1510
+ cg = 55;
1511
+ cb = 55;
1402
1512
  }
1403
1513
  if (color) {
1404
1514
  const p = parseColor(color);
@@ -1481,9 +1591,9 @@ function renderSilkBackground(ctx, width, height, time, options = {}) {
1481
1591
  ctx.textBaseline = "top";
1482
1592
  let cr = 255, cg = 255, cb = 255;
1483
1593
  if (lightMode) {
1484
- cr = 0;
1485
- cg = 0;
1486
- cb = 0;
1594
+ cr = 55;
1595
+ cg = 55;
1596
+ cb = 55;
1487
1597
  }
1488
1598
  if (color) {
1489
1599
  const p = parseColor(color);
@@ -1558,9 +1668,9 @@ function renderVoidBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1558
1668
  ctx.textBaseline = "top";
1559
1669
  let cr = 255, cg = 255, cb = 255;
1560
1670
  if (lightMode) {
1561
- cr = 0;
1562
- cg = 0;
1563
- cb = 0;
1671
+ cr = 55;
1672
+ cg = 55;
1673
+ cb = 55;
1564
1674
  }
1565
1675
  if (color) {
1566
1676
  const p = parseColor(color);
@@ -1630,9 +1740,9 @@ function renderMorphBackground(ctx, width, height, time, options = {}) {
1630
1740
  ctx.textBaseline = "top";
1631
1741
  let cr = 255, cg = 255, cb = 255;
1632
1742
  if (lightMode) {
1633
- cr = 0;
1634
- cg = 0;
1635
- cb = 0;
1743
+ cr = 55;
1744
+ cg = 55;
1745
+ cb = 55;
1636
1746
  }
1637
1747
  if (color) {
1638
1748
  const p = parseColor(color);
@@ -1674,6 +1784,292 @@ function renderMorphBackground(ctx, width, height, time, options = {}) {
1674
1784
  }
1675
1785
  }
1676
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
+
1677
2073
  // src/backgrounds/index.ts
1678
2074
  function _parseColor(c) {
1679
2075
  const hex = c.match(/^#([0-9a-f]{3,8})$/i)?.[1];
@@ -1778,6 +2174,24 @@ function asciiBackground(target, options = {}) {
1778
2174
  lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight(),
1779
2175
  color: color ?? renderOpts.color
1780
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
+ });
1781
2195
  const optsRef = { current: buildWaveOpts() };
1782
2196
  const rebuildOpts = () => {
1783
2197
  if (type === "rain") optsRef.current = buildRainOpts();
@@ -1789,6 +2203,10 @@ function asciiBackground(target, options = {}) {
1789
2203
  else if (type === "silk") optsRef.current = buildSilkOpts();
1790
2204
  else if (type === "void") optsRef.current = buildVoidOpts();
1791
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();
1792
2210
  else optsRef.current = buildWaveOpts();
1793
2211
  };
1794
2212
  rebuildOpts();
@@ -1835,6 +2253,14 @@ function asciiBackground(target, options = {}) {
1835
2253
  renderVoidBackground(ctx, r.width, r.height, time, smoothMouse, optsRef.current);
1836
2254
  } else if (type === "morph") {
1837
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);
1838
2264
  } else {
1839
2265
  renderWaveBackground(ctx, r.width, r.height, time, smoothMouse, optsRef.current);
1840
2266
  }
@@ -1855,6 +2281,178 @@ function asciiBackground(target, options = {}) {
1855
2281
  }
1856
2282
  var mountWaveBackground = asciiBackground;
1857
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
+
1858
2456
  // src/webgl-engine.ts
1859
2457
  var VERT_SRC = (
1860
2458
  /* glsl */
@@ -2117,8 +2715,8 @@ function makeTexture(gl, filter = gl.NEAREST) {
2117
2715
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
2118
2716
  return t;
2119
2717
  }
2120
- var ANIM_STYLES = ["none", "wave", "pulse", "rain", "breathe", "sparkle", "glitch", "spiral", "typewriter", "scatter", "waveField"];
2121
- 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"];
2122
2720
  var COLOR_MODES = ["grayscale", "fullcolor", "matrix", "accent"];
2123
2721
  function tryCreateWebGLRenderer(canvas) {
2124
2722
  const glOrNull = canvas.getContext("webgl2", {
@@ -2303,6 +2901,6 @@ function tryCreateWebGLRenderer(canvas) {
2303
2901
  }
2304
2902
  }
2305
2903
 
2306
- 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 };
2307
2905
  //# sourceMappingURL=index.js.map
2308
2906
  //# sourceMappingURL=index.js.map