Commit graph

629 commits

Author SHA1 Message Date
9394bab1fb Big Sweep: Security + Race-Conditions + Tests + DSGVO + A11y, SW by-v1095
SECURITY (auth.py, routes/auth.py, database.py, main.py)
- JWT bekommt jti; Logout trägt in neue jwt_blacklist-Tabelle ein,
  decode_token() prüft → server-side Invalidierung
- JWT-Expiry default 30 → 7 Tage (ENV JWT_EXPIRY_DAYS überschreibt)
- Sliding-Refresh-Middleware: erneuert Cookie wenn >50% verbraucht
  (Schwelle via JWT_REFRESH_FRACTION, Default 2)
- Login-Lockout in DB-Tabelle login_attempts (5 Versuche / 15 Min,
  überlebt Container-Restart) — alte In-Memory-Lockouts ersetzt
- SMTP-Versand: alle 'except: pass' durch logger.exception ersetzt;
  Fehlversuche landen in failed_emails-Tabelle für späteres Retry
- Referral-Counter Race gefixt: UPDATE partner_codes SET uses=uses+1
  ... WHERE uses<max_uses RETURNING — atomar statt SELECT+UPDATE

RACE CONDITIONS (routes/invoices.py, database.py)
- Neue invoice_counters-Tabelle für atomare Nummernvergabe
- _next_invoice_number nutzt BEGIN IMMEDIATE + atomares UPDATE
- Funktioniert für RG- und ST-Prefixe (Stornorechnungen)
- Race-Test verifiziert (5 Threads × 20 Calls = 100 eindeutige Nummern)

VERSION + TESTS + ERROR-DIGEST (VERSION, Makefile, tests/, scheduler.py)
- Neue VERSION-Datei (Single Source of Truth) — main.py liest beim
  Startup
- Makefile-Target 'make bump' propagiert in sw.js, app.js, index.html
- Makefile-Target 'make test' setzt venv auf, läuft pytest
- 19 Smoke-Tests in tests/ (health, auth, diary, invoice) — alle grün
- Scheduler: täglicher _job_error_digest um 06:30 → schickt Error-
  Zusammenfassung an ADMIN_EMAIL (still wenn keine Errors)

DSGVO + A11Y + ERSTE-HILFE
- landing.html: 'HTML und ODS' → 'JSON' (tatsächlich implementiert)
- datenschutz.js: Sektion Account-Löschung erweitert (sofort gelöscht /
  anonymisiert / 10 Jahre für Rechnungen)
- erste-hilfe.js: prominentes Warning-Banner oben (ersetzt keine
  Tierarzt-Beratung); Notfallnummern gruppiert nach Land, TODO-Platz-
  halter für AT-Uni-Klinik, CH Tox Info Suisse, CH Tierspital Zürich
- ui.js Modal: ESC schließt, Focus-Trap, Auto-Focus erstes Element,
  Restore Focus auf vorigen Caller
- impressum.js Kontaktformular: Labels mit for=cf-name etc.

NEUE DB-TABELLEN (idempotent via CREATE TABLE IF NOT EXISTS)
- jwt_blacklist, login_attempts, failed_emails, invoice_counters

NEUE ENV-VARS
- JWT_REFRESH_FRACTION (Default 2)
- JWT_EXPIRY_DAYS Default geändert (30 → 7)
2026-05-26 20:12:01 +02:00
6224044654 Fix: Long-Press auf FAB 350ms statt 600ms + iOS-Textauswahl unterdrücken, SW by-v1094
User-Feedback: 600ms Long-Press triggert iOS-Textauswahl.

- Trigger-Dauer von 600ms auf 350ms verkürzt — feuert vor iOS-
  Auswahl-Callout (typisch ~500ms)
- Am #worlds-fab user-select:none, -webkit-user-select:none,
  -webkit-touch-callout:none — verhindert dass iOS bei Press ein
  Lupensymbol/Auswahl-Menü öffnet
2026-05-26 19:18:38 +02:00
61af803d99 UX: Step 4 wieder strikt + Long-Press auf FAB = Status-Modal, SW by-v1093
- Step 4 wieder strikt: alle 3 (expenses + routes + notes) müssen
  im Cache sein, sonst weiß. 5/5 Braun = wirklich alles bereit.
