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 @@ + + +