Files
AudioCore/show.php
2026-03-04 20:46:11 +00:00

251 lines
14 KiB
PHP

<?php
declare(strict_types=1);
$pageTitle = $title ?? 'Release';
$release = $release ?? null;
$tracks = $tracks ?? [];
$releaseCover = (string)($release['cover_url'] ?? '');
ob_start();
?>
<section class="card" style="display:grid; gap:18px; padding-bottom:110px;">
<div class="badge">Release</div>
<?php if (!$release): ?>
<h1 style="margin:0; font-size:28px;">Release not found</h1>
<p style="color:var(--muted);">This release is unavailable.</p>
<?php else: ?>
<div class="release-wrap" style="display:grid; gap:18px;">
<div class="release-hero" style="display:grid; grid-template-columns:minmax(0,1fr) 360px; gap:22px; align-items:start;">
<div class="release-meta" style="display:grid; gap:14px;">
<h1 style="margin:0; font-size:46px; line-height:1.06;"><?= htmlspecialchars((string)$release['title'], ENT_QUOTES, 'UTF-8') ?></h1>
<div style="display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:10px; max-width:640px;">
<?php if (!empty($release['catalog_no'])): ?>
<div style="padding:10px 12px; border-radius:12px; border:1px solid rgba(255,255,255,0.08); background:rgba(0,0,0,0.22);">
<div class="badge" style="font-size:9px;">Catalog</div>
<div style="margin-top:6px; font-size:14px;"><?= htmlspecialchars((string)$release['catalog_no'], ENT_QUOTES, 'UTF-8') ?></div>
</div>
<?php endif; ?>
<?php if (!empty($release['release_date'])): ?>
<div style="padding:10px 12px; border-radius:12px; border:1px solid rgba(255,255,255,0.08); background:rgba(0,0,0,0.22);">
<div class="badge" style="font-size:9px;">Release Date</div>
<div style="margin-top:6px; font-size:14px;"><?= htmlspecialchars((string)$release['release_date'], ENT_QUOTES, 'UTF-8') ?></div>
</div>
<?php endif; ?>
</div>
<?php if (!empty($release['description'])): ?>
<div style="color:var(--muted); line-height:1.75; max-width:760px;">
<?= nl2br(htmlspecialchars((string)$release['description'], ENT_QUOTES, 'UTF-8')) ?>
</div>
<?php endif; ?>
</div>
<div class="release-cover-box" style="border-radius:20px; overflow:hidden; background:rgba(255,255,255,0.05); border:1px solid rgba(255,255,255,0.1); aspect-ratio:1/1;">
<?php if ($releaseCover !== ''): ?>
<img id="releaseCoverMain" src="<?= htmlspecialchars($releaseCover, ENT_QUOTES, 'UTF-8') ?>" alt="" style="width:100%; height:100%; object-fit:cover;">
<?php else: ?>
<div id="releaseCoverMain" style="height:100%; display:grid; place-items:center; color:var(--muted); letter-spacing:0.3em; font-size:12px;">AUDIOCORE</div>
<?php endif; ?>
</div>
</div>
<?php if (!empty($release['credits'])): ?>
<div style="padding:14px 16px; border-radius:16px; border:1px solid rgba(255,255,255,0.08); background:rgba(0,0,0,0.22);">
<div class="badge">Credits</div>
<div style="margin-top:8px; color:var(--muted); line-height:1.7;">
<?= nl2br(htmlspecialchars((string)$release['credits'], ENT_QUOTES, 'UTF-8')) ?>
</div>
</div>
<?php endif; ?>
<?php if ($tracks): ?>
<div style="padding:16px; border-radius:18px; border:1px solid rgba(255,255,255,0.08); background:rgba(0,0,0,0.22); display:grid; gap:10px;">
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px;">
<div class="badge">Tracklist</div>
<div id="releasePlayerNow" style="font-size:12px; color:var(--muted);">Select a track to play sample</div>
</div>
<div style="display:grid; gap:8px;">
<?php foreach ($tracks as $track): ?>
<?php
$sample = (string)($track['sample_url'] ?? '');
$trackTitle = (string)($track['title'] ?? 'Track');
$mix = (string)($track['mix_name'] ?? '');
$fullTitle = $mix !== '' ? ($trackTitle . ' (' . $mix . ')') : $trackTitle;
?>
<div class="track-row" style="display:grid; grid-template-columns:92px minmax(0,1fr) auto; gap:12px; align-items:center; padding:10px 12px; border-radius:12px; border:1px solid rgba(255,255,255,0.08); background:rgba(0,0,0,0.26);">
<button type="button"
class="track-play-btn"
data-src="<?= htmlspecialchars($sample, ENT_QUOTES, 'UTF-8') ?>"
data-title="<?= htmlspecialchars($fullTitle, ENT_QUOTES, 'UTF-8') ?>"
data-cover="<?= htmlspecialchars($releaseCover, ENT_QUOTES, 'UTF-8') ?>"
<?= $sample === '' ? 'disabled' : '' ?>>
<i class="fa-solid fa-play"></i> <span>Play</span>
</button>
<div style="min-width:0;">
<div style="font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">
<?= htmlspecialchars($trackTitle, ENT_QUOTES, 'UTF-8') ?>
<?php if ($mix !== ''): ?>
<span style="color:var(--muted); font-weight:400;">(<?= htmlspecialchars($mix, ENT_QUOTES, 'UTF-8') ?>)</span>
<?php endif; ?>
</div>
<div style="font-size:12px; color:var(--muted); margin-top:4px;">
<?= (int)($track['track_no'] ?? 0) > 0 ? '#' . (int)$track['track_no'] : 'Track' ?>
<?= !empty($track['duration']) ? ' - ' . htmlspecialchars((string)$track['duration'], ENT_QUOTES, 'UTF-8') : '' ?>
<?= !empty($track['bpm']) ? ' - ' . htmlspecialchars((string)$track['bpm'], ENT_QUOTES, 'UTF-8') . ' BPM' : '' ?>
<?= !empty($track['key_signature']) ? ' - ' . htmlspecialchars((string)$track['key_signature'], ENT_QUOTES, 'UTF-8') : '' ?>
</div>
</div>
<div style="font-size:11px; color:var(--muted); text-transform:uppercase; letter-spacing:0.18em;">
<?= $sample !== '' ? 'Sample' : 'No sample' ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
</section>
<div id="acDock" class="ac-dock" hidden>
<div class="ac-dock-inner">
<div class="ac-dock-meta">
<div id="acDockArt" class="ac-dock-art">AC</div>
<div class="ac-dock-title-wrap">
<div class="badge" style="font-size:9px;">Now Playing</div>
<div id="acDockTitle" class="ac-dock-title">Track sample</div>
</div>
</div>
<button type="button" id="acDockToggle" class="ac-dock-toggle"><i class="fa-solid fa-play"></i> <span>Play</span></button>
<input id="acDockSeek" class="ac-seek" type="range" min="0" max="100" value="0" step="0.1">
<div class="ac-dock-time"><span id="acDockCurrent">0:00</span> / <span id="acDockDuration">0:00</span></div>
<input id="acDockVolume" class="ac-volume" type="range" min="0" max="1" value="1" step="0.01">
<button type="button" id="acDockClose" class="ac-dock-close" aria-label="Close player">X</button>
<audio id="acDockAudio" preload="none"></audio>
</div>
</div>
<style>
.release-wrap {
min-width: 0;
}
.track-row.is-active { border-color: rgba(34,242,165,.45)!important; background: rgba(34,242,165,.08)!important; }
.track-play-btn,.ac-dock-toggle{height:34px;border:1px solid rgba(34,242,165,.35);border-radius:999px;background:rgba(34,242,165,.12);color:#bffff0;font-weight:700;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:0 14px}
.track-play-btn[disabled]{border-color:rgba(255,255,255,.2);background:rgba(255,255,255,.08);color:var(--muted);cursor:not-allowed}
.ac-dock{position:fixed;left:14px;right:14px;bottom:14px;z-index:50}
.ac-dock-inner{display:grid;grid-template-columns:minmax(210px,280px) 96px minmax(160px,1fr) 110px 110px 44px;gap:10px;align-items:center;padding:12px;border-radius:14px;border:1px solid rgba(255,255,255,.14);background:rgba(10,11,15,.95)}
.ac-dock-meta{display:grid;grid-template-columns:44px minmax(0,1fr);align-items:center;gap:10px;min-width:0}
.ac-dock-art{width:44px;height:44px;border-radius:10px;border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.06);overflow:hidden;display:grid;place-items:center;font-size:10px;color:var(--muted)}
.ac-dock-art img{width:100%;height:100%;object-fit:cover;display:block}
.ac-dock-title-wrap{min-width:0}.ac-dock-title{margin-top:4px;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ac-dock-time{font-size:12px;color:var(--muted);font-family:'IBM Plex Mono',monospace}
.ac-dock-close{height:34px;border-radius:10px;border:1px solid rgba(255,255,255,.2);background:rgba(255,255,255,.05);color:var(--muted);cursor:pointer}
.ac-seek,.ac-volume{-webkit-appearance:none;appearance:none;height:6px;border-radius:999px;background:rgba(255,255,255,.2);outline:none}
.ac-seek::-webkit-slider-thumb,.ac-volume::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:14px;height:14px;border-radius:50%;background:#22f2a5;border:1px solid rgba(0,0,0,.45)}
@media (max-width: 980px) {
.release-hero {
grid-template-columns: 1fr !important;
gap: 16px !important;
}
.release-cover-box {
max-width: 420px;
}
}
@media (max-width: 700px) {
.release-meta h1 {
font-size: 32px !important;
line-height: 1.12 !important;
}
.track-row {
grid-template-columns: 88px minmax(0,1fr) !important;
gap: 10px !important;
}
.track-row > :last-child {
display: none;
}
.ac-dock {
left: 8px;
right: 8px;
bottom: 8px;
}
.ac-dock-inner {
grid-template-columns: minmax(0,1fr) 90px !important;
grid-template-areas:
"meta close"
"toggle time"
"seek seek"
"volume volume";
row-gap: 8px;
}
.ac-dock-meta { grid-area: meta; }
.ac-dock-close { grid-area: close; }
.ac-dock-toggle { grid-area: toggle; }
.ac-dock-time { grid-area: time; text-align: right; }
.ac-seek { grid-area: seek; }
.ac-volume { grid-area: volume; }
}
</style>
<script>
(function () {
const dock = document.getElementById('acDock');
const audio = document.getElementById('acDockAudio');
const toggle = document.getElementById('acDockToggle');
const seek = document.getElementById('acDockSeek');
const volume = document.getElementById('acDockVolume');
const current = document.getElementById('acDockCurrent');
const duration = document.getElementById('acDockDuration');
const titleEl = document.getElementById('acDockTitle');
const dockArt = document.getElementById('acDockArt');
const closeBtn = document.getElementById('acDockClose');
const status = document.getElementById('releasePlayerNow');
const mainCover = document.getElementById('releaseCoverMain');
if (!dock || !audio || !toggle || !seek || !volume || !current || !duration || !titleEl || !closeBtn) return;
const mainCoverSrc = mainCover && mainCover.tagName === 'IMG' ? (mainCover.getAttribute('src') || '') : '';
const defaultCover = mainCoverSrc || <?= json_encode($releaseCover, JSON_UNESCAPED_SLASHES) ?> || '';
let activeBtn = null;
function fmt(sec){ if(!isFinite(sec)||sec<0) return '0:00'; const m=Math.floor(sec/60), s=Math.floor(sec%60); return m+':'+String(s).padStart(2,'0'); }
function setPlayState(btn,playing){ if(!btn) return; btn.innerHTML = playing ? '<i class="fa-solid fa-pause"></i> <span>Pause</span>' : '<i class="fa-solid fa-play"></i> <span>Play</span>'; }
function setDockArt(src){ if(!dockArt) return; const finalSrc = src || defaultCover; dockArt.innerHTML = finalSrc ? ('<img src="'+finalSrc.replace(/"/g,'&quot;')+'" alt="">') : 'AC'; }
function setActive(btn,title){
document.querySelectorAll('.track-play-btn').forEach((b)=>{ setPlayState(b,false); const row=b.closest('.track-row'); if(row) row.classList.remove('is-active'); });
activeBtn = btn;
if(btn){ setPlayState(btn,true); const row=btn.closest('.track-row'); if(row) row.classList.add('is-active'); }
titleEl.textContent = title || 'Track sample';
if(status) status.textContent = title ? ('Now Playing: '+title) : 'Select a track to play sample';
}
function openAndPlay(src,title,btn,cover){
if(!src) return;
if(dock.hidden) dock.hidden = false;
setDockArt(cover || '');
if(audio.getAttribute('src') === src){ if(audio.paused) audio.play().catch(()=>{}); else audio.pause(); return; }
audio.setAttribute('src', src);
setActive(btn,title);
audio.play().catch(()=>{ if(btn) setPlayState(btn,false); });
}
document.querySelectorAll('.track-play-btn').forEach((btn)=>{
btn.addEventListener('click', ()=>{
openAndPlay(btn.getAttribute('data-src') || '', btn.getAttribute('data-title') || 'Track sample', btn, btn.getAttribute('data-cover') || '');
});
});
toggle.addEventListener('click', ()=>{ if(!audio.getAttribute('src')) return; if(audio.paused) audio.play().catch(()=>{}); else audio.pause(); });
closeBtn.addEventListener('click', ()=>{ audio.pause(); audio.removeAttribute('src'); seek.value='0'; current.textContent='0:00'; duration.textContent='0:00'; setPlayState(toggle,false); setActive(null,''); setDockArt(''); dock.hidden=true; });
volume.addEventListener('input', ()=>{ audio.volume = Number(volume.value); });
seek.addEventListener('input', ()=>{ if(!isFinite(audio.duration)||audio.duration<=0) return; audio.currentTime = (Number(seek.value)/100)*audio.duration; });
audio.addEventListener('loadedmetadata', ()=>{ duration.textContent = fmt(audio.duration); });
audio.addEventListener('timeupdate', ()=>{ current.textContent = fmt(audio.currentTime); if(isFinite(audio.duration)&&audio.duration>0){ seek.value = String((audio.currentTime/audio.duration)*100); } });
audio.addEventListener('play', ()=>{ setPlayState(toggle,true); if(activeBtn) setPlayState(activeBtn,true); });
audio.addEventListener('pause', ()=>{ setPlayState(toggle,false); if(activeBtn) setPlayState(activeBtn,false); });
audio.addEventListener('ended', ()=>{ setPlayState(toggle,false); if(activeBtn) setPlayState(activeBtn,false); seek.value='0'; current.textContent='0:00'; });
})();
</script>
<?php
$content = ob_get_clean();
require __DIR__ . '/../../../../views/site/layout.php';