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
341 lines
15 KiB
JavaScript
341 lines
15 KiB
JavaScript
/* ============================================================
|
||
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 };
|
||
|
||
})();
|