asciify-engine 1.0.16 → 1.0.18

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
@@ -113,6 +113,8 @@ var HOVER_PRESETS = {
113
113
  options: { hoverStrength: 0.5, hoverEffect: "glow", hoverRadius: 0.15, hoverColor: "#60d5f7" }
114
114
  }
115
115
  };
116
+
117
+ // src/core/utils.ts
116
118
  function createOffscreenCanvas(width, height) {
117
119
  const canvas = document.createElement("canvas");
118
120
  canvas.width = width;
@@ -143,29 +145,29 @@ var BAYER_4X4 = [
143
145
  [3, 11, 1, 9],
144
146
  [15, 7, 13, 5]
145
147
  ];
146
- var _GRAY_LUT = new Array(256);
147
- var _GREEN_LUT = new Array(256);
148
- for (let _i = 0; _i < 256; _i++) {
149
- _GRAY_LUT[_i] = `rgb(${_i},${_i},${_i})`;
150
- _GREEN_LUT[_i] = `rgb(0,${_i},0)`;
151
- }
152
148
  function applyDither(lum, x, y, strength) {
153
149
  if (strength <= 0) return lum;
154
150
  const threshold = (BAYER_4X4[y % 4][x % 4] / 16 - 0.5) * strength * 128;
155
151
  return Math.max(0, Math.min(255, lum + threshold));
156
152
  }
153
+ var GRAY_LUT = new Array(256);
154
+ var GREEN_LUT = new Array(256);
155
+ for (let _i = 0; _i < 256; _i++) {
156
+ GRAY_LUT[_i] = `rgb(${_i},${_i},${_i})`;
157
+ GREEN_LUT[_i] = `rgb(0,${_i},0)`;
158
+ }
157
159
  function getCellColorStr(cell, colorMode, acR, acG, acB) {
158
160
  switch (colorMode) {
159
161
  case "fullcolor":
160
162
  return `rgb(${cell.r},${cell.g},${cell.b})`;
161
163
  case "matrix":
162
- return _GREEN_LUT[0.299 * cell.r + 0.587 * cell.g + 0.114 * cell.b | 0];
164
+ return GREEN_LUT[0.299 * cell.r + 0.587 * cell.g + 0.114 * cell.b | 0];
163
165
  case "accent": {
164
166
  const ab = (0.299 * cell.r + 0.587 * cell.g + 0.114 * cell.b) / 255;
165
167
  return `rgb(${acR * ab | 0},${acG * ab | 0},${acB * ab | 0})`;
166
168
  }
167
169
  default:
168
- return _GRAY_LUT[0.299 * cell.r + 0.587 * cell.g + 0.114 * cell.b | 0];
170
+ return GRAY_LUT[0.299 * cell.r + 0.587 * cell.g + 0.114 * cell.b | 0];
169
171
  }
170
172
  }
171
173
  var _colorRGB = [0, 0, 0];
@@ -200,6 +202,11 @@ function getCellColorRGB(cell, colorMode, acR, acG, acB) {
200
202
  }
201
203
  return _colorRGB;
202
204
  }
