Add PWA support: manifest, service worker, icons

This commit is contained in:
Andre
2026-02-27 09:55:21 +01:00
parent 5a035134b2
commit bf0e3a9038
13 changed files with 207 additions and 9 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+9
View File
@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="192" viewBox="0 0 192 192">
<rect width="192" height="192" rx="24" fill="#0F172A"/>
<circle cx="96" cy="96" r="70" fill="none" stroke="#34D399" stroke-width="6"/>
<circle cx="96" cy="96" r="55" fill="none" stroke="#34D399" stroke-width="3" opacity="0.5"/>
<line x1="96" y1="96" x2="96" y2="46" stroke="#F8FAFC" stroke-width="6" stroke-linecap="round"/>
<line x1="96" y1="96" x2="126" y2="96" stroke="#F8FAFC" stroke-width="4" stroke-linecap="round"/>
<line x1="96" y1="96" x2="96" y2="66" stroke="#34D399" stroke-width="3" stroke-linecap="round"/>
<circle cx="96" cy="96" r="6" fill="#34D399"/>
</svg>

After

Width:  |  Height:  |  Size: 676 B

+10
View File
@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
<rect width="512" height="512" rx="64" fill="#0F172A"/>
<circle cx="256" cy="256" r="180" fill="none" stroke="#34D399" stroke-width="16"/>
<circle cx="256" cy="256" r="140" fill="none" stroke="#34D399" stroke-width="8" opacity="0.5"/>
<line x1="256" y1="256" x2="256" y2="116" stroke="#F8FAFC" stroke-width="16" stroke-linecap="round"/>
<line x1="256" y1="256" x2="336" y2="256" stroke="#F8FAFC" stroke-width="10" stroke-linecap="round"/>
<line x1="256" y1="256" x2="256" y2="166" stroke="#34D399" stroke-width="8" stroke-linecap="round"/>
<circle cx="256" cy="256" r="16" fill="#34D399"/>
<text x="256" y="420" font-family="Arial" font-size="48" fill="#34D399" text-anchor="middle" font-weight="bold">NADIRI</text>
</svg>

After

Width:  |  Height:  |  Size: 827 B

+15 -5
View File
@@ -3,14 +3,19 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#0f0f0f" />
<link rel="manifest" href="/manifest.json" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#0F172A" />
<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="Nadiri Timer" />
<link rel="apple-touch-icon" href="/icon-192.svg" />
<title>Nadiri Sports</title>
<style>
html, body {
margin: 0;
padding: 0;
background: #0f0f0f;
background: #0F172A;
color: #e8e8e8;
font-family: system-ui, -apple-system, sans-serif;
overflow: hidden;
@@ -20,10 +25,15 @@
min-height: 100%;
}
</style>
<script type="module" crossorigin src="/assets/index-DwSC3Fr9.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-D9hi2kGf.css">
<script type="module" crossorigin src="/assets/index-Cax2IsI8.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CxSjc_eW.css">
</head>
<body>
<div id="root"></div>
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js');
}
</script>
</body>
</html>
+27
View File
@@ -0,0 +1,27 @@
{
"name": "Nadiri Sports Timer",
"short_name": "Nadiri Timer",
"description": "TV-optimierter Sport-Timer für Training und Fitness",
"start_url": "/",
"display": "standalone",
"background_color": "#0F172A",
"theme_color": "#0F172A",
"orientation": "landscape",
"icons": [
{
"src": "/icon-192.svg",
"sizes": "192x192",
"type": "image/svg+xml",
"purpose": "any"
},
{
"src": "/icon-512.svg",
"sizes": "512x512",
"type": "image/svg+xml",
"purpose": "any"
}
],
"categories": ["sports", "fitness", "utilities"],
"lang": "de",
"dir": "ltr"
}
+43
View File
@@ -0,0 +1,43 @@
const CACHE_NAME = 'nadiri-timer-v1';
const urlsToCache = [
'/',
'/index.html',
'/manifest.json',
'/logo.png',
'/vite.svg'
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => {
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => {
if (response) {
return response;
}
return fetch(event.request);
})
);
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== CACHE_NAME) {
return caches.delete(cacheName);
}
})
);
})
);
});
+13 -3
View File
@@ -3,14 +3,19 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#0f0f0f" />
<link rel="manifest" href="/manifest.json" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="theme-color" content="#0F172A" />
<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="Nadiri Timer" />
<link rel="apple-touch-icon" href="/icon-192.svg" />
<title>Nadiri Sports</title>
<style>
html, body {
margin: 0;
padding: 0;
background: #0f0f0f;
background: #0F172A;
color: #e8e8e8;
font-family: system-ui, -apple-system, sans-serif;
overflow: hidden;
@@ -24,5 +29,10 @@
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js');
}
</script>
</body>
</html>
+9
View File
@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="192" viewBox="0 0 192 192">
<rect width="192" height="192" rx="24" fill="#0F172A"/>
<circle cx="96" cy="96" r="70" fill="none" stroke="#34D399" stroke-width="6"/>
<circle cx="96" cy="96" r="55" fill="none" stroke="#34D399" stroke-width="3" opacity="0.5"/>
<line x1="96" y1="96" x2="96" y2="46" stroke="#F8FAFC" stroke-width="6" stroke-linecap="round"/>
<line x1="96" y1="96" x2="126" y2="96" stroke="#F8FAFC" stroke-width="4" stroke-linecap="round"/>
<line x1="96" y1="96" x2="96" y2="66" stroke="#34D399" stroke-width="3" stroke-linecap="round"/>
<circle cx="96" cy="96" r="6" fill="#34D399"/>
</svg>