- Long-Press (600ms) auf #worlds-fab öffnet das Status-Modal mit
  detaillierter 5-Punkte-Checkliste + 'Fehlende nachladen'-Button.
  Normaler Click bleibt unverändert (FAB-Schnellaktion).
  Wenn Long-Press feuert, wird nachfolgender Click unterdrückt.
- _bindLongPress wird mehrfach gebunden falls FAB neu gerendert
  wird (data-lpBound verhindert Doppel-Binding).
2026-05-26 19:13:12 +02:00
66d2d96a2f Fix: Step 4 toleranter + Prefetch im 60s-Interval, SW by-v1092
Step 4 verlangte alle 3 von expenses/routes/notes — einer fehlte
(vermutlich notes 401 weil beim Prefetch noch nicht eingeloggt) →
Pfote weiß. Jetzt reichen 2 von 3.

Außerdem läuft _prefetchData() nicht nur beim Init + 2/5/10/20s-
Retries, sondern auch alle 60s mit refresh() — falls Login erst
spät erfolgt, kommt die fehlende API beim nächsten Tick.
2026-05-26 18:22:20 +02:00
0ba0de12b3 UX: Wetter-Preset + robusteres Tile-Prefetch (gemeinsamer LastPos), SW by-v1091
Beide nutzen jetzt by_last_position aus localStorage:

- wetter.js: speichert jede erfolgreiche GPS-Position; bei GPS-Fehler
  (kein Netz, Permission verweigert) wird der letzte bekannte Ort
  als Fallback genutzt — kein Error-Banner mehr wenn man ihn schon
  einmal hatte
- offline-indicator.js: _prefetchTiles versucht erst GPS, fällt
  dann auf den gespeicherten letzten Ort zurück → Step 5 wird auch
  ohne aktive GPS-Permission grün, sobald Wetter (oder andere
  Module) einmal eine Position eingeloggt haben
- TILE_MIN von 50 auf 20 gesenkt — 5x4 Tiles reichen für eine
  brauchbare Offline-Karte im Nahbereich
2026-05-26 17:10:52 +02:00
2876469e91 UX: Offline-Pfote in Ban-Yaro-Braun statt Grün, SW by-v1090
Filled-Farbe der Pfoten-Linien von #16a34a (grün) auf #5C3517
(dunkles Ban Yaro Braun) — passt zum Brand statt fremder
Signalfarbe, klar erkennbar auf orangem FAB.
2026-05-26 16:00:25 +02:00
d47fb61abf Fix: /api/notes ins SW Cacheable-Liste aufnehmen, SW by-v1089
Step 4 der Offline-Pfote (Weitere Listen) blieb weiß weil
/api/notes nicht in _CACHEABLE_GET stand — Notes-Requests wurden
vom SW gar nicht gecacht, egal wie oft sie geladen wurden.

Regex /^\/api\/notes/ ergänzt — jetzt cached der SW Notes-GETs
mit Stale-While-Revalidate (default 5min TTL).
2026-05-26 15:56:03 +02:00
03725d6682 Fix: Offline-Pfote — Step 2+3 tolerant, mehr Prefetch, SW by-v1088
Steps wurden nicht grün weil Probes zu strikt waren (alle 7 Module
bzw. 3 URL-Patterns erforderlich) — Cache-Inhalt zum Refresh-
Zeitpunkt oft unvollständig.

- Step 2 toleriert 1 fehlendes Page-Modul (have >= want-1)
- Step 3 verlangt nur noch Profil ODER welcome-dashboard PLUS Diary
  ODER Health (nicht beides)
- Neuer _prefetchPages() lädt alle 10 Page-Module proaktiv beim
  App-Start — unabhängig von SW-Install-Status
- _prefetchData() wird jetzt mehrmals retried (2s, 5s, 10s, 20s),
  damit hund-spezifische Daten geholt werden sobald
  _appState.activeDog gesetzt ist
