// urban.js — Urban theme for procedural background system // 8 variants: citySkyline, street, rooftop, neonDistrict, subway, park, mall, alley (function(engine) { if (!engine) return; var D = engine.Draw, H = engine.helpers; engine.registerTheme('urban', { citySkyline: { meta: { id: 'urban_citySkyline', mood: 'inspiring', colors: ['#1A1A2E', '#2D2D44', '#4A4A66', '#FF8844'], tags: ['skyline', 'sunset', 'grand'], description: 'City skyline silhouetted against a glowing sunset', compatibleMusicMoods: ['epic', 'calm', 'ambient'], recommendedFor: ['story', 'rpg', 'creative'] }, colors: ['#1A1A2E', '#2D2D44', '#4A4A66', '#FF8844'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, ['#1A0A2E', '#4A2255', '#CC6633', c[3]], 90); // Sun glow on horizon Draw.glow(ctx, w * 0.5, h * 0.58, 200, H.rgba(c[3], 0.3 + Math.sin(t * 0.2) * 0.05)); Draw.clouds(ctx, w, h, h * 0.12, 5, '#FF9966', 100); }, renderMid: function(ctx, w, h, c, Draw, t) { // Far buildings silhouette Draw.buildings(ctx, w, h, h * 0.58, 20, [H.rgba(c[0], 0.7), H.rgba(c[1], 0.6)], 101); // Main skyline Draw.buildings(ctx, w, h, h * 0.65, 14, [c[0], c[1]], 102); // Foreground buildings Draw.buildings(ctx, w, h, h * 0.75, 8, [c[1], c[2]], 103); // Ground Draw.rect(ctx, 0, h * 0.88, w, h * 0.12, H.darken(c[0], 0.5)); // Antenna lights var rng = H.seededRandom(104); for (var i = 0; i < 6; i++) { var ax = rng() * w; var ay = h * 0.3 + rng() * h * 0.2; var blink = Math.sin(t * 3 + i * 1.8) > 0.4 ? 0.9 : 0.15; Draw.circle(ctx, ax, ay, 2, H.rgba('#FF3333', blink)); } }, particles: { type: 'dust', count: 15, color: '#FFAA66' } }, street: { meta: { id: 'urban_street', mood: 'busy', colors: ['#2C3E50', '#566573', '#808B96', '#FFD700'], tags: ['road', 'traffic', 'evening'], description: 'Busy city street with lamp posts and passing lights', compatibleMusicMoods: ['energetic', 'ambient', 'electronic'], recommendedFor: ['racing', 'action', 'sports'] }, colors: ['#2C3E50', '#566573', '#808B96', '#FFD700'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, ['#1A1A30', '#2A2A44', c[0]], 90); }, renderMid: function(ctx, w, h, c, Draw, t) { // Buildings on sides Draw.buildings(ctx, w * 0.4, h, h * 0.15, 6, [c[0], c[1]], 201); ctx.save(); ctx.translate(w * 0.6, 0); Draw.buildings(ctx, w * 0.4, h, h * 0.15, 6, [c[0], c[1]], 202); ctx.restore(); // Road surface (perspective) ctx.fillStyle = '#333340'; ctx.beginPath(); ctx.moveTo(w * 0.3, h * 0.35); ctx.lineTo(w * 0.7, h * 0.35); ctx.lineTo(w, h); ctx.lineTo(0, h); ctx.closePath(); ctx.fill(); // Road lines var rng = H.seededRandom(203); ctx.strokeStyle = H.rgba(c[3], 0.5); ctx.lineWidth = 2; ctx.setLineDash([15, 20]); ctx.beginPath(); ctx.moveTo(w * 0.5, h * 0.35); ctx.lineTo(w * 0.5, h); ctx.stroke(); ctx.setLineDash([]); // Lamp posts for (var i = 0; i < 4; i++) { var lx = w * 0.25 + (i % 2) * w * 0.5; var ly = h * 0.4 + i * h * 0.15; var lh = 40 + i * 10; Draw.rect(ctx, lx - 2, ly - lh, 4, lh, '#555566'); Draw.rect(ctx, lx - 8, ly - lh - 3, 16, 5, '#666677'); // Lamp glow var flicker = 0.6 + Math.sin(t * 4 + i * 2.5) * 0.05; Draw.glow(ctx, lx, ly - lh, 35, H.rgba(c[3], 0.2 * flicker)); Draw.circle(ctx, lx, ly - lh, 3, H.rgba(c[3], flicker)); } // Car headlights for (var j = 0; j < 3; j++) { var cy = (h * 0.5 + j * h * 0.15 + t * 20) % (h * 0.7) + h * 0.35; var cx = w * 0.45 + rng() * w * 0.1; var alpha = H.clamp((cy - h * 0.35) / (h * 0.4), 0, 0.7); Draw.glow(ctx, cx - 5, cy, 6 * alpha + 3, H.rgba('#FFFFFF', alpha * 0.5)); Draw.glow(ctx, cx + 5, cy, 6 * alpha + 3, H.rgba('#FFFFFF', alpha * 0.5)); } }, particles: { type: 'dust', count: 20, color: '#AAAAAA' } }, rooftop: { meta: { id: 'urban_rooftop', mood: 'reflective', colors: ['#1B2631', '#2C3E50', '#5D6D7E', '#F4D03F'], tags: ['night', 'view', 'quiet'], description: 'Rooftop view over a twinkling nighttime city', compatibleMusicMoods: ['calm', 'dreamy', 'ambient'], recommendedFor: ['story', 'puzzle', 'creative'] }, colors: ['#1B2631', '#2C3E50', '#5D6D7E', '#F4D03F'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, ['#0A0A1A', '#151528', c[0]], 90); Draw.stars(ctx, w, h * 0.4, 60, '#FFFFFF', 301, [0.3, 1.5]); // Moon Draw.glow(ctx, w * 0.8, h * 0.12, 50, H.rgba('#CCDDFF', 0.15)); Draw.circle(ctx, w * 0.8, h * 0.12, 18, '#DDDDEE'); }, renderMid: function(ctx, w, h, c, Draw, t) { // Distant city Draw.buildings(ctx, w, h, h * 0.45, 25, [c[0], c[1]], 302); // City lights twinkling var rng = H.seededRandom(303); for (var i = 0; i < 50; i++) { var lx = rng() * w; var ly = h * 0.3 + rng() * h * 0.25; var twinkle = 0.2 + Math.sin(t * 2 + rng() * 10) * 0.3; var lc = rng() > 0.7 ? c[3] : '#FFFFFF'; Draw.circle(ctx, lx, ly, 1, H.rgba(lc, twinkle)); } // Rooftop surface Draw.rect(ctx, 0, h * 0.7, w, h * 0.3, c[1]); Draw.rect(ctx, 0, h * 0.7, w, 3, c[2]); // Rooftop elements Draw.rect(ctx, w * 0.05, h * 0.6, w * 0.08, h * 0.1, c[0]); // AC unit Draw.rect(ctx, w * 0.85, h * 0.55, w * 0.04, h * 0.15, c[2]); // Antenna // Railing ctx.strokeStyle = c[2]; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(0, h * 0.68); ctx.lineTo(w, h * 0.68); ctx.stroke(); for (var j = 0; j < 20; j++) { var rx = j * (w / 19); ctx.beginPath(); ctx.moveTo(rx, h * 0.68); ctx.lineTo(rx, h * 0.7); ctx.stroke(); } // String lights ctx.strokeStyle = H.rgba('#332211', 0.5); ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(w * 0.1, h * 0.62); ctx.quadraticCurveTo(w * 0.5, h * 0.66, w * 0.9, h * 0.62); ctx.stroke(); for (var k = 0; k < 12; k++) { var bx = w * 0.1 + k * (w * 0.8 / 11); var sag = Math.sin((k / 11) * Math.PI) * h * 0.04; var by = h * 0.62 + sag; var glow = 0.5 + Math.sin(t * 1.5 + k * 0.9) * 0.2; Draw.glow(ctx, bx, by, 8, H.rgba(c[3], 0.2 * glow)); Draw.circle(ctx, bx, by, 2.5, H.rgba(c[3], glow)); } }, particles: { type: 'fireflies', count: 8, color: '#FFD700' } }, neonDistrict: { meta: { id: 'urban_neonDistrict', mood: 'electric', colors: ['#0D0D1A', '#1A0A2E', '#FF00FF', '#00FFCC'], tags: ['neon', 'cyberpunk', 'vibrant'], description: 'Rain-slicked streets glowing with neon signs', compatibleMusicMoods: ['electronic', 'energetic', 'tense'], recommendedFor: ['action', 'racing', 'music'] }, colors: ['#0D0D1A', '#1A0A2E', '#FF00FF', '#00FFCC'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, [c[0], c[1], H.darken(c[1], 0.3)], 90); }, renderMid: function(ctx, w, h, c, Draw, t) { // Dark buildings Draw.buildings(ctx, w, h, h * 0.1, 12, [c[0], H.darken(c[1], 0.3)], 401); // Neon signs var rng = H.seededRandom(402); var neonColors = [c[2], c[3], '#FF4488', '#44AAFF', '#FFDD00']; for (var i = 0; i < 8; i++) { var nx = rng() * w; var ny = h * 0.15 + rng() * h * 0.35; var nw = 20 + rng() * 50; var nh = 8 + rng() * 20; var nc = neonColors[Math.floor(rng() * neonColors.length)]; var pulse = 0.6 + Math.sin(t * 2 + i * 1.7) * 0.25; // Sign glow Draw.glow(ctx, nx + nw / 2, ny + nh / 2, nw * 0.8, H.rgba(nc, 0.15 * pulse)); // Sign border ctx.strokeStyle = H.rgba(nc, pulse); ctx.lineWidth = 2; ctx.strokeRect(nx, ny, nw, nh); // Sign fill ctx.fillStyle = H.rgba(nc, 0.08 * pulse); ctx.fillRect(nx, ny, nw, nh); } // Wet ground Draw.rect(ctx, 0, h * 0.75, w, h * 0.25, '#0A0A15'); // Neon reflections on ground for (var j = 0; j < 6; j++) { var rx = rng() * w; var rc = neonColors[Math.floor(rng() * neonColors.length)]; var rAlpha = 0.05 + Math.sin(t * 1.5 + j * 2) * 0.03; ctx.fillStyle = H.rgba(rc, rAlpha); ctx.fillRect(rx - 25, h * 0.78, 50, h * 0.2); } // Power lines ctx.strokeStyle = H.rgba('#333344', 0.5); ctx.lineWidth = 1; for (var k = 0; k < 3; k++) { var py = h * 0.08 + k * 15; ctx.beginPath(); ctx.moveTo(0, py); ctx.quadraticCurveTo(w * 0.5, py + 10, w, py); ctx.stroke(); } // Scanline overlay Draw.scanlines(ctx, w, h, 4, H.rgba(c[0], 0.06)); }, particles: { type: 'rain', count: 80, color: '#8899BB' } }, subway: { meta: { id: 'urban_subway', mood: 'gritty', colors: ['#1C1C1C', '#333333', '#555555', '#FFCC00'], tags: ['underground', 'industrial', 'dark'], description: 'Underground subway platform with arriving train lights', compatibleMusicMoods: ['tense', 'electronic', 'ambient'], recommendedFor: ['action', 'puzzle', 'rpg'] }, colors: ['#1C1C1C', '#333333', '#555555', '#FFCC00'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, [c[0], c[1], c[0]], 90); }, renderMid: function(ctx, w, h, c, Draw, t) { // Ceiling Draw.rect(ctx, 0, 0, w, h * 0.15, c[0]); // Ceiling panels var rng = H.seededRandom(501); for (var i = 0; i < 10; i++) { var px = i * (w / 10) + 5; Draw.rect(ctx, px, h * 0.02, w / 10 - 10, h * 0.1, H.lighten(c[0], 0.05)); } // Fluorescent lights for (var j = 0; j < 6; j++) { var lx = w * 0.1 + j * (w * 0.8 / 5); var flicker = 0.7 + Math.sin(t * 8 + j * 3) * 0.1 + (rng() > 0.97 ? -0.4 : 0); Draw.rect(ctx, lx - 20, h * 0.12, 40, 4, H.rgba('#FFFFFF', flicker)); Draw.glow(ctx, lx, h * 0.2, 50, H.rgba('#FFFFFF', 0.08 * flicker)); } // Walls (tiled) Draw.rect(ctx, 0, h * 0.15, w, h * 0.45, c[1]); Draw.grid(ctx, w, h * 0.45, 25, H.rgba(c[2], 0.15), 0.5); // Colored tile stripe Draw.rect(ctx, 0, h * 0.35, w, 8, c[3]); // Platform Draw.rect(ctx, 0, h * 0.6, w, h * 0.08, c[2]); // Yellow safety line Draw.rect(ctx, 0, h * 0.6, w, 4, H.rgba(c[3], 0.8)); // Track area Draw.rect(ctx, 0, h * 0.68, w, h * 0.32, H.darken(c[0], 0.3)); // Rails ctx.strokeStyle = H.rgba('#888888', 0.4); ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo(0, h * 0.78); ctx.lineTo(w, h * 0.78); ctx.stroke(); ctx.beginPath(); ctx.moveTo(0, h * 0.88); ctx.lineTo(w, h * 0.88); ctx.stroke(); // Train approaching light var approach = (Math.sin(t * 0.3) + 1) * 0.5; if (approach > 0.6) { var intensity = (approach - 0.6) / 0.4; Draw.glow(ctx, w * 0.5, h * 0.82, 80 * intensity, H.rgba('#FFFFFF', 0.3 * intensity)); Draw.glow(ctx, w * 0.4, h * 0.82, 20 * intensity, H.rgba('#FFFFFF', 0.5 * intensity)); Draw.glow(ctx, w * 0.6, h * 0.82, 20 * intensity, H.rgba('#FFFFFF', 0.5 * intensity)); } // Bench Draw.rect(ctx, w * 0.15, h * 0.54, w * 0.12, h * 0.04, '#444444'); Draw.rect(ctx, w * 0.16, h * 0.56, 4, h * 0.04, '#444444'); Draw.rect(ctx, w * 0.26, h * 0.56, 4, h * 0.04, '#444444'); }, particles: { type: 'dust', count: 15, color: '#888888' } }, park: { meta: { id: 'urban_park', mood: 'relaxed', colors: ['#2E7D32', '#4CAF50', '#81C784', '#795548'], tags: ['trees', 'nature', 'city'], description: 'A peaceful city park with trees and walking paths', compatibleMusicMoods: ['calm', 'happy', 'dreamy'], recommendedFor: ['pet', 'puzzle', 'story', 'creative'] }, colors: ['#2E7D32', '#4CAF50', '#81C784', '#795548'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, ['#66AADD', '#88CCEE', '#BBDDEE'], 90); Draw.clouds(ctx, w, h, h * 0.08, 5, '#FFFFFF', 600); // Distant buildings Draw.buildings(ctx, w, h, h * 0.35, 12, ['#8899AA', '#99AABB'], 601); }, renderMid: function(ctx, w, h, c, Draw, t) { // Far tree line Draw.hills(ctx, w, h, h * 0.45, 8, H.darken(c[0], 0.2), 602); Draw.trees(ctx, w, h, h * 0.5, 15, c[3], c[0], 603); // Park ground Draw.hills(ctx, w, h, h * 0.6, 5, c[1], 604); Draw.rect(ctx, 0, h * 0.68, w, h * 0.32, c[2]); // Walking path ctx.strokeStyle = H.rgba('#CCBBAA', 0.6); ctx.lineWidth = 12; ctx.beginPath(); ctx.moveTo(0, h * 0.82); ctx.quadraticCurveTo(w * 0.3, h * 0.75, w * 0.5, h * 0.78); ctx.quadraticCurveTo(w * 0.7, h * 0.82, w, h * 0.76); ctx.stroke(); // Park trees Draw.trees(ctx, w, h, h * 0.72, 8, c[3], c[1], 605); // Park bench Draw.rect(ctx, w * 0.6, h * 0.7, 25, 3, c[3]); Draw.rect(ctx, w * 0.602, h * 0.66, 3, h * 0.04, c[3]); Draw.rect(ctx, w * 0.63, h * 0.66, 3, h * 0.04, c[3]); // Pond ctx.fillStyle = H.rgba('#4488AA', 0.4); ctx.beginPath(); ctx.ellipse(w * 0.25, h * 0.85, 50, 20, 0, 0, Math.PI * 2); ctx.fill(); // Pond shimmer var shimmer = 0.15 + Math.sin(t * 1.5) * 0.05; ctx.fillStyle = H.rgba('#AADDEE', shimmer); ctx.beginPath(); ctx.ellipse(w * 0.24, h * 0.84, 15, 5, 0.2, 0, Math.PI * 2); ctx.fill(); // Lamp post Draw.rect(ctx, w * 0.45 - 2, h * 0.58, 4, h * 0.14, '#555555'); Draw.circle(ctx, w * 0.45, h * 0.58, 5, H.rgba('#FFEE88', 0.3 + Math.sin(t) * 0.05)); }, particles: { type: 'leaves', count: 12, color: '#CC9944' } }, mall: { meta: { id: 'urban_mall', mood: 'lively', colors: ['#E8E8E8', '#CCCCCC', '#888888', '#FF6B6B'], tags: ['indoor', 'bright', 'commercial'], description: 'Bright mall interior with shop windows and escalators', compatibleMusicMoods: ['happy', 'energetic', 'electronic'], recommendedFor: ['trivia', 'creative', 'pet'] }, colors: ['#E8E8E8', '#CCCCCC', '#888888', '#FF6B6B'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, [c[0], c[1], c[0]], 90); }, renderMid: function(ctx, w, h, c, Draw, t) { var rng = H.seededRandom(701); // Ceiling structure Draw.rect(ctx, 0, 0, w, h * 0.08, c[2]); // Skylight panels for (var i = 0; i < 4; i++) { var sx = w * 0.15 + i * (w * 0.7 / 3); Draw.rect(ctx, sx, h * 0.01, w * 0.12, h * 0.05, H.rgba('#AADDFF', 0.3)); } // Ceiling lights for (var j = 0; j < 8; j++) { var lx = w * 0.1 + j * (w * 0.8 / 7); Draw.rect(ctx, lx - 12, h * 0.07, 24, 3, '#FFFFFF'); Draw.glow(ctx, lx, h * 0.12, 30, H.rgba('#FFFFFF', 0.06)); } // Floor Draw.rect(ctx, 0, h * 0.7, w, h * 0.3, '#DDDDDD'); // Floor tiles Draw.grid(ctx, w, h * 0.3, 40, H.rgba('#BBBBBB', 0.3), 0.5); // Store fronts - left side var shopColors = ['#FF6B6B', '#4FC3F7', '#81C784', '#FFB74D', '#BA68C8']; for (var k = 0; k < 3; k++) { var sy = h * 0.12 + k * h * 0.2; var sc = shopColors[Math.floor(rng() * shopColors.length)]; // Shop frame Draw.rect(ctx, 0, sy, w * 0.2, h * 0.18, '#F5F5F5'); Draw.rect(ctx, w * 0.01, sy + h * 0.01, w * 0.18, h * 0.12, H.rgba(sc, 0.15)); // Shop sign var signPulse = 0.7 + Math.sin(t * 1.5 + k * 2.3) * 0.15; Draw.rect(ctx, w * 0.03, sy + h * 0.005, w * 0.14, h * 0.025, H.rgba(sc, signPulse)); } // Store fronts - right side for (var m = 0; m < 3; m++) { var rsy = h * 0.12 + m * h * 0.2; var rsc = shopColors[Math.floor(rng() * shopColors.length)]; Draw.rect(ctx, w * 0.8, rsy, w * 0.2, h * 0.18, '#F5F5F5'); Draw.rect(ctx, w * 0.81, rsy + h * 0.01, w * 0.18, h * 0.12, H.rgba(rsc, 0.15)); var rPulse = 0.7 + Math.sin(t * 1.5 + m * 2 + 1) * 0.15; Draw.rect(ctx, w * 0.83, rsy + h * 0.005, w * 0.14, h * 0.025, H.rgba(rsc, rPulse)); } // Escalator Draw.rect(ctx, w * 0.4, h * 0.4, w * 0.2, h * 0.35, c[2]); // Escalator steps (animated) for (var s = 0; s < 8; s++) { var stepY = h * 0.42 + ((s * h * 0.04 + t * 15) % (h * 0.3)); ctx.strokeStyle = H.rgba('#AAAAAA', 0.5); ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(w * 0.42, stepY); ctx.lineTo(w * 0.58, stepY); ctx.stroke(); } // Railing ctx.strokeStyle = c[2]; ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo(w * 0.4, h * 0.4); ctx.lineTo(w * 0.4, h * 0.75); ctx.stroke(); ctx.beginPath(); ctx.moveTo(w * 0.6, h * 0.4); ctx.lineTo(w * 0.6, h * 0.75); ctx.stroke(); // Potted plant Draw.rect(ctx, w * 0.32, h * 0.64, 14, 12, '#8B6914'); Draw.circle(ctx, w * 0.325, h * 0.6, 12, '#4CAF50'); }, particles: { type: 'sparkles', count: 20, color: '#FFFFFF' } }, alley: { meta: { id: 'urban_alley', mood: 'mysterious', colors: ['#111118', '#1E1E28', '#333340', '#BB8844'], tags: ['dark', 'narrow', 'moody'], description: 'A narrow alley with a single warm light overhead', compatibleMusicMoods: ['tense', 'ambient', 'electronic'], recommendedFor: ['rpg', 'action', 'story'] }, colors: ['#111118', '#1E1E28', '#333340', '#BB8844'], renderBase: function(ctx, w, h, c, Draw, t) { Draw.fillGradient(ctx, w, h, [c[0], c[1], c[0]], 90); }, renderMid: function(ctx, w, h, c, Draw, t) { var rng = H.seededRandom(801); // Left wall Draw.rect(ctx, 0, 0, w * 0.3, h, c[1]); // Right wall Draw.rect(ctx, w * 0.7, 0, w * 0.3, h, c[1]); // Brick texture (left) for (var row = 0; row < 30; row++) { var by = row * (h / 30); var offset = (row % 2) * 8; for (var col = 0; col < 4; col++) { var bx = col * (w * 0.3 / 4) + offset; ctx.strokeStyle = H.rgba(c[2], 0.15); ctx.lineWidth = 0.5; ctx.strokeRect(bx, by, w * 0.3 / 4 - 2, h / 30 - 2); } } // Brick texture (right) for (var row2 = 0; row2 < 30; row2++) { var by2 = row2 * (h / 30); var offset2 = (row2 % 2) * 8; for (var col2 = 0; col2 < 4; col2++) { var bx2 = w * 0.7 + col2 * (w * 0.3 / 4) + offset2; ctx.strokeStyle = H.rgba(c[2], 0.15); ctx.lineWidth = 0.5; ctx.strokeRect(bx2, by2, w * 0.3 / 4 - 2, h / 30 - 2); } } // Ground Draw.rect(ctx, w * 0.28, h * 0.85, w * 0.44, h * 0.15, H.darken(c[1], 0.3)); // Puddle ctx.fillStyle = H.rgba('#223344', 0.4); ctx.beginPath(); ctx.ellipse(w * 0.5, h * 0.92, 35, 8, 0, 0, Math.PI * 2); ctx.fill(); // Puddle light reflection var refPulse = 0.15 + Math.sin(t * 1.2) * 0.05; ctx.fillStyle = H.rgba(c[3], refPulse); ctx.beginPath(); ctx.ellipse(w * 0.5, h * 0.92, 15, 4, 0, 0, Math.PI * 2); ctx.fill(); // Overhead light Draw.rect(ctx, w * 0.48, h * 0.15, w * 0.04, h * 0.02, '#555555'); var lightFlicker = 0.7 + Math.sin(t * 6) * 0.15 + Math.sin(t * 13) * 0.05; Draw.glow(ctx, w * 0.5, h * 0.18, 100, H.rgba(c[3], 0.18 * lightFlicker)); Draw.glow(ctx, w * 0.5, h * 0.18, 40, H.rgba(c[3], 0.3 * lightFlicker)); Draw.circle(ctx, w * 0.5, h * 0.17, 4, H.rgba('#FFDDAA', lightFlicker)); // Light cone ctx.fillStyle = H.rgba(c[3], 0.04 * lightFlicker); ctx.beginPath(); ctx.moveTo(w * 0.48, h * 0.18); ctx.lineTo(w * 0.3, h * 0.85); ctx.lineTo(w * 0.7, h * 0.85); ctx.closePath(); ctx.fill(); // Dumpster Draw.rect(ctx, w * 0.62, h * 0.7, w * 0.07, h * 0.12, '#2A3A2A'); Draw.rect(ctx, w * 0.615, h * 0.69, w * 0.08, h * 0.02, '#334433'); // Fire escape (left wall) ctx.strokeStyle = H.rgba(c[2], 0.4); ctx.lineWidth = 2; for (var fe = 0; fe < 4; fe++) { var fey = h * 0.2 + fe * h * 0.15; ctx.beginPath(); ctx.moveTo(w * 0.3, fey); ctx.lineTo(w * 0.38, fey); ctx.lineTo(w * 0.38, fey + h * 0.03); ctx.stroke(); } // Steam wisps var steamAlpha = 0.05 + Math.sin(t * 0.8) * 0.03; Draw.glow(ctx, w * 0.65, h * 0.68, 25, H.rgba('#AAAAAA', steamAlpha)); }, particles: { type: 'fog', count: 6, color: '#555555' } } }); })(window.BackgroundEngine);