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