Files
personal-site/static/about.js
T
2026-05-24 21:35:04 +02:00

461 lines
15 KiB
JavaScript

const toggle = document.getElementById('toggle');
const track = document.getElementById('track');
const knob = document.getElementById('knob');
const rays = document.getElementById('rays');
const stateLabel = document.getElementById('state-label');
const themeRoot = document.getElementById('themeRoot');
let isDay = true;
const RAY_COUNT = 8;
for (let i = 0; i < RAY_COUNT; i++) {
const r = document.createElement('div');
r.className = 'ray';
r.style.transform = `rotate(${i * (360/RAY_COUNT)}deg)`;
rays.appendChild(r);
}
function applyState() {
if (isDay) {
track.className = 'track day';
knob.className = 'knob day';
toggle.setAttribute('aria-checked', 'true');
themeRoot.classList.remove('night');
} else {
track.className = 'track night';
knob.className = 'knob night';
toggle.setAttribute('aria-checked', 'false');
themeRoot.classList.add('night');
}
}
toggle.addEventListener('click', () => { isDay = !isDay; applyState(); });
toggle.addEventListener('keydown', e => {
if (e.key === ' ' || e.key === 'Enter') { e.preventDefault(); isDay = !isDay; applyState(); }
});
function copyClass() {
navigator.clipboard.writeText(classOut.textContent).catch(() => {});
}
// 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() {
const alicegameSection = document.getElementById('alice');
const tetrisSection = document.getElementById('tetris');
const experiencesection = document.getElementById('experience');
const educationsection = document.getElementById('education');
const automationsection = document.getElementById('automation');
const interestssection = document.getElementById('interests');
const opinionsection = document.getElementById('opinion');
const interestlistsection = document.getElementById('interestlist');
const hiddenssection = document.getElementById('secret');
if (hiddenssection.style.display == 'none') {
hiddenssection.style.display = 'block';
} else {
hiddenssection.style.display = 'none';
}
alicegameSection.style.display = 'none';
tetrisSection.style.display = 'none';
experiencesection.style.display = 'none';
educationsection.style.display = 'none';
automationsection.style.display = 'none';
interestssection.style.display = 'none';
opinionsection.style.display = 'none';
interestlistsection.style.display = 'none';
}