Session 2026-04-23: Desktop Multi-Column, Forum, Fixes, Analytics

- Desktop ≥1024px: page-container 680→860px
- Walks: Liste+Karte nebeneinander, View-Toggle ausgeblendet
- Forum: Rubriken 2-zeilig via CSS Grid (ceil(n/2) Spalten, zentriert)
- Welcome: max-width 920px, Feature-Sections 2-spaltig
- Wissen: Toggle-Mechanismus entfernt, Items immer sichtbar
- Übungen Plan-Karten: vertikal statt horizontal gestapelt
- Admin Analytics: Umami v2 gibt plain numbers statt {value:X}
- CSS-Spezifität: #page-forum nötig wegen layout.css < components.css
- SW by-v312, APP_VER 300
This commit is contained in:
rene 2026-04-23 17:52:28 +02:00
parent 44081a6b9d
commit 71a1371b44
10 changed files with 149 additions and 72 deletions

View file

@ -210,6 +210,7 @@
}
.by-tabs::-webkit-scrollbar { display: none; }
.by-tab {
flex-shrink: 0;
padding: var(--space-2) var(--space-3);

View file

@ -567,6 +567,89 @@
.grid-3 { grid-template-columns: 1fr 1fr; }
}
/* ============================================================
Desktop Multi-Column Layouts (min-width: 1024px)
============================================================ */
@media (min-width: 1024px) {
/* Etwas breiterer Standard-Container auf großen Screens */
.page-container { max-width: 860px; }
/* ----------------------------------------------------------
WELCOME: 2-spaltige Feature-Sections, zentrierter Hero
---------------------------------------------------------- */
.welcome-layout { max-width: 920px; }
.welcome-sections {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-4);
align-items: start;
}
.welcome-sections .card { margin-bottom: 0; }
/* ----------------------------------------------------------
WALKS: Liste (links) + Karte (rechts) nebeneinander
---------------------------------------------------------- */
#page-walks .page-container { max-width: none; }
.walks-layout {
flex-direction: row;
align-items: stretch;
}
/* View-Toggle auf Desktop nicht nötig */
.walks-view-toggle { display: none; }
/* Beide Panels immer sichtbar */
#walks-list-view,
#walks-map-view {
display: flex !important;
flex-direction: column;
}
#walks-list-view {
width: 340px;
flex-shrink: 0;
border-right: 1px solid var(--c-border-light);
}
#walks-map-view { flex: 1; }
/* Toolbar-Zeile kompakter da Toggle wegfällt */
#page-walks .by-toolbar { padding: var(--space-2) var(--space-4); }
/* ----------------------------------------------------------
FORUM: Rubriken über volle Breite, Threads darunter
---------------------------------------------------------- */
#page-forum .page-container { max-width: 1100px; }
/* Rubriken auf genau 2 Zeilen verteilen, zentriert
#page-forum nötig für Spezifität > .by-tabs (components.css lädt später) */
#page-forum .forum-category-tabs {
display: grid;
grid-template-columns: repeat(var(--forum-tab-cols, 7), minmax(0, 1fr));
overflow: visible;
gap: var(--space-1);
padding-bottom: var(--space-2);
}
#page-forum .forum-category-tabs .by-tab {
justify-content: center;
text-align: center;
white-space: nowrap;
}
/* Suche + Threads volle Breite */
.forum-main-col {
display: flex;
flex-direction: column;
gap: var(--space-3);
}
}
/* ============================================================
Phosphor Icons SVG Sprite
============================================================ */

View file

