RECORDGOD/site/release.html
type-two 28a46615a5 feat(shop): entity browse pages — artist/label/genre/style storefront views
records.html reads /artist|label|genre|style/{val} from the path, sets the
filter + an entity header (artist/label name via /shop/{kind}/{id}). /shop/browse
gains artist/label EXISTS filters + primary artist_id; release page links
artist→/artist/{id} and genre/style pills→/{kind}/{name}.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 01:18:15 +10:00

85 lines
5.9 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Release</title>
<style>
:root{--primary:#ff5db1;--accent:#46d18a;--bg:#0c0c0e;--panel:#141418;--text:#f0f0f2;--mut:#8a8a98;--line:#26262c;--radius:12px;--font:system-ui}
*{box-sizing:border-box}
html,body{margin:0;background:var(--bg);color:var(--text);font-family:var(--font),system-ui,sans-serif}
a{color:inherit;text-decoration:none}
header{display:flex;align-items:center;gap:16px;padding:14px 22px;border-bottom:1px solid var(--line);position:sticky;top:0;background:var(--bg);z-index:10}
.logo{font-weight:800;font-size:20px}.logo b{color:var(--primary)}.logo img{height:30px;vertical-align:middle}
nav.menu{display:flex;gap:16px;flex:1}nav.menu a{color:var(--mut);font-size:14px}nav.menu a:hover{color:var(--text)}
button,.btn{cursor:pointer;border:0;border-radius:9px;font:600 14px var(--font),system-ui;padding:11px 16px;background:var(--primary);color:#10070c}
.ghost{background:var(--panel);color:var(--text);border:1px solid var(--line)}
.wrap{max-width:1080px;margin:0 auto;padding:24px}
.crumb{color:var(--mut);font-size:13px;margin-bottom:14px}.crumb a:hover{color:var(--text)}
.top{display:grid;grid-template-columns:340px 1fr;gap:28px}
.cov{width:100%;aspect-ratio:1;object-fit:cover;border-radius:var(--radius);background:#222;border:1px solid var(--line)}
h1{font-size:26px;margin:0 0 2px}.artist{font-size:18px;color:var(--mut);margin-bottom:14px}
.meta{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:14px}
.pill{background:var(--panel);border:1px solid var(--line);border-radius:20px;padding:4px 11px;font-size:12px;color:var(--mut)}
.copies{margin:16px 0}
.copy{display:flex;align-items:center;gap:12px;background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:12px 14px;margin-bottom:8px}
.copy .cd{flex:1}.copy .pr{font-size:20px;font-weight:800;color:var(--primary)}
.sec{margin-top:26px}.sec h3{font-size:15px;border-bottom:1px solid var(--line);padding-bottom:6px}
table{width:100%;border-collapse:collapse;font-size:14px}td{padding:6px 4px;border-bottom:1px solid var(--line)}
.muted{color:var(--mut)}.desc{color:#c8c8d0;line-height:1.6;font-size:14px;white-space:pre-wrap}
@media(max-width:760px){.top{grid-template-columns:1fr}}
</style>
</head>
<body>
<header>
<a class="logo" href="/records" id="logo">Record<b>God</b></a>
<nav class="menu" id="menu"></nav>
</header>
<div class="wrap" id="app"><div class="muted">loading…</div></div>
<script>
const $=s=>document.querySelector(s);
const esc=s=>(s||'').replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));
const money=n=>n==null?'':'$'+Number(n).toFixed(2);
const RID=(location.pathname.match(/\/release\/(\d+)/)||[])[1] || new URLSearchParams(location.search).get('id');
let CFG={};
async function boot(){
CFG=await fetch('/shop/config').then(r=>r.json()).catch(()=>({}));
const t=CFG.theme||{}, R=document.documentElement.style;
for(const [k,v] of Object.entries({'--primary':t.primary,'--accent':t.accent,'--bg':t.bg,'--panel':t.panel,'--text':t.text,'--font':t.font})) if(v) R.setProperty(k,v);
if(t.radius) R.setProperty('--radius',t.radius+'px');
if(t.logo) $('#logo').innerHTML=`<img src="${esc(t.logo)}">`;
$('#menu').innerHTML=(CFG.menu||[]).map(m=>`<a href="${esc(m.href||'#')}">${esc(m.label||'')}</a>`).join('');
render();
}
async function render(){
if(!RID){ $('#app').innerHTML='<div class="muted">no release</div>'; return; }
const d=await fetch('/shop/release/'+RID).then(r=>r.json());
const r=d.release;
const show=k=>(CFG.product_page||['cover','title','artist','price','condition','genre','tracklist','cart']).includes(k);
if(!r){ $('#app').innerHTML=`<div class="muted">release ${esc(RID)} not found</div>`; return; }
const meta=[]; if(show('label')&&r.label)meta.push(r.label); if(show('format')&&r.format)meta.push(r.format);
if(show('country')&&r.country)meta.push(r.country); if(show('year')&&r.year)meta.push(r.year);
const tags=[]; if(show('genre')&&r.genre)r.genre.split(', ').forEach(g=>tags.push({n:g,k:'genre'})); if(r.style)r.style.split(', ').forEach(s=>tags.push({n:s,k:'style'}));
const aHtml=r.artist_id?`<a href="/artist/${r.artist_id}">${esc(r.artist||'')}</a>`:esc(r.artist||'');
const buy=c=> c.product_url ? `<a class="btn" href="${esc(c.product_url)}" target="_blank">Buy online</a>` : `<span class="muted">in store</span>`;
$('#app').innerHTML=`
<div class="crumb"><a href="/records">Records</a> ${aHtml}</div>
<div class="top">
<div>${show('cover')?`<img class="cov" src="/img/r/${r.id}" onerror="this.style.visibility='hidden'">`:''}</div>
<div>
${show('title')?`<h1>${esc(r.title||'')}</h1>`:''}${show('artist')?`<div class="artist">${aHtml}</div>`:''}
<div class="meta">${meta.map(m=>`<span class="pill">${esc(String(m))}</span>`).join('')}</div>
<div class="meta">${tags.map(t=>`<a class="pill" href="/${t.k}/${encodeURIComponent(t.n)}">${esc(t.n)}</a>`).join('')}</div>
${show('cart')||show('price')?`<div class="copies">${(d.copies||[]).map(c=>`<div class="copy">
<div class="cd"><div class="pr">${money(c.price)}</div><div class="muted">${esc(c.condition||'')}${c.sleeve_cond?' / '+esc(c.sleeve_cond):''} · ${esc(c.sku)}</div></div>${buy(c)}</div>`).join('')||'<div class="muted">no copies in stock</div>'}</div>`:''}
</div>
</div>
${show('tracklist')&&d.tracks.length?`<div class="sec"><h3>Tracklist</h3><table>${d.tracks.map(t=>`<tr><td class="muted" style="width:40px">${esc(t.position||'')}</td><td>${esc(t.title||'')}</td><td class="muted" style="width:50px;text-align:right">${esc(t.duration||'')}</td></tr>`).join('')}</table></div>`:''}
${show('description')&&r.notes?`<div class="sec"><h3>Notes</h3><div class="desc">${esc(r.notes)}</div></div>`:''}`;
}
boot();
</script>
</body>
</html>