Design-System Sprint A: utilities.css + 948 Inline-Styles → Utility-Klassen, SW by-v1102

PHASE 1 — Sofort-Cleanup ohne Risiko:
- Neue Datei utilities.css mit ~25 Klassen für häufige Kombinationen:
  * text-xs-muted, text-xs-secondary, text-sm-muted, text-sm-secondary
  * flex-gap-2/3, flex-col-gap-2/3/4, flex-center-gap-1/2/3
  * flex-between, flex-1-min, mb-1/3, mt-1/3
  * icon-xs/sm/md/lg, label-block, caption
- index.html bindet utilities.css ein
- mb-3/mt-3 ergänzt (waren in design-system.css unvollständig)

PHASE 2 — .by-tab Modifier für Vereinheitlichung:
- .by-tabs.grid (mit --tab-cols Variable für Admin/Health/etc.)
- .by-tabs.sticky (Desktop vertikale Tabs für Admin)
- .by-tabs.wrap (Zuchthunde, flex-wrap statt scroll)
- .by-tabs.separated (Sitting, mit eigenem Hintergrund + Border)

PHASE 3 — Inline-Style → Klassen-Migration (Python-Script):
- 948 Inline-Styles entfernt (5101 → 4153, -18%)
- 962 Migrationen über 47 Page-Dateien
- Top-Treffer: admin.js (180), health.js (67), dog-profile.js (67),
  litters.js (62), settings.js (61), zuchthunde.js (51)
- Patterns: text-muted, text-secondary, text-danger, text-xs-muted,
  text-sm-muted, grid-2 (Duplikat-Bug behoben!), flex-col-gap-3,
  p-3/4, mb-2/3/4, hidden, w-full, flex-1, ...
- Bewahrt bestehende class-Attribute (mergt korrekt)

Alle 19 Tests grün. Kein visueller Diff erwartet (gleiche Property-Werte).
This commit is contained in:
rene 2026-05-27 07:11:27 +02:00
parent 279f76714e
commit 459cd425f2
54 changed files with 1809 additions and 956 deletions

View file

@ -1 +1 @@
1101
1102

View file

@ -235,6 +235,45 @@
color: var(--c-primary);
}
/* ----- .by-tabs Modifier-Varianten ----------------------------- */
/* Grid-Layout (Admin/Health/Übungen — Desktop oft 2-3 Spalten) */
.by-tabs.grid {
display: grid;
grid-template-columns: repeat(var(--tab-cols, 4), minmax(0, 1fr));
overflow: visible;
gap: var(--space-2);
}
/* Flex-Wrap (Zuchthunde — Buttons brechen um statt zu scrollen) */
.by-tabs.wrap {
flex-wrap: wrap;
overflow-x: visible;
}
/* Separated — eigener Hintergrund + Border (Sitting) */
.by-tabs.separated {
padding: var(--space-3) var(--space-4) var(--space-2);
border-bottom: 1px solid var(--c-border);
background: var(--c-surface);
}
/* Sticky (Admin Desktop vertikal) — nur ab 1024px */
@media (min-width: 1024px) {
.by-tabs.sticky {
position: sticky;
top: var(--space-3);
flex-direction: column;
width: 190px;
gap: var(--space-1);
}
.by-tabs.sticky .by-tab {
justify-content: flex-start;
text-align: left;
padding: var(--space-2) var(--space-3);
}
}
/* ------------------------------------------------------------
4. BY-SECTION-LABEL + BY-TOOLBAR weitere gemeinsame Elemente
------------------------------------------------------------ */

View file

@ -0,0 +1,65 @@
/* ============================================================
BAN YARO Utility-Klassen für häufige Inline-Patterns
Ergänzt design-system.css (Single-Property-Utilities sind dort)
============================================================ */
/* ------------------------------------------------------------
Text + Farb-Kombinationen (häufigste Inline-Patterns)
------------------------------------------------------------ */
.text-xs-muted { font-size: var(--text-xs); color: var(--c-text-muted); }
.text-xs-secondary { font-size: var(--text-xs); color: var(--c-text-secondary); }
.text-sm-muted { font-size: var(--text-sm); color: var(--c-text-muted); }
.text-sm-secondary { font-size: var(--text-sm); color: var(--c-text-secondary); }
/* Caption = Mini-Label/Hinweis unter einem Wert */
.caption {
font-size: var(--text-xs);
color: var(--c-text-secondary);
margin-top: 2px;
}
/* ------------------------------------------------------------
Flex-Layouts (kombiniert)
------------------------------------------------------------ */
.flex-gap-2 { display: flex; gap: var(--space-2); }
.flex-gap-3 { display: flex; gap: var(--space-3); }
.flex-col-gap-2 { display: flex; flex-direction: column; gap: var(--space-2); }
.flex-col-gap-3 { display: flex; flex-direction: column; gap: var(--space-3); }
.flex-col-gap-4 { display: flex; flex-direction: column; gap: var(--space-4); }
.flex-center { display: flex; align-items: center; }
.flex-center-gap-1 { display: flex; align-items: center; gap: var(--space-1); }
.flex-center-gap-2 { display: flex; align-items: center; gap: var(--space-2); }
.flex-center-gap-3 { display: flex; align-items: center; gap: var(--space-3); }
.flex-between { display: flex; align-items: center; justify-content: space-between; }
.flex-between-gap-2 { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); }
/* min-width:0 + flex:1 — verhindert Overflow in Flex-Children */
.flex-1-min { flex: 1; min-width: 0; }
/* ------------------------------------------------------------
Spacing-Lücken in design-system.css füllen
------------------------------------------------------------ */
.mb-1 { margin-bottom: var(--space-1); }
.mb-3 { margin-bottom: var(--space-3); }
.mt-1 { margin-top: var(--space-1); }
.mt-3 { margin-top: var(--space-3); }
/* ------------------------------------------------------------
Icon-Größen (statt width:NNpx;height:NNpx inline)
------------------------------------------------------------ */
.icon-xs { width: 12px; height: 12px; }
.icon-sm { width: 14px; height: 14px; }
.icon-md { width: 18px; height: 18px; }
.icon-lg { width: 22px; height: 22px; }
/* ------------------------------------------------------------
Form-Helper
------------------------------------------------------------ */
.label-block {
display: block;
font-size: var(--text-sm);
font-weight: 600;
margin-bottom: var(--space-1);
}

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -270,7 +270,7 @@ window.Page_adoption = (() => {
content.innerHTML = `
<div style="text-align:center;padding:var(--space-8) var(--space-4)">
<div style="font-size:2.5rem;margin-bottom:var(--space-3)">🐾</div>
<h3 style="margin-bottom:var(--space-2)">Finde Hunde in deiner Nähe</h3>
<h3 class="mb-2">Finde Hunde in deiner Nähe</h3>
<p style="color:var(--c-text-secondary);margin-bottom:var(--space-5);max-width:320px;margin-inline:auto">
Erlaube den Zugriff auf deinen Standort oder gib eine PLZ ein, um Tierheim-Hunde
in deiner Umgebung zu finden.
@ -339,7 +339,7 @@ window.Page_adoption = (() => {
</p>
<a href="https://www.tierheimhelden.de/hunde/liste"
target="_blank" rel="noopener noreferrer"
class="btn btn-secondary" style="font-size:var(--text-sm)">
class="btn btn-secondary text-sm">
${UI.icon('arrow-square-out')} Tierheimhelden.de alle Hunde
</a>
</div>
@ -434,7 +434,7 @@ window.Page_adoption = (() => {
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);margin-bottom:var(--space-3)">
${shelters.length} Tierheim${shelters.length !== 1 ? 'e' : ''} im Umkreis von ${_radius} km
</p>
<div style="display:flex;flex-direction:column;gap:var(--space-2)">
<div class="flex-col-gap-2">
${shelters.map(s => _shelterRow(s)).join('')}
</div>
<div style="margin-top:var(--space-5);padding-top:var(--space-4);border-top:1px solid var(--c-border)">
@ -444,12 +444,12 @@ window.Page_adoption = (() => {
<div style="display:flex;gap:var(--space-2);flex-wrap:wrap">
<a href="https://www.tierheimhelden.de"
target="_blank" rel="noopener noreferrer"
class="btn btn-secondary btn-sm" style="font-size:var(--text-sm)">
class="btn btn-secondary btn-sm text-sm">
${UI.icon('arrow-square-out')} Tierheimhelden.de
</a>
<a href="https://www.tierschutz.com/tierheimsuche/"
target="_blank" rel="noopener noreferrer"
class="btn btn-secondary btn-sm" style="font-size:var(--text-sm)">
class="btn btn-secondary btn-sm text-sm">
${UI.icon('magnifying-glass')} tierschutz.com
</a>
</div>
@ -473,12 +473,12 @@ window.Page_adoption = (() => {
font-size:1.2rem">
🏠
</div>
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="font-weight:600;font-size:var(--text-sm);
white-space:nowrap;overflow:hidden;text-overflow:ellipsis">
${_esc(s.name)}
</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">
<div class="text-xs-secondary">
${_esc(s.plz)} ${_esc(s.stadt)}
</div>
</div>
@ -520,7 +520,7 @@ window.Page_adoption = (() => {
content.innerHTML = `
<div style="text-align:center;padding:var(--space-8) var(--space-4)">
<div style="font-size:2.5rem;margin-bottom:var(--space-3)">🐾</div>
<h3 style="margin-bottom:var(--space-2)">Noch keine Hunde zur Weitervermittlung</h3>
<h3 class="mb-2">Noch keine Hunde zur Weitervermittlung</h3>
<p style="color:var(--c-text-secondary);margin-bottom:var(--space-5);max-width:320px;margin-inline:auto">
Hier können Halter Hunde privat zur Weitervermittlung anbieten
zum Beispiel bei Umzug, Krankheit oder Allergie.
@ -530,7 +530,7 @@ window.Page_adoption = (() => {
${UI.icon('plus')} Hund zur Vermittlung anbieten
</button>
` : `
<p style="font-size:var(--text-sm);color:var(--c-text-secondary)">
<p class="text-sm-secondary">
Bitte anmelden, um ein Inserat zu erstellen.
</p>
`}
@ -556,8 +556,8 @@ window.Page_adoption = (() => {
${isLoggedIn && _myListings && _myListings.length ? `
<div id="adp-my-listings" style="margin-top:var(--space-6);padding-top:var(--space-4);border-top:1px solid var(--c-border)">
<h4 style="margin-bottom:var(--space-3)">Meine Inserate</h4>
<div style="display:flex;flex-direction:column;gap:var(--space-2)">
<h4 class="mb-3">Meine Inserate</h4>
<div class="flex-col-gap-2">
${_myListings.map(l => _myListingRow(l)).join('')}
</div>
</div>
@ -714,12 +714,12 @@ window.Page_adoption = (() => {
<div style="display:flex;align-items:center;gap:var(--space-2);
padding:var(--space-2) var(--space-3);border-radius:var(--radius-md);
background:var(--c-surface-2);border:1px solid var(--c-border)">
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="font-weight:600;font-size:var(--text-sm);
white-space:nowrap;overflow:hidden;text-overflow:ellipsis">
${_esc(l.name)}
</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">
<div class="text-xs-secondary">
${l.interesse_count || 0} Interessent${(l.interesse_count || 0) !== 1 ? 'en' : ''}
</div>
</div>
@ -764,7 +764,7 @@ window.Page_adoption = (() => {
// Interesse bekunden — Modal mit optionaler Nachricht
const body = `
<form id="adp-interest-form" style="display:flex;flex-direction:column;gap:var(--space-3)">
<form id="adp-interest-form" class="flex-col-gap-3">
<p style="color:var(--c-text-secondary);font-size:var(--text-sm)">
Du kannst optional eine Nachricht an den Anbieter schicken.
</p>
@ -816,9 +816,9 @@ window.Page_adoption = (() => {
}
const body = `
<form id="adp-create-form" style="display:flex;flex-direction:column;gap:var(--space-3)">
<form id="adp-create-form" class="flex-col-gap-3">
<div class="form-group">
<label class="form-label">Name <span style="color:var(--c-danger)">*</span></label>
<label class="form-label">Name <span class="text-danger">*</span></label>
<input class="form-control" name="name" required placeholder="z.B. Bello">
</div>
<div class="form-group">
@ -857,7 +857,7 @@ window.Page_adoption = (() => {
</div>
</div>
<div class="form-group">
<label class="form-label">Beschreibung <span style="color:var(--c-danger)">*</span></label>
<label class="form-label">Beschreibung <span class="text-danger">*</span></label>
<textarea class="form-control" name="beschreibung" rows="4" required minlength="80"
placeholder="Erzähle, warum du deinen Hund abgeben musst, und was ihn besonders macht…"></textarea>
<div style="font-size:10px;color:var(--c-text-muted);margin-top:2px">Mindestens 80 Zeichen</div>
@ -876,7 +876,7 @@ window.Page_adoption = (() => {
const footer = `
<div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%">
<button type="submit" form="adp-create-form" class="btn btn-primary" style="width:100%" id="adp-create-submit">
<button type="submit" form="adp-create-form" class="btn btn-primary w-full" id="adp-create-submit">
${UI.icon('plus')} Inserat erstellen
</button>
<button type="button" class="btn btn-secondary" onclick="UI.modal.close()">Abbrechen</button>

View file

@ -0,0 +1,322 @@
/* ============================================================
BAN YARO Züchter-Profil-Editor
Selbstverwaltung des öffentlichen Züchter-Profils.
============================================================ */
window.Page_breeder_editor = (() => {
let _container = null;
let _data = null; // { profile, litters, storage_mb, storage_limit_mb }
async function init(container) {
_container = container;
_container.innerHTML = `<div style="max-width:680px;margin:0 auto;padding:var(--space-4)">${UI.skeleton(5)}</div>`;
await _load();
}
function refresh() { _load(); }
function onDogChange() {}
async function _load() {
try {
_data = await API.get('/breeder/my-editor');
_render();
} catch (e) {
_container.innerHTML = `<div style="padding:var(--space-6);color:var(--c-danger)">${e.message}</div>`;
}
}
function _render() {
const { profile: p, litters, storage_mb, storage_limit_mb } = _data;
_container.innerHTML = `
<div style="max-width:680px;margin:0 auto;padding:var(--space-4)">
<div style="margin-bottom:var(--space-5)">
<h1 style="font-size:var(--text-xl);font-weight:800;margin:0 0 var(--space-1)">Mein Züchter-Profil</h1>
<p style="color:var(--c-text-secondary);font-size:var(--text-sm);margin:0">
Gestalte deine öffentliche Profilseite Fotos, Videos und Infos zu deinen Würfen.
</p>
</div>
<!-- Logo & Grundinfos -->
<div class="card" style="padding:var(--space-4);margin-bottom:var(--space-3)">
<div style="font-size:var(--text-xs);font-weight:700;text-transform:uppercase;
letter-spacing:.06em;color:var(--c-text-muted);margin-bottom:var(--space-3)">Logo / Titelbild</div>
<div style="display:flex;align-items:center;gap:var(--space-4)">
<div id="be-logo-preview" style="width:80px;height:80px;border-radius:var(--radius-md);
background:var(--c-surface-2);overflow:hidden;flex-shrink:0;
display:flex;align-items:center;justify-content:center">
${p.logo_url
? `<img src="${_esc(p.logo_url)}" style="width:100%;height:100%;object-fit:cover">`
: `<svg class="ph-icon" style="width:32px;height:32px;opacity:.3"><use href="/icons/phosphor.svg#image"></use></svg>`}
</div>
<div>
<label class="btn btn-secondary btn-sm" style="cursor:pointer">
Logo hochladen
<input type="file" id="be-logo-input" accept="image/*" class="hidden">
</label>
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:var(--space-1)">
Quadratisch · max. 5 MB · HEIC wird unterstützt
</div>
</div>
</div>
</div>
<!-- Profil-Texte -->
<div class="card" style="padding:var(--space-4);margin-bottom:var(--space-3)">
<div style="font-size:var(--text-xs);font-weight:700;text-transform:uppercase;
letter-spacing:.06em;color:var(--c-text-muted);margin-bottom:var(--space-3)">Profil-Texte</div>
<form id="be-text-form" class="flex-col-gap-3">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Zwingername *</label>
<input class="form-control" name="zwingername" type="text" required
value="${_esc(p.zwingername || '')}">
</div>
<div class="form-group">
<label class="form-label">Rasse(n)</label>
<input class="form-control" name="rasse_text" type="text"
value="${_esc(p.rasse_text || '')}">
</div>
</div>
<div class="form-group">
<label class="form-label">Slogan <span style="font-weight:400;color:var(--c-text-muted)">(max. 80 Zeichen)</span></label>
<input class="form-control" name="tagline" type="text" maxlength="80"
placeholder="z. B. Liebevolle Aufzucht seit 2010 · VDH-anerkannt"
value="${_esc(p.tagline || '')}">
</div>
<div class="form-group">
<label class="form-label">Über uns / Zwingerbeschreibung</label>
<textarea class="form-control" name="beschreibung" rows="4" maxlength="800"
placeholder="Wer seid ihr, was ist euch bei der Zucht wichtig?">${_esc(p.beschreibung || '')}</textarea>
</div>
<div class="grid-2">
<div class="form-group">
<label class="form-label">Stadt</label>
<input class="form-control" name="stadt" type="text" value="${_esc(p.stadt || '')}">
</div>
<div class="form-group">
<label class="form-label">Verein</label>
<input class="form-control" name="verein" type="text" value="${_esc(p.verein || '')}">
</div>
</div>
<div class="grid-2">
<div class="form-group">
<label class="form-label">Website</label>
<input class="form-control" name="website" type="url"
placeholder="https://" value="${_esc(p.website || '')}">
</div>
<div class="form-group">
<label class="form-label">Instagram</label>
<input class="form-control" name="instagram" type="text"
placeholder="@zwingername" value="${_esc(p.instagram || '')}">
</div>
</div>
<button type="submit" class="btn btn-secondary btn-sm" style="align-self:flex-start">
Profil speichern
</button>
</form>
</div>
<!-- Profil-Fotos & Videos -->
<div class="card" style="padding:var(--space-4);margin-bottom:var(--space-3)">
<div style="display:flex;align-items:baseline;justify-content:space-between;margin-bottom:var(--space-2)">
<div style="font-size:var(--text-xs);font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--c-text-muted)">
Profil-Fotos & Videos
</div>
</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:var(--space-2)">
JPG, PNG, HEIC, MP4, MOV · max. 200 MB pro Datei
</div>
${_storageBar(storage_mb, storage_limit_mb)}
<div id="be-photos-grid" style="display:grid;grid-template-columns:repeat(3,1fr);gap:var(--space-2);margin:var(--space-3) 0">
${_renderPhotoGrid(p.photos || [])}
</div>
<label class="btn btn-secondary btn-sm" style="cursor:pointer;display:inline-flex;align-items:center;gap:6px">
<svg class="ph-icon" style="width:16px;height:16px"><use href="/icons/phosphor.svg#plus"></use></svg>
Foto / Video hinzufügen
<input type="file" id="be-profile-photo-input" accept="image/*,video/*" class="hidden">
</label>
</div>
<!-- Würfe Schnellupload -->
${litters.length ? `
<div class="card" style="padding:var(--space-4);margin-bottom:var(--space-3)">
<div style="font-size:var(--text-xs);font-weight:700;text-transform:uppercase;
letter-spacing:.06em;color:var(--c-text-muted);margin-bottom:var(--space-3)">
Aktuelle Würfe Fotos & Videos
</div>
<div class="flex-col-gap-3">
${litters.map(l => _renderLitterCard(l)).join('')}
</div>
</div>` : ''}
</div>
`;
_bindEvents();
}
function _renderPhotoGrid(photos) {
return photos.map((ph, i) => {
const isVid = ph.media_type === 'video' || (ph.url || '').endsWith('.mp4');
return `
<div style="position:relative;aspect-ratio:1;border-radius:var(--radius-md);overflow:hidden;background:var(--c-surface-2)">
${isVid
? `<video src="${_esc(ph.url)}" style="width:100%;height:100%;object-fit:cover" muted playsinline loop
onmouseenter="this.play()" onmouseleave="this.pause()"></video>
<div style="position:absolute;bottom:4px;left:4px;background:rgba(0,0,0,.55);border-radius:4px;padding:1px 5px;font-size:10px;color:#fff"> Video</div>`
: `<img src="${_esc(ph.thumbnail_url || ph.url)}" style="width:100%;height:100%;object-fit:cover">`}
${ph.is_primary ? `<div style="position:absolute;top:4px;left:4px;background:rgba(196,132,58,.9);border-radius:3px;padding:1px 5px;font-size:9px;color:#fff;font-weight:700">LOGO</div>` : ''}
<button class="be-photo-del" data-id="${ph.id}"
style="position:absolute;top:4px;right:4px;background:rgba(0,0,0,.6);
border:none;border-radius:50%;width:24px;height:24px;cursor:pointer;
color:#fff;font-size:14px;display:flex;align-items:center;justify-content:center">×</button>
${!ph.is_primary ? `<button class="be-photo-primary" data-id="${ph.id}"
title="Als Logo setzen"
style="position:absolute;bottom:4px;right:4px;background:rgba(0,0,0,.55);
border:none;border-radius:3px;padding:1px 5px;font-size:9px;cursor:pointer;color:#fff">Logo</button>` : ''}
</div>`;
}).join('');
}
function _renderLitterCard(l) {
const label = l.geburtsdatum
? `Wurf vom ${new Date(l.geburtsdatum).toLocaleDateString('de-DE')}`
: `Wurf #${l.id}`;
const info = [
l.welpen_gesamt ? `${l.welpen_gesamt} Welpen` : null,
`${l.foto_count} Medien`,
].filter(Boolean).join(' · ');
return `
<div style="border:1px solid var(--c-border);border-radius:var(--radius-md);padding:var(--space-3)">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--space-2)">
<div>
<div style="font-weight:700;font-size:var(--text-sm)">${_esc(label)}</div>
<div class="text-xs-muted">${info}</div>
</div>
<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="${_esc(label)}" accept="image/*,video/*" class="hidden">
</label>
</div>
</div>`;
}
function _storageBar(usedMb, limitMb) {
const pct = Math.min(100, Math.round((usedMb / limitMb) * 100));
const color = pct > 85 ? '#dc2626' : pct > 60 ? '#f59e0b' : '#22c55e';
return `
<div style="display:flex;align-items:center;gap:var(--space-2);font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:var(--space-2)">
<div style="flex:1;height:4px;background:var(--c-surface-2);border-radius:2px;overflow:hidden">
<div style="width:${pct}%;height:100%;background:${color};border-radius:2px"></div>
</div>
<span style="white-space:nowrap">${usedMb.toFixed(1)} / ${limitMb} MB</span>
</div>`;
}
function _bindEvents() {
const el = _container;
// Logo hochladen
el.querySelector('#be-logo-input')?.addEventListener('change', async e => {
const file = e.target.files[0];
if (!file) return;
const fd = new FormData();
fd.append('file', file);
fd.append('entity_type', 'breeder');
fd.append('entity_id', String(_data.profile.id));
fd.append('is_primary', '1');
fd.append('visibility', 'public');
try {
await API.breederPhotos.upload(fd);
UI.toast.success('Logo gespeichert.');
await _load();
} catch (err) { UI.toast.error(err.message); }
});
// Profil-Texte speichern
el.querySelector('#be-text-form')?.addEventListener('submit', async e => {
e.preventDefault();
const btn = e.target.querySelector('[type="submit"]');
const fd = UI.formData(e.target);
await UI.asyncButton(btn, async () => {
await API.put('/breeder/profile', fd);
_data.profile = { ..._data.profile, ...fd };
UI.toast.success('Profil gespeichert.');
});
});
// Profil-Foto/-Video hochladen
el.querySelector('#be-profile-photo-input')?.addEventListener('change', async e => {
const file = e.target.files[0];
if (!file) return;
const isVideo = file.type.startsWith('video/');
if (isVideo) UI.toast.info('Video wird komprimiert das kann 12 Minuten dauern …', 120_000);
const fd = new FormData();
fd.append('file', file);
fd.append('entity_type', 'breeder');
fd.append('entity_id', String(_data.profile.id));
fd.append('visibility', 'public');
try {
await API.breederPhotos.upload(fd);
UI.toast.success(isVideo ? 'Video hinzugefügt.' : 'Foto hinzugefügt.');
await _load();
} catch (err) { UI.toast.error(err.message); }
});
// Foto löschen
el.querySelectorAll('.be-photo-del').forEach(btn => {
btn.addEventListener('click', async () => {
if (!confirm('Löschen?')) return;
try {
await API.breederPhotos.remove(parseInt(btn.dataset.id));
await _load();
} catch (err) { UI.toast.error(err.message); }
});
});
// Als Logo setzen
el.querySelectorAll('.be-photo-primary').forEach(btn => {
btn.addEventListener('click', async () => {
try {
await API.patch(`/breeder/photos/${btn.dataset.id}/primary`, {});
await _load();
} catch (err) { UI.toast.error(err.message); }
});
});
// Wurf-Upload
el.querySelectorAll('.be-litter-input').forEach(input => {
input.addEventListener('change', async e => {
const file = e.target.files[0];
if (!file) return;
const isVideo = file.type.startsWith('video/');
const litterId = input.dataset.litterId;
const label = input.dataset.label;
if (isVideo) UI.toast.info('Video wird komprimiert das kann 12 Minuten dauern …', 120_000);
const fd = new FormData();
fd.append('file', file);
fd.append('entity_type', 'litter');
fd.append('entity_id', litterId);
fd.append('visibility', 'public');
try {
await API.breederPhotos.upload(fd);
UI.toast.success(`${isVideo ? 'Video' : 'Foto'} zu „${label}" hinzugefügt.`);
// Foto-Count aktualisieren
const litter = _data.litters.find(l => String(l.id) === String(litterId));
if (litter) litter.foto_count++;
_render();
} catch (err) { UI.toast.error(err.message); }
});
});
}
function _esc(s) {
return String(s ?? '').replace(/[&<>"']/g, c =>
({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
}
return { init, refresh, onDogChange };
})();

View file

@ -75,7 +75,7 @@ window.Page_breeder = (() => {
padding:var(--space-6) var(--space-4) var(--space-8);color:white;position:relative">
<div style="max-width:640px;margin:0 auto">
<div style="display:flex;align-items:flex-start;gap:var(--space-3);flex-wrap:wrap">
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<p style="margin:0 0 var(--space-1);font-size:var(--text-xs);opacity:.7;text-transform:uppercase;letter-spacing:.1em">
${UI.icon('seal-check')} Verifizierter Züchter
</p>
@ -157,7 +157,7 @@ window.Page_breeder = (() => {
display:flex;align-items:center;gap:var(--space-2)">
${UI.icon('baby')} Aktuelle Würfe
</h2>
<div style="display:flex;flex-direction:column;gap:var(--space-3)">
<div class="flex-col-gap-3">
${p.wuerfe.map(w => _wurfCard(w)).join('')}
</div>
</div>` : ''}
@ -201,7 +201,7 @@ window.Page_breeder = (() => {
<!-- Fotos / Gallery -->
${p.fotos?.length ? `
<div style="margin-bottom:var(--space-4)">
<div class="mb-4">
<h2 style="margin:0 0 var(--space-3);font-size:var(--text-base);font-weight:700;
display:flex;align-items:center;gap:var(--space-2)">
${UI.icon('images')} Galerie
@ -226,7 +226,7 @@ window.Page_breeder = (() => {
</div>
</div>` : ''}
<div id="breeder-photos-section" style="display:none"></div>
<div id="breeder-photos-section" class="hidden"></div>
</div>`;
@ -262,7 +262,7 @@ window.Page_breeder = (() => {
).join('');
const genBadge = h.gentests_total > 0
? `<span style="font-size:var(--text-xs);color:var(--c-text-muted)">
? `<span class="text-xs-muted">
${h.gentests_clear}/${h.gentests_total} Gentests frei
</span>`
: '';
@ -271,7 +271,7 @@ window.Page_breeder = (() => {
<div style="background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--radius-lg);
padding:var(--space-3);display:flex;flex-direction:column;gap:var(--space-2)">
<div style="display:flex;align-items:center;gap:var(--space-2)">
<span style="color:var(--c-primary)">${gIcon}</span>
<span class="text-primary">${gIcon}</span>
<span style="font-weight:700;font-size:var(--text-sm)">${_esc(h.name)}</span>
${h.rufname ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">"${_esc(h.rufname)}"</span>` : ''}
${alter !== null ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs);margin-left:auto">${alter} J.</span>` : ''}
@ -345,7 +345,7 @@ window.Page_breeder = (() => {
${stats.map(r => `
<div style="display:flex;align-items:center;gap:6px;font-size:var(--text-sm)">
<span style="font-weight:700">${_esc(r.ergebnis || '—')}</span>
<span style="color:var(--c-text-muted)">${r.cnt}×</span>
<span class="text-muted">${r.cnt}×</span>
<span style="background:var(--c-border);border-radius:999px;height:6px;
width:${Math.round(r.cnt/total*80)+16}px;display:inline-block"></span>
</div>`).join('')}
@ -377,7 +377,7 @@ window.Page_breeder = (() => {
const photos = await API.breederPhotos.list('breeder', breederId);
if (!photos?.length) return;
section.innerHTML = `
<div style="margin-bottom:var(--space-4)">
<div class="mb-4">
<h2 style="margin:0 0 var(--space-3);font-size:var(--text-base);font-weight:700">
${UI.icon('images')} Fotos
</h2>

View file

@ -178,7 +178,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" style="display:none"></span>
<span class="online-dot chat-avatar-dot" id="chat-partner-dot" class="hidden"></span>
</div>
<span class="chat-thread-partner" id="chat-partner-name"></span>
</div>
@ -188,7 +188,7 @@ window.Page_chat = (() => {
</div>
</div>
<div class="chat-input-bar">
<input type="file" id="chat-photo-input" accept="image/*" style="display:none"
<input type="file" id="chat-photo-input" accept="image/*" class="hidden"
onchange="Page_chat._onPhotoSelected(this)">
<button class="chat-photo-btn" onclick="document.getElementById('chat-photo-input').click()" title="Foto senden">
<svg class="ph-icon"><use href="/icons/phosphor.svg#camera"></use></svg>

View file

@ -212,7 +212,7 @@ window.Page_diary = (() => {
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#download-simple"></use></svg>
</button>
</div>
<div id="diary-stats-bar" class="diary-stats-bar" style="display:none"></div>
<div id="diary-stats-bar" class="diary-stats-bar hidden"></div>
<div id="diary-view-content">
<div id="diary-list"></div>
</div>
@ -295,7 +295,7 @@ window.Page_diary = (() => {
`;
card.innerHTML = `
<div style="font-size:1.8rem;flex-shrink:0;line-height:1">🐾</div>
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="font-size:var(--text-xs);font-weight:var(--weight-semibold);
color:var(--c-primary-dark);text-transform:uppercase;
letter-spacing:.06em;margin-bottom:var(--space-1)">
@ -963,7 +963,7 @@ window.Page_diary = (() => {
// Hunde-Chips (bei mehreren Hunden)
const dogsHtml = dogIds.length > 1
? `<div class="diary-detail-dogs" style="margin-bottom:var(--space-3)">
? `<div class="diary-detail-dogs mb-3">
${dogIds.map(did => {
const dog = _appState.dogs.find(d => d.id === did);
return dog ? `<div class="diary-dog-chip">
@ -1279,7 +1279,7 @@ window.Page_diary = (() => {
value="${entry?.datum || today}" required>
</div>
<div class="form-group">
<label class="form-label">Titel <span style="color:var(--c-text-secondary)">(optional)</span></label>
<label class="form-label">Titel <span class="text-secondary">(optional)</span></label>
<input class="form-control" type="text" name="titel"
value="${UI.escape(entry?.titel || '')}" placeholder="z.B. Erster Schultag">
</div>
@ -1293,10 +1293,10 @@ window.Page_diary = (() => {
<div id="diary-existing-media"></div>
<!-- Neue Medien: Vorschau-Grid -->
<div id="diary-new-media-grid" class="diary-media-grid" style="display:none"></div>
<div id="diary-new-media-grid" class="diary-media-grid hidden"></div>
<!-- versteckter Input multiple für Mehrfachauswahl -->
<input type="file" id="diary-media-input" accept="image/*,video/*,application/pdf" multiple style="display:none">
<input type="file" id="diary-media-input" accept="image/*,video/*,application/pdf" multiple class="hidden">
<!-- Einzelner Button iOS zeigt nativen Picker (Mediathek / Kamera / Datei) -->
<label for="diary-media-input" class="btn btn-secondary" style="cursor:pointer;display:flex;align-items:center;gap:var(--space-2);justify-content:center">
@ -1305,7 +1305,7 @@ window.Page_diary = (() => {
</label>
</div>
<div class="form-group" id="diary-location-group">
<label class="form-label">Ort <span style="color:var(--c-text-secondary)">(optional)</span></label>
<label class="form-label">Ort <span class="text-secondary">(optional)</span></label>
<!-- Karte (Lesemodus, Edit per Button aktivierbar) -->
<div style="position:relative">
@ -1318,7 +1318,7 @@ window.Page_diary = (() => {
</div>
<!-- POI-Name + Aktionen -->
<div style="margin-top:var(--space-2)">
<div class="mt-2">
<div id="diary-location-chip-wrap" style="${entry?.location_name ? '' : 'display:none'}">
<div class="diary-location-chip">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-pin"></use></svg>
@ -1341,7 +1341,7 @@ window.Page_diary = (() => {
${dogPickerHtml}
<div class="form-group" style="margin-top:var(--space-5)">
<input type="checkbox" name="is_milestone" id="diary-milestone-cb"
${entry?.is_milestone ? 'checked' : ''} style="display:none">
${entry?.is_milestone ? 'checked' : ''} class="hidden">
<button type="button" id="diary-milestone-btn"
class="diary-milestone-toggle${entry?.is_milestone ? ' diary-milestone-toggle--active' : ''}">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#trophy"></use></svg>
@ -1353,10 +1353,10 @@ window.Page_diary = (() => {
const footer = `
<div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%">
<button type="submit" form="diary-form" class="btn btn-primary" style="width:100%">
<button type="submit" form="diary-form" class="btn btn-primary w-full">
${isEdit ? 'Speichern' : 'Erstellen'}
</button>
<div style="display:flex;gap:var(--space-2)">
<div class="flex-gap-2">
${isEdit ? `<button type="button" class="btn btn-danger" id="diary-form-delete">Löschen</button>` : ''}
<button type="button" class="btn btn-secondary flex-1" id="diary-form-cancel">Abbrechen</button>
</div>
@ -1843,32 +1843,32 @@ window.Page_diary = (() => {
<strong>${UI.escape(_appState.activeDog?.name || 'deinem Hund')}</strong>.
</p>
<div style="display:flex;flex-direction:column;gap:var(--space-3)">
<div class="flex-col-gap-3">
<label class="import-format-card" id="fmt-nsx">
<input type="radio" name="import-fmt" value="nsx" checked style="display:none">
<input type="radio" name="import-fmt" value="nsx" checked class="hidden">
<div class="import-format-icon">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note"></use></svg>
</div>
<div>
<div style="font-weight:var(--weight-semibold)">Synology NoteStation</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">.nsx-Datei aus dem NoteStation-Export</div>
<div class="text-xs-muted">.nsx-Datei aus dem NoteStation-Export</div>
</div>
</label>
<label class="import-format-card" id="fmt-csv">
<input type="radio" name="import-fmt" value="csv" style="display:none">
<input type="radio" name="import-fmt" value="csv" class="hidden">
<div class="import-format-icon">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#file-csv"></use></svg>
</div>
<div>
<div style="font-weight:var(--weight-semibold)">CSV / Excel</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">Spalten: datum, titel, text, tags, gps_lat, gps_lon, is_milestone</div>
<div class="text-xs-muted">Spalten: datum, titel, text, tags, gps_lat, gps_lon, is_milestone</div>
</div>
</label>
</div>
<div style="margin-top:var(--space-4)">
<div class="mt-4">
<label class="form-label">Datei auswählen</label>
<input type="file" class="form-control" id="import-file-input"
accept=".nsx,.csv" style="cursor:pointer">
@ -1917,7 +1917,7 @@ window.Page_diary = (() => {
: await API.importData.csv(dogId, file);
const errHtml = res.errors?.length
? `<details style="margin-top:var(--space-2)"><summary style="font-size:var(--text-xs);cursor:pointer">${res.errors.length} Fehler anzeigen</summary>
? `<details class="mt-2"><summary style="font-size:var(--text-xs);cursor:pointer">${res.errors.length} Fehler anzeigen</summary>
<pre style="font-size:var(--text-xs);white-space:pre-wrap;margin-top:var(--space-1)">${UI.escape(res.errors.join('\n'))}</pre></details>`
: '';
@ -1925,7 +1925,7 @@ window.Page_diary = (() => {
<div style="background:var(--c-success-subtle);border-radius:var(--radius-md);
padding:var(--space-3) var(--space-4);color:var(--c-success)">
<strong>${res.imported} Einträge importiert</strong>
${res.skipped ? `<span style="color:var(--c-text-muted);font-size:var(--text-sm)"> · ${res.skipped} übersprungen</span>` : ''}
${res.skipped ? `<span class="text-sm-muted"> · ${res.skipped} übersprungen</span>` : ''}
${errHtml}
</div>`;
resultEl.style.display = 'block';

View file

@ -101,22 +101,22 @@ window.Page_dog_profile = (() => {
: `<p style="margin:0 0 var(--space-2)"></p>`}
<!-- Rassen-Community-Chip (wird async geladen) -->
<div id="dp-same-breed-chip" style="margin-bottom:var(--space-4)"></div>
<div id="dp-same-breed-chip" class="mb-4"></div>
<!-- Info-Grid -->
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3);
margin-bottom:var(--space-5);text-align:left">
${geburtstag ? `
<div class="card" style="padding:var(--space-3)">
<div class="card p-3">
<div class="dp-info-label"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#calendar-dots"></use></svg> Geburtstag</div>
<div style="font-weight:500;font-size:var(--text-sm)">${geburtstag}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">
<div class="text-xs-secondary">
${_calcAlter(dog.geburtstag)}
</div>
</div>
` : ''}
${dog.geschlecht ? `
<div class="card" style="padding:var(--space-3)">
<div class="card p-3">
<div class="dp-info-label">${dog.geschlecht === 'm' ? '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#gender-male"></use></svg>' : '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#gender-female"></use></svg>'} Geschlecht</div>
<div style="font-weight:500;font-size:var(--text-sm)">
${dog.geschlecht === 'm' ? 'Rüde' : 'Hündin'}
@ -130,19 +130,19 @@ window.Page_dog_profile = (() => {
</div>
` : ''}
${dog.widerrist_cm ? `
<div class="card" style="padding:var(--space-3)">
<div class="card p-3">
<div class="dp-info-label"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#ruler"></use></svg> Widerrist</div>
<div style="font-weight:500;font-size:var(--text-sm)">${dog.widerrist_cm} cm</div>
</div>
` : ''}
<div class="card" style="padding:var(--space-3)">
<div class="card p-3">
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);
margin-bottom:2px">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#wave-sine"></use></svg> Transponder
</div>
${dog.chip_nr
? `<div style="font-size:var(--text-xs);font-weight:500;word-break:break-all">${_esc(dog.chip_nr)}</div>`
: `<div style="font-size:var(--text-xs);color:var(--c-text-muted)">nicht eingetragen
: `<div class="text-xs-muted">nicht eingetragen
<button class="btn btn-link btn-sm" id="dp-chip-edit-btn"
style="padding:0 0 0 var(--space-1);font-size:var(--text-xs)">Eintragen</button>
</div>`
@ -230,12 +230,12 @@ window.Page_dog_profile = (() => {
<div class="card" style="margin-bottom:var(--space-5)">
<div style="padding:var(--space-4);border-bottom:1px solid var(--c-border)">
<div style="font-weight:600">Sitter-Zugang</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">
<div class="text-xs-secondary">
Gib einem Freund temporären Schreibzugang für diesen Hund.
Deine bestehenden Daten und Medien bleiben unsichtbar und privat der Sitter kann nur neue Einträge anlegen.
</div>
</div>
<div id="dp-sitting-access" style="padding:var(--space-4)">Lade</div>
<div id="dp-sitting-access" class="p-4">Lade</div>
</div>
` : ''}
`;
@ -340,7 +340,7 @@ window.Page_dog_profile = (() => {
};
const sitztBlock = sitzt.length ? `
<div style="margin-bottom:var(--space-3)">
<div class="mb-3">
<div style="font-size:var(--text-xs);font-weight:var(--weight-semibold);
color:var(--c-text-secondary);margin-bottom:var(--space-2);
text-transform:uppercase;letter-spacing:.04em">Sitzt</div>
@ -360,7 +360,7 @@ window.Page_dog_profile = (() => {
</div>` : '';
el.innerHTML = `
<div class="card" style="padding:var(--space-4)">
<div class="card p-4">
<div style="display:flex;align-items:center;gap:var(--space-2);margin-bottom:var(--space-3)">
<svg class="ph-icon" style="width:16px;height:16px;color:var(--c-primary)" aria-hidden="true">
<use href="/icons/phosphor.svg#list-checks"></use>
@ -409,7 +409,7 @@ window.Page_dog_profile = (() => {
: '';
el.innerHTML = `
<div class="card" style="padding:var(--space-4)">
<div class="card p-4">
<div style="display:flex;align-items:center;gap:var(--space-2);margin-bottom:var(--space-3)">
<span style="font-size:1.1em">🛁</span>
<span style="font-size:var(--text-sm);font-weight:600">
@ -457,7 +457,7 @@ window.Page_dog_profile = (() => {
const katTipps = data.tipps.filter(t=>t.kategorie===kat);
const katBadge = kat === 'Fell' ? pflegeArtBadge : '';
return `
<div style="margin-bottom:var(--space-3)">
<div class="mb-3">
<div style="font-size:11px;font-weight:700;color:var(--c-text-muted);
text-transform:uppercase;margin-bottom:8px;display:flex;align-items:center">
${kat_icons[kat]||_ph('paw-print')} ${_esc(kat)}${katBadge}</div>
@ -528,7 +528,7 @@ window.Page_dog_profile = (() => {
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#user"></use></svg>
<div style="flex:1;font-size:var(--text-sm)">
<strong>${_esc(s.sitter_name)}</strong>
<span style="color:var(--c-text-muted)"> · bis ${_esc(s.valid_until)}</span>
<span class="text-muted"> · bis ${_esc(s.valid_until)}</span>
</div>
<button class="btn btn-link btn-sm sa-revoke-btn" data-sub-id="${s.id}"
style="color:var(--c-danger);padding:0">
@ -547,26 +547,26 @@ window.Page_dog_profile = (() => {
wrap.innerHTML = `
${activeHtml}
${friends.length ? `
<div style="margin-top:var(--space-3)">
<div class="mt-3">
<div style="font-size:var(--text-xs);color:var(--c-text-muted);
margin-bottom:var(--space-2);font-weight:600">Zugang gewähren</div>
<div style="display:grid;grid-template-columns:1fr auto;gap:var(--space-2);
align-items:end">
<div class="form-group" style="margin:0">
<label class="form-label" style="font-size:var(--text-xs)">Freund</label>
<label class="form-label text-xs">Freund</label>
<select class="form-control form-control-sm" id="sa-friend-select">
<option value="">Freund wählen</option>
${friendOptions}
</select>
</div>
<div class="form-group" style="margin:0">
<label class="form-label" style="font-size:var(--text-xs)">Gültig bis</label>
<label class="form-label text-xs">Gültig bis</label>
<input class="form-control form-control-sm" type="date" id="sa-until-input"
value="${defaultUntil}" min="${today}">
</div>
</div>
<button class="btn btn-primary btn-sm w-full" id="sa-grant-btn"
style="margin-top:var(--space-2)">
class="mt-2">
Zugang gewähren
</button>
</div>
@ -621,7 +621,7 @@ window.Page_dog_profile = (() => {
</div>`,
footer: `
<div class="w3-btn-stack">
<button class="btn btn-primary" id="chip-edit-save-btn" style="width:100%">Speichern</button>
<button class="btn btn-primary" id="chip-edit-save-btn" class="w-full">Speichern</button>
<button class="btn btn-secondary" onclick="UI.modal.close()">Abbrechen</button>
</div>`,
});
@ -666,20 +666,20 @@ window.Page_dog_profile = (() => {
<div class="photo-editor-controls">
<label class="form-label">Zoom</label>
<input type="range" id="pe-zoom" min="1" max="3" step="0.05" value="${zoom}"
style="width:100%">
class="w-full">
</div>
` : ''}
<label class="btn btn-secondary" style="cursor:pointer">
${UI.icon('upload-simple')} Neues Foto wählen
<input type="file" id="pe-file-input" accept="image/*" style="display:none">
<input type="file" id="pe-file-input" accept="image/*" class="hidden">
</label>
</div>
`;
const footer = `
<div class="w3-btn-stack">
${hasPhoto ? `<button class="btn btn-primary" id="pe-save-btn" style="width:100%">Speichern</button>` : ''}
<div style="display:flex;gap:var(--space-2)">
${hasPhoto ? `<button class="btn btn-primary" id="pe-save-btn" class="w-full">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" onclick="UI.modal.close()">Abbrechen</button>
</div>
@ -860,7 +860,7 @@ window.Page_dog_profile = (() => {
<!-- Owner + QR -->
<div style="display:flex;align-items:flex-end;justify-content:space-between;gap:12px">
<div style="flex:1;min-width:0">
<div class="flex-1-min">
${ownerName ? `<div style="font-size:0.7rem;color:rgba(255,255,255,0.4);text-transform:uppercase;letter-spacing:.06em;margin-bottom:4px">Besitzer</div>
<div style="font-size:0.9rem;font-weight:600;color:rgba(255,255,255,0.85)">${_esc(ownerName)}</div>` : ''}
<div style="font-size:0.65rem;color:rgba(255,255,255,0.35);margin-top:8px">banyaro.app</div>
@ -878,7 +878,7 @@ window.Page_dog_profile = (() => {
UI.modal.open({
title: 'Visitenkarte',
body: `
<div style="margin-bottom:var(--space-4)">${cardHtml}</div>
<div class="mb-4">${cardHtml}</div>
<p style="font-size:var(--text-xs);color:var(--c-text-secondary);text-align:center;margin-bottom:0">
QR-Code auf NFC-Tag oder Anhänger kleben jeder kann das Profil von ${_esc(dog.name)} sofort öffnen.
</p>
@ -952,7 +952,7 @@ window.Page_dog_profile = (() => {
<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
style="font-size:var(--text-xs)">
class="text-xs">
<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>
@ -961,7 +961,7 @@ window.Page_dog_profile = (() => {
Dieser Link kann einmalig angenommen werden.
</p>
</div>
<div id="share-list-wrap" style="margin-top:var(--space-4)"></div>`,
<div id="share-list-wrap" class="mt-4"></div>`,
footer: `
<button class="btn btn-secondary" onclick="UI.modal.close()">Schließen</button>
<button class="btn btn-primary" id="share-create-btn">Link erstellen</button>`,
@ -1010,7 +1010,7 @@ window.Page_dog_profile = (() => {
<div style="flex:1;font-size:var(--text-sm)">
${s.shared_with_name
? `<strong>${_esc(s.shared_with_name)}</strong> · ${s.role}`
: `<em style="color:var(--c-text-muted)">Ausstehend</em> · ${s.role}`}
: `<em class="text-muted">Ausstehend</em> · ${s.role}`}
</div>
<button class="btn btn-link btn-sm share-revoke-btn" data-share-id="${s.id}"
style="color:var(--c-danger);padding:0">
@ -1056,7 +1056,7 @@ window.Page_dog_profile = (() => {
body: _formHTML(null, true),
footer: `
<div class="w3-btn-stack">
<button type="submit" form="dp-form" class="btn btn-primary" style="width:100%">${UI.icon('dog')} Hund anlegen</button>
<button type="submit" form="dp-form" class="btn btn-primary w-full">${UI.icon('dog')} Hund anlegen</button>
<button type="button" class="btn btn-secondary" id="dp-form-cancel">Abbrechen</button>
</div>
`,
@ -1073,8 +1073,8 @@ window.Page_dog_profile = (() => {
body: _formHTML(dog, true),
footer: `
<div class="w3-btn-stack">
<button type="submit" form="dp-form" class="btn btn-primary" style="width:100%">Speichern</button>
<div style="display:flex;gap:var(--space-2)">
<button type="submit" form="dp-form" class="btn btn-primary w-full">Speichern</button>
<div class="flex-gap-2">
<button type="button" class="btn btn-danger" id="dp-delete-btn">Löschen</button>
<button type="button" id="dp-gedenken-btn"
style="flex:1;padding:var(--space-2) var(--space-3);border-radius:var(--radius-md);
@ -1108,7 +1108,7 @@ window.Page_dog_profile = (() => {
<div class="form-group">
<label class="form-label">
Rasse
<span style="color:var(--c-text-secondary)">(optional)</span>
<span class="text-secondary">(optional)</span>
${UI.help('Verknüpfe deine Rasse mit unserem Wiki für personalisierte Pflegetipps.')}
</label>
<input class="form-control" type="text" name="rasse"
@ -1126,7 +1126,7 @@ window.Page_dog_profile = (() => {
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Geburtstag</label>
<input class="form-control" type="date" name="geburtstag"
@ -1142,7 +1142,7 @@ window.Page_dog_profile = (() => {
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Gewicht (kg)</label>
<input class="form-control" type="number" name="gewicht_kg"
@ -1160,7 +1160,7 @@ window.Page_dog_profile = (() => {
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">
Chip-Nummer
@ -1175,7 +1175,7 @@ window.Page_dog_profile = (() => {
<div class="form-group">
<label class="form-label">
Felltyp
<span style="color:var(--c-text-secondary)">(optional)</span>
<span class="text-secondary">(optional)</span>
${UI.help('Der Felltyp wird für personalisierte Wetter-Hinweise genutzt.')}
</label>
<select class="form-control" name="fell_typ">
@ -1192,7 +1192,7 @@ window.Page_dog_profile = (() => {
<div class="form-group">
<label class="form-label">
Bio / Steckbrief
<span style="color:var(--c-text-secondary)">(optional)</span>
<span class="text-secondary">(optional)</span>
</label>
<textarea class="form-control" name="bio" rows="2"
placeholder="Kurze Beschreibung…">${_esc(dog?.bio || '')}</textarea>
@ -1216,7 +1216,7 @@ window.Page_dog_profile = (() => {
display:${dog?.foto_url ? 'block' : 'none'}">
<label class="btn btn-secondary btn-sm" style="cursor:pointer;margin:0">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#camera"></use></svg> Foto auswählen
<input type="file" name="foto" accept="image/*" style="display:none"
<input type="file" name="foto" accept="image/*" class="hidden"
id="dp-form-foto">
</label>
<button type="button" class="btn btn-secondary btn-sm" id="dp-rasse-erkennen-btn"
@ -1225,7 +1225,7 @@ window.Page_dog_profile = (() => {
Rasse erkennen
</button>
<input type="file" accept="image/jpeg,image/png,image/webp"
id="dp-rasse-foto-input" style="display:none">
id="dp-rasse-foto-input" class="hidden">
</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:4px">
Foto hochladen um die Rasse per KI zu erkennen
@ -1473,7 +1473,7 @@ window.Page_dog_profile = (() => {
title: 'Kein Hund erkannt',
body: `<div style="text-align:center;padding:var(--space-6) var(--space-2)">
<div style="font-size:3rem;margin-bottom:var(--space-3)">🐾</div>
<p style="color:var(--c-text-secondary)">
<p class="text-secondary">
Auf diesem Foto konnte kein Hund erkannt werden.<br>
Bitte lade ein deutlicheres Foto hoch.
</p>
@ -1500,10 +1500,10 @@ window.Page_dog_profile = (() => {
${r.beschreibung ? `<div class="rasse-result-desc">${_esc(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="${_esc(r.name)}" style="flex:1">
data-rasse="${_esc(r.name)}" class="flex-1">
Rasse übernehmen
</button>` : `<button class="btn btn-secondary btn-sm" data-action="uebernehmen"
data-rasse="${_esc(r.name)}" style="flex:1">
data-rasse="${_esc(r.name)}" class="flex-1">
Diese wählen
</button>`}
${r.wiki_slug ? `<button class="btn btn-ghost btn-sm" data-action="wiki"
@ -1636,7 +1636,7 @@ window.Page_dog_profile = (() => {
try {
data = await API.get(`/passport/${dog.id}`);
} catch (e) {
wrap.innerHTML = `<p style="color:var(--c-danger)">Fehler beim Laden: ${_esc(e.message)}</p>`;
wrap.innerHTML = `<p class="text-danger">Fehler beim Laden: ${_esc(e.message)}</p>`;
return;
}
@ -1666,24 +1666,24 @@ window.Page_dog_profile = (() => {
Bearbeiten
</button>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">Blutgruppe</div>
<div class="text-xs-secondary">Blutgruppe</div>
<div id="pp-meta-blutgruppe" style="font-size:var(--text-sm);font-weight:500">
${_esc(meta.blutgruppe) || '<span style="color:var(--c-text-muted)">nicht eingetragen</span>'}
${_esc(meta.blutgruppe) || '<span class="text-muted">nicht eingetragen</span>'}
</div>
</div>
<div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">Allergien</div>
<div id="pp-meta-allergien" style="font-size:var(--text-sm)">
${_esc(meta.allergien) || '<span style="color:var(--c-text-muted)">keine</span>'}
<div class="text-xs-secondary">Allergien</div>
<div id="pp-meta-allergien" class="text-sm">
${_esc(meta.allergien) || '<span class="text-muted">keine</span>'}
</div>
</div>
</div>
${meta.besonderheiten ? `
<div style="margin-top:var(--space-3)">
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">Besonderheiten</div>
<div id="pp-meta-besonderheiten" style="font-size:var(--text-sm)">
<div class="mt-3">
<div class="text-xs-secondary">Besonderheiten</div>
<div id="pp-meta-besonderheiten" class="text-sm">
${_esc(meta.besonderheiten)}
</div>
</div>` : ''}
@ -1708,7 +1708,7 @@ window.Page_dog_profile = (() => {
: vaccs.map(v => `
<div class="pp-vacc-row" data-id="${v.id}"
class="pp-data-row">
<div style="flex:1">
<div class="flex-1">
<div style="font-weight:600;font-size:var(--text-sm)">${_esc(v.krankheit)}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:2px">
Gegeben: ${_fmt(v.datum)}
@ -1727,7 +1727,7 @@ window.Page_dog_profile = (() => {
</div>
<!-- Medikamente -->
<div class="card" style="padding:var(--space-4)">
<div class="card p-4">
<div style="display:flex;align-items:center;justify-content:space-between;
margin-bottom:var(--space-3)">
<span style="font-weight:700;font-size:var(--text-sm)">
@ -1745,7 +1745,7 @@ window.Page_dog_profile = (() => {
: meds.map(m => `
<div class="pp-med-row" data-id="${m.id}"
class="pp-data-row">
<div style="flex:1">
<div class="flex-1">
<div style="font-weight:600;font-size:var(--text-sm)">${_esc(m.name)}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:2px">
${m.dosierung ? `${_esc(m.dosierung)} · ` : ''}
@ -1871,7 +1871,7 @@ window.Page_dog_profile = (() => {
<option value="DHPP (Kombi)">
</datalist>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Datum *</label>
<input id="pp-vacc-datum" class="form-control" type="date" value="${today}">
@ -1938,13 +1938,13 @@ window.Page_dog_profile = (() => {
<input id="pp-med-dosierung" class="form-control" type="text"
placeholder="z. B. 1× täglich, 5 mg">
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Von</label>
<input id="pp-med-von" class="form-control" type="date" value="${today}">
</div>
<div class="form-group">
<label class="form-label">Bis <span style="color:var(--c-text-muted)">(leer = dauerhaft)</span></label>
<label class="form-label">Bis <span class="text-muted">(leer = dauerhaft)</span></label>
<input id="pp-med-bis" class="form-control" type="date">
</div>
</div>
@ -2001,7 +2001,7 @@ window.Page_dog_profile = (() => {
</p>
<div style="display:flex;gap:var(--space-2);align-items:center">
<input id="pp-sharelink-input" class="form-control" type="text" readonly
value="${_esc(url)}" style="font-size:var(--text-xs)">
value="${_esc(url)}" class="text-xs">
<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>
@ -2314,7 +2314,7 @@ window.Page_dog_profile = (() => {
data = await API.get(`/dogs/${dog.id}/timeline`);
} catch (e) {
const b = document.getElementById('dp-timeline-body');
if (b) b.innerHTML = `<p style="color:var(--c-danger)">Fehler: ${_esc(e.message)}</p>`;
if (b) b.innerHTML = `<p class="text-danger">Fehler: ${_esc(e.message)}</p>`;
return;
}
@ -2498,7 +2498,7 @@ window.Page_dog_profile = (() => {
</form>`,
footer: `
<div class="w3-btn-stack">
<button type="submit" form="gedenken-form" id="gedenken-save-btn" class="btn btn-primary" style="width:100%">
<button type="submit" form="gedenken-form" id="gedenken-save-btn" class="btn btn-primary w-full">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#heart"></use></svg>
Gedenkseite erstellen
</button>
@ -2550,22 +2550,22 @@ window.Page_dog_profile = (() => {
${d.km_total ? `<div class="card" style="padding:var(--space-3);text-align:center">
<svg class="ph-icon" style="width:20px;height:20px;color:var(--c-primary)" aria-hidden="true"><use href="/icons/phosphor.svg#path"></use></svg>
<div style="font-size:var(--text-xl);font-weight:800;color:var(--c-primary)">${d.km_total}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">km zusammen</div>
<div class="text-xs-secondary">km zusammen</div>
</div>` : ''}
${d.diary_count ? `<div class="card" style="padding:var(--space-3);text-align:center">
<svg class="ph-icon" style="width:20px;height:20px;color:var(--c-primary)" aria-hidden="true"><use href="/icons/phosphor.svg#book-open"></use></svg>
<div style="font-size:var(--text-xl);font-weight:800;color:var(--c-primary)">${d.diary_count}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">Tagebucheinträge</div>
<div class="text-xs-secondary">Tagebucheinträge</div>
</div>` : ''}
${d.media_count ? `<div class="card" style="padding:var(--space-3);text-align:center">
<svg class="ph-icon" style="width:20px;height:20px;color:var(--c-primary)" aria-hidden="true"><use href="/icons/phosphor.svg#images"></use></svg>
<div style="font-size:var(--text-xl);font-weight:800;color:var(--c-primary)">${d.media_count}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">Fotos</div>
<div class="text-xs-secondary">Fotos</div>
</div>` : ''}
${d.gemeinsam_tage ? `<div class="card" style="padding:var(--space-3);text-align:center">
<svg class="ph-icon" style="width:20px;height:20px;color:var(--c-primary)" aria-hidden="true"><use href="/icons/phosphor.svg#calendar-heart"></use></svg>
<div style="font-size:var(--text-xl);font-weight:800;color:var(--c-primary)">${d.gemeinsam_tage}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">gemeinsame Tage</div>
<div class="text-xs-secondary">gemeinsame Tage</div>
</div>` : ''}
</div>`;
@ -2596,8 +2596,8 @@ window.Page_dog_profile = (() => {
Professionelle Hilfe bei Tiertrauer: <strong>Tiertrauer-Hotline 0800 111 0 111</strong> (kostenlos)
</div>
</div>
<div id="gedenk-ki-wrap" style="margin-top:var(--space-4)">
<button id="gedenk-ki-btn" class="btn btn-secondary" style="width:100%">
<div id="gedenk-ki-wrap" class="mt-4">
<button id="gedenk-ki-btn" class="btn btn-secondary w-full">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#sparkle"></use></svg>
Persönlichen Abschiedstext erstellen
</button>

View file

@ -166,7 +166,7 @@ window.Page_ernaehrung = (() => {
</div>
<!-- Aktivität als Pill-Buttons -->
<div style="margin-bottom:var(--space-4)">
<div class="mb-4">
<div class="ern-section-label">🏃 Aktivität</div>
<div class="ern-pill-group">
<button class="ern-pill" data-akt="gering">🛋 Gemütlich</button>
@ -288,13 +288,13 @@ window.Page_ernaehrung = (() => {
<div style="background:var(--c-surface);border-radius:var(--radius-md);
padding:var(--space-3);border:1px solid var(--c-border)">
<div style="font-weight:600;margin-bottom:4px">🌾 Trockenfutter</div>
<div style="font-size:var(--text-sm);color:var(--c-text-secondary)">
<div class="text-sm-secondary">
(~350 kcal/100g)
</div>
<div style="font-size:var(--text-lg);font-weight:600;margin-top:6px">
${trocken} g / Tag
</div>
<div style="font-size:var(--text-sm);color:var(--c-text-secondary)">
<div class="text-sm-secondary">
= ${Math.round(trocken/2)} g morgens + ${Math.round(trocken/2)} g abends
</div>
</div>
@ -302,13 +302,13 @@ window.Page_ernaehrung = (() => {
<div style="background:var(--c-surface);border-radius:var(--radius-md);
padding:var(--space-3);border:1px solid var(--c-border)">
<div style="font-weight:600;margin-bottom:4px">🥫 Nassfutter</div>
<div style="font-size:var(--text-sm);color:var(--c-text-secondary)">
<div class="text-sm-secondary">
(~85 kcal/100g)
</div>
<div style="font-size:var(--text-lg);font-weight:600;margin-top:6px">
${nass} g / Tag
</div>
<div style="font-size:var(--text-sm);color:var(--c-text-secondary)">
<div class="text-sm-secondary">
= ${Math.round(nass/2)} g morgens + ${Math.round(nass/2)} g abends
</div>
</div>
@ -316,13 +316,13 @@ window.Page_ernaehrung = (() => {
<div style="background:var(--c-surface);border-radius:var(--radius-md);
padding:var(--space-3);border:1px solid var(--c-border)">
<div style="font-weight:600;margin-bottom:4px">🥩 BARF</div>
<div style="font-size:var(--text-sm);color:var(--c-text-secondary)">
<div class="text-sm-secondary">
(~150 kcal/100g)
</div>
<div style="font-size:var(--text-lg);font-weight:600;margin-top:6px">
${barf} g / Tag
</div>
<div style="font-size:var(--text-sm);color:var(--c-text-secondary)">
<div class="text-sm-secondary">
= ${Math.round(barf/2)} g morgens + ${Math.round(barf/2)} g abends
</div>
</div>
@ -525,7 +525,7 @@ window.Page_ernaehrung = (() => {
].map(q => `
<button class="btn btn-sm btn-secondary ern-ki-vorschlag"
data-q="${_esc(q)}"
style="font-size:var(--text-xs)">${_esc(q)}</button>
class="text-xs">${_esc(q)}</button>
`).join('')}
</div>
@ -533,7 +533,7 @@ window.Page_ernaehrung = (() => {
<div id="ern-ki-chat" style="min-height:80px;margin-bottom:var(--space-3)"></div>
<!-- Eingabe -->
<div style="display:flex;gap:var(--space-2)">
<div class="flex-gap-2">
<textarea id="ern-ki-frage" class="by-input" rows="2"
placeholder="Deine Frage zur Ernährung..."
style="flex:1;resize:vertical"></textarea>
@ -586,7 +586,7 @@ window.Page_ernaehrung = (() => {
// KI-Antwort Placeholder
const placeholderId = `ern-ki-placeholder-${Date.now()}`;
chatEl.insertAdjacentHTML('beforeend', `
<div id="${placeholderId}" style="margin-bottom:var(--space-3)">
<div id="${placeholderId}" class="mb-3">
<div style="background:var(--c-surface);border:1px solid var(--c-border);
border-radius:var(--radius-md);padding:var(--space-2) var(--space-3);
font-size:var(--text-sm);color:var(--c-text-muted)">
@ -905,7 +905,7 @@ window.Page_ernaehrung = (() => {
try {
data = await API.dogs.futterAnalyse(dog.id);
} catch (_) {
analyseEl.innerHTML = `<p style="color:var(--c-text-muted);font-size:var(--text-sm)">Analyse nicht verfügbar.</p>`;
analyseEl.innerHTML = `<p class="text-sm-muted">Analyse nicht verfügbar.</p>`;
return;
}
@ -990,7 +990,7 @@ window.Page_ernaehrung = (() => {
white-space:nowrap;overflow:hidden;text-overflow:ellipsis">
${_esc(f.name)}
</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">
<div class="text-xs-muted">
${_esc(TYP_LABELS[f.typ] || f.typ)} &middot; ${f.mahlzeiten} Mahlzeit${f.mahlzeiten !== 1 ? 'en' : ''}
${f.status !== 'neu' ? `&middot; <span style="color:var(--c-success,#22c55e)">+${f.positiv}</span> / <span style="color:var(--c-danger,#ef4444)">-${f.negativ}</span>` : ''}
</div>
@ -1084,9 +1084,9 @@ window.Page_ernaehrung = (() => {
<svg class="ph-icon" aria-hidden="true" style="flex-shrink:0;color:var(--c-primary)">
<use href="/icons/phosphor.svg#bowl-food"></use>
</svg>
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="font-weight:600;font-size:var(--text-sm)">${_esc(item.futter_name)}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">
<div class="text-xs-muted">
${_esc(item.datum)} ${_esc(item.uhrzeit)}
${item.menge_g ? ` &middot; ${item.menge_g} g` : ''}
</div>
@ -1110,12 +1110,12 @@ window.Page_ernaehrung = (() => {
<svg class="ph-icon" aria-hidden="true" style="flex-shrink:0;color:${col}">
<use href="/icons/phosphor.svg#heartbeat"></use>
</svg>
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="font-weight:600;font-size:var(--text-sm);color:${col}">
${_esc(REAK_LABELS[item.reaktion_typ] || item.reaktion_typ)}
<span style="font-weight:400;color:var(--c-text-muted)">(${item.intensitaet}/5)</span>
</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">
<div class="text-xs-muted">
${_esc(item.datum)} ${_esc(item.uhrzeit)}
</div>
</div>

View file

@ -253,13 +253,13 @@ window.Page_erste_hilfe = (() => {
</div>
${KATEGORIEN.map(k => `
<div class="eh-tab-panel" id="eh-panel-${k.id}" style="display:none">
<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('')}
</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" style="color:var(--c-primary)"><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.
</div>
</div>
@ -311,7 +311,7 @@ window.Page_erste_hilfe = (() => {
<div style="font-size:var(--text-xs);font-weight:var(--weight-semibold);color:rgba(255,255,255,0.85);text-transform:uppercase;letter-spacing:0.04em;margin-bottom:var(--space-1)">
${g.flag} · ${g.land}
</div>
<div style="display:flex;flex-direction:column;gap:var(--space-2)">
<div class="flex-col-gap-2">
${g.eintraege.map(renderEintrag).join('')}
</div>
</div>
@ -323,7 +323,7 @@ window.Page_erste_hilfe = (() => {
<svg class="ph-icon" style="width:20px;height:20px" aria-hidden="true"><use href="/icons/phosphor.svg#siren"></use></svg>
Tiergiftzentralen jetzt anrufen
</div>
<div style="display:flex;flex-direction:column;gap:var(--space-3)">
<div class="flex-col-gap-3">
${gruppen}
</div>
<p style="margin-top:var(--space-3);font-size:var(--text-xs);color:rgba(255,255,255,0.8)">
@ -345,7 +345,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" style="color:var(--c-primary)"><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
</div>
<div style="overflow-x:auto">

View file

@ -75,7 +75,7 @@ window.Page_events = (() => {
<button class="events-view-btn active" data-ev-view="liste">${UI.icon('list')} Liste</button>
<button class="events-view-btn" data-ev-view="karte">${UI.icon('map-trifold')} Karte</button>
</div>
<div style="flex:1"></div>
<div class="flex-1"></div>
${_state.user ? `<button class="btn btn-primary btn-sm" id="ev-new-btn">${UI.icon('plus')} Event</button>` : ''}
</div>
@ -102,7 +102,7 @@ window.Page_events = (() => {
</div>
<div class="events-list" id="ev-list"></div>
<div class="events-map" id="ev-map" style="display:none"></div>
<div class="events-map" id="ev-map" class="hidden"></div>
`;
_container.addEventListener('click', _onClick);
@ -231,7 +231,7 @@ window.Page_events = (() => {
${_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-ort="${UI.escape(ev.ort_name || '')}"
title="Notiz" style="color:var(--c-text-muted)" onclick="event.stopPropagation()">
title="Notiz" class="text-muted" onclick="event.stopPropagation()">
${_icon('note-pencil')}</button>` : ''}
</div>
</div>
@ -496,7 +496,7 @@ window.Page_events = (() => {
<label class="form-label">GPS-Position</label>
<div id="ev-location-picker"></div>
</div>
<div class="form-group" style="margin-top:var(--space-3)">
<div class="form-group mt-3">
<label class="form-label">Beschreibung</label>
<textarea class="form-control" name="beschreibung" rows="3">${ev?.beschreibung || ''}</textarea>
</div>
@ -509,10 +509,10 @@ 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" style="width:100%">
<button class="btn btn-primary" type="submit" form="${id}" id="ev-submit-btn" class="w-full">
${isEdit ? 'Speichern' : 'Event erstellen'}
</button>
<div style="display:flex;gap:var(--space-2)">
<div class="flex-gap-2">
${isEdit ? `<button type="button" class="btn btn-danger" id="ev-form-delete">Löschen</button>` : ''}
<button class="btn btn-secondary flex-1" onclick="UI.modal.close()">Abbrechen</button>
</div>
@ -672,7 +672,7 @@ window.Page_events = (() => {
<div style="width:100%;max-width:600px;background:var(--c-surface);border-radius:var(--radius-lg) var(--radius-lg) 0 0;
padding:var(--space-4);box-sizing:border-box;max-height:80vh;display:flex;flex-direction:column">
<div style="display:flex;align-items:center;gap:var(--space-3);margin-bottom:var(--space-3)">
<svg class="ph-icon" aria-hidden="true" style="color:var(--c-primary)"><use href="/icons/phosphor.svg#note-pencil"></use></svg>
<svg class="ph-icon" aria-hidden="true" class="text-primary"><use href="/icons/phosphor.svg#note-pencil"></use></svg>
<span style="font-weight:600;flex:1"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz ${UI.escape(parentLabel)}</span>
<button id="ev-note-close" style="background:none;border:none;cursor:pointer;color:var(--c-text-muted);padding:4px">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#x"></use></svg>

View file

@ -162,7 +162,7 @@ window.Page_expenses = (() => {
<div class="exp-kachel-icon" style="background:${k.color}20;color:${k.color}">
${UI.icon(k.icon)}
</div>
<div class="exp-kachel-betrag" style="color:var(--c-primary)">${_fmt(jahr)}</div>
<div class="exp-kachel-betrag text-primary">${_fmt(jahr)}</div>
<div class="exp-kachel-label">${k.label}</div>
${monatLine}
<div class="exp-kachel-add">${UI.icon('plus')} eintragen</div>
@ -477,13 +477,13 @@ window.Page_expenses = (() => {
</div>
${dogOptions ? `
<div class="form-group">
<label class="form-label">Hund <span style="color:var(--c-text-muted)">(optional)</span></label>
<label class="form-label">Hund <span class="text-muted">(optional)</span></label>
<select class="form-control" name="dog_id">
<option value="">Kein Hund</option>${dogOptions}
</select>
</div>` : ''}
<div class="form-group">
<label class="form-label">Bezeichnung <span style="color:var(--c-text-muted)">(optional)</span></label>
<label class="form-label">Bezeichnung <span class="text-muted">(optional)</span></label>
<input class="form-control" type="text" name="notiz"
value="${_esc(r?.notiz || '')}" placeholder="z.B. Haftpflicht Allianz">
</div>
@ -694,7 +694,7 @@ window.Page_expenses = (() => {
// Kategorie-Kacheln statt Dropdown
const katKacheln = KATEGORIEN.map(k => `
<label class="exp-kat-tile${selKat === k.id ? ' exp-kat-tile--sel' : ''}" data-kat="${k.id}">
<input type="radio" name="kategorie" value="${k.id}" ${selKat === k.id ? 'checked' : ''} style="display:none">
<input type="radio" name="kategorie" value="${k.id}" ${selKat === k.id ? 'checked' : ''} class="hidden">
<span class="exp-kat-tile-icon" style="color:${k.color}">${UI.icon(k.icon)}</span>
<span class="exp-kat-tile-label">${k.label}</span>
</label>`).join('');
@ -707,7 +707,7 @@ window.Page_expenses = (() => {
<div class="exp-kat-grid">${katKacheln}</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group" style="margin-bottom:0">
<label class="form-label">Betrag</label>
<div class="exp-betrag-wrap">

View file

@ -99,7 +99,7 @@ window.Page_forum = (() => {
<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" style="color:var(--c-text-muted)">${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>
</div>
</div>
@ -280,7 +280,7 @@ window.Page_forum = (() => {
<div class="hdm-kandidaten-search">
<input type="search" id="hdm-search" class="form-control"
placeholder="Name oder Rasse suchen …" autocomplete="off"
style="font-size:var(--text-sm)">
class="text-sm">
</div>
<div id="hdm-kandidaten-grid" class="hdm-vote-grid">
${UI.skeleton(3)}
@ -328,8 +328,8 @@ window.Page_forum = (() => {
<div class="hdm-vote-av">${av}</div>
<div class="hdm-vote-name">${_esc(dog.name)}</div>
${dog.rasse ? `<div class="hdm-vote-rasse">${_esc(dog.rasse)}</div>` : ''}
${vorname ? `<div class="hdm-vote-besitzer" style="font-size:var(--text-xs);color:var(--c-text-muted)">von ${vorname}</div>` : ''}
${dog.stimmen > 0 ? `<div style="font-size:var(--text-xs);color:var(--c-text-muted)">${dog.stimmen} ${UI.icon('star')}</div>` : ''}
${vorname ? `<div class="hdm-vote-besitzer text-xs-muted">von ${vorname}</div>` : ''}
${dog.stimmen > 0 ? `<div class="text-xs-muted">${dog.stimmen} ${UI.icon('star')}</div>` : ''}
<button class="btn btn-sm ${isVoted ? 'btn-primary' : 'btn-secondary'} hdm-vote-btn"
data-dog-id="${dog.id}" ${isVoted ? 'disabled' : ''}>
${isVoted ? `${UI.icon('check-circle')} Gewählt` : 'Abstimmen'}
@ -411,8 +411,8 @@ window.Page_forum = (() => {
el.innerHTML = `
<div style="text-align:center;padding:var(--space-10) var(--space-4)">
<div style="font-size:3rem;margin-bottom:var(--space-3)">${UI.icon('chat-circle-dots')}</div>
<p style="color:var(--c-text-secondary)">Noch keine Beiträge in dieser Kategorie.</p>
<button class="btn btn-primary" style="margin-top:var(--space-4)" id="forum-first-btn">
<p class="text-secondary">Noch keine Beiträge in dieser Kategorie.</p>
<button class="btn btn-primary mt-4" id="forum-first-btn">
Ersten Beitrag erstellen
</button>
</div>`;
@ -493,7 +493,7 @@ window.Page_forum = (() => {
document.getElementById('forum-main').innerHTML = `
<div style="text-align:center;padding:var(--space-8)">
<div style="font-size:2rem;margin-bottom:var(--space-2)">${UI.icon('magnifying-glass')}</div>
<p style="color:var(--c-text-secondary)">Keine Ergebnisse für ${_esc(q)}"</p>
<p class="text-secondary">Keine Ergebnisse für ${_esc(q)}"</p>
</div>`;
return;
}
@ -533,7 +533,7 @@ window.Page_forum = (() => {
<button class="btn btn-ghost btn-sm forum-mod-lock" title="${thread.is_locked ? 'Entsperren' : 'Sperren'}">
${UI.icon('lock')} ${thread.is_locked ? 'Entsperren' : 'Sperren'}
</button>
<button class="btn btn-ghost btn-sm forum-mod-delete-thread" style="color:var(--c-danger)">${UI.icon('trash')} Thread</button>
<button class="btn btn-ghost btn-sm forum-mod-delete-thread text-danger">${UI.icon('trash')} Thread</button>
</div>` : '';
const _forumMediaHtml = (u) => {
@ -565,7 +565,7 @@ window.Page_forum = (() => {
<div class="forum-reply-actions">
<label class="btn btn-ghost btn-sm forum-upload-label" title="Foto anhängen">
${UI.icon('camera')}
<input type="file" accept="image/*" id="forum-reply-file" style="display:none">
<input type="file" accept="image/*" id="forum-reply-file" class="hidden">
</label>
<div id="forum-reply-previews" class="forum-upload-previews"></div>
</div>
@ -862,7 +862,7 @@ window.Page_forum = (() => {
try {
await API.forum.deletePost(postId);
if (postEl) {
postEl.innerHTML = '<em style="color:var(--c-text-muted)">Beitrag wurde entfernt</em>';
postEl.innerHTML = '<em class="text-muted">Beitrag wurde entfernt</em>';
postEl.className = 'forum-post forum-post--deleted';
}
const idx = _threads.findIndex(t => t.id === threadId);
@ -1011,16 +1011,16 @@ window.Page_forum = (() => {
placeholder="Beschreibe dein Thema ausführlich…" required></textarea>
</div>
<div class="form-group">
<label class="form-label">Standort <span style="color:var(--c-text-secondary)">(optional)</span></label>
<label class="form-label">Standort <span class="text-secondary">(optional)</span></label>
<div id="forum-location-picker"></div>
</div>
<div class="form-group">
<label class="form-label">Fotos / Dateien (max. 5)</label>
<div class="forum-upload-area">
<label class="btn btn-secondary btn-sm" for="forum-thread-files">${UI.icon('image')} Fotos / Video / PDF</label>
<input type="file" id="forum-thread-files" accept="image/*,video/*,application/pdf" multiple style="display:none">
<input type="file" id="forum-thread-files" accept="image/*,video/*,application/pdf" multiple class="hidden">
</div>
<div id="forum-thread-previews" class="forum-upload-previews" style="margin-top:var(--space-2)"></div>
<div id="forum-thread-previews" class="forum-upload-previews mt-2"></div>
</div>
</form>
<p style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:var(--space-3)">
@ -1295,14 +1295,14 @@ window.Page_forum = (() => {
? `<div class="forum-mod-reports">
${reports.map(r => `
<div class="forum-mod-report-item" data-id="${r.id}">
<div style="font-size:var(--text-sm)">
<div class="text-sm">
<strong>${_esc(r.target_type)} #${r.target_id}</strong>
${_esc(r.grund)}
</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">
<div class="text-xs-muted">
von ${_esc(r.melder_name || '?')} · ${_fmtDate(r.created_at)}
</div>
<button class="btn btn-sm btn-secondary forum-resolve-btn" data-id="${r.id}" style="margin-top:var(--space-2)">
<button class="btn btn-sm btn-secondary forum-resolve-btn" data-id="${r.id}" class="mt-2">
${UI.icon('check')} Erledigt
</button>
</div>`).join('')}
@ -1380,7 +1380,7 @@ window.Page_forum = (() => {
<textarea class="form-control" name="text" rows="5">${_esc(thread.text || '')}</textarea>
</div>
<div class="form-group">
<label class="form-label">Standort <span style="color:var(--c-text-secondary)">(optional)</span></label>
<label class="form-label">Standort <span class="text-secondary">(optional)</span></label>
<div id="forum-edit-location-picker"></div>
</div>
</form>`,

View file

@ -51,12 +51,12 @@ window.Page_friends = (() => {
<div>
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
color:var(--c-text)">Dein Freundes-Link</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">
<div class="text-xs-secondary">
Teile ihn der andere tippt drauf und findet dich sofort.
</div>
</div>
</div>
<div style="display:flex;gap:var(--space-2)">
<div class="flex-gap-2">
<div style="flex:1;padding:var(--space-2) var(--space-3);
background:var(--c-surface-2);border-radius:var(--radius-md);
font-size:var(--text-xs);color:var(--c-text-secondary);
@ -392,10 +392,10 @@ window.Page_friends = (() => {
font-size:var(--text-sm);flex-shrink:0">
${_esc((r.addressee_name || '?')[0].toUpperCase())}
</div>
<div style="flex:1">
<div class="flex-1">
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
color:var(--c-text)">${_esc(r.addressee_name)}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">Anfrage ausstehend</div>
<div class="text-xs-muted">Anfrage ausstehend</div>
</div>
<button class="btn btn-ghost btn-sm"
onclick="Page_friends._cancel(${r.id})" title="Zurückziehen">
@ -480,7 +480,7 @@ window.Page_friends = (() => {
${_userAvatar(f.friend_name, dogs[0], f.avatar_url)}
<!-- Name + Infos + Hunde -->
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="display:flex;align-items:center;flex-wrap:wrap;gap:2px;
margin-bottom:var(--space-1)">
<span style="font-weight:var(--weight-semibold);color:var(--c-text)">
@ -495,7 +495,7 @@ window.Page_friends = (() => {
? `<div style="display:flex;flex-wrap:wrap;gap:var(--space-1);align-items:center">
${_dogPills(dogs, 3)}
</div>`
: `<div style="font-size:var(--text-xs);color:var(--c-text-muted)">Noch kein Hund eingetragen</div>`
: `<div class="text-xs-muted">Noch kein Hund eingetragen</div>`
}
</div>
</div>
@ -536,7 +536,7 @@ window.Page_friends = (() => {
<div style="display:flex;gap:var(--space-2);margin-top:var(--space-3);
padding-top:var(--space-3);border-top:1px solid var(--c-border)">
${withPhotos.slice(0, 4).map(d => `
<div style="text-align:center">
<div class="text-center">
<img src="${_esc(d.foto_url)}" alt="${_esc(d.name)}"
style="width:44px;height:44px;border-radius:50%;object-fit:cover;
border:2px solid var(--c-surface)">
@ -558,7 +558,7 @@ window.Page_friends = (() => {
? `<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr));
gap:var(--space-3);margin-top:var(--space-4)">
${dogs.map(d => `
<div style="text-align:center">
<div class="text-center">
${d.foto_url
? `<img src="${_esc(d.foto_url)}" alt="${_esc(d.name)}"
style="width:72px;height:72px;border-radius:50%;object-fit:cover;
@ -571,7 +571,7 @@ window.Page_friends = (() => {
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
color:var(--c-text)">${_esc(d.name)}</div>
${d.rasse
? `<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">${_esc(d.rasse)}</div>`
? `<div class="text-xs-secondary">${_esc(d.rasse)}</div>`
: ''}
</div>
`).join('')}
@ -589,7 +589,7 @@ window.Page_friends = (() => {
</div>`);
}
if (profile.erfahrung && _erfahrungBadge[profile.erfahrung]) {
parts.push(`<div style="font-size:var(--text-sm);color:var(--c-text-secondary)">
parts.push(`<div class="text-sm-secondary">
${_erfahrungBadge[profile.erfahrung]}
</div>`);
}
@ -602,7 +602,7 @@ window.Page_friends = (() => {
if (profile.social_link) {
parts.push(`<div style="font-size:var(--text-xs);word-break:break-all">
<a href="${_esc(profile.social_link)}" target="_blank" rel="noopener noreferrer"
style="color:var(--c-primary)">${_esc(profile.social_link)}</a>
class="text-primary">${_esc(profile.social_link)}</a>
</div>`);
}
if (!parts.length) return '';
@ -638,7 +638,7 @@ window.Page_friends = (() => {
Nachricht schreiben
</button>
<button class="btn btn-ghost" id="modal-remove-btn" form=""
style="color:var(--c-danger)">
class="text-danger">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#user-minus"></use></svg>
Entfernen
</button>
@ -679,7 +679,7 @@ window.Page_friends = (() => {
padding:var(--space-3) var(--space-4);
${i < results.length - 1 ? 'border-bottom:1px solid var(--c-border)' : ''}">
${_userAvatar(u.name, null, u.avatar_url)}
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="display:flex;align-items:center;flex-wrap:wrap;gap:4px;
margin-bottom:2px">
<span style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
@ -817,7 +817,7 @@ window.Page_friends = (() => {
function _wohnortLine(wohnort) {
if (!wohnort) return '';
return `<span style="font-size:var(--text-xs);color:var(--c-text-muted)">📍 ${_esc(wohnort)}</span>`;
return `<span class="text-xs-muted">📍 ${_esc(wohnort)}</span>`;
}
function _bioLine(bio, sichtbarkeit) {

View file

@ -82,7 +82,7 @@ window.Page_gruender = (() => {
<p style="font-size:var(--text-xs);color:var(--c-text-muted);margin:0 0 var(--space-4)">
Unsere Partner treten gegeneinander an wer bringt die meisten Gründer?
</p>
<div style="display:flex;flex-direction:column;gap:var(--space-2)">
<div class="flex-col-gap-2">
${d.partners.map((p, i) => {
const medal = i === 0 ? '🥇' : i === 1 ? '🥈' : i === 2 ? '🥉' : `#${i+1}`;
const barPct = d.partners[0].uses > 0 ? Math.round((p.uses / d.partners[0].uses) * 100) : 0;
@ -91,7 +91,7 @@ window.Page_gruender = (() => {
padding:var(--space-3);border-radius:var(--radius-md);
background:${i === 0 ? 'linear-gradient(135deg,#fef9c3,#fef3c7)' : 'var(--c-surface-2)'}">
<div style="font-size:22px;min-width:32px;text-align:center">${medal}</div>
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="font-weight:700;font-size:var(--text-sm)">${_esc(p.label)}</div>
<div style="background:var(--c-surface-3,rgba(0,0,0,.08));border-radius:var(--radius-full);
height:6px;margin-top:var(--space-1);overflow:hidden">
@ -131,13 +131,13 @@ window.Page_gruender = (() => {
<span style="font-size:var(--text-xs);font-weight:800;color:var(--c-text-muted);min-width:28px">
#${d.total + i + 1}
</span>
<span style="font-size:var(--text-sm);color:var(--c-text-muted)">frei</span>
<span class="text-sm-muted">frei</span>
</div>
`).join('')}
</div>
</div>` : `
<div class="by-card" style="padding:var(--space-6);text-align:center">
<p style="color:var(--c-text-muted);font-size:var(--text-sm)">
<p class="text-sm-muted">
Noch keine Gründer sei der Erste!
</p>
</div>`}

View file

@ -92,7 +92,7 @@ window.Page_health = (() => {
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#wave-sine"></use></svg>
<span class="health-transponder-label">Transponder:</span>
<span class="health-transponder-nr" id="health-transponder-nr">
${dog?.chip_nr ? `<strong>${_esc(dog.chip_nr)}</strong>` : '<em style="color:var(--c-text-muted)">nicht eingetragen</em>'}
${dog?.chip_nr ? `<strong>${_esc(dog.chip_nr)}</strong>` : '<em class="text-muted">nicht eingetragen</em>'}
</span>
<button class="btn btn-link btn-sm health-transponder-edit" id="health-transponder-edit"
style="padding:0;font-size:var(--text-xs);color:var(--c-text-muted)">
@ -194,12 +194,12 @@ window.Page_health = (() => {
background:var(--c-surface);border-radius:var(--radius-md);
border-left:3px solid ${ampel.color === 'red' ? '#ef4444' : ampel.color === 'yellow' ? '#f59e0b' : '#22c55e'}">
<span style="font-size:1.2rem">${ICONS[e._typ] || '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#clipboard-text"></use></svg>'}</span>
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="font-size:var(--text-sm);font-weight:var(--weight-medium);
white-space:nowrap;overflow:hidden;text-overflow:ellipsis">
${_esc(e.bezeichnung)}
</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">
<div class="text-xs-muted">
${ageLabel} · ${dateStr}
</div>
</div>
@ -482,11 +482,11 @@ window.Page_health = (() => {
<div class="health-card" data-id="${e.id}" data-action="open-entry"
style="padding:var(--space-3) var(--space-4)">
<div style="display:flex;justify-content:space-between;align-items:center;width:100%">
<span style="font-size:var(--text-sm);color:var(--c-text-secondary)">
<span class="text-sm-secondary">
${UI.time.format(e.datum + 'T00:00:00')}
</span>
<span style="font-weight:var(--weight-bold);font-size:var(--text-lg)">
${e.wert} <span style="font-size:var(--text-sm);color:var(--c-text-secondary)">${e.einheit || 'kg'}</span>
${e.wert} <span class="text-sm-secondary">${e.einheit || 'kg'}</span>
</span>
</div>
${e.notiz ? `<div class="health-card-note" style="padding-top:var(--space-1)">${_esc(e.notiz)}</div>` : ''}
@ -512,7 +512,7 @@ window.Page_health = (() => {
</div>
${chart}
</div>` : ''}
<div class="health-list" style="margin-top:var(--space-2)">${items}</div>
<div class="health-list mt-2">${items}</div>
<div style="text-align:center;padding:var(--space-4)">${addBtn}</div>
`;
}
@ -693,7 +693,7 @@ window.Page_health = (() => {
<span style="font-size:1.5rem">${UI.icon('gender-female')}</span>
<div>
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold)">Nächste Läufigkeit erwartet</div>
<div style="font-size:var(--text-sm);color:var(--c-text-secondary)">${label}
<div class="text-sm-secondary">${label}
${avgInterval ? ` · Ø ${avgInterval} Tage Abstand` : ''}
</div>
</div>
@ -737,7 +737,7 @@ window.Page_health = (() => {
return `
${banner}
<div class="health-list" style="margin-top:var(--space-4)">${items}</div>
<div class="health-list mt-4">${items}</div>
<div style="text-align:center;padding:var(--space-4)">${addBtn}</div>`;
}
@ -869,7 +869,7 @@ window.Page_health = (() => {
</a>`
).join('')}
</div>`
: `<span style="font-size:var(--text-xs);color:var(--c-text-muted)">Noch keine Datei hochgeladen</span>`}
: `<span class="text-xs-muted">Noch keine Datei hochgeladen</span>`}
</div>
</div>
`;
@ -990,7 +990,7 @@ window.Page_health = (() => {
: (entry.datei_url ? [{ id: null, url: entry.datei_url, media_type: entry.datei_typ || 'image' }] : []);
const mediaHtml = mediaItems.length
? `<div class="health-media-gallery" style="margin-top:var(--space-4)">
? `<div class="health-media-gallery mt-4">
${mediaItems.map(m => m.media_type === 'pdf'
? `<a href="${_esc(m.url)}" target="_blank" rel="noopener"
class="btn btn-secondary btn-sm health-media-gallery-pdf">
@ -1042,8 +1042,8 @@ window.Page_health = (() => {
if (praxis) {
const adresse = [praxis.strasse, [praxis.plz, praxis.ort].filter(Boolean).join(' ')].filter(Boolean).join(', ');
const tel = praxis.telefon ? ` · <a href="tel:${_esc(praxis.telefon)}">${_esc(praxis.telefon)}</a>` : '';
const oh = praxis.opening_hours ? `<br><small style="color:var(--c-text-secondary)">🕐 ${_esc(_fmtOeffnungszeiten(praxis.opening_hours))}</small>` : '';
rows.push(['Praxis', `<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#first-aid"></use></svg> ${_esc(praxis.name)}${adresse ? `<br><small style="color:var(--c-text-secondary)">${_esc(adresse)}${tel}</small>` : tel}${oh}`]);
const oh = praxis.opening_hours ? `<br><small class="text-secondary">🕐 ${_esc(_fmtOeffnungszeiten(praxis.opening_hours))}</small>` : '';
rows.push(['Praxis', `<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#first-aid"></use></svg> ${_esc(praxis.name)}${adresse ? `<br><small class="text-secondary">${_esc(adresse)}${tel}</small>` : tel}${oh}`]);
}
} else if (e.tierarzt_name) {
rows.push(['Tierarzt', _esc(e.tierarzt_name)]);
@ -1143,8 +1143,8 @@ window.Page_health = (() => {
const footer = `
<div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%">
<button type="submit" form="health-form" class="btn btn-primary" style="width:100%">${isEdit ? 'Speichern' : 'Erstellen'}</button>
<div style="display:flex;gap:var(--space-2)">
<button type="submit" form="health-form" class="btn btn-primary w-full">${isEdit ? 'Speichern' : 'Erstellen'}</button>
<div class="flex-gap-2">
${isEdit ? `<button type="button" class="btn btn-danger" id="health-form-delete">Löschen</button>` : ''}
<button type="button" class="btn btn-secondary flex-1" id="health-form-cancel">Abbrechen</button>
</div>
@ -1384,7 +1384,7 @@ window.Page_health = (() => {
<button type="button" class="btn btn-ghost btn-sm" style="padding:0;font-size:inherit"
data-action="goto-praxen">Praxis im Tab Praxen anlegen</button>
</div>
<label class="form-label" style="margin-top:var(--space-2)">Tierarzt / Praxis (Freitext)</label>
<label class="form-label mt-2">Tierarzt / Praxis (Freitext)</label>
<input class="form-control" name="tierarzt_name"
value="${_esc(entry?.tierarzt_name || '')}" placeholder="Dr. Muster">
</div>`;
@ -1423,7 +1423,7 @@ window.Page_health = (() => {
<input class="form-control" type="text" name="haeufigkeit"
value="${_esc(entry?.haeufigkeit || '')}" placeholder="z.B. täglich, 2x wöchentlich">
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Gabe bis (optional)</label>
<input class="form-control" type="date" name="bis_datum" value="${entry?.bis_datum || ''}">
@ -1486,7 +1486,7 @@ window.Page_health = (() => {
</div>` : '';
return `
${lastInfo}
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Dauer (Tage)</label>
<input class="form-control" type="number" min="1" max="60" name="wert"
@ -1512,7 +1512,7 @@ window.Page_health = (() => {
text-transform:uppercase;letter-spacing:0.05em;margin-bottom:var(--space-3)">
Zucht (optional)
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Deckdatum</label>
<input class="form-control" type="date" name="deckdatum"
@ -1623,7 +1623,7 @@ window.Page_health = (() => {
const ratingHtml = hasRating
? `<div style="display:flex;align-items:center;gap:var(--space-1);margin-top:var(--space-1);font-size:var(--text-sm)">
${stars}
<span style="color:var(--c-text-secondary)">${p.avg_rating.toFixed(1)} (${p.anz_bewertungen} Bew.)</span>
<span class="text-secondary">${p.avg_rating.toFixed(1)} (${p.anz_bewertungen} Bew.)</span>
</div>`
: `<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:var(--space-1)">Noch keine Bewertungen</div>`;
return `
@ -1688,7 +1688,7 @@ window.Page_health = (() => {
const favCard = _favoritVet ? `
<div style="margin-bottom:var(--space-4)">
<div class="mb-4">
<div style="font-size:var(--text-xs);font-weight:600;color:var(--c-primary);
text-transform:uppercase;letter-spacing:.05em;margin-bottom:var(--space-2)">
${UI.icon('heart')} Mein Tierarzt
@ -1794,7 +1794,7 @@ window.Page_health = (() => {
<div style="padding:var(--space-3) 0;border-bottom:1px solid var(--c-border)">
<div style="display:flex;align-items:center;gap:var(--space-2);margin-bottom:var(--space-1)">
${_renderStarsReadonly(k.gesamt)}
<span style="font-size:var(--text-xs);color:var(--c-text-muted)">
<span class="text-xs-muted">
${k.created_at ? k.created_at.slice(0, 10) : ''}
</span>
</div>
@ -1806,7 +1806,7 @@ window.Page_health = (() => {
</div>` : ''}
<p style="margin:0;font-size:var(--text-sm)">${_esc(k.text || '')}</p>
</div>`).join('')
: `<p style="color:var(--c-text-muted);font-size:var(--text-sm)">Noch keine Kommentare.</p>`;
: `<p class="text-sm-muted">Noch keine Kommentare.</p>`;
const bewBody = anz_bewertungen === 0
? `<p style="color:var(--c-text-muted);text-align:center;padding:var(--space-4) 0">
@ -1814,12 +1814,12 @@ window.Page_health = (() => {
</p>`
: `
<div style="display:flex;align-items:center;gap:var(--space-4);margin-bottom:var(--space-4)">
<div style="text-align:center">
<div class="text-center">
<div style="font-size:3rem;font-weight:700;line-height:1">${avg_rating.toFixed(1)}</div>
<div>${_renderStarsReadonly(avg_rating)}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">${anz_bewertungen} Bewertung${anz_bewertungen !== 1 ? 'en' : ''}</div>
<div class="text-xs-muted">${anz_bewertungen} Bewertung${anz_bewertungen !== 1 ? 'en' : ''}</div>
</div>
<div style="flex:1">${balken}</div>
<div class="flex-1">${balken}</div>
</div>
<div>${kommentarHtml}</div>`;
@ -1845,22 +1845,22 @@ window.Page_health = (() => {
${_renderStarsInput('gesamt', cur.gesamt || 0)}
<input type="hidden" name="gesamt" id="bew-gesamt" value="${cur.gesamt || 0}">
</div>
<div class="form-group" style="margin-top:var(--space-3)">
<div class="form-group mt-3">
<label class="form-label">Wartezeit</label>
${_renderStarsInput('wartezeit', cur.wartezeit || 0)}
<input type="hidden" name="wartezeit" id="bew-wartezeit" value="${cur.wartezeit || 0}">
</div>
<div class="form-group" style="margin-top:var(--space-3)">
<div class="form-group mt-3">
<label class="form-label">Freundlichkeit</label>
${_renderStarsInput('freundlichkeit', cur.freundlichkeit || 0)}
<input type="hidden" name="freundlichkeit" id="bew-freundlichkeit" value="${cur.freundlichkeit || 0}">
</div>
<div class="form-group" style="margin-top:var(--space-3)">
<div class="form-group mt-3">
<label class="form-label">Kompetenz</label>
${_renderStarsInput('kompetenz', cur.kompetenz || 0)}
<input type="hidden" name="kompetenz" id="bew-kompetenz" value="${cur.kompetenz || 0}">
</div>
<div class="form-group" style="margin-top:var(--space-3)">
<div class="form-group mt-3">
<label class="form-label">Kommentar <span style="font-weight:400;color:var(--c-text-muted)">(optional, anonym)</span></label>
<textarea class="form-control" name="text" maxlength="500" rows="3"
placeholder="Deine Erfahrungen mit dieser Praxis…">${_esc(cur.text || '')}</textarea>
@ -1967,7 +1967,7 @@ window.Page_health = (() => {
value="${_esc(praxis?.ort || '')}" placeholder="Musterstadt">
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Telefon</label>
<input class="form-control" type="tel" name="telefon"
@ -2037,11 +2037,11 @@ window.Page_health = (() => {
const hits = await API.tieraerzte.osmNearby(pos.lat, pos.lon);
if (!hits.length) {
resultsEl.style.display = 'block';
resultsEl.innerHTML = '<p style="font-size:var(--text-sm);color:var(--c-text-secondary)">Keine Praxen in der Nähe im OSM-Cache gefunden.</p>';
resultsEl.innerHTML = '<p class="text-sm-secondary">Keine Praxen in der Nähe im OSM-Cache gefunden.</p>';
} else {
resultsEl.style.display = 'block';
resultsEl.innerHTML = hits.map(h => `
<div class="health-card" style="margin-bottom:var(--space-2)">
<div class="health-card mb-2">
<div style="cursor:pointer;flex:1"
data-osm-id="${_esc(h.osm_id)}"
data-name="${_esc(h.name)}"
@ -2049,8 +2049,8 @@ window.Page_health = (() => {
data-phone="${_esc(h.phone || '')}"
data-action="pick-osm">
<div style="font-weight:600">${_esc(h.name)}</div>
${h.opening_hours_fmt ? `<div style="font-size:var(--text-sm);color:var(--c-text-secondary)">${_esc(h.opening_hours_fmt)}</div>` : '<div style="font-size:var(--text-sm);color:var(--c-text-muted)">Öffnungszeiten unbekannt</div>'}
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">${h.distanz_km} km entfernt</div>
${h.opening_hours_fmt ? `<div class="text-sm-secondary">${_esc(h.opening_hours_fmt)}</div>` : '<div class="text-sm-muted">Öffnungszeiten unbekannt</div>'}
<div class="text-xs-secondary">${h.distanz_km} km entfernt</div>
</div>
<button class="btn btn-secondary btn-sm" style="flex-shrink:0;align-self:flex-start"
data-action="korrigieren"
@ -2130,8 +2130,8 @@ window.Page_health = (() => {
// ----------------------------------------------------------
function _renderSymptomCheck(content) {
content.innerHTML = `
<div style="padding:var(--space-4)">
<div class="card" style="padding:var(--space-4)">
<div class="p-4">
<div class="card p-4">
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);margin:0 0 var(--space-4)">
Beschreibe die Symptome deines Hundes. Die KI gibt eine erste Einschätzung kein Ersatz für den Tierarzt.
</p>
@ -2140,7 +2140,7 @@ window.Page_health = (() => {
<textarea id="symptom-input" class="form-control" rows="4"
placeholder="z.B. frisst nicht, trinkt viel, schläft mehr als sonst..."></textarea>
</div>
<button id="symptom-submit-btn" class="btn btn-primary" style="width:100%">
<button id="symptom-submit-btn" class="btn btn-primary w-full">
Symptome analysieren <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#magnifying-glass"></use></svg>
</button>
<div id="symptom-result" style="display:none;margin-top:var(--space-5)"></div>
@ -2261,7 +2261,7 @@ window.Page_health = (() => {
const nrEl = _container.querySelector('#health-transponder-nr');
if (nrEl) nrEl.innerHTML = nr
? `<strong>${_esc(nr)}</strong>`
: '<em style="color:var(--c-text-muted)">nicht eingetragen</em>';
: '<em class="text-muted">nicht eingetragen</em>';
} catch (e) {
UI.setLoading(btn, false);
UI.toast('Fehler beim Speichern', 'error');
@ -2299,7 +2299,7 @@ window.Page_health = (() => {
">
<div style="display:flex;align-items:center;gap:var(--space-2);margin-bottom:var(--space-1)">
<svg class="ph-icon" aria-hidden="true" style="flex-shrink:0"><use href="/icons/phosphor.svg#star"></use></svg>
<strong style="font-size:var(--text-sm)">KI-Gesundheitsbericht</strong>
<strong class="text-sm">KI-Gesundheitsbericht</strong>
${datum ? `<span style="font-size:var(--text-xs);color:var(--c-text-muted);margin-left:auto">${datum}</span>` : ''}
</div>
<div style="font-size:var(--text-sm);color:var(--c-text-muted);line-height:1.5">${preview}</div>
@ -2318,7 +2318,7 @@ window.Page_health = (() => {
<button onclick="window._kiPrev()" style="padding:6px 16px;border-radius:999px;
border:1.5px solid var(--c-border);background:var(--c-surface);cursor:pointer;
font-size:var(--text-sm);${idx >= berichte.length-1 ? 'opacity:.3;pointer-events:none' : ''}"> Älter</button>
<span style="font-size:var(--text-xs);color:var(--c-text-muted)">${idx+1} / ${berichte.length}</span>
<span class="text-xs-muted">${idx+1} / ${berichte.length}</span>
<button onclick="window._kiNext()" style="padding:6px 16px;border-radius:999px;
border:1.5px solid var(--c-border);background:var(--c-surface);cursor:pointer;
font-size:var(--text-sm);${idx <= 0 ? 'opacity:.3;pointer-events:none' : ''}">Neuer </button>
@ -2357,27 +2357,27 @@ window.Page_health = (() => {
});
el.innerHTML = `
<div style="margin-bottom:var(--space-3)">
<div class="mb-3">
<div style="font-size:var(--text-xs);font-weight:600;color:var(--c-text-muted);
text-transform:uppercase;letter-spacing:.05em;margin-bottom:var(--space-2)">
Terminvorschläge
</div>
<div style="display:flex;flex-direction:column;gap:var(--space-2)">
<div class="flex-col-gap-2">
${vorschlaege.map(v => {
const badge = v.ueberfaellig
? `<span style="font-size:var(--text-xs);color:var(--c-danger);font-weight:600">Überfällig seit ${_fmtDatum(v.naechstes)}</span>`
: `<span style="font-size:var(--text-xs);color:var(--c-warning);font-weight:600">Fällig am ${_fmtDatum(v.naechstes)}</span>`;
return `
<div class="health-card" style="flex-direction:row;align-items:center;gap:var(--space-3)">
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="font-weight:600;font-size:var(--text-sm)">${_esc(v.bezeichnung)}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">${_esc(v.label)}${v.praxis_name ? ' · ' + _esc(v.praxis_name) : ''}</div>
<div class="text-xs-secondary">${_esc(v.label)}${v.praxis_name ? ' · ' + _esc(v.praxis_name) : ''}</div>
${badge}
</div>
<div style="text-align:right;flex-shrink:0">
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">Vorschlag</div>
<div class="text-xs-muted">Vorschlag</div>
<div style="font-size:var(--text-sm);font-weight:600">${_fmtDatum(v.datum_vorschlag)}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">${v.uhrzeit_vorschlag} Uhr</div>
<div class="text-xs-secondary">${v.uhrzeit_vorschlag} Uhr</div>
<button class="btn btn-primary btn-sm" style="margin-top:var(--space-1)"
data-action="termin-anlegen"
data-v='${_esc(JSON.stringify(v))}'>
@ -2419,7 +2419,7 @@ window.Page_health = (() => {
<label class="form-label">Bezeichnung</label>
<input class="form-control" type="text" name="titel" value="${_esc(titel)}" required>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Datum</label>
<input class="form-control" type="date" name="datum" value="${_esc(v.datum_vorschlag)}" required>
@ -2488,12 +2488,12 @@ window.Page_health = (() => {
<div style="font-size:1.6rem;flex-shrink:0">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#first-aid"></use></svg>
</div>
<div class="health-card-body" style="flex:1;min-width:0">
<div class="health-card-body flex-1-min">
${vet ? `
<div class="health-card-title">${_esc(vet.name)}</div>
${adresse ? `<div class="health-card-meta">${_esc(adresse)}</div>` : ''}
${vet.telefon ? `
<div style="margin-top:var(--space-2)">
<div class="mt-2">
<a href="tel:${_esc(vet.telefon)}" class="btn btn-secondary btn-sm"
onclick="event.stopPropagation()">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#phone"></use></svg> ${_esc(vet.telefon)}
@ -2507,10 +2507,10 @@ window.Page_health = (() => {
</a>
</div>` : ''}
` : `
<div style="color:var(--c-text-muted);font-size:var(--text-sm)">
<div class="text-sm-muted">
Noch kein Tierarzt als Favorit gespeichert.
</div>
<button class="btn btn-secondary btn-sm" style="margin-top:var(--space-2)"
<button class="btn btn-secondary btn-sm mt-2"
id="health-suche-tierarzt-btn">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-pin"></use></svg> Tierarzt suchen
</button>
@ -2583,7 +2583,7 @@ window.Page_health = (() => {
<div style="font-size:1.4rem;flex-shrink:0;color:var(--c-primary)">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#${_esc(icon)}"></use></svg>
</div>
<div class="health-card-body" style="flex:1;min-width:0">
<div class="health-card-body flex-1-min">
<div class="health-card-title">${_esc(doc.titel)}</div>
<div class="health-card-meta">
${_esc(label)}${datum ? ' · ' + datum : ''}
@ -2597,7 +2597,7 @@ window.Page_health = (() => {
? '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#image"></use></svg> Bild öffnen'
: '<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#file-text"></use></svg> PDF öffnen'}
</a>
<button class="btn btn-ghost btn-xs" style="color:var(--c-danger)"
<button class="btn btn-ghost btn-xs text-danger"
data-action="delete-hdoc" data-doc-id="${doc.id}"
onclick="event.stopPropagation()">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#trash"></use></svg>
@ -2815,7 +2815,7 @@ window.Page_health = (() => {
<div class="form-group">
<label class="form-label">Aktuelle Angabe</label>
<input class="form-control" type="text" value="${_esc(currentOh)}" disabled
style="color:var(--c-text-muted)">
class="text-muted">
</div>
<div class="form-group">
<label class="form-label">Korrekte Öffnungszeiten *</label>
@ -2958,7 +2958,7 @@ window.Page_health = (() => {
<textarea id="ki-tierarzt-symptom" class="form-control" rows="4"
placeholder="${_esc(placeholder)}"></textarea>
</div>
<div id="ki-tierarzt-result" style="display:none"></div>
<div id="ki-tierarzt-result" class="hidden"></div>
<div style="margin-top:var(--space-3);padding:var(--space-3);
background:#fff3cd;border-radius:var(--radius-md);
font-size:var(--text-xs);color:#856404;
@ -3089,7 +3089,7 @@ window.Page_health = (() => {
<svg class="ph-icon" style="width:16px;height:16px;color:${color};flex-shrink:0" aria-hidden="true">
<use href="/icons/phosphor.svg#bell-ringing"></use>
</svg>
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<span style="font-weight:600;font-size:var(--text-sm);color:var(--c-text)">${_esc(r.bezeichnung)}</span>
<span style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-left:var(--space-1)">${TYPE_LABEL[r.typ] || r.typ}</span>
</div>
@ -3133,15 +3133,15 @@ window.Page_health = (() => {
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-2);margin-top:var(--space-3);font-size:var(--text-sm)">
<div><span style="color:var(--c-text-secondary)">Jahresbeitrag</span><br><strong>${_fmtEur(p.jahresbeitrag)}</strong></div>
<div><span style="color:var(--c-text-secondary)">Läuft ab</span><br><strong>${_fmtDate(p.ablaufdatum)}</strong></div>
${p.kontakt ? `<div style="grid-column:1/-1"><span style="color:var(--c-text-secondary)">Kontakt</span><br>${_esc(p.kontakt)}</div>` : ''}
${p.notizen ? `<div style="grid-column:1/-1"><span style="color:var(--c-text-secondary)">Notizen</span><br>${_esc(p.notizen)}</div>` : ''}
<div><span class="text-secondary">Jahresbeitrag</span><br><strong>${_fmtEur(p.jahresbeitrag)}</strong></div>
<div><span class="text-secondary">Läuft ab</span><br><strong>${_fmtDate(p.ablaufdatum)}</strong></div>
${p.kontakt ? `<div style="grid-column:1/-1"><span class="text-secondary">Kontakt</span><br>${_esc(p.kontakt)}</div>` : ''}
${p.notizen ? `<div style="grid-column:1/-1"><span class="text-secondary">Notizen</span><br>${_esc(p.notizen)}</div>` : ''}
</div>
</div>`).join('') : `
<div style="text-align:center;padding:var(--space-6);color:var(--c-text-muted)">
<svg class="ph-icon" style="width:2.5rem;height:2.5rem;margin-bottom:var(--space-3);display:block;margin-inline:auto" aria-hidden="true"><use href="/icons/phosphor.svg#shield-check"></use></svg>
<div style="font-size:var(--text-sm)">Noch keine Versicherung eingetragen.</div>
<div class="text-sm">Noch keine Versicherung eingetragen.</div>
</div>`;
content.innerHTML = `<div style="padding:var(--space-4) 0">
@ -3175,7 +3175,7 @@ window.Page_health = (() => {
<div class="by-form-group"><label class="by-label">Police-Nr.</label>
<input type="text" name="police_nr" class="form-control by-input" value="${_esc(existing?.police_nr||'')}" placeholder="optional">
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="by-form-group"><label class="by-label">Jahresbeitrag ()</label>
<input type="number" name="jahresbeitrag" class="form-control by-input" value="${existing?.jahresbeitrag||''}" min="0" step="0.01" placeholder="z. B. 149.00">
</div>
@ -3267,7 +3267,7 @@ window.Page_health = (() => {
return `
<div class="card" style="padding:var(--space-3);margin-bottom:var(--space-2);display:flex;align-items:flex-start;gap:var(--space-3)">
<div style="width:3px;border-radius:2px;background:${color};align-self:stretch;flex-shrink:0"></div>
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap">
<span style="font-weight:700;font-size:var(--text-sm);color:${color}">${_esc(katLabel)}</span>
${trigLabel ? `<span style="font-size:var(--text-xs);background:var(--c-surface-2);padding:1px 6px;border-radius:100px;color:var(--c-text-secondary)">${_esc(trigLabel)}</span>` : ''}
@ -3283,7 +3283,7 @@ window.Page_health = (() => {
}).join('') : `
<div style="text-align:center;padding:var(--space-6);color:var(--c-text-muted)">
<svg class="ph-icon" style="width:2.5rem;height:2.5rem;margin-bottom:var(--space-3);display:block;margin-inline:auto" aria-hidden="true"><use href="/icons/phosphor.svg#brain"></use></svg>
<div style="font-size:var(--text-sm)">Noch keine Einträge. Protokolliere auffälliges Verhalten um Muster zu erkennen.</div>
<div class="text-sm">Noch keine Einträge. Protokolliere auffälliges Verhalten um Muster zu erkennen.</div>
</div>`;
content.innerHTML = `<div style="padding:var(--space-4) 0">
@ -3309,7 +3309,7 @@ window.Page_health = (() => {
const today = new Date().toISOString().slice(0, 10);
const nowTime = (() => { const d=new Date(); return `${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`; })();
const body = `<form id="${id}">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="by-form-group"><label class="by-label">Datum</label>
<input type="date" name="datum" class="form-control by-input" value="${today}" required>
</div>
@ -3324,7 +3324,7 @@ window.Page_health = (() => {
</select>
</div>
<div class="by-form-group"><label class="by-label">Intensität (1 = gering, 5 = stark)</label>
<div style="display:flex;gap:var(--space-2)">
<div class="flex-gap-2">
${[1,2,3,4,5].map(n => `<button type="button" class="beh-int-btn" data-val="${n}"
style="flex:1;padding:10px;border-radius:8px;border:1.5px solid var(--c-border);
background:${n<=3?'var(--c-primary)':'var(--c-bg-card)'};

View file

@ -169,7 +169,7 @@ window.Page_hilfe = (() => {
display:flex;align-items:flex-start;gap:var(--space-2);
font-size:var(--text-sm);font-weight:600;
color:var(--c-text);line-height:1.4">
<span style="flex:1">${frageHtml}</span>
<span class="flex-1">${frageHtml}</span>
<svg id="${chevronId}" class="ph-icon" aria-hidden="true"
style="width:1rem;height:1rem;flex-shrink:0;margin-top:2px;
color:var(--c-text-muted);

View file

@ -26,12 +26,12 @@ window.Page_impressum = (() => {
color:var(--c-text);margin:0 0 var(--space-2)">Kontakt</h2>
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.7;margin:0 0 var(--space-4)">
E-Mail: <a href="mailto:hallo@banyaro.app"
style="color:var(--c-primary)">hallo@banyaro.app</a><br>
class="text-primary">hallo@banyaro.app</a><br>
Oder nutze das Formular wir antworten in der Regel innerhalb von 24 Stunden.
</p>
<form id="contact-form" style="display:flex;flex-direction:column;gap:var(--space-3)">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<form id="contact-form" class="flex-col-gap-3">
<div class="grid-2">
<div>
<label for="cf-name" style="display:block;font-size:var(--text-xs);font-weight:600;margin-bottom:4px;color:var(--c-text)">Name *</label>
<input id="cf-name" type="text" required maxlength="100"

View file

@ -44,7 +44,7 @@ window.Page_jobs = (() => {
</div>
<!-- Stellenbeschreibung -->
<div class="card" style="margin-bottom:var(--space-4)">
<div class="card mb-4">
<div style="padding:var(--space-5)">
<h2 style="font-size:var(--text-lg);font-weight:700;margin:0 0 var(--space-4);color:var(--c-primary)">Die Stelle</h2>
<div style="display:grid;gap:var(--space-3)">
@ -76,7 +76,7 @@ window.Page_jobs = (() => {
</div>
<!-- Wen wir suchen -->
<div class="card" style="margin-bottom:var(--space-4)">
<div class="card mb-4">
<div style="padding:var(--space-5)">
<h2 style="font-size:var(--text-lg);font-weight:700;margin:0 0 var(--space-4);color:var(--c-primary)">Wen wir suchen</h2>
<ul style="margin:0;padding-left:var(--space-5);display:grid;gap:var(--space-2);color:var(--c-text-secondary);font-size:var(--text-sm)">
@ -121,7 +121,7 @@ window.Page_jobs = (() => {
const s = statusMap[app.status] || statusMap.pending;
return `
<div class="card" style="padding:var(--space-5);text-align:center">
<div style="margin-bottom:var(--space-3)">
<div class="mb-3">
<svg class="ph-icon" aria-hidden="true" style="width:48px;height:48px;color:${s.color}"><use href="/icons/phosphor.svg#${s.icon}"></use></svg>
</div>
<div style="font-weight:700;color:${s.color};font-size:var(--text-lg);margin-bottom:var(--space-2)">${s.text}</div>
@ -194,7 +194,7 @@ window.Page_jobs = (() => {
<label class="form-label">Anhänge (optional)</label>
<input class="form-control" type="file" name="files" id="jobs-files"
multiple accept=".pdf,.jpg,.jpeg,.png,.webp,.mp4,.mov"
style="padding:var(--space-2)">
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.
@ -205,7 +205,7 @@ window.Page_jobs = (() => {
padding:var(--space-3);font-size:var(--text-sm);color:var(--c-text-secondary);
margin-bottom:var(--space-4)">
💡 <b>Tipp:</b> Wenn du dich vorher
<a href="#" id="jobs-login-link" style="color:var(--c-primary)">anmeldest oder registrierst</a>,
<a href="#" id="jobs-login-link" class="text-primary">anmeldest oder registrierst</a>,
bekommst du sofort den 14-tägigen Luna-Probezugang.
</div>` : ''}

View file

@ -173,7 +173,7 @@ window.Page_knigge = (() => {
// ----------------------------------------------------------
function _renderVoting() {
const cards = SZENARIEN.map(s => `
<div class="card" style="margin-bottom:var(--space-4)" id="sz-${s.id}">
<div class="card mb-4" id="sz-${s.id}">
<p style="font-weight:var(--weight-semibold);margin:0;padding:var(--space-5) var(--space-5) var(--space-3);line-height:1.5">
${_esc(s.frage)}
</p>
@ -260,12 +260,12 @@ window.Page_knigge = (() => {
? 'var(--c-success, #22c55e)'
: (isU && !isR ? 'var(--c-danger, #ef4444)' : 'var(--c-border)');
return `
<div style="margin-bottom:var(--space-3)">
<div class="mb-3">
<div style="display:flex;justify-content:space-between;margin-bottom:4px;font-size:var(--text-sm)">
<span style="color:${isU ? 'var(--c-text)' : 'var(--c-text-secondary)'};font-weight:${isU ? 'var(--weight-semibold)' : 'normal'}">
${isU ? UI.icon('arrow-right') + ' ' : ''}${_esc(a.text)}${isR ? ' ' + UI.icon('check') : ''}
</span>
<span style="color:var(--c-text-secondary)">${pct}% (${cnt})</span>
<span class="text-secondary">${pct}% (${cnt})</span>
</div>
<div style="background:var(--c-surface-2);border-radius:4px;height:8px;overflow:hidden">
<div style="width:${pct}%;background:${color};height:8px;border-radius:4px;transition:width 0.4s"></div>
@ -282,7 +282,7 @@ window.Page_knigge = (() => {
<div style="margin-bottom:var(--space-4);padding:0 var(--space-5)">${bars}</div>
<div style="background:var(--c-surface-2);border-radius:var(--radius-md);padding:var(--space-3) var(--space-5);font-size:var(--text-sm);line-height:1.5">
${badge}
<span style="color:var(--c-text-secondary)">${_esc(szenario.erklaerung)}</span>
<span class="text-secondary">${_esc(szenario.erklaerung)}</span>
</div>
`;
}
@ -300,8 +300,8 @@ window.Page_knigge = (() => {
<textarea id="ki-situation-input" class="form-control"
rows="3"
placeholder="Beschreibe deine Situation…"
style="margin-bottom:var(--space-3)"></textarea>
<button class="btn btn-primary" id="ki-rat-btn" style="width:100%">
class="mb-3"></textarea>
<button class="btn btn-primary" id="ki-rat-btn" class="w-full">
Rat holen ${UI.icon('robot')}
</button>
<div id="ki-rat-result" style="margin-top:var(--space-4);display:none"></div>

View file

@ -14,7 +14,7 @@ window.Page_laeufi = (() => {
_appState = appState;
if (!appState.user || !['breeder','admin'].includes(appState.user.rolle)) {
_container.innerHTML = `<div style="text-align:center;padding:var(--space-10)">
<p style="color:var(--c-text-secondary)">Nur für verifizierte Züchter.</p></div>`;
<p class="text-secondary">Nur für verifizierte Züchter.</p></div>`;
return;
}
API.breeder.status().then(s => {
@ -53,7 +53,7 @@ window.Page_laeufi = (() => {
padding:var(--space-3) var(--space-4);
display:flex;align-items:center;gap:var(--space-3)">
${logoHtml}
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<h2 style="margin:0 0 2px;font-size:var(--text-lg);font-weight:700;
color:var(--c-text);white-space:nowrap;overflow:hidden;
text-overflow:ellipsis;line-height:1.2">${UI.escape(zwinger)}</h2>
@ -61,7 +61,7 @@ window.Page_laeufi = (() => {
<svg style="width:11px;height:11px;color:var(--c-primary);flex-shrink:0" viewBox="0 0 256 256">
<use href="/icons/phosphor.svg#lock-key"></use>
</svg>
<span style="font-size:var(--text-xs);color:var(--c-text-secondary)">Privater Bereich · Nur du siehst das</span>
<span class="text-xs-secondary">Privater Bereich · Nur du siehst das</span>
</div>
</div>
</div>`;
@ -89,7 +89,7 @@ window.Page_laeufi = (() => {
_renderHundeList();
} catch (err) {
document.getElementById('laeufi-list').innerHTML =
`<p style="color:var(--c-danger)">${UI.escape(err.message || 'Fehler')}</p>`;
`<p class="text-danger">${UI.escape(err.message || 'Fehler')}</p>`;
}
}
@ -129,22 +129,22 @@ window.Page_laeufi = (() => {
<div id="laeufi-toggle-${h.id}"
style="padding:var(--space-4);display:flex;align-items:center;gap:var(--space-3);
cursor:pointer;user-select:none">
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap">
<span style="font-size:var(--text-base);font-weight:700">${UI.escape(h.name)}</span>
${h.rufname ? `<span style="color:var(--c-text-muted);font-size:var(--text-sm)">"${UI.escape(h.rufname)}"</span>` : ''}
${alter ? `<span style="font-size:var(--text-xs);color:var(--c-text-muted)">${alter}</span>` : ''}
${h.rufname ? `<span class="text-sm-muted">"${UI.escape(h.rufname)}"</span>` : ''}
${alter ? `<span class="text-xs-muted">${alter}</span>` : ''}
</div>
${h.rasse_text || h.farbe ? `<div style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-top:2px">
${[h.rasse_text, h.farbe].filter(Boolean).map(s => UI.escape(s)).join(' · ')}
</div>` : ''}
</div>
<span style="color:var(--c-text-muted)">${UI.icon('caret-down')}</span>
<span class="text-muted">${UI.icon('caret-down')}</span>
</div>
<div id="laeufi-detail-${h.id}" style="display:none;border-top:1px solid var(--c-border)">
<div id="laeufi-content-${h.id}"
style="padding:var(--space-4)">
<p style="color:var(--c-text-muted);font-size:var(--text-sm)">Lädt</p>
class="p-4">
<p class="text-sm-muted">Lädt</p>
</div>
</div>
</div>`;
@ -177,7 +177,7 @@ window.Page_laeufi = (() => {
]);
_renderHundContent(el, hundId, laeufiList, deckList);
} catch (err) {
el.innerHTML = `<p style="color:var(--c-danger)">${UI.escape(err.message || 'Fehler')}</p>`;
el.innerHTML = `<p class="text-danger">${UI.escape(err.message || 'Fehler')}</p>`;
}
}
@ -270,11 +270,11 @@ window.Page_laeufi = (() => {
return list.map(l => `
<div style="background:var(--c-surface);border:1px solid var(--c-border);border-radius:var(--radius-md);
padding:var(--space-3);margin-bottom:var(--space-2);display:flex;gap:var(--space-3);align-items:flex-start">
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap">
<span style="font-weight:600;font-size:var(--text-sm)">${_fmtDate(l.beginn)}</span>
${l.ende ? `<span style="font-size:var(--text-xs);color:var(--c-text-muted)">→ ${_fmtDate(l.ende)}</span>
<span style="font-size:var(--text-xs);color:var(--c-text-muted)">${_daysDiff(l.beginn, l.ende)} Tage</span>` : ''}
${l.ende ? `<span class="text-xs-muted">→ ${_fmtDate(l.ende)}</span>
<span class="text-xs-muted">${_daysDiff(l.beginn, l.ende)} Tage</span>` : ''}
</div>
${l.notiz ? `<p style="font-size:var(--text-xs);color:var(--c-text-secondary);margin:var(--space-1) 0 0;font-style:italic">${UI.escape(l.notiz)}</p>` : ''}
</div>
@ -286,7 +286,7 @@ window.Page_laeufi = (() => {
${UI.icon('pencil-simple')}
</button>
<button class="btn btn-ghost btn-xs laeufi-delete-btn" data-id="${l.id}"
title="Löschen" style="color:var(--c-danger)">
title="Löschen" class="text-danger">
${UI.icon('trash')}
</button>
</div>
@ -314,7 +314,7 @@ window.Page_laeufi = (() => {
margin-bottom:var(--space-3);overflow:hidden">
<!-- Deck-Header -->
<div style="padding:var(--space-3);display:flex;gap:var(--space-3);align-items:flex-start">
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;margin-bottom:var(--space-1)">
<span style="font-weight:700;font-size:var(--text-sm)">${UI.icon('heart')} Deckung ${_fmtDate(d.deckdatum)}</span>
<span style="background:${tc.color}1a;color:${tc.color};border:1px solid ${tc.color}30;
@ -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" style="color:var(--c-danger)">${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>
<!-- Meilensteine -->
@ -358,7 +358,7 @@ window.Page_laeufi = (() => {
color:${m.vorbei ? 'white' : 'var(--c-text-muted)'};font-size:9px">
${m.vorbei ? '✓' : m.tag}
</span>
<span style="color:var(--c-text-secondary)">${_fmtDate(m.datum)}</span>
<span class="text-secondary">${_fmtDate(m.datum)}</span>
<span style="color:${m.vorbei ? 'var(--c-text-muted)' : 'var(--c-text)'};font-weight:${m.vorbei ? '400' : '600'}">
${UI.escape(m.label)}
</span>
@ -377,8 +377,8 @@ window.Page_laeufi = (() => {
UI.modal.open({
title: isEdit ? 'Läufigkeit bearbeiten' : 'Läufigkeit eintragen',
body: `
<form id="laeufi-form" style="display:flex;flex-direction:column;gap:var(--space-3)">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<form id="laeufi-form" class="flex-col-gap-3">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Beginn *</label>
<input class="form-control" type="date" name="beginn" required value="${v.beginn || today}">
@ -421,8 +421,8 @@ window.Page_laeufi = (() => {
UI.modal.open({
title: isEdit ? 'Deckung bearbeiten' : 'Deckung eintragen',
body: `
<form id="deck-form" style="display:flex;flex-direction:column;gap:var(--space-3)">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<form id="deck-form" class="flex-col-gap-3">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Deckdatum *</label>
<input class="form-control" type="date" name="deckdatum" required value="${v.deckdatum || today}">
@ -435,7 +435,7 @@ window.Page_laeufi = (() => {
</select>
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Rüde</label>
<input class="form-control" name="ruede_name" placeholder="Name des Deckrüden"
@ -451,7 +451,7 @@ window.Page_laeufi = (() => {
</select>
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Trächtigkeitsstatus</label>
<select class="form-control" name="traechtig">
@ -503,7 +503,7 @@ window.Page_laeufi = (() => {
async function _showProgModal(hundId, laeufi) {
UI.modal.open({
title: `Progesterontests — ${_fmtDate(laeufi.beginn)}`,
body: `<div id="prog-modal-content"><p style="color:var(--c-text-muted)">Lädt…</p></div>`,
body: `<div id="prog-modal-content"><p class="text-muted">Lädt…</p></div>`,
footer: `
<button class="btn btn-secondary" onclick="UI.modal.close()">Schließen</button>
<button class="btn btn-primary" id="prog-add-btn">${UI.icon('plus')} Test eintragen</button>`,
@ -535,7 +535,7 @@ window.Page_laeufi = (() => {
<tbody>
${tests.map(t => `
<tr style="border-top:1px solid var(--c-border)">
<td style="padding:var(--space-2)">${_fmtDate(t.datum)}</td>
<td class="p-2">${_fmtDate(t.datum)}</td>
<td style="text-align:right;padding:var(--space-2);font-weight:600">
${t.wert != null ? `${t.wert} ${UI.escape(t.einheit)}` : '—'}
${t.wert != null ? `<span style="font-size:10px;margin-left:4px;color:var(--c-text-muted)">${_progEinschaetzung(t.wert, t.einheit)}</span>` : ''}
@ -543,7 +543,7 @@ window.Page_laeufi = (() => {
<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}"
style="color:var(--c-danger)">${UI.icon('trash')}</button>
class="text-danger">${UI.icon('trash')}</button>
</td>
</tr>`).join('')}
</tbody>
@ -572,8 +572,8 @@ window.Page_laeufi = (() => {
UI.modal.open({
title: 'Progesterontest eintragen',
body: `
<form id="prog-form" style="display:flex;flex-direction:column;gap:var(--space-3)">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<form id="prog-form" class="flex-col-gap-3">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Datum *</label>
<input class="form-control" type="date" name="datum" required value="${today}">
@ -586,7 +586,7 @@ window.Page_laeufi = (() => {
</select>
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Wert</label>
<input class="form-control" type="number" step="0.01" name="wert" placeholder="z.B. 8.5">

View file

@ -118,7 +118,7 @@ window.Page_litters = (() => {
padding:var(--space-3) var(--space-4);
display:flex;align-items:center;gap:var(--space-3)">
${logoHtml}
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<h2 style="margin:0 0 2px;font-size:var(--text-lg);font-weight:700;
color:var(--c-text);white-space:nowrap;overflow:hidden;
text-overflow:ellipsis;line-height:1.2">${_esc(zwinger)}</h2>
@ -126,7 +126,7 @@ window.Page_litters = (() => {
<svg style="width:11px;height:11px;color:var(--c-primary);flex-shrink:0" viewBox="0 0 256 256">
<use href="/icons/phosphor.svg#lock-key"></use>
</svg>
<span style="font-size:var(--text-xs);color:var(--c-text-secondary)">Privater Bereich · Nur du siehst das</span>
<span class="text-xs-secondary">Privater Bereich · Nur du siehst das</span>
</div>
</div>
</div>`;
@ -232,7 +232,7 @@ window.Page_litters = (() => {
el.innerHTML = `
<div style="text-align:center;padding:var(--space-8) var(--space-4);
border:1px dashed var(--c-border);border-radius:var(--radius-lg)">
<p style="color:var(--c-text-muted)">Keine Würfe für diesen Filter.</p>
<p class="text-muted">Keine Würfe für diesen Filter.</p>
</div>`;
return;
}
@ -248,8 +248,8 @@ window.Page_litters = (() => {
el.innerHTML = `
<div style="text-align:center;padding:var(--space-10) var(--space-4)">
<div style="font-size:3rem;margin-bottom:var(--space-3)">${UI.icon('dog')}</div>
<p style="color:var(--c-text-secondary)">Noch keine Würfe angelegt.</p>
<button class="btn btn-primary" style="margin-top:var(--space-4)" id="litters-first-btn">
<p class="text-secondary">Noch keine Würfe angelegt.</p>
<button class="btn btn-primary mt-4" id="litters-first-btn">
${UI.icon('plus')} Ersten Wurf anlegen
</button>
</div>`;
@ -325,10 +325,10 @@ window.Page_litters = (() => {
const label = l.geburt_datum ? `Geburt ${_fmtDate(l.geburt_datum)}` : `Erwartet ${_fmtDate(l.erwartetes_datum)}`;
let countdownHtml = '';
if (days !== null && !l.geburt_datum) {
const c = days < 0 ? `<span style="color:var(--c-danger)">überfällig</span>`
: days === 0 ? `<span style="color:var(--c-success)">heute!</span>`
const c = days < 0 ? `<span class="text-danger">überfällig</span>`
: days === 0 ? `<span class="text-success">heute!</span>`
: days <= 7 ? `<span style="color:var(--c-warning,#f59e0b)">${days}d</span>`
: `<span style="color:var(--c-text-muted)">${days}d</span>`;
: `<span class="text-muted">${days}d</span>`;
countdownHtml = ` · ${c}`;
}
datumChip = `<span style="display:inline-flex;align-items:center;gap:4px;font-size:var(--text-xs);color:var(--c-text-secondary)">${UI.icon('calendar-dots')} ${label}${countdownHtml}</span>`;
@ -359,7 +359,7 @@ window.Page_litters = (() => {
${l.wurf_name ? `<span style="font-size:var(--text-base);font-weight:700;color:var(--c-text)">${_esc(l.wurf_name)}</span>` : ''}
</div>` : ''}
<div style="display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;margin-bottom:var(--space-2)">
<span style="font-size:var(--text-sm);color:var(--c-text-secondary)">${elternLabel}</span>
<span class="text-sm-secondary">${elternLabel}</span>
${_statusBadge(l.status)}
${sichtbarChip}
</div>
@ -390,7 +390,7 @@ window.Page_litters = (() => {
${UI.icon('pencil-simple')}
</button>
<button class="btn btn-ghost btn-sm litters-delete-btn" data-id="${l.id}" title="Löschen"
style="color:var(--c-danger)">
class="text-danger">
${UI.icon('trash')}
</button>
</div>
@ -401,10 +401,10 @@ window.Page_litters = (() => {
<!-- Welpen-Bereich -->
<div id="puppies-wrap-${l.id}" style="display:none;padding:var(--space-3) var(--space-4)">
<div id="puppies-inner-${l.id}">
<p style="color:var(--c-text-muted);font-size:var(--text-sm)">Lädt</p>
<p class="text-sm-muted">Lädt</p>
</div>
<button class="btn btn-secondary btn-sm litters-add-puppy-btn" data-id="${l.id}"
style="margin-top:var(--space-3)">
class="mt-3">
${UI.icon('plus')} Welpen hinzufügen
</button>
</div>
@ -412,10 +412,10 @@ window.Page_litters = (() => {
<!-- Wartelisten-Bereich -->
<div id="waitlist-wrap-${l.id}" style="display:none;padding:var(--space-3) var(--space-4)">
<div id="waitlist-inner-${l.id}">
<p style="color:var(--c-text-muted);font-size:var(--text-sm)">Lädt</p>
<p class="text-sm-muted">Lädt</p>
</div>
<button class="btn btn-secondary btn-sm litters-add-waitlist-btn" data-id="${l.id}"
style="margin-top:var(--space-3)">
class="mt-3">
${UI.icon('plus')} Interessent eintragen
</button>
</div>
@ -461,7 +461,7 @@ window.Page_litters = (() => {
function _renderPuppies(container, litterId, puppies) {
if (!puppies.length) {
container.innerHTML = `<p style="color:var(--c-text-muted);font-size:var(--text-sm)">Noch keine Welpen eingetragen.</p>`;
container.innerHTML = `<p class="text-sm-muted">Noch keine Welpen eingetragen.</p>`;
return;
}
@ -469,10 +469,10 @@ window.Page_litters = (() => {
<div class="litters-puppy-row" data-puppy-id="${p.id}">
<div class="litters-puppy-info">
${_genderIcon(p.geschlecht)}
<span class="litters-puppy-name">${p.name ? _esc(p.name) : '<em style="color:var(--c-text-muted)">Unbenannt</em>'}</span>
<span class="litters-puppy-name">${p.name ? _esc(p.name) : '<em class="text-muted">Unbenannt</em>'}</span>
${p.farbe ? `<span style="color:var(--c-text-secondary);font-size:var(--text-xs)">${_esc(p.farbe)}</span>` : ''}
${_puppyStatusBadge(p.status)}
<span class="litters-puppy-last-weight" id="puppy-last-weight-${p.id}" style="font-size:var(--text-xs);color:var(--c-text-secondary)"></span>
<span class="litters-puppy-last-weight" id="puppy-last-weight-${p.id}" class="text-xs-secondary"></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}"
@ -542,16 +542,16 @@ window.Page_litters = (() => {
const puppyLabel = puppy.name || 'Welpe';
const body = `
<div id="weight-history" style="margin-bottom:var(--space-3)">
<p style="color:var(--c-text-muted);font-size:var(--text-sm)">Lädt</p>
<div id="weight-history" class="mb-3">
<p class="text-sm-muted">Lädt</p>
</div>
<hr style="margin:var(--space-3) 0;border:none;border-top:1px solid var(--c-border)">
<form id="weight-form" style="display:flex;gap:var(--space-2);align-items:flex-end">
<div style="flex:1">
<div class="flex-1">
<label style="display:block;font-size:var(--text-xs);color:var(--c-text-secondary);margin-bottom:var(--space-1)">Gewicht (g)</label>
<input class="form-control" name="gewicht_g" type="number" min="1" max="99999" step="1" required placeholder="z. B. 420">
</div>
<div style="flex:1">
<div class="flex-1">
<label style="display:block;font-size:var(--text-xs);color:var(--c-text-secondary);margin-bottom:var(--space-1)">Datum</label>
<input class="form-control" name="gemessen_am" type="date" required value="${today}">
</div>
@ -600,7 +600,7 @@ window.Page_litters = (() => {
try {
const weights = await API.get(`/litters/puppies/${puppyId}/weights`);
if (!weights || !weights.length) {
el.innerHTML = `<p style="color:var(--c-text-muted);font-size:var(--text-sm)">Noch keine Messungen eingetragen.</p>`;
el.innerHTML = `<p class="text-sm-muted">Noch keine Messungen eingetragen.</p>`;
return;
}
@ -630,22 +630,22 @@ window.Page_litters = (() => {
el.innerHTML = `
<!-- Stats-Zeile -->
<div style="display:flex;gap:var(--space-3);flex-wrap:wrap;margin-bottom:var(--space-3)">
<div style="text-align:center">
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">Aktuell</div>
<div class="text-center">
<div class="text-xs-muted">Aktuell</div>
<div style="font-size:var(--text-base);font-weight:700;color:var(--c-primary)">${last} g</div>
</div>
<div style="text-align:center">
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">Zunahme</div>
<div class="text-center">
<div class="text-xs-muted">Zunahme</div>
<div style="font-size:var(--text-base);font-weight:700;color:${gain >= 0 ? 'var(--c-success)' : 'var(--c-danger)'}">
${gain >= 0 ? '+' : ''}${gain} g
</div>
</div>
<div style="text-align:center">
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">Ø tägl.</div>
<div class="text-center">
<div class="text-xs-muted">Ø tägl.</div>
<div style="font-size:var(--text-base);font-weight:700;color:var(--c-text)">${dailyGain} g</div>
</div>
<div style="text-align:center">
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">Messungen</div>
<div class="text-center">
<div class="text-xs-muted">Messungen</div>
<div style="font-size:var(--text-base);font-weight:700;color:var(--c-text)">${weights.length}</div>
</div>
</div>
@ -764,22 +764,22 @@ window.Page_litters = (() => {
<div style="text-align:center;padding:var(--space-6) var(--space-4);border:1px dashed var(--c-border);border-radius:var(--radius-md)">
<div style="font-size:2rem;margin-bottom:var(--space-2)">${UI.icon('users')}</div>
<p style="font-weight:600;font-size:var(--text-sm);color:var(--c-text);margin-bottom:var(--space-1)">Noch keine Interessenten</p>
<p style="font-size:var(--text-xs);color:var(--c-text-muted)">Trage Anfragen ein mit Wunsch-Geschlecht, Kontaktdaten und Status.</p>
<p class="text-xs-muted">Trage Anfragen ein mit Wunsch-Geschlecht, Kontaktdaten und Status.</p>
</div>`;
return;
}
container.innerHTML = header + `
<div style="display:flex;flex-direction:column;gap:var(--space-2)">
<div class="flex-col-gap-2">
${entries.map((e, i) => `
<div style="background:var(--c-bg-secondary);border-radius:var(--radius-md);padding:var(--space-3) var(--space-3);display:flex;gap:var(--space-3);align-items:flex-start" data-entry-id="${e.id}">
<div style="background:var(--c-primary);color:white;border-radius:50%;width:1.6rem;height:1.6rem;display:flex;align-items:center;justify-content:center;font-size:var(--text-xs);font-weight:700;flex-shrink:0;margin-top:2px">${i + 1}</div>
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap;margin-bottom:var(--space-1)">
<span style="font-weight:600;font-size:var(--text-sm)">${_esc(e.name)}</span>
${_wlStatusBadge(e.status)}
${e.wunsch_geschlecht && e.wunsch_geschlecht !== 'egal' ? `<span style="font-size:var(--text-xs);color:var(--c-text-secondary)">${e.wunsch_geschlecht === 'maennlich' ? '♂ Rüde' : '♀ Hündin'}</span>` : ''}
${e.wunsch_farbe ? `<span style="font-size:var(--text-xs);color:var(--c-text-secondary)">${_esc(e.wunsch_farbe)}</span>` : ''}
${e.wunsch_geschlecht && e.wunsch_geschlecht !== 'egal' ? `<span class="text-xs-secondary">${e.wunsch_geschlecht === 'maennlich' ? '♂ Rüde' : '♀ Hündin'}</span>` : ''}
${e.wunsch_farbe ? `<span class="text-xs-secondary">${_esc(e.wunsch_farbe)}</span>` : ''}
</div>
<div style="display:flex;gap:var(--space-4);flex-wrap:wrap;font-size:var(--text-xs);color:var(--c-text-secondary)">
${e.email ? `<span>${UI.icon('envelope')} ${_esc(e.email)}</span>` : ''}
@ -791,7 +791,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" style="color:var(--c-danger)">${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>`).join('')}
</div>`;
@ -820,12 +820,12 @@ window.Page_litters = (() => {
UI.modal.open({
title: isEdit ? 'Interessent bearbeiten' : 'Interessent eintragen',
body: `
<form id="wl-form" style="display:flex;flex-direction:column;gap:var(--space-3)">
<form id="wl-form" class="flex-col-gap-3">
<div class="form-group">
<label class="form-label">Name *</label>
<input class="form-control" name="name" required value="${_esc(v.name || '')}">
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">E-Mail</label>
<input class="form-control" type="email" name="email" value="${_esc(v.email || '')}">
@ -835,7 +835,7 @@ window.Page_litters = (() => {
<input class="form-control" name="telefon" value="${_esc(v.telefon || '')}">
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Wunsch Geschlecht</label>
<select class="form-control" name="wunsch_geschlecht">
@ -853,7 +853,7 @@ window.Page_litters = (() => {
<label class="form-label">Nachricht des Interessenten</label>
<textarea class="form-control" name="nachricht" rows="2" placeholder="Was hat der Interessent geschrieben?">${_esc(v.nachricht || '')}</textarea>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Status</label>
<select class="form-control" name="status">
@ -922,7 +922,7 @@ window.Page_litters = (() => {
return `<option value="${h.id}" data-name="${_esc(h.name)}" ${currentId == h.id ? 'selected' : ''}>${_esc(label)}</option>`;
}).join('');
return `
<select class="form-control" name="${idName}" id="${idName}-sel" style="margin-bottom:var(--space-2)">
<select class="form-control" name="${idName}" id="${idName}-sel" class="mb-2">
<option value=""> ${placeholder} </option>
${opts}
</select>
@ -953,7 +953,7 @@ window.Page_litters = (() => {
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Vater</label>
${buildSelect('vater_name', 'vater_id', maennlich, v.vater_id, v.vater_name, 'Aus Zuchtkartei')}
@ -979,7 +979,7 @@ window.Page_litters = (() => {
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Welpen gesamt</label>
<input class="form-control" type="number" name="welpen_gesamt" min="0"
@ -1009,13 +1009,13 @@ window.Page_litters = (() => {
</div>
<div class="form-group">
<label class="form-label">Beschreibung <span style="color:var(--c-text-secondary)">(optional)</span></label>
<label class="form-label">Beschreibung <span class="text-secondary">(optional)</span></label>
<textarea class="form-control" name="beschreibung" rows="3"
placeholder="Elternlinie, Besonderheiten, Charakter…">${_esc(v.beschreibung || '')}</textarea>
</div>
<div class="form-group">
<label class="form-label">Gesundheitstests <span style="color:var(--c-text-secondary)">(optional)</span></label>
<label class="form-label">Gesundheitstests <span class="text-secondary">(optional)</span></label>
<textarea class="form-control" name="gesundheitstests" rows="2"
placeholder="HD, ED, Gentest, Augenkontrolle…">${_esc(v.gesundheitstests || '')}</textarea>
</div>
@ -1028,7 +1028,7 @@ window.Page_litters = (() => {
</div>
<div class="form-group">
<label class="form-label">Sichtbar bis <span style="color:var(--c-text-secondary)">(optional)</span></label>
<label class="form-label">Sichtbar bis <span class="text-secondary">(optional)</span></label>
<input class="form-control" type="date" name="sichtbar_bis"
value="${_esc(v.sichtbar_bis || '')}">
</div>
@ -1134,9 +1134,9 @@ window.Page_litters = (() => {
const body = `
<form id="puppy-form" autocomplete="off">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Name <span style="color:var(--c-text-secondary)">(optional)</span></label>
<label class="form-label">Name <span class="text-secondary">(optional)</span></label>
<input class="form-control" type="text" name="name"
value="${_esc(v.name || '')}" placeholder="z. B. Max">
</div>
@ -1165,7 +1165,7 @@ window.Page_litters = (() => {
</select>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Chip-Nr.</label>
<input class="form-control" type="text" name="chip_nr"
@ -1186,7 +1186,7 @@ window.Page_litters = (() => {
</div>
<div class="form-group">
<label class="form-label">Notiz <span style="color:var(--c-text-secondary)">(intern)</span></label>
<label class="form-label">Notiz <span class="text-secondary">(intern)</span></label>
<textarea class="form-control" name="notiz" rows="2"
placeholder="Interne Notizen…">${_esc(v.notiz || '')}</textarea>
</div>
@ -1249,22 +1249,22 @@ window.Page_litters = (() => {
const body = `
<form id="contract-form" autocomplete="off">
<div class="form-group">
<label class="form-label">Name des Käufers <span style="color:var(--c-danger)">*</span></label>
<label class="form-label">Name des Käufers <span class="text-danger">*</span></label>
<input class="form-control" type="text" name="kaeufer_name" required
placeholder="Vor- und Nachname">
</div>
<div class="form-group">
<label class="form-label">Adresse des Käufers <span style="color:var(--c-danger)">*</span></label>
<label class="form-label">Adresse des Käufers <span class="text-danger">*</span></label>
<textarea class="form-control" name="kaeufer_adresse" rows="2" required
placeholder="Straße, PLZ, Ort"></textarea>
</div>
<div class="form-group">
<label class="form-label">E-Mail des Käufers <span style="color:var(--c-text-secondary)">(optional)</span></label>
<label class="form-label">E-Mail des Käufers <span class="text-secondary">(optional)</span></label>
<input class="form-control" type="email" name="kaeufer_email"
placeholder="kaeufer@beispiel.de">
</div>
<div class="form-group">
<label class="form-label">Kaufpreis <span style="color:var(--c-text-secondary)">(optional)</span></label>
<label class="form-label">Kaufpreis <span class="text-secondary">(optional)</span></label>
<input class="form-control" type="text" name="preis"
placeholder="z. B. 1.500 €">
</div>
@ -1317,11 +1317,11 @@ window.Page_litters = (() => {
const visOrder = ['public', 'inquiry', 'private'];
const body = `
<div id="${galleryId}" style="margin-bottom:var(--space-4)">
<p style="color:var(--c-text-muted);font-size:var(--text-sm)">Lädt</p>
<div id="${galleryId}" class="mb-4">
<p class="text-sm-muted">Lädt</p>
</div>
<hr style="margin:var(--space-3) 0;border:none;border-top:1px solid var(--c-border)">
<form id="${uploadFormId}" style="display:flex;flex-direction:column;gap:var(--space-2)">
<form id="${uploadFormId}" class="flex-col-gap-2">
<label style="font-size:var(--text-sm);font-weight:var(--weight-semibold)">
${UI.icon('upload-simple')} Foto hochladen
</label>
@ -1348,7 +1348,7 @@ window.Page_litters = (() => {
try {
const photos = await API.breederPhotos.list(entityType, entityId);
if (!photos.length) {
el.innerHTML = `<p style="color:var(--c-text-muted);font-size:var(--text-sm)">Noch keine Fotos vorhanden.</p>`;
el.innerHTML = `<p class="text-sm-muted">Noch keine Fotos vorhanden.</p>`;
return;
}
el.innerHTML = `
@ -1464,13 +1464,13 @@ window.Page_litters = (() => {
const issueHTML = (welfare.issues || []).map(i => `
<div style="display:flex;gap:8px;padding:8px 0;border-bottom:1px solid rgba(0,0,0,.06)">
<span style="color:${color};flex-shrink:0">${UI.icon('warning')}</span>
<span style="font-size:var(--text-sm)">${_esc(i.text)}</span>
<span class="text-sm">${_esc(i.text)}</span>
</div>`).join('');
const okHTML = (welfare.ok_points || []).map(p => `
<div style="display:flex;gap:8px;padding:4px 0">
<span style="color:#16a34a;flex-shrink:0">${UI.icon('check')}</span>
<span style="font-size:var(--text-sm);color:var(--c-text-secondary)">${_esc(p)}</span>
<span class="text-sm-secondary">${_esc(p)}</span>
</div>`).join('');
const isProblematic = welfare.level === 'warning' || welfare.level === 'critical';
@ -1500,7 +1500,7 @@ window.Page_litters = (() => {
Trotzdem fortfahren
</button>
</div>` : `
<button class="btn btn-primary" data-modal-close style="width:100%">
<button class="btn btn-primary" data-modal-close class="w-full">
${UI.icon('check')} Verstanden
</button>`,
});
@ -1540,7 +1540,7 @@ window.Page_litters = (() => {
} catch (err) {
UI.modal.open({
title: `${UI.icon('sparkle')} KI-Wurfankündigung`,
body: `<p style="color:var(--c-danger)">${_esc(err.message || 'Fehler beim Generieren.')}</p>`,
body: `<p class="text-danger">${_esc(err.message || 'Fehler beim Generieren.')}</p>`,
footer: `<button class="btn btn-secondary" data-modal-close>Schließen</button>`,
});
return;

View file

@ -413,7 +413,7 @@ window.Page_lost = (() => {
border-radius:var(--radius-md);flex-shrink:0;
display:flex;align-items:center;justify-content:center;
font-size:2rem">🐕</div>`}
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="display:flex;align-items:center;gap:var(--space-2);
margin-bottom:var(--space-1);flex-wrap:wrap">
<span style="font-weight:var(--weight-semibold);font-size:var(--text-base)">
@ -436,7 +436,7 @@ window.Page_lost = (() => {
color:var(--c-text)">
${_escape(r.beschreibung.slice(0, 120))}${r.beschreibung.length > 120 ? '…' : ''}
</p>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">
<div class="text-xs-secondary">
Gemeldet ${_fmtDate(r.created_at)}
${r.melder_name ? '· ' + _escape(r.melder_name.split(' ')[0]) : ''}
</div>
@ -450,7 +450,7 @@ window.Page_lost = (() => {
🗑 Verwerfen
</button>
</div>`
: (_appState.user ? `<div style="margin-top:var(--space-2)">
: (_appState.user ? `<div class="mt-2">
<button class="btn btn-ghost btn-xs lost-note-btn"
data-lost-note-id="${r.id}"
data-lost-note-name="${_escape(r.name)}"
@ -600,7 +600,7 @@ window.Page_lost = (() => {
<div class="form-group">
<label class="form-label">
Registrierter Hund
<span style="color:var(--c-text-secondary)">(optional)</span>
<span class="text-secondary">(optional)</span>
</label>
<select class="form-control" name="dog_id" id="lf-dog-select">
${dogOpts}
@ -616,7 +616,7 @@ window.Page_lost = (() => {
<div class="form-group">
<label class="form-label">
Rasse
<span style="color:var(--c-text-secondary)">(optional)</span>
<span class="text-secondary">(optional)</span>
</label>
<input class="form-control" type="text" name="rasse"
placeholder="z. B. Labrador">
@ -643,7 +643,7 @@ window.Page_lost = (() => {
</div>
<input type="hidden" name="lat" id="lf-lat">
<input type="hidden" name="lon" id="lf-lon">
<small id="lf-gps-hint" style="color:var(--c-text-secondary)">
<small id="lf-gps-hint" class="text-secondary">
${_userPos
? '✅ Aktueller Standort vorausgefüllt'
: 'GPS-Button drücken um Standort zu ermitteln'}
@ -653,7 +653,7 @@ window.Page_lost = (() => {
<div class="form-group">
<label class="form-label">
Foto
<span style="color:var(--c-text-secondary)">(optional)</span>
<span class="text-secondary">(optional)</span>
</label>
<input class="form-control" type="file" name="photo"
accept="image/*" capture="environment">

View file

@ -221,7 +221,7 @@ window.Page_map = (() => {
<div class="map-statusbar" id="map-statusbar">
<span id="map-zoom-info"></span>
<span id="map-osm-status" style="display:none"></span>
<span id="map-osm-status" class="hidden"></span>
<span class="map-statusbar-sep map-weather-chip--hidden" id="map-weather-sep">·</span>
<span class="map-weather-chip--hidden" id="map-weather-info"></span>
</div>
@ -1780,7 +1780,7 @@ window.Page_map = (() => {
border:1.5px solid var(--c-border);border-radius:100px;cursor:pointer;
font-size:var(--text-xs);font-weight:600;user-select:none">
<input type="checkbox" name="dog_ids" value="${d.id}" ${checked ? 'checked' : ''}
style="display:none" class="rec-dog-cb">
class="rec-dog-cb hidden">
${av}<span>${UI.escape(d.name)}</span>
</label>`;
}).join('')}
@ -1798,7 +1798,7 @@ window.Page_map = (() => {
<input class="form-control" type="text" name="name"
placeholder="Wird automatisch ermittelt…" required>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Schwierigkeit</label>
<select class="form-control" name="schwierigkeit">
@ -1842,7 +1842,7 @@ window.Page_map = (() => {
</label>
</div>
<div class="form-group">
<label class="form-label">Beschreibung <span style="color:var(--c-text-secondary)">(optional)</span></label>
<label class="form-label">Beschreibung <span class="text-secondary">(optional)</span></label>
<textarea class="form-control" name="beschreibung" rows="2"
placeholder="Besonderheiten, Highlights, Tipps…"></textarea>
</div>

View file

@ -161,7 +161,7 @@ window.Page_moderation = (() => {
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));
gap:var(--space-4)">
${fotos.map(f => `
<div class="card" style="padding:var(--space-4)" data-id="${f.id}">
<div class="card p-4" data-id="${f.id}">
<a href="#wiki?rasse=${_esc(f.rasse_slug)}" style="display:block;text-decoration:none">
<img src="${_esc(f.foto_url)}" alt=""
style="width:100%;height:140px;object-fit:cover;
@ -174,7 +174,7 @@ window.Page_moderation = (() => {
margin-bottom:var(--space-2)">
von ${_esc(f.user_name)}
</div>
<div style="margin-bottom:var(--space-3)">
<div class="mb-3">
${f.rights_confirmed
? `<span style="font-size:10px;font-weight:700;padding:2px 8px;border-radius:20px;
background:#dcfce7;color:#166534"> Bildrechte bestätigt</span>`
@ -189,11 +189,11 @@ window.Page_moderation = (() => {
margin-bottom:var(--space-3)">
` : `<div style="font-size:var(--text-xs);color:var(--c-warning);
margin-bottom:var(--space-3)">Noch kein Foto vorhanden</div>`}
<div style="display:flex;gap:var(--space-2)">
<div class="flex-gap-2">
<button class="btn btn-sm btn-primary mod-foto-approve"
data-id="${f.id}" style="flex:1"><svg class="ph-icon" style="width:14px;height:14px" aria-hidden="true"><use href="/icons/phosphor.svg#check"></use></svg> Freigeben</button>
data-id="${f.id}" 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}" style="color:var(--c-danger)"><svg class="ph-icon" style="width:14px;height:14px" aria-hidden="true"><use href="/icons/phosphor.svg#x"></use></svg> Ablehnen</button>
data-id="${f.id}" 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>
`).join('')}
@ -287,7 +287,7 @@ window.Page_moderation = (() => {
el.innerHTML = `
<div style="margin-bottom:var(--space-2);font-size:var(--text-xs);
color:var(--c-text-muted)">${total} Nutzer gefunden</div>
<div style="display:flex;flex-direction:column;gap:var(--space-2)">
<div class="flex-col-gap-2">
${visible.map(u => {
const isAdminUser = u.rolle === 'admin' || u.is_admin;
const canAction = isAdmin && !isAdminUser;
@ -301,7 +301,7 @@ window.Page_moderation = (() => {
font-weight:var(--weight-bold);color:var(--c-text-secondary)">
${_esc(u.name[0].toUpperCase())}
</div>
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
color:var(--c-text)">
${_esc(u.name)}
@ -309,7 +309,7 @@ window.Page_moderation = (() => {
border-radius:3px;background:var(--c-danger);
color:#fff;margin-left:4px">GESPERRT</span>` : ''}
</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">
<div class="text-xs-muted">
${_esc(u.email)} ·
<span style="color:${
u.rolle === 'admin' ? 'var(--c-danger)'
@ -324,12 +324,12 @@ window.Page_moderation = (() => {
? (u.is_banned
? `<button class="btn btn-sm btn-ghost mod-unban"
data-uid="${u.id}" data-name="${_esc(u.name)}"
title="Sperre aufheben" style="color:var(--c-success)">
title="Sperre aufheben" class="text-success">
${UI.icon('lock-open')}
</button>`
: `<button class="btn btn-sm btn-ghost mod-ban"
data-uid="${u.id}" data-name="${_esc(u.name)}"
title="Sperren" style="color:var(--c-danger)">
title="Sperren" class="text-danger">
${UI.icon('lock')}
</button>`)
: ''
@ -400,12 +400,12 @@ window.Page_moderation = (() => {
return;
}
el.innerHTML = `
<div style="display:flex;flex-direction:column;gap:var(--space-3)">
<div class="flex-col-gap-3">
${reports.map(r => `
<div class="card" style="padding:var(--space-4);
border-left:3px solid var(--c-danger)">
<div style="display:flex;align-items:flex-start;gap:var(--space-3)">
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="font-size:var(--text-xs);color:var(--c-text-muted);
margin-bottom:var(--space-1)">
${_esc(r.target_type)} #${r.target_id} ·
@ -476,13 +476,13 @@ window.Page_moderation = (() => {
const STATUS_COLOR = { pending: 'var(--c-warning)', approved: 'var(--c-success,#22c55e)', rejected: 'var(--c-danger)' };
el.innerHTML = `
<div style="display:flex;flex-direction:column;gap:var(--space-3)">
<div class="flex-col-gap-3">
${edits.map(e => `
<div class="card" style="padding:var(--space-4)" data-edit-id="${e.id}">
<div class="card p-4" data-edit-id="${e.id}">
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:var(--space-2);flex-wrap:wrap">
<div>
<div style="font-weight:600">${_esc(e.poi_name)}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">
<div class="text-xs-muted">
OSM-ID: ${_esc(e.osm_id)} · Feld: ${_esc(e.field)} · von ${_esc(e.einreicher_name)}
· ${new Date(e.created_at).toLocaleDateString('de-DE')}
</div>
@ -494,7 +494,7 @@ window.Page_moderation = (() => {
<div style="margin-top:var(--space-3);display:grid;grid-template-columns:1fr 1fr;gap:var(--space-2)">
<div style="background:var(--c-surface-2);border-radius:var(--radius-sm);padding:var(--space-2)">
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:2px">Aktuell</div>
<div style="font-size:var(--text-sm)">${_esc(e.old_value) || '<em style="color:var(--c-text-muted)">leer</em>'}</div>
<div class="text-sm">${_esc(e.old_value) || '<em class="text-muted">leer</em>'}</div>
</div>
<div style="background:var(--c-surface-2);border-radius:var(--radius-sm);padding:var(--space-2)">
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:2px">Vorschlag</div>

View file

@ -96,7 +96,7 @@ window.Page_movies = (() => {
<button class="movies-filter-btn${_filter === 'ueberlebt' ? ' movies-filter-btn--active' : ''}" data-filter="ueberlebt"><svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#paw-print"></use></svg> Hund überlebt</button>
<button class="movies-filter-btn${_filter === 'top' ? ' movies-filter-btn--active' : ''}" data-filter="top"><svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#star"></use></svg> Top</button>
</div>
<div class="movies-filter-row" style="margin-top:var(--space-2)">
<div class="movies-filter-row mt-2">
<button class="movies-filter-btn movies-type-btn${_typ === 'alle' ? ' movies-filter-btn--active' : ''}" data-typ="alle">Alle</button>
<button class="movies-filter-btn movies-type-btn${_typ === 'film' ? ' movies-filter-btn--active' : ''}" data-typ="film"><svg class="ph-icon" aria-hidden="true" style="width:15px;height:15px"><use href="/icons/phosphor.svg#film-slate"></use></svg> Filme</button>
<button class="movies-filter-btn movies-type-btn${_typ === 'serie' ? ' movies-filter-btn--active' : ''}" data-typ="serie"><svg class="ph-icon" aria-hidden="true" style="width:15px;height:15px"><use href="/icons/phosphor.svg#list"></use></svg> Serien</button>
@ -201,8 +201,8 @@ window.Page_movies = (() => {
const stars = _starsHtml(film.bewertung_avg, film.id, film.user_rating, false);
const _ico = name => `<svg class="ph-icon" aria-hidden="true" style="width:12px;height:12px;vertical-align:middle"><use href="/icons/phosphor.svg#${name}"></use></svg>`;
const typLabel = film.typ === 'serie' ? `${_ico('list')} Serie` : film.typ === 'doku' ? `${_ico('camera')} Doku` : '';
const imdb = film.imdb_rating ? `<span style="font-size:var(--text-xs);color:var(--c-text-muted)">IMDb ${film.imdb_rating}</span>` : '';
const streaming = film.streaming ? `<span style="font-size:var(--text-xs);color:var(--c-text-muted)">${_esc(film.streaming)}</span>` : '';
const imdb = film.imdb_rating ? `<span class="text-xs-muted">IMDb ${film.imdb_rating}</span>` : '';
const streaming = film.streaming ? `<span class="text-xs-muted">${_esc(film.streaming)}</span>` : '';
return `
<div class="movie-card" data-film-id="${_esc(film.id)}">
@ -210,7 +210,7 @@ window.Page_movies = (() => {
<div class="movie-card-body">
<div class="movie-card-title">${_esc(film.titel)} <span class="movie-card-year">(${film.jahr})</span></div>
<div class="movie-card-genre" style="display:flex;gap:var(--space-2);align-items:center;flex-wrap:wrap">
<span>${_esc(film.genre)}</span>${typLabel ? `<span style="font-size:var(--text-xs);color:var(--c-text-muted)">${typLabel}</span>` : ''}
<span>${_esc(film.genre)}</span>${typLabel ? `<span class="text-xs-muted">${typLabel}</span>` : ''}
</div>
<div class="movie-card-rasse"><svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#paw-print"></use></svg> ${_esc(film.hund_rasse)}</div>
${tag}
@ -240,7 +240,7 @@ window.Page_movies = (() => {
</div>
<div class="${bannerClass}" style="margin-bottom:var(--space-4);font-size:var(--text-base)">${bannerText}</div>
<p style="line-height:1.6;color:var(--c-text);margin-bottom:var(--space-5)">${_esc(film.beschreibung)}</p>
<div style="margin-bottom:var(--space-2)">
<div class="mb-2">
<strong>Community-Bewertung:</strong>
</div>
<div id="modal-stars-${_esc(film.id)}">${stars}</div>

View file

@ -499,7 +499,7 @@ window.Page_notes = (() => {
<h3 style="font-size:var(--text-base);font-weight:700;margin:0 0 var(--space-4)">Neue Notiz</h3>
<!-- Kategorie-Auswahl -->
<div style="margin-bottom:var(--space-4)">
<div class="mb-4">
<label style="display:block;font-size:var(--text-sm);font-weight:600;color:var(--c-text);margin-bottom:var(--space-2)">Kategorie</label>
<div style="display:flex;flex-wrap:wrap;gap:var(--space-2)">
${ERSTELL_RUBRIKEN.map(r => `
@ -514,7 +514,7 @@ window.Page_notes = (() => {
</div>
<!-- Text -->
<div style="margin-bottom:var(--space-4)">
<div class="mb-4">
<label style="display:block;font-size:var(--text-sm);font-weight:600;color:var(--c-text);margin-bottom:var(--space-2)">Notiz</label>
<textarea id="nc-text" rows="5" placeholder="Was möchtest du festhalten…"
style="width:100%;padding:var(--space-3);border:1.5px solid var(--c-border);
@ -524,9 +524,9 @@ window.Page_notes = (() => {
box-sizing:border-box"></textarea>
</div>
<div style="display:flex;gap:var(--space-3)">
<button id="nc-cancel" class="btn btn-ghost" style="flex:1">Abbrechen</button>
<button id="nc-save" class="btn btn-primary" style="flex:1">Speichern</button>
<div class="flex-gap-3">
<button id="nc-cancel" class="btn btn-ghost flex-1">Abbrechen</button>
<button id="nc-save" class="btn btn-primary flex-1">Speichern</button>
</div>
</div>`;
};
@ -627,7 +627,7 @@ window.Page_notes = (() => {
<div>
<label style="display:block;font-size:var(--text-sm);font-weight:var(--weight-semibold);
color:var(--c-text);margin-bottom:var(--space-2)">Bewertung</label>
<div style="display:flex;gap:var(--space-2)">
<div class="flex-gap-2">
${[1,2,3,4,5].map(n => `
<button type="button" class="notes-pfote" data-val="${n}"
style="font-size:1.3rem;border:1.5px solid var(--c-border);border-radius:var(--radius-md);
@ -642,7 +642,7 @@ window.Page_notes = (() => {
<div>
<label style="display:block;font-size:var(--text-sm);font-weight:var(--weight-semibold);
color:var(--c-text);margin-bottom:var(--space-2)">Umgebung</label>
<div style="display:flex;gap:var(--space-2)">
<div class="flex-gap-2">
${[['🏠','zuhause'],['🌿','natur'],['🌆','stadt']].map(([emoji,val]) => `
<button type="button" class="notes-umgebung" data-val="${val}"
style="font-size:1.2rem;border:1.5px solid var(--c-border);border-radius:var(--radius-md);
@ -657,7 +657,7 @@ window.Page_notes = (() => {
<div>
<label style="display:block;font-size:var(--text-sm);font-weight:var(--weight-semibold);
color:var(--c-text);margin-bottom:var(--space-2)">Stimmung des Hundes</label>
<div style="display:flex;gap:var(--space-2)">
<div class="flex-gap-2">
${[['😊','super'],['😐','ok'],['😔','mude']].map(([emoji,val]) => `
<button type="button" class="notes-stimmung" data-val="${val}"
style="font-size:1.2rem;border:1.5px solid var(--c-border);border-radius:var(--radius-md);

View file

@ -85,7 +85,7 @@ window.Page_onboarding = (() => {
// ----------------------------------------------------------
function _step1() {
return `
<div style="text-align:center">
<div class="text-center">
<!-- Logo -->
<div style="margin-bottom:var(--space-6)">
@ -133,19 +133,19 @@ window.Page_onboarding = (() => {
<div>
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
color:var(--c-text)">${title}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">${desc}</div>
<div class="text-xs-secondary">${desc}</div>
</div>
</div>
`).join('')}
</div>
<!-- Buttons -->
<div style="display:flex;flex-direction:column;gap:var(--space-3)">
<button class="btn btn-primary" id="ob-next-btn" style="width:100%">
<div class="flex-col-gap-3">
<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>
Los geht's
</button>
<button class="btn btn-ghost" id="ob-skip-btn" style="width:100%">
<button class="btn btn-ghost" id="ob-skip-btn" class="w-full">
Überspringen
</button>
</div>
@ -222,7 +222,7 @@ window.Page_onboarding = (() => {
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#camera"></use></svg>
<span id="ob-photo-label">Foto auswählen</span>
<input type="file" name="foto" id="ob-photo-input"
accept="image/*" style="display:none">
accept="image/*" class="hidden">
</label>
</div>
@ -234,13 +234,13 @@ window.Page_onboarding = (() => {
<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"
style="flex:1">
class="flex-1">
<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" style="font-size:var(--text-sm)">
<button class="btn btn-ghost" id="ob-skip-btn" class="text-sm">
Ohne Hund fortfahren
</button>
</div>
@ -255,7 +255,7 @@ window.Page_onboarding = (() => {
function _step3() {
const dogName = _appState.activeDog?.name;
return `
<div style="text-align:center">
<div class="text-center">
<!-- Erfolgs-Icon -->
<div style="margin-bottom:var(--space-6)">
@ -294,13 +294,13 @@ window.Page_onboarding = (() => {
</p>
<!-- CTA -->
<div style="display:flex;flex-direction:column;gap:var(--space-3)">
<button class="btn btn-primary" id="ob-diary-btn" style="width:100%">
<div class="flex-col-gap-3">
<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>
Zum Tagebuch
</button>
${dogName ? `
<button class="btn btn-secondary" id="ob-profile-btn" style="width:100%">
<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>
Profil vervollständigen
</button>

View file

@ -0,0 +1,278 @@
/* ============================================================
BAN YARO Partner-Profil-Editor
Nur für User mit is_partner=1.
============================================================ */
window.Page_partner_profil = (() => {
let _container = null;
let _profile = null;
async function init(container, appState) {
_container = container;
_render();
await _load();
}
function refresh() { _load(); }
function onDogChange() {}
function _render() {
_container.innerHTML = `
<div style="max-width:640px;margin:0 auto;padding:var(--space-4)">
<div style="margin-bottom:var(--space-5)">
<h1 style="font-size:var(--text-xl);font-weight:800;margin:0 0 var(--space-1)">
Mein Partner-Profil
</h1>
<p style="color:var(--c-text-secondary);font-size:var(--text-sm);margin:0">
Richte deine öffentliche Präsenz auf der Partner-Seite ein.
Nach dem Absenden prüfen wir dein Profil und schalten es frei.
</p>
</div>
<div id="pp-content">
<div style="text-align:center;padding:var(--space-8);color:var(--c-text-muted)">Lade</div>
</div>
</div>
`;
}
async function _load() {
const el = _container.querySelector('#pp-content');
try {
const d = await API.get('/partner/my-profile');
_profile = d.profile || {};
_profile._storage_mb = d.storage_mb || 0;
_profile._storage_limit_mb = d.storage_limit_mb || 200;
el.innerHTML = _renderEditor();
_bindEvents(el);
} catch (e) {
el.innerHTML = `<p class="text-danger">${e.message}</p>`;
}
}
function _statusBadge() {
if (!_profile?.submitted_at && !_profile?.approved) return '';
const a = _profile.approved;
if (a === 1) return `<span style="background:#dcfce7;color:#16a34a;padding:3px 10px;border-radius:999px;font-size:var(--text-xs);font-weight:700">✓ Freigegeben</span>`;
if (a === -1) return `<span style="background:#fee2e2;color:#dc2626;padding:3px 10px;border-radius:999px;font-size:var(--text-xs);font-weight:700">✗ Abgelehnt</span>`;
if (_profile.submitted_at) return `<span style="background:#fef9c3;color:#a16207;padding:3px 10px;border-radius:999px;font-size:var(--text-xs);font-weight:700">⏳ In Prüfung</span>`;
return `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">Entwurf</span>`;
}
function _renderEditor() {
const p = _profile || {};
const photos = p.photos || [];
return `
<!-- Status -->
<div style="display:flex;align-items:center;gap:var(--space-3);margin-bottom:var(--space-4)">
<span class="text-sm-muted">Status:</span>
${_statusBadge() || '<span style="color:var(--c-text-muted);font-size:var(--text-xs)">Noch kein Profil angelegt</span>'}
</div>
<!-- Logo -->
<div class="card" style="padding:var(--space-4);margin-bottom:var(--space-3)">
<div style="font-size:var(--text-xs);font-weight:700;text-transform:uppercase;
letter-spacing:.06em;color:var(--c-text-muted);margin-bottom:var(--space-3)">Logo</div>
<div style="display:flex;align-items:center;gap:var(--space-4)">
<div id="pp-logo-preview" style="width:80px;height:80px;border-radius:var(--radius-md);
background:var(--c-surface-2);display:flex;align-items:center;justify-content:center;
overflow:hidden;flex-shrink:0">
${p.logo_url
? `<img src="${_esc(p.logo_url)}" style="width:100%;height:100%;object-fit:contain">`
: `<svg class="ph-icon" style="width:32px;height:32px;opacity:.3"><use href="/icons/phosphor.svg#image"></use></svg>`}
</div>
<div>
<label class="btn btn-secondary btn-sm" style="cursor:pointer">
Logo hochladen
<input type="file" id="pp-logo-input" accept="image/*" class="hidden">
</label>
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:var(--space-1)">
PNG, JPG oder WebP · max. 5 MB · wird quadratisch zugeschnitten
</div>
</div>
</div>
</div>
<!-- Texte -->
<div class="card" style="padding:var(--space-4);margin-bottom:var(--space-3)">
<div style="font-size:var(--text-xs);font-weight:700;text-transform:uppercase;
letter-spacing:.06em;color:var(--c-text-muted);margin-bottom:var(--space-3)">Texte</div>
<form id="pp-text-form" class="flex-col-gap-3">
<div class="form-group">
<label class="form-label">Anzeigename *</label>
<input class="form-control" name="display_name" type="text" maxlength="60" required
placeholder="z. B. Hundeblog Musterfrau"
value="${_esc(p.display_name || '')}">
</div>
<div class="form-group">
<label class="form-label">Kurzslogan <span style="font-weight:400;color:var(--c-text-muted)">(max. 80 Zeichen)</span></label>
<input class="form-control" name="tagline" type="text" maxlength="80"
placeholder="z. B. Hundetrainerin · 15.000 Follower auf Instagram"
value="${_esc(p.tagline || '')}">
</div>
<div class="form-group">
<label class="form-label">Über dich / euer Kanal</label>
<textarea class="form-control" name="bio" rows="4" maxlength="500"
placeholder="Wer bist du, was machst du, was verbindet dich mit Hunden?">${_esc(p.bio || p.pp_bio || '')}</textarea>
</div>
<div class="form-group">
<label class="form-label">Website</label>
<input class="form-control" name="website" type="url"
placeholder="https://deine-seite.de"
value="${_esc(p.website || '')}">
</div>
<div class="form-group">
<label class="form-label">Instagram</label>
<input class="form-control" name="instagram" type="text"
placeholder="@deinkanal"
value="${_esc(p.instagram || '')}">
</div>
<button type="submit" class="btn btn-secondary btn-sm" style="align-self:flex-start">
Texte speichern
</button>
</form>
</div>
<!-- Fotos -->
<div class="card" style="padding:var(--space-4);margin-bottom:var(--space-3)">
<div style="display:flex;align-items:baseline;justify-content:space-between;margin-bottom:var(--space-2)">
<div style="font-size:var(--text-xs);font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--c-text-muted)">
Fotos & Videos <span style="font-weight:400">(max. 6)</span>
</div>
</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-bottom:var(--space-2)">
JPG, PNG, HEIC, MP4, MOV · max. 200 MB pro Datei
</div>
<div class="mb-3">
${_storageBar(p._storage_mb || 0, p._storage_limit_mb || 200)}
</div>
<div id="pp-photos-grid" style="display:grid;grid-template-columns:repeat(3,1fr);
gap:var(--space-2);margin-bottom:var(--space-3)">
${photos.map((url, i) => {
const isVid = url.endsWith('.mp4') || url.endsWith('.webm');
return `
<div style="position:relative;aspect-ratio:1;border-radius:var(--radius-md);overflow:hidden;
background:var(--c-surface-2)">
${isVid
? `<video src="${_esc(url)}" style="width:100%;height:100%;object-fit:cover" muted playsinline loop
onmouseenter="this.play()" onmouseleave="this.pause()"></video>
<div style="position:absolute;bottom:4px;left:4px;background:rgba(0,0,0,.55);
border-radius:4px;padding:1px 5px;font-size:10px;color:#fff"> Video</div>`
: `<img src="${_esc(url)}" style="width:100%;height:100%;object-fit:cover">`}
<button class="pp-photo-del" data-idx="${i}"
style="position:absolute;top:4px;right:4px;background:rgba(0,0,0,.6);
border:none;border-radius:50%;width:24px;height:24px;cursor:pointer;
color:#fff;font-size:14px;display:flex;align-items:center;justify-content:center">
×
</button>
</div>`;
}).join('')}
${photos.length < 6 ? `
<label style="aspect-ratio:1;border-radius:var(--radius-md);border:2px dashed var(--c-border);
display:flex;align-items:center;justify-content:center;cursor:pointer;
color:var(--c-text-muted);flex-direction:column;gap:4px">
<svg class="ph-icon" style="width:24px;height:24px"><use href="/icons/phosphor.svg#plus"></use></svg>
<span style="font-size:10px">Foto</span>
<input type="file" id="pp-photo-input" accept="image/*,video/*" class="hidden">
</label>` : ''}
</div>
</div>
<!-- Absenden -->
<div style="display:flex;gap:var(--space-3);justify-content:flex-end;margin-top:var(--space-4)">
<button id="pp-submit-btn" class="btn btn-primary">
Zur Freigabe einreichen
</button>
</div>
`;
}
function _bindEvents(el) {
// Logo hochladen
el.querySelector('#pp-logo-input')?.addEventListener('change', async e => {
const file = e.target.files[0];
if (!file) return;
const fd = new FormData();
fd.append('file', file);
try {
const r = await API.upload('/partner/my-profile/logo', fd);
el.querySelector('#pp-logo-preview').innerHTML =
`<img src="${_esc(r.logo_url)}" style="width:100%;height:100%;object-fit:contain">`;
_profile = { ..._profile, logo_url: r.logo_url };
UI.toast.success('Logo gespeichert.');
} catch (err) { UI.toast.error(err.message); }
});
// Texte speichern
el.querySelector('#pp-text-form')?.addEventListener('submit', async e => {
e.preventDefault();
const btn = e.target.querySelector('[type="submit"]');
const fd = UI.formData(e.target);
await UI.asyncButton(btn, async () => {
await API.put('/partner/my-profile', fd);
_profile = { ..._profile, ...fd };
UI.toast.success('Gespeichert.');
});
});
// Foto/Video hochladen
el.querySelector('#pp-photo-input')?.addEventListener('change', async e => {
const file = e.target.files[0];
if (!file) return;
const isVideo = file.type.startsWith('video/');
const fd = new FormData();
fd.append('file', file);
if (isVideo) UI.toast.info('Video wird hochgeladen und komprimiert das kann 12 Minuten dauern …', 120_000);
try {
const r = await API.upload('/partner/my-profile/photos', fd);
_profile = { ..._profile, photos: r.photos };
await _load();
UI.toast.success(isVideo ? 'Video hinzugefügt.' : 'Foto hinzugefügt.');
} catch (err) { UI.toast.error(err.message); }
});
// Foto löschen
el.querySelectorAll('.pp-photo-del').forEach(btn => {
btn.addEventListener('click', async () => {
const idx = parseInt(btn.dataset.idx);
try {
const r = await API.post(`/partner/my-profile/photos/${idx}/delete`, {});
_profile = { ..._profile, photos: r.photos };
await _load();
} catch (err) { UI.toast.error(err.message); }
});
});
// Einreichen
el.querySelector('#pp-submit-btn')?.addEventListener('click', async () => {
const btn = el.querySelector('#pp-submit-btn');
await UI.asyncButton(btn, async () => {
await API.post('/partner/my-profile/submit', {});
UI.toast.success('Eingereicht! Wir prüfen dein Profil und schalten es bald frei.');
await _load();
});
});
}
function _storageBar(usedMb, limitMb) {
const pct = Math.min(100, Math.round((usedMb / limitMb) * 100));
const color = pct > 85 ? '#dc2626' : pct > 60 ? '#f59e0b' : '#22c55e';
return `
<div style="display:flex;align-items:center;gap:var(--space-2);font-size:var(--text-xs);color:var(--c-text-muted)">
<div style="flex:1;height:4px;background:var(--c-surface-2);border-radius:2px;overflow:hidden">
<div style="width:${pct}%;height:100%;background:${color};border-radius:2px;transition:width .4s"></div>
</div>
<span style="white-space:nowrap;color:${pct > 85 ? '#dc2626' : 'var(--c-text-muted)'}">
${usedMb.toFixed(1)} / ${limitMb} MB
</span>
</div>`;
}
function _esc(s) {
return String(s || '').replace(/[&<>"']/g, c =>
({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
}
return { init, refresh, onDogChange };
})();

View file

@ -0,0 +1,153 @@
/* ============================================================
BAN YARO Partner-Seite
Showcase der offiziellen Ban Yaro Partner.
============================================================ */
window.Page_partner = (() => {
let _container = null;
async function init(container) {
_container = container;
_render();
_load();
}
function refresh() { _load(); }
function onDogChange() {}
function _render() {
_container.innerHTML = `
<div style="max-width:680px;margin:0 auto;padding:var(--space-4)">
<div style="text-align:center;margin-bottom:var(--space-6)">
<div style="font-size:48px;margin-bottom:var(--space-2)">🤝</div>
<h1 style="font-size:var(--text-2xl);font-weight:800;margin:0 0 var(--space-2)">
Unsere Partner
</h1>
<p style="color:var(--c-text-secondary);font-size:var(--text-sm);max-width:480px;margin:0 auto">
Diese Menschen glauben an Ban Yaro und helfen uns, die Community zu wachsen.
Über ihre persönlichen Einladungscodes können sie neue Gründer vermitteln.
</p>
</div>
<div id="partner-content">
<div style="text-align:center;padding:var(--space-8);color:var(--c-text-muted)">Lade</div>
</div>
</div>
`;
}
async function _load() {
const el = _container.querySelector('#partner-content');
try {
const d = await API.get('/partners/public');
if (!d?.partners) throw new Error('Keine Daten.');
el.innerHTML = _renderPartners(d.partners);
} catch (e) {
el.innerHTML = `<p style="color:var(--c-text-muted);text-align:center">${e.message || 'Fehler beim Laden.'}</p>`;
}
}
function _renderPartners(partners) {
if (!partners.length) {
return `
<div class="by-card" style="padding:var(--space-6);text-align:center">
<p class="text-sm-muted">
Noch keine Partner das könnte schon bald du sein.
</p>
</div>
${_cta()}
`;
}
const COLORS = [
'linear-gradient(135deg,#7c3aed,#a855f7)',
'linear-gradient(135deg,#2563eb,#3b82f6)',
'linear-gradient(135deg,#059669,#10b981)',
'linear-gradient(135deg,#d97706,#f59e0b)',
'linear-gradient(135deg,#db2777,#ec4899)',
];
return `
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:var(--space-3);margin-bottom:var(--space-5)">
${partners.map((p, i) => {
const initial = (p.name || '?')[0].toUpperCase();
const grad = COLORS[i % COLORS.length];
return `
<div class="by-card" style="padding:var(--space-4);position:relative;overflow:hidden">
<div style="position:absolute;top:0;left:0;right:0;height:3px;background:${grad}"></div>
<div style="display:flex;align-items:center;gap:var(--space-3)">
${p.logo_url
? `<img src="${_esc(p.logo_url)}" alt=""
style="width:56px;height:56px;border-radius:var(--radius-md);object-fit:contain;flex-shrink:0;background:var(--c-surface-2);padding:4px">`
: p.avatar_url
? `<img src="${_esc(p.avatar_url)}" alt=""
style="width:56px;height:56px;border-radius:50%;object-fit:cover;flex-shrink:0">`
: `<div style="width:56px;height:56px;border-radius:50%;flex-shrink:0;
background:${grad};display:flex;align-items:center;
justify-content:center;font-size:24px;font-weight:800;color:#fff">
${initial}
</div>`
}
<div class="flex-1-min">
<div style="font-weight:700;font-size:var(--text-base)">${_esc(p.display_name || p.name)}</div>
${p.tagline ? `<div style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:1px">${_esc(p.tagline)}</div>` : ''}
<div style="display:flex;flex-wrap:wrap;gap:var(--space-2);margin-top:var(--space-1)">
${p.website ? `<a href="${_esc(p.website)}" target="_blank" rel="noopener"
style="font-size:var(--text-xs);color:var(--c-primary)">
🌐 ${_esc(p.website.replace(/^https?:\/\//, ''))}</a>` : ''}
${p.instagram ? `<span class="text-xs-muted">📸 ${_esc(p.instagram)}</span>` : ''}
</div>
</div>
</div>
${p.pp_bio || p.bio ? `<p style="margin:var(--space-3) 0 0;font-size:var(--text-sm);
color:var(--c-text-secondary);line-height:1.5">
${_esc(p.pp_bio || p.bio)}
</p>` : ''}
${p.photos?.length ? `
<div style="display:grid;grid-template-columns:repeat(${Math.min(p.photos.length,3)},1fr);
gap:var(--space-1);margin-top:var(--space-3);border-radius:var(--radius-md);overflow:hidden">
${p.photos.slice(0,3).map(url => {
const isVid = url.endsWith('.mp4') || url.endsWith('.webm');
return isVid
? `<video src="${_esc(url)}" style="width:100%;aspect-ratio:1;object-fit:cover"
muted playsinline loop autoplay></video>`
: `<img src="${_esc(url)}" style="width:100%;aspect-ratio:1;object-fit:cover">`;
}).join('')}
</div>` : ''}
</div>
`;
}).join('')}
</div>
${_cta()}
`;
}
function _cta() {
return `
<div class="by-card" style="padding:var(--space-5);text-align:center;
background:linear-gradient(135deg,rgba(124,58,237,.08),rgba(168,85,247,.08));
border:1px solid rgba(124,58,237,.2)">
<div style="font-size:var(--text-base);font-weight:700;margin-bottom:var(--space-2)">
Du möchtest Partner werden?
</div>
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);margin:0 0 var(--space-3)">
Schreib uns wir richten deinen persönlichen Einladungscode ein.
</p>
<a href="mailto:partner@banyaro.app?subject=Ban Yaro Partner"
style="display:inline-block;padding:10px 24px;background:linear-gradient(135deg,#7c3aed,#a855f7);
color:#fff;border-radius:var(--radius-full);font-weight:700;
font-size:var(--text-sm);text-decoration:none">
📧 partner@banyaro.app
</a>
</div>
`;
}
function _esc(s) {
return String(s || '').replace(/[&<>"']/g, c =>
({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
}
return { init, refresh, onDogChange };
})();

View file

@ -237,7 +237,7 @@ window.Page_personality = (() => {
<!-- Fortschritt -->
<div style="padding:var(--space-4) var(--space-4) 0">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
<span style="font-size:var(--text-xs);color:var(--c-text-muted)">
<span class="text-xs-muted">
Frage ${_current + 1} von ${FRAGEN.length}
</span>
<span style="font-size:var(--text-xs);font-weight:600;color:var(--c-primary)">${pct}%</span>
@ -344,7 +344,7 @@ window.Page_personality = (() => {
return `
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
<span style="font-size:1rem;width:24px;text-align:center">${tp.emoji}</span>
<div style="flex:1">
<div class="flex-1">
<div style="height:8px;background:var(--c-border);border-radius:4px;overflow:hidden">
<div style="height:100%;width:${pct}%;background:${tp.color};border-radius:4px;transition:width .6s"></div>
</div>
@ -414,7 +414,7 @@ window.Page_personality = (() => {
<div style="padding:var(--space-3) var(--space-4);font-size:var(--text-xs);font-weight:600;
color:var(--c-text-secondary);text-transform:uppercase;letter-spacing:0.05em;
border-bottom:1px solid var(--c-border)">Dein Profil</div>
<div style="padding:var(--space-4)">${scoreBars}</div>
<div class="p-4">${scoreBars}</div>
</div>
<!-- Teilen + Nochmal -->

View file

@ -281,8 +281,8 @@ window.Page_places = (() => {
</div>
</div>
${place.adresse ? `<p style="color:var(--c-text-secondary);margin-bottom:var(--space-2)">${UI.icon('map-pin')} ${UI.escape(place.adresse)}</p>` : ''}
${place.telefon ? `<p style="margin-bottom:var(--space-2)"><a href="tel:${UI.escape(place.telefon)}" style="color:var(--c-primary)">${UI.icon('phone')} ${UI.escape(place.telefon)}</a></p>` : ''}
${place.website ? `<p style="margin-bottom:var(--space-2)"><a href="${UI.escape(place.website)}" target="_blank" style="color:var(--c-primary)">${UI.icon('arrow-square-out')} ${UI.escape(place.website)}</a></p>` : ''}
${place.telefon ? `<p class="mb-2"><a href="tel:${UI.escape(place.telefon)}" class="text-primary">${UI.icon('phone')} ${UI.escape(place.telefon)}</a></p>` : ''}
${place.website ? `<p class="mb-2"><a href="${UI.escape(place.website)}" target="_blank" class="text-primary">${UI.icon('arrow-square-out')} ${UI.escape(place.website)}</a></p>` : ''}
${flags.length ? `<div style="display:flex;flex-wrap:wrap;gap:var(--space-2);margin-top:var(--space-3)">${flags.map(f => `<span class="places-flag places-flag--detail">${f}</span>`).join('')}</div>` : ''}
<div id="place-rating-${place.id}"></div>
<p style="color:var(--c-text-muted);font-size:0.8rem;margin-top:var(--space-4)">
@ -291,7 +291,7 @@ window.Page_places = (() => {
`;
const footer = isOwn ? `
<button type="button" class="btn btn-secondary" style="width:100%" id="place-detail-edit">Bearbeiten</button>
<button type="button" class="btn btn-secondary w-full" id="place-detail-edit">Bearbeiten</button>
<button type="button" class="btn btn-ghost" style="width:100%;margin-top:var(--space-2)" id="place-detail-close">Schließen</button>
` : `
<button type="button" class="btn btn-primary flex-1" id="place-detail-close">Schließen</button>
@ -348,24 +348,24 @@ window.Page_places = (() => {
</div>
<div class="form-group">
<label class="form-label">Adresse <span style="color:var(--c-text-secondary)">(optional)</span></label>
<label class="form-label">Adresse <span class="text-secondary">(optional)</span></label>
<input class="form-control" type="text" name="adresse"
value="${UI.escape(place?.adresse || '')}" placeholder="Musterstraße 1, 12345 Musterstadt">
</div>
<div class="form-group">
<label class="form-label">Website <span style="color:var(--c-text-secondary)">(optional)</span></label>
<label class="form-label">Website <span class="text-secondary">(optional)</span></label>
<input class="form-control" type="url" name="website"
value="${UI.escape(place?.website || '')}" placeholder="https://…">
</div>
<div class="form-group">
<label class="form-label">Telefon <span style="color:var(--c-text-secondary)">(optional)</span></label>
<label class="form-label">Telefon <span class="text-secondary">(optional)</span></label>
<input class="form-control" type="tel" name="telefon"
value="${UI.escape(place?.telefon || '')}" placeholder="+49 89 123456">
</div>
<div class="form-group" style="display:flex;flex-direction:column;gap:var(--space-2)">
<div class="form-group flex-col-gap-2">
<label class="form-label">Hundefreundlichkeit</label>
<label style="display:flex;align-items:center;gap:var(--space-2);cursor:pointer">
<input type="checkbox" name="hund_rein" ${place?.hund_rein ? 'checked' : ''}>
@ -386,10 +386,10 @@ window.Page_places = (() => {
const footer = `
<div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%">
<button type="submit" form="place-form" class="btn btn-primary" style="width:100%">
<button type="submit" form="place-form" class="btn btn-primary w-full">
${isEdit ? 'Speichern' : 'Ort hinzufügen'}
</button>
<div style="display:flex;gap:var(--space-2)">
<div class="flex-gap-2">
${isEdit ? `<button type="button" class="btn btn-danger" id="place-form-delete">Löschen</button>` : ''}
<button type="button" class="btn btn-secondary flex-1" id="place-form-cancel">Abbrechen</button>
</div>

View file

@ -86,7 +86,7 @@ window.Page_playdate = (() => {
<div class="playdate-layout">
<!-- Tabs -->
<div class="by-tabs" id="playdate-tabs" style="margin-bottom:var(--space-4)">
<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" data-tab="listings">Meine Inserate</button>
<button class="by-tab" data-tab="requests">
@ -133,7 +133,7 @@ window.Page_playdate = (() => {
<div style="display:flex;align-items:center;gap:var(--space-3);margin-bottom:var(--space-4);flex-wrap:wrap">
<div style="display:flex;align-items:center;gap:var(--space-2)">
${UI.icon('map-pin')}
<span style="font-size:var(--text-sm);color:var(--c-text-secondary)" id="nearby-location-label">
<span class="text-sm-secondary" id="nearby-location-label">
${_userPos ? 'Standort bekannt' : 'Kein Standort'}
</span>
</div>
@ -245,14 +245,14 @@ window.Page_playdate = (() => {
function _nearbyCard(d) {
return `
<div class="card" style="padding:var(--space-4)">
<div class="card p-4">
<div style="display:flex;gap:var(--space-3);align-items:flex-start;margin-bottom:var(--space-3)">
${_dogAvatar(d.foto_url, d.dog_name, 56)}
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="font-weight:var(--weight-semibold);font-size:var(--text-base);
color:var(--c-text)">${_esc(d.dog_name)}</div>
${d.rasse ? `<div style="font-size:var(--text-sm);color:var(--c-text-secondary)">${_esc(d.rasse)}</div>` : ''}
${d.alter ? `<div style="font-size:var(--text-xs);color:var(--c-text-muted)">${_esc(d.alter)}</div>` : ''}
${d.rasse ? `<div class="text-sm-secondary">${_esc(d.rasse)}</div>` : ''}
${d.alter ? `<div class="text-xs-muted">${_esc(d.alter)}</div>` : ''}
</div>
</div>
@ -261,7 +261,7 @@ window.Page_playdate = (() => {
${UI.icon('map-pin')}
${d.ort_name ? _esc(d.ort_name) + ' · ' : ''}${d.entfernung_km} km entfernt
</span>
${d.geschlecht ? `<span style="font-size:var(--text-xs);color:var(--c-text-muted)">${_esc(d.geschlecht)}</span>` : ''}
${d.geschlecht ? `<span class="text-xs-muted">${_esc(d.geschlecht)}</span>` : ''}
</div>
${d.beschreibung ? `
@ -389,12 +389,12 @@ window.Page_playdate = (() => {
function _listingCard(dog, listing) {
const isAktiv = listing && listing.aktiv;
return `
<div class="card" style="padding:var(--space-4)">
<div class="card p-4">
<div style="display:flex;gap:var(--space-3);align-items:center;margin-bottom:var(--space-3)">
${_dogAvatar(dog.foto_url, dog.name, 44)}
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="font-weight:var(--weight-semibold);color:var(--c-text)">${_esc(dog.name)}</div>
${dog.rasse ? `<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">${_esc(dog.rasse)}</div>` : ''}
${dog.rasse ? `<div class="text-xs-secondary">${_esc(dog.rasse)}</div>` : ''}
</div>
<span style="font-size:var(--text-xs);font-weight:600;
padding:2px 10px;border-radius:999px;
@ -442,7 +442,7 @@ window.Page_playdate = (() => {
<form id="${formId}">
<div class="form-group">
<label class="form-label">Ort / Standort</label>
<div style="display:flex;gap:var(--space-2)">
<div class="flex-gap-2">
<input type="text" id="listing-ort" class="form-control"
placeholder="z.B. München"
value="${_esc(existing?.ort_name || '')}">
@ -578,7 +578,7 @@ window.Page_playdate = (() => {
<h3 style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
color:var(--c-text-muted);text-transform:uppercase;letter-spacing:0.05em;
margin:0 0 var(--space-3)">Eingehende Anfragen</h3>
<div style="display:flex;flex-direction:column;gap:var(--space-3)">
<div class="flex-col-gap-3">
${incoming.map(r => _incomingCard(r)).join('')}
</div>
</div>` : ''}
@ -588,7 +588,7 @@ window.Page_playdate = (() => {
<h3 style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
color:var(--c-text-muted);text-transform:uppercase;letter-spacing:0.05em;
margin:0 0 var(--space-3)">Ausgehende Anfragen</h3>
<div style="display:flex;flex-direction:column;gap:var(--space-3)">
<div class="flex-col-gap-3">
${outgoing.map(r => _outgoingCard(r)).join('')}
</div>
</div>` : ''}
@ -631,17 +631,17 @@ window.Page_playdate = (() => {
function _incomingCard(r) {
const isPending = r.status === 'pending';
return `
<div class="card" style="padding:var(--space-4)">
<div class="card p-4">
<div style="display:flex;gap:var(--space-3);align-items:flex-start;margin-bottom:var(--space-3)">
${_dogAvatar(r.from_dog_foto, r.from_dog_name, 44)}
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="font-weight:var(--weight-semibold);color:var(--c-text)">${_esc(r.from_dog_name)}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">
<div class="text-xs-secondary">
${r.from_dog_rasse ? _esc(r.from_dog_rasse) + ' · ' : ''}
${r.alter ? _esc(r.alter) + ' · ' : ''}
von ${_esc(r.from_user_name)}
</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">${_fmtDate(r.created_at)}</div>
<div class="text-xs-muted">${_fmtDate(r.created_at)}</div>
</div>
${_statusBadge(r.status)}
</div>
@ -655,7 +655,7 @@ window.Page_playdate = (() => {
</div>` : ''}
${isPending ? `
<div style="display:flex;gap:var(--space-2)">
<div class="flex-gap-2">
<button class="btn btn-primary btn-sm req-accept-btn"
data-req-id="${r.id}" data-status="accepted">
${UI.icon('check')} Annehmen
@ -676,16 +676,16 @@ window.Page_playdate = (() => {
function _outgoingCard(r) {
return `
<div class="card" style="padding:var(--space-4)">
<div class="card p-4">
<div style="display:flex;gap:var(--space-3);align-items:flex-start;margin-bottom:var(--space-3)">
${_dogAvatar(r.to_dog_foto, r.to_dog_name, 44)}
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="font-weight:var(--weight-semibold);color:var(--c-text)">${_esc(r.to_dog_name)}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">
<div class="text-xs-secondary">
${r.to_dog_rasse ? _esc(r.to_dog_rasse) + ' · ' : ''}
von ${_esc(r.to_user_name)}
</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">${_fmtDate(r.created_at)}</div>
<div class="text-xs-muted">${_fmtDate(r.created_at)}</div>
</div>
${_statusBadge(r.status)}
</div>

View file

@ -73,7 +73,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" style="flex:1">
<button class="btn btn-secondary" id="poison-btn-erstehilfe" class="flex-1">
${UI.icon('first-aid')} Erste Hilfe & Tiergift
</button>
</div>
@ -221,7 +221,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" style="color:var(--c-success)"><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));
@ -276,13 +276,13 @@ window.Page_poison = (() => {
border-left:4px solid ${typ.color}">
<div style="display:flex;gap:var(--space-3);align-items:flex-start">
<div style="width:40px;height:40px;flex-shrink:0;color:${typ.color};display:flex;align-items:center;justify-content:center">${UI.icon(typ.icon)}</div>
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="display:flex;align-items:center;gap:var(--space-2);
margin-bottom:var(--space-1);flex-wrap:wrap">
<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" style="color:var(--c-success)"><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);
font-size:var(--text-sm);white-space:nowrap">
@ -295,7 +295,7 @@ window.Page_poison = (() => {
${UI.escape(r.beschreibung.slice(0, 120))}${r.beschreibung.length > 120 ? '…' : ''}
</p>`
: ''}
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">
<div class="text-xs-secondary">
Gemeldet ${_fmtDate(r.created_at)} ·
läuft ab ${_fmtDate(r.expires_at)}
</div>
@ -336,7 +336,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" style="color:var(--c-success)"><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>
${r.beschreibung
@ -353,7 +353,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" style="color:var(--c-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" 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>
${isOwnEntry || isAdmin
@ -472,7 +472,7 @@ window.Page_poison = (() => {
<div class="form-group">
<label class="form-label">
Beschreibung
<span style="color:var(--c-text-secondary)">(optional)</span>
<span class="text-secondary">(optional)</span>
</label>
<textarea class="form-control" name="beschreibung" rows="3"
placeholder="z. B. Wurstköder mit Nadeln, liegt beim Eingang Hundeparkplatz, linke Seite…"></textarea>
@ -481,7 +481,7 @@ window.Page_poison = (() => {
<div class="form-group">
<label class="form-label">
Foto
<span style="color:var(--c-text-secondary)">(optional)</span>
<span class="text-secondary">(optional)</span>
</label>
<input class="form-control" type="file" name="photo"
accept="image/*" capture="environment">
@ -593,7 +593,7 @@ window.Page_poison = (() => {
title: 'Danke für deine Meldung!',
body: `
<div style="text-align:center;padding:var(--space-2) 0 var(--space-4)">
<div style="margin-bottom:var(--space-4)">
<div class="mb-4">
<svg class="ph-icon" aria-hidden="true" style="width:48px;height:48px;color:var(--c-danger)"><use href="/icons/phosphor.svg#siren"></use></svg>
</div>
<p style="color:var(--c-text);font-size:var(--text-base);line-height:1.7;margin:0">

View file

@ -239,7 +239,7 @@ window.Page_reise = (() => {
}
return `<label class="reise-check-row${done ? ' done' : ''}">
<input type="checkbox" class="reise-cb" data-key="${_esc(key)}" ${done ? 'checked' : ''}>
<span style="color:var(--c-primary)">${_esc(item)}</span>
<span class="text-primary">${_esc(item)}</span>
</label>`;
}).join('');
@ -257,10 +257,10 @@ window.Page_reise = (() => {
</div>
</div>` : '';
return `<div class="card" style="margin-bottom:var(--space-4)">
return `<div class="card mb-4">
<div style="padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--c-border);
display:flex;align-items:center;gap:var(--space-2)">
<svg class="ph-icon" style="color:var(--c-primary)" aria-hidden="true">
<svg class="ph-icon text-primary" aria-hidden="true">
<use href="/icons/phosphor.svg#${_esc(cat.icon)}"></use>
</svg>
<span style="font-weight:var(--weight-semibold)">${_esc(cat.label)}</span>
@ -289,7 +289,7 @@ window.Page_reise = (() => {
<!-- Fortschritt + Buttons -->
<div style="margin-bottom:var(--space-5)">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--space-2)">
<span style="font-size:var(--text-sm);color:var(--c-text-secondary)">${doneItems} von ${totalItems} erledigt</span>
<span class="text-sm-secondary">${doneItems} von ${totalItems} erledigt</span>
<div style="display:flex;gap:8px;align-items:center">
<span style="font-size:var(--text-sm);font-weight:700;color:var(--c-primary)">${pct}%</span>
<button id="reise-edit-toggle" style="background:${_editMode ? 'var(--c-primary)' : 'var(--c-bg-card)'};
@ -387,11 +387,11 @@ window.Page_reise = (() => {
<div class="card" style="margin-bottom:var(--space-3);padding:var(--space-4)">
<div style="display:flex;align-items:flex-start;gap:var(--space-3)">
<span style="font-size:2rem;line-height:1">${l.flag}</span>
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="font-weight:var(--weight-semibold);margin-bottom:var(--space-1)">
${_esc(l.name)}
</div>
<div style="font-size:var(--text-sm);color:var(--c-text-secondary)">
<div class="text-sm-secondary">
${_esc(l.regel)}
</div>
</div>
@ -431,7 +431,7 @@ window.Page_reise = (() => {
</div>`).join('');
el.innerHTML = `
<div class="card" style="margin-bottom:var(--space-4)">
<div class="card mb-4">
<div style="padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--c-border);
font-weight:var(--weight-semibold);display:flex;align-items:center;gap:var(--space-2)">
<svg class="ph-icon" style="color:var(--c-danger,#ef4444)" aria-hidden="true">
@ -439,7 +439,7 @@ window.Page_reise = (() => {
</svg>
Notrufnummern
</div>
<div style="padding:var(--space-4)">
<div class="p-4">
<a href="tel:112" class="btn btn-danger w-full"
style="margin-bottom:var(--space-3);display:flex;align-items:center;
justify-content:center;gap:var(--space-2)">
@ -458,10 +458,10 @@ window.Page_reise = (() => {
</div>
</div>
<div class="card" style="margin-bottom:var(--space-4)">
<div class="card mb-4">
<div style="padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--c-border);
font-weight:var(--weight-semibold)">Tierarzt finden</div>
<div style="padding:var(--space-4)">
<div class="p-4">
<button class="btn btn-primary w-full" id="reise-map-btn"
style="display:flex;align-items:center;justify-content:center;gap:var(--space-2)">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-pin"></use></svg>
@ -470,7 +470,7 @@ window.Page_reise = (() => {
</div>
</div>
<div class="card" style="margin-bottom:var(--space-4)">
<div class="card mb-4">
<div style="padding:var(--space-3) var(--space-4);border-bottom:1px solid var(--c-border);
font-weight:var(--weight-semibold);display:flex;align-items:center;gap:var(--space-2)">
<svg class="ph-icon" style="color:var(--c-warning,#f59e0b)" aria-hidden="true">

View file

@ -146,11 +146,11 @@ window.Page_routes = (() => {
btnRow.innerHTML = `
<button id="rk-filter-btn" style="${_btnStyle()}position:relative">
${UI.icon('gear')} Filter
<span class="rk-filter-badge" id="rk-filter-badge" style="display:none"></span>
<span class="rk-filter-badge" id="rk-filter-badge" class="hidden"></span>
</button>
<label id="rk-imp-wrap" title="GPX / KML / TCX importieren" style="${_btnStyle()}">
${UI.icon('download-simple')} Import
<input type="file" id="rk-import-input" accept=".gpx,.kml,.tcx" style="display:none">
<input type="file" id="rk-import-input" accept=".gpx,.kml,.tcx" class="hidden">
</label>
<button class="rk-rec-btn" id="rk-rec-btn" style="${_btnStyle(true)}">${UI.icon('path')} Aufzeichnen</button>
`;
@ -215,15 +215,15 @@ window.Page_routes = (() => {
<div style="display:flex;gap:8px">
<button id="rk-filter-btn" style="${_btnStyle()}position:relative">
${UI.icon('gear')} Filter
<span class="rk-filter-badge" id="rk-filter-badge" style="display:none"></span>
<span class="rk-filter-badge" id="rk-filter-badge" class="hidden"></span>
</button>
<label id="rk-imp-wrap" title="GPX / KML / TCX importieren" style="${_btnStyle()}">
${UI.icon('download-simple')} Import
<input type="file" id="rk-import-input" accept=".gpx,.kml,.tcx" style="display:none">
<input type="file" id="rk-import-input" accept=".gpx,.kml,.tcx" class="hidden">
</label>
<button class="rk-rec-btn" id="rk-rec-btn" style="${_btnStyle(true)}">${UI.icon('path')} Aufzeichnen</button>
</div>
<div class="rk-filter-panel" id="rk-filter-panel" style="display:none">
<div class="rk-filter-panel" id="rk-filter-panel" class="hidden">
<div class="rk-filters" id="rk-filters">
<div class="rk-filter-group">
<div class="rk-filter-label">Schwierigkeit</div>
@ -253,13 +253,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" style="display:none">
<div class="rk-filter-group" id="rk-mine-group" class="hidden">
<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" style="display:none">
<div class="rk-filter-group" id="rk-nearby-group" class="hidden">
<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>
@ -360,7 +360,7 @@ window.Page_routes = (() => {
gate.style.cssText = 'padding:var(--space-6);text-align:center;color:var(--c-text-muted)';
gate.innerHTML = `<svg class="ph-icon" style="width:36px;height:36px;color:var(--c-primary);margin-bottom:var(--space-3)" aria-hidden="true"><use href="/icons/phosphor.svg#star"></use></svg>
<div style="font-weight:600;color:var(--c-text);margin-bottom:var(--space-2)">Ban Yaro Pro</div>
<div style="font-size:var(--text-sm)">Routenvorschläge sind ein Pro-Feature.</div>`;
<div class="text-sm">Routenvorschläge sind ein Pro-Feature.</div>`;
document.getElementById('rk-list')?.appendChild(gate);
} else {
_renderSuggestTab();
@ -432,7 +432,7 @@ window.Page_routes = (() => {
text-transform:uppercase;letter-spacing:.06em;margin-bottom:var(--space-3)">
Gewünschte Distanz
</div>
<div style="display:flex;gap:var(--space-3)" id="rks-km-row">
<div class="flex-gap-3" id="rks-km-row">
<button class="rks-km-chip${_suggestKm===2?' active':''}" data-km="2">2 km</button>
<button class="rks-km-chip${_suggestKm===4?' active':''}" data-km="4">4 km</button>
<button class="rks-km-chip${_suggestKm===6?' active':''}" data-km="6">6 km</button>
@ -445,7 +445,7 @@ window.Page_routes = (() => {
text-transform:uppercase;letter-spacing:.06em;margin-bottom:var(--space-3)">
Variante
</div>
<div style="display:flex;gap:var(--space-2)" id="rks-var-row">
<div class="flex-gap-2" id="rks-var-row">
<button class="rks-var-btn${_suggestSeed===0?' active':''}" data-seed="0">Variante 1</button>
<button class="rks-var-btn${_suggestSeed===1?' active':''}" data-seed="1">Variante 2</button>
<button class="rks-var-btn${_suggestSeed===2?' active':''}" data-seed="2">Variante 3</button>
@ -591,7 +591,7 @@ window.Page_routes = (() => {
<span style="font-size:0.85rem;color:var(--c-text-secondary);flex:1;min-width:0;
overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${UI.escape(result.name || '')}</span>
</div>
<div style="display:flex;gap:var(--space-3)">
<div class="flex-gap-3">
<button id="rks-nav-btn" style="${_btnStyle(false)}flex:1">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#navigation-arrow"></use></svg>
Navigation starten
@ -991,7 +991,7 @@ window.Page_routes = (() => {
<label class="form-label">Name der Route *</label>
<input class="form-control" type="text" name="name" placeholder="Wird automatisch ermittelt…" required>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Schwierigkeit</label>
<select class="form-control" name="schwierigkeit">
@ -1035,7 +1035,7 @@ window.Page_routes = (() => {
</label>
</div>
<div class="form-group">
<label class="form-label">Beschreibung <span style="color:var(--c-text-secondary)">(optional)</span></label>
<label class="form-label">Beschreibung <span class="text-secondary">(optional)</span></label>
<textarea class="form-control" name="beschreibung" rows="2" placeholder="Besonderheiten, Highlights, Tipps…"></textarea>
</div>
</form>
@ -1631,9 +1631,9 @@ window.Page_routes = (() => {
<!-- Aktions-Buttons -->
<div style="display:flex;gap:8px;padding:8px 16px calc(8px + env(safe-area-inset-bottom,0px));
background:var(--c-surface);border-top:1px solid var(--c-border);flex-shrink:0">
<button id="rk-nav-pois" class="btn btn-secondary btn-sm" style="flex:1">${UI.icon('map-pin')} POIs</button>
<button id="rk-nav-rate" class="btn btn-secondary btn-sm" style="flex:1">${UI.icon('star')} Bewerten</button>
<button id="rk-nav-feedback" class="btn btn-secondary btn-sm" style="flex:1">${UI.icon('chat-circle-dots')} Feedback</button>
<button id="rk-nav-pois" class="btn btn-secondary btn-sm flex-1">${UI.icon('map-pin')} POIs</button>
<button id="rk-nav-rate" class="btn btn-secondary btn-sm flex-1">${UI.icon('star')} Bewerten</button>
<button id="rk-nav-feedback" class="btn btn-secondary btn-sm flex-1">${UI.icon('chat-circle-dots')} Feedback</button>
</div>
<!-- Dim-Overlay -->
@ -1944,7 +1944,7 @@ window.Page_routes = (() => {
<div>
<div style="font-weight:600;font-size:14px">${UI.escape(p.name||label)}</div>
${p.opening_hours ? `<div style="font-size:12px;color:var(--c-text-secondary)">🕐 ${UI.escape(p.opening_hours)}</div>` : ''}
${p.phone ? `<div style="font-size:12px;color:var(--c-text-secondary)">📞 <a href="tel:${UI.escape(p.phone)}" style="color:var(--c-primary)">${UI.escape(p.phone)}</a></div>` : ''}
${p.phone ? `<div style="font-size:12px;color:var(--c-text-secondary)">📞 <a href="tel:${UI.escape(p.phone)}" class="text-primary">${UI.escape(p.phone)}</a></div>` : ''}
</div>
<a href="${/iPad|iPhone|iPod/.test(navigator.userAgent)
? `maps://maps.apple.com/?q=${encodeURIComponent(p.name||label)}&ll=${p.lat},${p.lon}`
@ -2071,10 +2071,10 @@ window.Page_routes = (() => {
<div style="padding:16px;background:var(--c-surface);border-top:1px solid var(--c-border);flex-shrink:0">
<!-- Modus-Toggle -->
<div style="display:flex;gap:8px;margin-bottom:12px">
<button id="rk-trim-mode-start" class="btn btn-sm btn-primary" style="flex:1">
<button id="rk-trim-mode-start" class="btn btn-sm btn-primary flex-1">
📍 Klick setzt: Start
</button>
<button id="rk-trim-mode-end" class="btn btn-sm btn-secondary" style="flex:1">
<button id="rk-trim-mode-end" class="btn btn-sm btn-secondary flex-1">
📍 Klick setzt: Ende
</button>
</div>
@ -2143,7 +2143,7 @@ window.Page_routes = (() => {
document.getElementById('rk-trim-end-lbl').textContent = `${fullTrack.length - 1 - endIdx} Punkte`;
document.getElementById('rk-trim-stats').innerHTML =
`Neue Länge: <strong>${newKm.toFixed(2)} km</strong> · ca. <strong>${newMin} min</strong>
&nbsp;·&nbsp; <span style="color:var(--c-text-muted)">Original: ${origKm.toFixed(2)} km · ${origMin} min (bleibt angerechnet)</span>`;
&nbsp;·&nbsp; <span class="text-muted">Original: ${origKm.toFixed(2)} km · ${origMin} min (bleibt angerechnet)</span>`;
};
update();
trimMap.fitBounds(L.polyline(fullTrack.map(p => [p.lat, p.lon])).getBounds(), { padding: [20, 20] });
@ -2231,12 +2231,12 @@ window.Page_routes = (() => {
${photos.map(u => `<img src="${UI.escape(u)}" class="rk-photo-thumb" onclick="window.open('${UI.escape(u)}','_blank')">`).join('')}
${isOwn ? `<label class="rk-photo-add" title="Foto hinzufügen">
<span>+</span>
<input type="file" id="rk-photo-input" accept="image/*" multiple style="display:none">
<input type="file" id="rk-photo-input" accept="image/*" multiple class="hidden">
</label>` : ''}
</div>` :
isOwn ? `<label class="rk-photo-add-empty">
${UI.icon('camera')} Foto hinzufügen
<input type="file" id="rk-photo-input" accept="image/*" multiple style="display:none">
<input type="file" id="rk-photo-input" accept="image/*" multiple class="hidden">
</label>` : '';
const body = `
@ -2261,7 +2261,7 @@ window.Page_routes = (() => {
</div>
${route.beschreibung ? `<p style="color:var(--c-text-secondary);margin-bottom:var(--space-3)">${UI.escape(route.beschreibung)}</p>` : ''}
<div id="rk-nearby" class="rk-nearby-section">
<div style="color:var(--c-text-muted);font-size:var(--text-sm)">Lädt Orte entlang der Route</div>
<div class="text-sm-muted">Lädt Orte entlang der Route</div>
</div>
<div id="rk-rating-${route.id}"></div>
<p style="color:var(--c-text-muted);font-size:0.75rem;margin-top:var(--space-2)">
@ -2283,7 +2283,7 @@ window.Page_routes = (() => {
</button>`;
const ownerRow = isOwn ? `
<div style="display:flex;gap:var(--space-2)">
<div class="flex-gap-2">
${_actionBtn('rd-send-friend', 'paper-plane-tilt', 'Senden')}
${track.length >= 4 ? _actionBtn('rd-trim', 'pencil-simple', 'Kürzen') : ''}
${_actionBtn('rd-reverse', 'path', 'Umkehren')}
@ -2293,7 +2293,7 @@ window.Page_routes = (() => {
const footer = `
<div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%">
<div style="display:flex;gap:var(--space-2)">
<div class="flex-gap-2">
${_actionBtn('rd-gpx', 'download-simple', 'GPX')}
${_actionBtn('rd-share', 'arrow-square-out', 'Teilen')}
${_actionBtn('rd-navi', 'map-pin', 'Navi')}
@ -2460,7 +2460,7 @@ window.Page_routes = (() => {
color:${checked ? 'var(--c-primary)' : ''};
font-size:var(--text-xs);font-weight:600;user-select:none">
<input type="checkbox" name="dog_ids" value="${d.id}" ${checked ? 'checked' : ''}
style="display:none" class="rd-dog-cb">
class="rd-dog-cb hidden">
${av}<span>${UI.escape(d.name)}</span>
</label>`;
}).join('');
@ -2901,7 +2901,7 @@ window.Page_routes = (() => {
<label class="form-label">Beschreibung</label>
<textarea class="form-input" id="ri-desc" rows="2" placeholder="Kurze Beschreibung der Route…"></textarea>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Schwierigkeit</label>
<select class="form-input" id="ri-diff">
@ -3084,7 +3084,7 @@ window.Page_routes = (() => {
<div style="width:100%;max-width:600px;background:var(--c-surface);border-radius:var(--radius-lg) var(--radius-lg) 0 0;
padding:var(--space-4);box-sizing:border-box;max-height:80vh;display:flex;flex-direction:column">
<div style="display:flex;align-items:center;gap:var(--space-3);margin-bottom:var(--space-3)">
<svg class="ph-icon" aria-hidden="true" style="color:var(--c-primary)"><use href="/icons/phosphor.svg#note-pencil"></use></svg>
<svg class="ph-icon" aria-hidden="true" class="text-primary"><use href="/icons/phosphor.svg#note-pencil"></use></svg>
<span style="font-weight:600;flex:1"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz ${UI.escape(parentLabel)}</span>
<button id="rk-note-close" style="background:none;border:none;cursor:pointer;color:var(--c-text-muted);padding:4px">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#x"></use></svg>

View file

@ -158,11 +158,11 @@ window.Page_settings = (() => {
}
return `
<div class="card" style="margin-bottom:var(--space-4)">
<div class="card mb-4">
<div class="by-card-section-header">Abo &amp; Tarif</div>
<div style="padding:var(--space-4)">
<div class="p-4">
<div style="display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap">
<span style="font-size:var(--text-sm);color:var(--c-text-secondary)">Aktueller Tarif:</span>
<span class="text-sm-secondary">Aktueller Tarif:</span>
${statusHtml}
</div>
${_expiryInfo()}
@ -178,7 +178,7 @@ window.Page_settings = (() => {
const price = isPro ? '29 €/Jahr' : '49 €/Jahr';
const color = isPro ? '#16a34a' : '#C4843A';
const _group = (title, items) => `
<div style="margin-bottom:var(--space-3)">
<div class="mb-3">
<div style="font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
color:var(--c-text-muted);margin-bottom:var(--space-2)">${title}</div>
${items.map(f => `
@ -243,17 +243,17 @@ window.Page_settings = (() => {
text-transform:uppercase;letter-spacing:.05em;margin-bottom:var(--space-3)">
Dein Zwinger
</div>
<form id="breeder-upgrade-form" style="display:flex;flex-direction:column;gap:var(--space-3)">
<form id="breeder-upgrade-form" class="flex-col-gap-3">
<div>
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:4px">
Zwingername <span style="color:var(--c-danger)">*</span>
Zwingername <span class="text-danger">*</span>
</label>
<input name="zwingername" type="text" maxlength="100" required
placeholder="z. B. vom Sonnenfeld" style="${inputStyle}">
</div>
<div>
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:4px">
Rasse <span style="color:var(--c-danger)">*</span>
Rasse <span class="text-danger">*</span>
</label>
<input name="rasse_text" type="text" maxlength="100" required
placeholder="z. B. Labrador Retriever" style="${inputStyle}">
@ -545,7 +545,7 @@ window.Page_settings = (() => {
</div>
</div>
<input type="file" id="settings-avatar-input" accept="image/*"
style="display:none">
class="hidden">
<div>
<div style="font-weight:700;font-size:var(--text-lg)">${_esc(u.name)}</div>
<div style="display:flex;align-items:center;gap:var(--space-2);color:var(--c-text-secondary);font-size:var(--text-sm)">
@ -562,7 +562,7 @@ window.Page_settings = (() => {
? `<span class="badge badge-primary">
<svg class="ph-icon" aria-hidden="true" style="width:12px;height:12px"><use href="/icons/phosphor.svg#star"></use></svg> Ban Yaro Plus
</span>`
: `<span class="badge" style="color:var(--c-text-secondary)">Kostenlos</span>`}
: `<span class="badge text-secondary">Kostenlos</span>`}
${u.is_founder
? `<span class="badge" style="background:#7c3aed;color:#fff;cursor:pointer" data-page="gruender">
<svg class="ph-icon" aria-hidden="true" style="width:12px;height:12px"><use href="/icons/phosphor.svg#key"></use></svg>
@ -584,7 +584,7 @@ window.Page_settings = (() => {
</div>
<!-- Mein Profil -->
<div class="card" style="margin-bottom:var(--space-4)">
<div class="card mb-4">
<div style="padding:var(--space-3) var(--space-4);
font-size:var(--text-xs);font-weight:600;
color:var(--c-text-secondary);text-transform:uppercase;
@ -599,41 +599,41 @@ window.Page_settings = (() => {
</div>
<div style="padding:var(--space-4);display:flex;flex-direction:column;gap:var(--space-3)">
${memberSince
? `<div style="font-size:var(--text-sm);color:var(--c-text-secondary)">
? `<div class="text-sm-secondary">
Mitglied seit ${_esc(memberSince)}
</div>`
: ''}
${u.bio
? `<div style="font-size:var(--text-sm)">${_esc(u.bio)}</div>`
? `<div class="text-sm">${_esc(u.bio)}</div>`
: ''}
${u.wohnort
? `<div style="font-size:var(--text-sm);color:var(--c-text-secondary)">
? `<div class="text-sm-secondary">
📍 ${_esc(u.wohnort)}
</div>`
: ''}
${u.erfahrung && erfahrungLabel[u.erfahrung]
? `<div style="font-size:var(--text-sm);color:var(--c-text-secondary)">
? `<div class="text-sm-secondary">
${_esc(erfahrungLabel[u.erfahrung])}
</div>`
: ''}
${u.social_link
? `<div style="font-size:var(--text-sm)">
? `<div class="text-sm">
<a href="${_esc(u.social_link)}" target="_blank" rel="noopener"
style="color:var(--c-primary)">${_esc(u.social_link)}</a>
class="text-primary">${_esc(u.social_link)}</a>
</div>`
: ''}
${!u.bio && !u.wohnort && !u.erfahrung && !u.social_link
? `<div style="font-size:var(--text-sm);color:var(--c-text-secondary)">
? `<div class="text-sm-secondary">
Noch kein Profil ausgefüllt.
</div>`
: ''}
</div>
</div>
<div class="card" id="settings-stats-card" style="margin-bottom:var(--space-4)">
<div class="card" id="settings-stats-card" class="mb-4">
<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 style="color:var(--c-text-muted);font-size:var(--text-sm)">Lädt</div>
<div class="text-sm-muted">Lädt</div>
</div>
<div id="settings-streak" style="display:flex;align-items:center;gap:8px;
padding:0 var(--space-4) var(--space-3);flex-wrap:wrap"></div>
@ -643,14 +643,14 @@ window.Page_settings = (() => {
<!-- Züchter-Profil Slot -->
<div id="breeder-card-slot"></div>
<div class="card" style="margin-bottom:var(--space-4)">
<div class="card mb-4">
<div class="by-card-section-header">Trophäen</div>
<div id="settings-badges-body" style="padding:var(--space-4)">
<div style="color:var(--c-text-muted);font-size:var(--text-sm)">Lädt</div>
<div id="settings-badges-body" class="p-4">
<div class="text-sm-muted">Lädt</div>
</div>
</div>
<div class="card" style="margin-bottom:var(--space-4)">
<div class="card mb-4">
<div class="card-body" style="padding:0">
<div class="sidebar-item" data-page="dog-profile"
style="padding:var(--space-4);border-radius:0;border-bottom:1px solid var(--c-border)">
@ -724,7 +724,7 @@ window.Page_settings = (() => {
${_tierCard(u)}
<div class="card" style="margin-bottom:var(--space-4)">
<div class="card mb-4">
<div class="by-card-section-header">
App-Einstellungen
</div>
@ -827,15 +827,15 @@ window.Page_settings = (() => {
<div class="card" style="margin-bottom:var(--space-5)" id="referral-card">
<div style="padding:var(--space-4);border-bottom:1px solid var(--c-border)">
<div style="font-weight:600;margin-bottom:2px">${UI.icon('arrow-square-out')} Freunde werben dauerhafter Rabatt</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">
<div class="text-xs-secondary">
10 Freunde 20% · 20 Freunde 30% · 50 Freunde 50% lebenslang, sobald Bezahlfunktionen aktiv sind.
</div>
</div>
<div id="referral-body" style="padding:var(--space-4)">Lade</div>
<div id="referral-body" class="p-4">Lade</div>
</div>
<!-- App installieren -->
<div class="card" style="margin-bottom:var(--space-4)">
<div class="card mb-4">
<div class="by-card-section-header">
App installieren
</div>
@ -888,7 +888,7 @@ window.Page_settings = (() => {
${av}
<div style="display:flex;flex-direction:column;gap:1px;flex:1;min-width:0">
<span style="font-weight:600;font-size:var(--text-sm)">${_esc(d.name)}</span>
<span style="font-size:var(--text-xs);color:var(--c-text-muted)">
<span class="text-xs-muted">
<svg class="ph-icon" style="width:11px;height:11px" aria-hidden="true"><use href="/icons/phosphor.svg#heart-break"></use></svg>
Erinnerungen${jahr ? ' · ' + jahr : ''}
</span>
@ -911,7 +911,7 @@ window.Page_settings = (() => {
const s = a.stats || {}, streak = a.streak || {};
const stat = (val, label) => `
<div style="text-align:center">
<div class="text-center">
<div style="font-size:1.3rem;font-weight:700;color:var(--c-text)">${val}</div>
<div style="font-size:10px;color:var(--c-text-secondary);text-transform:uppercase;letter-spacing:.05em;margin-top:2px">${label}</div>
</div>`;
@ -928,7 +928,7 @@ window.Page_settings = (() => {
? `<span style="font-size:1.3rem">🔥</span>
<span style="font-weight:700;font-size:1.05rem">${cur} Tage Streak</span>
${mx > cur ? `<span style="color:var(--c-text-muted);font-size:11px;margin-left:auto">Best: ${mx}</span>` : ''}`
: `<span style="color:var(--c-text-muted);font-size:var(--text-sm)">🔥 Noch kein Streak — heute aktiv werden!</span>`;
: `<span class="text-sm-muted">🔥 Noch kein Streak — heute aktiv werden!</span>`;
}
// Lifetime-km Balken mit Meilenstein-Markierungen
@ -1059,7 +1059,7 @@ window.Page_settings = (() => {
<div style="display:flex;gap:14px;align-items:flex-start;padding:12px 0;
border-bottom:1px solid var(--c-border)">
${shieldSvg}
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="display:flex;align-items:center;gap:6px;margin-bottom:2px">
<span style="font-weight:700;font-size:var(--text-sm)">${_esc(cat.name)}</span>
${cur ? `<span style="font-size:10px;font-weight:600;padding:1px 6px;border-radius:999px;
@ -1080,7 +1080,7 @@ window.Page_settings = (() => {
}
}).catch(() => {
const el = document.getElementById('settings-stats-body');
if (el) el.innerHTML = '<div style="color:var(--c-text-muted);font-size:var(--text-sm)"></div>';
if (el) el.innerHTML = '<div class="text-sm-muted"></div>';
});
// Avatar-Hover-Overlay
@ -1201,7 +1201,7 @@ window.Page_settings = (() => {
`,
footer: `
<div class="w3-btn-stack">
<button type="submit" form="profile-form" class="btn btn-primary" style="width:100%">Speichern</button>
<button type="submit" form="profile-form" class="btn btn-primary w-full">Speichern</button>
<button type="button" class="btn btn-secondary" data-modal-close>Abbrechen</button>
</div>
`,
@ -1310,7 +1310,7 @@ window.Page_settings = (() => {
`,
footer: `
<div class="w3-btn-stack">
<button type="submit" form="feedback-form" id="feedback-submit-btn" class="btn btn-primary" style="width:100%">Absenden</button>
<button type="submit" form="feedback-form" id="feedback-submit-btn" class="btn btn-primary w-full">Absenden</button>
<button type="button" class="btn btn-secondary" data-modal-close>Abbrechen</button>
</div>
`,
@ -1422,10 +1422,9 @@ window.Page_settings = (() => {
word-break:break-all;margin-bottom:var(--space-4)">
${httpsUrl}
</div>
<div style="display:flex;flex-direction:column;gap:var(--space-2)">
<div class="flex-col-gap-2">
<a href="${url}"
class="btn btn-primary"
style="text-align:center">
class="btn btn-primary text-center">
${UI.icon('calendar-dots')} In Kalender-App öffnen
</a>
<button class="btn btn-secondary" id="cal-copy-btn">
@ -1573,19 +1572,19 @@ window.Page_settings = (() => {
</span>`;
actionBlock = `
<div style="margin-top:var(--space-3);font-size:var(--text-sm);display:flex;flex-direction:column;gap:var(--space-1)">
${profile?.zwingername ? `<div style="color:var(--c-text-secondary)">Zwinger: <strong>${_esc(profile.zwingername)}</strong></div>` : ''}
${profile?.rasse_text ? `<div style="color:var(--c-text-secondary)">Rasse: <strong>${_esc(profile.rasse_text)}</strong></div>` : ''}
${profile?.zwingername ? `<div class="text-secondary">Zwinger: <strong>${_esc(profile.zwingername)}</strong></div>` : ''}
${profile?.rasse_text ? `<div class="text-secondary">Rasse: <strong>${_esc(profile.rasse_text)}</strong></div>` : ''}
</div>
${rolle === 'breeder' && profile ? `
<button class="btn btn-secondary btn-sm" id="breeder-edit-profile-btn" style="margin-top:var(--space-3)">
<button class="btn btn-secondary btn-sm" id="breeder-edit-profile-btn" class="mt-3">
${UI.icon('pencil-simple')} Profil bearbeiten
</button>` : ''}
${rolle === 'admin' && !profile ? `
<button class="btn btn-primary btn-sm" id="breeder-admin-create-btn" style="margin-top:var(--space-3)">
<button class="btn btn-primary btn-sm" id="breeder-admin-create-btn" class="mt-3">
${UI.icon('plus')} Admin-Züchterprofil anlegen
</button>` : ''}
${rolle === 'admin' && profile ? `
<button class="btn btn-secondary btn-sm" id="breeder-edit-profile-btn" style="margin-top:var(--space-3)">
<button class="btn btn-secondary btn-sm" id="breeder-edit-profile-btn" class="mt-3">
${UI.icon('pencil-simple')} Profil bearbeiten
</button>` : ''}
${profile ? `
@ -1612,7 +1611,7 @@ window.Page_settings = (() => {
${UI.icon('x-circle')} Abgelehnt
</span>`;
actionBlock = `
<div style="margin-top:var(--space-3)">
<div class="mt-3">
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);margin:0 0 var(--space-2)">
Du kannst einen neuen Antrag stellen.
</p>
@ -1627,9 +1626,9 @@ window.Page_settings = (() => {
}
slot.innerHTML = `
<div class="card" style="margin-bottom:var(--space-4)">
<div class="card mb-4">
<div class="by-card-section-header">Züchter-Profil</div>
<div style="padding:var(--space-4)">
<div class="p-4">
${statusBadge}
${actionBlock}
</div>
@ -1779,7 +1778,7 @@ window.Page_settings = (() => {
<form id="breeder-apply-form" style="display:flex;flex-direction:column;gap:var(--space-4)">
<div>
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">
Zwingername <span style="color:var(--c-danger)">*</span>
Zwingername <span class="text-danger">*</span>
</label>
<input name="zwingername" type="text" maxlength="100" required
placeholder="z. B. vom Sonnenfeld"
@ -1787,7 +1786,7 @@ window.Page_settings = (() => {
</div>
<div>
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">
Rasse <span style="color:var(--c-danger)">*</span>
Rasse <span class="text-danger">*</span>
</label>
<input name="rasse_text" type="text" maxlength="100" required
placeholder="z. B. Labrador Retriever"
@ -1795,7 +1794,7 @@ window.Page_settings = (() => {
</div>
<div>
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">
Zuchtverein <span style="color:var(--c-danger)">*</span>
Zuchtverein <span class="text-danger">*</span>
</label>
<input name="verein" type="text" maxlength="100" required
placeholder="z. B. DLRG, VDH, BCD"
@ -1803,7 +1802,7 @@ window.Page_settings = (() => {
</div>
<div>
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">
Stadt <span style="color:var(--c-danger)">*</span>
Stadt <span class="text-danger">*</span>
</label>
<input name="stadt" type="text" maxlength="80" required
placeholder="z. B. München"
@ -1834,7 +1833,7 @@ window.Page_settings = (() => {
</div>
<div>
<label style="display:block;font-size:var(--text-sm);font-weight:600;margin-bottom:var(--space-1)">
Dokument hochladen <span style="color:var(--c-danger)">*</span>
Dokument hochladen <span class="text-danger">*</span>
</label>
<input name="dokument" type="file" id="breeder-doc-input" required
accept=".pdf,.jpg,.jpeg,.png,.webp"
@ -1848,7 +1847,7 @@ window.Page_settings = (() => {
footer: `
<div class="w3-btn-stack">
<button type="submit" form="breeder-apply-form" class="btn btn-primary" id="breeder-apply-submit"
style="width:100%">Antrag einreichen</button>
class="w-full">Antrag einreichen</button>
<button type="button" class="btn btn-secondary" data-modal-close>Abbrechen</button>
</div>
`,
@ -1913,7 +1912,7 @@ window.Page_settings = (() => {
</div>
<!-- Zähler + Fortschritt -->
<div style="margin-bottom:var(--space-4)">
<div class="mb-4">
<div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:var(--space-1)">
<span style="font-size:var(--text-sm);font-weight:600">
${UI.icon('users')} <strong>${r.count}</strong> ${r.count === 1 ? 'Freund geworben' : 'Freunde geworben'}
@ -1995,15 +1994,15 @@ window.Page_settings = (() => {
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3);margin:var(--space-4) 0">
${d.km_total ? `<div class="card" style="padding:var(--space-3);text-align:center">
<div style="font-size:var(--text-xl);font-weight:800;color:var(--c-primary)">${d.km_total}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">km zusammen</div>
<div class="text-xs-secondary">km zusammen</div>
</div>` : ''}
${d.diary_count ? `<div class="card" style="padding:var(--space-3);text-align:center">
<div style="font-size:var(--text-xl);font-weight:800;color:var(--c-primary)">${d.diary_count}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">Tagebucheinträge</div>
<div class="text-xs-secondary">Tagebucheinträge</div>
</div>` : ''}
${d.gemeinsam_tage ? `<div class="card" style="padding:var(--space-3);text-align:center">
<div style="font-size:var(--text-xl);font-weight:800;color:var(--c-primary)">${d.gemeinsam_tage}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">gemeinsame Tage</div>
<div class="text-xs-secondary">gemeinsame Tage</div>
</div>` : ''}
</div>`;
@ -2063,12 +2062,10 @@ window.Page_settings = (() => {
Danach kannst du dich hier anmelden.
</p>
</div>
<button id="verify-resend-btn2" class="btn btn-ghost w-full"
style="margin-bottom:var(--space-3)">
<button id="verify-resend-btn2" class="btn btn-ghost w-full mb-3">
Link erneut senden
</button>
<button id="verify-back-btn" class="btn btn-ghost w-full"
style="color:var(--c-text-muted);font-size:var(--text-sm)">
<button id="verify-back-btn" class="btn btn-ghost w-full text-sm-muted">
Anderes Konto / Anmelden
</button>
</div>
@ -2166,7 +2163,7 @@ window.Page_settings = (() => {
</button>
</div>
</div>
<button type="submit" class="btn btn-primary w-full" style="margin-top:var(--space-2)">
<button type="submit" class="btn btn-primary w-full mt-2">
Anmelden
</button>
<p style="text-align:center;margin-top:var(--space-3);font-size:var(--text-xs)">
@ -2240,8 +2237,8 @@ window.Page_settings = (() => {
</div>
</div>
</div>
<div class="form-group" style="margin-top:var(--space-2)">
<label class="form-label" style="font-size:var(--text-xs)">
<div class="form-group mt-2">
<label class="form-label text-xs">
Einladungscode <span style="color:var(--c-text-muted);font-weight:400">(optional)</span>
</label>
<input class="form-control" type="text" name="partner_code" id="reg-partner-code"
@ -2250,7 +2247,7 @@ window.Page_settings = (() => {
<div id="reg-partner-hint" style="display:none;margin-top:var(--space-1);font-size:var(--text-xs);
padding:var(--space-2) var(--space-3);border-radius:var(--radius-sm)"></div>
</div>
<button type="submit" class="btn btn-primary w-full" style="margin-top:var(--space-2)">
<button type="submit" class="btn btn-primary w-full mt-2">
Konto erstellen
</button>
<p style="text-align:center;font-size:var(--text-xs);
@ -2283,7 +2280,7 @@ window.Page_settings = (() => {
UI.modal.open({
title: 'Passwort zurücksetzen',
body: `
<form id="${id}" style="display:flex;flex-direction:column;gap:var(--space-3)">
<form id="${id}" class="flex-col-gap-3">
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);margin:0">
Gib deine E-Mail-Adresse ein. Du erhältst einen Link zum Zurücksetzen deines Passworts.
</p>

View file

@ -176,7 +176,7 @@ window.Page_sitting = (() => {
<div class="sitting-profil-fact"><strong>${s.max_hunde}</strong> Hund${s.max_hunde !== 1 ? 'e' : ''} max.</div>
<div class="sitting-profil-fact"><strong>${s.radius_km} km</strong> Umkreis</div>
</div>
<div class="sitting-services" style="margin-top:var(--space-3)">${svcs || '(keine Services angegeben)'}</div>
<div class="sitting-services mt-3">${svcs || '(keine Services angegeben)'}</div>
</div>
`;
}
@ -194,7 +194,7 @@ window.Page_sitting = (() => {
}
if (myReqs.length) {
html += `<div class="by-section-label" style="margin-top:var(--space-4)">${UI.icon('upload')} Meine Anfragen</div>`;
html += `<div class="by-section-label mt-4">${UI.icon('upload')} Meine Anfragen</div>`;
html += myReqs.map(r => _requestCardHTML(r, 'sent')).join('');
}
@ -273,7 +273,7 @@ window.Page_sitting = (() => {
const footer = _state.user && _mySitter?.user_id !== s.user_id ? `
<button class="btn btn-primary" id="sit-anfrage-btn">${UI.icon('bell')} Anfrage senden</button>
` : (!_state.user ? `<span style="color:var(--c-text-secondary)">Zum Anfragen bitte einloggen.</span>` : '');
` : (!_state.user ? `<span class="text-secondary">Zum Anfragen bitte einloggen.</span>` : '');
UI.modal.open({ title: 'Sitter-Profil', body, footer });
@ -389,10 +389,10 @@ window.Page_sitting = (() => {
`).join('')}
</div>
<div class="form-group">
<label class="form-label">Mein Standort <span style="color:var(--c-text-secondary)">(für Umkreis-Suche)</span></label>
<label class="form-label">Mein Standort <span class="text-secondary">(für Umkreis-Suche)</span></label>
<div id="sit-loc-picker"></div>
</div>
<div class="form-group" style="margin-top:var(--space-3)">
<div class="form-group mt-3">
<label class="form-label">Umkreis (km)</label>
<input class="form-control" type="number" min="1" max="100" name="radius_km" value="${s?.radius_km ?? 20}">
</div>
@ -407,7 +407,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" style="width:100%">
<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`}
</button>
<button class="btn btn-secondary" onclick="UI.modal.close()">Abbrechen</button>
@ -773,7 +773,7 @@ window.Page_sitting = (() => {
<div style="width:100%;max-width:600px;background:var(--c-surface);border-radius:var(--radius-lg) var(--radius-lg) 0 0;
padding:var(--space-4);box-sizing:border-box;max-height:80vh;display:flex;flex-direction:column">
<div style="display:flex;align-items:center;gap:var(--space-3);margin-bottom:var(--space-3)">
<svg class="ph-icon" aria-hidden="true" style="color:var(--c-primary)"><use href="/icons/phosphor.svg#note-pencil"></use></svg>
<svg class="ph-icon" aria-hidden="true" class="text-primary"><use href="/icons/phosphor.svg#note-pencil"></use></svg>
<span style="font-weight:600;flex:1"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz ${UI.escape(parentLabel)}</span>
<button id="sit-note-close" style="background:none;border:none;cursor:pointer;color:var(--c-text-muted);padding:4px">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#x"></use></svg>

View file

@ -49,7 +49,7 @@ window.Page_social = (() => {
margin-bottom:var(--space-4)">
<div style="display:flex;align-items:center;gap:var(--space-3);margin-bottom:var(--space-3)">
<span style="font-size:1.6em">📱</span>
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="font-size:var(--text-base);font-weight:700">Social Media</div>
<div style="font-size:11px;color:var(--c-text-muted)">Luna ist dein KI-Coach</div>
</div>
@ -92,7 +92,7 @@ window.Page_social = (() => {
<div style="display:flex;justify-content:space-between;font-size:12px;
color:var(--c-text);margin-bottom:6px;font-weight:500">
<span>${s.level}</span>
${s.next_level ? `<span style="color:var(--c-text-secondary)">${s.xp_next - s.xp} XP bis ${s.next_level}</span>` : ''}
${s.next_level ? `<span class="text-secondary">${s.xp_next - s.xp} XP bis ${s.next_level}</span>` : ''}
</div>
<div style="background:var(--c-surface-2);border-radius:var(--radius-full);height:8px;overflow:hidden">
<div style="height:100%;background:linear-gradient(90deg,var(--c-primary),var(--c-primary-light));
@ -171,9 +171,9 @@ window.Page_social = (() => {
<div style="background:var(--c-surface);border-radius:var(--radius-lg);
box-shadow:var(--shadow-sm);padding:var(--space-4)">
<!-- Plattform -->
<div style="margin-bottom:var(--space-4)">
<div class="mb-4">
<div class="sm-label">Plattform</div>
<div style="display:flex;gap:var(--space-2)">
<div class="flex-gap-2">
${['both','instagram','tiktok'].map((p,i) => `
<button class="btn btn-sm sm-plat ${i===0?'btn-primary':'btn-secondary'}"
data-p="${p}" style="flex:1;min-height:36px;font-size:12px;padding:4px 8px;
@ -182,7 +182,7 @@ window.Page_social = (() => {
</div>
</div>
<!-- Format -->
<div style="margin-bottom:var(--space-4)">
<div class="mb-4">
<div class="sm-label">Format</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-2)">
${['post','reel','story','carousel'].map((f,i) => `
@ -193,7 +193,7 @@ window.Page_social = (() => {
</div>
</div>
<!-- Thema -->
<div style="margin-bottom:var(--space-4)">
<div class="mb-4">
<div class="sm-label">Thema</div>
<textarea id="sm-topic" rows="3"
placeholder="z.B. Mein Hund beim ersten Schnee 🐾"
@ -211,12 +211,12 @@ window.Page_social = (() => {
<svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#check"></use></svg> Idee übernommen prüf die Einstellungen und tippe auf <strong>Los geht's!</strong> 👇
</div>
<!-- Medien-Upload -->
<div style="margin-bottom:var(--space-4)">
<div class="mb-4">
<div class="sm-label">Foto / Video (optional)</div>
<div style="display:flex;gap:var(--space-2);flex-wrap:wrap">
<label style="cursor:pointer;flex:1">
<input type="file" id="sm-media-file" accept="image/*,video/*"
capture="environment" style="display:none">
capture="environment" class="hidden">
<span class="btn btn-secondary btn-sm"
style="min-height:40px;display:flex;align-items:center;justify-content:center;
gap:6px;font-size:12px;width:100%;border-radius:var(--radius-full)">
@ -224,7 +224,7 @@ window.Page_social = (() => {
</label>
<label style="cursor:pointer;flex:1">
<input type="file" id="sm-media-file2" accept="image/*,video/*"
style="display:none">
class="hidden">
<span class="btn btn-secondary btn-sm"
style="min-height:40px;display:flex;align-items:center;justify-content:center;
gap:6px;font-size:12px;width:100%;border-radius:var(--radius-full)">
@ -235,7 +235,7 @@ window.Page_social = (() => {
max-width:100px;max-height:100px;border-radius:var(--radius-md);overflow:hidden"></div>
</div>
<!-- Rasse Luna-Vorschlag + Suche -->
<div style="margin-bottom:var(--space-4)">
<div class="mb-4">
<div class="sm-label">Rasse (optional)</div>
${_unusedBreeds.length ? `
<div style="font-size:11px;color:var(--c-text-muted);margin-bottom:8px">
@ -265,7 +265,7 @@ window.Page_social = (() => {
</div>
<!-- Generier-Buttons als Cards -->
<div class="sm-label" style="margin-bottom:var(--space-3)">Schnell generieren</div>
<div class="sm-label mb-3">Schnell generieren</div>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:var(--space-2);
margin-bottom:var(--space-3)">
<button id="sm-breed-day"
@ -319,7 +319,7 @@ window.Page_social = (() => {
border-radius:var(--radius-lg);box-shadow:var(--shadow-md)">
<svg class="ph-icon" aria-hidden="true" style="width:16px;height:16px"><use href="/icons/phosphor.svg#sparkle"></use></svg> Los geht's!
</button>
<div id="sm-gen-result" style="margin-top:var(--space-4)"></div>
<div id="sm-gen-result" class="mt-4"></div>
</div>`;
// Platform toggle
@ -405,14 +405,14 @@ window.Page_social = (() => {
try {
const ideas = await API.get('/social/suggestions');
clearInterval(sgInt);
if (!ideas?.length) { box.innerHTML = '<div style="color:var(--c-text-muted);font-size:var(--text-sm)">Keine Ideen erhalten.</div>'; return; }
if (!ideas?.length) { box.innerHTML = '<div class="text-sm-muted">Keine Ideen erhalten.</div>'; return; }
box.innerHTML = ideas.map((idea, i) => `
<div class="card sm-idea" style="padding:12px;margin-bottom:8px;cursor:pointer;
border:2px solid transparent;transition:border-color .15s;
-webkit-tap-highlight-color:transparent" data-i="${i}">
<div style="display:flex;align-items:flex-start;gap:10px">
<span style="font-size:1.4em;flex-shrink:0;line-height:1.2">${idea.emoji||'💡'}</span>
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="font-weight:600;font-size:var(--text-sm);margin-bottom:4px;
line-height:1.3">${_esc(idea.thema)}</div>
<div style="font-size:11px;color:var(--c-text-secondary);margin-bottom:6px;
@ -520,7 +520,7 @@ window.Page_social = (() => {
${exercises.filter(e=>e.kategorie===cat).map(e => `
<div style="background:var(--c-surface);border-radius:8px;padding:10px;
margin-bottom:6px;display:flex;align-items:center;gap:10px">
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="font-size:var(--text-sm);font-weight:600;color:var(--c-text)">
${_esc(e.name)}</div>
<div style="font-size:10px;color:var(--c-text-muted)">
@ -733,7 +733,7 @@ window.Page_social = (() => {
function _lunaThinking(msg = 'Denkt nach…') {
return `<div style="text-align:center;padding:var(--space-4);color:var(--c-text-muted)">
<div style="font-size:1.8em;margin-bottom:8px;animation:luna-pulse 1.5s infinite">🌙</div>
<div style="font-size:var(--text-sm)">${msg}</div>
<div class="text-sm">${msg}</div>
</div>`;
}
@ -811,7 +811,7 @@ window.Page_social = (() => {
<div style="background:var(--c-primary-subtle);
border-radius:var(--radius-lg);padding:var(--space-4);margin-bottom:var(--space-3);
border-left:4px solid var(--c-primary)">
<div style="display:flex;gap:var(--space-3)">
<div class="flex-gap-3">
<span style="font-size:1.3em;flex-shrink:0">🌙</span>
<div>
<div style="font-size:11px;font-weight:700;color:var(--c-primary);margin-bottom:4px;
@ -830,7 +830,7 @@ window.Page_social = (() => {
</div>
<!-- Aktions-Buttons: Primär volle Breite, Sekundär nebeneinander -->
<div style="margin-bottom:var(--space-4)">
<div class="mb-4">
<button class="btn btn-primary sm-posted-btn"
data-id="${data.id}"
style="width:100%;min-height:48px;font-size:var(--text-sm);
@ -838,7 +838,7 @@ window.Page_social = (() => {
background:#10b981;border-color:#10b981;box-shadow:var(--shadow-sm)">
📤 Habe ich gepostet!
</button>
<div style="display:flex;gap:var(--space-2)">
<div class="flex-gap-2">
<button class="btn btn-sm btn-secondary sm-preview-btn"
data-id="${data.id}"
style="flex:1;font-size:12px;padding:6px 10px;min-height:36px;
@ -1146,7 +1146,7 @@ window.Page_social = (() => {
: items.map(c => `
<div class="card" style="padding:12px;margin-bottom:10px">
<div style="display:flex;align-items:flex-start;gap:10px">
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:4px;
align-items:center">
<span style="font-size:11px;padding:2px 6px;border-radius:4px;
@ -1230,7 +1230,7 @@ window.Page_social = (() => {
value="${new Date().toISOString().slice(0,10)}">
</div>
<div class="form-group">
<label class="form-label">Post-Link <span style="color:var(--c-text-muted)">(optional)</span></label>
<label class="form-label">Post-Link <span class="text-muted">(optional)</span></label>
<input class="form-control" type="url" id="qp-url-${id}"
placeholder="https://www.instagram.com/p/…">
</div>`,
@ -1341,7 +1341,7 @@ window.Page_social = (() => {
box-shadow:var(--shadow-md)">
🔍 Luna, schau mal drüber!
</button>
<div id="sm-eval-res" style="margin-top:var(--space-4)"></div>
<div id="sm-eval-res" class="mt-4"></div>
</div>`;
el.querySelectorAll('.sm-ep').forEach(b => b.addEventListener('click', () => {
@ -1369,7 +1369,7 @@ window.Page_social = (() => {
${data.notes ? `<div style="background:var(--c-primary-subtle);
border-radius:var(--radius-lg);padding:var(--space-4);margin-bottom:var(--space-3);
border-left:4px solid var(--c-primary);box-shadow:var(--shadow-xs)">
<div style="display:flex;gap:var(--space-3)">
<div class="flex-gap-3">
<span style="font-size:1.3em;flex-shrink:0">🌙</span>
<div>
<div style="font-size:11px;font-weight:700;color:var(--c-primary);margin-bottom:4px;
@ -1384,7 +1384,7 @@ window.Page_social = (() => {
API.get('/social/stats').then(s => { _stats = s; });
} catch(e) {
clearInterval(interval);
res.innerHTML = `<div style="color:var(--c-danger)">😬 Fehler: ${_esc(e.message||String(e))}</div>`;
res.innerHTML = `<div class="text-danger">😬 Fehler: ${_esc(e.message||String(e))}</div>`;
} finally {
btn.disabled = false;
btn.innerHTML = '🔍 Luna, schau mal drüber!';

View file

@ -93,7 +93,7 @@ window.Page_trainingsplaene = (() => {
<span style="font-size:var(--text-sm);font-weight:var(--weight-semibold);color:var(--c-text-secondary)">
${_icon('check-circle')} Lernziele
</span>
<span class="tp-progress-label" style="font-size:var(--text-xs);color:var(--c-text-secondary)">
<span class="tp-progress-label text-xs-secondary">
${doneCount} von ${total} erreicht
</span>
</div>
@ -355,7 +355,7 @@ window.Page_trainingsplaene = (() => {
// ----------------------------------------------------------
function _renderErwachsenTabs() {
return `
<div class="by-tabs" style="margin-bottom:var(--space-4)">
<div class="by-tabs mb-4">
<button class="by-tab${_activeAdultTab === 'grundkurs' ? ' active' : ''}" data-tab="grundkurs">Grundkurs</button>
<button class="by-tab${_activeAdultTab === 'aufbaukurs' ? ' active' : ''}" data-tab="aufbaukurs">Aufbaukurs</button>
<button class="by-tab${_activeAdultTab === 'uebersicht' ? ' active' : ''}" data-tab="uebersicht">Übersicht</button>

View file

@ -637,7 +637,7 @@ window.Page_uebungen = (() => {
</span>
`).join('');
const more = rest > 0
? `<span style="font-size:var(--text-xs);color:var(--c-text-secondary)">+${rest} weitere</span>`
? `<span class="text-xs-secondary">+${rest} weitere</span>`
: '';
badgesHtml = `<div style="display:flex;flex-wrap:wrap;gap:var(--space-2);margin-top:var(--space-2)">${pills}${more}</div>`;
}
@ -667,7 +667,7 @@ window.Page_uebungen = (() => {
el.innerHTML = `
<div style="background:var(--c-surface-2);border:1px solid var(--c-border);
border-radius:var(--radius-md);padding:var(--space-3) var(--space-4)">
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">Lade Trainingsplan</div>
<div class="text-xs-secondary">Lade Trainingsplan</div>
</div>`;
const data = await API.training.getRecommendations(dogId).catch(() => null);
@ -719,7 +719,7 @@ window.Page_uebungen = (() => {
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);line-height:1.4">${_esc(r.reason)}</div>
<div style="display:flex;flex-direction:column;gap:var(--space-2);margin-top:auto;padding-top:var(--space-1)">
<div>
<span style="font-size:var(--text-xs);color:var(--c-text-secondary)">${r.suggested_reps}× empfohlen</span>
<span class="text-xs-secondary">${r.suggested_reps}× empfohlen</span>
<span style="font-size:var(--text-xs);font-weight:700;color:${trendColor};margin-left:4px">${trend}</span>
${prognose}
</div>
@ -742,7 +742,7 @@ window.Page_uebungen = (() => {
</span>
<span id="ueb-help-anchor" style="margin-left:auto"></span>
</div>
<div style="display:flex;flex-direction:column;gap:var(--space-2)">
<div class="flex-col-gap-2">
${cards.join('')}
</div>`;
if (_helpHandle) {
@ -1020,7 +1020,7 @@ window.Page_uebungen = (() => {
el.innerHTML = `<div style="padding:var(--space-6);text-align:center;color:var(--c-text-muted)">
<svg class="ph-icon" style="width:36px;height:36px;color:var(--c-primary);margin-bottom:var(--space-3)" aria-hidden="true"><use href="/icons/phosphor.svg#star"></use></svg>
<div style="font-weight:600;color:var(--c-text);margin-bottom:var(--space-2)">Ban Yaro Pro</div>
<div style="font-size:var(--text-sm)">Der KI-Trainer ist ein Pro-Feature.</div>
<div class="text-sm">Der KI-Trainer ist ein Pro-Feature.</div>
</div>`;
} else {
el.innerHTML = _renderKiTrainer();
@ -1176,15 +1176,15 @@ window.Page_uebungen = (() => {
</div>
<!-- Meta -->
<div style="display:flex;flex-wrap:wrap;gap:var(--space-2);margin-bottom:${u.beschreibung ? 'var(--space-3)' : '0'}">
<span style="font-size:var(--text-xs);color:var(--c-text-secondary)">
<span class="text-xs-secondary">
<svg class="ph-icon" style="width:12px;height:12px" aria-hidden="true"><use href="/icons/phosphor.svg#clock"></use></svg>
${_esc(u.dauer)}
</span>
<span style="font-size:var(--text-xs);color:var(--c-text-secondary)">
<span class="text-xs-secondary">
<svg class="ph-icon" style="width:12px;height:12px" aria-hidden="true"><use href="/icons/phosphor.svg#dog"></use></svg>
${_esc(u.alter)}
</span>
<span style="font-size:var(--text-xs);color:var(--c-text-secondary)">
<span class="text-xs-secondary">
<svg class="ph-icon" style="width:12px;height:12px" aria-hidden="true"><use href="/icons/phosphor.svg#package"></use></svg>
${_esc(u.material)}
</span>
@ -1366,7 +1366,7 @@ window.Page_uebungen = (() => {
<div>
<label style="display:block;font-size:var(--text-sm);font-weight:var(--weight-semibold);
color:var(--c-text);margin-bottom:var(--space-2)">Bewertung (optional)</label>
<div style="display:flex;gap:var(--space-2)">
<div class="flex-gap-2">
${[1,2,3,4,5].map(n => `
<button type="button" class="ueb-notiz-pfote" data-val="${n}"
style="font-size:1.4rem;border:1.5px solid var(--c-border);
@ -1382,7 +1382,7 @@ window.Page_uebungen = (() => {
<div>
<label style="display:block;font-size:var(--text-sm);font-weight:var(--weight-semibold);
color:var(--c-text);margin-bottom:var(--space-2)">Umgebung (optional)</label>
<div style="display:flex;gap:var(--space-2)">
<div class="flex-gap-2">
${[['🏠','zuhause'],['🌿','natur'],['🌆','stadt']].map(([emoji, val]) => `
<button type="button" class="ueb-notiz-umgebung" data-val="${val}"
style="font-size:1.2rem;border:1.5px solid var(--c-border);
@ -1398,7 +1398,7 @@ window.Page_uebungen = (() => {
<div>
<label style="display:block;font-size:var(--text-sm);font-weight:var(--weight-semibold);
color:var(--c-text);margin-bottom:var(--space-2)">Stimmung des Hundes (optional)</label>
<div style="display:flex;gap:var(--space-2)">
<div class="flex-gap-2">
${[['😊','super'],['😐','ok'],['😔','mude']].map(([emoji, val]) => `
<button type="button" class="ueb-notiz-stimmung" data-val="${val}"
style="font-size:1.2rem;border:1.5px solid var(--c-border);
@ -1829,7 +1829,7 @@ window.Page_uebungen = (() => {
<div style="font-size:var(--text-base);font-weight:var(--weight-semibold);color:var(--c-text)">
KI-Trainer
</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">
<div class="text-xs-secondary">
Personalisiertes Feedback basierend auf deinen Trainingseinheiten
</div>
</div>
@ -1861,7 +1861,7 @@ window.Page_uebungen = (() => {
style="font-size:var(--text-sm);color:var(--c-text);line-height:1.7;white-space:pre-wrap"></div>
</div>
<div style="display:flex;align-items:center;justify-content:space-between;margin-top:var(--space-3)">
<span id="ki-feedback-meta" style="font-size:var(--text-xs);color:var(--c-text-muted)"></span>
<span id="ki-feedback-meta" class="text-xs-muted"></span>
<button id="ki-regenerate"
style="font-size:var(--text-xs);padding:var(--space-1) var(--space-3);
border-radius:var(--radius-md);border:1px solid var(--c-border);
@ -1986,12 +1986,12 @@ window.Page_uebungen = (() => {
const dogId = _dogId();
if (!dogId) {
return `<div style="padding:var(--space-8);text-align:center;color:var(--c-text-muted)">
<p style="font-size:var(--text-sm)">Wähle einen Hund aus um das Protokoll zu sehen.</p>
<p class="text-sm">Wähle einen Hund aus um das Protokoll zu sehen.</p>
</div>`;
}
return `<div id="verlauf-wrap" style="padding:var(--space-4);display:flex;flex-direction:column;gap:var(--space-3)">
${_verlaufToggleHtml()}
<div id="verlauf-list" style="display:flex;flex-direction:column;gap:var(--space-2)">
<div id="verlauf-list" class="flex-col-gap-2">
<div style="text-align:center;padding:var(--space-6);color:var(--c-text-muted)">
<svg class="ph-icon" style="width:24px;height:24px;animation:spin 1s linear infinite" aria-hidden="true">
<use href="/icons/phosphor.svg#spinner-gap"></use>
@ -2008,7 +2008,7 @@ window.Page_uebungen = (() => {
const active = `background:var(--c-primary);color:#fff;border-color:var(--c-primary)`;
const inactive = `background:var(--c-surface-2);color:var(--c-text-secondary)`;
return `
<div style="display:flex;gap:var(--space-2)">
<div class="flex-gap-2">
<button id="verlauf-btn-datum" style="${btnBase};${_verlaufView==='datum'?active:inactive}">
Nach Datum
</button>
@ -2110,7 +2110,7 @@ window.Page_uebungen = (() => {
padding:var(--space-2) var(--space-3);border-radius:var(--radius-md);
background:var(--c-surface-2)">
<span style="font-size:1.2rem;flex-shrink:0;margin-top:1px">${erfolg}</span>
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="display:flex;align-items:center;gap:var(--space-2);flex-wrap:wrap">
<span style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
color:var(--c-text)">${_esc(s.exercise_name)}</span>
@ -2219,7 +2219,7 @@ window.Page_uebungen = (() => {
<span style="flex-shrink:0;min-width:52px">${_esc(dateLabel)}</span>
<span style="flex-shrink:0">${erfolg}</span>
<span style="flex-shrink:0">${s.erfolgsquote}%${top}</span>
<span style="flex:1;min-width:0">${s.wiederholungen}× Wdh.${stimmung ? ' ' + stimmung : ''}</span>
<span class="flex-1-min">${s.wiederholungen}× Wdh.${stimmung ? ' ' + stimmung : ''}</span>
</div>`;
}).join('');
@ -2240,7 +2240,7 @@ window.Page_uebungen = (() => {
</span>
</div>
<!-- Info -->
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
color:var(--c-text);line-height:1.3;
white-space:nowrap;overflow:hidden;text-overflow:ellipsis">
@ -2296,7 +2296,7 @@ window.Page_uebungen = (() => {
<div style="padding:var(--space-4);display:flex;flex-direction:column;gap:var(--space-4)">
<!-- Markerwort -->
<div class="card" style="padding:var(--space-4)">
<div class="card p-4">
<h3 style="font-size:var(--text-base);font-weight:var(--weight-semibold);
color:var(--c-text);margin:0 0 var(--space-3);display:flex;align-items:center;gap:var(--space-2)">
<svg class="ph-icon" style="width:16px;height:16px;color:var(--c-primary);flex-shrink:0" aria-hidden="true">

View file

@ -150,14 +150,14 @@ window.Page_walks = (() => {
</div>
<!-- Tab: Challenge -->
<div id="walks-tab-challenge" class="walks-tab-panel" style="display:none">
<div id="walks-tab-challenge" class="walks-tab-panel hidden">
<div id="challenge-content">
<p style="color:var(--c-text-secondary);text-align:center;padding:var(--space-8)">Lädt</p>
</div>
</div>
<!-- Tab: Stamm-Gassis -->
<div id="walks-tab-stamm" class="walks-tab-panel" style="display:none">
<div id="walks-tab-stamm" class="walks-tab-panel hidden">
<div class="by-toolbar">
<span style="font-weight:600;color:var(--c-text)">${UI.icon('clock')} Stamm-Gassi-Zeiten</span>
<button class="btn btn-primary btn-sm" id="gassi-zeit-add-btn">${UI.icon('plus')} Meine Zeit eintragen</button>
@ -279,8 +279,8 @@ window.Page_walks = (() => {
el.innerHTML = `
<div style="text-align:center;padding:var(--space-10) var(--space-4)">
<div style="font-size:3rem;margin-bottom:var(--space-3)">${UI.icon('dog')}</div>
<p style="color:var(--c-text-secondary)">Noch keine Treffen in deiner Nähe.</p>
<button class="btn btn-primary" style="margin-top:var(--space-4)" id="walks-first-btn">
<p class="text-secondary">Noch keine Treffen in deiner Nähe.</p>
<button class="btn btn-primary mt-4" id="walks-first-btn">
Erstes Treffen planen
</button>
</div>`;
@ -463,13 +463,13 @@ window.Page_walks = (() => {
</div>`;
return `<div style="display:flex;align-items:center;gap:4px">
${av}
<span style="font-size:var(--text-xs);color:var(--c-text-secondary)">${UI.escape(d.name)}${d.rasse ? ` · ${UI.escape(d.rasse)}` : ''}</span>
<span class="text-xs-secondary">${UI.escape(d.name)}${d.rasse ? ` · ${UI.escape(d.rasse)}` : ''}</span>
</div>`;
}).join('');
return `
<div class="walks-participant">
<div class="walks-inv-avatar walks-inv-avatar--sm">${_avatarInitials(t.user_name)}</div>
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div class="walks-participant-name">${UI.escape(t.user_name)}</div>
${dogsHTML ? `<div style="display:flex;flex-wrap:wrap;gap:var(--space-1);margin-top:4px">${dogsHTML}</div>` : ''}
</div>
@ -480,7 +480,7 @@ window.Page_walks = (() => {
// Einladungsliste
const invListHTML = invitations.length
? invitations.map(inv => _invitationRowHTML(inv)).join('')
: `<p style="color:var(--c-text-muted);font-size:var(--text-sm)">Noch keine Einladungen.</p>`;
: `<p class="text-sm-muted">Noch keine Einladungen.</p>`;
// RSVP-Section für eingeladene Nutzer
const rsvpSectionHTML = (isInvited && !isOwn) ? `
@ -548,7 +548,7 @@ window.Page_walks = (() => {
<div class="walks-detail-section-label" style="margin-bottom:0">${UI.icon('images')} Fotos</div>
${(isPast || _isToday(walk.datum)) && (isJoined || isOwn) ? `
<label style="cursor:pointer">
<input type="file" id="wd-photo-input" accept="image/*" style="display:none">
<input type="file" id="wd-photo-input" accept="image/*" class="hidden">
<span class="btn btn-secondary btn-sm">${UI.icon('camera')} Foto hinzufügen</span>
</label>` : ''}
</div>
@ -572,7 +572,7 @@ window.Page_walks = (() => {
</p>
${isOwn && !isPast ? `
<div id="wd-cancel-wrap" style="margin-top:var(--space-3)">
<div id="wd-cancel-wrap" class="mt-3">
<button type="button" class="btn btn-ghost btn-sm" id="wd-cancel-walk"
style="color:var(--c-danger);width:100%">
${UI.icon('x-circle')} Treffen stornieren
@ -580,7 +580,7 @@ window.Page_walks = (() => {
</div>` : ''}
${isJoined && !isOwn ? `
<div id="wd-leave-wrap" style="margin-top:var(--space-3)">
<div id="wd-leave-wrap" class="mt-3">
<button type="button" class="btn btn-ghost btn-sm" id="wd-leave"
style="color:var(--c-danger);width:100%">
${UI.icon('sign-out')} Nicht mehr teilnehmen
@ -782,12 +782,12 @@ window.Page_walks = (() => {
? candidates.map(f => `
<div class="walks-invite-row" data-friend-id="${f.friend_id}" data-friend-name="${UI.escape(f.friend_name)}">
<div class="walks-inv-avatar">${_avatarInitials(f.friend_name)}</div>
<div class="walks-inv-name" style="flex:1">${UI.escape(f.friend_name)}</div>
<div class="walks-inv-name flex-1">${UI.escape(f.friend_name)}</div>
<button type="button" class="btn btn-primary btn-sm walks-invite-send">
${UI.icon('paper-plane-tilt')} Einladen
</button>
</div>`).join('')
: `<p style="color:var(--c-text-muted)">Alle Freunde wurden bereits eingeladen.</p>`;
: `<p class="text-muted">Alle Freunde wurden bereits eingeladen.</p>`;
const body = `
<p style="color:var(--c-text-secondary);margin-bottom:var(--space-3)">
@ -817,7 +817,7 @@ window.Page_walks = (() => {
await API.walks.invite(walk.id, friendId);
row.innerHTML = `
<div class="walks-inv-avatar">${_avatarInitials(name)}</div>
<div class="walks-inv-name" style="flex:1">${UI.escape(name)}</div>
<div class="walks-inv-name flex-1">${UI.escape(name)}</div>
<span class="walks-rsvp-badge walks-rsvp--invited">Eingeladen</span>
`;
UI.toast.success(`${name} eingeladen.`);
@ -838,7 +838,7 @@ window.Page_walks = (() => {
<input type="checkbox" name="dog" value="${d.id}" checked>
${UI.icon('dog')} ${UI.escape(d.name)}
</label>`).join('')
: `<p style="color:var(--c-text-muted)">Keine Hunde im Profil — du kannst trotzdem mitmachen.</p>`;
: `<p class="text-muted">Keine Hunde im Profil — du kannst trotzdem mitmachen.</p>`;
const body = `
<p style="color:var(--c-text-secondary);margin-bottom:var(--space-4)">
@ -913,7 +913,7 @@ window.Page_walks = (() => {
placeholder="z. B. Sonntagsspaziergang im Stadtpark" required>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Datum *</label>
<input class="form-control" type="date" name="datum"
@ -943,7 +943,7 @@ window.Page_walks = (() => {
</div>
<!-- Ort-Chip -->
<div style="margin-top:var(--space-2)">
<div class="mt-2">
<div id="wf-location-chip-wrap" style="${_locName ? '' : 'display:none'}">
<div class="diary-location-chip">
${UI.icon('map-pin')}
@ -979,7 +979,7 @@ window.Page_walks = (() => {
</div>
<div class="form-group">
<label class="form-label">Beschreibung <span style="color:var(--c-text-secondary)">(optional)</span></label>
<label class="form-label">Beschreibung <span class="text-secondary">(optional)</span></label>
<textarea class="form-control" name="beschreibung" rows="3"
placeholder="Treffpunkt-Details, Streckenlänge, Hundefreundlichkeit…">${UI.escape(v.beschreibung || '')}</textarea>
</div>
@ -989,7 +989,7 @@ window.Page_walks = (() => {
const footer = `
<div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%">
<button type="submit" form="walk-form" class="btn btn-primary" style="width:100%">
<button type="submit" form="walk-form" class="btn btn-primary w-full">
${isEdit ? `${UI.icon('floppy-disk')} Speichern` : `${UI.icon('calendar-dots')} Treffen planen`}
</button>
<button type="button" class="btn btn-secondary" id="wf-cancel">Abbrechen</button>
@ -1225,7 +1225,7 @@ window.Page_walks = (() => {
<div style="width:100%;max-width:600px;background:var(--c-surface);border-radius:var(--radius-lg) var(--radius-lg) 0 0;
padding:var(--space-4);box-sizing:border-box;max-height:80vh;display:flex;flex-direction:column">
<div style="display:flex;align-items:center;gap:var(--space-3);margin-bottom:var(--space-3)">
<svg class="ph-icon" aria-hidden="true" style="color:var(--c-primary)"><use href="/icons/phosphor.svg#note-pencil"></use></svg>
<svg class="ph-icon" aria-hidden="true" class="text-primary"><use href="/icons/phosphor.svg#note-pencil"></use></svg>
<span style="font-weight:600;flex:1"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note-pencil"></use></svg> Notiz ${UI.escape(parentLabel)}</span>
<button id="wk-note-close" style="background:none;border:none;cursor:pointer;color:var(--c-text-muted);padding:4px">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#x"></use></svg>
@ -1296,8 +1296,8 @@ 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" style="margin-top:var(--space-3)">${UI.icon('camera')} Foto einreichen</button>` : ''}
${my_submission_id ? `<span class="badge badge-success" style="margin-top:var(--space-2)">${UI.icon('check')} Du hast bereits teilgenommen</span>` : ''}
${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>` : ''}
</div>
</div>
@ -1371,7 +1371,7 @@ window.Page_walks = (() => {
<img src="${UI.escape(w.winner.foto_url)}" alt="Gewinner" onerror="this.src='/icons/icon-192.png'">
<div>
<div style="font-weight:600;font-size:var(--text-xs)">${UI.escape(w.challenge.thema)}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">${UI.escape(w.winner.user_name)} · ${w.winner.votes} </div>
<div class="text-xs-secondary">${UI.escape(w.winner.user_name)} · ${w.winner.votes} </div>
</div>
</div>`;
}).join('') +
@ -1387,21 +1387,21 @@ window.Page_walks = (() => {
UI.modal.open({
title: `📸 ${UI.escape(_challengeData.challenge.thema)}`,
body: `
<form id="challenge-submit-form" style="display:flex;flex-direction:column;gap:var(--space-3)">
<form id="challenge-submit-form" class="flex-col-gap-3">
<div class="form-group">
<label>Foto *</label>
<input type="file" id="challenge-foto-input" accept="image/*" required style="width:100%">
<input type="file" id="challenge-foto-input" accept="image/*" required class="w-full">
</div>
${dogs.length ? `<div class="form-group">
<label>Hund</label>
<select id="challenge-dog-select" style="width:100%">
<select id="challenge-dog-select" class="w-full">
<option value="">Kein Hund</option>
${dogOptions}
</select>
</div>` : ''}
<div class="form-group">
<label>Bildunterschrift</label>
<input type="text" id="challenge-caption" placeholder="z.B. Mein Bello beim besten Schnüffeln…" maxlength="200" style="width:100%">
<input type="text" id="challenge-caption" placeholder="z.B. Mein Bello beim besten Schnüffeln…" maxlength="200" class="w-full">
</div>
</form>
`,
@ -1465,7 +1465,7 @@ window.Page_walks = (() => {
<div style="text-align:center;padding:var(--space-8);color:var(--c-text-secondary)">
${UI.icon('clock')}
<p>Noch keine Stamm-Gassi-Zeiten in deiner Nähe.</p>
<p style="font-size:var(--text-sm)">Trag deine regelmäßigen Zeiten ein andere finden dich dann!</p>
<p class="text-sm">Trag deine regelmäßigen Zeiten ein andere finden dich dann!</p>
</div>`;
return;
}
@ -1537,7 +1537,7 @@ window.Page_walks = (() => {
</div>
<div class="gz-body">
<div class="gz-name">${UI.escape(z.dog_name || z.user_name || '?')}
${z.dog_rasse ? `<span class="badge" style="font-size:var(--text-xs)">${UI.escape(z.dog_rasse)}</span>` : ''}
${z.dog_rasse ? `<span class="badge text-xs">${UI.escape(z.dog_rasse)}</span>` : ''}
${!z.aktiv ? `<span class="badge badge-warning">Pausiert</span>` : ''}
</div>
<div class="gz-meta">
@ -1576,17 +1576,17 @@ window.Page_walks = (() => {
UI.modal.open({
title: `${UI.icon('clock')} Stamm-Gassi-Zeit eintragen`,
body: `
<form id="gassi-zeit-form" style="display:flex;flex-direction:column;gap:var(--space-3)">
<form id="gassi-zeit-form" class="flex-col-gap-3">
${dogs.length ? `<div class="form-group">
<label>Hund</label>
<select id="gz-dog-select" style="width:100%">
<select id="gz-dog-select" class="w-full">
<option value="">Kein Hund</option>
${dogOptions}
</select>
</div>` : ''}
<div class="form-group">
<label>Uhrzeit *</label>
<input type="time" id="gz-uhrzeit" required style="width:100%">
<input type="time" id="gz-uhrzeit" required class="w-full">
</div>
<div class="form-group">
<label>Wochentage *</label>
@ -1594,11 +1594,11 @@ window.Page_walks = (() => {
</div>
<div class="form-group">
<label>Ort (optional)</label>
<input type="text" id="gz-ort-name" placeholder="z.B. Stadtpark Ebersberg" style="width:100%">
<input type="text" id="gz-ort-name" placeholder="z.B. Stadtpark Ebersberg" class="w-full">
</div>
<div class="form-group">
<label>Notiz (optional)</label>
<input type="text" id="gz-notiz" placeholder="z.B. Wir sind eine ruhige Gruppe…" maxlength="200" style="width:100%">
<input type="text" id="gz-notiz" placeholder="z.B. Wir sind eine ruhige Gruppe…" maxlength="200" class="w-full">
</div>
</form>
`,

View file

@ -164,11 +164,11 @@ window.Page_welcome = (() => {
` : ''}
<p class="wc-footer">
<a href="/#impressum" style="color:var(--c-text-muted)">Impressum</a>
<a href="/#impressum" class="text-muted">Impressum</a>
&nbsp;·&nbsp;
<a href="/#datenschutz" style="color:var(--c-text-muted)">Datenschutz</a>
<a href="/#datenschutz" class="text-muted">Datenschutz</a>
&nbsp;·&nbsp;
<a href="/#agb" style="color:var(--c-text-muted)">AGB</a>
<a href="/#agb" class="text-muted">AGB</a>
</p>
</div>
`;
@ -403,7 +403,7 @@ window.Page_welcome = (() => {
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#download-simple"></use></svg>
Immer griffbereit kein App Store
</div>
<div style="padding:var(--space-4)">${_installHTML()}</div>
<div class="p-4">${_installHTML()}</div>
</div>
` : ''}
@ -485,16 +485,16 @@ window.Page_welcome = (() => {
}
const medals = ['🥇', '🥈', '🥉'];
return `
<div style="display:flex;flex-direction:column;gap:var(--space-2)">
<div class="flex-col-gap-2">
${rows.map((r, i) => `
<div style="display:flex;align-items:center;gap:var(--space-3);padding:var(--space-2) 0;border-bottom:1px solid var(--c-border-subtle)">
<span style="font-size:1.4rem;width:28px;text-align:center;flex-shrink:0">${medals[i] || (i + 1) + '.'}</span>
${r.foto_url
? `<img src="${UI.escape(r.foto_url)}" style="width:36px;height:36px;border-radius:50%;object-fit:cover;flex-shrink:0" alt="">`
: `<div style="width:36px;height:36px;border-radius:50%;background:var(--c-primary-subtle);flex-shrink:0"></div>`}
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="font-weight:var(--weight-semibold);font-size:var(--text-sm);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${UI.escape(r.dog_name)}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">${UI.escape(r.rasse || '')}${r.user_name ? ' · ' + UI.escape(r.user_name) : ''}</div>
<div class="text-xs-secondary">${UI.escape(r.rasse || '')}${r.user_name ? ' · ' + UI.escape(r.user_name) : ''}</div>
</div>
<div style="display:flex;align-items:center;gap:4px;flex-shrink:0">
<span style="font-size:1.1rem">🔥</span>
@ -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" style="flex:1">
<button class="btn btn-sm btn-ghost" id="inst-tab-ios" class="flex-1">
iPhone / iPad
</button>
</div>
@ -1118,7 +1118,7 @@ window.Page_welcome = (() => {
</p>
</div>
</div>
<div id="inst-panel-ios" style="display:none">
<div id="inst-panel-ios" class="hidden">
${_steps([
['arrow-square-in', 'Öffne <strong>banyaro.app</strong> in Safari auf dem iPhone'],
['share', 'Tippe auf das <strong>Teilen-Symbol</strong> <svg class="ph-icon" style="width:14px;height:14px;vertical-align:-2px"><use href="/icons/phosphor.svg#share"></use></svg>'],

View file

@ -84,8 +84,8 @@ window.Page_wetter = (() => {
_container.innerHTML = `
<div id="wttr-body">
<div style="text-align:center;padding:var(--space-10) var(--space-4)">
<div style="margin-bottom:var(--space-3)">${_wmoIcon(2, '2.5rem')}</div>
<p style="color:var(--c-text-secondary)">Standort wird ermittelt</p>
<div class="mb-3">${_wmoIcon(2, '2.5rem')}</div>
<p class="text-secondary">Standort wird ermittelt</p>
</div>
</div>
`;
@ -198,7 +198,7 @@ window.Page_wetter = (() => {
</div>
<!-- CTAs -->
<div style="display:flex;flex-direction:column;gap:var(--space-3)">
<div class="flex-col-gap-3">
<button class="btn btn-primary" id="wttr-btn-retry"
style="display:flex;align-items:center;justify-content:center;gap:var(--space-2)">
<svg class="ph-icon" style="width:1rem;height:1rem">
@ -247,7 +247,7 @@ window.Page_wetter = (() => {
if (body) body.innerHTML = `
<div style="text-align:center;padding:var(--space-10) var(--space-4)">
<div style="font-size:2.5rem;margin-bottom:var(--space-3)"></div>
<h3 style="margin-bottom:var(--space-2)">Wetter nicht verfügbar</h3>
<h3 class="mb-2">Wetter nicht verfügbar</h3>
<p style="color:var(--c-text-secondary);margin-bottom:var(--space-5)">
Die Wetterdaten konnten nicht geladen werden.
</p>
@ -287,13 +287,11 @@ window.Page_wetter = (() => {
</div>
<!-- Detail-Card -->
<div id="wttr-detail" class="section-card"
style="margin-bottom:var(--space-4)">
<div id="wttr-detail" class="section-card mb-4">
</div>
<!-- Niederschlagswahrscheinlichkeit Zeitskala -->
<div id="wttr-rain" class="section-card"
style="margin-bottom:var(--space-4)">
<div id="wttr-rain" class="section-card mb-4">
</div>
<!-- Hunde-Wetter -->
@ -441,7 +439,7 @@ window.Page_wetter = (() => {
<!-- Sonnenaufgang / -untergang -->
${sunriseStr && sunsetStr ? `
<div style="margin-bottom:var(--space-4)">
<div class="mb-4">
<div style="display:flex;justify-content:space-between;
font-size:var(--text-xs);color:var(--c-text-secondary);
margin-bottom:var(--space-1)">
@ -469,18 +467,18 @@ window.Page_wetter = (() => {
<span style="font-size:1.4rem;transform:rotate(${windDir}deg);display:inline-block;line-height:1">
${UI.icon('arrow-up')}
</span>
<div style="flex:1">
<div class="flex-1">
<div style="font-size:var(--text-sm);font-weight:600">
${_esc(compass)} · ${Math.round(d.wind_kmh ?? 0)} km/h
</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">${_esc(bft)}</div>
<div class="text-xs-secondary">${_esc(bft)}</div>
</div>
${d.precip_sum != null ? `
<div style="text-align:right">
<div class="text-right">
<div style="font-size:var(--text-sm);font-weight:600">
${d.precip_sum} mm
</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)">Niederschlag</div>
<div class="text-xs-secondary">Niederschlag</div>
</div>` : ''}
</div>
@ -488,7 +486,7 @@ window.Page_wetter = (() => {
<div>
<div style="display:flex;justify-content:space-between;
font-size:var(--text-xs);margin-bottom:4px">
<span style="color:var(--c-text-secondary)">UV-Index</span>
<span class="text-secondary">UV-Index</span>
<span style="font-weight:600;color:${uvColor}">
${d.uv_index ?? 0} ${_esc(uvLabel)}
</span>
@ -670,7 +668,7 @@ window.Page_wetter = (() => {
background:${aspColor}1a;border:1px solid ${aspColor}55;
margin-bottom:var(--space-3)">
<svg class="ph-icon" style="width:1.3rem;height:1.3rem;flex-shrink:0;color:var(--c-primary)"><use href="/icons/phosphor.svg#paw-print"></use></svg>
<div style="flex:1">
<div class="flex-1">
<div style="font-weight:600;font-size:var(--text-sm);color:${aspColor}">
Asphalt ~${Math.round(d.asphalt_temp)}°C ${_esc(aspText)}
</div>
@ -690,7 +688,7 @@ window.Page_wetter = (() => {
background:#3b82f61a;border:1px solid #3b82f655;
margin-bottom:var(--space-3)">
<svg class="ph-icon" style="width:1.3rem;height:1.3rem;color:#38BDF8"><use href="/icons/phosphor.svg#snowflake"></use></svg>
<div style="font-size:var(--text-sm)">
<div class="text-sm">
<strong>Kälteschutz für Pfoten:</strong>
Eis und Streusalz können die Pfoten reizen. Pfotenpflege empfohlen.
</div>
@ -706,7 +704,7 @@ window.Page_wetter = (() => {
background:#f59e0b1a;border:1px solid #f59e0b55;
margin-bottom:var(--space-3)">
<svg class="ph-icon" style="width:1.3rem;height:1.3rem;color:#7C3AED"><use href="/icons/phosphor.svg#cloud-lightning"></use></svg>
<div style="font-size:var(--text-sm)">
<div class="text-sm">
<strong>Gewitter erwartet:</strong>
Hunde können auf Gewitter sensibel reagieren. Sichere Umgebung schaffen.
</div>
@ -721,7 +719,7 @@ window.Page_wetter = (() => {
.filter(([, v]) => v != null && v.level > 0);
if (pollenEntries.length) {
html += `
<div style="margin-bottom:var(--space-3)">
<div class="mb-3">
<div style="font-size:var(--text-xs);font-weight:600;
color:var(--c-text-secondary);margin-bottom:var(--space-2)">
<svg class="ph-icon" style="width:1em;height:1em;vertical-align:-1px;color:#16A34A"><use href="/icons/phosphor.svg#leaf"></use></svg>
@ -755,7 +753,7 @@ window.Page_wetter = (() => {
background:${tickColor}1a;border:1px solid ${tickColor}55;
margin-bottom:var(--space-3)">
<svg class="ph-icon" style="width:1.3rem;height:1.3rem;color:#92400E"><use href="/icons/phosphor.svg#bug"></use></svg>
<div style="flex:1">
<div class="flex-1">
<span style="font-size:var(--text-sm);font-weight:600">Zecken-Risiko: </span>
<span style="font-size:var(--text-sm);color:${tickColor};font-weight:700">
${_esc(tickLabel)}
@ -788,7 +786,7 @@ window.Page_wetter = (() => {
<svg class="ph-icon" style="width:1.3rem;height:1.3rem;flex-shrink:0;color:${fellHint.color}">
<use href="/icons/phosphor.svg#${fellHint.icon}"></use>
</svg>
<div style="font-size:var(--text-sm)">${_esc(fellHint.text)}</div>
<div class="text-sm">${_esc(fellHint.text)}</div>
</div>
`;
}
@ -808,7 +806,7 @@ window.Page_wetter = (() => {
if (!d.asphalt_temp && !d.paw_cold && !d.thunderstorm
&& !d.zecken && !(pollen && Object.keys(pollen).length)) {
html += `
<p style="font-size:var(--text-sm);color:var(--c-text-secondary)">
<p class="text-sm-secondary">
Keine besonderen Hinweise für heute.
</p>
`;
@ -876,7 +874,7 @@ window.Page_wetter = (() => {
<span style="font-size:var(--text-2xl);font-weight:900;color:${color};line-height:1">
${score}
</span>
<span style="font-size:var(--text-xs);color:var(--c-text-secondary)">/&nbsp;10</span>
<span class="text-xs-secondary">/&nbsp;10</span>
<span style="font-size:var(--text-xs);font-weight:600;color:${color};
white-space:nowrap"> ${_esc(text)}</span>
</div>
@ -947,7 +945,7 @@ window.Page_wetter = (() => {
background:#f59e0b1a;border:1px solid #f59e0b55;
margin-bottom:var(--space-3)">
<svg class="ph-icon" style="width:1.3rem;height:1.3rem;flex-shrink:0;color:#F59E0B"><use href="/icons/phosphor.svg#baby"></use></svg>
<div style="font-size:var(--text-sm)">
<div class="text-sm">
<strong>Welpe</strong> kurze Spaziergänge, max. 15 Min bei Hitze.
Gelenke und Pfoten besonders schonen.
</div>
@ -961,7 +959,7 @@ window.Page_wetter = (() => {
background:#6b7280 1a;border:1px solid #6b728055;
margin-bottom:var(--space-3)">
<svg class="ph-icon" style="width:1.3rem;height:1.3rem;flex-shrink:0;color:#9CA3AF"><use href="/icons/phosphor.svg#person-simple-walk"></use></svg>
<div style="font-size:var(--text-sm)">
<div class="text-sm">
<strong>Seniorhund</strong> Hitze und Kälte vermeiden, kurze Runden bevorzugen.
Auf Gelenkbeschwerden achten.
</div>

View file

@ -18,7 +18,7 @@ window.Page_widget = (() => {
async function _render() {
_container.innerHTML = `
<div style="padding:var(--space-4)">
<div class="p-4">
<div style="text-align:center;color:var(--c-text-muted);padding:var(--space-8)">
<div style="font-size:2rem"></div>
<div>Lade</div>
@ -57,7 +57,7 @@ window.Page_widget = (() => {
</div>`
: `<div class="widget-photo-wrap widget-photo-placeholder">
<svg class="ph-icon" style="font-size:3rem" aria-hidden="true"><use href="/icons/phosphor.svg#image"></use></svg>
<div style="color:var(--c-text-muted);font-size:var(--text-sm)">Noch keine Fotos im Tagebuch</div>
<div class="text-sm-muted">Noch keine Fotos im Tagebuch</div>
</div>`;
const reminderHtml = rem
@ -65,17 +65,17 @@ window.Page_widget = (() => {
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#calendar-check"></use></svg>
<div>
<div style="font-weight:var(--weight-semibold);font-size:var(--text-sm)">${_esc(rem.bezeichnung)}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">${_fmtDate(rem.naechstes)}</div>
<div class="text-xs-muted">${_fmtDate(rem.naechstes)}</div>
</div>
</div>`
: data.overdue > 0
? `<div class="widget-reminder widget-reminder--overdue">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#warning"></use></svg>
<div style="font-size:var(--text-sm)">${data.overdue} überfällige Erinnerung${data.overdue > 1 ? 'en' : ''}</div>
<div class="text-sm">${data.overdue} überfällige Erinnerung${data.overdue > 1 ? 'en' : ''}</div>
</div>`
: `<div class="widget-reminder widget-reminder--ok">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#check-circle"></use></svg>
<div style="font-size:var(--text-sm)">Keine offenen Erinnerungen</div>
<div class="text-sm">Keine offenen Erinnerungen</div>
</div>`;
const dogAvatar = dog.foto_url
@ -83,14 +83,14 @@ window.Page_widget = (() => {
: `<div class="widget-dog-av widget-dog-av--placeholder">🐕</div>`;
_container.innerHTML = `
<div style="padding:var(--space-4)">
<div class="p-4">
<div class="widget-card">
<div class="widget-dog-row">
${dogAvatar}
<div>
<div style="font-weight:var(--weight-bold);font-size:var(--text-lg)">${_esc(dog.name)}</div>
${dog.rasse ? `<div style="font-size:var(--text-sm);color:var(--c-text-muted)">${_esc(dog.rasse)}</div>` : ''}
${dog.rasse ? `<div class="text-sm-muted">${_esc(dog.rasse)}</div>` : ''}
</div>
<button class="btn btn-secondary btn-sm" id="widget-refresh-btn" style="margin-left:auto"
title="Neues Zufallsbild">
@ -113,11 +113,11 @@ window.Page_widget = (() => {
<p style="font-size:var(--text-xs);color:var(--c-text-secondary);margin-bottom:var(--space-3)">
Füge diese Seite zum Home-Screen hinzu und öffne sie mit einem Tipp.
</p>
<div style="display:flex;flex-direction:column;gap:var(--space-2)">
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">
<div class="flex-col-gap-2">
<div class="text-xs-muted">
<strong>iOS Safari:</strong> Teilen-Symbol Zum Home-Bildschirm"
</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">
<div class="text-xs-muted">
<strong>Android Chrome:</strong> Menü () Zum Startbildschirm hinzufügen"
</div>
</div>

View file

@ -102,7 +102,7 @@ window.Page_wiki = (() => {
<button class="by-tab${_tab === 'gesundheit'? ' active' : ''}" data-tab="gesundheit">${UI.icon('syringe')} Gesundheit</button>
<button class="by-tab${_tab === 'recht' ? ' active' : ''}" data-tab="recht">${UI.icon('handshake')} Recht</button>
<button class="by-tab${_tab === 'quiz' ? ' active' : ''}" data-tab="quiz">${UI.icon('star')} Quiz</button>
${isMod ? `<button class="by-tab${_tab === 'fotos' ? ' active' : ''}" data-tab="fotos" id="wiki-fotos-tab">${UI.icon('camera')} Fotos <span id="wiki-fotos-badge" style="display:none" class="badge badge-sm">0</span></button>` : ''}
${isMod ? `<button class="by-tab${_tab === 'fotos' ? ' active' : ''}" data-tab="fotos" id="wiki-fotos-tab">${UI.icon('camera')} Fotos <span id="wiki-fotos-badge" class="badge badge-sm hidden">0</span></button>` : ''}
</div>
<div id="wiki-content"></div>
`;
@ -132,7 +132,7 @@ window.Page_wiki = (() => {
// TAB: Foto-Einreichungen (Mod/Admin)
// ----------------------------------------------------------
async function _renderFotoSubmissions(el) {
el.innerHTML = `<div style="padding:var(--space-4)">${UI.skeleton(3)}</div>`;
el.innerHTML = `<div class="p-4">${UI.skeleton(3)}</div>`;
let subs;
try {
subs = await _apiFetch('/api/wiki/foto-submissions');
@ -155,7 +155,7 @@ window.Page_wiki = (() => {
}
el.innerHTML = `
<div style="padding:var(--space-4)">
<div class="p-4">
<h3 style="font-size:var(--text-base);font-weight:var(--weight-semibold);margin-bottom:var(--space-4)">
Ausstehende Fotos (${subs.length})
</h3>
@ -165,9 +165,9 @@ window.Page_wiki = (() => {
<div style="display:flex;gap:var(--space-3);align-items:flex-start">
<img src="${_esc(s.foto_url)}" alt=""
style="width:100px;height:80px;object-fit:cover;border-radius:var(--radius-md);flex-shrink:0;background:var(--c-surface-2)">
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div style="font-weight:var(--weight-semibold)">${_esc(s.rasse_name)}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">
<div class="text-xs-muted">
von ${_esc(s.user_name)} · ${_formatDate(s.created_at)}
</div>
${s.aktuell_foto
@ -257,12 +257,12 @@ window.Page_wiki = (() => {
</div>
<div style="padding:0 0 var(--space-3)">
<button class="btn btn-secondary w-full" id="wiki-rasse-erkennen-btn"
style="font-size:var(--text-sm)">
class="text-sm">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#magnifying-glass"></use></svg>
Welche Rasse ist das? Foto analysieren
</button>
<input type="file" accept="image/jpeg,image/png,image/webp"
id="wiki-rasse-foto-input" style="display:none">
id="wiki-rasse-foto-input" class="hidden">
</div>
<div class="wiki-breed-grid" id="wiki-breed-grid"></div>
<div id="wiki-mehr-wrap" style="text-align:center;padding:var(--space-4) 0;display:none">
@ -460,14 +460,14 @@ window.Page_wiki = (() => {
: (rasse.gewicht_max_kg ? `bis ${rasse.gewicht_max_kg} kg` : '&mdash;');
const kinderLabel = rasse.kinder_geeignet === true
? `<span style="color:var(--c-success)">&#10003; Ja</span>`
? `<span class="text-success">&#10003; Ja</span>`
: rasse.kinder_geeignet === false
? `<span style="color:var(--c-warning)">&#9889; Bedingt</span>`
: '&mdash;';
const wohnungLabel = rasse.wohnung_geeignet
? `<span style="color:var(--c-success)">&#10003; Ja</span>`
: `<span style="color:var(--c-text-secondary)">&#10007; Besser Garten</span>`;
? `<span class="text-success">&#10003; Ja</span>`
: `<span class="text-secondary">&#10007; Besser Garten</span>`;
const rows = [
['Größe', _groesseLabel(rasse.groesse) || '&mdash;'],
@ -514,7 +514,7 @@ window.Page_wiki = (() => {
<span id="wiki-hat-count">&#128021; <strong>${hatCount}</strong> haben diesen Hund</span>
<span id="wiki-will-count">&#10084;&#65039; <strong>${willCount}</strong> m&ouml;chten ihn</span>
</div>
<div style="display:flex;gap:var(--space-2)">
<div class="flex-gap-2">
<button class="btn btn-sm wiki-interesse-btn" id="wiki-btn-hat"
style="flex:1;${hatStyle}"
data-slug="${_esc(slug)}" data-typ="hat">
@ -597,7 +597,7 @@ window.Page_wiki = (() => {
${z.zwingername ? `<em style="font-weight:normal;color:var(--c-text-secondary)"> &bdquo;${_esc(z.zwingername)}&ldquo;</em>` : ''}
${z.vdh_mitglied ? `<span class="badge badge-sm" style="margin-left:var(--space-1);background:var(--c-primary);color:#fff">VDH</span>` : ''}
</div>
${(z.ort || z.bundesland) ? `<div style="font-size:var(--text-sm);color:var(--c-text-secondary)">${[z.ort, z.bundesland].filter(Boolean).map(_esc).join(', ')}</div>` : ''}
${(z.ort || z.bundesland) ? `<div class="text-sm-secondary">${[z.ort, z.bundesland].filter(Boolean).map(_esc).join(', ')}</div>` : ''}
${z.beschreibung ? `<p style="font-size:var(--text-sm);margin-top:var(--space-1)">${_esc(z.beschreibung)}</p>` : ''}
${z.website ? `<a href="${_esc(z.website)}" target="_blank" rel="noopener" style="font-size:var(--text-sm);color:var(--c-primary)">${_esc(z.website)}</a>` : ''}
</div>
@ -656,7 +656,7 @@ window.Page_wiki = (() => {
</div>
</form>
</div>
<button class="btn btn-secondary btn-sm" id="wiki-zuchter-add-btn" style="margin-top:var(--space-3)">
<button class="btn btn-secondary btn-sm" id="wiki-zuchter-add-btn" class="mt-3">
+ Züchter eintragen
</button>
` : '';
@ -745,7 +745,7 @@ window.Page_wiki = (() => {
<img class="wiki-detail-photo wiki-gallery-main" id="wiki-main-photo"
src="${_esc(allFotos[0].foto_url)}" alt="${_esc(rasse.name)}"
onerror="this.style.display='none';document.getElementById('wiki-photo-fallback').style.display='flex'">
<div id="wiki-photo-fallback" class="wiki-detail-photo-placeholder" style="display:none">${_dogSvgLg}<span>Kein Foto verfügbar</span></div>
<div id="wiki-photo-fallback" class="wiki-detail-photo-placeholder hidden">${_dogSvgLg}<span>Kein Foto verfügbar</span></div>
${allFotos.length > 1 ? `
<div class="wiki-gallery-strip" id="wiki-gallery-strip">
${allFotos.map((f, i) => `
@ -772,7 +772,7 @@ window.Page_wiki = (() => {
${photoHtml}
${userFotosHtml}
<h1 style="font-size:var(--text-xl);font-weight:var(--weight-bold);margin:var(--space-2) 0 var(--space-1)">${_esc(rasse.name)}</h1>
${rasse.herkunft ? `<div style="font-size:var(--text-sm);color:var(--c-text-secondary)">${UI.icon('map-pin')} ${_esc(rasse.herkunft)}</div>` : ''}
${rasse.herkunft ? `<div class="text-sm-secondary">${UI.icon('map-pin')} ${_esc(rasse.herkunft)}</div>` : ''}
${rasse.gruppe ? `<div style="font-size:var(--text-sm);color:var(--c-text-muted);margin-top:2px">${_esc(rasse.gruppe)}</div>` : ''}
</div>
@ -822,14 +822,14 @@ window.Page_wiki = (() => {
${berichteHtml}
</div>
${_appState.user
? `<button class="btn btn-secondary w-full" id="wiki-bericht-add-btn" style="margin-top:var(--space-3)">+ 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)">
<a href="#settings" style="color:var(--c-primary)">Anmelden</a>, um einen Bericht zu schreiben.
<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" style="font-size:var(--text-sm)">
<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'}
</button>
</div>` : ''}`}
@ -1098,7 +1098,7 @@ window.Page_wiki = (() => {
<span class="wiki-section-titel">${_esc(s.titel)}</span>
<span class="wiki-section-arrow">${UI.icon('caret-down')}</span>
</div>
<div class="wiki-section-body" style="display:none">
<div class="wiki-section-body hidden">
<p style="white-space:pre-wrap;line-height:1.6;color:var(--c-text)">${_esc(s.text)}</p>
</div>
</div>
@ -1129,7 +1129,7 @@ window.Page_wiki = (() => {
<span class="wiki-section-titel">${_esc(r.land)}</span>
<span class="wiki-section-arrow">${UI.icon('caret-down')}</span>
</div>
<div class="wiki-section-body" style="display:none">
<div class="wiki-section-body hidden">
<div class="wiki-recht-row"><span class="wiki-recht-label">Leinenpflicht</span><span>${_esc(r.leine)}</span></div>
<div class="wiki-recht-row"><span class="wiki-recht-label">Rasseliste</span><span>${_esc(r.rasse)}</span></div>
<div class="wiki-recht-row"><span class="wiki-recht-label">Hundesteuer</span><span>${_esc(r.steuer)}</span></div>
@ -1242,7 +1242,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="${_esc(r.slug)}" style="margin-top:var(--space-2)">Mehr erfahren</button>
<button class="btn btn-secondary btn-sm wiki-quiz-mehr" data-slug="${_esc(r.slug)}" class="mt-2">Mehr erfahren</button>
</div>
</div>
`;
@ -1251,11 +1251,11 @@ window.Page_wiki = (() => {
el.innerHTML = `
<div class="wiki-quiz-wrap">
<div class="wiki-quiz-progress-bar">
<div class="wiki-quiz-progress" style="width:100%"></div>
<div class="wiki-quiz-progress w-full"></div>
</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" style="margin-top:var(--space-4)">Quiz neu starten</button>
<button class="btn btn-secondary w-full" id="quiz-restart" class="mt-4">Quiz neu starten</button>
</div>
`;
@ -1401,7 +1401,7 @@ window.Page_wiki = (() => {
title: 'Kein Hund erkannt',
body: `<div style="text-align:center;padding:var(--space-6) var(--space-2)">
<div style="font-size:3rem;margin-bottom:var(--space-3)">🐾</div>
<p style="color:var(--c-text-secondary)">
<p class="text-secondary">
Auf diesem Foto konnte kein Hund erkannt werden.<br>
Bitte lade ein deutlicheres Foto hoch.
</p>
@ -1427,7 +1427,7 @@ window.Page_wiki = (() => {
</div>
${r.beschreibung ? `<div class="rasse-result-desc">${_esc(r.beschreibung)}</div>` : ''}
${r.wiki_slug ? `
<div style="margin-top:var(--space-3)">
<div class="mt-3">
<button class="btn btn-${isTop ? 'primary' : 'secondary'} btn-sm w-full"
data-action="wiki" data-slug="${_esc(r.wiki_slug)}">
Im Wiki nachschlagen

View file

@ -93,7 +93,7 @@ window.Page_zucht_profil = (() => {
_container.innerHTML = `
<div style="text-align:center;padding:var(--space-10) var(--space-4)">
<div style="font-size:3rem;margin-bottom:var(--space-3)">${UI.icon('warning')}</div>
<p style="color:var(--c-text-secondary)">Kein Hund angegeben.</p>
<p class="text-secondary">Kein Hund angegeben.</p>
</div>`;
return;
}
@ -126,7 +126,7 @@ window.Page_zucht_profil = (() => {
_container.innerHTML = `
<div style="text-align:center;padding:var(--space-10) var(--space-4)">
<div style="font-size:3rem;margin-bottom:var(--space-3)">${UI.icon('warning')}</div>
<p style="color:var(--c-danger)">${_esc(err.message || 'Fehler beim Laden.')}</p>
<p class="text-danger">${_esc(err.message || 'Fehler beim Laden.')}</p>
<button class="btn btn-secondary" onclick="history.back()">Zurück</button>
</div>`;
}
@ -138,7 +138,7 @@ window.Page_zucht_profil = (() => {
function _renderSkeleton() {
_container.innerHTML = `
<div class="zp-layout">
<button class="btn btn-ghost btn-sm zp-back-btn" style="margin-bottom:var(--space-4)">
<button class="btn btn-ghost btn-sm zp-back-btn mb-4">
${UI.icon('arrow-left')} Zurück zur Zuchtkartei
</button>
${UI.skeleton(6)}
@ -274,7 +274,7 @@ window.Page_zucht_profil = (() => {
${identItems.map(m => `<span>${m}</span>`).join('')}
</div>` : ''}
${elternItems.length ? `
<div class="zp-header-meta" style="font-size:var(--text-xs);color:var(--c-text-secondary)">
<div class="zp-header-meta text-xs-secondary">
${elternItems.join(' &nbsp;·&nbsp; ')}
</div>` : ''}
${h.notiz ? `<div class="zp-header-notiz">${_esc(h.notiz)}</div>` : ''}
@ -378,7 +378,7 @@ window.Page_zucht_profil = (() => {
<tr>
<td class="zp-td">
<span style="font-weight:var(--weight-medium)">${_esc(t.test_typ || 'Sonstiges')}</span>
${t.test_name ? `<br><span style="font-size:var(--text-xs);color:var(--c-text-secondary)">${_esc(t.test_name)}</span>` : ''}
${t.test_name ? `<br><span class="text-xs-secondary">${_esc(t.test_name)}</span>` : ''}
</td>
<td class="zp-td">${_healthBadge(t.test_typ || '', t.ergebnis)}</td>
<td class="zp-td zp-td-muted">${t.untersuch_am ? _fmtDate(t.untersuch_am) : '—'}</td>
@ -463,7 +463,7 @@ window.Page_zucht_profil = (() => {
${t.verliehen_am ? `${UI.icon('calendar-dots')} ${_fmtDate(t.verliehen_am)}` : ''}
${t.ort ? `&nbsp;·&nbsp; ${UI.icon('map-pin')} ${_esc(t.ort)}` : ''}
${t.richter ? `&nbsp;·&nbsp; ${UI.icon('user')} ${_esc(t.richter)}` : ''}
${t.ausstellung ? `<br><span style="font-size:var(--text-xs)">${UI.icon('ticket')} ${_esc(t.ausstellung)}</span>` : ''}
${t.ausstellung ? `<br><span class="text-xs">${UI.icon('ticket')} ${_esc(t.ausstellung)}</span>` : ''}
</div>
</div>`).join('');

View file

@ -120,7 +120,7 @@ window.Page_zuchthunde = (() => {
padding:var(--space-3) var(--space-4);
display:flex;align-items:center;gap:var(--space-3)">
${logoHtml}
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<h2 style="margin:0 0 2px;font-size:var(--text-lg);font-weight:700;
color:var(--c-text);white-space:nowrap;overflow:hidden;
text-overflow:ellipsis;line-height:1.2">${_esc(zwinger)}</h2>
@ -128,7 +128,7 @@ window.Page_zuchthunde = (() => {
<svg style="width:11px;height:11px;color:var(--c-primary);flex-shrink:0" viewBox="0 0 256 256">
<use href="/icons/phosphor.svg#lock-key"></use>
</svg>
<span style="font-size:var(--text-xs);color:var(--c-text-secondary)">Privater Bereich · Nur du siehst das</span>
<span class="text-xs-secondary">Privater Bereich · Nur du siehst das</span>
</div>
</div>
</div>`;
@ -232,8 +232,8 @@ window.Page_zuchthunde = (() => {
: `
<div style="text-align:center;padding:var(--space-10) var(--space-4)">
<div style="font-size:3rem;margin-bottom:var(--space-3)">${UI.icon('dog')}</div>
<p style="color:var(--c-text-secondary)">Noch keine Hunde angelegt.</p>
<button class="btn btn-primary" style="margin-top:var(--space-4)" id="zh-first-btn">
<p class="text-secondary">Noch keine Hunde angelegt.</p>
<button class="btn btn-primary mt-4" id="zh-first-btn">
${UI.icon('plus')} Ersten Hund anlegen
</button>
</div>`;
@ -299,7 +299,7 @@ window.Page_zuchthunde = (() => {
// Hund-Card HTML
// ----------------------------------------------------------
function _hundCardHTML(h) {
const nameLabel = h.name ? _esc(h.name) : '<em style="color:var(--c-text-muted)">Unbenannt</em>';
const nameLabel = h.name ? _esc(h.name) : '<em class="text-muted">Unbenannt</em>';
const rufname = h.rufname ? ` (${_esc(h.rufname)})` : '';
const geburtstag = h.geburtsdatum ? _fmtDate(h.geburtsdatum) : null;
@ -314,7 +314,7 @@ window.Page_zuchthunde = (() => {
return `
<div class="zh-card" id="zh-card-${h.id}">
<div class="zh-card-header">
<div style="flex:1;min-width:0">
<div class="flex-1-min">
<div class="zh-card-title">
${_genderIcon(h.geschlecht)}
${nameLabel}${_esc(rufname)}
@ -326,7 +326,7 @@ window.Page_zuchthunde = (() => {
${h.chip_nr ? `${UI.icon('barcode')} ${_esc(h.chip_nr)}&nbsp;&nbsp;` : ''}
${h.zuchtbuchnummer ? `${UI.icon('book-open')} ${_esc(h.zuchtbuchnummer)}&nbsp;&nbsp;` : ''}
</div>
${eltern ? `<div class="zh-card-meta" style="font-size:var(--text-xs);color:var(--c-text-secondary)">${eltern}</div>` : ''}
${eltern ? `<div class="zh-card-meta text-xs-secondary">${eltern}</div>` : ''}
</div>
<div class="zh-card-actions">
<button class="btn btn-ghost btn-sm zh-pedigree-btn" data-id="${h.id}"
@ -346,7 +346,7 @@ window.Page_zuchthunde = (() => {
${UI.icon('pencil-simple')}
</button>
<button class="btn btn-ghost btn-sm zh-delete-btn" data-id="${h.id}"
title="Löschen" style="color:var(--c-danger)">
title="Löschen" class="text-danger">
${UI.icon('trash')}
</button>
</div>
@ -364,7 +364,7 @@ window.Page_zuchthunde = (() => {
</button>
</div>
<div class="zh-section-wrap" id="zh-section-${h.id}" style="display:none"></div>
<div class="zh-section-wrap" id="zh-section-${h.id}" class="hidden"></div>
</div>`;
}
@ -432,9 +432,9 @@ window.Page_zuchthunde = (() => {
${t.labor ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${_esc(t.labor)}</span>` : ''}
</div>
<button class="btn btn-ghost btn-xs zh-health-del-btn" data-tid="${t.id}" title="Löschen"
style="color:var(--c-danger)">${UI.icon('trash')}</button>
class="text-danger">${UI.icon('trash')}</button>
</div>`).join('')
: `<p style="color:var(--c-text-muted);font-size:var(--text-sm)">Noch keine Gesundheitstests eingetragen.</p>`;
: `<p class="text-sm-muted">Noch keine Gesundheitstests eingetragen.</p>`;
wrap.innerHTML = `
<div class="zh-section-inner">
@ -495,9 +495,9 @@ window.Page_zuchthunde = (() => {
${t.labor ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${_esc(t.labor)}</span>` : ''}
</div>
<button class="btn btn-ghost btn-xs zh-genetic-del-btn" data-tid="${t.id}" title="Löschen"
style="color:var(--c-danger)">${UI.icon('trash')}</button>
class="text-danger">${UI.icon('trash')}</button>
</div>`).join('')
: `<p style="color:var(--c-text-muted);font-size:var(--text-sm)">Noch keine Gentests eingetragen.</p>`;
: `<p class="text-sm-muted">Noch keine Gentests eingetragen.</p>`;
wrap.innerHTML = `
<div class="zh-section-inner">
@ -560,9 +560,9 @@ window.Page_zuchthunde = (() => {
${t.formwert ? `<span class="zh-badge" style="background:#3B82F6">${_esc(t.formwert)}</span>` : ''}
</div>
<button class="btn btn-ghost btn-xs zh-title-del-btn" data-tid="${t.id}" title="Löschen"
style="color:var(--c-danger)">${UI.icon('trash')}</button>
class="text-danger">${UI.icon('trash')}</button>
</div>`).join('')
: `<p style="color:var(--c-text-muted);font-size:var(--text-sm)">Noch keine Titel eingetragen.</p>`;
: `<p class="text-sm-muted">Noch keine Titel eingetragen.</p>`;
wrap.innerHTML = `
<div class="zh-section-inner">
@ -626,9 +626,9 @@ window.Page_zuchthunde = (() => {
const body = `
<form id="zh-hund-form" autocomplete="off">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Vollständiger Name <span style="color:var(--c-danger)">*</span></label>
<label class="form-label">Vollständiger Name <span class="text-danger">*</span></label>
<input class="form-control" type="text" name="name" required
value="${_esc(v.name || '')}" placeholder="z. B. Banyaro's Black Diamond">
</div>
@ -639,7 +639,7 @@ window.Page_zuchthunde = (() => {
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Geschlecht</label>
<select class="form-control" name="geschlecht">
@ -655,7 +655,7 @@ window.Page_zuchthunde = (() => {
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Sterbedatum</label>
<input class="form-control" type="date" name="sterbedatum"
@ -668,7 +668,7 @@ window.Page_zuchthunde = (() => {
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Chip-Nr.</label>
<input class="form-control" type="text" name="chip_nr"
@ -687,7 +687,7 @@ window.Page_zuchthunde = (() => {
value="${_esc(v.zuchtbuchnummer || '')}" placeholder="z. B. SZ 123456">
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Vater</label>
<select class="form-control" name="vater_id">${vaterOptions}</select>
@ -698,7 +698,7 @@ window.Page_zuchthunde = (() => {
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Züchter-Name</label>
<input class="form-control" type="text" name="zuechter_name"
@ -712,7 +712,7 @@ window.Page_zuchthunde = (() => {
</div>
<div class="form-group">
<label class="form-label">Notiz <span style="color:var(--c-text-secondary)">(intern)</span></label>
<label class="form-label">Notiz <span class="text-secondary">(intern)</span></label>
<textarea class="form-control" name="notiz" rows="2"
placeholder="Interne Anmerkungen…">${_esc(v.notiz || '')}</textarea>
</div>
@ -807,9 +807,9 @@ window.Page_zuchthunde = (() => {
const body = `
<form id="zh-health-form" autocomplete="off">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Test-Typ <span style="color:var(--c-danger)">*</span></label>
<label class="form-label">Test-Typ <span class="text-danger">*</span></label>
<select class="form-control" name="test_typ" id="zh-health-typ" required>
<option value="HD">HD (Hüftgelenksdysplasie)</option>
<option value="ED">ED (Ellbogendysplasie)</option>
@ -822,13 +822,13 @@ window.Page_zuchthunde = (() => {
</select>
</div>
<div class="form-group" id="zh-health-name-wrap">
<label class="form-label">Test-Name <span style="color:var(--c-text-secondary)">(bei Sonstiges)</span></label>
<label class="form-label">Test-Name <span class="text-secondary">(bei Sonstiges)</span></label>
<input class="form-control" type="text" name="test_name" placeholder="Bezeichnung des Tests">
</div>
</div>
<div class="form-group">
<label class="form-label">Ergebnis <span style="color:var(--c-danger)">*</span></label>
<label class="form-label">Ergebnis <span class="text-danger">*</span></label>
<input class="form-control" type="text" name="ergebnis" required
id="zh-health-ergebnis" placeholder="z. B. A1, A2, B1 …">
<small class="form-hint" id="zh-health-hint" style="color:var(--c-text-secondary);font-size:var(--text-xs)">
@ -836,7 +836,7 @@ window.Page_zuchthunde = (() => {
</small>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Untersuchungsdatum</label>
<input class="form-control" type="date" name="untersuch_am" value="${today}">
@ -847,7 +847,7 @@ window.Page_zuchthunde = (() => {
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Untersucher / Tierarzt</label>
<input class="form-control" type="text" name="untersucher" placeholder="Dr. Müller">
@ -931,9 +931,9 @@ window.Page_zuchthunde = (() => {
const body = `
<form id="zh-genetic-form" autocomplete="off">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Marker / Gen <span style="color:var(--c-danger)">*</span></label>
<label class="form-label">Marker / Gen <span class="text-danger">*</span></label>
<select class="form-control" name="marker_name" required>
<option value="MDR1">MDR1 (Multi-Drug Resistance)</option>
<option value="PRA-prcd">PRA-prcd (Progressive Retinaatrophie)</option>
@ -947,7 +947,7 @@ window.Page_zuchthunde = (() => {
</select>
</div>
<div class="form-group">
<label class="form-label">Ergebnis <span style="color:var(--c-danger)">*</span></label>
<label class="form-label">Ergebnis <span class="text-danger">*</span></label>
<select class="form-control" name="ergebnis_klasse" required>
<option value="clear">clear (frei)</option>
<option value="carrier">carrier (Träger)</option>
@ -956,7 +956,7 @@ window.Page_zuchthunde = (() => {
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Testdatum</label>
<input class="form-control" type="date" name="getestet_am" value="${today}">
@ -1020,9 +1020,9 @@ window.Page_zuchthunde = (() => {
const body = `
<form id="zh-title-form" autocomplete="off">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Titel-Typ <span style="color:var(--c-danger)">*</span></label>
<label class="form-label">Titel-Typ <span class="text-danger">*</span></label>
<select class="form-control" name="titel_typ" required>
<option value="Ausstellung">Ausstellung</option>
<option value="Arbeit">Arbeit</option>
@ -1033,13 +1033,13 @@ window.Page_zuchthunde = (() => {
</select>
</div>
<div class="form-group">
<label class="form-label">Titel-Name <span style="color:var(--c-danger)">*</span></label>
<label class="form-label">Titel-Name <span class="text-danger">*</span></label>
<input class="form-control" type="text" name="titel_name" required
placeholder="z. B. CAC, CACIB, BOB, IPO 1, BH">
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Datum</label>
<input class="form-control" type="date" name="verliehen_am" value="${today}">
@ -1056,7 +1056,7 @@ window.Page_zuchthunde = (() => {
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)">
<div class="grid-2">
<div class="form-group">
<label class="form-label">Ort</label>
<input class="form-control" type="text" name="ort" placeholder="Stadt / Veranstaltungsort">
@ -1208,7 +1208,7 @@ window.Page_zuchthunde = (() => {
const genStr = genInfo.length ? ` <span style="color:var(--c-text-secondary);font-size:var(--text-xs)">(${genInfo.join(' / ')})</span>` : '';
return `<li style="padding:var(--space-1) 0">${_esc(v.name || '—')}${genStr}</li>`;
}).join('')
: `<li style="color:var(--c-text-muted)">Keine gemeinsamen Vorfahren gefunden.</li>`;
: `<li class="text-muted">Keine gemeinsamen Vorfahren gefunden.</li>`;
const welfare = result.welfare;
let welfareHTML = '';
@ -1220,13 +1220,13 @@ window.Page_zuchthunde = (() => {
const wIssueHTML = (welfare.issues || []).map(i => `
<div style="display:flex;gap:8px;padding:6px 0;border-bottom:1px solid rgba(0,0,0,.06)">
<span style="color:${wColor};flex-shrink:0">${UI.icon('warning')}</span>
<span style="font-size:var(--text-sm)">${_esc(i.text)}</span>
<span class="text-sm">${_esc(i.text)}</span>
</div>`).join('');
const wOkHTML = (welfare.ok_points || []).map(p => `
<div style="display:flex;gap:8px;padding:4px 0">
<span style="color:#16a34a;flex-shrink:0">${UI.icon('check')}</span>
<span style="font-size:var(--text-sm);color:var(--c-text-secondary)">${_esc(p)}</span>
<span class="text-sm-secondary">${_esc(p)}</span>
</div>`).join('');
welfareHTML = `
@ -1278,7 +1278,7 @@ window.Page_zuchthunde = (() => {
const footer = `
<div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%">
${kiPaarungBtn}
<div style="display:flex;gap:var(--space-2)">
<div class="flex-gap-2">
<button type="button" class="btn btn-secondary flex-1" id="zhresult-back">
${UI.icon('arrow-left')} Zurück
</button>
@ -1314,7 +1314,7 @@ window.Page_zuchthunde = (() => {
} catch (err) {
UI.modal.open({
title: `${UI.icon('sparkle')} KI-Hunde-Beschreibung`,
body: `<p style="color:var(--c-danger)">${_esc(err.message || 'Fehler beim Generieren.')}</p>`,
body: `<p class="text-danger">${_esc(err.message || 'Fehler beim Generieren.')}</p>`,
footer: `<button class="btn btn-secondary" data-modal-close>Schließen</button>`,
});
return;
@ -1414,7 +1414,7 @@ window.Page_zuchthunde = (() => {
} catch (err) {
UI.modal.open({
title: `${UI.icon('chart-bar')} KI-Jahresbericht`,
body: `<p style="color:var(--c-danger)">${_esc(err.message || 'Fehler beim Generieren.')}</p>`,
body: `<p class="text-danger">${_esc(err.message || 'Fehler beim Generieren.')}</p>`,
footer: `<button class="btn btn-secondary" data-modal-close>Schließen</button>`,
});
return;
@ -1484,7 +1484,7 @@ window.Page_zuchthunde = (() => {
<div style="font-weight:var(--weight-semibold);font-size:var(--text-sm)">
Jahresbericht ${b.jahr}
</div>
<div style="font-size:var(--text-xs);color:var(--c-text-muted)">
<div class="text-xs-muted">
${new Date(b.created_at).toLocaleDateString('de', {day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'})}
</div>
</div>
@ -1532,7 +1532,7 @@ window.Page_zuchthunde = (() => {
} catch (err) {
UI.modal.open({
title: `${UI.icon('sparkle')} KI-Paarungsanalyse`,
body: `<p style="color:var(--c-danger)">${_esc(err.message || 'Fehler beim Generieren.')}</p>`,
body: `<p class="text-danger">${_esc(err.message || 'Fehler beim Generieren.')}</p>`,
footer: `<button class="btn btn-secondary" data-modal-close>Schließen</button>`,
});
return;
@ -1719,11 +1719,11 @@ window.Page_zuchthunde = (() => {
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);margin:0 0 var(--space-3)">
Diese Fotos erscheinen im öffentlichen Züchterprofil. Das primäre Foto wird als <strong>Logo</strong> im Hero angezeigt.
</p>
<div id="${galleryId}" style="margin-bottom:var(--space-4)">
<p style="color:var(--c-text-muted);font-size:var(--text-sm)">Lädt</p>
<div id="${galleryId}" class="mb-4">
<p class="text-sm-muted">Lädt</p>
</div>
<hr style="margin:var(--space-3) 0;border:none;border-top:1px solid var(--c-border)">
<form id="bp-upload-form" style="display:flex;flex-direction:column;gap:var(--space-2)">
<form id="bp-upload-form" class="flex-col-gap-2">
<label style="font-size:var(--text-sm);font-weight:600">${UI.icon('upload-simple')} Foto hochladen</label>
<input class="form-control" type="file" name="file" accept="image/*" required>
<input class="form-control" type="text" name="caption" placeholder="Bildunterschrift (optional)">
@ -1739,7 +1739,7 @@ window.Page_zuchthunde = (() => {
try {
const photos = await API.breederPhotos.list('breeder', breederId);
if (!photos.length) {
el.innerHTML = `<p style="color:var(--c-text-muted);font-size:var(--text-sm)">Noch keine Fotos — lade das erste hoch.</p>`;
el.innerHTML = `<p class="text-sm-muted">Noch keine Fotos — lade das erste hoch.</p>`;
return;
}
el.innerHTML = `
@ -1805,7 +1805,7 @@ window.Page_zuchthunde = (() => {
});
} catch (err) {
const el = document.getElementById(galleryId);
if (el) el.innerHTML = `<p style="color:var(--c-danger)">${_esc(err.message || 'Fehler')}</p>`;
if (el) el.innerHTML = `<p class="text-danger">${_esc(err.message || 'Fehler')}</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=1101"></script>
<script src="/js/landing-init.js?v=1102"></script>
<title>Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz</title>
<meta name="description" content="Ban Yaro: Die kostenlose All-in-One Hunde-App für DACH. Tagebuch, Giftköder-Alarm, Training mit KI, Forum, Wurfbörse, Stammbaum, Inzucht-Check — DSGVO-konform, offline-fähig, ohne App Store.">
<meta name="keywords" content="Hunde App, Hunde Community, Wurfbörse, Züchter, Welpen kaufen, Stammbaum Hund, Inzuchtkoeffizient, Hundezucht, Impfpass Hund, Giftköder Alarm, Gassi Community, Hundetraining App, Hunde Forum, Hunde KI, Hundefilm Datenbank, Welpen Marktplatz">

View file

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