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:
parent
279f76714e
commit
459cd425f2
54 changed files with 1809 additions and 956 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
1101
|
||||
1102
|
||||
|
|
@ -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
|
||||
------------------------------------------------------------ */
|
||||
|
|
|
|||
65
backend/static/css/utilities.css
Normal file
65
backend/static/css/utilities.css
Normal 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);
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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>
|
||||
|
|
|
|||
322
backend/static/js/pages/breeder-editor.js
Normal file
322
backend/static/js/pages/breeder-editor.js
Normal 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 1–2 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 1–2 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 =>
|
||||
({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
}
|
||||
|
||||
return { init, refresh, onDogChange };
|
||||
|
||||
})();
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)} · ${f.mahlzeiten} Mahlzeit${f.mahlzeiten !== 1 ? 'en' : ''}
|
||||
${f.status !== 'neu' ? `· <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 ? ` · ${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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>`,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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>`}
|
||||
|
|
|
|||
|
|
@ -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)'};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>` : ''}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
278
backend/static/js/pages/partner-profil.js
Normal file
278
backend/static/js/pages/partner-profil.js
Normal 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 1–2 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 =>
|
||||
({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
}
|
||||
|
||||
return { init, refresh, onDogChange };
|
||||
|
||||
})();
|
||||
153
backend/static/js/pages/partner.js
Normal file
153
backend/static/js/pages/partner.js
Normal 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 =>
|
||||
({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
}
|
||||
|
||||
return { init, refresh, onDogChange };
|
||||
|
||||
})();
|
||||
|
|
@ -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 -->
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
· <span style="color:var(--c-text-muted)">Original: ${origKm.toFixed(2)} km · ${origMin} min (bleibt angerechnet)</span>`;
|
||||
· <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>
|
||||
|
|
|
|||
|
|
@ -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 & 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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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!';
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
· ${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>
|
||||
`,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
·
|
||||
<a href="/#datenschutz" style="color:var(--c-text-muted)">Datenschutz</a>
|
||||
<a href="/#datenschutz" class="text-muted">Datenschutz</a>
|
||||
·
|
||||
<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>'],
|
||||
|
|
|
|||
|
|
@ -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)">/ 10</span>
|
||||
<span class="text-xs-secondary">/ 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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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` : '—');
|
||||
|
||||
const kinderLabel = rasse.kinder_geeignet === true
|
||||
? `<span style="color:var(--c-success)">✓ Ja</span>`
|
||||
? `<span class="text-success">✓ Ja</span>`
|
||||
: rasse.kinder_geeignet === false
|
||||
? `<span style="color:var(--c-warning)">⚡ Bedingt</span>`
|
||||
: '—';
|
||||
|
||||
const wohnungLabel = rasse.wohnung_geeignet
|
||||
? `<span style="color:var(--c-success)">✓ Ja</span>`
|
||||
: `<span style="color:var(--c-text-secondary)">✗ Besser Garten</span>`;
|
||||
? `<span class="text-success">✓ Ja</span>`
|
||||
: `<span class="text-secondary">✗ Besser Garten</span>`;
|
||||
|
||||
const rows = [
|
||||
['Größe', _groesseLabel(rasse.groesse) || '—'],
|
||||
|
|
@ -514,7 +514,7 @@ window.Page_wiki = (() => {
|
|||
<span id="wiki-hat-count">🐕 <strong>${hatCount}</strong> haben diesen Hund</span>
|
||||
<span id="wiki-will-count">❤️ <strong>${willCount}</strong> mö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)"> „${_esc(z.zwingername)}“</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
|
||||
|
|
|
|||
|
|
@ -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(' · ')}
|
||||
</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 ? ` · ${UI.icon('map-pin')} ${_esc(t.ort)}` : ''}
|
||||
${t.richter ? ` · ${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('');
|
||||
|
||||
|
|
|
|||
|
|
@ -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)} ` : ''}
|
||||
${h.zuchtbuchnummer ? `${UI.icon('book-open')} ${_esc(h.zuchtbuchnummer)} ` : ''}
|
||||
</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>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue