Feature: Generische Seiten-Hilfe (UI.pageInfo), POI Multi-Select, Tagessprüche-DB (SW by-v654)

- UI.pageInfo(): generische Hilfe-Funktion — erstes Öffnen zeigt Info-Banner, danach ? Button oben rechts; CSS-Klassen pinfo-*
- Übungen-Seite nutzt UI.pageInfo() als erstes Beispiel
- Karte POI: Mehrfachauswahl (außer Giftköder), Kombi-Typen entfernt, type als comma-separated im Backend
- daily_quotes Tabelle in DB (346 Einträge via import_quotes.py importiert)
- GET /widget/quote — deterministischer Tagesspruch (wechselt täglich)
This commit is contained in:
rene 2026-05-03 20:10:01 +02:00
parent 1fdba57365
commit 9103c7950f
12 changed files with 623 additions and 38 deletions

View file

@ -280,6 +280,68 @@ const UI = (() => {
// Alias für ältere Aufrufe
const escHtml = escape;
// ----------------------------------------------------------
// PAGE INFO — generische Seiten-Hilfe
// config: { pageId, title, icon?, intro, steps?: [{icon,title,text}], tip? }
// Erstes Öffnen: expandierter Banner. Danach: kleines ? im Header.
// ----------------------------------------------------------
function pageInfo(container, config) {
const seenKey = 'help_seen_' + config.pageId;
const seen = !!localStorage.getItem(seenKey);
function _buildSteps() {
if (!config.steps?.length) return '';
return config.steps.map(s => `
<div class="pinfo-step">
${s.icon ? `<span class="pinfo-step-icon">${_svgIcon(s.icon)}</span>` : ''}
<div>
${s.title ? `<div class="pinfo-step-title">${s.title}</div>` : ''}
<div class="pinfo-step-text">${s.text}</div>
</div>
</div>`).join('');
}
function _openModal() {
modal.open({
title: `${_svgIcon(config.icon || 'question')} ${config.title}`,
body: `
<div class="pinfo-modal">
<p class="pinfo-intro">${config.intro}</p>
${config.steps?.length ? `<div class="pinfo-steps">${_buildSteps()}</div>` : ''}
${config.tip ? `<div class="pinfo-tip">${_svgIcon('lightbulb')} ${config.tip}</div>` : ''}
</div>`,
});
}
// Kleiner ? Button oben rechts — immer sichtbar
const headerBtn = document.createElement('button');
headerBtn.className = 'pinfo-trigger';
headerBtn.setAttribute('aria-label', 'Hilfe');
headerBtn.innerHTML = _svgIcon('question');
headerBtn.addEventListener('click', _openModal);
container.appendChild(headerBtn);
// Banner beim ersten Besuch
if (!seen) {
localStorage.setItem(seenKey, '1');
const banner = document.createElement('div');
banner.className = 'pinfo-banner';
banner.innerHTML = `
<div class="pinfo-banner-head">
<span class="pinfo-banner-icon">${_svgIcon(config.icon || 'info')}</span>
<span class="pinfo-banner-title">${config.title}</span>
<button class="pinfo-banner-close" aria-label="Schließen">${_svgIcon('x')}</button>
</div>
<div class="pinfo-banner-intro">${config.intro}</div>
${config.steps?.length ? `<div class="pinfo-steps pinfo-steps--compact">${_buildSteps()}</div>` : ''}
<button class="pinfo-banner-more">Mehr erfahren ${_svgIcon('arrow-right')}</button>
`;
banner.querySelector('.pinfo-banner-close').addEventListener('click', () => banner.remove());
banner.querySelector('.pinfo-banner-more').addEventListener('click', () => { banner.remove(); _openModal(); });
container.insertAdjacentElement('afterbegin', banner);
}
}
// ----------------------------------------------------------
// HELP TOOLTIP — inline ? Badge mit Klick-Tooltip
// ----------------------------------------------------------
@ -915,7 +977,7 @@ const UI = (() => {
emptyState, time,
setupPhotoPreview, scrollTop, skeleton,
icon: _svgIcon,
escape, escHtml, help,
escape, escHtml, help, pageInfo,
saveToAlbum,
loadLeaflet,
leafletMarker,