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

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