initial
This commit is contained in:
314
js/matrix.js
Normal file
314
js/matrix.js
Normal file
@@ -0,0 +1,314 @@
|
||||
class MatrixBackground {
|
||||
constructor() {
|
||||
this.canvas = document.getElementById('matrixCanvas');
|
||||
this.dots = [];
|
||||
this.connections = [];
|
||||
this.config = {
|
||||
dotCount: 50,
|
||||
dotSpeed: 0.2, // pixels per frame
|
||||
connectionDuration: { min: 2000, max: 6000 }, // milliseconds
|
||||
connectionChance: 0.0002, // chance per frame per dot pair
|
||||
maxConnections: 8, // maximum simultaneous connections
|
||||
dotSize: 3,
|
||||
colors: {
|
||||
dotDefault: 'rgba(255, 255, 255, 0.8)',
|
||||
dotDefaultGlow: 'rgba(255, 255, 255, 0.4)',
|
||||
dotConnected: 'rgba(100, 149, 237, 0.8)',
|
||||
dotConnectedGlow: 'rgba(100, 149, 237, 0.6)',
|
||||
connection: {
|
||||
start: 'rgba(100, 149, 237, 0)',
|
||||
middle: 'rgba(138, 43, 226, 0.8)',
|
||||
end: 'rgba(100, 149, 237, 0)'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.createDots();
|
||||
this.animate();
|
||||
this.handleResize();
|
||||
|
||||
// Handle window resize
|
||||
window.addEventListener('resize', () => this.handleResize());
|
||||
}
|
||||
|
||||
handleResize() {
|
||||
// Recreate dots with new screen dimensions
|
||||
// Clear previous DOM elements to avoid orphan/sticky dots
|
||||
this.clearCanvas();
|
||||
this.createDots();
|
||||
}
|
||||
|
||||
clearCanvas() {
|
||||
// Remove all dot and connection elements from the canvas
|
||||
try {
|
||||
while (this.canvas.firstChild) {
|
||||
this.canvas.removeChild(this.canvas.firstChild);
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
// Reset arrays
|
||||
this.dots = [];
|
||||
this.connections = [];
|
||||
}
|
||||
|
||||
createDots() {
|
||||
this.dots = [];
|
||||
for (let i = 0; i < this.config.dotCount; i++) {
|
||||
this.createDot();
|
||||
}
|
||||
}
|
||||
|
||||
createDot() {
|
||||
const dot = document.createElement('div');
|
||||
dot.className = 'matrix-dot';
|
||||
|
||||
// Random starting position within screen bounds
|
||||
const startX = Math.random() * (window.innerWidth - 20) + 10;
|
||||
const startY = Math.random() * (window.innerHeight - 20) + 10;
|
||||
|
||||
// Random movement direction (flying in all directions)
|
||||
let velocityX = 0, velocityY = 0;
|
||||
let attempts = 0;
|
||||
while ((Math.abs(velocityX) < 0.15 || Math.abs(velocityY) < 0.15) && attempts < 10) {
|
||||
const angle = Math.random() * Math.PI * 2;
|
||||
const speed = this.config.dotSpeed * (0.8 + Math.random() * 0.4); // Ensure minimum speed
|
||||
velocityX = Math.cos(angle) * speed;
|
||||
velocityY = Math.sin(angle) * speed;
|
||||
attempts++;
|
||||
}
|
||||
// Final fallback if still too low
|
||||
if (Math.abs(velocityX) < 0.15) velocityX = velocityX < 0 ? -0.15 : 0.15;
|
||||
if (Math.abs(velocityY) < 0.15) velocityY = velocityY < 0 ? -0.15 : 0.15;
|
||||
const dotData = {
|
||||
element: dot,
|
||||
x: startX,
|
||||
y: startY,
|
||||
vx: velocityX,
|
||||
vy: velocityY,
|
||||
opacity: Math.random() * 0.3 + 0.7,
|
||||
size: this.config.dotSize + Math.random() * 2,
|
||||
isConnected: false,
|
||||
connectionCount: 0,
|
||||
stuckFrames: 0 // Track how long dot is stuck
|
||||
};
|
||||
|
||||
// Set initial styles (white by default)
|
||||
dot.style.left = startX + 'px';
|
||||
dot.style.top = startY + 'px';
|
||||
dot.style.width = dotData.size + 'px';
|
||||
dot.style.height = dotData.size + 'px';
|
||||
dot.style.opacity = dotData.opacity;
|
||||
dot.style.background = this.config.colors.dotDefault;
|
||||
dot.style.boxShadow = `0 0 6px ${this.config.colors.dotDefaultGlow}, 0 0 12px rgba(255, 255, 255, 0.2)`;
|
||||
|
||||
this.canvas.appendChild(dot);
|
||||
this.dots.push(dotData);
|
||||
}
|
||||
|
||||
updateDots() {
|
||||
this.dots.forEach((dot, index) => {
|
||||
// Update position
|
||||
dot.x += dot.vx;
|
||||
dot.y += dot.vy;
|
||||
|
||||
// Track if dot is stuck (velocity very low for several frames)
|
||||
if (Math.abs(dot.vx) < 0.03 && Math.abs(dot.vy) < 0.03) {
|
||||
dot.stuckFrames = (dot.stuckFrames || 0) + 1;
|
||||
} else {
|
||||
dot.stuckFrames = 0;
|
||||
}
|
||||
// If stuck for more than 30 frames, give a strong nudge
|
||||
if (dot.stuckFrames > 30) {
|
||||
const angle = Math.random() * Math.PI * 2;
|
||||
const speed = this.config.dotSpeed * (0.8 + Math.random() * 0.4);
|
||||
dot.vx = Math.cos(angle) * speed;
|
||||
dot.vy = Math.sin(angle) * speed;
|
||||
dot.stuckFrames = 0;
|
||||
} else {
|
||||
// Prevent dots from getting stuck - add small random nudge if velocity is too low
|
||||
if (Math.abs(dot.vx) < 0.05) {
|
||||
dot.vx += (Math.random() - 0.5) * 0.1;
|
||||
}
|
||||
if (Math.abs(dot.vy) < 0.05) {
|
||||
dot.vy += (Math.random() - 0.5) * 0.1;
|
||||
}
|
||||
}
|
||||
|
||||
// Bounce off screen edges (consider dot size so centers stay inside)
|
||||
const maxX = window.innerWidth - dot.size;
|
||||
const maxY = window.innerHeight - dot.size;
|
||||
if (dot.x <= 0 || dot.x >= maxX) {
|
||||
dot.vx = -dot.vx;
|
||||
dot.x = Math.max(0, Math.min(maxX, dot.x));
|
||||
}
|
||||
if (dot.y <= 0 || dot.y >= maxY) {
|
||||
dot.vy = -dot.vy;
|
||||
dot.y = Math.max(0, Math.min(maxY, dot.y));
|
||||
}
|
||||
|
||||
// Update DOM element position (top-left)
|
||||
dot.element.style.left = dot.x + 'px';
|
||||
dot.element.style.top = dot.y + 'px';
|
||||
|
||||
// Update dot color based on connection status
|
||||
if (dot.connectionCount > 0 && !dot.isConnected) {
|
||||
dot.isConnected = true;
|
||||
dot.element.style.background = this.config.colors.dotConnected;
|
||||
dot.element.style.boxShadow = `0 0 6px ${this.config.colors.dotConnectedGlow}, 0 0 12px rgba(100, 149, 237, 0.3)`;
|
||||
} else if (dot.connectionCount === 0 && dot.isConnected) {
|
||||
dot.isConnected = false;
|
||||
dot.element.style.background = this.config.colors.dotDefault;
|
||||
dot.element.style.boxShadow = `0 0 6px ${this.config.colors.dotDefaultGlow}, 0 0 12px rgba(255, 255, 255, 0.2)`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createConnection(dot1, dot2) {
|
||||
if (this.connections.length >= this.config.maxConnections) {
|
||||
return;
|
||||
}
|
||||
|
||||
const connection = document.createElement('div');
|
||||
connection.className = 'connection-line';
|
||||
|
||||
// Calculate center positions of dots
|
||||
const dot1Rect = dot1.element.getBoundingClientRect();
|
||||
const dot2Rect = dot2.element.getBoundingClientRect();
|
||||
const canvasRect = this.canvas.getBoundingClientRect();
|
||||
const dot1CenterX = dot1Rect.left - canvasRect.left + dot1Rect.width / 2;
|
||||
const dot1CenterY = dot1Rect.top - canvasRect.top + dot1Rect.height / 2;
|
||||
const dot2CenterX = dot2Rect.left - canvasRect.left + dot2Rect.width / 2;
|
||||
const dot2CenterY = dot2Rect.top - canvasRect.top + dot2Rect.height / 2;
|
||||
|
||||
const dx = dot2CenterX - dot1CenterX;
|
||||
const dy = dot2CenterY - dot1CenterY;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
const angle = Math.atan2(dy, dx) * (180 / Math.PI);
|
||||
|
||||
// Position and style the connection line from center to center
|
||||
const lineHalf = 1;
|
||||
connection.style.left = Math.round(dot1CenterX) + 'px';
|
||||
connection.style.top = Math.round(dot1CenterY - lineHalf) + 'px';
|
||||
connection.style.width = Math.round(distance) + 'px';
|
||||
connection.style.transformOrigin = '0 50%';
|
||||
connection.style.transform = `rotate(${angle}deg)`;
|
||||
connection.style.background = `linear-gradient(90deg,
|
||||
${this.config.colors.connection.start} 0%,
|
||||
${this.config.colors.connection.middle} 50%,
|
||||
${this.config.colors.connection.end} 100%)`;
|
||||
|
||||
this.canvas.appendChild(connection);
|
||||
|
||||
// Fade in
|
||||
setTimeout(() => {
|
||||
connection.classList.add('active');
|
||||
}, 50);
|
||||
|
||||
const connectionData = {
|
||||
element: connection,
|
||||
dot1: dot1,
|
||||
dot2: dot2,
|
||||
startTime: Date.now(),
|
||||
duration: Math.random() * (this.config.connectionDuration.max - this.config.connectionDuration.min) + this.config.connectionDuration.min
|
||||
};
|
||||
|
||||
// Mark dots as connected
|
||||
dot1.connectionCount++;
|
||||
dot2.connectionCount++;
|
||||
|
||||
this.connections.push(connectionData);
|
||||
}
|
||||
|
||||
updateConnections() {
|
||||
this.connections.forEach((connection, index) => {
|
||||
const elapsed = Date.now() - connection.startTime;
|
||||
|
||||
// Update connection position as dots move (using center positions)
|
||||
const dot1Rect = connection.dot1.element.getBoundingClientRect();
|
||||
const dot2Rect = connection.dot2.element.getBoundingClientRect();
|
||||
const canvasRect = this.canvas.getBoundingClientRect();
|
||||
const dot1CenterX = dot1Rect.left - canvasRect.left + dot1Rect.width / 2;
|
||||
const dot1CenterY = dot1Rect.top - canvasRect.top + dot1Rect.height / 2;
|
||||
const dot2CenterX = dot2Rect.left - canvasRect.left + dot2Rect.width / 2;
|
||||
const dot2CenterY = dot2Rect.top - canvasRect.top + dot2Rect.height / 2;
|
||||
|
||||
const dx = dot2CenterX - dot1CenterX;
|
||||
const dy = dot2CenterY - dot1CenterY;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
const angle = Math.atan2(dy, dx) * (180 / Math.PI);
|
||||
|
||||
const lineHalf = 1;
|
||||
connection.element.style.left = Math.round(dot1CenterX) + 'px';
|
||||
connection.element.style.top = Math.round(dot1CenterY - lineHalf) + 'px';
|
||||
connection.element.style.width = Math.round(distance) + 'px';
|
||||
connection.element.style.transformOrigin = '0 50%';
|
||||
connection.element.style.transform = `rotate(${angle}deg)`;
|
||||
|
||||
// Remove expired connections
|
||||
if (elapsed > connection.duration) {
|
||||
connection.element.classList.remove('active');
|
||||
|
||||
// Mark dots as no longer connected
|
||||
connection.dot1.connectionCount--;
|
||||
connection.dot2.connectionCount--;
|
||||
|
||||
setTimeout(() => {
|
||||
connection.element.remove();
|
||||
}, 500); // Wait for fade out
|
||||
this.connections.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tryCreateConnections() {
|
||||
// Randomly try to create connections between nearby dots
|
||||
for (let i = 0; i < this.dots.length; i++) {
|
||||
for (let j = i + 1; j < this.dots.length; j++) {
|
||||
if (Math.random() < this.config.connectionChance) {
|
||||
const dot1 = this.dots[i];
|
||||
const dot2 = this.dots[j];
|
||||
|
||||
// Calculate distance between dot centers
|
||||
const dot1CenterX = dot1.x + dot1.size / 2;
|
||||
const dot1CenterY = dot1.y + dot1.size / 2;
|
||||
const dot2CenterX = dot2.x + dot2.size / 2;
|
||||
const dot2CenterY = dot2.y + dot2.size / 2;
|
||||
|
||||
const dx = dot2CenterX - dot1CenterX;
|
||||
const dy = dot2CenterY - dot1CenterY;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < 150 && distance > 30) {
|
||||
// Check if these dots are already connected
|
||||
const alreadyConnected = this.connections.some(conn =>
|
||||
(conn.dot1 === dot1 && conn.dot2 === dot2) ||
|
||||
(conn.dot1 === dot2 && conn.dot2 === dot1)
|
||||
);
|
||||
|
||||
if (!alreadyConnected) {
|
||||
this.createConnection(dot1, dot2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
animate() {
|
||||
this.updateDots();
|
||||
this.updateConnections();
|
||||
this.tryCreateConnections();
|
||||
|
||||
requestAnimationFrame(() => this.animate());
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the matrix background when the DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new MatrixBackground();
|
||||
});
|
60
js/social.js
Normal file
60
js/social.js
Normal file
@@ -0,0 +1,60 @@
|
||||
|
||||
// Initialize the matrix background when the DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new MatrixBackground();
|
||||
|
||||
// Email copy-to-clipboard and tooltip logic
|
||||
const emailLink = document.getElementById('email-link');
|
||||
const emailTooltip = document.getElementById('email-tooltip');
|
||||
const obfuscatedEmail = 'Bg4aC0IJDAAeCBgbCU8AGA0PKQkBAAACQgIGAw=='; // generated through utils/xor-enc.py
|
||||
const xorKey = 'lain';
|
||||
|
||||
function decodeEmail(obf, key) {
|
||||
// Double base64 decode
|
||||
let b64 = atob(obf);
|
||||
// XOR decode
|
||||
let email = '';
|
||||
for (let i = 0; i < b64.length; i++) {
|
||||
email += String.fromCharCode(b64.charCodeAt(i) ^ key.charCodeAt(i % key.length));
|
||||
}
|
||||
return email;
|
||||
}
|
||||
const emailAddress = decodeEmail(obfuscatedEmail, xorKey);
|
||||
|
||||
if (emailLink && emailTooltip) {
|
||||
emailLink.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
// Copy email to clipboard
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(emailAddress);
|
||||
} else {
|
||||
// Fallback for older browsers
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = emailAddress;
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
|
||||
// Position tooltip above email link
|
||||
const emailRect = emailLink.getBoundingClientRect();
|
||||
const socialLinksRect = emailLink.parentElement.getBoundingClientRect();
|
||||
|
||||
const left = emailRect.left - socialLinksRect.left + (emailRect.width / 2);
|
||||
const top = emailRect.top - socialLinksRect.top - 10;
|
||||
|
||||
emailTooltip.style.left = left + 'px';
|
||||
emailTooltip.style.top = top + 'px';
|
||||
emailTooltip.style.transform = 'translateX(-50%) translateY(-75%)';
|
||||
|
||||
// Show tooltip
|
||||
emailTooltip.style.opacity = '1';
|
||||
|
||||
// Hide after 1.5s
|
||||
setTimeout(() => {
|
||||
emailTooltip.style.opacity = '0';
|
||||
}, 1500);
|
||||
});
|
||||
}
|
||||
});
|
Reference in New Issue
Block a user