Initial commit

This commit is contained in:
Allen
2026-04-30 02:14:25 +00:00
commit 7090e6f01d
243 changed files with 115122 additions and 0 deletions
@@ -0,0 +1,595 @@
// sports.js — Sports venue background themes
// Variants: stadium, raceTrack, court, field, arena, pool, gym, skatepark
(function(engine) {
if (!engine) return;
var D = engine.Draw, H = engine.helpers;
engine.registerTheme('sports', {
stadium: {
meta: { id: 'sports_stadium', mood: 'exciting', colors: ['#1a3a1a', '#2a5a2a', '#88cc44', '#ffffff'], tags: ['stadium', 'football', 'soccer', 'crowd'], description: 'Large outdoor stadium with green pitch, bright floodlights, and packed stands', compatibleMusicMoods: ['exciting', 'energetic', 'upbeat'], recommendedFor: ['sports', 'action', 'trivia'] },
colors: ['#1a3a1a', '#2a5a2a', '#88cc44', '#ffffff'],
renderBase: function(ctx, w, h, c, Draw, t) {
// Night sky
Draw.fillGradient(ctx, w, h, ['#0a0a1e', '#1a1a2e'], 90);
// Pitch
Draw.rect(ctx, w * 0.08, h * 0.4, w * 0.84, h * 0.45, c[1]);
// Mowed stripes on pitch
for (var s = 0; s < 8; s++) {
var sx = w * 0.08 + s * w * 0.105;
Draw.rect(ctx, sx, h * 0.4, w * 0.105, h * 0.45, s % 2 === 0 ? c[1] : H.lighten(c[1], 0.08));
}
},
renderMid: function(ctx, w, h, c, Draw, t) {
var rng = H.seededRandom(1001);
// Field markings
ctx.strokeStyle = H.rgba(c[3], 0.6); ctx.lineWidth = 2;
ctx.strokeRect(w * 0.1, h * 0.42, w * 0.8, h * 0.41);
// Center line
ctx.beginPath(); ctx.moveTo(w * 0.5, h * 0.42); ctx.lineTo(w * 0.5, h * 0.83); ctx.stroke();
// Center circle
ctx.beginPath(); ctx.arc(w * 0.5, h * 0.625, h * 0.08, 0, Math.PI * 2); ctx.stroke();
Draw.circle(ctx, w * 0.5, h * 0.625, 3, H.rgba(c[3], 0.6));
// Goal boxes
ctx.strokeRect(w * 0.1, h * 0.52, w * 0.08, h * 0.21);
ctx.strokeRect(w * 0.82, h * 0.52, w * 0.08, h * 0.21);
// Stands
for (var row = 0; row < 5; row++) {
var ry = h * 0.08 + row * h * 0.06;
Draw.rect(ctx, w * 0.02, ry, w * 0.96, h * 0.05, H.rgba('#444466', 0.6 - row * 0.08));
// Crowd dots
for (var c2 = 0; c2 < 40; c2++) {
var cx = w * 0.04 + rng() * w * 0.92;
var crowdColor = H.pick(['#cc3333', '#3333cc', '#ffffff', '#ffcc00', '#33cc33', '#ff6600']);
var bob = Math.sin(t * 2 + c2 * 0.3 + row) * 1;
Draw.circle(ctx, cx, ry + h * 0.025 + bob, 2, H.rgba(crowdColor, 0.5));
}
}
// Bottom stands
for (var row2 = 0; row2 < 3; row2++) {
var ry2 = h * 0.87 + row2 * h * 0.04;
Draw.rect(ctx, w * 0.02, ry2, w * 0.96, h * 0.035, H.rgba('#444466', 0.5 - row2 * 0.1));
for (var c3 = 0; c3 < 30; c3++) {
var cx2 = w * 0.04 + rng() * w * 0.92;
Draw.circle(ctx, cx2, ry2 + h * 0.017, 2, H.rgba(H.pick(['#cc3333', '#3333cc', '#ffffff', '#ffcc00']), 0.4));
}
}
// Floodlights
for (var fl = 0; fl < 4; fl++) {
var fx = w * 0.05 + fl * w * 0.3;
Draw.rect(ctx, fx, h * 0.0, 4, h * 0.12, '#666');
Draw.rect(ctx, fx - 8, 0, 20, 8, '#888');
var lightPulse = 0.15 + Math.sin(t * 0.3 + fl * 0.5) * 0.03;
Draw.glow(ctx, fx + 2, h * 0.02, 50, H.rgba('#FFFFFF', lightPulse));
// Light cones on pitch
ctx.fillStyle = H.rgba('#FFFFFF', 0.02 + Math.sin(t * 0.2 + fl) * 0.005);
ctx.beginPath(); ctx.moveTo(fx, h * 0.08); ctx.lineTo(fx - w * 0.15, h * 0.85);
ctx.lineTo(fx + w * 0.15, h * 0.85); ctx.closePath(); ctx.fill();
}
},
particles: { type: 'sparkles', count: 12, color: '#FFFFFF' }
},
raceTrack: {
meta: { id: 'sports_racetrack', mood: 'fast', colors: ['#333333', '#555555', '#cc0000', '#ffffff'], tags: ['racing', 'speed', 'track', 'cars'], description: 'Asphalt race track with lane markings, kerbing, and grandstand', compatibleMusicMoods: ['energetic', 'fast', 'intense'], recommendedFor: ['racing', 'action', 'sports'] },
colors: ['#333333', '#555555', '#cc0000', '#ffffff'],
renderBase: function(ctx, w, h, c, Draw, t) {
// Sky
Draw.fillGradient(ctx, w, h, ['#4488bb', '#88bbdd'], 90);
// Green surroundings
Draw.rect(ctx, 0, h * 0.3, w, h * 0.7, '#3a6a2a');
},
renderMid: function(ctx, w, h, c, Draw, t) {
var rng = H.seededRandom(1101);
// Track surface (oval perspective)
ctx.fillStyle = c[0];
ctx.beginPath();
ctx.moveTo(w * 0.05, h * 0.85); ctx.lineTo(w * 0.2, h * 0.4);
ctx.lineTo(w * 0.8, h * 0.4); ctx.lineTo(w * 0.95, h * 0.85);
ctx.closePath(); ctx.fill();
// Inner grass
ctx.fillStyle = '#4a7a3a';
ctx.beginPath();
ctx.moveTo(w * 0.2, h * 0.82); ctx.lineTo(w * 0.3, h * 0.48);
ctx.lineTo(w * 0.7, h * 0.48); ctx.lineTo(w * 0.8, h * 0.82);
ctx.closePath(); ctx.fill();
// Lane markings
ctx.strokeStyle = H.rgba(c[3], 0.6); ctx.lineWidth = 2; ctx.setLineDash([15, 15]);
for (var l = 1; l < 4; l++) {
var t1 = l / 4;
ctx.beginPath();
ctx.moveTo(H.lerp(w * 0.05, w * 0.2, t1), H.lerp(h * 0.85, h * 0.82, t1));
ctx.lineTo(H.lerp(w * 0.2, w * 0.3, t1), H.lerp(h * 0.4, h * 0.48, t1));
ctx.lineTo(H.lerp(w * 0.8, w * 0.7, t1), H.lerp(h * 0.4, h * 0.48, t1));
ctx.lineTo(H.lerp(w * 0.95, w * 0.8, t1), H.lerp(h * 0.85, h * 0.82, t1));
ctx.stroke();
}
ctx.setLineDash([]);
// Kerbing (red-white stripes)
for (var k = 0; k < 20; k++) {
var kx1 = H.lerp(w * 0.2, w * 0.8, k / 20);
var kx2 = H.lerp(w * 0.2, w * 0.8, (k + 1) / 20);
Draw.rect(ctx, kx1, h * 0.39, kx2 - kx1, h * 0.02, k % 2 === 0 ? c[2] : c[3]);
}
// Start/finish line
for (var sq = 0; sq < 10; sq++) {
for (var sr = 0; sr < 3; sr++) {
var checker = (sq + sr) % 2 === 0;
Draw.rect(ctx, w * 0.48 + sq * 4, h * 0.65 + sr * 4, 4, 4, checker ? '#000' : '#fff');
}
}
// Grandstand
Draw.rect(ctx, w * 0.3, h * 0.25, w * 0.4, h * 0.12, '#556677');
for (var gr = 0; gr < 4; gr++) {
var gy = h * 0.26 + gr * h * 0.025;
for (var gs = 0; gs < 25; gs++) {
var gx = w * 0.32 + rng() * w * 0.36;
Draw.circle(ctx, gx, gy + h * 0.012, 1.5, H.rgba(H.pick(['#ff3333', '#3355cc', '#ffcc00', '#ffffff']), 0.5));
}
}
// Clouds
Draw.clouds(ctx, w, h, h * 0.08, 4, '#FFFFFF', 1102);
// Tire marks with animation
var skidOffset = (t * 40) % w;
ctx.strokeStyle = H.rgba('#222', 0.15); ctx.lineWidth = 3;
ctx.beginPath(); ctx.moveTo(w * 0.35, h * 0.75);
ctx.quadraticCurveTo(w * 0.45, h * 0.7, w * 0.55, h * 0.72); ctx.stroke();
},
particles: { type: 'dust', count: 15, color: '#AAAAAA' }
},
court: {
meta: { id: 'sports_court', mood: 'competitive', colors: ['#c47a34', '#e8944a', '#ffffff', '#2255aa'], tags: ['basketball', 'court', 'indoor', 'wood'], description: 'Indoor basketball court with polished hardwood, court lines, and arena lighting', compatibleMusicMoods: ['energetic', 'upbeat', 'competitive'], recommendedFor: ['sports', 'action', 'trivia'] },
colors: ['#c47a34', '#e8944a', '#ffffff', '#2255aa'],
renderBase: function(ctx, w, h, c, Draw, t) {
// Arena dark upper
Draw.fillGradient(ctx, w, h, ['#1a1a2a', '#2a2a3a'], 90);
// Hardwood floor
var floorY = h * 0.35;
Draw.rect(ctx, w * 0.03, floorY, w * 0.94, h * 0.55, c[0]);
// Wood grain lines
var rng = H.seededRandom(1201);
for (var g = 0; g < 20; g++) {
var gx = w * 0.03 + g * (w * 0.94 / 20);
ctx.strokeStyle = H.rgba(H.darken(c[0], 0.15), 0.3); ctx.lineWidth = 0.5;
ctx.beginPath(); ctx.moveTo(gx, floorY); ctx.lineTo(gx, floorY + h * 0.55); ctx.stroke();
}
},
renderMid: function(ctx, w, h, c, Draw, t) {
var rng = H.seededRandom(1202);
var floorY = h * 0.35;
// Court lines
ctx.strokeStyle = c[2]; ctx.lineWidth = 2;
ctx.strokeRect(w * 0.08, floorY + h * 0.03, w * 0.84, h * 0.49);
// Center line
ctx.beginPath(); ctx.moveTo(w * 0.5, floorY + h * 0.03); ctx.lineTo(w * 0.5, floorY + h * 0.52); ctx.stroke();
// Center circle
ctx.beginPath(); ctx.arc(w * 0.5, floorY + h * 0.275, h * 0.08, 0, Math.PI * 2); ctx.stroke();
// Three-point arcs
ctx.beginPath(); ctx.arc(w * 0.1, floorY + h * 0.275, h * 0.2, -0.9, 0.9); ctx.stroke();
ctx.beginPath(); ctx.arc(w * 0.9, floorY + h * 0.275, h * 0.2, Math.PI - 0.9, Math.PI + 0.9); ctx.stroke();
// Free throw lanes (paint)
ctx.fillStyle = H.rgba(c[3], 0.15);
ctx.fillRect(w * 0.08, floorY + h * 0.14, w * 0.12, h * 0.27);
ctx.fillRect(w * 0.8, floorY + h * 0.14, w * 0.12, h * 0.27);
ctx.strokeRect(w * 0.08, floorY + h * 0.14, w * 0.12, h * 0.27);
ctx.strokeRect(w * 0.8, floorY + h * 0.14, w * 0.12, h * 0.27);
// Backboards and hoops
for (var side = 0; side < 2; side++) {
var hx = side === 0 ? w * 0.09 : w * 0.91;
Draw.rect(ctx, hx - 8, floorY + h * 0.2, 16, h * 0.06, '#FFFFFF');
ctx.strokeStyle = '#FF4400'; ctx.lineWidth = 2;
ctx.beginPath(); ctx.arc(hx, floorY + h * 0.275, 8, 0, Math.PI * 2); ctx.stroke();
}
// Scoreboard
Draw.rect(ctx, w * 0.35, h * 0.04, w * 0.3, h * 0.08, '#111');
var scoreGlow = 0.7 + Math.sin(t * 0.5) * 0.15;
ctx.fillStyle = H.rgba('#ff3333', scoreGlow);
ctx.font = 'bold 14px monospace';
ctx.fillText('HOME', w * 0.38, h * 0.08);
ctx.fillText('AWAY', w * 0.55, h * 0.08);
ctx.fillStyle = H.rgba('#00ff44', scoreGlow);
ctx.fillText('88', w * 0.42, h * 0.1);
ctx.fillText('85', w * 0.58, h * 0.1);
// Arena lights
for (var lt = 0; lt < 6; lt++) {
var lx = w * 0.1 + lt * w * 0.16;
Draw.rect(ctx, lx, h * 0.13, w * 0.06, 4, '#ddd');
var intensity = 0.06 + Math.sin(t * 0.2 + lt * 0.4) * 0.015;
Draw.glow(ctx, lx + w * 0.03, h * 0.14, 40, H.rgba('#FFFFFF', intensity));
}
// Crowd in upper stands
for (var cr = 0; cr < 3; cr++) {
var cry = h * 0.15 + cr * h * 0.06;
for (var ci = 0; ci < 25; ci++) {
var ccx = w * 0.06 + rng() * w * 0.88;
Draw.circle(ctx, ccx, cry, 1.5, H.rgba(H.pick(['#cc3333', '#3355cc', '#ffffff', '#ffaa00']), 0.35));
}
}
// Floor reflection shimmer
var shimmer = 0.03 + Math.sin(t * 0.4) * 0.01;
ctx.fillStyle = H.rgba('#FFFFFF', shimmer);
ctx.fillRect(w * 0.03, floorY, w * 0.94, h * 0.55);
},
particles: { type: 'sparkles', count: 8, color: '#FFFFFF' }
},
field: {
meta: { id: 'sports_field', mood: 'fresh', colors: ['#2a7a2a', '#3a9a3a', '#ffffff', '#88cc44'], tags: ['soccer', 'field', 'outdoor', 'grass'], description: 'Open soccer field under blue sky with goals, corner flags, and freshly cut grass', compatibleMusicMoods: ['upbeat', 'cheerful', 'energetic'], recommendedFor: ['sports', 'action', 'pet'] },
colors: ['#2a7a2a', '#3a9a3a', '#ffffff', '#88cc44'],
renderBase: function(ctx, w, h, c, Draw, t) {
// Sky
Draw.fillGradient(ctx, w, h, ['#3388cc', '#88ccee', '#aaddee'], 90);
// Field with mowed stripes
for (var s = 0; s < 10; s++) {
var sx = s * (w / 10);
Draw.rect(ctx, sx, h * 0.45, w / 10, h * 0.55, s % 2 === 0 ? c[0] : c[1]);
}
},
renderMid: function(ctx, w, h, c, Draw, t) {
var rng = H.seededRandom(1301);
// Field markings
ctx.strokeStyle = c[2]; ctx.lineWidth = 2;
// Boundary
ctx.strokeRect(w * 0.05, h * 0.47, w * 0.9, h * 0.48);
// Halfway line
ctx.beginPath(); ctx.moveTo(w * 0.5, h * 0.47); ctx.lineTo(w * 0.5, h * 0.95); ctx.stroke();
// Center circle
ctx.beginPath(); ctx.arc(w * 0.5, h * 0.71, h * 0.08, 0, Math.PI * 2); ctx.stroke();
Draw.circle(ctx, w * 0.5, h * 0.71, 3, c[2]);
// Penalty areas
ctx.strokeRect(w * 0.05, h * 0.57, w * 0.1, h * 0.28);
ctx.strokeRect(w * 0.85, h * 0.57, w * 0.1, h * 0.28);
// Goal areas
ctx.strokeRect(w * 0.05, h * 0.63, w * 0.04, h * 0.16);
ctx.strokeRect(w * 0.91, h * 0.63, w * 0.04, h * 0.16);
// Goals (nets)
for (var g = 0; g < 2; g++) {
var gx = g === 0 ? w * 0.02 : w * 0.95;
var gw = w * 0.03;
Draw.rect(ctx, gx, h * 0.63, gw, h * 0.16, H.rgba('#FFFFFF', 0.15));
ctx.strokeStyle = '#FFFFFF'; ctx.lineWidth = 2;
ctx.strokeRect(gx, h * 0.63, gw, h * 0.16);
// Net mesh
ctx.strokeStyle = H.rgba('#FFFFFF', 0.2); ctx.lineWidth = 0.5;
for (var n = 0; n < 5; n++) {
ctx.beginPath(); ctx.moveTo(gx, h * 0.63 + n * h * 0.032); ctx.lineTo(gx + gw, h * 0.63 + n * h * 0.032); ctx.stroke();
ctx.beginPath(); ctx.moveTo(gx + n * gw / 5, h * 0.63); ctx.lineTo(gx + n * gw / 5, h * 0.79); ctx.stroke();
}
}
// Corner flags
for (var cf = 0; cf < 4; cf++) {
var cfx = cf < 2 ? w * 0.05 : w * 0.95;
var cfy = cf % 2 === 0 ? h * 0.47 : h * 0.95;
Draw.rect(ctx, cfx - 1, cfy - 16, 2, 16, '#888');
var flagWave = Math.sin(t * 3 + cf) * 3;
ctx.fillStyle = '#ff4444';
ctx.beginPath(); ctx.moveTo(cfx, cfy - 16); ctx.lineTo(cfx + 8 + flagWave, cfy - 13);
ctx.lineTo(cfx, cfy - 10); ctx.closePath(); ctx.fill();
}
// Clouds
Draw.clouds(ctx, w, h, h * 0.08, 5, '#FFFFFF', 1302);
// Sun
var sunPulse = 0.8 + Math.sin(t * 0.2) * 0.1;
Draw.circle(ctx, w * 0.85, h * 0.1, 20, H.rgba('#ffdd44', sunPulse));
Draw.glow(ctx, w * 0.85, h * 0.1, 50, '#ffdd44');
// Trees along side
Draw.trees(ctx, w, h, h * 0.45, 8, '#5a3a1a', '#2a6a2a', 1303);
},
particles: { type: 'leaves', count: 8, color: '#88CC44' }
},
arena: {
meta: { id: 'sports_arena', mood: 'intense', colors: ['#1a1a2e', '#2a2a44', '#ff4444', '#ffaa00'], tags: ['arena', 'boxing', 'mma', 'spotlight'], description: 'Dark fighting arena with dramatic spotlights, ropes, and smoky atmosphere', compatibleMusicMoods: ['intense', 'dramatic', 'energetic'], recommendedFor: ['action', 'sports', 'rpg'] },
colors: ['#1a1a2e', '#2a2a44', '#ff4444', '#ffaa00'],
renderBase: function(ctx, w, h, c, Draw, t) {
Draw.fillGradient(ctx, w, h, [c[0], c[1]], 90);
// Dark floor
Draw.rect(ctx, 0, h * 0.7, w, h * 0.3, H.darken(c[0], 0.3));
},
renderMid: function(ctx, w, h, c, Draw, t) {
var rng = H.seededRandom(1401);
// Ring platform
ctx.fillStyle = '#333344';
ctx.beginPath();
ctx.moveTo(w * 0.15, h * 0.7); ctx.lineTo(w * 0.25, h * 0.5);
ctx.lineTo(w * 0.75, h * 0.5); ctx.lineTo(w * 0.85, h * 0.7);
ctx.closePath(); ctx.fill();
// Ring canvas (mat)
ctx.fillStyle = '#e8e0d0';
ctx.beginPath();
ctx.moveTo(w * 0.22, h * 0.52); ctx.lineTo(w * 0.27, h * 0.48);
ctx.lineTo(w * 0.73, h * 0.48); ctx.lineTo(w * 0.78, h * 0.52);
ctx.closePath(); ctx.fill();
// Ring mat surface
ctx.fillStyle = H.rgba(c[2], 0.08);
ctx.beginPath();
ctx.moveTo(w * 0.22, h * 0.52); ctx.lineTo(w * 0.27, h * 0.48);
ctx.lineTo(w * 0.73, h * 0.48); ctx.lineTo(w * 0.78, h * 0.52);
ctx.closePath(); ctx.fill();
// Corner posts
var posts = [[w * 0.27, h * 0.48], [w * 0.73, h * 0.48], [w * 0.22, h * 0.52], [w * 0.78, h * 0.52]];
for (var p = 0; p < 4; p++) {
Draw.rect(ctx, posts[p][0] - 3, posts[p][1] - h * 0.12, 6, h * 0.12, '#888');
// Turnbuckle
Draw.circle(ctx, posts[p][0], posts[p][1] - h * 0.12, 4, p < 2 ? c[2] : c[3]);
}
// Ropes
for (var r = 0; r < 3; r++) {
var ropeY = h * 0.48 - (r + 1) * h * 0.03;
var sag = Math.sin(t * 0.5 + r) * 2;
ctx.strokeStyle = c[2]; ctx.lineWidth = 2;
// Top ropes
ctx.beginPath(); ctx.moveTo(w * 0.27, ropeY);
ctx.quadraticCurveTo(w * 0.5, ropeY + sag + 3, w * 0.73, ropeY); ctx.stroke();
// Side ropes (perspective)
var sideRopeY = ropeY + h * 0.02;
ctx.beginPath(); ctx.moveTo(w * 0.27, ropeY);
ctx.quadraticCurveTo(w * 0.245, sideRopeY + sag, w * 0.22, sideRopeY + h * 0.04); ctx.stroke();
ctx.beginPath(); ctx.moveTo(w * 0.73, ropeY);
ctx.quadraticCurveTo(w * 0.755, sideRopeY + sag, w * 0.78, sideRopeY + h * 0.04); ctx.stroke();
}
// Spotlights
for (var sp = 0; sp < 3; sp++) {
var spx = w * 0.3 + sp * w * 0.2;
var pulse = 0.12 + Math.sin(t * 0.6 + sp * 1.5) * 0.04;
Draw.glow(ctx, spx, 0, 30, H.rgba(c[3], 0.4));
ctx.fillStyle = H.rgba(c[3], pulse);
ctx.beginPath(); ctx.moveTo(spx - 15, 0); ctx.lineTo(spx + 15, 0);
ctx.lineTo(spx + w * 0.1, h * 0.7); ctx.lineTo(spx - w * 0.1, h * 0.7);
ctx.closePath(); ctx.fill();
}
// Crowd silhouettes
for (var cr = 0; cr < 50; cr++) {
var ccx = rng() * w;
var ccy = h * 0.7 + rng() * h * 0.15;
var bob = Math.sin(t * 1.5 + cr * 0.5) * 1.5;
Draw.circle(ctx, ccx, ccy + bob, 3 + rng() * 2, H.rgba('#333344', 0.7));
}
// Smoke effect
for (var sm = 0; sm < 4; sm++) {
var smx = w * 0.3 + rng() * w * 0.4 + Math.sin(t * 0.2 + sm) * 15;
var smy = h * 0.3 + rng() * h * 0.15;
ctx.fillStyle = H.rgba('#aaaacc', 0.03);
ctx.beginPath(); ctx.ellipse(smx, smy, 60 + rng() * 40, 20, 0, 0, Math.PI * 2); ctx.fill();
}
},
particles: { type: 'dust', count: 20, color: '#CCBBAA' }
},
pool: {
meta: { id: 'sports_pool', mood: 'calm', colors: ['#1a6088', '#2288aa', '#44bbdd', '#88eeff'], tags: ['swimming', 'water', 'pool', 'blue'], description: 'Olympic swimming pool with lane dividers, rippling water, and tiled edges', compatibleMusicMoods: ['calm', 'peaceful', 'ambient'], recommendedFor: ['sports', 'racing', 'puzzle'] },
colors: ['#1a6088', '#2288aa', '#44bbdd', '#88eeff'],
renderBase: function(ctx, w, h, c, Draw, t) {
// Indoor ceiling
Draw.fillGradient(ctx, w, h, ['#e8e4dc', '#d8d4cc'], 90);
// Pool water
var waterStart = h * 0.35;
ctx.fillStyle = Draw.linearGradient(ctx, 0, waterStart, 0, h, [
[0, c[1]], [0.5, c[2]], [1, c[0]]
]);
ctx.fillRect(w * 0.05, waterStart, w * 0.9, h * 0.55);
},
renderMid: function(ctx, w, h, c, Draw, t) {
var rng = H.seededRandom(1501);
var waterStart = h * 0.35;
var poolW = w * 0.9;
var poolX = w * 0.05;
// Lane dividers
var lanes = 8;
for (var l = 1; l < lanes; l++) {
var lx = poolX + l * (poolW / lanes);
ctx.strokeStyle = H.rgba('#FFFFFF', 0.3); ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(lx, waterStart); ctx.lineTo(lx, waterStart + h * 0.55); ctx.stroke();
// Lane rope beads
for (var b = 0; b < 15; b++) {
var by = waterStart + b * (h * 0.55 / 15);
var beadColor = b % 3 === 0 ? c[3] : (b % 3 === 1 ? '#ff4444' : '#ffffff');
Draw.circle(ctx, lx, by, 2, H.rgba(beadColor, 0.5));
}
}
// Water caustics (animated light patterns)
for (var ca = 0; ca < 25; ca++) {
var cx = poolX + rng() * poolW;
var cy = waterStart + rng() * h * 0.5;
var caSize = 15 + rng() * 30;
var caAlpha = 0.03 + Math.sin(t * 1.5 + ca * 0.7) * 0.02;
ctx.fillStyle = H.rgba(c[3], caAlpha);
ctx.beginPath();
ctx.moveTo(cx, cy - caSize * 0.3);
ctx.quadraticCurveTo(cx + caSize * 0.4, cy, cx, cy + caSize * 0.3);
ctx.quadraticCurveTo(cx - caSize * 0.4, cy, cx, cy - caSize * 0.3);
ctx.fill();
}
// Water surface ripples
for (var rp = 0; rp < 12; rp++) {
var ry = waterStart + rp * 2;
ctx.strokeStyle = H.rgba('#FFFFFF', 0.06);
ctx.lineWidth = 0.5;
ctx.beginPath();
for (var rx = poolX; rx < poolX + poolW; rx += 3) {
ctx.lineTo(rx, ry + Math.sin(rx * 0.03 + t * 2 + rp * 0.5) * 2);
}
ctx.stroke();
}
// Pool edge tiles
Draw.rect(ctx, poolX - 5, waterStart - 5, poolW + 10, 8, '#ddd');
Draw.rect(ctx, poolX - 5, waterStart + h * 0.55 - 3, poolW + 10, 8, '#ddd');
// Tile pattern on edge
for (var ti = 0; ti < 30; ti++) {
var tix = poolX + ti * (poolW / 30);
ctx.strokeStyle = H.rgba('#bbb', 0.4); ctx.lineWidth = 0.5;
ctx.beginPath(); ctx.moveTo(tix, waterStart - 5); ctx.lineTo(tix, waterStart + 3); ctx.stroke();
}
// Starting blocks
for (var sb = 0; sb < lanes; sb++) {
var sbx = poolX + sb * (poolW / lanes) + poolW / lanes / 2 - 8;
Draw.rect(ctx, sbx, waterStart - 12, 16, 8, '#888');
Draw.rect(ctx, sbx + 2, waterStart - 16, 12, 5, '#aaa');
}
// Lane numbers on bottom
ctx.font = '12px monospace'; ctx.fillStyle = H.rgba('#000', 0.15);
for (var ln = 0; ln < lanes; ln++) {
var lnx = poolX + ln * (poolW / lanes) + poolW / lanes / 2 - 3;
ctx.fillText(String(ln + 1), lnx, waterStart + h * 0.5);
}
// Ceiling lights reflected in water
for (var cl = 0; cl < 5; cl++) {
var clx = w * 0.15 + cl * w * 0.18;
Draw.rect(ctx, clx, h * 0.05, w * 0.08, 4, '#eee');
var refAlpha = 0.04 + Math.sin(t * 0.4 + cl) * 0.02;
Draw.glow(ctx, clx + w * 0.04, waterStart + h * 0.2, 40, H.rgba(c[3], refAlpha));
}
},
particles: { type: 'sparkles', count: 15, color: '#88EEFF' }
},
gym: {
meta: { id: 'sports_gym', mood: 'competitive', colors: ['#ddc8a0', '#c4a870', '#ffffff', '#cc3333'], tags: ['gymnastics', 'gym', 'mats', 'indoor'], description: 'Gymnastics arena with spring floor, apparatus silhouettes, and competition banners', compatibleMusicMoods: ['upbeat', 'focused', 'energetic'], recommendedFor: ['sports', 'action', 'creative'] },
colors: ['#ddc8a0', '#c4a870', '#ffffff', '#cc3333'],
renderBase: function(ctx, w, h, c, Draw, t) {
// Arena interior
Draw.fillGradient(ctx, w, h, ['#2a2a3a', '#3a3a4a'], 90);
// Spring floor
Draw.rect(ctx, w * 0.1, h * 0.6, w * 0.8, h * 0.3, c[0]);
Draw.rect(ctx, w * 0.1, h * 0.6, w * 0.8, h * 0.3, H.rgba(c[2], 0.05));
},
renderMid: function(ctx, w, h, c, Draw, t) {
var rng = H.seededRandom(1601);
// Floor exercise boundary
ctx.strokeStyle = c[2]; ctx.lineWidth = 2;
ctx.strokeRect(w * 0.12, h * 0.62, w * 0.76, h * 0.26);
// Diagonal line on floor
ctx.strokeStyle = H.rgba(c[3], 0.2); ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(w * 0.12, h * 0.62); ctx.lineTo(w * 0.88, h * 0.88); ctx.stroke();
ctx.beginPath(); ctx.moveTo(w * 0.88, h * 0.62); ctx.lineTo(w * 0.12, h * 0.88); ctx.stroke();
// Balance beam
Draw.rect(ctx, w * 0.03, h * 0.55, w * 0.18, h * 0.015, c[1]);
Draw.rect(ctx, w * 0.06, h * 0.565, 4, h * 0.12, '#666');
Draw.rect(ctx, w * 0.17, h * 0.565, 4, h * 0.12, '#666');
// Vault table
Draw.rect(ctx, w * 0.78, h * 0.54, w * 0.08, h * 0.03, '#888');
Draw.rect(ctx, w * 0.8, h * 0.57, 4, h * 0.1, '#666');
Draw.rect(ctx, w * 0.84, h * 0.57, 4, h * 0.1, '#666');
// Runway mat
Draw.rect(ctx, w * 0.88, h * 0.56, w * 0.1, h * 0.015, '#4488dd');
// Rings (hanging)
for (var ring = 0; ring < 2; ring++) {
var rx = w * 0.4 + ring * w * 0.06;
ctx.strokeStyle = '#888'; ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(rx, 0); ctx.lineTo(rx, h * 0.25); ctx.stroke();
var sway = Math.sin(t * 0.3 + ring * Math.PI) * 3;
ctx.strokeStyle = c[1]; ctx.lineWidth = 3;
ctx.beginPath(); ctx.arc(rx + sway, h * 0.28, 8, 0, Math.PI * 2); ctx.stroke();
}
// Parallel bars
Draw.rect(ctx, w * 0.55, h * 0.45, w * 0.15, 3, '#aa8855');
Draw.rect(ctx, w * 0.55, h * 0.5, w * 0.15, 3, '#aa8855');
Draw.rect(ctx, w * 0.58, h * 0.5, 3, h * 0.17, '#777');
Draw.rect(ctx, w * 0.67, h * 0.5, 3, h * 0.17, '#777');
// Scoreboard
Draw.rect(ctx, w * 0.3, h * 0.05, w * 0.4, h * 0.08, '#111');
ctx.strokeStyle = '#333'; ctx.lineWidth = 2;
ctx.strokeRect(w * 0.3, h * 0.05, w * 0.4, h * 0.08);
var scoreGlow = 0.7 + Math.sin(t * 0.6) * 0.2;
ctx.fillStyle = H.rgba('#00ff66', scoreGlow);
ctx.font = 'bold 12px monospace';
ctx.fillText('FLOOR 9.450', w * 0.35, h * 0.09);
// Competition banners
for (var bn = 0; bn < 5; bn++) {
var bx = w * 0.1 + bn * w * 0.18;
var bannerColor = H.pick([c[3], '#3355cc', '#ffcc00', '#33aa55', '#aa33aa']);
Draw.rect(ctx, bx, h * 0.14, 18, h * 0.08, bannerColor);
// Banner point
ctx.fillStyle = bannerColor;
ctx.beginPath(); ctx.moveTo(bx, h * 0.22); ctx.lineTo(bx + 9, h * 0.24); ctx.lineTo(bx + 18, h * 0.22); ctx.closePath(); ctx.fill();
}
// Arena lights
for (var al = 0; al < 4; al++) {
var alx = w * 0.15 + al * w * 0.22;
var lightAlpha = 0.06 + Math.sin(t * 0.25 + al * 0.6) * 0.015;
Draw.glow(ctx, alx, h * 0.14, 35, H.rgba('#FFFFFF', lightAlpha));
}
},
particles: { type: 'dust', count: 10, color: '#DDCC99' }
},
skatepark: {
meta: { id: 'sports_skatepark', mood: 'cool', colors: ['#555566', '#777788', '#ff6600', '#ffcc00'], tags: ['skate', 'urban', 'concrete', 'graffiti'], description: 'Urban skatepark with concrete ramps, half-pipes, graffiti, and warm sunset light', compatibleMusicMoods: ['cool', 'energetic', 'retro', 'upbeat'], recommendedFor: ['action', 'sports', 'racing', 'creative'] },
colors: ['#555566', '#777788', '#ff6600', '#ffcc00'],
renderBase: function(ctx, w, h, c, Draw, t) {
// Warm sunset sky
Draw.fillGradient(ctx, w, h, ['#dd6633', '#ee9955', '#ffcc88'], 90);
// Concrete ground
Draw.rect(ctx, 0, h * 0.55, w, h * 0.45, c[0]);
// Concrete texture
var rng = H.seededRandom(1700);
for (var sp = 0; sp < 30; sp++) {
Draw.circle(ctx, rng() * w, h * 0.55 + rng() * h * 0.45, 1, H.rgba('#444', 0.15));
}
},
renderMid: function(ctx, w, h, c, Draw, t) {
var rng = H.seededRandom(1701);
// Half-pipe
ctx.fillStyle = c[1];
ctx.beginPath();
ctx.moveTo(w * 0.05, h * 0.55); ctx.quadraticCurveTo(w * 0.05, h * 0.9, w * 0.2, h * 0.9);
ctx.lineTo(w * 0.2, h * 0.55); ctx.closePath(); ctx.fill();
// Coping on half-pipe
Draw.rect(ctx, w * 0.04, h * 0.54, w * 0.17, 3, '#999');
// Quarter pipe on right
ctx.fillStyle = c[1];
ctx.beginPath();
ctx.moveTo(w * 0.75, h * 0.9); ctx.quadraticCurveTo(w * 0.95, h * 0.9, w * 0.95, h * 0.6);
ctx.lineTo(w * 0.95, h * 0.55); ctx.lineTo(w * 0.75, h * 0.55); ctx.closePath(); ctx.fill();
Draw.rect(ctx, w * 0.74, h * 0.54, w * 0.22, 3, '#999');
// Fun box / pyramid
ctx.fillStyle = H.darken(c[0], 0.1);
ctx.beginPath();
ctx.moveTo(w * 0.35, h * 0.85); ctx.lineTo(w * 0.4, h * 0.72);
ctx.lineTo(w * 0.6, h * 0.72); ctx.lineTo(w * 0.65, h * 0.85);
ctx.closePath(); ctx.fill();
// Rail on top
Draw.rect(ctx, w * 0.4, h * 0.715, w * 0.2, 3, '#aaa');
// Grind rail
ctx.strokeStyle = '#bbb'; ctx.lineWidth = 3;
ctx.beginPath(); ctx.moveTo(w * 0.25, h * 0.8); ctx.lineTo(w * 0.4, h * 0.8); ctx.stroke();
Draw.rect(ctx, w * 0.25, h * 0.8, 3, h * 0.1, '#888');
Draw.rect(ctx, w * 0.4, h * 0.8, 3, h * 0.1, '#888');
// Graffiti on walls/ramps
var graffitiColors = ['#ff3366', '#33ccff', '#ffcc00', '#44ff44', '#ff6600', '#cc44ff'];
// Back wall
Draw.rect(ctx, 0, h * 0.2, w, h * 0.35, H.rgba(c[0], 0.5));
// Graffiti blobs
for (var g = 0; g < 8; g++) {
var gx = rng() * w * 0.9 + w * 0.05;
var gy = h * 0.25 + rng() * h * 0.22;
var gc = graffitiColors[Math.floor(rng() * graffitiColors.length)];
ctx.fillStyle = H.rgba(gc, 0.2 + rng() * 0.15);
ctx.beginPath();
ctx.ellipse(gx, gy, 15 + rng() * 25, 8 + rng() * 12, rng() * Math.PI, 0, Math.PI * 2);
ctx.fill();
}
// Street light
Draw.rect(ctx, w * 0.7, h * 0.15, 3, h * 0.4, '#666');
Draw.rect(ctx, w * 0.68, h * 0.14, 20, 6, '#777');
var lampGlow = 0.3 + Math.sin(t * 0.4) * 0.1;
Draw.circle(ctx, w * 0.69, h * 0.17, 5, H.rgba(c[3], lampGlow));
Draw.glow(ctx, w * 0.69, h * 0.17, 40, H.rgba(c[3], lampGlow * 0.5));
// Fence in background
for (var f = 0; f < 15; f++) {
var fx = w * 0.02 + f * w * 0.07;
Draw.rect(ctx, fx, h * 0.35, 2, h * 0.2, H.rgba('#888', 0.3));
}
ctx.strokeStyle = H.rgba('#888', 0.25); ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(0, h * 0.4); ctx.lineTo(w, h * 0.4); ctx.stroke();
ctx.beginPath(); ctx.moveTo(0, h * 0.48); ctx.lineTo(w, h * 0.48); ctx.stroke();
// Warm sun glow
Draw.glow(ctx, w * 0.15, h * 0.08, 60, '#ffaa44');
Draw.circle(ctx, w * 0.15, h * 0.08, 18, H.rgba('#ffcc66', 0.8));
},
particles: { type: 'leaves', count: 10, color: '#CC8833' }
}
});
})(window.BackgroundEngine);