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

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