asciify-engine 1.0.17 → 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,6 +874,60 @@ 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
  }
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
743
931
  async function asciify(source, canvas, { fontSize = 10, style = "classic", options = {} } = {}) {
744
932
  let el;
745
933
  if (typeof source === "string") {
@@ -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.17";
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,8 @@ function renderAuroraBackground(ctx, width, height, time, mousePos = { x: 0.5, y
1415
1452
  }
1416
1453
  }
1417
1454
  }
1455
+
1456
+ // src/backgrounds/silk.ts
1418
1457
  function renderSilkBackground(ctx, width, height, time, options = {}) {
1419
1458
  const {
1420
1459
  fontSize = 13,
@@ -1439,7 +1478,7 @@ function renderSilkBackground(ctx, width, height, time, options = {}) {
1439
1478
  cb = 0;
1440
1479
  }
1441
1480
  if (color) {
1442
- const p = _parseColor(color);
1481
+ const p = parseColor(color);
1443
1482
  if (p) {
1444
1483
  cr = p.r;
1445
1484
  cg = p.g;
@@ -1447,7 +1486,7 @@ function renderSilkBackground(ctx, width, height, time, options = {}) {
1447
1486
  }
1448
1487
  }
1449
1488
  let acR = 212, acG = 255, acB = 0;
1450
- const ap = _parseColor(accentColor);
1489
+ const ap = parseColor(accentColor);
1451
1490
  if (ap) {
1452
1491
  acR = ap.r;
1453
1492
  acG = ap.g;
@@ -1462,12 +1501,12 @@ function renderSilkBackground(ctx, width, height, time, options = {}) {
1462
1501
  let angleSum = 0;
1463
1502
  let intensitySum = 0;
1464
1503
  for (let l = 0; l < layers; l++) {
1465
- const ls = _hash2(l * 13, l * 7 + 3);
1466
- const ls2 = _hash2(l * 29, l * 11 + 1);
1504
+ const ls = hash2(l * 13, l * 7 + 3);
1505
+ const ls2 = hash2(l * 29, l * 11 + 1);
1467
1506
  const fx = 1.1 + ls * 2.4;
1468
1507
  const fy = 0.9 + ls2 * 2;
1469
1508
  const ph = ls * Math.PI * 6;
1470
- const dr = (0.2 + _hash2(l * 41, l * 17) * 0.5) * (l % 2 === 0 ? 1 : -1.3);
1509
+ const dr = (0.2 + hash2(l * 41, l * 17) * 0.5) * (l % 2 === 0 ? 1 : -1.3);
1471
1510
  const u = Math.sin(nx * fx * Math.PI * 2 + t * dr + ph);
1472
1511
  const v = Math.cos(ny * fy * Math.PI * 2 + t * dr * 0.6 + ph * 1.7);
1473
1512
  const cross = Math.sin(nx * fy * Math.PI * turbulence + ny * fx * Math.PI * turbulence + t * dr * 0.4);
@@ -1487,6 +1526,8 @@ function renderSilkBackground(ctx, width, height, time, options = {}) {
1487
1526
  }
1488
1527
  }
1489
1528
  }
1529
+
1530
+ // src/backgrounds/void.ts
1490
1531
  function renderVoidBackground(ctx, width, height, time, mousePos = { x: 0.5, y: 0.5 }, options = {}) {
1491
1532
  const {
1492
1533
  fontSize = 13,
@@ -1513,7 +1554,7 @@ function renderVoidBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1513
1554
  cb = 0;
1514
1555
  }
1515
1556
  if (color) {
1516
- const p = _parseColor(color);
1557
+ const p = parseColor(color);
1517
1558
  if (p) {
1518
1559
  cr = p.r;
1519
1560
  cg = p.g;
@@ -1521,7 +1562,7 @@ function renderVoidBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1521
1562
  }
1522
1563
  }
1523
1564
  let acR = 212, acG = 255, acB = 0;
1524
- const ap = _parseColor(accentColor);
1565
+ const ap = parseColor(accentColor);
1525
1566
  if (ap) {
1526
1567
  acR = ap.r;
1527
1568
  acG = ap.g;
@@ -1537,25 +1578,20 @@ function renderVoidBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1537
1578
  const dist = Math.sqrt(dx * dx + dy * dy);
1538
1579
  const r = dist / radius;
1539
1580
  if (r > 1) {
1540
- const outerNoise = _hash2(col * 3, row * 7) * Math.max(0, 1 - (r - 1) * 3);
1581
+ const outerNoise = hash2(col * 3, row * 7) * Math.max(0, 1 - (r - 1) * 3);
1541
1582
  if (outerNoise < 0.62) continue;
1542
1583
  const alpha2 = outerNoise * (lightMode ? 0.05 : 0.04);
1543
- const ch2 = chars[1];
1544
1584
  ctx.fillStyle = `rgba(${cr},${cg},${cb},${alpha2})`;
1545
- ctx.fillText(ch2, col * charW, row * lineH);
1585
+ ctx.fillText(chars[1], col * charW, row * lineH);
1546
1586
  continue;
1547
1587
  }
1548
- const baseAngle = Math.atan2(dy, dx * aspect);
1549
- const swirlAmt = (1 - r) * swirl;
1550
- const angle = baseAngle + swirlAmt + t * 0.4;
1551
1588
  const pulseRing = Math.max(0, 1 - Math.abs(r - (0.15 + 0.12 * Math.sin(t * 1.1))) / 0.07);
1552
1589
  const gravity = Math.pow(1 - r, 2.2);
1553
1590
  const intensity = Math.min(1, gravity + pulseRing * 0.6);
1554
1591
  if (intensity < 0.06) continue;
1555
- const angleIdx = Math.floor((angle % (Math.PI * 2) + Math.PI * 2) % (Math.PI * 2) / (Math.PI * 2) * 4);
1556
1592
  const densityI = Math.floor(intensity * (chars.length - 1));
1557
1593
  const charIdx = Math.min(chars.length - 1, densityI);
1558
- const ch = chars[charIdx + (angleIdx > 2 && densityI > 2 ? 0 : 0)];
1594
+ const ch = chars[charIdx];
1559
1595
  const isAccent = pulseRing > 0.35 || r < 0.08;
1560
1596
  const alpha = lightMode ? intensity * 0.22 : intensity * 0.18;
1561
1597
  ctx.fillStyle = isAccent ? `rgba(${acR},${acG},${acB},${lightMode ? 0.55 : 0.38})` : `rgba(${cr},${cg},${cb},${alpha})`;
@@ -1563,6 +1599,8 @@ function renderVoidBackground(ctx, width, height, time, mousePos = { x: 0.5, y:
1563
1599
  }
1564
1600
  }
1565
1601
  }
1602
+
1603
+ // src/backgrounds/morph.ts
1566
1604
  function renderMorphBackground(ctx, width, height, time, options = {}) {
1567
1605
  const {
1568
1606
  fontSize = 14,
@@ -1587,7 +1625,7 @@ function renderMorphBackground(ctx, width, height, time, options = {}) {
1587
1625
  cb = 0;
1588
1626
  }
1589
1627
  if (color) {
1590
- const p = _parseColor(color);
1628
+ const p = parseColor(color);
1591
1629
  if (p) {
1592
1630
  cr = p.r;
1593
1631
  cg = p.g;
@@ -1595,24 +1633,24 @@ function renderMorphBackground(ctx, width, height, time, options = {}) {
1595
1633
  }
1596
1634
  }
1597
1635
  let acR = 212, acG = 255, acB = 0;
1598
- const ap = _parseColor(accentColor);
1636
+ const ap = parseColor(accentColor);
1599
1637
  if (ap) {
1600
1638
  acR = ap.r;
1601
1639
  acG = ap.g;
1602
1640
  acB = ap.b;
1603
1641
  }
1604
1642
  const t = time * speed;
1643
+ const maxV = Array.from({ length: harmonics }, (_, h) => 1 / (h + 1)).reduce((a, b) => a + b, 0);
1605
1644
  for (let row = 0; row < rows; row++) {
1606
1645
  for (let col = 0; col < cols; col++) {
1607
1646
  let v = 0;
1608
1647
  for (let h = 0; h < harmonics; h++) {
1609
- const fBase = _hash2(col * (h + 3) + 7, row * (h + 5) + 11);
1648
+ const fBase = hash2(col * (h + 3) + 7, row * (h + 5) + 11);
1610
1649
  const fineF = 0.18 + fBase * 1.4;
1611
- const phase = _hash2(col * (h + 7), row * (h + 9) + 3) * Math.PI * 2;
1650
+ const phase = hash2(col * (h + 7), row * (h + 9) + 3) * Math.PI * 2;
1612
1651
  const weight = 1 / (h + 1);
1613
1652
  v += Math.sin(t * fineF + phase) * weight;
1614
1653
  }
1615
- const maxV = Array.from({ length: harmonics }, (_, h) => 1 / (h + 1)).reduce((a, b) => a + b, 0);
1616
1654
  const norm = (v / maxV + 1) * 0.5;
1617
1655
  if (norm < 0.28) continue;
1618
1656
  const remapped = (norm - 0.28) / 0.72;
@@ -1625,6 +1663,8 @@ function renderMorphBackground(ctx, width, height, time, options = {}) {
1625
1663
  }
1626
1664
  }
1627
1665
  }
1666
+
1667
+ // src/backgrounds/index.ts
1628
1668
  function _parseColor(c) {
1629
1669
  const hex = c.match(/^#([0-9a-f]{3,8})$/i)?.[1];
1630
1670
  if (hex) {