467 lines
21 KiB
JavaScript
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);
|