@ -169,23 +169,18 @@
<span class="sidebar-item-badge" id="lost-badge" style="display:none">0</span>
</div>
<button class="sidebar-section-toggle" id="wissen-toggle" aria-expanded="false">
<span>Wissen</span>
<svg class="ph-icon wissen-caret" aria-hidden="true"><use href="/icons/phosphor.svg#caret-right"></use></svg>
</button>
<div class="sidebar-section-body" id="wissen-body">
<div class="sidebar-item" data-page="wiki">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#books"></use></svg> Wiki
</div>
<div class="sidebar-item" data-page="knigge">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#handshake"></use></svg> Knigge
</div>
<div class="sidebar-item" data-page="movies">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#film-slate"></use></svg> Filme
</div>
<div class="sidebar-item" data-page="erste-hilfe">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#first-aid"></use></svg> Erste Hilfe
</div>
<span class="sidebar-section-label">Wissen</span>
<div class="sidebar-item" data-page="wiki">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#books"></use></svg> Wiki
</div>
<div class="sidebar-item" data-page="knigge">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#handshake"></use></svg> Knigge
</div>
<div class="sidebar-item" data-page="movies">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#film-slate"></use></svg> Filme
</div>
<div class="sidebar-item" data-page="erste-hilfe">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#first-aid"></use></svg> Erste Hilfe
</div>
<div class="sidebar-item" data-page="admin" id="sidebar-admin"

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
const APP_VER = '290'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VER = '300'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const App = (() => {
@ -103,7 +103,6 @@ const App = (() => {
state.page = pageId;
UI.scrollTop();
_expandWissenIfActive(pageId);
// Seiten-Modul lazy laden (einmalig)
_loadPage(pageId, params);
@ -310,12 +309,6 @@ const App = (() => {
return;
}
// Wissen-Toggle aufklappen/zuklappen
if (e.target.closest('#wissen-toggle')) {
_toggleWissen();
return;
}
// Sidebar-Item auf Mobile → schließen nach Navigation
if (e.target.closest('#sidebar .sidebar-item')) {
_closeSidebar();
@ -353,22 +346,6 @@ const App = (() => {
document.getElementById('sidebar-backdrop')?.classList.remove('visible');
}
const _WISSEN_PAGES = new Set(['wiki', 'knigge', 'movies', 'erste-hilfe']);
function _toggleWissen(force) {
const toggle = document.getElementById('wissen-toggle');
const body = document.getElementById('wissen-body');
if (!toggle || !body) return;
const open = force !== undefined ? force : toggle.getAttribute('aria-expanded') !== 'true';
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
body.classList.toggle('open', open);
try { localStorage.setItem('by_wissen_open', open ? '1' : '0'); } catch (_) {}
}
function _expandWissenIfActive(page) {
if (_WISSEN_PAGES.has(page)) _toggleWissen(true);
}
// ----------------------------------------------------------
// SCHNELL-HINZUFÜGEN (+ Button)
// ----------------------------------------------------------
@ -770,10 +747,7 @@ const App = (() => {
_bindNavigation();
// Wissen-Sektion: gespeicherten Zustand wiederherstellen
try {
if (localStorage.getItem('by_wissen_open') === '1') _toggleWissen(true);
} catch (_) {}
try { localStorage.removeItem('by_wissen_open'); } catch (_) {}
await _checkAuth();

View file

@ -114,17 +114,20 @@ window.Page_admin = (() => {
</svg>`;
}
const tv = v => v?.value ?? 0;
// Umami v2 liefert plain numbers, v1 liefert {value: X} — beide abfangen
const tv = v => (v != null && typeof v === 'object') ? (v.value ?? 0) : (v ?? 0);
const fmt = v => Number(v).toLocaleString('de');
// Bounce Rate & Verweildauer
const bounceToday = d.today?.bounceRate?.value != null
? (d.today.bounceRate.value * 100).toFixed(0) + ' %'
: (d.today?.bounces?.value != null && d.today?.visits?.value > 0
? ((d.today.bounces.value / d.today.visits.value) * 100).toFixed(0) + ' %'
: '—');
const timeWeek = d.week?.totaltime?.value > 0 && d.week?.visits?.value > 0
? Math.round(d.week.totaltime.value / d.week.visits.value) + ' s'
const _bounces = tv(d.today?.bounces);
const _visits = tv(d.today?.visits);
const bounceToday = _visits > 0
? ((_bounces / _visits) * 100).toFixed(0) + ' %'
: '—';
const _totaltime = tv(d.week?.totaltime);
const _visitsW = tv(d.week?.visits);
const timeWeek = _totaltime > 0 && _visitsW > 0
? Math.round(_totaltime / _visitsW) + ' s'
: '—';
el.innerHTML = `

View file

@ -104,22 +104,31 @@ window.Page_forum = (() => {
data-section="map">${UI.icon('users')} Mitgliederkarte</button>
</div>
<!-- Suchleiste -->
<div class="forum-search-wrap">
<input type="search" class="forum-search" id="forum-search"
placeholder="Forum durchsuchen…" autocomplete="off">
</div>
<!-- Rechte Spalte: Suche + Threads -->
<div class="forum-main-col">
<div class="forum-search-wrap">
<input type="search" class="forum-search" id="forum-search"
placeholder="Forum durchsuchen…" autocomplete="off">
</div>
<!-- Thread-Liste / Karte / Suche -->
<div id="forum-main">
<p style="color:var(--c-text-secondary);text-align:center;padding:var(--space-8)">Lädt</p>
</div>
<!-- Thread-Liste / Karte / Suche -->
<div id="forum-main">
<p style="color:var(--c-text-secondary);text-align:center;padding:var(--space-8)">Lädt</p>
</div>
</div>
`;
// Desktop: Tabs gleichmäßig auf 2 Zeilen verteilen
const _tabsEl = document.getElementById('forum-tabs');
const _tabCount = _tabsEl.querySelectorAll('.by-tab').length;
_tabsEl.style.setProperty('--forum-tab-cols', Math.ceil(_tabCount / 2));
// Tab-Klicks
document.getElementById('forum-tabs').addEventListener('click', e => {
_tabsEl.addEventListener('click', e => {
const btn = e.target.closest('[data-kat], [data-section]');
if (!btn) return;

View file

@ -632,7 +632,7 @@ window.Page_uebungen = (() => {
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
color:var(--c-text);line-height:1.3">${_esc(r.exercise_name)}</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;align-items:center;justify-content:space-between;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>
<span style="font-size:var(--text-xs);color:var(--c-text-secondary)">${r.suggested_reps}× empfohlen</span>
<span style="font-size:var(--text-xs);font-weight:700;color:${trendColor};margin-left:4px">${trend}</span>
@ -640,7 +640,7 @@ window.Page_uebungen = (() => {
</div>
<button class="ueb-trainer-btn btn btn-primary"
data-tab="${_esc(r.tab)}" data-name="${_esc(r.exercise_name)}" data-reps="${r.suggested_reps}"
style="font-size:var(--text-xs);padding:4px 10px;flex-shrink:0">
style="font-size:var(--text-xs);padding:4px 10px;width:100%">
Üben
</button>
</div>
@ -656,7 +656,7 @@ window.Page_uebungen = (() => {
Dein Plan für heute
</span>
</div>
<div style="display:flex;gap:var(--space-2);align-items:stretch">
<div style="display:flex;flex-direction:column;gap:var(--space-2)">
${cards.join('')}
</div>`;

View file

@ -111,6 +111,10 @@ window.Page_walks = (() => {
_view = view;
document.querySelectorAll('.walks-view-btn').forEach(b =>
b.classList.toggle('active', b.dataset.view === view));
// Desktop: beide Panels bleiben via CSS sichtbar
if (window.innerWidth >= 1024) return;
document.getElementById('walks-list-view').style.display = view === 'liste' ? '' : 'none';
document.getElementById('walks-map-view').style.display = view === 'karte' ? '' : 'none';
@ -134,6 +138,14 @@ window.Page_walks = (() => {
);
_renderList();
_renderMarkers();
// Desktop: Karte direkt initialisieren (beide Panels sichtbar)
if (window.innerWidth >= 1024) {
UI.loadLeaflet().then(() => {
_initMap();
setTimeout(() => _map?.invalidateSize(), 150);
setTimeout(() => _map?.invalidateSize(), 400);
});
}
} catch (err) {
UI.toast.error(err.message || 'Fehler beim Laden.');
}

View file

@ -28,7 +28,7 @@ window.Page_welcome = (() => {
|| window.navigator.standalone === true;
_container.innerHTML = `
<div style="max-width:480px;margin:0 auto;padding:var(--space-6) var(--space-4) var(--space-8)">
<div class="welcome-layout" style="margin:0 auto;padding:var(--space-6) var(--space-4) var(--space-8)">
<!-- Hero -->
<div style="text-align:center;margin-bottom:var(--space-8)">
@ -68,7 +68,9 @@ window.Page_welcome = (() => {
</div>
</button>
<!-- Features: Mein Hund -->
<!-- Feature-Abschnitte (auf Desktop 2-spaltig) -->
<div class="welcome-sections">
${_featureCard('Mein Hund', [
['book-open', 'Tagebuch', 'Momente, Fotos und Meilensteine festhalten', 'diary'],
['first-aid', 'Gesundheit', 'Impfungen, Tierarztbesuche & Medikamente', 'health'],
@ -76,21 +78,18 @@ window.Page_welcome = (() => {
['clipboard-text', 'Trainingspläne', 'Strukturierte Pläne für jedes Lernziel', 'trainingsplaene'],
])}
<!-- Features: Entdecken -->
${_featureCard('Entdecken', [
['map-trifold', 'Karte', 'Orte, Routen und Meldungen in der Nähe', 'map'],
['path', 'Routen', 'GPS-Routen aufzeichnen und bewerten', 'routes'],
['calendar-dots', 'Events', 'Turniere und Veranstaltungen', 'events'],
])}
<!-- Features: Soziales -->
${_featureCard('Soziales', [
['users', 'Freunde', 'Verbinde dich mit anderen Hundebesitzern', 'friends'],
['chat-circle-dots', 'Nachrichten', 'Private Chats mit deinen Freunden', 'chat'],
['bell', 'Aktuelles', 'Benachrichtigungen und Neuigkeiten', 'notifications'],
])}
<!-- Features: Community -->
${_featureCard('Community', [
['warning-octagon', 'Giftköder-Alarm', 'Warnungen sofort melden und empfangen', 'poison'],
['paw-print', 'Gassi-Treffen', 'Hunde-Dates mit anderen Besitzern', 'walks'],
@ -99,7 +98,6 @@ window.Page_welcome = (() => {
['magnifying-glass', 'Verlorene Hunde', 'Hilf vermisste Hunde zu finden', 'lost'],
])}
<!-- Features: Wissen -->
${_featureCard('Wissen', [
['books', 'Wiki', 'Rassendatenbank, Gesundheits-Wiki, Quiz', 'wiki'],
['handshake', 'Knigge', 'Regeln, Begegnungen, Leinenpflicht', 'knigge'],
@ -107,6 +105,8 @@ window.Page_welcome = (() => {
['first-aid', 'Erste Hilfe','Notfallratgeber für häufige Situationen', 'erste-hilfe'],
])}
</div>
<!-- App installieren -->
<div class="card" style="margin-bottom:var(--space-5)" id="install-section">
<div style="padding:var(--space-3) var(--space-4);

View file

@ -3,7 +3,7 @@
Offline-Cache + Push Notifications + Tile-Cache
============================================================ */
const CACHE_VERSION = 'by-v302';
const CACHE_VERSION = 'by-v312';
const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten