/* =================================================== JAESWIFT.XYZ — Main JavaScript Animations, particles, typing, scroll effects =================================================== */ (function () { 'use strict'; // ─── CONFIG ─── const CONFIG = { typingStrings: [ 'Developer // Tinkerer // Builder', 'Self-hosted everything.', 'Linux enthusiast since day one.', 'Building the future, one commit at a time.', 'root@jaeswift:~# echo "Hello, World"', 'Cybersecurity & Infrastructure.', 'AI Agent Operator.', ], typingSpeed: 60, typingDeleteSpeed: 30, typingPause: 2500, particleCount: 80, particleMaxSpeed: 0.3, particleConnectionDist: 120, startTime: Date.now(), }; // ─── UTILITIES ─── const $ = (sel, ctx = document) => ctx.querySelector(sel); const $$ = (sel, ctx = document) => [...ctx.querySelectorAll(sel)]; // ─── NAVBAR ─── function initNavbar() { const navbar = $('#navbar'); const toggle = $('#navToggle'); const menu = $('#navMenu'); const navItems = $$('.nav-item'); // Scroll effect let lastScroll = 0; window.addEventListener('scroll', () => { const scrollY = window.scrollY; navbar.classList.toggle('scrolled', scrollY > 50); lastScroll = scrollY; }, { passive: true }); // Mobile toggle toggle.addEventListener('click', () => { menu.classList.toggle('active'); toggle.classList.toggle('active'); }); // Mobile dropdown toggle if (window.innerWidth <= 768) { navItems.forEach(item => { const link = item.querySelector('.nav-link'); link.addEventListener('click', (e) => { if (item.querySelector('.dropdown')) { e.preventDefault(); item.classList.toggle('active'); } }); }); } // Active link on scroll const sections = $$('section[id]'); window.addEventListener('scroll', () => { const scrollPos = window.scrollY + 100; sections.forEach(section => { const top = section.offsetTop; const height = section.offsetHeight; const id = section.getAttribute('id'); if (scrollPos >= top && scrollPos < top + height) { $$('.nav-link').forEach(l => l.classList.remove('active')); const activeLink = $(`.nav-link[href="#${id}"]`); if (activeLink) activeLink.classList.add('active'); } }); }, { passive: true }); // Smooth scroll for nav links $$('.nav-link, .dropdown a[href^="#"]').forEach(link => { link.addEventListener('click', (e) => { const href = link.getAttribute('href'); if (href && href.startsWith('#')) { e.preventDefault(); const target = $(href); if (target) { target.scrollIntoView({ behavior: 'smooth', block: 'start' }); menu.classList.remove('active'); toggle.classList.remove('active'); } } }); }); } // ─── LIVE CLOCK ─── function initClock() { const navTime = $('#navTime'); if (!navTime) return; function update() { const now = new Date(); const h = String(now.getHours()).padStart(2, '0'); const m = String(now.getMinutes()).padStart(2, '0'); const s = String(now.getSeconds()).padStart(2, '0'); navTime.textContent = `${h}:${m}:${s}`; } update(); setInterval(update, 1000); } // ─── UPTIME COUNTER ─── function initUptime() { const uptimeEl = $('#uptime'); if (!uptimeEl) return; function update() { const elapsed = Date.now() - CONFIG.startTime; const totalSec = Math.floor(elapsed / 1000); const days = Math.floor(totalSec / 86400); const hours = Math.floor((totalSec % 86400) / 3600); const mins = Math.floor((totalSec % 3600) / 60); uptimeEl.textContent = `${days}d ${String(hours).padStart(2, '0')}h ${String(mins).padStart(2, '0')}m`; } update(); setInterval(update, 60000); } // ─── TYPING EFFECT ─── function initTyping() { const el = $('#typingText'); if (!el) return; let stringIndex = 0; let charIndex = 0; let isDeleting = false; function type() { const current = CONFIG.typingStrings[stringIndex]; if (isDeleting) { el.textContent = current.substring(0, charIndex - 1); charIndex--; } else { el.textContent = current.substring(0, charIndex + 1); charIndex++; } let delay = isDeleting ? CONFIG.typingDeleteSpeed : CONFIG.typingSpeed; if (!isDeleting && charIndex === current.length) { delay = CONFIG.typingPause; isDeleting = true; } else if (isDeleting && charIndex === 0) { isDeleting = false; stringIndex = (stringIndex + 1) % CONFIG.typingStrings.length; delay = 400; } setTimeout(type, delay); } setTimeout(type, 1200); } // ─── SCROLL REVEAL ─── function initScrollReveal() { const elements = $$('[data-animate]'); if (!elements.length) return; const observer = new IntersectionObserver((entries) => { entries.forEach((entry, index) => { if (entry.isIntersecting) { setTimeout(() => { entry.target.classList.add('visible'); }, index * 100); observer.unobserve(entry.target); } }); }, { threshold: 0.1, rootMargin: '0px 0px -50px 0px' }); elements.forEach(el => observer.observe(el)); } // ─── SKILL BARS ANIMATION ─── function initSkillBars() { const bars = $$('.bar-fill'); if (!bars.length) return; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const bar = entry.target; const width = bar.getAttribute('data-width'); bar.style.setProperty('--target-width', width + '%'); bar.classList.add('animated'); observer.unobserve(bar); } }); }, { threshold: 0.5 }); bars.forEach(bar => observer.observe(bar)); } // ─── PARTICLE SYSTEM ─── function initParticles() { const canvas = $('#particles'); if (!canvas) return; const ctx = canvas.getContext('2d'); let particles = []; let animationId; let mouseX = -1000; let mouseY = -1000; function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; } function createParticle() { return { x: Math.random() * canvas.width, y: Math.random() * canvas.height, vx: (Math.random() - 0.5) * CONFIG.particleMaxSpeed, vy: (Math.random() - 0.5) * CONFIG.particleMaxSpeed, size: Math.random() * 1.5 + 0.5, opacity: Math.random() * 0.5 + 0.1, }; } function initParticleArray() { particles = []; for (let i = 0; i < CONFIG.particleCount; i++) { particles.push(createParticle()); } } function drawParticle(p) { ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); ctx.fillStyle = `rgba(0, 255, 200, ${p.opacity})`; ctx.fill(); } function drawConnections() { for (let i = 0; i < particles.length; i++) { for (let j = i + 1; j < particles.length; j++) { const dx = particles[i].x - particles[j].x; const dy = particles[i].y - particles[j].y; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < CONFIG.particleConnectionDist) { const opacity = (1 - dist / CONFIG.particleConnectionDist) * 0.15; ctx.beginPath(); ctx.moveTo(particles[i].x, particles[i].y); ctx.lineTo(particles[j].x, particles[j].y); ctx.strokeStyle = `rgba(0, 255, 200, ${opacity})`; ctx.lineWidth = 0.5; ctx.stroke(); } } } // Mouse connections particles.forEach(p => { const dx = p.x - mouseX; const dy = p.y - mouseY; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < 200) { const opacity = (1 - dist / 200) * 0.3; ctx.beginPath(); ctx.moveTo(p.x, p.y); ctx.lineTo(mouseX, mouseY); ctx.strokeStyle = `rgba(0, 255, 200, ${opacity})`; ctx.lineWidth = 0.8; ctx.stroke(); } }); } function update() { particles.forEach(p => { p.x += p.vx; p.y += p.vy; // Wrap around if (p.x < 0) p.x = canvas.width; if (p.x > canvas.width) p.x = 0; if (p.y < 0) p.y = canvas.height; if (p.y > canvas.height) p.y = 0; // Mouse repulsion const dx = p.x - mouseX; const dy = p.y - mouseY; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < 100) { const force = (100 - dist) / 100 * 0.02; p.vx += (dx / dist) * force; p.vy += (dy / dist) * force; } // Speed limit const speed = Math.sqrt(p.vx * p.vx + p.vy * p.vy); if (speed > CONFIG.particleMaxSpeed * 2) { p.vx *= 0.98; p.vy *= 0.98; } }); } function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height); update(); drawConnections(); particles.forEach(drawParticle); animationId = requestAnimationFrame(animate); } // Event listeners window.addEventListener('resize', () => { resize(); initParticleArray(); }); window.addEventListener('mousemove', (e) => { mouseX = e.clientX; mouseY = e.clientY; }, { passive: true }); window.addEventListener('mouseleave', () => { mouseX = -1000; mouseY = -1000; }); // Visibility API - pause when tab hidden document.addEventListener('visibilitychange', () => { if (document.hidden) { cancelAnimationFrame(animationId); } else { animate(); } }); resize(); initParticleArray(); animate(); } // ─── GLITCH EFFECT (random intensification) ─── function initGlitchEffect() { const glitch = $('.glitch'); if (!glitch) return; setInterval(() => { glitch.classList.add('glitch-active'); setTimeout(() => glitch.classList.remove('glitch-active'), 200); }, 5000 + Math.random() * 5000); } // ─── TERMINAL ANIMATION ─── function initTerminal() { const terminal = $('#terminal'); if (!terminal) return; const commands = [ { prompt: 'uptime', output: ' up 47 days, 3:22, 1 user, load: 0.12' }, { prompt: 'df -h / | tail -1', output: '/dev/sda1 50G 12G 36G 25% /' }, { prompt: 'curl -s ifconfig.me', output: '███.███.███.███' }, { prompt: 'docker ps --format "table {{.Names}}"', output: 'gitea\nnginx-proxy\nagent-zero\nmonitoring' }, { prompt: 'echo $SHELL', output: '/bin/zsh' }, { prompt: 'neofetch --off | head -3', output: 'OS: Debian GNU/Linux 12\nKernel: 6.1.0-18-amd64\nUptime: 47 days, 3 hours' }, ]; let cmdIndex = 0; function addTermLine(content, isOutput = false) { const line = document.createElement('div'); line.className = 'term-line' + (isOutput ? ' term-output' : ''); if (isOutput) { line.textContent = content; } else { line.innerHTML = `jae@swift:~$ ${content}`; } // Insert before the last line (cursor line) const cursorLine = terminal.lastElementChild; terminal.insertBefore(line, cursorLine); } function typeCommand() { if (cmdIndex >= commands.length) cmdIndex = 0; const cmd = commands[cmdIndex]; let charIdx = 0; // Remove old cursor line, add new command line being typed const cursorLine = terminal.lastElementChild; const typingLine = document.createElement('div'); typingLine.className = 'term-line'; typingLine.innerHTML = `jae@swift:~$ `; terminal.insertBefore(typingLine, cursorLine); cursorLine.remove(); const cmdSpan = typingLine.querySelector('.term-cmd'); function typeChar() { if (charIdx < cmd.prompt.length) { cmdSpan.textContent += cmd.prompt[charIdx]; charIdx++; setTimeout(typeChar, 50 + Math.random() * 80); } else { // Remove cursor from typing line const cursor = typingLine.querySelector('.term-cursor'); if (cursor) cursor.remove(); // Show output setTimeout(() => { const outputLines = cmd.output.split('\n'); outputLines.forEach(line => { const outputEl = document.createElement('div'); outputEl.className = 'term-line term-output'; outputEl.textContent = line; terminal.appendChild(outputEl); }); // Add new cursor line const newCursor = document.createElement('div'); newCursor.className = 'term-line'; newCursor.innerHTML = `jae@swift:~$ `; terminal.appendChild(newCursor); // Scroll terminal terminal.scrollTop = terminal.scrollHeight; // Keep only last ~15 lines while (terminal.children.length > 15) { terminal.removeChild(terminal.firstChild); } cmdIndex++; }, 300); } } typeChar(); } // Run terminal animation every 6-10 seconds setInterval(typeCommand, 8000 + Math.random() * 4000); } // ─── CONTACT FORM ─── function initContactForm() { const form = $('#contactForm'); if (!form) return; form.addEventListener('submit', (e) => { e.preventDefault(); const btn = form.querySelector('.form-submit'); const originalText = btn.querySelector('.submit-text').textContent; btn.querySelector('.submit-text').textContent = 'TRANSMITTING...'; btn.disabled = true; btn.style.borderColor = 'var(--warning)'; btn.style.color = 'var(--warning)'; setTimeout(() => { btn.querySelector('.submit-text').textContent = '✓ TRANSMITTED'; btn.style.borderColor = 'var(--accent)'; btn.style.color = 'var(--accent)'; setTimeout(() => { btn.querySelector('.submit-text').textContent = originalText; btn.disabled = false; btn.style.borderColor = ''; btn.style.color = ''; form.reset(); }, 2000); }, 1500); }); } // ─── FOOTER SIGNAL ANIMATION ─── function initFooterSignal() { const signal = $('#footerSignal'); if (!signal) return; const blocks = ['░', '█']; function update() { const strength = 6 + Math.floor(Math.random() * 4); // 6-9 out of 10 let bar = ''; for (let i = 0; i < 10; i++) { bar += i < strength ? blocks[1] : blocks[0]; } signal.textContent = `SIGNAL: ${bar} ${strength * 10}%`; } setInterval(update, 3000); } // ─── RANDOM HUD DATA FLICKERS ─── function initHUDFlickers() { const statValues = $$('.stat-value:not(.stat-online)'); setInterval(() => { statValues.forEach(el => { if (Math.random() > 0.7) { el.style.opacity = '0.3'; setTimeout(() => { el.style.opacity = '1'; }, 100 + Math.random() * 200); } }); }, 2000); } // ─── PANEL HOVER GLOW TRAIL ─── function initPanelGlow() { $$('.panel, .blog-card, .dev-card, .link-card').forEach(card => { card.addEventListener('mousemove', (e) => { const rect = card.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; card.style.setProperty('--mouse-x', `${x}px`); card.style.setProperty('--mouse-y', `${y}px`); card.style.background = `radial-gradient(circle 200px at ${x}px ${y}px, rgba(0, 255, 200, 0.04), var(--bg-panel))`; }); card.addEventListener('mouseleave', () => { card.style.background = ''; }); }); } // ─── INIT ─── document.addEventListener('DOMContentLoaded', () => { initNavbar(); initClock(); initUptime(); initTyping(); initScrollReveal(); initSkillBars(); initParticles(); initGlitchEffect(); initTerminal(); initContactForm(); initFooterSignal(); initHUDFlickers(); initPanelGlow(); // Page load animation document.body.style.opacity = '0'; document.body.style.transition = 'opacity 0.8s ease'; requestAnimationFrame(() => { document.body.style.opacity = '1'; }); console.log('%c[JAESWIFT] %cSystems Online', 'color: #00ffc8; font-weight: bold; font-size: 14px;', 'color: #c8d6e5; font-size: 12px;'); }); })();