it was weird. i pressed delete on a subfolder, i think one of the pages.off folders that i was using. and then, suddenly, nvim on windows 7 decided to delete every file in the directory. they weren't shred off the space time continuum, but just marked deleted. i had to pay $80 to get access to a software that could see them. bleh! just seeing all my work, a little over a week, was pretty heart shattering. but i remembered that long ago, a close friend said i could call them whenever i was feeling sad. i finally took them up on that offer. the first time i've ever called someone for emotional support. but it's ok. i got it back. and the site framework is better than ever. i'm gonna commit and push more often. the repo is private anyways.
197 lines
5.8 KiB
TypeScript
197 lines
5.8 KiB
TypeScript
// Vibe coded with AI
|
|
(globalThis as any).canvas_2020 = function (canvas: HTMLCanvasElement) {
|
|
const isStandalone = canvas.getAttribute("data-standalone") === "true";
|
|
// Rain effect with slanted lines
|
|
// Configuration interface for the rain effect
|
|
interface RainConfig {
|
|
fps: number; // frames per second
|
|
color: string; // color of rain particles
|
|
angle: number; // angle in degrees
|
|
particleDensity: number; // particles per 10000 pixels of canvas area
|
|
speed: number; // speed of particles (pixels per frame)
|
|
lineWidth: number; // thickness of rain lines
|
|
lineLength: number; // length of rain lines
|
|
}
|
|
|
|
// Rain particle interface
|
|
interface RainParticle {
|
|
x: number; // x position
|
|
y: number; // y position
|
|
}
|
|
|
|
// Default configuration
|
|
const config: RainConfig = {
|
|
fps: 16,
|
|
color: isStandalone ? "#00FEFB99" : "#081F24",
|
|
angle: -18,
|
|
particleDensity: 1,
|
|
speed: 400,
|
|
lineWidth: 8,
|
|
lineLength: 100,
|
|
};
|
|
|
|
// Get the canvas context
|
|
const ctx = canvas.getContext("2d");
|
|
if (!ctx) {
|
|
console.error("Could not get canvas context");
|
|
return () => {};
|
|
}
|
|
|
|
// Make canvas transparent
|
|
if (isStandalone) {
|
|
canvas.style.backgroundColor = "#0F252B";
|
|
} else {
|
|
canvas.style.backgroundColor = "transparent";
|
|
}
|
|
|
|
// Calculate canvas dimensions and update when resized
|
|
let width = canvas.width;
|
|
let height = canvas.height;
|
|
let particles: RainParticle[] = [];
|
|
let animationFrameId: number;
|
|
let lastFrameTime = 0;
|
|
const frameInterval = 1000 / config.fps;
|
|
|
|
// Calculate angle in radians
|
|
const angleRad = (config.angle * Math.PI) / 180;
|
|
|
|
// Update canvas dimensions and particle count when resized
|
|
const updateDimensions = () => {
|
|
width = canvas.width = canvas.offsetWidth;
|
|
height = canvas.height = canvas.offsetHeight;
|
|
|
|
// Calculate the canvas area in pixels
|
|
const canvasArea = width * height;
|
|
|
|
// Calculate target number of particles based on canvas area
|
|
const targetParticleCount = Math.floor(
|
|
(canvasArea / 10000) * config.particleDensity,
|
|
);
|
|
|
|
// Calculate buffer for horizontal offset due to slanted angle
|
|
const buffer = Math.abs(height * Math.tan(angleRad)) + config.lineLength;
|
|
|
|
// Adjust the particles array
|
|
if (particles.length < targetParticleCount) {
|
|
// Add more particles if needed
|
|
for (let i = particles.length; i < targetParticleCount; i++) {
|
|
particles.push(createParticle(true, buffer));
|
|
}
|
|
} else if (particles.length > targetParticleCount) {
|
|
// Remove excess particles
|
|
particles = particles.slice(0, targetParticleCount);
|
|
}
|
|
};
|
|
|
|
// Create a new particle
|
|
// Added initialDistribution parameter to distribute particles across the entire canvas at startup
|
|
const createParticle = (
|
|
initialDistribution = false,
|
|
buffer: number,
|
|
): RainParticle => {
|
|
// For initial distribution, place particles throughout the canvas
|
|
// Otherwise start them above the canvas
|
|
let x = Math.random() * (width + buffer * 2) - buffer;
|
|
let y;
|
|
|
|
if (initialDistribution) {
|
|
// Distribute across the entire canvas height for initial setup
|
|
y = Math.random() * (height + config.lineLength * 2) - config.lineLength;
|
|
} else {
|
|
// Start new particles from above the canvas with some randomization
|
|
y = -config.lineLength - (Math.random() * config.lineLength * 20);
|
|
}
|
|
|
|
return {
|
|
x,
|
|
y,
|
|
};
|
|
};
|
|
|
|
// Update particle positions
|
|
const updateParticles = () => {
|
|
// Calculate buffer for horizontal offset due to slanted angle
|
|
const buffer = Math.abs(height * Math.tan(angleRad)) + config.lineLength;
|
|
|
|
for (let i = 0; i < particles.length; i++) {
|
|
const p = particles[i];
|
|
|
|
// Update position based on speed and angle
|
|
p.x += Math.sin(angleRad) * config.speed;
|
|
p.y += Math.cos(angleRad) * config.speed;
|
|
|
|
// Reset particles that go offscreen - only determined by position
|
|
// Add extra buffer to ensure particles fully exit the visible area before resetting
|
|
if (
|
|
p.y > height + config.lineLength ||
|
|
p.x < -buffer ||
|
|
p.x > width + buffer
|
|
) {
|
|
particles[i] = createParticle(false, buffer);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Draw particles
|
|
const drawParticles = () => {
|
|
// Clear canvas
|
|
ctx.clearRect(0, 0, width, height);
|
|
|
|
// Set drawing properties
|
|
ctx.strokeStyle = config.color;
|
|
ctx.lineWidth = config.lineWidth;
|
|
ctx.lineCap = "square";
|
|
|
|
// Draw each rain line
|
|
ctx.beginPath();
|
|
for (const p of particles) {
|
|
// Only draw particles that are either on screen or within a reasonable buffer
|
|
// This is for performance reasons - we don't need to draw particles far offscreen
|
|
if (p.y >= -config.lineLength * 2 && p.y <= height + config.lineLength) {
|
|
const endX = p.x + Math.sin(angleRad) * config.lineLength;
|
|
const endY = p.y + Math.cos(angleRad) * config.lineLength;
|
|
|
|
ctx.moveTo(p.x, p.y);
|
|
ctx.lineTo(endX, endY);
|
|
}
|
|
}
|
|
ctx.stroke();
|
|
};
|
|
|
|
// Animation loop
|
|
const animate = (currentTime: number) => {
|
|
animationFrameId = requestAnimationFrame(animate);
|
|
|
|
// Control frame rate
|
|
if (currentTime - lastFrameTime < frameInterval) {
|
|
return;
|
|
}
|
|
|
|
lastFrameTime = currentTime;
|
|
|
|
updateParticles();
|
|
drawParticles();
|
|
};
|
|
|
|
// Initialize the animation
|
|
const init = () => {
|
|
// Set up resize handler
|
|
globalThis.addEventListener("resize", updateDimensions);
|
|
|
|
// Initial setup
|
|
updateDimensions();
|
|
|
|
// Start animation
|
|
lastFrameTime = performance.now();
|
|
animationFrameId = requestAnimationFrame(animate);
|
|
};
|
|
|
|
// Start the animation
|
|
init();
|
|
|
|
// Return cleanup function
|
|
return () => {
|
|
globalThis.removeEventListener("resize", updateDimensions);
|
|
cancelAnimationFrame(animationFrameId);
|
|
};
|
|
};
|