Spaces:
Running
Running
| document.addEventListener('DOMContentLoaded', () => { | |
| // ============================================= | |
| // π§ NEURAL PARTICLE NETWORK β Living Background | |
| // ============================================= | |
| const canvas = document.getElementById('particle-canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| let particles = []; | |
| const PARTICLE_COUNT = window.innerWidth < 768 ? 40 : 75; | |
| const CONNECTION_DIST = 150; | |
| let mouseX = window.innerWidth / 2; | |
| let mouseY = window.innerHeight / 2; | |
| function resizeCanvas() { | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| } | |
| resizeCanvas(); | |
| window.addEventListener('resize', resizeCanvas); | |
| class Particle { | |
| constructor() { | |
| this.reset(); | |
| } | |
| reset() { | |
| this.x = Math.random() * canvas.width; | |
| this.y = Math.random() * canvas.height; | |
| this.vx = (Math.random() - 0.5) * 0.6; | |
| this.vy = (Math.random() - 0.5) * 0.6; | |
| this.size = Math.random() * 2.5 + 1; | |
| this.opacity = Math.random() * 0.6 + 0.2; | |
| } | |
| update() { | |
| // Gentle mouse repulsion | |
| const dx = this.x - mouseX; | |
| const dy = this.y - mouseY; | |
| const dist = Math.sqrt(dx * dx + dy * dy); | |
| if (dist < 180) { | |
| const force = (180 - dist) / 180 * 0.3; | |
| this.vx += (dx / dist) * force * 0.1; | |
| this.vy += (dy / dist) * force * 0.1; | |
| } | |
| // Damping | |
| this.vx *= 0.998; | |
| this.vy *= 0.998; | |
| this.x += this.vx; | |
| this.y += this.vy; | |
| // Wrap edges | |
| if (this.x < -20) this.x = canvas.width + 20; | |
| if (this.x > canvas.width + 20) this.x = -20; | |
| if (this.y < -20) this.y = canvas.height + 20; | |
| if (this.y > canvas.height + 20) this.y = -20; | |
| } | |
| draw() { | |
| ctx.beginPath(); | |
| ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); | |
| ctx.fillStyle = `rgba(0, 242, 254, ${this.opacity})`; | |
| ctx.fill(); | |
| } | |
| } | |
| // Initialize particles | |
| for (let i = 0; i < PARTICLE_COUNT; i++) { | |
| particles.push(new Particle()); | |
| } | |
| function animateParticles() { | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| // Update & draw particles | |
| particles.forEach(p => { p.update(); p.draw(); }); | |
| // Draw connections | |
| 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 < CONNECTION_DIST) { | |
| const alpha = (1 - dist / CONNECTION_DIST) * 0.2; | |
| ctx.beginPath(); | |
| ctx.moveTo(particles[i].x, particles[i].y); | |
| ctx.lineTo(particles[j].x, particles[j].y); | |
| ctx.strokeStyle = `rgba(0, 220, 255, ${alpha})`; | |
| ctx.lineWidth = 0.5; | |
| ctx.stroke(); | |
| } | |
| } | |
| } | |
| requestAnimationFrame(animateParticles); | |
| } | |
| animateParticles(); | |
| // --- Update mouse for particles --- | |
| document.addEventListener('mousemove', (e) => { | |
| mouseX = e.clientX; | |
| mouseY = e.clientY; | |
| document.documentElement.style.setProperty('--cursor-x', e.clientX / window.innerWidth); | |
| document.documentElement.style.setProperty('--cursor-y', e.clientY / window.innerHeight); | |
| }); | |
| // ============================================= | |
| // π SCROLL PROGRESS BAR | |
| // ============================================= | |
| const scrollProgress = document.getElementById('scroll-progress'); | |
| window.addEventListener('scroll', () => { | |
| const scrollTop = window.scrollY; | |
| const docHeight = document.documentElement.scrollHeight - window.innerHeight; | |
| const progress = docHeight > 0 ? (scrollTop / docHeight) * 100 : 0; | |
| scrollProgress.style.width = progress + '%'; | |
| }); | |
| // ============================================= | |
| // π― 3D TILT EFFECT FOR CARDS | |
| // ============================================= | |
| const tiltCards = document.querySelectorAll('.tilt-card'); | |
| tiltCards.forEach(card => { | |
| card.addEventListener('mousemove', (e) => { | |
| const rect = card.getBoundingClientRect(); | |
| const x = e.clientX - rect.left; | |
| const y = e.clientY - rect.top; | |
| const centerX = rect.width / 2; | |
| const centerY = rect.height / 2; | |
| const rotateX = ((y - centerY) / centerY) * -6; | |
| const rotateY = ((x - centerX) / centerX) * 6; | |
| const glareX = (x / rect.width) * 100; | |
| const glareY = (y / rect.height) * 100; | |
| card.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale3d(1.02, 1.02, 1.02)`; | |
| card.style.setProperty('--glare-x', glareX + '%'); | |
| card.style.setProperty('--glare-y', glareY + '%'); | |
| }); | |
| card.addEventListener('mouseleave', () => { | |
| card.style.transform = 'perspective(1000px) rotateX(0deg) rotateY(0deg) scale3d(1, 1, 1)'; | |
| card.style.setProperty('--glare-x', '50%'); | |
| card.style.setProperty('--glare-y', '50%'); | |
| }); | |
| }); | |
| // ============================================= | |
| // π§² MAGNETIC BUTTONS | |
| // ============================================= | |
| const magneticBtns = document.querySelectorAll('.magnetic-btn'); | |
| magneticBtns.forEach(btn => { | |
| btn.addEventListener('mousemove', (e) => { | |
| const rect = btn.getBoundingClientRect(); | |
| const x = e.clientX - rect.left - rect.width / 2; | |
| const y = e.clientY - rect.top - rect.height / 2; | |
| const dist = Math.sqrt(x * x + y * y); | |
| const maxDist = 120; | |
| if (dist < maxDist) { | |
| const strength = (1 - dist / maxDist) * 10; | |
| btn.style.transform = `translate(${x * strength * 0.06}px, ${y * strength * 0.06}px) scale(1.05)`; | |
| } | |
| }); | |
| btn.addEventListener('mouseleave', () => { | |
| btn.style.transform = 'translate(0px, 0px) scale(1)'; | |
| }); | |
| }); | |
| // ============================================= | |
| // π SCROLL-DRIVEN COLOR MORPHING | |
| // ============================================= | |
| window.addEventListener('scroll', () => { | |
| const scrollTop = window.scrollY; | |
| const maxScroll = document.documentElement.scrollHeight - window.innerHeight; | |
| const scrollFraction = maxScroll > 0 ? scrollTop / maxScroll : 0; | |
| // Hue shifts from 188 (cyan) β 200 (blue) β 270 (violet) | |
| const hue = 188 + scrollFraction * 82; | |
| document.documentElement.style.setProperty('--accent-hue', hue); | |
| }); | |
| // ============================================= | |
| // ποΈ FADE-IN OBSERVER FOR TIMELINE | |
| // ============================================= | |
| const timelineItems = document.querySelectorAll('.timeline-item'); | |
| const observer = new IntersectionObserver((entries) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| entry.target.classList.add('revealed'); | |
| } | |
| }); | |
| }, { threshold: 0.2 }); | |
| timelineItems.forEach(item => observer.observe(item)); | |
| // ============================================= | |
| // π MODE TOGGLE: Bento Grid <-> Terminal | |
| // ============================================= | |
| const toggleBtn = document.getElementById('mode-toggle'); | |
| const mainContent = document.getElementById('main-content'); | |
| const terminalMode = document.getElementById('terminal-mode'); | |
| const terminalOutput = document.getElementById('terminal-output'); | |
| const terminalInput = document.getElementById('terminal-input'); | |
| const toggleText = document.getElementById('toggle-text'); | |
| let isTerminal = false; | |
| toggleBtn.addEventListener('click', () => { | |
| if (!isTerminal) { | |
| if (document.startViewTransition) { | |
| document.startViewTransition(() => { | |
| mainContent.classList.add('hidden'); | |
| terminalMode.classList.remove('hidden'); | |
| }); | |
| } else { | |
| mainContent.classList.add('hidden'); | |
| terminalMode.classList.remove('hidden'); | |
| } | |
| toggleText.textContent = 'π GUI'; | |
| printTerminal("SYSTEM > Terminal Mode Activated.", 'cyan'); | |
| printTerminal("Type 'help' for a list of commands.", 'dim'); | |
| isTerminal = true; | |
| terminalInput.focus(); | |
| } else { | |
| if (document.startViewTransition) { | |
| document.startViewTransition(() => { | |
| terminalMode.classList.add('hidden'); | |
| mainContent.classList.remove('hidden'); | |
| }); | |
| } else { | |
| terminalMode.classList.add('hidden'); | |
| mainContent.classList.remove('hidden'); | |
| } | |
| toggleText.textContent = '>_ TERMINAL'; | |
| isTerminal = false; | |
| } | |
| }); | |
| // ============================================= | |
| // π» ENHANCED TERMINAL LOGIC | |
| // ============================================= | |
| const commands = { | |
| help: { | |
| desc: "Shows this help message", | |
| action: () => { | |
| let output = "Available Commands:\n"; | |
| for (let cmd in commands) { | |
| output += ` ${cmd.padEnd(14)} - ${commands[cmd].desc}\n`; | |
| } | |
| return output; | |
| } | |
| }, | |
| whoami: { | |
| desc: "Display current user", | |
| action: () => "π‘οΈ Sebastian F. Nestler | TryHackMe Top 7% | Annaberg-Buchholz, DE" | |
| }, | |
| skills: { | |
| desc: "List core competencies", | |
| action: () => "β‘ Python | JavaScript | C | SQL | Logfile Analysis | Incident Response | Applied AI & Automation" | |
| }, | |
| contact: { | |
| desc: "Show contact info", | |
| action: () => "π§ snestler110@gmail.com | π nestler.dev | π credly.com/users/sebastian-nestler" | |
| }, | |
| experience: { | |
| desc: "Show career timeline", | |
| action: () => { | |
| return `π CAREER TIMELINE: | |
| [2022-2023] 3rd Level Support Engineer @ GK Software SE | |
| [2022] Ethical Hacking Trainee @ T-Systems | |
| [2016] Dispatcher & Medical Assistant @ KV Brandenburg | |
| [2010-2014] Independent Entrepreneur @ Rio de Janeiro | |
| [2001-2015] Paramedic / Medical Assistant @ Multiple Orgs`; | |
| } | |
| }, | |
| education: { | |
| desc: "Show educational background", | |
| action: () => { | |
| return `π EDUCATION: | |
| β’ Medical Studies (1st State Exam) β Friedrich Schiller Univ. Jena | |
| β’ Certified Pharmaceutical Consultant (IHK) β Pharmaakademie Leipzig | |
| β’ Paramedic Training β Landesschule Sachsen Rotes Kreuz`; | |
| } | |
| }, | |
| languages: { | |
| desc: "Show spoken languages", | |
| action: () => "π£οΈ German (Native) | English (Fluent) | Portuguese (Fluent - Brazilian)" | |
| }, | |
| clear: { | |
| desc: "Clear the terminal", | |
| action: () => { | |
| terminalOutput.innerHTML = ''; | |
| return ''; | |
| } | |
| }, | |
| matrix: { | |
| desc: "Fun easter egg", | |
| action: () => { | |
| let rain = ''; | |
| const chars = '01γ’γ€γ¦γ¨γͺγ«γγ―γ±γ³γ΅γ·γΉγ»γ½γΏγγγγ'; | |
| for (let i = 0; i < 15; i++) { | |
| let line = ''; | |
| for (let j = 0; j < 50; j++) { | |
| line += chars[Math.floor(Math.random() * chars.length)]; | |
| } | |
| rain += line + '\n'; | |
| } | |
| return rain; | |
| } | |
| } | |
| }; | |
| function printTerminal(text, color = 'green') { | |
| const span = document.createElement('span'); | |
| span.textContent = text + '\n'; | |
| if (color === 'dim') span.style.opacity = '0.5'; | |
| if (color === 'cyan') span.style.color = '#00F2FE'; | |
| if (color === 'red') span.style.color = '#ff4444'; | |
| if (color === 'white') span.style.color = '#e0ffe0'; | |
| terminalOutput.appendChild(span); | |
| terminalOutput.scrollTop = terminalOutput.scrollHeight; | |
| } | |
| terminalInput.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter') { | |
| const input = terminalInput.value.trim().toLowerCase(); | |
| terminalInput.value = ''; | |
| if (commands[input]) { | |
| const result = commands[input].action(); | |
| printTerminal(`β― ${input}`, 'white'); | |
| if (result) printTerminal(result, 'white'); | |
| } else if (input !== '') { | |
| printTerminal(`β― ${input}`, 'white'); | |
| printTerminal(`Command not found: ${input}. Type 'help' for options.`, 'red'); | |
| } | |
| } | |
| }); | |
| function initTerminal() { | |
| const welcomeMsg = ` | |
| ββββββββββ ββββββββββ βββββββββββββββ ββββ βββββββββββββββββββ | |
| ββββββββββββ ββββββββββββββββββββββββββββ βββββ βββββββββββββββββββββ | |
| βββ βββββββ ββββββββββββββ ββββββββ βββββββββββββββββ βββ βββ | |
| βββ βββββ ββββββββββββββ ββββββββ βββββββββββββββββ βββ βββ | |
| ββββββββ βββ βββββββββββββββββββ βββ βββ βββ βββββββββββββββββββ | |
| βββββββ βββ βββββββ βββββββββββ βββ βββ ββββββββββββββββββ | |
| `; | |
| printTerminal(welcomeMsg, 'cyan'); | |
| printTerminal('β‘ CYBER-MEDICAL TERMINAL v2.0 β Sebastian F. Nestler', 'white'); | |
| printTerminal('π‘οΈ Cybersecurity | π€ AI Applications | π₯ Emergency Operations', 'dim'); | |
| printTerminal('\nType "help" to explore available commands.', 'dim'); | |
| } | |
| initTerminal(); | |
| }); |