asciify-engine 1.0.23 → 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;
@@ -755,17 +864,18 @@ function renderFrameToCanvas(ctx, frame, options, canvasWidth, canvasHeight, tim
755
864
  const fontSize = Math.min(cellW / charAspect, cellH) * 0.9;
756
865
  const useFastRect = fontSize < 6;
757
866
  if (!useFastRect) {
758
- 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`;
759
869
  ctx.textAlign = "center";
760
870
  ctx.textBaseline = "middle";
761
871
  }
762
872
  let charWeights = null;
763
873
  if (useFastRect) {
764
874
  charWeights = {};
765
- const cs = options.charset;
766
- const csLen = cs.length;
875
+ const csChars = [...options.charset];
876
+ const csLen = csChars.length;
767
877
  for (let i = 0; i < csLen; i++) {
768
- charWeights[cs[i]] = Math.max(0.1, (i + 0.3) / csLen);
878
+ charWeights[csChars[i]] = Math.max(0.1, (i + 0.3) / csLen);
769
879
  }
770
880
  }
771
881
  const baseTransform = !useFastRect ? ctx.getTransform() : null;
@@ -1676,6 +1786,292 @@ function renderMorphBackground(ctx, width, height, time, options = {}) {
1676
1786
  }
1677
1787
  }
1678
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
+
1679
2075
  // src/backgrounds/index.ts
1680
2076
  function _parseColor(c) {
1681
2077
  const hex = c.match(/^#([0-9a-f]{3,8})$/i)?.[1];
@@ -1780,6 +2176,24 @@ function asciiBackground(target, options = {}) {
1780
2176
  lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight(),
1781
2177
  color: color ?? renderOpts.color
1782
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
+ });
1783
2197
  const optsRef = { current: buildWaveOpts() };
1784
2198
  const rebuildOpts = () => {
1785
2199
  if (type === "rain") optsRef.current = buildRainOpts();
@@ -1791,6 +2205,10 @@ function asciiBackground(target, options = {}) {
1791
2205
  else if (type === "silk") optsRef.current = buildSilkOpts();
1792
2206
  else if (type === "void") optsRef.current = buildVoidOpts();
1793
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();
1794
2212
  else optsRef.current = buildWaveOpts();
1795
2213
  };
1796
2214
  rebuildOpts();
@@ -1837,6 +2255,14 @@ function asciiBackground(target, options = {}) {
1837
2255
  renderVoidBackground(ctx, r.width, r.height, time, smoothMouse, optsRef.current);
1838
2256
  } else if (type === "morph") {
1839
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);
1840
2266
  } else {
1841
2267
  renderWaveBackground(ctx, r.width, r.height, time, smoothMouse, optsRef.current);
1842
2268
  }
@@ -1857,6 +2283,178 @@ function asciiBackground(target, options = {}) {
1857
2283
  }
1858
2284
  var mountWaveBackground = asciiBackground;
1859
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
+
1860
2458
  // src/webgl-engine.ts
1861
2459
  var VERT_SRC = (
1862
2460
  /* glsl */
@@ -2119,8 +2717,8 @@ function makeTexture(gl, filter = gl.NEAREST) {
2119
2717
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
2120
2718
  return t;
2121
2719
  }
2122
- var ANIM_STYLES = ["none", "wave", "pulse", "rain", "breathe", "sparkle", "glitch", "spiral", "typewriter", "scatter", "waveField"];
2123
- 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"];
2124
2722
  var COLOR_MODES = ["grayscale", "fullcolor", "matrix", "accent"];
2125
2723
  function tryCreateWebGLRenderer(canvas) {
2126
2724
  const glOrNull = canvas.getContext("webgl2", {
@@ -2309,16 +2907,24 @@ exports.ART_STYLE_PRESETS = ART_STYLE_PRESETS;
2309
2907
  exports.CHARSETS = CHARSETS;
2310
2908
  exports.DEFAULT_OPTIONS = DEFAULT_OPTIONS;
2311
2909
  exports.HOVER_PRESETS = HOVER_PRESETS;
2910
+ exports.PALETTE_THEMES = PALETTE_THEMES;
2312
2911
  exports.asciiBackground = asciiBackground;
2912
+ exports.asciiText = asciiText;
2913
+ exports.asciiTextAnsi = asciiTextAnsi;
2313
2914
  exports.asciify = asciify;
2314
2915
  exports.asciifyGif = asciifyGif;
2315
2916
  exports.asciifyVideo = asciifyVideo;
2917
+ exports.createRecorder = createRecorder;
2316
2918
  exports.generateAnimatedEmbedCode = generateAnimatedEmbedCode;
2317
2919
  exports.generateEmbedCode = generateEmbedCode;
2318
2920
  exports.gifToAsciiFrames = gifToAsciiFrames;
2319
2921
  exports.imageToAsciiFrame = imageToAsciiFrame;
2320
2922
  exports.mountWaveBackground = mountWaveBackground;
2923
+ exports.recordAndDownload = recordAndDownload;
2321
2924
  exports.renderAuroraBackground = renderAuroraBackground;
2925
+ exports.renderCircuitBackground = renderCircuitBackground;
2926
+ exports.renderDnaBackground = renderDnaBackground;
2927
+ exports.renderFireBackground = renderFireBackground;
2322
2928
  exports.renderFrameToCanvas = renderFrameToCanvas;
2323
2929
  exports.renderGridBackground = renderGridBackground;
2324
2930
  exports.renderMorphBackground = renderMorphBackground;
@@ -2327,6 +2933,7 @@ exports.renderPulseBackground = renderPulseBackground;
2327
2933
  exports.renderRainBackground = renderRainBackground;
2328
2934
  exports.renderSilkBackground = renderSilkBackground;
2329
2935
  exports.renderStarsBackground = renderStarsBackground;
2936
+ exports.renderTerrainBackground = renderTerrainBackground;
2330
2937
  exports.renderVoidBackground = renderVoidBackground;
2331
2938
  exports.renderWaveBackground = renderWaveBackground;
2332
2939
  exports.tryCreateWebGLRenderer = tryCreateWebGLRenderer;