diff --git a/archetypes/media/index.md b/archetypes/media/index.md new file mode 100644 index 0000000..749a745 --- /dev/null +++ b/archetypes/media/index.md @@ -0,0 +1,13 @@ +--- +title: "" +date: {{ .Date }} +tags: ["album"] # album, film, show, book, game +format: "stream" # stream, cd, vinyl, cassette, dvd, bluray, digital +description: "" +cover: "cover.jpg" +rating: 0 # out of 10 +build: + render: never +--- + +Review here :) diff --git a/assets/js/pages/media.js b/assets/js/pages/media.js new file mode 100644 index 0000000..f4548b6 --- /dev/null +++ b/assets/js/pages/media.js @@ -0,0 +1,185 @@ +// Matrix Rain Background Effect +function initMatrixRain() { + const canvas = document.getElementById("matrix-bg"); + if (!canvas) return; + + const ctx = canvas.getContext("2d"); + + // Set canvas size to window size + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + + // Characters to use in the matrix effect + const chars = "アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン01"; + const charArray = chars.split(""); + + const fontSize = 14; + const columns = Math.floor(canvas.width / fontSize); + + // Array to track y position of each column + const drops = Array(columns).fill(1); + + // Array to store random max heights for each column (within top 50%) + const maxHeights = Array(columns) + .fill(0) + .map(() => Math.random() * (canvas.height * 0.5)); + + // Color options - muted pink and blue + const colors = [ + "rgba(255, 0, 255, 0.15)", // Muted magenta/pink + "rgba(0, 255, 255, 0.15)", // Muted cyan/blue + ]; + + function draw() { + // Semi-transparent black to create fade effect + ctx.fillStyle = "rgba(13, 13, 13, 0.05)"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + ctx.font = fontSize + "px monospace"; + + // Draw characters + for (let i = 0; i < drops.length; i++) { + // Only draw if the drop hasn't reached its max height + if (drops[i] * fontSize < maxHeights[i]) { + // Randomly choose between pink and blue + const color = colors[Math.floor(Math.random() * colors.length)]; + ctx.fillStyle = color; + + // Random character + const char = charArray[Math.floor(Math.random() * charArray.length)]; + + // Draw character + ctx.fillText(char, i * fontSize, drops[i] * fontSize); + + // Move drop down + drops[i]++; + } else { + // Reset drop randomly to start a new stream + if (Math.random() > 0.99) { + drops[i] = 0; + // Assign new random max height within top 50% + maxHeights[i] = Math.random() * (canvas.height * 0.5); + } + } + } + } + + // Start animation + setInterval(draw, 50); + + // Handle window resize + window.addEventListener("resize", () => { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + const newColumns = Math.floor(canvas.width / fontSize); + drops.length = newColumns; + drops.fill(1); + // Regenerate max heights for new canvas size + maxHeights.length = newColumns; + for (let i = 0; i < newColumns; i++) { + maxHeights[i] = Math.random() * (canvas.height * 0.5); + } + }); +} + +// 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; + } +} + +// Render tracks to the DOM +function renderTracks(tracks) { + const container = document.getElementById("lastfm-tracks"); + if (!container) return; + + if (!tracks || tracks.length === 0) { + container.innerHTML = + '
No recent tracks found
'; + return; + } + + container.innerHTML = ""; + + tracks.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 = `lastfm-track ${isNowPlaying ? "now-playing" : ""}`; + + // Get album art (use largest available) + const albumArt = track.image[3]["#text"]; + const hasArt = albumArt && albumArt.trim() !== ""; + + // Get timestamp + const timestamp = track.date ? track.date.uts : null; + const timeAgo = timestamp ? getTimeAgo(timestamp) : ""; + + trackElement.innerHTML = ` +
+ ${hasArt ? `${track.name}` : ""} +
+
+
${track.name}
+
${track.artist["#text"]}
+ ${!isNowPlaying && timeAgo ? `
${timeAgo}
` : ""} +
+ `; + + container.appendChild(trackElement); + }); +} + +// Initialize Last.fm feed +async function initLastFmFeed() { + const tracks = await fetchRecentTracks(); + renderTracks(tracks); + + // Update every 30 seconds + setInterval(async () => { + const updatedTracks = await fetchRecentTracks(); + renderTracks(updatedTracks); + }, 30000); +} + +// Initialize everything when DOM is ready +function init() { + initMatrixRain(); + initLastFmFeed(); +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); +} else { + init(); +} diff --git a/assets/sass/pages/media.scss b/assets/sass/pages/media.scss new file mode 100644 index 0000000..4bcda70 --- /dev/null +++ b/assets/sass/pages/media.scss @@ -0,0 +1,506 @@ +.media-page { + min-height: 100vh; + padding: 2rem; + position: relative; + background: #0d0d0d; + + @include media-down(lg) { + padding: 1rem; + } + + // Matrix rain background canvas + .matrix-background { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 0; + pointer-events: none; + } + + // Main container - simplified + .media-container { + max-width: 1100px; + margin: 0 auto; + padding: 40px; + position: relative; + z-index: 1; + + @include media-down(lg) { + padding: 20px; + } + } + + // Simplified screen - no CRT effects + .media-screen { + width: 100%; + background: transparent; + } + + // Content area - clean typography + .media-content { + width: 100%; + color: #e0e0e0; + font-family: "Lato", sans-serif; + + @include media-down(lg) { + padding: 0; + } + } + + // Header - simplified + .media-header { + 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; + display: flex; + justify-content: center; + + pre { + background: linear-gradient(90deg, #ff00ff, #00ffff, #ff00ff); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + font-size: 16px; + line-height: 1.4; + font-weight: 300; + letter-spacing: 2px; + margin: 0; + + @include media-down(lg) { + font-size: 12px; + } + } + } + + // Two-column layout + .media-layout { + display: grid; + grid-template-columns: 1fr 350px; + gap: 40px; + margin-bottom: 40px; + + @include media-down(lg) { + grid-template-columns: 1fr; + gap: 30px; + } + } + + // Media list + .media-list { + display: flex; + flex-direction: column; + gap: 30px; + + @include media-down(lg) { + gap: 25px; + } + + // Subtle divider between items + .media-item + .media-item { + position: relative; + + &::before { + content: ""; + position: absolute; + top: -15px; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, #333 20%, #333 80%, transparent); + } + } + } + + // Individual media item - clean and readable + .media-item { + display: flex; + gap: 25px; + padding: 20px; + border: 1px solid #333; + background: rgba(0,0,0,0.6); + border-radius: 8px; + transition: all 0.3s ease; + + @include media-down(lg) { + gap: 20px; + flex-direction: column; + } + + &:hover { + border-color: #ff00ff; + box-shadow: 0 0 20px rgba(255, 0, 255, 0.2); + + .media-cover { + box-shadow: 0 8px 30px rgba(255, 0, 255, 0.3); + } + + .media-title { + color: #00ffff; + } + } + + // Media cover - clean with subtle effects + .media-cover { + position: relative; + width: 100px; + height: 100px; + background: #0a0a0a; + overflow: hidden; + border-radius: 4px; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5); + transition: all 0.3s ease; + + @include media-down(lg) { + width: 100%; + max-width: 300px; + height: 450px; + margin: 0 auto; + } + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + + .no-cover { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background: #111; + border: 2px dashed #333; + + .cover-placeholder { + color: #555; + font-size: 0.7rem; + font-weight: 600; + letter-spacing: 1.5px; + text-transform: uppercase; + } + } + } + + // Media info - readable typography + .media-info { + flex: 1; + display: flex; + flex-direction: column; + gap: 12px; + min-width: 0; // Allow text truncation + + @include media-down(lg) { + padding: 0; + } + } + + .media-meta { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; + } + + .media-type { + font-size: 0.7rem; + color: #ff00ff; + text-transform: uppercase; + letter-spacing: 1.5px; + padding: 4px 12px; + background: rgba(255, 0, 255, 0.1); + border: 1px solid rgba(255, 0, 255, 0.3); + border-radius: 12px; + font-weight: 600; + } + + .media-format { + font-size: 0.7rem; + color: #ffa500; + text-transform: uppercase; + letter-spacing: 1.5px; + padding: 4px 12px; + background: rgba(255, 165, 0, 0.1); + border: 1px solid rgba(255, 165, 0, 0.3); + border-radius: 12px; + font-weight: 600; + } + + .media-date { + font-size: 0.7rem; + color: #00ffff; + text-transform: uppercase; + letter-spacing: 1.5px; + padding: 4px 12px; + background: rgba(0, 255, 255, 0.1); + border: 1px solid rgba(0, 255, 255, 0.3); + border-radius: 12px; + font-weight: 600; + } + + .media-title { + font-size: 1.5rem; + margin: 0; + line-height: 1.3; + color: #fff; + font-weight: 600; + transition: color 0.3s ease; + + @include media-down(lg) { + font-size: 1.3rem; + } + } + + .media-rating { + display: inline-flex; + align-items: baseline; + gap: 1px; + font-family: "DSEG7-Classic", monospace; + line-height: 1; + + .rating-value { + font-size: 1.6rem; + color: #00ffff; + font-weight: bold; + text-shadow: + 0 0 10px rgba(0, 255, 255, 0.8), + 0 0 20px rgba(0, 255, 255, 0.4); + letter-spacing: 0.1em; + } + + .rating-max { + font-size: 1rem; + color: #555; + font-weight: 400; + margin-left: 3px; + } + + @include media-down(lg) { + .rating-value { + font-size: 1.3rem; + } + + .rating-max { + font-size: 0.85rem; + } + } + } + } + + // Pagination styling - clean + .pagination { + margin-top: 50px; + padding-top: 30px; + border-top: 1px solid #333; + text-align: center; + + ul { + display: flex; + justify-content: center; + align-items: center; + gap: 8px; + list-style: none; + padding: 0; + flex-wrap: wrap; + + li { + a, + span { + display: block; + padding: 10px 16px; + background: transparent; + border: 1px solid #333; + color: #999; + text-decoration: none; + font-size: 0.9rem; + transition: all 0.3s ease; + min-width: 45px; + text-align: center; + border-radius: 4px; + font-weight: 500; + + &:hover { + border-color: #ff00ff; + color: #ff00ff; + box-shadow: 0 0 15px rgba(255, 0, 255, 0.2); + } + } + + &.active span { + border-color: #00ffff; + color: #00ffff; + box-shadow: 0 0 15px rgba(0, 255, 255, 0.3); + } + + &.disabled { + a, + span { + opacity: 0.3; + cursor: not-allowed; + + &:hover { + border-color: #333; + color: #999; + box-shadow: none; + } + } + } + } + } + } + + // Last.fm sidebar + .lastfm-sidebar { + display: flex; + flex-direction: column; + gap: 20px; + + @include media-down(lg) { + order: -1; // Show above media list on mobile + } + + .lastfm-header { + display: flex; + justify-content: space-between; + align-items: center; + padding-bottom: 15px; + border-bottom: 2px solid transparent; + border-image: linear-gradient(90deg, #ff00ff, #00ffff) 1; + + h3 { + font-size: 1.2rem; + margin: 0; + color: #fff; + font-weight: 600; + } + + .lastfm-profile-link { + font-size: 0.8rem; + color: #999; + text-decoration: none; + transition: color 0.3s ease; + white-space: nowrap; + + &:hover { + color: #ff00ff; + } + } + } + + .lastfm-tracks { + display: flex; + flex-direction: column; + gap: 15px; + } + + .lastfm-loading { + color: #999; + text-align: center; + padding: 20px; + font-size: 0.9rem; + font-style: italic; + } + + .lastfm-track { + display: flex; + gap: 12px; + padding: 12px; + background: rgba(255, 255, 255, 0.02); + background-color: rgba(0, 0, 0, 0.5); + border: 1px solid #333; + border-radius: 6px; + transition: all 0.3s ease; + text-decoration: none; + color: inherit; + + &:hover { + border-color: #00ffff; + box-shadow: 0 0 15px rgba(0, 255, 255, 0.15); + background: rgba(0, 255, 255, 0.05); + + .track-name { + color: #00ffff; + } + } + + .track-art { + width: 60px; + height: 60px; + border-radius: 4px; + overflow: hidden; + flex-shrink: 0; + background: #0a0a0a; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + + &.no-art { + display: flex; + align-items: center; + justify-content: center; + background: #111; + border: 1px dashed #333; + + &::after { + content: "♪"; + font-size: 1.5rem; + color: #555; + } + } + } + + .track-info { + flex: 1; + display: flex; + flex-direction: column; + gap: 4px; + min-width: 0; + + .track-name { + font-size: 0.9rem; + font-weight: 600; + color: #fff; + transition: color 0.3s ease; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .track-artist { + font-size: 0.8rem; + color: #999; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .track-date { + font-size: 0.7rem; + color: #666; + margin-top: auto; + } + } + + &.now-playing { + border-color: #ff00ff; + background: rgba(255, 0, 255, 0.05); + + .track-info::before { + content: "♫ Now Playing"; + font-size: 0.65rem; + color: #ff00ff; + text-transform: uppercase; + letter-spacing: 1px; + font-weight: 600; + margin-bottom: 4px; + } + } + } + } +} diff --git a/assets/sass/style.scss b/assets/sass/style.scss index 6350831..bf52ede 100644 --- a/assets/sass/style.scss +++ b/assets/sass/style.scss @@ -20,6 +20,7 @@ @import "pages/audio"; @import "pages/now"; @import "pages/blog"; +@import "pages/media"; @import url("https://fonts.googleapis.com/css2?family=Caveat:wght@400..700&family=Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900&family=Neonderthaw&display=swap"); diff --git a/content/media/neon-odin-allfather/index.md b/content/media/neon-odin-allfather/index.md index 667a5ef..08f4b6d 100644 --- a/content/media/neon-odin-allfather/index.md +++ b/content/media/neon-odin-allfather/index.md @@ -2,8 +2,10 @@ title: "Neon Odin - Allfather" date: 2025-01-12 tags: ["album"] +format: "stream" description: "" cover: "cover.jpg" +rating: 7 render: never build: render: never diff --git a/content/media/ninajirachi-i-love-my-computer/index.md b/content/media/ninajirachi-i-love-my-computer/index.md index 87dbe74..8b5a2d5 100644 --- a/content/media/ninajirachi-i-love-my-computer/index.md +++ b/content/media/ninajirachi-i-love-my-computer/index.md @@ -2,13 +2,15 @@ title: "Ninajirachi - I Love My Computer" date: 2025-12-12 tags: ["album"] +format: "stream" description: "" cover: "cover.jpg" +rating: 8 build: render: never --- -Ninajirachi's _I Love My Computer_ captures something truthful about growing up shaped by screens and bandwidth. The album explores that strange intimacy we developed with our machines - the late nights, the secret discoveries, the way technology became less of a tool and more of a companion. +Ninajirachi's _I Love My Computer_ captures something truthful about growing up shaped by the digital world. The album explores that strange relationships we developed with our machines - the late nights, the discoveries, the way technology became less of a tool and more of a companion. Tracks like "Fuck My Computer" and "iPod Touch" aren't just bangers; they're confessions about a relationship that transcends generations. Whether you learned HTML on Geocities or taught yourself production in GarageBand, there's recognition here. diff --git a/content/media/wake-up-dead-man/cover.jpg b/content/media/wake-up-dead-man/cover.jpg new file mode 100644 index 0000000..3ce7c36 Binary files /dev/null and b/content/media/wake-up-dead-man/cover.jpg differ diff --git a/content/media/wake-up-dead-man/index.md b/content/media/wake-up-dead-man/index.md new file mode 100644 index 0000000..7936aca --- /dev/null +++ b/content/media/wake-up-dead-man/index.md @@ -0,0 +1,13 @@ +--- +title: "Wake Up Dead Man" +date: 2025-12-23T23:22:02Z +tags: ["film"] +format: "stream" +description: "" +cover: "cover.jpg" +rating: 7 +build: + render: never +--- + + diff --git a/layouts/index.html b/layouts/index.html index 5b93693..04eca75 100644 --- a/layouts/index.html +++ b/layouts/index.html @@ -270,7 +270,7 @@ -
+
{{ partial "elements/crt-tv.html" . }}
diff --git a/layouts/media/list.html b/layouts/media/list.html new file mode 100644 index 0000000..4f03ef8 --- /dev/null +++ b/layouts/media/list.html @@ -0,0 +1,72 @@ +{{ define "main" }} +
+ +
+
+
+
+
+  ▄▄▄     ▄▄▄                          ▄▄▄
+   ███▄ ▄███            █▄            ▀██▀
+   ██ ▀█▀ ██            ██ ▀▀          ██               ▄▄
+   ██     ██   ▄█▀█▄ ▄████ ██ ▄▀▀█▄    ██      ▄███▄ ▄████
+   ██     ██   ██▄█▀ ██ ██ ██ ▄█▀██    ██      ██ ██ ██ ██
+ ▀██▀     ▀██▄▄▀█▄▄▄▄█▀███▄██▄▀█▄██   ████████▄▀███▀ ▀████
+                                                        ██
+                                                      ▀▀▀
+
+
+ +
+
+ {{ 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 }} +
+
+ {{ end }} + + {{ partial "pagination.html" .Paginator }} +
+ +
+
+

Recently Listened

+ last.fm → +
+
+
Loading tracks...
+
+
+
+
+
+
+
+ + +{{ end }}