Compare commits

..

No commits in common. "178aef7fb00593624b7e054fe7781c279601be5f" and "6d9a04fd100fbb231496bffaa53f6879cfb19402" have entirely different histories.

32 changed files with 198 additions and 207 deletions

View file

@ -1 +1 @@
1252 1250

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=1252"></script> <script src="/js/boot-early.js?v=1250"></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=1252"> <link rel="stylesheet" href="/css/design-system.css?v=1250">
<link rel="stylesheet" href="/css/layout.css?v=1252"> <link rel="stylesheet" href="/css/layout.css?v=1250">
<link rel="stylesheet" href="/css/components.css?v=1252"> <link rel="stylesheet" href="/css/components.css?v=1250">
<link rel="stylesheet" href="/css/utilities.css?v=1252"> <link rel="stylesheet" href="/css/utilities.css?v=1250">
<link rel="stylesheet" href="/css/lists.css?v=1252"> <link rel="stylesheet" href="/css/lists.css?v=1250">
</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=1252"></script> <script src="/js/api.js?v=1250"></script>
<script src="/js/ui.js?v=1252"></script> <script src="/js/ui.js?v=1250"></script>
<script src="/js/app.js?v=1252"></script> <script src="/js/app.js?v=1250"></script>
<script src="/js/worlds.js?v=1252"></script> <script src="/js/worlds.js?v=1250"></script>
<script src="/js/offline-indicator.js?v=1252"></script> <script src="/js/offline-indicator.js?v=1250"></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=1252"></script> <script src="/js/boot.js?v=1250"></script>
</body> </body>

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung. Router, State-Management, Navigation, Initialisierung.
============================================================ */ ============================================================ */
const APP_VER = '1252'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const APP_VER = '1250'; // ← 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,15 +469,12 @@ const App = (() => {
break; break;
case 'sibling': case 'sibling':
el.style.display = 'none'; el.style.display = 'none';
if (el.nextElementSibling) { if (el.nextElementSibling) el.nextElementSibling.style.display = 'flex';
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.classList.remove('hidden'); t.style.display = 'flex'; } // .hidden hat !important if (t) t.style.display = 'flex';
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 text-primary" aria-hidden="true"> <svg class="ph-icon" aria-hidden="true" class="text-primary">
<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 text-success" data-uid="${u.id}" data-name="${UI.escape(u.name)}" ? `<button class="btn btn-sm btn-ghost adm-unban" data-uid="${u.id}" data-name="${UI.escape(u.name)}"
title="Sperre aufheben"> title="Sperre aufheben" class="text-success">
<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 text-danger" data-uid="${u.id}" data-name="${UI.escape(u.name)}" : `<button class="btn btn-sm btn-ghost adm-ban" data-uid="${u.id}" data-name="${UI.escape(u.name)}"
title="Sperren"> title="Sperren" class="text-danger">
<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 text-danger" data-uid="${u.id}" <button class="btn btn-sm btn-ghost adm-delete" 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 text-danger" <button class="btn btn-sm btn-ghost adm-del-content"
data-type="${r.target_type}" data-id="${r.target_id}" data-type="${r.target_type}" data-id="${r.target_id}"
title="Inhalt löschen"> title="Inhalt 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>` : ''}
</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 text-danger" data-tid="${t.id}" <button class="btn btn-sm btn-ghost adm-del-thread" data-tid="${t.id}"
title="Löschen"> 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>
` : ` ` : `
<button class="btn btn-sm btn-ghost adm-restore-thread text-success" data-tid="${t.id}" <button class="btn btn-sm btn-ghost adm-restore-thread" data-tid="${t.id}"
title="Wiederherstellen"> title="Wiederherstellen" class="text-success">
<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 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> <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>
</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 flex-1" data-id="${f.id}"></button> <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 text-danger" data-id="${f.id}"></button> <button class="btn btn-sm btn-ghost adm-foto-reject" data-id="${f.id}" class="text-danger"></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 text-danger" data-id="${e.id}"> <button class="btn btn-sm btn-ghost adm-poi-reject" data-id="${e.id}" class="text-danger">
${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 text-danger" <button class="btn btn-sm btn-ghost adm-breeder-reject"
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 text-xs" <button class="btn btn-sm btn-ghost adm-breeder-tier-btn"
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 text-primary" 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" data-id="${UI.escape(j.id)}" data-name="${UI.escape(j.name)}"
title="Jetzt ausführen"> title="Jetzt ausführen" class="text-primary">
${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 text-xs" data-id="${a.id}" <button class="btn btn-secondary btn-sm adm-hilfe-edit-cancel" data-id="${a.id}"
>Abbrechen</button> class="text-xs">Abbrechen</button>
<button class="btn btn-primary btn-sm adm-hilfe-edit-save text-xs" data-id="${a.id}" <button class="btn btn-primary btn-sm adm-hilfe-edit-save" data-id="${a.id}"
>Speichern</button> class="text-xs">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 text-muted" 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" 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 text-danger" data-id="${inv.id}" data-num="${UI.escape(inv.invoice_number)}" actions.push(`<button class="btn btn-sm btn-ghost adm-inv-cancel" data-id="${inv.id}" data-num="${UI.escape(inv.invoice_number)}"
title="Stornieren"> class="text-danger" 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 w-full" data-modal-close>Schließen</button>` ? `<button class="btn btn-secondary" data-modal-close class="w-full">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 text-sm" type="text" placeholder="Beschreibung *" <input class="form-control inv-item-desc" type="text" placeholder="Beschreibung *"
value="${UI.escape(desc)}"> value="${UI.escape(desc)}" class="text-sm">
<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 hidden" data-litter-id="${l.id}" <input type="file" class="be-litter-input" data-litter-id="${l.id}"
data-label="${UI.escape(label)}" accept="image/*,video/*"> data-label="${UI.escape(label)}" accept="image/*,video/*" class="hidden">
</label> </label>
</div> </div>
</div>`; </div>`;

View file

@ -390,7 +390,6 @@ 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 hidden" id="chat-partner-dot"></span> <span class="online-dot chat-avatar-dot" id="chat-partner-dot" class="hidden"></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.classList.toggle('hidden', !data.partner_online); if (dotEl) dotEl.style.display = data.partner_online ? '' : 'none';
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.classList.toggle('hidden', !data.partner_online); if (dotEl) dotEl.style.display = data.partner_online ? '' : 'none';
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.classList.add('hidden'); return; } if (!s && _entries.length === 0) { bar.style.display = 'none'; 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,8 +425,7 @@ window.Page_diary = (() => {
</button> </button>
</div> </div>
`; `;
// .hidden hat !important → nur per classList togglen (style.display verliert immer) bar.style.display = 'flex';
bar.classList.remove('hidden');
bar.querySelectorAll('.diary-view-btn').forEach(btn => { bar.querySelectorAll('.diary-view-btn').forEach(btn => {
btn.addEventListener('click', () => { btn.addEventListener('click', () => {
@ -1333,9 +1332,8 @@ 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.classList.add('hidden'); grid.innerHTML = ''; return; } if (_newFiles.length === 0) { grid.style.display = 'none'; grid.innerHTML = ''; return; }
// .hidden hat !important → classList togglen; Grid-Layout kommt aus .diary-media-grid grid.style.cssText = 'display:grid;grid-template-columns:repeat(auto-fill,minmax(90px,1fr));gap:8px;margin-bottom:8px';
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 mt-2" id="sa-grant-btn" <button class="btn btn-primary btn-sm w-full" 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 w-full" id="chip-edit-save-btn">Speichern</button> <button class="btn btn-primary" id="chip-edit-save-btn" class="w-full">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 w-full" id="pe-save-btn">Speichern</button>` : ''} ${hasPhoto ? `<button class="btn btn-primary" id="pe-save-btn" class="w-full">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 text-xs" id="share-link-input" type="text" readonly <input class="form-control" 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 flex-1" data-action="uebernehmen" ${isTop ? `<button class="btn btn-primary btn-sm" data-action="uebernehmen"
data-rasse="${UI.escape(r.name)}"> data-rasse="${UI.escape(r.name)}" class="flex-1">
Rasse übernehmen Rasse übernehmen
</button>` : `<button class="btn btn-secondary btn-sm flex-1" data-action="uebernehmen" </button>` : `<button class="btn btn-secondary btn-sm" data-action="uebernehmen"
data-rasse="${UI.escape(r.name)}"> data-rasse="${UI.escape(r.name)}" class="flex-1">
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 pp-data-row" data-id="${v.id}" <div class="pp-vacc-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 pp-data-row" data-id="${m.id}" <div class="pp-med-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 text-xs" type="text" readonly <input id="pp-sharelink-input" class="form-control" type="text" readonly
value="${UI.escape(url)}"> value="${UI.escape(url)}" class="text-xs">
<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 text-xs" <button class="btn btn-sm btn-secondary ern-ki-vorschlag"
data-q="${UI.escape(q)}" data-q="${UI.escape(q)}"
>${UI.escape(q)}</button> class="text-xs">${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 hidden" id="eh-panel-${k.id}"> <div class="eh-tab-panel" id="eh-panel-${k.id}" class="hidden">
${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 text-primary" aria-hidden="true"><use href="/icons/phosphor.svg#info"></use></svg> <svg class="ph-icon" aria-hidden="true" class="text-primary"><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 text-primary" aria-hidden="true"><use href="/icons/phosphor.svg#list-bullets"></use></svg> <svg class="ph-icon" aria-hidden="true" class="text-primary"><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.classList.toggle('hidden', panel.id !== `eh-panel-${id}`); // .hidden hat !important panel.style.display = panel.id === `eh-panel-${id}` ? 'block' : 'none';
}); });
} }

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 hidden" id="ev-map"></div> <div class="events-map" id="ev-map" class="hidden"></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 text-muted" data-ev-note-id="${ev.id}" ${_state.user ? `<button class="btn-icon ev-note-btn" 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"> title="Notiz" class="text-muted">
${_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 w-full" type="submit" form="${id}" id="ev-submit-btn"> <button class="btn btn-primary" type="submit" form="${id}" id="ev-submit-btn" class="w-full">
${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.classList.remove('hidden'); // .hidden hat !important → nur classList mapEl.style.display = 'block';
// 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.classList.add('hidden'); mapEl.style.display = 'none';
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 text-muted" id="forum-rules-btn" title="Regeln & Netiquette">${UI.icon('info')} Regeln</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-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 text-sm" <input type="search" id="hdm-search" class="form-control"
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 mt-2" data-id="${r.id}"> <button class="btn btn-sm btn-secondary forum-resolve-btn" data-id="${r.id}" class="mt-2">
${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 text-danger" id="modal-remove-btn" form="" <button class="btn btn-ghost" 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 text-muted" type="text" value="${UI.escape(currentOh)}" disabled <input class="form-control" 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,8 +3009,7 @@ function _showPoiKorrekturModal(osmId, poiName, currentOh) {
} }
await UI.asyncButton(btn, async () => { await UI.asyncButton(btn, async () => {
// .hidden hat !important → nur per classList togglen (style.display verliert immer) resultEl.style.display = 'none';
resultEl.classList.add('hidden');
resultEl.innerHTML = ''; resultEl.innerHTML = '';
let result; let result;
@ -3041,7 +3040,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.classList.remove('hidden'); resultEl.style.display = '';
return; return;
} }
@ -3075,7 +3074,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.classList.remove('hidden'); resultEl.style.display = '';
// 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 p-2" type="file" name="files" id="jobs-files" <input class="form-control" 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 mb-3" <textarea id="ki-situation-input" class="form-control"
rows="3" rows="3"
placeholder="Beschreibe deine Situation…" placeholder="Beschreibe deine Situation…"
></textarea> class="mb-3"></textarea>
<button class="btn btn-primary w-full" id="ki-rat-btn"> <button class="btn btn-primary" id="ki-rat-btn" class="w-full">
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 text-danger" data-id="${l.id}" <button class="btn btn-ghost btn-xs laeufi-delete-btn" data-id="${l.id}"
title="Löschen"> title="Löschen" class="text-danger">
${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 text-danger" data-id="${d.id}" title="Löschen">${UI.icon('trash')}</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>
</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 text-danger" data-id="${t.id}" <button class="btn btn-ghost btn-xs prog-delete-btn" data-id="${t.id}"
>${UI.icon('trash')}</button> class="text-danger">${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 text-danger" data-id="${l.id}" title="Löschen" <button class="btn btn-ghost btn-sm litters-delete-btn" 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 mt-3" data-id="${l.id}" <button class="btn btn-secondary btn-sm litters-add-puppy-btn" 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 mt-3" data-id="${l.id}" <button class="btn btn-secondary btn-sm litters-add-waitlist-btn" 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 text-xs-secondary" id="puppy-last-weight-${p.id}"></span> <span class="litters-puppy-last-weight" id="puppy-last-weight-${p.id}" class="text-xs-secondary"></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 text-danger" data-entry-id="${e.id}" title="Entfernen">${UI.icon('trash')}</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>
</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 mb-2" name="${idName}" id="${idName}-sel"> <select class="form-control" name="${idName}" id="${idName}-sel" class="mb-2">
<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 w-full" data-modal-close> <button class="btn btn-primary" data-modal-close class="w-full">
${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 flex-1" <button class="btn btn-sm btn-primary mod-foto-approve"
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> 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 text-danger" <button class="btn btn-sm btn-ghost mod-foto-reject"
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> 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>
</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 text-success" ? `<button class="btn btn-sm btn-ghost mod-unban"
data-uid="${u.id}" data-name="${UI.escape(u.name)}" data-uid="${u.id}" data-name="${UI.escape(u.name)}"
title="Sperre aufheben"> title="Sperre aufheben" class="text-success">
${UI.icon('lock-open')} ${UI.icon('lock-open')}
</button>` </button>`
: `<button class="btn btn-sm btn-ghost mod-ban text-danger" : `<button class="btn btn-sm btn-ghost mod-ban"
data-uid="${u.id}" data-name="${UI.escape(u.name)}" data-uid="${u.id}" data-name="${UI.escape(u.name)}"
title="Sperren"> title="Sperren" class="text-danger">
${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 w-full" id="ob-next-btn"> <button class="btn btn-primary" id="ob-next-btn" class="w-full">
<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 w-full" id="ob-skip-btn"> <button class="btn btn-ghost" id="ob-skip-btn" class="w-full">
Ü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 flex-1" id="ob-save-btn" <button type="submit" form="ob-dog-form" class="btn btn-primary" 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 text-sm" id="ob-skip-btn"> <button class="btn btn-ghost" id="ob-skip-btn" class="text-sm">
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 w-full" id="ob-diary-btn"> <button class="btn btn-primary" id="ob-diary-btn" class="w-full">
<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 w-full" id="ob-profile-btn"> <button class="btn btn-secondary" id="ob-profile-btn" class="w-full">
<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 mb-4" id="playdate-tabs"> <div class="by-tabs" id="playdate-tabs" class="mb-4">
<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 flex-1" id="poison-btn-erstehilfe"> <button class="btn btn-secondary" id="poison-btn-erstehilfe" class="flex-1">
${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 text-success" aria-hidden="true"><use href="/icons/phosphor.svg#check-circle"></use></svg> Bestätigt</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>' : ''}
`); `);
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 text-success" aria-hidden="true"><use href="/icons/phosphor.svg#check-circle"></use></svg> Bestätigt</span>' ? '<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 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 text-success" aria-hidden="true"><use href="/icons/phosphor.svg#check-circle"></use></svg> Bestätigt</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>' : ''}
</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 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-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-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 hidden" id="rk-mine-group"> <div class="rk-filter-group" id="rk-mine-group" class="hidden">
<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 hidden" id="rk-nearby-group"> <div class="rk-filter-group" id="rk-nearby-group" class="hidden">
<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.classList.toggle('hidden', !hasFilter); // .hidden hat !important → nur classList badge.style.display = hasFilter ? '' : 'none';
} }
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.classList.add('hidden'); if (mineGrp) mineGrp.style.display = 'none';
if (nearbyGrp) nearbyGrp.classList.add('hidden'); if (nearbyGrp) nearbyGrp.style.display = 'none';
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.classList.add('hidden'); if (mineGrp) mineGrp.style.display = 'none';
if (nearbyGrp && _userPos) nearbyGrp.classList.remove('hidden'); if (nearbyGrp && _userPos) nearbyGrp.style.display = '';
} 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.classList.remove('hidden'); if (_appState.user && mineGrp) mineGrp.style.display = '';
if (nearbyGrp) nearbyGrp.classList.add('hidden'); if (nearbyGrp) nearbyGrp.style.display = 'none';
} }
_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')?.classList.remove('hidden'); document.getElementById('rk-mine-group')?.style.setProperty('display', '');
if (_browseMode === 'discover' && _userPos) if (_browseMode === 'discover' && _userPos)
document.getElementById('rk-nearby-group')?.classList.remove('hidden'); document.getElementById('rk-nearby-group')?.style.setProperty('display', '');
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 mb-4" id="settings-stats-card"> <div class="card" id="settings-stats-card" class="mb-4">
<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 mt-3" id="breeder-edit-profile-btn"> <button class="btn btn-secondary btn-sm" id="breeder-edit-profile-btn" class="mt-3">
${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 mt-3" id="breeder-admin-create-btn"> <button class="btn btn-primary btn-sm" id="breeder-admin-create-btn" class="mt-3">
${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 mt-3" id="breeder-edit-profile-btn"> <button class="btn btn-secondary btn-sm" id="breeder-edit-profile-btn" class="mt-3">
${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 w-full" id="breeder-apply-submit" <button type="submit" form="breeder-apply-form" class="btn btn-primary" id="breeder-apply-submit"
>Antrag einreichen</button> class="w-full">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 w-full" type="submit" form="${id}" id="sit-profil-submit"> <button class="btn btn-primary" type="submit" form="${id}" id="sit-profil-submit" class="w-full">
${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,10 +202,9 @@ 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));
// .hidden hat !important → nur per classList togglen (style.display verliert immer) document.querySelectorAll('.walks-tab-panel').forEach(p => p.style.display = 'none');
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.classList.remove('hidden'); if (panel) panel.style.display = '';
if (tab === 'challenge' && !_challengeData) _loadChallenge(); if (tab === 'challenge' && !_challengeData) _loadChallenge();
if (tab === 'stamm' && !_gassiZeiten.length) _loadGassiZeiten(); if (tab === 'stamm' && !_gassiZeiten.length) _loadGassiZeiten();
@ -1059,7 +1058,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 mt-3" id="challenge-submit-btn">${UI.icon('camera')} Foto einreichen</button>` : ''} ${canSubmit ? `<button class="btn btn-primary btn-sm" id="challenge-submit-btn" class="mt-3">${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 flex-1" id="inst-tab-ios"> <button class="btn btn-sm btn-ghost" id="inst-tab-ios" class="flex-1">
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').classList.remove('hidden'); _container.querySelector('#inst-panel-android').style.display = '';
_container.querySelector('#inst-panel-ios').classList.add('hidden'); _container.querySelector('#inst-panel-ios').style.display = 'none';
_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').classList.add('hidden'); _container.querySelector('#inst-panel-android').style.display = 'none';
_container.querySelector('#inst-panel-ios').classList.remove('hidden'); _container.querySelector('#inst-panel-ios').style.display = '';
_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.classList.toggle('hidden', !subs.length); } if (badge) { badge.textContent = subs.length; badge.style.display = subs.length ? '' : 'none'; }
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.classList.toggle('hidden', !n); badge.textContent = n; badge.style.display = n ? '' : 'none';
} }
} 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 text-sm" id="wiki-rasse-erkennen-btn" <button class="btn btn-secondary w-full" 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 mt-3" id="wiki-zuchter-add-btn"> <button class="btn btn-secondary btn-sm" id="wiki-zuchter-add-btn" class="mt-3">
+ 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 mt-3" id="wiki-bericht-add-btn">+ Eigenen Bericht hinzufügen</button>` ? `<button class="btn btn-secondary w-full" id="wiki-bericht-add-btn" class="mt-3">+ 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 text-sm" id="wiki-foto-submit-btn"> <button class="btn btn-ghost w-full" id="wiki-foto-submit-btn" class="text-sm">
${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,8 +878,7 @@ 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 = '';
const fb = document.getElementById('wiki-photo-fallback'); document.getElementById('wiki-photo-fallback').style.display = 'none';
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));
}); });
}); });
@ -1256,7 +1255,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 mt-2" data-slug="${UI.escape(r.slug)}">Mehr erfahren</button> <button class="btn btn-secondary btn-sm wiki-quiz-mehr" data-slug="${UI.escape(r.slug)}" class="mt-2">Mehr erfahren</button>
</div> </div>
</div> </div>
`; `;
@ -1269,7 +1268,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 mt-4" id="quiz-restart">Quiz neu starten</button> <button class="btn btn-secondary w-full" id="quiz-restart" class="mt-4">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 text-danger" data-id="${h.id}" <button class="btn btn-ghost btn-sm zh-delete-btn" data-id="${h.id}"
title="Löschen"> title="Löschen" class="text-danger">
${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 hidden" id="zh-section-${h.id}"></div> <div class="zh-section-wrap" id="zh-section-${h.id}" class="hidden"></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.classList.add('hidden'); // .hidden hat !important → nur classList if (wrap) wrap.style.display = 'none';
_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.classList.remove('hidden'); wrap.style.display = '';
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 text-danger" data-tid="${t.id}" title="Löschen" <button class="btn btn-ghost btn-xs zh-health-del-btn" data-tid="${t.id}" title="Löschen"
>${UI.icon('trash')}</button> class="text-danger">${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 text-danger" data-tid="${t.id}" title="Löschen" <button class="btn btn-ghost btn-xs zh-genetic-del-btn" data-tid="${t.id}" title="Löschen"
>${UI.icon('trash')}</button> class="text-danger">${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 text-danger" data-tid="${t.id}" title="Löschen" <button class="btn btn-ghost btn-xs zh-title-del-btn" data-tid="${t.id}" title="Löschen"
>${UI.icon('trash')}</button> class="text-danger">${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=1252"></script> <script src="/js/landing-init.js?v=1250"></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, direkt im Browser."> <meta name="description" content="Ban Yaro: Die kostenlose All-in-One Hunde-App für DACH. Tagebuch, Giftköder-Alarm, Training mit KI, Forum, Wurfbörse, Stammbaum, Inzucht-Check — DSGVO-konform, offline-fähig, ohne App Store.">
<meta name="keywords" content="Hunde App, Hunde Community, Wurfbörse, Züchter, Welpen kaufen, Stammbaum Hund, Inzuchtkoeffizient, Hundezucht, Impfpass Hund, Giftköder Alarm, Gassi Community, Hundetraining App, Hunde Forum, Hunde KI, Hundefilm Datenbank, Welpen Marktplatz"> <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 Web-App direkt im Browser. Kostenlos."> <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: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, direkt im Browser."> <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: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, direkt im Browser.", "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.",
"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 direkt in der App", "Integrierte Hilfe und FAQ ohne App Store",
"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">Läuft direkt im Browser</span> <span class="badge">Kein App Store nötig</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 — direkt im Browser.</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 — ohne App Store.</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>Direkt im Browser nutzbar (PWA)</td> <td>Kein App Store nötig (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>Direkt im Browser</h3> <h3>Kein App Store</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. Keine Installation, keine Kreditkarte.</p> <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>
<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 = '1252'; const VER = '1250';
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