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.
This commit is contained in:
rene 2026-06-07 15:09:43 +02:00
parent 5acbaaa97b
commit 178aef7fb0
32 changed files with 197 additions and 188 deletions

View file

@ -1 +1 @@
1251
1252

View file

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

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
const APP_VER = '1251'; // ← 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
window.APP_VER = APP_VER; // global verfügbar für andere Module (z.B. offline-indicator)
window.APP_VERSION = APP_VERSION;
@ -469,12 +469,15 @@ const App = (() => {
break;
case 'sibling':
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;
case 'show-el': {
el.style.display = 'none';
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;
}
case 'emoji':

View file

@ -745,7 +745,7 @@ window.Page_admin = (() => {
<div class="card p-4">
<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>
</svg>
Ersten Admin per SQL setzen:
@ -875,12 +875,12 @@ window.Page_admin = (() => {
<!-- Aktionen -->
<div style="display:flex;gap:var(--space-1);flex-shrink:0">
${u.is_banned
? `<button class="btn btn-sm btn-ghost adm-unban" data-uid="${u.id}" data-name="${UI.escape(u.name)}"
title="Sperre aufheben" class="text-success">
? `<button class="btn btn-sm btn-ghost adm-unban text-success" data-uid="${u.id}" data-name="${UI.escape(u.name)}"
title="Sperre aufheben">
<svg class="ph-icon"><use href="/icons/phosphor.svg#lock-open"></use></svg>
</button>`
: `<button class="btn btn-sm btn-ghost adm-ban" data-uid="${u.id}" data-name="${UI.escape(u.name)}"
title="Sperren" class="text-danger">
: `<button class="btn btn-sm btn-ghost adm-ban text-danger" data-uid="${u.id}" data-name="${UI.escape(u.name)}"
title="Sperren">
<svg class="ph-icon"><use href="/icons/phosphor.svg#lock"></use></svg>
</button>`
}
@ -895,9 +895,9 @@ window.Page_admin = (() => {
title="Abo-Stufe ändern">
<svg class="ph-icon"><use href="/icons/phosphor.svg#star"></use></svg>
</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"
class="text-danger">
>
<svg class="ph-icon"><use href="/icons/phosphor.svg#trash"></use></svg>
</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>
</button>
${!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}"
title="Inhalt löschen" class="text-danger">
title="Inhalt löschen">
<svg class="ph-icon"><use href="/icons/phosphor.svg#trash"></use></svg>
</button>` : ''}
</div>
@ -1193,13 +1193,13 @@ window.Page_admin = (() => {
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>
</button>
<button class="btn btn-sm btn-ghost adm-del-thread" data-tid="${t.id}"
title="Löschen" class="text-danger">
<button class="btn btn-sm btn-ghost adm-del-thread text-danger" data-tid="${t.id}"
title="Löschen">
<svg class="ph-icon"><use href="/icons/phosphor.svg#trash"></use></svg>
</button>
` : `
<button class="btn btn-sm btn-ghost adm-restore-thread" data-tid="${t.id}"
title="Wiederherstellen" class="text-success">
<button class="btn btn-sm btn-ghost adm-restore-thread text-success" data-tid="${t.id}"
title="Wiederherstellen">
<svg class="ph-icon"><use href="/icons/phosphor.svg#arrow-square-out"></use></svg>
</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" 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-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>
</tr>`).join('')}
</tbody></table></div></div>`;
@ -1747,8 +1747,8 @@ window.Page_admin = (() => {
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 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-ghost adm-foto-reject" data-id="${f.id}" class="text-danger"></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 text-danger" data-id="${f.id}"></button>
</div>
</div>`).join('')}
</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">
${UI.icon('check')}
</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')}
</button>
</td>
@ -2024,9 +2024,9 @@ window.Page_admin = (() => {
data-uid="${a.user_id || a.id}" data-name="${UI.escape(a.name)}">
${UI.icon('check')} Freischalten
</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)}"
class="text-danger">
>
${UI.icon('x')} Ablehnen
</button>
</div>
@ -2170,9 +2170,9 @@ window.Page_admin = (() => {
${b.verified_at ? new Date(b.verified_at).toLocaleDateString('de-DE') : '—'}
</td>
<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')}"
class="text-xs">
>
Abo
</button>
</td>
@ -2251,8 +2251,8 @@ window.Page_admin = (() => {
${UI.escape(j.trigger)}
</td>
<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)}"
title="Jetzt ausführen" class="text-primary">
<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">
${UI.icon('play')}
</button>
</td>
@ -3203,10 +3203,10 @@ window.Page_admin = (() => {
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)">
<div class="flex-1"></div>
<button class="btn btn-secondary btn-sm adm-hilfe-edit-cancel" data-id="${a.id}"
class="text-xs">Abbrechen</button>
<button class="btn btn-primary btn-sm adm-hilfe-edit-save" data-id="${a.id}"
class="text-xs">Speichern</button>
<button class="btn btn-secondary btn-sm adm-hilfe-edit-cancel text-xs" data-id="${a.id}"
>Abbrechen</button>
<button class="btn btn-primary btn-sm adm-hilfe-edit-save text-xs" data-id="${a.id}"
>Speichern</button>
</div>
</div>
</div>
@ -3839,8 +3839,8 @@ window.Page_admin = (() => {
</button>`);
}
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"
class="text-muted">
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"
>
${UI.icon('paper-plane-tilt')} Erneut senden
</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">
${UI.icon('check-circle')} Bezahlt
</button>`);
actions.push(`<button class="btn btn-sm btn-ghost adm-inv-cancel" data-id="${inv.id}" data-num="${UI.escape(inv.invoice_number)}"
class="text-danger" title="Stornieren">
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)}"
title="Stornieren">
${UI.icon('x-circle')} Storno
</button>`);
}
@ -4067,7 +4067,7 @@ window.Page_admin = (() => {
footer: `
<div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%">
${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)">
<button class="btn btn-secondary" data-modal-close>Abbrechen</button>
<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.style.cssText = 'display:grid;grid-template-columns:1fr 60px 100px auto;gap:var(--space-2);align-items:center';
itemEl.innerHTML = `
<input class="form-control inv-item-desc" type="text" placeholder="Beschreibung *"
value="${UI.escape(desc)}" class="text-sm">
<input class="form-control inv-item-desc text-sm" type="text" placeholder="Beschreibung *"
value="${UI.escape(desc)}">
<input class="form-control inv-item-qty" type="number" min="1" value="${qty}"
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)}"

View file

@ -196,8 +196,8 @@ window.Page_breeder_editor = (() => {
<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>
Upload
<input type="file" class="be-litter-input" data-litter-id="${l.id}"
data-label="${UI.escape(label)}" accept="image/*,video/*" class="hidden">
<input type="file" class="be-litter-input hidden" data-litter-id="${l.id}"
data-label="${UI.escape(label)}" accept="image/*,video/*">
</label>
</div>
</div>`;

View file

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

View file

@ -195,7 +195,7 @@ window.Page_chat = (() => {
</button>`}
<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>
<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>
<span class="chat-thread-partner" id="chat-partner-name"></span>
</div>
@ -265,7 +265,7 @@ window.Page_chat = (() => {
const dotEl = document.getElementById('chat-partner-dot');
if (nameEl) nameEl.textContent = data.partner_name;
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) {
el.innerHTML = `
@ -304,7 +304,7 @@ window.Page_chat = (() => {
// Online-Dot aktualisieren
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 _updateChatBadge();

View file

@ -391,7 +391,7 @@ window.Page_diary = (() => {
const bar = _container.querySelector('#diary-stats-bar');
if (!bar) return;
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 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;
@ -425,7 +425,8 @@ window.Page_diary = (() => {
</button>
</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 => {
btn.addEventListener('click', () => {
@ -1332,8 +1333,9 @@ window.Page_diary = (() => {
function _renderNewGrid() {
const grid = document.getElementById('diary-new-media-grid');
if (!grid) return;
if (_newFiles.length === 0) { grid.style.display = 'none'; grid.innerHTML = ''; return; }
grid.style.cssText = 'display:grid;grid-template-columns:repeat(auto-fill,minmax(90px,1fr));gap:8px;margin-bottom:8px';
if (_newFiles.length === 0) { grid.classList.add('hidden'); grid.innerHTML = ''; return; }
// .hidden hat !important → classList togglen; Grid-Layout kommt aus .diary-media-grid
grid.classList.remove('hidden');
grid.innerHTML = _newFiles.map((f, i) => {
const objUrl = URL.createObjectURL(f);
const thumb = f.type === 'application/pdf' || f.name?.endsWith('.pdf')

View file

@ -559,8 +559,8 @@ window.Page_dog_profile = (() => {
value="${defaultUntil}" min="${today}">
</div>
</div>
<button class="btn btn-primary btn-sm w-full" id="sa-grant-btn"
class="mt-2">
<button class="btn btn-primary btn-sm w-full mt-2" id="sa-grant-btn"
>
Zugang gewähren
</button>
</div>
@ -615,7 +615,7 @@ window.Page_dog_profile = (() => {
</div>`,
footer: `
<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>
</div>`,
});
@ -672,7 +672,7 @@ window.Page_dog_profile = (() => {
const footer = `
<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">
${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>
@ -945,8 +945,8 @@ window.Page_dog_profile = (() => {
<div id="share-link-result" style="display:none;margin-top:var(--space-4)">
<label class="form-label">Einladungslink</label>
<div style="display:flex;gap:var(--space-2);align-items:center">
<input class="form-control" id="share-link-input" type="text" readonly
class="text-xs">
<input class="form-control text-xs" id="share-link-input" type="text" readonly
>
<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>
</button>
@ -1509,11 +1509,11 @@ window.Page_dog_profile = (() => {
</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">
${isTop ? `<button class="btn btn-primary btn-sm" data-action="uebernehmen"
data-rasse="${UI.escape(r.name)}" class="flex-1">
${isTop ? `<button class="btn btn-primary btn-sm flex-1" data-action="uebernehmen"
data-rasse="${UI.escape(r.name)}">
Rasse übernehmen
</button>` : `<button class="btn btn-secondary btn-sm" data-action="uebernehmen"
data-rasse="${UI.escape(r.name)}" class="flex-1">
</button>` : `<button class="btn btn-secondary btn-sm flex-1" data-action="uebernehmen"
data-rasse="${UI.escape(r.name)}">
Diese wählen
</button>`}
${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>
</div>`
: vaccs.map(v => `
<div class="pp-vacc-row" data-id="${v.id}"
class="pp-data-row">
<div class="pp-vacc-row pp-data-row" data-id="${v.id}"
>
<div class="flex-1">
<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">
@ -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>
</div>`
: meds.map(m => `
<div class="pp-med-row" data-id="${m.id}"
class="pp-data-row">
<div class="pp-med-row pp-data-row" data-id="${m.id}"
>
<div class="flex-1">
<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">
@ -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.
</p>
<div style="display:flex;gap:var(--space-2);align-items:center">
<input id="pp-sharelink-input" class="form-control" type="text" readonly
value="${UI.escape(url)}" class="text-xs">
<input id="pp-sharelink-input" class="form-control text-xs" type="text" readonly
value="${UI.escape(url)}">
<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>
</button>

View file

@ -567,9 +567,9 @@ window.Page_ernaehrung = (() => {
'Ist Getreide im Futter schlecht?',
'Welche Leckerlis sind gesund?',
].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)}"
class="text-xs">${UI.escape(q)}</button>
>${UI.escape(q)}</button>
`).join('')}
</div>

View file

@ -254,13 +254,13 @@ window.Page_erste_hilfe = (() => {
</div>
${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('')}
</div>
`).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">
<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.
</div>
</div>
@ -346,7 +346,7 @@ window.Page_erste_hilfe = (() => {
return `
<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)">
<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
</div>
<div style="overflow-x:auto">
@ -433,7 +433,7 @@ window.Page_erste_hilfe = (() => {
btn.style.color = active ? '#fff' : kat.color;
});
_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 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);
@ -228,10 +228,10 @@ window.Page_events = (() => {
</div>
<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>` : ''}
${_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-ort="${UI.escape(ev.ort_name || '')}"
title="Notiz" class="text-muted">
title="Notiz">
${_icon('note-pencil')}</button>` : ''}
</div>
</div>
@ -507,7 +507,7 @@ window.Page_events = (() => {
const footer = `
<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'}
</button>
<div class="flex-gap-2">
@ -605,7 +605,7 @@ window.Page_events = (() => {
const mapEl = document.getElementById('ev-map');
if (_view === 'karte') {
listEl.style.display = 'none';
mapEl.style.display = 'block';
mapEl.classList.remove('hidden'); // .hidden hat !important → nur classList
// Erst div sichtbar machen, dann Karte initialisieren
_renderMap(_filtered());
} else {
@ -616,7 +616,7 @@ window.Page_events = (() => {
_clusterGroup = null;
_markers = [];
}
mapEl.style.display = 'none';
mapEl.classList.add('hidden');
listEl.style.display = '';
_renderList();
}

View file

@ -92,7 +92,7 @@ function _fmtDate(iso) {
<h2 class="forum-header-title">Forum</h2>
<div class="forum-header-actions">
${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>
</div>
</div>
@ -271,9 +271,9 @@ function _fmtDate(iso) {
: `<div class="hdm-section">
<h3 class="hdm-section-title">Für welchen Hund möchtest du abstimmen?</h3>
<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"
class="text-sm">
>
</div>
<div id="hdm-kandidaten-grid" class="hdm-vote-grid">
${UI.skeleton(3)}
@ -1293,7 +1293,7 @@ function _fmtDate(iso) {
<div class="text-xs-muted">
von ${UI.escape(r.melder_name || '?')} · ${_fmtDate(r.created_at)}
</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
</button>
</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>
Nachricht schreiben
</button>
<button class="btn btn-ghost" id="modal-remove-btn" form=""
class="text-danger">
<button class="btn btn-ghost text-danger" id="modal-remove-btn" form=""
>
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#user-minus"></use></svg>
Entfernen
</button>

View file

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

View file

@ -191,9 +191,9 @@ window.Page_jobs = (() => {
<div class="form-group">
<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"
class="p-2">
>
<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.
PDF, Bild oder Video.

View file

@ -297,11 +297,11 @@ window.Page_knigge = (() => {
</h2>
<div class="card">
<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"
placeholder="Beschreibe deine Situation…"
class="mb-3"></textarea>
<button class="btn btn-primary" id="ki-rat-btn" class="w-full">
></textarea>
<button class="btn btn-primary w-full" id="ki-rat-btn">
Rat holen ${UI.icon('robot')}
</button>
<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">
${UI.icon('pencil-simple')}
</button>
<button class="btn btn-ghost btn-xs laeufi-delete-btn" data-id="${l.id}"
title="Löschen" class="text-danger">
<button class="btn btn-ghost btn-xs laeufi-delete-btn text-danger" data-id="${l.id}"
title="Löschen">
${UI.icon('trash')}
</button>
</div>
@ -335,7 +335,7 @@ window.Page_laeufi = (() => {
</div>
<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-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>
<!-- Meilensteine -->
@ -542,8 +542,8 @@ window.Page_laeufi = (() => {
</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">
<button class="btn btn-ghost btn-xs prog-delete-btn" data-id="${t.id}"
class="text-danger">${UI.icon('trash')}</button>
<button class="btn btn-ghost btn-xs prog-delete-btn text-danger" data-id="${t.id}"
>${UI.icon('trash')}</button>
</td>
</tr>`).join('')}
</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">
${UI.icon('pencil-simple')}
</button>
<button class="btn btn-ghost btn-sm litters-delete-btn" data-id="${l.id}" title="Löschen"
class="text-danger">
<button class="btn btn-ghost btn-sm litters-delete-btn text-danger" data-id="${l.id}" title="Löschen"
>
${UI.icon('trash')}
</button>
</div>
@ -399,8 +399,8 @@ window.Page_litters = (() => {
<div id="puppies-inner-${l.id}">
<p class="text-sm-muted">Lädt</p>
</div>
<button class="btn btn-secondary btn-sm litters-add-puppy-btn" data-id="${l.id}"
class="mt-3">
<button class="btn btn-secondary btn-sm litters-add-puppy-btn mt-3" data-id="${l.id}"
>
${UI.icon('plus')} Welpen hinzufügen
</button>
</div>
@ -410,8 +410,8 @@ window.Page_litters = (() => {
<div id="waitlist-inner-${l.id}">
<p class="text-sm-muted">Lädt</p>
</div>
<button class="btn btn-secondary btn-sm litters-add-waitlist-btn" data-id="${l.id}"
class="mt-3">
<button class="btn btn-secondary btn-sm litters-add-waitlist-btn mt-3" data-id="${l.id}"
>
${UI.icon('plus')} Interessent eintragen
</button>
</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>
${p.farbe ? `<span style="color:var(--c-text-secondary);font-size:var(--text-xs)">${UI.escape(p.farbe)}</span>` : ''}
${_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 class="litters-puppy-actions">
<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 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-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>`).join('')}
</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>`;
}).join('');
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>
${opts}
</select>
@ -1496,7 +1496,7 @@ window.Page_litters = (() => {
Trotzdem fortfahren
</button>
</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
</button>`,
});

View file

@ -190,10 +190,10 @@ window.Page_moderation = (() => {
` : `<div style="font-size:var(--text-xs);color:var(--c-warning);
margin-bottom:var(--space-3)">Noch kein Foto vorhanden</div>`}
<div class="flex-gap-2">
<button class="btn btn-sm btn-primary mod-foto-approve"
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>
<button class="btn btn-sm btn-ghost mod-foto-reject"
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>
<button class="btn btn-sm btn-primary mod-foto-approve flex-1"
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 text-danger"
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>
`).join('')}
@ -322,14 +322,14 @@ window.Page_moderation = (() => {
<div style="flex-shrink:0">
${canAction
? (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)}"
title="Sperre aufheben" class="text-success">
title="Sperre aufheben">
${UI.icon('lock-open')}
</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)}"
title="Sperren" class="text-danger">
title="Sperren">
${UI.icon('lock')}
</button>`)
: ''

View file

@ -163,11 +163,11 @@ window.Page_onboarding = (() => {
<!-- Buttons -->
<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>
Los geht's
</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
</button>
</div>
@ -255,14 +255,14 @@ window.Page_onboarding = (() => {
<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>
</button>
<button type="submit" form="ob-dog-form" class="btn btn-primary" id="ob-save-btn"
class="flex-1">
<button type="submit" form="ob-dog-form" class="btn btn-primary flex-1" id="ob-save-btn"
>
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#dog"></use></svg>
Hund anlegen
</button>
</div>
<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
</button>
</div>
@ -317,12 +317,12 @@ window.Page_onboarding = (() => {
<!-- CTA -->
<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>
Zum Tagebuch
</button>
${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>
Profil vervollständigen
</button>

View file

@ -81,7 +81,7 @@ function _fmtDate(iso) {
<div class="playdate-layout">
<!-- 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" data-tab="listings">Meine Inserate</button>
<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">
${UI.icon('phone')} <strong>110</strong> Polizei
</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
</button>
</div>
@ -215,7 +215,7 @@ window.Page_poison = (() => {
${r.beschreibung ? UI.escape(r.beschreibung.slice(0, 80)) + '<br>' : ''}
<small>📍 ${distStr} entfernt</small><br>
<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));
@ -276,7 +276,7 @@ window.Page_poison = (() => {
<span class="badge"
style="background:${typ.color};color:#fff">${typ.label}</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>'
? '<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);
font-size:var(--text-sm);white-space:nowrap">
@ -330,7 +330,7 @@ window.Page_poison = (() => {
<span class="badge" style="background:${typ.color};color:#fff">
${UI.icon(typ.icon)} ${typ.label}
</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>
${r.beschreibung
@ -347,7 +347,7 @@ window.Page_poison = (() => {
<div style="display:flex;gap:var(--space-2);flex-wrap:wrap">
${!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>
${isOwnEntry || isAdmin

View file

@ -352,13 +352,13 @@ window.Page_routes = (() => {
<button class="rk-chip" data-filter="sort" data-val="dog">Hundefreundlich</button>
</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-chips-row">
<button class="rk-chip" data-filter="mine" data-val="mine">🔒 Nur meine</button>
</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-chips-row">
<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');
if (!badge) return;
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) {
@ -450,8 +450,8 @@ window.Page_routes = (() => {
if (mode === 'suggest') {
if (recBtn) recBtn.style.display = 'none';
if (impWrap) impWrap.style.display = 'none';
if (mineGrp) mineGrp.style.display = 'none';
if (nearbyGrp) nearbyGrp.style.display = 'none';
if (mineGrp) mineGrp.classList.add('hidden');
if (nearbyGrp) nearbyGrp.classList.add('hidden');
if (searchRow) searchRow.style.display = 'none';
if (actRow) actRow.style.display = 'none';
const filterPanel = document.getElementById('rk-filter-panel');
@ -474,13 +474,13 @@ window.Page_routes = (() => {
if (mode === 'discover') {
if (recBtn) recBtn.style.display = 'none';
if (impWrap) impWrap.style.display = 'none';
if (mineGrp) mineGrp.style.display = 'none';
if (nearbyGrp && _userPos) nearbyGrp.style.display = '';
if (mineGrp) mineGrp.classList.add('hidden');
if (nearbyGrp && _userPos) nearbyGrp.classList.remove('hidden');
} else {
if (recBtn) recBtn.style.display = '';
if (impWrap) impWrap.style.display = '';
if (_appState.user && mineGrp) mineGrp.style.display = '';
if (nearbyGrp) nearbyGrp.style.display = 'none';
if (_appState.user && mineGrp) mineGrp.classList.remove('hidden');
if (nearbyGrp) nearbyGrp.classList.add('hidden');
}
_onlyMine = false;
document.querySelectorAll('#rk-mine-group .rk-chip').forEach(c => c.classList.remove('active'));
@ -1439,9 +1439,9 @@ window.Page_routes = (() => {
const pending = _getPending();
if (pending.length) _data = [...pending, ..._data];
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)
document.getElementById('rk-nearby-group')?.style.setProperty('display', '');
document.getElementById('rk-nearby-group')?.classList.remove('hidden');
if (!online && pending.length)
UI.toast.info('Offline — ' + pending.length + ' Route(n) warten auf Sync.');
_applyFilter();

View file

@ -652,7 +652,7 @@ window.Page_settings = (() => {
</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 id="settings-stats-body" style="padding:var(--space-4);display:flex;justify-content:space-around">
<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>` : ''}
</div>
${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
</button>` : ''}
${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
</button>` : ''}
${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
</button>` : ''}
${profile ? `
@ -1991,8 +1991,8 @@ window.Page_settings = (() => {
`,
footer: `
<div class="w3-btn-stack">
<button type="submit" form="breeder-apply-form" class="btn btn-primary" id="breeder-apply-submit"
class="w-full">Antrag einreichen</button>
<button type="submit" form="breeder-apply-form" class="btn btn-primary w-full" id="breeder-apply-submit"
>Antrag einreichen</button>
<button type="button" class="btn btn-secondary" data-modal-close>Abbrechen</button>
</div>
`,

View file

@ -406,7 +406,7 @@ window.Page_sitting = (() => {
`;
const footer = `
<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`}
</button>
<button class="btn btn-secondary" data-modal-close>Abbrechen</button>

View file

@ -202,9 +202,10 @@ window.Page_walks = (() => {
_tab = tab;
document.querySelectorAll('.by-tab').forEach(b =>
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}`);
if (panel) panel.style.display = '';
if (panel) panel.classList.remove('hidden');
if (tab === 'challenge' && !_challengeData) _loadChallenge();
if (tab === 'stamm' && !_gassiZeiten.length) _loadGassiZeiten();
@ -1058,7 +1059,7 @@ window.Page_walks = (() => {
${UI.icon('calendar')} ${_fmtDate(challenge.start_date)} ${_fmtDate(challenge.end_date)}
&nbsp;·&nbsp; ${UI.icon('timer')} Noch ${dayLabel}
</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>` : ''}
</div>
</div>

View file

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

View file

@ -156,7 +156,7 @@ window.Page_wiki = (() => {
// Badge updaten
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) {
el.innerHTML = `
@ -216,7 +216,7 @@ window.Page_wiki = (() => {
const badge = document.getElementById('wiki-fotos-badge');
if (badge) {
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'); }
}
@ -269,8 +269,8 @@ window.Page_wiki = (() => {
</select>
</div>
<div style="padding:0 0 var(--space-3)">
<button class="btn btn-secondary w-full" id="wiki-rasse-erkennen-btn"
class="text-sm">
<button class="btn btn-secondary w-full text-sm" id="wiki-rasse-erkennen-btn"
>
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#magnifying-glass"></use></svg>
Welche Rasse ist das? Foto analysieren
</button>
@ -669,7 +669,7 @@ window.Page_wiki = (() => {
</div>
</form>
</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
</button>
` : '';
@ -835,14 +835,14 @@ window.Page_wiki = (() => {
${berichteHtml}
</div>
${_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)">
<a href="#settings" class="text-primary">Anmelden</a>, um einen Bericht zu schreiben.
</p>`
}
${_appState.user ? `
<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'}
</button>
</div>` : ''}`}
@ -878,7 +878,8 @@ window.Page_wiki = (() => {
const idx = parseInt(btn.dataset.idx);
mainImg.src = allFotos[idx].foto_url;
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));
});
});
@ -1255,7 +1256,7 @@ window.Page_wiki = (() => {
<span>${UI.icon('house-line')} ${r.wohnung_geeignet ? 'Wohnung' : 'Haus'}</span>
<span>${UI.icon('users')} ${r.kinder_geeignet ? 'Kinderfreundlich' : 'Erfahrung nötig'}</span>
</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>
`;
@ -1268,7 +1269,7 @@ window.Page_wiki = (() => {
</div>
<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>
<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>
`;

View file

@ -341,8 +341,8 @@ window.Page_zuchthunde = (() => {
title="Bearbeiten">
${UI.icon('pencil-simple')}
</button>
<button class="btn btn-ghost btn-sm zh-delete-btn" data-id="${h.id}"
title="Löschen" class="text-danger">
<button class="btn btn-ghost btn-sm zh-delete-btn text-danger" data-id="${h.id}"
title="Löschen">
${UI.icon('trash')}
</button>
</div>
@ -360,7 +360,7 @@ window.Page_zuchthunde = (() => {
</button>
</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>`;
}
@ -379,7 +379,7 @@ window.Page_zuchthunde = (() => {
function _closeSection(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;
_updateSectionButtons(hundId, null);
}
@ -389,7 +389,7 @@ window.Page_zuchthunde = (() => {
_updateSectionButtons(hundId, section);
const wrap = document.getElementById(`zh-section-${hundId}`);
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>`;
if (section === 'health') _loadHealthSection(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.labor ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${UI.escape(t.labor)}</span>` : ''}
</div>
<button class="btn btn-ghost btn-xs zh-health-del-btn" data-tid="${t.id}" title="Löschen"
class="text-danger">${UI.icon('trash')}</button>
<button class="btn btn-ghost btn-xs zh-health-del-btn text-danger" data-tid="${t.id}" title="Löschen"
>${UI.icon('trash')}</button>
</div>`).join('')
: `<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.labor ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${UI.escape(t.labor)}</span>` : ''}
</div>
<button class="btn btn-ghost btn-xs zh-genetic-del-btn" data-tid="${t.id}" title="Löschen"
class="text-danger">${UI.icon('trash')}</button>
<button class="btn btn-ghost btn-xs zh-genetic-del-btn text-danger" data-tid="${t.id}" title="Löschen"
>${UI.icon('trash')}</button>
</div>`).join('')
: `<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.formwert ? `<span class="zh-badge" style="background:#3B82F6">${UI.escape(t.formwert)}</span>` : ''}
</div>
<button class="btn btn-ghost btn-xs zh-title-del-btn" data-tid="${t.id}" title="Löschen"
class="text-danger">${UI.icon('trash')}</button>
<button class="btn btn-ghost btn-xs zh-title-del-btn text-danger" data-tid="${t.id}" title="Löschen"
>${UI.icon('trash')}</button>
</div>`).join('')
: `<p class="text-sm-muted">Noch keine Titel eingetragen.</p>`;

View file

@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<script src="/js/landing-init.js?v=1251"></script>
<script src="/js/landing-init.js?v=1252"></script>
<title>Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz</title>
<meta name="description" content="Ban Yaro: Die kostenlose All-in-One Hunde-App für DACH. Tagebuch, Giftköder-Alarm, Training mit KI, Forum, Wurfbörse, Stammbaum, Inzucht-Check — DSGVO-konform, offline-fähig, 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">

View file

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