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,505 @@
// fantasy.js — Fantasy theme backgrounds
// Variants: castle, dungeon, enchantedForest, floatingIslands, magicalLibrary, crystalCave, temple, tower
(function(engine) {
if (!engine) return;
var D = engine.Draw, H = engine.helpers;
engine.registerTheme('fantasy', {
castle: {
meta: { id: 'fantasy_castle', mood: 'heroic', colors: ['#1a1040', '#2d1b69', '#8b7355', '#ffd700'], tags: ['castle', 'medieval', 'heroic', 'stone'], description: 'A grand castle silhouette against a twilight sky with golden banners', compatibleMusicMoods: ['epic', 'heroic', 'adventure'], recommendedFor: ['rpg', 'action', 'story'] },
colors: ['#1a1040', '#2d1b69', '#8b7355', '#ffd700'],
renderBase: function(ctx, w, h, c, Draw, t) {
Draw.fillGradient(ctx, w, h, [c[0], c[1], H.lighten(c[1], 0.15)], 90);
Draw.stars(ctx, w, h * 0.5, 80, '#FFFFFF', 101, [0.5, 2]);
var moonGlow = 0.35 + Math.sin(t * 0.3) * 0.1;
Draw.glow(ctx, w * 0.8, h * 0.15, 80, H.rgba('#FFFFCC', moonGlow));
Draw.circle(ctx, w * 0.8, h * 0.15, 25, '#FFFFDD');
},
renderMid: function(ctx, w, h, c, Draw, t) {
var rng = H.seededRandom(200);
Draw.mountains(ctx, w, h, h * 0.6, 6, H.darken(c[2], 0.6), 201);
// Castle wall
Draw.rect(ctx, w * 0.25, h * 0.4, w * 0.5, h * 0.6, H.darken(c[2], 0.4));
// Towers
var towerW = w * 0.08;
Draw.rect(ctx, w * 0.22, h * 0.25, towerW, h * 0.75, H.darken(c[2], 0.45));
Draw.rect(ctx, w * 0.70, h * 0.25, towerW, h * 0.75, H.darken(c[2], 0.45));
// Battlements
for (var i = 0; i < 12; i++) {
var bx = w * 0.25 + i * (w * 0.5 / 12);
if (i % 2 === 0) Draw.rect(ctx, bx, h * 0.38, w * 0.5 / 12, h * 0.04, H.darken(c[2], 0.4));
}
// Tower caps (triangles)
ctx.fillStyle = c[2];
ctx.beginPath(); ctx.moveTo(w * 0.22, h * 0.25); ctx.lineTo(w * 0.26, h * 0.15); ctx.lineTo(w * 0.30, h * 0.25); ctx.fill();
ctx.beginPath(); ctx.moveTo(w * 0.70, h * 0.25); ctx.lineTo(w * 0.74, h * 0.15); ctx.lineTo(w * 0.78, h * 0.25); ctx.fill();
// Gate
Draw.rect(ctx, w * 0.44, h * 0.6, w * 0.12, h * 0.4, '#0a0a15');
ctx.strokeStyle = c[3]; ctx.lineWidth = 2;
ctx.beginPath(); ctx.arc(w * 0.5, h * 0.6, w * 0.06, Math.PI, 0); ctx.stroke();
// Windows with flickering glow
for (var j = 0; j < 6; j++) {
var wx = w * 0.3 + rng() * w * 0.4;
var wy = h * 0.42 + rng() * h * 0.15;
var flicker = 0.4 + Math.sin(t * 2 + j * 1.7) * 0.2;
Draw.glow(ctx, wx, wy, 12, H.rgba(c[3], flicker));
Draw.rect(ctx, wx - 3, wy - 5, 6, 10, H.rgba(c[3], flicker + 0.2));
}
// Banner
var bannerSway = Math.sin(t * 1.5) * 3;
ctx.fillStyle = '#8B0000';
ctx.beginPath(); ctx.moveTo(w * 0.26, h * 0.16); ctx.lineTo(w * 0.26 + bannerSway, h * 0.22); ctx.lineTo(w * 0.26 - 8, h * 0.22); ctx.fill();
},
particles: { type: 'fireflies', count: 12, color: '#FFD700' }
},
dungeon: {
meta: { id: 'fantasy_dungeon', mood: 'dark', colors: ['#0a0a0f', '#1a1520', '#4a3728', '#ff6a00'], tags: ['dungeon', 'dark', 'underground', 'torches'], description: 'Dark stone corridors lit by flickering torches and glowing embers', compatibleMusicMoods: ['dark', 'tense', 'mysterious'], recommendedFor: ['rpg', 'action', 'puzzle'] },
colors: ['#0a0a0f', '#1a1520', '#4a3728', '#ff6a00'],
renderBase: function(ctx, w, h, c, Draw, t) {
Draw.fillGradient(ctx, w, h, [c[0], c[1]], 90);
// Stone texture
var rng = H.seededRandom(300);
for (var i = 0; i < 40; i++) {
var sx = rng() * w, sy = rng() * h;
var sw = 20 + rng() * 60, sh = 15 + rng() * 40;
Draw.rect(ctx, sx, sy, sw, sh, H.rgba(c[2], 0.08 + rng() * 0.06));
}
},
renderMid: function(ctx, w, h, c, Draw, t) {
// Stone pillars
for (var p = 0; p < 3; p++) {
var px = w * 0.2 + p * w * 0.3;
Draw.rect(ctx, px - 15, h * 0.1, 30, h * 0.9, H.darken(c[2], 0.3));
Draw.rect(ctx, px - 20, h * 0.08, 40, 15, H.darken(c[2], 0.2));
Draw.rect(ctx, px - 20, h * 0.92, 40, 15, H.darken(c[2], 0.2));
}
// Chains hanging
var rng = H.seededRandom(301);
for (var ch = 0; ch < 4; ch++) {
var cx = w * 0.15 + rng() * w * 0.7;
var chainLen = h * 0.1 + rng() * h * 0.2;
ctx.strokeStyle = H.rgba('#666655', 0.5);
ctx.lineWidth = 2;
for (var link = 0; link < chainLen; link += 8) {
ctx.beginPath();
ctx.ellipse(cx, link + h * 0.02, 3, 4, 0, 0, Math.PI * 2);
ctx.stroke();
}
}
// Torches with animated glow
for (var ti = 0; ti < 4; ti++) {
var tx = w * 0.1 + ti * w * 0.27;
var ty = h * 0.35;
Draw.rect(ctx, tx - 3, ty, 6, 20, '#5a3a1a');
var flicker = 0.5 + Math.sin(t * 4 + ti * 2.3) * 0.3 + Math.sin(t * 7 + ti) * 0.15;
Draw.glow(ctx, tx, ty - 5, 40 + Math.sin(t * 3 + ti) * 8, H.rgba(c[3], flicker * 0.4));
Draw.circle(ctx, tx, ty - 5, 5, H.rgba(c[3], flicker));
Draw.circle(ctx, tx, ty - 8, 3, H.rgba('#FFCC44', flicker));
}
// Ground cracks
ctx.strokeStyle = H.rgba('#222', 0.3);
ctx.lineWidth = 1;
var crng = H.seededRandom(302);
for (var cr = 0; cr < 8; cr++) {
var crx = crng() * w;
ctx.beginPath(); ctx.moveTo(crx, h * 0.9);
for (var seg = 0; seg < 4; seg++) {
crx += (crng() - 0.5) * 30;
ctx.lineTo(crx, h * 0.9 + seg * 8 + crng() * 10);
}
ctx.stroke();
}
},
particles: { type: 'particles', count: 20, color: '#FF6A00' }
},
enchantedForest: {
meta: { id: 'fantasy_enchantedForest', mood: 'mystical', colors: ['#0a1a0f', '#0d2818', '#1a4a2a', '#44ff88'], tags: ['forest', 'magical', 'nature', 'enchanted'], description: 'A mystical forest with glowing mushrooms and magical light filtering through ancient trees', compatibleMusicMoods: ['mystical', 'calm', 'magical'], recommendedFor: ['rpg', 'story', 'puzzle'] },
colors: ['#0a1a0f', '#0d2818', '#1a4a2a', '#44ff88'],
renderBase: function(ctx, w, h, c, Draw, t) {
Draw.fillGradient(ctx, w, h, [c[0], c[1], H.darken(c[2], 0.3)], 90);
// Filtered light rays
for (var r = 0; r < 5; r++) {
var rx = w * 0.15 + r * w * 0.18;
var rayAlpha = 0.03 + Math.sin(t * 0.5 + r) * 0.015;
ctx.fillStyle = H.rgba(c[3], rayAlpha);
ctx.beginPath();
ctx.moveTo(rx - 5, 0); ctx.lineTo(rx + 30, h);
ctx.lineTo(rx - 30, h); ctx.lineTo(rx + 5, 0);
ctx.fill();
}
},
renderMid: function(ctx, w, h, c, Draw, t) {
// Background trees
Draw.trees(ctx, w, h, h * 0.85, 8, '#1a0d0a', H.darken(c[2], 0.3), 401);
// Large foreground trees
var rng = H.seededRandom(402);
for (var tr = 0; tr < 4; tr++) {
var tx = rng() * w;
var treeH = h * 0.5 + rng() * h * 0.3;
var trunkW = 12 + rng() * 10;
Draw.rect(ctx, tx - trunkW / 2, h - treeH, trunkW, treeH, '#2a1a0f');
// Roots
for (var rt = 0; rt < 3; rt++) {
ctx.fillStyle = '#2a1a0f';
ctx.beginPath();
ctx.moveTo(tx - trunkW / 2 - rt * 8, h);
ctx.quadraticCurveTo(tx - trunkW / 2, h - 15, tx - trunkW / 4, h - treeH * 0.1);
ctx.lineTo(tx - trunkW / 2, h - treeH * 0.1);
ctx.fill();
}
// Canopy
for (var can = 0; can < 3; can++) {
var canR = 30 + rng() * 40;
var canX = tx + (rng() - 0.5) * 30;
var canY = h - treeH - canR * 0.3 + can * 15;
Draw.circle(ctx, canX, canY, canR, H.rgba(c[2], 0.7 + rng() * 0.3));
}
}
// Glowing mushrooms
for (var m = 0; m < 8; m++) {
var mx = rng() * w, my = h * 0.82 + rng() * h * 0.15;
var mSize = 4 + rng() * 8;
var pulse = 0.4 + Math.sin(t * 1.2 + m * 1.1) * 0.2;
Draw.glow(ctx, mx, my, mSize * 3, H.rgba(c[3], pulse * 0.3));
Draw.rect(ctx, mx - 1, my, 2, mSize, '#8B7355');
ctx.fillStyle = H.rgba(c[3], pulse + 0.3);
ctx.beginPath(); ctx.ellipse(mx, my, mSize, mSize * 0.5, 0, Math.PI, 0); ctx.fill();
}
// Ground moss/grass
Draw.hills(ctx, w, h, h * 0.88, 10, H.darken(c[2], 0.2), 403);
},
particles: { type: 'fireflies', count: 25, color: '#44FF88' }
},
floatingIslands: {
meta: { id: 'fantasy_floatingIslands', mood: 'wonder', colors: ['#1a0a3a', '#2d1b69', '#5a8a5a', '#88ccff'], tags: ['sky', 'floating', 'islands', 'clouds'], description: 'Majestic islands suspended in a violet sky with waterfalls cascading into the void', compatibleMusicMoods: ['wonder', 'adventure', 'calm'], recommendedFor: ['rpg', 'puzzle', 'creative'] },
colors: ['#1a0a3a', '#2d1b69', '#5a8a5a', '#88ccff'],
renderBase: function(ctx, w, h, c, Draw, t) {
Draw.fillGradient(ctx, w, h, [H.darken(c[0], 0.3), c[0], c[1], H.lighten(c[1], 0.1)], 90);
Draw.stars(ctx, w, h * 0.4, 60, '#FFFFFF', 501, [0.5, 1.5]);
Draw.clouds(ctx, w, h, h * 0.2, 5, c[3], 502);
Draw.clouds(ctx, w, h, h * 0.7, 4, H.rgba(c[3], 0.5), 503);
},
renderMid: function(ctx, w, h, c, Draw, t) {
var rng = H.seededRandom(504);
// Draw 3 floating islands
var islands = [
{ x: w * 0.2, y: h * 0.35, sz: 1.0 },
{ x: w * 0.65, y: h * 0.25, sz: 0.7 },
{ x: w * 0.45, y: h * 0.55, sz: 0.5 }
];
for (var i = 0; i < islands.length; i++) {
var isl = islands[i];
var bob = Math.sin(t * 0.4 + i * 2) * 5;
var ix = isl.x, iy = isl.y + bob, sz = isl.sz;
// Bottom rocky underside
ctx.fillStyle = H.darken(c[2], 0.5);
ctx.beginPath();
ctx.moveTo(ix - 60 * sz, iy + 10); ctx.quadraticCurveTo(ix - 20 * sz, iy + 80 * sz, ix, iy + 100 * sz);
ctx.quadraticCurveTo(ix + 30 * sz, iy + 70 * sz, ix + 60 * sz, iy + 10);
ctx.fill();
// Top grassy surface
ctx.fillStyle = c[2];
ctx.beginPath(); ctx.ellipse(ix, iy, 65 * sz, 15 * sz, 0, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = H.lighten(c[2], 0.15);
ctx.beginPath(); ctx.ellipse(ix, iy - 3, 60 * sz, 10 * sz, 0, 0, Math.PI * 2); ctx.fill();
// Small trees on top
if (sz > 0.6) {
for (var tr = 0; tr < 3; tr++) {
var ttx = ix - 30 * sz + rng() * 60 * sz;
Draw.rect(ctx, ttx - 2, iy - 20 * sz, 4, 15 * sz, '#3a2a15');
Draw.circle(ctx, ttx, iy - 25 * sz, 8 * sz, H.lighten(c[2], 0.1));
}
}
// Waterfall from main island
if (i === 0) {
var wfAlpha = 0.2 + Math.sin(t * 2) * 0.08;
ctx.fillStyle = H.rgba(c[3], wfAlpha);
ctx.fillRect(ix + 20, iy + 10, 6, h - iy);
for (var sp = 0; sp < 5; sp++) {
var spy = iy + 10 + rng() * (h - iy);
Draw.glow(ctx, ix + 23, spy, 8, H.rgba(c[3], 0.15));
}
}
}
},
particles: { type: 'sparkles', count: 35, color: '#88CCFF' }
},
magicalLibrary: {
meta: { id: 'fantasy_magicalLibrary', mood: 'scholarly', colors: ['#1a0f0a', '#2d1a10', '#6b4423', '#cc88ff'], tags: ['library', 'books', 'magical', 'scholarly'], description: 'Towering bookshelves with floating magical tomes and arcane symbols', compatibleMusicMoods: ['calm', 'mystical', 'scholarly'], recommendedFor: ['puzzle', 'trivia', 'story'] },
colors: ['#1a0f0a', '#2d1a10', '#6b4423', '#cc88ff'],
renderBase: function(ctx, w, h, c, Draw, t) {
Draw.fillGradient(ctx, w, h, [c[0], c[1]], 90);
// Warm ambient light
Draw.glow(ctx, w * 0.5, h * 0.3, w * 0.6, H.rgba('#553311', 0.15));
},
renderMid: function(ctx, w, h, c, Draw, t) {
var rng = H.seededRandom(600);
// Bookshelves
var shelfColors = ['#8B4513', '#6B3410', '#5a2d0e', '#7a3d18'];
for (var s = 0; s < 5; s++) {
var sx = s * w * 0.2 + w * 0.02;
var sw = w * 0.18;
// Shelf frame
Draw.rect(ctx, sx, h * 0.05, sw, h * 0.92, H.darken(c[2], 0.4));
// Shelves and books
for (var row = 0; row < 6; row++) {
var sy = h * 0.1 + row * h * 0.14;
Draw.rect(ctx, sx, sy + h * 0.11, sw, 4, c[2]);
// Books on shelf
var bx = sx + 3;
while (bx < sx + sw - 5) {
var bw = 4 + rng() * 8;
var bh = h * 0.08 + rng() * h * 0.03;
var bookColor = H.pick(['#AA2222', '#2244AA', '#22AA44', '#8844AA', '#AA8822', '#228888']);
Draw.rect(ctx, bx, sy + h * 0.11 - bh, bw, bh, bookColor);
if (rng() > 0.6) {
Draw.rect(ctx, bx + 1, sy + h * 0.11 - bh + 2, bw - 2, 2, H.lighten(bookColor, 0.3));
}
bx += bw + 1;
}
}
}
// Floating books
for (var fb = 0; fb < 4; fb++) {
var fbx = w * 0.15 + rng() * w * 0.7;
var fby = h * 0.2 + rng() * h * 0.4 + Math.sin(t * 0.8 + fb * 1.5) * 10;
var rot = Math.sin(t * 0.3 + fb) * 0.2;
ctx.save();
ctx.translate(fbx, fby); ctx.rotate(rot);
Draw.rect(ctx, -10, -7, 20, 14, H.pick(['#882222', '#224488', '#228844']));
Draw.rect(ctx, -8, -5, 16, 1, '#FFD700');
ctx.restore();
Draw.glow(ctx, fbx, fby, 20, H.rgba(c[3], 0.2 + Math.sin(t + fb) * 0.1));
}
// Arcane symbols floating
var symbols = ['\u2606', '\u2609', '\u263E', '\u2726'];
ctx.font = '16px serif';
for (var sym = 0; sym < 6; sym++) {
var smx = rng() * w, smy = rng() * h;
var symAlpha = 0.15 + Math.sin(t * 0.7 + sym * 1.3) * 0.1;
ctx.fillStyle = H.rgba(c[3], symAlpha);
ctx.fillText(symbols[sym % symbols.length], smx + Math.sin(t * 0.4 + sym) * 5, smy);
}
},
particles: { type: 'sparkles', count: 20, color: '#CC88FF' }
},
crystalCave: {
meta: { id: 'fantasy_crystalCave', mood: 'ethereal', colors: ['#0a0a1a', '#0f1a2d', '#225588', '#44ddff'], tags: ['cave', 'crystals', 'underground', 'glow'], description: 'An underground cavern filled with luminous crystal formations and reflected light', compatibleMusicMoods: ['ethereal', 'mystical', 'calm'], recommendedFor: ['puzzle', 'rpg', 'creative'] },
colors: ['#0a0a1a', '#0f1a2d', '#225588', '#44ddff'],
renderBase: function(ctx, w, h, c, Draw, t) {
Draw.fillGradient(ctx, w, h, [c[0], c[1]], 90);
// Cavern ceiling
var rng = H.seededRandom(700);
ctx.fillStyle = H.darken(c[1], 0.3);
ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(0, h * 0.15);
for (var i = 0; i <= 20; i++) {
var cx = (i / 20) * w;
var cy = h * 0.08 + rng() * h * 0.12;
ctx.lineTo(cx, cy);
}
ctx.lineTo(w, 0); ctx.closePath(); ctx.fill();
},
renderMid: function(ctx, w, h, c, Draw, t) {
var rng = H.seededRandom(701);
// Stalactites
for (var st = 0; st < 12; st++) {
var stx = rng() * w;
var stLen = h * 0.05 + rng() * h * 0.15;
var stW = 3 + rng() * 8;
ctx.fillStyle = H.darken(c[2], 0.4);
ctx.beginPath();
ctx.moveTo(stx - stW, h * 0.08 + rng() * h * 0.07);
ctx.lineTo(stx, h * 0.08 + stLen);
ctx.lineTo(stx + stW, h * 0.08 + rng() * h * 0.07);
ctx.fill();
}
// Crystal clusters
var crystalHues = [c[2], c[3], H.lighten(c[2], 0.2), H.lighten(c[3], 0.2)];
for (var cl = 0; cl < 8; cl++) {
var clx = rng() * w;
var cly = h * 0.6 + rng() * h * 0.35;
var clSize = 15 + rng() * 30;
var pulse = 0.4 + Math.sin(t * 0.8 + cl * 0.9) * 0.2;
Draw.glow(ctx, clx, cly, clSize * 2, H.rgba(crystalHues[cl % crystalHues.length], pulse * 0.25));
// Individual crystal shards
for (var sh = 0; sh < 3 + Math.floor(rng() * 3); sh++) {
var shAngle = -Math.PI / 2 + (rng() - 0.5) * 1.2;
var shLen = clSize * (0.5 + rng() * 0.8);
var shW = 3 + rng() * 5;
var shColor = H.rgba(crystalHues[Math.floor(rng() * crystalHues.length)], pulse + 0.2);
ctx.fillStyle = shColor;
ctx.save();
ctx.translate(clx + (rng() - 0.5) * 10, cly);
ctx.rotate(shAngle);
ctx.beginPath();
ctx.moveTo(-shW / 2, 0); ctx.lineTo(0, -shLen); ctx.lineTo(shW / 2, 0);
ctx.fill();
ctx.restore();
}
}
// Cave floor with stalagmites
Draw.hills(ctx, w, h, h * 0.9, 15, H.darken(c[1], 0.4), 702);
for (var sg = 0; sg < 6; sg++) {
var sgx = rng() * w, sgH = h * 0.05 + rng() * h * 0.1;
ctx.fillStyle = H.darken(c[2], 0.3);
ctx.beginPath();
ctx.moveTo(sgx - 5, h * 0.9); ctx.lineTo(sgx, h * 0.9 - sgH); ctx.lineTo(sgx + 5, h * 0.9);
ctx.fill();
}
// Water reflections on cave floor
var refAlpha = 0.06 + Math.sin(t * 0.5) * 0.03;
Draw.water(ctx, w, h, h * 0.92, H.rgba(c[3], refAlpha), 2, 703);
},
particles: { type: 'sparkles', count: 40, color: '#44DDFF' }
},
temple: {
meta: { id: 'fantasy_temple', mood: 'sacred', colors: ['#1a1025', '#2d1a3a', '#c9b77a', '#ffd700'], tags: ['temple', 'sacred', 'ancient', 'pillars'], description: 'An ancient temple with towering marble pillars and sacred golden light', compatibleMusicMoods: ['sacred', 'epic', 'mystical'], recommendedFor: ['rpg', 'story', 'trivia'] },
colors: ['#1a1025', '#2d1a3a', '#c9b77a', '#ffd700'],
renderBase: function(ctx, w, h, c, Draw, t) {
Draw.fillGradient(ctx, w, h, [c[0], c[1], H.lighten(c[1], 0.08)], 90);
// Sacred light from above
var beamAlpha = 0.05 + Math.sin(t * 0.3) * 0.02;
ctx.fillStyle = H.rgba(c[3], beamAlpha);
ctx.beginPath();
ctx.moveTo(w * 0.4, 0); ctx.lineTo(w * 0.3, h); ctx.lineTo(w * 0.7, h); ctx.lineTo(w * 0.6, 0);
ctx.fill();
},
renderMid: function(ctx, w, h, c, Draw, t) {
// Floor tiles
var tileColor1 = H.darken(c[2], 0.5), tileColor2 = H.darken(c[2], 0.55);
for (var ty = 0; ty < 5; ty++) {
for (var tx = 0; tx < 10; tx++) {
var color = (tx + ty) % 2 === 0 ? tileColor1 : tileColor2;
Draw.rect(ctx, tx * w * 0.1, h * 0.85 + ty * h * 0.03, w * 0.1, h * 0.03, color);
}
}
// Pillars
var pillarColor = H.lighten(c[2], 0.1);
for (var p = 0; p < 6; p++) {
var px = w * 0.08 + p * w * 0.18;
var pw = 18, ph = h * 0.6;
// Base
Draw.rect(ctx, px - pw, h * 0.83, pw * 2, h * 0.04, pillarColor);
// Shaft
Draw.rect(ctx, px - pw * 0.6, h * 0.25, pw * 1.2, ph, pillarColor);
// Fluting effect
for (var f = 0; f < 3; f++) {
Draw.rect(ctx, px - pw * 0.5 + f * pw * 0.4, h * 0.25, 2, ph, H.darken(pillarColor, 0.15));
}
// Capital
Draw.rect(ctx, px - pw, h * 0.22, pw * 2, h * 0.04, pillarColor);
Draw.rect(ctx, px - pw * 1.2, h * 0.21, pw * 2.4, h * 0.02, pillarColor);
}
// Altar
Draw.rect(ctx, w * 0.4, h * 0.65, w * 0.2, h * 0.2, H.darken(c[2], 0.3));
Draw.rect(ctx, w * 0.38, h * 0.63, w * 0.24, h * 0.04, c[2]);
// Sacred flame on altar
var flameSize = 12 + Math.sin(t * 3) * 4;
Draw.glow(ctx, w * 0.5, h * 0.58, flameSize * 3, H.rgba(c[3], 0.3 + Math.sin(t * 2) * 0.1));
Draw.circle(ctx, w * 0.5, h * 0.58, flameSize, H.rgba(c[3], 0.7 + Math.sin(t * 4) * 0.2));
// Runes on floor
var rng = H.seededRandom(800);
ctx.strokeStyle = H.rgba(c[3], 0.1 + Math.sin(t * 0.5) * 0.05);
ctx.lineWidth = 1;
ctx.beginPath();
ctx.arc(w * 0.5, h * 0.9, w * 0.15, 0, Math.PI * 2);
ctx.stroke();
for (var rn = 0; rn < 8; rn++) {
var angle = (rn / 8) * Math.PI * 2;
ctx.beginPath();
ctx.moveTo(w * 0.5, h * 0.9);
ctx.lineTo(w * 0.5 + Math.cos(angle) * w * 0.15, h * 0.9 + Math.sin(angle) * w * 0.15);
ctx.stroke();
}
},
particles: { type: 'dust', count: 25, color: '#FFD700' }
},
tower: {
meta: { id: 'fantasy_tower', mood: 'arcane', colors: ['#0a0a20', '#151540', '#554488', '#aa66ff'], tags: ['tower', 'wizard', 'arcane', 'night'], description: 'A tall wizard tower under a starlit sky with arcane energy crackling at its peak', compatibleMusicMoods: ['mystical', 'dark', 'arcane'], recommendedFor: ['rpg', 'puzzle', 'trivia'] },
colors: ['#0a0a20', '#151540', '#554488', '#aa66ff'],
renderBase: function(ctx, w, h, c, Draw, t) {
Draw.fillGradient(ctx, w, h, [c[0], c[1]], 90);
Draw.stars(ctx, w, h * 0.6, 100, '#FFFFFF', 901, [0.5, 2.5]);
// Nebula glow
Draw.glow(ctx, w * 0.3, h * 0.2, 150, H.rgba(c[2], 0.08));
Draw.glow(ctx, w * 0.7, h * 0.15, 100, H.rgba(c[3], 0.05));
},
renderMid: function(ctx, w, h, c, Draw, t) {
// Rolling hills
Draw.hills(ctx, w, h, h * 0.8, 8, H.darken(c[2], 0.6), 902);
// Tower structure
var towerX = w * 0.5, towerW = w * 0.08;
var towerBot = h * 0.85, towerTop = h * 0.15;
// Main shaft
Draw.rect(ctx, towerX - towerW / 2, towerTop, towerW, towerBot - towerTop, H.darken(c[2], 0.4));
// Stone texture lines
for (var sl = 0; sl < 15; sl++) {
var sly = towerTop + sl * (towerBot - towerTop) / 15;
Draw.rect(ctx, towerX - towerW / 2, sly, towerW, 1, H.rgba('#000', 0.15));
}
// Wider base
ctx.fillStyle = H.darken(c[2], 0.45);
ctx.beginPath();
ctx.moveTo(towerX - towerW * 0.8, towerBot);
ctx.lineTo(towerX - towerW / 2, towerBot - h * 0.1);
ctx.lineTo(towerX + towerW / 2, towerBot - h * 0.1);
ctx.lineTo(towerX + towerW * 0.8, towerBot);
ctx.fill();
// Pointed roof
ctx.fillStyle = H.darken(c[2], 0.2);
ctx.beginPath();
ctx.moveTo(towerX - towerW * 0.7, towerTop);
ctx.lineTo(towerX, towerTop - h * 0.08);
ctx.lineTo(towerX + towerW * 0.7, towerTop);
ctx.fill();
// Windows with glow
var rng = H.seededRandom(903);
for (var wi = 0; wi < 5; wi++) {
var wy = towerTop + h * 0.05 + wi * (towerBot - towerTop - h * 0.1) / 5;
var wGlow = 0.4 + Math.sin(t * 1.2 + wi * 1.5) * 0.2;
Draw.glow(ctx, towerX, wy, 15, H.rgba(c[3], wGlow * 0.3));
Draw.rect(ctx, towerX - 4, wy - 6, 8, 12, H.rgba(c[3], wGlow));
ctx.strokeStyle = H.darken(c[2], 0.3);
ctx.lineWidth = 1;
ctx.beginPath(); ctx.arc(towerX, wy - 6, 4, Math.PI, 0); ctx.stroke();
}
// Arcane energy at top
var arcAngle = t * 1.5;
var arcPulse = 0.4 + Math.sin(t * 2) * 0.2;
Draw.glow(ctx, towerX, towerTop - h * 0.08, 30 + Math.sin(t) * 10, H.rgba(c[3], arcPulse * 0.5));
for (var arc = 0; arc < 5; arc++) {
var aa = arcAngle + (arc / 5) * Math.PI * 2;
var ar = 15 + Math.sin(t * 3 + arc) * 5;
var ax = towerX + Math.cos(aa) * ar;
var ay = towerTop - h * 0.08 + Math.sin(aa) * ar * 0.5;
Draw.circle(ctx, ax, ay, 2, H.rgba(c[3], arcPulse));
}
// Lightning bolt effect (intermittent)
if (Math.sin(t * 0.7) > 0.9) {
ctx.strokeStyle = H.rgba(c[3], 0.6);
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(towerX, towerTop - h * 0.08);
var lx = towerX, ly = towerTop - h * 0.08;
for (var bolt = 0; bolt < 4; bolt++) {
lx += (rng() - 0.5) * 30;
ly -= 15 + rng() * 10;
ctx.lineTo(lx, ly);
}
ctx.stroke();
}
},
particles: { type: 'sparkles', count: 30, color: '#AA66FF' }
}
});
})(window.BackgroundEngine);