2026-05-26 15:52:20 +02:00
87462cb2fe UX: Offline-Pfote misst echte Offline-Bereitschaft, SW by-v1087
Steps so umverteilt dass sie genau die Datentypen abdecken die der
User offline braucht — auch wenn er die Seiten nie geöffnet hat:

  1 App-Grundgerüst     CSS + Core-JS
  2 Wichtige Seiten     alle 10 Page-Module (precached via SW)
  3 Hund-Daten          Profil + Tagebuch + Gesundheit
  4 Weitere Listen      Ausgaben + Routen + Notizen
  5 Karten-Kacheln      OSM-Tiles im Umkreis

Automatischer Prefetch im _prefetchData() beim App-Start:
- /api/expenses · /api/routes · /api/notes (Step 4)
- /api/dogs/{id}/health · /api/dogs/{id}/diary (Step 3)
- Tiles via _prefetchTiles wenn GPS-Permission da (Step 5)

Wiki, Übungen, Streak, Wetter werden NICHT mehr vorgeladen — kommen
beim normalen Welten-Besuch ins Cache, sind aber nicht Pflicht für
'offline-bereit'.

setTimeout-Retry nach 3s: aktiver Hund ist beim ersten Init oft
noch nicht in _appState, danach kommt der Health/Diary-Prefetch.
2026-05-26 15:34:42 +02:00
307b4a5486 UX: Offline-Pfote — automatischer Tile-Prefetch + Step 5 umgebaut, SW by-v1086
- Step 5 misst jetzt 'Welt-Daten' (Streak + Wetter) statt
  Wiki/Übungen — die kommen automatisch beim Welten-Aufruf, kein
  zusätzliches Prefetch nötig (User-Wunsch: Wiki+Übungen NICHT
  preloaden)
- Neuer _prefetchTiles(): beim App-Start werden 49+9 OSM-Tiles
  (Zoom 14 +13) im 3km-Umkreis automatisch gecacht — aber NUR wenn
  GPS-Permission schon erteilt ist (kein nerviger Popup beim
  Start). Damit wird Step 4 nach kurzer Zeit grün.
- _fetchMissing für Step 5 lädt jetzt Streak + Wetter (statt Wiki)
2026-05-26 15:25:54 +02:00
94f02dbe3a UX: Mehr Offline-Seiten precachen + nur Strich grün, SW by-v1085
PRIORITY_PAGES erweitert auf 10 Seiten (war 8): zusätzlich
health.js, notes.js, expenses.js. admin.js raus — 233 KB, offline
irrelevant. Damit funktionieren offline ohne vorherigen Besuch:
Tagebuch · Gesundheit · Karte · Gassi · Erste Hilfe · Notizblock
Ausgaben · Routen · Giftköder · Vermisst.

Offline-Indikator Step 2 prüft jetzt alle 7 vom User genannten
Seiten (diary, map, walks, erste-hilfe, notes, expenses, routes) —
Pfote wird grün wenn alle im Static-Cache sind.

CSS-Färbung umgestellt: nur stroke (Linie) wird grün, kein fill
mehr. Pfote behält ihre offene Optik, nur die Outlines wechseln
von weiß zu Grün.
2026-05-26 15:14:07 +02:00
95dccd03be Fix: Offline-Score Cache-Detection robust, SW by-v1084
Bug: APP_VER war in app.js nur lokale const, nicht window.APP_VER
→ offline-indicator.js öffnete Cache 'by-v0-static' statt
'by-v1083-static' → fast alle Stufen blieben grau.

Fixes:
- app.js: window.APP_VER + window.APP_VERSION explizit setzen
- offline-indicator.js: _staticCache() Helper findet den aktuellen
  Static-Cache per Regex /^by-v\d+-static$/ — versions-unabhängig
- Step 1 (App-Shell) prüft jetzt korrekt auf design-system.css UND
  app.js im Static-Cache, nicht mehr caches.match() mit URL
2026-05-26 15:06:43 +02:00
b9fe5b5bc3 UX: Offline-Score direkt im FAB statt separater Pfote, SW by-v1083
User-Feedback: separater Indikator zu viel — die Pfote IM FAB selbst
soll je nach Score grün eingefärbt werden.

- Separater #offline-indicator Button entfernt (HTML + CSS)
- Welten-FAB-Icon: <use phosphor.svg#paw-print> ersetzt durch
  Inline-SVG mit 5 einzelnen paw-elem-Pfaden (1 Ballen + 4 Zehen)
