Add PWA support for MischLabs
All checks were successful
Build & Push Docker Image to Gitea Registry / build-and-push (push) Successful in 11s

This commit is contained in:
Kroonk
2026-05-21 13:56:14 +02:00
parent 81ddb781ca
commit 1ecbcc38ae
11 changed files with 178 additions and 2 deletions

View File

@@ -8,6 +8,7 @@ Eine ansprechende Landing Page fuer mischlabs.de, die alle Dienste als Kacheln m
- [x] Dienste-Liste vom Screenshot erfasst (9 Services inkl. MiChess)
- [x] Landing Page Design und Implementierung (index.html + style.css)
- [x] Docker-Setup (Dockerfile, docker-compose.yml, nginx.conf)
- [x] PWA-Grundlage fuer iOS/Home-Screen-Installation
- [x] Push zum Remote
- [x] MiChess-Kachel hinzugefuegt (Commit lokal, Push steht aus wg. Auth-Fehler)
- [ ] Push des MiChess-Commits (Git-Auth-Token erneuern)
@@ -31,13 +32,14 @@ Eine ansprechende Landing Page fuer mischlabs.de, die alle Dienste als Kacheln m
| 8 | tom.mischlabs.de | Persoenliche Seite | #ec4899 (Pink) |
## Technische Entscheidungen
- **Reines HTML + CSS** (kein Framework, kein JS) - maximale Performance, minimales Docker-Image
- **Reines HTML + CSS mit minimalem PWA-JS** (kein Framework) - maximale Performance, minimales Docker-Image
- **nginx:alpine** als Base-Image - extrem leichtgewichtig (~5MB)
- **Google Fonts (Inter)** fuer moderne Typografie
- **Inline SVG Icons** - keine externen Dependencies
- **CSS Grid** mit `auto-fill` + `minmax(240px, 1fr)` fuer responsives Layout
- **Dark Theme** mit subtilen Glow-Effekten
- **Staggered Animations**: Kacheln faden nacheinander ein (9 Stueck)
- **PWA**: Manifest, Apple-Touch-Icon, App-Icons und Service Worker fuer installierbare iOS/Web-App
## Docker-Setup
- **Container-Name**: `mischlabs`
@@ -51,6 +53,9 @@ Eine ansprechende Landing Page fuer mischlabs.de, die alle Dienste als Kacheln m
mischlabs/
index.html - Haupt-HTML mit allen 9 Service-Kacheln
style.css - Komplettes Styling (Dark Theme, Grid, Animationen)
manifest.webmanifest - PWA-Metadaten fuer installierbare App
sw.js - Schlanker Service Worker mit App-Shell-Cache
icons/ - App-Icons fuer iOS/PWA
Dockerfile - nginx:alpine mit kopierten Static Files
docker-compose.yml - Container-Definition (Name: mischlabs, Port 8085)
nginx.conf - Server-Konfiguration, Caching, Security-Headers
@@ -72,3 +77,28 @@ mischlabs/
- Git-Push-Fehler aufgetreten (Authentication failed) - Token muss erneuert werden
- DNS: Alte A-Records (185.181.104.242) von INWX wurden geloescht, nur noch Tunnel-Eintraege
- NAS hat kein git installiert - entweder Git Server Paket installieren oder wget nutzen
## PWA-Stand 2026-05-21
- MischLabs wurde als PWA vorbereitet.
- iOS-relevante Meta-Tags in `index.html`:
- `apple-mobile-web-app-capable`
- `apple-mobile-web-app-status-bar-style`
- `apple-mobile-web-app-title`
- `apple-touch-icon`
- Manifest:
- `name`: MischLabs
- `display`: standalone
- `theme_color`: #10b981
- `background_color`: #0a0a0f
- Icons:
- `icons/apple-touch-icon.png` (180x180)
- `icons/icon-192.png` (192x192)
- `icons/icon-512.png` (512x512)
- `icons/maskable-512.png` (512x512, maskable)
- `icons/app-icon.svg` als editierbare Quelle
- Service Worker:
- Cache-Name: `mischlabs-pwa-v1`
- Cached die App-Shell (`/`, `index.html`, `style.css?v=8`, Manifest und Icons)
- Navigationen fallen offline auf `index.html` zurueck
- Dockerfile kopiert Manifest, Service Worker und Icons in das Nginx-Image.
- `nginx.conf` liefert `manifest.webmanifest` mit `application/manifest+json` und `sw.js` mit `no-cache` aus.

View File

