Ban Yaro Blues — Hymne in der WELT-Welt
Eigener Song (KI-Demo via Suno) als Marken-Hymne. Dezente Player-Karte unter dem Tageszitat; preload=none → 6 MB MP3 lädt erst bei Play, der SW cacht sie danach für offline. Der Banner ist einmalige Einladung und verschwindet nach erstem Hören (durchgehört oder >30s + Pause); danach dezenter runder Play-Button unten links als Gegenspieler zum FAB, nur in WELT. Audio-Element zentral in index.html → übersteht Welt-Wechsel & Re-Renders. „Gehört" wird hybrid gemerkt: localStorage (sofort/offline) + DB-Flag anthem_heard am User (neue Spalte, über /auth/me, gesetzt via POST /api/profile/anthem-heard) — geräte- und deploy-übergreifend, damit der Banner nicht erneut nervt.
This commit is contained in:
parent
f7370028da
commit
d0a76e1b54
11 changed files with 149 additions and 18 deletions
|
|
@ -605,6 +605,7 @@ def _migrate(conn_factory):
|
|||
("notes", "location_name", "TEXT"),
|
||||
("notes", "parent_label", "TEXT"),
|
||||
("users", "notes_ki_enabled", "INTEGER NOT NULL DEFAULT 1"),
|
||||
("users", "anthem_heard", "INTEGER NOT NULL DEFAULT 0"),
|
||||
# Züchter-Rolle
|
||||
("users", "breeder_status", "TEXT"),
|
||||
# Würfe: Verknüpfung mit Zuchtkartei-Hunden + Welfare
|
||||
|
|
|
|||
|
|
@ -398,7 +398,7 @@ async def me(user=Depends(get_current_user)):
|
|||
bio, wohnort, erfahrung, social_link,
|
||||
profil_sichtbarkeit, avatar_url, created_at,
|
||||
is_founder, is_partner, founder_number, is_founder_pending,
|
||||
notes_ki_enabled, gassi_stunde_push,
|
||||
notes_ki_enabled, gassi_stunde_push, anthem_heard,
|
||||
preferred_theme, subscription_tier,
|
||||
subscription_expires_at, subscription_cancelled_at, needs_dog_selection,
|
||||
billing_address, geburtstag
|
||||
|
|
|
|||
|
|
@ -50,6 +50,16 @@ def _load_user(user_id: int) -> dict:
|
|||
return data
|
||||
|
||||
|
||||
@router.post("/anthem-heard", status_code=204)
|
||||
async def mark_anthem_heard(user=Depends(get_current_user)):
|
||||
"""Merkt server-seitig, dass die Hymne gehört wurde — geräteübergreifend und
|
||||
übersteht Cache-Clear bzw. die iOS-Trennung von PWA- und Safari-localStorage,
|
||||
damit der Banner nach einem Deploy nicht erneut auftaucht."""
|
||||
with db() as conn:
|
||||
conn.execute("UPDATE users SET anthem_heard=1 WHERE id=?", (user["id"],))
|
||||
return None
|
||||
|
||||
|
||||
@router.patch("")
|
||||
async def update_profile(data: ProfileUpdate, user=Depends(get_current_user)):
|
||||
fields = data.model_dump(exclude_none=True)
|
||||
|
|
|
|||
|
|
@ -8267,6 +8267,29 @@ svg.empty-state-icon {
|
|||
}
|
||||
#worlds-fab:active { transform: scale(0.92); box-shadow: 0 2px 10px rgba(196,132,58,0.3); }
|
||||
|
||||
/* Hymne-Button — Gegenspieler zum FAB, unten links, nur WELT + nach erstem Hören */
|
||||
#worlds-anthem {
|
||||
position: fixed;
|
||||
bottom: calc(env(safe-area-inset-bottom, 16px) + 16px);
|
||||
left: 20px;
|
||||
width: 54px; height: 54px;
|
||||
border-radius: 50%;
|
||||
background: rgba(22,26,30,0.66);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
backdrop-filter: blur(8px);
|
||||
color: #fff;
|
||||
border: 1px solid rgba(255,255,255,0.20);
|
||||
cursor: pointer;
|
||||
z-index: 60;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4px 18px rgba(0,0,0,0.40);
|
||||
transition: transform 0.12s, background 0.2s;
|
||||
}
|
||||
#worlds-anthem:active { transform: scale(0.92); }
|
||||
#worlds-anthem.playing { background: var(--c-primary); border-color: transparent; }
|
||||
|
||||
/* Header + Bottom-Nav: vollständig entfernt — Welten übernehmen Navigation */
|
||||
#app-header { display: none !important; }
|
||||
#bottom-nav { display: none !important; }
|
||||
|
|
|
|||
|
|
@ -86,14 +86,14 @@
|
|||
<title>Ban Yaro</title>
|
||||
|
||||
<!-- Theme + theme-color Statusleiste vor CSS setzen -->
|
||||
<script src="/js/boot-early.js?v=1292"></script>
|
||||
<script src="/js/boot-early.js?v=1295"></script>
|
||||
|
||||
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
|
||||
<link rel="stylesheet" href="/css/design-system.css?v=1292">
|
||||
<link rel="stylesheet" href="/css/layout.css?v=1292">
|
||||
<link rel="stylesheet" href="/css/components.css?v=1292">
|
||||
<link rel="stylesheet" href="/css/utilities.css?v=1292">
|
||||
<link rel="stylesheet" href="/css/lists.css?v=1292">
|
||||
<link rel="stylesheet" href="/css/design-system.css?v=1295">
|
||||
<link rel="stylesheet" href="/css/layout.css?v=1295">
|
||||
<link rel="stylesheet" href="/css/components.css?v=1295">
|
||||
<link rel="stylesheet" href="/css/utilities.css?v=1295">
|
||||
<link rel="stylesheet" href="/css/lists.css?v=1295">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
|
@ -596,6 +596,10 @@
|
|||
<div class="world-panel" id="wp-hund"><div id="wh-content"></div></div>
|
||||
<div class="world-panel" id="wp-welt"><div id="ww-content"></div></div>
|
||||
</div>
|
||||
<button id="worlds-anthem" class="hidden" aria-label="Hymne abspielen" title="Ban Yaro Blues — unsere Hymne">
|
||||
<svg class="ph-icon" aria-hidden="true" style="width:22px;height:22px"><use href="/icons/phosphor.svg#play"></use></svg>
|
||||
</button>
|
||||
<audio id="anthem-audio" src="/sounds/ban-yaro-blues.mp3" preload="none"></audio>
|
||||
<button id="worlds-fab" aria-label="Hinzufügen">
|
||||
<svg class="offline-paw" viewBox="0 0 256 256" aria-hidden="true" style="width:24px;height:24px">
|
||||
<!-- 5 Sub-Pfade einzeln einfärbbar via .paw-elem; Default: weiß auf orange -->
|
||||
|
|
@ -620,12 +624,12 @@
|
|||
<div id="modal-container"></div>
|
||||
|
||||
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
|
||||
<script src="/js/api.js?v=1292"></script>
|
||||
<script src="/js/ui.js?v=1292"></script>
|
||||
<script src="/js/app.js?v=1292"></script>
|
||||
<script src="/js/worlds.js?v=1292"></script>
|
||||
<script src="/js/offline-indicator.js?v=1292"></script>
|
||||
<script src="/js/contact-form.js?v=1292"></script>
|
||||
<script src="/js/api.js?v=1295"></script>
|
||||
<script src="/js/ui.js?v=1295"></script>
|
||||
<script src="/js/app.js?v=1295"></script>
|
||||
<script src="/js/worlds.js?v=1295"></script>
|
||||
<script src="/js/offline-indicator.js?v=1295"></script>
|
||||
<script src="/js/contact-form.js?v=1295"></script>
|
||||
|
||||
<!-- Feature-Seiten werden lazy geladen -->
|
||||
|
||||
|
|
@ -635,7 +639,7 @@
|
|||
|
||||
|
||||
<!-- Boot: Offline-Banner + SW-Registration (extrahiert für CSP) -->
|
||||
<script src="/js/boot.js?v=1292"></script>
|
||||
<script src="/js/boot.js?v=1295"></script>
|
||||
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Router, State-Management, Navigation, Initialisierung.
|
||||
============================================================ */
|
||||
|
||||
const APP_VER = '1292'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
const APP_VER = '1295'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt
|
||||
window.APP_VER = APP_VER; // global verfügbar für andere Module (z.B. offline-indicator)
|
||||
window.APP_VERSION = APP_VERSION;
|
||||
|
|
|
|||
|
|
@ -220,6 +220,7 @@ window.Worlds = (() => {
|
|||
track.style.transform = `translateX(${-_cur * (100 / 3)}%)`;
|
||||
_updateDots();
|
||||
_updateFab();
|
||||
_anthem.updateButton();
|
||||
// Karte neu rendern nachdem Transition abgeschlossen
|
||||
if (_cur === 2 && _map) {
|
||||
setTimeout(() => _map.invalidateSize(), animated ? 380 : 50);
|
||||
|
|
@ -1933,6 +1934,83 @@ window.Worlds = (() => {
|
|||
{ t:"Ein Hund braucht keinen Kalender, er weiß genau, wann du nach Hause kommst.", a:"Unbekannt" },
|
||||
];
|
||||
|
||||
// ── HYMNE (Ban Yaro Blues) ───────────────────────────────────
|
||||
// Banner in der WELT-Welt lädt zum ersten Hören ein und verschwindet danach.
|
||||
// Ab dann: dezenter runder Play-Button unten links (Gegenspieler zum FAB).
|
||||
// Audio-Element lebt zentral in index.html → übersteht Re-Renders & Welt-Wechsel.
|
||||
const _anthem = (() => {
|
||||
const KEY = 'by_anthem_heard';
|
||||
let _bound = false;
|
||||
const _audio = () => document.getElementById('anthem-audio');
|
||||
// Gehört? Server-Flag (geräteübergreifend, deploy-fest) ODER lokal (sofort/offline).
|
||||
const heard = () => {
|
||||
if (_state?.user?.anthem_heard) return true;
|
||||
try { return localStorage.getItem(KEY) === '1'; } catch (_) { return false; }
|
||||
};
|
||||
|
||||
function _markHeard() {
|
||||
try { localStorage.setItem(KEY, '1'); } catch (_) {}
|
||||
if (!_state?.user?.anthem_heard) { // server-seitig genau einmal merken
|
||||
if (_state?.user) _state.user.anthem_heard = 1;
|
||||
try { API.post('/profile/anthem-heard', {}).catch(() => {}); } catch (_) {}
|
||||
}
|
||||
document.getElementById('ww-anthem-card')?.classList.add('hidden'); // Banner live ausblenden
|
||||
updateButton();
|
||||
}
|
||||
|
||||
function _sync() {
|
||||
const a = _audio();
|
||||
const playing = !!(a && !a.paused && !a.ended);
|
||||
document.getElementById('ww-anthem-icon')?.querySelector('use')
|
||||
?.setAttribute('href', `/icons/phosphor.svg#${playing ? 'pause' : 'play'}`);
|
||||
const sub = document.getElementById('ww-anthem-sub');
|
||||
if (sub) sub.textContent = playing ? 'läuft … (tippen zum Pausieren)' : 'unsere Hymne · anhören';
|
||||
const btn = document.getElementById('worlds-anthem');
|
||||
if (btn) {
|
||||
btn.classList.toggle('playing', playing);
|
||||
btn.querySelector('use')?.setAttribute('href', `/icons/phosphor.svg#${playing ? 'pause' : 'play'}`);
|
||||
}
|
||||
}
|
||||
|
||||
function _bindAudio() {
|
||||
if (_bound) return;
|
||||
const a = _audio();
|
||||
if (!a) return;
|
||||
_bound = true;
|
||||
a.addEventListener('play', _sync);
|
||||
a.addEventListener('pause', () => { if (a.currentTime >= 30) _markHeard(); _sync(); });
|
||||
a.addEventListener('ended', () => { _markHeard(); _sync(); });
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
const a = _audio();
|
||||
if (!a) return;
|
||||
if (a.paused) a.play().catch(() => {});
|
||||
else a.pause();
|
||||
}
|
||||
|
||||
function updateButton() {
|
||||
document.getElementById('worlds-anthem')?.classList.toggle('hidden', !(_cur === 2 && heard()));
|
||||
}
|
||||
|
||||
// Bei jedem WELT-Render aufrufen: Audio-Listener sichern, Klicks binden, Status setzen.
|
||||
function initWelt() {
|
||||
_bindAudio();
|
||||
const card = document.getElementById('ww-anthem-card');
|
||||
if (card && !card._anthemBound) {
|
||||
card._anthemBound = true;
|
||||
card.addEventListener('click', toggle);
|
||||
card.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggle(); } });
|
||||
}
|
||||
const btn = document.getElementById('worlds-anthem');
|
||||
if (btn && !btn._anthemBound) { btn._anthemBound = true; btn.addEventListener('click', toggle); }
|
||||
_sync();
|
||||
updateButton();
|
||||
}
|
||||
|
||||
return { heard, toggle, updateButton, initWelt };
|
||||
})();
|
||||
|
||||
function _renderWelt() {
|
||||
const el = document.getElementById('ww-content');
|
||||
if (!el) return;
|
||||
|
|
@ -1959,6 +2037,19 @@ window.Worlds = (() => {
|
|||
— ${_esc(quote.a)}
|
||||
</div>
|
||||
</div>
|
||||
${_anthem.heard() ? '' : `
|
||||
<div class="world-info-card" id="ww-anthem-card" role="button" tabindex="0"
|
||||
style="margin-top:10px;display:flex;align-items:center;gap:14px;cursor:pointer">
|
||||
<div style="width:42px;height:42px;border-radius:50%;background:rgba(255,255,255,0.12);
|
||||
display:flex;align-items:center;justify-content:center;flex-shrink:0">
|
||||
<svg class="ph-icon" id="ww-anthem-icon" style="width:20px;height:20px;color:#fff" aria-hidden="true"><use href="/icons/phosphor.svg#play"></use></svg>
|
||||
</div>
|
||||
<div style="flex:1;min-width:0">
|
||||
<div style="font-size:var(--text-sm);font-weight:600;color:#fff">Ban Yaro Blues</div>
|
||||
<div id="ww-anthem-sub" style="font-size:11px;color:rgba(255,255,255,0.45);margin-top:2px">unsere Hymne · anhören</div>
|
||||
</div>
|
||||
<svg class="ph-icon" style="width:15px;height:15px;color:rgba(255,255,255,0.3);flex-shrink:0" aria-hidden="true"><use href="/icons/phosphor.svg#music-notes"></use></svg>
|
||||
</div>`}
|
||||
</div>
|
||||
<div class="world-bottom">
|
||||
<div class="world-chips-grid">
|
||||
|
|
@ -1972,6 +2063,8 @@ window.Worlds = (() => {
|
|||
</div>
|
||||
`;
|
||||
el.querySelectorAll('[data-wnav]').forEach(e => e.addEventListener('click', () => navigateTo(e.dataset.wnav)));
|
||||
|
||||
_anthem.initWelt();
|
||||
}
|
||||
|
||||
// ── HELPERS ──────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<script src="/js/landing-init.js?v=1292"></script>
|
||||
<script src="/js/landing-init.js?v=1295"></script>
|
||||
<title>Ban Yaro — Die Hunde-App für Deutschland, Österreich & Schweiz</title>
|
||||
<meta name="description" content="Ban Yaro: Die kostenlose All-in-One Hunde-App für DACH. Tagebuch, Giftköder-Alarm, Training mit KI, Forum, Wurfbörse, Stammbaum, Inzucht-Check — DSGVO-konform, offline-fähig, direkt im Browser oder als native iPhone-App (Ban Yaro Go).">
|
||||
<meta name="keywords" content="Hunde App, Hunde Community, Wurfbörse, Züchter, Welpen kaufen, Stammbaum Hund, Inzuchtkoeffizient, Hundezucht, Impfpass Hund, Giftköder Alarm, Gassi Community, Hundetraining App, Hunde Forum, Hunde KI, Hundefilm Datenbank, Welpen Marktplatz">
|
||||
|
|
|
|||
BIN
backend/static/sounds/ban-yaro-blues.mp3
Normal file
BIN
backend/static/sounds/ban-yaro-blues.mp3
Normal file
Binary file not shown.
|
|
@ -4,7 +4,7 @@
|
|||
============================================================ */
|
||||
|
||||
// ← EINZIGE Stelle für die Version — STATIC_ASSETS und CACHE_VERSION leiten sich ab
|
||||
const VER = '1292';
|
||||
const VER = '1295';
|
||||
const CACHE_VERSION = `by-v${VER}`;
|
||||
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
||||
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue