/* ===== TIAN Building Engineering - Main JavaScript ===== */ document.addEventListener('DOMContentLoaded', () => { // --- Hero Video Slider --- const heroSlides = document.querySelectorAll('.hero-slide'); const projectSlides = document.querySelectorAll('.project-slide'); const videoDots = document.querySelectorAll('.video-dot'); let currentSlide = 0; const slideInterval = 12000; // 12 seconds per video function switchSlide(index) { heroSlides.forEach((v, i) => { v.classList.toggle('active', i === index); if (i === index) { v.play(); } else { v.pause(); } }); projectSlides.forEach((b, i) => b.classList.toggle('active', i === index)); videoDots.forEach((d, i) => d.classList.toggle('active', i === index)); currentSlide = index; } if (heroSlides.length > 1) { const totalSlides = heroSlides.length; // Auto-switch let autoTimer = setInterval(() => { switchSlide((currentSlide + 1) % totalSlides); }, slideInterval); function resetTimer() { clearInterval(autoTimer); autoTimer = setInterval(() => { switchSlide((currentSlide + 1) % totalSlides); }, slideInterval); } // Dot clicks videoDots.forEach(dot => { dot.addEventListener('click', () => { switchSlide(parseInt(dot.dataset.index)); resetTimer(); }); }); // Arrow clicks const prevBtn = document.querySelector('.video-prev'); const nextBtn = document.querySelector('.video-next'); if (prevBtn) prevBtn.addEventListener('click', () => { switchSlide((currentSlide - 1 + totalSlides) % totalSlides); resetTimer(); }); if (nextBtn) nextBtn.addEventListener('click', () => { switchSlide((currentSlide + 1) % totalSlides); resetTimer(); }); // Touch swipe const heroSection = document.querySelector('.hero'); let touchStartX = 0; let touchEndX = 0; if (heroSection) { heroSection.addEventListener('touchstart', (e) => { touchStartX = e.changedTouches[0].screenX; }, { passive: true }); heroSection.addEventListener('touchend', (e) => { touchEndX = e.changedTouches[0].screenX; const diff = touchStartX - touchEndX; if (Math.abs(diff) > 50) { if (diff > 0) { switchSlide((currentSlide + 1) % totalSlides); } else { switchSlide((currentSlide - 1 + totalSlides) % totalSlides); } resetTimer(); } }, { passive: true }); } } // --- Navbar Scroll Effect --- const navbar = document.getElementById('navbar'); let lastScroll = 0; if (navbar) { window.addEventListener('scroll', () => { const currentScroll = window.scrollY; navbar.classList.toggle('scrolled', currentScroll > 50); lastScroll = currentScroll; }, { passive: true }); } // --- Mobile Menu Toggle --- const navToggle = document.getElementById('navToggle'); const navMenu = document.getElementById('navMenu'); const mobileNavQuery = window.matchMedia('(max-width: 1199px)'); if (navToggle && navMenu) { const dropdowns = navMenu.querySelectorAll('.nav-dropdown'); const subDropdowns = navMenu.querySelectorAll('.nav-subdropdown'); function getDirectChildByClass(parent, className) { return Array.from(parent.children).find(child => child.classList.contains(className)); } navToggle.setAttribute('aria-expanded', 'false'); dropdowns.forEach(dropdown => { const trigger = getDirectChildByClass(dropdown, 'nav-link'); if (trigger) { trigger.setAttribute('aria-expanded', 'false'); } }); function closeDropdowns() { dropdowns.forEach(dropdown => { dropdown.classList.remove('open'); const trigger = getDirectChildByClass(dropdown, 'nav-link'); if (trigger) { trigger.setAttribute('aria-expanded', 'false'); } }); subDropdowns.forEach(subDropdown => { subDropdown.classList.remove('open'); const toggle = subDropdown.querySelector('.nav-submenu-toggle'); if (toggle) { toggle.setAttribute('aria-expanded', 'false'); } }); } function closeMobileMenu() { navToggle.classList.remove('open'); navMenu.classList.remove('open'); navToggle.setAttribute('aria-expanded', 'false'); document.body.style.overflow = ''; closeDropdowns(); } navToggle.addEventListener('click', () => { const willOpen = !navMenu.classList.contains('open'); navToggle.classList.toggle('open', willOpen); navMenu.classList.toggle('open', willOpen); navToggle.setAttribute('aria-expanded', String(willOpen)); document.body.style.overflow = willOpen ? 'hidden' : ''; document.querySelector('.lang-more')?.classList.remove('open'); closeDropdowns(); }); // Mobile dropdowns open by tap; normal links close the menu. navMenu.querySelectorAll('.nav-link').forEach(link => { link.addEventListener('click', (event) => { const dropdown = link.closest('.nav-dropdown'); const dropdownMenu = dropdown ? getDirectChildByClass(dropdown, 'dropdown-menu') : null; if (mobileNavQuery.matches && dropdown && dropdownMenu) { event.preventDefault(); event.stopPropagation(); const willOpen = !dropdown.classList.contains('open'); closeDropdowns(); dropdown.classList.toggle('open', willOpen); link.setAttribute('aria-expanded', String(willOpen)); return; } closeMobileMenu(); }); }); navMenu.querySelectorAll('.dropdown-menu a').forEach(link => { link.addEventListener('click', closeMobileMenu); }); subDropdowns.forEach(subDropdown => { const toggle = subDropdown.querySelector('.nav-submenu-toggle'); if (!toggle) return; toggle.setAttribute('aria-expanded', 'false'); toggle.addEventListener('click', (event) => { event.preventDefault(); event.stopPropagation(); const willOpen = !subDropdown.classList.contains('open'); subDropdowns.forEach(item => { item.classList.remove('open'); item.querySelector('.nav-submenu-toggle')?.setAttribute('aria-expanded', 'false'); }); subDropdown.classList.toggle('open', willOpen); toggle.setAttribute('aria-expanded', String(willOpen)); }); }); document.addEventListener('click', (event) => { if (!navMenu.contains(event.target)) { subDropdowns.forEach(item => { item.classList.remove('open'); item.querySelector('.nav-submenu-toggle')?.setAttribute('aria-expanded', 'false'); }); } }); document.addEventListener('keydown', (event) => { if (event.key === 'Escape') { closeMobileMenu(); } }); const handleNavBreakpointChange = (event) => { if (!event.matches) { closeMobileMenu(); } }; if (mobileNavQuery.addEventListener) { mobileNavQuery.addEventListener('change', handleNavBreakpointChange); } else if (mobileNavQuery.addListener) { mobileNavQuery.addListener(handleNavBreakpointChange); } } // --- Scroll Animations (Intersection Observer) --- const animateElements = document.querySelectorAll('[data-animate]'); const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('visible'); observer.unobserve(entry.target); } }); }, { threshold: 0.15, rootMargin: '0px 0px -40px 0px' }); animateElements.forEach(el => observer.observe(el)); // --- Animated Counter --- const counters = document.querySelectorAll('[data-count]'); const counterObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { animateCounter(entry.target); counterObserver.unobserve(entry.target); } }); }, { threshold: 0.5 }); counters.forEach(counter => { // Keep the final value in the HTML for search engines, then animate it in the browser. counter.textContent = '0'; counterObserver.observe(counter); }); function animateCounter(el) { const target = parseInt(el.dataset.count); const duration = 2000; const start = performance.now(); function update(now) { const elapsed = now - start; const progress = Math.min(elapsed / duration, 1); // Ease out cubic const eased = 1 - Math.pow(1 - progress, 3); const current = Math.round(eased * target); el.textContent = current.toLocaleString('de-DE'); if (progress < 1) { requestAnimationFrame(update); } } requestAnimationFrame(update); } // --- Project Filter System --- const projectCards = document.querySelectorAll('.project-card'); const tagBtns = document.querySelectorAll('.tag-btn'); const filterCountry = document.getElementById('filterCountry'); const filterCategory = document.getElementById('filterCategory'); const filterSoftware = document.getElementById('filterSoftware'); const legendItems = document.querySelectorAll('.legend-item'); function filterProjects() { const activeTag = document.querySelector('.tag-btn.active')?.dataset.tag || 'all'; const country = filterCountry ? filterCountry.value : 'all'; const category = filterCategory ? filterCategory.value : 'all'; const software = filterSoftware ? filterSoftware.value : 'all'; projectCards.forEach(card => { const cardTags = (card.dataset.tags || '').split(','); const cardCountry = card.dataset.country || ''; let show = true; // Tag filter if (activeTag !== 'all' && !cardTags.includes(activeTag)) show = false; // Country filter if (country !== 'all' && cardCountry !== country) show = false; // Category dropdown if (category !== 'all' && !cardTags.includes(category)) show = false; // Software dropdown if (software !== 'all' && !cardTags.includes(software)) show = false; if (show) { card.classList.remove('hidden'); card.style.animation = 'fadeInUp 0.4s ease forwards'; } else { card.classList.add('hidden'); } }); // Update map markers document.querySelectorAll('.map-marker').forEach(marker => { const markerCountry = marker.dataset.country; if (country === 'all' || markerCountry === country) { marker.style.opacity = '1'; } else { marker.style.opacity = '0.2'; } }); } // Tag button clicks tagBtns.forEach(btn => { btn.addEventListener('click', () => { tagBtns.forEach(b => b.classList.remove('active')); btn.classList.add('active'); filterProjects(); }); }); // Dropdown filter changes [filterCountry, filterCategory, filterSoftware].forEach(select => { if (select) select.addEventListener('change', filterProjects); }); // Map legend clicks legendItems.forEach(item => { item.addEventListener('click', () => { const country = item.dataset.country; const isActive = item.classList.contains('active'); legendItems.forEach(l => l.classList.remove('active')); if (isActive) { if (filterCountry) filterCountry.value = 'all'; } else { item.classList.add('active'); if (filterCountry) filterCountry.value = country; } filterProjects(); }); }); // Map marker clicks document.querySelectorAll('.map-marker').forEach(marker => { marker.addEventListener('click', () => { const country = marker.dataset.country; if (filterCountry) filterCountry.value = country; legendItems.forEach(l => { l.classList.toggle('active', l.dataset.country === country); }); filterProjects(); }); }); // --- Hero Particles --- const particlesContainer = document.getElementById('heroParticles'); if (particlesContainer) { createParticles(particlesContainer); } function createParticles(container) { const count = 30; for (let i = 0; i < count; i++) { const particle = document.createElement('div'); const size = Math.random() * 4 + 2; particle.style.cssText = ` position: absolute; width: ${size}px; height: ${size}px; background: rgba(49, 130, 206, ${Math.random() * 0.15 + 0.05}); border-radius: 50%; left: ${Math.random() * 100}%; top: ${Math.random() * 100}%; animation: particleFloat ${Math.random() * 8 + 6}s ease-in-out infinite; animation-delay: ${Math.random() * -8}s; `; container.appendChild(particle); } // Add keyframes if not present if (!document.getElementById('particleStyles')) { const style = document.createElement('style'); style.id = 'particleStyles'; style.textContent = ` @keyframes particleFloat { 0%, 100% { transform: translate(0, 0) scale(1); opacity: 0.5; } 25% { transform: translate(${rand()}px, ${rand()}px) scale(1.2); opacity: 0.8; } 50% { transform: translate(${rand()}px, ${rand()}px) scale(0.8); opacity: 0.4; } 75% { transform: translate(${rand()}px, ${rand()}px) scale(1.1); opacity: 0.7; } } @keyframes fadeInUp { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } } `; document.head.appendChild(style); } } function rand() { return Math.round((Math.random() - 0.5) * 60); } // --- Smooth Scroll for anchor links --- document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', (e) => { const targetId = anchor.getAttribute('href'); if (targetId === '#') return; const target = document.querySelector(targetId); if (target) { e.preventDefault(); const offset = navbar.offsetHeight + 20; const position = target.getBoundingClientRect().top + window.scrollY - offset; window.scrollTo({ top: position, behavior: 'smooth' }); } }); }); // --- Form submission feedback --- const contactForm = document.querySelector('.contact-form'); if (contactForm) { contactForm.addEventListener('submit', (e) => { const btn = contactForm.querySelector('button[type="submit"]'); btn.textContent = 'Wird gesendet...'; btn.style.opacity = '0.7'; }); } });