diff --git a/DotMatrix.ttf b/DotMatrix.ttf
new file mode 100644
index 0000000..d81f879
Binary files /dev/null and b/DotMatrix.ttf differ
diff --git a/images/favicon.png b/images/favicon.png
new file mode 100644
index 0000000..06f61a4
Binary files /dev/null and b/images/favicon.png differ
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..d87f499
--- /dev/null
+++ b/index.html
@@ -0,0 +1,1556 @@
+
+
+
+
+
+
+
+
+ VBB Netz Status
+
+
+
+
+
+
+
+ 📵 Offline-Modus - Zeige gespeicherte Daten
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/manifest.json b/manifest.json
new file mode 100644
index 0000000..5097102
--- /dev/null
+++ b/manifest.json
@@ -0,0 +1,26 @@
+{
+ "name": "VBB Netz Status",
+ "short_name": "VBB Status",
+ "description": "Echtzeit-Abfahrtsmonitor für den Verkehrsverbund Berlin-Brandenburg - by Aaron K.",
+ "start_url": "/",
+ "display": "standalone",
+ "background_color": "#000000",
+ "theme_color": "#000000",
+ "orientation": "portrait",
+ "icons": [
+ {
+ "src": "images/favicon.png",
+ "sizes": "192x192",
+ "type": "image/png",
+ "purpose": "any maskable"
+ },
+ {
+ "src": "images/favicon.png",
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "any maskable"
+ }
+ ],
+ "categories": ["travel", "navigation"],
+ "lang": "de-DE"
+}
diff --git a/service-worker.js b/service-worker.js
new file mode 100644
index 0000000..a023c60
--- /dev/null
+++ b/service-worker.js
@@ -0,0 +1,69 @@
+const CACHE_NAME = 'vbb-status-v3';
+const urlsToCache = [
+ '/',
+ '/index.html',
+ '/manifest.json',
+ '/DotMatrix.ttf',
+ '/images/favicon.png'
+];
+
+// Installation
+self.addEventListener('install', event => {
+ event.waitUntil(
+ caches.open(CACHE_NAME)
+ .then(cache => {
+ console.log('Cache geöffnet');
+ return cache.addAll(urlsToCache);
+ })
+ );
+ self.skipWaiting();
+});
+
+// Aktivierung
+self.addEventListener('activate', event => {
+ event.waitUntil(
+ caches.keys().then(cacheNames => {
+ return Promise.all(
+ cacheNames.map(cacheName => {
+ if (cacheName !== CACHE_NAME) {
+ console.log('Alter Cache wird gelöscht:', cacheName);
+ return caches.delete(cacheName);
+ }
+ })
+ );
+ })
+ );
+ self.clients.claim();
+});
+
+// Fetch
+self.addEventListener('fetch', event => {
+ event.respondWith(
+ caches.match(event.request)
+ .then(response => {
+ // Cache hit - return response
+ if (response) {
+ return response;
+ }
+
+ return fetch(event.request).then(
+ response => {
+ // Check if valid response
+ if (!response || response.status !== 200 || response.type !== 'basic') {
+ return response;
+ }
+
+ // Clone the response
+ const responseToCache = response.clone();
+
+ caches.open(CACHE_NAME)
+ .then(cache => {
+ cache.put(event.request, responseToCache);
+ });
+
+ return response;
+ }
+ );
+ })
+ );
+});