180 lines
6.1 KiB
JavaScript
180 lines
6.1 KiB
JavaScript
// Main JS file for blog interactivity
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// Show toast message from URL parameter if present
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const msg = urlParams.get('msg');
|
|
|
|
if (msg) {
|
|
showToast(msg);
|
|
}
|
|
});
|
|
|
|
function showToast(message) {
|
|
const toast = document.createElement('div');
|
|
toast.className = 'toast';
|
|
toast.textContent = message;
|
|
toast.style.display = 'block';
|
|
document.body.appendChild(toast);
|
|
|
|
setTimeout(() => {
|
|
toast.style.opacity = '0';
|
|
toast.style.transition = 'opacity 0.5s';
|
|
setTimeout(() => toast.remove(), 500);
|
|
}, 3000);
|
|
}
|
|
|
|
// --- Interactive Background ---
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
initInteractiveBackground();
|
|
});
|
|
|
|
function initInteractiveBackground() {
|
|
// 1. Mouse Glow & Parallax shapes
|
|
const mouseShape = document.querySelector('.shape-mouse');
|
|
const bgShapes = document.querySelectorAll('.bg-shapes .shape:not(.shape-mouse)');
|
|
|
|
let mouseX = window.innerWidth / 2;
|
|
let mouseY = window.innerHeight / 2;
|
|
let currentX = mouseX;
|
|
let currentY = mouseY;
|
|
|
|
// Parallax variables
|
|
const windowCenterX = window.innerWidth / 2;
|
|
const windowCenterY = window.innerHeight / 2;
|
|
|
|
document.addEventListener('mousemove', (e) => {
|
|
mouseX = e.clientX;
|
|
mouseY = e.clientY;
|
|
|
|
// Make the mouse glow visible once mouse moves
|
|
if (mouseShape && mouseShape.style.opacity === '0') {
|
|
mouseShape.style.opacity = '1';
|
|
}
|
|
|
|
// Simple parallax for the background blobs
|
|
bgShapes.forEach(shape => {
|
|
const speed = parseFloat(shape.getAttribute('data-speed') || 1);
|
|
const x = (windowCenterX - mouseX) * speed * 0.05;
|
|
const y = (windowCenterY - mouseY) * speed * 0.05;
|
|
// Use translation combined with the existing animation (in CSS, we'll just layer it)
|
|
// Note: Since css animation overrides transform, we can apply parallax translation
|
|
// by updating margin or a nested div. Alternatively, since shape-1 and shape-2
|
|
// have keyframes using transform, we shouldn't overwrite transform directly.
|
|
// Let's use margin instead for a simple offset!
|
|
shape.style.marginLeft = `${x}px`;
|
|
shape.style.marginTop = `${y}px`;
|
|
});
|
|
});
|
|
|
|
// Smooth follow for the glowing orb
|
|
function animateGlow() {
|
|
if (mouseShape) {
|
|
currentX += (mouseX - currentX) * 0.1;
|
|
currentY += (mouseY - currentY) * 0.1;
|
|
// Center the 300x300 shape on the cursor (so subtract half width/height)
|
|
mouseShape.style.transform = `translate(${currentX}px, ${currentY}px)`;
|
|
}
|
|
requestAnimationFrame(animateGlow);
|
|
}
|
|
animateGlow();
|
|
|
|
// 2. Interactive Canvas Particles
|
|
const canvas = document.getElementById('bg-canvas');
|
|
if (!canvas) return;
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
let particles = [];
|
|
|
|
function resizeCanvas() {
|
|
canvas.width = window.innerWidth;
|
|
canvas.height = window.innerHeight;
|
|
initParticles();
|
|
}
|
|
|
|
class Particle {
|
|
constructor() {
|
|
this.x = Math.random() * canvas.width;
|
|
this.y = Math.random() * canvas.height;
|
|
this.size = Math.random() * 2 + 0.5;
|
|
this.baseX = this.x;
|
|
this.baseY = this.y;
|
|
this.density = (Math.random() * 30) + 1;
|
|
this.speedX = (Math.random() - 0.5) * 0.5;
|
|
this.speedY = (Math.random() - 0.5) * 0.5;
|
|
}
|
|
|
|
update() {
|
|
this.x += this.speedX;
|
|
this.y += this.speedY;
|
|
|
|
// Bounce off edges
|
|
if (this.x > canvas.width || this.x < 0) this.speedX = -this.speedX;
|
|
if (this.y > canvas.height || this.y < 0) this.speedY = -this.speedY;
|
|
|
|
// Mouse interaction (repel)
|
|
let dx = mouseX - this.x;
|
|
let dy = mouseY - this.y;
|
|
let distance = Math.sqrt(dx * dx + dy * dy);
|
|
let forceDirectionX = dx / distance;
|
|
let forceDirectionY = dy / distance;
|
|
let maxDistance = 150;
|
|
let force = (maxDistance - distance) / maxDistance;
|
|
let directionX = forceDirectionX * force * this.density;
|
|
let directionY = forceDirectionY * force * this.density;
|
|
|
|
if (distance < maxDistance) {
|
|
this.x -= directionX;
|
|
this.y -= directionY;
|
|
}
|
|
}
|
|
|
|
draw() {
|
|
ctx.fillStyle = 'rgba(37, 99, 235, 0.4)';
|
|
ctx.beginPath();
|
|
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
}
|
|
}
|
|
|
|
function initParticles() {
|
|
particles = [];
|
|
let numParticles = Math.min((canvas.width * canvas.height) / 10000, 100); // cap at 100 for perf
|
|
for (let i = 0; i < numParticles; i++) {
|
|
particles.push(new Particle());
|
|
}
|
|
}
|
|
|
|
function animateParticles() {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
|
for (let i = 0; i < particles.length; i++) {
|
|
particles[i].update();
|
|
particles[i].draw();
|
|
|
|
// Connect particles
|
|
for (let j = i; j < particles.length; j++) {
|
|
let dx = particles[i].x - particles[j].x;
|
|
let dy = particles[i].y - particles[j].y;
|
|
let distance = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
if (distance < 120) {
|
|
ctx.beginPath();
|
|
ctx.strokeStyle = `rgba(37, 99, 235, ${0.1 * (1 - distance/120)})`;
|
|
ctx.lineWidth = 1;
|
|
ctx.moveTo(particles[i].x, particles[i].y);
|
|
ctx.lineTo(particles[j].x, particles[j].y);
|
|
ctx.stroke();
|
|
ctx.closePath();
|
|
}
|
|
}
|
|
}
|
|
requestAnimationFrame(animateParticles);
|
|
}
|
|
|
|
window.addEventListener('resize', resizeCanvas);
|
|
resizeCanvas();
|
|
animateParticles();
|
|
}
|