Compare commits

...

2 commits

Author SHA1 Message Date
178aef7fb0 Fix: Design-System-Regression v1102 — .hidden(!important) vs style.display app-weit
Rene: 'Tagebuch Kalenderansicht/Karte nicht mehr da' — Root-Cause: 459cd42
ersetzte style="display:none" durch class="hidden", aber die Show-Pfade
setzten weiter style.display. .hidden hat !important und gewinnt immer
(gleiche Klasse wie Filter-Panel-Hotfix v1242). Prod-Logs bewiesen: kein
einziger /diary/calendar- oder /locations-Request kam je an.

Unsichtbar seit v1102, jetzt per classList gefixt:
- diary: Stats-Bar mit View-Switcher (Liste/Medien/Kalender/Karte) + Medien-Grid neuer Eintrag
- health: KI-Tierarzt-Ergebnis erschien nie
- walks: Challenge-/Stamm-Gassi-Tabs leer
- welcome: iOS-Panel der Desktop-Install-Anleitung
- wiki: Fotos-Mod-Badge + Foto-Fallback (via app.js data-fb show-el/sibling-Handler)
- routes: Filter-Badge; breeder: Fotos-Section

Zweite Fehlerklasse aus demselben Sprint: doppelte class-Attribute
(class="x" id=… class="hidden") — Browser verwirft das zweite Attribut.
87 Vorkommen in 23 Dateien zusammengeführt; betroffene Show/Hide-Pfade
(ev-map, rk-mine/nearby-group, chat-partner-dot, eh-panel, zh-section)
auf classList umgestellt.
2026-06-07 15:09:43 +02:00
5acbaaa97b Landing: 'ohne App Store'-Formulierungen entschärft (3.1.1-Trigger) — PWA-Pitch jetzt 'direkt im Browser' 2026-06-07 11:03:36 +02:00
32 changed files with 207 additions and 198 deletions

View file

@ -1 +1 @@
1250 1252

View file

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

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung. Router, State-Management, Navigation, Initialisierung.
============================================================ */ ============================================================ */
const APP_VER = '1250'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const APP_VER = '1252'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt 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_VER = APP_VER; // global verfügbar für andere Module (z.B. offline-indicator)
window.APP_VERSION = APP_VERSION; window.APP_VERSION = APP_VERSION;
@ -469,12 +469,15 @@ const App = (() => {
break; break;
case 'sibling': case 'sibling':
el.style.display = 'none'; el.style.display = 'none';
if (el.nextElementSibling) el.nextElementSibling.style.display = 'flex'; if (el.nextElementSibling) {
el.nextElementSibling.classList.remove('hidden'); // .hidden hat !important
el.nextElementSibling.style.display = 'flex';
}
break; break;
case 'show-el': { case 'show-el': {
el.style.display = 'none'; el.style.display = 'none';
const t = el.dataset.fbEl && document.getElementById(el.dataset.fbEl); const t = el.dataset.fbEl && document.getElementById(el.dataset.fbEl);
if (t) t.style.display = 'flex'; if (t) { t.classList.remove('hidden'); t.style.display = 'flex'; } // .hidden hat !important
break; break;
} }
case 'emoji': case 'emoji':

View file

@ -745,7 +745,7 @@ window.Page_admin = (() => {
<div class="card p-4"> <div class="card p-4">
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);margin:0;line-height:1.6"> <p style="font-size:var(--text-sm);color:var(--c-text-secondary);margin:0;line-height:1.6">
<svg class="ph-icon" aria-hidden="true" class="text-primary"> <svg class="ph-icon text-primary" aria-hidden="true">
<use href="/icons/phosphor.svg#info"></use> <use href="/icons/phosphor.svg#info"></use>
</svg> </svg>
Ersten Admin per SQL setzen: Ersten Admin per SQL setzen:
@ -875,12 +875,12 @@ window.Page_admin = (() => {
<!-- Aktionen --> <!-- Aktionen -->
<div style="display:flex;gap:var(--space-1);flex-shrink:0"> <div style="display:flex;gap:var(--space-1);flex-shrink:0">
${u.is_banned ${u.is_banned
? `<button class="btn btn-sm btn-ghost adm-unban" data-uid="${u.id}" data-name="${UI.escape(u.name)}" ? `<button class="btn btn-sm btn-ghost adm-unban text-success" data-uid="${u.id}" data-name="${UI.escape(u.name)}"
title="Sperre aufheben" class="text-success"> title="Sperre aufheben">
<svg class="ph-icon"><use href="/icons/phosphor.svg#lock-open"></use></svg> <svg class="ph-icon"><use href="/icons/phosphor.svg#lock-open"></use></svg>
</button>` </button>`
: `<button class="btn btn-sm btn-ghost adm-ban" data-uid="${u.id}" data-name="${UI.escape(u.name)}" : `<button class="btn btn-sm btn-ghost adm-ban text-danger" data-uid="${u.id}" data-name="${UI.escape(u.name)}"
title="Sperren" class="text-danger"> title="Sperren">
<svg class="ph-icon"><use href="/icons/phosphor.svg#lock"></use></svg> <svg class="ph-icon"><use href="/icons/phosphor.svg#lock"></use></svg>
</button>` </button>`
} }
@ -895,9 +895,9 @@ window.Page_admin = (() => {
title="Abo-Stufe ändern"> title="Abo-Stufe ändern">
<svg class="ph-icon"><use href="/icons/phosphor.svg#star"></use></svg> <svg class="ph-icon"><use href="/icons/phosphor.svg#star"></use></svg>
</button> </button>
<button class="btn btn-sm btn-ghost adm-delete" data-uid="${u.id}" <button class="btn btn-sm btn-ghost adm-delete text-danger" data-uid="${u.id}"
data-name="${UI.escape(u.name)}" title="Löschen" data-name="${UI.escape(u.name)}" title="Löschen"
class="text-danger"> >
<svg class="ph-icon"><use href="/icons/phosphor.svg#trash"></use></svg> <svg class="ph-icon"><use href="/icons/phosphor.svg#trash"></use></svg>
</button> </button>
` : ''} ` : ''}
@ -1108,9 +1108,9 @@ window.Page_admin = (() => {
<svg class="ph-icon"><use href="/icons/phosphor.svg#${r.resolved ? 'arrow-square-out' : 'check'}"></use></svg> <svg class="ph-icon"><use href="/icons/phosphor.svg#${r.resolved ? 'arrow-square-out' : 'check'}"></use></svg>
</button> </button>
${!r.resolved ? ` ${!r.resolved ? `
<button class="btn btn-sm btn-ghost adm-del-content" <button class="btn btn-sm btn-ghost adm-del-content text-danger"
data-type="${r.target_type}" data-id="${r.target_id}" data-type="${r.target_type}" data-id="${r.target_id}"
title="Inhalt löschen" class="text-danger"> title="Inhalt löschen">
<svg class="ph-icon"><use href="/icons/phosphor.svg#trash"></use></svg> <svg class="ph-icon"><use href="/icons/phosphor.svg#trash"></use></svg>
</button>` : ''} </button>` : ''}
</div> </div>
@ -1193,13 +1193,13 @@ window.Page_admin = (() => {
data-locked="${t.is_locked}" title="${t.is_locked ? 'Entsperren' : 'Sperren'}"> data-locked="${t.is_locked}" title="${t.is_locked ? 'Entsperren' : 'Sperren'}">
<svg class="ph-icon"><use href="/icons/phosphor.svg#${t.is_locked ? 'lock-open' : 'lock'}"></use></svg> <svg class="ph-icon"><use href="/icons/phosphor.svg#${t.is_locked ? 'lock-open' : 'lock'}"></use></svg>
</button> </button>
<button class="btn btn-sm btn-ghost adm-del-thread" data-tid="${t.id}" <button class="btn btn-sm btn-ghost adm-del-thread text-danger" data-tid="${t.id}"
title="Löschen" class="text-danger"> title="Löschen">
<svg class="ph-icon"><use href="/icons/phosphor.svg#trash"></use></svg> <svg class="ph-icon"><use href="/icons/phosphor.svg#trash"></use></svg>
</button> </button>
` : ` ` : `
<button class="btn btn-sm btn-ghost adm-restore-thread" data-tid="${t.id}" <button class="btn btn-sm btn-ghost adm-restore-thread text-success" data-tid="${t.id}"
title="Wiederherstellen" class="text-success"> title="Wiederherstellen">
<svg class="ph-icon"><use href="/icons/phosphor.svg#arrow-square-out"></use></svg> <svg class="ph-icon"><use href="/icons/phosphor.svg#arrow-square-out"></use></svg>
</button> </button>
`} `}
@ -1712,7 +1712,7 @@ window.Page_admin = (() => {
<td class="adm-td">${z.website ? `<a href="${UI.escape(z.website)}" target="_blank" style="color:var(--c-primary);font-size:var(--text-xs)">Link</a>` : '—'}</td> <td class="adm-td">${z.website ? `<a href="${UI.escape(z.website)}" target="_blank" style="color:var(--c-primary);font-size:var(--text-xs)">Link</a>` : '—'}</td>
<td class="adm-td" style="text-align:right;white-space:nowrap"> <td class="adm-td" style="text-align:right;white-space:nowrap">
<button class="btn btn-sm btn-primary adm-zuchter-approve" data-id="${z.id}" style="margin-right:4px"><svg class="ph-icon" style="width:14px;height:14px" aria-hidden="true"><use href="/icons/phosphor.svg#check"></use></svg> Freigeben</button> <button class="btn btn-sm btn-primary adm-zuchter-approve" data-id="${z.id}" style="margin-right:4px"><svg class="ph-icon" style="width:14px;height:14px" aria-hidden="true"><use href="/icons/phosphor.svg#check"></use></svg> Freigeben</button>
<button class="btn btn-sm btn-ghost adm-zuchter-delete" data-id="${z.id}" class="text-danger"><svg class="ph-icon" style="width:14px;height:14px" aria-hidden="true"><use href="/icons/phosphor.svg#x"></use></svg></button> <button class="btn btn-sm btn-ghost adm-zuchter-delete text-danger" data-id="${z.id}"><svg class="ph-icon" style="width:14px;height:14px" aria-hidden="true"><use href="/icons/phosphor.svg#x"></use></svg></button>
</td> </td>
</tr>`).join('')} </tr>`).join('')}
</tbody></table></div></div>`; </tbody></table></div></div>`;
@ -1747,8 +1747,8 @@ window.Page_admin = (() => {
opacity:.5;margin-bottom:var(--space-2)"> opacity:.5;margin-bottom:var(--space-2)">
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:var(--space-3)"> aktuelles Foto</div>` : ''} <div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:var(--space-3)"> aktuelles Foto</div>` : ''}
<div class="flex-gap-2"> <div class="flex-gap-2">
<button class="btn btn-sm btn-primary adm-foto-approve" data-id="${f.id}" class="flex-1"></button> <button class="btn btn-sm btn-primary adm-foto-approve flex-1" data-id="${f.id}"></button>
<button class="btn btn-sm btn-ghost adm-foto-reject" data-id="${f.id}" class="text-danger"></button> <button class="btn btn-sm btn-ghost adm-foto-reject text-danger" data-id="${f.id}"></button>
</div> </div>
</div>`).join('')} </div>`).join('')}
</div>`; </div>`;
@ -1841,7 +1841,7 @@ window.Page_admin = (() => {
<button class="btn btn-sm btn-primary adm-poi-approve" data-id="${e.id}" style="margin-right:4px"> <button class="btn btn-sm btn-primary adm-poi-approve" data-id="${e.id}" style="margin-right:4px">
${UI.icon('check')} ${UI.icon('check')}
</button> </button>
<button class="btn btn-sm btn-ghost adm-poi-reject" data-id="${e.id}" class="text-danger"> <button class="btn btn-sm btn-ghost adm-poi-reject text-danger" data-id="${e.id}">
${UI.icon('x')} ${UI.icon('x')}
</button> </button>
</td> </td>
@ -2024,9 +2024,9 @@ window.Page_admin = (() => {
data-uid="${a.user_id || a.id}" data-name="${UI.escape(a.name)}"> data-uid="${a.user_id || a.id}" data-name="${UI.escape(a.name)}">
${UI.icon('check')} Freischalten ${UI.icon('check')} Freischalten
</button> </button>
<button class="btn btn-sm btn-ghost adm-breeder-reject" <button class="btn btn-sm btn-ghost adm-breeder-reject text-danger"
data-uid="${a.user_id || a.id}" data-name="${UI.escape(a.name)}" data-uid="${a.user_id || a.id}" data-name="${UI.escape(a.name)}"
class="text-danger"> >
${UI.icon('x')} Ablehnen ${UI.icon('x')} Ablehnen
</button> </button>
</div> </div>
@ -2170,9 +2170,9 @@ window.Page_admin = (() => {
${b.verified_at ? new Date(b.verified_at).toLocaleDateString('de-DE') : '—'} ${b.verified_at ? new Date(b.verified_at).toLocaleDateString('de-DE') : '—'}
</td> </td>
<td style="padding:var(--space-2) var(--space-3)"> <td style="padding:var(--space-2) var(--space-3)">
<button class="btn btn-sm btn-ghost adm-breeder-tier-btn" <button class="btn btn-sm btn-ghost adm-breeder-tier-btn text-xs"
data-uid="${b.id}" data-name="${UI.escape(b.name)}" data-tier="${UI.escape(b.subscription_tier || 'standard')}" data-uid="${b.id}" data-name="${UI.escape(b.name)}" data-tier="${UI.escape(b.subscription_tier || 'standard')}"
class="text-xs"> >
Abo Abo
</button> </button>
</td> </td>
@ -2251,8 +2251,8 @@ window.Page_admin = (() => {
${UI.escape(j.trigger)} ${UI.escape(j.trigger)}
</td> </td>
<td class="adm-td text-right"> <td class="adm-td text-right">
<button class="btn btn-sm btn-ghost adm-job-trigger adm-icon-btn" data-id="${UI.escape(j.id)}" data-name="${UI.escape(j.name)}" <button class="btn btn-sm btn-ghost adm-job-trigger adm-icon-btn text-primary" data-id="${UI.escape(j.id)}" data-name="${UI.escape(j.name)}"
title="Jetzt ausführen" class="text-primary"> title="Jetzt ausführen">
${UI.icon('play')} ${UI.icon('play')}
</button> </button>
</td> </td>
@ -3203,10 +3203,10 @@ window.Page_admin = (() => {
border:1.5px solid var(--c-border);border-radius:var(--radius-md); border:1.5px solid var(--c-border);border-radius:var(--radius-md);
background:var(--c-surface);color:var(--c-text);font-size:var(--text-sm)"> background:var(--c-surface);color:var(--c-text);font-size:var(--text-sm)">
<div class="flex-1"></div> <div class="flex-1"></div>
<button class="btn btn-secondary btn-sm adm-hilfe-edit-cancel" data-id="${a.id}" <button class="btn btn-secondary btn-sm adm-hilfe-edit-cancel text-xs" data-id="${a.id}"
class="text-xs">Abbrechen</button> >Abbrechen</button>
<button class="btn btn-primary btn-sm adm-hilfe-edit-save" data-id="${a.id}" <button class="btn btn-primary btn-sm adm-hilfe-edit-save text-xs" data-id="${a.id}"
class="text-xs">Speichern</button> >Speichern</button>
</div> </div>
</div> </div>
</div> </div>
@ -3839,8 +3839,8 @@ window.Page_admin = (() => {
</button>`); </button>`);
} }
if (inv.status === 'sent') { if (inv.status === 'sent') {
actions.push(`<button class="btn btn-sm btn-ghost adm-inv-send" data-id="${inv.id}" data-num="${UI.escape(inv.invoice_number)}" title="Erneut senden" actions.push(`<button class="btn btn-sm btn-ghost adm-inv-send text-muted" data-id="${inv.id}" data-num="${UI.escape(inv.invoice_number)}" title="Erneut senden"
class="text-muted"> >
${UI.icon('paper-plane-tilt')} Erneut senden ${UI.icon('paper-plane-tilt')} Erneut senden
</button>`); </button>`);
} }
@ -3848,8 +3848,8 @@ window.Page_admin = (() => {
actions.push(`<button class="btn btn-sm btn-secondary adm-inv-pay" data-id="${inv.id}" data-amount="${inv.amount_gross}" title="Als bezahlt markieren"> actions.push(`<button class="btn btn-sm btn-secondary adm-inv-pay" data-id="${inv.id}" data-amount="${inv.amount_gross}" title="Als bezahlt markieren">
${UI.icon('check-circle')} Bezahlt ${UI.icon('check-circle')} Bezahlt
</button>`); </button>`);
actions.push(`<button class="btn btn-sm btn-ghost adm-inv-cancel" data-id="${inv.id}" data-num="${UI.escape(inv.invoice_number)}" actions.push(`<button class="btn btn-sm btn-ghost adm-inv-cancel text-danger" data-id="${inv.id}" data-num="${UI.escape(inv.invoice_number)}"
class="text-danger" title="Stornieren"> title="Stornieren">
${UI.icon('x-circle')} Storno ${UI.icon('x-circle')} Storno
</button>`); </button>`);
} }
@ -4067,7 +4067,7 @@ window.Page_admin = (() => {
footer: ` footer: `
<div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%"> <div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%">
${isLocked ${isLocked
? `<button class="btn btn-secondary" data-modal-close class="w-full">Schließen</button>` ? `<button class="btn btn-secondary w-full" data-modal-close>Schließen</button>`
: `<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-2)"> : `<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-2)">
<button class="btn btn-secondary" data-modal-close>Abbrechen</button> <button class="btn btn-secondary" data-modal-close>Abbrechen</button>
<button class="btn btn-primary" form="${id}" type="submit" style="min-width:0"> <button class="btn btn-primary" form="${id}" type="submit" style="min-width:0">
@ -4112,8 +4112,8 @@ window.Page_admin = (() => {
itemEl.className = 'adm-inv-item-row'; itemEl.className = 'adm-inv-item-row';
itemEl.style.cssText = 'display:grid;grid-template-columns:1fr 60px 100px auto;gap:var(--space-2);align-items:center'; itemEl.style.cssText = 'display:grid;grid-template-columns:1fr 60px 100px auto;gap:var(--space-2);align-items:center';
itemEl.innerHTML = ` itemEl.innerHTML = `
<input class="form-control inv-item-desc" type="text" placeholder="Beschreibung *" <input class="form-control inv-item-desc text-sm" type="text" placeholder="Beschreibung *"
value="${UI.escape(desc)}" class="text-sm"> value="${UI.escape(desc)}">
<input class="form-control inv-item-qty" type="number" min="1" value="${qty}" <input class="form-control inv-item-qty" type="number" min="1" value="${qty}"
style="font-size:var(--text-sm);text-align:right" title="Menge"> style="font-size:var(--text-sm);text-align:right" title="Menge">
<input class="form-control inv-item-price" type="number" min="0" step="0.01" value="${price.toFixed(2)}" <input class="form-control inv-item-price" type="number" min="0" step="0.01" value="${price.toFixed(2)}"

View file

@ -196,8 +196,8 @@ window.Page_breeder_editor = (() => {
<label class="btn btn-secondary btn-sm" style="cursor:pointer"> <label class="btn btn-secondary btn-sm" style="cursor:pointer">
<svg class="ph-icon" style="width:14px;height:14px"><use href="/icons/phosphor.svg#upload-simple"></use></svg> <svg class="ph-icon" style="width:14px;height:14px"><use href="/icons/phosphor.svg#upload-simple"></use></svg>
Upload Upload
<input type="file" class="be-litter-input" data-litter-id="${l.id}" <input type="file" class="be-litter-input hidden" data-litter-id="${l.id}"
data-label="${UI.escape(label)}" accept="image/*,video/*" class="hidden"> data-label="${UI.escape(label)}" accept="image/*,video/*">
</label> </label>
</div> </div>
</div>`; </div>`;

View file

@ -390,6 +390,7 @@ window.Page_breeder = (() => {
</a>`).join('')} </a>`).join('')}
</div> </div>
</div>`; </div>`;
section.classList.remove('hidden'); // Template startet mit .hidden (!important)
} catch (_) {} } catch (_) {}
} }

View file

@ -195,7 +195,7 @@ window.Page_chat = (() => {
</button>`} </button>`}
<div style="position:relative;flex-shrink:0"> <div style="position:relative;flex-shrink:0">
<div class="chat-conv-avatar" id="chat-partner-av" style="width:32px;height:32px;font-size:var(--text-sm)">?</div> <div class="chat-conv-avatar" id="chat-partner-av" style="width:32px;height:32px;font-size:var(--text-sm)">?</div>
<span class="online-dot chat-avatar-dot" id="chat-partner-dot" class="hidden"></span> <span class="online-dot chat-avatar-dot hidden" id="chat-partner-dot"></span>
</div> </div>
<span class="chat-thread-partner" id="chat-partner-name"></span> <span class="chat-thread-partner" id="chat-partner-name"></span>
</div> </div>
@ -265,7 +265,7 @@ window.Page_chat = (() => {
const dotEl = document.getElementById('chat-partner-dot'); const dotEl = document.getElementById('chat-partner-dot');
if (nameEl) nameEl.textContent = data.partner_name; if (nameEl) nameEl.textContent = data.partner_name;
if (avEl) avEl.textContent = (data.partner_name || '?')[0].toUpperCase(); if (avEl) avEl.textContent = (data.partner_name || '?')[0].toUpperCase();
if (dotEl) dotEl.style.display = data.partner_online ? '' : 'none'; if (dotEl) dotEl.classList.toggle('hidden', !data.partner_online);
if (!data.messages.length) { if (!data.messages.length) {
el.innerHTML = ` el.innerHTML = `
@ -304,7 +304,7 @@ window.Page_chat = (() => {
// Online-Dot aktualisieren // Online-Dot aktualisieren
const dotEl = document.getElementById('chat-partner-dot'); const dotEl = document.getElementById('chat-partner-dot');
if (dotEl) dotEl.style.display = data.partner_online ? '' : 'none'; if (dotEl) dotEl.classList.toggle('hidden', !data.partner_online);
await API.chat.markRead(_convId).catch(() => {}); await API.chat.markRead(_convId).catch(() => {});
await _updateChatBadge(); await _updateChatBadge();

View file

@ -391,7 +391,7 @@ window.Page_diary = (() => {
const bar = _container.querySelector('#diary-stats-bar'); const bar = _container.querySelector('#diary-stats-bar');
if (!bar) return; if (!bar) return;
const s = _totalStats; const s = _totalStats;
if (!s && _entries.length === 0) { bar.style.display = 'none'; return; } if (!s && _entries.length === 0) { bar.classList.add('hidden'); return; }
const entries = s?.entries ?? _entries.length; const entries = s?.entries ?? _entries.length;
const photos = s?.photos ?? _entries.reduce((n, e) => n + _allMedia(e).length, 0); const photos = s?.photos ?? _entries.reduce((n, e) => n + _allMedia(e).length, 0);
const days = s?.days ?? new Set(_entries.map(e => e.datum).filter(Boolean)).size; const days = s?.days ?? new Set(_entries.map(e => e.datum).filter(Boolean)).size;
@ -425,7 +425,8 @@ window.Page_diary = (() => {
</button> </button>
</div> </div>
`; `;
bar.style.display = 'flex'; // .hidden hat !important → nur per classList togglen (style.display verliert immer)
bar.classList.remove('hidden');
bar.querySelectorAll('.diary-view-btn').forEach(btn => { bar.querySelectorAll('.diary-view-btn').forEach(btn => {
btn.addEventListener('click', () => { btn.addEventListener('click', () => {
@ -1332,8 +1333,9 @@ window.Page_diary = (() => {
function _renderNewGrid() { function _renderNewGrid() {
const grid = document.getElementById('diary-new-media-grid'); const grid = document.getElementById('diary-new-media-grid');
if (!grid) return; if (!grid) return;
if (_newFiles.length === 0) { grid.style.display = 'none'; grid.innerHTML = ''; return; } if (_newFiles.length === 0) { grid.classList.add('hidden'); grid.innerHTML = ''; return; }
grid.style.cssText = 'display:grid;grid-template-columns:repeat(auto-fill,minmax(90px,1fr));gap:8px;margin-bottom:8px'; // .hidden hat !important → classList togglen; Grid-Layout kommt aus .diary-media-grid
grid.classList.remove('hidden');
grid.innerHTML = _newFiles.map((f, i) => { grid.innerHTML = _newFiles.map((f, i) => {
const objUrl = URL.createObjectURL(f); const objUrl = URL.createObjectURL(f);
const thumb = f.type === 'application/pdf' || f.name?.endsWith('.pdf') const thumb = f.type === 'application/pdf' || f.name?.endsWith('.pdf')

View file

@ -559,8 +559,8 @@ window.Page_dog_profile = (() => {
value="${defaultUntil}" min="${today}"> value="${defaultUntil}" min="${today}">
</div> </div>
</div> </div>
<button class="btn btn-primary btn-sm w-full" id="sa-grant-btn" <button class="btn btn-primary btn-sm w-full mt-2" id="sa-grant-btn"
class="mt-2"> >
Zugang gewähren Zugang gewähren
</button> </button>
</div> </div>
@ -615,7 +615,7 @@ window.Page_dog_profile = (() => {
</div>`, </div>`,
footer: ` footer: `
<div class="w3-btn-stack"> <div class="w3-btn-stack">
<button class="btn btn-primary" id="chip-edit-save-btn" class="w-full">Speichern</button> <button class="btn btn-primary w-full" id="chip-edit-save-btn">Speichern</button>
<button class="btn btn-secondary" data-modal-close>Abbrechen</button> <button class="btn btn-secondary" data-modal-close>Abbrechen</button>
</div>`, </div>`,
}); });
@ -672,7 +672,7 @@ window.Page_dog_profile = (() => {
const footer = ` const footer = `
<div class="w3-btn-stack"> <div class="w3-btn-stack">
${hasPhoto ? `<button class="btn btn-primary" id="pe-save-btn" class="w-full">Speichern</button>` : ''} ${hasPhoto ? `<button class="btn btn-primary w-full" id="pe-save-btn">Speichern</button>` : ''}
<div class="flex-gap-2"> <div class="flex-gap-2">
${hasPhoto ? `<button class="btn btn-danger" id="pe-delete-btn">${UI.icon('trash')} Löschen</button>` : ''} ${hasPhoto ? `<button class="btn btn-danger" id="pe-delete-btn">${UI.icon('trash')} Löschen</button>` : ''}
<button class="btn btn-secondary flex-1" data-modal-close>Abbrechen</button> <button class="btn btn-secondary flex-1" data-modal-close>Abbrechen</button>
@ -945,8 +945,8 @@ window.Page_dog_profile = (() => {
<div id="share-link-result" style="display:none;margin-top:var(--space-4)"> <div id="share-link-result" style="display:none;margin-top:var(--space-4)">
<label class="form-label">Einladungslink</label> <label class="form-label">Einladungslink</label>
<div style="display:flex;gap:var(--space-2);align-items:center"> <div style="display:flex;gap:var(--space-2);align-items:center">
<input class="form-control" id="share-link-input" type="text" readonly <input class="form-control text-xs" id="share-link-input" type="text" readonly
class="text-xs"> >
<button class="btn btn-secondary btn-sm" id="share-link-copy"> <button class="btn btn-secondary btn-sm" id="share-link-copy">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#clipboard-text"></use></svg> <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#clipboard-text"></use></svg>
</button> </button>
@ -1509,11 +1509,11 @@ window.Page_dog_profile = (() => {
</div> </div>
${r.beschreibung ? `<div class="rasse-result-desc">${UI.escape(r.beschreibung)}</div>` : ''} ${r.beschreibung ? `<div class="rasse-result-desc">${UI.escape(r.beschreibung)}</div>` : ''}
<div style="display:flex;gap:var(--space-2);margin-top:var(--space-3);flex-wrap:wrap"> <div style="display:flex;gap:var(--space-2);margin-top:var(--space-3);flex-wrap:wrap">
${isTop ? `<button class="btn btn-primary btn-sm" data-action="uebernehmen" ${isTop ? `<button class="btn btn-primary btn-sm flex-1" data-action="uebernehmen"
data-rasse="${UI.escape(r.name)}" class="flex-1"> data-rasse="${UI.escape(r.name)}">
Rasse übernehmen Rasse übernehmen
</button>` : `<button class="btn btn-secondary btn-sm" data-action="uebernehmen" </button>` : `<button class="btn btn-secondary btn-sm flex-1" data-action="uebernehmen"
data-rasse="${UI.escape(r.name)}" class="flex-1"> data-rasse="${UI.escape(r.name)}">
Diese wählen Diese wählen
</button>`} </button>`}
${r.wiki_slug ? `<button class="btn btn-ghost btn-sm" data-action="wiki" ${r.wiki_slug ? `<button class="btn btn-ghost btn-sm" data-action="wiki"
@ -1711,8 +1711,8 @@ window.Page_dog_profile = (() => {
<p style="font-size:var(--text-sm);margin:0">Noch keine Impfungen eingetragen.<br>Klicke auf + Eintragen" um loszulegen.</p> <p style="font-size:var(--text-sm);margin:0">Noch keine Impfungen eingetragen.<br>Klicke auf + Eintragen" um loszulegen.</p>
</div>` </div>`
: vaccs.map(v => ` : vaccs.map(v => `
<div class="pp-vacc-row" data-id="${v.id}" <div class="pp-vacc-row pp-data-row" data-id="${v.id}"
class="pp-data-row"> >
<div class="flex-1"> <div class="flex-1">
<div style="font-weight:600;font-size:var(--text-sm)">${UI.escape(v.krankheit)}</div> <div style="font-weight:600;font-size:var(--text-sm)">${UI.escape(v.krankheit)}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:2px"> <div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:2px">
@ -1748,8 +1748,8 @@ window.Page_dog_profile = (() => {
<p style="font-size:var(--text-sm);margin:0">Noch keine Medikamente eingetragen.<br>Klicke auf + Eintragen" um loszulegen.</p> <p style="font-size:var(--text-sm);margin:0">Noch keine Medikamente eingetragen.<br>Klicke auf + Eintragen" um loszulegen.</p>
</div>` </div>`
: meds.map(m => ` : meds.map(m => `
<div class="pp-med-row" data-id="${m.id}" <div class="pp-med-row pp-data-row" data-id="${m.id}"
class="pp-data-row"> >
<div class="flex-1"> <div class="flex-1">
<div style="font-weight:600;font-size:var(--text-sm)">${UI.escape(m.name)}</div> <div style="font-weight:600;font-size:var(--text-sm)">${UI.escape(m.name)}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:2px"> <div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:2px">
@ -2005,8 +2005,8 @@ window.Page_dog_profile = (() => {
Dieser Link ist 30 Tage gültig. Tierärzte und Sitter können den Pass ohne Login öffnen. Dieser Link ist 30 Tage gültig. Tierärzte und Sitter können den Pass ohne Login öffnen.
</p> </p>
<div style="display:flex;gap:var(--space-2);align-items:center"> <div style="display:flex;gap:var(--space-2);align-items:center">
<input id="pp-sharelink-input" class="form-control" type="text" readonly <input id="pp-sharelink-input" class="form-control text-xs" type="text" readonly
value="${UI.escape(url)}" class="text-xs"> value="${UI.escape(url)}">
<button class="btn btn-secondary btn-sm" id="pp-sharelink-copy" style="flex-shrink:0"> <button class="btn btn-secondary btn-sm" id="pp-sharelink-copy" style="flex-shrink:0">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#clipboard-text"></use></svg> <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#clipboard-text"></use></svg>
</button> </button>

View file

@ -567,9 +567,9 @@ window.Page_ernaehrung = (() => {
'Ist Getreide im Futter schlecht?', 'Ist Getreide im Futter schlecht?',
'Welche Leckerlis sind gesund?', 'Welche Leckerlis sind gesund?',
].map(q => ` ].map(q => `
<button class="btn btn-sm btn-secondary ern-ki-vorschlag" <button class="btn btn-sm btn-secondary ern-ki-vorschlag text-xs"
data-q="${UI.escape(q)}" data-q="${UI.escape(q)}"
class="text-xs">${UI.escape(q)}</button> >${UI.escape(q)}</button>
`).join('')} `).join('')}
</div> </div>

View file

@ -254,13 +254,13 @@ window.Page_erste_hilfe = (() => {
</div> </div>
${KATEGORIEN.map(k => ` ${KATEGORIEN.map(k => `
<div class="eh-tab-panel" id="eh-panel-${k.id}" class="hidden"> <div class="eh-tab-panel hidden" id="eh-panel-${k.id}">
${k.eintraege.map((e, i) => _renderEintrag(e, k.id, i, k.color)).join('')} ${k.eintraege.map((e, i) => _renderEintrag(e, k.id, i, k.color)).join('')}
</div> </div>
`).join('')} `).join('')}
<div style="margin-top:var(--space-6);padding:var(--space-4);background:var(--c-surface-2);border-radius:var(--radius-md);font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.6"> <div style="margin-top:var(--space-6);padding:var(--space-4);background:var(--c-surface-2);border-radius:var(--radius-md);font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.6">
<svg class="ph-icon" aria-hidden="true" class="text-primary"><use href="/icons/phosphor.svg#info"></use></svg> <svg class="ph-icon text-primary" aria-hidden="true"><use href="/icons/phosphor.svg#info"></use></svg>
Diese Inhalte ersetzen keinen Tierarztbesuch. Im Zweifel immer sofort zum Tierarzt oder den tierärztlichen Notdienst anrufen. Diese Inhalte ersetzen keinen Tierarztbesuch. Im Zweifel immer sofort zum Tierarzt oder den tierärztlichen Notdienst anrufen.
</div> </div>
</div> </div>
@ -346,7 +346,7 @@ window.Page_erste_hilfe = (() => {
return ` return `
<div class="card" style="padding:0;overflow:hidden;margin-bottom:var(--space-4)"> <div class="card" style="padding:0;overflow:hidden;margin-bottom:var(--space-4)">
<div style="padding:var(--space-3) var(--space-4);background:var(--c-surface-2);font-weight:var(--weight-semibold);font-size:var(--text-sm);display:flex;align-items:center;gap:var(--space-2)"> <div style="padding:var(--space-3) var(--space-4);background:var(--c-surface-2);font-weight:var(--weight-semibold);font-size:var(--text-sm);display:flex;align-items:center;gap:var(--space-2)">
<svg class="ph-icon" aria-hidden="true" class="text-primary"><use href="/icons/phosphor.svg#list-bullets"></use></svg> <svg class="ph-icon text-primary" aria-hidden="true"><use href="/icons/phosphor.svg#list-bullets"></use></svg>
Schnellübersicht: Was tun bei Schnellübersicht: Was tun bei
</div> </div>
<div style="overflow-x:auto"> <div style="overflow-x:auto">
@ -433,7 +433,7 @@ window.Page_erste_hilfe = (() => {
btn.style.color = active ? '#fff' : kat.color; btn.style.color = active ? '#fff' : kat.color;
}); });
_container.querySelectorAll('.eh-tab-panel').forEach(panel => { _container.querySelectorAll('.eh-tab-panel').forEach(panel => {
panel.style.display = panel.id === `eh-panel-${id}` ? 'block' : 'none'; panel.classList.toggle('hidden', panel.id !== `eh-panel-${id}`); // .hidden hat !important
}); });
} }

View file

@ -102,7 +102,7 @@ window.Page_events = (() => {
</div> </div>
<div class="events-list" id="ev-list"></div> <div class="events-list" id="ev-list"></div>
<div class="events-map" id="ev-map" class="hidden"></div> <div class="events-map hidden" id="ev-map"></div>
`; `;
_container.addEventListener('click', _onClick); _container.addEventListener('click', _onClick);
@ -228,10 +228,10 @@ window.Page_events = (() => {
</div> </div>
<div style="display:flex;flex-direction:column;align-items:flex-end;gap:var(--space-1)"> <div style="display:flex;flex-direction:column;align-items:flex-end;gap:var(--space-1)">
${isOwn ? `<button class="btn-icon" data-ev-edit="${ev.id}" title="Bearbeiten">${_icon('pencil-simple')}</button>` : ''} ${isOwn ? `<button class="btn-icon" data-ev-edit="${ev.id}" title="Bearbeiten">${_icon('pencil-simple')}</button>` : ''}
${_state.user ? `<button class="btn-icon ev-note-btn" data-ev-note-id="${ev.id}" ${_state.user ? `<button class="btn-icon ev-note-btn text-muted" data-ev-note-id="${ev.id}"
data-ev-note-label="${UI.escape(ev.titel + ' ' + ev.datum)}" data-ev-note-label="${UI.escape(ev.titel + ' ' + ev.datum)}"
data-ev-note-ort="${UI.escape(ev.ort_name || '')}" data-ev-note-ort="${UI.escape(ev.ort_name || '')}"
title="Notiz" class="text-muted"> title="Notiz">
${_icon('note-pencil')}</button>` : ''} ${_icon('note-pencil')}</button>` : ''}
</div> </div>
</div> </div>
@ -507,7 +507,7 @@ window.Page_events = (() => {
const footer = ` const footer = `
<div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%"> <div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%">
<button class="btn btn-primary" type="submit" form="${id}" id="ev-submit-btn" class="w-full"> <button class="btn btn-primary w-full" type="submit" form="${id}" id="ev-submit-btn">
${isEdit ? 'Speichern' : 'Event erstellen'} ${isEdit ? 'Speichern' : 'Event erstellen'}
</button> </button>
<div class="flex-gap-2"> <div class="flex-gap-2">
@ -605,7 +605,7 @@ window.Page_events = (() => {
const mapEl = document.getElementById('ev-map'); const mapEl = document.getElementById('ev-map');
if (_view === 'karte') { if (_view === 'karte') {
listEl.style.display = 'none'; listEl.style.display = 'none';
mapEl.style.display = 'block'; mapEl.classList.remove('hidden'); // .hidden hat !important → nur classList
// Erst div sichtbar machen, dann Karte initialisieren // Erst div sichtbar machen, dann Karte initialisieren
_renderMap(_filtered()); _renderMap(_filtered());
} else { } else {
@ -616,7 +616,7 @@ window.Page_events = (() => {
_clusterGroup = null; _clusterGroup = null;
_markers = []; _markers = [];
} }
mapEl.style.display = 'none'; mapEl.classList.add('hidden');
listEl.style.display = ''; listEl.style.display = '';
_renderList(); _renderList();
} }

View file

@ -92,7 +92,7 @@ function _fmtDate(iso) {
<h2 class="forum-header-title">Forum</h2> <h2 class="forum-header-title">Forum</h2>
<div class="forum-header-actions"> <div class="forum-header-actions">
${isMod ? `<button class="btn btn-ghost btn-sm" id="forum-mod-btn" title="Moderationsberichte">${UI.icon('warning')}</button>` : ''} ${isMod ? `<button class="btn btn-ghost btn-sm" id="forum-mod-btn" title="Moderationsberichte">${UI.icon('warning')}</button>` : ''}
<button class="btn btn-ghost btn-sm" id="forum-rules-btn" title="Regeln & Netiquette" class="text-muted">${UI.icon('info')} Regeln</button> <button class="btn btn-ghost btn-sm text-muted" id="forum-rules-btn" title="Regeln & Netiquette">${UI.icon('info')} Regeln</button>
<button class="btn btn-primary btn-sm" id="forum-new-btn">${UI.icon('plus')} Neues Thema</button> <button class="btn btn-primary btn-sm" id="forum-new-btn">${UI.icon('plus')} Neues Thema</button>
</div> </div>
</div> </div>
@ -271,9 +271,9 @@ function _fmtDate(iso) {
: `<div class="hdm-section"> : `<div class="hdm-section">
<h3 class="hdm-section-title">Für welchen Hund möchtest du abstimmen?</h3> <h3 class="hdm-section-title">Für welchen Hund möchtest du abstimmen?</h3>
<div class="hdm-kandidaten-search"> <div class="hdm-kandidaten-search">
<input type="search" id="hdm-search" class="form-control" <input type="search" id="hdm-search" class="form-control text-sm"
placeholder="Name oder Rasse suchen …" autocomplete="off" placeholder="Name oder Rasse suchen …" autocomplete="off"
class="text-sm"> >
</div> </div>
<div id="hdm-kandidaten-grid" class="hdm-vote-grid"> <div id="hdm-kandidaten-grid" class="hdm-vote-grid">
${UI.skeleton(3)} ${UI.skeleton(3)}
@ -1293,7 +1293,7 @@ function _fmtDate(iso) {
<div class="text-xs-muted"> <div class="text-xs-muted">
von ${UI.escape(r.melder_name || '?')} · ${_fmtDate(r.created_at)} von ${UI.escape(r.melder_name || '?')} · ${_fmtDate(r.created_at)}
</div> </div>
<button class="btn btn-sm btn-secondary forum-resolve-btn" data-id="${r.id}" class="mt-2"> <button class="btn btn-sm btn-secondary forum-resolve-btn mt-2" data-id="${r.id}">
${UI.icon('check')} Erledigt ${UI.icon('check')} Erledigt
</button> </button>
</div>`).join('')} </div>`).join('')}

View file

@ -657,8 +657,8 @@ window.Page_friends = (() => {
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#chat-circle-dots"></use></svg> <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#chat-circle-dots"></use></svg>
Nachricht schreiben Nachricht schreiben
</button> </button>
<button class="btn btn-ghost" id="modal-remove-btn" form="" <button class="btn btn-ghost text-danger" id="modal-remove-btn" form=""
class="text-danger"> >
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#user-minus"></use></svg> <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#user-minus"></use></svg>
Entfernen Entfernen
</button> </button>

View file

@ -2919,8 +2919,8 @@ function _showPoiKorrekturModal(osmId, poiName, currentOh) {
<form id="poi-korrektur-form"> <form id="poi-korrektur-form">
<div class="form-group"> <div class="form-group">
<label class="form-label">Aktuelle Angabe</label> <label class="form-label">Aktuelle Angabe</label>
<input class="form-control" type="text" value="${UI.escape(currentOh)}" disabled <input class="form-control text-muted" type="text" value="${UI.escape(currentOh)}" disabled
class="text-muted"> >
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label">Korrekte Öffnungszeiten *</label> <label class="form-label">Korrekte Öffnungszeiten *</label>
@ -3009,7 +3009,8 @@ function _showPoiKorrekturModal(osmId, poiName, currentOh) {
} }
await UI.asyncButton(btn, async () => { await UI.asyncButton(btn, async () => {
resultEl.style.display = 'none'; // .hidden hat !important → nur per classList togglen (style.display verliert immer)
resultEl.classList.add('hidden');
resultEl.innerHTML = ''; resultEl.innerHTML = '';
let result; let result;
@ -3040,7 +3041,7 @@ function _showPoiKorrekturModal(osmId, poiName, currentOh) {
UI.toast.error(err.message || 'Fehler bei der KI-Anfrage.'); UI.toast.error(err.message || 'Fehler bei der KI-Anfrage.');
return; return;
} }
resultEl.style.display = ''; resultEl.classList.remove('hidden');
return; return;
} }
@ -3074,7 +3075,7 @@ function _showPoiKorrekturModal(osmId, poiName, currentOh) {
<strong>&#9888;&#65039; Dies ist keine medizinische Diagnose.</strong> <strong>&#9888;&#65039; Dies ist keine medizinische Diagnose.</strong>
Bei ernsthaften Symptomen sofort zum Tierarzt. Bei ernsthaften Symptomen sofort zum Tierarzt.
</div>`; </div>`;
resultEl.style.display = ''; resultEl.classList.remove('hidden');
// Submit-Button ausblenden wenn Limit erschöpft // Submit-Button ausblenden wenn Limit erschöpft
if (result.anfragen_heute >= result.limit) { if (result.anfragen_heute >= result.limit) {

View file

@ -191,9 +191,9 @@ window.Page_jobs = (() => {
<div class="form-group"> <div class="form-group">
<label class="form-label">Anhänge (optional)</label> <label class="form-label">Anhänge (optional)</label>
<input class="form-control" type="file" name="files" id="jobs-files" <input class="form-control p-2" type="file" name="files" id="jobs-files"
multiple accept=".pdf,.jpg,.jpeg,.png,.webp,.mp4,.mov" multiple accept=".pdf,.jpg,.jpeg,.png,.webp,.mp4,.mov"
class="p-2"> >
<p style="margin:var(--space-1) 0 0;font-size:var(--text-xs);color:var(--c-text-muted)"> <p style="margin:var(--space-1) 0 0;font-size:var(--text-xs);color:var(--c-text-muted)">
Beispiel-Posts, Portfolio, kurzes Video von dir und deinem Hund max. 3 Dateien, je 10 MB. Beispiel-Posts, Portfolio, kurzes Video von dir und deinem Hund max. 3 Dateien, je 10 MB.
PDF, Bild oder Video. PDF, Bild oder Video.

View file

@ -297,11 +297,11 @@ window.Page_knigge = (() => {
</h2> </h2>
<div class="card"> <div class="card">
<div style="padding:var(--space-5)"> <div style="padding:var(--space-5)">
<textarea id="ki-situation-input" class="form-control" <textarea id="ki-situation-input" class="form-control mb-3"
rows="3" rows="3"
placeholder="Beschreibe deine Situation…" placeholder="Beschreibe deine Situation…"
class="mb-3"></textarea> ></textarea>
<button class="btn btn-primary" id="ki-rat-btn" class="w-full"> <button class="btn btn-primary w-full" id="ki-rat-btn">
Rat holen ${UI.icon('robot')} Rat holen ${UI.icon('robot')}
</button> </button>
<div id="ki-rat-result" style="margin-top:var(--space-4);display:none"></div> <div id="ki-rat-result" style="margin-top:var(--space-4);display:none"></div>

View file

@ -285,8 +285,8 @@ window.Page_laeufi = (() => {
<button class="btn btn-ghost btn-xs laeufi-edit-btn" data-id="${l.id}" title="Bearbeiten"> <button class="btn btn-ghost btn-xs laeufi-edit-btn" data-id="${l.id}" title="Bearbeiten">
${UI.icon('pencil-simple')} ${UI.icon('pencil-simple')}
</button> </button>
<button class="btn btn-ghost btn-xs laeufi-delete-btn" data-id="${l.id}" <button class="btn btn-ghost btn-xs laeufi-delete-btn text-danger" data-id="${l.id}"
title="Löschen" class="text-danger"> title="Löschen">
${UI.icon('trash')} ${UI.icon('trash')}
</button> </button>
</div> </div>
@ -335,7 +335,7 @@ window.Page_laeufi = (() => {
</div> </div>
<div style="display:flex;gap:var(--space-1);flex-shrink:0"> <div style="display:flex;gap:var(--space-1);flex-shrink:0">
<button class="btn btn-ghost btn-xs deck-edit-btn" data-id="${d.id}" title="Bearbeiten">${UI.icon('pencil-simple')}</button> <button class="btn btn-ghost btn-xs deck-edit-btn" data-id="${d.id}" title="Bearbeiten">${UI.icon('pencil-simple')}</button>
<button class="btn btn-ghost btn-xs deck-delete-btn" data-id="${d.id}" title="Löschen" class="text-danger">${UI.icon('trash')}</button> <button class="btn btn-ghost btn-xs deck-delete-btn text-danger" data-id="${d.id}" title="Löschen">${UI.icon('trash')}</button>
</div> </div>
</div> </div>
<!-- Meilensteine --> <!-- Meilensteine -->
@ -542,8 +542,8 @@ window.Page_laeufi = (() => {
</td> </td>
<td style="padding:var(--space-2);color:var(--c-text-secondary)">${t.labor ? UI.escape(t.labor) : '—'}</td> <td style="padding:var(--space-2);color:var(--c-text-secondary)">${t.labor ? UI.escape(t.labor) : '—'}</td>
<td style="padding:var(--space-2);text-align:right"> <td style="padding:var(--space-2);text-align:right">
<button class="btn btn-ghost btn-xs prog-delete-btn" data-id="${t.id}" <button class="btn btn-ghost btn-xs prog-delete-btn text-danger" data-id="${t.id}"
class="text-danger">${UI.icon('trash')}</button> >${UI.icon('trash')}</button>
</td> </td>
</tr>`).join('')} </tr>`).join('')}
</tbody> </tbody>

View file

@ -385,8 +385,8 @@ window.Page_litters = (() => {
<button class="btn btn-ghost btn-sm litters-edit-btn" data-id="${l.id}" title="Bearbeiten"> <button class="btn btn-ghost btn-sm litters-edit-btn" data-id="${l.id}" title="Bearbeiten">
${UI.icon('pencil-simple')} ${UI.icon('pencil-simple')}
</button> </button>
<button class="btn btn-ghost btn-sm litters-delete-btn" data-id="${l.id}" title="Löschen" <button class="btn btn-ghost btn-sm litters-delete-btn text-danger" data-id="${l.id}" title="Löschen"
class="text-danger"> >
${UI.icon('trash')} ${UI.icon('trash')}
</button> </button>
</div> </div>
@ -399,8 +399,8 @@ window.Page_litters = (() => {
<div id="puppies-inner-${l.id}"> <div id="puppies-inner-${l.id}">
<p class="text-sm-muted">Lädt</p> <p class="text-sm-muted">Lädt</p>
</div> </div>
<button class="btn btn-secondary btn-sm litters-add-puppy-btn" data-id="${l.id}" <button class="btn btn-secondary btn-sm litters-add-puppy-btn mt-3" data-id="${l.id}"
class="mt-3"> >
${UI.icon('plus')} Welpen hinzufügen ${UI.icon('plus')} Welpen hinzufügen
</button> </button>
</div> </div>
@ -410,8 +410,8 @@ window.Page_litters = (() => {
<div id="waitlist-inner-${l.id}"> <div id="waitlist-inner-${l.id}">
<p class="text-sm-muted">Lädt</p> <p class="text-sm-muted">Lädt</p>
</div> </div>
<button class="btn btn-secondary btn-sm litters-add-waitlist-btn" data-id="${l.id}" <button class="btn btn-secondary btn-sm litters-add-waitlist-btn mt-3" data-id="${l.id}"
class="mt-3"> >
${UI.icon('plus')} Interessent eintragen ${UI.icon('plus')} Interessent eintragen
</button> </button>
</div> </div>
@ -468,7 +468,7 @@ window.Page_litters = (() => {
<span class="litters-puppy-name">${p.name ? UI.escape(p.name) : '<em class="text-muted">Unbenannt</em>'}</span> <span class="litters-puppy-name">${p.name ? UI.escape(p.name) : '<em class="text-muted">Unbenannt</em>'}</span>
${p.farbe ? `<span style="color:var(--c-text-secondary);font-size:var(--text-xs)">${UI.escape(p.farbe)}</span>` : ''} ${p.farbe ? `<span style="color:var(--c-text-secondary);font-size:var(--text-xs)">${UI.escape(p.farbe)}</span>` : ''}
${_puppyStatusBadge(p.status)} ${_puppyStatusBadge(p.status)}
<span class="litters-puppy-last-weight" id="puppy-last-weight-${p.id}" class="text-xs-secondary"></span> <span class="litters-puppy-last-weight text-xs-secondary" id="puppy-last-weight-${p.id}"></span>
</div> </div>
<div class="litters-puppy-actions"> <div class="litters-puppy-actions">
<button class="btn btn-ghost btn-xs litters-puppy-photo-btn" data-litter-id="${litterId}" data-puppy-id="${p.id}" <button class="btn btn-ghost btn-xs litters-puppy-photo-btn" data-litter-id="${litterId}" data-puppy-id="${p.id}"
@ -787,7 +787,7 @@ window.Page_litters = (() => {
</div> </div>
<div style="display:flex;gap:var(--space-1);flex-shrink:0"> <div style="display:flex;gap:var(--space-1);flex-shrink:0">
<button class="btn btn-ghost btn-xs wl-edit-btn" data-entry-id="${e.id}" title="Bearbeiten">${UI.icon('pencil-simple')}</button> <button class="btn btn-ghost btn-xs wl-edit-btn" data-entry-id="${e.id}" title="Bearbeiten">${UI.icon('pencil-simple')}</button>
<button class="btn btn-ghost btn-xs wl-delete-btn" data-entry-id="${e.id}" title="Entfernen" class="text-danger">${UI.icon('trash')}</button> <button class="btn btn-ghost btn-xs wl-delete-btn text-danger" data-entry-id="${e.id}" title="Entfernen">${UI.icon('trash')}</button>
</div> </div>
</div>`).join('')} </div>`).join('')}
</div>`; </div>`;
@ -918,7 +918,7 @@ window.Page_litters = (() => {
return `<option value="${h.id}" data-name="${UI.escape(h.name)}" ${currentId == h.id ? 'selected' : ''}>${UI.escape(label)}</option>`; return `<option value="${h.id}" data-name="${UI.escape(h.name)}" ${currentId == h.id ? 'selected' : ''}>${UI.escape(label)}</option>`;
}).join(''); }).join('');
return ` return `
<select class="form-control" name="${idName}" id="${idName}-sel" class="mb-2"> <select class="form-control mb-2" name="${idName}" id="${idName}-sel">
<option value=""> ${placeholder} </option> <option value=""> ${placeholder} </option>
${opts} ${opts}
</select> </select>
@ -1496,7 +1496,7 @@ window.Page_litters = (() => {
Trotzdem fortfahren Trotzdem fortfahren
</button> </button>
</div>` : ` </div>` : `
<button class="btn btn-primary" data-modal-close class="w-full"> <button class="btn btn-primary w-full" data-modal-close>
${UI.icon('check')} Verstanden ${UI.icon('check')} Verstanden
</button>`, </button>`,
}); });

View file

@ -190,10 +190,10 @@ window.Page_moderation = (() => {
` : `<div style="font-size:var(--text-xs);color:var(--c-warning); ` : `<div style="font-size:var(--text-xs);color:var(--c-warning);
margin-bottom:var(--space-3)">Noch kein Foto vorhanden</div>`} margin-bottom:var(--space-3)">Noch kein Foto vorhanden</div>`}
<div class="flex-gap-2"> <div class="flex-gap-2">
<button class="btn btn-sm btn-primary mod-foto-approve" <button class="btn btn-sm btn-primary mod-foto-approve flex-1"
data-id="${f.id}" class="flex-1"><svg class="ph-icon" style="width:14px;height:14px" aria-hidden="true"><use href="/icons/phosphor.svg#check"></use></svg> Freigeben</button> data-id="${f.id}"><svg class="ph-icon" style="width:14px;height:14px" aria-hidden="true"><use href="/icons/phosphor.svg#check"></use></svg> Freigeben</button>
<button class="btn btn-sm btn-ghost mod-foto-reject" <button class="btn btn-sm btn-ghost mod-foto-reject text-danger"
data-id="${f.id}" class="text-danger"><svg class="ph-icon" style="width:14px;height:14px" aria-hidden="true"><use href="/icons/phosphor.svg#x"></use></svg> Ablehnen</button> data-id="${f.id}"><svg class="ph-icon" style="width:14px;height:14px" aria-hidden="true"><use href="/icons/phosphor.svg#x"></use></svg> Ablehnen</button>
</div> </div>
</div> </div>
`).join('')} `).join('')}
@ -322,14 +322,14 @@ window.Page_moderation = (() => {
<div style="flex-shrink:0"> <div style="flex-shrink:0">
${canAction ${canAction
? (u.is_banned ? (u.is_banned
? `<button class="btn btn-sm btn-ghost mod-unban" ? `<button class="btn btn-sm btn-ghost mod-unban text-success"
data-uid="${u.id}" data-name="${UI.escape(u.name)}" data-uid="${u.id}" data-name="${UI.escape(u.name)}"
title="Sperre aufheben" class="text-success"> title="Sperre aufheben">
${UI.icon('lock-open')} ${UI.icon('lock-open')}
</button>` </button>`
: `<button class="btn btn-sm btn-ghost mod-ban" : `<button class="btn btn-sm btn-ghost mod-ban text-danger"
data-uid="${u.id}" data-name="${UI.escape(u.name)}" data-uid="${u.id}" data-name="${UI.escape(u.name)}"
title="Sperren" class="text-danger"> title="Sperren">
${UI.icon('lock')} ${UI.icon('lock')}
</button>`) </button>`)
: '' : ''

View file

@ -163,11 +163,11 @@ window.Page_onboarding = (() => {
<!-- Buttons --> <!-- Buttons -->
<div class="flex-col-gap-3"> <div class="flex-col-gap-3">
<button class="btn btn-primary" id="ob-next-btn" class="w-full"> <button class="btn btn-primary w-full" id="ob-next-btn">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#arrow-right"></use></svg> <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#arrow-right"></use></svg>
Los geht's Los geht's
</button> </button>
<button class="btn btn-ghost" id="ob-skip-btn" class="w-full"> <button class="btn btn-ghost w-full" id="ob-skip-btn">
Überspringen Überspringen
</button> </button>
</div> </div>
@ -255,14 +255,14 @@ window.Page_onboarding = (() => {
<button class="btn btn-secondary" id="ob-back-btn"> <button class="btn btn-secondary" id="ob-back-btn">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#arrow-left"></use></svg> <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#arrow-left"></use></svg>
</button> </button>
<button type="submit" form="ob-dog-form" class="btn btn-primary" id="ob-save-btn" <button type="submit" form="ob-dog-form" class="btn btn-primary flex-1" id="ob-save-btn"
class="flex-1"> >
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#dog"></use></svg> <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#dog"></use></svg>
Hund anlegen Hund anlegen
</button> </button>
</div> </div>
<div style="text-align:center;margin-top:var(--space-3)"> <div style="text-align:center;margin-top:var(--space-3)">
<button class="btn btn-ghost" id="ob-skip-btn" class="text-sm"> <button class="btn btn-ghost text-sm" id="ob-skip-btn">
Ohne Hund fortfahren Ohne Hund fortfahren
</button> </button>
</div> </div>
@ -317,12 +317,12 @@ window.Page_onboarding = (() => {
<!-- CTA --> <!-- CTA -->
<div class="flex-col-gap-3"> <div class="flex-col-gap-3">
<button class="btn btn-primary" id="ob-diary-btn" class="w-full"> <button class="btn btn-primary w-full" id="ob-diary-btn">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#book-open"></use></svg> <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#book-open"></use></svg>
Zum Tagebuch Zum Tagebuch
</button> </button>
${dogName ? ` ${dogName ? `
<button class="btn btn-secondary" id="ob-profile-btn" class="w-full"> <button class="btn btn-secondary w-full" id="ob-profile-btn">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#dog"></use></svg> <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#dog"></use></svg>
Profil vervollständigen Profil vervollständigen
</button> </button>

View file

@ -81,7 +81,7 @@ function _fmtDate(iso) {
<div class="playdate-layout"> <div class="playdate-layout">
<!-- Tabs --> <!-- Tabs -->
<div class="by-tabs" id="playdate-tabs" class="mb-4"> <div class="by-tabs mb-4" id="playdate-tabs">
<button class="by-tab active" data-tab="nearby">In der Nähe</button> <button class="by-tab active" data-tab="nearby">In der Nähe</button>
<button class="by-tab" data-tab="listings">Meine Inserate</button> <button class="by-tab" data-tab="listings">Meine Inserate</button>
<button class="by-tab" data-tab="requests"> <button class="by-tab" data-tab="requests">

View file

@ -69,7 +69,7 @@ window.Page_poison = (() => {
<a href="tel:110" class="btn btn-secondary" style="flex:1;text-align:center;text-decoration:none"> <a href="tel:110" class="btn btn-secondary" style="flex:1;text-align:center;text-decoration:none">
${UI.icon('phone')} <strong>110</strong> Polizei ${UI.icon('phone')} <strong>110</strong> Polizei
</a> </a>
<button class="btn btn-secondary" id="poison-btn-erstehilfe" class="flex-1"> <button class="btn btn-secondary flex-1" id="poison-btn-erstehilfe">
${UI.icon('first-aid')} Erste Hilfe & Tiergift ${UI.icon('first-aid')} Erste Hilfe & Tiergift
</button> </button>
</div> </div>
@ -215,7 +215,7 @@ window.Page_poison = (() => {
${r.beschreibung ? UI.escape(r.beschreibung.slice(0, 80)) + '<br>' : ''} ${r.beschreibung ? UI.escape(r.beschreibung.slice(0, 80)) + '<br>' : ''}
<small>📍 ${distStr} entfernt</small><br> <small>📍 ${distStr} entfernt</small><br>
<small>📅 ${_fmtDate(r.created_at)}</small> <small>📅 ${_fmtDate(r.created_at)}</small>
${r.bestaetigt ? '<br><small><svg class="ph-icon" aria-hidden="true" class="text-success"><use href="/icons/phosphor.svg#check-circle"></use></svg> Bestätigt</small>' : ''} ${r.bestaetigt ? '<br><small><svg class="ph-icon text-success" aria-hidden="true"><use href="/icons/phosphor.svg#check-circle"></use></svg> Bestätigt</small>' : ''}
`); `);
marker.on('click', () => _openDetail(r)); marker.on('click', () => _openDetail(r));
@ -276,7 +276,7 @@ window.Page_poison = (() => {
<span class="badge" <span class="badge"
style="background:${typ.color};color:#fff">${typ.label}</span> style="background:${typ.color};color:#fff">${typ.label}</span>
${r.bestaetigt ${r.bestaetigt
? '<span class="badge badge-success"><svg class="ph-icon" aria-hidden="true" class="text-success"><use href="/icons/phosphor.svg#check-circle"></use></svg> Bestätigt</span>' ? '<span class="badge badge-success"><svg class="ph-icon text-success" aria-hidden="true"><use href="/icons/phosphor.svg#check-circle"></use></svg> Bestätigt</span>'
: ''} : ''}
<span style="margin-left:auto;color:var(--c-text-secondary); <span style="margin-left:auto;color:var(--c-text-secondary);
font-size:var(--text-sm);white-space:nowrap"> font-size:var(--text-sm);white-space:nowrap">
@ -330,7 +330,7 @@ window.Page_poison = (() => {
<span class="badge" style="background:${typ.color};color:#fff"> <span class="badge" style="background:${typ.color};color:#fff">
${UI.icon(typ.icon)} ${typ.label} ${UI.icon(typ.icon)} ${typ.label}
</span> </span>
${r.bestaetigt ? '<span class="badge badge-success"><svg class="ph-icon" aria-hidden="true" class="text-success"><use href="/icons/phosphor.svg#check-circle"></use></svg> Bestätigt</span>' : ''} ${r.bestaetigt ? '<span class="badge badge-success"><svg class="ph-icon text-success" aria-hidden="true"><use href="/icons/phosphor.svg#check-circle"></use></svg> Bestätigt</span>' : ''}
</div> </div>
${r.beschreibung ${r.beschreibung
@ -347,7 +347,7 @@ window.Page_poison = (() => {
<div style="display:flex;gap:var(--space-2);flex-wrap:wrap"> <div style="display:flex;gap:var(--space-2);flex-wrap:wrap">
${!r.bestaetigt && _appState.user && !isOwnEntry ${!r.bestaetigt && _appState.user && !isOwnEntry
? `<button class="btn btn-secondary flex-1" id="detail-confirm"><svg class="ph-icon" aria-hidden="true" class="text-success"><use href="/icons/phosphor.svg#check-circle"></use></svg> Bestätigen</button>` ? `<button class="btn btn-secondary flex-1" id="detail-confirm"><svg class="ph-icon text-success" aria-hidden="true"><use href="/icons/phosphor.svg#check-circle"></use></svg> Bestätigen</button>`
: ''} : ''}
<button class="btn btn-secondary flex-1" id="detail-show-map">🗺 Auf Karte</button> <button class="btn btn-secondary flex-1" id="detail-show-map">🗺 Auf Karte</button>
${isOwnEntry || isAdmin ${isOwnEntry || isAdmin

View file

@ -352,13 +352,13 @@ window.Page_routes = (() => {
<button class="rk-chip" data-filter="sort" data-val="dog">Hundefreundlich</button> <button class="rk-chip" data-filter="sort" data-val="dog">Hundefreundlich</button>
</div> </div>
</div> </div>
<div class="rk-filter-group" id="rk-mine-group" class="hidden"> <div class="rk-filter-group hidden" id="rk-mine-group">
<div class="rk-filter-label">Eigene</div> <div class="rk-filter-label">Eigene</div>
<div class="rk-chips-row"> <div class="rk-chips-row">
<button class="rk-chip" data-filter="mine" data-val="mine">🔒 Nur meine</button> <button class="rk-chip" data-filter="mine" data-val="mine">🔒 Nur meine</button>
</div> </div>
</div> </div>
<div class="rk-filter-group" id="rk-nearby-group" class="hidden"> <div class="rk-filter-group hidden" id="rk-nearby-group">
<div class="rk-filter-label">Umgebung</div> <div class="rk-filter-label">Umgebung</div>
<div class="rk-chips-row"> <div class="rk-chips-row">
<button class="rk-chip" id="rk-nearby-btn" data-filter="nearby" data-val="">${UI.icon('map-pin')} In meiner Nähe</button> <button class="rk-chip" id="rk-nearby-btn" data-filter="nearby" data-val="">${UI.icon('map-pin')} In meiner Nähe</button>
@ -430,7 +430,7 @@ window.Page_routes = (() => {
const badge = document.getElementById('rk-filter-badge'); const badge = document.getElementById('rk-filter-badge');
if (!badge) return; if (!badge) return;
const hasFilter = _difficulty !== '' || _terrain !== '' || _sortBy !== 'newest' || _onlyMine; const hasFilter = _difficulty !== '' || _terrain !== '' || _sortBy !== 'newest' || _onlyMine;
badge.style.display = hasFilter ? '' : 'none'; badge.classList.toggle('hidden', !hasFilter); // .hidden hat !important → nur classList
} }
function _setBrowseMode(mode) { function _setBrowseMode(mode) {
@ -450,8 +450,8 @@ window.Page_routes = (() => {
if (mode === 'suggest') { if (mode === 'suggest') {
if (recBtn) recBtn.style.display = 'none'; if (recBtn) recBtn.style.display = 'none';
if (impWrap) impWrap.style.display = 'none'; if (impWrap) impWrap.style.display = 'none';
if (mineGrp) mineGrp.style.display = 'none'; if (mineGrp) mineGrp.classList.add('hidden');
if (nearbyGrp) nearbyGrp.style.display = 'none'; if (nearbyGrp) nearbyGrp.classList.add('hidden');
if (searchRow) searchRow.style.display = 'none'; if (searchRow) searchRow.style.display = 'none';
if (actRow) actRow.style.display = 'none'; if (actRow) actRow.style.display = 'none';
const filterPanel = document.getElementById('rk-filter-panel'); const filterPanel = document.getElementById('rk-filter-panel');
@ -474,13 +474,13 @@ window.Page_routes = (() => {
if (mode === 'discover') { if (mode === 'discover') {
if (recBtn) recBtn.style.display = 'none'; if (recBtn) recBtn.style.display = 'none';
if (impWrap) impWrap.style.display = 'none'; if (impWrap) impWrap.style.display = 'none';
if (mineGrp) mineGrp.style.display = 'none'; if (mineGrp) mineGrp.classList.add('hidden');
if (nearbyGrp && _userPos) nearbyGrp.style.display = ''; if (nearbyGrp && _userPos) nearbyGrp.classList.remove('hidden');
} else { } else {
if (recBtn) recBtn.style.display = ''; if (recBtn) recBtn.style.display = '';
if (impWrap) impWrap.style.display = ''; if (impWrap) impWrap.style.display = '';
if (_appState.user && mineGrp) mineGrp.style.display = ''; if (_appState.user && mineGrp) mineGrp.classList.remove('hidden');
if (nearbyGrp) nearbyGrp.style.display = 'none'; if (nearbyGrp) nearbyGrp.classList.add('hidden');
} }
_onlyMine = false; _onlyMine = false;
document.querySelectorAll('#rk-mine-group .rk-chip').forEach(c => c.classList.remove('active')); document.querySelectorAll('#rk-mine-group .rk-chip').forEach(c => c.classList.remove('active'));
@ -1439,9 +1439,9 @@ window.Page_routes = (() => {
const pending = _getPending(); const pending = _getPending();
if (pending.length) _data = [...pending, ..._data]; if (pending.length) _data = [...pending, ..._data];
if (_appState.user && _browseMode === 'mine') if (_appState.user && _browseMode === 'mine')
document.getElementById('rk-mine-group')?.style.setProperty('display', ''); document.getElementById('rk-mine-group')?.classList.remove('hidden');
if (_browseMode === 'discover' && _userPos) if (_browseMode === 'discover' && _userPos)
document.getElementById('rk-nearby-group')?.style.setProperty('display', ''); document.getElementById('rk-nearby-group')?.classList.remove('hidden');
if (!online && pending.length) if (!online && pending.length)
UI.toast.info('Offline — ' + pending.length + ' Route(n) warten auf Sync.'); UI.toast.info('Offline — ' + pending.length + ' Route(n) warten auf Sync.');
_applyFilter(); _applyFilter();

View file

@ -652,7 +652,7 @@ window.Page_settings = (() => {
</div> </div>
</div> </div>
<div class="card" id="settings-stats-card" class="mb-4"> <div class="card mb-4" id="settings-stats-card">
<div class="by-card-section-header">Aktivität</div> <div class="by-card-section-header">Aktivität</div>
<div id="settings-stats-body" style="padding:var(--space-4);display:flex;justify-content:space-around"> <div id="settings-stats-body" style="padding:var(--space-4);display:flex;justify-content:space-around">
<div class="text-sm-muted">Lädt</div> <div class="text-sm-muted">Lädt</div>
@ -1721,15 +1721,15 @@ window.Page_settings = (() => {
${profile?.rasse_text ? `<div class="text-secondary">Rasse: <strong>${UI.escape(profile.rasse_text)}</strong></div>` : ''} ${profile?.rasse_text ? `<div class="text-secondary">Rasse: <strong>${UI.escape(profile.rasse_text)}</strong></div>` : ''}
</div> </div>
${rolle === 'breeder' && profile ? ` ${rolle === 'breeder' && profile ? `
<button class="btn btn-secondary btn-sm" id="breeder-edit-profile-btn" class="mt-3"> <button class="btn btn-secondary btn-sm mt-3" id="breeder-edit-profile-btn">
${UI.icon('pencil-simple')} Profil bearbeiten ${UI.icon('pencil-simple')} Profil bearbeiten
</button>` : ''} </button>` : ''}
${rolle === 'admin' && !profile ? ` ${rolle === 'admin' && !profile ? `
<button class="btn btn-primary btn-sm" id="breeder-admin-create-btn" class="mt-3"> <button class="btn btn-primary btn-sm mt-3" id="breeder-admin-create-btn">
${UI.icon('plus')} Admin-Züchterprofil anlegen ${UI.icon('plus')} Admin-Züchterprofil anlegen
</button>` : ''} </button>` : ''}
${rolle === 'admin' && profile ? ` ${rolle === 'admin' && profile ? `
<button class="btn btn-secondary btn-sm" id="breeder-edit-profile-btn" class="mt-3"> <button class="btn btn-secondary btn-sm mt-3" id="breeder-edit-profile-btn">
${UI.icon('pencil-simple')} Profil bearbeiten ${UI.icon('pencil-simple')} Profil bearbeiten
</button>` : ''} </button>` : ''}
${profile ? ` ${profile ? `
@ -1991,8 +1991,8 @@ window.Page_settings = (() => {
`, `,
footer: ` footer: `
<div class="w3-btn-stack"> <div class="w3-btn-stack">
<button type="submit" form="breeder-apply-form" class="btn btn-primary" id="breeder-apply-submit" <button type="submit" form="breeder-apply-form" class="btn btn-primary w-full" id="breeder-apply-submit"
class="w-full">Antrag einreichen</button> >Antrag einreichen</button>
<button type="button" class="btn btn-secondary" data-modal-close>Abbrechen</button> <button type="button" class="btn btn-secondary" data-modal-close>Abbrechen</button>
</div> </div>
`, `,

View file

@ -406,7 +406,7 @@ window.Page_sitting = (() => {
`; `;
const footer = ` const footer = `
<div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%"> <div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%">
<button class="btn btn-primary" type="submit" form="${id}" id="sit-profil-submit" class="w-full"> <button class="btn btn-primary w-full" type="submit" form="${id}" id="sit-profil-submit">
${s ? `${UI.icon('floppy-disk')} Speichern` : `${UI.icon('plus')} Profil erstellen`} ${s ? `${UI.icon('floppy-disk')} Speichern` : `${UI.icon('plus')} Profil erstellen`}
</button> </button>
<button class="btn btn-secondary" data-modal-close>Abbrechen</button> <button class="btn btn-secondary" data-modal-close>Abbrechen</button>

View file

@ -202,9 +202,10 @@ window.Page_walks = (() => {
_tab = tab; _tab = tab;
document.querySelectorAll('.by-tab').forEach(b => document.querySelectorAll('.by-tab').forEach(b =>
b.classList.toggle('active', b.dataset.tab === tab)); b.classList.toggle('active', b.dataset.tab === tab));
document.querySelectorAll('.walks-tab-panel').forEach(p => p.style.display = 'none'); // .hidden hat !important → nur per classList togglen (style.display verliert immer)
document.querySelectorAll('.walks-tab-panel').forEach(p => p.classList.add('hidden'));
const panel = document.getElementById(`walks-tab-${tab}`); const panel = document.getElementById(`walks-tab-${tab}`);
if (panel) panel.style.display = ''; if (panel) panel.classList.remove('hidden');
if (tab === 'challenge' && !_challengeData) _loadChallenge(); if (tab === 'challenge' && !_challengeData) _loadChallenge();
if (tab === 'stamm' && !_gassiZeiten.length) _loadGassiZeiten(); if (tab === 'stamm' && !_gassiZeiten.length) _loadGassiZeiten();
@ -1058,7 +1059,7 @@ window.Page_walks = (() => {
${UI.icon('calendar')} ${_fmtDate(challenge.start_date)} ${_fmtDate(challenge.end_date)} ${UI.icon('calendar')} ${_fmtDate(challenge.start_date)} ${_fmtDate(challenge.end_date)}
&nbsp;·&nbsp; ${UI.icon('timer')} Noch ${dayLabel} &nbsp;·&nbsp; ${UI.icon('timer')} Noch ${dayLabel}
</div> </div>
${canSubmit ? `<button class="btn btn-primary btn-sm" id="challenge-submit-btn" class="mt-3">${UI.icon('camera')} Foto einreichen</button>` : ''} ${canSubmit ? `<button class="btn btn-primary btn-sm mt-3" id="challenge-submit-btn">${UI.icon('camera')} Foto einreichen</button>` : ''}
${my_submission_id ? `<span class="badge badge-success mt-2">${UI.icon('check')} Du hast bereits teilgenommen</span>` : ''} ${my_submission_id ? `<span class="badge badge-success mt-2">${UI.icon('check')} Du hast bereits teilgenommen</span>` : ''}
</div> </div>
</div> </div>

View file

@ -1098,7 +1098,7 @@ window.Page_welcome = (() => {
style="flex:1;background:var(--c-primary);color:#fff;border:none"> style="flex:1;background:var(--c-primary);color:#fff;border:none">
Android Android
</button> </button>
<button class="btn btn-sm btn-ghost" id="inst-tab-ios" class="flex-1"> <button class="btn btn-sm btn-ghost flex-1" id="inst-tab-ios">
iPhone / iPad iPhone / iPad
</button> </button>
</div> </div>
@ -1174,15 +1174,15 @@ window.Page_welcome = (() => {
// Desktop-Tabs Android / iOS // Desktop-Tabs Android / iOS
_container.querySelector('#inst-tab-android')?.addEventListener('click', () => { _container.querySelector('#inst-tab-android')?.addEventListener('click', () => {
_container.querySelector('#inst-panel-android').style.display = ''; _container.querySelector('#inst-panel-android').classList.remove('hidden');
_container.querySelector('#inst-panel-ios').style.display = 'none'; _container.querySelector('#inst-panel-ios').classList.add('hidden');
_container.querySelector('#inst-tab-android').style.cssText += ';background:var(--c-primary);color:#fff;border:none'; _container.querySelector('#inst-tab-android').style.cssText += ';background:var(--c-primary);color:#fff;border:none';
_container.querySelector('#inst-tab-ios').className = 'btn btn-sm btn-ghost'; _container.querySelector('#inst-tab-ios').className = 'btn btn-sm btn-ghost';
_container.querySelector('#inst-tab-ios').style.cssText = 'flex:1'; _container.querySelector('#inst-tab-ios').style.cssText = 'flex:1';
}); });
_container.querySelector('#inst-tab-ios')?.addEventListener('click', () => { _container.querySelector('#inst-tab-ios')?.addEventListener('click', () => {
_container.querySelector('#inst-panel-android').style.display = 'none'; _container.querySelector('#inst-panel-android').classList.add('hidden');
_container.querySelector('#inst-panel-ios').style.display = ''; _container.querySelector('#inst-panel-ios').classList.remove('hidden');
_container.querySelector('#inst-tab-ios').style.cssText += ';background:var(--c-primary);color:#fff;border:none'; _container.querySelector('#inst-tab-ios').style.cssText += ';background:var(--c-primary);color:#fff;border:none';
_container.querySelector('#inst-tab-android').className = 'btn btn-sm btn-ghost'; _container.querySelector('#inst-tab-android').className = 'btn btn-sm btn-ghost';
_container.querySelector('#inst-tab-android').style.cssText = 'flex:1'; _container.querySelector('#inst-tab-android').style.cssText = 'flex:1';

View file

@ -156,7 +156,7 @@ window.Page_wiki = (() => {
// Badge updaten // Badge updaten
const badge = document.getElementById('wiki-fotos-badge'); const badge = document.getElementById('wiki-fotos-badge');
if (badge) { badge.textContent = subs.length; badge.style.display = subs.length ? '' : 'none'; } if (badge) { badge.textContent = subs.length; badge.classList.toggle('hidden', !subs.length); }
if (!subs.length) { if (!subs.length) {
el.innerHTML = ` el.innerHTML = `
@ -216,7 +216,7 @@ window.Page_wiki = (() => {
const badge = document.getElementById('wiki-fotos-badge'); const badge = document.getElementById('wiki-fotos-badge');
if (badge) { if (badge) {
const n = Math.max(0, parseInt(badge.textContent || '0') - 1); const n = Math.max(0, parseInt(badge.textContent || '0') - 1);
badge.textContent = n; badge.style.display = n ? '' : 'none'; badge.textContent = n; badge.classList.toggle('hidden', !n);
} }
} catch (e) { UI.toast(e.message, 'danger'); } } catch (e) { UI.toast(e.message, 'danger'); }
} }
@ -269,8 +269,8 @@ window.Page_wiki = (() => {
</select> </select>
</div> </div>
<div style="padding:0 0 var(--space-3)"> <div style="padding:0 0 var(--space-3)">
<button class="btn btn-secondary w-full" id="wiki-rasse-erkennen-btn" <button class="btn btn-secondary w-full text-sm" id="wiki-rasse-erkennen-btn"
class="text-sm"> >
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#magnifying-glass"></use></svg> <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#magnifying-glass"></use></svg>
Welche Rasse ist das? Foto analysieren Welche Rasse ist das? Foto analysieren
</button> </button>
@ -669,7 +669,7 @@ window.Page_wiki = (() => {
</div> </div>
</form> </form>
</div> </div>
<button class="btn btn-secondary btn-sm" id="wiki-zuchter-add-btn" class="mt-3"> <button class="btn btn-secondary btn-sm mt-3" id="wiki-zuchter-add-btn">
+ Züchter eintragen + Züchter eintragen
</button> </button>
` : ''; ` : '';
@ -835,14 +835,14 @@ window.Page_wiki = (() => {
${berichteHtml} ${berichteHtml}
</div> </div>
${_appState.user ${_appState.user
? `<button class="btn btn-secondary w-full" id="wiki-bericht-add-btn" class="mt-3">+ Eigenen Bericht hinzufügen</button>` ? `<button class="btn btn-secondary w-full mt-3" id="wiki-bericht-add-btn">+ Eigenen Bericht hinzufügen</button>`
: `<p style="color:var(--c-text-secondary);font-size:var(--text-sm);margin-top:var(--space-3)"> : `<p style="color:var(--c-text-secondary);font-size:var(--text-sm);margin-top:var(--space-3)">
<a href="#settings" class="text-primary">Anmelden</a>, um einen Bericht zu schreiben. <a href="#settings" class="text-primary">Anmelden</a>, um einen Bericht zu schreiben.
</p>` </p>`
} }
${_appState.user ? ` ${_appState.user ? `
<div style="margin-top:var(--space-4);padding-top:var(--space-4);border-top:1px solid var(--c-border-light)"> <div style="margin-top:var(--space-4);padding-top:var(--space-4);border-top:1px solid var(--c-border-light)">
<button class="btn btn-ghost w-full" id="wiki-foto-submit-btn" class="text-sm"> <button class="btn btn-ghost w-full text-sm" id="wiki-foto-submit-btn">
${UI.icon('camera')} ${rasse.foto_url ? 'Besseres Foto vorschlagen' : 'Foto hinzufügen'} ${UI.icon('camera')} ${rasse.foto_url ? 'Besseres Foto vorschlagen' : 'Foto hinzufügen'}
</button> </button>
</div>` : ''}`} </div>` : ''}`}
@ -878,7 +878,8 @@ window.Page_wiki = (() => {
const idx = parseInt(btn.dataset.idx); const idx = parseInt(btn.dataset.idx);
mainImg.src = allFotos[idx].foto_url; mainImg.src = allFotos[idx].foto_url;
mainImg.style.display = ''; mainImg.style.display = '';
document.getElementById('wiki-photo-fallback').style.display = 'none'; const fb = document.getElementById('wiki-photo-fallback');
fb.classList.add('hidden'); fb.style.display = '';
strip.querySelectorAll('.wiki-gallery-thumb').forEach(b => b.classList.toggle('active', b === btn)); strip.querySelectorAll('.wiki-gallery-thumb').forEach(b => b.classList.toggle('active', b === btn));
}); });
}); });
@ -1255,7 +1256,7 @@ window.Page_wiki = (() => {
<span>${UI.icon('house-line')} ${r.wohnung_geeignet ? 'Wohnung' : 'Haus'}</span> <span>${UI.icon('house-line')} ${r.wohnung_geeignet ? 'Wohnung' : 'Haus'}</span>
<span>${UI.icon('users')} ${r.kinder_geeignet ? 'Kinderfreundlich' : 'Erfahrung nötig'}</span> <span>${UI.icon('users')} ${r.kinder_geeignet ? 'Kinderfreundlich' : 'Erfahrung nötig'}</span>
</div> </div>
<button class="btn btn-secondary btn-sm wiki-quiz-mehr" data-slug="${UI.escape(r.slug)}" class="mt-2">Mehr erfahren</button> <button class="btn btn-secondary btn-sm wiki-quiz-mehr mt-2" data-slug="${UI.escape(r.slug)}">Mehr erfahren</button>
</div> </div>
</div> </div>
`; `;
@ -1268,7 +1269,7 @@ window.Page_wiki = (() => {
</div> </div>
<h3 style="margin:var(--space-4) 0 var(--space-2);text-align:center">Deine Top 3 Rassen</h3> <h3 style="margin:var(--space-4) 0 var(--space-2);text-align:center">Deine Top 3 Rassen</h3>
<div class="wiki-quiz-results">${cardsHtml}</div> <div class="wiki-quiz-results">${cardsHtml}</div>
<button class="btn btn-secondary w-full" id="quiz-restart" class="mt-4">Quiz neu starten</button> <button class="btn btn-secondary w-full mt-4" id="quiz-restart">Quiz neu starten</button>
</div> </div>
`; `;

View file

@ -341,8 +341,8 @@ window.Page_zuchthunde = (() => {
title="Bearbeiten"> title="Bearbeiten">
${UI.icon('pencil-simple')} ${UI.icon('pencil-simple')}
</button> </button>
<button class="btn btn-ghost btn-sm zh-delete-btn" data-id="${h.id}" <button class="btn btn-ghost btn-sm zh-delete-btn text-danger" data-id="${h.id}"
title="Löschen" class="text-danger"> title="Löschen">
${UI.icon('trash')} ${UI.icon('trash')}
</button> </button>
</div> </div>
@ -360,7 +360,7 @@ window.Page_zuchthunde = (() => {
</button> </button>
</div> </div>
<div class="zh-section-wrap" id="zh-section-${h.id}" class="hidden"></div> <div class="zh-section-wrap hidden" id="zh-section-${h.id}"></div>
</div>`; </div>`;
} }
@ -379,7 +379,7 @@ window.Page_zuchthunde = (() => {
function _closeSection(hundId) { function _closeSection(hundId) {
const wrap = document.getElementById(`zh-section-${hundId}`); const wrap = document.getElementById(`zh-section-${hundId}`);
if (wrap) wrap.style.display = 'none'; if (wrap) wrap.classList.add('hidden'); // .hidden hat !important → nur classList
_openSections[hundId] = null; _openSections[hundId] = null;
_updateSectionButtons(hundId, null); _updateSectionButtons(hundId, null);
} }
@ -389,7 +389,7 @@ window.Page_zuchthunde = (() => {
_updateSectionButtons(hundId, section); _updateSectionButtons(hundId, section);
const wrap = document.getElementById(`zh-section-${hundId}`); const wrap = document.getElementById(`zh-section-${hundId}`);
if (!wrap) return; if (!wrap) return;
wrap.style.display = ''; wrap.classList.remove('hidden');
wrap.innerHTML = `<p style="color:var(--c-text-muted);font-size:var(--text-sm);padding:var(--space-2)">Lädt…</p>`; wrap.innerHTML = `<p style="color:var(--c-text-muted);font-size:var(--text-sm);padding:var(--space-2)">Lädt…</p>`;
if (section === 'health') _loadHealthSection(hundId, wrap); if (section === 'health') _loadHealthSection(hundId, wrap);
if (section === 'genetic') _loadGeneticSection(hundId, wrap); if (section === 'genetic') _loadGeneticSection(hundId, wrap);
@ -427,8 +427,8 @@ window.Page_zuchthunde = (() => {
${t.untersuch_am ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${_fmtDate(t.untersuch_am)}</span>` : ''} ${t.untersuch_am ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${_fmtDate(t.untersuch_am)}</span>` : ''}
${t.labor ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${UI.escape(t.labor)}</span>` : ''} ${t.labor ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${UI.escape(t.labor)}</span>` : ''}
</div> </div>
<button class="btn btn-ghost btn-xs zh-health-del-btn" data-tid="${t.id}" title="Löschen" <button class="btn btn-ghost btn-xs zh-health-del-btn text-danger" data-tid="${t.id}" title="Löschen"
class="text-danger">${UI.icon('trash')}</button> >${UI.icon('trash')}</button>
</div>`).join('') </div>`).join('')
: `<p class="text-sm-muted">Noch keine Gesundheitstests eingetragen.</p>`; : `<p class="text-sm-muted">Noch keine Gesundheitstests eingetragen.</p>`;
@ -490,8 +490,8 @@ window.Page_zuchthunde = (() => {
${t.getestet_am ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${_fmtDate(t.getestet_am)}</span>` : ''} ${t.getestet_am ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${_fmtDate(t.getestet_am)}</span>` : ''}
${t.labor ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${UI.escape(t.labor)}</span>` : ''} ${t.labor ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${UI.escape(t.labor)}</span>` : ''}
</div> </div>
<button class="btn btn-ghost btn-xs zh-genetic-del-btn" data-tid="${t.id}" title="Löschen" <button class="btn btn-ghost btn-xs zh-genetic-del-btn text-danger" data-tid="${t.id}" title="Löschen"
class="text-danger">${UI.icon('trash')}</button> >${UI.icon('trash')}</button>
</div>`).join('') </div>`).join('')
: `<p class="text-sm-muted">Noch keine Gentests eingetragen.</p>`; : `<p class="text-sm-muted">Noch keine Gentests eingetragen.</p>`;
@ -555,8 +555,8 @@ window.Page_zuchthunde = (() => {
${t.richter ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${UI.escape(t.richter)}</span>` : ''} ${t.richter ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${UI.escape(t.richter)}</span>` : ''}
${t.formwert ? `<span class="zh-badge" style="background:#3B82F6">${UI.escape(t.formwert)}</span>` : ''} ${t.formwert ? `<span class="zh-badge" style="background:#3B82F6">${UI.escape(t.formwert)}</span>` : ''}
</div> </div>
<button class="btn btn-ghost btn-xs zh-title-del-btn" data-tid="${t.id}" title="Löschen" <button class="btn btn-ghost btn-xs zh-title-del-btn text-danger" data-tid="${t.id}" title="Löschen"
class="text-danger">${UI.icon('trash')}</button> >${UI.icon('trash')}</button>
</div>`).join('') </div>`).join('')
: `<p class="text-sm-muted">Noch keine Titel eingetragen.</p>`; : `<p class="text-sm-muted">Noch keine Titel eingetragen.</p>`;

View file

@ -4,9 +4,9 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark"> <meta name="color-scheme" content="light dark">
<script src="/js/landing-init.js?v=1250"></script> <script src="/js/landing-init.js?v=1252"></script>
<title>Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz</title> <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="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, direkt im Browser.">
<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"> <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">
<meta name="robots" content="index, follow"> <meta name="robots" content="index, follow">
<link rel="canonical" href="https://banyaro.app/"> <link rel="canonical" href="https://banyaro.app/">
@ -14,7 +14,7 @@
<!-- Open Graph --> <!-- Open Graph -->
<meta property="og:type" content="website"> <meta property="og:type" content="website">
<meta property="og:title" content="Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz"> <meta property="og:title" content="Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz">
<meta property="og:description" content="Tagebuch, Giftköder-Alarm, KI-Training, Forum, Wurfbörse, Stammbaum, Inzucht-Check — alles in einer DSGVO-konformen App ohne App Store. Kostenlos."> <meta property="og:description" content="Tagebuch, Giftköder-Alarm, KI-Training, Forum, Wurfbörse, Stammbaum, Inzucht-Check — alles in einer DSGVO-konformen Web-App direkt im Browser. Kostenlos.">
<meta property="og:url" content="https://banyaro.app/"> <meta property="og:url" content="https://banyaro.app/">
<meta property="og:image" content="https://banyaro.app/icons/icon-512.png"> <meta property="og:image" content="https://banyaro.app/icons/icon-512.png">
<meta property="og:locale" content="de_DE"> <meta property="og:locale" content="de_DE">
@ -23,7 +23,7 @@
<!-- Twitter Card --> <!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image"> <meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Ban Yaro — Die Hunde-App für DACH"> <meta name="twitter:title" content="Ban Yaro — Die Hunde-App für DACH">
<meta name="twitter:description" content="Giftköder-Alarm, KI-Training, Forum, Wurfbörse, Stammbaum, Inzucht-Check — kostenlos, DSGVO-konform, ohne App Store."> <meta name="twitter:description" content="Giftköder-Alarm, KI-Training, Forum, Wurfbörse, Stammbaum, Inzucht-Check — kostenlos, DSGVO-konform, direkt im Browser.">
<meta name="twitter:image" content="https://banyaro.app/icons/icon-512.png"> <meta name="twitter:image" content="https://banyaro.app/icons/icon-512.png">
<!-- Structured Data --> <!-- Structured Data -->
@ -33,7 +33,7 @@
"@type": "MobileApplication", "@type": "MobileApplication",
"name": "Ban Yaro", "name": "Ban Yaro",
"alternateName": "Ban Yaro — Die Hunde-Plattform", "alternateName": "Ban Yaro — Die Hunde-Plattform",
"description": "Ban Yaro ist die deutschsprachige All-in-One Hunde-Plattform für Hundebesitzer und Züchter. Kostenlos: Tagebuch, Impfpass, Gassi-Community, Giftköder-Alarm. Pro (29 €/Jahr): mehrere Hunde, Ernährung. Züchter (49 €/Jahr): Warteliste, Läufigkeit, Wurfverwaltung, Stammbaum, Inzucht-Koeffizient, KI-Assistent — DSGVO-konform, ohne App Store.", "description": "Ban Yaro ist die deutschsprachige All-in-One Hunde-Plattform für Hundebesitzer und Züchter. Kostenlos: Tagebuch, Impfpass, Gassi-Community, Giftköder-Alarm. Pro (29 €/Jahr): mehrere Hunde, Ernährung. Züchter (49 €/Jahr): Warteliste, Läufigkeit, Wurfverwaltung, Stammbaum, Inzucht-Koeffizient, KI-Assistent — DSGVO-konform, direkt im Browser.",
"url": "https://banyaro.app", "url": "https://banyaro.app",
"applicationCategory": "LifestyleApplication", "applicationCategory": "LifestyleApplication",
"applicationSubCategory": "PetApplication", "applicationSubCategory": "PetApplication",
@ -129,7 +129,7 @@
"DSGVO Datenexport (Art. 20): vollständiger JSON-Download aller eigenen Daten", "DSGVO Datenexport (Art. 20): vollständiger JSON-Download aller eigenen Daten",
"Hunde-Persönlichkeitstest mit Trainingstipps", "Hunde-Persönlichkeitstest mit Trainingstipps",
"Reise-Checkliste und EU-Länder-Einreiseregeln", "Reise-Checkliste und EU-Länder-Einreiseregeln",
"Integrierte Hilfe und FAQ ohne App Store", "Integrierte Hilfe und FAQ direkt in der App",
"Warteliste: Interessenten mit Präferenzen pro Zuchthündin verwalten", "Warteliste: Interessenten mit Präferenzen pro Zuchthündin verwalten",
"Läufigkeit und Trächtigkeit: Zykluskalender, Progesterontests, Deckdaten, Meilensteine", "Läufigkeit und Trächtigkeit: Zykluskalender, Progesterontests, Deckdaten, Meilensteine",
"Wurf-Buchstabe und Wurf-Name für jeden Wurf", "Wurf-Buchstabe und Wurf-Name für jeden Wurf",
@ -750,7 +750,7 @@
<div class="header-badges" style="margin-top:1.5rem"> <div class="header-badges" style="margin-top:1.5rem">
<span class="badge">Kostenlos nutzbar</span> <span class="badge">Kostenlos nutzbar</span>
<span class="badge">Daten in Deutschland</span> <span class="badge">Daten in Deutschland</span>
<span class="badge">Kein App Store nötig</span> <span class="badge">Läuft direkt im Browser</span>
<span class="badge">Made in Germany</span> <span class="badge">Made in Germany</span>
<span class="badge">Offline-fähig</span> <span class="badge">Offline-fähig</span>
</div> </div>
@ -1289,7 +1289,7 @@
<section id="vergleich"> <section id="vergleich">
<div class="container"> <div class="container">
<h2>Ban Yaro vs. Konkurrenz</h2> <h2>Ban Yaro vs. Konkurrenz</h2>
<p class="section-intro">Hundeo ist stark im Training — Dogorama stark in der Community. Ban Yaro vereint beides und geht weit darüber hinaus: Zucht-Management, KI-Trainer, Gesundheitsakte und Sitting in einer einzigen App — ohne App Store.</p> <p class="section-intro">Hundeo ist stark im Training — Dogorama stark in der Community. Ban Yaro vereint beides und geht weit darüber hinaus: Zucht-Management, KI-Trainer, Gesundheitsakte und Sitting in einer einzigen App — direkt im Browser.</p>
<div class="table-wrap"> <div class="table-wrap">
<table> <table>
<thead> <thead>
@ -1314,7 +1314,7 @@
<td class="check">✓ DE</td> <td class="check">✓ DE</td>
</tr> </tr>
<tr> <tr>
<td>Kein App Store nötig (PWA)</td> <td>Direkt im Browser nutzbar (PWA)</td>
<td class="check"></td> <td class="check"></td>
<td class="cross"></td> <td class="cross"></td>
<td class="cross"></td> <td class="cross"></td>
@ -1491,7 +1491,7 @@
<div class="usp-item"> <div class="usp-item">
<svg class="usp-icon" viewBox="0 0 256 256"><use href="/icons/phosphor.svg#device-mobile"></use></svg> <svg class="usp-icon" viewBox="0 0 256 256"><use href="/icons/phosphor.svg#device-mobile"></use></svg>
<div> <div>
<h3>Kein App Store</h3> <h3>Direkt im Browser</h3>
<p>Als Progressive Web App direkt über den Browser installierbar — auf iOS und Android. Sofort updatebar.</p> <p>Als Progressive Web App direkt über den Browser installierbar — auf iOS und Android. Sofort updatebar.</p>
</div> </div>
</div> </div>
@ -1537,7 +1537,7 @@
<section> <section>
<div class="container" style="text-align:center"> <div class="container" style="text-align:center">
<h2>Jetzt kostenlos starten</h2> <h2>Jetzt kostenlos starten</h2>
<p class="section-intro" style="margin:1rem auto 2rem">Einfach banyaro.app im Browser öffnen und "Zum Homescreen hinzufügen" — fertig. Kein App Store, keine Kreditkarte.</p> <p class="section-intro" style="margin:1rem auto 2rem">Einfach banyaro.app im Browser öffnen und "Zum Homescreen hinzufügen" — fertig. Keine Installation, keine Kreditkarte.</p>
<a href="/" class="cta-btn" style="background:var(--primary);color:white;box-shadow:0 4px 20px rgba(196,132,58,.4)">Ban Yaro öffnen</a> <a href="/" class="cta-btn" style="background:var(--primary);color:white;box-shadow:0 4px 20px rgba(196,132,58,.4)">Ban Yaro öffnen</a>
</div> </div>
</section> </section>

View file

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