all movies have animations

This commit is contained in:
samantha42
2026-05-24 22:13:07 +02:00
parent 05d8732e85
commit 87a19785b5
5 changed files with 732 additions and 417 deletions
+727
View File
@@ -0,0 +1,727 @@
function cureClick() {
if (document.getElementById('cure-canvas')) return;
if (document.getElementById('lightning-canvas')) stopLightning();
if (document.getElementById('typewriter-canvas')) stopTypewriter();
if (document.getElementById('matrix-canvas')) stopMatrix();
if (document.getElementById('rain-canvas')) stopRain();
const el = document.getElementById('interest');
el.textContent = "Actually... I'm feeling much better now!"
startWater();
isDay = false;
applyState();
setTimeout(() => {
el.textContent = 'interest';
stopWater();
}, 10000);
}
let cureCanvas, cureCtx, cureAnimFrame;
function startWater() {
cureCanvas = document.createElement('canvas');
cureCanvas.id = 'cure-canvas';
cureCanvas.width = window.innerWidth;
cureCanvas.height = window.innerHeight;
Object.assign(cureCanvas.style, {
position: 'fixed',
top: 0,
left: 0,
pointerEvents: 'none',
zIndex: -1, // behind everything
opacity: '0',
transition: 'opacity 1.4s ease'
});
document.body.appendChild(cureCanvas);
cureCtx = cureCanvas.getContext('2d');
requestAnimationFrame(() => { cureCanvas.style.opacity = '1'; });
const W = cureCanvas.width;
const H = cureCanvas.height;
// waterline sits at 38% from the top
const waterlineY = H * 0.38;
// floating particles below the surface
const particles = Array.from({ length: 55 }, () => ({
x: Math.random() * W,
y: waterlineY + 20 + Math.random() * (H - waterlineY - 20),
r: 1 + Math.random() * 2.5,
speedX: (Math.random() - 0.5) * 0.3,
speedY: -0.08 - Math.random() * 0.18, // slow upward drift
opacity: 0.08 + Math.random() * 0.18
}));
function drawFrame(ts) {
cureCtx.clearRect(0, 0, W, H);
// ── sky above waterline (very subtle, mostly transparent) ──────────
const sky = cureCtx.createLinearGradient(0, 0, 0, waterlineY);
sky.addColorStop(0, 'rgba(190, 230, 255, 0.06)');
sky.addColorStop(1, 'rgba(190, 230, 255, 0.01)');
cureCtx.fillStyle = sky;
cureCtx.fillRect(0, 0, W, waterlineY);
// ── water body below waterline ─────────────────────────────────────
const water = cureCtx.createLinearGradient(0, waterlineY, 0, H);
water.addColorStop(0, 'rgba(80, 190, 220, 0.28)');
water.addColorStop(0.35, 'rgba(40, 140, 190, 0.22)');
water.addColorStop(0.7, 'rgba(20, 90, 150, 0.20)');
water.addColorStop(1, 'rgba(10, 50, 110, 0.22)');
cureCtx.fillStyle = water;
cureCtx.fillRect(0, waterlineY, W, H - waterlineY);
// ── caustic shimmer streaks ────────────────────────────────────────
const numStreaks = 10;
for (let i = 0; i < numStreaks; i++) {
const phase = (ts * 0.0004 + i * 0.63) % 1;
const x = (i / numStreaks) * W + Math.sin(ts * 0.0008 + i) * 60;
const yTop = waterlineY + 10 + Math.sin(ts * 0.001 + i * 1.3) * 18;
const yBot = yTop + 40 + Math.sin(ts * 0.0007 + i * 0.9) * 30;
const streak = cureCtx.createLinearGradient(x, yTop, x + 6, yBot);
streak.addColorStop(0, 'rgba(200, 245, 255, 0)');
streak.addColorStop(0.4, `rgba(200, 245, 255, ${0.05 + 0.06 * Math.sin(ts * 0.002 + i)})`);
streak.addColorStop(1, 'rgba(200, 245, 255, 0)');
cureCtx.fillStyle = streak;
cureCtx.fillRect(x, yTop, 5, yBot - yTop);
}
// ── floating particles ─────────────────────────────────────────────
particles.forEach(p => {
p.x += p.speedX;
p.y += p.speedY;
// reset when reaching the surface
if (p.y < waterlineY + 5) {
p.y = H - 10;
p.x = Math.random() * W;
}
if (p.x < 0) p.x = W;
if (p.x > W) p.x = 0;
const depth = (p.y - waterlineY) / (H - waterlineY); // 0 = surface, 1 = bottom
cureCtx.beginPath();
cureCtx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
cureCtx.fillStyle = `rgba(180, 240, 255, ${p.opacity * (0.4 + 0.6 * (1 - depth))})`;
cureCtx.fill();
});
// ── animated surface waves (2 layers, different speeds & amplitudes) ─
function drawWaveLine(amp, freq, speed, lineW, alpha) {
cureCtx.beginPath();
for (let x = 0; x <= W; x += 3) {
const y = waterlineY
+ Math.sin(x * freq + ts * speed) * amp
+ Math.sin(x * freq * 1.7 + ts * speed * 0.6) * (amp * 0.4);
x === 0 ? cureCtx.moveTo(x, y) : cureCtx.lineTo(x, y);
}
cureCtx.strokeStyle = `rgba(210, 245, 255, ${alpha})`;
cureCtx.lineWidth = lineW;
cureCtx.stroke();
}
// primary wave
drawWaveLine(7, 0.012, 0.0018, 2.0, 0.70);
// secondary wave (slightly offset phase)
drawWaveLine(4, 0.018, 0.0024, 1.2, 0.40);
// micro ripple
drawWaveLine(2, 0.030, 0.0032, 0.7, 0.25);
// ── foam/highlight on the crest ────────────────────────────────────
cureCtx.beginPath();
for (let x = 0; x <= W; x += 3) {
const y = waterlineY
+ Math.sin(x * 0.012 + ts * 0.0018) * 7
+ Math.sin(x * 0.020 + ts * 0.0024) * 3;
x === 0 ? cureCtx.moveTo(x, y - 1) : cureCtx.lineTo(x, y - 1);
}
cureCtx.strokeStyle = 'rgba(255, 255, 255, 0.18)';
cureCtx.lineWidth = 1;
cureCtx.stroke();
if (cureCanvas) cureAnimFrame = requestAnimationFrame(drawFrame);
}
cureAnimFrame = requestAnimationFrame(drawFrame);
}
function stopWater() {
if (!cureCanvas) return;
cureCanvas.style.opacity = '0';
cancelAnimationFrame(cureAnimFrame);
setTimeout(() => {
cureCanvas.remove();
cureCanvas = null;
}, 1400);
}
function draculaClick() {
if (document.getElementById('lightning-canvas')) return;
if (document.getElementById('cure-canvas')) stopWater();
if (document.getElementById('typewriter-canvas')) stopTypewriter();
if (document.getElementById('matrix-canvas')) stopMatrix();
if (document.getElementById('rain-canvas')) stopRain();
const el = document.getElementById('interest');
el.textContent = 'You have your whole life ahead of you. And I only offer death.';
startLightning();
isDay = false;
applyState();
setTimeout(() => {
el.textContent = 'interest';
stopLightning();
}, 10000);
}
let lightningCanvas, lightningCtx, lightningInterval, lightningFlashTimeout;
function startLightning() {
lightningCanvas = document.createElement('canvas');
lightningCanvas.id = 'lightning-canvas';
lightningCanvas.width = window.innerWidth;
lightningCanvas.height = window.innerHeight;
Object.assign(lightningCanvas.style, {
position: 'fixed', top: 0, left: 0,
pointerEvents: 'none', zIndex: 9999,
opacity: '0', transition: 'opacity 0.3s ease'
});
document.body.appendChild(lightningCanvas);
lightningCtx = lightningCanvas.getContext('2d');
requestAnimationFrame(() => { lightningCanvas.style.opacity = '1'; });
function drawBolt(x1, y1, x2, y2, roughness, depth) {
if (depth === 0) {
lightningCtx.beginPath();
lightningCtx.moveTo(x1, y1);
lightningCtx.lineTo(x2, y2);
lightningCtx.stroke();
return;
}
const mx = (x1 + x2) / 2 + (Math.random() - 0.5) * roughness;
const my = (y1 + y2) / 2 + (Math.random() - 0.5) * roughness;
drawBolt(x1, y1, mx, my, roughness / 2, depth - 1);
drawBolt(mx, my, x2, y2, roughness / 2, depth - 1);
// random fork branch
if (depth > 2 && Math.random() < 0.4) {
const fx = mx + (Math.random() - 0.3) * roughness * 2;
const fy = my + Math.random() * roughness * 2;
lightningCtx.save();
lightningCtx.globalAlpha *= 0.5;
drawBolt(mx, my, fx, fy, roughness / 2, depth - 2);
lightningCtx.restore();
}
}
function flashStrike() {
lightningCtx.clearRect(0, 0, lightningCanvas.width, lightningCanvas.height);
const numBolts = 1 + Math.floor(Math.random() * 3);
for (let i = 0; i < numBolts; i++) {
const startX = lightningCanvas.width * (0.1 + Math.random() * 0.8);
const endX = startX + (Math.random() - 0.5) * 300;
const color = Math.random() < 0.6
? `rgba(220, 180, 255, ${0.6 + Math.random() * 0.4})` // purple
: `rgba(255, 255, 255, ${0.7 + Math.random() * 0.3})`; // white
lightningCtx.strokeStyle = color;
lightningCtx.lineWidth = 0.5 + Math.random() * 2;
lightningCtx.shadowColor = color;
lightningCtx.shadowBlur = 18 + Math.random() * 30;
lightningCtx.globalAlpha = 1;
drawBolt(startX, 0, endX, lightningCanvas.height * (0.4 + Math.random() * 0.5), 120, 7);
}
// ambient flash on the body
document.body.style.transition = 'background 0.05s';
document.body.style.background = `rgba(80, 0, 120, 0.08)`;
setTimeout(() => { document.body.style.background = ''; }, 80);
// fade the bolt out
lightningFlashTimeout = setTimeout(() => {
lightningCtx.clearRect(0, 0, lightningCanvas.width, lightningCanvas.height);
}, 80 + Math.random() * 120);
}
// irregular strike cadence — like real lightning
function scheduleStrike() {
flashStrike();
const delay = 600 + Math.random() * 2200;
lightningInterval = setTimeout(scheduleStrike, delay);
}
scheduleStrike();
}
function stopLightning() {
if (!lightningCanvas) return;
lightningCanvas.style.opacity = '0';
clearTimeout(lightningInterval);
clearTimeout(lightningFlashTimeout);
setTimeout(() => {
lightningCanvas.remove();
lightningCanvas = null;
}, 800);
}
function dragonTypewriterClick() {
if (document.getElementById('typewriter-canvas')) return;
if (document.getElementById('lightning-canvas')) stopLightning();
if (document.getElementById('cure-canvas')) stopWater();
if (document.getElementById('matrix-canvas')) stopMatrix();
if (document.getElementById('rain-canvas')) stopRain();
const el = document.getElementById('interest');
el.textContent = 'case file 36609...';
startTypewriter();
isDay = false;
applyState();
setTimeout(() => {
el.textContent = 'interest';
stopTypewriter();
}, 10000);
}
let twCanvas, twCtx, twAnimFrame;
function startTypewriter() {
twCanvas = document.createElement('canvas');
twCanvas.id = 'typewriter-canvas';
twCanvas.width = window.innerWidth;
twCanvas.height = window.innerHeight;
Object.assign(twCanvas.style, {
position: 'fixed',
top: 0,
left: 0,
pointerEvents: 'none',
zIndex: -1,
opacity: '0',
transition: 'opacity 1.2s ease'
});
document.body.appendChild(twCanvas);
twCtx = twCanvas.getContext('2d');
requestAnimationFrame(() => { twCanvas.style.opacity = '1'; });
const W = twCanvas.width;
const H = twCanvas.height;
// ── case file fragments ──────────────────────────────────────────────
const fragments = [
'SUBJECT: HARRIET VANGER',
'CASE NO. 36609-74',
'CLASSIFICATION: MISSING / PRESUMED',
'████████████ DECEASED ████████████',
'LAST SEEN: HEDEBY ISLAND',
'NOVEMBER 1, 1966',
'INVESTIGATING OFFICER: ██████████',
'FINANCIAL IRREGULARITIES NOTED',
'SEE ATTACHMENT ██-C',
'WENNERSTROM GROUP — CLASSIFIED',
'DO NOT DISTRIBUTE',
'SOURCE: CONFIDENTIAL',
'SALANDER, L. — AUTHORIZED',
'BLOMKVIST, M. — AUTHORIZED',
'ALL OTHERS: ACCESS DENIED',
'████████████████████████████',
'MOTIVE: UNKNOWN',
'SUSPECT LIST: REDACTED',
'EVIDENCE SEALED BY ORDER OF',
'████████ COURT ████████',
'TRAUMA INDICATED / SEE REPORT 7',
'LISBETH — CLEARANCE LEVEL 3',
'THE DRAGON TATTOO',
'——————————————————————',
'WHERE IS SHE.',
'WHO KNOWS.',
'FILE CLOSED: ██/██/████',
];
// glitch chars pool
const glitchChars = '!@#$%&*?|\\/<>[]{}~^░▒▓█▄▀■□▪▫';
function randGlitch() {
return glitchChars[Math.floor(Math.random() * glitchChars.length)];
}
// ── active lines on screen ───────────────────────────────────────────
const lines = [];
let fragmentPool = [...fragments];
function pickFragment() {
if (!fragmentPool.length) fragmentPool = [...fragments];
const i = Math.floor(Math.random() * fragmentPool.length);
return fragmentPool.splice(i, 1)[0];
}
function spawnLine() {
const text = pickFragment();
const fontSize = 11 + Math.floor(Math.random() * 9);
const x = 40 + Math.random() * (W * 0.55);
const y = 60 + Math.random() * (H - 120);
const isRedact = text.includes('█') || Math.random() < 0.12;
const speed = 28 + Math.random() * 22; // chars per second
lines.push({
text,
x, y,
fontSize,
typed: 0, // chars revealed so far (float)
speed,
glitching: false,
glitchTimer: 0,
glitchStr: '',
opacity: 0.72 + Math.random() * 0.26,
isRedact,
done: false,
holdTimer: 0,
fadeOut: false,
fadeAlpha: 1,
stamp: Math.random() < 0.08, // rare CLASSIFIED/DENIED stamp
});
}
// spawn cadence
spawnLine();
const spawnTimer = setInterval(() => {
if (lines.length < 14) spawnLine();
}, 600);
twCanvas._spawnTimer = spawnTimer;
let lastTs = null;
function drawFrame(ts) {
if (!lastTs) lastTs = ts;
const dt = (ts - lastTs) / 1000;
lastTs = ts;
twCtx.clearRect(0, 0, W, H);
// faint scanline overlay — gives it that CRT/photocopy feel
for (let sy = 0; sy < H; sy += 3) {
twCtx.fillStyle = 'rgba(0,0,0,0.04)';
twCtx.fillRect(0, sy, W, 1);
}
lines.forEach((line, idx) => {
twCtx.font = `${line.fontSize}px "Courier New", Courier, monospace`;
if (line.fadeOut) {
line.fadeAlpha -= dt * 0.6;
if (line.fadeAlpha <= 0) {
lines.splice(idx, 1);
return;
}
}
const alpha = line.opacity * (line.fadeOut ? line.fadeAlpha : 1);
if (!line.done) {
line.typed += line.speed * dt;
if (line.typed >= line.text.length) {
line.typed = line.text.length;
line.done = true;
}
// random mid-type glitch burst
if (!line.glitching && Math.random() < 0.004) {
line.glitching = true;
line.glitchTimer = 0.18 + Math.random() * 0.22;
line.glitchStr = Array.from(
{ length: 3 + Math.floor(Math.random() * 5) },
randGlitch
).join('');
}
} else {
// after typing finishes, hold then fade
line.holdTimer += dt;
if (line.holdTimer > 3.5 + Math.random() * 3) {
line.fadeOut = true;
// spawn a replacement
setTimeout(spawnLine, 200 + Math.random() * 600);
}
// occasional re-glitch on finished lines
if (!line.glitching && Math.random() < 0.003) {
line.glitching = true;
line.glitchTimer = 0.1 + Math.random() * 0.15;
line.glitchStr = Array.from(
{ length: 2 + Math.floor(Math.random() * 4) },
randGlitch
).join('');
}
}
// resolve glitch burst
if (line.glitching) {
line.glitchTimer -= dt;
if (line.glitchTimer <= 0) {
line.glitching = false;
line.glitchStr = '';
}
}
// ── build visible string ───────────────────────────────────────
const visible = line.text.slice(0, Math.floor(line.typed));
let display = visible;
// blinking cursor at the type head
if (!line.done) {
const blink = Math.floor(ts / 530) % 2 === 0;
display += blink ? '▌' : ' ';
}
// append glitch burst after cursor
if (line.glitching) display += line.glitchStr;
// ── color choice ───────────────────────────────────────────────
// redacted blocks stay white-ish; normal text is the faded amber/grey
const isRedactLine = line.text.includes('█');
if (isRedactLine) {
twCtx.fillStyle = `rgba(220, 215, 200, ${alpha})`;
} else if (line.stamp) {
twCtx.fillStyle = `rgba(180, 30, 30, ${alpha * 0.85})`;
} else {
// slight warm/cool variation per line
const warm = Math.random() < 0.5;
twCtx.fillStyle = warm
? `rgba(210, 200, 180, ${alpha})`
: `rgba(185, 200, 195, ${alpha})`;
}
twCtx.fillText(display, line.x, line.y);
// ── underline for "stamp" lines ────────────────────────────────
if (line.stamp && line.done) {
const mw = twCtx.measureText(line.text).width;
twCtx.strokeStyle = `rgba(180, 30, 30, ${alpha * 0.6})`;
twCtx.lineWidth = 1;
twCtx.beginPath();
twCtx.moveTo(line.x, line.y + 3);
twCtx.lineTo(line.x + mw, line.y + 3);
twCtx.stroke();
}
});
if (twCanvas) twAnimFrame = requestAnimationFrame(drawFrame);
}
twAnimFrame = requestAnimationFrame(drawFrame);
}
function stopTypewriter() {
if (!twCanvas) return;
twCanvas.style.opacity = '0';
clearInterval(twCanvas._spawnTimer);
cancelAnimationFrame(twAnimFrame);
setTimeout(() => {
twCanvas.remove();
twCanvas = null;
}, 1200);
}
function matrixClick() {
if (document.getElementById('matrix-canvas')) return;
if (document.getElementById('cure-canvas')) stopWater();
if (document.getElementById('lightning-canvas')) stopLightning();
if (document.getElementById('typewriter-canvas')) stopTypewriter();
if (document.getElementById('rain-canvas')) stopRain();
const el = document.getElementById('interest');
el.textContent = 'wake up, neo...';
startMatrix();
isDay = false;
applyState();
setTimeout(() => {
el.textContent = 'interest';
stopMatrix();
}, 10000);
}
let matrixCanvas, matrixCtx, matrixAnimFrame;
function startMatrix() {
matrixCanvas = document.createElement('canvas');
matrixCanvas.id = 'matrix-canvas';
matrixCanvas.width = window.innerWidth;
matrixCanvas.height = window.innerHeight;
Object.assign(matrixCanvas.style, {
position: 'fixed',
top: 0,
left: 0,
pointerEvents: 'none',
zIndex: -1,
opacity: '0',
transition: 'opacity 1.2s ease'
});
document.body.appendChild(matrixCanvas);
matrixCtx = matrixCanvas.getContext('2d');
requestAnimationFrame(() => { matrixCanvas.style.opacity = '1'; });
const W = matrixCanvas.width;
const H = matrixCanvas.height;
const FONT_SIZE = 16;
const COLS = Math.floor(W / FONT_SIZE);
// katakana unicode block ァ–ン (U+30A1U+30F6)
const KATA_START = 0x30A1;
const KATA_END = 0x30F6;
function randKata() {
return String.fromCharCode(
KATA_START + Math.floor(Math.random() * (KATA_END - KATA_START + 1))
);
}
// each column: how far down it has dripped (in rows)
// start negative so columns begin off-screen at staggered times
const drops = Array.from({ length: COLS }, () => ({
y: -(Math.random() * 10), // staggered start above top
speed: 0.1 + Math.random() * 0.5,
glyphs: [] // trail of { char, age }
}));
const TRAIL_LEN = 14; // how many glyphs in the tail
function drawFrame() {
// dim the previous frame — this creates the fade trail
matrixCtx.fillStyle = 'rgba(0, 0, 0, 0.10)';
matrixCtx.fillRect(0, 0, W, H);
matrixCtx.font = `${FONT_SIZE}px monospace`;
drops.forEach((drop, col) => {
const x = col * FONT_SIZE;
// advance drop
drop.y += drop.speed;
// push a new glyph at the head
if (drop.y >= 0) {
drop.glyphs.unshift({ char: randKata(), age: 0 });
if (drop.glyphs.length > TRAIL_LEN) drop.glyphs.pop();
}
// age each glyph and draw
drop.glyphs.forEach((g, i) => {
g.age++;
// randomly mutate the character occasionally
if (Math.random() < 0.04) g.char = randKata();
const headY = Math.floor(drop.y) - i;
if (headY < 0 || headY * FONT_SIZE > H) return;
const t = i / TRAIL_LEN; // 0 = head, 1 = tail
if (i === 0) {
// head glyph — bright white flash
matrixCtx.fillStyle = `rgba(200, 255, 220, 0.98)`;
} else if (i < 3) {
// near-head — bright green
matrixCtx.fillStyle = `rgba(100, 255, 140, ${1 - t * 0.3})`;
} else {
// tail — fade to dark green
const alpha = Math.max(0, 1 - t * 1.1);
const green = Math.floor(180 - t * 120);
matrixCtx.fillStyle = `rgba(0, ${green}, 60, ${alpha})`;
}
matrixCtx.fillText(g.char, x, headY * FONT_SIZE);
});
// reset column when it scrolls fully off the bottom
if (drop.y * FONT_SIZE > H + TRAIL_LEN * FONT_SIZE) {
drop.y = -(Math.random() * 30);
drop.speed = 0.3 + Math.random() * 0.3;
drop.glyphs = [];
}
});
if (matrixCanvas) matrixAnimFrame = requestAnimationFrame(drawFrame);
}
matrixAnimFrame = requestAnimationFrame(drawFrame);
}
function stopMatrix() {
if (!matrixCanvas) return;
matrixCanvas.style.opacity = '0';
cancelAnimationFrame(matrixAnimFrame);
setTimeout(() => {
matrixCanvas.remove();
matrixCanvas = null;
}, 1200);
}
function bladerunnerClick() {
if (document.getElementById('rain-canvas')) return; // already running, do nothing
if (document.getElementById('cure-canvas')) stopWater();
if (document.getElementById('matrix-canvas')) stopMatrix();
if (document.getElementById('typewriter-canvas')) stopTypewriter();
if (document.getElementById('lightning-canvas')) stopLightning();
const el = document.getElementById('interest');
el.textContent = 'tears in rain';
startRain();
isDay = false
applyState();
setTimeout(() => {
el.textContent = "interest";
stopRain();
}, 10000);
}
let rainCanvas, rainCtx, rainInterval;
function startRain() {
rainCanvas = document.createElement('canvas');
rainCanvas.id = 'rain-canvas';
rainCanvas.width = window.innerWidth;
rainCanvas.height = window.innerHeight;
document.body.appendChild(rainCanvas);
rainCtx = rainCanvas.getContext('2d');
requestAnimationFrame(() => { rainCanvas.style.opacity = '1'; });
const drops = Array.from({ length: 120 }, () => ({
x: Math.random() * rainCanvas.width,
y: Math.random() * rainCanvas.height,
speed: 8 + Math.random() * 7,
length: 12 + Math.random() * 20,
opacity: 0.2 + Math.random() * 0.4
}));
rainInterval = setInterval(() => {
rainCtx.clearRect(0, 0, rainCanvas.width, rainCanvas.height);
drops.forEach(drop => {
rainCtx.beginPath();
rainCtx.moveTo(drop.x, drop.y);
rainCtx.lineTo(drop.x - 2, drop.y + drop.length);
rainCtx.strokeStyle = `rgba(174, 214, 241, ${drop.opacity})`;
rainCtx.lineWidth = 3;
rainCtx.stroke();
drop.y += drop.speed;
if (drop.y > rainCanvas.height) {
drop.y = -drop.length;
drop.x = Math.random() * rainCanvas.width;
}
});
}, 30);
}
function stopRain() {
if (!rainCanvas) return;
rainCanvas.style.opacity = '0';
setTimeout(() => {
clearInterval(rainInterval);
rainCanvas.remove();
rainCanvas = null;
}, 800);
}