// nature.js — Nature theme for procedural background system // 8 variants: forest, jungle, desert, mountains, ocean, underwater, waterfall, meadow (function(engine) { if (!engine) return; var D = engine.Draw, H = engine.helpers; engine.registerTheme('nature', { forest: { meta: { id: 'nature_forest', mood: 'peaceful', colors: ['#1A3320', '#2D5A27', '#4A7A3F', '#8FB86A'], tags: ['trees', 'green', 'calm'], description: 'A dense forest with layered canopy and dappled light', compatibleMusicMoods: ['calm', 'ambient', 'dreamy'], recommendedFor: ['story', 'rpg', 'puzzle'] }, colors: ['#1A3320', '#2D5A27', '#4A7A3F', '#8FB86A'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, ['#5588AA', '#88BBCC', c[1]], 90); Draw.clouds(ctx, w, h, h * 0.08, 5, '#FFFFFF', 110); }, renderMid: function(ctx, w, h, c, Draw, t) { // Background tree layer Draw.hills(ctx, w, h, h * 0.4, 8, H.darken(c[0], 0.3), 111); Draw.trees(ctx, w, h, h * 0.5, 25, '#3D2B1F', c[0], 112); // Mid tree layer Draw.hills(ctx, w, h, h * 0.6, 6, c[1], 113); Draw.trees(ctx, w, h, h * 0.68, 18, '#5C3A1E', c[2], 114); // Foreground Draw.hills(ctx, w, h, h * 0.8, 5, c[2], 115); Draw.trees(ctx, w, h, h * 0.85, 10, '#6B4226', c[3], 116); // Forest floor Draw.rect(ctx, 0, h * 0.88, w, h * 0.12, H.darken(c[0], 0.4)); // Light rays var rng = H.seededRandom(117); ctx.save(); ctx.globalAlpha = 0.04 + Math.sin(t * 0.5) * 0.02; for (var i = 0; i < 5; i++) { var rx = rng() * w; ctx.fillStyle = '#FFFFCC'; ctx.beginPath(); ctx.moveTo(rx, 0); ctx.lineTo(rx - 30, h * 0.7); ctx.lineTo(rx + 30, h * 0.7); ctx.closePath(); ctx.fill(); } ctx.restore(); }, particles: { type: 'fireflies', count: 20, color: '#CCFF66' } }, jungle: { meta: { id: 'nature_jungle', mood: 'adventurous', colors: ['#0D2B0D', '#1A4D1A', '#2D7A2D', '#55AA33'], tags: ['tropical', 'dense', 'lush'], description: 'Thick tropical jungle with hanging vines and humid air', compatibleMusicMoods: ['adventurous', 'tense', 'ambient'], recommendedFor: ['action', 'rpg', 'pet'] }, colors: ['#0D2B0D', '#1A4D1A', '#2D7A2D', '#55AA33'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, ['#2A4420', c[0], H.darken(c[0], 0.3)], 90); }, renderMid: function(ctx, w, h, c, Draw, t) { Draw.hills(ctx, w, h, h * 0.35, 10, c[1], 210); Draw.trees(ctx, w, h, h * 0.5, 30, '#3B2510', c[1], 211); // Vines var rng = H.seededRandom(212); ctx.strokeStyle = H.rgba(c[2], 0.6); ctx.lineWidth = 2; for (var i = 0; i < 12; i++) { var vx = rng() * w; var sway = Math.sin(t * 0.8 + i * 1.3) * 8; ctx.beginPath(); ctx.moveTo(vx, 0); ctx.quadraticCurveTo(vx + sway, h * 0.2 + rng() * h * 0.15, vx + sway * 0.5, h * 0.3 + rng() * h * 0.2); ctx.stroke(); } // Large canopy leaves at top Draw.hills(ctx, w, h * 0.15, h * 0.02, 12, H.rgba(c[2], 0.5), 213); // Mid layer dense foliage Draw.trees(ctx, w, h, h * 0.7, 20, '#4B3520', c[2], 214); // Ground ferns Draw.hills(ctx, w, h, h * 0.85, 15, c[3], 215); Draw.rect(ctx, 0, h * 0.9, w, h * 0.1, H.darken(c[0], 0.5)); }, particles: { type: 'fireflies', count: 25, color: '#AAEE44' } }, desert: { meta: { id: 'nature_desert', mood: 'warm', colors: ['#F4A460', '#DAA06D', '#C2955A', '#8B6914'], tags: ['sand', 'hot', 'arid'], description: 'Rolling sand dunes under a blazing sun', compatibleMusicMoods: ['calm', 'epic', 'ambient'], recommendedFor: ['racing', 'action', 'rpg'] }, colors: ['#F4A460', '#DAA06D', '#C2955A', '#8B6914'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, ['#FFCC66', '#FF9933', c[0]], 90); // Sun var sunPulse = 1 + Math.sin(t * 0.3) * 0.03; Draw.glow(ctx, w * 0.75, h * 0.12, 100 * sunPulse, H.rgba('#FFEE88', 0.4)); Draw.circle(ctx, w * 0.75, h * 0.12, 35, '#FFDD44'); }, renderMid: function(ctx, w, h, c, Draw, t) { // Far dunes Draw.hills(ctx, w, h, h * 0.45, 5, H.lighten(c[0], 0.15), 310); // Mid dunes Draw.hills(ctx, w, h, h * 0.6, 4, c[1], 311); // Near dunes Draw.hills(ctx, w, h, h * 0.75, 3, c[2], 312); // Ground Draw.rect(ctx, 0, h * 0.82, w, h * 0.18, c[3]); // Heat shimmer var rng = H.seededRandom(313); ctx.save(); ctx.globalAlpha = 0.03 + Math.sin(t * 2) * 0.02; for (var i = 0; i < 8; i++) { var sy = h * 0.45 + rng() * h * 0.15; ctx.strokeStyle = '#FFFFFF'; ctx.lineWidth = 1; ctx.beginPath(); for (var x = 0; x < w; x += 6) { var yy = sy + Math.sin(x * 0.04 + t * 3 + i) * 3; x === 0 ? ctx.moveTo(x, yy) : ctx.lineTo(x, yy); } ctx.stroke(); } ctx.restore(); // Cacti for (var j = 0; j < 4; j++) { var cx = rng() * w; var cy = h * 0.72 + rng() * h * 0.06; var ch = 15 + rng() * 25; Draw.rect(ctx, cx - 3, cy - ch, 6, ch, '#2D5A27'); Draw.rect(ctx, cx + 3, cy - ch * 0.7, 10, 4, '#2D5A27'); Draw.rect(ctx, cx + 9, cy - ch * 0.7, 4, -10, '#2D5A27'); Draw.rect(ctx, cx - 13, cy - ch * 0.5, 10, 4, '#2D5A27'); Draw.rect(ctx, cx - 13, cy - ch * 0.5, 4, -8, '#2D5A27'); } }, particles: { type: 'dust', count: 20, color: '#DDCC99' } }, mountains: { meta: { id: 'nature_mountains', mood: 'majestic', colors: ['#2C3E50', '#4A6B5A', '#7A9B6B', '#FFFFFF'], tags: ['peaks', 'alpine', 'grand'], description: 'Snow-capped mountain peaks under sweeping skies', compatibleMusicMoods: ['epic', 'calm', 'dreamy'], recommendedFor: ['rpg', 'sports', 'story'] }, colors: ['#2C3E50', '#4A6B5A', '#7A9B6B', '#FFFFFF'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, ['#6699CC', '#AAC4DD', '#DDEEFF'], 90); Draw.clouds(ctx, w, h, h * 0.1, 8, '#FFFFFF', 410); }, renderMid: function(ctx, w, h, c, Draw, t) { // Far mountains Draw.mountains(ctx, w, h, h * 0.45, 6, H.rgba(c[0], 0.6), 411); // Snow caps Draw.mountains(ctx, w, h, h * 0.42, 6, H.rgba(c[3], 0.25), 411); // Mid mountains Draw.mountains(ctx, w, h, h * 0.55, 5, c[0], 412); Draw.mountains(ctx, w, h, h * 0.52, 5, H.rgba(c[3], 0.35), 412); // Foothills Draw.hills(ctx, w, h, h * 0.7, 8, c[1], 413); Draw.hills(ctx, w, h, h * 0.8, 6, c[2], 414); // Valley floor Draw.rect(ctx, 0, h * 0.85, w, h * 0.15, H.darken(c[2], 0.2)); // River ctx.strokeStyle = H.rgba('#5588BB', 0.5); ctx.lineWidth = 4; ctx.beginPath(); ctx.moveTo(w * 0.3, h * 0.85); ctx.quadraticCurveTo(w * 0.5, h * 0.88, w * 0.7, h * 0.92); ctx.quadraticCurveTo(w * 0.85, h * 0.95, w, h * 0.93); ctx.stroke(); }, particles: { type: 'fog', count: 8, color: '#CCCCCC' } }, ocean: { meta: { id: 'nature_ocean', mood: 'serene', colors: ['#1A5276', '#2E86C1', '#5DADE2', '#AED6F1'], tags: ['water', 'waves', 'calm'], description: 'Vast ocean stretching to the horizon at sunset', compatibleMusicMoods: ['calm', 'dreamy', 'ambient'], recommendedFor: ['puzzle', 'story', 'creative'] }, colors: ['#1A5276', '#2E86C1', '#5DADE2', '#AED6F1'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, ['#FF8844', '#FFAA66', '#FFCC88', c[3]], 90); // Setting sun var sunY = h * 0.35 + Math.sin(t * 0.1) * 2; Draw.glow(ctx, w * 0.5, sunY, 120, H.rgba('#FFAA44', 0.4)); Draw.circle(ctx, w * 0.5, sunY, 30, '#FFDD66'); }, renderMid: function(ctx, w, h, c, Draw, t) { // Horizon line Draw.rect(ctx, 0, h * 0.45, w, 2, H.rgba('#FFEECC', 0.3)); // Ocean Draw.water(ctx, w, h, h * 0.45, c[1], 3 + Math.sin(t) * 1, 510); // Sun reflection on water var rng = H.seededRandom(511); ctx.save(); ctx.globalAlpha = 0.08 + Math.sin(t * 0.5) * 0.04; for (var i = 0; i < 15; i++) { var ry = h * 0.47 + i * ((h * 0.53) / 15); var rw = 20 + rng() * 40 - i * 1.5; var rx = w * 0.5 - rw / 2 + Math.sin(t * 1.5 + i * 0.8) * 10; Draw.rect(ctx, rx, ry, rw, 2, '#FFDDAA'); } ctx.restore(); // Distant clouds Draw.clouds(ctx, w, h, h * 0.15, 4, '#FFDDBB', 512); }, particles: { type: 'fog', count: 6, color: '#AACCDD' } }, underwater: { meta: { id: 'nature_underwater', mood: 'mystical', colors: ['#0A2342', '#1A4B6E', '#2E86C1', '#48D1CC'], tags: ['deep', 'aquatic', 'blue'], description: 'Deep beneath the waves with shafts of sunlight', compatibleMusicMoods: ['dreamy', 'ambient', 'calm'], recommendedFor: ['puzzle', 'pet', 'creative'] }, colors: ['#0A2342', '#1A4B6E', '#2E86C1', '#48D1CC'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, [c[3], c[2], c[1], c[0]], 90); // Light rays from surface var rng = H.seededRandom(610); ctx.save(); for (var i = 0; i < 6; i++) { var rx = rng() * w; var sway = Math.sin(t * 0.4 + i * 1.5) * 15; ctx.fillStyle = H.rgba('#AADDFF', 0.03 + Math.sin(t * 0.3 + i) * 0.01); ctx.beginPath(); ctx.moveTo(rx + sway, 0); ctx.lineTo(rx - 40 + sway * 0.5, h); ctx.lineTo(rx + 40 + sway * 0.5, h); ctx.closePath(); ctx.fill(); } ctx.restore(); }, renderMid: function(ctx, w, h, c, Draw, t) { var rng = H.seededRandom(611); // Seaweed for (var i = 0; i < 10; i++) { var sx = rng() * w; var sh = 40 + rng() * 80; var sway = Math.sin(t * 0.8 + i * 2) * 10; ctx.strokeStyle = H.rgba('#228844', 0.5 + rng() * 0.3); ctx.lineWidth = 3 + rng() * 3; ctx.beginPath(); ctx.moveTo(sx, h); ctx.quadraticCurveTo(sx + sway, h - sh * 0.5, sx + sway * 1.3, h - sh); ctx.stroke(); } // Coral mounds for (var j = 0; j < 6; j++) { var cx = rng() * w; var cr = 15 + rng() * 30; var cc = H.pick(['#CC4466', '#FF7744', '#FFAA33', '#AA55CC']); Draw.circle(ctx, cx, h - cr * 0.5, cr, H.rgba(cc, 0.5)); Draw.circle(ctx, cx + cr * 0.4, h - cr * 0.3, cr * 0.6, H.rgba(cc, 0.35)); } // Fish silhouettes for (var k = 0; k < 8; k++) { var fx = (rng() * w + t * (10 + rng() * 30)) % (w + 60) - 30; var fy = h * 0.2 + rng() * h * 0.5; var fs = 4 + rng() * 8; ctx.fillStyle = H.rgba(c[3], 0.3 + rng() * 0.2); ctx.beginPath(); ctx.ellipse(fx, fy, fs * 2, fs, 0, 0, Math.PI * 2); ctx.fill(); // Tail ctx.beginPath(); ctx.moveTo(fx - fs * 2, fy); ctx.lineTo(fx - fs * 3, fy - fs * 0.8); ctx.lineTo(fx - fs * 3, fy + fs * 0.8); ctx.closePath(); ctx.fill(); } // Sandy bottom Draw.rect(ctx, 0, h * 0.92, w, h * 0.08, H.rgba('#C2B280', 0.25)); }, particles: { type: 'bubbles', count: 30, color: '#88CCEE' } }, waterfall: { meta: { id: 'nature_waterfall', mood: 'refreshing', colors: ['#1B4332', '#2D6A4F', '#52B788', '#B7E4C7'], tags: ['water', 'lush', 'dynamic'], description: 'A cascading waterfall in a lush green cliff', compatibleMusicMoods: ['calm', 'ambient', 'dreamy'], recommendedFor: ['puzzle', 'creative', 'story'] }, colors: ['#1B4332', '#2D6A4F', '#52B788', '#B7E4C7'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, ['#77AABB', '#99CCDD', c[2]], 90); Draw.clouds(ctx, w, h, h * 0.05, 3, '#FFFFFF', 710); }, renderMid: function(ctx, w, h, c, Draw, t) { // Cliff walls Draw.rect(ctx, 0, h * 0.15, w * 0.35, h * 0.85, c[0]); Draw.rect(ctx, w * 0.65, h * 0.15, w * 0.35, h * 0.85, c[0]); // Cliff vegetation Draw.hills(ctx, w * 0.35, h * 0.2, h * 0.15, 5, c[1], 711); ctx.save(); ctx.translate(w * 0.65, 0); Draw.hills(ctx, w * 0.35, h * 0.2, h * 0.15, 5, c[1], 712); ctx.restore(); // Waterfall var rng = H.seededRandom(713); var fallX = w * 0.38, fallW = w * 0.24; for (var i = 0; i < 20; i++) { var fx = fallX + rng() * fallW; var fy = h * 0.15 + rng() * h * 0.65; var offset = (t * 80 + rng() * 200) % (h * 0.7); var wy = h * 0.15 + offset; ctx.strokeStyle = H.rgba('#FFFFFF', 0.1 + rng() * 0.2); ctx.lineWidth = 1 + rng() * 2; ctx.beginPath(); ctx.moveTo(fx, wy); ctx.lineTo(fx + (rng() - 0.5) * 4, wy + 10 + rng() * 20); ctx.stroke(); } // Base glow Draw.fillGradient(ctx, w, h, [H.rgba(c[3], 0), H.rgba(c[3], 0), H.rgba('#FFFFFF', 0.15)], 90); // Pool at bottom Draw.water(ctx, w, h, h * 0.82, H.rgba('#3388AA', 0.6), 2, 714); // Mist Draw.glow(ctx, w * 0.5, h * 0.8, 100, H.rgba('#FFFFFF', 0.12 + Math.sin(t) * 0.04)); // Foreground plants Draw.trees(ctx, w, h, h * 0.9, 6, '#3D2B1F', c[2], 715); }, particles: { type: 'fog', count: 12, color: '#DDEEFF' } }, meadow: { meta: { id: 'nature_meadow', mood: 'joyful', colors: ['#4CAF50', '#81C784', '#A5D6A7', '#FFF59D'], tags: ['flowers', 'sunny', 'cheerful'], description: 'A sunny meadow with wildflowers and gentle breeze', compatibleMusicMoods: ['happy', 'calm', 'dreamy'], recommendedFor: ['pet', 'creative', 'story', 'puzzle'] }, colors: ['#4CAF50', '#81C784', '#A5D6A7', '#FFF59D'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, ['#55AADD', '#88CCEE', '#AADDEE'], 90); // Sun var pulse = 1 + Math.sin(t * 0.4) * 0.02; Draw.glow(ctx, w * 0.8, h * 0.1, 80 * pulse, H.rgba('#FFEE66', 0.4)); Draw.circle(ctx, w * 0.8, h * 0.1, 28, '#FFEE44'); Draw.clouds(ctx, w, h, h * 0.08, 6, '#FFFFFF', 810); }, renderMid: function(ctx, w, h, c, Draw, t) { // Distant hills Draw.hills(ctx, w, h, h * 0.55, 5, H.darken(c[0], 0.15), 811); // Main meadow Draw.hills(ctx, w, h, h * 0.65, 8, c[0], 812); Draw.hills(ctx, w, h, h * 0.72, 6, c[1], 813); // Ground Draw.rect(ctx, 0, h * 0.78, w, h * 0.22, c[2]); // Flowers var rng = H.seededRandom(814); var flowerColors = ['#FF6B6B', '#FFD93D', '#C084FC', '#FF8FA3', '#FFFFFF']; for (var i = 0; i < 40; i++) { var fx = rng() * w; var fy = h * 0.68 + rng() * h * 0.25; var fc = flowerColors[Math.floor(rng() * flowerColors.length)]; var sway = Math.sin(t * 1.2 + i * 0.8) * 2; // Stem ctx.strokeStyle = H.rgba('#338833', 0.5); ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(fx, fy + 8); ctx.lineTo(fx + sway, fy); ctx.stroke(); // Flower Draw.circle(ctx, fx + sway, fy, 2 + rng() * 2, fc); } // Butterflies for (var j = 0; j < 3; j++) { var bx = (rng() * w + Math.sin(t * 0.5 + j * 3) * 40); var by = h * 0.4 + rng() * h * 0.3 + Math.sin(t * 1.2 + j * 2) * 15; var wingFlap = Math.abs(Math.sin(t * 5 + j * 2)) * 4; var bc = H.pick(['#FFD700', '#FF69B4', '#87CEEB']); ctx.fillStyle = H.rgba(bc, 0.7); ctx.beginPath(); ctx.ellipse(bx - 3, by, wingFlap, 3, -0.3, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.ellipse(bx + 3, by, wingFlap, 3, 0.3, 0, Math.PI * 2); ctx.fill(); Draw.circle(ctx, bx, by, 1, '#333333'); } }, particles: { type: 'leaves', count: 10, color: '#AEDD77' } } }); })(window.BackgroundEngine);