mirror of
https://github.com/chemistry-software/website.git
synced 2026-11-07 08:57:29 +00:00
186 lines
5.6 KiB
JavaScript
186 lines
5.6 KiB
JavaScript
// Set current year in footer
|
|
document.getElementById('current-year').textContent = new Date().getFullYear();
|
|
|
|
// Mobile menu toggle
|
|
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
|
|
const mobileNav = document.getElementById('mobile-nav');
|
|
|
|
if (mobileMenuBtn && mobileNav) {
|
|
mobileMenuBtn.addEventListener('click', () => {
|
|
mobileNav.classList.toggle('open');
|
|
document.body.classList.toggle('mobile-menu-open');
|
|
});
|
|
|
|
// Close mobile menu when clicking on a link
|
|
const mobileNavLinks = mobileNav.querySelectorAll('.nav-link');
|
|
mobileNavLinks.forEach(link => {
|
|
link.addEventListener('click', () => {
|
|
mobileNav.classList.remove('open');
|
|
document.body.classList.remove('mobile-menu-open');
|
|
});
|
|
});
|
|
}
|
|
|
|
// Theme toggle functionality
|
|
const themeToggle = document.getElementById('theme-toggle');
|
|
const htmlElement = document.documentElement;
|
|
|
|
// Check for saved theme preference or use system preference
|
|
const getThemePreference = () => {
|
|
const savedTheme = localStorage.getItem('theme');
|
|
if (savedTheme) {
|
|
return savedTheme;
|
|
}
|
|
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
};
|
|
|
|
// Apply the theme
|
|
const setTheme = (theme) => {
|
|
if (theme === 'dark') {
|
|
htmlElement.classList.add('dark');
|
|
} else {
|
|
htmlElement.classList.remove('dark');
|
|
}
|
|
localStorage.setItem('theme', theme);
|
|
};
|
|
|
|
// Initialize theme
|
|
setTheme(getThemePreference());
|
|
|
|
// Toggle theme when button is clicked
|
|
if (themeToggle) {
|
|
themeToggle.addEventListener('click', () => {
|
|
const currentTheme = localStorage.getItem('theme') || 'light';
|
|
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
|
setTheme(newTheme);
|
|
});
|
|
}
|
|
|
|
// Contact form submission
|
|
const contactForm = document.getElementById('contact-form');
|
|
if (contactForm) {
|
|
const capWidget = document.getElementById('cap-widget');
|
|
let capToken = '';
|
|
|
|
if (capWidget) {
|
|
capWidget.addEventListener('solve', (e) => {
|
|
capToken = e.detail.token;
|
|
});
|
|
capWidget.addEventListener('reset', () => {
|
|
capToken = '';
|
|
});
|
|
capWidget.addEventListener('error', (e) => {
|
|
console.error('Human verification error:', e.detail && e.detail.message);
|
|
});
|
|
}
|
|
|
|
const resetCapWidget = () => {
|
|
capToken = '';
|
|
if (capWidget && typeof capWidget.reset === 'function') {
|
|
capWidget.reset();
|
|
}
|
|
};
|
|
|
|
contactForm.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
|
|
// Fall back to the hidden field the widget injects into the form
|
|
const hiddenTokenInput = contactForm.querySelector('input[name="cap-token"]');
|
|
const token = capToken || (hiddenTokenInput ? hiddenTokenInput.value : '');
|
|
if (!token) {
|
|
alert('Please complete the human verification before sending your message.');
|
|
return;
|
|
}
|
|
|
|
const submitButton = contactForm.querySelector('button[type="submit"]');
|
|
const originalText = submitButton ? submitButton.textContent : '';
|
|
if (submitButton) {
|
|
submitButton.disabled = true;
|
|
submitButton.textContent = 'Sending...';
|
|
}
|
|
|
|
const payload = {
|
|
name: document.getElementById('name').value.trim(),
|
|
email: document.getElementById('email').value.trim(),
|
|
subject: document.getElementById('subject').value.trim(),
|
|
message: document.getElementById('message').value.trim(),
|
|
capToken: token
|
|
};
|
|
|
|
try {
|
|
const res = await fetch('/api/contact', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(payload)
|
|
});
|
|
const data = await res.json().catch(() => ({ ok: false, message: 'Unexpected response' }));
|
|
if (!res.ok || data.ok === false) {
|
|
alert(data.message || 'Failed to send message. Please try again later.');
|
|
resetCapWidget();
|
|
} else {
|
|
alert('Thank you for your message! I\'ll get back to you soon.');
|
|
contactForm.reset();
|
|
resetCapWidget();
|
|
}
|
|
} catch (err) {
|
|
alert('Network error. Please try again later.');
|
|
resetCapWidget();
|
|
} finally {
|
|
if (submitButton) {
|
|
submitButton.disabled = false;
|
|
submitButton.textContent = originalText;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Add animation to chemistry elements
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// Add subtle animations to some hexagons and circles
|
|
const animateElements = () => {
|
|
const elements = document.querySelectorAll('.hexagon, .circle');
|
|
elements.forEach((el, index) => {
|
|
// Add random subtle movements
|
|
if (index % 3 === 0) { // Only animate every third element
|
|
el.style.animation = `pulse ${10 + index}s infinite`;
|
|
}
|
|
});
|
|
};
|
|
|
|
animateElements();
|
|
});
|
|
|
|
// Portfolio item hover effects
|
|
const portfolioItems = document.querySelectorAll('.portfolio-item');
|
|
portfolioItems.forEach(item => {
|
|
const overlay = item.querySelector('.portfolio-overlay');
|
|
const image = item.querySelector('.portfolio-image');
|
|
|
|
item.addEventListener('mouseenter', () => {
|
|
overlay.style.opacity = '1';
|
|
image.style.transform = 'scale(1.05)';
|
|
});
|
|
|
|
item.addEventListener('mouseleave', () => {
|
|
overlay.style.opacity = '0';
|
|
image.style.transform = 'scale(1)';
|
|
});
|
|
});
|
|
|
|
// Smooth scrolling for anchor links
|
|
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
|
anchor.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
|
|
const targetId = this.getAttribute('href');
|
|
if (targetId === '#') return;
|
|
|
|
const targetElement = document.querySelector(targetId);
|
|
if (targetElement) {
|
|
targetElement.scrollIntoView({
|
|
behavior: 'smooth',
|
|
block: 'start'
|
|
});
|
|
}
|
|
});
|
|
}); |