UI Rework

This commit is contained in:
2026-01-24 21:30:08 +00:00
parent 4183d7f122
commit 1e8b7082a4
37 changed files with 4648 additions and 2754 deletions

View File

@@ -0,0 +1,189 @@
import type { DrawContext } from '../types';
export function drawCityBuildings(dc: DrawContext): void {
const { ctx, width, height, state } = dc;
const parallax = state.mouseX * 0.35;
const drawBuilding = (x: number, bWidth: number, bHeight: number, color: string, hasSpire: boolean = false) => {
const baseY = height * 0.8;
ctx.fillStyle = color;
ctx.fillRect(x + parallax, baseY - bHeight, bWidth, bHeight);
const gradient = ctx.createLinearGradient(x + parallax, 0, x + parallax + bWidth, 0);
gradient.addColorStop(0, 'rgba(255, 255, 255, 0.1)');
gradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.2)');
gradient.addColorStop(1, 'rgba(255, 255, 255, 0.05)');
ctx.fillStyle = gradient;
ctx.fillRect(x + parallax, baseY - bHeight, bWidth, bHeight);
ctx.fillStyle = 'rgba(255, 255, 200, 0.8)';
const windowCols = Math.floor(bWidth / 18);
const windowRows = Math.floor(bHeight / 25);
for (let row = 0; row < windowRows; row++) {
for (let col = 0; col < windowCols; col++) {
if (Math.random() > 0.15) {
ctx.fillStyle = Math.random() > 0.7 ? 'rgba(255, 255, 200, 0.9)' : 'rgba(200, 220, 255, 0.6)';
ctx.fillRect(
x + parallax + col * 18 + 5,
baseY - bHeight + row * 25 + 8,
10, 12
);
}
}
}
if (hasSpire) {
ctx.fillStyle = '#90A4AE';
ctx.beginPath();
ctx.moveTo(x + parallax + bWidth / 2, baseY - bHeight - 40);
ctx.lineTo(x + parallax + bWidth / 2 - 8, baseY - bHeight);
ctx.lineTo(x + parallax + bWidth / 2 + 8, baseY - bHeight);
ctx.closePath();
ctx.fill();
}
};
drawBuilding(width * 0.02, 55, height * 0.28, '#607D8B');
drawBuilding(width * 0.08, 70, height * 0.42, '#78909C', true);
drawBuilding(width * 0.18, 50, height * 0.32, '#546E7A');
drawBuilding(width * 0.25, 85, height * 0.55, '#455A64', true);
drawBuilding(width * 0.38, 60, height * 0.38, '#607D8B');
drawBuilding(width * 0.48, 75, height * 0.48, '#78909C', true);
drawBuilding(width * 0.58, 55, height * 0.35, '#546E7A');
drawBuilding(width * 0.68, 90, height * 0.52, '#455A64', true);
drawBuilding(width * 0.8, 65, height * 0.4, '#607D8B');
drawBuilding(width * 0.9, 50, height * 0.3, '#78909C');
}
export function drawStreet(dc: DrawContext): void {
const { ctx, width, height } = dc;
const time = Date.now() * 0.001;
ctx.fillStyle = '#424242';
ctx.fillRect(0, height * 0.82, width, height * 0.08);
ctx.strokeStyle = '#FFEB3B';
ctx.lineWidth = 3;
ctx.setLineDash([30, 20]);
ctx.beginPath();
ctx.moveTo(0, height * 0.86);
ctx.lineTo(width, height * 0.86);
ctx.stroke();
ctx.setLineDash([]);
const drawCar = (baseX: number, y: number, color: string, direction: number) => {
const x = ((baseX + time * 50 * direction) % (width + 100)) - 50;
ctx.fillStyle = color;
ctx.beginPath();
ctx.roundRect(x, y, 40, 15, 3);
ctx.fill();
ctx.fillStyle = color;
ctx.beginPath();
ctx.roundRect(x + 8, y - 10, 24, 12, 3);
ctx.fill();
ctx.fillStyle = '#81D4FA';
ctx.fillRect(x + 10, y - 8, 9, 8);
ctx.fillRect(x + 21, y - 8, 9, 8);
ctx.fillStyle = '#212121';
ctx.beginPath();
ctx.arc(x + 10, y + 15, 5, 0, Math.PI * 2);
ctx.arc(x + 30, y + 15, 5, 0, Math.PI * 2);
ctx.fill();
};
drawCar(width * 0.1, height * 0.83, '#E53935', 1);
drawCar(width * 0.5, height * 0.83, '#1E88E5', 1);
drawCar(width * 0.3, height * 0.87, '#43A047', -1);
drawCar(width * 0.8, height * 0.87, '#FDD835', -1);
}
export function drawAirplane(dc: DrawContext): void {
const { ctx, width, height, state } = dc;
const time = Date.now() * 0.0003;
const parallax = state.mouseX * 0.1;
const x = (time * width) % (width + 200) - 100;
const y = height * 0.15 + Math.sin(time * 5) * 10;
ctx.save();
ctx.translate(x + parallax, y);
ctx.fillStyle = '#ECEFF1';
ctx.beginPath();
ctx.ellipse(0, 0, 40, 8, 0, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.ellipse(45, 0, 12, 6, 0, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#B0BEC5';
ctx.beginPath();
ctx.moveTo(-10, 0);
ctx.lineTo(-30, -35);
ctx.lineTo(10, -35);
ctx.lineTo(15, 0);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(-10, 0);
ctx.lineTo(-30, 35);
ctx.lineTo(10, 35);
ctx.lineTo(15, 0);
ctx.closePath();
ctx.fill();
ctx.fillStyle = '#E53935';
ctx.beginPath();
ctx.moveTo(-35, 0);
ctx.lineTo(-50, -20);
ctx.lineTo(-30, 0);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(-50, 0);
ctx.lineTo(-150, 3);
ctx.stroke();
ctx.restore();
}
export function drawParks(dc: DrawContext): void {
const { ctx, width, height } = dc;
ctx.fillStyle = '#81C784';
const parks = [
{ x: width * 0.05, y: height * 0.81, rx: 25, ry: 8 },
{ x: width * 0.35, y: height * 0.81, rx: 30, ry: 10 },
{ x: width * 0.75, y: height * 0.81, rx: 35, ry: 9 },
];
parks.forEach(p => {
ctx.beginPath();
ctx.ellipse(p.x, p.y, p.rx, p.ry, 0, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#4CAF50';
ctx.beginPath();
ctx.arc(p.x - 10, p.y - 5, 8, 0, Math.PI * 2);
ctx.arc(p.x + 10, p.y - 3, 6, 0, Math.PI * 2);
ctx.fill();
});
}
export function drawCityScene(dc: DrawContext): void {
drawAirplane(dc);
drawCityBuildings(dc);
drawParks(dc);
drawStreet(dc);
}

View File

@@ -0,0 +1,137 @@
import type { DrawContext } from '../types';
export function drawStumps(dc: DrawContext): void {
const { ctx, width, height, state } = dc;
const parallax = state.mouseX * 0.7 + state.scrollY * 0.5;
const drawStump = (x: number, y: number, scale: number) => {
ctx.fillStyle = '#5D4037';
ctx.fillRect(x - 12 * scale + parallax, y - 8 * scale, 24 * scale, 25 * scale);
ctx.fillStyle = '#8D6E63';
ctx.beginPath();
ctx.ellipse(x + parallax, y - 8 * scale, 15 * scale, 8 * scale, 0, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#5D4037';
ctx.lineWidth = 1;
for (let ring = 1; ring <= 3; ring++) {
ctx.beginPath();
ctx.ellipse(x + parallax, y - 8 * scale, ring * 4 * scale, ring * 2.5 * scale, 0, 0, Math.PI * 2);
ctx.stroke();
}
};
const stumpPositions = [
{ x: width * 0.08, y: height * 0.78, scale: 1.2 },
{ x: width * 0.18, y: height * 0.82, scale: 0.9 },
{ x: width * 0.28, y: height * 0.76, scale: 1.4 },
{ x: width * 0.42, y: height * 0.8, scale: 1.0 },
{ x: width * 0.55, y: height * 0.78, scale: 1.3 },
{ x: width * 0.68, y: height * 0.82, scale: 0.8 },
{ x: width * 0.78, y: height * 0.77, scale: 1.5 },
{ x: width * 0.9, y: height * 0.8, scale: 1.1 },
];
stumpPositions.forEach(s => drawStump(s.x, s.y, s.scale));
}
export function drawFallenLogs(dc: DrawContext): void {
const { ctx, width, height, state } = dc;
const parallax = state.mouseX * 0.6;
const drawLog = (x: number, y: number, length: number, angle: number) => {
ctx.save();
ctx.translate(x + parallax, y);
ctx.rotate(angle);
ctx.fillStyle = '#6D4C41';
ctx.beginPath();
ctx.ellipse(0, 0, length, 12, 0, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#8D6E63';
ctx.beginPath();
ctx.ellipse(length - 5, 0, 8, 12, 0, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#4E342E';
ctx.lineWidth = 2;
for (let i = -length + 20; i < length - 20; i += 25) {
ctx.beginPath();
ctx.moveTo(i, -10);
ctx.lineTo(i + 10, 10);
ctx.stroke();
}
ctx.restore();
};
drawLog(width * 0.2, height * 0.85, 80, 0.1);
drawLog(width * 0.5, height * 0.86, 100, -0.15);
drawLog(width * 0.75, height * 0.84, 70, 0.2);
}
export function drawLoggingTruck(dc: DrawContext): void {
const { ctx, width, height, state } = dc;
const parallax = state.mouseX * 0.4;
const x = width * 0.65 + parallax;
const y = height * 0.75;
ctx.fillStyle = '#FF9800';
ctx.fillRect(x, y, 50, 35);
ctx.fillStyle = '#F57C00';
ctx.fillRect(x, y, 50, 8);
ctx.fillStyle = '#81D4FA';
ctx.fillRect(x + 5, y + 10, 40, 15);
ctx.fillStyle = '#616161';
ctx.fillRect(x + 55, y + 5, 90, 30);
ctx.fillStyle = '#8D6E63';
for (let i = 0; i < 3; i++) {
ctx.beginPath();
ctx.ellipse(x + 75 + i * 25, y + 5, 12, 20, Math.PI / 2, 0, Math.PI * 2);
ctx.fill();
}
ctx.fillStyle = '#212121';
ctx.beginPath();
ctx.arc(x + 15, y + 38, 12, 0, Math.PI * 2);
ctx.arc(x + 80, y + 38, 12, 0, Math.PI * 2);
ctx.arc(x + 120, y + 38, 12, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#757575';
ctx.beginPath();
ctx.arc(x + 15, y + 38, 6, 0, Math.PI * 2);
ctx.arc(x + 80, y + 38, 6, 0, Math.PI * 2);
ctx.arc(x + 120, y + 38, 6, 0, Math.PI * 2);
ctx.fill();
}
export function drawDirtPatches(dc: DrawContext): void {
const { ctx, width, height } = dc;
ctx.fillStyle = '#4E342E';
const patches = [
{ x: width * 0.15, y: height * 0.83, rx: 40, ry: 15 },
{ x: width * 0.45, y: height * 0.85, rx: 60, ry: 20 },
{ x: width * 0.7, y: height * 0.82, rx: 50, ry: 18 },
];
patches.forEach(p => {
ctx.beginPath();
ctx.ellipse(p.x, p.y, p.rx, p.ry, 0, 0, Math.PI * 2);
ctx.fill();
});
}
export function drawDeforestationScene(dc: DrawContext): void {
drawDirtPatches(dc);
drawStumps(dc);
drawFallenLogs(dc);
drawLoggingTruck(dc);
}

View File

@@ -0,0 +1,145 @@
import type { DrawContext } from '../types';
import { getSceneColors } from '../colors';
const FLOWER_COLORS = ['#E91E63', '#FF5722', '#FFEB3B', '#9C27B0', '#FF4081'];
export function drawEcoTrees(dc: DrawContext): void {
const { ctx, width, height, state } = dc;
const colors = getSceneColors(state);
const parallax = state.mouseX * 0.8 + state.scrollY * 0.5;
const drawTree = (x: number, y: number, scale: number) => {
ctx.fillStyle = '#5D4037';
ctx.fillRect(x - 8 * scale + parallax, y, 16 * scale, 40 * scale);
ctx.fillStyle = colors.treeDark;
ctx.beginPath();
ctx.moveTo(x + parallax, y - 80 * scale);
ctx.lineTo(x - 35 * scale + parallax, y);
ctx.lineTo(x + 35 * scale + parallax, y);
ctx.closePath();
ctx.fill();
ctx.fillStyle = colors.treeLight;
ctx.beginPath();
ctx.moveTo(x + parallax, y - 100 * scale);
ctx.lineTo(x - 25 * scale + parallax, y - 30 * scale);
ctx.lineTo(x + 25 * scale + parallax, y - 30 * scale);
ctx.closePath();
ctx.fill();
};
const treePositions = [
{ x: width * 0.1, y: height * 0.75, scale: 1.3 },
{ x: width * 0.2, y: height * 0.78, scale: 1.0 },
{ x: width * 0.35, y: height * 0.73, scale: 1.5 },
{ x: width * 0.65, y: height * 0.76, scale: 1.2 },
{ x: width * 0.8, y: height * 0.74, scale: 1.4 },
{ x: width * 0.92, y: height * 0.77, scale: 1.1 },
];
treePositions.forEach((t) => drawTree(t.x, t.y, t.scale));
}
export function drawFlowers(dc: DrawContext): void {
const { ctx, width, height, state } = dc;
const parallax = state.mouseX * 1.0 + state.scrollY * 0.6;
const time = Date.now() * 0.001;
const drawFlower = (x: number, y: number, color: string, size: number) => {
const sway = Math.sin(time + x * 0.01) * 3;
ctx.strokeStyle = '#2E7D32';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(x + parallax, y + 15);
ctx.quadraticCurveTo(x + parallax + sway, y + 7, x + parallax, y);
ctx.stroke();
ctx.fillStyle = color;
for (let i = 0; i < 5; i++) {
ctx.beginPath();
const angle = (i * Math.PI * 2) / 5;
ctx.ellipse(
x + Math.cos(angle) * size * 0.5 + parallax,
y + Math.sin(angle) * size * 0.5,
size * 0.4, size * 0.25, angle, 0, Math.PI * 2
);
ctx.fill();
}
ctx.fillStyle = '#FFEB3B';
ctx.beginPath();
ctx.arc(x + parallax, y, size * 0.3, 0, Math.PI * 2);
ctx.fill();
};
for (let i = 0; i < 25; i++) {
const x = (i * width) / 25 + (i % 3) * 15;
const y = height * 0.82 + Math.sin(i * 1.5) * 12;
const color = FLOWER_COLORS[i % FLOWER_COLORS.length];
drawFlower(x, y, color, 6 + (i % 4) * 2);
}
}
export function drawBirds(dc: DrawContext): void {
const { ctx, width, height, state } = dc;
ctx.strokeStyle = '#37474F';
ctx.lineWidth = 2;
ctx.lineCap = 'round';
const parallax = state.mouseX * 0.15 - state.scrollY * 0.02;
const time = Date.now() * 0.002;
const drawBird = (x: number, y: number, size: number, phase: number) => {
const wingOffset = Math.sin(time + phase) * 5;
ctx.beginPath();
ctx.moveTo(x + parallax - 10 * size, y + wingOffset);
ctx.quadraticCurveTo(x + parallax, y - 5 * size, x + parallax + 10 * size, y + wingOffset);
ctx.stroke();
};
drawBird(width * 0.3, height * 0.25, 0.8, 0);
drawBird(width * 0.35, height * 0.28, 0.6, 1);
drawBird(width * 0.45, height * 0.22, 0.7, 2);
drawBird(width * 0.7, height * 0.18, 0.9, 0.5);
drawBird(width * 0.75, height * 0.21, 0.5, 1.5);
}
export function drawButterflies(dc: DrawContext): void {
const { ctx, width, height, state } = dc;
const time = Date.now() * 0.003;
const parallax = state.mouseX * 0.3;
const drawButterfly = (x: number, y: number, color: string, phase: number) => {
const wingFlap = Math.sin(time * 5 + phase) * 0.3 + 0.7;
const floatY = Math.sin(time + phase) * 10;
const floatX = Math.cos(time * 0.5 + phase) * 15;
ctx.save();
ctx.translate(x + parallax + floatX, y + floatY);
ctx.fillStyle = color;
ctx.beginPath();
ctx.ellipse(-8 * wingFlap, 0, 8, 12, -0.3, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.ellipse(8 * wingFlap, 0, 8, 12, 0.3, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#1a1a1a';
ctx.fillRect(-1, -8, 2, 16);
ctx.restore();
};
drawButterfly(width * 0.25, height * 0.6, '#E91E63', 0);
drawButterfly(width * 0.55, height * 0.55, '#FFEB3B', 1.5);
drawButterfly(width * 0.75, height * 0.62, '#9C27B0', 3);
}
export function drawEcoScene(dc: DrawContext): void {
drawEcoTrees(dc);
drawFlowers(dc);
drawBirds(dc);
drawButterflies(dc);
}

View File

@@ -0,0 +1,168 @@
import type { DrawContext } from '../types';
import { getSceneColors } from '../colors';
export function drawForestTrees(dc: DrawContext): void {
const { ctx, width, height, state } = dc;
const colors = getSceneColors(state);
const parallax = state.mouseX * 0.8 + state.scrollY * 0.5;
const drawPineTree = (x: number, y: number, scale: number, dark: boolean) => {
ctx.fillStyle = dark ? '#3E2723' : '#5D4037';
ctx.fillRect(x - 6 * scale + parallax, y, 12 * scale, 35 * scale);
const foliageColor = dark ? colors.treeDark : colors.treeLight;
ctx.fillStyle = foliageColor;
for (let layer = 0; layer < 4; layer++) {
const layerY = y + 5 * scale - layer * 22 * scale;
const layerWidth = (35 - layer * 5) * scale;
ctx.beginPath();
ctx.moveTo(x + parallax, layerY - 25 * scale);
ctx.lineTo(x - layerWidth + parallax, layerY);
ctx.lineTo(x + layerWidth + parallax, layerY);
ctx.closePath();
ctx.fill();
}
};
const treePositions = [
{ x: width * 0.05, y: height * 0.68, scale: 0.7, dark: true },
{ x: width * 0.15, y: height * 0.66, scale: 0.8, dark: true },
{ x: width * 0.28, y: height * 0.67, scale: 0.6, dark: true },
{ x: width * 0.42, y: height * 0.65, scale: 0.75, dark: true },
{ x: width * 0.58, y: height * 0.68, scale: 0.65, dark: true },
{ x: width * 0.72, y: height * 0.66, scale: 0.7, dark: true },
{ x: width * 0.88, y: height * 0.67, scale: 0.8, dark: true },
{ x: width * 0.95, y: height * 0.68, scale: 0.6, dark: true },
{ x: width * 0.08, y: height * 0.76, scale: 1.4, dark: false },
{ x: width * 0.22, y: height * 0.78, scale: 1.2, dark: false },
{ x: width * 0.38, y: height * 0.74, scale: 1.6, dark: false },
{ x: width * 0.52, y: height * 0.77, scale: 1.3, dark: false },
{ x: width * 0.68, y: height * 0.75, scale: 1.5, dark: false },
{ x: width * 0.82, y: height * 0.78, scale: 1.1, dark: false },
{ x: width * 0.94, y: height * 0.76, scale: 1.4, dark: false },
];
treePositions.forEach((t) => drawPineTree(t.x, t.y, t.scale, t.dark));
}
export function drawMushrooms(dc: DrawContext): void {
const { ctx, width, height, state } = dc;
const parallax = state.mouseX * 0.9 + state.scrollY * 0.6;
const drawMushroom = (x: number, y: number, scale: number, isRed: boolean) => {
ctx.fillStyle = '#F5F5DC';
ctx.beginPath();
ctx.ellipse(x + parallax, y + 8 * scale, 6 * scale, 10 * scale, 0, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = isRed ? '#D32F2F' : '#8D6E63';
ctx.beginPath();
ctx.ellipse(x + parallax, y - 2 * scale, 12 * scale, 8 * scale, 0, Math.PI, Math.PI * 2);
ctx.fill();
if (isRed) {
ctx.fillStyle = '#FFFFFF';
ctx.beginPath();
ctx.arc(x + parallax - 4, y - 4 * scale, 2 * scale, 0, Math.PI * 2);
ctx.arc(x + parallax + 5, y - 3 * scale, 1.5 * scale, 0, Math.PI * 2);
ctx.fill();
}
};
drawMushroom(width * 0.15, height * 0.84, 1.0, true);
drawMushroom(width * 0.35, height * 0.86, 0.8, false);
drawMushroom(width * 0.52, height * 0.83, 1.2, true);
drawMushroom(width * 0.78, height * 0.85, 0.9, false);
drawMushroom(width * 0.88, height * 0.84, 1.1, true);
}
export function drawDeer(dc: DrawContext): void {
const { ctx, width, height, state } = dc;
const time = Date.now() * 0.001;
const parallax = state.mouseX * 0.5;
const x = width * 0.6 + parallax;
const y = height * 0.72;
const headBob = Math.sin(time * 2) * 3;
ctx.fillStyle = '#8D6E63';
ctx.beginPath();
ctx.ellipse(x, y, 35, 25, 0, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#6D4C41';
ctx.fillRect(x - 20, y + 15, 8, 30);
ctx.fillRect(x - 5, y + 18, 8, 28);
ctx.fillRect(x + 10, y + 15, 8, 30);
ctx.fillRect(x + 25, y + 18, 8, 28);
ctx.fillStyle = '#8D6E63';
ctx.beginPath();
ctx.ellipse(x + 40, y - 15 + headBob, 12, 18, 0.3, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.ellipse(x + 55, y - 25 + headBob, 10, 12, 0.2, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#5D4037';
ctx.lineWidth = 3;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(x + 50, y - 35 + headBob);
ctx.lineTo(x + 45, y - 50 + headBob);
ctx.lineTo(x + 40, y - 45 + headBob);
ctx.moveTo(x + 45, y - 50 + headBob);
ctx.lineTo(x + 50, y - 55 + headBob);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x + 60, y - 35 + headBob);
ctx.lineTo(x + 65, y - 50 + headBob);
ctx.lineTo(x + 70, y - 45 + headBob);
ctx.moveTo(x + 65, y - 50 + headBob);
ctx.lineTo(x + 60, y - 55 + headBob);
ctx.stroke();
ctx.fillStyle = '#1a1a1a';
ctx.beginPath();
ctx.arc(x + 60, y - 25 + headBob, 3, 0, Math.PI * 2);
ctx.fill();
}
export function drawLightRays(dc: DrawContext): void {
const { ctx, width, height, state } = dc;
const time = Date.now() * 0.0005;
ctx.save();
ctx.globalAlpha = 0.15 + Math.sin(time) * 0.05;
const gradient = ctx.createLinearGradient(width * 0.7, 0, width * 0.5, height * 0.7);
gradient.addColorStop(0, 'rgba(255, 255, 200, 0.8)');
gradient.addColorStop(1, 'rgba(255, 255, 200, 0)');
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.moveTo(width * 0.75, 0);
ctx.lineTo(width * 0.85, 0);
ctx.lineTo(width * 0.55, height * 0.7);
ctx.lineTo(width * 0.45, height * 0.7);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(width * 0.55, 0);
ctx.lineTo(width * 0.62, 0);
ctx.lineTo(width * 0.35, height * 0.65);
ctx.lineTo(width * 0.28, height * 0.65);
ctx.closePath();
ctx.fill();
ctx.restore();
}
export function drawForestScene(dc: DrawContext): void {
drawLightRays(dc);
drawForestTrees(dc);
drawMushrooms(dc);
drawDeer(dc);
}

View File

@@ -0,0 +1,130 @@
import type { DrawContext } from '../types';
export function drawFactories(dc: DrawContext): void {
const { ctx, width, height, state } = dc;
const parallax = state.mouseX * 0.4 + state.scrollY * 0.3;
const drawFactory = (x: number, baseY: number, factoryWidth: number, factoryHeight: number) => {
ctx.fillStyle = '#424242';
ctx.fillRect(x + parallax, baseY - factoryHeight, factoryWidth, factoryHeight);
ctx.fillStyle = '#2E2E2E';
ctx.fillRect(x + parallax, baseY - factoryHeight, factoryWidth * 0.1, factoryHeight);
ctx.fillStyle = 'rgba(255, 200, 100, 0.6)';
const windowRows = 3;
const windowCols = 4;
const windowW = factoryWidth * 0.12;
const windowH = factoryHeight * 0.08;
const spacingX = (factoryWidth - windowCols * windowW) / (windowCols + 1);
const spacingY = (factoryHeight * 0.6 - windowRows * windowH) / (windowRows + 1);
for (let row = 0; row < windowRows; row++) {
for (let col = 0; col < windowCols; col++) {
const wx = x + parallax + spacingX * (col + 1) + windowW * col;
const wy = baseY - factoryHeight + spacingY * (row + 1) + windowH * row + factoryHeight * 0.3;
ctx.fillRect(wx, wy, windowW, windowH);
}
}
const chimneyWidth = factoryWidth * 0.15;
const chimneyHeight = factoryHeight * 0.6;
ctx.fillStyle = '#263238';
ctx.fillRect(x + parallax + factoryWidth * 0.7, baseY - factoryHeight - chimneyHeight, chimneyWidth, chimneyHeight);
ctx.fillStyle = '#D32F2F';
ctx.fillRect(x + parallax + factoryWidth * 0.7, baseY - factoryHeight - chimneyHeight, chimneyWidth, 10);
ctx.fillRect(x + parallax + factoryWidth * 0.7, baseY - factoryHeight - chimneyHeight * 0.5, chimneyWidth, 10);
drawSmoke(ctx, x + parallax + factoryWidth * 0.7 + chimneyWidth / 2, baseY - factoryHeight - chimneyHeight);
};
drawFactory(width * 0.08, height * 0.8, width * 0.18, height * 0.28);
drawFactory(width * 0.35, height * 0.8, width * 0.22, height * 0.38);
drawFactory(width * 0.7, height * 0.8, width * 0.15, height * 0.3);
}
function drawSmoke(ctx: CanvasRenderingContext2D, x: number, baseY: number): void {
const time = Date.now() * 0.001;
ctx.fillStyle = 'rgba(100, 100, 100, 0.5)';
for (let i = 0; i < 6; i++) {
const smokeY = baseY - i * 22 - Math.sin(time + i) * 8;
const smokeRadius = 12 + i * 10 + Math.sin(time * 2 + i) * 4;
const offsetX = Math.sin(time + i * 0.5) * 20;
const alpha = 0.5 - i * 0.07;
ctx.globalAlpha = alpha;
ctx.beginPath();
ctx.arc(x + offsetX, smokeY, smokeRadius, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
}
export function drawPowerLines(dc: DrawContext): void {
const { ctx, width, height, state } = dc;
const parallax = state.mouseX * 0.3;
ctx.fillStyle = '#37474F';
const polePositions = [width * 0.25, width * 0.6, width * 0.95];
polePositions.forEach(px => {
ctx.fillRect(px + parallax - 4, height * 0.5, 8, height * 0.35);
ctx.fillRect(px + parallax - 25, height * 0.52, 50, 6);
});
ctx.strokeStyle = '#1a1a1a';
ctx.lineWidth = 2;
for (let wire = 0; wire < 3; wire++) {
ctx.beginPath();
const yOffset = wire * 8;
ctx.moveTo(polePositions[0] + parallax - 20, height * 0.52 + yOffset);
const midX = (polePositions[0] + polePositions[1]) / 2 + parallax;
ctx.quadraticCurveTo(midX, height * 0.58 + yOffset, polePositions[1] + parallax + 20, height * 0.52 + yOffset);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(polePositions[1] + parallax - 20, height * 0.52 + yOffset);
const midX2 = (polePositions[1] + polePositions[2]) / 2 + parallax;
ctx.quadraticCurveTo(midX2, height * 0.58 + yOffset, polePositions[2] + parallax + 20, height * 0.52 + yOffset);
ctx.stroke();
}
}
export function drawDeadTrees(dc: DrawContext): void {
const { ctx, width, height, state } = dc;
const parallax = state.mouseX * 0.6;
const drawDeadTree = (x: number, y: number, scale: number) => {
ctx.strokeStyle = '#4E342E';
ctx.lineWidth = 8 * scale;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(x + parallax, y + 40 * scale);
ctx.lineTo(x + parallax, y - 30 * scale);
ctx.stroke();
ctx.lineWidth = 4 * scale;
ctx.beginPath();
ctx.moveTo(x + parallax, y - 10 * scale);
ctx.lineTo(x + parallax - 25 * scale, y - 40 * scale);
ctx.moveTo(x + parallax, y - 20 * scale);
ctx.lineTo(x + parallax + 30 * scale, y - 45 * scale);
ctx.moveTo(x + parallax, y - 30 * scale);
ctx.lineTo(x + parallax - 15 * scale, y - 55 * scale);
ctx.stroke();
};
drawDeadTree(width * 0.02, height * 0.78, 1.0);
drawDeadTree(width * 0.88, height * 0.76, 1.2);
}
export function drawIndustrialScene(dc: DrawContext): void {
drawPowerLines(dc);
drawFactories(dc);
drawDeadTrees(dc);
}

View File

@@ -0,0 +1,197 @@
import type { DrawContext } from '../types';
import { getSceneColors } from '../colors';
export function drawOceanWaves(dc: DrawContext): void {
const { ctx, width, height, state } = dc;
const colors = getSceneColors(state);
const time = Date.now() * 0.001;
for (let layer = 0; layer < 3; layer++) {
const alpha = 0.3 + layer * 0.25;
const yBase = height * (0.55 + layer * 0.12);
const waveHeight = 15 - layer * 3;
const speed = 40 + layer * 20;
ctx.fillStyle = layer === 2 ? colors.water : `rgba(0, 150, 200, ${alpha})`;
ctx.beginPath();
ctx.moveTo(-100, height);
for (let x = -100; x <= width + 100; x += 10) {
const y = yBase + Math.sin((x + time * speed) * 0.02) * waveHeight;
ctx.lineTo(x, y);
}
ctx.lineTo(width + 100, height);
ctx.closePath();
ctx.fill();
}
ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
for (let i = 0; i < 8; i++) {
const x = (width / 8) * i + Math.sin(time + i * 2) * 30;
const y = height * 0.58 + Math.sin(time * 1.5 + i) * 8;
ctx.beginPath();
ctx.ellipse(x, y, 20 + i * 3, 5, 0, 0, Math.PI * 2);
ctx.fill();
}
}
export function drawBoats(dc: DrawContext): void {
const { ctx, width, height, state } = dc;
const time = Date.now() * 0.001;
const parallax = state.mouseX * 0.3;
const drawSailboat = (x: number, baseY: number, scale: number, phase: number) => {
const bob = Math.sin(time * 2 + phase) * 5;
const y = baseY + bob;
ctx.fillStyle = '#5D4037';
ctx.beginPath();
ctx.moveTo(x + parallax - 30 * scale, y);
ctx.lineTo(x + parallax + 30 * scale, y);
ctx.lineTo(x + parallax + 20 * scale, y + 15 * scale);
ctx.lineTo(x + parallax - 20 * scale, y + 15 * scale);
ctx.closePath();
ctx.fill();
ctx.fillStyle = '#8D6E63';
ctx.fillRect(x + parallax - 2 * scale, y - 60 * scale, 4 * scale, 60 * scale);
ctx.fillStyle = '#FFFFFF';
ctx.beginPath();
ctx.moveTo(x + parallax, y - 55 * scale);
ctx.lineTo(x + parallax + 25 * scale, y - 10 * scale);
ctx.lineTo(x + parallax, y - 10 * scale);
ctx.closePath();
ctx.fill();
ctx.fillStyle = '#E91E63';
ctx.beginPath();
ctx.moveTo(x + parallax, y - 60 * scale);
ctx.lineTo(x + parallax + 12 * scale, y - 55 * scale);
ctx.lineTo(x + parallax, y - 50 * scale);
ctx.closePath();
ctx.fill();
};
drawSailboat(width * 0.2, height * 0.58, 1.0, 0);
drawSailboat(width * 0.7, height * 0.62, 1.3, 1.5);
drawSailboat(width * 0.9, height * 0.56, 0.8, 3);
}
export function drawDolphins(dc: DrawContext): void {
const { ctx, width, height } = dc;
const time = Date.now() * 0.001;
const drawDolphin = (baseX: number, baseY: number, scale: number, phase: number) => {
const cycleLength = 3;
const cycleProgress = ((time + phase) % cycleLength) / cycleLength;
let jumpProgress = 0;
if (cycleProgress > 0.2 && cycleProgress < 0.8) {
jumpProgress = (cycleProgress - 0.2) / 0.6;
} else {
return;
}
const jumpHeight = 80 * scale * Math.sin(jumpProgress * Math.PI);
const x = baseX + (jumpProgress - 0.5) * 150 * scale;
const y = baseY - jumpHeight;
const rotation = Math.cos(jumpProgress * Math.PI) * -0.8;
ctx.save();
ctx.translate(x, y);
ctx.rotate(rotation);
ctx.fillStyle = '#546E7A';
ctx.beginPath();
ctx.ellipse(0, 0, 30 * scale, 12 * scale, 0, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.moveTo(-5 * scale, -10 * scale);
ctx.lineTo(5 * scale, -25 * scale);
ctx.lineTo(12 * scale, -8 * scale);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(-28 * scale, 0);
ctx.lineTo(-45 * scale, -12 * scale);
ctx.lineTo(-35 * scale, 0);
ctx.lineTo(-45 * scale, 12 * scale);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.ellipse(35 * scale, 0, 12 * scale, 6 * scale, 0, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#1a1a1a';
ctx.beginPath();
ctx.arc(28 * scale, -3 * scale, 2 * scale, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
};
drawDolphin(width * 0.25, height * 0.75, 1.0, 0);
drawDolphin(width * 0.5, height * 0.78, 0.8, 1);
}
export function drawSeagulls(dc: DrawContext): void {
const { ctx, width, height, state } = dc;
const time = Date.now() * 0.003;
const parallax = state.mouseX * 0.2;
const drawSeagull = (x: number, y: number, scale: number, phase: number) => {
const wingFlap = Math.sin(time * 3 + phase) * 0.4;
ctx.save();
ctx.translate(x + parallax, y);
ctx.fillStyle = '#FFFFFF';
ctx.beginPath();
ctx.ellipse(0, 0, 10 * scale, 5 * scale, 0, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#FFFFFF';
ctx.lineWidth = 3 * scale;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(-12 * scale, 0);
ctx.quadraticCurveTo(-18 * scale, -15 * scale * (1 + wingFlap), -25 * scale, -5 * scale);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(12 * scale, 0);
ctx.quadraticCurveTo(18 * scale, -15 * scale * (1 + wingFlap), 25 * scale, -5 * scale);
ctx.stroke();
ctx.fillStyle = '#FFFFFF';
ctx.beginPath();
ctx.arc(12 * scale, -2 * scale, 4 * scale, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#FF9800';
ctx.beginPath();
ctx.moveTo(16 * scale, -2 * scale);
ctx.lineTo(22 * scale, 0);
ctx.lineTo(16 * scale, 2 * scale);
ctx.closePath();
ctx.fill();
ctx.restore();
};
drawSeagull(width * 0.15, height * 0.2, 1.0, 0);
drawSeagull(width * 0.35, height * 0.15, 0.8, 1);
drawSeagull(width * 0.55, height * 0.22, 1.2, 2);
drawSeagull(width * 0.8, height * 0.18, 0.9, 3);
}
export function drawOceanScene(dc: DrawContext): void {
drawSeagulls(dc);
drawBoats(dc);
drawDolphins(dc);
}

View File

@@ -0,0 +1,162 @@
import type { DrawContext } from '../types';
export function drawOilRigs(dc: DrawContext): void {
const { ctx, width, height, state } = dc;
const parallax = state.mouseX * 0.3;
const drawRig = (x: number, baseY: number, scale: number) => {
const time = Date.now() * 0.005;
ctx.fillStyle = '#37474F';
ctx.fillRect(x - 50 * scale + parallax, baseY, 10 * scale, 80 * scale);
ctx.fillRect(x + 40 * scale + parallax, baseY, 10 * scale, 80 * scale);
ctx.strokeStyle = '#455A64';
ctx.lineWidth = 4 * scale;
ctx.beginPath();
ctx.moveTo(x - 45 * scale + parallax, baseY + 20 * scale);
ctx.lineTo(x + 45 * scale + parallax, baseY + 60 * scale);
ctx.moveTo(x + 45 * scale + parallax, baseY + 20 * scale);
ctx.lineTo(x - 45 * scale + parallax, baseY + 60 * scale);
ctx.stroke();
ctx.fillStyle = '#546E7A';
ctx.fillRect(x - 70 * scale + parallax, baseY - 25 * scale, 140 * scale, 25 * scale);
ctx.fillStyle = '#455A64';
ctx.fillRect(x - 40 * scale + parallax, baseY - 60 * scale, 40 * scale, 35 * scale);
ctx.fillStyle = 'rgba(255, 200, 100, 0.7)';
ctx.fillRect(x - 35 * scale + parallax, baseY - 50 * scale, 12 * scale, 10 * scale);
ctx.fillRect(x - 18 * scale + parallax, baseY - 50 * scale, 12 * scale, 10 * scale);
ctx.fillStyle = '#37474F';
ctx.beginPath();
ctx.moveTo(x + 20 * scale + parallax, baseY - 25 * scale);
ctx.lineTo(x + 10 * scale + parallax, baseY - 120 * scale);
ctx.lineTo(x + 50 * scale + parallax, baseY - 120 * scale);
ctx.lineTo(x + 40 * scale + parallax, baseY - 25 * scale);
ctx.closePath();
ctx.fill();
ctx.fillStyle = '#FF5722';
ctx.fillRect(x + 20 * scale + parallax, baseY - 100 * scale, 70 * scale, 6 * scale);
const flameX = x + 90 * scale + parallax;
const flameY = baseY - 100 * scale;
ctx.fillStyle = '#263238';
ctx.fillRect(x + 85 * scale + parallax, baseY - 25 * scale, 10 * scale, -75 * scale);
ctx.fillStyle = '#FF6F00';
ctx.beginPath();
ctx.moveTo(flameX - 5 * scale, flameY);
ctx.quadraticCurveTo(flameX + Math.sin(time) * 8, flameY - 35 * scale, flameX + 5 * scale, flameY);
ctx.fill();
ctx.fillStyle = '#FFEB3B';
ctx.beginPath();
ctx.moveTo(flameX - 3 * scale, flameY);
ctx.quadraticCurveTo(flameX + Math.sin(time * 1.5) * 5, flameY - 25 * scale, flameX + 3 * scale, flameY);
ctx.fill();
};
drawRig(width * 0.25, height * 0.6, 1.0);
drawRig(width * 0.75, height * 0.55, 1.3);
}
export function drawOilTanker(dc: DrawContext): void {
const { ctx, width, height, state } = dc;
const time = Date.now() * 0.001;
const parallax = state.mouseX * 0.4;
const x = width * 0.5 + parallax;
const y = height * 0.72 + Math.sin(time) * 3;
ctx.fillStyle = '#1C313A';
ctx.beginPath();
ctx.moveTo(x - 100, y);
ctx.lineTo(x + 100, y);
ctx.lineTo(x + 80, y + 25);
ctx.lineTo(x - 80, y + 25);
ctx.closePath();
ctx.fill();
ctx.fillStyle = '#B71C1C';
ctx.beginPath();
ctx.moveTo(x - 90, y + 15);
ctx.lineTo(x + 90, y + 15);
ctx.lineTo(x + 80, y + 25);
ctx.lineTo(x - 80, y + 25);
ctx.closePath();
ctx.fill();
ctx.fillStyle = '#37474F';
ctx.fillRect(x - 95, y - 8, 190, 8);
ctx.fillStyle = '#455A64';
for (let i = 0; i < 5; i++) {
ctx.beginPath();
ctx.ellipse(x - 70 + i * 35, y - 15, 15, 20, 0, Math.PI, 0);
ctx.fill();
}
ctx.fillStyle = '#263238';
ctx.fillRect(x + 60, y - 35, 30, 30);
ctx.fillStyle = 'rgba(255, 200, 100, 0.6)';
ctx.fillRect(x + 65, y - 30, 8, 8);
ctx.fillRect(x + 78, y - 30, 8, 8);
ctx.fillStyle = '#37474F';
ctx.fillRect(x + 70, y - 50, 10, 20);
ctx.fillStyle = 'rgba(60, 60, 60, 0.4)';
for (let i = 0; i < 3; i++) {
ctx.beginPath();
ctx.arc(x + 75 + Math.sin(time + i) * 8, y - 55 - i * 15, 8 + i * 5, 0, Math.PI * 2);
ctx.fill();
}
}
export function drawOilSlicks(dc: DrawContext): void {
const { ctx, width, height } = dc;
const time = Date.now() * 0.0005;
ctx.fillStyle = '#1C313A';
ctx.fillRect(0, height * 0.75, width, height * 0.25);
const slicks = [
{ x: width * 0.15, y: height * 0.82, rx: 80, ry: 25 },
{ x: width * 0.45, y: height * 0.85, rx: 120, ry: 30 },
{ x: width * 0.75, y: height * 0.8, rx: 90, ry: 28 },
];
slicks.forEach((slick, i) => {
ctx.fillStyle = 'rgba(20, 20, 20, 0.7)';
ctx.beginPath();
ctx.ellipse(slick.x, slick.y, slick.rx, slick.ry, 0, 0, Math.PI * 2);
ctx.fill();
const gradient = ctx.createRadialGradient(
slick.x - slick.rx * 0.3, slick.y,
0,
slick.x, slick.y,
slick.rx
);
gradient.addColorStop(0, 'rgba(128, 0, 128, 0.3)');
gradient.addColorStop(0.3, 'rgba(0, 100, 200, 0.2)');
gradient.addColorStop(0.6, 'rgba(0, 200, 100, 0.15)');
gradient.addColorStop(1, 'rgba(200, 200, 0, 0.1)');
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.ellipse(slick.x + Math.sin(time + i) * 5, slick.y, slick.rx * 0.9, slick.ry * 0.9, 0, 0, Math.PI * 2);
ctx.fill();
});
}
export function drawOilRigScene(dc: DrawContext): void {
drawOilSlicks(dc);
drawOilRigs(dc);
drawOilTanker(dc);
}

View File

@@ -0,0 +1,176 @@
import type { DrawContext } from '../types';
export function drawSmoggyBuildings(dc: DrawContext): void {
const { ctx, width, height, state } = dc;
const parallax = state.mouseX * 0.35;
const drawBuilding = (x: number, bWidth: number, bHeight: number) => {
const baseY = height * 0.8;
ctx.fillStyle = '#37474F';
ctx.fillRect(x + parallax, baseY - bHeight, bWidth, bHeight);
ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
ctx.fillRect(x + parallax, baseY - bHeight * 0.8, bWidth * 0.5, bHeight * 0.3);
ctx.fillRect(x + parallax + bWidth * 0.6, baseY - bHeight * 0.5, bWidth * 0.3, bHeight * 0.2);
const windowCols = Math.floor(bWidth / 18);
const windowRows = Math.floor(bHeight / 25);
for (let row = 0; row < windowRows; row++) {
for (let col = 0; col < windowCols; col++) {
const isBroken = Math.random() > 0.85;
const isLit = Math.random() > 0.5;
if (!isBroken) {
ctx.fillStyle = isLit ? 'rgba(255, 180, 100, 0.5)' : 'rgba(50, 50, 50, 0.8)';
ctx.fillRect(
x + parallax + col * 18 + 5,
baseY - bHeight + row * 25 + 8,
10, 12
);
}
}
}
};
drawBuilding(width * 0.02, 60, height * 0.35);
drawBuilding(width * 0.1, 75, height * 0.48);
drawBuilding(width * 0.22, 55, height * 0.38);
drawBuilding(width * 0.32, 90, height * 0.55);
drawBuilding(width * 0.48, 65, height * 0.42);
drawBuilding(width * 0.58, 80, height * 0.52);
drawBuilding(width * 0.72, 55, height * 0.36);
drawBuilding(width * 0.82, 70, height * 0.45);
}
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';
ctx.fillRect(x + parallax - 12, baseY - stackHeight, 24, stackHeight);
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;
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) => {
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();
};
const carColors = ['#616161', '#424242', '#757575', '#546E7A', '#455A64'];
for (let i = 0; i < 15; i++) {
const lane = i % 2;
const x = i * 55 + 20;
const y = height * 0.83 + lane * 35;
drawCar(x, y, carColors[i % carColors.length]);
}
ctx.fillStyle = 'rgba(255, 0, 0, 0.4)';
for (let i = 0; i < 15; i++) {
const x = i * 55 + 48;
const y = height * 0.835 + (i % 2) * 35 + 7;
ctx.beginPath();
ctx.arc(x, y, 3, 0, Math.PI * 2);
ctx.fill();
}
}
export function drawDebris(dc: DrawContext): void {
const { ctx, width, height } = dc;
ctx.fillStyle = '#5D4037';
for (let i = 0; i < 12; i++) {
const x = Math.random() * width;
const y = height * 0.81 + Math.random() * 8;
const size = 3 + Math.random() * 5;
ctx.beginPath();
ctx.rect(x, y, size, size * 0.7);
ctx.fill();
}
}
export function drawPollutedCityScene(dc: DrawContext): void {
drawSmog(dc);
drawSmoggyBuildings(dc);
drawSmokestacks(dc);
drawDebris(dc);
drawTrafficJam(dc);
}