Compare commits
2 commits
d0a76e1b54
...
79c66f2469
| Author | SHA1 | Date | |
|---|---|---|---|
| 79c66f2469 | |||
| 0643cf87cc |
11 changed files with 129 additions and 51 deletions
|
|
@ -37,8 +37,10 @@ Legende: 🟢 läuft/erledigt · 🟡 angefangen · ⬜ offen · 💡 Idee ·
|
|||
- [ ] **Verzeichnisse** — Product Hunt, progressivewebappstore.com, pwafire.org/directory, Google Business (Ebersberg).
|
||||
- [ ] **Landing-Page-Redesign** nach Briefing (3 Zielgruppen-Einstiege Hundebesitzer/Züchter/Welpenkäufer, Outcomes statt Features, Züchter-SaaS prominent, Datenschutz als Argument, Gründer-Story + Foto).
|
||||
- [ ] **Messung einbauen** — „Wie hast du von uns gehört?" im Onboarding + QR-refs pro Kanal.
|
||||
- [ ] **Ban-Yaro-Album** — hin und wieder einen Suno-Song ergänzen, Player unten → Album-Modal (alle Lieder). Voraussetzung: **Suno Pro** (kommerzielle Rechte). Bonus-Asset: Song + App-Screenshots → **Reel/YouTube-Video** für Social.
|
||||
|
||||
## ✅ Erledigt
|
||||
- [x] **Eigener Marken-Song „Ban Yaro Blues"** als Hymne in der WELT-Welt (Suno-KI-Demo, Player unten links, Banner einmalig) — 14.06., Prod v1295. ⚠️ Suno-Lizenz: Free = nicht-kommerziell → für saubere App-Nutzung **Suno Pro** nötig (Songs unter Abo neu rendern)
|
||||
- [x] 1000 Flyer A5 (zweiseitig) gedruckt — 03.06.2026
|
||||
- [x] iOS-App nativ gebaut + **im App Store freigegeben** (Ban Yaro Go, 09.06.) — Details im Repo `banyaro-ios`
|
||||
- [x] Landing-Promotion für „Ban Yaro Go" LIVE (iOS-Abschnitt + Profil, eigenes braunes App-Store-Badge; Hero bewusst ohne Badge) — 09.06., Prod v1278
|
||||
|
|
|
|||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
1295
|
||||
1297
|
||||
|
|
@ -8290,6 +8290,33 @@ svg.empty-state-icon {
|
|||
#worlds-anthem:active { transform: scale(0.92); }
|
||||
#worlds-anthem.playing { background: var(--c-primary); border-color: transparent; }
|
||||
|
||||
/* Album-Modal — Ban Yaro Songs (Bottom-Sheet) */
|
||||
#album-modal {
|
||||
position: fixed; inset: 0; z-index: 2000;
|
||||
background: rgba(0,0,0,.55);
|
||||
display: flex; align-items: flex-end; justify-content: center;
|
||||
}
|
||||
.album-sheet {
|
||||
background: var(--c-surface);
|
||||
border-radius: var(--radius-xl) var(--radius-xl) 0 0;
|
||||
width: 100%; max-width: 480px;
|
||||
padding: var(--space-5) var(--space-4) calc(env(safe-area-inset-bottom,0px) + var(--space-5));
|
||||
box-shadow: 0 -4px 24px rgba(0,0,0,.2);
|
||||
}
|
||||
.album-head { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: var(--space-4); }
|
||||
.album-title { font-size: var(--text-base); font-weight: 700; color: var(--c-text); }
|
||||
.album-subtitle { font-size: var(--text-xs); color: var(--c-text-muted); margin-top: 2px; }
|
||||
.album-close { background: none; border: none; font-size: 1.5rem; line-height: 1; color: var(--c-text-muted); cursor: pointer; padding: 0 4px; }
|
||||
.album-list { display: flex; flex-direction: column; gap: var(--space-2); }
|
||||
.album-song { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3); border-radius: var(--radius-md); background: var(--c-surface-2); cursor: pointer; transition: background .15s; }
|
||||
.album-song:active { background: var(--c-border); }
|
||||
.album-song--active { background: var(--c-primary-subtle); }
|
||||
.album-song-play { width: 38px; height: 38px; border-radius: 50%; flex-shrink: 0; background: var(--c-primary); color: #fff; display: flex; align-items: center; justify-content: center; }
|
||||
.album-song-play svg { width: 18px; height: 18px; }
|
||||
.album-song-meta { display: flex; flex-direction: column; min-width: 0; }
|
||||
.album-song-title { font-size: var(--text-sm); font-weight: 600; color: var(--c-text); }
|
||||
.album-song-sub { font-size: var(--text-xs); color: var(--c-text-muted); margin-top: 1px; }
|
||||
|
||||
/* 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=1295"></script>
|
||||
<script src="/js/boot-early.js?v=1297"></script>
|
||||
|
||||
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
|
||||
<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">
|
||||
<link rel="stylesheet" href="/css/design-system.css?v=1297">
|
||||
<link rel="stylesheet" href="/css/layout.css?v=1297">
|
||||
<link rel="stylesheet" href="/css/components.css?v=1297">
|
||||
<link rel="stylesheet" href="/css/utilities.css?v=1297">
|
||||
<link rel="stylesheet" href="/css/lists.css?v=1297">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
|
@ -596,10 +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 id="worlds-anthem" class="hidden" aria-label="Album öffnen" title="Ban Yaro — das Album">
|
||||
<svg class="ph-icon" aria-hidden="true" style="width:22px;height:22px"><use href="/icons/phosphor.svg#music-notes"></use></svg>
|
||||
</button>
|
||||
<audio id="anthem-audio" src="/sounds/ban-yaro-blues.mp3" preload="none"></audio>
|
||||
<audio id="anthem-audio" 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 -->
|
||||
|
|
@ -624,12 +624,12 @@
|
|||
<div id="modal-container"></div>
|
||||
|
||||
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
|
||||
<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>
|
||||
<script src="/js/api.js?v=1297"></script>
|
||||
<script src="/js/ui.js?v=1297"></script>
|
||||
<script src="/js/app.js?v=1297"></script>
|
||||
<script src="/js/worlds.js?v=1297"></script>
|
||||
<script src="/js/offline-indicator.js?v=1297"></script>
|
||||
<script src="/js/contact-form.js?v=1297"></script>
|
||||
|
||||
<!-- Feature-Seiten werden lazy geladen -->
|
||||
|
||||
|
|
@ -639,7 +639,7 @@
|
|||
|
||||
|
||||
<!-- Boot: Offline-Banner + SW-Registration (extrahiert für CSP) -->
|
||||
<script src="/js/boot.js?v=1295"></script>
|
||||
<script src="/js/boot.js?v=1297"></script>
|
||||
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Router, State-Management, Navigation, Initialisierung.
|
||||
============================================================ */
|
||||
|
||||
const APP_VER = '1295'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
const APP_VER = '1297'; // ← 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;
|
||||
|
|
|
|||
|
|
@ -1934,15 +1934,21 @@ 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.
|
||||
// ── ALBUM (Ban Yaro — eigene Songs) ──────────────────────────
|
||||
// Banner in der WELT-Welt lädt zum Entdecken ein und verschwindet nach erstem
|
||||
// Öffnen. Ab dann: dezenter runder Button unten links (Gegenspieler zum FAB),
|
||||
// der das Album-Modal öffnet (Liste mit Play je Titel). Audio-Element lebt
|
||||
// zentral in index.html → übersteht Re-Renders & Welt-Wechsel.
|
||||
const _anthem = (() => {
|
||||
const KEY = 'by_anthem_heard';
|
||||
let _bound = false;
|
||||
const SONGS = [
|
||||
{ title: 'Ban Yaro Blues', sub: 'Die Hymne', file: '/sounds/ban-yaro-blues.mp3?v=2' },
|
||||
{ title: 'Ban Yaro Mobil', sub: 'Erste Fahrt im Anhänger', file: '/sounds/ban-yaro-mobil.mp3' },
|
||||
{ title: 'Amy', sub: 'Eine Liebesromanze', file: '/sounds/amy.mp3' },
|
||||
];
|
||||
let _bound = false, _curIdx = -1;
|
||||
const _audio = () => document.getElementById('anthem-audio');
|
||||
// Gehört? Server-Flag (geräteübergreifend, deploy-fest) ODER lokal (sofort/offline).
|
||||
// Entdeckt? 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; }
|
||||
|
|
@ -1958,18 +1964,16 @@ window.Worlds = (() => {
|
|||
updateButton();
|
||||
}
|
||||
|
||||
// Runder Button (orange wenn etwas läuft) + Modal-Songzeilen an Wiedergabe angleichen.
|
||||
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'}`);
|
||||
}
|
||||
document.getElementById('worlds-anthem')?.classList.toggle('playing', playing);
|
||||
document.querySelectorAll('#album-modal .album-song').forEach((row, i) => {
|
||||
const active = (i === _curIdx) && playing;
|
||||
row.classList.toggle('album-song--active', active);
|
||||
row.querySelector('use')?.setAttribute('href', `/icons/phosphor.svg#${active ? 'pause' : 'play'}`);
|
||||
});
|
||||
}
|
||||
|
||||
function _bindAudio() {
|
||||
|
|
@ -1978,37 +1982,82 @@ window.Worlds = (() => {
|
|||
if (!a) return;
|
||||
_bound = true;
|
||||
a.addEventListener('play', _sync);
|
||||
a.addEventListener('pause', () => { if (a.currentTime >= 30) _markHeard(); _sync(); });
|
||||
a.addEventListener('ended', () => { _markHeard(); _sync(); });
|
||||
a.addEventListener('pause', _sync);
|
||||
a.addEventListener('ended', () => { // automatisch zum nächsten Song
|
||||
if (_curIdx >= 0 && _curIdx < SONGS.length - 1) _play(_curIdx + 1);
|
||||
else { _curIdx = -1; _sync(); }
|
||||
});
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
function _play(i) {
|
||||
const a = _audio();
|
||||
if (!a) return;
|
||||
if (a.paused) a.play().catch(() => {});
|
||||
else a.pause();
|
||||
if (!a || !SONGS[i]) return;
|
||||
if (i === _curIdx && !a.paused) { a.pause(); return; } // aktiven Song pausieren
|
||||
_curIdx = i;
|
||||
a.src = SONGS[i].file;
|
||||
a.play().catch(() => {});
|
||||
_markHeard();
|
||||
_sync();
|
||||
}
|
||||
|
||||
function _closeAlbum() { document.getElementById('album-modal')?.remove(); }
|
||||
|
||||
function openAlbum() {
|
||||
_markHeard();
|
||||
if (document.getElementById('album-modal')) return;
|
||||
const ov = document.createElement('div');
|
||||
ov.id = 'album-modal';
|
||||
ov.innerHTML = `
|
||||
<div class="album-sheet">
|
||||
<div class="album-head">
|
||||
<div>
|
||||
<div class="album-title">Ban Yaro — das Album</div>
|
||||
<div class="album-subtitle">${SONGS.length} Songs · selbst gemacht 🎸</div>
|
||||
</div>
|
||||
<button class="album-close" aria-label="Schließen">×</button>
|
||||
</div>
|
||||
<div class="album-list">
|
||||
${SONGS.map((s, i) => `
|
||||
<div class="album-song" data-i="${i}" role="button" tabindex="0">
|
||||
<span class="album-song-play"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor.svg#play"></use></svg></span>
|
||||
<span class="album-song-meta">
|
||||
<span class="album-song-title">${_esc(s.title)}</span>
|
||||
<span class="album-song-sub">${_esc(s.sub)}</span>
|
||||
</span>
|
||||
</div>`).join('')}
|
||||
</div>
|
||||
</div>`;
|
||||
document.body.appendChild(ov);
|
||||
ov.addEventListener('click', e => { if (e.target === ov) _closeAlbum(); });
|
||||
ov.querySelector('.album-close').addEventListener('click', _closeAlbum);
|
||||
ov.querySelectorAll('.album-song').forEach(row => {
|
||||
const i = parseInt(row.dataset.i, 10);
|
||||
row.addEventListener('click', () => _play(i));
|
||||
row.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); _play(i); } });
|
||||
});
|
||||
_sync();
|
||||
}
|
||||
|
||||
function updateButton() {
|
||||
document.getElementById('worlds-anthem')?.classList.toggle('hidden', !(_cur === 2 && heard()));
|
||||
}
|
||||
|
||||
// Bei jedem WELT-Render aufrufen: Audio-Listener sichern, Klicks binden, Status setzen.
|
||||
// Bei jedem WELT-Render: 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(); } });
|
||||
card.addEventListener('click', openAlbum);
|
||||
card.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openAlbum(); } });
|
||||
}
|
||||
const btn = document.getElementById('worlds-anthem');
|
||||
if (btn && !btn._anthemBound) { btn._anthemBound = true; btn.addEventListener('click', toggle); }
|
||||
if (btn && !btn._anthemBound) { btn._anthemBound = true; btn.addEventListener('click', openAlbum); }
|
||||
_sync();
|
||||
updateButton();
|
||||
}
|
||||
|
||||
return { heard, toggle, updateButton, initWelt };
|
||||
return { heard, toggle: openAlbum, updateButton, initWelt };
|
||||
})();
|
||||
|
||||
function _renderWelt() {
|
||||
|
|
@ -2042,13 +2091,13 @@ window.Worlds = (() => {
|
|||
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>
|
||||
<svg class="ph-icon" style="width:20px;height:20px;color:#fff" aria-hidden="true"><use href="/icons/phosphor.svg#music-notes"></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 style="font-size:var(--text-sm);font-weight:600;color:#fff">Ban Yaro — das Album</div>
|
||||
<div style="font-size:11px;color:rgba(255,255,255,0.45);margin-top:2px">3 Songs · zum 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>
|
||||
<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#caret-right"></use></svg>
|
||||
</div>`}
|
||||
</div>
|
||||
<div class="world-bottom">
|
||||
|
|
|
|||
|
|
@ -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=1295"></script>
|
||||
<script src="/js/landing-init.js?v=1297"></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/amy.mp3
Normal file
BIN
backend/static/sounds/amy.mp3
Normal file
Binary file not shown.
Binary file not shown.
BIN
backend/static/sounds/ban-yaro-mobil.mp3
Normal file
BIN
backend/static/sounds/ban-yaro-mobil.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 = '1295';
|
||||
const VER = '1297';
|
||||
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