/* ========================================================= Unger Reisen — behaviour - render data-driven sections (stats / services / fleet) - scroll-driven bus across the hero - nav solidify on scroll - reveal-on-scroll (IntersectionObserver) - interactive MapLibre map (two locations + route) ========================================================= */ (function () { 'use strict'; var ACCENT = '#C8102E'; var reduceMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion:reduce)').matches; /* ---------- Data ---------- */ var stats = [ { big: '2', label: 'Standorte in Bayern' }, { big: '60+', label: 'Sitzplätze pro Reisebus' }, { big: '24/7', label: 'Für Sie erreichbar' }, { big: '∞', label: 'Ziele in ganz Europa' } ]; var services = [ { no: '01', title: 'Busanmietung', desc: 'Moderne Reisebusse mit Fahrer — flexibel für einzelne Fahrten oder mehrtägige Touren.' }, { no: '02', title: 'Firmenreisen', desc: 'Betriebsausflüge, Tagungen und Events. Ihr Team sicher und entspannt am Ziel.' }, { no: '03', title: 'Vereinsfahrten', desc: 'Ausflüge, Feste und Sportevents — komfortabel für die ganze Gruppe.' }, { no: '04', title: 'Transfers', desc: 'Flughafen-, Hotel- und Event-Transfers. Pünktlich und rund um die Uhr planbar.' } ]; var fleet = [ { name: 'Reisebus', seats: '60', desc: 'Der Große für lange Strecken und große Gruppen — voll ausgestattet.', tags: ['Klimaanlage', 'WC', 'Bordküche', 'WLAN'] }, { name: 'Midibus', seats: '35', desc: 'Wendig und komfortabel — ideal für mittlere Gruppen und Tagestouren.', tags: ['Klimaanlage', 'Komfortbestuhlung'] }, { name: 'Kleinbus', seats: '19', desc: 'Persönlich und flexibel für kleine Gruppen, Transfers und Sonderfahrten.', tags: ['Flexibel', 'Wendig'] } ]; function el(tag, cls, html) { var n = document.createElement(tag); if (cls) n.className = cls; if (html != null) n.innerHTML = html; return n; } /* ---------- Render sections ---------- */ function renderStats() { var grid = document.getElementById('stats-grid'); if (!grid) return; stats.forEach(function (s) { var wrap = el('div', null); wrap.setAttribute('data-reveal', ''); wrap.appendChild(el('div', 'display stat__big', s.big)); wrap.appendChild(el('div', 'stat__label', s.label)); grid.appendChild(wrap); }); } function renderServices() { var grid = document.getElementById('services-grid'); if (!grid) return; services.forEach(function (s) { var card = el('div', 'service'); card.setAttribute('data-reveal', ''); card.appendChild(el('div', 'display service__no', s.no)); card.appendChild(el('h3', 'service__title', s.title)); card.appendChild(el('p', 'service__desc', s.desc)); grid.appendChild(card); }); } function renderFleet() { var grid = document.getElementById('fleet-grid'); if (!grid) return; fleet.forEach(function (f) { var card = el('div', 'fleetCard'); card.setAttribute('data-reveal', ''); var head = el('div', 'fleetCard__head'); head.appendChild(el('h3', 'display fleetCard__name', f.name)); var seats = el('div', 'fleetCard__seats', 'bis' + '' + f.seats + '' + 'Sitze'); head.appendChild(seats); card.appendChild(head); var body = el('div', 'fleetCard__body'); body.appendChild(el('p', 'fleetCard__desc', f.desc)); var tags = el('div', 'fleetCard__tags'); f.tags.forEach(function (t) { tags.appendChild(el('span', 'tag', t)); }); body.appendChild(tags); card.appendChild(body); grid.appendChild(card); }); } /* ---------- Nav solidify on scroll ---------- */ function initNav() { var nav = document.getElementById('nav'); if (!nav) return; var onScroll = function () { nav.classList.toggle('is-scrolled', window.scrollY > 40); }; window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); } /* ---------- Mobile menu ---------- */ function initMobileMenu() { var burger = document.getElementById('nav-burger'); var menu = document.getElementById('mobile-menu'); if (!burger || !menu) return; function setOpen(open) { menu.classList.toggle('is-open', open); burger.classList.toggle('is-open', open); burger.setAttribute('aria-expanded', open ? 'true' : 'false'); burger.setAttribute('aria-label', open ? 'Menü schließen' : 'Menü öffnen'); document.body.style.overflow = open ? 'hidden' : ''; } burger.addEventListener('click', function () { setOpen(!menu.classList.contains('is-open')); }); menu.querySelectorAll('a').forEach(function (a) { a.addEventListener('click', function () { setOpen(false); }); }); window.addEventListener('keydown', function (e) { if (e.key === 'Escape') setOpen(false); }); // Close if we grow past the mobile breakpoint. window.addEventListener('resize', function () { if (window.innerWidth > 680) setOpen(false); }); } /* ---------- Scroll-driven bus ---------- */ function initBus() { var bus = document.getElementById('bus'); var hero = document.getElementById('top'); if (!bus || !hero) return; function tick() { var hp = Math.min(1, Math.max(0, window.scrollY / (hero.offsetHeight * 0.9))); var scene = bus.parentNode; var sceneW = scene ? scene.offsetWidth : window.innerWidth; var busW = bus.offsetWidth || 400; var minX = -busW * 0.30; var maxX = sceneW - busW * 0.70; var x = minX + (maxX - minX) * hp; var bob = reduceMotion ? 0 : Math.sin(Date.now() / 380) * 4; bus.style.transform = 'translate(' + x.toFixed(1) + 'px,' + bob.toFixed(1) + 'px)'; requestAnimationFrame(tick); } requestAnimationFrame(tick); } /* ---------- Reveal on scroll ---------- */ function initReveal() { var els = Array.prototype.slice.call(document.querySelectorAll('[data-reveal]')); if ('IntersectionObserver' in window) { var io = new IntersectionObserver(function (entries) { entries.forEach(function (e) { if (e.isIntersecting) { e.target.classList.add('is-visible'); io.unobserve(e.target); } }); }, { threshold: 0.12 }); els.forEach(function (n) { io.observe(n); }); } else { els.forEach(function (n) { n.classList.add('is-visible'); }); } // Safety fallback: reveal everything after 2.5s regardless. setTimeout(function () { els.forEach(function (n) { n.classList.add('is-visible'); }); }, 2500); } /* ---------- Interactive map ---------- */ function initMap() { var HOME = [12.0517, 48.4703]; // Buch am Erlbach / Vatersdorf var BRANCH = [12.1508, 48.5449]; // Landshut var container = document.getElementById('unger-map'); if (!container || typeof maplibregl === 'undefined') return; function makePin(fill, ring, title, sub) { var wrap = el('div', 'pin'); wrap.appendChild(el('div', 'pin__label', '
' + title + '
' + '
' + sub + '
')); var dot = el('div', 'pin__dot'); dot.style.background = fill; dot.style.border = '3px solid ' + ring; dot.style.boxShadow = '0 0 0 5px ' + ACCENT + '40, 0 2px 6px rgba(0,0,0,.55)'; wrap.appendChild(dot); return wrap; } var map = new maplibregl.Map({ container: container, style: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json', center: [(HOME[0] + BRANCH[0]) / 2, (HOME[1] + BRANCH[1]) / 2], zoom: 10.4, attributionControl: { compact: true } }); map.scrollZoom.disable(); map.addControl(new maplibregl.NavigationControl({ showCompass: true, visualizePitch: false }), 'top-right'); // On phones, keep the map static so a one-finger drag scrolls the page // instead of getting trapped panning the map. if (window.matchMedia && window.matchMedia('(max-width:680px)').matches) { map.dragPan.disable(); if (map.touchZoomRotate) map.touchZoomRotate.disable(); if (map.touchPitch) map.touchPitch.disable(); } new maplibregl.Marker({ element: makePin(ACCENT, '#fff', 'Landshut', 'Niederlassung'), anchor: 'bottom' }) .setLngLat(BRANCH).addTo(map); new maplibregl.Marker({ element: makePin('#fff', ACCENT, 'Buch a. Erlbach', 'Hauptsitz'), anchor: 'bottom' }) .setLngLat(HOME).addTo(map); map.on('load', function () { map.addSource('unger-route', { type: 'geojson', data: { type: 'Feature', properties: {}, geometry: { type: 'LineString', coordinates: [HOME, BRANCH] } } }); map.addLayer({ id: 'unger-route-casing', type: 'line', source: 'unger-route', layout: { 'line-join': 'round', 'line-cap': 'round' }, paint: { 'line-color': ACCENT, 'line-width': 9, 'line-opacity': 0.18 } }); map.addLayer({ id: 'unger-route-line', type: 'line', source: 'unger-route', layout: { 'line-join': 'round', 'line-cap': 'round' }, paint: { 'line-color': ACCENT, 'line-width': 3, 'line-dasharray': [1.6, 1.4] } }); map.fitBounds([HOME, BRANCH], { padding: { top: 96, bottom: 70, left: 70, right: 70 }, duration: 0, maxZoom: 12 }); setTimeout(function () { try { map.resize(); } catch (e) {} }, 60); }); } /* ---------- Boot ---------- */ function init() { renderStats(); renderServices(); renderFleet(); initNav(); initMobileMenu(); initBus(); initReveal(); initMap(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();