Files
gamercomp/frontend/public/assets/backgrounds/themes/urban.js
T
2026-04-30 02:14:25 +00:00

467 lines
21 KiB
JavaScript

// 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);