refactor(pwa): replace custom service workers with Vite PWA plugin and centralize offline page

Remove hand-written sw.js, offline.html, and manifest.json from todo/skilltree/zitare web apps
in favor of the Workbox-based service worker generated by @vite-pwa/sveltekit. This fixes an
issue where the custom SW could get stuck serving the offline fallback page even when the server
was reachable. Also extracts the duplicated offline page (~80 lines each across 19 apps) into a
shared OfflinePage component in @manacore/shared-ui with 3 props (appName, offlineMessage,
accentColor), reducing each app's offline route to an 8-line wrapper.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-22 18:16:43 +01:00
parent 93a7c90f4f
commit 9bdb997394
33 changed files with 262 additions and 2023 deletions

View file

@ -1,78 +1,9 @@
<script lang="ts">
import { onMount } from 'svelte';
let isOnline = $state(false);
onMount(() => {
isOnline = navigator.onLine;
const handleOnline = () => {
isOnline = true;
setTimeout(() => {
window.location.href = '/';
}, 1000);
};
const handleOffline = () => {
isOnline = false;
};
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
});
import { OfflinePage } from '@manacore/shared-ui';
</script>
<svelte:head>
<title>Offline - Calendar</title>
</svelte:head>
<div class="min-h-screen bg-slate-900 flex items-center justify-center px-4">
<div class="text-center max-w-md">
<div class="mb-8">
<svg class="w-24 h-24 mx-auto text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a5 5 0 01-.354-7.072L8.95 7.636m1.414 5.657L7.535 16.12m8.485 0a5 5 0 01-7.07 0" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3l18 18" />
</svg>
</div>
<h1 class="text-2xl font-bold text-white mb-4">
{isOnline ? 'Verbindung wiederhergestellt!' : 'Du bist offline'}
</h1>
<p class="text-slate-400 mb-8">
{#if isOnline}
Du wirst gleich weitergeleitet...
{:else}
Deine Termine werden lokal gespeichert und sind offline verfügbar.
{/if}
</p>
{#if !isOnline}
<div class="space-y-4">
<a href="/" class="inline-flex items-center justify-center px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
Zur Startseite
</a>
<button onclick={() => window.location.reload()} class="block w-full px-6 py-3 text-slate-400 hover:text-white transition-colors">
Erneut versuchen
</button>
</div>
{:else}
<div class="flex items-center justify-center text-green-400">
<svg class="w-5 h-5 mr-2 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Weiterleitung...
</div>
{/if}
</div>
</div>
<OfflinePage
appName="Calendar"
offlineMessage="Deine Termine werden lokal gespeichert und sind offline verfügbar."
accentColor="#2563eb"
/>

View file

@ -1,78 +1,9 @@
<script lang="ts">
import { onMount } from 'svelte';
let isOnline = $state(false);
onMount(() => {
isOnline = navigator.onLine;
const handleOnline = () => {
isOnline = true;
setTimeout(() => {
window.location.href = '/';
}, 1000);
};
const handleOffline = () => {
isOnline = false;
};
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
});
import { OfflinePage } from '@manacore/shared-ui';
</script>
<svelte:head>
<title>Offline - Chat</title>
</svelte:head>
<div class="min-h-screen bg-slate-900 flex items-center justify-center px-4">
<div class="text-center max-w-md">
<div class="mb-8">
<svg class="w-24 h-24 mx-auto text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a5 5 0 01-.354-7.072L8.95 7.636m1.414 5.657L7.535 16.12m8.485 0a5 5 0 01-7.07 0" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3l18 18" />
</svg>
</div>
<h1 class="text-2xl font-bold text-white mb-4">
{isOnline ? 'Verbindung wiederhergestellt!' : 'Du bist offline'}
</h1>
<p class="text-slate-400 mb-8">
{#if isOnline}
Du wirst gleich weitergeleitet...
{:else}
Chat benötigt eine Internetverbindung, um Nachrichten zu senden.
{/if}
</p>
{#if !isOnline}
<div class="space-y-4">
<a href="/" class="inline-flex items-center justify-center px-6 py-3 bg-violet-600 hover:bg-violet-700 text-white font-medium rounded-lg transition-colors">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
Zur Startseite
</a>
<button onclick={() => window.location.reload()} class="block w-full px-6 py-3 text-slate-400 hover:text-white transition-colors">
Erneut versuchen
</button>
</div>
{:else}
<div class="flex items-center justify-center text-green-400">
<svg class="w-5 h-5 mr-2 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Weiterleitung...
</div>
{/if}
</div>
</div>
<OfflinePage
appName="Chat"
offlineMessage="Chat benötigt eine Internetverbindung, um Nachrichten zu senden."
accentColor="#7c3aed"
/>

View file

@ -1,78 +1,9 @@
<script lang="ts">
import { onMount } from 'svelte';
let isOnline = $state(false);
onMount(() => {
isOnline = navigator.onLine;
const handleOnline = () => {
isOnline = true;
setTimeout(() => {
window.location.href = '/';
}, 1000);
};
const handleOffline = () => {
isOnline = false;
};
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
});
import { OfflinePage } from '@manacore/shared-ui';
</script>
<svelte:head>
<title>Offline - Clock</title>
</svelte:head>
<div class="min-h-screen bg-slate-900 flex items-center justify-center px-4">
<div class="text-center max-w-md">
<div class="mb-8">
<svg class="w-24 h-24 mx-auto text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a5 5 0 01-.354-7.072L8.95 7.636m1.414 5.657L7.535 16.12m8.485 0a5 5 0 01-7.07 0" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3l18 18" />
</svg>
</div>
<h1 class="text-2xl font-bold text-white mb-4">
{isOnline ? 'Verbindung wiederhergestellt!' : 'Du bist offline'}
</h1>
<p class="text-slate-400 mb-8">
{#if isOnline}
Du wirst gleich weitergeleitet...
{:else}
Die Uhr funktioniert auch offline - Timer und Stoppuhr sind verfügbar.
{/if}
</p>
{#if !isOnline}
<div class="space-y-4">
<a href="/" class="inline-flex items-center justify-center px-6 py-3 bg-cyan-600 hover:bg-cyan-700 text-white font-medium rounded-lg transition-colors">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
Zur Startseite
</a>
<button onclick={() => window.location.reload()} class="block w-full px-6 py-3 text-slate-400 hover:text-white transition-colors">
Erneut versuchen
</button>
</div>
{:else}
<div class="flex items-center justify-center text-green-400">
<svg class="w-5 h-5 mr-2 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Weiterleitung...
</div>
{/if}
</div>
</div>
<OfflinePage
appName="Clock"
offlineMessage="Die Uhr funktioniert auch offline - Timer und Stoppuhr sind verfügbar."
accentColor="#0891b2"
/>

View file

@ -1,78 +1,9 @@
<script lang="ts">
import { onMount } from 'svelte';
let isOnline = $state(false);
onMount(() => {
isOnline = navigator.onLine;
const handleOnline = () => {
isOnline = true;
setTimeout(() => {
window.location.href = '/';
}, 1000);
};
const handleOffline = () => {
isOnline = false;
};
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
});
import { OfflinePage } from '@manacore/shared-ui';
</script>
<svelte:head>
<title>Offline - Contacts</title>
</svelte:head>
<div class="min-h-screen bg-slate-900 flex items-center justify-center px-4">
<div class="text-center max-w-md">
<div class="mb-8">
<svg class="w-24 h-24 mx-auto text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a5 5 0 01-.354-7.072L8.95 7.636m1.414 5.657L7.535 16.12m8.485 0a5 5 0 01-7.07 0" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3l18 18" />
</svg>
</div>
<h1 class="text-2xl font-bold text-white mb-4">
{isOnline ? 'Verbindung wiederhergestellt!' : 'Du bist offline'}
</h1>
<p class="text-slate-400 mb-8">
{#if isOnline}
Du wirst gleich weitergeleitet...
{:else}
Kontakte benötigt eine Internetverbindung für Synchronisierung.
{/if}
</p>
{#if !isOnline}
<div class="space-y-4">
<a href="/" class="inline-flex items-center justify-center px-6 py-3 bg-cyan-600 hover:bg-cyan-700 text-white font-medium rounded-lg transition-colors">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
Zur Startseite
</a>
<button onclick={() => window.location.reload()} class="block w-full px-6 py-3 text-slate-400 hover:text-white transition-colors">
Erneut versuchen
</button>
</div>
{:else}
<div class="flex items-center justify-center text-green-400">
<svg class="w-5 h-5 mr-2 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Weiterleitung...
</div>
{/if}
</div>
</div>
<OfflinePage
appName="Contacts"
offlineMessage="Kontakte benötigt eine Internetverbindung für Synchronisierung."
accentColor="#0891b2"
/>

View file

@ -1,96 +1,9 @@
<script lang="ts">
import { onMount } from 'svelte';
let isOnline = $state(false);
onMount(() => {
isOnline = navigator.onLine;
const handleOnline = () => {
isOnline = true;
setTimeout(() => {
window.location.href = '/';
}, 1000);
};
const handleOffline = () => {
isOnline = false;
};
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
});
import { OfflinePage } from '@manacore/shared-ui';
</script>
<svelte:head>
<title>Offline - Context</title>
</svelte:head>
<div class="min-h-screen bg-slate-900 flex items-center justify-center px-4">
<div class="text-center max-w-md">
<div class="mb-8">
<svg
class="w-24 h-24 mx-auto text-slate-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a5 5 0 01-.354-7.072L8.95 7.636m1.414 5.657L7.535 16.12m8.485 0a5 5 0 01-7.07 0"
/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3l18 18" />
</svg>
</div>
<h1 class="text-2xl font-bold text-white mb-4">
{isOnline ? 'Verbindung wiederhergestellt!' : 'Du bist offline'}
</h1>
<p class="text-slate-400 mb-8">
{#if isOnline}
Du wirst gleich weitergeleitet...
{:else}
Einige Funktionen sind offline nicht verfügbar. Bitte überprüfe deine Internetverbindung.
{/if}
</p>
{#if !isOnline}
<div class="space-y-4">
<a
href="/"
class="inline-flex items-center justify-center px-6 py-3 bg-sky-600 hover:bg-sky-700 text-white font-medium rounded-lg transition-colors"
>
Zur Startseite
</a>
<button
onclick={() => window.location.reload()}
class="block w-full px-6 py-3 text-slate-400 hover:text-white transition-colors"
>
Erneut versuchen
</button>
</div>
{:else}
<div class="flex items-center justify-center text-green-400">
<svg class="w-5 h-5 mr-2 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
Weiterleitung...
</div>
{/if}
</div>
</div>
<OfflinePage
appName="Context"
offlineMessage="Einige Funktionen sind offline nicht verfügbar. Bitte überprüfe deine Internetverbindung."
accentColor="#0284c7"
/>

View file

@ -1,78 +1,9 @@
<script lang="ts">
import { onMount } from 'svelte';
let isOnline = $state(false);
onMount(() => {
isOnline = navigator.onLine;
const handleOnline = () => {
isOnline = true;
setTimeout(() => {
window.location.href = '/';
}, 1000);
};
const handleOffline = () => {
isOnline = false;
};
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
});
import { OfflinePage } from '@manacore/shared-ui';
</script>
<svelte:head>
<title>Offline - ManaCore</title>
</svelte:head>
<div class="min-h-screen bg-slate-900 flex items-center justify-center px-4">
<div class="text-center max-w-md">
<div class="mb-8">
<svg class="w-24 h-24 mx-auto text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a5 5 0 01-.354-7.072L8.95 7.636m1.414 5.657L7.535 16.12m8.485 0a5 5 0 01-7.07 0" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3l18 18" />
</svg>
</div>
<h1 class="text-2xl font-bold text-white mb-4">
{isOnline ? 'Verbindung wiederhergestellt!' : 'Du bist offline'}
</h1>
<p class="text-slate-400 mb-8">
{#if isOnline}
Du wirst gleich weitergeleitet...
{:else}
ManaCore benötigt eine Internetverbindung.
{/if}
</p>
{#if !isOnline}
<div class="space-y-4">
<a href="/" class="inline-flex items-center justify-center px-6 py-3 bg-indigo-600 hover:bg-indigo-700 text-white font-medium rounded-lg transition-colors">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
Zur Startseite
</a>
<button onclick={() => window.location.reload()} class="block w-full px-6 py-3 text-slate-400 hover:text-white transition-colors">
Erneut versuchen
</button>
</div>
{:else}
<div class="flex items-center justify-center text-green-400">
<svg class="w-5 h-5 mr-2 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Weiterleitung...
</div>
{/if}
</div>
</div>
<OfflinePage
appName="ManaCore"
offlineMessage="ManaCore benötigt eine Internetverbindung."
accentColor="#4f46e5"
/>

View file

@ -1,78 +1,9 @@
<script lang="ts">
import { onMount } from 'svelte';
let isOnline = $state(false);
onMount(() => {
isOnline = navigator.onLine;
const handleOnline = () => {
isOnline = true;
setTimeout(() => {
window.location.href = '/';
}, 1000);
};
const handleOffline = () => {
isOnline = false;
};
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
});
import { OfflinePage } from '@manacore/shared-ui';
</script>
<svelte:head>
<title>Offline - ManaDeck</title>
</svelte:head>
<div class="min-h-screen bg-slate-900 flex items-center justify-center px-4">
<div class="text-center max-w-md">
<div class="mb-8">
<svg class="w-24 h-24 mx-auto text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a5 5 0 01-.354-7.072L8.95 7.636m1.414 5.657L7.535 16.12m8.485 0a5 5 0 01-7.07 0" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3l18 18" />
</svg>
</div>
<h1 class="text-2xl font-bold text-white mb-4">
{isOnline ? 'Verbindung wiederhergestellt!' : 'Du bist offline'}
</h1>
<p class="text-slate-400 mb-8">
{#if isOnline}
Du wirst gleich weitergeleitet...
{:else}
ManaDeck benötigt eine Internetverbindung für deine Kartendecks.
{/if}
</p>
{#if !isOnline}
<div class="space-y-4">
<a href="/" class="inline-flex items-center justify-center px-6 py-3 bg-amber-600 hover:bg-amber-700 text-white font-medium rounded-lg transition-colors">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
Zur Startseite
</a>
<button onclick={() => window.location.reload()} class="block w-full px-6 py-3 text-slate-400 hover:text-white transition-colors">
Erneut versuchen
</button>
</div>
{:else}
<div class="flex items-center justify-center text-green-400">
<svg class="w-5 h-5 mr-2 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Weiterleitung...
</div>
{/if}
</div>
</div>
<OfflinePage
appName="ManaDeck"
offlineMessage="ManaDeck benötigt eine Internetverbindung für deine Kartendecks."
accentColor="#d97706"
/>

View file

@ -1,78 +1,9 @@
<script lang="ts">
import { onMount } from 'svelte';
let isOnline = $state(false);
onMount(() => {
isOnline = navigator.onLine;
const handleOnline = () => {
isOnline = true;
setTimeout(() => {
window.location.href = '/';
}, 1000);
};
const handleOffline = () => {
isOnline = false;
};
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
});
import { OfflinePage } from '@manacore/shared-ui';
</script>
<svelte:head>
<title>Offline - Matrix</title>
</svelte:head>
<div class="min-h-screen bg-slate-900 flex items-center justify-center px-4">
<div class="text-center max-w-md">
<div class="mb-8">
<svg class="w-24 h-24 mx-auto text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a5 5 0 01-.354-7.072L8.95 7.636m1.414 5.657L7.535 16.12m8.485 0a5 5 0 01-7.07 0" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3l18 18" />
</svg>
</div>
<h1 class="text-2xl font-bold text-white mb-4">
{isOnline ? 'Verbindung wiederhergestellt!' : 'Du bist offline'}
</h1>
<p class="text-slate-400 mb-8">
{#if isOnline}
Du wirst gleich weitergeleitet...
{:else}
Matrix benötigt eine Internetverbindung für Nachrichten.
{/if}
</p>
{#if !isOnline}
<div class="space-y-4">
<a href="/" class="inline-flex items-center justify-center px-6 py-3 bg-sky-600 hover:bg-sky-700 text-white font-medium rounded-lg transition-colors">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
Zur Startseite
</a>
<button onclick={() => window.location.reload()} class="block w-full px-6 py-3 text-slate-400 hover:text-white transition-colors">
Erneut versuchen
</button>
</div>
{:else}
<div class="flex items-center justify-center text-green-400">
<svg class="w-5 h-5 mr-2 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Weiterleitung...
</div>
{/if}
</div>
</div>
<OfflinePage
appName="Matrix"
offlineMessage="Matrix benötigt eine Internetverbindung für Nachrichten."
accentColor="#0284c7"
/>

View file

@ -1,104 +1,9 @@
<script lang="ts">
import { onMount } from 'svelte';
let isOnline = $state(false);
onMount(() => {
isOnline = navigator.onLine;
const handleOnline = () => {
isOnline = true;
setTimeout(() => {
window.location.href = '/';
}, 1000);
};
const handleOffline = () => {
isOnline = false;
};
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
});
import { OfflinePage } from '@manacore/shared-ui';
</script>
<svelte:head>
<title>Offline - Mukke</title>
</svelte:head>
<div class="min-h-screen bg-slate-900 flex items-center justify-center px-4">
<div class="text-center max-w-md">
<div class="mb-8">
<svg
class="w-24 h-24 mx-auto text-slate-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a5 5 0 01-.354-7.072L8.95 7.636m1.414 5.657L7.535 16.12m8.485 0a5 5 0 01-7.07 0"
/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3l18 18" />
</svg>
</div>
<h1 class="text-2xl font-bold text-white mb-4">
{isOnline ? 'Verbindung wiederhergestellt!' : 'Du bist offline'}
</h1>
<p class="text-slate-400 mb-8">
{#if isOnline}
Du wirst gleich weitergeleitet...
{:else}
Mukke benötigt eine Internetverbindung für Audio.
{/if}
</p>
{#if !isOnline}
<div class="space-y-4">
<a
href="/"
class="inline-flex items-center justify-center px-6 py-3 bg-orange-600 hover:bg-orange-700 text-white font-medium rounded-lg transition-colors"
>
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
/>
</svg>
Zur Startseite
</a>
<button
onclick={() => window.location.reload()}
class="block w-full px-6 py-3 text-slate-400 hover:text-white transition-colors"
>
Erneut versuchen
</button>
</div>
{:else}
<div class="flex items-center justify-center text-green-400">
<svg class="w-5 h-5 mr-2 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
Weiterleitung...
</div>
{/if}
</div>
</div>
<OfflinePage
appName="Mukke"
offlineMessage="Mukke benötigt eine Internetverbindung für Audio."
accentColor="#ea580c"
/>

View file

@ -1,78 +1,9 @@
<script lang="ts">
import { onMount } from 'svelte';
let isOnline = $state(false);
onMount(() => {
isOnline = navigator.onLine;
const handleOnline = () => {
isOnline = true;
setTimeout(() => {
window.location.href = '/';
}, 1000);
};
const handleOffline = () => {
isOnline = false;
};
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
});
import { OfflinePage } from '@manacore/shared-ui';
</script>
<svelte:head>
<title>Offline - NutriPhi</title>
</svelte:head>
<div class="min-h-screen bg-slate-900 flex items-center justify-center px-4">
<div class="text-center max-w-md">
<div class="mb-8">
<svg class="w-24 h-24 mx-auto text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a5 5 0 01-.354-7.072L8.95 7.636m1.414 5.657L7.535 16.12m8.485 0a5 5 0 01-7.07 0" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3l18 18" />
</svg>
</div>
<h1 class="text-2xl font-bold text-white mb-4">
{isOnline ? 'Verbindung wiederhergestellt!' : 'Du bist offline'}
</h1>
<p class="text-slate-400 mb-8">
{#if isOnline}
Du wirst gleich weitergeleitet...
{:else}
NutriPhi benötigt eine Internetverbindung für Analysen.
{/if}
</p>
{#if !isOnline}
<div class="space-y-4">
<a href="/" class="inline-flex items-center justify-center px-6 py-3 bg-green-600 hover:bg-green-700 text-white font-medium rounded-lg transition-colors">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
Zur Startseite
</a>
<button onclick={() => window.location.reload()} class="block w-full px-6 py-3 text-slate-400 hover:text-white transition-colors">
Erneut versuchen
</button>
</div>
{:else}
<div class="flex items-center justify-center text-green-400">
<svg class="w-5 h-5 mr-2 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Weiterleitung...
</div>
{/if}
</div>
</div>
<OfflinePage
appName="NutriPhi"
offlineMessage="NutriPhi benötigt eine Internetverbindung für Analysen."
accentColor="#16a34a"
/>

View file

@ -1,78 +1,9 @@
<script lang="ts">
import { onMount } from 'svelte';
let isOnline = $state(false);
onMount(() => {
isOnline = navigator.onLine;
const handleOnline = () => {
isOnline = true;
setTimeout(() => {
window.location.href = '/';
}, 1000);
};
const handleOffline = () => {
isOnline = false;
};
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
});
import { OfflinePage } from '@manacore/shared-ui';
</script>
<svelte:head>
<title>Offline - Photos</title>
</svelte:head>
<div class="min-h-screen bg-slate-900 flex items-center justify-center px-4">
<div class="text-center max-w-md">
<div class="mb-8">
<svg class="w-24 h-24 mx-auto text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a5 5 0 01-.354-7.072L8.95 7.636m1.414 5.657L7.535 16.12m8.485 0a5 5 0 01-7.07 0" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3l18 18" />
</svg>
</div>
<h1 class="text-2xl font-bold text-white mb-4">
{isOnline ? 'Verbindung wiederhergestellt!' : 'Du bist offline'}
</h1>
<p class="text-slate-400 mb-8">
{#if isOnline}
Du wirst gleich weitergeleitet...
{:else}
Photos benötigt eine Internetverbindung für deine Fotos.
{/if}
</p>
{#if !isOnline}
<div class="space-y-4">
<a href="/" class="inline-flex items-center justify-center px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
Zur Startseite
</a>
<button onclick={() => window.location.reload()} class="block w-full px-6 py-3 text-slate-400 hover:text-white transition-colors">
Erneut versuchen
</button>
</div>
{:else}
<div class="flex items-center justify-center text-green-400">
<svg class="w-5 h-5 mr-2 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Weiterleitung...
</div>
{/if}
</div>
</div>
<OfflinePage
appName="Photos"
offlineMessage="Photos benötigt eine Internetverbindung für deine Fotos."
accentColor="#2563eb"
/>

View file

@ -1,78 +1,9 @@
<script lang="ts">
import { onMount } from 'svelte';
let isOnline = $state(false);
onMount(() => {
isOnline = navigator.onLine;
const handleOnline = () => {
isOnline = true;
setTimeout(() => {
window.location.href = '/';
}, 1000);
};
const handleOffline = () => {
isOnline = false;
};
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
});
import { OfflinePage } from '@manacore/shared-ui';
</script>
<svelte:head>
<title>Offline - Picture</title>
</svelte:head>
<div class="min-h-screen bg-slate-900 flex items-center justify-center px-4">
<div class="text-center max-w-md">
<div class="mb-8">
<svg class="w-24 h-24 mx-auto text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a5 5 0 01-.354-7.072L8.95 7.636m1.414 5.657L7.535 16.12m8.485 0a5 5 0 01-7.07 0" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3l18 18" />
</svg>
</div>
<h1 class="text-2xl font-bold text-white mb-4">
{isOnline ? 'Verbindung wiederhergestellt!' : 'Du bist offline'}
</h1>
<p class="text-slate-400 mb-8">
{#if isOnline}
Du wirst gleich weitergeleitet...
{:else}
Picture benötigt eine Internetverbindung, um Bilder zu generieren.
{/if}
</p>
{#if !isOnline}
<div class="space-y-4">
<a href="/" class="inline-flex items-center justify-center px-6 py-3 bg-pink-600 hover:bg-pink-700 text-white font-medium rounded-lg transition-colors">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
Zur Startseite
</a>
<button onclick={() => window.location.reload()} class="block w-full px-6 py-3 text-slate-400 hover:text-white transition-colors">
Erneut versuchen
</button>
</div>
{:else}
<div class="flex items-center justify-center text-green-400">
<svg class="w-5 h-5 mr-2 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Weiterleitung...
</div>
{/if}
</div>
</div>
<OfflinePage
appName="Picture"
offlineMessage="Picture benötigt eine Internetverbindung, um Bilder zu generieren."
accentColor="#db2777"
/>

View file

@ -1,78 +1,9 @@
<script lang="ts">
import { onMount } from 'svelte';
let isOnline = $state(false);
onMount(() => {
isOnline = navigator.onLine;
const handleOnline = () => {
isOnline = true;
setTimeout(() => {
window.location.href = '/';
}, 1000);
};
const handleOffline = () => {
isOnline = false;
};
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
});
import { OfflinePage } from '@manacore/shared-ui';
</script>
<svelte:head>
<title>Offline - Planta</title>
</svelte:head>
<div class="min-h-screen bg-slate-900 flex items-center justify-center px-4">
<div class="text-center max-w-md">
<div class="mb-8">
<svg class="w-24 h-24 mx-auto text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a5 5 0 01-.354-7.072L8.95 7.636m1.414 5.657L7.535 16.12m8.485 0a5 5 0 01-7.07 0" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3l18 18" />
</svg>
</div>
<h1 class="text-2xl font-bold text-white mb-4">
{isOnline ? 'Verbindung wiederhergestellt!' : 'Du bist offline'}
</h1>
<p class="text-slate-400 mb-8">
{#if isOnline}
Du wirst gleich weitergeleitet...
{:else}
Planta benötigt eine Internetverbindung für Pflegetipps.
{/if}
</p>
{#if !isOnline}
<div class="space-y-4">
<a href="/" class="inline-flex items-center justify-center px-6 py-3 bg-green-600 hover:bg-green-700 text-white font-medium rounded-lg transition-colors">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
Zur Startseite
</a>
<button onclick={() => window.location.reload()} class="block w-full px-6 py-3 text-slate-400 hover:text-white transition-colors">
Erneut versuchen
</button>
</div>
{:else}
<div class="flex items-center justify-center text-green-400">
<svg class="w-5 h-5 mr-2 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Weiterleitung...
</div>
{/if}
</div>
</div>
<OfflinePage
appName="Planta"
offlineMessage="Planta benötigt eine Internetverbindung für Pflegetipps."
accentColor="#16a34a"
/>

View file

@ -1,78 +1,9 @@
<script lang="ts">
import { onMount } from 'svelte';
let isOnline = $state(false);
onMount(() => {
isOnline = navigator.onLine;
const handleOnline = () => {
isOnline = true;
setTimeout(() => {
window.location.href = '/';
}, 1000);
};
const handleOffline = () => {
isOnline = false;
};
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
});
import { OfflinePage } from '@manacore/shared-ui';
</script>
<svelte:head>
<title>Offline - Presi</title>
</svelte:head>
<div class="min-h-screen bg-slate-900 flex items-center justify-center px-4">
<div class="text-center max-w-md">
<div class="mb-8">
<svg class="w-24 h-24 mx-auto text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a5 5 0 01-.354-7.072L8.95 7.636m1.414 5.657L7.535 16.12m8.485 0a5 5 0 01-7.07 0" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3l18 18" />
</svg>
</div>
<h1 class="text-2xl font-bold text-white mb-4">
{isOnline ? 'Verbindung wiederhergestellt!' : 'Du bist offline'}
</h1>
<p class="text-slate-400 mb-8">
{#if isOnline}
Du wirst gleich weitergeleitet...
{:else}
Presi benötigt eine Internetverbindung für Präsentationen.
{/if}
</p>
{#if !isOnline}
<div class="space-y-4">
<a href="/" class="inline-flex items-center justify-center px-6 py-3 bg-purple-600 hover:bg-purple-700 text-white font-medium rounded-lg transition-colors">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
Zur Startseite
</a>
<button onclick={() => window.location.reload()} class="block w-full px-6 py-3 text-slate-400 hover:text-white transition-colors">
Erneut versuchen
</button>
</div>
{:else}
<div class="flex items-center justify-center text-green-400">
<svg class="w-5 h-5 mr-2 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Weiterleitung...
</div>
{/if}
</div>
</div>
<OfflinePage
appName="Presi"
offlineMessage="Presi benötigt eine Internetverbindung für Präsentationen."
accentColor="#9333ea"
/>

View file

@ -1,78 +1,9 @@
<script lang="ts">
import { onMount } from 'svelte';
let isOnline = $state(false);
onMount(() => {
isOnline = navigator.onLine;
const handleOnline = () => {
isOnline = true;
setTimeout(() => {
window.location.href = '/';
}, 1000);
};
const handleOffline = () => {
isOnline = false;
};
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
});
import { OfflinePage } from '@manacore/shared-ui';
</script>
<svelte:head>
<title>Offline - Questions</title>
</svelte:head>
<div class="min-h-screen bg-slate-900 flex items-center justify-center px-4">
<div class="text-center max-w-md">
<div class="mb-8">
<svg class="w-24 h-24 mx-auto text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a5 5 0 01-.354-7.072L8.95 7.636m1.414 5.657L7.535 16.12m8.485 0a5 5 0 01-7.07 0" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3l18 18" />
</svg>
</div>
<h1 class="text-2xl font-bold text-white mb-4">
{isOnline ? 'Verbindung wiederhergestellt!' : 'Du bist offline'}
</h1>
<p class="text-slate-400 mb-8">
{#if isOnline}
Du wirst gleich weitergeleitet...
{:else}
Questions benötigt eine Internetverbindung für Antworten.
{/if}
</p>
{#if !isOnline}
<div class="space-y-4">
<a href="/" class="inline-flex items-center justify-center px-6 py-3 bg-violet-600 hover:bg-violet-700 text-white font-medium rounded-lg transition-colors">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
Zur Startseite
</a>
<button onclick={() => window.location.reload()} class="block w-full px-6 py-3 text-slate-400 hover:text-white transition-colors">
Erneut versuchen
</button>
</div>
{:else}
<div class="flex items-center justify-center text-green-400">
<svg class="w-5 h-5 mr-2 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Weiterleitung...
</div>
{/if}
</div>
</div>
<OfflinePage
appName="Questions"
offlineMessage="Questions benötigt eine Internetverbindung für Antworten."
accentColor="#7c3aed"
/>

View file

@ -42,6 +42,7 @@
"@manacore/shared-icons": "workspace:*",
"@manacore/shared-tailwind": "workspace:*",
"@manacore/shared-theme": "workspace:*",
"@manacore/shared-ui": "workspace:^",
"@manacore/shared-utils": "workspace:*",
"idb": "^8.0.0",
"svelte-i18n": "^4.0.1",

View file

@ -7,12 +7,8 @@
<!-- Favicon -->
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<!-- PWA Manifest -->
<link rel="manifest" href="/manifest.json" />
<!-- Theme Color -->
<meta name="theme-color" content="#10b981" />
<meta name="msapplication-TileColor" content="#10b981" />
<!-- PWA -->
<meta name="mobile-web-app-capable" content="yes" />

View file

@ -1,78 +1,9 @@
<script lang="ts">
import { onMount } from 'svelte';
let isOnline = $state(false);
onMount(() => {
isOnline = navigator.onLine;
const handleOnline = () => {
isOnline = true;
setTimeout(() => {
window.location.href = '/';
}, 1000);
};
const handleOffline = () => {
isOnline = false;
};
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
});
import { OfflinePage } from '@manacore/shared-ui';
</script>
<svelte:head>
<title>Offline - SkillTree</title>
</svelte:head>
<div class="min-h-screen bg-slate-900 flex items-center justify-center px-4">
<div class="text-center max-w-md">
<div class="mb-8">
<svg class="w-24 h-24 mx-auto text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a5 5 0 01-.354-7.072L8.95 7.636m1.414 5.657L7.535 16.12m8.485 0a5 5 0 01-7.07 0" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3l18 18" />
</svg>
</div>
<h1 class="text-2xl font-bold text-white mb-4">
{isOnline ? 'Verbindung wiederhergestellt!' : 'Du bist offline'}
</h1>
<p class="text-slate-400 mb-8">
{#if isOnline}
Du wirst gleich weitergeleitet...
{:else}
Deine Skills werden lokal gespeichert und sind offline verfügbar.
{/if}
</p>
{#if !isOnline}
<div class="space-y-4">
<a href="/" class="inline-flex items-center justify-center px-6 py-3 bg-violet-600 hover:bg-violet-700 text-white font-medium rounded-lg transition-colors">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
Zur Startseite
</a>
<button onclick={() => window.location.reload()} class="block w-full px-6 py-3 text-slate-400 hover:text-white transition-colors">
Erneut versuchen
</button>
</div>
{:else}
<div class="flex items-center justify-center text-green-400">
<svg class="w-5 h-5 mr-2 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Weiterleitung...
</div>
{/if}
</div>
</div>
<OfflinePage
appName="SkillTree"
offlineMessage="Deine Skills werden lokal gespeichert und sind offline verfügbar."
accentColor="#7c3aed"
/>

View file

@ -1,21 +0,0 @@
{
"name": "SkillTree",
"short_name": "SkillTree",
"description": "Track your skills like a game. Level up in real life.",
"start_url": "/",
"display": "standalone",
"background_color": "#111827",
"theme_color": "#10b981",
"icons": [
{
"src": "/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}

View file

@ -1,106 +1,9 @@
<script lang="ts">
import { onMount } from 'svelte';
let isOnline = $state(false);
onMount(() => {
isOnline = navigator.onLine;
const handleOnline = () => {
isOnline = true;
setTimeout(() => {
window.location.href = '/';
}, 1000);
};
const handleOffline = () => {
isOnline = false;
};
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
});
import { OfflinePage } from '@manacore/shared-ui';
</script>
<svelte:head>
<title>Offline - Storage</title>
</svelte:head>
<div class="min-h-screen bg-slate-900 flex items-center justify-center px-4">
<div class="text-center max-w-md">
<div class="mb-8">
<svg
class="w-24 h-24 mx-auto text-slate-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a5 5 0 01-.354-7.072L8.95 7.636m1.414 5.657L7.535 16.12m8.485 0a5 5 0 01-7.07 0"
/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3l18 18" />
</svg>
</div>
<h1 class="text-2xl font-bold text-white mb-4">
{isOnline ? 'Verbindung wiederhergestellt!' : 'Du bist offline'}
</h1>
<p class="text-slate-400 mb-8">
{#if isOnline}
Du wirst gleich weitergeleitet...
{:else}
Storage benötigt eine Internetverbindung für Cloud-Dateien. Kürzlich besuchte Seiten sind
möglicherweise noch verfügbar.
{/if}
</p>
{#if !isOnline}
<div class="space-y-4">
<div class="flex flex-col gap-3 mb-4">
<a
href="/files"
class="inline-flex items-center justify-center px-6 py-3 bg-slate-700 hover:bg-slate-600 text-white font-medium rounded-lg transition-colors"
>
Meine Dateien (cached)
</a>
<a
href="/favorites"
class="inline-flex items-center justify-center px-6 py-3 bg-slate-700 hover:bg-slate-600 text-white font-medium rounded-lg transition-colors"
>
Favoriten (cached)
</a>
</div>
<button
onclick={() => window.location.reload()}
class="block w-full px-6 py-3 text-slate-400 hover:text-white transition-colors"
>
Erneut versuchen
</button>
</div>
{:else}
<div class="flex items-center justify-center text-green-400" role="status" aria-live="polite">
<svg class="w-5 h-5 mr-2 animate-spin" fill="none" viewBox="0 0 24 24" aria-hidden="true">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
Weiterleitung...
</div>
{/if}
</div>
</div>
<OfflinePage
appName="Storage"
offlineMessage="Storage benötigt eine Internetverbindung für Cloud-Dateien. Kürzlich besuchte Seiten sind möglicherweise noch verfügbar."
accentColor="#475569"
/>

View file

@ -8,22 +8,13 @@
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<link rel="icon" type="image/svg+xml" href="/icons/icon.svg" />
<!-- PWA Manifest -->
<link rel="manifest" href="/manifest.json" />
<!-- Theme Color -->
<!-- PWA meta tags (manifest injected by @vite-pwa/sveltekit via %sveltekit.head%) -->
<meta name="theme-color" content="#8b5cf6" />
<meta name="msapplication-TileColor" content="#8b5cf6" />
<!-- PWA -->
<meta name="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="Todo" />
<link rel="apple-touch-icon" href="/icons/icon.svg" />
<!-- Microsoft Tiles -->
<meta name="msapplication-config" content="none" />
<title>Todo</title>
%sveltekit.head%
<!-- Umami Analytics -->

View file

@ -279,31 +279,6 @@
} catch {
// localStorage not available
}
// Register Service Worker for PWA
if ('serviceWorker' in navigator) {
try {
const registration = await navigator.serviceWorker.register('/sw.js', {
scope: '/',
});
console.log('Todo PWA: Service Worker registered', registration.scope);
// Check for updates
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
if (newWorker) {
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
// New version available
console.log('Todo PWA: New version available');
}
});
}
});
} catch (error) {
console.error('Todo PWA: Service Worker registration failed', error);
}
}
});
</script>

View file

@ -1,78 +1,9 @@
<script lang="ts">
import { onMount } from 'svelte';
let isOnline = $state(false);
onMount(() => {
isOnline = navigator.onLine;
const handleOnline = () => {
isOnline = true;
setTimeout(() => {
window.location.href = '/';
}, 1000);
};
const handleOffline = () => {
isOnline = false;
};
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
});
import { OfflinePage } from '@manacore/shared-ui';
</script>
<svelte:head>
<title>Offline - Todo</title>
</svelte:head>
<div class="min-h-screen bg-slate-900 flex items-center justify-center px-4">
<div class="text-center max-w-md">
<div class="mb-8">
<svg class="w-24 h-24 mx-auto text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a5 5 0 01-.354-7.072L8.95 7.636m1.414 5.657L7.535 16.12m8.485 0a5 5 0 01-7.07 0" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3l18 18" />
</svg>
</div>
<h1 class="text-2xl font-bold text-white mb-4">
{isOnline ? 'Verbindung wiederhergestellt!' : 'Du bist offline'}
</h1>
<p class="text-slate-400 mb-8">
{#if isOnline}
Du wirst gleich weitergeleitet...
{:else}
Deine Aufgaben werden lokal gespeichert und sind offline verfügbar.
{/if}
</p>
{#if !isOnline}
<div class="space-y-4">
<a href="/" class="inline-flex items-center justify-center px-6 py-3 bg-emerald-600 hover:bg-emerald-700 text-white font-medium rounded-lg transition-colors">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
Zur Startseite
</a>
<button onclick={() => window.location.reload()} class="block w-full px-6 py-3 text-slate-400 hover:text-white transition-colors">
Erneut versuchen
</button>
</div>
{:else}
<div class="flex items-center justify-center text-green-400">
<svg class="w-5 h-5 mr-2 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Weiterleitung...
</div>
{/if}
</div>
</div>
<OfflinePage
appName="Todo"
offlineMessage="Deine Aufgaben werden lokal gespeichert und sind offline verfügbar."
accentColor="#059669"
/>

View file

@ -1,71 +0,0 @@
{
"name": "Todo - Aufgabenverwaltung",
"short_name": "Todo",
"description": "Aufgaben und Projekte verwalten mit Kanban-Board, Subtasks und mehr",
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#8b5cf6",
"orientation": "any",
"categories": ["productivity", "utilities"],
"lang": "de",
"dir": "ltr",
"icons": [
{
"src": "/icons/icon.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "any"
},
{
"src": "/icons/icon.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "maskable"
}
],
"shortcuts": [
{
"name": "Neue Aufgabe",
"short_name": "Neu",
"description": "Neue Aufgabe erstellen",
"url": "/?action=new",
"icons": [
{
"src": "/icons/icon.svg",
"sizes": "any",
"type": "image/svg+xml"
}
]
},
{
"name": "Kanban Board",
"short_name": "Kanban",
"description": "Kanban-Ansicht öffnen",
"url": "/kanban",
"icons": [
{
"src": "/icons/icon.svg",
"sizes": "any",
"type": "image/svg+xml"
}
]
},
{
"name": "Einstellungen",
"short_name": "Settings",
"description": "App-Einstellungen öffnen",
"url": "/settings",
"icons": [
{
"src": "/icons/icon.svg",
"sizes": "any",
"type": "image/svg+xml"
}
]
}
],
"screenshots": [],
"prefer_related_applications": false
}

View file

@ -1,228 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Offline - Todo</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, #f5f3ff 0%, #ede9fe 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
}
@media (prefers-color-scheme: dark) {
body {
background: linear-gradient(135deg, #1e1b2e 0%, #2d2640 100%);
color: #f3f4f6;
}
}
.container {
text-align: center;
max-width: 400px;
padding: 2rem;
background: rgba(255, 255, 255, 0.9);
border-radius: 1.5rem;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.15);
backdrop-filter: blur(20px);
}
@media (prefers-color-scheme: dark) {
.container {
background: rgba(30, 27, 46, 0.9);
border: 1px solid rgba(255, 255, 255, 0.1);
}
}
.icon {
width: 80px;
height: 80px;
margin: 0 auto 1.5rem;
background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
border-radius: 1rem;
display: flex;
align-items: center;
justify-content: center;
}
.icon svg {
width: 48px;
height: 48px;
color: white;
}
h1 {
font-size: 1.5rem;
font-weight: 600;
color: #1f2937;
margin-bottom: 0.75rem;
}
@media (prefers-color-scheme: dark) {
h1 {
color: #f3f4f6;
}
}
p {
color: #6b7280;
margin-bottom: 1.5rem;
line-height: 1.6;
}
@media (prefers-color-scheme: dark) {
p {
color: #9ca3af;
}
}
.status {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
background: rgba(239, 68, 68, 0.1);
border-radius: 9999px;
color: #ef4444;
font-size: 0.875rem;
margin-bottom: 1.5rem;
}
.status-dot {
width: 8px;
height: 8px;
background: #ef4444;
border-radius: 50%;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
button {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
color: white;
border: none;
border-radius: 9999px;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: transform 0.15s, box-shadow 0.15s;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px -10px rgba(139, 92, 246, 0.5);
}
button:active {
transform: translateY(0);
}
.tips {
margin-top: 2rem;
padding-top: 1.5rem;
border-top: 1px solid rgba(0, 0, 0, 0.1);
text-align: left;
}
@media (prefers-color-scheme: dark) {
.tips {
border-top-color: rgba(255, 255, 255, 0.1);
}
}
.tips h2 {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #9ca3af;
margin-bottom: 0.75rem;
}
.tips ul {
list-style: none;
font-size: 0.875rem;
color: #6b7280;
}
@media (prefers-color-scheme: dark) {
.tips ul {
color: #9ca3af;
}
}
.tips li {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
.tips li::before {
content: '•';
color: #8b5cf6;
}
</style>
</head>
<body>
<div class="container">
<div class="icon">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414" />
</svg>
</div>
<h1>Du bist offline</h1>
<p>Keine Internetverbindung. Sobald du wieder online bist, kannst du deine Aufgaben verwalten.</p>
<div class="status">
<span class="status-dot"></span>
<span>Verbindung wird gesucht...</span>
</div>
<button onclick="location.reload()">
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
Erneut versuchen
</button>
<div class="tips">
<h2>Was du tun kannst</h2>
<ul>
<li>Überprüfe deine WLAN-Verbindung</li>
<li>Prüfe deine mobilen Daten</li>
<li>Versuche es in einigen Sekunden erneut</li>
</ul>
</div>
</div>
<script>
// Automatisch neu laden, wenn wieder online
window.addEventListener('online', () => {
location.reload();
});
</script>
</body>
</html>

View file

@ -1,147 +0,0 @@
const CACHE_NAME = 'todo-v3';
const OFFLINE_URL = '/offline.html';
// Assets, die immer gecacht werden sollen
const STATIC_CACHE_URLS = ['/', '/offline.html', '/icons/icon.svg', '/manifest.json'];
// Cache-Strategien für verschiedene Ressourcen
const CACHE_STRATEGIES = {
// Netzwerk zuerst, dann Cache (für HTML/Navigation)
networkFirst: [/\/$/, /\.html$/, /^\/kanban/, /^\/settings/, /^\/mana/, /^\/feedback/],
// Cache zuerst, dann Netzwerk (für Assets) - nur für gebaute Assets, nicht /src/
cacheFirst: [
/\/_app\//, // SvelteKit gebaute Assets
/\.woff2?$/,
/\.ttf$/,
/\.otf$/,
/\.ico$/,
],
// Nur Netzwerk (für API-Calls und Dev-Server)
networkOnly: [/\/api\//, /^\/src\//, /^\/@/, /^\/node_modules\//],
};
// Service Worker Installation
self.addEventListener('install', (event) => {
event.waitUntil(
caches
.open(CACHE_NAME)
.then((cache) => {
console.log('Todo Service Worker: Caching static assets');
return cache.addAll(STATIC_CACHE_URLS);
})
.then(() => self.skipWaiting())
);
});
// Service Worker Aktivierung
self.addEventListener('activate', (event) => {
event.waitUntil(
caches
.keys()
.then((cacheNames) => {
return Promise.all(
cacheNames
.filter((cacheName) => cacheName.startsWith('todo-') && cacheName !== CACHE_NAME)
.map((cacheName) => caches.delete(cacheName))
);
})
.then(() => self.clients.claim())
);
});
// Fetch-Event Handler
self.addEventListener('fetch', (event) => {
const { request } = event;
const url = new URL(request.url);
// Ignoriere Chrome Extension Requests
if (url.protocol === 'chrome-extension:') {
return;
}
// Ignoriere Cross-Origin Requests (z.B. Backend API)
if (url.origin !== self.location.origin) {
return;
}
// Bestimme die Cache-Strategie
const strategy = getStrategy(url.pathname);
if (strategy === 'networkFirst') {
event.respondWith(networkFirst(request));
} else if (strategy === 'cacheFirst') {
event.respondWith(cacheFirst(request));
} else if (strategy === 'networkOnly') {
event.respondWith(networkOnly(request));
} else {
// Standard: Network First
event.respondWith(networkFirst(request));
}
});
// Cache-Strategien Implementierung
async function networkFirst(request) {
try {
const networkResponse = await fetch(request);
if (networkResponse.ok) {
const cache = await caches.open(CACHE_NAME);
cache.put(request, networkResponse.clone());
}
return networkResponse;
} catch (error) {
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
// Wenn es eine Navigation ist und wir offline sind, zeige die Offline-Seite
if (request.mode === 'navigate') {
const offlineResponse = await caches.match(OFFLINE_URL);
if (offlineResponse) {
return offlineResponse;
}
}
throw error;
}
}
async function cacheFirst(request) {
const cachedResponse = await caches.match(request);
if (cachedResponse) {
return cachedResponse;
}
try {
const networkResponse = await fetch(request);
if (networkResponse.ok) {
const cache = await caches.open(CACHE_NAME);
cache.put(request, networkResponse.clone());
}
return networkResponse;
} catch (error) {
console.error('Todo SW: Fetch failed:', error);
throw error;
}
}
async function networkOnly(request) {
return fetch(request);
}
// Hilfsfunktion zur Bestimmung der Cache-Strategie
function getStrategy(pathname) {
for (const [strategy, patterns] of Object.entries(CACHE_STRATEGIES)) {
if (patterns.some((pattern) => pattern.test(pathname))) {
return strategy;
}
}
return 'networkFirst';
}
// Message Handler für Updates
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});

View file

@ -12,10 +12,30 @@ export default defineConfig({
sveltekit(),
SvelteKitPWA(
createPWAConfig({
name: 'Todo - Aufgaben',
name: 'Todo - Aufgabenverwaltung',
shortName: 'Todo',
description: 'Aufgaben und Projekte verwalten',
themeColor: '#10b981',
description: 'Aufgaben und Projekte verwalten mit Kanban-Board, Subtasks und mehr',
themeColor: '#8b5cf6',
shortcuts: [
{
name: 'Neue Aufgabe',
short_name: 'Neu',
description: 'Neue Aufgabe erstellen',
url: '/?action=new',
},
{
name: 'Kanban Board',
short_name: 'Kanban',
description: 'Kanban-Ansicht öffnen',
url: '/kanban',
},
{
name: 'Einstellungen',
short_name: 'Settings',
description: 'App-Einstellungen öffnen',
url: '/settings',
},
],
})
),
],

View file

@ -6,7 +6,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#8b5cf6" />
<meta name="description" content="Zitare - Inspirierende Zitate jeden Tag" />
<link rel="manifest" href="%sveltekit.assets%/manifest.json" />
<title>Zitare</title>
%sveltekit.head%
<!-- Umami Analytics -->

View file

@ -1,78 +1,9 @@
<script lang="ts">
import { onMount } from 'svelte';
let isOnline = $state(false);
onMount(() => {
isOnline = navigator.onLine;
const handleOnline = () => {
isOnline = true;
setTimeout(() => {
window.location.href = '/';
}, 1000);
};
const handleOffline = () => {
isOnline = false;
};
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
});
import { OfflinePage } from '@manacore/shared-ui';
</script>
<svelte:head>
<title>Offline - Zitare</title>
</svelte:head>
<div class="min-h-screen bg-slate-900 flex items-center justify-center px-4">
<div class="text-center max-w-md">
<div class="mb-8">
<svg class="w-24 h-24 mx-auto text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a5 5 0 01-.354-7.072L8.95 7.636m1.414 5.657L7.535 16.12m8.485 0a5 5 0 01-7.07 0" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3l18 18" />
</svg>
</div>
<h1 class="text-2xl font-bold text-white mb-4">
{isOnline ? 'Verbindung wiederhergestellt!' : 'Du bist offline'}
</h1>
<p class="text-slate-400 mb-8">
{#if isOnline}
Du wirst gleich weitergeleitet...
{:else}
Zitare benötigt eine Internetverbindung für neue Zitate.
{/if}
</p>
{#if !isOnline}
<div class="space-y-4">
<a href="/" class="inline-flex items-center justify-center px-6 py-3 bg-amber-600 hover:bg-amber-700 text-white font-medium rounded-lg transition-colors">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
Zur Startseite
</a>
<button onclick={() => window.location.reload()} class="block w-full px-6 py-3 text-slate-400 hover:text-white transition-colors">
Erneut versuchen
</button>
</div>
{:else}
<div class="flex items-center justify-center text-green-400">
<svg class="w-5 h-5 mr-2 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Weiterleitung...
</div>
{/if}
</div>
</div>
<OfflinePage
appName="Zitare"
offlineMessage="Zitare benötigt eine Internetverbindung für neue Zitate."
accentColor="#d97706"
/>

View file

@ -1,16 +0,0 @@
{
"name": "Zitare",
"short_name": "Zitare",
"description": "Inspirierende Zitate jeden Tag",
"start_url": "/",
"display": "standalone",
"background_color": "#1a1a2e",
"theme_color": "#8b5cf6",
"icons": [
{
"src": "/favicon.png",
"sizes": "192x192",
"type": "image/png"
}
]
}

View file

@ -155,6 +155,7 @@ export type { QuickInputItem, QuickAction, CreatePreview, InputBarSettings } fro
// Pages
export { default as AppsPage } from './pages/AppsPage.svelte';
export { default as OfflinePage } from './pages/OfflinePage.svelte';
// Charts - Statistics Visualization
export {

View file

@ -0,0 +1,119 @@
<script lang="ts">
import { onMount } from 'svelte';
interface Props {
appName: string;
offlineMessage: string;
accentColor?: string;
}
let { appName, offlineMessage, accentColor = '#3b82f6' }: Props = $props();
let isOnline = $state(false);
onMount(() => {
isOnline = navigator.onLine;
const handleOnline = () => {
isOnline = true;
setTimeout(() => {
window.location.href = '/';
}, 1000);
};
const handleOffline = () => {
isOnline = false;
};
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
});
</script>
<svelte:head>
<title>Offline - {appName}</title>
</svelte:head>
<div class="min-h-screen bg-slate-900 flex items-center justify-center px-4">
<div class="text-center max-w-md">
<div class="mb-8">
<svg
class="w-24 h-24 mx-auto text-slate-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a5 5 0 01-.354-7.072L8.95 7.636m1.414 5.657L7.535 16.12m8.485 0a5 5 0 01-7.07 0"
/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3l18 18" />
</svg>
</div>
<h1 class="text-2xl font-bold text-white mb-4">
{isOnline ? 'Verbindung wiederhergestellt!' : 'Du bist offline'}
</h1>
<p class="text-slate-400 mb-8">
{#if isOnline}
Du wirst gleich weitergeleitet...
{:else}
{offlineMessage}
{/if}
</p>
{#if !isOnline}
<div class="space-y-4">
<a
href="/"
class="inline-flex items-center justify-center px-6 py-3 text-white font-medium rounded-lg transition-colors"
style="background-color: {accentColor};"
onmouseenter={(e) => {
e.currentTarget.style.filter = 'brightness(0.85)';
}}
onmouseleave={(e) => {
e.currentTarget.style.filter = '';
}}
>
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
/>
</svg>
Zur Startseite
</a>
<button
onclick={() => window.location.reload()}
class="block w-full px-6 py-3 text-slate-400 hover:text-white transition-colors"
>
Erneut versuchen
</button>
</div>
{:else}
<div class="flex items-center justify-center text-green-400">
<svg class="w-5 h-5 mr-2 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
Weiterleitung...
</div>
{/if}
</div>
</div>

3
pnpm-lock.yaml generated
View file

@ -4959,6 +4959,9 @@ importers:
'@manacore/shared-theme':
specifier: workspace:*
version: link:../../../../packages/shared-theme
'@manacore/shared-ui':
specifier: workspace:^
version: link:../../../../packages/shared-ui
'@manacore/shared-utils':
specifier: workspace:*
version: link:../../../../packages/shared-utils