Files
2026-04-30 02:14:25 +00:00

268 lines
18 KiB
JavaScript

// underwater.js — Underwater background theme (8 variants)
// Depends on backgroundEngine.js being loaded first
(function(engine) {
if (!engine) return;
var D = engine.Draw, H = engine.helpers;
function drawFish(ctx, x, y, s, col, dir) {
ctx.fillStyle = col;
ctx.beginPath(); ctx.ellipse(x, y, s, s * 0.5, 0, 0, Math.PI * 2); ctx.fill();
ctx.beginPath(); ctx.moveTo(x - dir * s, y); ctx.lineTo(x - dir * s * 1.6, y - s * 0.5); ctx.lineTo(x - dir * s * 1.6, y + s * 0.5); ctx.closePath(); ctx.fill();
ctx.fillStyle = '#FFF'; ctx.beginPath(); ctx.arc(x + dir * s * 0.4, y - s * 0.15, s * 0.18, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = '#000'; ctx.beginPath(); ctx.arc(x + dir * s * 0.45, y - s * 0.15, s * 0.08, 0, Math.PI * 2); ctx.fill();
}
function sunRays(ctx, w, h, n, col, t) {
ctx.save(); ctx.globalCompositeOperation = 'lighter';
for (var i = 0; i < n; i++) {
var rx = (w / n) * i + Math.sin(t * 0.3 + i) * 20, rw = 15 + Math.sin(t * 0.5 + i * 1.3) * 8;
ctx.fillStyle = H.rgba(col, 0.03 + Math.sin(t * 0.4 + i * 0.7) * 0.02);
ctx.beginPath(); ctx.moveTo(rx - rw, 0); ctx.lineTo(rx + rw, 0); ctx.lineTo(rx + rw * 2.5, h); ctx.lineTo(rx - rw * 1.5, h); ctx.closePath(); ctx.fill();
}
ctx.restore();
}
engine.registerTheme('underwater', {
coralReef: {
meta: { id: 'underwater_coralReef', mood: 'cheerful', colors: ['#006994', '#00A8CC', '#FF6F61', '#FFB347'], tags: ['coral', 'reef', 'colorful', 'ocean'], description: 'A vibrant coral reef teeming with colorful marine life', compatibleMusicMoods: ['happy', 'upbeat', 'peaceful'], recommendedFor: ['puzzle', 'pet', 'creative'] },
colors: ['#006994', '#00A8CC', '#FF6F61', '#FFB347'],
renderBase: function(ctx, w, h, c, Draw, t) {
Draw.fillGradient(ctx, w, h, [H.lighten(c[0], 0.15), c[0], H.darken(c[0], 0.3)], 90);
sunRays(ctx, w, h, 6, '#88DDFF', t);
Draw.rect(ctx, 0, h * 0.82, w, h * 0.18, '#C2A366');
Draw.hills(ctx, w, h, h * 0.82, 10, '#B89555', 22);
},
renderMid: function(ctx, w, h, c, Draw, t) {
var rng = H.seededRandom(50);
for (var i = 0; i < 14; i++) {
var cx = rng() * w, cy = h * 0.72 + rng() * h * 0.15, ch = 15 + rng() * 35;
var cc = [c[2], c[3], '#FF8899', '#FF77AA'][Math.floor(rng() * 4)];
for (var b = 0; b < 3 + Math.floor(rng() * 3); b++) {
var a = -Math.PI / 2 + (rng() - 0.5) * 1.2, bL = ch * (0.5 + rng() * 0.5), sw = Math.sin(t * 0.8 + i + b) * 2;
ctx.strokeStyle = cc; ctx.lineWidth = 3 + rng() * 4; ctx.lineCap = 'round';
ctx.beginPath(); ctx.moveTo(cx, cy); ctx.quadraticCurveTo(cx + Math.cos(a) * bL * 0.5 + sw, cy + Math.sin(a) * bL * 0.5, cx + Math.cos(a) * bL + sw, cy + Math.sin(a) * bL); ctx.stroke();
Draw.circle(ctx, cx + Math.cos(a) * bL + sw, cy + Math.sin(a) * bL, 3 + rng() * 3, H.lighten(cc, 0.2));
}
}
for (var f = 0; f < 10; f++) {
var fx = (rng() * w + t * 20 * (0.5 + rng())) % (w + 40) - 20, fy = h * 0.15 + rng() * h * 0.55 + Math.sin(t + f * 2) * 8;
drawFish(ctx, fx, fy, 5 + rng() * 8, [c[2], c[3], '#FFDD44', '#44DDFF'][Math.floor(rng() * 4)], 1);
}
},
particles: { type: 'bubbles', count: 30, color: '#88DDFF' }
},
deepSea: {
meta: { id: 'underwater_deepSea', mood: 'mysterious', colors: ['#0A0A2E', '#0D1B3E', '#1A3A5C', '#00FFCC'], tags: ['deep', 'dark', 'bioluminescent', 'abyss'], description: 'The mysterious deep sea with bioluminescent creatures and faint light', compatibleMusicMoods: ['mysterious', 'ambient', 'dark'], recommendedFor: ['rpg', 'story', 'puzzle'] },
colors: ['#0A0A2E', '#0D1B3E', '#1A3A5C', '#00FFCC'],
renderBase: function(ctx, w, h, c, Draw, t) {
Draw.fillGradient(ctx, w, h, [c[2], c[1], c[0]], 90);
Draw.rect(ctx, 0, h * 0.88, w, h * 0.12, H.darken(c[0], 0.3));
},
renderMid: function(ctx, w, h, c, Draw, t) {
var rng = H.seededRandom(71);
for (var j = 0; j < 6; j++) {
var jx = rng() * w, jy = h * 0.2 + rng() * h * 0.5 + Math.sin(t * 0.6 + j * 1.5) * 15;
var jr = 8 + rng() * 14, jC = rng() > 0.5 ? c[3] : '#BB77FF', p = 0.4 + Math.sin(t * 1.5 + j * 2) * 0.2;
Draw.glow(ctx, jx, jy, jr * 3, H.rgba(jC, p * 0.3));
ctx.fillStyle = H.rgba(jC, p); ctx.beginPath(); ctx.arc(jx, jy, jr, Math.PI, 0); ctx.closePath(); ctx.fill();
ctx.strokeStyle = H.rgba(jC, p * 0.6); ctx.lineWidth = 1;
for (var tt = 0; tt < 5; tt++) {
var tx = jx - jr + (tt / 4) * jr * 2; ctx.beginPath(); ctx.moveTo(tx, jy);
for (var tp = 0; tp < jr * 2 + rng() * jr; tp += 4) ctx.lineTo(tx + Math.sin(tp * 0.3 + t * 2 + tt) * 4, jy + tp);
ctx.stroke();
}
}
for (var a = 0; a < 4; a++) {
var ax = rng() * w, ay = h * 0.6 + rng() * h * 0.25, gl = 0.3 + Math.sin(t * 2.5 + a * 3) * 0.25;
Draw.glow(ctx, ax, ay, 12, H.rgba(c[3], gl)); Draw.circle(ctx, ax, ay, 2, H.rgba('#FFF', gl + 0.2));
}
for (var r = 0; r < 10; r++) Draw.circle(ctx, rng() * w, h * 0.88 + rng() * h * 0.05, 5 + rng() * 15, H.darken(c[1], 0.2 + rng() * 0.15));
},
particles: { type: 'fireflies', count: 20, color: '#00FFCC' }
},
kelpForest: {
meta: { id: 'underwater_kelpForest', mood: 'peaceful', colors: ['#0B4F3A', '#1A7A5C', '#2FA87A', '#7EC8A0'], tags: ['kelp', 'forest', 'green', 'serene'], description: 'A towering kelp forest with dappled light filtering through fronds', compatibleMusicMoods: ['peaceful', 'calm', 'ambient'], recommendedFor: ['puzzle', 'creative', 'story'] },
colors: ['#0B4F3A', '#1A7A5C', '#2FA87A', '#7EC8A0'],
renderBase: function(ctx, w, h, c, Draw, t) {
Draw.fillGradient(ctx, w, h, [H.lighten(c[0], 0.25), c[0], H.darken(c[0], 0.2)], 90);
sunRays(ctx, w, h, 5, '#AAFFCC', t);
Draw.rect(ctx, 0, h * 0.88, w, h * 0.12, H.darken(c[0], 0.35));
},
renderMid: function(ctx, w, h, c, Draw, t) {
var rng = H.seededRandom(38);
for (var i = 0; i < 18; i++) {
var kx = rng() * w, kB = h * 0.9, kT = h * 0.05 + rng() * h * 0.3;
var kC = [c[1], c[2], c[3]][Math.floor(rng() * 3)];
ctx.strokeStyle = H.rgba(kC, 0.6 + rng() * 0.3); ctx.lineWidth = 3 + rng() * 3;
ctx.beginPath(); ctx.moveTo(kx, kB);
var seg = 8 + Math.floor(rng() * 6);
for (var s = 1; s <= seg; s++) ctx.lineTo(kx + Math.sin(s * 0.8 + t * 0.7 + i) * (12 + rng() * 8), kB - (kB - kT) * (s / seg));
ctx.stroke();
for (var lf = 0; lf < 4; lf++) {
var lfY = kB - (kB - kT) * ((lf + 1) / 5), d = rng() > 0.5 ? 1 : -1;
ctx.fillStyle = H.rgba(kC, 0.4);
ctx.beginPath(); ctx.ellipse(kx + Math.sin((lf + 1) * 0.8 + t * 0.7 + i) * 12 + d * 8, lfY, 10, 3, d * 0.3, 0, Math.PI * 2); ctx.fill();
}
}
for (var f = 0; f < 7; f++) drawFish(ctx, (rng() * w + t * 25 * (rng() + 0.3)) % (w + 30) - 15, h * 0.2 + rng() * h * 0.5 + Math.sin(t * 2 + f) * 6, 4 + rng() * 4, H.rgba(c[3], 0.7), 1);
},
particles: { type: 'bubbles', count: 20, color: '#88EEBB' }
},
submarine: {
meta: { id: 'underwater_submarine', mood: 'adventurous', colors: ['#0A1628', '#1A3050', '#D4AA44', '#CC4444'], tags: ['submarine', 'exploration', 'industrial', 'adventure'], description: 'A submarine expedition through dark ocean depths with spotlight beams', compatibleMusicMoods: ['adventurous', 'mysterious', 'tense'], recommendedFor: ['action', 'rpg', 'story'] },
colors: ['#0A1628', '#1A3050', '#D4AA44', '#CC4444'],
renderBase: function(ctx, w, h, c, Draw, t) {
Draw.fillGradient(ctx, w, h, [c[1], c[0]], 90);
Draw.rect(ctx, 0, h * 0.85, w, h * 0.15, H.darken(c[0], 0.4));
},
renderMid: function(ctx, w, h, c, Draw, t) {
var rng = H.seededRandom(82), sx = w * 0.5 + Math.sin(t * 0.3) * 20, sy = h * 0.4 + Math.sin(t * 0.5) * 10;
ctx.save(); ctx.globalCompositeOperation = 'lighter';
ctx.fillStyle = H.rgba(c[2], 0.06); ctx.beginPath(); ctx.moveTo(sx + 55, sy + 5); ctx.lineTo(sx + 200, sy - 40); ctx.lineTo(sx + 200, sy + 80); ctx.closePath(); ctx.fill();
ctx.restore();
ctx.fillStyle = '#556677'; ctx.beginPath(); ctx.ellipse(sx, sy, 55, 18, 0, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = '#667788'; ctx.fillRect(sx - 10, sy - 28, 20, 12);
ctx.beginPath(); ctx.arc(sx, sy - 28, 10, Math.PI, 0); ctx.fill();
ctx.strokeStyle = '#778899'; ctx.lineWidth = 2;
ctx.beginPath(); ctx.moveTo(sx, sy - 28); ctx.lineTo(sx, sy - 42); ctx.lineTo(sx + 5, sy - 42); ctx.stroke();
Draw.circle(ctx, sx + 20, sy, 5, c[2]); Draw.circle(ctx, sx + 20, sy, 3, H.rgba('#FFEE88', 0.6));
ctx.save(); ctx.translate(sx - 55, sy);
for (var p = 0; p < 3; p++) { var pa = t * 4 + p * Math.PI * 2 / 3; ctx.fillStyle = '#8899AA'; ctx.beginPath(); ctx.ellipse(Math.cos(pa) * 6, Math.sin(pa) * 6, 8, 2, pa, 0, Math.PI * 2); ctx.fill(); }
ctx.restore();
for (var r = 0; r < 12; r++) Draw.circle(ctx, rng() * w, h * 0.85 + rng() * h * 0.08, 4 + rng() * 10, H.darken(c[0], 0.15 + rng() * 0.15));
},
particles: { type: 'bubbles', count: 25, color: '#AACCDD' }
},
shipwreck: {
meta: { id: 'underwater_shipwreck', mood: 'mysterious', colors: ['#0A1A2A', '#1A3344', '#8B7355', '#44AA88'], tags: ['shipwreck', 'treasure', 'mysterious', 'old'], description: 'A sunken ship resting on the ocean floor, overgrown with sea life', compatibleMusicMoods: ['mysterious', 'adventurous', 'dark'], recommendedFor: ['rpg', 'story', 'puzzle'] },
colors: ['#0A1A2A', '#1A3344', '#8B7355', '#44AA88'],
renderBase: function(ctx, w, h, c, Draw, t) {
Draw.fillGradient(ctx, w, h, [c[1], c[0]], 90);
sunRays(ctx, w, h, 4, '#66BBAA', t);
Draw.hills(ctx, w, h, h * 0.78, 8, '#5A4A3A', 55);
},
renderMid: function(ctx, w, h, c, Draw, t) {
var rng = H.seededRandom(93), sx = w * 0.45, sy = h * 0.62;
ctx.fillStyle = c[2]; ctx.save(); ctx.translate(sx, sy); ctx.rotate(0.15);
ctx.fillRect(-60, -10, 120, 25); ctx.fillRect(-50, -25, 100, 18);
ctx.fillStyle = H.darken(c[2], 0.2); ctx.fillRect(-40, -35, 80, 12);
ctx.strokeStyle = c[2]; ctx.lineWidth = 4; ctx.beginPath(); ctx.moveTo(-10, -35); ctx.lineTo(-20, -80); ctx.stroke();
ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(-20, -60); ctx.lineTo(15, -55); ctx.stroke();
ctx.fillStyle = H.darken(c[0], 0.3);
ctx.beginPath(); ctx.ellipse(20, 0, 8, 5, 0, 0, Math.PI * 2); ctx.fill();
ctx.beginPath(); ctx.ellipse(-30, -5, 6, 4, 0.2, 0, Math.PI * 2); ctx.fill();
ctx.restore();
for (var s = 0; s < 8; s++) {
var swx = sx - 50 + rng() * 100; ctx.strokeStyle = H.rgba(c[3], 0.5 + rng() * 0.3); ctx.lineWidth = 2;
ctx.beginPath(); ctx.moveTo(swx, sy + 5);
for (var sp = 0; sp < 15 + rng() * 25; sp += 3) ctx.lineTo(swx + Math.sin(sp * 0.4 + t * 1.2 + s) * 5, sy + 5 - sp);
ctx.stroke();
}
for (var f = 0; f < 5; f++) drawFish(ctx, (rng() * w + t * 12 * (rng() + 0.2)) % (w + 30) - 15, h * 0.3 + rng() * h * 0.35 + Math.sin(t + f) * 5, 4 + rng() * 5, H.rgba(c[3], 0.6), 1);
},
particles: { type: 'bubbles', count: 20, color: '#66AAAA' }
},
abyss: {
meta: { id: 'underwater_abyss', mood: 'dark', colors: ['#020208', '#050515', '#0A0A30', '#FF2200'], tags: ['abyss', 'dark', 'deep', 'volcanic'], description: 'The abyssal zone with volcanic vents and glowing magma cracks', compatibleMusicMoods: ['dark', 'tense', 'ambient'], recommendedFor: ['action', 'rpg', 'story'] },
colors: ['#020208', '#050515', '#0A0A30', '#FF2200'],
renderBase: function(ctx, w, h, c, Draw, t) {
Draw.fillGradient(ctx, w, h, [c[2], c[1], c[0]], 90);
Draw.hills(ctx, w, h, h * 0.8, 6, '#0D0D12', 80);
},
renderMid: function(ctx, w, h, c, Draw, t) {
var rng = H.seededRandom(13);
for (var v = 0; v < 3; v++) {
var vx = w * 0.2 + rng() * w * 0.6, vy = h * 0.82, vg = 0.3 + Math.sin(t * 2 + v * 3) * 0.15;
ctx.fillStyle = '#1A1510'; ctx.fillRect(vx - 8, vy - 20, 16, 25); ctx.fillRect(vx - 12, vy - 22, 24, 5);
Draw.glow(ctx, vx, vy - 22, 25, H.rgba(c[3], vg));
for (var sm = 0; sm < 6; sm++) {
ctx.fillStyle = H.rgba(c[3], 0.15 * (1 - sm / 6));
ctx.beginPath(); ctx.arc(vx + Math.sin(t * 1.5 + sm * 2 + v) * 8, vy - 25 - sm * 18 - (t * 10 % 30), 5 + sm * 2, 0, Math.PI * 2); ctx.fill();
}
}
for (var lc = 0; lc < 8; lc++) {
var lcx = rng() * w, lcy = h * 0.83 + rng() * h * 0.1, g2 = 0.2 + Math.sin(t * 1.8 + lc) * 0.15;
Draw.glow(ctx, lcx, lcy, 10, H.rgba(c[3], g2 * 0.4));
ctx.strokeStyle = H.rgba(c[3], g2); ctx.lineWidth = 2;
ctx.beginPath(); ctx.moveTo(lcx, lcy); ctx.lineTo(lcx + (rng() - 0.5) * 30, lcy + (rng() - 0.5) * 8); ctx.stroke();
}
for (var s = 0; s < 12; s++) Draw.circle(ctx, rng() * w, rng() * h * 0.75, 1 + rng(), H.rgba('#4488FF', 0.15 + Math.sin(t * 3 + s * 2.3) * 0.1));
},
particles: { type: 'particles', count: 15, color: '#FF4400' }
},
iceCave: {
meta: { id: 'underwater_iceCave', mood: 'calm', colors: ['#0A2A4A', '#1A5A8A', '#88CCEE', '#E0F0FF'], tags: ['ice', 'cave', 'arctic', 'frozen'], description: 'An underwater ice cavern with crystalline formations and pale blue light', compatibleMusicMoods: ['calm', 'ambient', 'mysterious'], recommendedFor: ['puzzle', 'story', 'creative'] },
colors: ['#0A2A4A', '#1A5A8A', '#88CCEE', '#E0F0FF'],
renderBase: function(ctx, w, h, c, Draw, t) {
Draw.fillGradient(ctx, w, h, [c[3], c[2], c[1], c[0]], 90);
Draw.hills(ctx, w, h, h * 0.08, 12, H.rgba(c[3], 0.3), 18);
},
renderMid: function(ctx, w, h, c, Draw, t) {
var rng = H.seededRandom(27);
for (var i = 0; i < 14; i++) {
var stx = rng() * w, sth = 20 + rng() * 60, stw = 4 + rng() * 8;
ctx.fillStyle = H.rgba(c[2], 0.4 + rng() * 0.3);
ctx.beginPath(); ctx.moveTo(stx - stw, 0); ctx.lineTo(stx, sth); ctx.lineTo(stx + stw, 0); ctx.closePath(); ctx.fill();
ctx.fillStyle = H.rgba(c[3], 0.2);
ctx.beginPath(); ctx.moveTo(stx - stw * 0.3, 0); ctx.lineTo(stx - 1, sth * 0.8); ctx.lineTo(stx + stw * 0.2, 0); ctx.closePath(); ctx.fill();
}
for (var j = 0; j < 10; j++) {
var sgx = rng() * w, sgh = 15 + rng() * 40, sgw = 5 + rng() * 10;
ctx.fillStyle = H.rgba(c[1], 0.5 + rng() * 0.3);
ctx.beginPath(); ctx.moveTo(sgx - sgw, h); ctx.lineTo(sgx, h - sgh); ctx.lineTo(sgx + sgw, h); ctx.closePath(); ctx.fill();
}
for (var k = 0; k < 8; k++) {
var kx = rng() * w, ky = h * 0.2 + rng() * h * 0.5 + Math.sin(t * 0.4 + k * 1.7) * 8, ks = 4 + rng() * 8;
Draw.polygon(ctx, kx, ky, ks, 6, H.rgba(c[3], 0.25 + Math.sin(t + k) * 0.1), t * 0.3 + k * 1.2);
Draw.circle(ctx, kx, ky, ks * 0.3, H.rgba('#FFF', 0.4));
}
ctx.save(); ctx.globalCompositeOperation = 'lighter';
for (var m = 0; m < 6; m++) Draw.circle(ctx, rng() * w + Math.sin(t * 0.5 + m) * 10, h * 0.3 + rng() * h * 0.3, 20 + rng() * 40, H.rgba(c[2], 0.02 + Math.sin(t * 0.8 + m * 1.5) * 0.015));
ctx.restore();
},
particles: { type: 'sparkles', count: 25, color: '#E0F0FF' }
},
treasure: {
meta: { id: 'underwater_treasure', mood: 'adventurous', colors: ['#0D2B45', '#1A4A6A', '#FFD700', '#FF8C00'], tags: ['treasure', 'gold', 'adventure', 'pirate'], description: 'A hidden underwater treasure trove glowing with golden riches', compatibleMusicMoods: ['adventurous', 'happy', 'upbeat'], recommendedFor: ['rpg', 'action', 'puzzle'] },
colors: ['#0D2B45', '#1A4A6A', '#FFD700', '#FF8C00'],
renderBase: function(ctx, w, h, c, Draw, t) {
Draw.fillGradient(ctx, w, h, [c[1], c[0]], 90);
sunRays(ctx, w, h, 3, '#FFDDAA', t);
Draw.hills(ctx, w, h, h * 0.75, 7, '#3A2A1A', 62);
},
renderMid: function(ctx, w, h, c, Draw, t) {
var rng = H.seededRandom(57), tcx = w * 0.5, tcy = h * 0.72;
ctx.fillStyle = '#6B3A1F'; ctx.fillRect(tcx - 25, tcy - 5, 50, 22);
ctx.fillStyle = '#8B5A2F'; ctx.beginPath(); ctx.ellipse(tcx, tcy - 5, 25, 10, 0, Math.PI, 0); ctx.fill();
ctx.fillStyle = '#7B4A25'; ctx.save(); ctx.translate(tcx - 25, tcy - 5); ctx.rotate(-0.6); ctx.fillRect(0, -16, 50, 16); ctx.restore();
var gp = 0.4 + Math.sin(t * 1.5) * 0.15;
Draw.glow(ctx, tcx, tcy - 10, 40, H.rgba(c[2], gp));
for (var g = 0; g < 12; g++) { var gx = tcx - 15 + rng() * 30, gy = tcy - 2 + rng() * 15; Draw.circle(ctx, gx, gy, 3 + rng() * 2, c[2]); Draw.circle(ctx, gx - 0.5, gy - 0.5, 2, H.lighten(c[2], 0.3)); }
ctx.strokeStyle = c[3]; ctx.lineWidth = 2; ctx.strokeRect(tcx - 24, tcy - 4, 48, 20);
for (var j = 0; j < 8; j++) {
var jx = tcx - 40 + rng() * 80, jy = tcy + 10 + rng() * 15, jC = ['#FF0044', '#00AAFF', '#44FF44', c[2]][Math.floor(rng() * 4)], jT = 0.5 + Math.sin(t * 3 + j * 2) * 0.3;
Draw.polygon(ctx, jx, jy, 2 + rng() * 3, 5 + Math.floor(rng() * 3), H.rgba(jC, jT), t * 0.2 + j);
Draw.glow(ctx, jx, jy, 6, H.rgba(jC, jT * 0.3));
}
for (var p = 0; p < 2; p++) {
var px = p === 0 ? w * 0.15 : w * 0.85;
ctx.fillStyle = '#4A4A3A'; ctx.fillRect(px - 8, h * 0.4, 16, h * 0.38); ctx.fillRect(px - 12, h * 0.38, 24, 6); ctx.fillRect(px - 12, h * 0.76, 24, 6);
ctx.strokeStyle = H.rgba('#2A2A20', 0.5); ctx.lineWidth = 1;
ctx.beginPath(); ctx.moveTo(px - 3, h * 0.5); ctx.lineTo(px + 2, h * 0.55); ctx.lineTo(px - 1, h * 0.62); ctx.stroke();
}
},
particles: { type: 'sparkles', count: 35, color: '#FFD700' }
}
});
})(window.BackgroundEngine);