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

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