205
+
206
+ // src/core/animation.ts
207
+ function smoothstep(t) {
208
+ return t * t * (3 - 2 * t);
209
+ }
203
210
  function getAnimationMultiplier(x, y, cols, rows, time, style, speed) {
204
211
  if (style === "none") return 1;
205
212
  const t = time * speed;
@@ -219,8 +226,8 @@ function getAnimationMultiplier(x, y, cols, rows, time, style, speed) {
219
226
  }
220
227
  case "rain": {
221
228
  const drop = Math.sin(y / rows * Math.PI * 8 - t * 5 + x * 0.3) * 0.5 + 0.5;
222
- const fade = Math.sin(x / cols * Math.PI * 2 + t) * 0.3 + 0.7;
223
- return 0.1 + 0.9 * drop * fade;
229
+ const fade2 = Math.sin(x / cols * Math.PI * 2 + t) * 0.3 + 0.7;
230
+ return 0.1 + 0.9 * drop * fade2;
224
231
  }
225
232
  case "breathe": {
226
233
  const breathe = Math.sin(t * 2) * 0.3 + 0.7;
@@ -260,8 +267,8 @@ function getAnimationMultiplier(x, y, cols, rows, time, style, speed) {
260
267
  const revealPoint = progress * totalCells * 1.3;
261
268
  const dist = cellIndex - revealPoint;
262
269
  if (dist > 0) return 0;
263
- const fade = Math.max(0, 1 + dist / (totalCells * 0.15));
264
- return Math.min(1, fade);
270
+ const fade2 = Math.max(0, 1 + dist / (totalCells * 0.15));
271
+ return Math.min(1, fade2);
265
272
  }
266
273
  case "scatter": {
267
274
  const scx = cols / 2;
@@ -282,6 +289,200 @@ function getAnimationMultiplier(x, y, cols, rows, time, style, speed) {
282
289
  return 1;
283
290
  }
284
291
  }
292
+ var _hoverResult = { scale: 1, offsetX: 0, offsetY: 0, glow: 0, colorBlend: 0, proximity: 0 };
293
+ function computeHoverEffect(nx, ny, hoverX, hoverY, hoverIntensity, strength, cellW, cellH, effect = "spotlight", radiusFactor = 0.5) {
294
+ const dx = nx - hoverX;
295
+ const dy = ny - hoverY;
296
+ const distSq = dx * dx + dy * dy;
297
+ const radius = 0.08 + radiusFactor * 0.35 + strength * 0.04;
298
+ if (distSq >= radius * radius) {
299
+ _hoverResult.scale = 1;
300
+ _hoverResult.offsetX = 0;
301
+ _hoverResult.offsetY = 0;
302
+ _hoverResult.glow = 0;
303
+ _hoverResult.colorBlend = 0;
304
+ _hoverResult.proximity = 0;
305
+ return _hoverResult;
306
+ }
307
+ const dist = Math.sqrt(distSq);
308
+ const t = 1 - dist / radius;
309
+ const eased = smoothstep(t) * hoverIntensity;
310
+ let scale = 1;
311
+ let offsetX = 0;
312
+ let offsetY = 0;
313
+ let glow = 0;
314
+ let colorBlend = 0;
315
+ switch (effect) {
316
+ case "spotlight": {
317
+ scale = 1 + eased * strength * 1.8;
318
+ const angle = Math.atan2(dy, dx);
319
+ const pushForce = eased * eased * strength * 0.6;
320
+ offsetX = Math.cos(angle) * pushForce * cellW;
321
+ offsetY = Math.sin(angle) * pushForce * cellH;
322
+ glow = eased * strength * 0.4;
323
+ colorBlend = eased * eased * strength * 0.25;
324
+ break;
325
+ }
326
+ case "magnify":
327
+ scale = 1 + eased * strength * 2.5;
328
+ glow = eased * strength * 0.15;
329
+ break;
330
+ case "repel": {
331
+ scale = 1 + eased * strength * 0.3;
332
+ const angle2 = Math.atan2(dy, dx);
333
+ const push = eased * eased * strength * 1.2;
334
+ offsetX = Math.cos(angle2) * push * cellW;
335
+ offsetY = Math.sin(angle2) * push * cellH;
336
+ break;
337
+ }
338
+ case "glow":
339
+ glow = eased * strength * 0.8;
340
+ colorBlend = eased * strength * 0.4;
341
+ break;
342
+ case "colorShift":
343
+ scale = 1 + eased * strength * 0.4;
344
+ glow = eased * strength * 0.2;
345
+ colorBlend = eased * strength * 0.7;
346
+ break;
347
+ }
348
+ _hoverResult.scale = scale;
349
+ _hoverResult.offsetX = offsetX;
350
+ _hoverResult.offsetY = offsetY;
351
+ _hoverResult.glow = glow;
352
+ _hoverResult.colorBlend = colorBlend;
353
+ _hoverResult.proximity = eased;
354
+ return _hoverResult;
355
+ }
356
+
357
+ // src/backgrounds/_shared.ts
358
+ function parseColor(c) {
359
+ const hex = c.match(/^#([0-9a-f]{3,8})$/i)?.[1];
360
+ if (hex) {
361
+ const h = hex.length <= 4 ? hex.split("").map((x) => parseInt(x + x, 16)) : [parseInt(hex.slice(0, 2), 16), parseInt(hex.slice(2, 4), 16), parseInt(hex.slice(4, 6), 16)];
362
+ return { r: h[0], g: h[1], b: h[2] };
363
+ }
364
+ const rgb = c.match(/rgba?\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)/i);
365
+ if (rgb) return { r: +rgb[1], g: +rgb[2], b: +rgb[3] };
366
+ return null;
367
+ }
368
+ function fade(t) {
369
+ return t * t * t * (t * (t * 6 - 15) + 10);
370
+ }
371
+ function lerp(a, b, t) {
372
+ return a + (b - a) * t;
373
+ }
374
+ function hash2(ix, iy) {
375
+ let n = ix * 127 + iy * 311;
376
+ n = n >> 13 ^ n;
377
+ return (n * (n * n * 15731 + 789221) + 1376312589 & 2147483647) / 1073741823 - 1;
378
+ }
379
+ function vnoise(x, y) {
380
+ const ix = Math.floor(x), iy = Math.floor(y);
381
+ const fx = x - ix, fy = y - iy;
382
+ const ux = fade(fx), uy = fade(fy);
383
+ const v00 = hash2(ix, iy);
384
+ const v10 = hash2(ix + 1, iy);
385
+ const v01 = hash2(ix, iy + 1);
386
+ const v11 = hash2(ix + 1, iy + 1);
387
+ return lerp(lerp(v00, v10, ux), lerp(v01, v11, ux), uy);
388
+ }
389
+ function fbm(x, y) {
390
+ return (vnoise(x, y) * 0.5 + vnoise(x * 2.1, y * 2.1) * 0.25 + vnoise(x * 4.3, y * 4.3) * 0.125) / 0.875;
391
+ }
392
+
393
+ // src/backgrounds/wave.ts
394
+ function renderWaveBackground(ctx, width, height, time, mousePos = { x: 0.5, y: 0.5 }, options = {}) {
395
+ const {
396
+ fontSize = 13,
397
+ charAspect = 0.62,
398
+ lineHeightRatio = 1.4,
399
+ chars = " .:-=+*#%@",
400
+ baseColor = null,
401
+ accentColor = "#d4ff00",
402
+ accentThreshold = 0.52,
403
+ mouseInfluence = 0.55,
404
+ mouseFalloff = 2.8,
405
+ speed = 1,
406
+ vortex = true,
407
+ sparkles = true,
408
+ breathe = true,
409
+ lightMode = false
410
+ } = options;
411
+ const charW = fontSize * charAspect;
412
+ const lineH = fontSize * lineHeightRatio;
413
+ const cols = Math.ceil(width / charW);
414
+ const rows = Math.ceil(height / lineH);
415
+ const mx = mousePos.x;
416
+ const my = mousePos.y;
417
+ let acR = 212, acG = 255, acB = 0;
418
+ if (accentColor) {
419
+ const hex = accentColor.replace("#", "");
420
+ if (hex.length === 6) {
421
+ acR = parseInt(hex.slice(0, 2), 16);
422
+ acG = parseInt(hex.slice(2, 4), 16);
423
+ acB = parseInt(hex.slice(4, 6), 16);
424
+ }
425
+ }
426
+ ctx.clearRect(0, 0, width, height);
427
+ ctx.font = `${fontSize}px "JetBrains Mono", monospace`;
428
+ ctx.textBaseline = "top";
429
+ const t = time * speed;
430
+ const breatheAmp = breathe ? (Math.sin(t * 0.22) * 0.5 + 0.5) * 0.12 : 0;
431
+ for (let row = 0; row < rows; row++) {
432
+ for (let col = 0; col < cols; col++) {
433
+ const nx = col / cols;
434
+ const ny = row / rows;
435
+ const w1 = Math.sin(col * 0.08 + row * 0.05 + t * 0.6) * 0.5 + 0.5;
436
+ const w2 = Math.sin(col * 0.03 - row * 0.07 + t * 0.4) * 0.5 + 0.5;
437
+ const w3 = Math.sin(col * 0.05 + row * 0.03 + t * 0.8) * 0.5 + 0.5;
438
+ const sinePart = (w1 + w2 + w3) / 3;
439
+ const noiseScale = 0.045;
440
+ const noiseShift = t * 0.08;
441
+ const noisePart = fbm(col * noiseScale + noiseShift, row * noiseScale * 1.4 - noiseShift * 0.7) * 0.5 + 0.5;
442
+ const driftFreq = 0.06;
443
+ const driftPart = Math.sin((col + row * 0.65) * driftFreq + t * 1.1) * 0.5 + 0.5;
444
+ const wavePart = sinePart * 0.45 + noisePart * 0.35 + driftPart * 0.2 + breatheAmp;
445
+ const dxRaw = nx - mx;
446
+ const dyRaw = ny - my;
447
+ const distRaw = Math.sqrt(dxRaw * dxRaw + dyRaw * dyRaw);
448
+ let vortexBump = 0;
449
+ if (vortex && distRaw < 0.35) {
450
+ const angle = Math.atan2(dyRaw, dxRaw);
451
+ const swirl = Math.sin(angle * 4 + t * 2.2 - distRaw * 14);
452
+ const falloff = Math.max(0, 1 - distRaw / 0.35);
453
+ vortexBump = swirl * falloff * falloff * 0.22;
454
+ }
455
+ const proximity = Math.max(0, 1 - distRaw * mouseFalloff);
456
+ const intensity = wavePart * (1 - mouseInfluence) + (proximity + vortexBump * 0.5) * mouseInfluence + vortexBump * 0.15;
457
+ const clamped = Math.min(1, Math.max(0, intensity));
458
+ let finalIntensity = clamped;
459
+ if (sparkles && clamped > 0.72) {
460
+ const bucket = Math.floor(t * 8);
461
+ const sparkleSeed = hash2(col * 7 + bucket * 3, row * 11 + bucket);
462
+ if (sparkleSeed > 0.88) {
463
+ finalIntensity = Math.min(1, clamped + (sparkleSeed - 0.88) * 4);
464
+ }
465
+ }
466
+ const charIdx = Math.floor(finalIntensity * (chars.length - 1));
467
+ if (chars[charIdx] === " ") continue;
468
+ const alpha = 0.015 + finalIntensity * 0.07;
469
+ const isAccent = finalIntensity > accentThreshold;
470
+ if (isAccent) {
471
+ const accentAlpha = Math.min(lightMode ? 0.4 : 0.28, alpha * (lightMode ? 4 : 2.8));
472
+ ctx.fillStyle = `rgba(${acR},${acG},${acB},${accentAlpha})`;
473
+ } else if (baseColor) {
474
+ ctx.fillStyle = baseColor.replace("{a}", String(alpha));
475
+ } else if (lightMode) {
476
+ ctx.fillStyle = `rgba(0,0,0,${alpha * 1.3})`;
477
+ } else {
478
+ ctx.fillStyle = `rgba(255,255,255,${alpha})`;
479
+ }
480
+ ctx.fillText(chars[charIdx], col * charW, row * lineH);
481
+ }
482
+ }
483
+ }
484
+
485
+ // src/core/renderer.ts
285
486
  function imageToAsciiFrame(source, options, targetWidth, targetHeight) {
286
487
  const srcWidth = source instanceof HTMLVideoElement ? source.videoWidth : source.width;
287
488
  const srcHeight = source instanceof HTMLVideoElement ? source.videoHeight : source.height;
@@ -402,80 +603,10 @@ async function gifToAsciiFrames(buffer, options, targetWidth, targetHeight, onPr
402
603
  }
403
604
  return { frames, cols, rows, fps };
404
605
  }
405
- function smoothstep(t) {
406
- return t * t * (3 - 2 * t);
407
- }
408
- var _hoverResult = { scale: 1, offsetX: 0, offsetY: 0, glow: 0, colorBlend: 0, proximity: 0 };
409
- function computeHoverEffect(nx, ny, hoverX, hoverY, hoverIntensity, strength, cellW, cellH, effect = "spotlight", radiusFactor = 0.5) {
410
- const dx = nx - hoverX;
411
- const dy = ny - hoverY;
412
- const distSq = dx * dx + dy * dy;
413
- const radius = 0.08 + radiusFactor * 0.35 + strength * 0.04;
414
- if (distSq >= radius * radius) {
415
- _hoverResult.scale = 1;
416
- _hoverResult.offsetX = 0;
417
- _hoverResult.offsetY = 0;
418
- _hoverResult.glow = 0;
419
- _hoverResult.colorBlend = 0;
420
- _hoverResult.proximity = 0;
421
- return _hoverResult;
422
- }
423
- const dist = Math.sqrt(distSq);
424
- const t = 1 - dist / radius;
425
- const eased = smoothstep(t) * hoverIntensity;
426
- let scale = 1;
427
- let offsetX = 0;
428
- let offsetY = 0;
429
- let glow = 0;
430
- let colorBlend = 0;
431
- switch (effect) {
432
- case "spotlight": {
433
- scale = 1 + eased * strength * 1.8;
434
- const angle = Math.atan2(dy, dx);
435
- const pushForce = eased * eased * strength * 0.6;
436
- offsetX = Math.cos(angle) * pushForce * cellW;
437
- offsetY = Math.sin(angle) * pushForce * cellH;
438
- glow = eased * strength * 0.4;
439
- colorBlend = eased * eased * strength * 0.25;
440
- break;
441
- }
442
- case "magnify":
443
- scale = 1 + eased * strength * 2.5;
444
- glow = eased * strength * 0.15;
445
- break;
446
- case "repel": {
447
- scale = 1 + eased * strength * 0.3;
448
- const angle2 = Math.atan2(dy, dx);
449
- const push = eased * eased * strength * 1.2;
450
- offsetX = Math.cos(angle2) * push * cellW;
451
- offsetY = Math.sin(angle2) * push * cellH;
452
- break;
453
- }
454
- case "glow":
455
- glow = eased * strength * 0.8;
456
- colorBlend = eased * strength * 0.4;
457
- break;
458
- case "colorShift":
459
- scale = 1 + eased * strength * 0.4;
460
- glow = eased * strength * 0.2;
461
- colorBlend = eased * strength * 0.7;
462
- break;
463
- }
464
- _hoverResult.scale = scale;
465
- _hoverResult.offsetX = offsetX;
466
- _hoverResult.offsetY = offsetY;
467
- _hoverResult.glow = glow;
468
- _hoverResult.colorBlend = colorBlend;
469
- _hoverResult.proximity = eased;
470
- return _hoverResult;
471
- }
472
606
  function renderFrameToCanvas(ctx, frame, options, canvasWidth, canvasHeight, time = 0, hoverPos) {
473
607
  if (options.animationStyle === "waveField") {
474
608
  const mouseNorm = hoverPos ? { x: hoverPos.x, y: hoverPos.y } : { x: 0.5, y: 0.5 };
475
609
  const acHexWF = (options.accentColor || "#d4ff00").replace("#", "");
476
- parseInt(acHexWF.substring(0, 2), 16) || 212;
477
- parseInt(acHexWF.substring(2, 4), 16) || 255;
478
- parseInt(acHexWF.substring(4, 6), 16) || 0;
479
610
  renderWaveBackground(ctx, canvasWidth, canvasHeight, time, mouseNorm, {
480
611
  accentColor: `#${acHexWF}`,
481
612
  accentThreshold: 0.52,
@@ -719,6 +850,9 @@ function renderFrameToCanvas(ctx, frame, options, canvasWidth, canvasHeight, tim
719
850
  }
720
851
  ctx.globalAlpha = 1;
721
852
  }
853
+
854
+ // src/core/embed-gen.ts
855
+ var EMBED_CDN_VERSION = "1.0.17";
722
856
  function serializeFrame(frame, fullColor) {
723
857
  const rows = frame.length;
724
858
  const cols = rows > 0 ? frame[0].length : 0;
@@ -742,9 +876,63 @@ function serializeFrame(frame, fullColor) {
742
876
  for (let j = 0; j < buf.length; j++) binary += String.fromCharCode(buf[j]);
743
877
  return btoa(binary);
744
878
  }
745
- async function asciify(source, canvas, { fontSize = 10, style = "classic", options = {} } = {}) {
746
- let el;
747
- if (typeof source === "string") {
879
+ function buildEmbedOpts(options, rows, cols, width, height, fps, animated) {
880
+ const o = {
881
+ r: rows,
882
+ c: cols,
883
+ w: width,
884
+ h: height,
885
+ cs: options.charset,
886
+ cm: options.colorMode,
887
+ as: options.animationStyle,
888
+ sp: options.animationSpeed,
889
+ inv: options.invert,
890
+ hs: options.hoverStrength,
891
+ hr: options.hoverRadius,
892
+ he: options.hoverEffect,
893
+ hc: options.hoverColor,
894
+ dr: options.dotSizeRatio,
895
+ dots: options.renderMode === "dots"
896
+ };
897
+ if (options.colorMode === "accent") o.ac = options.accentColor;
898
+ if (fps !== void 0) o.fps = fps;
899
+ if (animated) o.anim = true;
900
+ return JSON.stringify(o);
901
+ }
902
+ var CDN_SCRIPT = `<script src="https://cdn.jsdelivr.net/npm/asciify-engine@${EMBED_CDN_VERSION}/dist/embed.js" async></script>`;
903
+ function generateEmbedCode(frame, options, width, height) {
904
+ const rows = frame.length;
905
+ if (rows === 0) return "";
906
+ const cols = frame[0].length;
907
+ const isFullColor = options.colorMode === "fullcolor";
908
+ const data = serializeFrame(frame, isFullColor);
909
+ const id = `ar-${Math.random().toString(36).slice(2, 9)}`;
910
+ const opts = buildEmbedOpts(options, rows, cols, width, height);
911
+ return `<!-- Asciify Embed -->
912
+ <canvas id="${id}" data-asciify-opts='${opts}' width="${width}" height="${height}"></canvas>
913
+ <script type="application/json" id="${id}-d">"${data}"</script>
914
+ ${CDN_SCRIPT}
915
+ <!-- /Asciify Embed -->`;
916
+ }
917
+ function generateAnimatedEmbedCode(frames, options, fps, width, height) {
918
+ if (frames.length === 0) return "";
919
+ const rows = frames[0].length;
920
+ const cols = frames[0][0].length;
921
+ const isFullColor = options.colorMode === "fullcolor";
922
+ const allData = frames.map((f) => serializeFrame(f, isFullColor));
923
+ const id = `ar-${Math.random().toString(36).slice(2, 9)}`;
924
+ const opts = buildEmbedOpts(options, rows, cols, width, height, fps, true);
925
+ return `<!-- Asciify Animated Embed -->
926
+ <canvas id="${id}" data-asciify-opts='${opts}' width="${width}" height="${height}"></canvas>
927
+ <script type="application/json" id="${id}-d">${JSON.stringify(allData)}</script>
928
+ ${CDN_SCRIPT}
929
+ <!-- /Asciify Animated Embed -->`;
930
+ }
931
+
932
+ // src/core/simple-api.ts
933
+ async function asciify(source, canvas, { fontSize = 10, style = "classic", options = {} } = {}) {
934
+ let el;
935
+ if (typeof source === "string") {
748
936
  const img = new Image();
749
937
  img.crossOrigin = "anonymous";
750
938
  await new Promise((resolve, reject) => {
@@ -832,173 +1020,8 @@ async function asciifyVideo(source, canvas, { fontSize = 10, style = "classic",
832
1020
  cancelAnimationFrame(animId);
833
1021
  };
834
1022
  }
835
- var EMBED_CDN_VERSION = "1.0.16";
836
- function buildEmbedOpts(options, rows, cols, width, height, fps, animated) {
837
- const o = {
838
- r: rows,
839
- c: cols,
840
- w: width,
841
- h: height,
842
- cs: options.charset,
843
- cm: options.colorMode,
844
- as: options.animationStyle,
845
- sp: options.animationSpeed,
846
- inv: options.invert,
847
- hs: options.hoverStrength,
848
- hr: options.hoverRadius,
849
- he: options.hoverEffect,
850
- hc: options.hoverColor,
851
- dr: options.dotSizeRatio,
852
- dots: options.renderMode === "dots"
853
- };
854
- if (options.colorMode === "accent") o.ac = options.accentColor;
855
- if (fps !== void 0) o.fps = fps;
856
- if (animated) o.anim = true;
857
- return JSON.stringify(o);
858
- }
859
- var CDN_SCRIPT = `<script src="https://cdn.jsdelivr.net/npm/asciify-engine@${EMBED_CDN_VERSION}/dist/embed.js" async></script>`;
860
- function generateEmbedCode(frame, options, width, height) {
861
- const rows = frame.length;
862
- if (rows === 0) return "";
863
- const cols = frame[0].length;
864
- const isFullColor = options.colorMode === "fullcolor";
865
- const data = serializeFrame(frame, isFullColor);
866
- const id = `ar-${Math.random().toString(36).slice(2, 9)}`;
867
- const opts = buildEmbedOpts(options, rows, cols, width, height);
868
- return `<!-- Asciify Embed -->
869
- <canvas id="${id}" data-asciify-opts='${opts}' width="${width}" height="${height}"></canvas>
870
- <script type="application/json" id="${id}-d">"${data}"</script>
871
- ${CDN_SCRIPT}
872
- <!-- /Asciify Embed -->`;
873
- }
874
- function generateAnimatedEmbedCode(frames, options, fps, width, height) {
875
- if (frames.length === 0) return "";
876
- const rows = frames[0].length;
877
- const cols = frames[0][0].length;
878
- const isFullColor = options.colorMode === "fullcolor";
879
- const allData = frames.map((f) => serializeFrame(f, isFullColor));
880
- const id = `ar-${Math.random().toString(36).slice(2, 9)}`;
881
- const opts = buildEmbedOpts(options, rows, cols, width, height, fps, true);
882
- return `<!-- Asciify Animated Embed -->
883
- <canvas id="${id}" data-asciify-opts='${opts}' width="${width}" height="${height}"></canvas>
884
- <script type="application/json" id="${id}-d">${JSON.stringify(allData)}</script>
885
- ${CDN_SCRIPT}
886
- <!-- /Asciify Animated Embed -->`;
887
- }
888
- function _fade(t) {
889
- return t * t * t * (t * (t * 6 - 15) + 10);
890
- }
891
- function _lerp(a, b, t) {
892
- return a + (b - a) * t;
893
- }
894
- function _hash2(ix, iy) {
895
- let n = ix * 127 + iy * 311;
896
- n = n >> 13 ^ n;
897
- return (n * (n * n * 15731 + 789221) + 1376312589 & 2147483647) / 1073741823 - 1;
898
- }
899
- function _vnoise(x, y) {
900
- const ix = Math.floor(x), iy = Math.floor(y);
901
- const fx = x - ix, fy = y - iy;
902
- const ux = _fade(fx), uy = _fade(fy);
903
- const v00 = _hash2(ix, iy);
904
- const v10 = _hash2(ix + 1, iy);
905
- const v01 = _hash2(ix, iy + 1);
906
- const v11 = _hash2(ix + 1, iy + 1);
907
- return _lerp(_lerp(v00, v10, ux), _lerp(v01, v11, ux), uy);
908
- }
909
- function _fbm(x, y) {
910
- return (_vnoise(x, y) * 0.5 + _vnoise(x * 2.1, y * 2.1) * 0.25 + _vnoise(x * 4.3, y * 4.3) * 0.125) / 0.875;
911
- }
912
- function renderWaveBackground(ctx, width, height, time, mousePos = { x: 0.5, y: 0.5 }, options = {}) {
913
- const {
914
- fontSize = 13,
915
- charAspect = 0.62,
916
- lineHeightRatio = 1.4,
917
- chars = " .:-=+*#%@",
918
- baseColor = null,
919
- accentColor = "#d4ff00",
920
- accentThreshold = 0.52,
921
- mouseInfluence = 0.55,
922
- mouseFalloff = 2.8,
923
- speed = 1,
924
- vortex = true,
925
- sparkles = true,
926
- breathe = true,
927
- lightMode = false
928
- } = options;
929
- const charW = fontSize * charAspect;
930
- const lineH = fontSize * lineHeightRatio;
931
- const cols = Math.ceil(width / charW);
932
- const rows = Math.ceil(height / lineH);
933
- const mx = mousePos.x;
934
- const my = mousePos.y;
935
- let acR = 212, acG = 255, acB = 0;
936
- if (accentColor) {
937
- const hex = accentColor.replace("#", "");
938
- if (hex.length === 6) {
939
- acR = parseInt(hex.slice(0, 2), 16);
940
- acG = parseInt(hex.slice(2, 4), 16);
941
- acB = parseInt(hex.slice(4, 6), 16);
942
- }
943
- }
944
- ctx.clearRect(0, 0, width, height);
945
- ctx.font = `${fontSize}px "JetBrains Mono", monospace`;
946
- ctx.textBaseline = "top";
947
- const t = time * speed;
948
- const breatheAmp = breathe ? (Math.sin(t * 0.22) * 0.5 + 0.5) * 0.12 : 0;
949
- for (let row = 0; row < rows; row++) {
950
- for (let col = 0; col < cols; col++) {
951
- const nx = col / cols;
952
- const ny = row / rows;
953
- const w1 = Math.sin(col * 0.08 + row * 0.05 + t * 0.6) * 0.5 + 0.5;
954
- const w2 = Math.sin(col * 0.03 - row * 0.07 + t * 0.4) * 0.5 + 0.5;
955
- const w3 = Math.sin(col * 0.05 + row * 0.03 + t * 0.8) * 0.5 + 0.5;
956
- const sinePart = (w1 + w2 + w3) / 3;
957
- const noiseScale = 0.045;
958
- const noiseShift = t * 0.08;
959
- const noisePart = _fbm(col * noiseScale + noiseShift, row * noiseScale * 1.4 - noiseShift * 0.7) * 0.5 + 0.5;
960
- const driftFreq = 0.06;
961
- const driftPart = Math.sin((col + row * 0.65) * driftFreq + t * 1.1) * 0.5 + 0.5;
962
- const wavePart = sinePart * 0.45 + noisePart * 0.35 + driftPart * 0.2 + breatheAmp;
963
- const dxRaw = nx - mx;
964
- const dyRaw = ny - my;
965
- const distRaw = Math.sqrt(dxRaw * dxRaw + dyRaw * dyRaw);
966
- let vortexBump = 0;
967
- if (vortex && distRaw < 0.35) {
968
- const angle = Math.atan2(dyRaw, dxRaw);
969
- const swirl = Math.sin(angle * 4 + t * 2.2 - distRaw * 14);
970
- const falloff = Math.max(0, 1 - distRaw / 0.35);
971
- vortexBump = swirl * falloff * falloff * 0.22;
972
- }
973
- const proximity = Math.max(0, 1 - distRaw * mouseFalloff);
974
- const intensity = wavePart * (1 - mouseInfluence) + (proximity + vortexBump * 0.5) * mouseInfluence + vortexBump * 0.15;
975
- const clamped = Math.min(1, Math.max(0, intensity));
976
- let finalIntensity = clamped;
977
- if (sparkles && clamped > 0.72) {
978
- const bucket = Math.floor(t * 8);
979
- const sparkleSeed = _hash2(col * 7 + bucket * 3, row * 11 + bucket);
980
- if (sparkleSeed > 0.88) {
981
- finalIntensity = Math.min(1, clamped + (sparkleSeed - 0.88) * 4);
982
- }
983
- }
984
- const charIdx = Math.floor(finalIntensity * (chars.length - 1));
985
- if (chars[charIdx] === " ") continue;
986
- const alpha = 0.015 + finalIntensity * 0.07;
987
- const isAccent = finalIntensity > accentThreshold;
988
- if (isAccent) {
989
- const accentAlpha = Math.min(lightMode ? 0.4 : 0.28, alpha * (lightMode ? 4 : 2.8));
990
- ctx.fillStyle = `rgba(${acR},${acG},${acB},${accentAlpha})`;
991
- } else if (baseColor) {
992
- ctx.fillStyle = baseColor.replace("{a}", String(alpha));
993
- } else if (lightMode) {
994
- ctx.fillStyle = `rgba(0,0,0,${alpha * 1.3})`;
995
- } else {
996
- ctx.fillStyle = `rgba(255,255,255,${alpha})`;
997
- }
998
- ctx.fillText(chars[charIdx], col * charW, row * lineH);
999
- }
1000
- }
1001
- }
1023
+
1024
+ // src/backgrounds/rain.ts
1002
1025
  function renderRainBackground(ctx, width, height, time, options = {}) {
1003
1026
  const {
1004
1027
  fontSize = 13,
@@ -1024,7 +1047,7 @@ function renderRainBackground(ctx, width, height, time, options = {}) {
1024
1047
  bb = 0;
1025
1048
  }
1026
1049
  if (color) {
1027
- const p = _parseColor(color);
1050
+ const p = parseColor(color);
1028
1051
  if (p) {
1029
1052
  br = p.r;
1030
1053
  bg = p.g;
@@ -1032,7 +1055,7 @@ function renderRainBackground(ctx, width, height, time, options = {}) {
1032
1055
  }
1033
1056
  }
1034
1057
  let acR = 212, acG = 255, acB = 0;
1035
- const ap = _parseColor(accentColor);
1058
+ const ap = parseColor(accentColor);
1036
1059
  if (ap) {
1037
1060
  acR = ap.r;
1038
1061
  acG = ap.g;
@@ -1040,28 +1063,30 @@ function renderRainBackground(ctx, width, height, time, options = {}) {
1040
1063
  }
1041
1064
  const period = rows + tailLength;
1042
1065
  for (let c = 0; c < cols; c++) {
1043
- if (_hash2(c * 17, 3) > density) continue;
1044
- const colSpeed = (0.5 + _hash2(c * 31, 7) * 1.5) * speed;
1045
- const phase = _hash2(c * 13, 11) * period;
1066
+ if (hash2(c * 17, 3) > density) continue;
1067
+ const colSpeed = (0.5 + hash2(c * 31, 7) * 1.5) * speed;
1068
+ const phase = hash2(c * 13, 11) * period;
1046
1069
  const headRow = Math.floor((time * colSpeed * 7 + phase) % period);
1047
1070
  const x = c * charW;
1048
1071
  for (let k = 0; k <= tailLength; k++) {
1049
1072
  const row = headRow - (tailLength - k);
1050
1073
  if (row < 0 || row >= rows) continue;
1051
1074
  const y = row * lineH;
1052
- const charSeed = _hash2(c * 53 + Math.floor(time * 5 + k), row * 7);
1075
+ const charSeed = hash2(c * 53 + Math.floor(time * 5 + k), row * 7);
1053
1076
  const ch = chars[Math.floor(charSeed * chars.length)];
1054
- const t = k / tailLength;
1077
+ const tRatio = k / tailLength;
1055
1078
  if (k === tailLength) {
1056
1079
  ctx.fillStyle = `rgba(${acR},${acG},${acB},${lightMode ? 0.7 : 0.85})`;
1057
1080
  } else {
1058
- const alpha = lightMode ? t * 0.22 : t * 0.15;
1081
+ const alpha = lightMode ? tRatio * 0.22 : tRatio * 0.15;
1059
1082
  ctx.fillStyle = `rgba(${br},${bg},${bb},${alpha})`;
1060
1083
  }
1061
1084
  ctx.fillText(ch, x, y);
1062
1085
  }
1063
1086
  }
1064
1087
  }
1088
+
1089
+ // src/backgrounds/stars.ts
1065
1090
  function renderStarsBackground(ctx, width, height, time, mousePos = { x: 0.5, y: 0.5 }, options = {}) {
1066
1091
  const {
1067
1092
  fontSize = 14,
@@ -1085,7 +1110,7 @@ function renderStarsBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1085
1110
  bb = 0;
1086
1111
  }
1087
1112
  if (color) {
1088
- const p = _parseColor(color);
1113
+ const p = parseColor(color);
1089
1114
  if (p) {
1090
1115
  br = p.r;
1091
1116
  bg = p.g;
@@ -1093,7 +1118,7 @@ function renderStarsBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1093
1118
  }
1094
1119
  }
1095
1120
  let acR = 212, acG = 255, acB = 0;
1096
- const ap = _parseColor(accentColor);
1121
+ const ap = parseColor(accentColor);
1097
1122
  if (ap) {
1098
1123
  acR = ap.r;
1099
1124
  acG = ap.g;
@@ -1102,9 +1127,9 @@ function renderStarsBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1102
1127
  const charArr = chars.replace(/ /g, "").split("");
1103
1128
  if (charArr.length === 0) return;
1104
1129
  for (let i = 0; i < count; i++) {
1105
- const angle = _hash2(i * 17, 3) * Math.PI * 2;
1106
- const baseSpd = 0.15 + _hash2(i * 31, 7) * 0.85;
1107
- const phase = _hash2(i * 13, 11);
1130
+ const angle = hash2(i * 17, 3) * Math.PI * 2;
1131
+ const baseSpd = 0.15 + hash2(i * 31, 7) * 0.85;
1132
+ const phase = hash2(i * 13, 11);
1108
1133
  const r = (time * baseSpd * speed * 0.22 + phase) % 1;
1109
1134
  const x = cx + Math.cos(angle) * r * maxR;
1110
1135
  const y = cy + Math.sin(angle) * r * maxR;
@@ -1120,68 +1145,81 @@ function renderStarsBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1120
1145
  }
1121
1146
  ctx.textAlign = "left";
1122
1147
  }
1148
+
1149
+ // src/backgrounds/pulse.ts
1123
1150
  function renderPulseBackground(ctx, width, height, time, mousePos = { x: 0.5, y: 0.5 }, options = {}) {
1124
1151
  const {
1125
- fontSize = 13,
1126
- chars = " .:-=+*#%@",
1127
- accentColor = "#d4ff00",
1152
+ fontSize = 14,
1153
+ chars = ". \xB7 \u25CB \u25CE \u25CF",
1154
+ accentColor = "#00ffcc",
1128
1155
  color,
1129
- rings = 6,
1156
+ rings = 5,
1130
1157
  speed = 1,
1131
1158
  sharpness = 4,
1132
1159
  lightMode = false
1133
1160
  } = options;
1134
- const charW = fontSize * 0.62;
1135
- const lineH = fontSize * 1.4;
1136
- const cols = Math.ceil(width / charW);
1137
- const rows = Math.ceil(height / lineH);
1138
1161
  ctx.clearRect(0, 0, width, height);
1139
- ctx.font = `${fontSize}px monospace`;
1140
- ctx.textBaseline = "top";
1141
- const maxR = Math.sqrt(width * width + height * height) * 0.5;
1142
- const ox = mousePos.x * width;
1143
- const oy = mousePos.y * height;
1144
- let br = 255, bg2 = 255, bb = 255;
1162
+ ctx.textBaseline = "middle";
1163
+ ctx.textAlign = "center";
1164
+ const cx = width * mousePos.x;
1165
+ const cy = height * mousePos.y;
1166
+ const maxDist = Math.sqrt(cx * cx + cy * cy) * 1.6 + Math.sqrt(width * width + height * height) * 0.2;
1167
+ let br = 255, bg = 255, bb = 255;
1145
1168
  if (lightMode) {
1146
1169
  br = 0;
1147
- bg2 = 0;
1170
+ bg = 0;
1148
1171
  bb = 0;
1149
1172
  }
1150
1173
  if (color) {
1151
- const p = _parseColor(color);
1174
+ const p = parseColor(color);
1152
1175
  if (p) {
1153
1176
  br = p.r;
1154
- bg2 = p.g;
1177
+ bg = p.g;
1155
1178
  bb = p.b;
1156
1179
  }
1157
1180
  }
1158
- let acR = 212, acG = 255, acB = 0;
1159
- const ap = _parseColor(accentColor);
1181
+ let acR = 0, acG = 255, acB = 204;
1182
+ const ap = parseColor(accentColor);
1160
1183
  if (ap) {
1161
1184
  acR = ap.r;
1162
1185
  acG = ap.g;
1163
1186
  acB = ap.b;
1164
1187
  }
1165
- const t = time * speed;
1188
+ const charArr = chars.replace(/ /g, "").split("");
1189
+ if (charArr.length === 0) return;
1190
+ const cols = Math.ceil(width / fontSize);
1191
+ const rows = Math.ceil(height / fontSize);
1166
1192
  for (let row = 0; row < rows; row++) {
1167
1193
  for (let col = 0; col < cols; col++) {
1168
- const px = col * charW + charW * 0.5;
1169
- const py = row * lineH + lineH * 0.5;
1170
- const dist = Math.sqrt((px - ox) ** 2 + (py - oy) ** 2);
1171
- const norm = dist / maxR;
1172
- const phase = (norm * rings - t * 1.4) % 1;
1173
- const wave = Math.pow(Math.max(0, Math.cos(phase * Math.PI * 2)), sharpness);
1174
- if (wave < 0.02) continue;
1175
- const charIdx = Math.floor(wave * (chars.length - 1));
1176
- const ch = chars[charIdx];
1177
- if (ch === " ") continue;
1178
- const isAccent = wave > 0.75;
1179
- const alpha = lightMode ? wave * 0.22 : wave * 0.14;
1180
- ctx.fillStyle = isAccent ? `rgba(${acR},${acG},${acB},${lightMode ? 0.5 : 0.35})` : `rgba(${br},${bg2},${bb},${alpha})`;
1181
- ctx.fillText(ch, col * charW, row * lineH);
1194
+ const px = col * fontSize + fontSize * 0.5;
1195
+ const py = row * fontSize + fontSize * 0.5;
1196
+ const dx = px - cx;
1197
+ const dy = py - cy;
1198
+ const dist = Math.sqrt(dx * dx + dy * dy);
1199
+ const norm = dist / maxDist;
1200
+ let totalIntensity = 0;
1201
+ for (let r = 0; r < rings; r++) {
1202
+ const phase = r / rings;
1203
+ const t = (time * speed * 0.38 + phase) % 1;
1204
+ const ringDist = Math.abs(norm - t);
1205
+ const ringNorm = Math.max(0, 1 - ringDist * maxDist / (fontSize * (12 - sharpness)));
1206
+ totalIntensity += Math.cos(ringNorm * Math.PI * 0.5) * ringNorm;
1207
+ }
1208
+ totalIntensity = Math.min(1, totalIntensity);
1209
+ if (totalIntensity < 0.02) continue;
1210
+ const isAccent = totalIntensity > 0.6;
1211
+ ctx.font = `${fontSize}px monospace`;
1212
+ const charIdx = Math.floor(totalIntensity * (charArr.length - 1));
1213
+ const ch = charArr[Math.min(charIdx, charArr.length - 1)];
1214
+ const alpha = lightMode ? totalIntensity * 0.32 : totalIntensity * 0.22;
1215
+ ctx.fillStyle = isAccent ? `rgba(${acR},${acG},${acB},${Math.min(lightMode ? 0.5 : 0.4, totalIntensity * 0.55)})` : `rgba(${br},${bg},${bb},${alpha})`;
1216
+ ctx.fillText(ch, px, py);
1182
1217
  }
1183
1218
  }
1219
+ ctx.textAlign = "left";
1184
1220
  }
1221
+
1222
+ // src/backgrounds/noise.ts
1185
1223
  function renderNoiseBackground(ctx, width, height, time, mousePos = { x: 0.5, y: 0.5 }, options = {}) {
1186
1224
  const {
1187
1225
  fontSize = 14,
@@ -1209,7 +1247,7 @@ function renderNoiseBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1209
1247
  bb = 0;
1210
1248
  }
1211
1249
  if (color) {
1212
- const p = _parseColor(color);
1250
+ const p = parseColor(color);
1213
1251
  if (p) {
1214
1252
  br = p.r;
1215
1253
  bgc = p.g;
@@ -1217,7 +1255,7 @@ function renderNoiseBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1217
1255
  }
1218
1256
  }
1219
1257
  let acR = 212, acG = 255, acB = 0;
1220
- const ap = _parseColor(accentColor);
1258
+ const ap = parseColor(accentColor);
1221
1259
  if (ap) {
1222
1260
  acR = ap.r;
1223
1261
  acG = ap.g;
@@ -1229,7 +1267,7 @@ function renderNoiseBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1229
1267
  const fbmN = (x, y) => {
1230
1268
  let v = 0, amp = 0.5, freq = 1, norm = 0;
1231
1269
  for (let o = 0; o < oct; o++) {
1232
- v += _vnoise(x * freq, y * freq) * amp;
1270
+ v += vnoise(x * freq, y * freq) * amp;
1233
1271
  norm += amp;
1234
1272
  amp *= 0.5;
1235
1273
  freq *= 2.1;
@@ -1259,6 +1297,8 @@ function renderNoiseBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1259
1297
  }
1260
1298
  }
1261
1299
  }
1300
+
1301
+ // src/backgrounds/grid.ts
1262
1302
  function renderGridBackground(ctx, width, height, time, mousePos = { x: 0.5, y: 0.5 }, options = {}) {
1263
1303
  const {
1264
1304
  fontSize = 12,
@@ -1285,7 +1325,7 @@ function renderGridBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1285
1325
  bb = 0;
1286
1326
  }
1287
1327
  if (color) {
1288
- const p = _parseColor(color);
1328
+ const p = parseColor(color);
1289
1329
  if (p) {
1290
1330
  br = p.r;
1291
1331
  bgv = p.g;
@@ -1293,7 +1333,7 @@ function renderGridBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1293
1333
  }
1294
1334
  }
1295
1335
  let acR = 212, acG = 255, acB = 0;
1296
- const ap = _parseColor(accentColor);
1336
+ const ap = parseColor(accentColor);
1297
1337
  if (ap) {
1298
1338
  acR = ap.r;
1299
1339
  acG = ap.g;
@@ -1305,7 +1345,7 @@ function renderGridBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1305
1345
  const ny = row / rows;
1306
1346
  const scanPhase = ((ny * bands - t * 0.5) % 1 + 1) % 1;
1307
1347
  const bandIntensity = Math.max(0, 1 - scanPhase / bandWidth);
1308
- const gridSeed = _hash2(col * 3, row * 7);
1348
+ const gridSeed = hash2(col * 3, row * 7);
1309
1349
  const gridBase = (gridSeed * 0.5 + 0.5) * 0.35;
1310
1350
  let glitchBump = 0;
1311
1351
  if (glitch) {
@@ -1313,7 +1353,7 @@ function renderGridBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1313
1353
  const dy = ny - mousePos.y;
1314
1354
  const d = Math.sqrt(dx * dx + dy * dy);
1315
1355
  if (d < 0.18) {
1316
- const g = _hash2(col * 11 + Math.floor(t * 12), row * 5);
1356
+ const g = hash2(col * 11 + Math.floor(t * 12), row * 5);
1317
1357
  glitchBump = Math.max(0, 1 - d / 0.18) * (g > 0.5 ? g - 0.3 : 0);
1318
1358
  }
1319
1359
  }
@@ -1328,6 +1368,8 @@ function renderGridBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1328
1368
  }
1329
1369
  }
1330
1370
  }
1371
+
1372
+ // src/backgrounds/aurora.ts
1331
1373
  function renderAuroraBackground(ctx, width, height, time, mousePos = { x: 0.5, y: 0.5 }, options = {}) {
1332
1374
  const {
1333
1375
  fontSize = 14,
@@ -1354,7 +1396,7 @@ function renderAuroraBackground(ctx, width, height, time, mousePos = { x: 0.5, y
1354
1396
  cb = 0;
1355
1397
  }
1356
1398
  if (color) {
1357
- const p = _parseColor(color);
1399
+ const p = parseColor(color);
1358
1400
  if (p) {
1359
1401
  cr = p.r;
1360
1402
  cg = p.g;
@@ -1362,7 +1404,7 @@ function renderAuroraBackground(ctx, width, height, time, mousePos = { x: 0.5, y
1362
1404
  }
1363
1405
  }
1364
1406
  let acR = 212, acG = 255, acB = 0;
1365
- const ap = _parseColor(accentColor);
1407
+ const ap = parseColor(accentColor);
1366
1408
  if (ap) {
1367
1409
  acR = ap.r;
1368
1410
  acG = ap.g;
@@ -1371,19 +1413,14 @@ function renderAuroraBackground(ctx, width, height, time, mousePos = { x: 0.5, y
1371
1413
  const t = time * speed;
1372
1414
  const layerParams = [];
1373
1415
  for (let l = 0; l < layers; l++) {
1374
- const seed = _hash2(l * 17, l * 31 + 7);
1375
- const seed2 = _hash2(l * 23 + 5, l * 11);
1416
+ const seed = hash2(l * 17, l * 31 + 7);
1417
+ const seed2 = hash2(l * 23 + 5, l * 11);
1376
1418
  layerParams.push({
1377
1419
  fx: 0.8 + seed * 2.2,
1378
- // x spatial frequency
1379
1420
  fy: 1.2 + seed2 * 1.8,
1380
- // y spatial frequency
1381
1421
  phase: seed * Math.PI * 4,
1382
- // phase offset
1383
- dt: (0.3 + _hash2(l * 7, l * 13 + 3) * 0.5) * (l % 2 === 0 ? 1 : -1),
1384
- // drift speed & direction
1385
- amp: 0.55 + _hash2(l * 29, l * 3) * 0.45
1386
- // amplitude weight
1422
+ dt: (0.3 + hash2(l * 7, l * 13 + 3) * 0.5) * (l % 2 === 0 ? 1 : -1),
1423
+ amp: 0.55 + hash2(l * 29, l * 3) * 0.45
1387
1424
  });
1388
1425
  }
1389
1426
  for (let row = 0; row < rows; row++) {
@@ -1417,6 +1454,219 @@ function renderAuroraBackground(ctx, width, height, time, mousePos = { x: 0.5, y
1417
1454
  }
1418
1455
  }
1419
1456
  }
1457
+
1458
+ // src/backgrounds/silk.ts
1459
+ function renderSilkBackground(ctx, width, height, time, options = {}) {
1460
+ const {
1461
+ fontSize = 13,
1462
+ color,
1463
+ accentColor = "#d4ff00",
1464
+ speed = 0.4,
1465
+ layers = 4,
1466
+ turbulence = 0.8,
1467
+ lightMode = false
1468
+ } = options;
1469
+ const charW = fontSize * 0.62;
1470
+ const lineH = fontSize * 1.4;
1471
+ const cols = Math.ceil(width / charW);
1472
+ const rows = Math.ceil(height / lineH);
1473
+ ctx.clearRect(0, 0, width, height);
1474
+ ctx.font = `${fontSize}px monospace`;
1475
+ ctx.textBaseline = "top";
1476
+ let cr = 255, cg = 255, cb = 255;
1477
+ if (lightMode) {
1478
+ cr = 0;
1479
+ cg = 0;
1480
+ cb = 0;
1481
+ }
1482
+ if (color) {
1483
+ const p = parseColor(color);
1484
+ if (p) {
1485
+ cr = p.r;
1486
+ cg = p.g;
1487
+ cb = p.b;
1488
+ }
1489
+ }
1490
+ let acR = 212, acG = 255, acB = 0;
1491
+ const ap = parseColor(accentColor);
1492
+ if (ap) {
1493
+ acR = ap.r;
1494
+ acG = ap.g;
1495
+ acB = ap.b;
1496
+ }
1497
+ const t = time * speed;
1498
+ const dirChars = ["\u2500", "\u2500", "\u254C", "\xB7", "\u254C", "\u2500", "\u2500", "\u254C", "\xB7"];
1499
+ for (let row = 0; row < rows; row++) {
1500
+ const ny = row / rows;
1501
+ for (let col = 0; col < cols; col++) {
1502
+ const nx = col / cols;
1503
+ let angleSum = 0;
1504
+ let intensitySum = 0;
1505
+ for (let l = 0; l < layers; l++) {
1506
+ const ls = hash2(l * 13, l * 7 + 3);
1507
+ const ls2 = hash2(l * 29, l * 11 + 1);
1508
+ const fx = 1.1 + ls * 2.4;
1509
+ const fy = 0.9 + ls2 * 2;
1510
+ const ph = ls * Math.PI * 6;
1511
+ const dr = (0.2 + hash2(l * 41, l * 17) * 0.5) * (l % 2 === 0 ? 1 : -1.3);
1512
+ const u = Math.sin(nx * fx * Math.PI * 2 + t * dr + ph);
1513
+ const v = Math.cos(ny * fy * Math.PI * 2 + t * dr * 0.6 + ph * 1.7);
1514
+ const cross = Math.sin(nx * fy * Math.PI * turbulence + ny * fx * Math.PI * turbulence + t * dr * 0.4);
1515
+ angleSum += Math.atan2(v + cross * 0.3, u);
1516
+ intensitySum += (u * v + 1) * 0.5;
1517
+ }
1518
+ const angle = angleSum / layers;
1519
+ const intensity = Math.min(1, intensitySum / layers);
1520
+ if (intensity < 0.1) continue;
1521
+ const angleNorm = (angle + Math.PI) / (Math.PI * 2);
1522
+ const charIdx = Math.floor(angleNorm * dirChars.length) % dirChars.length;
1523
+ const ch = dirChars[charIdx];
1524
+ const isAccent = intensity > 0.8;
1525
+ const alpha = lightMode ? intensity * 0.16 : intensity * 0.13;
1526
+ ctx.fillStyle = isAccent ? `rgba(${acR},${acG},${acB},${lightMode ? 0.44 : 0.26})` : `rgba(${cr},${cg},${cb},${alpha})`;
1527
+ ctx.fillText(ch, col * charW, row * lineH);
1528
+ }
1529
+ }
1530
+ }
1531
+
1532
+ // src/backgrounds/void.ts
1533
+ function renderVoidBackground(ctx, width, height, time, mousePos = { x: 0.5, y: 0.5 }, options = {}) {
1534
+ const {
1535
+ fontSize = 13,
1536
+ chars = " \xB7:;=+*#%@",
1537
+ color,
1538
+ accentColor = "#d4ff00",
1539
+ speed = 1,
1540
+ radius = 0.38,
1541
+ swirl = 3,
1542
+ lightMode = false
1543
+ } = options;
1544
+ const charW = fontSize * 0.62;
1545
+ const lineH = fontSize * 1.4;
1546
+ const cols = Math.ceil(width / charW);
1547
+ const rows = Math.ceil(height / lineH);
1548
+ const aspect = width / height;
1549
+ ctx.clearRect(0, 0, width, height);
1550
+ ctx.font = `${fontSize}px monospace`;
1551
+ ctx.textBaseline = "top";
1552
+ let cr = 255, cg = 255, cb = 255;
1553
+ if (lightMode) {
1554
+ cr = 0;
1555
+ cg = 0;
1556
+ cb = 0;
1557
+ }
1558
+ if (color) {
1559
+ const p = parseColor(color);
1560
+ if (p) {
1561
+ cr = p.r;
1562
+ cg = p.g;
1563
+ cb = p.b;
1564
+ }
1565
+ }
1566
+ let acR = 212, acG = 255, acB = 0;
1567
+ const ap = parseColor(accentColor);
1568
+ if (ap) {
1569
+ acR = ap.r;
1570
+ acG = ap.g;
1571
+ acB = ap.b;
1572
+ }
1573
+ const t = time * speed;
1574
+ for (let row = 0; row < rows; row++) {
1575
+ const ny = row / rows;
1576
+ for (let col = 0; col < cols; col++) {
1577
+ const nx = col / cols;
1578
+ const dx = (nx - mousePos.x) * aspect;
1579
+ const dy = ny - mousePos.y;
1580
+ const dist = Math.sqrt(dx * dx + dy * dy);
1581
+ const r = dist / radius;
1582
+ if (r > 1) {
1583
+ const outerNoise = hash2(col * 3, row * 7) * Math.max(0, 1 - (r - 1) * 3);
1584
+ if (outerNoise < 0.62) continue;
1585
+ const alpha2 = outerNoise * (lightMode ? 0.05 : 0.04);
1586
+ ctx.fillStyle = `rgba(${cr},${cg},${cb},${alpha2})`;
1587
+ ctx.fillText(chars[1], col * charW, row * lineH);
1588
+ continue;
1589
+ }
1590
+ const pulseRing = Math.max(0, 1 - Math.abs(r - (0.15 + 0.12 * Math.sin(t * 1.1))) / 0.07);
1591
+ const gravity = Math.pow(1 - r, 2.2);
1592
+ const intensity = Math.min(1, gravity + pulseRing * 0.6);
1593
+ if (intensity < 0.06) continue;
1594
+ const densityI = Math.floor(intensity * (chars.length - 1));
1595
+ const charIdx = Math.min(chars.length - 1, densityI);
1596
+ const ch = chars[charIdx];
1597
+ const isAccent = pulseRing > 0.35 || r < 0.08;
1598
+ const alpha = lightMode ? intensity * 0.22 : intensity * 0.18;
1599
+ ctx.fillStyle = isAccent ? `rgba(${acR},${acG},${acB},${lightMode ? 0.55 : 0.38})` : `rgba(${cr},${cg},${cb},${alpha})`;
1600
+ ctx.fillText(ch, col * charW, row * lineH);
1601
+ }
1602
+ }
1603
+ }
1604
+
1605
+ // src/backgrounds/morph.ts
1606
+ function renderMorphBackground(ctx, width, height, time, options = {}) {
1607
+ const {
1608
+ fontSize = 14,
1609
+ chars = " \xB7\u2219\u2022:-=+*#",
1610
+ color,
1611
+ accentColor = "#d4ff00",
1612
+ speed = 0.5,
1613
+ harmonics = 3,
1614
+ lightMode = false
1615
+ } = options;
1616
+ const charW = fontSize * 0.62;
1617
+ const lineH = fontSize * 1.4;
1618
+ const cols = Math.ceil(width / charW);
1619
+ const rows = Math.ceil(height / lineH);
1620
+ ctx.clearRect(0, 0, width, height);
1621
+ ctx.font = `${fontSize}px monospace`;
1622
+ ctx.textBaseline = "top";
1623
+ let cr = 255, cg = 255, cb = 255;
1624
+ if (lightMode) {
1625
+ cr = 0;
1626
+ cg = 0;
1627
+ cb = 0;
1628
+ }
1629
+ if (color) {
1630
+ const p = parseColor(color);
1631
+ if (p) {
1632
+ cr = p.r;
1633
+ cg = p.g;
1634
+ cb = p.b;
1635
+ }
1636
+ }
1637
+ let acR = 212, acG = 255, acB = 0;
1638
+ const ap = parseColor(accentColor);
1639
+ if (ap) {
1640
+ acR = ap.r;
1641
+ acG = ap.g;
1642
+ acB = ap.b;
1643
+ }
1644
+ const t = time * speed;
1645
+ const maxV = Array.from({ length: harmonics }, (_, h) => 1 / (h + 1)).reduce((a, b) => a + b, 0);
1646
+ for (let row = 0; row < rows; row++) {
1647
+ for (let col = 0; col < cols; col++) {
1648
+ let v = 0;
1649
+ for (let h = 0; h < harmonics; h++) {
1650
+ const fBase = hash2(col * (h + 3) + 7, row * (h + 5) + 11);
1651
+ const fineF = 0.18 + fBase * 1.4;
1652
+ const phase = hash2(col * (h + 7), row * (h + 9) + 3) * Math.PI * 2;
1653
+ const weight = 1 / (h + 1);
1654
+ v += Math.sin(t * fineF + phase) * weight;
1655
+ }
1656
+ const norm = (v / maxV + 1) * 0.5;
1657
+ if (norm < 0.28) continue;
1658
+ const remapped = (norm - 0.28) / 0.72;
1659
+ const charIdx = Math.min(chars.length - 1, Math.floor(remapped * chars.length));
1660
+ const ch = chars[charIdx];
1661
+ const isAccent = norm > 0.88;
1662
+ const alpha = lightMode ? remapped * 0.17 : remapped * 0.13;
1663
+ ctx.fillStyle = isAccent ? `rgba(${acR},${acG},${acB},${lightMode ? 0.45 : 0.28})` : `rgba(${cr},${cg},${cb},${alpha})`;
1664
+ ctx.fillText(ch, col * charW, row * lineH);
1665
+ }
1666
+ }
1667
+ }
1668
+
1669
+ // src/backgrounds/index.ts
1420
1670
  function _parseColor(c) {
1421
1671
  const hex = c.match(/^#([0-9a-f]{3,8})$/i)?.[1];
1422
1672
  if (hex) {
@@ -1505,6 +1755,21 @@ function asciiBackground(target, options = {}) {
1505
1755
  lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight(),
1506
1756
  color: color ?? renderOpts.color
1507
1757
  });
1758
+ const buildSilkOpts = () => ({
1759
+ ...renderOpts,
1760
+ lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight(),
1761
+ color: color ?? renderOpts.color
1762
+ });
1763
+ const buildVoidOpts = () => ({
1764
+ ...renderOpts,
1765
+ lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight(),
1766
+ color: color ?? renderOpts.color
1767
+ });
1768
+ const buildMorphOpts = () => ({
1769
+ ...renderOpts,
1770
+ lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight(),
1771
+ color: color ?? renderOpts.color
1772
+ });
1508
1773
  const optsRef = { current: buildWaveOpts() };
1509
1774
  const rebuildOpts = () => {
1510
1775
  if (type === "rain") optsRef.current = buildRainOpts();
@@ -1513,6 +1778,9 @@ function asciiBackground(target, options = {}) {
1513
1778
  else if (type === "noise") optsRef.current = buildNoiseOpts();
1514
1779
  else if (type === "grid") optsRef.current = buildGridOpts();
1515
1780
  else if (type === "aurora") optsRef.current = buildAuroraOpts();
1781
+ else if (type === "silk") optsRef.current = buildSilkOpts();
1782
+ else if (type === "void") optsRef.current = buildVoidOpts();
1783
+ else if (type === "morph") optsRef.current = buildMorphOpts();
1516
1784
  else optsRef.current = buildWaveOpts();
1517
1785
  };
1518
1786
  rebuildOpts();
@@ -1553,6 +1821,12 @@ function asciiBackground(target, options = {}) {
1553
1821
  renderGridBackground(ctx, r.width, r.height, time, smoothMouse, optsRef.current);
1554
1822
  } else if (type === "aurora") {
1555
1823
  renderAuroraBackground(ctx, r.width, r.height, time, smoothMouse, optsRef.current);
1824
+ } else if (type === "silk") {
1825
+ renderSilkBackground(ctx, r.width, r.height, time, optsRef.current);
1826
+ } else if (type === "void") {
1827
+ renderVoidBackground(ctx, r.width, r.height, time, smoothMouse, optsRef.current);
1828
+ } else if (type === "morph") {
1829
+ renderMorphBackground(ctx, r.width, r.height, time, optsRef.current);
1556
1830
  } else {
1557
1831
  renderWaveBackground(ctx, r.width, r.height, time, smoothMouse, optsRef.current);
1558
1832
  }
@@ -2037,10 +2311,13 @@ exports.mountWaveBackground = mountWaveBackground;
2037
2311
  exports.renderAuroraBackground = renderAuroraBackground;
2038
2312
  exports.renderFrameToCanvas = renderFrameToCanvas;
2039
2313
  exports.renderGridBackground = renderGridBackground;
2314
+ exports.renderMorphBackground = renderMorphBackground;
2040
2315
  exports.renderNoiseBackground = renderNoiseBackground;
2041
2316
  exports.renderPulseBackground = renderPulseBackground;
2042
2317
  exports.renderRainBackground = renderRainBackground;
2318
+ exports.renderSilkBackground = renderSilkBackground;
2043
2319
  exports.renderStarsBackground = renderStarsBackground;
2320
+ exports.renderVoidBackground = renderVoidBackground;
2044
2321
  exports.renderWaveBackground = renderWaveBackground;
2045
2322
  exports.tryCreateWebGLRenderer = tryCreateWebGLRenderer;
2046
2323
  exports.videoToAsciiFrames = videoToAsciiFrames;