asciify-engine 1.0.16 → 1.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +594 -317
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +160 -186
- package/dist/index.d.ts +160 -186
- package/dist/index.js +592 -318
- 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,9 +876,63 @@ function serializeFrame(frame, fullColor) {
|
|
|
742
876
|
for (let j = 0; j < buf.length; j++) binary += String.fromCharCode(buf[j]);
|
|
743
877
|
return btoa(binary);
|
|
744
878
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
879
|
+
function buildEmbedOpts(options, rows, cols, width, height, fps, animated) {
|
|
880
|
+
const o = {
|
|
881
|
+
r: rows,
|
|
882
|
+
c: cols,
|
|
883
|
+
w: width,
|
|
884
|
+
h: height,
|
|
885
|
+
cs: options.charset,
|
|
886
|
+
cm: options.colorMode,
|
|
887
|
+
as: options.animationStyle,
|
|
888
|
+
sp: options.animationSpeed,
|
|
889
|
+
inv: options.invert,
|
|
890
|
+
hs: options.hoverStrength,
|
|
891
|
+
hr: options.hoverRadius,
|
|
892
|
+
he: options.hoverEffect,
|
|
893
|
+
hc: options.hoverColor,
|
|
894
|
+
dr: options.dotSizeRatio,
|
|
895
|
+
dots: options.renderMode === "dots"
|
|
896
|
+
};
|
|
897
|
+
if (options.colorMode === "accent") o.ac = options.accentColor;
|
|
898
|
+
if (fps !== void 0) o.fps = fps;
|
|
899
|
+
if (animated) o.anim = true;
|
|
900
|
+
return JSON.stringify(o);
|
|
901
|
+
}
|
|
902
|
+
var CDN_SCRIPT = `<script src="https://cdn.jsdelivr.net/npm/asciify-engine@${EMBED_CDN_VERSION}/dist/embed.js" async></script>`;
|
|
903
|
+
function generateEmbedCode(frame, options, width, height) {
|
|
904
|
+
const rows = frame.length;
|
|
905
|
+
if (rows === 0) return "";
|
|
906
|
+
const cols = frame[0].length;
|
|
907
|
+
const isFullColor = options.colorMode === "fullcolor";
|
|
908
|
+
const data = serializeFrame(frame, isFullColor);
|
|
909
|
+
const id = `ar-${Math.random().toString(36).slice(2, 9)}`;
|
|
910
|
+
const opts = buildEmbedOpts(options, rows, cols, width, height);
|
|
911
|
+
return `<!-- Asciify Embed -->
|
|
912
|
+
<canvas id="${id}" data-asciify-opts='${opts}' width="${width}" height="${height}"></canvas>
|
|
913
|
+
<script type="application/json" id="${id}-d">"${data}"</script>
|
|
914
|
+
${CDN_SCRIPT}
|
|
915
|
+
<!-- /Asciify Embed -->`;
|
|
916
|
+
}
|
|
917
|
+
function generateAnimatedEmbedCode(frames, options, fps, width, height) {
|
|
918
|
+
if (frames.length === 0) return "";
|
|
919
|
+
const rows = frames[0].length;
|
|
920
|
+
const cols = frames[0][0].length;
|
|
921
|
+
const isFullColor = options.colorMode === "fullcolor";
|
|
922
|
+
const allData = frames.map((f) => serializeFrame(f, isFullColor));
|
|
923
|
+
const id = `ar-${Math.random().toString(36).slice(2, 9)}`;
|
|
924
|
+
const opts = buildEmbedOpts(options, rows, cols, width, height, fps, true);
|
|
925
|
+
return `<!-- Asciify Animated Embed -->
|
|
926
|
+
<canvas id="${id}" data-asciify-opts='${opts}' width="${width}" height="${height}"></canvas>
|
|
927
|
+
<script type="application/json" id="${id}-d">${JSON.stringify(allData)}</script>
|
|
928
|
+
${CDN_SCRIPT}
|
|
929
|
+
<!-- /Asciify Animated Embed -->`;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// src/core/simple-api.ts
|
|
933
|
+
async function asciify(source, canvas, { fontSize = 10, style = "classic", options = {} } = {}) {
|
|
934
|
+
let el;
|
|
935
|
+
if (typeof source === "string") {
|
|
748
936
|
const img = new Image();
|
|
749
937
|
img.crossOrigin = "anonymous";
|
|
750
938
|
await new Promise((resolve, reject) => {
|
|
@@ -832,173 +1020,8 @@ async function asciifyVideo(source, canvas, { fontSize = 10, style = "classic",
|
|
|
832
1020
|
cancelAnimationFrame(animId);
|
|
833
1021
|
};
|
|
834
1022
|
}
|
|
835
|
-
|
|
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,219 @@ function renderAuroraBackground(ctx, width, height, time, mousePos = { x: 0.5, y
|
|
|
1417
1454
|
}
|
|
1418
1455
|
}
|
|
1419
1456
|
}
|
|
1457
|
+
|
|
1458
|
+
// src/backgrounds/silk.ts
|
|
1459
|
+
function renderSilkBackground(ctx, width, height, time, options = {}) {
|
|
1460
|
+
const {
|
|
1461
|
+
fontSize = 13,
|
|
1462
|
+
color,
|
|
1463
|
+
accentColor = "#d4ff00",
|
|
1464
|
+
speed = 0.4,
|
|
1465
|
+
layers = 4,
|
|
1466
|
+
turbulence = 0.8,
|
|
1467
|
+
lightMode = false
|
|
1468
|
+
} = options;
|
|
1469
|
+
const charW = fontSize * 0.62;
|
|
1470
|
+
const lineH = fontSize * 1.4;
|
|
1471
|
+
const cols = Math.ceil(width / charW);
|
|
1472
|
+
const rows = Math.ceil(height / lineH);
|
|
1473
|
+
ctx.clearRect(0, 0, width, height);
|
|
1474
|
+
ctx.font = `${fontSize}px monospace`;
|
|
1475
|
+
ctx.textBaseline = "top";
|
|
1476
|
+
let cr = 255, cg = 255, cb = 255;
|
|
1477
|
+
if (lightMode) {
|
|
1478
|
+
cr = 0;
|
|
1479
|
+
cg = 0;
|
|
1480
|
+
cb = 0;
|
|
1481
|
+
}
|
|
1482
|
+
if (color) {
|
|
1483
|
+
const p = parseColor(color);
|
|
1484
|
+
if (p) {
|
|
1485
|
+
cr = p.r;
|
|
1486
|
+
cg = p.g;
|
|
1487
|
+
cb = p.b;
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
let acR = 212, acG = 255, acB = 0;
|
|
1491
|
+
const ap = parseColor(accentColor);
|
|
1492
|
+
if (ap) {
|
|
1493
|
+
acR = ap.r;
|
|
1494
|
+
acG = ap.g;
|
|
1495
|
+
acB = ap.b;
|
|
1496
|
+
}
|
|
1497
|
+
const t = time * speed;
|
|
1498
|
+
const dirChars = ["\u2500", "\u2500", "\u254C", "\xB7", "\u254C", "\u2500", "\u2500", "\u254C", "\xB7"];
|
|
1499
|
+
for (let row = 0; row < rows; row++) {
|
|
1500
|
+
const ny = row / rows;
|
|
1501
|
+
for (let col = 0; col < cols; col++) {
|
|
1502
|
+
const nx = col / cols;
|
|
1503
|
+
let angleSum = 0;
|
|
1504
|
+
let intensitySum = 0;
|
|
1505
|
+
for (let l = 0; l < layers; l++) {
|
|
1506
|
+
const ls = hash2(l * 13, l * 7 + 3);
|
|
1507
|
+
const ls2 = hash2(l * 29, l * 11 + 1);
|
|
1508
|
+
const fx = 1.1 + ls * 2.4;
|
|
1509
|
+
const fy = 0.9 + ls2 * 2;
|
|
1510
|
+
const ph = ls * Math.PI * 6;
|
|
1511
|
+
const dr = (0.2 + hash2(l * 41, l * 17) * 0.5) * (l % 2 === 0 ? 1 : -1.3);
|
|
1512
|
+
const u = Math.sin(nx * fx * Math.PI * 2 + t * dr + ph);
|
|
1513
|
+
const v = Math.cos(ny * fy * Math.PI * 2 + t * dr * 0.6 + ph * 1.7);
|
|
1514
|
+
const cross = Math.sin(nx * fy * Math.PI * turbulence + ny * fx * Math.PI * turbulence + t * dr * 0.4);
|
|
1515
|
+
angleSum += Math.atan2(v + cross * 0.3, u);
|
|
1516
|
+
intensitySum += (u * v + 1) * 0.5;
|
|
1517
|
+
}
|
|
1518
|
+
const angle = angleSum / layers;
|
|
1519
|
+
const intensity = Math.min(1, intensitySum / layers);
|
|
1520
|
+
if (intensity < 0.1) continue;
|
|
1521
|
+
const angleNorm = (angle + Math.PI) / (Math.PI * 2);
|
|
1522
|
+
const charIdx = Math.floor(angleNorm * dirChars.length) % dirChars.length;
|
|
1523
|
+
const ch = dirChars[charIdx];
|
|
1524
|
+
const isAccent = intensity > 0.8;
|
|
1525
|
+
const alpha = lightMode ? intensity * 0.16 : intensity * 0.13;
|
|
1526
|
+
ctx.fillStyle = isAccent ? `rgba(${acR},${acG},${acB},${lightMode ? 0.44 : 0.26})` : `rgba(${cr},${cg},${cb},${alpha})`;
|
|
1527
|
+
ctx.fillText(ch, col * charW, row * lineH);
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
// src/backgrounds/void.ts
|
|
1533
|
+
function renderVoidBackground(ctx, width, height, time, mousePos = { x: 0.5, y: 0.5 }, options = {}) {
|
|
1534
|
+
const {
|
|
1535
|
+
fontSize = 13,
|
|
1536
|
+
chars = " \xB7:;=+*#%@",
|
|
1537
|
+
color,
|
|
1538
|
+
accentColor = "#d4ff00",
|
|
1539
|
+
speed = 1,
|
|
1540
|
+
radius = 0.38,
|
|
1541
|
+
swirl = 3,
|
|
1542
|
+
lightMode = false
|
|
1543
|
+
} = options;
|
|
1544
|
+
const charW = fontSize * 0.62;
|
|
1545
|
+
const lineH = fontSize * 1.4;
|
|
1546
|
+
const cols = Math.ceil(width / charW);
|
|
1547
|
+
const rows = Math.ceil(height / lineH);
|
|
1548
|
+
const aspect = width / height;
|
|
1549
|
+
ctx.clearRect(0, 0, width, height);
|
|
1550
|
+
ctx.font = `${fontSize}px monospace`;
|
|
1551
|
+
ctx.textBaseline = "top";
|
|
1552
|
+
let cr = 255, cg = 255, cb = 255;
|
|
1553
|
+
if (lightMode) {
|
|
1554
|
+
cr = 0;
|
|
1555
|
+
cg = 0;
|
|
1556
|
+
cb = 0;
|
|
1557
|
+
}
|
|
1558
|
+
if (color) {
|
|
1559
|
+
const p = parseColor(color);
|
|
1560
|
+
if (p) {
|
|
1561
|
+
cr = p.r;
|
|
1562
|
+
cg = p.g;
|
|
1563
|
+
cb = p.b;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
let acR = 212, acG = 255, acB = 0;
|
|
1567
|
+
const ap = parseColor(accentColor);
|
|
1568
|
+
if (ap) {
|
|
1569
|
+
acR = ap.r;
|
|
1570
|
+
acG = ap.g;
|
|
1571
|
+
acB = ap.b;
|
|
1572
|
+
}
|
|
1573
|
+
const t = time * speed;
|
|
1574
|
+
for (let row = 0; row < rows; row++) {
|
|
1575
|
+
const ny = row / rows;
|
|
1576
|
+
for (let col = 0; col < cols; col++) {
|
|
1577
|
+
const nx = col / cols;
|
|
1578
|
+
const dx = (nx - mousePos.x) * aspect;
|
|
1579
|
+
const dy = ny - mousePos.y;
|
|
1580
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
1581
|
+
const r = dist / radius;
|
|
1582
|
+
if (r > 1) {
|
|
1583
|
+
const outerNoise = hash2(col * 3, row * 7) * Math.max(0, 1 - (r - 1) * 3);
|
|
1584
|
+
if (outerNoise < 0.62) continue;
|
|
1585
|
+
const alpha2 = outerNoise * (lightMode ? 0.05 : 0.04);
|
|
1586
|
+
ctx.fillStyle = `rgba(${cr},${cg},${cb},${alpha2})`;
|
|
1587
|
+
ctx.fillText(chars[1], col * charW, row * lineH);
|
|
1588
|
+
continue;
|
|
1589
|
+
}
|
|
1590
|
+
const pulseRing = Math.max(0, 1 - Math.abs(r - (0.15 + 0.12 * Math.sin(t * 1.1))) / 0.07);
|
|
1591
|
+
const gravity = Math.pow(1 - r, 2.2);
|
|
1592
|
+
const intensity = Math.min(1, gravity + pulseRing * 0.6);
|
|
1593
|
+
if (intensity < 0.06) continue;
|
|
1594
|
+
const densityI = Math.floor(intensity * (chars.length - 1));
|
|
1595
|
+
const charIdx = Math.min(chars.length - 1, densityI);
|
|
1596
|
+
const ch = chars[charIdx];
|
|
1597
|
+
const isAccent = pulseRing > 0.35 || r < 0.08;
|
|
1598
|
+
const alpha = lightMode ? intensity * 0.22 : intensity * 0.18;
|
|
1599
|
+
ctx.fillStyle = isAccent ? `rgba(${acR},${acG},${acB},${lightMode ? 0.55 : 0.38})` : `rgba(${cr},${cg},${cb},${alpha})`;
|
|
1600
|
+
ctx.fillText(ch, col * charW, row * lineH);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
// src/backgrounds/morph.ts
|
|
1606
|
+
function renderMorphBackground(ctx, width, height, time, options = {}) {
|
|
1607
|
+
const {
|
|
1608
|
+
fontSize = 14,
|
|
1609
|
+
chars = " \xB7\u2219\u2022:-=+*#",
|
|
1610
|
+
color,
|
|
1611
|
+
accentColor = "#d4ff00",
|
|
1612
|
+
speed = 0.5,
|
|
1613
|
+
harmonics = 3,
|
|
1614
|
+
lightMode = false
|
|
1615
|
+
} = options;
|
|
1616
|
+
const charW = fontSize * 0.62;
|
|
1617
|
+
const lineH = fontSize * 1.4;
|
|
1618
|
+
const cols = Math.ceil(width / charW);
|
|
1619
|
+
const rows = Math.ceil(height / lineH);
|
|
1620
|
+
ctx.clearRect(0, 0, width, height);
|
|
1621
|
+
ctx.font = `${fontSize}px monospace`;
|
|
1622
|
+
ctx.textBaseline = "top";
|
|
1623
|
+
let cr = 255, cg = 255, cb = 255;
|
|
1624
|
+
if (lightMode) {
|
|
1625
|
+
cr = 0;
|
|
1626
|
+
cg = 0;
|
|
1627
|
+
cb = 0;
|
|
1628
|
+
}
|
|
1629
|
+
if (color) {
|
|
1630
|
+
const p = parseColor(color);
|
|
1631
|
+
if (p) {
|
|
1632
|
+
cr = p.r;
|
|
1633
|
+
cg = p.g;
|
|
1634
|
+
cb = p.b;
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
let acR = 212, acG = 255, acB = 0;
|
|
1638
|
+
const ap = parseColor(accentColor);
|
|
1639
|
+
if (ap) {
|
|
1640
|
+
acR = ap.r;
|
|
1641
|
+
acG = ap.g;
|
|
1642
|
+
acB = ap.b;
|
|
1643
|
+
}
|
|
1644
|
+
const t = time * speed;
|
|
1645
|
+
const maxV = Array.from({ length: harmonics }, (_, h) => 1 / (h + 1)).reduce((a, b) => a + b, 0);
|
|
1646
|
+
for (let row = 0; row < rows; row++) {
|
|
1647
|
+
for (let col = 0; col < cols; col++) {
|
|
1648
|
+
let v = 0;
|
|
1649
|
+
for (let h = 0; h < harmonics; h++) {
|
|
1650
|
+
const fBase = hash2(col * (h + 3) + 7, row * (h + 5) + 11);
|
|
1651
|
+
const fineF = 0.18 + fBase * 1.4;
|
|
1652
|
+
const phase = hash2(col * (h + 7), row * (h + 9) + 3) * Math.PI * 2;
|
|
1653
|
+
const weight = 1 / (h + 1);
|
|
1654
|
+
v += Math.sin(t * fineF + phase) * weight;
|
|
1655
|
+
}
|
|
1656
|
+
const norm = (v / maxV + 1) * 0.5;
|
|
1657
|
+
if (norm < 0.28) continue;
|
|
1658
|
+
const remapped = (norm - 0.28) / 0.72;
|
|
1659
|
+
const charIdx = Math.min(chars.length - 1, Math.floor(remapped * chars.length));
|
|
1660
|
+
const ch = chars[charIdx];
|
|
1661
|
+
const isAccent = norm > 0.88;
|
|
1662
|
+
const alpha = lightMode ? remapped * 0.17 : remapped * 0.13;
|
|
1663
|
+
ctx.fillStyle = isAccent ? `rgba(${acR},${acG},${acB},${lightMode ? 0.45 : 0.28})` : `rgba(${cr},${cg},${cb},${alpha})`;
|
|
1664
|
+
ctx.fillText(ch, col * charW, row * lineH);
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
// src/backgrounds/index.ts
|
|
1420
1670
|
function _parseColor(c) {
|
|
1421
1671
|
const hex = c.match(/^#([0-9a-f]{3,8})$/i)?.[1];
|
|
1422
1672
|
if (hex) {
|
|
@@ -1505,6 +1755,21 @@ function asciiBackground(target, options = {}) {
|
|
|
1505
1755
|
lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight(),
|
|
1506
1756
|
color: color ?? renderOpts.color
|
|
1507
1757
|
});
|
|
1758
|
+
const buildSilkOpts = () => ({
|
|
1759
|
+
...renderOpts,
|
|
1760
|
+
lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight(),
|
|
1761
|
+
color: color ?? renderOpts.color
|
|
1762
|
+
});
|
|
1763
|
+
const buildVoidOpts = () => ({
|
|
1764
|
+
...renderOpts,
|
|
1765
|
+
lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight(),
|
|
1766
|
+
color: color ?? renderOpts.color
|
|
1767
|
+
});
|
|
1768
|
+
const buildMorphOpts = () => ({
|
|
1769
|
+
...renderOpts,
|
|
1770
|
+
lightMode: renderOpts.lightMode !== void 0 ? renderOpts.lightMode : isLight(),
|
|
1771
|
+
color: color ?? renderOpts.color
|
|
1772
|
+
});
|
|
1508
1773
|
const optsRef = { current: buildWaveOpts() };
|
|
1509
1774
|
const rebuildOpts = () => {
|
|
1510
1775
|
if (type === "rain") optsRef.current = buildRainOpts();
|
|
@@ -1513,6 +1778,9 @@ function asciiBackground(target, options = {}) {
|
|
|
1513
1778
|
else if (type === "noise") optsRef.current = buildNoiseOpts();
|
|
1514
1779
|
else if (type === "grid") optsRef.current = buildGridOpts();
|
|
1515
1780
|
else if (type === "aurora") optsRef.current = buildAuroraOpts();
|
|
1781
|
+
else if (type === "silk") optsRef.current = buildSilkOpts();
|
|
1782
|
+
else if (type === "void") optsRef.current = buildVoidOpts();
|
|
1783
|
+
else if (type === "morph") optsRef.current = buildMorphOpts();
|
|
1516
1784
|
else optsRef.current = buildWaveOpts();
|
|
1517
1785
|
};
|
|
1518
1786
|
rebuildOpts();
|
|
@@ -1553,6 +1821,12 @@ function asciiBackground(target, options = {}) {
|
|
|
1553
1821
|
renderGridBackground(ctx, r.width, r.height, time, smoothMouse, optsRef.current);
|
|
1554
1822
|
} else if (type === "aurora") {
|
|
1555
1823
|
renderAuroraBackground(ctx, r.width, r.height, time, smoothMouse, optsRef.current);
|
|
1824
|
+
} else if (type === "silk") {
|
|
1825
|
+
renderSilkBackground(ctx, r.width, r.height, time, optsRef.current);
|
|
1826
|
+
} else if (type === "void") {
|
|
1827
|
+
renderVoidBackground(ctx, r.width, r.height, time, smoothMouse, optsRef.current);
|
|
1828
|
+
} else if (type === "morph") {
|
|
1829
|
+
renderMorphBackground(ctx, r.width, r.height, time, optsRef.current);
|
|
1556
1830
|
} else {
|
|
1557
1831
|
renderWaveBackground(ctx, r.width, r.height, time, smoothMouse, optsRef.current);
|
|
1558
1832
|
}
|
|
@@ -2037,10 +2311,13 @@ exports.mountWaveBackground = mountWaveBackground;
|
|
|
2037
2311
|
exports.renderAuroraBackground = renderAuroraBackground;
|
|
2038
2312
|
exports.renderFrameToCanvas = renderFrameToCanvas;
|
|
2039
2313
|
exports.renderGridBackground = renderGridBackground;
|
|
2314
|
+
exports.renderMorphBackground = renderMorphBackground;
|
|
2040
2315
|
exports.renderNoiseBackground = renderNoiseBackground;
|
|
2041
2316
|
exports.renderPulseBackground = renderPulseBackground;
|
|
2042
2317
|
exports.renderRainBackground = renderRainBackground;
|
|
2318
|
+
exports.renderSilkBackground = renderSilkBackground;
|
|
2043
2319
|
exports.renderStarsBackground = renderStarsBackground;
|
|
2320
|
+
exports.renderVoidBackground = renderVoidBackground;
|
|
2044
2321
|
exports.renderWaveBackground = renderWaveBackground;
|
|
2045
2322
|
exports.tryCreateWebGLRenderer = tryCreateWebGLRenderer;
|
|
2046
2323
|
exports.videoToAsciiFrames = videoToAsciiFrames;
|