diff --git a/VERSION b/VERSION index 0da1d63..5afb033 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1103 \ No newline at end of file +1104 \ No newline at end of file diff --git a/backend/static/css/lists.css b/backend/static/css/lists.css new file mode 100644 index 0000000..f8d2014 --- /dev/null +++ b/backend/static/css/lists.css @@ -0,0 +1,328 @@ +/* ============================================================ + BAN YARO — Listen-Komponenten + Wiederverwendbare Klassen für Seiten mit Listen+Detail-Pattern: + Notes, Expenses, Health, Diary, Behavior-Log, ... + + Verwendung: +
+
...
+
Mai 2026
+
+
🍖
+
+
Titel
+
Vorschau-Text
+
+ 10:30 · 📍 Berlin +
+
+
25,50 €
+
+
+ ============================================================ */ + +/* ------------------------------------------------------------ + Shell + Header + ------------------------------------------------------------ */ +.list-shell { + display: flex; + flex-direction: column; + gap: var(--space-2); +} + +.list-filter-bar { + display: flex; + gap: var(--space-2); + padding: var(--space-3) var(--space-4); + flex-wrap: wrap; + align-items: center; +} + +.list-search-wrap { + flex: 1; + min-width: 200px; + position: relative; + display: flex; + align-items: center; +} +.list-search-wrap > input { width: 100%; } + +/* ------------------------------------------------------------ + Group-Header (Monat / Datums-Gruppe) + ------------------------------------------------------------ */ +.list-group-header { + font-size: var(--text-xs); + font-weight: 600; + color: var(--c-text-secondary); + text-transform: uppercase; + letter-spacing: 0.04em; + padding: var(--space-3) var(--space-4) var(--space-1); + margin-top: var(--space-2); +} + +/* ------------------------------------------------------------ + Item-Card (universelle Listen-Karte) + ------------------------------------------------------------ */ +.list-item-card { + display: flex; + gap: var(--space-3); + padding: var(--space-3) var(--space-4); + background: var(--c-surface); + border-radius: var(--radius-lg); + border: 1px solid var(--c-border-light); + align-items: flex-start; + transition: background 0.15s, transform 0.1s; +} + +.list-item-card--clickable { + cursor: pointer; +} +.list-item-card--clickable:hover { + background: var(--c-surface-2); +} +.list-item-card--clickable:active { + transform: scale(0.98); +} + +.list-item-card--milestone { + border-left: 3px solid #f5c518; +} + +.list-item-card--inactive { + opacity: 0.55; + filter: grayscale(0.8); +} + +/* ------------------------------------------------------------ + Linke Spalte: Date-Col oder Meta-Badge + ------------------------------------------------------------ */ + +/* Date-Column (Diary-Style: Wochentag + Tag) */ +.list-item-date-col { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + flex-shrink: 0; + min-width: 44px; + text-align: center; +} +.list-item-date-col-weekday { + font-size: var(--text-xs); + font-weight: 600; + color: var(--c-text-secondary); + text-transform: uppercase; + letter-spacing: 0.05em; +} +.list-item-date-col-day { + font-size: 1.5rem; + font-weight: 700; + color: var(--c-text); + line-height: 1.1; +} + +/* Meta-Badge (Expenses/Health-Style: farbiges Icon im Kreis) */ +.list-item-meta-badge { + width: 44px; + height: 44px; + border-radius: 50%; + background: color-mix(in srgb, var(--meta-color, var(--c-primary)) 15%, transparent); + color: var(--meta-color, var(--c-primary)); + display: flex; + align-items: center; + justify-content: center; + font-size: 1.4rem; + flex-shrink: 0; +} + +/* ------------------------------------------------------------ + Body (Hauptinhalt mittig) + ------------------------------------------------------------ */ +.list-item-body { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: var(--space-1); +} + +.list-item-title { + font-weight: 600; + font-size: var(--text-base); + color: var(--c-text); + line-height: 1.3; +} + +.list-item-text { + font-size: var(--text-sm); + color: var(--c-text-secondary); + line-height: 1.5; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.list-item-meta-row { + display: flex; + align-items: center; + gap: var(--space-1); + font-size: var(--text-xs); + color: var(--c-text-muted); + flex-wrap: wrap; +} + +/* ------------------------------------------------------------ + Chips + Micro-Badges (in Item-Body) + ------------------------------------------------------------ */ +.list-item-chips { + display: flex; + gap: var(--space-1); + flex-wrap: wrap; +} + +.list-item-chip { + padding: 2px var(--space-2); + border-radius: var(--radius-sm); + font-size: var(--text-xs); + font-weight: 600; + display: inline-flex; + align-items: center; + gap: 4px; + background: color-mix(in srgb, var(--chip-color, var(--c-primary)) 15%, transparent); + color: var(--chip-color, var(--c-primary)); +} + +.list-item-micro-badges { + display: flex; + gap: var(--space-1); + flex-wrap: wrap; + margin-top: 2px; +} +.list-item-micro-badge { + padding: 1px 6px; + background: var(--c-surface-2); + border-radius: var(--radius-sm); + font-size: 11px; + color: var(--c-text-secondary); +} + +/* ------------------------------------------------------------ + Rechte Spalte: Thumbnail, Amount, Actions + ------------------------------------------------------------ */ +.list-item-thumb { + width: 64px; + height: 64px; + border-radius: var(--radius-md); + overflow: hidden; + object-fit: cover; + flex-shrink: 0; + background: var(--c-surface-2); + position: relative; +} +.list-item-thumb-count { + position: absolute; + bottom: 4px; + right: 4px; + background: rgba(0,0,0,0.65); + color: #fff; + font-size: 10px; + font-weight: 700; + padding: 1px 5px; + border-radius: var(--radius-sm); +} + +.list-item-amount { + font-weight: 700; + font-size: var(--text-base); + white-space: nowrap; + flex-shrink: 0; + align-self: center; +} +.list-item-amount--positive { color: var(--c-success); } +.list-item-amount--negative { color: var(--c-danger); } +.list-item-amount--neutral { color: var(--c-text); } + +.list-item-actions { + display: flex; + gap: 2px; + flex-shrink: 0; + align-self: center; +} +.list-item-action-btn { + padding: 6px 8px; + border-radius: var(--radius-sm); + border: none; + background: transparent; + color: var(--c-text-muted); + cursor: pointer; + font-size: var(--text-sm); + transition: all 0.15s; +} +.list-item-action-btn:hover { + color: var(--c-text); + background: var(--c-surface-2); +} +.list-item-action-btn--danger:hover { + color: var(--c-danger); + background: color-mix(in srgb, var(--c-danger) 10%, transparent); +} + +/* ------------------------------------------------------------ + Reminder/Hinweis-Banner (Health-Style) + ------------------------------------------------------------ */ +.list-reminders-banner { + display: flex; + flex-direction: column; + gap: var(--space-1); + padding: var(--space-2) var(--space-4); +} + +.list-reminder-item { + display: flex; + align-items: center; + gap: var(--space-3); + padding: var(--space-2) var(--space-3); + background: var(--c-surface); + border-radius: var(--radius-md); + border-left: 3px solid var(--c-text-muted); + font-size: var(--text-sm); +} +.list-reminder-item--urgent { border-left-color: var(--c-danger); } +.list-reminder-item--warning { border-left-color: var(--c-warning, #f59e0b); } +.list-reminder-item--success { border-left-color: var(--c-success); } + +/* ------------------------------------------------------------ + FAB (Floating Action Button) + ------------------------------------------------------------ */ +.list-fab { + position: fixed; + bottom: calc(env(safe-area-inset-bottom, 16px) + 16px); + right: 20px; + width: 54px; + height: 54px; + border-radius: 50%; + background: var(--c-primary); + color: #fff; + border: none; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + box-shadow: 0 4px 18px rgba(196,132,58,0.4); + font-size: 26px; + z-index: 80; + transition: transform 0.12s, box-shadow 0.12s; +} +.list-fab:active { + transform: scale(0.92); + box-shadow: 0 2px 10px rgba(196,132,58,0.3); +} + +/* ------------------------------------------------------------ + Load-More + Empty-List in Listen-Context + ------------------------------------------------------------ */ +.list-load-more { + text-align: center; + padding: var(--space-4); +} diff --git a/backend/static/index.html b/backend/static/index.html index 249f2d3..846078e 100644 --- a/backend/static/index.html +++ b/backend/static/index.html @@ -86,13 +86,14 @@ Ban Yaro - + - - - - + + + + + @@ -616,11 +617,11 @@ - - - - - + + + + + @@ -630,7 +631,7 @@ - + diff --git a/backend/static/js/app.js b/backend/static/js/app.js index 2af1e81..514e893 100644 --- a/backend/static/js/app.js +++ b/backend/static/js/app.js @@ -3,7 +3,7 @@ Router, State-Management, Navigation, Initialisierung. ============================================================ */ -const APP_VER = '1103'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '1104'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt window.APP_VER = APP_VER; // global verfügbar für andere Module (z.B. offline-indicator) window.APP_VERSION = APP_VERSION; diff --git a/backend/static/js/pages/expenses.js b/backend/static/js/pages/expenses.js index fd9984a..ec9d07d 100644 --- a/backend/static/js/pages/expenses.js +++ b/backend/static/js/pages/expenses.js @@ -87,7 +87,7 @@ window.Page_expenses = (() => { ${_dogSelectorHtml()}
- `; @@ -283,28 +283,28 @@ window.Page_expenses = (() => { const datum = new Date(e.datum + 'T00:00:00') .toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' }); const dogBadge = e.dog_name - ? `${UI.icon('paw-print')} ${_esc(e.dog_name)}` + ? `${UI.icon('paw-print')} ${_esc(e.dog_name)}` : ''; const notiz = e.notiz - ? `${_esc(e.notiz)}` + ? `
${_esc(e.notiz)}
` : ''; return ` -
-
+
+
${UI.icon(k.icon)}
-
-
- ${datum} - ${k.label} - ${dogBadge} -
+
+
${k.label}
${notiz} +
+ ${datum} + ${dogBadge ? `· ${dogBadge}` : ''} +
-
-
${_fmt(e.betrag)}
-
@@ -313,15 +313,15 @@ window.Page_expenses = (() => { return `
-
- ${titel} - ${_fmt(summe)} +
+ ${titel} + ${_fmt(summe)}
${rows}
`; }).join(''); - el.innerHTML = `
${html}
`; + el.innerHTML = `
${html}
`; // Klick auf Zeile → Bearbeiten (nur wenn nicht Löschen-Button) el.querySelectorAll('.exp-entry').forEach(row => { @@ -372,30 +372,26 @@ window.Page_expenses = (() => { .toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }) : '—'; return ` -
-
${UI.icon(k.icon)}
-
-
- ${k.label} - ${HAEUFIGKEIT_LABEL[r.haeufigkeit] || r.haeufigkeit} - ${r.dog_name ? `${UI.icon('paw-print')} ${_esc(r.dog_name)}` : ''} -
- ${r.notiz ? `
${_esc(r.notiz)}
` : ''} -
- ${UI.icon('calendar')} Nächste Buchung: ${naechste} - ${!r.aktiv ? 'Pausiert' : ''} +
+
${UI.icon(k.icon)}
+
+
${k.label}
+ ${r.notiz ? `
${_esc(r.notiz)}
` : ''} +
+ ${HAEUFIGKEIT_LABEL[r.haeufigkeit] || r.haeufigkeit} + · ${UI.icon('calendar')} ${naechste} + ${r.dog_name ? `· ${UI.icon('paw-print')} ${_esc(r.dog_name)}` : ''} + ${!r.aktiv ? '· Pausiert' : ''}
-
-
${_fmt(r.betrag)}
-
- - -
+
${_fmt(r.betrag)}
+
+ +
`; }).join(''); @@ -407,7 +403,7 @@ window.Page_expenses = (() => {
${recurring.length - ? `
${cards}
` + ? `
${cards}
` : UI.emptyState({ icon: UI.icon('arrows-clockwise'), title: 'Keine Daueraufträge', text: 'Erfasse regelmäßige Ausgaben wie Hundesteuer oder Versicherung.' })} @@ -458,9 +454,8 @@ window.Page_expenses = (() => {
- - + + ${UI.moneyInput({ name: 'betrag', value: r?.betrag ?? '', required: true })}
@@ -501,7 +496,7 @@ window.Page_expenses = (() => { const fd = UI.formData(e.target); const payload = { kategorie: fd.kategorie, - betrag: parseFloat(fd.betrag), + betrag: UI.parseMoney(fd.betrag), haeufigkeit: fd.haeufigkeit, startdatum: fd.startdatum, notiz: fd.notiz || null, @@ -710,12 +705,7 @@ window.Page_expenses = (() => {
-
- - -
+ ${UI.moneyInput({ name: 'betrag', value: entry?.betrag ?? '', required: true })}
@@ -810,7 +800,7 @@ window.Page_expenses = (() => { const fd = UI.formData(ev.target); const payload = { kategorie: fd.kategorie, - betrag: parseFloat(fd.betrag), + betrag: UI.parseMoney(fd.betrag), datum: fd.datum, notiz: fd.notiz || null, dog_id: fd.dog_id ? parseInt(fd.dog_id) : null, diff --git a/backend/static/js/pages/health.js b/backend/static/js/pages/health.js index f22f5af..f0f5d7d 100644 --- a/backend/static/js/pages/health.js +++ b/backend/static/js/pages/health.js @@ -376,11 +376,11 @@ window.Page_health = (() => { const praxis = _praxen.find(p => p.id === e.tierarzt_id); const vetName = praxis?.name || e.tierarzt_name || ''; return ` -
+
-
-
${_esc(e.bezeichnung)}
-
+
+
${_esc(e.bezeichnung)}
+
${UI.time.format(e.datum + 'T00:00:00')} ${e.charge_nr ? ` · Ch.-Nr: ${_esc(e.charge_nr)}` : ''}
@@ -388,7 +388,7 @@ window.Page_health = (() => { ${e.naechstes ? `
Nächste Impfung: ${UI.time.format(e.naechstes + 'T00:00:00')} ${ampel.icon}
` : ''} - ${e.notiz ? `
${_esc(e.notiz)}
` : ''} + ${e.notiz ? `
${_esc(e.notiz)}
` : ''}
- ${e.notiz ? `
${_esc(e.notiz)}
` : ''} -
`).join(''); @@ -714,19 +715,19 @@ window.Page_health = (() => { ? Math.round((new Date(e.datum) - new Date(prev.datum)) / 86400000) : null; return ` -
+
${UI.icon('gender-female')}
-
-
Läufigkeit · ${UI.time.format(e.datum + 'T00:00:00')}
-
+
+
Läufigkeit · ${UI.time.format(e.datum + 'T00:00:00')}
+
${e.wert ? `Dauer: ${e.wert} Tage` : 'Dauer nicht angegeben'} ${interval ? ` · Abstand zur Vorherigen: ${interval} Tage` : ''}
- ${e.notiz ? `
${_esc(e.notiz)}
` : ''} + ${e.notiz ? `
${_esc(e.notiz)}
` : ''}
` : `
Noch keine Bewertungen
`; return ` -
-
-
+
+
${_esc(p.name)} ${!p.aktiv ? ' · Ehemalig' : ''}
${(p.strasse || p.plz || p.ort) ? ` -
+
${[p.strasse, [p.plz, p.ort].filter(Boolean).join(' ')].filter(Boolean).map(_esc).join(', ')}
` : ''} ${p.opening_hours ? ` -
+
${_esc(_fmtOeffnungszeiten(p.opening_hours))}
` : ''} @@ -2484,14 +2484,14 @@ window.Page_health = (() => { text-transform:uppercase;letter-spacing:.05em;margin-bottom:var(--space-2)"> Mein Tierarzt
-
+
-
+
${vet ? ` -
${_esc(vet.name)}
- ${adresse ? `
${_esc(adresse)}
` : ''} +
${_esc(vet.name)}
+ ${adresse ? `
${_esc(adresse)}
` : ''} ${vet.telefon ? `
{ const isImg = !['pdf'].includes(doc.file_type); const datum = doc.datum ? UI.time.format(doc.datum + 'T00:00:00') : ''; return ` -
+
-
-
${_esc(doc.titel)}
-
+
+
${_esc(doc.titel)}
+
${_esc(label)}${datum ? ' · ' + datum : ''} ${doc.vet_name ? ' · ' + _esc(doc.vet_name) : ''}
- ${doc.beschreibung ? `
${_esc(doc.beschreibung)}
` : ''} + ${doc.beschreibung ? `
${_esc(doc.beschreibung)}
` : ''}
@@ -3076,16 +3076,18 @@ window.Page_health = (() => { const fmt = d => { try { const p = d.split('-'); return `${parseInt(p[2])}.${parseInt(p[1])}.${p[0]}`; } catch { return d; } }; wrap.style.display = ''; + wrap.classList.add('list-reminders-banner'); wrap.innerHTML = items.slice(0, 3).map(r => { const overdue = r.ueberfaellig; + const modifier = overdue ? 'list-reminder-item--urgent' + : r.delta_tage <= 3 ? 'list-reminder-item--warning' + : ''; const color = overdue ? 'var(--c-danger,#ef4444)' : r.delta_tage <= 3 ? '#f59e0b' : 'var(--c-primary)'; - const bg = overdue ? 'rgba(239,68,68,0.08)' : r.delta_tage <= 3 ? 'rgba(245,158,11,0.08)' : 'var(--c-primary-subtle)'; const label = overdue ? `Überfällig seit ${Math.abs(r.delta_tage)} Tag${Math.abs(r.delta_tage)!==1?'en':''}` : r.delta_tage === 0 ? 'Heute fällig' : `in ${r.delta_tage} Tag${r.delta_tage!==1?'en':''}`; return ` -
+
diff --git a/backend/static/js/pages/notes.js b/backend/static/js/pages/notes.js index 10bdfa3..200bb81 100644 --- a/backend/static/js/pages/notes.js +++ b/backend/static/js/pages/notes.js @@ -125,7 +125,7 @@ window.Page_notes = (() => { .filter(([, items]) => items.length > 0) .map(([label, items]) => `
-
${_esc(label)}
+
${_esc(label)}
${items.map(_noteCard).join('')}
`).join(''); @@ -243,21 +243,28 @@ window.Page_notes = (() => { /* Gruppen */ .notes-group { display: flex; flex-direction: column; gap: var(--space-2); } + /* TODO nach Migration entfernen: ersetzt durch .list-group-header in lists.css */ .notes-group-label { font-size: var(--text-xs); font-weight: var(--weight-semibold); color: var(--c-text-muted); text-transform: uppercase; letter-spacing: .05em; padding: var(--space-1) 0; } - /* Karten */ - .notes-card { background: var(--c-surface); border: 1px solid var(--c-border); border-radius: var(--radius-lg); padding: var(--space-3) var(--space-4); display: flex; flex-direction: column; gap: var(--space-2); } - .notes-card-top { display: flex; align-items: flex-start; gap: var(--space-2); } - .notes-rubrik-chip { display: inline-flex; align-items: center; gap: 4px; font-size: var(--text-xs); font-weight: var(--weight-semibold); padding: 2px var(--space-2); border-radius: 999px; flex-shrink: 0; } + /* Karten — Notes-spezifischer Override: vertikales Layout statt horizontalem .list-item-card */ + .notes-card { flex-direction: column; gap: var(--space-2); } + .notes-card-top { display: flex; align-items: flex-start; gap: var(--space-2); width: 100%; } + /* TODO nach Migration entfernen: ersetzt durch .list-item-chip */ + /* .notes-rubrik-chip { display: inline-flex; align-items: center; gap: 4px; font-size: var(--text-xs); font-weight: var(--weight-semibold); padding: 2px var(--space-2); border-radius: 999px; flex-shrink: 0; } */ .notes-parent-label { font-size: var(--text-xs); color: var(--c-text-secondary); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; align-self: center; } - .notes-card-meta { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-xs); color: var(--c-text-muted); } - .notes-card-actions { display: flex; gap: var(--space-2); margin-left: auto; flex-shrink: 0; } - .notes-card-text { font-size: var(--text-sm); color: var(--c-text); line-height: 1.55; white-space: pre-wrap; margin: 0; } - .notes-micro-badges { display: flex; flex-wrap: wrap; gap: var(--space-1); } - .notes-micro-badge { font-size: var(--text-xs); padding: 2px 6px; border-radius: var(--radius-sm); background: var(--c-surface-2); color: var(--c-text-secondary); } - .notes-action-btn { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: var(--radius-md); border: 1px solid var(--c-border); background: var(--c-surface-2); color: var(--c-text-muted); cursor: pointer; font-size: 1rem; transition: background .15s, color .15s; } - .notes-action-btn:hover { background: var(--c-surface); color: var(--c-text); } - .notes-action-btn--danger:hover { background: #fef2f2; color: var(--c-danger); border-color: var(--c-danger); } + /* TODO nach Migration entfernen: ersetzt durch .list-item-meta-row */ + /* .notes-card-meta { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-xs); color: var(--c-text-muted); } */ + /* Notes-Override: Actions in Top-Zeile rechts ausrichten (statt align-self:center bei list-item-actions) */ + .notes-card-actions { margin-left: auto; align-self: flex-start; } + /* Notes-Override: Text ohne -webkit-line-clamp (komplett anzeigen) + pre-wrap */ + .notes-card-text { line-height: 1.55; white-space: pre-wrap; margin: 0; display: block; -webkit-line-clamp: unset; overflow: visible; color: var(--c-text); } + /* TODO nach Migration entfernen: ersetzt durch .list-item-micro-badges / .list-item-micro-badge */ + /* .notes-micro-badges { display: flex; flex-wrap: wrap; gap: var(--space-1); } */ + /* .notes-micro-badge { font-size: var(--text-xs); padding: 2px 6px; border-radius: var(--radius-sm); background: var(--c-surface-2); color: var(--c-text-secondary); } */ + /* TODO nach Migration entfernen: ersetzt durch .list-item-action-btn / --danger */ + /* .notes-action-btn { display: flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: var(--radius-md); border: 1px solid var(--c-border); background: var(--c-surface-2); color: var(--c-text-muted); cursor: pointer; font-size: 1rem; transition: background .15s, color .15s; } */ + /* .notes-action-btn:hover { background: var(--c-surface); color: var(--c-text); } */ + /* .notes-action-btn--danger:hover { background: #fef2f2; color: var(--c-danger); border-color: var(--c-danger); } */ .notes-list { display: flex; flex-direction: column; gap: var(--space-4); } @keyframes spin { to { transform: rotate(360deg); } } @@ -314,11 +321,10 @@ window.Page_notes = (() => { const hasLocation = !!note.location_name; return ` -
+
- + ${_esc(rb.label)} @@ -326,28 +332,28 @@ window.Page_notes = (() => { ? `${_esc(note.parent_label)}` : '' } -
- -
-

${_esc(_truncate(note.text))}

+

${_esc(_truncate(note.text))}

${microBadges.length ? ` -
- ${microBadges.map(b => `${_esc(b)}`).join('')} +
+ ${microBadges.map(b => `${_esc(b)}`).join('')}
` : ''} -
+
${_esc(_formatTime(note.updated_at || note.created_at))} ${hasLocation ? ` ${_esc(note.location_name)}` : ''} diff --git a/backend/static/landing.html b/backend/static/landing.html index e9018f3..dccab59 100644 --- a/backend/static/landing.html +++ b/backend/static/landing.html @@ -4,7 +4,7 @@ - + Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz diff --git a/backend/static/sw.js b/backend/static/sw.js index 410846c..4f7215b 100644 --- a/backend/static/sw.js +++ b/backend/static/sw.js @@ -4,7 +4,7 @@ ============================================================ */ // ← EINZIGE Stelle für die Version — STATIC_ASSETS und CACHE_VERSION leiten sich ab -const VER = '1103'; +const VER = '1104'; const CACHE_VERSION = `by-v${VER}`; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten