Karte: Blickrichtungs-Kegel (Kompass) + ruhigeres Folgen (Rene: 'weiss nicht wo es ist/hinschaut')

- Standort-Punkt zeigt jetzt einen Kompass-KEGEL (wie Google Maps): CSS-Kegel
  im loc-icon (GL + Leaflet), deviceorientation mit webkitCompassHeading (iOS)
  bzw. 360-alpha (Android), Glaettung ueber kuerzesten Winkelweg, rAF-throttled.
  iOS-Permission via User-Geste (Follow-/Standort-Button startet den Kompass).
- Follow pant nur noch bei brauchbarem Fix (accuracy < 75m) — ungenaue
  Positionen liessen die Karte zappeln; GPS-Fixes frischer (maximumAge 2s
  statt 5s). Marker aktualisiert weiterhin jeden Fix.
Bump v1249
This commit is contained in:
rene 2026-06-06 20:30:22 +02:00
parent 85d578874a
commit a31d08a2dc
7 changed files with 83 additions and 22 deletions

View file

@ -3625,6 +3625,25 @@ html.modal-open {
top: 0; left: 0;
animation: loc-pulse 2s ease-out infinite;
}
/* Blickrichtungs-Kegel (Kompass): JS rotiert .loc-heading um die Marker-Mitte;
sichtbar erst mit Kompass-Daten (René 2026-06-06: weiß nicht, wo es hinschaut"). */
.loc-heading {
position: absolute;
left: 12px; top: 12px; /* Mitte des 24×24-Icons = Rotationsachse */
width: 0; height: 0;
display: none;
pointer-events: none;
}
.loc-heading::before {
content: '';
position: absolute;
left: -11px;
top: -28px; /* Kegel ragt von der Mitte nach oben */
width: 22px;
height: 24px;
background: linear-gradient(to top, rgba(59,130,246,0.55), rgba(59,130,246,0.05));
clip-path: polygon(50% 100%, 0 0, 100% 0); /* Spitze unten (Mitte), öffnet nach oben */
}
@keyframes loc-pulse {
0% { transform: scale(0.8); opacity: 1; }
100% { transform: scale(2.2); opacity: 0; }

View file

@ -86,14 +86,14 @@
<title>Ban Yaro</title>
<!-- Theme + theme-color Statusleiste vor CSS setzen -->
<script src="/js/boot-early.js?v=1248"></script>
<script src="/js/boot-early.js?v=1249"></script>
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
<link rel="stylesheet" href="/css/design-system.css?v=1248">
<link rel="stylesheet" href="/css/layout.css?v=1248">
<link rel="stylesheet" href="/css/components.css?v=1248">
<link rel="stylesheet" href="/css/utilities.css?v=1248">
<link rel="stylesheet" href="/css/lists.css?v=1248">
<link rel="stylesheet" href="/css/design-system.css?v=1249">
<link rel="stylesheet" href="/css/layout.css?v=1249">
<link rel="stylesheet" href="/css/components.css?v=1249">
<link rel="stylesheet" href="/css/utilities.css?v=1249">
<link rel="stylesheet" href="/css/lists.css?v=1249">
</head>
<body>
@ -612,11 +612,11 @@
<div id="modal-container"></div>
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
<script src="/js/api.js?v=1248"></script>
<script src="/js/ui.js?v=1248"></script>
<script src="/js/app.js?v=1248"></script>
<script src="/js/worlds.js?v=1248"></script>
<script src="/js/offline-indicator.js?v=1248"></script>
<script src="/js/api.js?v=1249"></script>
<script src="/js/ui.js?v=1249"></script>
<script src="/js/app.js?v=1249"></script>
<script src="/js/worlds.js?v=1249"></script>
<script src="/js/offline-indicator.js?v=1249"></script>
<!-- Feature-Seiten werden lazy geladen -->
@ -626,7 +626,7 @@
<!-- Boot: Offline-Banner + SW-Registration (extrahiert für CSP) -->
<script src="/js/boot.js?v=1248"></script>
<script src="/js/boot.js?v=1249"></script>
</body>

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
const APP_VER = '1248'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VER = '1249'; // ← 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;

View file

@ -369,6 +369,7 @@ window.Page_map = (() => {
// Follow-Mode (René 2026-06-08): Karte wandert ab jetzt mit dem Standort;
// manuelles Verschieben beendet das Folgen (dragstart-Listener im Map-Init).
_followGps = true;
_startCompass(); // User-Geste → iOS-Kompass-Permission
_updateFollowBtn();
UI.toast.info('Karte folgt deinem Standort — zum Beenden Karte verschieben.');
} else {
@ -836,6 +837,42 @@ window.Page_map = (() => {
const b = document.getElementById('map-follow-btn');
if (b) b.style.color = _followGps ? 'var(--c-primary)' : 'var(--c-text-secondary, #9ca3af)';
}
// Kompass → Blickrichtungs-Kegel am Standort-Punkt (René 2026-06-06: „fühlt sich an,
// als ob es nicht weiß, wo es hinschaut"). iOS verlangt requestPermission in einer
// User-Geste → Start über Follow-/Standort-Button. Karte bleibt genordet (dragRotate
// aus), daher gilt: Bildschirm-Rotation des Kegels = Kompass-Heading direkt.
let _compassOn = false, _compassRaf = null;
async function _startCompass() {
if (_compassOn || !window.DeviceOrientationEvent) return;
_compassOn = true;
if (typeof DeviceOrientationEvent.requestPermission === 'function') {
try { await DeviceOrientationEvent.requestPermission(); } catch (e) {}
}
let smoothed = null;
window.addEventListener('deviceorientation', e => {
let raw = null;
if (e.webkitCompassHeading != null) raw = e.webkitCompassHeading; // iOS
else if (e.alpha != null && e.absolute !== false) raw = 360 - e.alpha; // Android
if (raw == null) return;
// Glätten über den kürzesten Winkelweg (sonst springt der Kegel bei 359↔0)
if (smoothed == null) smoothed = raw;
else {
const d = ((raw - smoothed + 540) % 360) - 180;
smoothed = (smoothed + d * 0.25 + 360) % 360;
}
if (_compassRaf) return;
_compassRaf = requestAnimationFrame(() => {
_compassRaf = null;
const cone = document.querySelector('#central-map .loc-heading');
if (cone) {
cone.style.display = 'block';
cone.style.transform = `rotate(${smoothed}deg)`;
}
});
}, true);
}
function _ensureFollowBtn() {
const host = document.getElementById('central-map');
if (!host || document.getElementById('map-follow-btn')) { _updateFollowBtn(); return; }
@ -853,6 +890,7 @@ window.Page_map = (() => {
b.addEventListener('click', () => {
if (!_userPos) { UI.toast.error('Standort noch nicht verfügbar.'); return; }
_followGps = true;
_startCompass(); // User-Geste → iOS-Kompass-Permission
_mapSetView(_userPos.lat, _userPos.lon, Math.max(14, Math.round(_mapGetZoom())));
_updateFollowBtn();
});
@ -1150,14 +1188,18 @@ window.Page_map = (() => {
} else {
const elx = document.createElement('div');
elx.className = 'loc-icon';
elx.innerHTML = '<div class="loc-dot"></div><div class="loc-ring"></div>';
elx.innerHTML = '<div class="loc-heading"></div><div class="loc-dot"></div><div class="loc-ring"></div>';
_locationMarker = new maplibregl.Marker({ element: elx, anchor: 'center' })
.setLngLat([lon, lat]).addTo(_map);
}
if (_followGps && !_recActive) _map.easeTo({ center: [lon, lat], duration: 600 });
// Pan nur bei brauchbarem Fix — ungenaue Positionen (>75 m) lassen die
// Karte sonst zappeln („weiß nicht, wo es ist", René 2026-06-06).
if (_followGps && !_recActive && (pos.coords.accuracy ?? 999) < 75) {
_map.easeTo({ center: [lon, lat], duration: 600 });
}
},
() => {},
{ enableHighAccuracy: true, maximumAge: 5000, timeout: 15000 }
{ enableHighAccuracy: true, maximumAge: 2000, timeout: 15000 }
);
return;
}
@ -1165,7 +1207,7 @@ window.Page_map = (() => {
if (!window.L) return;
const icon = L.divIcon({
className: 'loc-icon',
html: '<div class="loc-dot"></div><div class="loc-ring"></div>',
html: '<div class="loc-heading"></div><div class="loc-dot"></div><div class="loc-ring"></div>',
iconSize: [24, 24],
iconAnchor: [12, 12],
});
@ -1187,10 +1229,10 @@ window.Page_map = (() => {
icon, zIndexOffset: 500, interactive: false,
}).addTo(_map);
}
if (_followGps && !_recActive) _map.panTo([lat, lon]);
if (_followGps && !_recActive && (pos.coords.accuracy ?? 999) < 75) _map.panTo([lat, lon]);
},
() => {},
{ enableHighAccuracy: true, maximumAge: 5000, timeout: 15000 }
{ enableHighAccuracy: true, maximumAge: 2000, timeout: 15000 }
);
}

View file

@ -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=1248"></script>
<script src="/js/landing-init.js?v=1249"></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">

View file

@ -4,7 +4,7 @@
============================================================ */
// ← EINZIGE Stelle für die Version — STATIC_ASSETS und CACHE_VERSION leiten sich ab
const VER = '1248';
const VER = '1249';
const CACHE_VERSION = `by-v${VER}`;
const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten