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

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