Initial commit
This commit is contained in:
@@ -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);
|
||||
Reference in New Issue
Block a user