// underwater.js — Underwater background theme (8 variants) // Depends on backgroundEngine.js being loaded first (function(engine) { if (!engine) return; var D = engine.Draw, H = engine.helpers; function drawFish(ctx, x, y, s, col, dir) { ctx.fillStyle = col; ctx.beginPath(); ctx.ellipse(x, y, s, s * 0.5, 0, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.moveTo(x - dir * s, y); ctx.lineTo(x - dir * s * 1.6, y - s * 0.5); ctx.lineTo(x - dir * s * 1.6, y + s * 0.5); ctx.closePath(); ctx.fill(); ctx.fillStyle = '#FFF'; ctx.beginPath(); ctx.arc(x + dir * s * 0.4, y - s * 0.15, s * 0.18, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#000'; ctx.beginPath(); ctx.arc(x + dir * s * 0.45, y - s * 0.15, s * 0.08, 0, Math.PI * 2); ctx.fill(); } function sunRays(ctx, w, h, n, col, t) { ctx.save(); ctx.globalCompositeOperation = 'lighter'; for (var i = 0; i < n; i++) { var rx = (w / n) * i + Math.sin(t * 0.3 + i) * 20, rw = 15 + Math.sin(t * 0.5 + i * 1.3) * 8; ctx.fillStyle = H.rgba(col, 0.03 + Math.sin(t * 0.4 + i * 0.7) * 0.02); ctx.beginPath(); ctx.moveTo(rx - rw, 0); ctx.lineTo(rx + rw, 0); ctx.lineTo(rx + rw * 2.5, h); ctx.lineTo(rx - rw * 1.5, h); ctx.closePath(); ctx.fill(); } ctx.restore(); } engine.registerTheme('underwater', { coralReef: { meta: { id: 'underwater_coralReef', mood: 'cheerful', colors: ['#006994', '#00A8CC', '#FF6F61', '#FFB347'], tags: ['coral', 'reef', 'colorful', 'ocean'], description: 'A vibrant coral reef teeming with colorful marine life', compatibleMusicMoods: ['happy', 'upbeat', 'peaceful'], recommendedFor: ['puzzle', 'pet', 'creative'] }, colors: ['#006994', '#00A8CC', '#FF6F61', '#FFB347'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, [H.lighten(c[0], 0.15), c[0], H.darken(c[0], 0.3)], 90); sunRays(ctx, w, h, 6, '#88DDFF', t); Draw.rect(ctx, 0, h * 0.82, w, h * 0.18, '#C2A366'); Draw.hills(ctx, w, h, h * 0.82, 10, '#B89555', 22); }, renderMid: function(ctx, w, h, c, Draw, t) { var rng = H.seededRandom(50); for (var i = 0; i < 14; i++) { var cx = rng() * w, cy = h * 0.72 + rng() * h * 0.15, ch = 15 + rng() * 35; var cc = [c[2], c[3], '#FF8899', '#FF77AA'][Math.floor(rng() * 4)]; for (var b = 0; b < 3 + Math.floor(rng() * 3); b++) { var a = -Math.PI / 2 + (rng() - 0.5) * 1.2, bL = ch * (0.5 + rng() * 0.5), sw = Math.sin(t * 0.8 + i + b) * 2; ctx.strokeStyle = cc; ctx.lineWidth = 3 + rng() * 4; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(cx, cy); ctx.quadraticCurveTo(cx + Math.cos(a) * bL * 0.5 + sw, cy + Math.sin(a) * bL * 0.5, cx + Math.cos(a) * bL + sw, cy + Math.sin(a) * bL); ctx.stroke(); Draw.circle(ctx, cx + Math.cos(a) * bL + sw, cy + Math.sin(a) * bL, 3 + rng() * 3, H.lighten(cc, 0.2)); } } for (var f = 0; f < 10; f++) { var fx = (rng() * w + t * 20 * (0.5 + rng())) % (w + 40) - 20, fy = h * 0.15 + rng() * h * 0.55 + Math.sin(t + f * 2) * 8; drawFish(ctx, fx, fy, 5 + rng() * 8, [c[2], c[3], '#FFDD44', '#44DDFF'][Math.floor(rng() * 4)], 1); } }, particles: { type: 'bubbles', count: 30, color: '#88DDFF' } }, deepSea: { meta: { id: 'underwater_deepSea', mood: 'mysterious', colors: ['#0A0A2E', '#0D1B3E', '#1A3A5C', '#00FFCC'], tags: ['deep', 'dark', 'bioluminescent', 'abyss'], description: 'The mysterious deep sea with bioluminescent creatures and faint light', compatibleMusicMoods: ['mysterious', 'ambient', 'dark'], recommendedFor: ['rpg', 'story', 'puzzle'] }, colors: ['#0A0A2E', '#0D1B3E', '#1A3A5C', '#00FFCC'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, [c[2], c[1], c[0]], 90); Draw.rect(ctx, 0, h * 0.88, w, h * 0.12, H.darken(c[0], 0.3)); }, renderMid: function(ctx, w, h, c, Draw, t) { var rng = H.seededRandom(71); for (var j = 0; j < 6; j++) { var jx = rng() * w, jy = h * 0.2 + rng() * h * 0.5 + Math.sin(t * 0.6 + j * 1.5) * 15; var jr = 8 + rng() * 14, jC = rng() > 0.5 ? c[3] : '#BB77FF', p = 0.4 + Math.sin(t * 1.5 + j * 2) * 0.2; Draw.glow(ctx, jx, jy, jr * 3, H.rgba(jC, p * 0.3)); ctx.fillStyle = H.rgba(jC, p); ctx.beginPath(); ctx.arc(jx, jy, jr, Math.PI, 0); ctx.closePath(); ctx.fill(); ctx.strokeStyle = H.rgba(jC, p * 0.6); ctx.lineWidth = 1; for (var tt = 0; tt < 5; tt++) { var tx = jx - jr + (tt / 4) * jr * 2; ctx.beginPath(); ctx.moveTo(tx, jy); for (var tp = 0; tp < jr * 2 + rng() * jr; tp += 4) ctx.lineTo(tx + Math.sin(tp * 0.3 + t * 2 + tt) * 4, jy + tp); ctx.stroke(); } } for (var a = 0; a < 4; a++) { var ax = rng() * w, ay = h * 0.6 + rng() * h * 0.25, gl = 0.3 + Math.sin(t * 2.5 + a * 3) * 0.25; Draw.glow(ctx, ax, ay, 12, H.rgba(c[3], gl)); Draw.circle(ctx, ax, ay, 2, H.rgba('#FFF', gl + 0.2)); } for (var r = 0; r < 10; r++) Draw.circle(ctx, rng() * w, h * 0.88 + rng() * h * 0.05, 5 + rng() * 15, H.darken(c[1], 0.2 + rng() * 0.15)); }, particles: { type: 'fireflies', count: 20, color: '#00FFCC' } }, kelpForest: { meta: { id: 'underwater_kelpForest', mood: 'peaceful', colors: ['#0B4F3A', '#1A7A5C', '#2FA87A', '#7EC8A0'], tags: ['kelp', 'forest', 'green', 'serene'], description: 'A towering kelp forest with dappled light filtering through fronds', compatibleMusicMoods: ['peaceful', 'calm', 'ambient'], recommendedFor: ['puzzle', 'creative', 'story'] }, colors: ['#0B4F3A', '#1A7A5C', '#2FA87A', '#7EC8A0'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, [H.lighten(c[0], 0.25), c[0], H.darken(c[0], 0.2)], 90); sunRays(ctx, w, h, 5, '#AAFFCC', t); Draw.rect(ctx, 0, h * 0.88, w, h * 0.12, H.darken(c[0], 0.35)); }, renderMid: function(ctx, w, h, c, Draw, t) { var rng = H.seededRandom(38); for (var i = 0; i < 18; i++) { var kx = rng() * w, kB = h * 0.9, kT = h * 0.05 + rng() * h * 0.3; var kC = [c[1], c[2], c[3]][Math.floor(rng() * 3)]; ctx.strokeStyle = H.rgba(kC, 0.6 + rng() * 0.3); ctx.lineWidth = 3 + rng() * 3; ctx.beginPath(); ctx.moveTo(kx, kB); var seg = 8 + Math.floor(rng() * 6); for (var s = 1; s <= seg; s++) ctx.lineTo(kx + Math.sin(s * 0.8 + t * 0.7 + i) * (12 + rng() * 8), kB - (kB - kT) * (s / seg)); ctx.stroke(); for (var lf = 0; lf < 4; lf++) { var lfY = kB - (kB - kT) * ((lf + 1) / 5), d = rng() > 0.5 ? 1 : -1; ctx.fillStyle = H.rgba(kC, 0.4); ctx.beginPath(); ctx.ellipse(kx + Math.sin((lf + 1) * 0.8 + t * 0.7 + i) * 12 + d * 8, lfY, 10, 3, d * 0.3, 0, Math.PI * 2); ctx.fill(); } } for (var f = 0; f < 7; f++) drawFish(ctx, (rng() * w + t * 25 * (rng() + 0.3)) % (w + 30) - 15, h * 0.2 + rng() * h * 0.5 + Math.sin(t * 2 + f) * 6, 4 + rng() * 4, H.rgba(c[3], 0.7), 1); }, particles: { type: 'bubbles', count: 20, color: '#88EEBB' } }, submarine: { meta: { id: 'underwater_submarine', mood: 'adventurous', colors: ['#0A1628', '#1A3050', '#D4AA44', '#CC4444'], tags: ['submarine', 'exploration', 'industrial', 'adventure'], description: 'A submarine expedition through dark ocean depths with spotlight beams', compatibleMusicMoods: ['adventurous', 'mysterious', 'tense'], recommendedFor: ['action', 'rpg', 'story'] }, colors: ['#0A1628', '#1A3050', '#D4AA44', '#CC4444'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, [c[1], c[0]], 90); Draw.rect(ctx, 0, h * 0.85, w, h * 0.15, H.darken(c[0], 0.4)); }, renderMid: function(ctx, w, h, c, Draw, t) { var rng = H.seededRandom(82), sx = w * 0.5 + Math.sin(t * 0.3) * 20, sy = h * 0.4 + Math.sin(t * 0.5) * 10; ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.fillStyle = H.rgba(c[2], 0.06); ctx.beginPath(); ctx.moveTo(sx + 55, sy + 5); ctx.lineTo(sx + 200, sy - 40); ctx.lineTo(sx + 200, sy + 80); ctx.closePath(); ctx.fill(); ctx.restore(); ctx.fillStyle = '#556677'; ctx.beginPath(); ctx.ellipse(sx, sy, 55, 18, 0, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#667788'; ctx.fillRect(sx - 10, sy - 28, 20, 12); ctx.beginPath(); ctx.arc(sx, sy - 28, 10, Math.PI, 0); ctx.fill(); ctx.strokeStyle = '#778899'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(sx, sy - 28); ctx.lineTo(sx, sy - 42); ctx.lineTo(sx + 5, sy - 42); ctx.stroke(); Draw.circle(ctx, sx + 20, sy, 5, c[2]); Draw.circle(ctx, sx + 20, sy, 3, H.rgba('#FFEE88', 0.6)); ctx.save(); ctx.translate(sx - 55, sy); for (var p = 0; p < 3; p++) { var pa = t * 4 + p * Math.PI * 2 / 3; ctx.fillStyle = '#8899AA'; ctx.beginPath(); ctx.ellipse(Math.cos(pa) * 6, Math.sin(pa) * 6, 8, 2, pa, 0, Math.PI * 2); ctx.fill(); } ctx.restore(); for (var r = 0; r < 12; r++) Draw.circle(ctx, rng() * w, h * 0.85 + rng() * h * 0.08, 4 + rng() * 10, H.darken(c[0], 0.15 + rng() * 0.15)); }, particles: { type: 'bubbles', count: 25, color: '#AACCDD' } }, shipwreck: { meta: { id: 'underwater_shipwreck', mood: 'mysterious', colors: ['#0A1A2A', '#1A3344', '#8B7355', '#44AA88'], tags: ['shipwreck', 'treasure', 'mysterious', 'old'], description: 'A sunken ship resting on the ocean floor, overgrown with sea life', compatibleMusicMoods: ['mysterious', 'adventurous', 'dark'], recommendedFor: ['rpg', 'story', 'puzzle'] }, colors: ['#0A1A2A', '#1A3344', '#8B7355', '#44AA88'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, [c[1], c[0]], 90); sunRays(ctx, w, h, 4, '#66BBAA', t); Draw.hills(ctx, w, h, h * 0.78, 8, '#5A4A3A', 55); }, renderMid: function(ctx, w, h, c, Draw, t) { var rng = H.seededRandom(93), sx = w * 0.45, sy = h * 0.62; ctx.fillStyle = c[2]; ctx.save(); ctx.translate(sx, sy); ctx.rotate(0.15); ctx.fillRect(-60, -10, 120, 25); ctx.fillRect(-50, -25, 100, 18); ctx.fillStyle = H.darken(c[2], 0.2); ctx.fillRect(-40, -35, 80, 12); ctx.strokeStyle = c[2]; ctx.lineWidth = 4; ctx.beginPath(); ctx.moveTo(-10, -35); ctx.lineTo(-20, -80); ctx.stroke(); ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(-20, -60); ctx.lineTo(15, -55); ctx.stroke(); ctx.fillStyle = H.darken(c[0], 0.3); ctx.beginPath(); ctx.ellipse(20, 0, 8, 5, 0, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.ellipse(-30, -5, 6, 4, 0.2, 0, Math.PI * 2); ctx.fill(); ctx.restore(); for (var s = 0; s < 8; s++) { var swx = sx - 50 + rng() * 100; ctx.strokeStyle = H.rgba(c[3], 0.5 + rng() * 0.3); ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(swx, sy + 5); for (var sp = 0; sp < 15 + rng() * 25; sp += 3) ctx.lineTo(swx + Math.sin(sp * 0.4 + t * 1.2 + s) * 5, sy + 5 - sp); ctx.stroke(); } for (var f = 0; f < 5; f++) drawFish(ctx, (rng() * w + t * 12 * (rng() + 0.2)) % (w + 30) - 15, h * 0.3 + rng() * h * 0.35 + Math.sin(t + f) * 5, 4 + rng() * 5, H.rgba(c[3], 0.6), 1); }, particles: { type: 'bubbles', count: 20, color: '#66AAAA' } }, abyss: { meta: { id: 'underwater_abyss', mood: 'dark', colors: ['#020208', '#050515', '#0A0A30', '#FF2200'], tags: ['abyss', 'dark', 'deep', 'volcanic'], description: 'The abyssal zone with volcanic vents and glowing magma cracks', compatibleMusicMoods: ['dark', 'tense', 'ambient'], recommendedFor: ['action', 'rpg', 'story'] }, colors: ['#020208', '#050515', '#0A0A30', '#FF2200'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, [c[2], c[1], c[0]], 90); Draw.hills(ctx, w, h, h * 0.8, 6, '#0D0D12', 80); }, renderMid: function(ctx, w, h, c, Draw, t) { var rng = H.seededRandom(13); for (var v = 0; v < 3; v++) { var vx = w * 0.2 + rng() * w * 0.6, vy = h * 0.82, vg = 0.3 + Math.sin(t * 2 + v * 3) * 0.15; ctx.fillStyle = '#1A1510'; ctx.fillRect(vx - 8, vy - 20, 16, 25); ctx.fillRect(vx - 12, vy - 22, 24, 5); Draw.glow(ctx, vx, vy - 22, 25, H.rgba(c[3], vg)); for (var sm = 0; sm < 6; sm++) { ctx.fillStyle = H.rgba(c[3], 0.15 * (1 - sm / 6)); ctx.beginPath(); ctx.arc(vx + Math.sin(t * 1.5 + sm * 2 + v) * 8, vy - 25 - sm * 18 - (t * 10 % 30), 5 + sm * 2, 0, Math.PI * 2); ctx.fill(); } } for (var lc = 0; lc < 8; lc++) { var lcx = rng() * w, lcy = h * 0.83 + rng() * h * 0.1, g2 = 0.2 + Math.sin(t * 1.8 + lc) * 0.15; Draw.glow(ctx, lcx, lcy, 10, H.rgba(c[3], g2 * 0.4)); ctx.strokeStyle = H.rgba(c[3], g2); ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(lcx, lcy); ctx.lineTo(lcx + (rng() - 0.5) * 30, lcy + (rng() - 0.5) * 8); ctx.stroke(); } for (var s = 0; s < 12; s++) Draw.circle(ctx, rng() * w, rng() * h * 0.75, 1 + rng(), H.rgba('#4488FF', 0.15 + Math.sin(t * 3 + s * 2.3) * 0.1)); }, particles: { type: 'particles', count: 15, color: '#FF4400' } }, iceCave: { meta: { id: 'underwater_iceCave', mood: 'calm', colors: ['#0A2A4A', '#1A5A8A', '#88CCEE', '#E0F0FF'], tags: ['ice', 'cave', 'arctic', 'frozen'], description: 'An underwater ice cavern with crystalline formations and pale blue light', compatibleMusicMoods: ['calm', 'ambient', 'mysterious'], recommendedFor: ['puzzle', 'story', 'creative'] }, colors: ['#0A2A4A', '#1A5A8A', '#88CCEE', '#E0F0FF'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, [c[3], c[2], c[1], c[0]], 90); Draw.hills(ctx, w, h, h * 0.08, 12, H.rgba(c[3], 0.3), 18); }, renderMid: function(ctx, w, h, c, Draw, t) { var rng = H.seededRandom(27); for (var i = 0; i < 14; i++) { var stx = rng() * w, sth = 20 + rng() * 60, stw = 4 + rng() * 8; ctx.fillStyle = H.rgba(c[2], 0.4 + rng() * 0.3); ctx.beginPath(); ctx.moveTo(stx - stw, 0); ctx.lineTo(stx, sth); ctx.lineTo(stx + stw, 0); ctx.closePath(); ctx.fill(); ctx.fillStyle = H.rgba(c[3], 0.2); ctx.beginPath(); ctx.moveTo(stx - stw * 0.3, 0); ctx.lineTo(stx - 1, sth * 0.8); ctx.lineTo(stx + stw * 0.2, 0); ctx.closePath(); ctx.fill(); } for (var j = 0; j < 10; j++) { var sgx = rng() * w, sgh = 15 + rng() * 40, sgw = 5 + rng() * 10; ctx.fillStyle = H.rgba(c[1], 0.5 + rng() * 0.3); ctx.beginPath(); ctx.moveTo(sgx - sgw, h); ctx.lineTo(sgx, h - sgh); ctx.lineTo(sgx + sgw, h); ctx.closePath(); ctx.fill(); } for (var k = 0; k < 8; k++) { var kx = rng() * w, ky = h * 0.2 + rng() * h * 0.5 + Math.sin(t * 0.4 + k * 1.7) * 8, ks = 4 + rng() * 8; Draw.polygon(ctx, kx, ky, ks, 6, H.rgba(c[3], 0.25 + Math.sin(t + k) * 0.1), t * 0.3 + k * 1.2); Draw.circle(ctx, kx, ky, ks * 0.3, H.rgba('#FFF', 0.4)); } ctx.save(); ctx.globalCompositeOperation = 'lighter'; for (var m = 0; m < 6; m++) Draw.circle(ctx, rng() * w + Math.sin(t * 0.5 + m) * 10, h * 0.3 + rng() * h * 0.3, 20 + rng() * 40, H.rgba(c[2], 0.02 + Math.sin(t * 0.8 + m * 1.5) * 0.015)); ctx.restore(); }, particles: { type: 'sparkles', count: 25, color: '#E0F0FF' } }, treasure: { meta: { id: 'underwater_treasure', mood: 'adventurous', colors: ['#0D2B45', '#1A4A6A', '#FFD700', '#FF8C00'], tags: ['treasure', 'gold', 'adventure', 'pirate'], description: 'A hidden underwater treasure trove glowing with golden riches', compatibleMusicMoods: ['adventurous', 'happy', 'upbeat'], recommendedFor: ['rpg', 'action', 'puzzle'] }, colors: ['#0D2B45', '#1A4A6A', '#FFD700', '#FF8C00'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, [c[1], c[0]], 90); sunRays(ctx, w, h, 3, '#FFDDAA', t); Draw.hills(ctx, w, h, h * 0.75, 7, '#3A2A1A', 62); }, renderMid: function(ctx, w, h, c, Draw, t) { var rng = H.seededRandom(57), tcx = w * 0.5, tcy = h * 0.72; ctx.fillStyle = '#6B3A1F'; ctx.fillRect(tcx - 25, tcy - 5, 50, 22); ctx.fillStyle = '#8B5A2F'; ctx.beginPath(); ctx.ellipse(tcx, tcy - 5, 25, 10, 0, Math.PI, 0); ctx.fill(); ctx.fillStyle = '#7B4A25'; ctx.save(); ctx.translate(tcx - 25, tcy - 5); ctx.rotate(-0.6); ctx.fillRect(0, -16, 50, 16); ctx.restore(); var gp = 0.4 + Math.sin(t * 1.5) * 0.15; Draw.glow(ctx, tcx, tcy - 10, 40, H.rgba(c[2], gp)); for (var g = 0; g < 12; g++) { var gx = tcx - 15 + rng() * 30, gy = tcy - 2 + rng() * 15; Draw.circle(ctx, gx, gy, 3 + rng() * 2, c[2]); Draw.circle(ctx, gx - 0.5, gy - 0.5, 2, H.lighten(c[2], 0.3)); } ctx.strokeStyle = c[3]; ctx.lineWidth = 2; ctx.strokeRect(tcx - 24, tcy - 4, 48, 20); for (var j = 0; j < 8; j++) { var jx = tcx - 40 + rng() * 80, jy = tcy + 10 + rng() * 15, jC = ['#FF0044', '#00AAFF', '#44FF44', c[2]][Math.floor(rng() * 4)], jT = 0.5 + Math.sin(t * 3 + j * 2) * 0.3; Draw.polygon(ctx, jx, jy, 2 + rng() * 3, 5 + Math.floor(rng() * 3), H.rgba(jC, jT), t * 0.2 + j); Draw.glow(ctx, jx, jy, 6, H.rgba(jC, jT * 0.3)); } for (var p = 0; p < 2; p++) { var px = p === 0 ? w * 0.15 : w * 0.85; ctx.fillStyle = '#4A4A3A'; ctx.fillRect(px - 8, h * 0.4, 16, h * 0.38); ctx.fillRect(px - 12, h * 0.38, 24, 6); ctx.fillRect(px - 12, h * 0.76, 24, 6); ctx.strokeStyle = H.rgba('#2A2A20', 0.5); ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(px - 3, h * 0.5); ctx.lineTo(px + 2, h * 0.55); ctx.lineTo(px - 1, h * 0.62); ctx.stroke(); } }, particles: { type: 'sparkles', count: 35, color: '#FFD700' } } }); })(window.BackgroundEngine);