02f13db22c
Replaces build-time Eleventy data fetches with live client-side fetches so listening data is always current without requiring a blog rebuild. - Add js/listening.js with three Alpine.data components: listeningWidget, funkwhalePage, listeningPage - Replace _data/funkwhaleActivity.js and lastfmActivity.js with sync stubs (source: 'indiekit') — keeps slashes.njk links and widget guard working at build time - Rewrite widgets/funkwhale.njk, funkwhale.njk, listening.njk as Alpine-driven templates with loading skeletons and error states - Load listening.js globally in base.njk before Alpine core Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
96 lines
5.2 KiB
Plaintext
96 lines
5.2 KiB
Plaintext
{# Listening Widget — combined Funkwhale + Last.fm recent tracks (client-side) #}
|
|
{% set hasListening = (funkwhaleActivity and funkwhaleActivity.source == 'indiekit') or (lastfmActivity and lastfmActivity.source == 'indiekit') %}
|
|
{% if hasListening %}
|
|
<is-land on:visible>
|
|
<div class="widget" x-data="listeningWidget()" x-init="init()">
|
|
<h3 class="widget-title flex items-center gap-2">
|
|
<svg class="w-5 h-5 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3"/>
|
|
</svg>
|
|
Listening
|
|
</h3>
|
|
|
|
{# Loading skeleton #}
|
|
<div x-show="loading" class="space-y-2 animate-pulse">
|
|
<div class="h-4 bg-surface-200 dark:bg-surface-700 rounded w-3/4"></div>
|
|
<div class="h-4 bg-surface-200 dark:bg-surface-700 rounded w-1/2"></div>
|
|
<div class="h-4 bg-surface-200 dark:bg-surface-700 rounded w-2/3"></div>
|
|
</div>
|
|
|
|
{# Error state #}
|
|
<p x-show="!loading && error" class="text-xs text-surface-500 dark:text-surface-400">Unavailable</p>
|
|
|
|
{# Now Playing #}
|
|
<template x-if="!loading && nowPlaying">
|
|
<div class="bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg p-3 mb-3">
|
|
<div class="flex items-center gap-1.5 text-xs text-green-600 dark:text-green-400 mb-2">
|
|
<span class="flex gap-0.5 items-end h-2.5" aria-hidden="true">
|
|
<span class="w-0.5 bg-green-500 animate-pulse" style="height: 30%;"></span>
|
|
<span class="w-0.5 bg-green-500 animate-pulse" style="height: 70%; animation-delay: 0.2s;"></span>
|
|
<span class="w-0.5 bg-green-500 animate-pulse" style="height: 50%; animation-delay: 0.4s;"></span>
|
|
</span>
|
|
Now Playing
|
|
<span class="ml-1" :class="nowPlaying?._source === 'funkwhale' ? 'text-purple-600 dark:text-purple-400' : 'text-red-600 dark:text-red-400'" x-text="nowPlaying?._source === 'funkwhale' ? '(Funkwhale)' : '(Last.fm)'"></span>
|
|
</div>
|
|
<div class="flex items-center gap-3">
|
|
<template x-if="nowPlaying?.coverUrl">
|
|
<img :src="nowPlaying.coverUrl" alt="" class="w-10 h-10 rounded object-cover flex-shrink-0 shadow-lg" loading="lazy">
|
|
</template>
|
|
<div class="min-w-0 flex-1">
|
|
<p class="font-medium text-sm text-surface-900 dark:text-surface-100 truncate">
|
|
<template x-if="nowPlaying?.trackUrl">
|
|
<a :href="nowPlaying.trackUrl" class="hover:text-purple-600 dark:hover:text-purple-400" target="_blank" rel="noopener" x-text="nowPlaying.track"></a>
|
|
</template>
|
|
<template x-if="!nowPlaying?.trackUrl">
|
|
<span x-text="nowPlaying?.track"></span>
|
|
</template>
|
|
</p>
|
|
<p class="text-xs text-surface-600 dark:text-surface-400 truncate" x-text="nowPlaying?.artist"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
{# Recent tracks #}
|
|
<ul x-show="!loading && recentTracks.length" class="space-y-2">
|
|
<template x-for="item in recentTracks" :key="item._ts + item.track">
|
|
<li class="flex items-center gap-2">
|
|
<template x-if="item.coverUrl">
|
|
<img :src="item.coverUrl" alt="" class="w-8 h-8 rounded object-cover flex-shrink-0 shadow-lg" loading="lazy">
|
|
</template>
|
|
<template x-if="!item.coverUrl">
|
|
<div :class="item._source === 'funkwhale' ? 'bg-purple-100 dark:bg-purple-900/30' : 'bg-red-100 dark:bg-red-900/30'" class="w-8 h-8 rounded flex items-center justify-center flex-shrink-0">
|
|
<svg class="w-4 h-4" :class="item._source === 'funkwhale' ? 'text-purple-400' : 'text-red-400'" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19V6l12-3v13"/>
|
|
</svg>
|
|
</div>
|
|
</template>
|
|
<div class="min-w-0 flex-1">
|
|
<p class="text-sm text-surface-900 dark:text-surface-100 truncate">
|
|
<template x-if="item.trackUrl">
|
|
<a :href="item.trackUrl" class="hover:text-purple-600 dark:hover:text-purple-400" target="_blank" rel="noopener" x-text="item.track"></a>
|
|
</template>
|
|
<template x-if="!item.trackUrl">
|
|
<span x-text="item.track"></span>
|
|
</template>
|
|
<template x-if="item.loved">
|
|
<span class="text-red-500 ml-0.5">♥</span>
|
|
</template>
|
|
</p>
|
|
<p class="text-xs text-surface-600 dark:text-surface-400 truncate">
|
|
<span x-text="item.artist"></span>
|
|
<span class="ml-1" :class="item._source === 'funkwhale' ? 'text-purple-500' : 'text-red-500'" x-text="item._source === 'funkwhale' ? 'Funkwhale' : 'Last.fm'"></span>
|
|
</p>
|
|
</div>
|
|
</li>
|
|
</template>
|
|
</ul>
|
|
|
|
<a href="/listening/" class="text-sm text-purple-600 dark:text-purple-400 hover:underline flex items-center gap-1 mt-3">
|
|
View full listening history
|
|
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
|
</a>
|
|
</div>
|
|
</is-land>
|
|
{% endif %}
|