Ban Yaro Album: 3 Songs + Album-Modal, Hymne auf Pro-Version getauscht

Aus der einzelnen Hymne wird ein Album (WELT-Welt): runder Button / Banner
öffnen jetzt ein Album-Modal mit Liste (Play je Titel, aktiver Song orange,
Auto-Next zum nächsten, zentrales Audio läuft über Welt-Wechsel weiter). Songs
als Array (statischer Content), on-demand-Cache pro Datei.

Songs — alle Suno Pro, kommerziell lizenziert:
- Ban Yaro Blues (Hymne) — Pro-Version ersetzt die Free-Aufnahme (Cache-Bust ?v=2)
- Ban Yaro Mobil — erste Anhänger-Fahrt durch die Prärie
- Amy — Liebesromanze (Jack-Russell-Mädchen)

audio-src in index.html geleert (Album setzt src dynamisch).
This commit is contained in:
rene 2026-06-14 22:32:56 +02:00
parent 0643cf87cc
commit 79c66f2469
10 changed files with 127 additions and 51 deletions

View file

@ -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() {
@ -1977,38 +1981,83 @@ window.Worlds = (() => {
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(); });
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);
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">