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

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

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

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

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

View file

@ -1 +1 @@
1101 1102

View file

@ -235,6 +235,45 @@
color: var(--c-primary); 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 4. BY-SECTION-LABEL + BY-TOOLBAR weitere gemeinsame Elemente
------------------------------------------------------------ */ ------------------------------------------------------------ */

View file

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

View file

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

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung. 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 const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt
window.APP_VER = APP_VER; // global verfügbar für andere Module (z.B. offline-indicator) window.APP_VER = APP_VER; // global verfügbar für andere Module (z.B. offline-indicator)
window.APP_VERSION = APP_VERSION; window.APP_VERSION = APP_VERSION;

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -75,7 +75,7 @@ window.Page_breeder = (() => {
padding:var(--space-6) var(--space-4) var(--space-8);color:white;position:relative"> padding:var(--space-6) var(--space-4) var(--space-8);color:white;position:relative">
<div style="max-width:640px;margin:0 auto"> <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="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"> <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 ${UI.icon('seal-check')} Verifizierter Züchter
</p> </p>
@ -157,7 +157,7 @@ window.Page_breeder = (() => {
display:flex;align-items:center;gap:var(--space-2)"> display:flex;align-items:center;gap:var(--space-2)">
${UI.icon('baby')} Aktuelle Würfe ${UI.icon('baby')} Aktuelle Würfe
</h2> </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('')} ${p.wuerfe.map(w => _wurfCard(w)).join('')}
</div> </div>
</div>` : ''} </div>` : ''}
@ -201,7 +201,7 @@ window.Page_breeder = (() => {
<!-- Fotos / Gallery --> <!-- Fotos / Gallery -->
${p.fotos?.length ? ` ${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; <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)"> display:flex;align-items:center;gap:var(--space-2)">
${UI.icon('images')} Galerie ${UI.icon('images')} Galerie
@ -226,7 +226,7 @@ window.Page_breeder = (() => {
</div> </div>
</div>` : ''} </div>` : ''}
<div id="breeder-photos-section" style="display:none"></div> <div id="breeder-photos-section" class="hidden"></div>
</div>`; </div>`;
@ -262,7 +262,7 @@ window.Page_breeder = (() => {
).join(''); ).join('');
const genBadge = h.gentests_total > 0 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 ${h.gentests_clear}/${h.gentests_total} Gentests frei
</span>` </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); <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)"> padding:var(--space-3);display:flex;flex-direction:column;gap:var(--space-2)">
<div style="display:flex;align-items:center;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> <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>` : ''} ${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>` : ''} ${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 => ` ${stats.map(r => `
<div style="display:flex;align-items:center;gap:6px;font-size:var(--text-sm)"> <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="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; <span style="background:var(--c-border);border-radius:999px;height:6px;
width:${Math.round(r.cnt/total*80)+16}px;display:inline-block"></span> width:${Math.round(r.cnt/total*80)+16}px;display:inline-block"></span>
</div>`).join('')} </div>`).join('')}
@ -377,7 +377,7 @@ window.Page_breeder = (() => {
const photos = await API.breederPhotos.list('breeder', breederId); const photos = await API.breederPhotos.list('breeder', breederId);
if (!photos?.length) return; if (!photos?.length) return;
section.innerHTML = ` 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"> <h2 style="margin:0 0 var(--space-3);font-size:var(--text-base);font-weight:700">
${UI.icon('images')} Fotos ${UI.icon('images')} Fotos
</h2> </h2>

View file

@ -178,7 +178,7 @@ window.Page_chat = (() => {
</button>`} </button>`}
<div style="position:relative;flex-shrink:0"> <div style="position:relative;flex-shrink:0">
<div class="chat-conv-avatar" id="chat-partner-av" style="width:32px;height:32px;font-size:var(--text-sm)">?</div> <div class="chat-conv-avatar" id="chat-partner-av" style="width:32px;height:32px;font-size:var(--text-sm)">?</div>
<span class="online-dot chat-avatar-dot" id="chat-partner-dot" style="display:none"></span> <span class="online-dot chat-avatar-dot" id="chat-partner-dot" class="hidden"></span>
</div> </div>
<span class="chat-thread-partner" id="chat-partner-name"></span> <span class="chat-thread-partner" id="chat-partner-name"></span>
</div> </div>
@ -188,7 +188,7 @@ window.Page_chat = (() => {
</div> </div>
</div> </div>
<div class="chat-input-bar"> <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)"> onchange="Page_chat._onPhotoSelected(this)">
<button class="chat-photo-btn" onclick="document.getElementById('chat-photo-input').click()" title="Foto senden"> <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> <svg class="ph-icon"><use href="/icons/phosphor.svg#camera"></use></svg>

View file

@ -212,7 +212,7 @@ window.Page_diary = (() => {
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#download-simple"></use></svg> <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#download-simple"></use></svg>
</button> </button>
</div> </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-view-content">
<div id="diary-list"></div> <div id="diary-list"></div>
</div> </div>
@ -295,7 +295,7 @@ window.Page_diary = (() => {
`; `;
card.innerHTML = ` card.innerHTML = `
<div style="font-size:1.8rem;flex-shrink:0;line-height:1">🐾</div> <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); <div style="font-size:var(--text-xs);font-weight:var(--weight-semibold);
color:var(--c-primary-dark);text-transform:uppercase; color:var(--c-primary-dark);text-transform:uppercase;
letter-spacing:.06em;margin-bottom:var(--space-1)"> letter-spacing:.06em;margin-bottom:var(--space-1)">
@ -963,7 +963,7 @@ window.Page_diary = (() => {
// Hunde-Chips (bei mehreren Hunden) // Hunde-Chips (bei mehreren Hunden)
const dogsHtml = dogIds.length > 1 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 => { ${dogIds.map(did => {
const dog = _appState.dogs.find(d => d.id === did); const dog = _appState.dogs.find(d => d.id === did);
return dog ? `<div class="diary-dog-chip"> return dog ? `<div class="diary-dog-chip">
@ -1279,7 +1279,7 @@ window.Page_diary = (() => {
value="${entry?.datum || today}" required> value="${entry?.datum || today}" required>
</div> </div>
<div class="form-group"> <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" <input class="form-control" type="text" name="titel"
value="${UI.escape(entry?.titel || '')}" placeholder="z.B. Erster Schultag"> value="${UI.escape(entry?.titel || '')}" placeholder="z.B. Erster Schultag">
</div> </div>
@ -1293,10 +1293,10 @@ window.Page_diary = (() => {
<div id="diary-existing-media"></div> <div id="diary-existing-media"></div>
<!-- Neue Medien: Vorschau-Grid --> <!-- 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 --> <!-- 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) --> <!-- 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"> <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> </label>
</div> </div>
<div class="form-group" id="diary-location-group"> <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) --> <!-- Karte (Lesemodus, Edit per Button aktivierbar) -->
<div style="position:relative"> <div style="position:relative">
@ -1318,7 +1318,7 @@ window.Page_diary = (() => {
</div> </div>
<!-- POI-Name + Aktionen --> <!-- 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 id="diary-location-chip-wrap" style="${entry?.location_name ? '' : 'display:none'}">
<div class="diary-location-chip"> <div class="diary-location-chip">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-pin"></use></svg> <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#map-pin"></use></svg>
@ -1341,7 +1341,7 @@ window.Page_diary = (() => {
${dogPickerHtml} ${dogPickerHtml}
<div class="form-group" style="margin-top:var(--space-5)"> <div class="form-group" style="margin-top:var(--space-5)">
<input type="checkbox" name="is_milestone" id="diary-milestone-cb" <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" <button type="button" id="diary-milestone-btn"
class="diary-milestone-toggle${entry?.is_milestone ? ' diary-milestone-toggle--active' : ''}"> 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> <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#trophy"></use></svg>
@ -1353,10 +1353,10 @@ window.Page_diary = (() => {
const footer = ` const footer = `
<div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%"> <div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%">
<button 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'} ${isEdit ? 'Speichern' : 'Erstellen'}
</button> </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>` : ''} ${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> <button type="button" class="btn btn-secondary flex-1" id="diary-form-cancel">Abbrechen</button>
</div> </div>
@ -1843,32 +1843,32 @@ window.Page_diary = (() => {
<strong>${UI.escape(_appState.activeDog?.name || 'deinem Hund')}</strong>. <strong>${UI.escape(_appState.activeDog?.name || 'deinem Hund')}</strong>.
</p> </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"> <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"> <div class="import-format-icon">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note"></use></svg> <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#note"></use></svg>
</div> </div>
<div> <div>
<div style="font-weight:var(--weight-semibold)">Synology NoteStation</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> </div>
</label> </label>
<label class="import-format-card" id="fmt-csv"> <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"> <div class="import-format-icon">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#file-csv"></use></svg> <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#file-csv"></use></svg>
</div> </div>
<div> <div>
<div style="font-weight:var(--weight-semibold)">CSV / Excel</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> </div>
</label> </label>
</div> </div>
<div style="margin-top:var(--space-4)"> <div class="mt-4">
<label class="form-label">Datei auswählen</label> <label class="form-label">Datei auswählen</label>
<input type="file" class="form-control" id="import-file-input" <input type="file" class="form-control" id="import-file-input"
accept=".nsx,.csv" style="cursor:pointer"> accept=".nsx,.csv" style="cursor:pointer">
@ -1917,7 +1917,7 @@ window.Page_diary = (() => {
: await API.importData.csv(dogId, file); : await API.importData.csv(dogId, file);
const errHtml = res.errors?.length 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>` <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); <div style="background:var(--c-success-subtle);border-radius:var(--radius-md);
padding:var(--space-3) var(--space-4);color:var(--c-success)"> padding:var(--space-3) var(--space-4);color:var(--c-success)">
<strong>${res.imported} Einträge importiert</strong> <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} ${errHtml}
</div>`; </div>`;
resultEl.style.display = 'block'; resultEl.style.display = 'block';

View file

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

View file

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

View file

@ -253,13 +253,13 @@ window.Page_erste_hilfe = (() => {
</div> </div>
${KATEGORIEN.map(k => ` ${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('')} ${k.eintraege.map((e, i) => _renderEintrag(e, k.id, i, k.color)).join('')}
</div> </div>
`).join('')} `).join('')}
<div style="margin-top:var(--space-6);padding:var(--space-4);background:var(--c-surface-2);border-radius:var(--radius-md);font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.6"> <div style="margin-top:var(--space-6);padding:var(--space-4);background:var(--c-surface-2);border-radius:var(--radius-md);font-size:var(--text-sm);color:var(--c-text-secondary);line-height:1.6">
<svg class="ph-icon" aria-hidden="true" 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. Diese Inhalte ersetzen keinen Tierarztbesuch. Im Zweifel immer sofort zum Tierarzt oder den tierärztlichen Notdienst anrufen.
</div> </div>
</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)"> <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} ${g.flag} · ${g.land}
</div> </div>
<div style="display:flex;flex-direction:column;gap:var(--space-2)"> <div class="flex-col-gap-2">
${g.eintraege.map(renderEintrag).join('')} ${g.eintraege.map(renderEintrag).join('')}
</div> </div>
</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> <svg class="ph-icon" style="width:20px;height:20px" aria-hidden="true"><use href="/icons/phosphor.svg#siren"></use></svg>
Tiergiftzentralen jetzt anrufen Tiergiftzentralen jetzt anrufen
</div> </div>
<div style="display:flex;flex-direction:column;gap:var(--space-3)"> <div class="flex-col-gap-3">
${gruppen} ${gruppen}
</div> </div>
<p style="margin-top:var(--space-3);font-size:var(--text-xs);color:rgba(255,255,255,0.8)"> <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 ` return `
<div class="card" style="padding:0;overflow:hidden;margin-bottom:var(--space-4)"> <div class="card" style="padding:0;overflow:hidden;margin-bottom:var(--space-4)">
<div style="padding:var(--space-3) var(--space-4);background:var(--c-surface-2);font-weight:var(--weight-semibold);font-size:var(--text-sm);display:flex;align-items:center;gap:var(--space-2)"> <div style="padding:var(--space-3) var(--space-4);background:var(--c-surface-2);font-weight:var(--weight-semibold);font-size:var(--text-sm);display:flex;align-items:center;gap:var(--space-2)">
<svg class="ph-icon" aria-hidden="true" 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 Schnellübersicht: Was tun bei
</div> </div>
<div style="overflow-x:auto"> <div style="overflow-x:auto">

View file

@ -75,7 +75,7 @@ window.Page_events = (() => {
<button class="events-view-btn active" data-ev-view="liste">${UI.icon('list')} Liste</button> <button class="events-view-btn 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> <button class="events-view-btn" data-ev-view="karte">${UI.icon('map-trifold')} Karte</button>
</div> </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>` : ''} ${_state.user ? `<button class="btn btn-primary btn-sm" id="ev-new-btn">${UI.icon('plus')} Event</button>` : ''}
</div> </div>
@ -102,7 +102,7 @@ window.Page_events = (() => {
</div> </div>
<div class="events-list" id="ev-list"></div> <div class="events-list" id="ev-list"></div>
<div class="events-map" id="ev-map" style="display:none"></div> <div class="events-map" id="ev-map" class="hidden"></div>
`; `;
_container.addEventListener('click', _onClick); _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}" ${_state.user ? `<button class="btn-icon ev-note-btn" data-ev-note-id="${ev.id}"
data-ev-note-label="${UI.escape(ev.titel + ' ' + ev.datum)}" data-ev-note-label="${UI.escape(ev.titel + ' ' + ev.datum)}"
data-ev-note-ort="${UI.escape(ev.ort_name || '')}" data-ev-note-ort="${UI.escape(ev.ort_name || '')}"
title="Notiz" style="color:var(--c-text-muted)" onclick="event.stopPropagation()"> title="Notiz" class="text-muted" onclick="event.stopPropagation()">
${_icon('note-pencil')}</button>` : ''} ${_icon('note-pencil')}</button>` : ''}
</div> </div>
</div> </div>
@ -496,7 +496,7 @@ window.Page_events = (() => {
<label class="form-label">GPS-Position</label> <label class="form-label">GPS-Position</label>
<div id="ev-location-picker"></div> <div id="ev-location-picker"></div>
</div> </div>
<div class="form-group" style="margin-top:var(--space-3)"> <div class="form-group mt-3">
<label class="form-label">Beschreibung</label> <label class="form-label">Beschreibung</label>
<textarea class="form-control" name="beschreibung" rows="3">${ev?.beschreibung || ''}</textarea> <textarea class="form-control" name="beschreibung" rows="3">${ev?.beschreibung || ''}</textarea>
</div> </div>
@ -509,10 +509,10 @@ window.Page_events = (() => {
const footer = ` const footer = `
<div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%"> <div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%">
<button class="btn btn-primary" type="submit" form="${id}" id="ev-submit-btn" style="width:100%"> <button class="btn btn-primary" type="submit" form="${id}" id="ev-submit-btn" class="w-full">
${isEdit ? 'Speichern' : 'Event erstellen'} ${isEdit ? 'Speichern' : 'Event erstellen'}
</button> </button>
<div 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>` : ''} ${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> <button class="btn btn-secondary flex-1" onclick="UI.modal.close()">Abbrechen</button>
</div> </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; <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"> 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)"> <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> <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"> <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> <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#x"></use></svg>

View file

@ -162,7 +162,7 @@ window.Page_expenses = (() => {
<div class="exp-kachel-icon" style="background:${k.color}20;color:${k.color}"> <div class="exp-kachel-icon" style="background:${k.color}20;color:${k.color}">
${UI.icon(k.icon)} ${UI.icon(k.icon)}
</div> </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> <div class="exp-kachel-label">${k.label}</div>
${monatLine} ${monatLine}
<div class="exp-kachel-add">${UI.icon('plus')} eintragen</div> <div class="exp-kachel-add">${UI.icon('plus')} eintragen</div>
@ -477,13 +477,13 @@ window.Page_expenses = (() => {
</div> </div>
${dogOptions ? ` ${dogOptions ? `
<div class="form-group"> <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"> <select class="form-control" name="dog_id">
<option value="">Kein Hund</option>${dogOptions} <option value="">Kein Hund</option>${dogOptions}
</select> </select>
</div>` : ''} </div>` : ''}
<div class="form-group"> <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" <input class="form-control" type="text" name="notiz"
value="${_esc(r?.notiz || '')}" placeholder="z.B. Haftpflicht Allianz"> value="${_esc(r?.notiz || '')}" placeholder="z.B. Haftpflicht Allianz">
</div> </div>
@ -694,7 +694,7 @@ window.Page_expenses = (() => {
// Kategorie-Kacheln statt Dropdown // Kategorie-Kacheln statt Dropdown
const katKacheln = KATEGORIEN.map(k => ` const katKacheln = KATEGORIEN.map(k => `
<label class="exp-kat-tile${selKat === k.id ? ' exp-kat-tile--sel' : ''}" data-kat="${k.id}"> <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-icon" style="color:${k.color}">${UI.icon(k.icon)}</span>
<span class="exp-kat-tile-label">${k.label}</span> <span class="exp-kat-tile-label">${k.label}</span>
</label>`).join(''); </label>`).join('');
@ -707,7 +707,7 @@ window.Page_expenses = (() => {
<div class="exp-kat-grid">${katKacheln}</div> <div class="exp-kat-grid">${katKacheln}</div>
</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"> <div class="form-group" style="margin-bottom:0">
<label class="form-label">Betrag</label> <label class="form-label">Betrag</label>
<div class="exp-betrag-wrap"> <div class="exp-betrag-wrap">

View file

@ -99,7 +99,7 @@ window.Page_forum = (() => {
<h2 class="forum-header-title">Forum</h2> <h2 class="forum-header-title">Forum</h2>
<div class="forum-header-actions"> <div class="forum-header-actions">
${isMod ? `<button class="btn btn-ghost btn-sm" id="forum-mod-btn" title="Moderationsberichte">${UI.icon('warning')}</button>` : ''} ${isMod ? `<button class="btn btn-ghost btn-sm" id="forum-mod-btn" title="Moderationsberichte">${UI.icon('warning')}</button>` : ''}
<button class="btn btn-ghost btn-sm" id="forum-rules-btn" title="Regeln & Netiquette" 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> <button class="btn btn-primary btn-sm" id="forum-new-btn">${UI.icon('plus')} Neues Thema</button>
</div> </div>
</div> </div>
@ -280,7 +280,7 @@ window.Page_forum = (() => {
<div class="hdm-kandidaten-search"> <div class="hdm-kandidaten-search">
<input type="search" id="hdm-search" class="form-control" <input type="search" id="hdm-search" class="form-control"
placeholder="Name oder Rasse suchen …" autocomplete="off" placeholder="Name oder Rasse suchen …" autocomplete="off"
style="font-size:var(--text-sm)"> class="text-sm">
</div> </div>
<div id="hdm-kandidaten-grid" class="hdm-vote-grid"> <div id="hdm-kandidaten-grid" class="hdm-vote-grid">
${UI.skeleton(3)} ${UI.skeleton(3)}
@ -328,8 +328,8 @@ window.Page_forum = (() => {
<div class="hdm-vote-av">${av}</div> <div class="hdm-vote-av">${av}</div>
<div class="hdm-vote-name">${_esc(dog.name)}</div> <div class="hdm-vote-name">${_esc(dog.name)}</div>
${dog.rasse ? `<div class="hdm-vote-rasse">${_esc(dog.rasse)}</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>` : ''} ${vorname ? `<div class="hdm-vote-besitzer text-xs-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>` : ''} ${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" <button class="btn btn-sm ${isVoted ? 'btn-primary' : 'btn-secondary'} hdm-vote-btn"
data-dog-id="${dog.id}" ${isVoted ? 'disabled' : ''}> data-dog-id="${dog.id}" ${isVoted ? 'disabled' : ''}>
${isVoted ? `${UI.icon('check-circle')} Gewählt` : 'Abstimmen'} ${isVoted ? `${UI.icon('check-circle')} Gewählt` : 'Abstimmen'}
@ -411,8 +411,8 @@ window.Page_forum = (() => {
el.innerHTML = ` el.innerHTML = `
<div style="text-align:center;padding:var(--space-10) var(--space-4)"> <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> <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> <p class="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"> <button class="btn btn-primary mt-4" id="forum-first-btn">
Ersten Beitrag erstellen Ersten Beitrag erstellen
</button> </button>
</div>`; </div>`;
@ -493,7 +493,7 @@ window.Page_forum = (() => {
document.getElementById('forum-main').innerHTML = ` document.getElementById('forum-main').innerHTML = `
<div style="text-align:center;padding:var(--space-8)"> <div style="text-align:center;padding:var(--space-8)">
<div style="font-size:2rem;margin-bottom:var(--space-2)">${UI.icon('magnifying-glass')}</div> <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>`; </div>`;
return; return;
} }
@ -533,7 +533,7 @@ window.Page_forum = (() => {
<button class="btn btn-ghost btn-sm forum-mod-lock" title="${thread.is_locked ? 'Entsperren' : 'Sperren'}"> <button class="btn btn-ghost btn-sm forum-mod-lock" title="${thread.is_locked ? 'Entsperren' : 'Sperren'}">
${UI.icon('lock')} ${thread.is_locked ? 'Entsperren' : 'Sperren'} ${UI.icon('lock')} ${thread.is_locked ? 'Entsperren' : 'Sperren'}
</button> </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>` : ''; </div>` : '';
const _forumMediaHtml = (u) => { const _forumMediaHtml = (u) => {
@ -565,7 +565,7 @@ window.Page_forum = (() => {
<div class="forum-reply-actions"> <div class="forum-reply-actions">
<label class="btn btn-ghost btn-sm forum-upload-label" title="Foto anhängen"> <label class="btn btn-ghost btn-sm forum-upload-label" title="Foto anhängen">
${UI.icon('camera')} ${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> </label>
<div id="forum-reply-previews" class="forum-upload-previews"></div> <div id="forum-reply-previews" class="forum-upload-previews"></div>
</div> </div>
@ -862,7 +862,7 @@ window.Page_forum = (() => {
try { try {
await API.forum.deletePost(postId); await API.forum.deletePost(postId);
if (postEl) { 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'; postEl.className = 'forum-post forum-post--deleted';
} }
const idx = _threads.findIndex(t => t.id === threadId); const idx = _threads.findIndex(t => t.id === threadId);
@ -1011,16 +1011,16 @@ window.Page_forum = (() => {
placeholder="Beschreibe dein Thema ausführlich…" required></textarea> placeholder="Beschreibe dein Thema ausführlich…" required></textarea>
</div> </div>
<div class="form-group"> <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 id="forum-location-picker"></div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label">Fotos / Dateien (max. 5)</label> <label class="form-label">Fotos / Dateien (max. 5)</label>
<div class="forum-upload-area"> <div class="forum-upload-area">
<label class="btn btn-secondary btn-sm" for="forum-thread-files">${UI.icon('image')} Fotos / Video / PDF</label> <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>
<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> </div>
</form> </form>
<p style="font-size:var(--text-xs);color:var(--c-text-muted);margin-top:var(--space-3)"> <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"> ? `<div class="forum-mod-reports">
${reports.map(r => ` ${reports.map(r => `
<div class="forum-mod-report-item" data-id="${r.id}"> <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> <strong>${_esc(r.target_type)} #${r.target_id}</strong>
${_esc(r.grund)} ${_esc(r.grund)}
</div> </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)} von ${_esc(r.melder_name || '?')} · ${_fmtDate(r.created_at)}
</div> </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 ${UI.icon('check')} Erledigt
</button> </button>
</div>`).join('')} </div>`).join('')}
@ -1380,7 +1380,7 @@ window.Page_forum = (() => {
<textarea class="form-control" name="text" rows="5">${_esc(thread.text || '')}</textarea> <textarea class="form-control" name="text" rows="5">${_esc(thread.text || '')}</textarea>
</div> </div>
<div class="form-group"> <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 id="forum-edit-location-picker"></div>
</div> </div>
</form>`, </form>`,

View file

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

View file

@ -82,7 +82,7 @@ window.Page_gruender = (() => {
<p style="font-size:var(--text-xs);color:var(--c-text-muted);margin:0 0 var(--space-4)"> <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? Unsere Partner treten gegeneinander an wer bringt die meisten Gründer?
</p> </p>
<div style="display:flex;flex-direction:column;gap:var(--space-2)"> <div class="flex-col-gap-2">
${d.partners.map((p, i) => { ${d.partners.map((p, i) => {
const medal = i === 0 ? '🥇' : i === 1 ? '🥈' : i === 2 ? '🥉' : `#${i+1}`; 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; 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); padding:var(--space-3);border-radius:var(--radius-md);
background:${i === 0 ? 'linear-gradient(135deg,#fef9c3,#fef3c7)' : 'var(--c-surface-2)'}"> 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="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="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); <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"> 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"> <span style="font-size:var(--text-xs);font-weight:800;color:var(--c-text-muted);min-width:28px">
#${d.total + i + 1} #${d.total + i + 1}
</span> </span>
<span style="font-size:var(--text-sm);color:var(--c-text-muted)">frei</span> <span class="text-sm-muted">frei</span>
</div> </div>
`).join('')} `).join('')}
</div> </div>
</div>` : ` </div>` : `
<div class="by-card" style="padding:var(--space-6);text-align:center"> <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! Noch keine Gründer sei der Erste!
</p> </p>
</div>`} </div>`}

View file

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

View file

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

View file

@ -26,12 +26,12 @@ window.Page_impressum = (() => {
color:var(--c-text);margin:0 0 var(--space-2)">Kontakt</h2> 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)"> <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" 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. Oder nutze das Formular wir antworten in der Regel innerhalb von 24 Stunden.
</p> </p>
<form id="contact-form" style="display:flex;flex-direction:column;gap:var(--space-3)"> <form id="contact-form" class="flex-col-gap-3">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)"> <div class="grid-2">
<div> <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> <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" <input id="cf-name" type="text" required maxlength="100"

View file

@ -44,7 +44,7 @@ window.Page_jobs = (() => {
</div> </div>
<!-- Stellenbeschreibung --> <!-- Stellenbeschreibung -->
<div class="card" style="margin-bottom:var(--space-4)"> <div class="card mb-4">
<div style="padding:var(--space-5)"> <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> <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)"> <div style="display:grid;gap:var(--space-3)">
@ -76,7 +76,7 @@ window.Page_jobs = (() => {
</div> </div>
<!-- Wen wir suchen --> <!-- Wen wir suchen -->
<div class="card" style="margin-bottom:var(--space-4)"> <div class="card mb-4">
<div style="padding:var(--space-5)"> <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> <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)"> <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; const s = statusMap[app.status] || statusMap.pending;
return ` return `
<div class="card" style="padding:var(--space-5);text-align:center"> <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> <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>
<div style="font-weight:700;color:${s.color};font-size:var(--text-lg);margin-bottom:var(--space-2)">${s.text}</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> <label class="form-label">Anhänge (optional)</label>
<input class="form-control" type="file" name="files" id="jobs-files" <input class="form-control" type="file" name="files" id="jobs-files"
multiple accept=".pdf,.jpg,.jpeg,.png,.webp,.mp4,.mov" multiple accept=".pdf,.jpg,.jpeg,.png,.webp,.mp4,.mov"
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)"> <p style="margin:var(--space-1) 0 0;font-size:var(--text-xs);color:var(--c-text-muted)">
Beispiel-Posts, Portfolio, kurzes Video von dir und deinem Hund max. 3 Dateien, je 10 MB. Beispiel-Posts, Portfolio, kurzes Video von dir und deinem Hund max. 3 Dateien, je 10 MB.
PDF, Bild oder Video. PDF, Bild oder Video.
@ -205,7 +205,7 @@ window.Page_jobs = (() => {
padding:var(--space-3);font-size:var(--text-sm);color:var(--c-text-secondary); padding:var(--space-3);font-size:var(--text-sm);color:var(--c-text-secondary);
margin-bottom:var(--space-4)"> margin-bottom:var(--space-4)">
💡 <b>Tipp:</b> Wenn du dich vorher 💡 <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. bekommst du sofort den 14-tägigen Luna-Probezugang.
</div>` : ''} </div>` : ''}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -96,7 +96,7 @@ window.Page_movies = (() => {
<button class="movies-filter-btn${_filter === 'ueberlebt' ? ' movies-filter-btn--active' : ''}" data-filter="ueberlebt"><svg class="ph-icon" aria-hidden="true" style="width:14px;height:14px"><use href="/icons/phosphor.svg#paw-print"></use></svg> Hund überlebt</button> <button class="movies-filter-btn${_filter === '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> <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>
<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 === '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 === '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> <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 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 _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 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 imdb = film.imdb_rating ? `<span class="text-xs-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 streaming = film.streaming ? `<span class="text-xs-muted">${_esc(film.streaming)}</span>` : '';
return ` return `
<div class="movie-card" data-film-id="${_esc(film.id)}"> <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-body">
<div class="movie-card-title">${_esc(film.titel)} <span class="movie-card-year">(${film.jahr})</span></div> <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"> <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>
<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> <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} ${tag}
@ -240,7 +240,7 @@ window.Page_movies = (() => {
</div> </div>
<div class="${bannerClass}" style="margin-bottom:var(--space-4);font-size:var(--text-base)">${bannerText}</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> <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> <strong>Community-Bewertung:</strong>
</div> </div>
<div id="modal-stars-${_esc(film.id)}">${stars}</div> <div id="modal-stars-${_esc(film.id)}">${stars}</div>

View file

@ -499,7 +499,7 @@ window.Page_notes = (() => {
<h3 style="font-size:var(--text-base);font-weight:700;margin:0 0 var(--space-4)">Neue Notiz</h3> <h3 style="font-size:var(--text-base);font-weight:700;margin:0 0 var(--space-4)">Neue Notiz</h3>
<!-- Kategorie-Auswahl --> <!-- 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> <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)"> <div style="display:flex;flex-wrap:wrap;gap:var(--space-2)">
${ERSTELL_RUBRIKEN.map(r => ` ${ERSTELL_RUBRIKEN.map(r => `
@ -514,7 +514,7 @@ window.Page_notes = (() => {
</div> </div>
<!-- Text --> <!-- 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> <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…" <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); 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> box-sizing:border-box"></textarea>
</div> </div>
<div style="display:flex;gap:var(--space-3)"> <div class="flex-gap-3">
<button id="nc-cancel" class="btn btn-ghost" style="flex:1">Abbrechen</button> <button id="nc-cancel" class="btn btn-ghost flex-1">Abbrechen</button>
<button id="nc-save" class="btn btn-primary" style="flex:1">Speichern</button> <button id="nc-save" class="btn btn-primary flex-1">Speichern</button>
</div> </div>
</div>`; </div>`;
}; };
@ -627,7 +627,7 @@ window.Page_notes = (() => {
<div> <div>
<label style="display:block;font-size:var(--text-sm);font-weight:var(--weight-semibold); <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> 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 => ` ${[1,2,3,4,5].map(n => `
<button type="button" class="notes-pfote" data-val="${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); 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> <div>
<label style="display:block;font-size:var(--text-sm);font-weight:var(--weight-semibold); <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> 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]) => ` ${[['🏠','zuhause'],['🌿','natur'],['🌆','stadt']].map(([emoji,val]) => `
<button type="button" class="notes-umgebung" data-val="${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); 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> <div>
<label style="display:block;font-size:var(--text-sm);font-weight:var(--weight-semibold); <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> 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]) => ` ${[['😊','super'],['😐','ok'],['😔','mude']].map(([emoji,val]) => `
<button type="button" class="notes-stimmung" data-val="${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); style="font-size:1.2rem;border:1.5px solid var(--c-border);border-radius:var(--radius-md);

View file

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

View file

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

View file

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

View file

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

View file

@ -281,8 +281,8 @@ window.Page_places = (() => {
</div> </div>
</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.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.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 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.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>` : ''} ${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> <div id="place-rating-${place.id}"></div>
<p style="color:var(--c-text-muted);font-size:0.8rem;margin-top:var(--space-4)"> <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 ? ` 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-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> <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>
<div class="form-group"> <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" <input class="form-control" type="text" name="adresse"
value="${UI.escape(place?.adresse || '')}" placeholder="Musterstraße 1, 12345 Musterstadt"> value="${UI.escape(place?.adresse || '')}" placeholder="Musterstraße 1, 12345 Musterstadt">
</div> </div>
<div class="form-group"> <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" <input class="form-control" type="url" name="website"
value="${UI.escape(place?.website || '')}" placeholder="https://…"> value="${UI.escape(place?.website || '')}" placeholder="https://…">
</div> </div>
<div class="form-group"> <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" <input class="form-control" type="tel" name="telefon"
value="${UI.escape(place?.telefon || '')}" placeholder="+49 89 123456"> value="${UI.escape(place?.telefon || '')}" placeholder="+49 89 123456">
</div> </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 class="form-label">Hundefreundlichkeit</label>
<label style="display:flex;align-items:center;gap:var(--space-2);cursor:pointer"> <label style="display:flex;align-items:center;gap:var(--space-2);cursor:pointer">
<input type="checkbox" name="hund_rein" ${place?.hund_rein ? 'checked' : ''}> <input type="checkbox" name="hund_rein" ${place?.hund_rein ? 'checked' : ''}>
@ -386,10 +386,10 @@ window.Page_places = (() => {
const footer = ` const footer = `
<div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%"> <div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%">
<button 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'} ${isEdit ? 'Speichern' : 'Ort hinzufügen'}
</button> </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>` : ''} ${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> <button type="button" class="btn btn-secondary flex-1" id="place-form-cancel">Abbrechen</button>
</div> </div>

View file

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

View file

@ -73,7 +73,7 @@ window.Page_poison = (() => {
<a href="tel:110" class="btn btn-secondary" style="flex:1;text-align:center;text-decoration:none"> <a href="tel:110" class="btn btn-secondary" style="flex:1;text-align:center;text-decoration:none">
${UI.icon('phone')} <strong>110</strong> Polizei ${UI.icon('phone')} <strong>110</strong> Polizei
</a> </a>
<button class="btn btn-secondary" id="poison-btn-erstehilfe" style="flex:1"> <button class="btn btn-secondary" id="poison-btn-erstehilfe" class="flex-1">
${UI.icon('first-aid')} Erste Hilfe & Tiergift ${UI.icon('first-aid')} Erste Hilfe & Tiergift
</button> </button>
</div> </div>
@ -221,7 +221,7 @@ window.Page_poison = (() => {
${r.beschreibung ? UI.escape(r.beschreibung.slice(0, 80)) + '<br>' : ''} ${r.beschreibung ? UI.escape(r.beschreibung.slice(0, 80)) + '<br>' : ''}
<small>📍 ${distStr} entfernt</small><br> <small>📍 ${distStr} entfernt</small><br>
<small>📅 ${_fmtDate(r.created_at)}</small> <small>📅 ${_fmtDate(r.created_at)}</small>
${r.bestaetigt ? '<br><small><svg class="ph-icon" aria-hidden="true" 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)); marker.on('click', () => _openDetail(r));
@ -276,13 +276,13 @@ window.Page_poison = (() => {
border-left:4px solid ${typ.color}"> border-left:4px solid ${typ.color}">
<div style="display:flex;gap:var(--space-3);align-items:flex-start"> <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="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); <div style="display:flex;align-items:center;gap:var(--space-2);
margin-bottom:var(--space-1);flex-wrap:wrap"> margin-bottom:var(--space-1);flex-wrap:wrap">
<span class="badge" <span class="badge"
style="background:${typ.color};color:#fff">${typ.label}</span> style="background:${typ.color};color:#fff">${typ.label}</span>
${r.bestaetigt ${r.bestaetigt
? '<span class="badge badge-success"><svg class="ph-icon" aria-hidden="true" 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); <span style="margin-left:auto;color:var(--c-text-secondary);
font-size:var(--text-sm);white-space:nowrap"> font-size:var(--text-sm);white-space:nowrap">
@ -295,7 +295,7 @@ window.Page_poison = (() => {
${UI.escape(r.beschreibung.slice(0, 120))}${r.beschreibung.length > 120 ? '…' : ''} ${UI.escape(r.beschreibung.slice(0, 120))}${r.beschreibung.length > 120 ? '…' : ''}
</p>` </p>`
: ''} : ''}
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)"> <div class="text-xs-secondary">
Gemeldet ${_fmtDate(r.created_at)} · Gemeldet ${_fmtDate(r.created_at)} ·
läuft ab ${_fmtDate(r.expires_at)} läuft ab ${_fmtDate(r.expires_at)}
</div> </div>
@ -336,7 +336,7 @@ window.Page_poison = (() => {
<span class="badge" style="background:${typ.color};color:#fff"> <span class="badge" style="background:${typ.color};color:#fff">
${UI.icon(typ.icon)} ${typ.label} ${UI.icon(typ.icon)} ${typ.label}
</span> </span>
${r.bestaetigt ? '<span class="badge badge-success"><svg class="ph-icon" aria-hidden="true" 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> </div>
${r.beschreibung ${r.beschreibung
@ -353,7 +353,7 @@ window.Page_poison = (() => {
<div style="display:flex;gap:var(--space-2);flex-wrap:wrap"> <div style="display:flex;gap:var(--space-2);flex-wrap:wrap">
${!r.bestaetigt && _appState.user && !isOwnEntry ${!r.bestaetigt && _appState.user && !isOwnEntry
? `<button class="btn btn-secondary flex-1" id="detail-confirm"><svg class="ph-icon" aria-hidden="true" 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> <button class="btn btn-secondary flex-1" id="detail-show-map">🗺 Auf Karte</button>
${isOwnEntry || isAdmin ${isOwnEntry || isAdmin
@ -472,7 +472,7 @@ window.Page_poison = (() => {
<div class="form-group"> <div class="form-group">
<label class="form-label"> <label class="form-label">
Beschreibung Beschreibung
<span style="color:var(--c-text-secondary)">(optional)</span> <span class="text-secondary">(optional)</span>
</label> </label>
<textarea class="form-control" name="beschreibung" rows="3" <textarea class="form-control" name="beschreibung" rows="3"
placeholder="z. B. Wurstköder mit Nadeln, liegt beim Eingang Hundeparkplatz, linke Seite…"></textarea> 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"> <div class="form-group">
<label class="form-label"> <label class="form-label">
Foto Foto
<span style="color:var(--c-text-secondary)">(optional)</span> <span class="text-secondary">(optional)</span>
</label> </label>
<input class="form-control" type="file" name="photo" <input class="form-control" type="file" name="photo"
accept="image/*" capture="environment"> accept="image/*" capture="environment">
@ -593,7 +593,7 @@ window.Page_poison = (() => {
title: 'Danke für deine Meldung!', title: 'Danke für deine Meldung!',
body: ` body: `
<div style="text-align:center;padding:var(--space-2) 0 var(--space-4)"> <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> <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> </div>
<p style="color:var(--c-text);font-size:var(--text-base);line-height:1.7;margin:0"> <p style="color:var(--c-text);font-size:var(--text-base);line-height:1.7;margin:0">

View file

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

View file

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

View file

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

View file

@ -176,7 +176,7 @@ window.Page_sitting = (() => {
<div class="sitting-profil-fact"><strong>${s.max_hunde}</strong> Hund${s.max_hunde !== 1 ? 'e' : ''} max.</div> <div class="sitting-profil-fact"><strong>${s.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 class="sitting-profil-fact"><strong>${s.radius_km} km</strong> Umkreis</div>
</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> </div>
`; `;
} }
@ -194,7 +194,7 @@ window.Page_sitting = (() => {
} }
if (myReqs.length) { 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(''); 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 ? ` 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> <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 }); UI.modal.open({ title: 'Sitter-Profil', body, footer });
@ -389,10 +389,10 @@ window.Page_sitting = (() => {
`).join('')} `).join('')}
</div> </div>
<div class="form-group"> <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 id="sit-loc-picker"></div>
</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> <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}"> <input class="form-control" type="number" min="1" max="100" name="radius_km" value="${s?.radius_km ?? 20}">
</div> </div>
@ -407,7 +407,7 @@ window.Page_sitting = (() => {
`; `;
const footer = ` const footer = `
<div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%"> <div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%">
<button class="btn btn-primary" type="submit" form="${id}" id="sit-profil-submit" 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`} ${s ? `${UI.icon('floppy-disk')} Speichern` : `${UI.icon('plus')} Profil erstellen`}
</button> </button>
<button class="btn btn-secondary" onclick="UI.modal.close()">Abbrechen</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; <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"> 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)"> <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> <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"> <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> <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#x"></use></svg>

View file

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

View file

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

View file

@ -637,7 +637,7 @@ window.Page_uebungen = (() => {
</span> </span>
`).join(''); `).join('');
const more = rest > 0 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>`; 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 = ` el.innerHTML = `
<div style="background:var(--c-surface-2);border:1px solid var(--c-border); <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)"> 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>`; </div>`;
const data = await API.training.getRecommendations(dogId).catch(() => null); 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="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 style="display:flex;flex-direction:column;gap:var(--space-2);margin-top:auto;padding-top:var(--space-1)">
<div> <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> <span style="font-size:var(--text-xs);font-weight:700;color:${trendColor};margin-left:4px">${trend}</span>
${prognose} ${prognose}
</div> </div>
@ -742,7 +742,7 @@ window.Page_uebungen = (() => {
</span> </span>
<span id="ueb-help-anchor" style="margin-left:auto"></span> <span id="ueb-help-anchor" style="margin-left:auto"></span>
</div> </div>
<div style="display:flex;flex-direction:column;gap:var(--space-2)"> <div class="flex-col-gap-2">
${cards.join('')} ${cards.join('')}
</div>`; </div>`;
if (_helpHandle) { 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)"> 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> <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-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>`; </div>`;
} else { } else {
el.innerHTML = _renderKiTrainer(); el.innerHTML = _renderKiTrainer();
@ -1176,15 +1176,15 @@ window.Page_uebungen = (() => {
</div> </div>
<!-- Meta --> <!-- Meta -->
<div style="display:flex;flex-wrap:wrap;gap:var(--space-2);margin-bottom:${u.beschreibung ? 'var(--space-3)' : '0'}"> <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> <svg class="ph-icon" style="width:12px;height:12px" aria-hidden="true"><use href="/icons/phosphor.svg#clock"></use></svg>
${_esc(u.dauer)} ${_esc(u.dauer)}
</span> </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> <svg class="ph-icon" style="width:12px;height:12px" aria-hidden="true"><use href="/icons/phosphor.svg#dog"></use></svg>
${_esc(u.alter)} ${_esc(u.alter)}
</span> </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> <svg class="ph-icon" style="width:12px;height:12px" aria-hidden="true"><use href="/icons/phosphor.svg#package"></use></svg>
${_esc(u.material)} ${_esc(u.material)}
</span> </span>
@ -1366,7 +1366,7 @@ window.Page_uebungen = (() => {
<div> <div>
<label style="display:block;font-size:var(--text-sm);font-weight:var(--weight-semibold); <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> 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 => ` ${[1,2,3,4,5].map(n => `
<button type="button" class="ueb-notiz-pfote" data-val="${n}" <button type="button" class="ueb-notiz-pfote" data-val="${n}"
style="font-size:1.4rem;border:1.5px solid var(--c-border); style="font-size:1.4rem;border:1.5px solid var(--c-border);
@ -1382,7 +1382,7 @@ window.Page_uebungen = (() => {
<div> <div>
<label style="display:block;font-size:var(--text-sm);font-weight:var(--weight-semibold); <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> 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]) => ` ${[['🏠','zuhause'],['🌿','natur'],['🌆','stadt']].map(([emoji, val]) => `
<button type="button" class="ueb-notiz-umgebung" data-val="${val}" <button type="button" class="ueb-notiz-umgebung" data-val="${val}"
style="font-size:1.2rem;border:1.5px solid var(--c-border); style="font-size:1.2rem;border:1.5px solid var(--c-border);
@ -1398,7 +1398,7 @@ window.Page_uebungen = (() => {
<div> <div>
<label style="display:block;font-size:var(--text-sm);font-weight:var(--weight-semibold); <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> 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]) => ` ${[['😊','super'],['😐','ok'],['😔','mude']].map(([emoji, val]) => `
<button type="button" class="ueb-notiz-stimmung" data-val="${val}" <button type="button" class="ueb-notiz-stimmung" data-val="${val}"
style="font-size:1.2rem;border:1.5px solid var(--c-border); 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)"> <div style="font-size:var(--text-base);font-weight:var(--weight-semibold);color:var(--c-text)">
KI-Trainer KI-Trainer
</div> </div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary)"> <div class="text-xs-secondary">
Personalisiertes Feedback basierend auf deinen Trainingseinheiten Personalisiertes Feedback basierend auf deinen Trainingseinheiten
</div> </div>
</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> style="font-size:var(--text-sm);color:var(--c-text);line-height:1.7;white-space:pre-wrap"></div>
</div> </div>
<div style="display:flex;align-items:center;justify-content:space-between;margin-top:var(--space-3)"> <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" <button id="ki-regenerate"
style="font-size:var(--text-xs);padding:var(--space-1) var(--space-3); style="font-size:var(--text-xs);padding:var(--space-1) var(--space-3);
border-radius:var(--radius-md);border:1px solid var(--c-border); border-radius:var(--radius-md);border:1px solid var(--c-border);
@ -1986,12 +1986,12 @@ window.Page_uebungen = (() => {
const dogId = _dogId(); const dogId = _dogId();
if (!dogId) { if (!dogId) {
return `<div style="padding:var(--space-8);text-align:center;color:var(--c-text-muted)"> 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>`; </div>`;
} }
return `<div id="verlauf-wrap" style="padding:var(--space-4);display:flex;flex-direction:column;gap:var(--space-3)"> return `<div id="verlauf-wrap" style="padding:var(--space-4);display:flex;flex-direction:column;gap:var(--space-3)">
${_verlaufToggleHtml()} ${_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)"> <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"> <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> <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 active = `background:var(--c-primary);color:#fff;border-color:var(--c-primary)`;
const inactive = `background:var(--c-surface-2);color:var(--c-text-secondary)`; const inactive = `background:var(--c-surface-2);color:var(--c-text-secondary)`;
return ` 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}"> <button id="verlauf-btn-datum" style="${btnBase};${_verlaufView==='datum'?active:inactive}">
Nach Datum Nach Datum
</button> </button>
@ -2110,7 +2110,7 @@ window.Page_uebungen = (() => {
padding:var(--space-2) var(--space-3);border-radius:var(--radius-md); padding:var(--space-2) var(--space-3);border-radius:var(--radius-md);
background:var(--c-surface-2)"> background:var(--c-surface-2)">
<span style="font-size:1.2rem;flex-shrink:0;margin-top:1px">${erfolg}</span> <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"> <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); <span style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
color:var(--c-text)">${_esc(s.exercise_name)}</span> 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;min-width:52px">${_esc(dateLabel)}</span>
<span style="flex-shrink:0">${erfolg}</span> <span style="flex-shrink:0">${erfolg}</span>
<span style="flex-shrink:0">${s.erfolgsquote}%${top}</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>`; </div>`;
}).join(''); }).join('');
@ -2240,7 +2240,7 @@ window.Page_uebungen = (() => {
</span> </span>
</div> </div>
<!-- Info --> <!-- 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); <div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
color:var(--c-text);line-height:1.3; color:var(--c-text);line-height:1.3;
white-space:nowrap;overflow:hidden;text-overflow:ellipsis"> 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)"> <div style="padding:var(--space-4);display:flex;flex-direction:column;gap:var(--space-4)">
<!-- Markerwort --> <!-- 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); <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)"> 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"> <svg class="ph-icon" style="width:16px;height:16px;color:var(--c-primary);flex-shrink:0" aria-hidden="true">

View file

@ -150,14 +150,14 @@ window.Page_walks = (() => {
</div> </div>
<!-- Tab: Challenge --> <!-- 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"> <div id="challenge-content">
<p style="color:var(--c-text-secondary);text-align:center;padding:var(--space-8)">Lädt</p> <p style="color:var(--c-text-secondary);text-align:center;padding:var(--space-8)">Lädt</p>
</div> </div>
</div> </div>
<!-- Tab: Stamm-Gassis --> <!-- 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"> <div class="by-toolbar">
<span style="font-weight:600;color:var(--c-text)">${UI.icon('clock')} Stamm-Gassi-Zeiten</span> <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> <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 = ` el.innerHTML = `
<div style="text-align:center;padding:var(--space-10) var(--space-4)"> <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> <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> <p class="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"> <button class="btn btn-primary mt-4" id="walks-first-btn">
Erstes Treffen planen Erstes Treffen planen
</button> </button>
</div>`; </div>`;
@ -463,13 +463,13 @@ window.Page_walks = (() => {
</div>`; </div>`;
return `<div style="display:flex;align-items:center;gap:4px"> return `<div style="display:flex;align-items:center;gap:4px">
${av} ${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>`; </div>`;
}).join(''); }).join('');
return ` return `
<div class="walks-participant"> <div class="walks-participant">
<div class="walks-inv-avatar walks-inv-avatar--sm">${_avatarInitials(t.user_name)}</div> <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> <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>` : ''} ${dogsHTML ? `<div style="display:flex;flex-wrap:wrap;gap:var(--space-1);margin-top:4px">${dogsHTML}</div>` : ''}
</div> </div>
@ -480,7 +480,7 @@ window.Page_walks = (() => {
// Einladungsliste // Einladungsliste
const invListHTML = invitations.length const invListHTML = invitations.length
? invitations.map(inv => _invitationRowHTML(inv)).join('') ? 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 // RSVP-Section für eingeladene Nutzer
const rsvpSectionHTML = (isInvited && !isOwn) ? ` 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> <div class="walks-detail-section-label" style="margin-bottom:0">${UI.icon('images')} Fotos</div>
${(isPast || _isToday(walk.datum)) && (isJoined || isOwn) ? ` ${(isPast || _isToday(walk.datum)) && (isJoined || isOwn) ? `
<label style="cursor:pointer"> <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> <span class="btn btn-secondary btn-sm">${UI.icon('camera')} Foto hinzufügen</span>
</label>` : ''} </label>` : ''}
</div> </div>
@ -572,7 +572,7 @@ window.Page_walks = (() => {
</p> </p>
${isOwn && !isPast ? ` ${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" <button type="button" class="btn btn-ghost btn-sm" id="wd-cancel-walk"
style="color:var(--c-danger);width:100%"> style="color:var(--c-danger);width:100%">
${UI.icon('x-circle')} Treffen stornieren ${UI.icon('x-circle')} Treffen stornieren
@ -580,7 +580,7 @@ window.Page_walks = (() => {
</div>` : ''} </div>` : ''}
${isJoined && !isOwn ? ` ${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" <button type="button" class="btn btn-ghost btn-sm" id="wd-leave"
style="color:var(--c-danger);width:100%"> style="color:var(--c-danger);width:100%">
${UI.icon('sign-out')} Nicht mehr teilnehmen ${UI.icon('sign-out')} Nicht mehr teilnehmen
@ -782,12 +782,12 @@ window.Page_walks = (() => {
? candidates.map(f => ` ? 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-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-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"> <button type="button" class="btn btn-primary btn-sm walks-invite-send">
${UI.icon('paper-plane-tilt')} Einladen ${UI.icon('paper-plane-tilt')} Einladen
</button> </button>
</div>`).join('') </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 = ` const body = `
<p style="color:var(--c-text-secondary);margin-bottom:var(--space-3)"> <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); await API.walks.invite(walk.id, friendId);
row.innerHTML = ` row.innerHTML = `
<div class="walks-inv-avatar">${_avatarInitials(name)}</div> <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> <span class="walks-rsvp-badge walks-rsvp--invited">Eingeladen</span>
`; `;
UI.toast.success(`${name} eingeladen.`); UI.toast.success(`${name} eingeladen.`);
@ -838,7 +838,7 @@ window.Page_walks = (() => {
<input type="checkbox" name="dog" value="${d.id}" checked> <input type="checkbox" name="dog" value="${d.id}" checked>
${UI.icon('dog')} ${UI.escape(d.name)} ${UI.icon('dog')} ${UI.escape(d.name)}
</label>`).join('') </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 = ` const body = `
<p style="color:var(--c-text-secondary);margin-bottom:var(--space-4)"> <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> placeholder="z. B. Sonntagsspaziergang im Stadtpark" required>
</div> </div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)"> <div class="grid-2">
<div class="form-group"> <div class="form-group">
<label class="form-label">Datum *</label> <label class="form-label">Datum *</label>
<input class="form-control" type="date" name="datum" <input class="form-control" type="date" name="datum"
@ -943,7 +943,7 @@ window.Page_walks = (() => {
</div> </div>
<!-- Ort-Chip --> <!-- Ort-Chip -->
<div style="margin-top:var(--space-2)"> <div class="mt-2">
<div id="wf-location-chip-wrap" style="${_locName ? '' : 'display:none'}"> <div id="wf-location-chip-wrap" style="${_locName ? '' : 'display:none'}">
<div class="diary-location-chip"> <div class="diary-location-chip">
${UI.icon('map-pin')} ${UI.icon('map-pin')}
@ -979,7 +979,7 @@ window.Page_walks = (() => {
</div> </div>
<div class="form-group"> <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" <textarea class="form-control" name="beschreibung" rows="3"
placeholder="Treffpunkt-Details, Streckenlänge, Hundefreundlichkeit…">${UI.escape(v.beschreibung || '')}</textarea> placeholder="Treffpunkt-Details, Streckenlänge, Hundefreundlichkeit…">${UI.escape(v.beschreibung || '')}</textarea>
</div> </div>
@ -989,7 +989,7 @@ window.Page_walks = (() => {
const footer = ` const footer = `
<div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%"> <div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%">
<button 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`} ${isEdit ? `${UI.icon('floppy-disk')} Speichern` : `${UI.icon('calendar-dots')} Treffen planen`}
</button> </button>
<button type="button" class="btn btn-secondary" id="wf-cancel">Abbrechen</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; <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"> 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)"> <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> <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"> <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> <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('calendar')} ${_fmtDate(challenge.start_date)} ${_fmtDate(challenge.end_date)}
&nbsp;·&nbsp; ${UI.icon('timer')} Noch ${dayLabel} &nbsp;·&nbsp; ${UI.icon('timer')} Noch ${dayLabel}
</div> </div>
${canSubmit ? `<button class="btn btn-primary btn-sm" id="challenge-submit-btn" style="margin-top:var(--space-3)">${UI.icon('camera')} Foto einreichen</button>` : ''} ${canSubmit ? `<button class="btn btn-primary btn-sm" id="challenge-submit-btn" class="mt-3">${UI.icon('camera')} Foto einreichen</button>` : ''}
${my_submission_id ? `<span class="badge badge-success" style="margin-top:var(--space-2)">${UI.icon('check')} Du hast bereits teilgenommen</span>` : ''} ${my_submission_id ? `<span class="badge badge-success mt-2">${UI.icon('check')} Du hast bereits teilgenommen</span>` : ''}
</div> </div>
</div> </div>
@ -1371,7 +1371,7 @@ window.Page_walks = (() => {
<img src="${UI.escape(w.winner.foto_url)}" alt="Gewinner" onerror="this.src='/icons/icon-192.png'"> <img src="${UI.escape(w.winner.foto_url)}" alt="Gewinner" onerror="this.src='/icons/icon-192.png'">
<div> <div>
<div style="font-weight:600;font-size:var(--text-xs)">${UI.escape(w.challenge.thema)}</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>
</div>`; </div>`;
}).join('') + }).join('') +
@ -1387,21 +1387,21 @@ window.Page_walks = (() => {
UI.modal.open({ UI.modal.open({
title: `📸 ${UI.escape(_challengeData.challenge.thema)}`, title: `📸 ${UI.escape(_challengeData.challenge.thema)}`,
body: ` 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"> <div class="form-group">
<label>Foto *</label> <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> </div>
${dogs.length ? `<div class="form-group"> ${dogs.length ? `<div class="form-group">
<label>Hund</label> <label>Hund</label>
<select id="challenge-dog-select" style="width:100%"> <select id="challenge-dog-select" class="w-full">
<option value="">Kein Hund</option> <option value="">Kein Hund</option>
${dogOptions} ${dogOptions}
</select> </select>
</div>` : ''} </div>` : ''}
<div class="form-group"> <div class="form-group">
<label>Bildunterschrift</label> <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> </div>
</form> </form>
`, `,
@ -1465,7 +1465,7 @@ window.Page_walks = (() => {
<div style="text-align:center;padding:var(--space-8);color:var(--c-text-secondary)"> <div style="text-align:center;padding:var(--space-8);color:var(--c-text-secondary)">
${UI.icon('clock')} ${UI.icon('clock')}
<p>Noch keine Stamm-Gassi-Zeiten in deiner Nähe.</p> <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>`; </div>`;
return; return;
} }
@ -1537,7 +1537,7 @@ window.Page_walks = (() => {
</div> </div>
<div class="gz-body"> <div class="gz-body">
<div class="gz-name">${UI.escape(z.dog_name || z.user_name || '?')} <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>` : ''} ${!z.aktiv ? `<span class="badge badge-warning">Pausiert</span>` : ''}
</div> </div>
<div class="gz-meta"> <div class="gz-meta">
@ -1576,17 +1576,17 @@ window.Page_walks = (() => {
UI.modal.open({ UI.modal.open({
title: `${UI.icon('clock')} Stamm-Gassi-Zeit eintragen`, title: `${UI.icon('clock')} Stamm-Gassi-Zeit eintragen`,
body: ` 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"> ${dogs.length ? `<div class="form-group">
<label>Hund</label> <label>Hund</label>
<select id="gz-dog-select" style="width:100%"> <select id="gz-dog-select" class="w-full">
<option value="">Kein Hund</option> <option value="">Kein Hund</option>
${dogOptions} ${dogOptions}
</select> </select>
</div>` : ''} </div>` : ''}
<div class="form-group"> <div class="form-group">
<label>Uhrzeit *</label> <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>
<div class="form-group"> <div class="form-group">
<label>Wochentage *</label> <label>Wochentage *</label>
@ -1594,11 +1594,11 @@ window.Page_walks = (() => {
</div> </div>
<div class="form-group"> <div class="form-group">
<label>Ort (optional)</label> <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>
<div class="form-group"> <div class="form-group">
<label>Notiz (optional)</label> <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> </div>
</form> </form>
`, `,

View file

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

View file

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

View file

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

View file

@ -102,7 +102,7 @@ window.Page_wiki = (() => {
<button class="by-tab${_tab === 'gesundheit'? ' active' : ''}" data-tab="gesundheit">${UI.icon('syringe')} Gesundheit</button> <button class="by-tab${_tab === '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 === '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> <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>
<div id="wiki-content"></div> <div id="wiki-content"></div>
`; `;
@ -132,7 +132,7 @@ window.Page_wiki = (() => {
// TAB: Foto-Einreichungen (Mod/Admin) // TAB: Foto-Einreichungen (Mod/Admin)
// ---------------------------------------------------------- // ----------------------------------------------------------
async function _renderFotoSubmissions(el) { 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; let subs;
try { try {
subs = await _apiFetch('/api/wiki/foto-submissions'); subs = await _apiFetch('/api/wiki/foto-submissions');
@ -155,7 +155,7 @@ window.Page_wiki = (() => {
} }
el.innerHTML = ` 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)"> <h3 style="font-size:var(--text-base);font-weight:var(--weight-semibold);margin-bottom:var(--space-4)">
Ausstehende Fotos (${subs.length}) Ausstehende Fotos (${subs.length})
</h3> </h3>
@ -165,9 +165,9 @@ window.Page_wiki = (() => {
<div style="display:flex;gap:var(--space-3);align-items:flex-start"> <div style="display:flex;gap:var(--space-3);align-items:flex-start">
<img src="${_esc(s.foto_url)}" alt="" <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)"> 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-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)} von ${_esc(s.user_name)} · ${_formatDate(s.created_at)}
</div> </div>
${s.aktuell_foto ${s.aktuell_foto
@ -257,12 +257,12 @@ window.Page_wiki = (() => {
</div> </div>
<div style="padding:0 0 var(--space-3)"> <div style="padding:0 0 var(--space-3)">
<button class="btn btn-secondary w-full" id="wiki-rasse-erkennen-btn" <button class="btn btn-secondary w-full" 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> <svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#magnifying-glass"></use></svg>
Welche Rasse ist das? Foto analysieren Welche Rasse ist das? Foto analysieren
</button> </button>
<input type="file" accept="image/jpeg,image/png,image/webp" <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>
<div class="wiki-breed-grid" id="wiki-breed-grid"></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"> <div id="wiki-mehr-wrap" style="text-align:center;padding:var(--space-4) 0;display:none">
@ -460,14 +460,14 @@ window.Page_wiki = (() => {
: (rasse.gewicht_max_kg ? `bis ${rasse.gewicht_max_kg} kg` : '&mdash;'); : (rasse.gewicht_max_kg ? `bis ${rasse.gewicht_max_kg} kg` : '&mdash;');
const kinderLabel = rasse.kinder_geeignet === true const kinderLabel = rasse.kinder_geeignet === true
? `<span style="color:var(--c-success)">&#10003; Ja</span>` ? `<span class="text-success">&#10003; Ja</span>`
: rasse.kinder_geeignet === false : rasse.kinder_geeignet === false
? `<span style="color:var(--c-warning)">&#9889; Bedingt</span>` ? `<span style="color:var(--c-warning)">&#9889; Bedingt</span>`
: '&mdash;'; : '&mdash;';
const wohnungLabel = rasse.wohnung_geeignet const wohnungLabel = rasse.wohnung_geeignet
? `<span style="color:var(--c-success)">&#10003; Ja</span>` ? `<span class="text-success">&#10003; Ja</span>`
: `<span style="color:var(--c-text-secondary)">&#10007; Besser Garten</span>`; : `<span class="text-secondary">&#10007; Besser Garten</span>`;
const rows = [ const rows = [
['Größe', _groesseLabel(rasse.groesse) || '&mdash;'], ['Größe', _groesseLabel(rasse.groesse) || '&mdash;'],
@ -514,7 +514,7 @@ window.Page_wiki = (() => {
<span id="wiki-hat-count">&#128021; <strong>${hatCount}</strong> haben diesen Hund</span> <span id="wiki-hat-count">&#128021; <strong>${hatCount}</strong> haben diesen Hund</span>
<span id="wiki-will-count">&#10084;&#65039; <strong>${willCount}</strong> m&ouml;chten ihn</span> <span id="wiki-will-count">&#10084;&#65039; <strong>${willCount}</strong> m&ouml;chten ihn</span>
</div> </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" <button class="btn btn-sm wiki-interesse-btn" id="wiki-btn-hat"
style="flex:1;${hatStyle}" style="flex:1;${hatStyle}"
data-slug="${_esc(slug)}" data-typ="hat"> data-slug="${_esc(slug)}" data-typ="hat">
@ -597,7 +597,7 @@ window.Page_wiki = (() => {
${z.zwingername ? `<em style="font-weight:normal;color:var(--c-text-secondary)"> &bdquo;${_esc(z.zwingername)}&ldquo;</em>` : ''} ${z.zwingername ? `<em style="font-weight:normal;color:var(--c-text-secondary)"> &bdquo;${_esc(z.zwingername)}&ldquo;</em>` : ''}
${z.vdh_mitglied ? `<span class="badge badge-sm" style="margin-left:var(--space-1);background:var(--c-primary);color:#fff">VDH</span>` : ''} ${z.vdh_mitglied ? `<span class="badge badge-sm" style="margin-left:var(--space-1);background:var(--c-primary);color:#fff">VDH</span>` : ''}
</div> </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.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>` : ''} ${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> </div>
@ -656,7 +656,7 @@ window.Page_wiki = (() => {
</div> </div>
</form> </form>
</div> </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 + Züchter eintragen
</button> </button>
` : ''; ` : '';
@ -745,7 +745,7 @@ window.Page_wiki = (() => {
<img class="wiki-detail-photo wiki-gallery-main" id="wiki-main-photo" <img class="wiki-detail-photo wiki-gallery-main" id="wiki-main-photo"
src="${_esc(allFotos[0].foto_url)}" alt="${_esc(rasse.name)}" src="${_esc(allFotos[0].foto_url)}" alt="${_esc(rasse.name)}"
onerror="this.style.display='none';document.getElementById('wiki-photo-fallback').style.display='flex'"> 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 ? ` ${allFotos.length > 1 ? `
<div class="wiki-gallery-strip" id="wiki-gallery-strip"> <div class="wiki-gallery-strip" id="wiki-gallery-strip">
${allFotos.map((f, i) => ` ${allFotos.map((f, i) => `
@ -772,7 +772,7 @@ window.Page_wiki = (() => {
${photoHtml} ${photoHtml}
${userFotosHtml} ${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> <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>` : ''} ${rasse.gruppe ? `<div style="font-size:var(--text-sm);color:var(--c-text-muted);margin-top:2px">${_esc(rasse.gruppe)}</div>` : ''}
</div> </div>
@ -822,14 +822,14 @@ window.Page_wiki = (() => {
${berichteHtml} ${berichteHtml}
</div> </div>
${_appState.user ${_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)"> : `<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>` </p>`
} }
${_appState.user ? ` ${_appState.user ? `
<div style="margin-top:var(--space-4);padding-top:var(--space-4);border-top:1px solid var(--c-border-light)"> <div style="margin-top:var(--space-4);padding-top:var(--space-4);border-top:1px solid var(--c-border-light)">
<button class="btn btn-ghost w-full" id="wiki-foto-submit-btn" 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'} ${UI.icon('camera')} ${rasse.foto_url ? 'Besseres Foto vorschlagen' : 'Foto hinzufügen'}
</button> </button>
</div>` : ''}`} </div>` : ''}`}
@ -1098,7 +1098,7 @@ window.Page_wiki = (() => {
<span class="wiki-section-titel">${_esc(s.titel)}</span> <span class="wiki-section-titel">${_esc(s.titel)}</span>
<span class="wiki-section-arrow">${UI.icon('caret-down')}</span> <span class="wiki-section-arrow">${UI.icon('caret-down')}</span>
</div> </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> <p style="white-space:pre-wrap;line-height:1.6;color:var(--c-text)">${_esc(s.text)}</p>
</div> </div>
</div> </div>
@ -1129,7 +1129,7 @@ window.Page_wiki = (() => {
<span class="wiki-section-titel">${_esc(r.land)}</span> <span class="wiki-section-titel">${_esc(r.land)}</span>
<span class="wiki-section-arrow">${UI.icon('caret-down')}</span> <span class="wiki-section-arrow">${UI.icon('caret-down')}</span>
</div> </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">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">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> <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('house-line')} ${r.wohnung_geeignet ? 'Wohnung' : 'Haus'}</span>
<span>${UI.icon('users')} ${r.kinder_geeignet ? 'Kinderfreundlich' : 'Erfahrung nötig'}</span> <span>${UI.icon('users')} ${r.kinder_geeignet ? 'Kinderfreundlich' : 'Erfahrung nötig'}</span>
</div> </div>
<button class="btn btn-secondary btn-sm wiki-quiz-mehr" data-slug="${_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>
</div> </div>
`; `;
@ -1251,11 +1251,11 @@ window.Page_wiki = (() => {
el.innerHTML = ` el.innerHTML = `
<div class="wiki-quiz-wrap"> <div class="wiki-quiz-wrap">
<div class="wiki-quiz-progress-bar"> <div class="wiki-quiz-progress-bar">
<div class="wiki-quiz-progress" style="width:100%"></div> <div class="wiki-quiz-progress w-full"></div>
</div> </div>
<h3 style="margin:var(--space-4) 0 var(--space-2);text-align:center">Deine Top 3 Rassen</h3> <h3 style="margin:var(--space-4) 0 var(--space-2);text-align:center">Deine Top 3 Rassen</h3>
<div class="wiki-quiz-results">${cardsHtml}</div> <div class="wiki-quiz-results">${cardsHtml}</div>
<button class="btn btn-secondary w-full" id="quiz-restart" 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> </div>
`; `;
@ -1401,7 +1401,7 @@ window.Page_wiki = (() => {
title: 'Kein Hund erkannt', title: 'Kein Hund erkannt',
body: `<div style="text-align:center;padding:var(--space-6) var(--space-2)"> body: `<div style="text-align:center;padding:var(--space-6) var(--space-2)">
<div style="font-size:3rem;margin-bottom:var(--space-3)">🐾</div> <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> Auf diesem Foto konnte kein Hund erkannt werden.<br>
Bitte lade ein deutlicheres Foto hoch. Bitte lade ein deutlicheres Foto hoch.
</p> </p>
@ -1427,7 +1427,7 @@ window.Page_wiki = (() => {
</div> </div>
${r.beschreibung ? `<div class="rasse-result-desc">${_esc(r.beschreibung)}</div>` : ''} ${r.beschreibung ? `<div class="rasse-result-desc">${_esc(r.beschreibung)}</div>` : ''}
${r.wiki_slug ? ` ${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" <button class="btn btn-${isTop ? 'primary' : 'secondary'} btn-sm w-full"
data-action="wiki" data-slug="${_esc(r.wiki_slug)}"> data-action="wiki" data-slug="${_esc(r.wiki_slug)}">
Im Wiki nachschlagen Im Wiki nachschlagen

View file

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

View file

@ -120,7 +120,7 @@ window.Page_zuchthunde = (() => {
padding:var(--space-3) var(--space-4); padding:var(--space-3) var(--space-4);
display:flex;align-items:center;gap:var(--space-3)"> display:flex;align-items:center;gap:var(--space-3)">
${logoHtml} ${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; <h2 style="margin:0 0 2px;font-size:var(--text-lg);font-weight:700;
color:var(--c-text);white-space:nowrap;overflow:hidden; color:var(--c-text);white-space:nowrap;overflow:hidden;
text-overflow:ellipsis;line-height:1.2">${_esc(zwinger)}</h2> 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"> <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> <use href="/icons/phosphor.svg#lock-key"></use>
</svg> </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> </div>
</div>`; </div>`;
@ -232,8 +232,8 @@ window.Page_zuchthunde = (() => {
: ` : `
<div style="text-align:center;padding:var(--space-10) var(--space-4)"> <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> <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> <p class="text-secondary">Noch keine Hunde angelegt.</p>
<button class="btn btn-primary" style="margin-top:var(--space-4)" id="zh-first-btn"> <button class="btn btn-primary mt-4" id="zh-first-btn">
${UI.icon('plus')} Ersten Hund anlegen ${UI.icon('plus')} Ersten Hund anlegen
</button> </button>
</div>`; </div>`;
@ -299,7 +299,7 @@ window.Page_zuchthunde = (() => {
// Hund-Card HTML // Hund-Card HTML
// ---------------------------------------------------------- // ----------------------------------------------------------
function _hundCardHTML(h) { 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 rufname = h.rufname ? ` (${_esc(h.rufname)})` : '';
const geburtstag = h.geburtsdatum ? _fmtDate(h.geburtsdatum) : null; const geburtstag = h.geburtsdatum ? _fmtDate(h.geburtsdatum) : null;
@ -314,7 +314,7 @@ window.Page_zuchthunde = (() => {
return ` return `
<div class="zh-card" id="zh-card-${h.id}"> <div class="zh-card" id="zh-card-${h.id}">
<div class="zh-card-header"> <div class="zh-card-header">
<div style="flex:1;min-width:0"> <div class="flex-1-min">
<div class="zh-card-title"> <div class="zh-card-title">
${_genderIcon(h.geschlecht)} ${_genderIcon(h.geschlecht)}
${nameLabel}${_esc(rufname)} ${nameLabel}${_esc(rufname)}
@ -326,7 +326,7 @@ window.Page_zuchthunde = (() => {
${h.chip_nr ? `${UI.icon('barcode')} ${_esc(h.chip_nr)}&nbsp;&nbsp;` : ''} ${h.chip_nr ? `${UI.icon('barcode')} ${_esc(h.chip_nr)}&nbsp;&nbsp;` : ''}
${h.zuchtbuchnummer ? `${UI.icon('book-open')} ${_esc(h.zuchtbuchnummer)}&nbsp;&nbsp;` : ''} ${h.zuchtbuchnummer ? `${UI.icon('book-open')} ${_esc(h.zuchtbuchnummer)}&nbsp;&nbsp;` : ''}
</div> </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>
<div class="zh-card-actions"> <div class="zh-card-actions">
<button class="btn btn-ghost btn-sm zh-pedigree-btn" data-id="${h.id}" <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')} ${UI.icon('pencil-simple')}
</button> </button>
<button class="btn btn-ghost btn-sm zh-delete-btn" data-id="${h.id}" <button class="btn btn-ghost btn-sm zh-delete-btn" data-id="${h.id}"
title="Löschen" style="color:var(--c-danger)"> title="Löschen" class="text-danger">
${UI.icon('trash')} ${UI.icon('trash')}
</button> </button>
</div> </div>
@ -364,7 +364,7 @@ window.Page_zuchthunde = (() => {
</button> </button>
</div> </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>`; </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>` : ''} ${t.labor ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${_esc(t.labor)}</span>` : ''}
</div> </div>
<button class="btn btn-ghost btn-xs zh-health-del-btn" data-tid="${t.id}" title="Löschen" <button class="btn btn-ghost btn-xs zh-health-del-btn" 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('') </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 = ` wrap.innerHTML = `
<div class="zh-section-inner"> <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>` : ''} ${t.labor ? `<span style="color:var(--c-text-muted);font-size:var(--text-xs)">${_esc(t.labor)}</span>` : ''}
</div> </div>
<button class="btn btn-ghost btn-xs zh-genetic-del-btn" data-tid="${t.id}" title="Löschen" <button class="btn btn-ghost btn-xs zh-genetic-del-btn" 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('') </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 = ` wrap.innerHTML = `
<div class="zh-section-inner"> <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>` : ''} ${t.formwert ? `<span class="zh-badge" style="background:#3B82F6">${_esc(t.formwert)}</span>` : ''}
</div> </div>
<button class="btn btn-ghost btn-xs zh-title-del-btn" data-tid="${t.id}" title="Löschen" <button class="btn btn-ghost btn-xs zh-title-del-btn" 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('') </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 = ` wrap.innerHTML = `
<div class="zh-section-inner"> <div class="zh-section-inner">
@ -626,9 +626,9 @@ window.Page_zuchthunde = (() => {
const body = ` const body = `
<form id="zh-hund-form" autocomplete="off"> <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"> <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 <input class="form-control" type="text" name="name" required
value="${_esc(v.name || '')}" placeholder="z. B. Banyaro's Black Diamond"> value="${_esc(v.name || '')}" placeholder="z. B. Banyaro's Black Diamond">
</div> </div>
@ -639,7 +639,7 @@ window.Page_zuchthunde = (() => {
</div> </div>
</div> </div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)"> <div class="grid-2">
<div class="form-group"> <div class="form-group">
<label class="form-label">Geschlecht</label> <label class="form-label">Geschlecht</label>
<select class="form-control" name="geschlecht"> <select class="form-control" name="geschlecht">
@ -655,7 +655,7 @@ window.Page_zuchthunde = (() => {
</div> </div>
</div> </div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)"> <div class="grid-2">
<div class="form-group"> <div class="form-group">
<label class="form-label">Sterbedatum</label> <label class="form-label">Sterbedatum</label>
<input class="form-control" type="date" name="sterbedatum" <input class="form-control" type="date" name="sterbedatum"
@ -668,7 +668,7 @@ window.Page_zuchthunde = (() => {
</div> </div>
</div> </div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)"> <div class="grid-2">
<div class="form-group"> <div class="form-group">
<label class="form-label">Chip-Nr.</label> <label class="form-label">Chip-Nr.</label>
<input class="form-control" type="text" name="chip_nr" <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"> value="${_esc(v.zuchtbuchnummer || '')}" placeholder="z. B. SZ 123456">
</div> </div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)"> <div class="grid-2">
<div class="form-group"> <div class="form-group">
<label class="form-label">Vater</label> <label class="form-label">Vater</label>
<select class="form-control" name="vater_id">${vaterOptions}</select> <select class="form-control" name="vater_id">${vaterOptions}</select>
@ -698,7 +698,7 @@ window.Page_zuchthunde = (() => {
</div> </div>
</div> </div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)"> <div class="grid-2">
<div class="form-group"> <div class="form-group">
<label class="form-label">Züchter-Name</label> <label class="form-label">Züchter-Name</label>
<input class="form-control" type="text" name="zuechter_name" <input class="form-control" type="text" name="zuechter_name"
@ -712,7 +712,7 @@ window.Page_zuchthunde = (() => {
</div> </div>
<div class="form-group"> <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" <textarea class="form-control" name="notiz" rows="2"
placeholder="Interne Anmerkungen…">${_esc(v.notiz || '')}</textarea> placeholder="Interne Anmerkungen…">${_esc(v.notiz || '')}</textarea>
</div> </div>
@ -807,9 +807,9 @@ window.Page_zuchthunde = (() => {
const body = ` const body = `
<form id="zh-health-form" autocomplete="off"> <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"> <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> <select class="form-control" name="test_typ" id="zh-health-typ" required>
<option value="HD">HD (Hüftgelenksdysplasie)</option> <option value="HD">HD (Hüftgelenksdysplasie)</option>
<option value="ED">ED (Ellbogendysplasie)</option> <option value="ED">ED (Ellbogendysplasie)</option>
@ -822,13 +822,13 @@ window.Page_zuchthunde = (() => {
</select> </select>
</div> </div>
<div class="form-group" id="zh-health-name-wrap"> <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"> <input class="form-control" type="text" name="test_name" placeholder="Bezeichnung des Tests">
</div> </div>
</div> </div>
<div class="form-group"> <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 <input class="form-control" type="text" name="ergebnis" required
id="zh-health-ergebnis" placeholder="z. B. A1, A2, B1 …"> 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)"> <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> </small>
</div> </div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)"> <div class="grid-2">
<div class="form-group"> <div class="form-group">
<label class="form-label">Untersuchungsdatum</label> <label class="form-label">Untersuchungsdatum</label>
<input class="form-control" type="date" name="untersuch_am" value="${today}"> <input class="form-control" type="date" name="untersuch_am" value="${today}">
@ -847,7 +847,7 @@ window.Page_zuchthunde = (() => {
</div> </div>
</div> </div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)"> <div class="grid-2">
<div class="form-group"> <div class="form-group">
<label class="form-label">Untersucher / Tierarzt</label> <label class="form-label">Untersucher / Tierarzt</label>
<input class="form-control" type="text" name="untersucher" placeholder="Dr. Müller"> <input class="form-control" type="text" name="untersucher" placeholder="Dr. Müller">
@ -931,9 +931,9 @@ window.Page_zuchthunde = (() => {
const body = ` const body = `
<form id="zh-genetic-form" autocomplete="off"> <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"> <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> <select class="form-control" name="marker_name" required>
<option value="MDR1">MDR1 (Multi-Drug Resistance)</option> <option value="MDR1">MDR1 (Multi-Drug Resistance)</option>
<option value="PRA-prcd">PRA-prcd (Progressive Retinaatrophie)</option> <option value="PRA-prcd">PRA-prcd (Progressive Retinaatrophie)</option>
@ -947,7 +947,7 @@ window.Page_zuchthunde = (() => {
</select> </select>
</div> </div>
<div class="form-group"> <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> <select class="form-control" name="ergebnis_klasse" required>
<option value="clear">clear (frei)</option> <option value="clear">clear (frei)</option>
<option value="carrier">carrier (Träger)</option> <option value="carrier">carrier (Träger)</option>
@ -956,7 +956,7 @@ window.Page_zuchthunde = (() => {
</div> </div>
</div> </div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)"> <div class="grid-2">
<div class="form-group"> <div class="form-group">
<label class="form-label">Testdatum</label> <label class="form-label">Testdatum</label>
<input class="form-control" type="date" name="getestet_am" value="${today}"> <input class="form-control" type="date" name="getestet_am" value="${today}">
@ -1020,9 +1020,9 @@ window.Page_zuchthunde = (() => {
const body = ` const body = `
<form id="zh-title-form" autocomplete="off"> <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"> <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> <select class="form-control" name="titel_typ" required>
<option value="Ausstellung">Ausstellung</option> <option value="Ausstellung">Ausstellung</option>
<option value="Arbeit">Arbeit</option> <option value="Arbeit">Arbeit</option>
@ -1033,13 +1033,13 @@ window.Page_zuchthunde = (() => {
</select> </select>
</div> </div>
<div class="form-group"> <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 <input class="form-control" type="text" name="titel_name" required
placeholder="z. B. CAC, CACIB, BOB, IPO 1, BH"> placeholder="z. B. CAC, CACIB, BOB, IPO 1, BH">
</div> </div>
</div> </div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)"> <div class="grid-2">
<div class="form-group"> <div class="form-group">
<label class="form-label">Datum</label> <label class="form-label">Datum</label>
<input class="form-control" type="date" name="verliehen_am" value="${today}"> <input class="form-control" type="date" name="verliehen_am" value="${today}">
@ -1056,7 +1056,7 @@ window.Page_zuchthunde = (() => {
</div> </div>
</div> </div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-3)"> <div class="grid-2">
<div class="form-group"> <div class="form-group">
<label class="form-label">Ort</label> <label class="form-label">Ort</label>
<input class="form-control" type="text" name="ort" placeholder="Stadt / Veranstaltungsort"> <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>` : ''; 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>`; return `<li style="padding:var(--space-1) 0">${_esc(v.name || '—')}${genStr}</li>`;
}).join('') }).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; const welfare = result.welfare;
let welfareHTML = ''; let welfareHTML = '';
@ -1220,13 +1220,13 @@ window.Page_zuchthunde = (() => {
const wIssueHTML = (welfare.issues || []).map(i => ` const wIssueHTML = (welfare.issues || []).map(i => `
<div style="display:flex;gap:8px;padding:6px 0;border-bottom:1px solid rgba(0,0,0,.06)"> <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="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(''); </div>`).join('');
const wOkHTML = (welfare.ok_points || []).map(p => ` const wOkHTML = (welfare.ok_points || []).map(p => `
<div style="display:flex;gap:8px;padding:4px 0"> <div style="display:flex;gap:8px;padding:4px 0">
<span style="color:#16a34a;flex-shrink:0">${UI.icon('check')}</span> <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(''); </div>`).join('');
welfareHTML = ` welfareHTML = `
@ -1278,7 +1278,7 @@ window.Page_zuchthunde = (() => {
const footer = ` const footer = `
<div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%"> <div style="display:flex;flex-direction:column;gap:var(--space-2);width:100%">
${kiPaarungBtn} ${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"> <button type="button" class="btn btn-secondary flex-1" id="zhresult-back">
${UI.icon('arrow-left')} Zurück ${UI.icon('arrow-left')} Zurück
</button> </button>
@ -1314,7 +1314,7 @@ window.Page_zuchthunde = (() => {
} catch (err) { } catch (err) {
UI.modal.open({ UI.modal.open({
title: `${UI.icon('sparkle')} KI-Hunde-Beschreibung`, 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>`, footer: `<button class="btn btn-secondary" data-modal-close>Schließen</button>`,
}); });
return; return;
@ -1414,7 +1414,7 @@ window.Page_zuchthunde = (() => {
} catch (err) { } catch (err) {
UI.modal.open({ UI.modal.open({
title: `${UI.icon('chart-bar')} KI-Jahresbericht`, 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>`, footer: `<button class="btn btn-secondary" data-modal-close>Schließen</button>`,
}); });
return; return;
@ -1484,7 +1484,7 @@ window.Page_zuchthunde = (() => {
<div style="font-weight:var(--weight-semibold);font-size:var(--text-sm)"> <div style="font-weight:var(--weight-semibold);font-size:var(--text-sm)">
Jahresbericht ${b.jahr} Jahresbericht ${b.jahr}
</div> </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'})} ${new Date(b.created_at).toLocaleDateString('de', {day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'})}
</div> </div>
</div> </div>
@ -1532,7 +1532,7 @@ window.Page_zuchthunde = (() => {
} catch (err) { } catch (err) {
UI.modal.open({ UI.modal.open({
title: `${UI.icon('sparkle')} KI-Paarungsanalyse`, 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>`, footer: `<button class="btn btn-secondary" data-modal-close>Schließen</button>`,
}); });
return; 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)"> <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. Diese Fotos erscheinen im öffentlichen Züchterprofil. Das primäre Foto wird als <strong>Logo</strong> im Hero angezeigt.
</p> </p>
<div id="${galleryId}" style="margin-bottom:var(--space-4)"> <div id="${galleryId}" class="mb-4">
<p style="color:var(--c-text-muted);font-size:var(--text-sm)">Lädt</p> <p class="text-sm-muted">Lädt</p>
</div> </div>
<hr style="margin:var(--space-3) 0;border:none;border-top:1px solid var(--c-border)"> <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> <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="file" name="file" accept="image/*" required>
<input class="form-control" type="text" name="caption" placeholder="Bildunterschrift (optional)"> <input class="form-control" type="text" name="caption" placeholder="Bildunterschrift (optional)">
@ -1739,7 +1739,7 @@ window.Page_zuchthunde = (() => {
try { try {
const photos = await API.breederPhotos.list('breeder', breederId); const photos = await API.breederPhotos.list('breeder', breederId);
if (!photos.length) { 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; return;
} }
el.innerHTML = ` el.innerHTML = `
@ -1805,7 +1805,7 @@ window.Page_zuchthunde = (() => {
}); });
} catch (err) { } catch (err) {
const el = document.getElementById(galleryId); const el = document.getElementById(galleryId);
if (el) el.innerHTML = `<p style="color:var(--c-danger)">${_esc(err.message || 'Fehler')}</p>`; if (el) el.innerHTML = `<p class="text-danger">${_esc(err.message || 'Fehler')}</p>`;
} }
} }

View file

@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark"> <meta name="color-scheme" content="light dark">
<script src="/js/landing-init.js?v=1101"></script> <script src="/js/landing-init.js?v=1102"></script>
<title>Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz</title> <title>Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz</title>
<meta name="description" content="Ban Yaro: Die kostenlose All-in-One Hunde-App für DACH. Tagebuch, Giftköder-Alarm, Training mit KI, Forum, Wurfbörse, Stammbaum, Inzucht-Check — DSGVO-konform, offline-fähig, ohne App Store."> <meta name="description" content="Ban Yaro: Die kostenlose All-in-One Hunde-App für DACH. Tagebuch, Giftköder-Alarm, Training mit KI, Forum, Wurfbörse, Stammbaum, Inzucht-Check — DSGVO-konform, offline-fähig, ohne App Store.">
<meta name="keywords" content="Hunde App, Hunde Community, Wurfbörse, Züchter, Welpen kaufen, Stammbaum Hund, Inzuchtkoeffizient, Hundezucht, Impfpass Hund, Giftköder Alarm, Gassi Community, Hundetraining App, Hunde Forum, Hunde KI, Hundefilm Datenbank, Welpen Marktplatz"> <meta name="keywords" content="Hunde App, Hunde Community, Wurfbörse, Züchter, Welpen kaufen, Stammbaum Hund, Inzuchtkoeffizient, Hundezucht, Impfpass Hund, Giftköder Alarm, Gassi Community, Hundetraining App, Hunde Forum, Hunde KI, Hundefilm Datenbank, Welpen Marktplatz">

View file

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