banyaro/backend/static/js/pages/welcome.js
rene 9cb4a16cc2 UX: Installationsanleitung mobile-first, alle Plattformen — SW by-v443, APP_VER 422
Alle 5 Fälle abgedeckt: Android+Prompt (Button), Android ohne Prompt (Chrome-Schritte),
iOS Safari (Teilen-Menü), iOS non-Safari (Hinweis + Link kopieren), Desktop (Tabs
Android/iOS). Steps mit Icon statt Zahl. Link-kopieren-Button für manuelle Fälle.
2026-04-27 18:22:10 +02:00

400 lines
17 KiB
JavaScript

/* ============================================================
BAN YARO — Willkommensseite
============================================================ */
window.Page_welcome = (() => {
let _container = null;
let _appState = null;
let _showInstall = false;
async function init(container, appState, params = {}) {
_container = container;
_appState = appState;
_showInstall = !!params.install;
_render();
if (_showInstall) {
setTimeout(() => {
_container.querySelector('.wc-install-card')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, 100);
}
}
function refresh() { _render(); }
function onDogChange() {}
// ----------------------------------------------------------
// FEATURES — Icon, Titel, Beschreibung, Zielseite
// ----------------------------------------------------------
const FEATURES = [
{ icon: 'book-open', label: 'Tagebuch', page: 'diary' },
{ icon: 'first-aid', label: 'Gesundheit', page: 'health' },
{ icon: 'map-trifold', label: 'Karte', page: 'map' },
{ icon: 'path', label: 'Routen', page: 'routes' },
{ icon: 'target', label: 'Training', page: 'uebungen' },
{ icon: 'warning-octagon', label: 'Giftköder', page: 'poison' },
{ icon: 'users', label: 'Freunde', page: 'friends' },
{ icon: 'chat-circle-dots', label: 'Nachrichten', page: 'chat' },
{ icon: 'paw-print', label: 'Gassi-Treffen', page: 'walks' },
{ icon: 'house-line', label: 'Sitting', page: 'sitting' },
{ icon: 'push-pin', label: 'Forum', page: 'forum' },
{ icon: 'books', label: 'Rassen-Wiki', page: 'wiki' },
{ icon: 'calendar-dots', label: 'Events', page: 'events' },
{ icon: 'bell', label: 'Neuigkeiten', page: 'notifications' },
{ icon: 'handshake', label: 'Knigge', page: 'knigge' },
{ icon: 'magnifying-glass', label: 'Vermisste', page: 'lost' },
];
// ----------------------------------------------------------
// RENDER
// ----------------------------------------------------------
function _render() {
const isInstalled = window.matchMedia('(display-mode: standalone)').matches
|| window.navigator.standalone === true;
const user = _appState?.user;
_container.innerHTML = `
<div class="welcome-layout" style="margin:0 auto">
<!-- ── Hero ─────────────────────────────────────────── -->
<div class="wc-hero">
<img src="/icons/icon-180.png" alt="Ban Yaro" class="wc-hero-icon">
<h1 class="wc-hero-title">Ban Yaro</h1>
<p class="wc-hero-sub">
${user
? `Schön, dass du wieder da bist${user.name ? ', <strong>' + UI.escape(user.name) + '</strong>' : ''}! 🐾`
: 'Die Community für Hundebesitzer — Tagebuch, Gesundheit, Karte und mehr.'}
</p>
${!user ? `
<div class="wc-hero-cta">
<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">Anmelden</button>
</div>
` : ''}
</div>
<!-- ── Feature-Grid ──────────────────────────────────── -->
<div class="page-container" style="padding:var(--space-6) var(--space-4)">
<h2 class="wc-section-title">Was Ban Yaro kann</h2>
<div class="wc-grid">
${FEATURES.map(f => `
<button class="wc-tile" data-nav="${f.page}">
<div class="wc-tile-icon">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#${f.icon}"></use></svg>
</div>
<span class="wc-tile-label">${f.label}</span>
</button>
`).join('')}
</div>
<!-- ── App installieren ──────────────────────────── -->
${(!isInstalled || _showInstall) ? `
<div class="card wc-install-card">
<div class="wc-install-header">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#download-simple"></use></svg>
App installieren
</div>
<div style="padding:var(--space-4)">
${_installHTML()}
</div>
</div>
` : ''}
<!-- ── Footer ────────────────────────────────────── -->
<p class="wc-footer">
Ban Yaro · Deine Daten auf eigenem Server in Deutschland
</p>
</div>
</div>
`;
_injectStyles();
_bindEvents();
_pulseMenuBtn();
}
// ----------------------------------------------------------
// STYLES (einmalig injizieren)
// ----------------------------------------------------------
function _injectStyles() {
if (document.getElementById('wc-styles')) return;
const s = document.createElement('style');
s.id = 'wc-styles';
s.textContent = `
/* Hero */
.wc-hero {
background: linear-gradient(160deg, var(--c-primary-subtle) 0%, var(--c-bg) 70%);
text-align: center;
padding: var(--space-8) var(--space-4) var(--space-6);
border-bottom: 1px solid var(--c-border-light);
}
.wc-hero-icon {
width: 96px; height: 96px;
border-radius: 22px;
box-shadow: 0 8px 28px rgba(0,0,0,0.15);
display: block; margin: 0 auto var(--space-4);
}
.wc-hero-title {
font-size: var(--text-2xl); font-weight: var(--weight-bold);
color: var(--c-text); margin: 0 0 var(--space-2);
}
.wc-hero-sub {
font-size: var(--text-base); color: var(--c-text-secondary);
margin: 0; line-height: 1.6; max-width: 420px; margin: 0 auto;
}
.wc-hero-cta {
display: flex; gap: var(--space-3); justify-content: center;
flex-wrap: wrap; margin-top: var(--space-5);
}
/* Section title */
.wc-section-title {
font-size: var(--text-sm); font-weight: var(--weight-semibold);
color: var(--c-text-secondary); text-transform: uppercase;
letter-spacing: 0.06em; margin: 0 0 var(--space-4);
}
/* Feature Grid */
.wc-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: var(--space-3);
margin-bottom: var(--space-6);
}
.wc-tile {
display: flex; flex-direction: column;
align-items: center; gap: var(--space-2);
padding: var(--space-4) var(--space-2);
background: var(--c-surface);
border: 1px solid var(--c-border-light);
border-radius: var(--radius-lg);
cursor: pointer; transition: background var(--transition-fast), transform 0.1s;
}
.wc-tile:hover { background: var(--c-surface-2); }
.wc-tile:active { transform: scale(0.96); }
.wc-tile-icon {
width: 44px; height: 44px; border-radius: var(--radius-md);
background: var(--c-primary-subtle);
display: flex; align-items: center; justify-content: center;
}
.wc-tile-icon .ph-icon {
width: 22px; height: 22px; color: var(--c-primary);
}
.wc-tile-label {
font-size: var(--text-xs); font-weight: var(--weight-semibold);
color: var(--c-text); text-align: center; line-height: 1.3;
}
/* Install card */
.wc-install-card { margin-bottom: var(--space-5); }
.wc-install-header {
display: flex; align-items: center; gap: var(--space-2);
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);
}
.wc-install-header .ph-icon { width: 14px; height: 14px; }
/* Footer */
.wc-footer {
text-align: center; font-size: var(--text-xs);
color: var(--c-text-muted); margin: 0;
}
/* Desktop: 8-spaltig */
@media (min-width: 1024px) {
.wc-grid { grid-template-columns: repeat(8, 1fr); }
}
/* Pulse animation */
@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);
}
// ----------------------------------------------------------
// INSTALLATIONS-ANLEITUNG
// ----------------------------------------------------------
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 isMobile = isIOS || isAndroid;
const hasPrompt = !!App.getInstallPrompt();
// Android: Browser hat Install-Prompt bereit (Chrome, Edge, Samsung Internet neu)
if (hasPrompt) {
return `
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);margin:0 0 var(--space-3);line-height:1.5">
Kein App Store nötig — direkt auf den Home-Bildschirm.
</p>
<button class="btn btn-primary" id="install-android-btn" style="width:100%;margin-bottom:var(--space-2)">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#download-simple"></use></svg>
Ban Yaro installieren
</button>`;
}
// iOS Safari: Teilen-Menü
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">
So kommt Ban Yaro auf deinen Home-Bildschirm:
</p>
${_steps([
['share', 'Tippe unten in Safari auf das <strong>Teilen-Symbol</strong> <svg class="ph-icon" style="width:14px;height:14px;vertical-align:-2px"><use href="/icons/phosphor.svg#share"></use></svg>'],
['rows-plus-top', 'Wähle <strong>„Zum Home-Bildschirm"</strong> aus der Liste'],
['check', 'Tippe oben rechts auf <strong>„Hinzufügen"</strong>'],
])}
<p style="font-size:var(--text-xs);color:var(--c-text-muted);margin:var(--space-4) 0 0">
Funktioniert nur in Safari — nicht in Chrome oder Firefox auf iOS.
</p>`;
}
// iOS, aber nicht Safari (Chrome, Firefox, etc.)
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">
Auf dem iPhone geht die Installation nur über <strong>Safari</strong>.
</p>
${_steps([
['safari-logo', 'Öffne <strong>Safari</strong> auf deinem iPhone'],
['arrow-square-in','Gib <strong>banyaro.app</strong> in die Adressleiste ein'],
['share', 'Tippe auf das Teilen-Symbol und wähle <strong>„Zum Home-Bildschirm"</strong>'],
])}
<button class="btn btn-ghost btn-sm" id="install-copy-btn"
style="margin-top:var(--space-3);width:100%">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#copy"></use></svg>
Link kopieren
</button>`;
}
// Android ohne Prompt (Firefox, älterer Samsung Browser, etc.)
if (isAndroid) {
return `
<p style="font-size:var(--text-sm);color:var(--c-text-secondary);margin:0 0 var(--space-4);line-height:1.5">
Am einfachsten geht es mit <strong>Chrome</strong>:
</p>
${_steps([
['arrow-square-in', 'Öffne <strong>banyaro.app</strong> in Chrome'],
['dots-three-circle','Tippe auf das <strong>Menü ⋮</strong> oben rechts'],
['download-simple', 'Wähle <strong>„App installieren"</strong> oder<br>„Zum Startbildschirm hinzufügen"'],
])}
<button class="btn btn-ghost btn-sm" id="install-copy-btn"
style="margin-top:var(--space-3);width:100%">
<svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#copy"></use></svg>
Link kopieren
</button>`;
}
// Desktop — beide Plattformen zeigen
return `
<div style="display:flex;gap:var(--space-2);margin-bottom:var(--space-4)">
<button class="btn btn-sm" id="inst-tab-android"
style="flex:1;background:var(--c-primary);color:#fff;border:none">
Android
</button>
<button class="btn btn-sm btn-ghost" id="inst-tab-ios" style="flex:1">
iPhone / iPad
</button>
</div>
<div id="inst-panel-android">
${_steps([
['arrow-square-in', 'Öffne <strong>banyaro.app</strong> in Chrome oder Edge'],
['monitor', 'Klicke auf das <strong>Installations-Symbol</strong> in der Adressleiste'],
['check', 'Bestätigen — fertig!'],
])}
</div>
<div id="inst-panel-ios" style="display:none">
${_steps([
['arrow-square-in', 'Öffne <strong>banyaro.app</strong> in Safari auf dem iPhone'],
['share', 'Tippe auf das <strong>Teilen-Symbol</strong> <svg class="ph-icon" style="width:14px;height:14px;vertical-align:-2px"><use href="/icons/phosphor.svg#share"></use></svg>'],
['rows-plus-top', 'Wähle <strong>„Zum Home-Bildschirm"</strong> → <strong>„Hinzufügen"</strong>'],
])}
</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]) => `
<li style="display:flex;gap:var(--space-3);align-items:flex-start">
<div style="width:32px;height:32px;border-radius:var(--radius-md);flex-shrink:0;
background:var(--c-surface-2);display:flex;align-items:center;justify-content:center">
<svg class="ph-icon" style="width:16px;height:16px;color:var(--c-primary)" aria-hidden="true">
<use href="/icons/phosphor.svg#${icon}"></use>
</svg>
</div>
<span style="font-size:var(--text-sm);color:var(--c-text);line-height:1.5;padding-top:6px">${text}</span>
</li>
`).join('')}
</ol>`;
}
// ----------------------------------------------------------
// EVENTS
// ----------------------------------------------------------
function _bindEvents() {
_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();
}
});
_container.querySelector('#install-copy-btn')?.addEventListener('click', async () => {
await navigator.clipboard.writeText('https://banyaro.app');
UI.toast.success('Link kopiert!');
});
// Desktop-Tabs Android / iOS
_container.querySelector('#inst-tab-android')?.addEventListener('click', () => {
_container.querySelector('#inst-panel-android').style.display = '';
_container.querySelector('#inst-panel-ios').style.display = 'none';
_container.querySelector('#inst-tab-android').style.cssText += ';background:var(--c-primary);color:#fff;border:none';
_container.querySelector('#inst-tab-ios').className = 'btn btn-sm btn-ghost';
_container.querySelector('#inst-tab-ios').style.cssText = 'flex:1';
});
_container.querySelector('#inst-tab-ios')?.addEventListener('click', () => {
_container.querySelector('#inst-panel-android').style.display = 'none';
_container.querySelector('#inst-panel-ios').style.display = '';
_container.querySelector('#inst-tab-ios').style.cssText += ';background:var(--c-primary);color:#fff;border:none';
_container.querySelector('#inst-tab-android').className = 'btn btn-sm btn-ghost';
_container.querySelector('#inst-tab-android').style.cssText = 'flex:1';
});
_container.querySelector('#welcome-register-btn')?.addEventListener('click', () => App.navigate('settings'));
_container.querySelector('#welcome-login-btn')?.addEventListener('click', () => App.navigate('settings'));
_container.querySelectorAll('[data-nav]').forEach(btn => {
btn.addEventListener('click', () => App.navigate(btn.dataset.nav));
});
}
function _pulseMenuBtn() {
const btn = document.getElementById('header-menu-btn');
if (!btn) return;
setTimeout(() => {
btn.classList.add('wc-pulsing');
btn.addEventListener('animationend', () => btn.classList.remove('wc-pulsing'), { once: true });
}, 800);
}
return { init, refresh, onDogChange };
})();