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>
This commit is contained in:
260
js/main.js
Normal file
260
js/main.js
Normal file
@@ -0,0 +1,260 @@
|
||||
/* =========================================================
|
||||
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();
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user