update

working

reset

vue.js used

without footer

docker

default mode
This commit is contained in:
cpu
2025-05-06 20:19:38 +02:00
parent d741efa62d
commit 0bf368ea7a
40 changed files with 5117 additions and 2 deletions

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

57
public/manifest.json Normal file
View File

@@ -0,0 +1,57 @@
{
"name": "Nexus Timer - Multiplayer Turn Timer",
"short_name": "NexusTimer",
"description": "Dynamic multiplayer timer for games, workshops, or sequential turns. Focuses on current & next player.", // Add a description
"start_url": "/",
"scope": "/",
"display": "standalone",
"display_override": ["window-controls-overlay"],
"background_color": "#1f2937",
"theme_color": "#3b82f6",
"orientation": "portrait",
"version": "0.0.1",
"version_name": "0.0.1",
"lang": "en-US",
"icons": [
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/maskable-icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/maskable-icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"shortcuts": [
{
"name": "Game Setup",
"short_name": "Setup",
"description": "Configure players and settings",
"url": "/?utm_source=homescreen",
"icons": [{ "src": "/icons/shortcut-setup-96x96.png", "sizes": "96x96" }]
},
{
"name": "About Nexus Timer",
"short_name": "About",
"description": "Information about the app",
"url": "/info?utm_source=homescreen",
"icons": [{ "src": "/icons/shortcut-info-96x96.png", "sizes": "96x96" }]
}
]
}

154
public/service-worker.js Normal file
View File

@@ -0,0 +1,154 @@
const CACHE_VERSION = 'nexus-timer-cache-v3.3';
const APP_SHELL_URLS = [
// '/', // Let NetworkFirst handle '/'
'/manifest.json',
'/favicon.ico',
'/icons/icon-192x192.png',
'/icons/icon-512x512.png',
'/icons/maskable-icon-192x192.png',
'/icons/maskable-icon-512x512.png',
'/icons/shortcut-setup-96x96.png',
'/icons/shortcut-info-96x96.png',
// Add Vite output paths? Usually hard due to hashing. Rely on dynamic caching.
];
self.addEventListener('install', event => {
console.log(`[SW ${CACHE_VERSION}] Install`);
event.waitUntil(
caches.open(CACHE_VERSION)
.then(cache => {
console.log(`[SW ${CACHE_VERSION}] Caching app shell essentials`);
return cache.addAll(APP_SHELL_URLS); // Cache core static assets
})
.then(() => self.skipWaiting())
);
});
self.addEventListener('activate', event => {
console.log(`[SW ${CACHE_VERSION}] Activate`);
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== CACHE_VERSION) {
console.log(`[SW ${CACHE_VERSION}] Deleting old cache: ${cacheName}`);
return caches.delete(cacheName);
}
})
);
}).then(() => self.clients.claim())
);
});
self.addEventListener('fetch', event => {
const { request } = event;
const url = new URL(request.url);
// Ignore non-GET requests & non-http protocols
if (request.method !== 'GET' || !url.protocol.startsWith('http')) {
return;
}
// --- Strategy 1: Network First for HTML ---
if (request.mode === 'navigate' || (request.destination === 'document' || url.pathname === '/')) {
event.respondWith(
fetch(request)
.then(response => {
// If fetch is successful, cache it and return it
if (response.ok) {
const responseClone = response.clone();
caches.open(CACHE_VERSION).then(cache => cache.put(request, responseClone));
}
return response;
})
.catch(async () => {
// If fetch fails, try cache
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
// If specific request not in cache, try root '/' as SPA fallback
const rootCache = await caches.match('/');
if (rootCache) {
return rootCache;
}
// Optional: return a proper offline fallback page if available
// return caches.match('/offline.html');
// Or just let the browser show its offline error
return new Response('Network error and no cache found.', { status: 404, statusText: 'Not Found' });
})
);
return;
}
// --- Strategy 2: Stale-While-Revalidate for assets ---
if (request.destination === 'style' ||
request.destination === 'script' ||
request.destination === 'worker' ||
request.destination === 'image' ||
request.destination === 'font') {
event.respondWith(
caches.open(CACHE_VERSION).then(cache => { // Open cache first
return cache.match(request).then(cachedResponse => {
// Fetch in parallel (don't wait for it here)
const fetchPromise = fetch(request).then(networkResponse => {
// Check if fetch was successful
if (networkResponse.ok) {
// Clone before caching
const responseToCache = networkResponse.clone();
// Update the cache with the network response
cache.put(request, responseToCache);
} else {
console.warn(`[SW ${CACHE_VERSION}] Fetch for ${request.url} failed with status ${networkResponse.status}`);
}
// Return the original network response for the fetch promise resolution
// This isn't directly used for the *initial* response unless cache misses.
return networkResponse;
}).catch(err => {
console.warn(`[SW ${CACHE_VERSION}] Fetch error for ${request.url}:`, err);
// If fetch fails, we just rely on the cache if it existed.
// Re-throw error so if cache also missed, browser knows resource failed.
throw err;
});
// Return the cached response immediately if available.
// If not cached, this strategy requires waiting for the fetch.
// The common implementation returns cache THEN fetches, but if cache misses,
// the user waits for the network. Let's return fetchPromise if no cache.
return cachedResponse || fetchPromise;
}).catch(err => {
// Error during cache match or subsequent fetch
console.error(`[SW ${CACHE_VERSION}] Error handling fetch for ${request.url}:`, err);
// Fallback to network just in case cache interaction failed? Or let browser handle.
// Let's try network as a last resort if cache fails.
return fetch(request);
});
})
);
return;
}
// --- Strategy 3: Cache First for others ---
// (e.g., manifest, potentially icons not pre-cached)
event.respondWith(
caches.match(request)
.then(response => {
return response || fetch(request).then(networkResponse => {
if(networkResponse.ok) {
const responseClone = networkResponse.clone();
caches.open(CACHE_VERSION).then(cache => cache.put(request, responseClone));
}
return networkResponse;
});
})
);
});
// Listener for skipWaiting message
self.addEventListener('message', event => {
if (event.data && event.data.action === 'skipWaiting') {
console.log('[SW] Received skipWaiting message, activating new SW.');
self.skipWaiting();
}
});