Files
teppelin-labs-website/script.js
syntaxbullet e48582fa2b Initial commit
2026-06-12 17:44:52 +02:00

126 lines
4.2 KiB
JavaScript

const body = document.body;
const menuToggle = document.querySelector('.menu-toggle');
const navLinks = document.querySelectorAll('.site-nav a');
const progress = document.querySelector('.scroll-progress span');
if (menuToggle) {
menuToggle.addEventListener('click', () => {
const isOpen = body.classList.toggle('menu-open');
menuToggle.setAttribute('aria-expanded', String(isOpen));
});
}
navLinks.forEach((link) => {
link.addEventListener('click', () => {
body.classList.remove('menu-open');
menuToggle?.setAttribute('aria-expanded', 'false');
});
});
const updateProgress = () => {
if (!progress) return;
const max = document.documentElement.scrollHeight - window.innerHeight;
const value = max > 0 ? window.scrollY / max : 0;
progress.style.transform = `scaleX(${value})`;
};
window.addEventListener('scroll', updateProgress, { passive: true });
updateProgress();
const revealObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
revealObserver.unobserve(entry.target);
}
});
}, { threshold: 0.12, rootMargin: '0px 0px -5% 0px' });
document.querySelectorAll('.reveal').forEach((element) => revealObserver.observe(element));
const currentPage = body.dataset.page;
document.querySelectorAll(`[data-nav="${currentPage}"]`).forEach((link) => {
link.setAttribute('aria-current', 'page');
});
const dot = document.querySelector('.cursor-dot');
const ring = document.querySelector('.cursor-ring');
if (dot && ring && window.matchMedia('(pointer: fine)').matches) {
let mouseX = 0;
let mouseY = 0;
let ringX = 0;
let ringY = 0;
window.addEventListener('mousemove', (event) => {
mouseX = event.clientX;
mouseY = event.clientY;
dot.classList.add('is-active');
ring.classList.add('is-active');
dot.style.left = `${mouseX}px`;
dot.style.top = `${mouseY}px`;
});
const animateRing = () => {
ringX += (mouseX - ringX) * 0.16;
ringY += (mouseY - ringY) * 0.16;
ring.style.left = `${ringX}px`;
ring.style.top = `${ringY}px`;
requestAnimationFrame(animateRing);
};
animateRing();
document.querySelectorAll('a, button, input, textarea, select').forEach((target) => {
target.addEventListener('mouseenter', () => ring.classList.add('is-hovering'));
target.addEventListener('mouseleave', () => ring.classList.remove('is-hovering'));
});
}
document.querySelectorAll('[data-count]').forEach((element) => {
const target = Number(element.dataset.count);
const suffix = element.dataset.suffix || '';
const counterObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
const start = performance.now();
const duration = 1300;
const tick = (time) => {
const progressValue = Math.min((time - start) / duration, 1);
const eased = 1 - Math.pow(1 - progressValue, 4);
element.textContent = `${Math.round(target * eased)}${suffix}`;
if (progressValue < 1) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
counterObserver.disconnect();
});
}, { threshold: 0.5 });
counterObserver.observe(element);
});
const contactForm = document.querySelector('[data-contact-form]');
if (contactForm) {
contactForm.addEventListener('submit', (event) => {
event.preventDefault();
const status = contactForm.querySelector('.form-status');
const name = new FormData(contactForm).get('name') || 'there';
status.textContent = `Thank you, ${name}. Your collaboration brief is ready for review.`;
contactForm.reset();
});
}
const applicationForm = document.querySelector('[data-application-form]');
if (applicationForm) {
applicationForm.addEventListener('submit', (event) => {
event.preventDefault();
const status = applicationForm.querySelector('.form-status');
const name = new FormData(applicationForm).get('name') || 'builder';
status.textContent = `Application received, ${name}. Connect this form to a submission service before launch.`;
applicationForm.reset();
});
}
document.querySelectorAll('[data-year]').forEach((node) => {
node.textContent = new Date().getFullYear();
});