feat(pwa): Offline-Banner in App-Shell, reminders.css lazy geladen

Zeigt automatisch wenn navigator.onLine === false.
Blendet sich aus sobald Verbindung wiederhergestellt.
reminders.css aus globalem <link> entfernt (wird lazy geladen).
This commit is contained in:
Ulas Kalayci
2026-04-27 22:24:42 +02:00
parent d0adde29c4
commit 048e31e933
18 changed files with 123 additions and 16 deletions
+16 -2
View File
@@ -38,8 +38,6 @@
<link rel="stylesheet" href="/styles/layout.css" />
<link rel="stylesheet" href="/styles/glass.css" />
<link rel="stylesheet" href="/styles/login.css" />
<link rel="stylesheet" href="/styles/reminders.css" />
<!-- Theme: explizite Nutzer-Overrides vor CSS-Rendering anwenden (Flash-Prevention).
System-Präferenz wird durch @media (prefers-color-scheme: dark) in tokens.css
direkt per CSS behandelt — kein JS-matchMedia erforderlich. -->
@@ -62,6 +60,22 @@
<!-- Skip-Link: sichtbar bei Keyboard-Fokus, verknüpft mit <main id="main-content"> -->
<a href="#main-content" class="sr-only">Zum Hauptinhalt springen</a>
<!-- Offline-Banner: sichtbar wenn navigator.onLine === false -->
<div id="offline-banner" class="offline-banner" hidden aria-live="polite" role="status">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" aria-hidden="true">
<line x1="1" y1="1" x2="23" y2="23"></line>
<path d="M16.72 11.06A10.94 10.94 0 0 1 19 12.55"></path>
<path d="M5 12.55a10.94 10.94 0 0 1 5.17-2.39"></path>
<path d="M10.71 5.05A16 16 0 0 1 22.56 9"></path>
<path d="M1.42 9a15.91 15.91 0 0 1 4.7-2.88"></path>
<path d="M8.53 16.11a6 6 0 0 1 6.95 0"></path>
<line x1="12" y1="20" x2="12.01" y2="20"></line>
</svg>
<span data-i18n="offline.banner"></span>
</div>
<!-- App-Shell - wird durch JavaScript gefüllt -->
<div id="app" class="app-shell">
<!-- Skeleton-Loading während Initialisierung -->
+4 -1
View File
@@ -868,5 +868,8 @@
"next": "Next",
"done": "Get started",
"skip": "Skip"
},
"offline": {
"banner": "Offline reconnecting…"
}
}
}
+3
View File
@@ -888,5 +888,8 @@
"next": "Weiter",
"done": "Loslegen",
"skip": "Überspringen"
},
"offline": {
"banner": "Offline Verbindung wird wiederhergestellt…"
}
}
+4 -1
View File
@@ -868,5 +868,8 @@
"next": "Next",
"done": "Get started",
"skip": "Skip"
},
"offline": {
"banner": "Offline reconnecting…"
}
}
}
+4 -1
View File
@@ -869,5 +869,8 @@
"next": "Next",
"done": "Get started",
"skip": "Skip"
},
"offline": {
"banner": "Offline reconnecting…"
}
}
}
+4 -1
View File
@@ -868,5 +868,8 @@
"next": "Next",
"done": "Get started",
"skip": "Skip"
},
"offline": {
"banner": "Offline reconnecting…"
}
}
}
+4 -1
View File
@@ -868,5 +868,8 @@
"next": "Next",
"done": "Get started",
"skip": "Skip"
},
"offline": {
"banner": "Offline reconnecting…"
}
}
}
+4 -1
View File
@@ -868,5 +868,8 @@
"next": "Next",
"done": "Get started",
"skip": "Skip"
},
"offline": {
"banner": "Offline reconnecting…"
}
}
}
+4 -1
View File
@@ -868,5 +868,8 @@
"next": "Next",
"done": "Get started",
"skip": "Skip"
},
"offline": {
"banner": "Offline reconnecting…"
}
}
}
+4 -1
View File
@@ -868,5 +868,8 @@
"next": "Next",
"done": "Get started",
"skip": "Skip"
},
"offline": {
"banner": "Offline reconnecting…"
}
}
}
+4 -1
View File
@@ -869,5 +869,8 @@
"next": "Next",
"done": "Get started",
"skip": "Skip"
},
"offline": {
"banner": "Offline reconnecting…"
}
}
}
+4 -1
View File
@@ -868,5 +868,8 @@
"next": "Next",
"done": "Get started",
"skip": "Skip"
},
"offline": {
"banner": "Offline reconnecting…"
}
}
}
+4 -1
View File
@@ -868,5 +868,8 @@
"next": "Next",
"done": "Get started",
"skip": "Skip"
},
"offline": {
"banner": "Offline reconnecting…"
}
}
}
+4 -1
View File
@@ -868,5 +868,8 @@
"next": "Next",
"done": "Get started",
"skip": "Skip"
},
"offline": {
"banner": "Offline reconnecting…"
}
}
}
+4 -1
View File
@@ -868,5 +868,8 @@
"next": "Next",
"done": "Get started",
"skip": "Skip"
},
"offline": {
"banner": "Offline reconnecting…"
}
}
}
+4 -1
View File
@@ -868,5 +868,8 @@
"next": "Next",
"done": "Get started",
"skip": "Skip"
},
"offline": {
"banner": "Offline reconnecting…"
}
}
}
+14
View File
@@ -541,6 +541,20 @@ function renderAppShell(container) {
initMoreSheet(container);
initNavHideOnScroll(container);
initSearch(container);
initOfflineBanner();
}
function initOfflineBanner() {
const banner = document.getElementById('offline-banner');
if (!banner) return;
const i18nSpan = banner.querySelector('[data-i18n]');
function update() {
banner.hidden = navigator.onLine;
if (i18nSpan) i18nSpan.textContent = t('offline.banner');
}
window.addEventListener('online', update);
window.addEventListener('offline', update);
update();
}
/**
+34
View File
@@ -1897,6 +1897,40 @@
}
}
/* --------------------------------------------------------
* Offline-Banner
* -------------------------------------------------------- */
.offline-banner {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: calc(var(--z-toast) + 1);
display: flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-2) var(--space-4);
background-color: var(--color-warning-light);
color: var(--color-warning);
font-size: var(--text-sm);
font-weight: var(--font-weight-medium);
border-bottom: 1px solid color-mix(in srgb, var(--color-warning) 20%, transparent);
animation: offline-banner-in 0.2s var(--ease-out);
}
.offline-banner[hidden] {
display: none;
}
@keyframes offline-banner-in {
from { opacity: 0; transform: translateY(-100%); }
to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
.offline-banner { animation: none; }
}
/* --------------------------------------------------------
* Print-Styles
* -------------------------------------------------------- */