Compare commits

..

No commits in common. "bf26e5faf4ff8b5cdfeafa2c34ace3ec2332223e" and "01081fcd75d34f35f03cf1c715da70b54c44bd07" have entirely different histories.

7 changed files with 49 additions and 138 deletions

View file

@ -129,49 +129,6 @@ async def share_target(request: Request):
headers={"Cache-Control": "no-cache"}
)
# Cache-Reset-Seite — löscht SW + Caches, leitet zur App weiter
@app.get("/update")
async def force_update():
from fastapi.responses import HTMLResponse
html = """<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ban Yaro Aktualisieren</title>
<style>
body{font-family:-apple-system,sans-serif;display:flex;align-items:center;
justify-content:center;min-height:100vh;margin:0;background:#faf8f5}
.box{text-align:center;padding:2rem}
h1{color:#C4843A;margin-bottom:.5rem}
p{color:#666;margin-bottom:1.5rem}
.sp{width:44px;height:44px;border:4px solid #eee;border-top-color:#C4843A;
border-radius:50%;animation:s .8s linear infinite;margin:0 auto}
@keyframes s{to{transform:rotate(360deg)}}
</style>
</head>
<body>
<div class="box">
<h1>Ban Yaro</h1>
<p>App wird aktualisiert&#8230;</p>
<div class="sp"></div>
</div>
<script>
(async () => {
if ('serviceWorker' in navigator) {
const regs = await navigator.serviceWorker.getRegistrations();
await Promise.all(regs.map(r => r.unregister()));
}
const keys = await caches.keys();
await Promise.all(keys.map(k => caches.delete(k)));
window.location.replace('/');
})();
</script>
</body>
</html>"""
return HTMLResponse(html, headers={"Cache-Control": "no-store"})
# SPA Fallback — ALLE nicht-API-Routen gehen zur index.html
@app.get("/{full_path:path}")
async def spa_fallback(full_path: str):

View file