- CSS: Default weiß (wie bisher), .filled wird leuchtendes Grün
  (#16a34a) — überzeichnet auf orangem FAB klar erkennbar
- offline-indicator.js: zeigt jetzt nur noch die FAB-Pfade ein/aus,
  kein eigenes Element mehr; Klick-Status-Modal als window.OfflineIndicator.openStatus() weiter verfügbar (kann
  später bei Bedarf an Long-Press oder Menüpunkt gehängt werden)
2026-05-26 14:57:19 +02:00
53c80b9bf6 Fix: Offline-Pfote sichtbar by-default, JS versteckt nur, SW by-v1082
Logik umgedreht: Default ist 'sichtbar', JS setzt .is-hidden nur wenn
explizit nicht in Welten. So robust gegen Sibling-Selektor-Probleme
oder CSS-Compositing-Eigenheiten auf iOS PWA.

Außerdem: Hintergrund prominenter (rgba 0.95 statt 0.85), echter
Border statt Glas-Filter, stärkerer Schatten — bei den vorigen
Versuchen war die Pfote vermutlich auch durch Transparenz schwer zu
erkennen auf grauem Hintergrund.
2026-05-26 14:43:56 +02:00
eb0f460304 Fix: Offline-Pfote per JS-Klasse sichtbar (Fallback zum CSS-Sibling), SW by-v1081
Der reine CSS-Sibling-Selektor klappte nicht zuverlässig (vermutlich
SW-Cache-Mismatch oder DOM-Reihenfolge im aktuellen Zustand des
Users). Lösung: MutationObserver in offline-indicator.js beobachtet
class/style auf #worlds-overlay und togglet .visible auf
#offline-indicator. CSS akzeptiert jetzt beide Wege:

  #worlds-overlay.worlds-visible ~ #offline-indicator,
  #offline-indicator.visible { display: flex; }

So bleibt das Layout funktional auch wenn CSS-Compositing oder
Cache-Versatz mal nicht greift. console.warn wenn das Element nicht
im DOM ist (z.B. wenn alte index.html aus SW-Cache).
2026-05-26 14:36:27 +02:00
521b7b6bee UX: Offline-Pfote über FAB + nur in Welten sichtbar, SW by-v1080
- Position: bottom-right über dem #worlds-fab (right:20px, bottom-
  Berechnung folgt FAB + 12px Abstand). Gleiche horizontale Achse
  wie FAB → ergibt eine 'Pfoten-Säule' (Indikator oben, FAB unten)
- Sichtbarkeit per CSS-Sibling-Selektor:
  #worlds-overlay.worlds-visible ~ #offline-indicator { display:flex }
  → Indikator nur sichtbar wenn Welten aktiv sind. Auf Detail-Seiten
  (Tagebuch, Karte, Admin etc.) bleibt er aus.
