diff --git a/app.py b/app.py
index 8456717..5b99e20 100644
--- a/app.py
+++ b/app.py
@@ -1,7 +1,7 @@
from pathlib import Path
import json
from typing import Tuple, Dict, List
-from flask import Flask, render_template, request
+from flask import Flask, render_template, request, send_from_directory
app = Flask(__name__)
@@ -77,5 +77,16 @@ def index():
)
+@app.route('/manifest.webmanifest')
+def manifest_file():
+ return send_from_directory(Path(__file__).parent / 'static', 'manifest.webmanifest', mimetype='application/manifest+json')
+
+
+@app.route('/sw.js')
+def service_worker():
+ # Service Worker muss auf Top-Level liegen
+ return send_from_directory(Path(__file__).parent / 'static', 'sw.js', mimetype='application/javascript')
+
+
if __name__ == "__main__":
app.run(debug=True)
diff --git a/static/manifest.webmanifest b/static/manifest.webmanifest
new file mode 100644
index 0000000..a46cbdf
--- /dev/null
+++ b/static/manifest.webmanifest
@@ -0,0 +1,18 @@
+{
+ "name": "Wordle‑Cheater",
+ "short_name": "W‑Cheater",
+ "description": "Hilft bei der Lösung deutschsprachiger Wordle‑Rätsel mit Positions- und Buchstabenfiltern.",
+ "start_url": "/",
+ "scope": "/",
+ "display": "standalone",
+ "background_color": "#0b1220",
+ "theme_color": "#0b1220",
+ "icons": [
+ {
+ "src": "/static/favicon.svg",
+ "sizes": "any",
+ "type": "image/svg+xml",
+ "purpose": "any maskable"
+ }
+ ]
+}
diff --git a/static/sw.js b/static/sw.js
new file mode 100644
index 0000000..577c368
--- /dev/null
+++ b/static/sw.js
@@ -0,0 +1,39 @@
+const CACHE_NAME = 'wordle-cheater-v1';
+const APP_SHELL = [
+ '/',
+ '/static/favicon.svg',
+];
+
+self.addEventListener('install', (event) => {
+ event.waitUntil(
+ caches.open(CACHE_NAME).then((cache) => cache.addAll(APP_SHELL))
+ );
+});
+
+self.addEventListener('activate', (event) => {
+ event.waitUntil(
+ caches.keys().then((keys) => Promise.all(
+ keys.map((k) => (k === CACHE_NAME ? null : caches.delete(k)))
+ ))
+ );
+});
+
+self.addEventListener('fetch', (event) => {
+ const { request } = event;
+ // Netzwerk zuerst für HTML, sonst Cache-First
+ if (request.mode === 'navigate') {
+ event.respondWith(
+ fetch(request).catch(() => caches.match('/'))
+ );
+ return;
+ }
+ event.respondWith(
+ caches.match(request).then((cached) =>
+ cached || fetch(request).then((resp) => {
+ const copy = resp.clone();
+ caches.open(CACHE_NAME).then((cache) => cache.put(request, copy));
+ return resp;
+ })
+ )
+ );
+});
diff --git a/templates/index.html b/templates/index.html
index 2f6f65f..3e444b5 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -15,6 +15,8 @@
+
+
+