diff --git a/assets/js/lastfm-utils.js b/assets/js/lastfm-utils.js new file mode 100644 index 0000000..37f4a2d --- /dev/null +++ b/assets/js/lastfm-utils.js @@ -0,0 +1,68 @@ +// Shared Last.fm utilities - using global namespace pattern +(function(window) { + 'use strict'; + + // Create global LastFmUtils namespace + window.LastFmUtils = { + // Last.fm API configuration + LASTFM_API_URL: "https://ws.audioscrobbler.com/2.0/", + LASTFM_USER: "ritualplays", + LASTFM_API_KEY: "3a4fef48fecc593d25e0f9a40df1fefe", + + // Format time difference + getTimeAgo: function(timestamp) { + const now = Date.now() / 1000; + const diff = now - timestamp; + + 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`; + if (diff < 604800) return `${Math.floor(diff / 86400)}d ago`; + return `${Math.floor(diff / 604800)}w ago`; + }, + + // Fetch recent tracks from Last.fm + fetchRecentTracks: async function(limit = 10) { + const url = `${this.LASTFM_API_URL}?method=user.getrecenttracks&user=${this.LASTFM_USER}&api_key=${this.LASTFM_API_KEY}&format=json&limit=${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; + } + }, + + // Filter tracks to remove duplicates of now playing track + // Returns a limited number of unique tracks + filterAndLimitTracks: function(tracks, maxTracks = 5) { + if (!tracks || tracks.length === 0) { + return []; + } + + // Check if first track is now playing + const hasNowPlaying = tracks[0] && tracks[0]["@attr"] && tracks[0]["@attr"].nowplaying; + + if (hasNowPlaying) { + // Show now playing + (maxTracks - 1) 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; + }); + + return [nowPlayingTrack, ...remainingTracks.slice(0, maxTracks - 1)]; + } else { + // No now playing, show maxTracks latest + return tracks.slice(0, maxTracks); + } + } + }; +})(window); diff --git a/assets/js/pages/audio.js b/assets/js/pages/audio.js index 07c36c7..51c2062 100644 --- a/assets/js/pages/audio.js +++ b/assets/js/pages/audio.js @@ -83,27 +83,8 @@ if (document.getElementById("starfield")) { 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) { @@ -114,27 +95,8 @@ if (document.getElementById("starfield")) { 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); - } + // Filter and limit tracks to 5 (excluding duplicates) + const tracksToShow = LastFmUtils.filterAndLimitTracks(tracks, 5); tracksToShow.forEach((track) => { const isNowPlaying = track["@attr"] && track["@attr"].nowplaying; @@ -164,12 +126,12 @@ if (document.getElementById("starfield")) { // Initialize Last.fm feed for audio page async function initAudioRecentTracks() { - const tracks = await fetchAudioRecentTracks(); + const tracks = await LastFmUtils.fetchRecentTracks(AUDIO_TRACK_LIMIT); renderRecentTracks(tracks); // Update every 30 seconds setInterval(async () => { - const updatedTracks = await fetchAudioRecentTracks(); + const updatedTracks = await LastFmUtils.fetchRecentTracks(AUDIO_TRACK_LIMIT); renderRecentTracks(updatedTracks); }, 30000); } diff --git a/assets/js/pages/media.js b/assets/js/pages/media.js index f4548b6..c57c5b1 100644 --- a/assets/js/pages/media.js +++ b/assets/js/pages/media.js @@ -82,39 +82,8 @@ function initMatrixRain() { }); } -// Last.fm API configuration -const LASTFM_API_URL = "https://ws.audioscrobbler.com/2.0/"; -const LASTFM_USER = "ritualplays"; -const LASTFM_API_KEY = "3a4fef48fecc593d25e0f9a40df1fefe"; -const TRACK_LIMIT = 10; - -// Format time difference -function getTimeAgo(timestamp) { - const now = Date.now() / 1000; - const diff = now - timestamp; - - 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`; - if (diff < 604800) return `${Math.floor(diff / 86400)}d ago`; - return `${Math.floor(diff / 604800)}w ago`; -} - -// Fetch recent tracks from Last.fm -async function fetchRecentTracks() { - const url = `${LASTFM_API_URL}?method=user.getrecenttracks&user=${LASTFM_USER}&api_key=${LASTFM_API_KEY}&format=json&limit=${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; - } -} +// Media page configuration +const TRACK_LIMIT = 12; // Fetch 12 to have enough after filtering // Render tracks to the DOM function renderTracks(tracks) { @@ -129,7 +98,10 @@ function renderTracks(tracks) { container.innerHTML = ""; - tracks.forEach((track) => { + // Filter and limit tracks to 10 (excluding duplicates of now playing) + const tracksToShow = LastFmUtils.filterAndLimitTracks(tracks, 10); + + tracksToShow.forEach((track) => { const isNowPlaying = track["@attr"] && track["@attr"].nowplaying; const trackElement = document.createElement("a"); trackElement.href = track.url; @@ -143,7 +115,7 @@ function renderTracks(tracks) { // Get timestamp const timestamp = track.date ? track.date.uts : null; - const timeAgo = timestamp ? getTimeAgo(timestamp) : ""; + const timeAgo = timestamp ? LastFmUtils.getTimeAgo(timestamp) : ""; trackElement.innerHTML = `
@@ -162,12 +134,12 @@ function renderTracks(tracks) { // Initialize Last.fm feed async function initLastFmFeed() { - const tracks = await fetchRecentTracks(); + const tracks = await LastFmUtils.fetchRecentTracks(TRACK_LIMIT); renderTracks(tracks); // Update every 30 seconds setInterval(async () => { - const updatedTracks = await fetchRecentTracks(); + const updatedTracks = await LastFmUtils.fetchRecentTracks(TRACK_LIMIT); renderTracks(updatedTracks); }, 30000); } diff --git a/assets/sass/pages/media.scss b/assets/sass/pages/media.scss index 4bcda70..6ef18a0 100644 --- a/assets/sass/pages/media.scss +++ b/assets/sass/pages/media.scss @@ -54,11 +54,19 @@ margin-bottom: 50px; padding-bottom: 20px; border-bottom: 2px solid transparent; - border-image: linear-gradient(90deg, transparent, #ff00ff 20%, #00ffff 50%, #ff00ff 80%, transparent) 1; + border-image: linear-gradient( + 90deg, + transparent, + #ff00ff 20%, + #00ffff 50%, + #ff00ff 80%, + transparent + ) + 1; display: flex; justify-content: center; - pre { + > pre { background: linear-gradient(90deg, #ff00ff, #00ffff, #ff00ff); -webkit-background-clip: text; background-clip: text; @@ -70,7 +78,7 @@ margin: 0; @include media-down(lg) { - font-size: 12px; + font-size: 0.5rem; } } } @@ -98,6 +106,16 @@ gap: 25px; } + > h3 { + display: none; + padding-bottom: 15px; + border-bottom: 2px solid transparent; + border-image: linear-gradient(90deg, #ff00ff, #00ffff) 1; + @include media-down(lg) { + display: block; + } + } + // Subtle divider between items .media-item + .media-item { position: relative; @@ -109,7 +127,13 @@ left: 0; right: 0; height: 1px; - background: linear-gradient(90deg, transparent, #333 20%, #333 80%, transparent); + background: linear-gradient( + 90deg, + transparent, + #333 20%, + #333 80%, + transparent + ); } } } @@ -120,13 +144,16 @@ gap: 25px; padding: 20px; border: 1px solid #333; - background: rgba(0,0,0,0.6); + background: rgba(0, 0, 0, 0.6); border-radius: 8px; transition: all 0.3s ease; @include media-down(lg) { gap: 20px; flex-direction: column; + + display: grid; + grid-template-columns: auto 1fr; } &:hover { @@ -154,9 +181,9 @@ transition: all 0.3s ease; @include media-down(lg) { - width: 100%; - max-width: 300px; - height: 450px; + max-width: 100px; + max-height: 100px; + height: auto; margin: 0 auto; } @@ -394,6 +421,12 @@ display: flex; flex-direction: column; gap: 15px; + + @include media-down(lg) { + .lastfm-track:nth-child(n + 4) { + display: none; + } + } } .lastfm-loading { diff --git a/assets/sass/partials/_neon-sign.scss b/assets/sass/partials/_neon-sign.scss index 931c36a..7787410 100644 --- a/assets/sass/partials/_neon-sign.scss +++ b/assets/sass/partials/_neon-sign.scss @@ -17,6 +17,7 @@ width: 100%; height: 200px; position: absolute; + pointer-events: none; @include media-up(lg) { position: absolute; diff --git a/content/media/fackham-hall/cover.jpg b/content/media/fackham-hall/cover.jpg new file mode 100644 index 0000000..f6207dd Binary files /dev/null and b/content/media/fackham-hall/cover.jpg differ diff --git a/content/media/fackham-hall/index.md b/content/media/fackham-hall/index.md new file mode 100644 index 0000000..cad11f1 --- /dev/null +++ b/content/media/fackham-hall/index.md @@ -0,0 +1,13 @@ +--- +title: "Fackham Hall" +date: 2025-12-24 +tags: ["film"] # album, film, show, book, game +format: "stream" # stream, cd, vinyl, cassette, dvd, bluray, digital +description: "" +cover: "cover.jpg" +rating: 8 # out of 10 +build: + render: never +--- + +Review here :) diff --git a/content/media/taskmaster-champion-of-champions-4/cover.jpg b/content/media/taskmaster-champion-of-champions-4/cover.jpg new file mode 100644 index 0000000..d40b04d Binary files /dev/null and b/content/media/taskmaster-champion-of-champions-4/cover.jpg differ diff --git a/content/media/taskmaster-champion-of-champions-4/index.md b/content/media/taskmaster-champion-of-champions-4/index.md new file mode 100644 index 0000000..56c1efe --- /dev/null +++ b/content/media/taskmaster-champion-of-champions-4/index.md @@ -0,0 +1,13 @@ +--- +title: "Taskmaster - Champion of Champions 4" +date: 2025-12-26 +tags: ["show"] # album, film, show, book, game +format: "stream" # stream, cd, vinyl, cassette, dvd, bluray, digital +description: "" +cover: "cover.jpg" +rating: 9 # out of 10 +build: + render: never +--- + +Review here :) diff --git a/layouts/media/list.html b/layouts/media/list.html index 4f03ef8..af98f68 100644 --- a/layouts/media/list.html +++ b/layouts/media/list.html @@ -14,49 +14,63 @@ ▀██▀ ▀██▄▄▀█▄▄▄▄█▀███▄██▄▀█▄██ ████████▄▀███▀ ▀████ ██ ▀▀▀ - +
+

Recent Media

{{ range .Paginator.Pages }} -
-
- {{ with .Resources.GetMatch "cover.*" }} - {{ $image := .Resize "320x webp q85" }} - {{ $.Title }} - {{ else }} -
- NO COVER -
- {{ end }} -
-
-
-
{{ index .Params.tags 0 | upper }}
- {{ if .Params.format }} -
{{ .Params.format | upper }}
- {{ end }} -
{{ .Date.Format "02-01-2006" }}
-
-

{{ .Title }}

- {{ if .Params.rating }} -
- {{ .Params.rating }} - /10 -
- {{ end }} +
+
+ {{ with .Resources.GetMatch "cover.*" }} {{ $image := .Resize + "320x webp q85" }} + {{ $.Title }} + {{ else }} +
+ NO COVER
+ {{ end }}
- {{ end }} - - {{ partial "pagination.html" .Paginator }} +
+
+
+ {{ index .Params.tags 0 | upper }} +
+ {{ if .Params.format }} +
{{ .Params.format | upper }}
+ {{ end }} +
{{ .Date.Format "02-01-2006" }}
+
+

{{ .Title }}

+ {{ if .Params.rating }} +
+ {{ .Params.rating }} + /10 +
+ {{ end }} +
+
+ {{ end }} {{ partial "pagination.html" .Paginator }}

Recently Listened

- last.fm → + last.fm →
Loading tracks...
@@ -67,6 +81,4 @@
- - {{ end }}