@ -1565,20 +1565,9 @@ textarea.form-control {
ZENTRALE KARTE (map.js)
============================================================ */
.map-full-layout {
position: fixed;
top: calc(var(--header-height) + var(--safe-top));
left: 0;
right: 0;
bottom: calc(var(--nav-bottom-height) + var(--safe-bottom));
position: absolute;
inset: 0;
overflow: hidden;
z-index: 1;
}
@media (min-width: 768px) {
.map-full-layout {
top: 0;
left: var(--nav-sidebar-width);
bottom: 0;
}
}
.map-full {
width: 100%;

View file

@ -118,15 +118,17 @@
.sidebar-backdrop { display: none !important; }
}
/* Karte: volle Höhe, kein Scrollen, kein Padding */
#page-map {
height: 100%;
overflow: hidden;
}
#page-map > .page-body {
padding: 0 !important;
overflow: hidden;
height: 100%;
/* Mobile Sidebar als Drawer */
@media (max-width: 767px) {
#sidebar {
display: flex;
z-index: 500;
transform: translateX(-100%);
transition: transform 0.28s ease;
}
#sidebar.open {
transform: translateX(0);
}
}
/* ------------------------------------------------------------
@ -265,30 +267,6 @@
#sidebar { display: flex; }
}
/* Mobile Sidebar als Drawer */
@media (max-width: 767px) {
#sidebar {
display: flex !important; /* überschreibt display:none aus Base */
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: var(--nav-sidebar-width);
z-index: 1000; /* klar über Backdrop (999) */
transform: translateX(-100%);
transition: transform 0.28s ease;
background: var(--c-surface);
box-shadow: none;
}
#sidebar.open {
transform: translateX(0);
box-shadow: 4px 0 24px rgba(0,0,0,0.18);
}
.sidebar-backdrop {
z-index: 999; /* unter Sidebar, über Seiteninhalt */
}
}
.sidebar-logo {
padding: var(--space-6) var(--space-5);
display: flex;

View file

@ -20,17 +20,33 @@
<title>Ban Yaro</title>
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
<!-- CSS: Reihenfolge ist wichtig -->
<link rel="stylesheet" href="/css/design-system.css">
<link rel="stylesheet" href="/css/layout.css?v=32">
<link rel="stylesheet" href="/css/components.css?v=32">
<link rel="stylesheet" href="/css/layout.css">
<link rel="stylesheet" href="/css/components.css">
</head>
<body>
<!-- Backdrop + Sidebar direkt im body (kein Ancestor-Stacking-Context) -->
<div id="sidebar-backdrop" class="sidebar-backdrop"></div>
<!-- ============================================================
APP SHELL
============================================================ -->
<div id="app">
<nav id="sidebar" role="navigation" aria-label="Hauptnavigation">
<!-- MOBILE HEADER (wird per JS mit Seitentitel befüllt) -->
<header id="app-header">
<button class="header-back hidden" id="header-back" aria-label="Zurück">&#8592;</button>
<div id="header-dog-switcher" class="dog-switcher">
<span class="header-title" id="header-title">Ban Yaro</span>
</div>
<div id="header-actions"></div>
<button class="header-menu-btn" id="header-menu-btn" aria-label="Menü"></button>
</header>
<!-- Backdrop für mobile Sidebar-Drawer -->
<div id="sidebar-backdrop" class="sidebar-backdrop"></div>
<!-- DESKTOP SIDEBAR -->
<nav id="sidebar" role="navigation" aria-label="Hauptnavigation">
<div class="sidebar-logo" id="sidebar-dog-switcher">
<img class="sidebar-logo-img" src="/icons/icon-180.png" alt="Ban Yaro">
<span class="sidebar-logo-text">Ban Yaro</span>
@ -95,21 +111,6 @@
</div>
</nav>
<!-- ============================================================
APP SHELL
============================================================ -->
<div id="app">
<!-- MOBILE HEADER -->
<header id="app-header">
<button class="header-back hidden" id="header-back" aria-label="Zurück">&#8592;</button>
<div id="header-dog-switcher" class="dog-switcher">
<span class="header-title" id="header-title">Ban Yaro</span>
</div>
<div id="header-actions"></div>
<button class="header-menu-btn" id="header-menu-btn" aria-label="Menü"></button>
</header>
<!-- HAUPT-INHALTSBEREICH -->
<main id="page-content" role="main">
@ -214,9 +215,9 @@
<div id="modal-container"></div>
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
<script src="/js/api.js?v=32"></script>
<script src="/js/ui.js?v=32"></script>
<script src="/js/app.js?v=32"></script>
<script src="/js/api.js"></script>
<script src="/js/ui.js"></script>
<script src="/js/app.js"></script>
<!-- Feature-Seiten werden lazy geladen -->

View file

@ -193,11 +193,7 @@ const App = (() => {
}
function _openSidebar() {
const s = document.getElementById('sidebar');
if (!s) { UI.toast.error('sidebar: NULL'); return; }
s.classList.add('open');
const cs = getComputedStyle(s);
UI.toast.info(`display:${cs.display} | transform:${cs.transform} | z:${cs.zIndex}`);
document.getElementById('sidebar')?.classList.add('open');
document.getElementById('sidebar-backdrop')?.classList.add('visible');
}

View file

@ -148,9 +148,8 @@ window.Page_map = (() => {
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 })
.addTo(_map);
// invalidateSize zweimal: einmal früh, einmal nach möglichen Layout-Delays
setTimeout(() => _map.invalidateSize(), 100);
setTimeout(() => _map.invalidateSize(), 600);
// invalidateSize nach kurzer Verzögerung, damit der Browser das Layout abgeschlossen hat
setTimeout(() => _map.invalidateSize(), 150);
window.addEventListener('resize', () => _map.invalidateSize());
}

View file

@ -3,11 +3,12 @@
Offline-Cache + Push Notifications
============================================================ */
const CACHE_VERSION = 'by-v33';
const CACHE_VERSION = 'by-v27';
const CACHE_STATIC = `${CACHE_VERSION}-static`;
// index.html wird NICHT pre-gecacht (immer Network-First)
// Diese Dateien werden beim Install gecacht (App Shell)
const STATIC_ASSETS = [
'/',
'/css/design-system.css',
'/css/layout.css',
'/css/components.css',
@ -43,7 +44,7 @@ self.addEventListener('activate', event => {
});
// ----------------------------------------------------------
// FETCH — Network-First für HTML, Cache-First für Assets, Network für API
// FETCH — Cache-First für statische Assets, Network-First für API
// ----------------------------------------------------------
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
@ -59,25 +60,12 @@ self.addEventListener('fetch', event => {
return;
}
// Navigation (index.html): immer Network-First
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request)
.then(response => {
const clone = response.clone();
caches.open(CACHE_STATIC).then(c => c.put(event.request, clone));
return response;
})
.catch(() => caches.match('/'))
);
return;
}
// Statische Assets: Cache-First
event.respondWith(
caches.match(event.request)
.then(cached => cached || fetch(event.request)
.then(response => {
// Erfolgreiche Responses für statische Assets cachen
if (response.ok && event.request.method === 'GET') {
const clone = response.clone();
caches.open(CACHE_STATIC).then(c => c.put(event.request, clone));
@ -86,6 +74,7 @@ self.addEventListener('fetch', event => {
})
)
.catch(() => {
// Offline-Fallback: App Shell zurückgeben
if (event.request.mode === 'navigate') {
return caches.match('/');
}
@ -147,6 +136,7 @@ self.addEventListener('notificationclick', event => {
event.waitUntil(
clients.matchAll({ type: 'window', includeUncontrolled: true })
.then(windowClients => {
// Offenes Fenster fokussieren
for (const client of windowClients) {
if (client.url.includes(self.location.origin)) {
client.focus();
@ -154,6 +144,7 @@ self.addEventListener('notificationclick', event => {
return;
}
}
// Neues Fenster öffnen
return clients.openWindow(url);
})
);