@@ -3,5 +3,8 @@ FROM nginx:alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY index.html /usr/share/nginx/html/
COPY style.css /usr/share/nginx/html/
COPY manifest.webmanifest /usr/share/nginx/html/
COPY sw.js /usr/share/nginx/html/
COPY icons/ /usr/share/nginx/html/icons/
EXPOSE 80

27
icons/app-icon.svg Normal file
View File

@@ -0,0 +1,27 @@
<svg width="1024" height="1024" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bg" x1="176" y1="116" x2="848" y2="908" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#14f1c6"/>
<stop offset="0.52" stop-color="#10b981"/>
<stop offset="1" stop-color="#0f766e"/>
</linearGradient>
<radialGradient id="glow" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(320 236) rotate(52) scale(690)">
<stop stop-color="#ecfeff" stop-opacity="0.9"/>
<stop offset="0.42" stop-color="#99f6e4" stop-opacity="0.2"/>
<stop offset="1" stop-color="#042f2e" stop-opacity="0"/>
</radialGradient>
<filter id="shadow" x="120" y="132" width="784" height="784" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feDropShadow dx="0" dy="28" stdDeviation="38" flood-color="#042f2e" flood-opacity="0.34"/>
</filter>
</defs>
<rect width="1024" height="1024" rx="230" fill="#071412"/>
<rect x="72" y="72" width="880" height="880" rx="196" fill="url(#bg)"/>
<rect x="72" y="72" width="880" height="880" rx="196" fill="url(#glow)"/>
<g filter="url(#shadow)">
<path d="M294 690V334h88l130 205 130-205h88v356h-86V481L538 648h-52L380 482v208h-86Z" fill="#f8fffc"/>
<path d="M283 752h458" stroke="#d9fff4" stroke-width="42" stroke-linecap="round" opacity="0.72"/>
</g>
<circle cx="746" cy="276" r="34" fill="#ecfeff" opacity="0.86"/>
<circle cx="812" cy="350" r="17" fill="#ecfeff" opacity="0.64"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
icons/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
icons/icon-192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
icons/icon-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

BIN
icons/maskable-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

View File

@@ -4,7 +4,16 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MischLabs - Services</title>
<link rel="stylesheet" href="style.css?v=7">
<meta name="description" content="MischLabs service hub for self-hosted apps.">
<meta name="theme-color" content="#10b981">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="MischLabs">
<link rel="manifest" href="/manifest.webmanifest">
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="192x192" href="/icons/icon-192.png">
<link rel="icon" type="image/png" sizes="512x512" href="/icons/icon-512.png">
<link rel="stylesheet" href="style.css?v=8">
</head>
<body>
<div class="bg-glow bg-glow-1"></div>
@@ -160,5 +169,12 @@
<footer>
<p>&copy; 2026 MischLabs &mdash; Self-Hosted with care</p>
</footer>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js').catch(() => {});
});
}
</script>
</body>
</html>

34
manifest.webmanifest Normal file
View File

@@ -0,0 +1,34 @@
{
"name": "MischLabs",
"short_name": "MischLabs",
"description": "Self-hosted service hub for MischLabs.",
"lang": "de",
"start_url": "/",
"scope": "/",
"display": "standalone",
"orientation": "portrait",
"background_color": "#0a0a0f",
"theme_color": "#10b981",
"categories": ["productivity", "utilities"],
"icons": [
{
"src": "/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}

View File

@@ -9,6 +9,16 @@ server {
try_files $uri $uri/ /index.html;
}
location = /manifest.webmanifest {
default_type application/manifest+json;
add_header Cache-Control "public, max-age=3600" always;
}
location = /sw.js {
default_type application/javascript;
add_header Cache-Control "no-cache" always;
}
# Cache static assets
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
expires 7d;

56
sw.js Normal file
View File

@@ -0,0 +1,56 @@
const CACHE_NAME = 'mischlabs-pwa-v1';
const APP_SHELL = [
'/',
'/index.html',
'/style.css?v=8',
'/manifest.webmanifest',
'/icons/icon-192.png',
'/icons/icon-512.png',
'/icons/apple-touch-icon.png'
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(APP_SHELL))
);
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) => (
Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)))
))
);
self.clients.claim();
});
self.addEventListener('fetch', (event) => {
const request = event.request;
if (request.method !== 'GET') {
return;
}
if (request.mode === 'navigate') {
event.respondWith(
fetch(request).catch(() => caches.match('/index.html'))
);
return;
}
event.respondWith(
caches.match(request).then((cached) => (
cached || fetch(request).then((response) => {
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
const responseToCache = response.clone();
caches.open(CACHE_NAME).then((cache) => cache.put(request, responseToCache));
return response;
})
))
);
});