mirror of
https://github.com/avinal/avinal.github.io.git
synced 2026-07-03 23:30:09 +05:30
feat: use custom CSS for listenbrinaz data
- use custom Ui instead of embed Signed-off-by: Avinal Kumar <avinal.xlvii@gmail.com>
This commit is contained in:
+320
-111
@@ -7,7 +7,6 @@ interface Props {
|
|||||||
|
|
||||||
const { lb } = Astro.props;
|
const { lb } = Astro.props;
|
||||||
const profileUrl = `https://listenbrainz.org/user/${lb.username}`;
|
const profileUrl = `https://listenbrainz.org/user/${lb.username}`;
|
||||||
const embedUrl = `https://listenbrainz.org/user/${lb.username}/embed/playing-now`;
|
|
||||||
|
|
||||||
function timeAgo(ts?: number): string {
|
function timeAgo(ts?: number): string {
|
||||||
if (!ts) return "";
|
if (!ts) return "";
|
||||||
@@ -17,137 +16,202 @@ function timeAgo(ts?: number): string {
|
|||||||
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
|
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
|
||||||
return `${Math.floor(diff / 86400)}d ago`;
|
return `${Math.floor(diff / 86400)}d ago`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isLive = !!lb.nowPlaying;
|
||||||
|
const hero = lb.nowPlaying ?? lb.recentTracks[0] ?? null;
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="music-card card">
|
<div class="mc card" id="music-widget" data-user={lb.username} data-live={isLive ? "1" : "0"}>
|
||||||
<div class="music-header">
|
<div class="mc-header">
|
||||||
<h3 class="widget-title">
|
<h3 class="widget-title">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>
|
||||||
<a href={profileUrl} target="_blank" rel="noopener noreferrer" class="lb-title-link">Listening</a>
|
<a href={profileUrl} target="_blank" rel="noopener noreferrer" class="mc-link">Listening</a>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{lb.available ? (
|
<div class="mc-body" id="mc-body">
|
||||||
<div class="lb-content">
|
{hero ? (
|
||||||
<div class="lb-now-embed">
|
<>
|
||||||
<iframe
|
<div class="mc-hero" id="mc-hero">
|
||||||
src={embedUrl}
|
<div class="mc-art-wrap">
|
||||||
title="Now playing on ListenBrainz"
|
|
||||||
frameborder="0"
|
|
||||||
scrolling="no"
|
|
||||||
loading="lazy"
|
|
||||||
></iframe>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{lb.recentTracks.length > 0 && (
|
|
||||||
<div class="lb-recent-section">
|
|
||||||
<span class="lb-recent-label">Recent</span>
|
|
||||||
<ul class="lb-recent">
|
|
||||||
{lb.recentTracks.slice(0, 4).map((t) => (
|
|
||||||
<li class="lb-track">
|
|
||||||
{t.coverArtUrl && (
|
|
||||||
<img
|
<img
|
||||||
class="lb-cover"
|
class="mc-art"
|
||||||
src={t.coverArtUrl}
|
src={hero.coverArtUrl ?? ""}
|
||||||
alt=""
|
alt=""
|
||||||
loading="lazy"
|
|
||||||
onerror="this.style.display='none'"
|
onerror="this.style.display='none'"
|
||||||
/>
|
/>
|
||||||
)}
|
<div class="mc-art-ph">
|
||||||
<div class="lb-info">
|
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" opacity="0.25"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>
|
||||||
<span class="lb-name">
|
|
||||||
{t.listenUrl ? (
|
|
||||||
<a href={t.listenUrl} target="_blank" rel="noopener noreferrer">{t.trackName}</a>
|
|
||||||
) : t.trackName}
|
|
||||||
</span>
|
|
||||||
<span class="lb-artist">{t.artistName}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<span class="lb-time">{timeAgo(t.listenedAt)}</span>
|
</div>
|
||||||
|
<div class="mc-overlay">
|
||||||
|
<span class="mc-badge" id="mc-badge">
|
||||||
|
{isLive ? (<><span class="mc-dot"></span>playing</>) : "last played"}
|
||||||
|
</span>
|
||||||
|
<div class="mc-marquee-wrap">
|
||||||
|
<span class="mc-marquee" id="mc-marquee">{hero.trackName} — {hero.artistName}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="mc-recent" id="mc-recent">
|
||||||
|
{lb.recentTracks.slice(0, 4).map((t) => (
|
||||||
|
<li class="mc-track">
|
||||||
|
<img
|
||||||
|
class="mc-thumb"
|
||||||
|
src={t.coverArtUrl ?? ""}
|
||||||
|
alt=""
|
||||||
|
loading="lazy"
|
||||||
|
onerror="this.classList.add('mc-thumb-hide')"
|
||||||
|
/>
|
||||||
|
<div class="mc-track-info">
|
||||||
|
<span class="mc-track-name">{t.trackName}</span>
|
||||||
|
<span class="mc-track-artist">{t.artistName}</span>
|
||||||
|
</div>
|
||||||
|
<span class="mc-track-time">{timeAgo(t.listenedAt)}</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</>
|
||||||
)}
|
|
||||||
|
|
||||||
{lb.recentTracks.length === 0 && (
|
|
||||||
<div class="lb-empty">
|
|
||||||
<span class="text-muted text-xs">No recent listens yet</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<div class="lb-empty">
|
<div class="mc-empty">
|
||||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="opacity: 0.3"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>
|
<span class="text-muted text-xs">No listening data yet</span>
|
||||||
<span class="text-muted text-xs">Connect ListenBrainz to see listening activity</span>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<a href="https://listenbrainz.org" class="mc-powered" target="_blank" rel="noopener noreferrer">powered by ListenBrainz</a>
|
||||||
.music-card {
|
</div>
|
||||||
|
|
||||||
|
<style is:global>
|
||||||
|
.mc {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 220px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.music-header {
|
.mc-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lb-title-link {
|
.mc-header .widget-title {
|
||||||
color: inherit;
|
margin-bottom: 0;
|
||||||
text-decoration: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.lb-title-link:hover {
|
.mc-link { color: inherit; text-decoration: none; }
|
||||||
color: var(--accent);
|
.mc-link:hover { color: var(--accent); }
|
||||||
}
|
|
||||||
|
|
||||||
.lb-content {
|
.mc-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--space-3);
|
gap: var(--space-2);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lb-now-embed {
|
/* ---- Hero: art + overlay ---- */
|
||||||
|
.mc-hero {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
overflow: hidden;
|
|
||||||
height: 85px;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
background: var(--bg-surface-hover);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.lb-now-embed iframe {
|
.mc-art-wrap {
|
||||||
width: calc(100% + 2px);
|
position: relative;
|
||||||
height: 85px;
|
width: 100%;
|
||||||
border: none;
|
aspect-ratio: 1;
|
||||||
|
max-height: 200px;
|
||||||
|
background: linear-gradient(135deg, var(--bg-surface-hover) 0%, var(--border) 100%);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mc-art {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
display: block;
|
display: block;
|
||||||
margin: -1px;
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lb-recent-section {
|
.mc-art-ph {
|
||||||
flex: 1;
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mc-overlay {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: var(--space-3) var(--space-2) var(--space-2);
|
||||||
|
background: linear-gradient(to top, rgba(0,0,0,0.78) 0%, rgba(0,0,0,0.3) 65%, transparent 100%);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--space-1);
|
gap: 3px;
|
||||||
overflow: hidden;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lb-recent-label {
|
.mc-badge {
|
||||||
font-size: var(--text-xs);
|
font-size: 10px;
|
||||||
color: var(--text-muted);
|
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.08em;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
color: rgba(255,255,255,0.7);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lb-recent {
|
.mc-dot {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #22c55e;
|
||||||
|
animation: mc-pulse 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes mc-pulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.35; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.mc-marquee-wrap {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
mask-image: linear-gradient(to right, transparent, black 6%, black 94%, transparent);
|
||||||
|
-webkit-mask-image: linear-gradient(to right, transparent, black 6%, black 94%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mc-marquee {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
font-weight: 600;
|
||||||
|
color: #fff;
|
||||||
|
animation: mc-scroll 12s linear infinite;
|
||||||
|
padding-right: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes mc-scroll {
|
||||||
|
0% { transform: translateX(0); }
|
||||||
|
100% { transform: translateX(-50%); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.mc[data-live="0"] .mc-marquee {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Recent list ---- */
|
||||||
|
.mc-recent {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -158,80 +222,225 @@ function timeAgo(ts?: number): string {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lb-track {
|
.mc-track {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--space-2);
|
gap: var(--space-2);
|
||||||
padding: var(--space-1) var(--space-2);
|
padding: 3px 0;
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
transition: background var(--duration-fast) var(--ease-out);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.lb-track:hover {
|
.mc-thumb {
|
||||||
background: var(--bg-surface-hover);
|
width: 28px;
|
||||||
}
|
height: 28px;
|
||||||
|
|
||||||
.lb-cover {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
background: var(--border);
|
background: var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.lb-info {
|
.mc-thumb-hide { display: none; }
|
||||||
|
|
||||||
|
.mc-track-info {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lb-name {
|
.mc-track-name {
|
||||||
font-size: var(--text-xs);
|
font-size: var(--text-xs);
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
color: var(--text);
|
color: var(--text-secondary);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lb-name a {
|
.mc-track-artist {
|
||||||
color: inherit;
|
font-size: 11px;
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lb-name a:hover {
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.lb-artist {
|
|
||||||
font-size: var(--text-xs);
|
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lb-time {
|
.mc-track-time {
|
||||||
font-size: var(--text-xs);
|
font-size: 11px;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lb-empty {
|
.mc-empty {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: var(--space-2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
.mc-powered {
|
||||||
.music-card {
|
display: block;
|
||||||
min-height: 180px;
|
text-align: center;
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0.6;
|
||||||
|
padding-top: var(--space-2);
|
||||||
|
margin-top: auto;
|
||||||
|
transition: opacity var(--duration-fast) var(--ease-out);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mc-powered:hover {
|
||||||
|
opacity: 1;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Mid-size: side-by-side (art left, recents right) ---- */
|
||||||
|
@media (min-width: 600px) and (max-width: 1100px) {
|
||||||
|
.mc-body {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mc-hero {
|
||||||
|
width: 150px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mc-art-wrap {
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
max-height: none;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mc-recent {
|
||||||
|
flex: 1;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Mobile: column, smaller art ---- */
|
||||||
|
@media (max-width: 599px) {
|
||||||
|
.mc-art-wrap {
|
||||||
|
max-height: 160px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const API = "https://api.listenbrainz.org/1";
|
||||||
|
const widget = document.getElementById("music-widget");
|
||||||
|
|
||||||
|
if (widget) {
|
||||||
|
const user = widget.dataset.user;
|
||||||
|
const body = document.getElementById("mc-body")!;
|
||||||
|
|
||||||
|
function timeAgo(ts: number): string {
|
||||||
|
const diff = Math.floor(Date.now() / 1000) - ts;
|
||||||
|
if (diff < 60) return "just now";
|
||||||
|
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
|
||||||
|
if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
|
||||||
|
return `${Math.floor(diff / 86400)}d ago`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function coverUrl(listen: any): string {
|
||||||
|
const mbids = listen.track_metadata?.mbid_mapping ?? {};
|
||||||
|
return mbids.release_mbid
|
||||||
|
? `https://coverartarchive.org/release/${mbids.release_mbid}/front-250`
|
||||||
|
: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function esc(s: string): string {
|
||||||
|
const d = document.createElement("div");
|
||||||
|
d.textContent = s;
|
||||||
|
return d.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderHero(listen: any, isLive: boolean): string {
|
||||||
|
const meta = listen.track_metadata ?? {};
|
||||||
|
const cover = coverUrl(listen);
|
||||||
|
const track = esc(meta.track_name ?? "Unknown");
|
||||||
|
const artist = esc(meta.artist_name ?? "Unknown");
|
||||||
|
const marqueeText = `${track} — ${artist}`;
|
||||||
|
|
||||||
|
const badge = isLive
|
||||||
|
? `<span class="mc-dot"></span>playing`
|
||||||
|
: "last played";
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="mc-hero">
|
||||||
|
<div class="mc-art-wrap">
|
||||||
|
${cover ? `<img class="mc-art" src="${cover}" alt="" onerror="this.style.display='none'" />` : ""}
|
||||||
|
<div class="mc-art-ph">
|
||||||
|
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" opacity="0.25"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mc-overlay">
|
||||||
|
<span class="mc-badge">${badge}</span>
|
||||||
|
<div class="mc-marquee-wrap">
|
||||||
|
<span class="mc-marquee">${marqueeText}${isLive ? ` ${marqueeText}` : ""}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderRecent(listens: any[]): string {
|
||||||
|
return listens.slice(0, 4).map((l) => {
|
||||||
|
const meta = l.track_metadata ?? {};
|
||||||
|
const cover = coverUrl(l);
|
||||||
|
const ago = l.listened_at ? timeAgo(l.listened_at) : "";
|
||||||
|
const thumb = cover
|
||||||
|
? `<img class="mc-thumb" src="${cover}" alt="" loading="lazy" onerror="this.classList.add('mc-thumb-hide')" />`
|
||||||
|
: "";
|
||||||
|
return `<li class="mc-track">
|
||||||
|
${thumb}
|
||||||
|
<div class="mc-track-info">
|
||||||
|
<span class="mc-track-name">${esc(meta.track_name ?? "Unknown")}</span>
|
||||||
|
<span class="mc-track-artist">${esc(meta.artist_name ?? "Unknown")}</span>
|
||||||
|
</div>
|
||||||
|
<span class="mc-track-time">${ago}</span>
|
||||||
|
</li>`;
|
||||||
|
}).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refresh() {
|
||||||
|
try {
|
||||||
|
const [npRes, recRes] = await Promise.all([
|
||||||
|
fetch(`${API}/user/${user}/playing-now`),
|
||||||
|
fetch(`${API}/user/${user}/listens?count=5`),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let np: any = null;
|
||||||
|
if (npRes.ok) {
|
||||||
|
const d = await npRes.json();
|
||||||
|
np = d?.payload?.listens?.[0] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let listens: any[] = [];
|
||||||
|
if (recRes.ok) {
|
||||||
|
const d = await recRes.json();
|
||||||
|
listens = d?.payload?.listens ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLive = !!np;
|
||||||
|
const hero = np ?? listens[0] ?? null;
|
||||||
|
|
||||||
|
widget.setAttribute("data-live", isLive ? "1" : "0");
|
||||||
|
|
||||||
|
if (!hero) {
|
||||||
|
body.innerHTML = `<div class="mc-empty"><span class="text-muted text-xs">No listening data yet</span></div>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.innerHTML = renderHero(hero, isLive)
|
||||||
|
+ `<ul class="mc-recent">${renderRecent(listens)}</ul>`;
|
||||||
|
} catch {
|
||||||
|
// keep existing content on error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
setInterval(refresh, 30_000);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user