Routen: Detail/Vorschlag-Zoom robust (ResizeObserver) + Navi-Sperrbildschirm nur per Fingerabdruck
Punkt 3 (Zoom auf die Route): feste Timeouts (0/200/500ms) griffen auf iOS oft zu früh — der Modal-Container war noch nicht final vermessen, die Karte blieb beim Start-Zoom (zoom 14, center=Start) hängen statt auf die ganze Route zu zoomen. Jetzt _fitRouteMap mit ResizeObserver: fittet erneut, SOBALD der Container seine endgültige Größe hat (Detail + Vorschläge). Facade-fitBounds prüft jetzt auch clientHeight>0 (0-Höhe ergab schlechten Fit). Punkt 5 (Navigations-Sperrbildschirm): der 2-Sek-Halten-Handler hing am ganzen Dim-Overlay → Halten IRGENDWO entsperrte. Jetzt ein eigener Fingerabdruck-Knopf (rk-nav-unlock-btn) wie beim Aufzeichnen-Dim; nur dort entsperrt es, mit setPointerCapture. Tippen daneben tut bewusst nichts. Verifiziert (headless): Detail fittet die ganze Route (v1204, 0 Fehler); Dim-Hintergrund 2,2s halten → bleibt gesperrt, Knopf 2,2s halten → entsperrt.
This commit is contained in:
parent
285928f6f7
commit
d203ab17a8
7 changed files with 69 additions and 36 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
1203
|
||||
1204
|
||||
|
|
@ -86,14 +86,14 @@
|
|||
<title>Ban Yaro</title>
|
||||
|
||||
<!-- Theme + theme-color Statusleiste vor CSS setzen -->
|
||||
<script src="/js/boot-early.js?v=1203"></script>
|
||||
<script src="/js/boot-early.js?v=1204"></script>
|
||||
|
||||
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
|
||||
<link rel="stylesheet" href="/css/design-system.css?v=1203">
|
||||
<link rel="stylesheet" href="/css/layout.css?v=1203">
|
||||
<link rel="stylesheet" href="/css/components.css?v=1203">
|
||||
<link rel="stylesheet" href="/css/utilities.css?v=1203">
|
||||
<link rel="stylesheet" href="/css/lists.css?v=1203">
|
||||
<link rel="stylesheet" href="/css/design-system.css?v=1204">
|
||||
<link rel="stylesheet" href="/css/layout.css?v=1204">
|
||||
<link rel="stylesheet" href="/css/components.css?v=1204">
|
||||
<link rel="stylesheet" href="/css/utilities.css?v=1204">
|
||||
<link rel="stylesheet" href="/css/lists.css?v=1204">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
|
@ -617,11 +617,11 @@
|
|||
<div id="modal-container"></div>
|
||||
|
||||
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
|
||||
<script src="/js/api.js?v=1203"></script>
|
||||
<script src="/js/ui.js?v=1203"></script>
|
||||
<script src="/js/app.js?v=1203"></script>
|
||||
<script src="/js/worlds.js?v=1203"></script>
|
||||
<script src="/js/offline-indicator.js?v=1203"></script>
|
||||
<script src="/js/api.js?v=1204"></script>
|
||||
<script src="/js/ui.js?v=1204"></script>
|
||||
<script src="/js/app.js?v=1204"></script>
|
||||
<script src="/js/worlds.js?v=1204"></script>
|
||||
<script src="/js/offline-indicator.js?v=1204"></script>
|
||||
|
||||
<!-- Feature-Seiten werden lazy geladen -->
|
||||
|
||||
|
|
@ -631,7 +631,7 @@
|
|||
|
||||
|
||||
<!-- Boot: Offline-Banner + SW-Registration (extrahiert für CSP) -->
|
||||
<script src="/js/boot.js?v=1203"></script>
|
||||
<script src="/js/boot.js?v=1204"></script>
|
||||
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Router, State-Management, Navigation, Initialisierung.
|
||||
============================================================ */
|
||||
|
||||
const APP_VER = '1203'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
const APP_VER = '1204'; // ← 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;
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@
|
|||
// Nur fitten wenn Bounds gültig UND der Container eine Größe hat (im Modal
|
||||
// ist er beim Erstellen 0×0 → fitBounds würde NaN werfen; der Re-Fit nach
|
||||
// Modal-Animation greift dann).
|
||||
if (bb && !isNaN(bb.getWest()) && map.getContainer().clientWidth > 0) {
|
||||
var _c = map.getContainer();
|
||||
if (bb && !isNaN(bb.getWest()) && _c.clientWidth > 0 && _c.clientHeight > 0) {
|
||||
var pad = 30;
|
||||
if (opts && opts.padding) pad = Array.isArray(opts.padding) ? opts.padding[0] : opts.padding;
|
||||
try { map.fitBounds(bb, { padding: pad, maxZoom: opts && opts.maxZoom, duration: 0 }); } catch (e) {}
|
||||
|
|
|
|||
|
|
@ -617,8 +617,7 @@ window.Page_routes = (() => {
|
|||
UI.map.circleMarker(lls[0], { radius:7, color:'#22C55E', fillColor:'#22C55E', fillOpacity:1, weight:2 }).addTo(_suggestMap);
|
||||
UI.map.circleMarker(lls.at(-1), { radius:7, color:'#EF4444', fillColor:'#EF4444', fillOpacity:1, weight:2 }).addTo(_suggestMap);
|
||||
_addRouteArrows(_suggestMap, track, '#3b82f6');
|
||||
const _sfit = () => { _suggestMap?.invalidateSize(); _suggestMap?.fitBounds(poly.getBounds(), { padding: [16, 16] }); };
|
||||
_sfit(); setTimeout(_sfit, 200); setTimeout(_sfit, 500);
|
||||
_fitRouteMap(_suggestMap, mapEl, () => poly.getBounds());
|
||||
};
|
||||
_initMap();
|
||||
|
||||
|
|
@ -1713,14 +1712,25 @@ window.Page_routes = (() => {
|
|||
stroke-linejoin="round"
|
||||
stroke-linecap="round"/>
|
||||
</svg>
|
||||
<!-- 2-Sek-Halten-Ring -->
|
||||
<svg width="56" height="56" viewBox="0 0 56 56">
|
||||
<circle cx="28" cy="28" r="24" fill="none" stroke="rgba(255,255,255,.12)" stroke-width="2.5"/>
|
||||
<circle id="rk-nav-dim-prog" cx="28" cy="28" r="24" fill="none" stroke="rgba(255,255,255,.7)" stroke-width="2.5"
|
||||
stroke-dasharray="150.8" stroke-dashoffset="150.8" stroke-linecap="round"
|
||||
transform="rotate(-90 28 28)" style="transition:none"/>
|
||||
</svg>
|
||||
<div style="font-size:11px;opacity:.3;margin-top:8px">2 Sek. halten</div>
|
||||
<!-- 2-Sek-Halten-Knopf: NUR hier entsperrt sich der Bildschirm (Fingerabdruck).
|
||||
Tippen irgendwo sonst auf dem Dim-Overlay tut bewusst nichts. -->
|
||||
<button id="rk-nav-unlock-btn"
|
||||
style="background:none;border:none;cursor:pointer;outline:none;
|
||||
display:flex;flex-direction:column;align-items:center;gap:0;
|
||||
padding:0 16px 16px;-webkit-tap-highlight-color:transparent;
|
||||
touch-action:none;user-select:none">
|
||||
<svg width="56" height="56" viewBox="0 0 56 56">
|
||||
<circle cx="28" cy="28" r="24" fill="none" stroke="rgba(255,255,255,.12)" stroke-width="2.5"/>
|
||||
<circle id="rk-nav-dim-prog" cx="28" cy="28" r="24" fill="none" stroke="rgba(255,255,255,.7)" stroke-width="2.5"
|
||||
stroke-dasharray="150.8" stroke-dashoffset="150.8" stroke-linecap="round"
|
||||
transform="rotate(-90 28 28)" style="transition:none"/>
|
||||
</svg>
|
||||
<!-- Fingerabdruck, inline path (kein <use> wegen iOS-Bug) -->
|
||||
<svg viewBox="0 0 256 256" width="28" height="28" fill="white" style="margin-top:12px;opacity:0.5">
|
||||
<path d="M126.42,24C70.73,24.85,25.21,70.09,24,125.81a103.53,103.53,0,0,0,13.52,53.54,4,4,0,0,0,7.1-.3,119.35,119.35,0,0,0,11.37-51A71.77,71.77,0,0,1,83,71.83a8,8,0,1,1,9.86,12.61A55.82,55.82,0,0,0,72,128.07a135.28,135.28,0,0,1-18.45,68.35,4,4,0,0,0,.61,4.85c2,2,4.09,4,6.25,5.82a4,4,0,0,0,6-1A151.18,151.18,0,0,0,85,158.49a8,8,0,1,1,15.68,3.19,167.33,167.33,0,0,1-21.07,53.64,4,4,0,0,0,1.6,5.63c2.47,1.25,5,2.41,7.57,3.47a4,4,0,0,0,5-1.61A183,183,0,0,0,120,128.28a8.16,8.16,0,0,1,7.44-8.21,8,8,0,0,1,8.56,8,198.94,198.94,0,0,1-25.21,97.16,4,4,0,0,0,2.95,5.92q4.55.63,9.21.86a4,4,0,0,0,3.67-2.1A214.88,214.88,0,0,0,152,128.8c.05-13.25-10.3-24.49-23.54-24.74A24,24,0,0,0,104,128a8.1,8.1,0,0,1-7.29,8,8,8,0,0,1-8.71-8,40,40,0,0,1,40.42-40c22,.23,39.68,19.17,39.57,41.16a231.37,231.37,0,0,1-20.52,94.57,4,4,0,0,0,4.62,5.51,103.49,103.49,0,0,0,10.26-3,4,4,0,0,0,2.35-2.22,243.76,243.76,0,0,0,11.48-34,8,8,0,1,1,15.5,4q-1.12,4.37-2.4,8.7a4,4,0,0,0,6.46,4.17A104,104,0,0,0,126.42,24ZM198,161.08a8,8,0,0,1-7.92,7,8.39,8.39,0,0,1-1-.06,8,8,0,0,1-6.95-8.93,252.57,252.57,0,0,0,1.92-31,56.08,56.08,0,0,0-56-56,56.78,56.78,0,0,0-7,.43,8,8,0,0,1-2-15.89,72.1,72.1,0,0,1,81,71.49A266.93,266.93,0,0,1,198,161.08Z"/>
|
||||
</svg>
|
||||
<div style="font-size:11px;opacity:.3;margin-top:6px">2 Sek. halten</div>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(ovl);
|
||||
|
|
@ -1936,23 +1946,32 @@ window.Page_routes = (() => {
|
|||
_navResetInactTimer();
|
||||
|
||||
const dim = document.getElementById('rk-nav-dim');
|
||||
// Entsperren reagiert NUR auf den Fingerabdruck-Knopf (2 Sek. halten) — nicht mehr
|
||||
// auf das ganze Dim-Overlay. Tippen daneben lässt den Bildschirm bewusst gedimmt.
|
||||
const navUnlock = document.getElementById('rk-nav-unlock-btn');
|
||||
let _lpTimer = null;
|
||||
const cancelLp = () => {
|
||||
clearTimeout(_lpTimer);
|
||||
const prog = document.getElementById('rk-nav-dim-prog');
|
||||
if (prog) { prog.style.transition = 'none'; prog.style.strokeDashoffset = '150.8'; }
|
||||
};
|
||||
dim.addEventListener('pointerdown', e => {
|
||||
navUnlock.addEventListener('pointerdown', e => {
|
||||
e.stopPropagation();
|
||||
try { navUnlock.setPointerCapture(e.pointerId); } catch (err) {}
|
||||
const prog = document.getElementById('rk-nav-dim-prog');
|
||||
if (prog) { prog.style.transition = 'stroke-dashoffset 2s linear'; prog.style.strokeDashoffset = '0'; }
|
||||
_lpTimer = setTimeout(() => {
|
||||
dim.style.display = 'none'; _navDimmed = false; _navResetInactTimer();
|
||||
}, 2000);
|
||||
});
|
||||
dim.addEventListener('pointerup', cancelLp);
|
||||
dim.addEventListener('pointercancel', cancelLp);
|
||||
dim.addEventListener('pointerleave', cancelLp);
|
||||
navUnlock.addEventListener('pointerup', cancelLp);
|
||||
navUnlock.addEventListener('pointercancel', cancelLp);
|
||||
// Verlässt der Finger den Knopf während des Haltens → abbrechen (sonst entsperrt
|
||||
// ein wegrutschender Finger weiter). pointerleave reicht dank setPointerCapture.
|
||||
navUnlock.addEventListener('pointerleave', cancelLp);
|
||||
// Sicherheitsnetz: ein Tipp aufs Dim-Overlay (nicht auf den Knopf) tut nichts,
|
||||
// aber wir schlucken ihn, damit darunterliegende Buttons nicht reagieren.
|
||||
dim.addEventListener('pointerdown', e => { if (e.target === dim) e.stopPropagation(); });
|
||||
|
||||
// Aktions-Buttons
|
||||
document.getElementById('rk-nav-back').addEventListener('click', _closeNav);
|
||||
|
|
@ -2655,6 +2674,23 @@ window.Page_routes = (() => {
|
|||
}
|
||||
}
|
||||
|
||||
// Karte robust auf die ganze Route fitten — auch wenn der Container beim Erstellen
|
||||
// noch 0×0 ist (Modal-Animation / spätes Layout auf iOS). Feste Timeouts greifen dort
|
||||
// oft zu früh; der ResizeObserver fittet erneut, SOBALD der Container seine endgültige
|
||||
// Größe hat. Das war der Grund, warum die Detail-/Vorschlag-Karte auf dem Gerät beim
|
||||
// Start-Zoom (zoom 14, center=Start) hängen blieb statt auf die Route zu zoomen.
|
||||
function _fitRouteMap(m, el, getBounds, opts) {
|
||||
opts = opts || { padding: [16, 16] };
|
||||
const fit = () => { try { m.invalidateSize(); m.fitBounds(getBounds(), opts); } catch (e) {} };
|
||||
fit();
|
||||
setTimeout(fit, 150); setTimeout(fit, 400);
|
||||
if (window.ResizeObserver && el) {
|
||||
const ro = new ResizeObserver(() => { if (el.clientWidth > 0 && el.clientHeight > 0) fit(); });
|
||||
ro.observe(el);
|
||||
setTimeout(() => { try { ro.disconnect(); } catch (e) {} }, 4000);
|
||||
}
|
||||
}
|
||||
|
||||
async function _buildDetailMap(el, track) {
|
||||
const lls = track.map(p => [p.lat, p.lon]);
|
||||
const m = await UI.map.create(el, {
|
||||
|
|
@ -2665,11 +2701,7 @@ window.Page_routes = (() => {
|
|||
_addRouteArrows(m, track, '#3b82f6');
|
||||
UI.map.circleMarker(lls[0], { radius:7, color:'#22C55E', fillColor:'#22C55E', fillOpacity:1 }).addTo(m);
|
||||
UI.map.circleMarker(lls.at(-1), { radius:7, color:'#EF4444', fillColor:'#EF4444', fillOpacity:1 }).addTo(m);
|
||||
// Mehrfach fitten: beim Erstellen ist der Modal-Container evtl. noch 0×0 → nach
|
||||
// der Animation (resize) erneut auf die ganze Route zoomen.
|
||||
const _fit = () => { m.invalidateSize(); m.fitBounds(poly.getBounds(), { padding:[16,16] }); };
|
||||
_fit();
|
||||
setTimeout(_fit, 200); setTimeout(_fit, 500);
|
||||
_fitRouteMap(m, el, () => poly.getBounds());
|
||||
return m;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<script src="/js/landing-init.js?v=1203"></script>
|
||||
<script src="/js/landing-init.js?v=1204"></script>
|
||||
<title>Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz</title>
|
||||
<meta name="description" content="Ban Yaro: Die kostenlose All-in-One Hunde-App für DACH. Tagebuch, Giftköder-Alarm, Training mit KI, Forum, Wurfbörse, Stammbaum, Inzucht-Check — DSGVO-konform, offline-fähig, ohne App Store.">
|
||||
<meta name="keywords" content="Hunde App, Hunde Community, Wurfbörse, Züchter, Welpen kaufen, Stammbaum Hund, Inzuchtkoeffizient, Hundezucht, Impfpass Hund, Giftköder Alarm, Gassi Community, Hundetraining App, Hunde Forum, Hunde KI, Hundefilm Datenbank, Welpen Marktplatz">
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
============================================================ */
|
||||
|
||||
// ← EINZIGE Stelle für die Version — STATIC_ASSETS und CACHE_VERSION leiten sich ab
|
||||
const VER = '1203';
|
||||
const VER = '1204';
|
||||
const CACHE_VERSION = `by-v${VER}`;
|
||||
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
||||
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue