all movies have animations
This commit is contained in:
+4
-28
@@ -247,7 +247,7 @@
|
|||||||
<img src="/static/posters/Dracula.webp" alt="Dracula: A Love Tale">
|
<img src="/static/posters/Dracula.webp" alt="Dracula: A Love Tale">
|
||||||
<p>Dracula: A Love Tale</p>
|
<p>Dracula: A Love Tale</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="poster-item posterhover" onclick="dragonClick()">
|
<div class="poster-item posterhover" onclick="dragonTypewriterClick()">
|
||||||
<img src="/static/posters/TheGirlwiththeDragonTattoo.webp" alt="Girl with a Dragon Tattoos">
|
<img src="/static/posters/TheGirlwiththeDragonTattoo.webp" alt="Girl with a Dragon Tattoos">
|
||||||
<p>Girl with a Dragon Tattoo</p>
|
<p>Girl with a Dragon Tattoo</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -259,8 +259,8 @@
|
|||||||
<div class="toplist-body">
|
<div class="toplist-body">
|
||||||
<div class="poster-grid">
|
<div class="poster-grid">
|
||||||
<div class="poster-item">
|
<div class="poster-item">
|
||||||
<img src="/static/posters/Succession.webp" alt="Succession">
|
<img src="/static/posters/foundation.jpg" alt="foundation">
|
||||||
<p>Succession</p>
|
<p>Foundation</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="poster-item">
|
<div class="poster-item">
|
||||||
<img src="/static/posters/Chernobyl.webp" alt="Chernobyl">
|
<img src="/static/posters/Chernobyl.webp" alt="Chernobyl">
|
||||||
@@ -290,32 +290,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section style="display: none;" id="alice" class="play" >
|
|
||||||
<br>
|
|
||||||
<div class="section-head"><h2>Want to play a game Alice?!</h2></div>
|
|
||||||
<div class="story-text" id="story-text"></div>
|
|
||||||
<div class="choices" id="choices"></div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<script src="/static/scripts/game.js"></script>
|
|
||||||
|
|
||||||
<section style="display: none;" id="tetris" >
|
|
||||||
<h2>This game is</h2>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section id="secret" class="Hitchhiker">
|
|
||||||
<div class="number">42</div>
|
|
||||||
<div class="divider"></div>
|
|
||||||
<p class="quote">So long, and thanks for all the fish.</p>
|
|
||||||
<p class="attribution">Douglas Adams · The Hitchhiker's Guide to the Galaxy</p>
|
|
||||||
|
|
||||||
<div class="secret-hint">
|
|
||||||
<p>shhh. i have a secret to share — <a href="/secret">follow me</a></p>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
|
|
||||||
<script src="/static/scripts/tetris.js"></script>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -330,5 +305,6 @@
|
|||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script src="/static/about.js" ></script>
|
<script src="/static/about.js" ></script>
|
||||||
|
<script src="/static/scripts/posteranimation.js" ></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
-388
@@ -40,394 +40,6 @@
|
|||||||
// just some fun.
|
// just some fun.
|
||||||
console.log("%c👀 hi there!", "font-size:20px")
|
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() {
|
function unhide42() {
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 939 KiB |
@@ -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);
|
||||||
|
}
|
||||||
+1
-1
@@ -197,7 +197,7 @@ found here by: <span class="o-pink">being curious</span> (good trait btw)`,
|
|||||||
<span class="o-pink"> ♡</span> linux ricing
|
<span class="o-pink"> ♡</span> linux ricing
|
||||||
<span class="o-pink"> ♡</span> Japanese Katakana typography
|
<span class="o-pink"> ♡</span> Japanese Katakana typography
|
||||||
<span class="o-pink"> ♡</span> number go up. But Fuck btc, lol
|
<span class="o-pink"> ♡</span> number go up. But Fuck btc, lol
|
||||||
<span class="o-pink"> ♡</span> praying yto loard gaben. If you know, you know
|
<span class="o-pink"> ♡</span> praying to loard gaben. If you know, you know
|
||||||
<span class="o-pink"> ♡</span> building useless website pages, clearly`,
|
<span class="o-pink"> ♡</span> building useless website pages, clearly`,
|
||||||
|
|
||||||
42: () => `<span class="o-bright">"The Answer to the Ultimate Question of Life,
|
42: () => `<span class="o-bright">"The Answer to the Ultimate Question of Life,
|
||||||
|
|||||||
Reference in New Issue
Block a user