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