After

Width:  |  Height:  |  Size: 676 B

+10
View File
@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
<rect width="512" height="512" rx="64" fill="#0F172A"/>
<circle cx="256" cy="256" r="180" fill="none" stroke="#34D399" stroke-width="16"/>
<circle cx="256" cy="256" r="140" fill="none" stroke="#34D399" stroke-width="8" opacity="0.5"/>
<line x1="256" y1="256" x2="256" y2="116" stroke="#F8FAFC" stroke-width="16" stroke-linecap="round"/>
<line x1="256" y1="256" x2="336" y2="256" stroke="#F8FAFC" stroke-width="10" stroke-linecap="round"/>
<line x1="256" y1="256" x2="256" y2="166" stroke="#34D399" stroke-width="8" stroke-linecap="round"/>
<circle cx="256" cy="256" r="16" fill="#34D399"/>
<text x="256" y="420" font-family="Arial" font-size="48" fill="#34D399" text-anchor="middle" font-weight="bold">NADIRI</text>
</svg>

After

Width:  |  Height:  |  Size: 827 B

+27
View File
@@ -0,0 +1,27 @@
{
"name": "Nadiri Sports Timer",
"short_name": "Nadiri Timer",
"description": "TV-optimierter Sport-Timer für Training und Fitness",
"start_url": "/",
"display": "standalone",
"background_color": "#0F172A",
"theme_color": "#0F172A",
"orientation": "landscape",
"icons": [
{
"src": "/icon-192.svg",
"sizes": "192x192",
"type": "image/svg+xml",
"purpose": "any"
},
{
"src": "/icon-512.svg",
"sizes": "512x512",
"type": "image/svg+xml",
"purpose": "any"
}
],
"categories": ["sports", "fitness", "utilities"],
"lang": "de",
"dir": "ltr"
}
+43
View File
@@ -0,0 +1,43 @@
const CACHE_NAME = 'nadiri-timer-v1';
const urlsToCache = [
'/',
'/index.html',
'/manifest.json',
'/logo.png',
'/vite.svg'
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => {
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => {
if (response) {
return response;
}
return fetch(event.request);
})
);
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== CACHE_NAME) {
return caches.delete(cacheName);
}
})
);
})
);
});