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');
}
});
});
}
});