import type { DrawContext } from '../types'; export function drawSmoggyBuildings(dc: DrawContext): void { const { ctx, width, height, state } = dc; const parallax = state.mouseX * 0.35; // Helper to draw varied building shapes (Geometry matched to Clean City) const drawBuildingShape = ( bx: number, by: number, bw: number, bh: number, color: string, windowColor: string, type: number, stableSeedX: number ) => { ctx.fillStyle = color; // Base ctx.fillRect(bx, by - bh, bw, bh + height * 0.5); // Roof variations (Matched to Clean City) if (type === 1) { // Slanted ctx.beginPath(); ctx.moveTo(bx, by - bh); ctx.lineTo(bx + bw, by - bh); ctx.lineTo(bx + bw, by - bh - 20); ctx.fill(); } else if (type === 2) { // Spire (Matched Clean City) ctx.beginPath(); ctx.moveTo(bx + bw/2 - 5, by - bh); ctx.lineTo(bx + bw/2, by - bh - 40); ctx.lineTo(bx + bw/2 + 5, by - bh); ctx.fill(); // Flag/Light (Red for pollution warning?) ctx.fillStyle = '#B71C1C'; ctx.fillRect(bx + bw/2 - 1, by - bh - 40, 2, 40); ctx.fillStyle = color; } else if (type === 3) { // Tri-peak (Matched Clean City) ctx.beginPath(); ctx.moveTo(bx, by - bh); ctx.lineTo(bx + bw * 0.25, by - bh - 15); ctx.lineTo(bx + bw * 0.5, by - bh); ctx.lineTo(bx + bw * 0.75, by - bh - 15); ctx.lineTo(bx + bw, by - bh); ctx.fill(); } // Windows (Matched Clean City: 8x12) const cols = Math.floor(bw / 12); const rows = Math.floor(bh / 18); const padding = 4; ctx.fillStyle = windowColor; for(let r=0; r 0.8) continue; // Some gaps // Flicker logic if (Math.abs(Math.cos(seed)) > 0.92) { ctx.fillStyle = 'rgba(255, 200, 100, 0.4)'; // Yellowish light ctx.fillRect(bx + c*12 + padding, by - bh + r*18 + padding, 8, 12); ctx.fillStyle = windowColor; } else { ctx.fillRect(bx + c*12 + padding, by - bh + r*18 + padding, 8, 12); } } } }; // -- Background Layer -- (Darker, slower) const bgParallax = state.mouseX * 0.15; const bgBaseY = height * 0.78; let currentBgX = -100; while(currentBgX < width + 100) { const seed = currentBgX * 0.1; const bW = 60 + (Math.abs(Math.sin(seed)) * 40); const bH = height * 0.25 + (Math.abs(Math.cos(seed)) * height * 0.25); const gap = 10 + Math.abs(Math.sin(seed * 2)) * 20; // Exact same type distribution as Clean City BG const type = Math.floor(Math.abs(Math.sin(seed * 5)) * 4); drawBuildingShape( currentBgX + bgParallax, bgBaseY, bW, bH, '#1f292e', 'rgba(255, 255, 255, 0.03)', type, currentBgX ); currentBgX += bW + gap; } // -- Middle Layer -- const midParallax = state.mouseX * 0.25; const midBaseY = height * 0.8; let currentMidX = -50; while(currentMidX < width + 50) { const seed = currentMidX * 0.17; const bW = 70 + (Math.abs(Math.sin(seed)) * 50); const bH = height * 0.2 + (Math.abs(Math.cos(seed)) * height * 0.2); const gap = 30 + Math.abs(Math.sin(seed * 4)) * 40; // Exact same type distribution as Clean City Mid const type = Math.floor(Math.abs(Math.cos(seed * 7)) * 4); drawBuildingShape( currentMidX + midParallax, midBaseY, bW, bH, '#2d3b42', 'rgba(200, 200, 200, 0.08)', type, currentMidX ); currentMidX += bW + gap; } // -- Foreground Layer -- const fgBaseY = height * 0.82; let currentFgX = 20; while(currentFgX < width - 20) { const seed = currentFgX * 0.33; const bW = 90 + (Math.abs(Math.sin(seed)) * 60); const bH = height * 0.15 + (Math.abs(Math.cos(seed)) * height * 0.2); const gap = 80 + Math.abs(Math.sin(seed * 1.5)) * 80; // Exact same type distribution as Clean City FG const type = Math.floor(Math.abs(Math.sin(seed * 9)) * 4); ctx.save(); drawBuildingShape( currentFgX + parallax, fgBaseY, bW, bH, '#37474F', 'rgba(50, 50, 50, 0.8)', type, currentFgX ); // Extra FG details (AC units) - Rare if (Math.abs(Math.sin(seed)) > 0.7) { const bx = currentFgX + parallax; const by = fgBaseY - bH; if (type === 0) { // Only on flat roofs ctx.fillStyle = '#263238'; ctx.fillRect(bx + 10, by - 12, 15, 12); } } ctx.restore(); currentFgX += bW + gap; } } export function drawSmokestacks(dc: DrawContext): void { const { ctx, width, height, state } = dc; const parallax = state.mouseX * 0.4; const time = Date.now() * 0.001; const drawSmokestack = (x: number, stackHeight: number) => { const baseY = height * 0.8; ctx.fillStyle = '#263238'; // Extend smokestack down as well ctx.fillRect(x + parallax - 12, baseY - stackHeight, 24, stackHeight + height * 0.2); ctx.fillStyle = '#B71C1C'; ctx.fillRect(x + parallax - 12, baseY - stackHeight, 24, 15); for (let i = 0; i < 8; i++) { const smokeY = baseY - stackHeight - i * 20 - Math.sin(time + i * 0.5) * 10; const smokeRadius = 15 + i * 12; const offsetX = Math.sin(time * 0.8 + i * 0.3) * 25 * (i / 4); const alpha = 0.6 - i * 0.06; ctx.fillStyle = `rgba(60, 60, 60, ${alpha})`; ctx.beginPath(); ctx.arc(x + parallax + offsetX, smokeY, smokeRadius, 0, Math.PI * 2); ctx.fill(); } }; drawSmokestack(width * 0.15, height * 0.3); drawSmokestack(width * 0.55, height * 0.35); drawSmokestack(width * 0.85, height * 0.28); } export function drawSmog(dc: DrawContext): void { const { ctx, width, height } = dc; const time = Date.now() * 0.0003; for (let layer = 0; layer < 3; layer++) { const gradient = ctx.createLinearGradient(0, height * 0.3, 0, height * 0.7); const alpha = 0.15 + layer * 0.08; gradient.addColorStop(0, `rgba(80, 80, 80, 0)`); gradient.addColorStop(0.5, `rgba(100, 90, 80, ${alpha})`); gradient.addColorStop(1, `rgba(80, 80, 80, 0)`); ctx.fillStyle = gradient; ctx.beginPath(); ctx.moveTo(-100, height * 0.6); for (let x = -100; x <= width + 100; x += 50) { const y = height * 0.45 + Math.sin((x + time * 100 + layer * 500) * 0.005) * 40; ctx.lineTo(x, y); } ctx.lineTo(width + 100, height * 0.7); ctx.lineTo(-100, height * 0.7); ctx.closePath(); ctx.fill(); } } export function drawTrafficJam(dc: DrawContext): void { const { ctx, width, height } = dc; const time = Date.now() * 0.0005; // Adjust speed ctx.fillStyle = '#2E2E2E'; ctx.fillRect(0, height * 0.82, width, height * 0.08); ctx.strokeStyle = 'rgba(200, 180, 100, 0.4)'; ctx.lineWidth = 2; ctx.setLineDash([20, 30]); ctx.beginPath(); ctx.moveTo(0, height * 0.86); ctx.lineTo(width, height * 0.86); ctx.stroke(); ctx.setLineDash([]); const drawCar = (x: number, y: number, color: string, facingRight: boolean) => { ctx.fillStyle = color; ctx.beginPath(); ctx.roundRect(x, y, 35, 14, 2); ctx.fill(); ctx.fillStyle = 'rgba(100, 100, 100, 0.5)'; ctx.fillRect(x + 8, y - 7, 19, 8); ctx.fillStyle = '#1a1a1a'; ctx.beginPath(); ctx.arc(x + 8, y + 14, 4, 0, Math.PI * 2); ctx.arc(x + 27, y + 14, 4, 0, Math.PI * 2); ctx.fill(); // Lights if (facingRight) { // Headlights (right) ctx.fillStyle = 'rgba(255, 255, 200, 0.6)'; ctx.beginPath(); ctx.arc(x + 35, y + 5, 2, 0, Math.PI * 2); // Front right ctx.arc(x + 35, y + 10, 2, 0, Math.PI * 2); // Front left (perspective approx) ctx.fill(); // Taillights (left) - Red ctx.fillStyle = 'rgba(255, 0, 0, 0.6)'; ctx.beginPath(); ctx.arc(x, y + 5, 2, 0, Math.PI * 2); ctx.arc(x, y + 10, 2, 0, Math.PI * 2); ctx.fill(); } else { // Headlights (left) ctx.fillStyle = 'rgba(255, 255, 200, 0.6)'; ctx.beginPath(); ctx.arc(x, y + 5, 2, 0, Math.PI * 2); ctx.arc(x, y + 10, 2, 0, Math.PI * 2); ctx.fill(); // Taillights (right) - Red ctx.fillStyle = 'rgba(255, 0, 0, 0.6)'; ctx.beginPath(); ctx.arc(x + 35, y + 5, 2, 0, Math.PI * 2); ctx.arc(x + 35, y + 10, 2, 0, Math.PI * 2); ctx.fill(); } }; const carColors = ['#616161', '#424242', '#757575', '#546E7A', '#455A64']; // Top lane (moving left) const lane0Y = height * 0.82; const spacing = 70; const numCars = Math.ceil(width / spacing) + 2; for (let i = 0; i < numCars; i++) { // Lane 0: Move Left const offset0 = (time * 60 + i * spacing) % (width + 100); const x0 = (width + 50) - offset0 - 50; // Move from right to left drawCar(x0, lane0Y, carColors[i % carColors.length], false); } // Bottom lane (moving right) const lane1Y = height * 0.82 + 25; for (let i = 0; i < numCars; i++) { // Lane 1: Move Right const offset1 = (time * 60 + i * spacing) % (width + 100); const x1 = offset1 - 50; drawCar(x1, lane1Y, carColors[(i + 3) % carColors.length], true); } } export function drawDebris(dc: DrawContext): void { const { ctx, width, height } = dc; ctx.fillStyle = '#5D4037'; for (let i = 0; i < 12; i++) { const seed = i * 13.7; const x = (Math.abs(Math.sin(seed * 4.1)) * width); const y = height * 0.81 + Math.abs(Math.cos(seed * 2.9)) * 8; const size = 3 + Math.abs(Math.sin(seed * 7.3)) * 5; ctx.beginPath(); ctx.rect(x, y, size, size * 0.7); ctx.fill(); } } export function drawCityGround(dc: DrawContext): void { const { ctx, width, height } = dc; // Draw Sidewalk at the bottom const pavementY = height * 0.90; // Curb (side surface) ctx.fillStyle = '#546E7A'; ctx.fillRect(0, pavementY, width, height * 0.02); // Sidewalk pavement (top surface) - Plain ctx.fillStyle = '#37474F'; ctx.fillRect(0, pavementY + height * 0.02, width, height * 0.08); // Detailed Trash (retained for polluted theme, but kept sidewalk otherwise plain) const drawTrash = (tx: number, ty: number, type: 'can' | 'paper' | 'bottle') => { if (type === 'can') { ctx.fillStyle = '#B71C1C'; // Red can ctx.fillRect(tx, ty, 6, 8); ctx.fillStyle = '#E57373'; // Label ctx.fillRect(tx + 1, ty + 2, 4, 4); } else if (type === 'paper') { ctx.fillStyle = '#ECEFF1'; ctx.beginPath(); ctx.moveTo(tx, ty); ctx.lineTo(tx + 8, ty + 2); ctx.lineTo(tx + 6, ty + 6); ctx.lineTo(tx - 2, ty + 4); ctx.fill(); } else { // bottle ctx.fillStyle = 'rgba(76, 175, 80, 0.6)'; ctx.beginPath(); ctx.rect(tx, ty, 4, 10); ctx.rect(tx + 1, ty - 3, 2, 3); ctx.fill(); } }; for (let i = 0; i < 8; i++) { const seed = i * 997; const tx = (seed * 31) % width; const ty = pavementY + height * 0.02 + (seed % (height * 0.06)); const types: ('can' | 'paper' | 'bottle')[] = ['can', 'paper', 'bottle']; drawTrash(tx, ty, types[i % 3]); } } export function drawStreetLamps(dc: DrawContext): void { const { ctx, width, height, state } = dc; const parallax = state.mouseX * 0.8; const drawLamp = (x: number) => { const lampBaseX = x + parallax; // Anchor firmly on the sidewalk surface (sidewalk starts at 0.90 + 0.02) const pavementY = height * 0.94; // Pole ctx.fillStyle = '#263238'; ctx.fillRect(lampBaseX - 3, pavementY - 150, 6, 150); // Lamp Head ctx.beginPath(); ctx.moveTo(lampBaseX, pavementY - 150); ctx.lineTo(lampBaseX + 25, pavementY - 150); // Arm ctx.stroke(); ctx.fillStyle = '#263238'; ctx.beginPath(); ctx.moveTo(lampBaseX + 20, pavementY - 155); ctx.lineTo(lampBaseX + 45, pavementY - 145); ctx.lineTo(lampBaseX + 20, pavementY - 140); ctx.fill(); // Light Glow const gradient = ctx.createRadialGradient( lampBaseX + 35, pavementY - 142, 2, lampBaseX + 35, pavementY - 142, 40 ); gradient.addColorStop(0, 'rgba(255, 235, 59, 0.9)'); gradient.addColorStop(0.4, 'rgba(255, 193, 7, 0.3)'); gradient.addColorStop(1, 'rgba(255, 193, 7, 0)'); ctx.fillStyle = gradient; ctx.beginPath(); ctx.arc(lampBaseX + 35, pavementY - 142, 40, 0, Math.PI * 2); ctx.fill(); }; // Draw a few lamps spaced out drawLamp(width * 0.15); drawLamp(width * 0.5); drawLamp(width * 0.85); } export function drawPollutedClouds(dc: DrawContext): void { const { ctx, width, height, state } = dc; // Darker, heavier clouds for pollution const drawCloud = (x: number, y: number, scale: number) => { ctx.fillStyle = 'rgba(90, 90, 90, 0.85)'; ctx.beginPath(); ctx.ellipse(x, y, 60 * scale, 35 * scale, 0, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.ellipse(x + 50 * scale, y + 10 * scale, 50 * scale, 30 * scale, 0, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.ellipse(x - 40 * scale, y + 5 * scale, 45 * scale, 25 * scale, 0, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.ellipse(x + 20 * scale, y - 15 * scale, 40 * scale, 25 * scale, 0, 0, Math.PI * 2); ctx.fill(); }; const cloudOffsetX = state.mouseX * 0.5 - state.scrollY * 0.03; const cloudOffsetY = state.scrollY * 0.1; // Lower, more oppressive positioning drawCloud(width * 0.2 + cloudOffsetX, height * 0.18 + cloudOffsetY, 1.3); drawCloud(width * 0.6 + cloudOffsetX * 0.7, height * 0.22 + cloudOffsetY * 0.8, 1.1); drawCloud(width * 0.9 + cloudOffsetX * 0.5, height * 0.15 + cloudOffsetY * 0.6, 1.2); } export function drawPollutedCityScene(dc: DrawContext): void { drawPollutedClouds(dc); drawSmog(dc); drawSmoggyBuildings(dc); drawSmokestacks(dc); // Draw traffic BEFORE ground so it looks like it's behind the sidewalk "paved" area drawTrafficJam(dc); drawCityGround(dc); drawStreetLamps(dc); // Foreground, on top of sidewalk }