2026-03-04 20:46:11 +00:00
|
|
|
<?php
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
$pageTitle = $title ?? 'Release';
|
|
|
|
|
$release = $release ?? null;
|
|
|
|
|
$tracks = $tracks ?? [];
|
2026-04-01 14:12:17 +00:00
|
|
|
$bundles = is_array($bundles ?? null) ? $bundles : [];
|
2026-03-04 20:46:11 +00:00
|
|
|
$storePluginEnabled = (bool)($store_plugin_enabled ?? false);
|
|
|
|
|
$releaseCover = (string)($release['cover_url'] ?? '');
|
|
|
|
|
$returnUrl = (string)($_SERVER['REQUEST_URI'] ?? '/releases');
|
|
|
|
|
$releaseStoreEnabled = (int)($release['store_enabled'] ?? 0) === 1;
|
|
|
|
|
$bundlePrice = (float)($release['bundle_price'] ?? 0);
|
|
|
|
|
$bundleCurrency = (string)($release['store_currency'] ?? 'GBP');
|
|
|
|
|
$bundleLabel = trim((string)($release['purchase_label'] ?? ''));
|
|
|
|
|
$bundleLabel = $bundleLabel !== '' ? $bundleLabel : 'Buy Release';
|
|
|
|
|
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>
|
|
|
|
|
<?php if (!empty($release['artist_name'])): ?>
|
|
|
|
|
<div style="font-size:14px; color:var(--muted);">
|
|
|
|
|
By
|
|
|
|
|
<a href="/releases?artist=<?= rawurlencode((string)$release['artist_name']) ?>" style="color:#dfe7fb; text-decoration:none; border-bottom:1px solid rgba(223,231,251,.35);">
|
|
|
|
|
<?= htmlspecialchars((string)$release['artist_name'], ENT_QUOTES, 'UTF-8') ?>
|
|
|
|
|
</a>
|
|
|
|
|
</div>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
<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; ?>
|
|
|
|
|
<?php if ($storePluginEnabled && $releaseStoreEnabled && $bundlePrice > 0): ?>
|
|
|
|
|
<div style="margin-top:4px;">
|
|
|
|
|
<form method="post" action="/store/cart/add" style="margin:0;">
|
|
|
|
|
<input type="hidden" name="item_type" value="release">
|
|
|
|
|
<input type="hidden" name="item_id" value="<?= (int)($release['id'] ?? 0) ?>">
|
|
|
|
|
<input type="hidden" name="title" value="<?= htmlspecialchars((string)$release['title'], ENT_QUOTES, 'UTF-8') ?>">
|
|
|
|
|
<input type="hidden" name="cover_url" value="<?= htmlspecialchars($releaseCover, ENT_QUOTES, 'UTF-8') ?>">
|
|
|
|
|
<input type="hidden" name="currency" value="<?= htmlspecialchars($bundleCurrency, ENT_QUOTES, 'UTF-8') ?>">
|
|
|
|
|
<input type="hidden" name="price" value="<?= htmlspecialchars(number_format($bundlePrice, 2, '.', ''), ENT_QUOTES, 'UTF-8') ?>">
|
|
|
|
|
<input type="hidden" name="qty" value="1">
|
|
|
|
|
<input type="hidden" name="return_url" value="<?= htmlspecialchars($returnUrl, ENT_QUOTES, 'UTF-8') ?>">
|
|
|
|
|
<button type="submit" class="track-buy-btn">
|
|
|
|
|
<i class="fa-solid fa-cart-plus"></i>
|
|
|
|
|
<span><?= htmlspecialchars($bundleLabel, ENT_QUOTES, 'UTF-8') ?> <?= htmlspecialchars($bundleCurrency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($bundlePrice, 2) ?></span>
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
</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 ($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 style="display:flex; align-items:center; gap:10px;">
|
|
|
|
|
<div id="releasePlayerNow" style="font-size:12px; color:var(--muted);">Select a track to play sample</div>
|
|
|
|
|
</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="display:flex; align-items:center; gap:8px;">
|
|
|
|
|
<?php
|
|
|
|
|
$trackStoreEnabled = (int)($track['store_enabled'] ?? 0) === 1;
|
|
|
|
|
$trackPrice = (float)($track['track_price'] ?? 0);
|
|
|
|
|
$trackCurrency = (string)($track['store_currency'] ?? 'GBP');
|
|
|
|
|
?>
|
|
|
|
|
<?php if ($storePluginEnabled && $trackStoreEnabled && $trackPrice > 0): ?>
|
|
|
|
|
<form method="post" action="/store/cart/add" style="margin:0;">
|
|
|
|
|
<input type="hidden" name="item_type" value="track">
|
|
|
|
|
<input type="hidden" name="item_id" value="<?= (int)($track['id'] ?? 0) ?>">
|
|
|
|
|
<input type="hidden" name="title" value="<?= htmlspecialchars($fullTitle, ENT_QUOTES, 'UTF-8') ?>">
|
|
|
|
|
<input type="hidden" name="cover_url" value="<?= htmlspecialchars($releaseCover, ENT_QUOTES, 'UTF-8') ?>">
|
|
|
|
|
<input type="hidden" name="currency" value="<?= htmlspecialchars($trackCurrency, ENT_QUOTES, 'UTF-8') ?>">
|
|
|
|
|
<input type="hidden" name="price" value="<?= htmlspecialchars(number_format($trackPrice, 2, '.', ''), ENT_QUOTES, 'UTF-8') ?>">
|
|
|
|
|
<input type="hidden" name="qty" value="1">
|
|
|
|
|
<input type="hidden" name="return_url" value="<?= htmlspecialchars($returnUrl, ENT_QUOTES, 'UTF-8') ?>">
|
|
|
|
|
<button type="submit" class="track-buy-btn">
|
|
|
|
|
<i class="fa-solid fa-cart-plus"></i>
|
|
|
|
|
<span>Buy <?= htmlspecialchars($trackCurrency, ENT_QUOTES, 'UTF-8') ?> <?= number_format($trackPrice, 2) ?></span>
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
<?php else: ?>
|
|
|
|
|
<div style="font-size:11px; color:var(--muted); text-transform:uppercase; letter-spacing:0.18em;">
|
|
|
|
|
<?= $sample !== '' ? 'Sample' : 'No sample' ?>
|
|
|
|
|
</div>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
2026-04-01 14:12:17 +00:00
|
|
|
<?php if ($storePluginEnabled && $bundles): ?>
|
|
|
|
|
<div class="bundle-zone" style="display:grid; gap:10px;">
|
|
|
|
|
<div class="badge">Bundle Deals</div>
|
|
|
|
|
<div style="display:grid; gap:10px;">
|
|
|
|
|
<?php foreach ($bundles as $bundle): ?>
|
|
|
|
|
<?php
|
|
|
|
|
$bundleId = (int)($bundle['id'] ?? 0);
|
|
|
|
|
$bundleName = trim((string)($bundle['name'] ?? 'Bundle'));
|
|
|
|
|
$bundlePriceRow = (float)($bundle['bundle_price'] ?? 0);
|
|
|
|
|
$bundleCurrencyRow = (string)($bundle['currency'] ?? 'GBP');
|
|
|
|
|
$bundleLabelRow = trim((string)($bundle['purchase_label'] ?? ''));
|
|
|
|
|
$bundleLabelRow = $bundleLabelRow !== '' ? $bundleLabelRow : 'Buy Bundle';
|
|
|
|
|
$bundleCovers = is_array($bundle['covers'] ?? null) ? $bundle['covers'] : [];
|
|
|
|
|
$bundleCount = (int)($bundle['release_count'] ?? 0);
|
|
|
|
|
$regularTotal = (float)($bundle['regular_total'] ?? 0);
|
|
|
|
|
$saving = max(0, $regularTotal - $bundlePriceRow);
|
|
|
|
|
?>
|
|
|
|
|
<div class="bundle-card">
|
|
|
|
|
<div class="bundle-stack" style="--cover-count:<?= max(1, min(5, count($bundleCovers))) ?>;" aria-hidden="true">
|
|
|
|
|
<?php if ($bundleCovers): ?>
|
|
|
|
|
<?php foreach (array_slice($bundleCovers, 0, 5) as $i => $cover): ?>
|
|
|
|
|
<span class="bundle-cover" style="--i:<?= (int)$i ?>;">
|
|
|
|
|
<img src="<?= htmlspecialchars((string)$cover, ENT_QUOTES, 'UTF-8') ?>" alt="">
|
|
|
|
|
</span>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
<?php else: ?>
|
|
|
|
|
<span class="bundle-cover bundle-fallback">AC</span>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="bundle-copy">
|
|
|
|
|
<div class="bundle-title"><?= htmlspecialchars($bundleName, ENT_QUOTES, 'UTF-8') ?></div>
|
|
|
|
|
<div class="bundle-meta">
|
|
|
|
|
<?= $bundleCount > 0 ? $bundleCount . ' releases' : 'Multi-release bundle' ?>
|
|
|
|
|
<?php if ($saving > 0): ?>
|
|
|
|
|
<span class="bundle-save">Save <?= htmlspecialchars($bundleCurrencyRow, ENT_QUOTES, 'UTF-8') ?> <?= number_format($saving, 2) ?></span>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<form method="post" action="/store/cart/add" style="margin:0;">
|
|
|
|
|
<input type="hidden" name="item_type" value="bundle">
|
|
|
|
|
<input type="hidden" name="item_id" value="<?= $bundleId ?>">
|
|
|
|
|
<input type="hidden" name="title" value="<?= htmlspecialchars($bundleName, ENT_QUOTES, 'UTF-8') ?>">
|
|
|
|
|
<input type="hidden" name="cover_url" value="<?= htmlspecialchars((string)($bundleCovers[0] ?? $releaseCover), ENT_QUOTES, 'UTF-8') ?>">
|
|
|
|
|
<input type="hidden" name="currency" value="<?= htmlspecialchars($bundleCurrencyRow, ENT_QUOTES, 'UTF-8') ?>">
|
|
|
|
|
<input type="hidden" name="price" value="<?= htmlspecialchars(number_format($bundlePriceRow, 2, '.', ''), ENT_QUOTES, 'UTF-8') ?>">
|
|
|
|
|
<input type="hidden" name="qty" value="1">
|
|
|
|
|
<input type="hidden" name="return_url" value="<?= htmlspecialchars($returnUrl, ENT_QUOTES, 'UTF-8') ?>">
|
|
|
|
|
<button type="submit" class="track-buy-btn">
|
|
|
|
|
<i class="fa-solid fa-cart-plus"></i>
|
|
|
|
|
<span><?= htmlspecialchars($bundleLabelRow, ENT_QUOTES, 'UTF-8') ?> <?= htmlspecialchars($bundleCurrencyRow, ENT_QUOTES, 'UTF-8') ?> <?= number_format($bundlePriceRow, 2) ?></span>
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
<?php endforeach; ?>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<?php endif; ?>
|
|
|
|
|
|
2026-03-04 20:46:11 +00:00
|
|
|
<?php if (!empty($release['credits'])): ?>
|
|
|
|
|
<div style="padding:12px 14px; border-radius:14px; border:1px solid rgba(255,255,255,0.08); background:rgba(0,0,0,0.2);">
|
|
|
|
|
<div class="badge" style="font-size:9px;">Credits</div>
|
|
|
|
|
<div style="margin-top:6px; color:var(--muted); line-height:1.55; font-size:13px;">
|
|
|
|
|
<?= nl2br(htmlspecialchars((string)$release['credits'], ENT_QUOTES, 'UTF-8')) ?>
|
|
|
|
|
</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-buy-btn{height:34px;border:1px solid rgba(255,255,255,.18);border-radius:999px;background:rgba(255,255,255,.08);color:#e9eefc;font-weight:700;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:0 14px;font-size:12px;white-space:nowrap}
|
|
|
|
|
.track-buy-btn:hover{background:rgba(255,255,255,.14)}
|
|
|
|
|
.track-play-btn[disabled]{border-color:rgba(255,255,255,.2);background:rgba(255,255,255,.08);color:var(--muted);cursor:not-allowed}
|
2026-04-01 14:12:17 +00:00
|
|
|
.bundle-card{display:grid;grid-template-columns:max-content minmax(0,1fr) auto;gap:14px;align-items:center;padding:12px;border-radius:14px;border:1px solid rgba(255,255,255,.1);background:linear-gradient(180deg,rgba(255,255,255,.03),rgba(255,255,255,.01))}
|
|
|
|
|
.bundle-stack{position:relative;height:70px;width:calc(70px + (max(0, var(--cover-count, 1) - 1) * 16px));}
|
|
|
|
|
.bundle-cover{position:absolute;left:calc(var(--i,0) * 16px);top:0;width:70px;height:70px;border-radius:10px;overflow:hidden;border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.04);box-shadow:0 8px 20px rgba(0,0,0,.35)}
|
|
|
|
|
.bundle-cover img{width:100%;height:100%;object-fit:cover}
|
|
|
|
|
.bundle-fallback{display:grid;place-items:center;font-size:11px;color:var(--muted)}
|
|
|
|
|
.bundle-copy{min-width:0}
|
|
|
|
|
.bundle-title{font-size:17px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
|
|
|
.bundle-meta{margin-top:6px;font-size:12px;color:var(--muted);display:flex;flex-wrap:wrap;gap:10px;align-items:center}
|
|
|
|
|
.bundle-save{color:#9ff7d5;font-weight:700;letter-spacing:.08em;text-transform:uppercase;font-size:11px}
|
2026-03-04 20:46:11 +00:00
|
|
|
.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-buy-btn { font-size: 11px; padding: 0 10px; }
|
2026-04-01 14:12:17 +00:00
|
|
|
.bundle-card{grid-template-columns:1fr;gap:10px}
|
|
|
|
|
.bundle-stack{height:62px}
|
|
|
|
|
.bundle-cover{width:62px;height:62px}
|
|
|
|
|
.ac-dock {
|
2026-03-04 20:46:11 +00:00
|
|
|
left: 8px;
|
|
|
|
|
right: 8px;
|
|
|
|
|
bottom: 8px;
|
|
|
|
|
}
|
|
|
|
|
.ac-dock-inner {
|
|
|
|
|
grid-template-columns: minmax(0,1fr) 96px auto 44px !important;
|
|
|
|
|
grid-template-areas:
|
|
|
|
|
"meta toggle time close"
|
|
|
|
|
"seek seek seek seek";
|
|
|
|
|
row-gap: 8px;
|
|
|
|
|
padding: 10px;
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
}
|
|
|
|
|
.ac-dock-meta { grid-area: meta; gap: 8px; }
|
|
|
|
|
.ac-dock-art { width: 38px; height: 38px; border-radius: 8px; }
|
|
|
|
|
.ac-dock-title { font-size: 12px; }
|
|
|
|
|
.ac-dock-close { grid-area: close; width: 44px; justify-self: end; }
|
|
|
|
|
.ac-dock-toggle {
|
|
|
|
|
grid-area: toggle;
|
|
|
|
|
height: 32px;
|
|
|
|
|
padding: 0 8px;
|
|
|
|
|
border-radius: 999px;
|
|
|
|
|
justify-self: center;
|
|
|
|
|
width: 96px;
|
|
|
|
|
min-width: 96px;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
}
|
|
|
|
|
.ac-dock-time {
|
|
|
|
|
grid-area: time;
|
|
|
|
|
text-align: right;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
align-self: center;
|
|
|
|
|
}
|
|
|
|
|
.ac-seek { grid-area: seek; margin-top: 0; align-self: center; }
|
|
|
|
|
.ac-volume { display: none; }
|
|
|
|
|
}
|
|
|
|
|
</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,'"')+'" 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';
|