- z-index 61 (eine Stufe über dem FAB, unter Modals)
2026-05-26 14:30:57 +02:00
06b91dc54b Fix: Offline-Pfote als schwebendes Element (Welten verstecken Header), SW by-v1079
Der Header (#app-header) ist in den Welten per 'display:none !important'
ausgeblendet (Welten übernehmen Navigation). Mein Pfötchen saß da
drin und war genau dort unsichtbar wo es sichtbar sein sollte.

- Button aus dem Header rausgeholt, am Ende vom body als schwebendes
  Element platziert (position:fixed; top-right; z-index:9000)
- Eigener Stil: 40px runder Glas-Hintergrund, blur-Effekt, leichter
  Schatten — passt zur FAB-Optik unten rechts
- Dark-Mode Hintergrund: dunkles Glas
- Sichtbar in allen Welten und auf allen Seiten (auch wo Header da
  ist — sitzt daneben)
- 'hidden'-Default raus, Element ist sofort sichtbar (nur Färbung
  wartet auf refresh())
2026-05-26 14:24:45 +02:00
776641fa65 Fix: Offline-Indicator Cache-Namen + Step-5-Check, SW by-v1078
- CACHE_API hieß bei mir 'by-api', tatsächlich aber 'ban-yaro-api-v1'
  → korrigiert, sonst hätte step 3+5 nie grün werden können
- Step 5 prüfte auf gecachte Diary-Foto-Previews — die werden vom SW
  aber gar nicht gecacht (nur API-Routen sind in _CACHEABLE_GET).
  Stattdessen jetzt 'Training & Wissen' (training/exercises +
  wiki/rassen) — ist im SW-Cache abgedeckt und passt zur WELT-Welt
- _fetchMissing für Step 5 entsprechend angepasst
2026-05-26 14:18:47 +02:00
8097d21605 Feature: Offline-Bereitschafts-Indikator (Pfote im Header), SW by-v1077
- Neue Pfote oben rechts im Header zeigt 5-stufige Offline-Bereitschaft
  (1 Ballen + 4 Zehen, je 20% — grau outline → grün gefüllt)
- 5 Checks: App-Shell · Page-Module · Hund-/Tagebuchdaten · Karten-
  Tiles (≥50) · Foto-Previews
- Klick öffnet Status-Modal mit Checkliste + 'Fehlende nachladen'-
  Button. Lädt aktiv: Page-Module per fetch, API-Daten für aktiven
  Hund, Tile-Cache per SW-Message CACHE_TILES, Diary-Foto-Previews
- Refresh: alle 60s + bei SW CACHE_UPDATE-Message
- Eigene offline-indicator.js (nicht im app.js mit reingequetscht);
  ins STATIC_ASSETS-Precache aufgenommen
2026-05-26 14:16:57 +02:00
280213c11d UX: Rechnungs-Modal Footer für Mobile, SW by-v1076
Footer-Layout neu strukturiert — kein Umbruch-Chaos mehr:
- Erste Zeile: Abbrechen | Speichern (Grid 1fr 1fr, gleich breit)
  oder bei sent/paid nur 'Schließen' volle Breite
- Zweite Zeile (wenn vorhanden): Stornieren als volle Breite,
  ghost-Style mit rotem Rand — destruktive Aktion klar getrennt
- Button-Text 'Änderungen speichern' → 'Speichern' (kein Abschneiden
  mehr auf iPhone)
2026-05-26 14:03:09 +02:00
c4a82e96fd UX: Rechnungs-Edit-Modal status-bewusst + Storno-Button, SW by-v1075
- _openNeueRechnungModal akzeptiert jetzt status+invoice_number; je
  nach Status anderes Verhalten:
  • draft   → bearbeitbar, Stornieren + Abbrechen + Speichern
  • sent    → readonly Banner, Stornieren + Schließen
  • paid    → readonly Banner, Stornieren + Schließen
  • neu     → Abbrechen + Erstellen (kein Storno-Slot)
- Footer-Layout: Stornieren links, Abbrechen/Speichern rechts
  (justify-content:space-between → symmetrisch)
- Stornieren öffnet den existierenden _openStornoModal; das Edit-
  Modal wird durch UI.modal.open() automatisch geschlossen
- Submit-Handler ignoriert locked-Status (Schutz auch wenn Button
  irgendwie sichtbar wäre)
- Upgrades-Tab + Rechnungen-Tab geben jetzt inv.status+invoice_number
  beim Öffnen mit; Reload-Callback aus Upgrades-Tab rendert den Tab
  neu, damit nach Stornierung der gelbe Button zurück auf orange geht
2026-05-26 13:59:40 +02:00
5886e1b269 UX: Upgrades-Tab — Button zeigt vorhandene Rechnung an, SW by-v1074
- Backend: /admin/upgrade-requests liefert pro Request die offene
  Rechnung (id+number+status) per Subquery aus der invoices-Tabelle
  (status draft|sent → also nicht bezahlt, nicht storniert)
- Frontend: Wenn schon eine Rechnung existiert, wird statt 'Rechnung
  erstellen' (orange) der Button 'Rechnung bearbeiten' (gelb,
  #eab308) gezeigt. Klick lädt die Rechnung und öffnet das Modal im
  Edit-Modus — kein doppeltes Anlegen, Nummerierung bleibt sauber.
2026-05-26 13:50:03 +02:00
e5abdcab62 Fix: Tagebuch Foto-Löschen — null-crash + 404-Cleanup, SW by-v1073
- 'null is not an object (wrap2.remove)': Wrapper-Div hat keine
  Klasse .diary-media-thumb-wrap → closest() lieferte null. Fallback
  auf btn.parentElement + Null-Check vor remove()
- Bei 404 'Medium nicht gefunden' wird das verwaiste Foto jetzt
  trotzdem lokal aufgeräumt (entry.media_items + DOM), statt einen
  Error-Toast zu zeigen. Verwaiste Phantome verschwinden so beim
  ersten Lösch-Klick.
2026-05-26 13:38:11 +02:00
c03884cb81 Perf: 9 Performance-Fixes — SW by-v1072
Backend:
- DB: 3 neue Indizes (forum_posts thread+user, routes user) — Forum/Routen-Queries
- Caching: cache.py (TTL-Cache ohne neue Dependency) für 5 statische Listen
  (training_exercises, pflege_tipps, wiki_stats, wiki_gruppen, help_articles)
- diary.py + breeder_photos.py: Bildverarbeitung (ffmpeg/PIL/EXIF) per
  run_in_executor → blockiert Event-Loop nicht mehr
- scheduler.py: 11 kollidierende Jobs auf 5-Min-Intervalle gestaggert, coalesce=True
- social.py: ORDER BY RANDOM() ohne LIMIT in 2 Stellen gefixt
- alerts.py: Haversine-Loop bekommt SQL-Bounding-Box-Vorfilter

Frontend:
- sw.js: Tile-Cache mit LRU-Eviction (max 500 Einträge)
- admin.js: Event-Listener-Leak — Tab-Klicks per Delegation statt N Listener
- api.js: compressImage() Helper — Client-seitiges Resize auf max 2000px
  (HEIC/Videos/<500KB unverändert), integriert in 8 Upload-Stellen
  (diary, dog-profile×2, walks, poison, lost, health×2)

Bump APP_VER 1071 → 1072 (sw.js, app.js, main.py, index.html)
2026-05-26 06:30:36 +02:00
3abf974d29 Feature: Parallele Bild-Uploads, Heartbeat last_seen, Admin zuletzt aktiv, SW by-v1071
- Tagebuch: Bilder werden parallel hochgeladen (Promise.all), Button zeigt Fortschritt
- Auth: /heartbeat Route ergänzt — aktualisiert last_seen alle 5 Min
- Admin: last_seen + last_login in Nutzer-Liste angezeigt (🟢/🔵/)
- Bump SW by-v1071
2026-05-25 20:26:58 +02:00
9677d1e71a Fix: Pfoten-Button bleibt rechts (display:flex statt '' beim Einblenden), SW by-v1070 2026-05-19 19:04:32 +02:00
9f47cfe1d4 Fix: Pfoten-Button rechts neben Suchzeile (Dog-Chip eigene Zeile), SW by-v1069 2026-05-19 19:02:22 +02:00
b1e4d7fce1 Bump: SW by-v1068 (Übungen-Redesign) 2026-05-19 18:58:12 +02:00
d2c2c59abb Fix: Züchter-Header (Wurfverwaltung/Zuchtkartei/Läufigkeit) Hell-Mode kompatibel (CSS-Variablen statt hardcoded Dark-Gradient), SW by-v1036 2026-05-16 13:59:07 +02:00
416f32b056 Fix: Chips gleichförmig bei großer iOS-Schriftgröße (height 74px statt min-height, max-height px statt em), SW by-v1035 2026-05-16 13:51:03 +02:00
a736d66c47 UX: Panorama-Hinweis bleibt 4s sichtbar (erste 2x), Text + Querformat, SW by-v1034 2026-05-16 13:03:42 +02:00
a65e5a2723 Feature: Dark-Mode Overlay auf Welten-Panorama (rgba 0,0,0,0.45), SW by-v1033 2026-05-16 13:01:33 +02:00
f0cf0f2243 Fix: users.geburtstag Migration robuster (PRAGMA table_info Check)
Statt try/except mit pass: PRAGMA table_info prüft ob Spalte existiert,
loggt explizit ob hinzugefügt oder bereits vorhanden.
SW by-v1032, APP_VER 1032
2026-05-16 12:18:48 +02:00
fc8d396247 Fix: Geburtstags-Banner nur für aktiven Hund
bday/bdayYear: nur truthy wenn bdayDog.id === dog.id (aktiver Hund)
otherBdayDog: zeigt Hinweis wenn ein ANDERER Hund Geburtstag hat
SW by-v1031, APP_VER 1031
2026-05-16 12:13:15 +02:00
a50158d522 Fix: Worlds.refresh() nach Profil-Speichern aufrufen
Nach Object.assign(_appState.user, updated) wurde Worlds.refresh() nie
aufgerufen → JETZT-Welt zeigte alten Render ohne Geburtstags-Greeting.
SW by-v1030, APP_VER 1030
2026-05-16 12:07:31 +02:00
b54d9fda99 Fix: users.geburtstag Migration + Format TT.MM statt MM-DD
- database.py: ALTER TABLE users ADD COLUMN geburtstag TEXT (fehlte!)
- profile.py: Validierung auf \d{2}\.\d{2} (TT.MM Format)
- settings.js: Placeholder/Pattern auf TT.MM geändert
- worlds.js: Birthday-Check auf DD.MM Format angepasst
SW by-v1029, APP_VER 1029
2026-05-16 12:02:52 +02:00
a4377033ec Feature: User-Geburtstag im Profil + Glückwunsch in JETZT-Welt
Settings:
- Feld 'Dein Geburtstag (optional)' im Profil-Formular (Format MM-TT)
- Hinweis: nur für Geburtstagsgrüße, kein Jahr nötig
- profile.py: geburtstag gespeichert + Format-Validierung MM-DD

JETZT-Welt wenn heute User-Geburtstag:
- Greet-Text: 'Herzlichen Glückwunsch' statt Tageszeit-Gruß
- Animiertes Geburtstags-Reminder-Card (confetti + cake Icons)
- 'Alles Gute zum Geburtstag, [Name]!'

SW by-v1028, APP_VER 1028
2026-05-16 11:55:34 +02:00
1328e2c4e3 Feature: HUND-Geburtstag-Hint für Nicht-aktiven Hund
Wenn ein anderer Hund (nicht der angezeigte) Geburtstag hat:
- Cake-Icon (Phosphor) animiert bounce in der Info-Karte unten
- "[Name] hat heute/morgen Geburtstag!" + Pfeil-Button
- Klick → wechselt direkt zum Geburtstagshund + zeigt Birthday-Banner
Kein Tab-Indicator (nur HUND-Welt). SW by-v1027, APP_VER 1027
2026-05-16 11:51:06 +02:00
d8b75fbcab Fix: Globaler Dark-Mode-Filter für alle Leaflet-Karten
design-system.css: .leaflet-tile-pane bekommt den invert/hue-rotate-Filter
im Dark-Mode — gilt für walks, lost, poison, forum, routes und alle
anderen Seiten mit eingebetteten Leaflet-Karten.
design-system.css ?v=1025, SW by-v1026, APP_VER 1026
2026-05-16 11:41:12 +02:00
19640af288 Style: Marker + Cluster Rand Tactical-Olive statt Weiß
rgba(255,255,255,0.7/.8) → rgba(52,68,36,0.55/.65)
Dezentes Dunkeloliv auf allen Marker-Icons und Cluster-Bubbles.
SW by-v1025, APP_VER 1025
2026-05-16 11:35:01 +02:00
059002670a Feature: Karten-UI vollständig Dark-Mode-fähig
Filter-Chips, Status-Pille und Popup-Ballons passen sich dem Dark-Mode an:
- .map-legend-btn: dunkel frosted-glass (rgba(24,20,16,0.88)) statt weiß
- .map-legend-all (Filter-Btn): entsprechend dunkler Ton
- .map-statusbar: dunkler Hintergrund + helle Schrift
- .leaflet-popup-content-wrapper/.tip: --c-surface (#241C14) statt weiß
Aktive Tabs behalten Layer-Farbe, Marker-Farben bleiben unverändert.
components.css ?v=1015, SW by-v1024, APP_VER 1024
2026-05-16 11:25:04 +02:00
2cf0bc0d97 Fix: Karte CSS-Filter statt CartoDB Dark, Social Wrapper 860px
Karte Dark-Mode:
- CSS-Filter statt CartoDB Dark Matter (war zu dunkel)
  invert(93%) hue-rotate(180deg) brightness(0.88) contrast(0.88) saturate(0.85)
  Gleiche OSM-Tiles, Marker bleiben unbeeinflusst (tilePane separater Layer)
- Filter sofort bei Init + bei Theme-Wechsel via MutationObserver

Social:
- Outer Wrapper width:100%;max-width:860px;margin:0 auto
  Header-Karte und Tabs sind jetzt konsistent gleich breit
SW by-v1023, APP_VER 1023
2026-05-16 11:19:37 +02:00
721e630a34 Fix: Dark-Mode Karte + Badge-Farben + --c-bg-secondary
Karte:
- Dark-Mode: CartoDB Dark Matter Tiles statt OSM Standard
- MutationObserver + matchMedia watchee für Live-Theme-Wechsel
- _buildTileLayer() / _applyTileTheme() / _isDarkMode()

Badges litters.js:
- Hardgekodete dunkle Hintergründe → CSS-Klassen (badge-warning/-success/-muted)
- Funktioniert jetzt in Light + Dark Mode korrekt

movies.css:
- .movie-tag-stirbt/.movie-tag-ueberlebt → CSS-Variablen (danger-/success-subtle)
- Kein weißer Hintergrund mehr in Dark Mode

--c-bg-secondary: Zoom-Control 30px bleiben aus dem letzten Commit
SW by-v1022, APP_VER 1022
2026-05-16 11:11:09 +02:00
161c1e3f73 Fix: --c-bg-secondary definiert, Zoom-Control 30px, Dark-Mode Karten
- design-system.css: --c-bg-secondary hinzugefügt (fehlte → transparente Karten)
  Light: #F2EDE4, Dark: #221A12 — sichtbarer Kontrast zu --c-bg in beiden Modi
  Gilt für laeufi.js, litters.js, breeder.js Karten
- Zoom-Control: padding-top 28→30px (besser mittig zu 2 Filter-Reihen)
- components.css ?v=1014, SW by-v1021, APP_VER 1021
2026-05-16 10:50:10 +02:00
1c187675a9 Fix: Zoom-Control padding-top zurück auf 28px (war 33 = zu tief) 2026-05-16 10:42:44 +02:00
e44414015a Fix: Karte — Zoom-Ausrichtung, 'Z14' kürzer, Marker-Count ausgeblendet
- leaflet-top padding-top 28→33px (Zoom-Buttons mittig zu 2 Filter-Reihen)
- Zoom-Anzeige: 'Zoom 14 · ab 14: alle Layer' → 'Z14' (Hinweis als title)
- #map-osm-status ausgeblendet (Marker-Anzahl nimmt Platz von Wetter etc.)
- components.css ?v=1012, SW by-v1019, APP_VER 1019
2026-05-16 10:39:12 +02:00
562d64979f Fix: Karte — Filter-Button mit Label, kürzere Chip-Texte
- #map-legend-all: 'Filter' Text neben dem List-Icon
- 'Hundefreundl. Café/Restaurant' → 'Café & Restaurant'
- 'Hundefreundl. Hotel' → 'Hotel'
- .map-legend-all: padding angepasst für Text-Label
- components.css ?v=1011, SW by-v1018, APP_VER 1018
2026-05-16 10:32:11 +02:00
9a60c160a1 Feature: Läufigkeit-Spotlight in Züchter-Sektion (landing.html)
Zeigt ein realistisches Beispiel (Luna vom Bergwald) mit Deckdaten,
Trächtigkeits-Meilensteinen und Progesteronkurve — im bestehenden
Züchter-Abschnitt, kein neuer Section-Break.
SW by-v1017, APP_VER 1017
2026-05-16 10:23:46 +02:00
835c0ada21 Fix: laeufi.js width:100% für konsistente Breite kollabiert/aufgeklappt
SW by-v1016, APP_VER 1016
2026-05-16 10:20:19 +02:00
af4a61f5b3 Fix: laeufi.js Desktop — margin:0 auto + padding für Zentrierung
Äußerer Wrapper hatte max-width:860px aber kein margin:0 auto → klebte links.
Toolbar und List-Container bekamen fehlende Seitenabstände.
SW by-v1015, APP_VER 1015
2026-05-16 10:10:55 +02:00