diff --git a/_data/lastfmActivity.js b/_data/lastfmActivity.js
new file mode 100644
index 0000000..d7dd837
--- /dev/null
+++ b/_data/lastfmActivity.js
@@ -0,0 +1,83 @@
+/**
+ * Last.fm Activity Data
+ * Fetches from Indiekit's endpoint-lastfm public API
+ */
+
+import EleventyFetch from "@11ty/eleventy-fetch";
+
+const INDIEKIT_URL = process.env.SITE_URL || "https://example.com";
+const LASTFM_USERNAME = process.env.LASTFM_USERNAME || "";
+
+/**
+ * Fetch from Indiekit's public Last.fm API endpoint
+ */
+async function fetchFromIndiekit(endpoint) {
+ try {
+ const url = `${INDIEKIT_URL}/lastfmapi/api/${endpoint}`;
+ console.log(`[lastfmActivity] Fetching from Indiekit: ${url}`);
+ const data = await EleventyFetch(url, {
+ duration: "15m",
+ type: "json",
+ });
+ console.log(`[lastfmActivity] Indiekit ${endpoint} success`);
+ return data;
+ } catch (error) {
+ console.log(
+ `[lastfmActivity] Indiekit API unavailable for ${endpoint}: ${error.message}`
+ );
+ return null;
+ }
+}
+
+export default async function () {
+ try {
+ console.log("[lastfmActivity] Fetching Last.fm data...");
+
+ // Fetch all data from Indiekit API
+ const [nowPlaying, scrobbles, loved, stats] = await Promise.all([
+ fetchFromIndiekit("now-playing"),
+ fetchFromIndiekit("scrobbles"),
+ fetchFromIndiekit("loved"),
+ fetchFromIndiekit("stats"),
+ ]);
+
+ // Check if we got data
+ const hasData = nowPlaying || scrobbles?.scrobbles?.length || stats?.summary;
+
+ if (!hasData) {
+ console.log("[lastfmActivity] No data available from Indiekit");
+ return {
+ nowPlaying: null,
+ scrobbles: [],
+ loved: [],
+ stats: null,
+ username: LASTFM_USERNAME,
+ profileUrl: LASTFM_USERNAME ? `https://www.last.fm/user/${LASTFM_USERNAME}` : null,
+ source: "unavailable",
+ };
+ }
+
+ console.log("[lastfmActivity] Using Indiekit API data");
+
+ return {
+ nowPlaying: nowPlaying || null,
+ scrobbles: scrobbles?.scrobbles || [],
+ loved: loved?.loved || [],
+ stats: stats || null,
+ username: LASTFM_USERNAME,
+ profileUrl: LASTFM_USERNAME ? `https://www.last.fm/user/${LASTFM_USERNAME}` : null,
+ source: "indiekit",
+ };
+ } catch (error) {
+ console.error("[lastfmActivity] Error:", error.message);
+ return {
+ nowPlaying: null,
+ scrobbles: [],
+ loved: [],
+ stats: null,
+ username: LASTFM_USERNAME,
+ profileUrl: null,
+ source: "error",
+ };
+ }
+}
diff --git a/listening.njk b/listening.njk
new file mode 100644
index 0000000..256f267
--- /dev/null
+++ b/listening.njk
@@ -0,0 +1,432 @@
+---
+layout: layouts/base.njk
+title: Listening Activity
+permalink: /listening/
+withSidebar: true
+---
+
+
+
+ {# Source Filter Tabs #}
+
+
+ All Sources
+
+ {% if funkwhaleActivity.source == 'indiekit' %}
+
+
+ Funkwhale
+
+ {% endif %}
+ {% if lastfmActivity.source == 'indiekit' %}
+
+
+ Last.fm
+
+ {% endif %}
+
+
+ {# Now Playing Section - Combined #}
+ {% set fwNowPlaying = funkwhaleActivity.nowPlaying if funkwhaleActivity.nowPlaying and funkwhaleActivity.nowPlaying.status else null %}
+ {% set lfmNowPlaying = lastfmActivity.nowPlaying if lastfmActivity.nowPlaying and lastfmActivity.nowPlaying.status else null %}
+
+ {% if fwNowPlaying or lfmNowPlaying %}
+
+ {# Funkwhale Now Playing #}
+ {% if fwNowPlaying %}
+
+
+
+ {% if fwNowPlaying.coverUrl %}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+ Funkwhale
+ {% if fwNowPlaying.status == 'now-playing' %}
+
+
+
+
+
+
+ Now Playing
+
+ {% else %}
+
+ Recently Played
+
+ {% endif %}
+
+
+
+ {% if fwNowPlaying.trackUrl %}
+ {{ fwNowPlaying.track }}
+ {% else %}
+ {{ fwNowPlaying.track }}
+ {% endif %}
+
+
{{ fwNowPlaying.artist }}
+ {% if fwNowPlaying.album %}
+
{{ fwNowPlaying.album }}
+ {% endif %}
+
{{ fwNowPlaying.relativeTime }}
+
+
+
+
+ {% endif %}
+
+ {# Last.fm Now Playing #}
+ {% if lfmNowPlaying %}
+
+
+
+ {% if lfmNowPlaying.coverUrl %}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+ Last.fm
+ {% if lfmNowPlaying.status == 'now-playing' %}
+
+
+
+
+
+
+ Now Playing
+
+ {% else %}
+
+ Recently Played
+
+ {% endif %}
+ {% if lfmNowPlaying.loved %}
+ ♥
+ {% endif %}
+
+
+
+ {% if lfmNowPlaying.trackUrl %}
+ {{ lfmNowPlaying.track }}
+ {% else %}
+ {{ lfmNowPlaying.track }}
+ {% endif %}
+
+
{{ lfmNowPlaying.artist }}
+ {% if lfmNowPlaying.album %}
+
{{ lfmNowPlaying.album }}
+ {% endif %}
+
{{ lfmNowPlaying.relativeTime }}
+
+
+
+
+ {% endif %}
+
+ {% endif %}
+
+ {# Combined Stats Section #}
+ {% if funkwhaleActivity.stats or lastfmActivity.stats %}
+
+
+
+
+
+ Listening Statistics
+
+
+ {# Stats Cards Grid - Side by Side #}
+
+ {# Funkwhale Stats #}
+ {% if funkwhaleActivity.stats %}
+
+
+
+ Funkwhale
+
+
+
+
{{ funkwhaleActivity.stats.summary.all.totalPlays | default(0) }}
+
Plays
+
+
+
{{ funkwhaleActivity.stats.summary.all.uniqueArtists | default(0) }}
+
Artists
+
+
+
{{ funkwhaleActivity.stats.summary.all.uniqueTracks | default(0) }}
+
Tracks
+
+
+ {# Top Artists #}
+ {% if funkwhaleActivity.stats.topArtists.all.length %}
+
+
Top Artists
+
+ {% for artist in funkwhaleActivity.stats.topArtists.all | head(5) %}
+
+ {{ artist.name }}
+ {{ artist.playCount }}
+
+ {% endfor %}
+
+
+ {% endif %}
+
+ {% endif %}
+
+ {# Last.fm Stats #}
+ {% if lastfmActivity.stats %}
+
+
+
+ Last.fm
+
+
+
+
{{ lastfmActivity.stats.summary.all.totalPlays | default(0) }}
+
Scrobbles
+
+
+
{{ lastfmActivity.stats.summary.all.uniqueArtists | default(0) }}
+
Artists
+
+
+
{{ lastfmActivity.stats.summary.all.lovedCount | default(0) }}
+
Loved
+
+
+ {# Top Artists from Last.fm #}
+ {% if lastfmActivity.stats.topArtists.all.length %}
+
+
Top Artists
+
+ {% for artist in lastfmActivity.stats.topArtists.all | head(5) %}
+
+ {{ artist.name }}
+ {{ artist.playCount }}
+
+ {% endfor %}
+
+
+ {% endif %}
+
+ {% endif %}
+
+
+ {% endif %}
+
+ {# Recent Listens - Combined Timeline #}
+
+
+
+
+
+ Recent Listens
+
+
+
+ {# Funkwhale Listenings #}
+ {% if funkwhaleActivity.listenings.length %}
+
+ {% for listening in funkwhaleActivity.listenings | head(10) %}
+
+ {% if listening.coverUrl %}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+ {% if listening.trackUrl %}
+ {{ listening.track }}
+ {% else %}
+ {{ listening.track }}
+ {% endif %}
+
+
{{ listening.artist }}
+
+
+
+ Funkwhale
+ {{ listening.relativeTime }}
+
+
+ {% endfor %}
+
+ {% endif %}
+
+ {# Last.fm Scrobbles #}
+ {% if lastfmActivity.scrobbles.length %}
+
+ {% for scrobble in lastfmActivity.scrobbles | head(10) %}
+
+ {% if scrobble.coverUrl %}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+ {% if scrobble.trackUrl %}
+ {{ scrobble.track }}
+ {% else %}
+ {{ scrobble.track }}
+ {% endif %}
+ {% if scrobble.loved %}
+ ♥
+ {% endif %}
+
+
{{ scrobble.artist }}
+
+
+
+ Last.fm
+ {{ scrobble.relativeTime }}
+
+
+ {% endfor %}
+
+ {% endif %}
+
+ {% if not funkwhaleActivity.listenings.length and not lastfmActivity.scrobbles.length %}
+
No recent listening history available.
+ {% endif %}
+
+
+
+ {# Loved Tracks from Last.fm #}
+ {% if lastfmActivity.loved.length %}
+
+
+
+
+
+ Loved Tracks
+ (Last.fm)
+
+
+
+ {% for track in lastfmActivity.loved | head(10) %}
+
+ {% if track.coverUrl %}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+ {% if track.trackUrl %}
+ {{ track.track }}
+ {% else %}
+ {{ track.track }}
+ {% endif %}
+
+
{{ track.artist }}
+
+
+
♥
+
+ {% endfor %}
+
+
+ {% endif %}
+
+ {# Funkwhale Favorites #}
+ {% if funkwhaleActivity.favorites.length %}
+
+
+
+
+
+ Favorite Tracks
+ (Funkwhale)
+
+
+
+ {% for favorite in funkwhaleActivity.favorites | head(10) %}
+
+ {% if favorite.coverUrl %}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+ {% if favorite.trackUrl %}
+ {{ favorite.track }}
+ {% else %}
+ {{ favorite.track }}
+ {% endif %}
+
+
{{ favorite.artist }}
+ {% if favorite.album %}
+
{{ favorite.album }}
+ {% endif %}
+
+
+ {% endfor %}
+
+
+ {% endif %}
+