Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
299 changes: 298 additions & 1 deletion _datafiles/html/public/static/js/fx.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const FX = {
// level-up.
// duration: fade-out time in seconds.
// -----------------------------------------------------------------------
Flash(color = 'rgba(255,0,0,0.45)', duration = 0.5) {
Flash(color = '#ff0000', duration = 0.5) {
const el = document.createElement('div');
el.style.cssText = [
'position:fixed', 'inset:0', 'pointer-events:none', 'z-index:99999',
Expand Down Expand Up @@ -267,4 +267,301 @@ const FX = {
})(performance.now());
},

// -----------------------------------------------------------------------
// Snow — white flakes drift down with gentle sideways sway.
// count: number of flakes.
// duration: seconds the effect runs.
// -----------------------------------------------------------------------
Snow(count = 150, duration = 4.0) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
document.body.appendChild(canvas);
canvas.style.cssText = 'position:fixed;top:0;left:0;pointer-events:none;z-index:99999;';

function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }
resize();
window.addEventListener('resize', resize);

const flakes = Array.from({ length: count }, () => ({
x: Math.random() * canvas.width,
y: Math.random() * -canvas.height,
size: Math.random() * 3 + 1,
speed: Math.random() * 1.5 + 0.5,
sway: Math.random() * Math.PI * 2,
swaySpeed: Math.random() * 0.02 + 0.005,
alpha: Math.random() * 0.5 + 0.5,
}));

const start = performance.now();
const durationMs = duration * 1000;

(function animate(now) {
const elapsed = now - start;
ctx.clearRect(0, 0, canvas.width, canvas.height);
flakes.forEach(f => {
f.sway += f.swaySpeed;
f.x += Math.sin(f.sway) * 0.8;
f.y += f.speed;
if (f.y > canvas.height) { f.y = -f.size; f.x = Math.random() * canvas.width; }
ctx.globalAlpha = f.alpha;
ctx.beginPath();
ctx.arc(f.x, f.y, f.size, 0, Math.PI * 2);
ctx.fillStyle = '#ffffff';
ctx.fill();
});
ctx.globalAlpha = 1;
if (elapsed < durationMs) {
requestAnimationFrame(animate);
} else {
window.removeEventListener('resize', resize);
document.body.removeChild(canvas);
}
})(performance.now());
},

// -----------------------------------------------------------------------
// Embers — slow-rising glowing particles, good for fire rooms or forges.
// count: number of ember particles.
// duration: seconds the effect runs.
// -----------------------------------------------------------------------
Embers(count = 80, duration = 3.0) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
document.body.appendChild(canvas);
canvas.style.cssText = 'position:fixed;top:0;left:0;pointer-events:none;z-index:99999;';

function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }
resize();
window.addEventListener('resize', resize);

const colors = ['#ff6600', '#ff4400', '#ff9900', '#ffcc00', '#ff2200'];
const embers = Array.from({ length: count }, () => ({
x: Math.random() * canvas.width,
y: canvas.height + Math.random() * 40,
size: Math.random() * 2.5 + 0.5,
speed: Math.random() * 1.2 + 0.4,
sway: Math.random() * Math.PI * 2,
swaySpeed: Math.random() * 0.03 + 0.01,
color: colors[Math.floor(Math.random() * colors.length)],
life: Math.random() * 0.6 + 0.4,
}));

const start = performance.now();
const durationMs = duration * 1000;

(function animate(now) {
const elapsed = now - start;
const t = elapsed / durationMs;
ctx.clearRect(0, 0, canvas.width, canvas.height);
embers.forEach(e => {
e.sway += e.swaySpeed;
e.x += Math.sin(e.sway) * 1.2;
e.y -= e.speed;
if (e.y < -10) { e.y = canvas.height + 10; e.x = Math.random() * canvas.width; }
const alpha = Math.max(0, e.life - t) / e.life;
ctx.globalAlpha = alpha;
ctx.beginPath();
ctx.arc(e.x, e.y, e.size, 0, Math.PI * 2);
ctx.fillStyle = e.color;
ctx.fill();
});
ctx.globalAlpha = 1;
if (elapsed < durationMs) {
requestAnimationFrame(animate);
} else {
window.removeEventListener('resize', resize);
document.body.removeChild(canvas);
}
})(performance.now());
},

// -----------------------------------------------------------------------
// Fireflies — soft glowing dots that drift and pulse, good for forests.
// count: number of fireflies.
// duration: seconds the effect runs.
// -----------------------------------------------------------------------
Fireflies(count = 40, duration = 4.0) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
document.body.appendChild(canvas);
canvas.style.cssText = 'position:fixed;top:0;left:0;pointer-events:none;z-index:99999;';

function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }
resize();
window.addEventListener('resize', resize);

const flies = Array.from({ length: count }, () => ({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * 0.8,
vy: (Math.random() - 0.5) * 0.5,
phase: Math.random() * Math.PI * 2,
phaseSpeed: Math.random() * 0.04 + 0.02,
size: Math.random() * 3 + 2,
}));

const start = performance.now();
const durationMs = duration * 1000;

