Files
Unger-Bus-Unternehmen/js/main.js
ANDJ 306ea0862d Initiale Unger Reisen Website (statisch, mobil optimiert)
- One-Page-Site nach Design-Handoff: Hero mit scroll-gesteuertem Bus,
  Stats, Leistungen, Foto-Band, Flotte, interaktive MapLibre-Karte, Karriere, Footer
- Vollständige Handy-Optimierung: Hamburger-Menü, responsive Grids, touch-freundliche Karte
- Bus-Foto lokal in assets/

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 17:04:35 +02:00

261 lines
9.8 KiB
JavaScript

/* =========================================================
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',
'<span class="fleetCard__seatsWord">bis</span>' +
'<span class="display fleetCard__seatsNum">' + f.seats + '</span>' +
'<span class="fleetCard__seatsWord">Sitze</span>');
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',
'<div class="pin__title">' + title + '</div>' +
'<div class="pin__sub">' + sub + '</div>'));
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();
}
})();