Album: Englische Edition (7 Songs) + DE/EN-Umschalter im Album-Modal (v1301)
- 7 englische Suno-Pro-Songs als *-en.mp3 in static/sounds/ (MD5-geprüft, eigene Generierungen, alle != deutsche Tracks) - worlds.js _anthem: SONGS_DE/SONGS_EN, _lang-State (localStorage by_album_lang), DE/EN-Segmented-Control (_fillAlbum/_setLang), EN_READY=true, Modal-Chrome zweisprachig - components.css: .album-lang / .album-lang-btn - UX: DE bleibt Default, keine Auto-Vorwahl, User schaltet selbst, beides anhörbar, Wahl gemerkt - LIVE auf Prod + Staging v1301
This commit is contained in:
parent
6dc944eeb8
commit
aea489aa5a
14 changed files with 98 additions and 48 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
1300
|
||||
1301
|
||||
|
|
@ -8307,6 +8307,10 @@ svg.empty-state-icon {
|
|||
.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-head-actions { display: flex; align-items: center; gap: var(--space-2); flex-shrink: 0; }
|
||||
.album-lang { display: inline-flex; border: 1px solid var(--c-border); border-radius: var(--radius-full); overflow: hidden; background: var(--c-surface-2); }
|
||||
.album-lang-btn { background: none; border: none; cursor: pointer; padding: 4px 10px; font-size: var(--text-xs); font-weight: 700; color: var(--c-text-muted); line-height: 1.4; transition: background .15s, color .15s; }
|
||||
.album-lang-btn.is-active { background: var(--c-primary); color: #fff; }
|
||||
.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); }
|
||||
|
|
|
|||
|
|
@ -86,14 +86,14 @@
|
|||
<title>Ban Yaro</title>
|
||||
|
||||
<!-- Theme + theme-color Statusleiste vor CSS setzen -->
|
||||
<script src="/js/boot-early.js?v=1300"></script>
|
||||
<script src="/js/boot-early.js?v=1301"></script>
|
||||
|
||||
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
|
||||
<link rel="stylesheet" href="/css/design-system.css?v=1300">
|
||||
<link rel="stylesheet" href="/css/layout.css?v=1300">
|
||||
<link rel="stylesheet" href="/css/components.css?v=1300">
|
||||
<link rel="stylesheet" href="/css/utilities.css?v=1300">
|
||||
<link rel="stylesheet" href="/css/lists.css?v=1300">
|
||||
<link rel="stylesheet" href="/css/design-system.css?v=1301">
|
||||
<link rel="stylesheet" href="/css/layout.css?v=1301">
|
||||
<link rel="stylesheet" href="/css/components.css?v=1301">
|
||||
<link rel="stylesheet" href="/css/utilities.css?v=1301">
|
||||
<link rel="stylesheet" href="/css/lists.css?v=1301">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
|
@ -624,12 +624,12 @@
|
|||
<div id="modal-container"></div>
|
||||
|
||||
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
|
||||
<script src="/js/api.js?v=1300"></script>
|
||||
<script src="/js/ui.js?v=1300"></script>
|
||||
<script src="/js/app.js?v=1300"></script>
|
||||
<script src="/js/worlds.js?v=1300"></script>
|
||||
<script src="/js/offline-indicator.js?v=1300"></script>
|
||||
<script src="/js/contact-form.js?v=1300"></script>
|
||||
<script src="/js/api.js?v=1301"></script>
|
||||
<script src="/js/ui.js?v=1301"></script>
|
||||
<script src="/js/app.js?v=1301"></script>
|
||||
<script src="/js/worlds.js?v=1301"></script>
|
||||
<script src="/js/offline-indicator.js?v=1301"></script>
|
||||
<script src="/js/contact-form.js?v=1301"></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=1300"></script>
|
||||
<script src="/js/boot.js?v=1301"></script>
|
||||
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
Router, State-Management, Navigation, Initialisierung.
|
||||
============================================================ */
|
||||
|
||||
const APP_VER = '1300'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
|
||||
const APP_VER = '1301'; // ← 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;
|
||||
|
|
|
|||
|
|
@ -1941,7 +1941,11 @@ window.Worlds = (() => {
|
|||
// zentral in index.html → übersteht Re-Renders & Welt-Wechsel.
|
||||
const _anthem = (() => {
|
||||
const KEY = 'by_anthem_heard';
|
||||
const SONGS = [
|
||||
const LANG_KEY = 'by_album_lang';
|
||||
// EN-Album erst sichtbar, wenn die 7 *-en.mp3 in static/sounds/ liegen.
|
||||
// Aktivieren: Dateien ablegen → EN_READY = true → make bump → deploy.
|
||||
const EN_READY = true; // 2026-06-16: 7 *-en.mp3 liegen in static/sounds/
|
||||
const SONGS_DE = [
|
||||
{ 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' },
|
||||
|
|
@ -1950,6 +1954,19 @@ window.Worlds = (() => {
|
|||
{ title: 'Platsch!', sub: 'Ab ins kühle Nass', file: '/sounds/platsch.mp3' },
|
||||
{ title: 'Bester Freund', sub: 'Du und ich', file: '/sounds/bester-freund.mp3' },
|
||||
];
|
||||
const SONGS_EN = [
|
||||
{ title: 'Ban Yaro Blues', sub: 'The anthem', file: '/sounds/ban-yaro-blues-en.mp3' },
|
||||
{ title: 'Ban Yaro Mobile', sub: 'First ride in the trailer', file: '/sounds/ban-yaro-mobil-en.mp3' },
|
||||
{ title: 'Amy', sub: 'A love duet', file: '/sounds/amy-en.mp3' },
|
||||
{ title: "At the Groomer's", sub: 'Half the fur, all the energy', file: '/sounds/at-the-groomers-en.mp3' },
|
||||
{ title: 'Treat Paradise', sub: 'Full bowl, full heart', file: '/sounds/treat-paradise-en.mp3' },
|
||||
{ title: 'Splash!', sub: 'Into the cool water', file: '/sounds/splash-en.mp3' },
|
||||
{ title: 'Best Friend', sub: 'You and me', file: '/sounds/best-friend-en.mp3' },
|
||||
];
|
||||
let _lang = (() => {
|
||||
try { return (EN_READY && localStorage.getItem(LANG_KEY) === 'en') ? 'en' : 'de'; } catch (_) { return 'de'; }
|
||||
})();
|
||||
const _songs = () => (_lang === 'en' && EN_READY) ? SONGS_EN : SONGS_DE;
|
||||
let _bound = false, _curIdx = -1;
|
||||
const _audio = () => document.getElementById('anthem-audio');
|
||||
// Entdeckt? Server-Flag (geräteübergreifend, deploy-fest) ODER lokal (sofort/offline).
|
||||
|
|
@ -1988,17 +2005,18 @@ window.Worlds = (() => {
|
|||
a.addEventListener('play', _sync);
|
||||
a.addEventListener('pause', _sync);
|
||||
a.addEventListener('ended', () => { // automatisch zum nächsten Song
|
||||
if (_curIdx >= 0 && _curIdx < SONGS.length - 1) _play(_curIdx + 1);
|
||||
if (_curIdx >= 0 && _curIdx < _songs().length - 1) _play(_curIdx + 1);
|
||||
else { _curIdx = -1; _sync(); }
|
||||
});
|
||||
}
|
||||
|
||||
function _play(i) {
|
||||
const a = _audio();
|
||||
if (!a || !SONGS[i]) return;
|
||||
const songs = _songs();
|
||||
if (!a || !songs[i]) return;
|
||||
if (i === _curIdx && !a.paused) { a.pause(); return; } // aktiven Song pausieren
|
||||
_curIdx = i;
|
||||
a.src = SONGS[i].file;
|
||||
a.src = songs[i].file;
|
||||
a.play().catch(() => {});
|
||||
_markHeard();
|
||||
_sync();
|
||||
|
|
@ -2006,39 +2024,67 @@ window.Worlds = (() => {
|
|||
|
||||
function _closeAlbum() { document.getElementById('album-modal')?.remove(); }
|
||||
|
||||
// Sprache wechseln: aktuelle Wiedergabe stoppen (andere Datei) und Liste neu zeichnen.
|
||||
function _setLang(l) {
|
||||
if (l === _lang || !EN_READY) return;
|
||||
_lang = l;
|
||||
try { localStorage.setItem(LANG_KEY, l); } catch (_) {}
|
||||
const a = _audio(); if (a) a.pause();
|
||||
_curIdx = -1;
|
||||
_fillAlbum();
|
||||
_sync();
|
||||
}
|
||||
|
||||
// Inhalt des Sheets (neu) rendern + innere Controls binden — auch bei Sprachwechsel.
|
||||
function _fillAlbum() {
|
||||
const sheet = document.querySelector('#album-modal .album-sheet');
|
||||
if (!sheet) return;
|
||||
const songs = _songs();
|
||||
const en = _lang === 'en';
|
||||
sheet.innerHTML = `
|
||||
<div class="album-head">
|
||||
<div>
|
||||
<div class="album-title">${en ? 'Ban Yaro — The Album' : 'Ban Yaro — das Album'}</div>
|
||||
<div class="album-subtitle">${songs.length} ${en ? 'songs · homemade' : 'Songs · selbst gemacht'} 🎸</div>
|
||||
</div>
|
||||
<div class="album-head-actions">
|
||||
${EN_READY ? `
|
||||
<div class="album-lang" role="group" aria-label="Sprache / Language">
|
||||
<button class="album-lang-btn ${en ? '' : 'is-active'}" data-lang="de" type="button">DE</button>
|
||||
<button class="album-lang-btn ${en ? 'is-active' : ''}" data-lang="en" type="button">EN</button>
|
||||
</div>` : ''}
|
||||
<button class="album-close" aria-label="${en ? 'Close' : 'Schließen'}">×</button>
|
||||
</div>
|
||||
</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>`;
|
||||
sheet.querySelector('.album-close').addEventListener('click', _closeAlbum);
|
||||
sheet.querySelectorAll('.album-lang-btn').forEach(b =>
|
||||
b.addEventListener('click', () => _setLang(b.dataset.lang)));
|
||||
sheet.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); } });
|
||||
});
|
||||
}
|
||||
|
||||
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>`;
|
||||
ov.innerHTML = `<div class="album-sheet"></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); } });
|
||||
});
|
||||
_fillAlbum();
|
||||
_sync();
|
||||
}
|
||||
|
||||
|
|
@ -2061,7 +2107,7 @@ window.Worlds = (() => {
|
|||
updateButton();
|
||||
}
|
||||
|
||||
return { heard, toggle: openAlbum, updateButton, initWelt, count: SONGS.length };
|
||||
return { heard, toggle: openAlbum, updateButton, initWelt, count: SONGS_DE.length };
|
||||
})();
|
||||
|
||||
function _renderWelt() {
|
||||
|
|
|
|||
|
|
@ -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=1300"></script>
|
||||
<script src="/js/landing-init.js?v=1301"></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-en.mp3
Normal file
BIN
backend/static/sounds/amy-en.mp3
Normal file
Binary file not shown.
BIN
backend/static/sounds/at-the-groomers-en.mp3
Normal file
BIN
backend/static/sounds/at-the-groomers-en.mp3
Normal file
Binary file not shown.
BIN
backend/static/sounds/ban-yaro-blues-en.mp3
Normal file
BIN
backend/static/sounds/ban-yaro-blues-en.mp3
Normal file
Binary file not shown.
BIN
backend/static/sounds/ban-yaro-mobil-en.mp3
Normal file
BIN
backend/static/sounds/ban-yaro-mobil-en.mp3
Normal file
Binary file not shown.
BIN
backend/static/sounds/best-friend-en.mp3
Normal file
BIN
backend/static/sounds/best-friend-en.mp3
Normal file
Binary file not shown.
BIN
backend/static/sounds/splash-en.mp3
Normal file
BIN
backend/static/sounds/splash-en.mp3
Normal file
Binary file not shown.
BIN
backend/static/sounds/treat-paradise-en.mp3
Normal file
BIN
backend/static/sounds/treat-paradise-en.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 = '1300';
|
||||
const VER = '1301';
|
||||
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