397 lines
18 KiB
JavaScript
397 lines
18 KiB
JavaScript
// abstract.js — Abstract theme backgrounds
|
|
// Variants: geometricPatterns, gradients, particleFields, minimalist, waves, fractals, kaleidoscope, matrix
|
|
|
|
(function(engine) {
|
|
if (!engine) return;
|
|
var D = engine.Draw, H = engine.helpers;
|
|
|
|
engine.registerTheme('abstract', {
|
|
|
|
geometricPatterns: {
|
|
meta: { id: 'abstract_geometricPatterns', mood: 'focused', colors: ['#0d1b2a', '#1b2838', '#e07a5f', '#f2cc8f'], tags: ['geometric', 'shapes', 'patterns', 'clean'], description: 'Interlocking geometric shapes in warm contrasting tones on a deep navy canvas', compatibleMusicMoods: ['focused', 'calm', 'electronic'], recommendedFor: ['puzzle', 'trivia', 'creative'] },
|
|
colors: ['#0d1b2a', '#1b2838', '#e07a5f', '#f2cc8f'],
|
|
renderBase: function(ctx, w, h, c, Draw, t) {
|
|
Draw.fillGradient(ctx, w, h, [c[0], c[1]], 135);
|
|
},
|
|
renderMid: function(ctx, w, h, c, Draw, t) {
|
|
var rng = H.seededRandom(100);
|
|
// Rotating hexagonal grid
|
|
var spacing = 80;
|
|
for (var row = -1; row < h / spacing + 1; row++) {
|
|
for (var col = -1; col < w / spacing + 1; col++) {
|
|
var hx = col * spacing + (row % 2 === 0 ? spacing / 2 : 0);
|
|
var hy = row * spacing;
|
|
var dist = Math.sqrt(Math.pow(hx - w / 2, 2) + Math.pow(hy - h / 2, 2));
|
|
var alpha = H.clamp(0.08 + Math.sin(t * 0.5 + dist * 0.005) * 0.06, 0, 0.2);
|
|
var rot = t * 0.1 + dist * 0.002;
|
|
var colorChoice = rng() > 0.5 ? c[2] : c[3];
|
|
Draw.polygon(ctx, hx, hy, 25 + Math.sin(t * 0.3 + dist * 0.01) * 5, 6, H.rgba(colorChoice, alpha), rot);
|
|
}
|
|
}
|
|
// Accent triangles
|
|
for (var tri = 0; tri < 12; tri++) {
|
|
var tx = rng() * w, ty = rng() * h;
|
|
var tSize = 20 + rng() * 40;
|
|
var tRot = t * 0.2 * (rng() > 0.5 ? 1 : -1) + rng() * Math.PI * 2;
|
|
var tAlpha = 0.1 + Math.sin(t * 0.7 + tri) * 0.05;
|
|
Draw.polygon(ctx, tx, ty, tSize, 3, H.rgba(c[2], tAlpha), tRot);
|
|
}
|
|
// Connecting lines
|
|
ctx.strokeStyle = H.rgba(c[3], 0.06);
|
|
ctx.lineWidth = 1;
|
|
var lrng = H.seededRandom(101);
|
|
for (var ln = 0; ln < 15; ln++) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(lrng() * w, lrng() * h);
|
|
ctx.lineTo(lrng() * w, lrng() * h);
|
|
ctx.stroke();
|
|
}
|
|
},
|
|
particles: { type: 'dust', count: 20, color: '#F2CC8F' }
|
|
},
|
|
|
|
gradients: {
|
|
meta: { id: 'abstract_gradients', mood: 'dreamy', colors: ['#0f0c29', '#302b63', '#24243e', '#ff6b6b'], tags: ['gradient', 'smooth', 'colorful', 'flowing'], description: 'Smoothly blending gradient orbs that shift and pulse with ethereal color transitions', compatibleMusicMoods: ['dreamy', 'calm', 'ambient'], recommendedFor: ['creative', 'story', 'music'] },
|
|
colors: ['#0f0c29', '#302b63', '#24243e', '#ff6b6b'],
|
|
renderBase: function(ctx, w, h, c, Draw, t) {
|
|
Draw.fillGradient(ctx, w, h, [c[0], c[1], c[2]], 90);
|
|
},
|
|
renderMid: function(ctx, w, h, c, Draw, t) {
|
|
var rng = H.seededRandom(200);
|
|
// Animated gradient orbs
|
|
var orbs = [
|
|
{ x: 0.3, y: 0.3, color: c[3], r: 0.25 },
|
|
{ x: 0.7, y: 0.6, color: '#6b5bff', r: 0.3 },
|
|
{ x: 0.5, y: 0.8, color: '#ff9a76', r: 0.2 },
|
|
{ x: 0.2, y: 0.7, color: '#a66bff', r: 0.22 },
|
|
{ x: 0.8, y: 0.2, color: '#ff6bb5', r: 0.18 }
|
|
];
|
|
ctx.globalCompositeOperation = 'screen';
|
|
for (var i = 0; i < orbs.length; i++) {
|
|
var orb = orbs[i];
|
|
var ox = orb.x * w + Math.sin(t * 0.3 + i * 1.5) * w * 0.05;
|
|
var oy = orb.y * h + Math.cos(t * 0.2 + i * 1.2) * h * 0.05;
|
|
var or = orb.r * Math.min(w, h) * (1 + Math.sin(t * 0.4 + i) * 0.1);
|
|
var grad = Draw.radialGradient(ctx, ox, oy, or, [
|
|
[0, H.rgba(orb.color, 0.3)],
|
|
[0.5, H.rgba(orb.color, 0.1)],
|
|
[1, H.rgba(orb.color, 0)]
|
|
]);
|
|
ctx.fillStyle = grad;
|
|
ctx.beginPath(); ctx.arc(ox, oy, or, 0, Math.PI * 2); ctx.fill();
|
|
}
|
|
ctx.globalCompositeOperation = 'source-over';
|
|
// Subtle mesh lines
|
|
ctx.strokeStyle = H.rgba('#FFFFFF', 0.02);
|
|
ctx.lineWidth = 1;
|
|
for (var ml = 0; ml < 8; ml++) {
|
|
var my = h * 0.1 + ml * h * 0.1;
|
|
ctx.beginPath();
|
|
for (var mx = 0; mx < w; mx += 5) {
|
|
var mdy = my + Math.sin(mx * 0.01 + t * 0.5 + ml * 0.5) * 15;
|
|
mx === 0 ? ctx.moveTo(mx, mdy) : ctx.lineTo(mx, mdy);
|
|
}
|
|
ctx.stroke();
|
|
}
|
|
},
|
|
particles: { type: 'sparkles', count: 15, color: '#FF6B6B' }
|
|
},
|
|
|
|
particleFields: {
|
|
meta: { id: 'abstract_particleFields', mood: 'energetic', colors: ['#000000', '#111122', '#00ff88', '#0088ff'], tags: ['particles', 'field', 'connected', 'network'], description: 'A dynamic network of connected particle nodes with pulsing energy links', compatibleMusicMoods: ['energetic', 'electronic', 'focused'], recommendedFor: ['action', 'puzzle', 'physics'] },
|
|
colors: ['#000000', '#111122', '#00ff88', '#0088ff'],
|
|
renderBase: function(ctx, w, h, c, Draw, t) {
|
|
Draw.fillGradient(ctx, w, h, [c[0], c[1]], 90);
|
|
},
|
|
renderMid: function(ctx, w, h, c, Draw, t) {
|
|
var rng = H.seededRandom(300);
|
|
// Generate node positions (deterministic)
|
|
var nodes = [];
|
|
for (var n = 0; n < 30; n++) {
|
|
var baseX = rng() * w, baseY = rng() * h;
|
|
nodes.push({
|
|
x: baseX + Math.sin(t * 0.5 + n * 0.7) * 15,
|
|
y: baseY + Math.cos(t * 0.4 + n * 0.9) * 15,
|
|
size: 2 + rng() * 3,
|
|
color: rng() > 0.5 ? c[2] : c[3]
|
|
});
|
|
}
|
|
// Draw connections between nearby nodes
|
|
var maxDist = 150;
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
for (var j = i + 1; j < nodes.length; j++) {
|
|
var dx = nodes[i].x - nodes[j].x;
|
|
var dy = nodes[i].y - nodes[j].y;
|
|
var dist = Math.sqrt(dx * dx + dy * dy);
|
|
if (dist < maxDist) {
|
|
var lineAlpha = (1 - dist / maxDist) * 0.2;
|
|
var pulse = 0.5 + Math.sin(t * 1.5 + i + j) * 0.5;
|
|
ctx.strokeStyle = H.rgba(nodes[i].color, lineAlpha * pulse);
|
|
ctx.lineWidth = 1;
|
|
ctx.beginPath();
|
|
ctx.moveTo(nodes[i].x, nodes[i].y);
|
|
ctx.lineTo(nodes[j].x, nodes[j].y);
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
}
|
|
// Draw nodes
|
|
for (var k = 0; k < nodes.length; k++) {
|
|
var nd = nodes[k];
|
|
var nodePulse = 0.6 + Math.sin(t * 2 + k * 0.5) * 0.3;
|
|
Draw.glow(ctx, nd.x, nd.y, nd.size * 4, H.rgba(nd.color, 0.15 * nodePulse));
|
|
Draw.circle(ctx, nd.x, nd.y, nd.size, H.rgba(nd.color, 0.7 * nodePulse));
|
|
}
|
|
// Data stream accents along bottom
|
|
ctx.strokeStyle = H.rgba(c[2], 0.04);
|
|
ctx.lineWidth = 1;
|
|
for (var ds = 0; ds < 5; ds++) {
|
|
var dsy = h * 0.85 + ds * 8;
|
|
ctx.beginPath();
|
|
for (var dx2 = 0; dx2 < w; dx2 += 3) {
|
|
var ddy = dsy + Math.sin(dx2 * 0.03 + t * 2 + ds) * 3;
|
|
dx2 === 0 ? ctx.moveTo(dx2, ddy) : ctx.lineTo(dx2, ddy);
|
|
}
|
|
ctx.stroke();
|
|
}
|
|
},
|
|
particles: { type: 'particles', count: 15, color: '#00FF88' }
|
|
},
|
|
|
|
minimalist: {
|
|
meta: { id: 'abstract_minimalist', mood: 'calm', colors: ['#f5f5f0', '#e8e4dd', '#2d2d2d', '#c44536'], tags: ['minimal', 'clean', 'simple', 'elegant'], description: 'Clean open space with a single bold accent line and carefully placed geometric forms', compatibleMusicMoods: ['calm', 'ambient', 'focused'], recommendedFor: ['puzzle', 'trivia', 'creative'] },
|
|
colors: ['#f5f5f0', '#e8e4dd', '#2d2d2d', '#c44536'],
|
|
renderBase: function(ctx, w, h, c, Draw, t) {
|
|
Draw.fillGradient(ctx, w, h, [c[0], c[1]], 90);
|
|
},
|
|
renderMid: function(ctx, w, h, c, Draw, t) {
|
|
// Single horizon line
|
|
ctx.strokeStyle = H.rgba(c[2], 0.15);
|
|
ctx.lineWidth = 1;
|
|
ctx.beginPath(); ctx.moveTo(0, h * 0.618); ctx.lineTo(w, h * 0.618); ctx.stroke();
|
|
// Accent circle
|
|
var circX = w * 0.7, circY = h * 0.35;
|
|
var circR = Math.min(w, h) * 0.08;
|
|
var breathe = 1 + Math.sin(t * 0.5) * 0.03;
|
|
Draw.circle(ctx, circX, circY, circR * breathe, H.rgba(c[3], 0.7));
|
|
// Shadow beneath circle
|
|
ctx.fillStyle = H.rgba(c[2], 0.04);
|
|
ctx.beginPath(); ctx.ellipse(circX, h * 0.618, circR * 1.5, 4, 0, 0, Math.PI * 2); ctx.fill();
|
|
// Thin accent line from circle
|
|
ctx.strokeStyle = H.rgba(c[3], 0.2);
|
|
ctx.lineWidth = 2;
|
|
ctx.beginPath();
|
|
ctx.moveTo(circX, circY + circR * breathe);
|
|
ctx.lineTo(circX, h * 0.618);
|
|
ctx.stroke();
|
|
// Sparse dots
|
|
var rng = H.seededRandom(400);
|
|
for (var d = 0; d < 5; d++) {
|
|
var dx = rng() * w, dy = rng() * h;
|
|
Draw.circle(ctx, dx, dy, 2, H.rgba(c[2], 0.08));
|
|
}
|
|
// Small typography-like lines
|
|
var lx = w * 0.15;
|
|
for (var l = 0; l < 4; l++) {
|
|
var lw = 30 + rng() * 60;
|
|
ctx.fillStyle = H.rgba(c[2], 0.06);
|
|
ctx.fillRect(lx, h * 0.72 + l * 12, lw, 2);
|
|
}
|
|
},
|
|
particles: null
|
|
},
|
|
|
|
waves: {
|
|
meta: { id: 'abstract_waves', mood: 'flowing', colors: ['#0a192f', '#172a45', '#00b4d8', '#90e0ef'], tags: ['waves', 'ocean', 'flowing', 'smooth'], description: 'Layered sinusoidal waves undulating in cool blue tones with foam accents', compatibleMusicMoods: ['calm', 'flowing', 'ambient'], recommendedFor: ['creative', 'music', 'physics'] },
|
|
colors: ['#0a192f', '#172a45', '#00b4d8', '#90e0ef'],
|
|
renderBase: function(ctx, w, h, c, Draw, t) {
|
|
Draw.fillGradient(ctx, w, h, [c[0], c[1]], 90);
|
|
},
|
|
renderMid: function(ctx, w, h, c, Draw, t) {
|
|
// Layered wave bands
|
|
var waveLayers = [
|
|
{ y: 0.3, amp: 25, freq: 0.008, speed: 0.4, color: c[2], alpha: 0.08 },
|
|
{ y: 0.4, amp: 30, freq: 0.006, speed: 0.3, color: c[2], alpha: 0.1 },
|
|
{ y: 0.5, amp: 20, freq: 0.01, speed: 0.5, color: c[3], alpha: 0.12 },
|
|
{ y: 0.6, amp: 35, freq: 0.005, speed: 0.25, color: c[2], alpha: 0.15 },
|
|
{ y: 0.7, amp: 15, freq: 0.012, speed: 0.6, color: c[3], alpha: 0.1 },
|
|
{ y: 0.8, amp: 40, freq: 0.004, speed: 0.2, color: c[2], alpha: 0.18 }
|
|
];
|
|
for (var i = 0; i < waveLayers.length; i++) {
|
|
var wl = waveLayers[i];
|
|
ctx.fillStyle = H.rgba(wl.color, wl.alpha);
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, h);
|
|
for (var x = 0; x <= w; x += 3) {
|
|
var y = wl.y * h + Math.sin(x * wl.freq + t * wl.speed) * wl.amp +
|
|
Math.sin(x * wl.freq * 2.3 + t * wl.speed * 1.5) * wl.amp * 0.3;
|
|
ctx.lineTo(x, y);
|
|
}
|
|
ctx.lineTo(w, h);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
}
|
|
// Foam highlights on top wave
|
|
ctx.strokeStyle = H.rgba('#FFFFFF', 0.06);
|
|
ctx.lineWidth = 1.5;
|
|
ctx.beginPath();
|
|
for (var fx = 0; fx <= w; fx += 3) {
|
|
var fy = 0.3 * h + Math.sin(fx * 0.008 + t * 0.4) * 25;
|
|
fx === 0 ? ctx.moveTo(fx, fy) : ctx.lineTo(fx, fy);
|
|
}
|
|
ctx.stroke();
|
|
},
|
|
particles: { type: 'bubbles', count: 15, color: '#90E0EF' }
|
|
},
|
|
|
|
fractals: {
|
|
meta: { id: 'abstract_fractals', mood: 'complex', colors: ['#0a0a0a', '#1a1a2e', '#e94560', '#0f3460'], tags: ['fractal', 'recursive', 'mathematical', 'complex'], description: 'Recursive branching patterns that grow and pulse like living mathematical organisms', compatibleMusicMoods: ['complex', 'electronic', 'mystical'], recommendedFor: ['puzzle', 'physics', 'creative'] },
|
|
colors: ['#0a0a0a', '#1a1a2e', '#e94560', '#0f3460'],
|
|
renderBase: function(ctx, w, h, c, Draw, t) {
|
|
Draw.fillGradient(ctx, w, h, [c[0], c[1]], 90);
|
|
},
|
|
renderMid: function(ctx, w, h, c, Draw, t) {
|
|
// Recursive tree fractal
|
|
function branch(x, y, len, angle, depth, maxDepth) {
|
|
if (depth > maxDepth || len < 3) return;
|
|
var endX = x + Math.cos(angle) * len;
|
|
var endY = y + Math.sin(angle) * len;
|
|
var alpha = 0.15 * (1 - depth / maxDepth) + Math.sin(t * 0.5 + depth) * 0.03;
|
|
var lw = (maxDepth - depth) * 0.8 + 0.5;
|
|
ctx.strokeStyle = H.rgba(depth % 2 === 0 ? c[2] : c[3], alpha);
|
|
ctx.lineWidth = lw;
|
|
ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(endX, endY); ctx.stroke();
|
|
var spread = 0.4 + Math.sin(t * 0.3 + depth * 0.5) * 0.1;
|
|
branch(endX, endY, len * 0.7, angle - spread, depth + 1, maxDepth);
|
|
branch(endX, endY, len * 0.7, angle + spread, depth + 1, maxDepth);
|
|
}
|
|
// Main tree from bottom center
|
|
branch(w * 0.5, h * 0.85, h * 0.18, -Math.PI / 2, 0, 9);
|
|
// Smaller trees
|
|
branch(w * 0.2, h * 0.9, h * 0.1, -Math.PI / 2, 0, 7);
|
|
branch(w * 0.8, h * 0.9, h * 0.12, -Math.PI / 2, 0, 7);
|
|
// Spiral accents
|
|
var rng = H.seededRandom(500);
|
|
for (var sp = 0; sp < 4; sp++) {
|
|
var sx = rng() * w, sy = rng() * h * 0.6;
|
|
ctx.strokeStyle = H.rgba(c[2], 0.06);
|
|
ctx.lineWidth = 1;
|
|
ctx.beginPath();
|
|
for (var sa = 0; sa < Math.PI * 6; sa += 0.1) {
|
|
var sr = sa * 2 + Math.sin(t * 0.3 + sp) * 2;
|
|
var ssx = sx + Math.cos(sa) * sr;
|
|
var ssy = sy + Math.sin(sa) * sr;
|
|
sa === 0 ? ctx.moveTo(ssx, ssy) : ctx.lineTo(ssx, ssy);
|
|
}
|
|
ctx.stroke();
|
|
}
|
|
},
|
|
particles: { type: 'sparkles', count: 20, color: '#E94560' }
|
|
},
|
|
|
|
kaleidoscope: {
|
|
meta: { id: 'abstract_kaleidoscope', mood: 'playful', colors: ['#1a1a2e', '#16213e', '#e94560', '#ffd166'], tags: ['kaleidoscope', 'symmetry', 'colorful', 'playful'], description: 'Symmetric radial patterns in vibrant colors rotating like a living kaleidoscope', compatibleMusicMoods: ['playful', 'energetic', 'electronic'], recommendedFor: ['music', 'creative', 'puzzle'] },
|
|
colors: ['#1a1a2e', '#16213e', '#e94560', '#ffd166'],
|
|
renderBase: function(ctx, w, h, c, Draw, t) {
|
|
Draw.fillGradient(ctx, w, h, [c[0], c[1]], 90);
|
|
},
|
|
renderMid: function(ctx, w, h, c, Draw, t) {
|
|
var cx = w / 2, cy = h / 2;
|
|
var segments = 8;
|
|
var rng = H.seededRandom(600);
|
|
ctx.save();
|
|
ctx.translate(cx, cy);
|
|
// Draw mirrored segments
|
|
for (var seg = 0; seg < segments; seg++) {
|
|
var segAngle = (seg / segments) * Math.PI * 2 + t * 0.1;
|
|
ctx.save();
|
|
ctx.rotate(segAngle);
|
|
// Shapes within each segment
|
|
for (var s = 0; s < 5; s++) {
|
|
var sd = 40 + s * 35 + Math.sin(t * 0.4 + s) * 10;
|
|
var sSize = 8 + rng() * 15;
|
|
var sColor = s % 2 === 0 ? c[2] : c[3];
|
|
var sAlpha = 0.12 + Math.sin(t * 0.6 + s * 0.8) * 0.05;
|
|
Draw.polygon(ctx, sd, 0, sSize, 3 + (s % 3), H.rgba(sColor, sAlpha), t * 0.2 + s);
|
|
}
|
|
// Connecting arcs
|
|
ctx.strokeStyle = H.rgba(c[2], 0.06);
|
|
ctx.lineWidth = 1;
|
|
ctx.beginPath();
|
|
ctx.arc(0, 0, 80 + Math.sin(t * 0.5) * 10, 0, Math.PI / segments);
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
}
|
|
ctx.restore();
|
|
// Central glow
|
|
var centerPulse = 0.15 + Math.sin(t * 0.8) * 0.05;
|
|
Draw.glow(ctx, cx, cy, 60, H.rgba(c[3], centerPulse));
|
|
Draw.glow(ctx, cx, cy, 30, H.rgba(c[2], centerPulse * 1.5));
|
|
// Outer ring
|
|
ctx.strokeStyle = H.rgba(c[3], 0.08 + Math.sin(t * 0.3) * 0.03);
|
|
ctx.lineWidth = 2;
|
|
ctx.beginPath();
|
|
ctx.arc(cx, cy, Math.min(w, h) * 0.35, 0, Math.PI * 2);
|
|
ctx.stroke();
|
|
},
|
|
particles: { type: 'sparkles', count: 25, color: '#FFD166' }
|
|
},
|
|
|
|
matrix: {
|
|
meta: { id: 'abstract_matrix', mood: 'digital', colors: ['#000000', '#001100', '#00ff41', '#008f11'], tags: ['matrix', 'code', 'digital', 'hacker'], description: 'Cascading columns of digital characters streaming down like the matrix rain', compatibleMusicMoods: ['dark', 'electronic', 'tense'], recommendedFor: ['action', 'puzzle', 'trivia'] },
|
|
colors: ['#000000', '#001100', '#00ff41', '#008f11'],
|
|
renderBase: function(ctx, w, h, c, Draw, t) {
|
|
Draw.fillGradient(ctx, w, h, [c[0], c[1]], 90);
|
|
// CRT vignette
|
|
var vignette = Draw.radialGradient(ctx, w / 2, h / 2, Math.max(w, h) * 0.7, [
|
|
[0, H.rgba(c[1], 0)],
|
|
[0.7, H.rgba(c[0], 0.3)],
|
|
[1, H.rgba(c[0], 0.8)]
|
|
]);
|
|
ctx.fillStyle = vignette;
|
|
ctx.fillRect(0, 0, w, h);
|
|
},
|
|
renderMid: function(ctx, w, h, c, Draw, t) {
|
|
var rng = H.seededRandom(700);
|
|
var columns = Math.floor(w / 16);
|
|
var chars = '01アイウエオカキクケコサシスセソ{}[]<>+=/*#@$%&';
|
|
ctx.font = '13px monospace';
|
|
// Column rain
|
|
for (var col = 0; col < columns; col++) {
|
|
var cx = col * 16 + 4;
|
|
var speed = 0.3 + rng() * 0.7;
|
|
var colOffset = rng() * h;
|
|
var colLen = 8 + Math.floor(rng() * 20);
|
|
// Calculate head position
|
|
var headY = ((t * speed * 60 + colOffset) % (h + colLen * 16)) - colLen * 16;
|
|
for (var ch = 0; ch < colLen; ch++) {
|
|
var cy = headY + ch * 16;
|
|
if (cy < -16 || cy > h + 16) continue;
|
|
var charIdx = Math.floor(rng() * chars.length);
|
|
// Head is brightest, tail fades
|
|
var fade = 1 - (ch / colLen);
|
|
if (ch === 0) {
|
|
ctx.fillStyle = H.rgba('#FFFFFF', 0.8);
|
|
} else {
|
|
ctx.fillStyle = H.rgba(c[2], fade * 0.5 + Math.sin(t * 3 + col + ch) * 0.08);
|
|
}
|
|
ctx.fillText(chars[charIdx], cx, cy);
|
|
}
|
|
}
|
|
// Occasional bright flashes
|
|
if (Math.sin(t * 1.3) > 0.95) {
|
|
var flashX = Math.floor(rng() * columns) * 16;
|
|
var flashY = rng() * h;
|
|
Draw.glow(ctx, flashX, flashY, 30, H.rgba(c[2], 0.3));
|
|
}
|
|
// Subtle horizontal scan line
|
|
var scanY = (t * 40) % h;
|
|
ctx.fillStyle = H.rgba(c[2], 0.03);
|
|
ctx.fillRect(0, scanY, w, 2);
|
|
},
|
|
particles: null
|
|
}
|
|
|
|
});
|
|
})(window.BackgroundEngine);
|