diff --git a/static/about.html b/static/about.html index 96ac742..d1a2824 100644 --- a/static/about.html +++ b/static/about.html @@ -247,7 +247,7 @@ Dracula: A Love Tale

Dracula: A Love Tale

-
+
Girl with a Dragon Tattoos

Girl with a Dragon Tattoo

@@ -259,8 +259,8 @@
- Succession -

Succession

+ foundation +

Foundation

Chernobyl @@ -290,32 +290,7 @@

- - - - - -
-
42
-
-

So long, and thanks for all the fish.

-

Douglas Adams  ยท  The Hitchhiker's Guide to the Galaxy

- -
-

shhh. i have a secret to share — follow me

-
-
- - -
@@ -330,5 +305,6 @@ + \ No newline at end of file diff --git a/static/about.js b/static/about.js index dd0eab4..7a494c2 100644 --- a/static/about.js +++ b/static/about.js @@ -40,394 +40,6 @@ // just some fun. console.log("%c๐Ÿ‘€ hi there!", "font-size:20px") -function cureClick() { - if (document.getElementById('cure-canvas')) return; - 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; - const el = document.getElementById('interest'); - el.textContent = 'children of the night'; - 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 matrixClick() { - const sub = document.getElementById('interest-sub'); - sub.style.display = "none" - const matrixsub = document.getElementById('matrix-hidden'); - matrixsub.style.display = "block" - - setTimeout(() => { - matrixsub.style.display = "none" - sub.style.display = "block" - }, 15000); - } - - - function bladerunnerClick() { - if (document.getElementById('rain-canvas')) return; // already running, do nothing - - 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); - } - - function unhideGame() { - const alicegameSection = document.getElementById('alice'); - const gameSection = document.getElementById('tetris'); - if (alicegameSection.style.display == 'none') { - alicegameSection.style.display = 'block'; - } else { - alicegameSection.style.display = 'none'; - } - gameSection.style.display = 'none'; - - } - - function unhideTetris() { - const alicegameSection = document.getElementById('alice'); - const gameSection = document.getElementById('tetris'); - if (gameSection.style.display == 'none') { - gameSection.style.display = 'block'; - } else { - gameSection.style.display = 'none'; - } - alicegameSection.style.display = 'none'; - } -function dragonClick() { - const existing = document.getElementById('dragon-grid'); - if (existing) { existing.remove(); return; } - - const grid = document.createElement('canvas'); - grid.id = 'dragon-grid'; - grid.width = window.innerWidth; - grid.height = window.innerHeight; - grid.style.transition = 'opacity 1s ease'; - document.body.appendChild(grid); - - const ctx = grid.getContext('2d'); - const img = new Image(); - img.src = '/static/images/dragon.png'; - - img.onload = () => { - const size = 250; - const cols = Math.ceil(grid.width / size); - const rows = Math.ceil(grid.height / size); - - for (let r = 0; r < rows; r++) { - for (let c = 0; c < cols; c++) { - ctx.drawImage(img, c * size, r * size, size, size); - } - } - - setTimeout(() => { - grid.style.opacity = '0'; - setTimeout(() => grid.remove(), 1000); - }, 300); - }; -} function unhide42() { diff --git a/static/posters/foundation.jpg b/static/posters/foundation.jpg new file mode 100644 index 0000000..7edf378 Binary files /dev/null and b/static/posters/foundation.jpg differ diff --git a/static/scripts/posteranimation.js b/static/scripts/posteranimation.js new file mode 100644 index 0000000..eeb768c --- /dev/null +++ b/static/scripts/posteranimation.js @@ -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+30A1โ€“U+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); + } \ No newline at end of file diff --git a/static/secret.html b/static/secret.html index 862bb11..90c113b 100644 --- a/static/secret.html +++ b/static/secret.html @@ -197,7 +197,7 @@ found here by: being curious (good trait btw)`, โ™ก linux ricing โ™ก Japanese Katakana typography โ™ก number go up. But Fuck btc, lol - โ™ก praying yto loard gaben. If you know, you know + โ™ก praying to loard gaben. If you know, you know โ™ก building useless website pages, clearly`, 42: () => `"The Answer to the Ultimate Question of Life,