document.addEventListener('DOMContentLoaded', () => { // Initialize Lucide Icons if (typeof lucide !== 'undefined') { lucide.createIcons(); } /* ========================================================================== MOBILE NAVIGATION TOGGLE ========================================================================== */ const navToggle = document.getElementById('navToggle'); const navMenu = document.getElementById('navMenu'); const navToggleIcon = navToggle ? navToggle.querySelector('i') : null; if (navToggle && navMenu) { navToggle.addEventListener('click', () => { navMenu.classList.toggle('open'); const isOpen = navMenu.classList.contains('open'); if (navToggleIcon && typeof lucide !== 'undefined') { if (isOpen) { navToggleIcon.setAttribute('data-lucide', 'x'); } else { navToggleIcon.setAttribute('data-lucide', 'menu'); } lucide.createIcons(); } }); } // Close menu when clicking a link const navLinks = document.querySelectorAll('.nav-link'); navLinks.forEach(link => { link.addEventListener('click', () => { if (navMenu) { navMenu.classList.remove('open'); } if (navToggleIcon && typeof lucide !== 'undefined') { navToggleIcon.setAttribute('data-lucide', 'menu'); lucide.createIcons(); } }); }); /* ========================================================================== ACTIVE LINK HIGHLIGHTING ON SCROLL ========================================================================== */ const sections = document.querySelectorAll('section'); function highlightActiveLink() { let currentSectionId = 'home'; const scrollPosition = window.scrollY + 120; // Offset for navbar height sections.forEach(section => { const sectionTop = section.offsetTop; const sectionHeight = section.offsetHeight; if (scrollPosition >= sectionTop && scrollPosition < sectionTop + sectionHeight) { currentSectionId = section.getAttribute('id'); } }); navLinks.forEach(link => { link.classList.remove('active'); if (link.getAttribute('href') === `#${currentSectionId}`) { link.classList.add('active'); } }); } /* ========================================================================== NAVBAR SCROLL EFFECT ========================================================================== */ const navbar = document.querySelector('.navbar'); const handleScroll = () => { if (window.scrollY > 20) { navbar.classList.add('scrolled'); } else { navbar.classList.remove('scrolled'); } highlightActiveLink(); }; window.addEventListener('scroll', handleScroll); handleScroll(); // Initial run /* ========================================================================== INTERSECTION OBSERVER FOR SCROLL REVEALS ========================================================================== */ // Add reveal class to various sections const revealTargets = [ '.hero-text', '.hero-visual', '.about-info-panel', '.about-stats-panel', '.filter-controls', '.project-card', '.timeline-item', '.contact-info', '.contact-form-container', '.cert-filter-controls', '.cert-card' ]; revealTargets.forEach(selector => { document.querySelectorAll(selector).forEach(el => { el.classList.add('reveal'); }); }); const revealObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('active'); revealObserver.unobserve(entry.target); // Reveal once } }); }, { threshold: 0.1, rootMargin: '0px 0px -50px 0px' }); document.querySelectorAll('.reveal').forEach(el => { revealObserver.observe(el); }); /* ========================================================================== MOUSE GLOW BORDER EFFECT FOR CARDS (PROJECT & CERTIFICATE CARDS) ========================================================================== */ const glowCards = document.querySelectorAll('.project-card, .cert-card'); glowCards.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`); }); }); /* ========================================================================== PROJECT DYNAMIC FILTERING ========================================================================== */ const filterButtons = document.querySelectorAll('.filter-btn'); const projectCards = document.querySelectorAll('.project-card'); filterButtons.forEach(button => { button.addEventListener('click', () => { // Update active state of buttons filterButtons.forEach(btn => btn.classList.remove('active')); button.classList.add('active'); const filterValue = button.getAttribute('data-filter'); projectCards.forEach(card => { const category = card.getAttribute('data-category'); // Reset card styling animation trigger card.style.opacity = '0'; card.style.transform = 'scale(0.95) translateY(10px)'; setTimeout(() => { if (filterValue === 'all' || category === filterValue) { card.style.display = 'block'; // Trigger redraw to activate transitions card.offsetHeight; card.style.opacity = '1'; card.style.transform = 'scale(1) translateY(0)'; } else { card.style.display = 'none'; } }, 200); }); }); }); // Initialize display styles for smooth filter transition projectCards.forEach(card => { card.style.transition = 'opacity 0.3s ease, transform 0.3s ease, background 0.4s ease'; }); /* ========================================================================== CERTIFICATION DYNAMIC FILTERING ========================================================================== */ const certFilterButtons = document.querySelectorAll('.cert-filter-btn'); const certCards = document.querySelectorAll('.cert-card'); certFilterButtons.forEach(button => { button.addEventListener('click', () => { // Update active state of buttons certFilterButtons.forEach(btn => btn.classList.remove('active')); button.classList.add('active'); const filterValue = button.getAttribute('data-filter'); certCards.forEach(card => { const category = card.getAttribute('data-category'); // Reset card styling animation trigger card.style.opacity = '0'; card.style.transform = 'scale(0.95) translateY(10px)'; setTimeout(() => { if (filterValue === 'all' || category === filterValue) { card.style.display = 'block'; // Trigger redraw to activate transitions card.offsetHeight; card.style.opacity = '1'; card.style.transform = 'scale(1) translateY(0)'; } else { card.style.display = 'none'; } }, 200); }); }); }); // Initialize display styles for smooth filter transition certCards.forEach(card => { card.style.transition = 'opacity 0.3s ease, transform 0.3s ease, background 0.4s ease'; }); /* ======================================================================== INTERACTIVE ECOSYSTEM MAP & LIVE SYSTEM DASHBOARD ========================================================================== */ const ecosystemData = { vynalth: { kicker: 'SELECTED SYSTEM / 01', title: 'Vynalth AI', description: 'A privacy-first health intelligence platform evolving from sleep analytics into a broader human-centered AI system.', status: 'ACTIVE', domain: 'Health AI' }, shield: { kicker: 'SELECTED SYSTEM / 02', title: 'Vynalth AI Shield', description: 'An AI-native trust layer exploring behavioral signals, human verification, and anti-bot infrastructure.', status: 'ACTIVE', domain: 'Trust infrastructure' }, navigator: { kicker: 'SELECTED SYSTEM / 03', title: 'Navigator', description: 'A developing web-agent direction for assisted research, navigation, and useful digital workflows.', status: 'DEVELOPING', domain: 'Web agents' }, pedia: { kicker: 'SELECTED SYSTEM / 04', title: 'Pedia', description: 'A planned knowledge layer for organizing context, references, and shared understanding across the ecosystem.', status: 'PLANNED', domain: 'Knowledge systems' }, vynova: { kicker: 'SELECTED SYSTEM / 05', title: 'Vynova Health AI', description: 'A beta health-AI direction focused on reflection, wellness tooling, and human-centered guidance.', status: 'BETA', domain: 'Health AI' } }; const ecosystemNodes = document.querySelectorAll('.ecosystem-node'); const ecosystemDetail = document.getElementById('ecosystemDetail'); const ecosystemDetailKicker = document.getElementById('ecosystemDetailKicker'); const ecosystemDetailTitle = document.getElementById('ecosystemDetailTitle'); const ecosystemDetailDescription = document.getElementById('ecosystemDetailDescription'); const ecosystemDetailStatus = document.getElementById('ecosystemDetailStatus'); const ecosystemDetailDomain = document.getElementById('ecosystemDetailDomain'); const selectEcosystem = (key) => { const data = ecosystemData[key]; if (!data || !ecosystemDetail) return; ecosystemNodes.forEach(node => { const isActive = node.dataset.ecosystemKey === key; node.classList.toggle('active', isActive); node.setAttribute('aria-pressed', String(isActive)); }); ecosystemDetailKicker.textContent = data.kicker; ecosystemDetailTitle.textContent = data.title; ecosystemDetailDescription.textContent = data.description; ecosystemDetailStatus.textContent = data.status; ecosystemDetailDomain.textContent = data.domain; }; ecosystemNodes.forEach(node => { const key = node.dataset.ecosystemKey; node.addEventListener('pointerenter', () => selectEcosystem(key)); node.addEventListener('focus', () => selectEcosystem(key)); node.addEventListener('click', () => selectEcosystem(key)); }); if (ecosystemNodes.length > 0) selectEcosystem('vynalth'); document.querySelectorAll('.system-row').forEach(row => { row.addEventListener('click', () => { document.querySelectorAll('.system-row').forEach(item => item.classList.remove('active')); row.classList.add('active'); const key = row.dataset.systemKey; if (ecosystemData[key]) selectEcosystem(key); }); }); /* ======================================================================== CONTACT FORM VALIDATION & FEEDBACK ========================================================================== */ const contactForm = document.getElementById('contactForm'); const formFeedback = document.getElementById('formFeedback'); /* ========================================================================== VITASHIELD KINETICS / SDK INTEGRATION ========================================================================== */ const vsWidget = document.getElementById('vitashield-widget'); const vsTokenInput = document.getElementById('vmsShieldToken'); let isVsVerified = false; // Check if the live Vynalth AI Shield / VitaShield SDK has loaded successfully from the CDN const shieldSDK = window.VynalthAIShield || window.VitaShield; if (typeof shieldSDK !== 'undefined' && vsWidget) { // Initialize the live SDK from the shield repository behavior shieldSDK.init({ siteKey: 'vms_pub_live_38bf8c6e2d9a10bc', theme: { primary: '#00f2fe', background: 'rgba(13, 20, 35, 0.55)', text: '#94a3b8' } }); // Listen for the live validation success custom event dispatched by the SDK widget vsWidget.addEventListener('vms-verified', (e) => { isVsVerified = true; const token = e.detail.token; if (vsTokenInput) vsTokenInput.value = token; // Trigger AJAX submission animation upon successful SDK verification handleFormSubmission(); }); } else if (vsWidget) { // Fallback: Local simulation of VitaShield kinetics (offline/development mode) const vsProgress = document.getElementById('vsProgress'); const vsStatus = document.getElementById('vsStatus'); if (vsProgress && vsStatus && vsTokenInput) { let entropy = 0; const updateVerification = (addedEntropy) => { if (isVsVerified) return; entropy = Math.min(100, entropy + addedEntropy); vsProgress.style.width = `${entropy}%`; if (entropy >= 100) { isVsVerified = true; vsWidget.querySelector('.vitashield-card')?.classList.add('verified'); vsStatus.innerHTML = ' Human Verified'; vsTokenInput.value = 'vms_fallback_token_' + Math.random().toString(36).substring(2, 15); if (formFeedback && formFeedback.textContent.includes('verify')) { formFeedback.classList.add('hidden'); } } }; const trackingArea = document.querySelector('.contact-form-container'); let lastX = null, lastY = null, lastTime = null; if (trackingArea) { trackingArea.addEventListener('mousemove', (e) => { const currentTime = Date.now(); if (lastX !== null && lastY !== null && lastTime !== null) { const deltaX = Math.abs(e.clientX - lastX); const deltaY = Math.abs(e.clientY - lastY); const deltaTime = currentTime - lastTime; if (deltaTime > 10) { const speed = Math.sqrt(deltaX * deltaX + deltaY * deltaY) / deltaTime; if (speed > 0.05 && speed < 5) { updateVerification(0.5); } } } lastX = e.clientX; lastY = e.clientY; lastTime = currentTime; }); } if (contactForm) { const inputs = contactForm.querySelectorAll('input, textarea'); inputs.forEach(input => { input.addEventListener('input', () => { updateVerification(3); }); }); } } } // Submit handler if (contactForm && formFeedback) { contactForm.addEventListener('submit', (e) => { e.preventDefault(); // If using the live SDK, the SDK intercepts submit and handles validation. // Only trigger manual submission check if the SDK hasn't completed or isn't loaded. if (typeof window.VitaShield !== 'undefined') { if (!isVsVerified) { showFeedback('Please solve the VitaShield challenge or interact with the form to verify.', 'error'); } return; } // Fallback validation check if (!isVsVerified || !vsTokenInput.value) { showFeedback('Please verify humanity using VitaShield before sending (interact with the form naturally).', 'error'); return; } handleFormSubmission(); }); } function handleFormSubmission() { if (!contactForm) return; const name = document.getElementById('name').value.trim(); const email = document.getElementById('email').value.trim(); const subject = document.getElementById('subject').value.trim(); const message = document.getElementById('message').value.trim(); const submitBtn = contactForm.querySelector('button[type="submit"]'); const submitBtnText = submitBtn.querySelector('span'); const submitBtnIcon = submitBtn.querySelector('i'); if (!name || !email || !subject || !message) { showFeedback('Please fill out all fields.', 'error'); return; } submitBtn.disabled = true; if (submitBtnText) submitBtnText.textContent = 'Sending Message...'; if (submitBtnIcon && typeof lucide !== 'undefined') { submitBtnIcon.setAttribute('data-lucide', 'loader'); lucide.createIcons(); } // Build FormData object to trigger "simple request" and bypass CORS preflight OPTIONS check const formData = new FormData(); formData.append("name", name); formData.append("email", email); formData.append("subject", subject); formData.append("message", message); formData.append("_cc", "ongyuze1401@gmail.com"); formData.append("_subject", "New Message from Portfolio"); if (vsTokenInput && vsTokenInput.value) { formData.append("vms-shield-token", vsTokenInput.value); } // Dispatch a real AJAX post to FormSubmit fetch("https://formsubmit.co/ajax/contact@sleepsomno.com", { method: "POST", headers: { "Accept": "application/json" }, body: formData }) .then(response => response.json()) .then(data => { if (data.success === "true" || data.success === true) { showFeedback(`Thank you, ${name}! Your message has been sent successfully.`, 'success'); contactForm.reset(); // Reset state isVsVerified = false; if (vsTokenInput) vsTokenInput.value = ''; // Reset fallback visual elements if visible const vsProgress = document.getElementById('vsProgress'); const vsStatus = document.getElementById('vsStatus'); if (vsProgress) vsProgress.style.width = '0%'; if (vsStatus) vsStatus.innerHTML = ' Analyzing behavior...'; vsWidget.querySelector('.vitashield-card')?.classList.remove('verified'); } else { showFeedback('Oops! Something went wrong while sending your message. Please try again.', 'error'); } }) .catch(err => { showFeedback('Oops! Failed to connect to email gateway. Please check your network and try again.', 'error'); }) .finally(() => { // Reset button submitBtn.disabled = false; if (submitBtnText) submitBtnText.textContent = 'Send Message'; if (submitBtnIcon && typeof lucide !== 'undefined') { submitBtnIcon.setAttribute('data-lucide', 'send'); lucide.createIcons(); } }); } function showFeedback(text, type) { if (!formFeedback) return; formFeedback.textContent = text; formFeedback.className = `form-feedback ${type}`; // resets to default and adds type formFeedback.classList.remove('hidden'); // Auto scroll feedback into view if needed formFeedback.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); // Hide success message after 5 seconds if (type === 'success') { setTimeout(() => { formFeedback.classList.add('hidden'); }, 6000); } } /* ========================================================================== DYNAMIC COPYRIGHT YEAR ========================================================================== */ /* ======================================================================== RESTRAINED MOTION SYSTEM Mouse parallax and magnetic buttons only run on fine-pointer devices. ========================================================================== */ const motionQuery = window.matchMedia('(prefers-reduced-motion: reduce)'); const finePointerQuery = window.matchMedia('(hover: hover) and (pointer: fine)'); if (!motionQuery.matches && finePointerQuery.matches) { const parallaxTargets = { background: document.querySelectorAll('.glow-bg-orb'), logo: document.querySelectorAll('.logo-symbol'), visual: document.querySelectorAll('.hero-visual') }; let pointerFrame = null; let pointerX = window.innerWidth / 2; let pointerY = window.innerHeight / 2; const renderParallax = () => { const normalizedX = (pointerX / window.innerWidth - 0.5) * 2; const normalizedY = (pointerY / window.innerHeight - 0.5) * 2; parallaxTargets.background.forEach((element, index) => { const depth = 2 + index * 0.4; element.style.setProperty('--parallax-x', `${normalizedX * depth}px`); element.style.setProperty('--parallax-y', `${normalizedY * depth}px`); }); parallaxTargets.logo.forEach(element => { element.style.setProperty('--parallax-x', `${normalizedX * 4}px`); element.style.setProperty('--parallax-y', `${normalizedY * 4}px`); }); parallaxTargets.visual.forEach(element => { element.style.setProperty('--parallax-x', `${normalizedX * 8}px`); element.style.setProperty('--parallax-y', `${normalizedY * 8}px`); }); pointerFrame = null; }; window.addEventListener('pointermove', event => { pointerX = event.clientX; pointerY = event.clientY; if (!pointerFrame) pointerFrame = requestAnimationFrame(renderParallax); }, { passive: true }); document.querySelectorAll('.btn').forEach(button => { button.classList.add('magnetic'); button.addEventListener('pointermove', event => { const rect = button.getBoundingClientRect(); const offsetX = event.clientX - (rect.left + rect.width / 2); const offsetY = event.clientY - (rect.top + rect.height / 2); button.style.setProperty('--magnetic-x', `${offsetX * 0.22}px`); button.style.setProperty('--magnetic-y', `${offsetY * 0.22}px`); }, { passive: true }); button.addEventListener('pointerleave', () => { button.style.setProperty('--magnetic-x', '0px'); button.style.setProperty('--magnetic-y', '0px'); }); }); } const currentYearEl = document.getElementById('currentYear'); if (currentYearEl) { currentYearEl.textContent = new Date().getFullYear(); } /* ========================================================================== SCROLL TO TOP BUTTON BEHAVIOR ========================================================================== */ const scrollTopBtn = document.getElementById('scrollTopBtn'); if (scrollTopBtn) { window.addEventListener('scroll', () => { if (window.scrollY > 300) { scrollTopBtn.classList.add('visible'); } else { scrollTopBtn.classList.remove('visible'); } }); scrollTopBtn.addEventListener('click', () => { window.scrollTo({ top: 0, behavior: 'smooth' }); }); } /* ========================================================================== BIOGRAPHY TAB SWITCHING BEHAVIOR ========================================================================== */ const bioTabButtons = document.querySelectorAll('.bio-tab-btn'); const bioContentPanes = document.querySelectorAll('.bio-content-pane'); if (bioTabButtons.length > 0 && bioContentPanes.length > 0) { bioTabButtons.forEach(btn => { btn.addEventListener('click', () => { // Remove active classes bioTabButtons.forEach(b => b.classList.remove('active')); bioContentPanes.forEach(pane => pane.classList.remove('active')); // Add active class to clicked button btn.classList.add('active'); // Show corresponding pane const tabId = btn.getAttribute('data-tab'); const targetPane = document.getElementById(`pane-${tabId}`); if (targetPane) { targetPane.classList.add('active'); } }); }); } });