(function animate(now) {
const elapsed = now - start;
const t = elapsed / durationMs;
const fade = t < 0.15 ? t / 0.15 : t > 0.85 ? (1 - t) / 0.15 : 1;
ctx.clearRect(0, 0, canvas.width, canvas.height);
flies.forEach(f => {
f.phase += f.phaseSpeed;
f.x += f.vx + Math.sin(f.phase * 0.7) * 0.4;
f.y += f.vy + Math.cos(f.phase * 0.5) * 0.3;
if (f.x < 0) { f.x = canvas.width; }
if (f.x > canvas.width) { f.x = 0; }
if (f.y < 0) { f.y = canvas.height; }
if (f.y > canvas.height) { f.y = 0; }
const pulse = (Math.sin(f.phase) + 1) / 2;
const alpha = pulse * 0.8 * fade;
const grad = ctx.createRadialGradient(f.x, f.y, 0, f.x, f.y, f.size * 3);
grad.addColorStop(0, 'rgba(180,255,120,' + alpha + ')');
grad.addColorStop(1, 'rgba(180,255,120,0)');
ctx.beginPath();
ctx.arc(f.x, f.y, f.size * 3, 0, Math.PI * 2);
ctx.fillStyle = grad;
ctx.fill();
});
if (elapsed < durationMs) {
requestAnimationFrame(animate);
} else {
window.removeEventListener('resize', resize);
document.body.removeChild(canvas);
}
})(performance.now());
},

// -----------------------------------------------------------------------
// Bubbles — slow-rising translucent circles, good for underwater rooms.
// count: number of bubbles.
// duration: seconds the effect runs.
// -----------------------------------------------------------------------
Bubbles(count = 60, duration = 3.5) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
document.body.appendChild(canvas);
canvas.style.cssText = 'position:fixed;top:0;left:0;pointer-events:none;z-index:99999;';

function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }
resize();
window.addEventListener('resize', resize);

const bubbles = Array.from({ length: count }, () => ({
x: Math.random() * canvas.width,
y: canvas.height + Math.random() * canvas.height,
size: Math.random() * 10 + 3,
speed: Math.random() * 1.5 + 0.5,
sway: Math.random() * Math.PI * 2,
swaySpeed: Math.random() * 0.02 + 0.005,
alpha: Math.random() * 0.3 + 0.1,
}));

const start = performance.now();
const durationMs = duration * 1000;

(function animate(now) {
const elapsed = now - start;
ctx.clearRect(0, 0, canvas.width, canvas.height);
bubbles.forEach(b => {
b.sway += b.swaySpeed;
b.x += Math.sin(b.sway) * 0.6;
b.y -= b.speed;
if (b.y < -b.size) { b.y = canvas.height + b.size; b.x = Math.random() * canvas.width; }
ctx.globalAlpha = b.alpha;
ctx.beginPath();
ctx.arc(b.x, b.y, b.size, 0, Math.PI * 2);
ctx.strokeStyle = '#88ccff';
ctx.lineWidth = 1.5;
ctx.stroke();
ctx.fillStyle = 'rgba(136,204,255,0.06)';
ctx.fill();
});
ctx.globalAlpha = 1;
if (elapsed < durationMs) {
requestAnimationFrame(animate);
} else {
window.removeEventListener('resize', resize);
document.body.removeChild(canvas);
}
})(performance.now());
},

// -----------------------------------------------------------------------
// Shockwave — a single fast-expanding ring bursts from the screen center.
// color: ring colour.
// duration: seconds for the ring to expand and fade.
// -----------------------------------------------------------------------
Shockwave(color = '#ffffff', duration = 0.5) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
document.body.appendChild(canvas);
canvas.style.cssText = 'position:fixed;top:0;left:0;pointer-events:none;z-index:99999;';

function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }
resize();
window.addEventListener('resize', resize);

const cx = canvas.width / 2;
const cy = canvas.height / 2;
const maxRadius = Math.sqrt(cx * cx + cy * cy);
const start = performance.now();
const durationMs = duration * 1000;

(function animate(now) {
const elapsed = now - start;
const t = Math.min(elapsed / durationMs, 1);
// Ease out: fast start, slow end
const ease = 1 - Math.pow(1 - t, 3);
const radius = ease * maxRadius;
const alpha = 1 - t;
const width = (1 - t) * 18 + 2;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
ctx.strokeStyle = color;
ctx.globalAlpha = alpha;
ctx.lineWidth = width;
ctx.stroke();
ctx.globalAlpha = 1;
if (elapsed < durationMs) {
requestAnimationFrame(animate);
} else {
window.removeEventListener('resize', resize);
document.body.removeChild(canvas);
}
})(performance.now());
},

// -----------------------------------------------------------------------
// Pulse — #main-container breathes out and back once, like a heartbeat.
// scale: peak scale factor.
// duration: total animation time in seconds.
// -----------------------------------------------------------------------
Pulse(scale = 1.02, duration = 0.5) {
const target = document.getElementById('main-container') || document.body;
const original = target.style.transform;
const start = performance.now();
const durationMs = duration * 1000;

(function animate(now) {
const elapsed = now - start;
const t = Math.min(elapsed / durationMs, 1);
// Smooth sine pulse
const ease = Math.sin(t * Math.PI);
const s = 1 + (scale - 1) * ease;
target.style.transform = 'scale(' + s + ')';
if (t < 1) {
requestAnimationFrame(animate);
} else {
target.style.transform = original;
}
})(performance.now());
},

};

window.FX = FX;
Loading
Loading