Files
VBB-Status-Web-App/index.html
T
2026-07-10 12:16:47 +02:00

541 lines
27 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="theme-color" content="#000000">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<title>VBB Netz Status</title>
<link rel="manifest" href="manifest.json">
<link rel="icon" type="image/png" href="images/favicon.png">
<link rel="apple-touch-icon" href="images/favicon.png">
<link rel="stylesheet" href="styles.css">
</head>
<!-- Frühe Verbindungen: spart DNS+TLS-Zeit beim ersten Karten-/API-Zugriff -->
<link rel="preconnect" href="https://api.transitous.org" crossorigin>
<link rel="preconnect" href="https://a.basemaps.cartocdn.com" crossorigin>
<link rel="dns-prefetch" href="https://b.basemaps.cartocdn.com">
<link rel="dns-prefetch" href="https://c.basemaps.cartocdn.com">
<link rel="dns-prefetch" href="https://d.basemaps.cartocdn.com">
<link rel="preconnect" href="https://unpkg.com" crossorigin>
<!-- Leaflet CSS für Live-Map -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""/>
<!-- Leaflet JS für Live-Map -->
<script defer src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""></script>
</head>
<body>
<div class="offline-indicator" id="offlineIndicator" role="status">
📵 Offline-Modus - Zeige gespeicherte Daten
</div>
<div class="ptr-container" id="ptrContainer">
<div class="ptr-spinner"></div>
</div>
<div class="header">
<div class="header-content">
<div class="header-title">
<h1>VBB NETZ STATUS</h1>
<div class="subtitle">Live-Nahverkehr für Berlin & Brandenburg</div>
<div class="author">by Aaron K. & Claude (Anthropic)</div>
</div>
<button class="menu-btn" id="menuBtn" aria-label="Menü öffnen">
☰
</button>
</div>
</div>
<!-- Navigation Menu -->
<nav class="nav-menu" id="navMenu" aria-label="Hauptnavigation">
<div class="nav-header">
<h3>Funktionen</h3>
<button class="nav-close" id="navClose" aria-label="Menü schließen">×</button>
</div>
<div class="nav-items">
<button class="nav-item" data-view="home">
<span class="nav-icon">🏠</span>
<span class="nav-label">Start</span>
</button>
<button class="nav-item" data-view="departures">
<span class="nav-icon">🚇</span>
<span class="nav-label">Abfahrten</span>
</button>
<button class="nav-item" data-view="journey">
<span class="nav-icon">🗺️</span>
<span class="nav-label">Routenplaner</span>
</button>
<button class="nav-item" data-view="livemap">
<span class="nav-icon">🌍</span>
<span class="nav-label">Live-Map</span>
</button>
<button class="nav-item" data-view="developer">
<span class="nav-icon">👨‍💻</span>
<span class="nav-label">Für Entwickler</span>
</button>
</div>
</nav>
<div class="nav-overlay" id="navOverlay"></div>
<!-- View: Home -->
<div class="view-container active" id="view-home">
<div class="home-container">
<!-- Combined Hero + Navigation Box -->
<div class="home-hero-combined">
<!-- Navigation Hint OHNE Pfeil -->
<div class="home-nav-hint">
<div class="home-hint-text">
Tippe <strong>oben rechts auf "≡"</strong> um das Menü zu öffnen.<br>
Wähle dann <strong>Abfahrten</strong>, <strong>Routenplaner</strong> oder <strong>Live-Map</strong>.
</div>
</div>
<!-- Hero Section -->
<div class="home-hero-title">
<h1>VBB NETZ STATUS</h1>
<p>Live-Nahverkehr für Berlin & Brandenburg</p>
<div class="home-version" id="homeVersion"></div>
</div>
</div>
<!-- Favoriten (per JS aus localStorage gerendert) -->
<div class="home-favorites" id="homeFavorites" style="display: none;"></div>
<!-- Quick Info Cards -->
<div class="home-quick-info">
<button class="home-info-card home-info-card-btn" data-view="departures"
aria-label="Live-Abfahrten öffnen">
<div class="home-info-icon">🚇</div>
<div class="home-info-text">
<strong>Live-Abfahrten</strong>
<p>Echtzeit für alle VBB-Stationen</p>
</div>
</button>
<button class="home-info-card home-info-card-btn" data-view="journey"
aria-label="Routenplaner öffnen">
<div class="home-info-icon">🗺️</div>
<div class="home-info-text">
<strong>Routenplaner</strong>
<p>Schnellste Verbindungen finden</p>
</div>
</button>
<button class="home-info-card home-info-card-btn" data-view="livemap"
aria-label="Live-Map öffnen">
<div class="home-info-icon">🌍</div>
<div class="home-info-text">
<strong>Live-Map</strong>
<p>Fahrzeuge in Echtzeit verfolgen</p>
</div>
</button>
<button class="home-info-card home-info-card-btn" data-view="developer"
aria-label="Entwickler-Bereich mit Changelog öffnen">
<div class="home-info-icon">👨‍💻</div>
<div class="home-info-text">
<strong>Für Entwickler</strong>
<p>Changelog &amp; Infos – <span id="homeDevVersion"></span></p>
</div>
</button>
</div>
<!-- Footer -->
<div class="home-footer">
<p><strong>Inoffizielle App</strong> – Nicht von VBB/BVG betrieben</p>
<p class="home-footer-small">Made with ❤️ for Berlin & Brandenburg</p>
<p class="home-footer-github">
<a href="https://github.com/Serverlele30/VBB-Status-Web-App" target="_blank">
⭐ Star on GitHub
</a>
</p>
</div>
</div>
</div>
<!-- View: Abfahrten -->
<div class="view-container" id="view-departures">
<div class="container">
<!-- Favoriten-Chips: 1-Tap-Wechsel zwischen deinen Stationen -->
<div class="departure-favorites" id="departureFavorites" style="display: none;"
role="group" aria-label="Favoriten-Stationen"></div>
<div class="controls">
<div class="search-row">
<div class="search-container">
<input
type="text"
id="stationSearch"
class="search-input"
placeholder="Station suchen..."
aria-label="Station suchen"
autocomplete="off"
>
<button class="clear-btn" id="clearBtn" aria-label="Suche löschen">×</button>
<div class="suggestions" id="suggestions"></div>
</div>
<button class="location-btn-side" id="locationBtn" title="Nächste Station per GPS finden" aria-label="Nächste Station per GPS finden">
📍
</button>
</div>
<div class="button-group">
<button class="btn" id="refreshBtn" disabled>🔄 Aktualisieren</button>
</div>
<!-- Filter für Linientypen -->
<div class="departure-filters" id="departureFilters" style="display: none;">
<div class="filter-label">Filter:</div>
<div class="filter-chips">
<button class="filter-btn active" data-filter="all">Alle</button>
<button class="filter-btn" data-filter="subway">🚇 U-Bahn</button>
<button class="filter-btn" data-filter="suburban">🚆 S-Bahn</button>
<button class="filter-btn" data-filter="tram">🚊 Tram</button>
<button class="filter-btn" data-filter="bus">🚌 Bus</button>
<button class="filter-btn" data-filter="regional">🚄 Regional</button>
</div>
</div>
</div>
<div id="departuresContainer" class="departures-container"></div>
</div>
</div>
<!-- View: Routenplaner -->
<div class="view-container" id="view-journey">
<div class="container">
<!-- Gespeicherte Routen: 1-Tap-Suche für Pendler -->
<div class="departure-favorites" id="journeyFavorites" style="display: none;"
role="group" aria-label="Gespeicherte Routen"></div>
<div class="controls">
<div class="journey-inputs">
<div class="journey-input-group">
<label class="journey-label">🔵 Von</label>
<div class="search-row">
<div class="search-container">
<input
type="text"
id="journeyFrom"
class="search-input"
placeholder="Startstation..."
aria-label="Startstation"
autocomplete="off"
>
<button class="clear-btn" id="clearFromBtn" aria-label="Startstation löschen">×</button>
<div class="suggestions" id="suggestionsFrom"></div>
</div>
<button class="location-btn-side" id="locationFromBtn" title="Aktuellen Standort nutzen" aria-label="Aktuellen Standort als Start nutzen">
📍
</button>
</div>
</div>
<button class="swap-btn" id="swapBtn" title="Start und Ziel tauschen" aria-label="Start und Ziel tauschen">
⇅
</button>
<div class="journey-input-group">
<label class="journey-label">🔴 Nach</label>
<div class="search-row">
<div class="search-container">
<input
type="text"
id="journeyTo"
class="search-input"
placeholder="Zielstation..."
aria-label="Zielstation"
autocomplete="off"
>
<button class="clear-btn" id="clearToBtn" aria-label="Zielstation löschen">×</button>
<div class="suggestions" id="suggestionsTo"></div>
</div>
</div>
</div>
</div>
<!-- Via-Station (optional) -->
<div class="journey-via-row">
<label class="journey-label" for="journeyVia">📌 Über (optional)</label>
<div class="search-container">
<input type="text" id="journeyVia" class="search-input"
placeholder="Zwischenstation..." aria-label="Zwischenstation (optional)"
autocomplete="off">
<button class="clear-btn" id="clearViaBtn" aria-label="Zwischenstation löschen">×</button>
<div class="suggestions" id="suggestionsVia"></div>
</div>
</div>
<!-- Routing-Optionen -->
<div class="journey-options-row">
<label class="journey-label">⚙️ Optionen</label>
<div class="filter-chips">
<button class="filter-btn journey-option-chip" data-option="wheelchair"
aria-pressed="false" title="Nur barrierefreie Verbindungen">♿ Barrierefrei</button>
<button class="filter-btn journey-option-chip" data-option="bike"
aria-pressed="false" title="Nur Verbindungen mit Fahrrad-Mitnahme">🚲 Fahrrad dabei</button>
</div>
</div>
<!-- Zeitwahl: Abfahrt/Ankunft -->
<div class="journey-time-row">
<label class="journey-label" for="journeyTimeMode">🕐 Zeit</label>
<div class="journey-time-controls">
<select id="journeyTimeMode" class="journey-time-select" aria-label="Abfahrts- oder Ankunftszeit">
<option value="departure">Abfahrt um</option>
<option value="arrival">Ankunft bis</option>
</select>
<input type="datetime-local" id="journeyTimeInput" class="journey-time-input"
aria-label="Datum und Uhrzeit (leer = jetzt)">
<button type="button" id="journeyTimeClear" class="clear-btn journey-time-clear"
aria-label="Zeitwahl zurücksetzen" title="Zurücksetzen (= jetzt)">×</button>
</div>
<div class="journey-time-hint">Leer lassen = jetzt abfahren</div>
</div>
<div class="button-group">
<div style="font-size: 12px; opacity: 0.7; text-align: center; margin-bottom: 10px; color: #FFED00;">
Garantiert mind. 5 Min Umstiegszeit
</div>
<button class="btn btn-full-width" id="searchJourneyBtn" disabled>🔍 Route suchen</button>
</div>
<!-- Verkehrsmittel-Filter -->
<div class="journey-transport-filters">
<div class="filter-label">Verkehrsmittel:</div>
<div class="filter-chips">
<button class="transport-filter-btn active" data-transport="subway">🚇 U-Bahn</button>
<button class="transport-filter-btn active" data-transport="suburban">🚆 S-Bahn</button>
<button class="transport-filter-btn active" data-transport="tram">🚊 Tram</button>
<button class="transport-filter-btn active" data-transport="bus">🚌 Bus</button>
<button class="transport-filter-btn active" data-transport="regional">🚄 Regional</button>
<button class="transport-filter-btn active" data-transport="express">🚅 Express</button>
</div>
</div>
</div>
<div id="journeyContainer" class="journey-container"></div>
</div>
</div>
<!-- View: Live-Map -->
<div class="view-container" id="view-livemap">
<div class="livemap-container">
<div class="livemap-controls">
<div class="livemap-info">
<span id="vehicleCount" aria-live="polite">🚇 Lade Fahrzeuge...</span>
</div>
<div class="livemap-filters">
<button class="filter-btn locate-btn" id="locateBtn"
aria-label="Karte auf meinen Standort zentrieren"
title="Auf meinen Standort zentrieren">📍</button>
<button class="filter-btn active" data-type="all">🌐 Alle</button>
<button class="filter-btn" data-type="subway">🚇 U-Bahn</button>
<button class="filter-btn" data-type="suburban">🚆 S-Bahn</button>
<button class="filter-btn" data-type="tram">🚊 Tram</button>
<button class="filter-btn" data-type="bus">🚌 Bus</button>
</div>
</div>
<div id="liveMap"></div>
</div>
</div>
<!-- View: Für Entwickler -->
<div class="view-container" id="view-developer">
<div class="container">
<div class="developer-header">
<h2>👨‍💻 Für Entwickler</h2>
<p class="developer-subtitle">Technische Details, Changelog & Funktionen</p>
</div>
<!-- Developer Tabs -->
<div class="developer-tabs">
<button class="dev-tab active" data-tab="changelog">📋 Changelog</button>
<button class="dev-tab" data-tab="info">ℹ️ Info</button>
<button class="dev-tab" data-tab="features">✨ Features</button>
</div>
<!-- Tab: Changelog -->
<div class="dev-tab-content active" id="dev-changelog">
<div id="changelogContent" class="changelog-container">
<div class="loading-detail">⏳ Lade Changelog...</div>
</div>
</div>
<!-- Tab: Info -->
<div class="dev-tab-content" id="dev-info">
<div class="dev-info-container">
<div class="dev-info-box">
<h3>🚀 Version Info</h3>
<table class="dev-info-table">
<tr><td>Version:</td><td><strong id="devVersion"></strong></td></tr>
<tr><td>Release:</td><td id="devRelease"></td></tr>
<tr><td>API-Budget:</td><td id="devApiBudget">–</td></tr>
<tr><td>Lizenz:</td><td>MIT</td></tr>
<tr><td>Status:</td><td><span class="status-badge-dev">✅ Stabil</span></td></tr>
</table>
</div>
<div class="dev-info-box">
<h3>📦 Dependencies</h3>
<table class="dev-info-table">
<tr><td>Node.js:</td><td>≥ 16.0.0</td></tr>
<tr><td>npm:</td><td>≥ 8.0.0</td></tr>
<tr><td>mime-types:</td><td>^2.1.35</td></tr>
</table>
</div>
<div class="dev-info-box">
<h3>🛠️ Tech Stack</h3>
<table class="dev-info-table">
<tr><td>Frontend:</td><td>Vanilla JS, CSS3, HTML5</td></tr>
<tr><td>Maps:</td><td>Leaflet.js</td></tr>
<tr><td>API:</td><td>Transitous (MOTIS 2)</td></tr>
<tr><td>Daten:</td><td><a href="https://transitous.org/sources/" target="_blank" rel="noopener" style="color: #FFED00;">Transitous-Quellen</a> (inkl. DELFI/VBB &amp; OpenStreetMap)</td></tr>
<tr><td>PWA:</td><td>Service Worker, Manifest</td></tr>
<tr><td>Server:</td><td>Node.js HTTPS</td></tr>
</table>
</div>
<div class="dev-info-box">
<h3>📊 Architektur</h3>
<table class="dev-info-table">
<tr><td>Module:</td><td>6 (api, transitous, app, livemap, changelog, extras)</td></tr>
<tr><td>Datenquelle:</td><td>Transitous / MOTIS 2</td></tr>
<tr><td>Tests:</td><td>Adapter- + Smoke-Suite (GitHub Actions)</td></tr>
<tr><td>Code:</td><td>~8.500 Zeilen (HTML, CSS, JS)</td></tr>
</table>
</div>
<a href="https://github.com/Serverlele30/VBB-Status-Web-App"
target="_blank"
class="github-link-btn">
💻 Code auf GitHub ansehen
</a>
</div>
</div>
<!-- Tab: Features Detail -->
<div class="dev-tab-content" id="dev-features">
<div class="dev-features-container">
<details class="feature-detail" open>
<summary>🚇 Abfahrten-Monitor</summary>
<div class="feature-content">
<p><strong>Echtzeit-Abfahrten für alle VBB-Stationen</strong></p>
<ul>
<li>GPS-Standortsuche (nur Mobile)</li>
<li>Manuelle Stationssuche</li>
<li>Auto-Refresh alle 30s</li>
<li>Verspätungsanzeige</li>
<li>Linien-Filter</li>
<li>Detail-Ansicht mit Zwischenhalten</li>
</ul>
<p class="feature-api"><strong>API:</strong> <code>GET /v6/stoptimes</code></p>
</div>
</details>
<details class="feature-detail">
<summary>🗺️ Routenplaner</summary>
<div class="feature-content">
<p><strong>Intelligente Routenplanung im VBB-Netz</strong></p>
<ul>
<li>Von/Nach Suche mit Autocomplete</li>
<li>GPS-Standort als Start/Ziel (nur Mobile)</li>
<li>Mehrere Routenoptionen</li>
<li>Umstiegsanzeige mit Zeiten</li>
<li>Verkehrsmittel-Filter</li>
<li>Detail-Ansicht mit allen Haltestellen</li>
</ul>
<p class="feature-api"><strong>API:</strong> <code>GET /v6/plan</code></p>
</div>
</details>
<details class="feature-detail">
<summary>🌍 Live-Map</summary>
<div class="feature-content">
<p><strong>Echtzeit-Fahrzeugverfolgung</strong></p>
<ul>
<li>Leaflet.js Kartenintegration</li>
<li>Dynamische Bounding Box</li>
<li>Fahrzeug-Filter (U-Bahn, S-Bahn, Bus, Tram)</li>
<li>Offizielle Linienfarben aus dem GTFS-Feed</li>
<li>Strecken-Anzeige + Fokus-Modus per Fahrzeug-Tap</li>
<li>API-Poll alle 30s + lokale 1s-Animation</li>
<li>Zoom & Pan optimiert</li>
</ul>
<p class="feature-api"><strong>API:</strong> <code>GET /v6/map/trips</code></p>
</div>
</details>
<details class="feature-detail">
<summary>📱 Progressive Web App</summary>
<div class="feature-content">
<p><strong>Moderne PWA-Funktionen</strong></p>
<ul>
<li>Installierbar (Add to Home Screen)</li>
<li>Service Worker mit Caching</li>
<li>Offline-Modus</li>
<li>Favoriten &amp; Offline-Abfahrten (localStorage)</li>
<li>App-Manifest</li>
</ul>
<p class="feature-api"><strong>Cache:</strong> <code id="devCacheName"></code></p>
</div>
</details>
<details class="feature-detail">
<summary>🎨 UI/UX Features</summary>
<div class="feature-content">
<p><strong>Design & Benutzerfreundlichkeit</strong></p>
<ul>
<li>Dark Mode (Standard)</li>
<li>Responsive Design (Mobile → Desktop)</li>
<li>Touch-optimiert</li>
<li>Hover-Effekte (Desktop)</li>
<li>Accessibility (WCAG)</li>
</ul>
</div>
</details>
</div>
</div>
</div>
</div>
<!-- Detail Modal -->
<div class="detail-modal" id="detailModal">
<div class="detail-header">
<button class="detail-back-btn" id="detailBackBtn">←</button>
<div class="detail-title" id="detailTitle">Fahrzeug-Details</div>
<div style="width: 34px;"></div>
</div>
<div class="detail-content" id="detailContent">
<div class="loading-detail">⏳ Lade Details...</div>
</div>
</div>
<!-- Journey Detail Modal -->
<div class="detail-modal" id="journeyDetailModal">
<div class="detail-header">
<button class="detail-back-btn" id="journeyDetailBackBtn">←</button>
<div class="detail-title" id="journeyDetailTitle">Routen-Details</div>
<div style="width: 34px;"></div>
</div>
<div class="detail-content" id="journeyDetailContent">
<div class="loading-detail">⏳ Lade Details...</div>
</div>
</div>
<!-- Module in fester Reihenfolge (klassische Scripts, gemeinsamer Scope) -->
<script src="js/api.js"></script>
<script src="js/transitous.js"></script>
<script src="js/app.js"></script>
<script src="js/livemap.js"></script>
<script src="js/changelog.js"></script>
<script src="js/extras.js"></script>
</body>
</html>