diff --git a/assets/js/pages/audio.js b/assets/js/pages/audio.js
index 9bf40cd..07c36c7 100644
--- a/assets/js/pages/audio.js
+++ b/assets/js/pages/audio.js
@@ -77,3 +77,107 @@ if (document.getElementById("starfield")) {
});
});
})();
+
+// Audio page Last.fm integration (separate from media page)
+(function() {
+ const container = document.getElementById("recent-tracks");
+ if (!container) return;
+
+ const AUDIO_LASTFM_API_URL = "https://ws.audioscrobbler.com/2.0/";
+ const AUDIO_LASTFM_USER = "ritualplays";
+ const AUDIO_LASTFM_API_KEY = "3a4fef48fecc593d25e0f9a40df1fefe";
+ const AUDIO_TRACK_LIMIT = 6; // Fetch 6 to have enough after filtering
+
+ // Fetch recent tracks from Last.fm for audio page
+ async function fetchAudioRecentTracks() {
+ const url = `${AUDIO_LASTFM_API_URL}?method=user.getrecenttracks&user=${AUDIO_LASTFM_USER}&api_key=${AUDIO_LASTFM_API_KEY}&format=json&limit=${AUDIO_TRACK_LIMIT}`;
+
+ try {
+ const response = await fetch(url);
+ if (!response.ok) throw new Error("Failed to fetch tracks");
+
+ const data = await response.json();
+ return data.recenttracks.track;
+ } catch (error) {
+ console.error("Error fetching Last.fm data:", error);
+ return null;
+ }
+ }
+
+ // Render recent tracks for audio page (horizontal layout)
+ function renderRecentTracks(tracks) {
+ if (!tracks || tracks.length === 0) {
+ container.innerHTML =
+ '
No recent tracks found
';
+ return;
+ }
+
+ container.innerHTML = "";
+
+ // Check if first track is now playing
+ const hasNowPlaying = tracks[0] && tracks[0]["@attr"] && tracks[0]["@attr"].nowplaying;
+
+ // Filter and limit tracks
+ let tracksToShow;
+ if (hasNowPlaying) {
+ // Show now playing + 4 latest (excluding duplicates of now playing)
+ const nowPlayingTrack = tracks[0];
+ const nowPlayingId = `${nowPlayingTrack.name}-${nowPlayingTrack.artist["#text"]}`;
+
+ // Get remaining tracks, excluding duplicates of now playing
+ const remainingTracks = tracks.slice(1).filter(track => {
+ const trackId = `${track.name}-${track.artist["#text"]}`;
+ return trackId !== nowPlayingId;
+ });
+
+ tracksToShow = [nowPlayingTrack, ...remainingTracks.slice(0, 4)];
+ } else {
+ // No now playing, show 5 latest
+ tracksToShow = tracks.slice(0, 5);
+ }
+
+ tracksToShow.forEach((track) => {
+ const isNowPlaying = track["@attr"] && track["@attr"].nowplaying;
+ const trackElement = document.createElement("a");
+ trackElement.href = track.url;
+ trackElement.target = "_blank";
+ trackElement.rel = "noopener noreferrer";
+ trackElement.className = `recent-track ${isNowPlaying ? "now-playing" : ""}`;
+
+ // Get album art (use largest available)
+ const albumArt = track.image[3]["#text"];
+ const hasArt = albumArt && albumArt.trim() !== "";
+
+ trackElement.innerHTML = `
+
+ ${hasArt ? `

` : '
♪
'}
+
+
+
${track.name}
+
${track.artist["#text"]}
+
+ `;
+
+ container.appendChild(trackElement);
+ });
+ }
+
+ // Initialize Last.fm feed for audio page
+ async function initAudioRecentTracks() {
+ const tracks = await fetchAudioRecentTracks();
+ renderRecentTracks(tracks);
+
+ // Update every 30 seconds
+ setInterval(async () => {
+ const updatedTracks = await fetchAudioRecentTracks();
+ renderRecentTracks(updatedTracks);
+ }, 30000);
+ }
+
+ // Initialize recent tracks when DOM is ready
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", initAudioRecentTracks);
+ } else {
+ initAudioRecentTracks();
+ }
+})();
diff --git a/assets/sass/pages/audio.scss b/assets/sass/pages/audio.scss
index 49728fa..6a1733c 100644
--- a/assets/sass/pages/audio.scss
+++ b/assets/sass/pages/audio.scss
@@ -110,6 +110,180 @@
}
}
+ .recent-tracks-container {
+ width: 100%;
+ margin: 0 auto 100px;
+
+ padding-bottom: 15px;
+ border-bottom: 2px solid transparent;
+ border-image: linear-gradient(90deg, #ff00ff, #00ffff) 1;
+
+ .audio-sub-header {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+ padding-bottom: 15px;
+ border-bottom: 2px solid transparent;
+ border-image: linear-gradient(90deg, #00ffff, #ff00ff) 1;
+
+ h3 {
+ margin: 0;
+ font-size: 1.5rem;
+ color: #fff;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ }
+
+ .lastfm-link {
+ color: #999;
+ text-decoration: none;
+ font-size: 0.9rem;
+ transition: color 0.3s ease;
+
+ &:hover {
+ color: #ff00ff;
+ }
+ }
+ }
+
+ @include media-down(lg) {
+ width: 100%;
+ }
+
+ .recent-tracks-list {
+ display: grid;
+ grid-template-columns: repeat(5, 1fr);
+ gap: 20px;
+
+ @include media-down(lg) {
+ grid-template-columns: repeat(3, 1fr);
+ gap: 15px;
+ }
+
+ @include media-down(md) {
+ grid-template-columns: repeat(2, 1fr);
+ }
+ }
+
+ .track-loading {
+ grid-column: 1 / -1;
+ text-align: center;
+ color: #999;
+ padding: 40px 20px;
+ font-size: 0.9rem;
+ font-style: italic;
+ }
+
+ .recent-track {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ padding: 15px;
+ background: rgba(0, 0, 0, 0.6);
+ border: 2px solid #333;
+ border-radius: 8px;
+ transition: all 0.3s ease;
+ text-decoration: none;
+ color: inherit;
+ position: relative;
+
+ &:hover {
+ border-color: #00ffff;
+ box-shadow: 0 0 20px rgba(0, 255, 255, 0.3);
+ transform: translateY(-5px);
+
+ .track-title {
+ color: #00ffff;
+ }
+ }
+
+ &.now-playing {
+ border-color: #ff00ff;
+ background: rgba(255, 0, 255, 0.1);
+ box-shadow: 0 0 25px rgba(255, 0, 255, 0.4);
+
+ &::before {
+ content: "♫ NOW PLAYING";
+ position: absolute;
+ top: -10px;
+ left: 50%;
+ transform: translateX(-50%);
+ font-size: 0.65rem;
+ color: #ff00ff;
+ background: #0d0d0d;
+ padding: 4px 10px;
+ border-radius: 10px;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ font-weight: 700;
+ white-space: nowrap;
+ border: 1px solid #ff00ff;
+ }
+
+ .track-cover {
+ box-shadow: 0 0 20px rgba(255, 0, 255, 0.5);
+ }
+ }
+
+ .track-cover {
+ width: 100%;
+ aspect-ratio: 1/1;
+ border-radius: 6px;
+ overflow: hidden;
+ background: #0a0a0a;
+ transition: box-shadow 0.3s ease;
+
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+
+ &.no-art {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: 2px dashed #333;
+
+ .cover-placeholder {
+ font-size: 2rem;
+ color: #555;
+ }
+ }
+ }
+
+ .track-details {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+
+ .track-title {
+ font-size: 0.9rem;
+ font-weight: 600;
+ color: #fff;
+ transition: color 0.3s ease;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ line-height: 1.3;
+ }
+
+ .track-artist {
+ font-size: 0.8rem;
+ color: #999;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ }
+ }
+ }
+
.record-shelf-container {
.shelf {
display: flex;
@@ -118,6 +292,18 @@
overflow: visible;
padding-left: 5%;
padding-right: 5%;
+
+ .neon-sign {
+ position: absolute;
+ width: 200px;
+ left: -90px;
+ top: 50px;
+
+ > .neon-text {
+ font-size: 3em;
+ line-height: 2.5rem;
+ }
+ }
}
.shelf::before {
diff --git a/layouts/audio/single.html b/layouts/audio/single.html
index fb1f16b..fd0d4da 100644
--- a/layouts/audio/single.html
+++ b/layouts/audio/single.html
@@ -33,8 +33,27 @@
+
+
+
{{ $posts := where site.RegularPages "Type" "media" }}
{{ $posts = where $posts "Params.tags" "intersect" (slice "album") }}