// space.js — Space theme for procedural background system // 8 variants: nebula, asteroidField, deepSpace, planets, spaceStation, warpSpeed, satellites, blackHole (function(engine) { if (!engine) return; var D = engine.Draw, H = engine.helpers; engine.registerTheme('space', { nebula: { meta: { id: 'space_nebula', mood: 'wonder', colors: ['#0B0B2B', '#1A0A3E', '#3D1A78', '#7B2FBE'], tags: ['cosmic', 'colorful', 'calm'], description: 'Swirling nebula clouds with twinkling stars', compatibleMusicMoods: ['ambient', 'dreamy', 'epic'], recommendedFor: ['puzzle', 'story', 'creative'] }, colors: ['#0B0B2B', '#1A0A3E', '#3D1A78', '#7B2FBE'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, [c[0], c[1], c[2]], 90); var rng = H.seededRandom(101); for (var i = 0; i < 5; i++) { var cx = rng() * w, cy = rng() * h; var r = 80 + rng() * 200; var pulse = 0.9 + Math.sin(t * 0.3 + i) * 0.1; Draw.glow(ctx, cx, cy, r * pulse, c[3]); Draw.glow(ctx, cx + 40, cy - 30, r * 0.6 * pulse, c[2]); } }, renderMid: function(ctx, w, h, c, Draw, t) { Draw.stars(ctx, w, h, 180, '#FFFFFF', 10, [0.5, 2]); Draw.stars(ctx, w, h, 40, c[3], 20, [1, 3]); var rng = H.seededRandom(102); for (var i = 0; i < 3; i++) { var x = rng() * w, y = rng() * h * 0.8; var flicker = 0.5 + Math.sin(t * 2 + i * 1.7) * 0.3; Draw.glow(ctx, x, y, 4 + rng() * 4, H.rgba('#FFFFFF', flicker)); } }, particles: { type: 'sparkles', count: 40, color: '#CCBBFF' } }, asteroidField: { meta: { id: 'space_asteroidField', mood: 'tense', colors: ['#0A0A14', '#1C1C2E', '#4A3A2A', '#8B7355'], tags: ['rocky', 'danger', 'dark'], description: 'Rocky asteroids drifting through dark space', compatibleMusicMoods: ['tense', 'action', 'ambient'], recommendedFor: ['action', 'racing', 'sports'] }, colors: ['#0A0A14', '#1C1C2E', '#4A3A2A', '#8B7355'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, [c[0], c[1]], 90); Draw.stars(ctx, w, h, 100, '#FFFFFF', 30, [0.3, 1.5]); }, renderMid: function(ctx, w, h, c, Draw, t) { var rng = H.seededRandom(200); for (var i = 0; i < 18; i++) { var ax = (rng() * w + t * (5 + rng() * 15)) % (w + 100) - 50; var ay = rng() * h; var ar = 8 + rng() * 35; var sides = 5 + Math.floor(rng() * 4); var rot = rng() * Math.PI * 2 + t * (rng() - 0.5) * 0.2; var shade = H.blendColor(c[2], c[3], rng()); Draw.polygon(ctx, ax, ay, ar, sides, shade, rot); Draw.polygon(ctx, ax - ar * 0.15, ay - ar * 0.15, ar * 0.85, sides, H.lighten(shade, 0.15), rot); } }, particles: { type: 'dust', count: 25, color: '#887766' } }, deepSpace: { meta: { id: 'space_deepSpace', mood: 'serene', colors: ['#000005', '#050510', '#0A0A20', '#111133'], tags: ['dark', 'vast', 'minimal'], description: 'The void of deep space with distant starlight', compatibleMusicMoods: ['ambient', 'calm', 'dreamy'], recommendedFor: ['puzzle', 'story', 'rpg'] }, colors: ['#000005', '#050510', '#0A0A20', '#111133'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, [c[0], c[1], c[2]], 90); Draw.glow(ctx, w * 0.8, h * 0.15, 150, H.rgba('#1A1A55', 0.3)); }, renderMid: function(ctx, w, h, c, Draw, t) { Draw.stars(ctx, w, h, 300, '#FFFFFF', 50, [0.2, 1.5]); Draw.stars(ctx, w, h, 20, '#AABBFF', 51, [1.5, 3]); var rng = H.seededRandom(300); for (var i = 0; i < 5; i++) { var x = rng() * w, y = rng() * h; var twinkle = 0.3 + Math.sin(t * 1.5 + rng() * 10) * 0.3; Draw.glow(ctx, x, y, 8 + rng() * 12, H.rgba('#4466CC', twinkle)); } }, particles: { type: 'stars', count: 60, color: '#FFFFFF' } }, planets: { meta: { id: 'space_planets', mood: 'wonder', colors: ['#0B0B2B', '#0F1640', '#CC5533', '#44BBAA'], tags: ['planetary', 'colorful', 'majestic'], description: 'Colorful planets against a starry backdrop', compatibleMusicMoods: ['epic', 'dreamy', 'ambient'], recommendedFor: ['creative', 'rpg', 'story'] }, colors: ['#0B0B2B', '#0F1640', '#CC5533', '#44BBAA'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, [c[0], c[1]], 90); Draw.stars(ctx, w, h, 150, '#FFFFFF', 40, [0.3, 1.8]); }, renderMid: function(ctx, w, h, c, Draw, t) { var rng = H.seededRandom(400); // Large planet var px = w * 0.7, py = h * 0.35, pr = 60 + rng() * 40; var g = Draw.radialGradient(ctx, px - pr * 0.3, py - pr * 0.3, pr * 1.2, [[0, H.lighten(c[2], 0.3)], [0.6, c[2]], [1, H.darken(c[2], 0.6)]]); ctx.fillStyle = g; ctx.beginPath(); ctx.arc(px, py, pr, 0, Math.PI * 2); ctx.fill(); // Ring ctx.strokeStyle = H.rgba(c[2], 0.4); ctx.lineWidth = 3; ctx.beginPath(); ctx.ellipse(px, py, pr * 1.8, pr * 0.3, -0.2, 0, Math.PI * 2); ctx.stroke(); // Small planet var sx = w * 0.25, sy = h * 0.6, sr = 25 + rng() * 15; var g2 = Draw.radialGradient(ctx, sx - sr * 0.3, sy - sr * 0.3, sr * 1.2, [[0, H.lighten(c[3], 0.3)], [0.7, c[3]], [1, H.darken(c[3], 0.5)]]); ctx.fillStyle = g2; ctx.beginPath(); ctx.arc(sx, sy, sr, 0, Math.PI * 2); ctx.fill(); // Moon var bob = Math.sin(t * 0.5) * 5; var mx = w * 0.15, my = h * 0.25 + bob; Draw.circle(ctx, mx, my, 10, '#AAAAAA'); Draw.glow(ctx, mx, my, 18, H.rgba('#AAAAAA', 0.2)); }, particles: { type: 'sparkles', count: 30, color: '#FFFFFF' } }, spaceStation: { meta: { id: 'space_spaceStation', mood: 'focused', colors: ['#0A0A1E', '#151530', '#3A3A55', '#88AACC'], tags: ['tech', 'structure', 'industrial'], description: 'Orbital station with geometric structures', compatibleMusicMoods: ['ambient', 'tense', 'electronic'], recommendedFor: ['puzzle', 'action', 'physics'] }, colors: ['#0A0A1E', '#151530', '#3A3A55', '#88AACC'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, [c[0], c[1]], 90); Draw.stars(ctx, w, h, 100, '#FFFFFF', 60, [0.3, 1.2]); // Earth glow on horizon Draw.glow(ctx, w * 0.5, h * 1.1, h * 0.5, H.rgba('#2255AA', 0.25)); }, renderMid: function(ctx, w, h, c, Draw, t) { var rng = H.seededRandom(500); // Station core Draw.rect(ctx, w * 0.35, h * 0.38, w * 0.3, h * 0.06, c[2]); Draw.rect(ctx, w * 0.33, h * 0.4, w * 0.34, h * 0.02, H.lighten(c[2], 0.1)); // Solar panels for (var s = 0; s < 2; s++) { var sx = s === 0 ? w * 0.15 : w * 0.7; Draw.rect(ctx, sx, h * 0.36, w * 0.14, h * 0.1, H.rgba(c[3], 0.5)); Draw.grid(ctx, w * 0.14, h * 0.1, 10, H.rgba(c[2], 0.3), 0.5); ctx.save(); ctx.translate(sx, h * 0.36); Draw.grid(ctx, w * 0.14, h * 0.1, 12, H.rgba('#4466AA', 0.3), 0.5); ctx.restore(); } // Struts Draw.rect(ctx, w * 0.29, h * 0.4, w * 0.04, h * 0.005, c[2]); Draw.rect(ctx, w * 0.67, h * 0.4, w * 0.04, h * 0.005, c[2]); // Blinking lights for (var i = 0; i < 6; i++) { var lx = w * 0.36 + i * (w * 0.28 / 5); var blink = Math.sin(t * 3 + i * 1.2) > 0.3 ? 0.9 : 0.15; Draw.circle(ctx, lx, h * 0.38, 2, H.rgba('#FF3333', blink)); } }, particles: { type: 'dust', count: 15, color: '#667788' } }, warpSpeed: { meta: { id: 'space_warpSpeed', mood: 'energetic', colors: ['#000010', '#0A0A30', '#2244AA', '#66BBFF'], tags: ['fast', 'streaks', 'intense'], description: 'Light streaks from faster-than-light travel', compatibleMusicMoods: ['energetic', 'electronic', 'epic'], recommendedFor: ['racing', 'action', 'sports'] }, colors: ['#000010', '#0A0A30', '#2244AA', '#66BBFF'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, [c[0], c[1]], 90); // Central tunnel glow var pulse = 0.6 + Math.sin(t * 2) * 0.15; Draw.glow(ctx, w * 0.5, h * 0.5, Math.min(w, h) * 0.4 * pulse, c[2]); Draw.glow(ctx, w * 0.5, h * 0.5, Math.min(w, h) * 0.15, H.rgba(c[3], 0.4)); }, renderMid: function(ctx, w, h, c, Draw, t) { var rng = H.seededRandom(600); var cx = w * 0.5, cy = h * 0.5; ctx.lineWidth = 1.5; for (var i = 0; i < 60; i++) { var angle = rng() * Math.PI * 2; var dist = 30 + rng() * Math.min(w, h) * 0.45; var len = 20 + rng() * 60; var speed = 0.5 + rng() * 2; var offset = (t * speed * 40 + rng() * 200) % (dist + len); var x1 = cx + Math.cos(angle) * offset; var y1 = cy + Math.sin(angle) * offset; var x2 = cx + Math.cos(angle) * (offset + len); var y2 = cy + Math.sin(angle) * (offset + len); var alpha = H.clamp(0.15 + (offset / dist) * 0.6, 0, 0.8); var col = rng() > 0.7 ? c[3] : '#FFFFFF'; ctx.strokeStyle = H.rgba(col, alpha); ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke(); } }, particles: { type: 'particles', count: 30, color: '#88CCFF' } }, satellites: { meta: { id: 'space_satellites', mood: 'calm', colors: ['#060615', '#101030', '#334466', '#AACCDD'], tags: ['orbital', 'tech', 'night'], description: 'Satellites orbiting above a dark Earth', compatibleMusicMoods: ['ambient', 'calm', 'dreamy'], recommendedFor: ['puzzle', 'trivia', 'creative'] }, colors: ['#060615', '#101030', '#334466', '#AACCDD'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, [c[0], c[1]], 90); // Earth curve at bottom var g = Draw.radialGradient(ctx, w * 0.5, h * 1.6, h * 0.9, [[0, '#1A3355'], [0.5, '#0D1B2A'], [1, H.rgba(c[0], 0)]]); ctx.fillStyle = g; ctx.beginPath(); ctx.arc(w * 0.5, h * 1.6, h * 0.9, 0, Math.PI * 2); ctx.fill(); // Atmosphere glow Draw.glow(ctx, w * 0.5, h * 1.05, h * 0.3, H.rgba('#3388CC', 0.15)); Draw.stars(ctx, w, h * 0.7, 120, '#FFFFFF', 70, [0.3, 1.5]); }, renderMid: function(ctx, w, h, c, Draw, t) { var rng = H.seededRandom(700); for (var i = 0; i < 5; i++) { var orbit = 0.15 + rng() * 0.5; var speed = 0.1 + rng() * 0.25; var angle = t * speed + rng() * Math.PI * 2; var ox = w * 0.5 + Math.cos(angle) * w * orbit; var oy = h * 0.5 + Math.sin(angle) * h * orbit * 0.3; // Satellite body Draw.rect(ctx, ox - 4, oy - 2, 8, 4, c[2]); // Solar panels Draw.rect(ctx, ox - 14, oy - 1, 9, 2, H.rgba(c[3], 0.7)); Draw.rect(ctx, ox + 5, oy - 1, 9, 2, H.rgba(c[3], 0.7)); // Signal blink var blink = Math.sin(t * 4 + i * 2) > 0.5 ? 0.8 : 0.1; Draw.circle(ctx, ox, oy, 1.5, H.rgba('#FF4444', blink)); } // Orbit lines (faint) ctx.strokeStyle = H.rgba(c[2], 0.08); ctx.lineWidth = 0.5; for (var j = 0; j < 3; j++) { var orb = 0.2 + j * 0.15; ctx.beginPath(); ctx.ellipse(w * 0.5, h * 0.5, w * orb, h * orb * 0.3, 0, 0, Math.PI * 2); ctx.stroke(); } }, particles: { type: 'stars', count: 40, color: '#FFFFFF' } }, blackHole: { meta: { id: 'space_blackHole', mood: 'intense', colors: ['#000000', '#0A0510', '#220A33', '#FF6622'], tags: ['dark', 'gravity', 'dramatic'], description: 'A black hole bending light around its event horizon', compatibleMusicMoods: ['tense', 'epic', 'ambient'], recommendedFor: ['action', 'rpg', 'physics'] }, colors: ['#000000', '#0A0510', '#220A33', '#FF6622'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, [c[0], c[0], c[1]], 90); Draw.stars(ctx, w, h, 80, '#FFFFFF', 80, [0.3, 1]); }, renderMid: function(ctx, w, h, c, Draw, t) { var cx = w * 0.5, cy = h * 0.45; var r = Math.min(w, h) * 0.15; // Accretion disk var rng = H.seededRandom(800); ctx.save(); ctx.translate(cx, cy); for (var ring = 5; ring >= 0; ring--) { var rr = r * (1.3 + ring * 0.4); var alpha = 0.08 + ring * 0.04; var spin = t * (0.2 + ring * 0.05); ctx.save(); ctx.rotate(spin); var col = ring < 3 ? c[3] : c[2]; ctx.strokeStyle = H.rgba(col, alpha + Math.sin(t + ring) * 0.03); ctx.lineWidth = 3 + ring * 1.5; ctx.beginPath(); ctx.ellipse(0, 0, rr, rr * 0.22, 0, 0, Math.PI * 2); ctx.stroke(); ctx.restore(); } // Core glow Draw.glow(ctx, 0, 0, r * 1.5, H.rgba(c[3], 0.35 + Math.sin(t * 1.5) * 0.1)); Draw.glow(ctx, 0, 0, r * 0.8, H.rgba(c[2], 0.4)); // Black center var gc = Draw.radialGradient(ctx, 0, 0, r * 0.6, [[0, '#000000'], [0.7, '#000000'], [1, H.rgba('#000000', 0)]]); ctx.fillStyle = gc; ctx.beginPath(); ctx.arc(0, 0, r * 0.6, 0, Math.PI * 2); ctx.fill(); ctx.restore(); // Gravitational lensing — bent stars near hole for (var i = 0; i < 12; i++) { var a = rng() * Math.PI * 2; var d = r * (1.1 + rng() * 0.8); var sx = cx + Math.cos(a + t * 0.15) * d; var sy = cy + Math.sin(a + t * 0.15) * d * 0.4; var stretch = 1 + 2 / (1 + d / r); ctx.fillStyle = H.rgba('#FFCC88', 0.4 + rng() * 0.3); ctx.beginPath(); ctx.ellipse(sx, sy, 1.5 * stretch, 1, a, 0, Math.PI * 2); ctx.fill(); } }, particles: { type: 'particles', count: 35, color: '#FF8844' } } }); })(window.BackgroundEngine);