552 lines
26 KiB
JavaScript
552 lines
26 KiB
JavaScript
// retro.js — Retro theme backgrounds
|
|
// Variants: pixelGrid, arcadeCabinet, crtEffects, vaporwave, scanlines, glitch, eightBit, synthwave
|
|
|
|
(function(engine) {
|
|
if (!engine) return;
|
|
var D = engine.Draw, H = engine.helpers;
|
|
|
|
engine.registerTheme('retro', {
|
|
|
|
pixelGrid: {
|
|
meta: { id: 'retro_pixelGrid', mood: 'nostalgic', colors: ['#1a1a2e', '#16213e', '#00ff88', '#ff6600'], tags: ['pixel', 'grid', 'retro', 'classic'], description: 'A chunky pixel grid with classic 8-bit color blocks and pulsing cursor', compatibleMusicMoods: ['retro', 'playful', 'electronic'], recommendedFor: ['action', 'puzzle', 'creative'] },
|
|
colors: ['#1a1a2e', '#16213e', '#00ff88', '#ff6600'],
|
|
renderBase: function(ctx, w, h, c, Draw, t) {
|
|
Draw.fillGradient(ctx, w, h, [c[0], c[1]], 90);
|
|
Draw.grid(ctx, w, h, 32, H.rgba(c[2], 0.04), 1);
|
|
},
|
|
renderMid: function(ctx, w, h, c, Draw, t) {
|
|
var rng = H.seededRandom(100);
|
|
var pixelSize = 32;
|
|
var cols = Math.ceil(w / pixelSize);
|
|
var rows = Math.ceil(h / pixelSize);
|
|
// Scattered colored pixels
|
|
for (var i = 0; i < 30; i++) {
|
|
var px = Math.floor(rng() * cols) * pixelSize;
|
|
var py = Math.floor(rng() * rows) * pixelSize;
|
|
var pColor = rng() > 0.5 ? c[2] : c[3];
|
|
var pAlpha = 0.06 + rng() * 0.1 + Math.sin(t * 0.8 + i * 0.5) * 0.03;
|
|
Draw.rect(ctx, px, py, pixelSize - 1, pixelSize - 1, H.rgba(pColor, pAlpha));
|
|
}
|
|
// Pixel art sprite (simple ghost/invader shape)
|
|
var spriteX = w * 0.5, spriteY = h * 0.4;
|
|
var sp = pixelSize * 0.5;
|
|
var spriteMap = [
|
|
[0,0,1,1,1,0,0],
|
|
[0,1,1,1,1,1,0],
|
|
[1,1,0,1,0,1,1],
|
|
[1,1,1,1,1,1,1],
|
|
[1,0,1,0,1,0,1],
|
|
[0,1,0,1,0,1,0]
|
|
];
|
|
var spriteAlpha = 0.15 + Math.sin(t * 0.6) * 0.05;
|
|
var sOffX = spriteX - (spriteMap[0].length * sp) / 2;
|
|
var sOffY = spriteY - (spriteMap.length * sp) / 2;
|
|
for (var sr = 0; sr < spriteMap.length; sr++) {
|
|
for (var sc = 0; sc < spriteMap[sr].length; sc++) {
|
|
if (spriteMap[sr][sc]) {
|
|
Draw.rect(ctx, sOffX + sc * sp, sOffY + sr * sp, sp - 1, sp - 1, H.rgba(c[2], spriteAlpha));
|
|
}
|
|
}
|
|
}
|
|
// Blinking cursor
|
|
var cursorOn = Math.sin(t * 3) > 0;
|
|
if (cursorOn) {
|
|
Draw.rect(ctx, w * 0.3, h * 0.75, pixelSize * 0.6, pixelSize, H.rgba(c[2], 0.4));
|
|
}
|
|
// Score text (pixel-styled line)
|
|
for (var sc2 = 0; sc2 < 6; sc2++) {
|
|
Draw.rect(ctx, w * 0.35 + sc2 * 12, h * 0.12, 8, 3, H.rgba(c[3], 0.12));
|
|
}
|
|
},
|
|
particles: { type: 'sparkles', count: 10, color: '#00FF88' }
|
|
},
|
|
|
|
arcadeCabinet: {
|
|
meta: { id: 'retro_arcadeCabinet', mood: 'fun', colors: ['#0a0a15', '#1a0a2e', '#ff2277', '#ffcc00'], tags: ['arcade', 'cabinet', 'neon', 'classic'], description: 'The inside view of a dark arcade with neon-lit cabinet edges and attract mode glow', compatibleMusicMoods: ['retro', 'energetic', 'playful'], recommendedFor: ['action', 'racing', 'sports'] },
|
|
colors: ['#0a0a15', '#1a0a2e', '#ff2277', '#ffcc00'],
|
|
renderBase: function(ctx, w, h, c, Draw, t) {
|
|
Draw.fillGradient(ctx, w, h, [c[0], c[1]], 90);
|
|
// Ambient arcade glow
|
|
Draw.glow(ctx, w * 0.5, h * 0.5, w * 0.5, H.rgba(c[2], 0.04));
|
|
},
|
|
renderMid: function(ctx, w, h, c, Draw, t) {
|
|
var rng = H.seededRandom(200);
|
|
// Floor tiles (perspective grid)
|
|
ctx.strokeStyle = H.rgba(c[2], 0.05);
|
|
ctx.lineWidth = 1;
|
|
for (var fl = 0; fl < 15; fl++) {
|
|
var fy = h * 0.7 + fl * h * 0.02;
|
|
ctx.beginPath(); ctx.moveTo(0, fy); ctx.lineTo(w, fy); ctx.stroke();
|
|
}
|
|
for (var fv = 0; fv < 12; fv++) {
|
|
var fx = fv * w / 12;
|
|
ctx.beginPath(); ctx.moveTo(fx, h * 0.7); ctx.lineTo(w * 0.5 + (fx - w * 0.5) * 0.3, h); ctx.stroke();
|
|
}
|
|
// Arcade cabinets (silhouettes with neon trim)
|
|
var cabinets = [
|
|
{ x: 0.12, scale: 0.9 }, { x: 0.32, scale: 1.0 },
|
|
{ x: 0.52, scale: 1.0 }, { x: 0.72, scale: 0.9 }, { x: 0.88, scale: 0.8 }
|
|
];
|
|
for (var cab = 0; cab < cabinets.length; cab++) {
|
|
var cbx = cabinets[cab].x * w;
|
|
var cbs = cabinets[cab].scale;
|
|
var cbw = w * 0.12 * cbs;
|
|
var cbh = h * 0.45 * cbs;
|
|
var cby = h * 0.68 - cbh;
|
|
// Cabinet body
|
|
Draw.rect(ctx, cbx - cbw / 2, cby, cbw, cbh, '#0a0a12');
|
|
// Screen area
|
|
var scrW = cbw * 0.7, scrH = cbh * 0.4;
|
|
var scrX = cbx - scrW / 2, scrY = cby + cbh * 0.1;
|
|
var screenColor = rng() > 0.5 ? c[2] : c[3];
|
|
var screenGlow = 0.15 + Math.sin(t * 1.5 + cab * 1.2) * 0.08;
|
|
Draw.rect(ctx, scrX, scrY, scrW, scrH, H.rgba(screenColor, screenGlow));
|
|
Draw.glow(ctx, cbx, scrY + scrH / 2, scrW * 0.6, H.rgba(screenColor, screenGlow * 0.3));
|
|
// Neon trim
|
|
ctx.strokeStyle = H.rgba(screenColor, 0.25 + Math.sin(t * 2 + cab) * 0.1);
|
|
ctx.lineWidth = 1.5;
|
|
ctx.strokeRect(scrX - 2, scrY - 2, scrW + 4, scrH + 4);
|
|
// Control panel
|
|
Draw.rect(ctx, cbx - cbw * 0.4, cby + cbh * 0.7, cbw * 0.8, cbh * 0.1, '#151520');
|
|
// Joystick and buttons
|
|
Draw.circle(ctx, cbx - cbw * 0.15, cby + cbh * 0.75, 3, '#333');
|
|
for (var btn = 0; btn < 3; btn++) {
|
|
Draw.circle(ctx, cbx + cbw * 0.05 + btn * 10, cby + cbh * 0.75, 3,
|
|
H.rgba(btn === 0 ? '#FF2277' : btn === 1 ? '#2277FF' : '#FFCC00', 0.4));
|
|
}
|
|
}
|
|
// "INSERT COIN" text glow
|
|
var coinBlink = Math.sin(t * 2) > 0;
|
|
if (coinBlink) {
|
|
ctx.font = 'bold 14px monospace';
|
|
ctx.fillStyle = H.rgba(c[3], 0.3);
|
|
ctx.textAlign = 'center';
|
|
ctx.fillText('INSERT COIN', w * 0.5, h * 0.88);
|
|
ctx.textAlign = 'start';
|
|
}
|
|
// Neon sign at top
|
|
ctx.strokeStyle = H.rgba(c[2], 0.2 + Math.sin(t * 1.8) * 0.08);
|
|
ctx.lineWidth = 2;
|
|
ctx.beginPath(); ctx.moveTo(w * 0.25, h * 0.08); ctx.lineTo(w * 0.75, h * 0.08); ctx.stroke();
|
|
Draw.glow(ctx, w * 0.5, h * 0.08, w * 0.3, H.rgba(c[2], 0.04));
|
|
},
|
|
particles: { type: 'dust', count: 15, color: '#FF2277' }
|
|
},
|
|
|
|
crtEffects: {
|
|
meta: { id: 'retro_crtEffects', mood: 'vintage', colors: ['#0a0a0a', '#0f1a0f', '#33ff33', '#88ff88'], tags: ['crt', 'monitor', 'phosphor', 'vintage'], description: 'Authentic CRT monitor with phosphor glow, barrel distortion and scan line interference', compatibleMusicMoods: ['retro', 'dark', 'electronic'], recommendedFor: ['action', 'puzzle', 'trivia'] },
|
|
colors: ['#0a0a0a', '#0f1a0f', '#33ff33', '#88ff88'],
|
|
renderBase: function(ctx, w, h, c, Draw, t) {
|
|
Draw.fillGradient(ctx, w, h, [c[0], c[1]], 90);
|
|
// Phosphor glow in center
|
|
Draw.glow(ctx, w * 0.5, h * 0.5, Math.min(w, h) * 0.5, H.rgba(c[2], 0.03));
|
|
},
|
|
renderMid: function(ctx, w, h, c, Draw, t) {
|
|
// Scan lines
|
|
Draw.scanlines(ctx, w, h, 3, H.rgba(c[2], 0.03 + Math.sin(t * 0.5) * 0.01));
|
|
// RGB sub-pixel columns
|
|
for (var col = 0; col < w; col += 3) {
|
|
Draw.rect(ctx, col, 0, 1, h, H.rgba('#FF0000', 0.008));
|
|
Draw.rect(ctx, col + 1, 0, 1, h, H.rgba('#00FF00', 0.008));
|
|
Draw.rect(ctx, col + 2, 0, 1, h, H.rgba('#0000FF', 0.008));
|
|
}
|
|
// Rolling interference band
|
|
var bandY = ((t * 30) % (h + 80)) - 40;
|
|
ctx.fillStyle = H.rgba(c[2], 0.04);
|
|
ctx.fillRect(0, bandY, w, 40);
|
|
ctx.fillStyle = H.rgba(c[2], 0.06);
|
|
ctx.fillRect(0, bandY + 15, w, 8);
|
|
// Static noise bursts
|
|
var rng = H.seededRandom(Math.floor(t * 5) + 300);
|
|
if (rng() > 0.7) {
|
|
for (var nz = 0; nz < 50; nz++) {
|
|
var nx = rng() * w, ny = rng() * h;
|
|
var nw = 1 + rng() * 4;
|
|
Draw.rect(ctx, nx, ny, nw, 1, H.rgba(c[2], rng() * 0.15));
|
|
}
|
|
}
|
|
// Faux text content
|
|
var txtrng = H.seededRandom(301);
|
|
ctx.font = '12px monospace';
|
|
for (var ln = 0; ln < 8; ln++) {
|
|
var lineY = h * 0.25 + ln * 22;
|
|
var lineStr = '';
|
|
var lineLen = 10 + Math.floor(txtrng() * 25);
|
|
for (var ch = 0; ch < lineLen; ch++) {
|
|
lineStr += String.fromCharCode(33 + Math.floor(txtrng() * 93));
|
|
}
|
|
var txtAlpha = 0.08 + Math.sin(t * 0.4 + ln * 0.5) * 0.03;
|
|
ctx.fillStyle = H.rgba(c[2], txtAlpha);
|
|
ctx.fillText(lineStr, w * 0.15, lineY);
|
|
}
|
|
// Prompt cursor
|
|
var cursorBlink = Math.sin(t * 3) > 0;
|
|
if (cursorBlink) {
|
|
Draw.rect(ctx, w * 0.15, h * 0.25 + 8 * 22, 8, 14, H.rgba(c[2], 0.3));
|
|
}
|
|
// Vignette / barrel distortion feel
|
|
var vig = Draw.radialGradient(ctx, w / 2, h / 2, Math.max(w, h) * 0.65, [
|
|
[0, H.rgba(c[0], 0)],
|
|
[0.6, H.rgba(c[0], 0)],
|
|
[1, H.rgba(c[0], 0.7)]
|
|
]);
|
|
ctx.fillStyle = vig;
|
|
ctx.fillRect(0, 0, w, h);
|
|
// CRT bezel border
|
|
ctx.strokeStyle = H.rgba('#333333', 0.3);
|
|
ctx.lineWidth = 8;
|
|
ctx.strokeRect(4, 4, w - 8, h - 8);
|
|
},
|
|
particles: null
|
|
},
|
|
|
|
vaporwave: {
|
|
meta: { id: 'retro_vaporwave', mood: 'aesthetic', colors: ['#0d0221', '#261447', '#ff71ce', '#01cdfe'], tags: ['vaporwave', 'aesthetic', '90s', 'neon'], description: 'A e s t h e t i c sunset horizon with a perspective grid, palm silhouettes and neon glow', compatibleMusicMoods: ['dreamy', 'retro', 'calm'], recommendedFor: ['creative', 'music', 'racing'] },
|
|
colors: ['#0d0221', '#261447', '#ff71ce', '#01cdfe'],
|
|
renderBase: function(ctx, w, h, c, Draw, t) {
|
|
// Sunset gradient
|
|
Draw.fillGradient(ctx, w, h, [c[0], c[1], '#ff71ce', '#ff9de2'], 90);
|
|
// Sun
|
|
var sunY = h * 0.42 + Math.sin(t * 0.2) * 3;
|
|
var sunGrad = Draw.linearGradient(ctx, 0, sunY - 60, 0, sunY + 60, [
|
|
[0, '#FF6B9D'], [0.5, '#FFBE0B'], [1, '#FF006E']
|
|
]);
|
|
ctx.fillStyle = sunGrad;
|
|
ctx.beginPath(); ctx.arc(w * 0.5, sunY, 60, 0, Math.PI * 2); ctx.fill();
|
|
// Sun horizontal stripes
|
|
for (var ss = 0; ss < 6; ss++) {
|
|
var ssy = sunY + ss * 10 - 10;
|
|
var ssH = 3 + ss * 0.8;
|
|
Draw.rect(ctx, w * 0.5 - 60, ssy, 120, ssH, c[0]);
|
|
}
|
|
Draw.glow(ctx, w * 0.5, sunY, 120, H.rgba('#FF71CE', 0.15));
|
|
},
|
|
renderMid: function(ctx, w, h, c, Draw, t) {
|
|
// Horizon line
|
|
var horizonY = h * 0.55;
|
|
Draw.rect(ctx, 0, horizonY, w, h - horizonY, '#0d0221');
|
|
// Perspective grid on ground plane
|
|
var gridColor = H.rgba(c[2], 0.2);
|
|
ctx.strokeStyle = gridColor;
|
|
ctx.lineWidth = 1;
|
|
// Horizontal lines (receding)
|
|
for (var gy = 0; gy < 20; gy++) {
|
|
var lineFrac = gy / 20;
|
|
var lineY = horizonY + lineFrac * lineFrac * (h - horizonY);
|
|
ctx.beginPath(); ctx.moveTo(0, lineY); ctx.lineTo(w, lineY); ctx.stroke();
|
|
}
|
|
// Vertical lines (converging at center horizon)
|
|
for (var gx = -8; gx <= 8; gx++) {
|
|
var topX = w * 0.5 + gx * 2;
|
|
var botX = w * 0.5 + gx * w * 0.08;
|
|
ctx.strokeStyle = H.rgba(c[3], 0.15);
|
|
ctx.beginPath(); ctx.moveTo(topX, horizonY); ctx.lineTo(botX, h); ctx.stroke();
|
|
}
|
|
// Palm tree silhouettes
|
|
var rng = H.seededRandom(400);
|
|
var palms = [{ x: 0.12, s: 1.0 }, { x: 0.85, s: 0.9 }, { x: 0.05, s: 0.7 }];
|
|
for (var p = 0; p < palms.length; p++) {
|
|
var px = palms[p].x * w;
|
|
var ps = palms[p].s;
|
|
var trunkH = h * 0.35 * ps;
|
|
var palmBot = horizonY;
|
|
// Trunk
|
|
ctx.strokeStyle = '#0d0221';
|
|
ctx.lineWidth = 4 * ps;
|
|
ctx.beginPath();
|
|
ctx.moveTo(px, palmBot);
|
|
ctx.quadraticCurveTo(px + 10 * ps, palmBot - trunkH * 0.6, px + 5 * ps, palmBot - trunkH);
|
|
ctx.stroke();
|
|
// Fronds
|
|
for (var fr = 0; fr < 6; fr++) {
|
|
var frAngle = (fr / 6) * Math.PI * 2;
|
|
var frLen = 30 * ps + Math.sin(t * 0.8 + fr) * 3;
|
|
var sway = Math.sin(t * 0.5 + fr * 0.3) * 5;
|
|
ctx.strokeStyle = '#0d0221';
|
|
ctx.lineWidth = 2 * ps;
|
|
ctx.beginPath();
|
|
ctx.moveTo(px + 5 * ps, palmBot - trunkH);
|
|
ctx.quadraticCurveTo(
|
|
px + 5 * ps + Math.cos(frAngle) * frLen * 0.5 + sway,
|
|
palmBot - trunkH + Math.sin(frAngle) * frLen * 0.3 - 10,
|
|
px + 5 * ps + Math.cos(frAngle) * frLen + sway,
|
|
palmBot - trunkH + Math.sin(frAngle) * frLen * 0.5 + 5
|
|
);
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
// Neon highlights on horizon
|
|
ctx.strokeStyle = H.rgba(c[3], 0.3 + Math.sin(t) * 0.1);
|
|
ctx.lineWidth = 2;
|
|
ctx.beginPath(); ctx.moveTo(0, horizonY); ctx.lineTo(w, horizonY); ctx.stroke();
|
|
},
|
|
particles: { type: 'stars', count: 40, color: '#FF71CE' }
|
|
},
|
|
|
|
scanlines: {
|
|
meta: { id: 'retro_scanlines', mood: 'technical', colors: ['#0a0a1a', '#141428', '#00aaff', '#ff4444'], tags: ['scanlines', 'display', 'technical', 'monitor'], description: 'Dense horizontal scan lines over a shifting color field with signal noise artifacts', compatibleMusicMoods: ['electronic', 'tense', 'dark'], recommendedFor: ['action', 'puzzle', 'physics'] },
|
|
colors: ['#0a0a1a', '#141428', '#00aaff', '#ff4444'],
|
|
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) {
|
|
// Color field blocks shifting
|
|
var rng = H.seededRandom(500);
|
|
for (var blk = 0; blk < 8; blk++) {
|
|
var bx = rng() * w * 0.8;
|
|
var by = rng() * h * 0.8;
|
|
var bw = w * 0.1 + rng() * w * 0.2;
|
|
var bh = h * 0.05 + rng() * h * 0.15;
|
|
var bColor = rng() > 0.5 ? c[2] : c[3];
|
|
var bAlpha = 0.03 + Math.sin(t * 0.3 + blk * 0.7) * 0.02;
|
|
Draw.rect(ctx, bx, by, bw, bh, H.rgba(bColor, bAlpha));
|
|
}
|
|
// Dense scanlines
|
|
Draw.scanlines(ctx, w, h, 2, H.rgba('#FFFFFF', 0.03));
|
|
// Thicker periodic scanlines
|
|
for (var tsl = 0; tsl < h; tsl += 16) {
|
|
Draw.rect(ctx, 0, tsl, w, 1, H.rgba(c[2], 0.02 + Math.sin(t * 0.5 + tsl * 0.01) * 0.01));
|
|
}
|
|
// Horizontal displacement glitch bands
|
|
var glitchRng = H.seededRandom(Math.floor(t * 2) + 501);
|
|
if (glitchRng() > 0.6) {
|
|
for (var gb = 0; gb < 3; gb++) {
|
|
var gy = glitchRng() * h;
|
|
var gHeight = 2 + glitchRng() * 8;
|
|
var gOffset = (glitchRng() - 0.5) * 20;
|
|
ctx.fillStyle = H.rgba(c[2], 0.08);
|
|
ctx.fillRect(gOffset, gy, w, gHeight);
|
|
ctx.fillStyle = H.rgba(c[3], 0.06);
|
|
ctx.fillRect(-gOffset, gy + 1, w, gHeight * 0.5);
|
|
}
|
|
}
|
|
// Signal meter bars
|
|
for (var bar = 0; bar < 12; bar++) {
|
|
var barH = h * 0.03 + Math.sin(t * 2 + bar * 0.5) * h * 0.02;
|
|
var barColor = bar < 8 ? c[2] : c[3];
|
|
Draw.rect(ctx, w * 0.05 + bar * w * 0.04, h * 0.9 - barH, w * 0.03, barH, H.rgba(barColor, 0.15));
|
|
}
|
|
// Crosshair / target
|
|
var chAlpha = 0.1 + Math.sin(t * 0.6) * 0.04;
|
|
ctx.strokeStyle = H.rgba(c[2], chAlpha);
|
|
ctx.lineWidth = 1;
|
|
ctx.beginPath(); ctx.arc(w * 0.5, h * 0.5, 30, 0, Math.PI * 2); ctx.stroke();
|
|
ctx.beginPath(); ctx.moveTo(w * 0.5 - 40, h * 0.5); ctx.lineTo(w * 0.5 + 40, h * 0.5); ctx.stroke();
|
|
ctx.beginPath(); ctx.moveTo(w * 0.5, h * 0.5 - 40); ctx.lineTo(w * 0.5, h * 0.5 + 40); ctx.stroke();
|
|
},
|
|
particles: null
|
|
},
|
|
|
|
glitch: {
|
|
meta: { id: 'retro_glitch', mood: 'chaotic', colors: ['#0a0a0a', '#1a0a1a', '#ff0055', '#00ffcc'], tags: ['glitch', 'broken', 'chaotic', 'digital'], description: 'Fragmented display with chromatic aberration, displaced blocks and corrupted data streaks', compatibleMusicMoods: ['chaotic', 'electronic', 'tense'], recommendedFor: ['action', 'racing', 'physics'] },
|
|
colors: ['#0a0a0a', '#1a0a1a', '#ff0055', '#00ffcc'],
|
|
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(Math.floor(t * 3) + 600);
|
|
// Displaced rectangular blocks
|
|
for (var blk = 0; blk < 6; blk++) {
|
|
var bx = rng() * w;
|
|
var by = rng() * h;
|
|
var bw = 20 + rng() * 100;
|
|
var bh = 3 + rng() * 20;
|
|
var shift = (rng() - 0.5) * 30;
|
|
// RGB channel separation
|
|
Draw.rect(ctx, bx + shift, by, bw, bh, H.rgba(c[2], 0.08 + rng() * 0.1));
|
|
Draw.rect(ctx, bx - shift, by + 1, bw, bh, H.rgba(c[3], 0.06 + rng() * 0.08));
|
|
}
|
|
// Static deterministic elements
|
|
var srng = H.seededRandom(601);
|
|
// Corrupted horizontal streaks
|
|
for (var str = 0; str < 15; str++) {
|
|
var sy = srng() * h;
|
|
var sx = srng() * w * 0.3;
|
|
var sw = w * 0.2 + srng() * w * 0.5;
|
|
var jitter = Math.sin(t * 4 + str * 2) > 0.7 ? (srng() - 0.5) * 10 : 0;
|
|
Draw.rect(ctx, sx + jitter, sy, sw, 1, H.rgba(srng() > 0.5 ? c[2] : c[3], 0.06));
|
|
}
|
|
// Large chromatic aberration text
|
|
ctx.font = 'bold 48px monospace';
|
|
var errAlpha = 0.06 + Math.sin(t * 1.5) * 0.03;
|
|
var errShift = 3 + Math.sin(t * 5) * 2;
|
|
ctx.fillStyle = H.rgba(c[2], errAlpha);
|
|
ctx.fillText('ERR', w * 0.35 - errShift, h * 0.5);
|
|
ctx.fillStyle = H.rgba(c[3], errAlpha);
|
|
ctx.fillText('ERR', w * 0.35 + errShift, h * 0.5 + 2);
|
|
ctx.fillStyle = H.rgba('#FFFFFF', errAlpha * 0.5);
|
|
ctx.fillText('ERR', w * 0.35, h * 0.5 + 1);
|
|
// Data corruption blocks
|
|
for (var dc = 0; dc < 10; dc++) {
|
|
var dcx = srng() * w, dcy = srng() * h;
|
|
var dcw = 4 + srng() * 12, dch = 4 + srng() * 12;
|
|
var dcPulse = Math.sin(t * 3 + dc * 1.1) > 0.5;
|
|
if (dcPulse) {
|
|
Draw.rect(ctx, dcx, dcy, dcw, dch, H.rgba(srng() > 0.5 ? c[2] : c[3], 0.15));
|
|
}
|
|
}
|
|
// Thin scan lines
|
|
Draw.scanlines(ctx, w, h, 4, H.rgba('#FFFFFF', 0.02));
|
|
// Occasional full-width flash
|
|
if (Math.sin(t * 0.8) > 0.97) {
|
|
Draw.rect(ctx, 0, 0, w, h, H.rgba('#FFFFFF', 0.03));
|
|
}
|
|
},
|
|
particles: { type: 'particles', count: 15, color: '#FF0055' }
|
|
},
|
|
|
|
eightBit: {
|
|
meta: { id: 'retro_eightBit', mood: 'cheerful', colors: ['#2b2b6e', '#3d3d94', '#5bc0eb', '#fde74c'], tags: ['8bit', 'nintendo', 'cheerful', 'classic'], description: 'A charming 8-bit scene with blocky pixel clouds, hills and a bright primary-color sky', compatibleMusicMoods: ['playful', 'retro', 'cheerful'], recommendedFor: ['action', 'puzzle', 'creative'] },
|
|
colors: ['#2b2b6e', '#3d3d94', '#5bc0eb', '#fde74c'],
|
|
renderBase: function(ctx, w, h, c, Draw, t) {
|
|
// Bright 8-bit sky
|
|
Draw.fillGradient(ctx, w, h, [c[0], c[1], c[2]], 90);
|
|
},
|
|
renderMid: function(ctx, w, h, c, Draw, t) {
|
|
var rng = H.seededRandom(700);
|
|
var px = 8; // pixel size for blocky look
|
|
// Pixel sun
|
|
var sunX = w * 0.75, sunY = h * 0.2;
|
|
var sunPx = [
|
|
[-1,-2],[0,-2],[1,-2],
|
|
[-2,-1],[-1,-1],[0,-1],[1,-1],[2,-1],
|
|
[-2,0],[-1,0],[0,0],[1,0],[2,0],
|
|
[-2,1],[-1,1],[0,1],[1,1],[2,1],
|
|
[-1,2],[0,2],[1,2]
|
|
];
|
|
for (var sp = 0; sp < sunPx.length; sp++) {
|
|
Draw.rect(ctx, sunX + sunPx[sp][0] * px, sunY + sunPx[sp][1] * px, px, px, c[3]);
|
|
}
|
|
// Pixel clouds
|
|
var cloudPositions = [{ x: 0.15, y: 0.18 }, { x: 0.45, y: 0.12 }, { x: 0.8, y: 0.22 }];
|
|
var cloudShape = [[0,0],[1,0],[2,0],[3,0],[-1,1],[0,1],[1,1],[2,1],[3,1],[4,1],[0,2],[1,2],[2,2],[3,2]];
|
|
for (var cl = 0; cl < cloudPositions.length; cl++) {
|
|
var clx = cloudPositions[cl].x * w + Math.sin(t * 0.3 + cl) * 5;
|
|
var cly = cloudPositions[cl].y * h;
|
|
for (var cp = 0; cp < cloudShape.length; cp++) {
|
|
Draw.rect(ctx, clx + cloudShape[cp][0] * px, cly + cloudShape[cp][1] * px, px, px, '#FFFFFF');
|
|
}
|
|
}
|
|
// Pixel hills (green terrain at bottom)
|
|
var groundY = h * 0.7;
|
|
Draw.rect(ctx, 0, groundY, w, h - groundY, '#4CAF50');
|
|
// Stepped hill shapes
|
|
var hillRng = H.seededRandom(701);
|
|
for (var hill = 0; hill < 5; hill++) {
|
|
var hx = hillRng() * w;
|
|
var hPeakH = 4 + Math.floor(hillRng() * 6);
|
|
for (var hr = 0; hr < hPeakH; hr++) {
|
|
var hRowW = (hPeakH - hr) * 2 + 1;
|
|
for (var hc = 0; hc < hRowW; hc++) {
|
|
Draw.rect(ctx, hx + (hc - Math.floor(hRowW / 2)) * px, groundY - hr * px, px, px, '#388E3C');
|
|
}
|
|
}
|
|
}
|
|
// Ground detail
|
|
for (var gd = 0; gd < 15; gd++) {
|
|
var gdx = Math.floor(rng() * (w / px)) * px;
|
|
var gdy = groundY + Math.floor(rng() * ((h - groundY) / px)) * px;
|
|
Draw.rect(ctx, gdx, gdy, px, px, H.rgba('#2E7D32', 0.5));
|
|
}
|
|
// Small pixel flowers
|
|
var flowerColors = ['#FF5252', '#FFD740', '#E040FB', '#FFFFFF'];
|
|
for (var fl = 0; fl < 8; fl++) {
|
|
var flx = Math.floor(rng() * (w / px)) * px;
|
|
var fly = groundY + px + Math.floor(rng() * 3) * px;
|
|
Draw.rect(ctx, flx, fly, px, px, flowerColors[fl % flowerColors.length]);
|
|
}
|
|
// Coin block (floating, animated)
|
|
var coinY = h * 0.5 + Math.sin(t * 2) * 2;
|
|
var blockPulse = Math.sin(t * 1.5) > 0.8 ? '#FFD740' : c[3];
|
|
Draw.rect(ctx, w * 0.5, coinY, px * 2, px * 2, blockPulse);
|
|
ctx.strokeStyle = H.rgba('#000', 0.3);
|
|
ctx.lineWidth = 1;
|
|
ctx.strokeRect(w * 0.5, coinY, px * 2, px * 2);
|
|
Draw.rect(ctx, w * 0.5 + 3, coinY + 3, px * 2 - 6, px * 2 - 6, H.darken(blockPulse, 0.2));
|
|
},
|
|
particles: null
|
|
},
|
|
|
|
synthwave: {
|
|
meta: { id: 'retro_synthwave', mood: 'epic', colors: ['#0d0015', '#1a0033', '#ff2975', '#f222ff'], tags: ['synthwave', 'outrun', 'neon', 'night'], description: 'An outrun-style neon horizon with a receding road, mountain silhouettes and electric sky', compatibleMusicMoods: ['epic', 'energetic', 'electronic'], recommendedFor: ['racing', 'action', 'sports'] },
|
|
colors: ['#0d0015', '#1a0033', '#ff2975', '#f222ff'],
|
|
renderBase: function(ctx, w, h, c, Draw, t) {
|
|
// Dark sky with neon gradient at horizon
|
|
var skyGrad = Draw.linearGradient(ctx, 0, 0, 0, h * 0.55, [
|
|
[0, c[0]], [0.5, c[1]], [0.85, '#330044'], [1, '#ff297530']
|
|
]);
|
|
ctx.fillStyle = skyGrad;
|
|
ctx.fillRect(0, 0, w, h * 0.55);
|
|
Draw.stars(ctx, w, h * 0.4, 70, '#FFFFFF', 801, [0.5, 1.5]);
|
|
// Neon sun (half visible at horizon)
|
|
var sunCy = h * 0.53;
|
|
var sunGrad = Draw.linearGradient(ctx, 0, sunCy - 50, 0, sunCy + 50, [
|
|
[0, c[2]], [0.5, c[3]], [1, '#FF6600']
|
|
]);
|
|
ctx.fillStyle = sunGrad;
|
|
ctx.beginPath(); ctx.arc(w * 0.5, sunCy, 55, Math.PI, 0); ctx.fill();
|
|
// Sun horizontal lines
|
|
for (var sl = 0; sl < 5; sl++) {
|
|
var sly = sunCy - 45 + sl * 12;
|
|
Draw.rect(ctx, w * 0.5 - 55, sly, 110, 3 + sl, c[0]);
|
|
}
|
|
Draw.glow(ctx, w * 0.5, sunCy, 100, H.rgba(c[2], 0.1));
|
|
},
|
|
renderMid: function(ctx, w, h, c, Draw, t) {
|
|
var horizonY = h * 0.55;
|
|
// Mountain silhouettes
|
|
Draw.mountains(ctx, w, h, horizonY, 5, '#0a000f', 802);
|
|
// Ground plane
|
|
Draw.rect(ctx, 0, horizonY, w, h - horizonY, '#05000a');
|
|
// Perspective grid - horizontal lines (receding, animated)
|
|
var gridOffset = (t * 20) % 30;
|
|
ctx.strokeStyle = H.rgba(c[3], 0.25);
|
|
ctx.lineWidth = 1;
|
|
for (var gy = 0; gy < 25; gy++) {
|
|
var gridFrac = (gy + gridOffset / 30) / 25;
|
|
var lineY = horizonY + gridFrac * gridFrac * (h - horizonY);
|
|
var lineAlpha = gridFrac * 0.3;
|
|
ctx.strokeStyle = H.rgba(c[3], lineAlpha);
|
|
ctx.beginPath(); ctx.moveTo(0, lineY); ctx.lineTo(w, lineY); ctx.stroke();
|
|
}
|
|
// Vertical converging lines
|
|
ctx.strokeStyle = H.rgba(c[2], 0.2);
|
|
for (var gx = -10; gx <= 10; gx++) {
|
|
var topX = w * 0.5;
|
|
var botX = w * 0.5 + gx * w * 0.065;
|
|
ctx.beginPath(); ctx.moveTo(topX, horizonY); ctx.lineTo(botX, h); ctx.stroke();
|
|
}
|
|
// Road center stripe
|
|
for (var rs = 0; rs < 12; rs++) {
|
|
var rsFrac = rs / 12;
|
|
var rsY = horizonY + rsFrac * rsFrac * (h - horizonY);
|
|
var rsW = 2 + rsFrac * 6;
|
|
var rsH = 3 + rsFrac * 8;
|
|
var rsAlpha = 0.3 + rsFrac * 0.4;
|
|
Draw.rect(ctx, w * 0.5 - rsW / 2, rsY, rsW, rsH, H.rgba('#FFFFFF', rsAlpha));
|
|
}
|
|
// Neon side lights along road
|
|
for (var nl = 0; nl < 6; nl++) {
|
|
var nlFrac = (nl + 0.5) / 6;
|
|
var nlY = horizonY + nlFrac * nlFrac * (h - horizonY);
|
|
var spread = nlFrac * w * 0.3;
|
|
var nlSize = 2 + nlFrac * 5;
|
|
var nlPulse = 0.4 + Math.sin(t * 2 + nl) * 0.2;
|
|
// Left
|
|
Draw.glow(ctx, w * 0.5 - spread, nlY, nlSize * 3, H.rgba(c[2], nlPulse * 0.3));
|
|
Draw.circle(ctx, w * 0.5 - spread, nlY, nlSize, H.rgba(c[2], nlPulse));
|
|
// Right
|
|
Draw.glow(ctx, w * 0.5 + spread, nlY, nlSize * 3, H.rgba(c[3], nlPulse * 0.3));
|
|
Draw.circle(ctx, w * 0.5 + spread, nlY, nlSize, H.rgba(c[3], nlPulse));
|
|
}
|
|
// Horizon glow line
|
|
ctx.strokeStyle = H.rgba(c[2], 0.35 + Math.sin(t * 0.8) * 0.1);
|
|
ctx.lineWidth = 2;
|
|
ctx.beginPath(); ctx.moveTo(0, horizonY); ctx.lineTo(w, horizonY); ctx.stroke();
|
|
},
|
|
particles: { type: 'stars', count: 20, color: '#F222FF' }
|
|
}
|
|
|
|
});
|
|
})(window.BackgroundEngine);
|