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