banyaro/backend/static/js/pages/welcome.js
rene 553e9e7854 Sprint 12+13: Tagebuch Day-One-Redesign, Notiz-Feature, Icon-Fixes, SW by-v405
Tagebuch:
- Day-One-Listenansicht: Wochentag + Tageszahl + Meta-Zeile (Zeit/Ort/Wetter)
- 4 Ansichten: Liste, Medien-Mosaik, Kalender (mit Sprungbuttons), Karte (GPS-Marker)
- Detail-Ansicht inline im Content-Bereich (kein Fullscreen-Overlay mehr)
- Hero-Bild vollständig sichtbar (object-fit:contain), Lightbox mit Safe-Area
- 2-Spalten-Layout Desktop: Text + Leaflet-Karte + POI-Liste
- EXIF-GPS-Extraktion bei Foto-Upload, historisches Wetter via Archive-API
- NoteStation-Import: Fotos in diary_media (80 Einträge migriert, 94 Medien)
- Stats-Endpoints: /diary/stats, /diary/calendar, /diary/locations

Notiz-Feature:
- Generische notes-Tabelle (parent_type + parent_id + meta_json)
- 📝-Button in 8 Bereichen, Notizblock-Seite mit KI-Analyse
- KI-Toggle in Einstellungen, notes_ki_enabled in User-Profil

Icons & Design:
- fill:currentColor Fix für welcome/onboarding/friends.js
- --c-icon Variable, --c-text-muted Dark Mode aufgehellt
- 15+ neue Phosphor-Icons aus lokaler Kopie
- CSS Network-First im SW, Cache-Control-Middleware

Infrastruktur:
- Wiki-Anreicherungs-Scheduler-Jobs entfernt (abgeschlossen)
- auth.py: notes_ki_enabled + is_social_media im User-Response
2026-04-25 20:44:46 +02:00

341 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ============================================================
BAN YARO — Willkommensseite
Über die App, Features, Installations-Anleitung.
============================================================ */
window.Page_welcome = (() => {
let _container = null;
let _appState = null;
// ----------------------------------------------------------
// INIT
// ----------------------------------------------------------
async function init(container, appState) {
_container = container;
_appState = appState;
_render();
}
function refresh() { _render(); }
function onDogChange() {}
// ----------------------------------------------------------
// RENDER
// ----------------------------------------------------------
function _render() {
const isInstalled = window.matchMedia('(display-mode: standalone)').matches
|| window.navigator.standalone === true;
_container.innerHTML = `
<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)">
<img src="/icons/icon-180.png" alt="Ban Yaro"
style="width:88px;height:88px;border-radius:var(--radius-xl);
box-shadow:var(--shadow-md);margin:0 auto var(--space-4);display:block">
<h1 style="font-size:var(--text-2xl);font-weight:var(--weight-bold);
color:var(--c-text);margin:0 0 var(--space-2)">Ban Yaro</h1>
<p style="font-size:var(--text-base);color:var(--c-text-secondary);
margin:0;line-height:1.5">
Die Plattform für Hundebesitzer —<br>Tagebuch, Gesundheit, Community und mehr.
</p>
</div>
<!-- Burger-Menü-Hinweis (nur Mobile, auf Desktop gibt es die Sidebar) -->
<button id="welcome-menu-hint" class="mobile-only"
style="display:flex;align-items:center;gap:var(--space-3);
background:var(--c-primary-subtle);border-radius:var(--radius-md);
padding:var(--space-3) var(--space-4);margin-bottom:var(--space-5);
border:none;width:100%;text-align:left;cursor:pointer">
<div id="welcome-burger-icon"
style="width:36px;height:36px;border-radius:var(--radius-md);
background:var(--c-primary);flex-shrink:0;
display:flex;align-items:center;justify-content:center">
<svg style="fill:currentColor;width:20px;height:20px;color:#fff" aria-hidden="true">
<use href="/icons/phosphor.svg#list"></use>
</svg>
</div>
<div>
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
color:var(--c-primary);margin-bottom:2px">
Alle Funktionen im Menü oben rechts
</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);line-height:1.4">
Tippe auf das ☰-Symbol für Karte, Routen, Giftköder-Alarm, Forum und vieles mehr.
</div>
</div>
</button>
<!-- 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'],
['target', 'Übungen', 'Trainingsübungen mit KI-Unterstützung', 'uebungen'],
['clipboard-text', 'Trainingspläne', 'Strukturierte Pläne für jedes Lernziel', 'trainingsplaene'],
])}
${_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'],
])}
${_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'],
])}
${_featureCard('Community', [
['warning-octagon', 'Giftköder-Alarm', 'Warnungen sofort melden und empfangen', 'poison'],
['paw-print', 'Gassi-Treffen', 'Hunde-Dates mit anderen Besitzern', 'walks'],
['house-line', 'Sitting', 'Sitter finden oder selbst anbieten', 'sitting'],
['push-pin', 'Forum', 'Diskussionen, Tipps und Austausch', 'forum'],
['magnifying-glass', 'Verlorene Hunde', 'Hilf vermisste Hunde zu finden', 'lost'],
])}
${_featureCard('Wissen', [
['books', 'Wiki', 'Rassendatenbank, Gesundheits-Wiki, Quiz', 'wiki'],
['handshake', 'Knigge', 'Regeln, Begegnungen, Leinenpflicht', 'knigge'],
['film-slate', 'Filme', 'Stirbt der Hund? Die wichtigste Frage', 'movies'],
['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);
font-size:var(--text-xs);font-weight:600;
color:var(--c-text-secondary);text-transform:uppercase;
letter-spacing:0.05em;border-bottom:1px solid var(--c-border)">
App installieren
</div>
<div style="padding:var(--space-4)">
${isInstalled
? `<div style="display:flex;gap:var(--space-3);align-items:center;
color:var(--c-success)">
<svg style="width:20px;height:20px;flex-shrink:0" aria-hidden="true">
<use href="/icons/phosphor.svg#check"></use>
</svg>
<span style="font-size:var(--text-sm);font-weight:var(--weight-semibold)">
Ban Yaro ist bereits installiert.
</span>
</div>`
: _installHTML()
}
</div>
</div>
<!-- CTA wenn nicht eingeloggt -->
${!_appState.user ? `
<div style="display:flex;flex-direction:column;gap:var(--space-3)">
<button class="btn btn-primary" id="welcome-register-btn">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#user-plus"></use></svg>
Kostenlos registrieren
</button>
<button class="btn btn-ghost" id="welcome-login-btn">
Bereits registriert? Anmelden
</button>
</div>
` : ''}
<!-- Footer -->
<p style="text-align:center;font-size:var(--text-xs);color:var(--c-text-muted);
margin-top:var(--space-6)">
Ban Yaro · Deine Daten auf eigenem Server in Deutschland.
</p>
</div>
`;
_bindEvents();
}
// ----------------------------------------------------------
// INSTALLATIONS-ANLEITUNG (plattformabhängig)
// ----------------------------------------------------------
function _installHTML() {
const ua = navigator.userAgent;
const isIOS = /iPad|iPhone|iPod/.test(ua) && !window.MSStream;
const isSafari = /^((?!chrome|android).)*safari/i.test(ua);
const isAndroid = /android/i.test(ua);
const hasPrompt = !!App.getInstallPrompt();
// Android/Chrome mit nativem Prompt
if ((isAndroid || hasPrompt) && hasPrompt) {
return `
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);
margin:0 0 var(--space-3);line-height:1.5">
Installiere Ban Yaro direkt auf deinem Gerät — kein App Store nötig.
Die App verhält sich wie eine native App und funktioniert auch offline.
</p>
<button class="btn btn-primary" id="install-android-btn" style="width:100%">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#download-simple"></use></svg>
Ban Yaro installieren
</button>
`;
}
// iOS Safari
if (isIOS && isSafari) {
return `
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);
margin:0 0 var(--space-4);line-height:1.5">
Installiere Ban Yaro auf deinem iPhone oder iPad:
</p>
${_steps([
['share', 'Tippe auf das <strong>Teilen-Symbol</strong> in Safari (Rechteck mit Pfeil nach oben)'],
['plus', 'Scrolle nach unten und tippe auf <strong>„Zum Home-Bildschirm"</strong>'],
['check', 'Tippe rechts oben auf <strong>„Hinzufügen"</strong> — fertig!'],
])}
<p style="font-size:var(--text-xs);color:var(--c-text-muted);margin:var(--space-3) 0 0">
Funktioniert nur in Safari, nicht in anderen Browsern auf iOS.
</p>
`;
}
// Desktop oder andere Browser
return `
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);
margin:0 0 var(--space-4);line-height:1.5">
Ban Yaro lässt sich in Chrome, Edge und anderen modernen Browsern installieren:
</p>
${_steps([
['globe', 'Öffne Ban Yaro in <strong>Chrome</strong> oder <strong>Edge</strong>'],
['download-simple','Klicke in der Adressleiste auf das <strong>Installieren-Symbol</strong> (↓ mit Kreis)'],
['check', 'Bestätige die Installation — Ban Yaro öffnet sich dann wie eine Desktop-App'],
])}
<p style="font-size:var(--text-xs);color:var(--c-text-muted);margin:var(--space-3) 0 0">
Auf Android: Menü (⋮) → <strong>„App installieren"</strong> oder
<strong>„Zum Startbildschirm hinzufügen"</strong>.
</p>
`;
}
function _featureCard(heading, items) {
return `
<div class="card" style="margin-bottom:var(--space-5)">
<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;
letter-spacing:0.05em;border-bottom:1px solid var(--c-border)">
${heading}
</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:0">
${items.map(([icon, title, desc, page], i) => `
<button data-nav="${page}"
style="display:flex;gap:var(--space-3);align-items:flex-start;
padding:var(--space-4);text-align:left;background:none;border:none;
cursor:pointer;width:100%;transition:background var(--transition-fast);
${i % 2 === 0 ? 'border-right:1px solid var(--c-border);' : ''}
${i < items.length - (items.length % 2 === 0 ? 2 : 1) ? 'border-bottom:1px solid var(--c-border);' : ''}">
<div style="width:34px;height:34px;border-radius:var(--radius-md);
background:var(--c-primary-subtle);flex-shrink:0;
display:flex;align-items:center;justify-content:center">
<svg style="fill:currentColor;width:18px;height:18px;color:var(--c-primary)" aria-hidden="true">
<use href="/icons/phosphor.svg#${icon}"></use>
</svg>
</div>
<div>
<div style="font-size:var(--text-sm);font-weight:var(--weight-semibold);
color:var(--c-text);margin-bottom:2px">${title}</div>
<div style="font-size:var(--text-xs);color:var(--c-text-secondary);
line-height:1.4">${desc}</div>
</div>
</button>
`).join('')}
</div>
</div>`;
}
function _steps(list) {
return `
<ol style="margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:var(--space-3)">
${list.map(([icon, text], i) => `
<li style="display:flex;gap:var(--space-3);align-items:flex-start">
<div style="width:28px;height:28px;border-radius:50%;flex-shrink:0;
background:var(--c-primary);color:#fff;
display:flex;align-items:center;justify-content:center;
font-size:var(--text-xs);font-weight:var(--weight-bold)">
${i + 1}
</div>
<span style="font-size:var(--text-sm);color:var(--c-text);line-height:1.5;
padding-top:4px">${text}</span>
</li>
`).join('')}
</ol>
`;
}
// ----------------------------------------------------------
// EVENTS
// ----------------------------------------------------------
function _bindEvents() {
// Android-Install-Button
_container.querySelector('#install-android-btn')?.addEventListener('click', async () => {
const prompt = App.getInstallPrompt();
if (!prompt) return;
prompt.prompt();
const { outcome } = await prompt.userChoice;
if (outcome === 'accepted') {
UI.toast.success('Ban Yaro wird installiert!');
_render(); // zeigt "bereits installiert"
}
});
// CTAs für nicht-eingeloggte User
_container.querySelector('#welcome-register-btn')?.addEventListener('click', () => {
App.navigate('settings');
});
_container.querySelector('#welcome-login-btn')?.addEventListener('click', () => {
App.navigate('settings');
});
// Feature-Kacheln → navigieren
_container.querySelectorAll('[data-nav]').forEach(btn => {
btn.addEventListener('click', () => App.navigate(btn.dataset.nav));
btn.addEventListener('mouseenter', () => btn.style.background = 'var(--c-surface-2)');
btn.addEventListener('mouseleave', () => btn.style.background = '');
});
// Burger-Menü-Hinweis öffnet die Sidebar
_container.querySelector('#welcome-menu-hint')?.addEventListener('click', () => {
document.getElementById('header-menu-btn')?.click();
});
// Hamburger-Button 3× kurz pulsieren lassen
_pulseMenuBtn();
}
function _pulseMenuBtn() {
const btn = document.getElementById('header-menu-btn');
if (!btn) return;
if (!document.getElementById('welcome-pulse-style')) {
const s = document.createElement('style');
s.id = 'welcome-pulse-style';
s.textContent = `
@keyframes wc-pulse {
0%,100% { transform: scale(1); box-shadow: none; }
50% { transform: scale(1.25); box-shadow: 0 0 0 6px var(--c-primary-subtle); }
}
.wc-pulsing { animation: wc-pulse 0.6s ease-in-out 3; border-radius: var(--radius-md); }
`;
document.head.appendChild(s);
}
setTimeout(() => {
btn.classList.add('wc-pulsing');
btn.addEventListener('animationend', () => btn.classList.remove('wc-pulsing'), { once: true });
}, 800);
}
// ----------------------------------------------------------
// PUBLIC
// ----------------------------------------------------------
return { init, refresh, onDogChange };
})();