From fe647b8749874bde5102988b7942b4f2202f01ef Mon Sep 17 00:00:00 2001 From: sakrecoer Date: Sun, 16 Aug 2026 11:17:10 +0200 Subject: [PATCH 01/10] add playlist partial --- fetch-playlist.sh | 214 ++++++++++++++++++ themes/one-pager/assets/css/style.css | 184 +++++++++++++++ .../one-pager/layouts/partials/playlists.html | 90 ++++++++ 3 files changed, 488 insertions(+) create mode 100755 fetch-playlist.sh create mode 100644 themes/one-pager/layouts/partials/playlists.html diff --git a/fetch-playlist.sh b/fetch-playlist.sh new file mode 100755 index 0000000..d71dbed --- /dev/null +++ b/fetch-playlist.sh @@ -0,0 +1,214 @@ +#!/bin/bash + +# ============================================ +# Nostr Playlist Fetcher for Hugo +# ============================================ + +# Configuration +PUBKEY="3f11abb2e235da2d4dda5d6deb2f123173476a745a3ca56895a1d0f632a42f40" +RELAY="wss://drops.basspistol.org" +DATA_DIR="data/playlists" +NAK_BIN="nak" +JQ_BIN="jq" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +mkdir -p "$DATA_DIR" + +log() { echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"; } +log_success() { echo -e "${GREEN}✅ $1${NC}"; } +log_warning() { echo -e "${YELLOW}⚠️ $1${NC}"; } +log_error() { echo -e "${RED}❌ $1${NC}"; } + +# ============================================ +# Step 1: Fetch playlist events +# ============================================ + +log "Fetching playlist events (kind 34139) from $RELAY..." + +PLAYLISTS_JSON=$(mktemp) +$NAK_BIN req -k 34139 -a "$PUBKEY" "$RELAY" | $JQ_BIN -s '.' > "$PLAYLISTS_JSON" + +if [ ! -s "$PLAYLISTS_JSON" ]; then + log_error "No playlist data received." + rm -f "$PLAYLISTS_JSON" + exit 1 +fi + +PLAYLIST_COUNT=$(cat "$PLAYLISTS_JSON" | $JQ_BIN 'length') +log_success "Found $PLAYLIST_COUNT playlist(s)" + +# ============================================ +# Step 2: Fetch ALL tracks +# ============================================ + +log "Fetching ALL tracks (kind 36787) from $PUBKEY..." + +ALL_TRACKS_JSON=$(mktemp) +$NAK_BIN req -k 36787 -a "$PUBKEY" "$RELAY" | $JQ_BIN -s '.' > "$ALL_TRACKS_JSON" + +TRACK_COUNT=$(cat "$ALL_TRACKS_JSON" | $JQ_BIN 'length' 2>/dev/null || echo "0") +log_success "Found $TRACK_COUNT total tracks from pubkey" + +# ============================================ +# Step 3: Build playlist data +# ============================================ + +log "Building playlist data structure..." + +OUTPUT_FILE="$DATA_DIR/playlists.json" +FLATTENED_OUTPUT="$DATA_DIR/tracks.json" + +# Create a file with track lookup by d-tag +TRACK_LOOKUP=$(mktemp) +cat "$ALL_TRACKS_JSON" | $JQ_BIN -r ' + .[] | + . as $track | + (.tags[] | select(.[0] == "d") | .[1]) as $d_tag | + "\($d_tag)|\($track | tojson)" +' > "$TRACK_LOOKUP" + +# Build the playlists JSON +echo '{"playlists": []}' > "$OUTPUT_FILE" + +# Process each playlist +cat "$PLAYLISTS_JSON" | $JQ_BIN -c '.[]' | while read -r playlist; do + # Extract playlist metadata + PLAYLIST_ID=$(echo "$playlist" | $JQ_BIN -r '.id') + PLAYLIST_CREATED=$(echo "$playlist" | $JQ_BIN -r '.created_at') + + # FIX: Extract name from "title" tag (not "name") + PLAYLIST_NAME=$(echo "$playlist" | $JQ_BIN -r '.tags[] | select(.[0] == "title") | .[1] // ""') + PLAYLIST_DESC=$(echo "$playlist" | $JQ_BIN -r '.tags[] | select(.[0] == "description") | .[1] // ""') + PLAYLIST_IMAGE=$(echo "$playlist" | $JQ_BIN -r '.tags[] | select(.[0] == "image") | .[1] // ""') + + # Debug: show what was extracted + echo " Playlist: '$PLAYLIST_NAME'" >&2 + + # Get track references from 'a' tags + TRACK_REFS=$(echo "$playlist" | $JQ_BIN -r '.tags[] | select(.[0] == "a") | .[1]') + + # Build tracks array for this playlist + TRACKS_JSON="[]" + + while read -r ref; do + if [ -n "$ref" ]; then + # Extract identifier (the part after the second colon) + IDENTIFIER=$(echo "$ref" | cut -d':' -f3-) + + # Find the track in the lookup + TRACK_DATA=$(grep "^$IDENTIFIER|" "$TRACK_LOOKUP" | head -1 | cut -d'|' -f2-) + + if [ -n "$TRACK_DATA" ] && [ "$TRACK_DATA" != "null" ]; then + # Extract track fields + TRACK_TITLE=$(echo "$TRACK_DATA" | $JQ_BIN -r '.tags[] | select(.[0] == "title") | .[1] // "Untitled"') + TRACK_ARTIST=$(echo "$TRACK_DATA" | $JQ_BIN -r '.tags[] | select(.[0] == "artist") | .[1] // "Unknown Artist"') + TRACK_ALBUM=$(echo "$TRACK_DATA" | $JQ_BIN -r '.tags[] | select(.[0] == "album") | .[1] // ""') + TRACK_URL=$(echo "$TRACK_DATA" | $JQ_BIN -r '.tags[] | select(.[0] == "url") | .[1] // ""') + TRACK_IMAGE=$(echo "$TRACK_DATA" | $JQ_BIN -r '.tags[] | select(.[0] == "image") | .[1] // ""') + TRACK_DURATION=$(echo "$TRACK_DATA" | $JQ_BIN -r '.tags[] | select(.[0] == "duration") | .[1] // ""') + TRACK_ID=$(echo "$TRACK_DATA" | $JQ_BIN -r '.id') + TRACK_CREATED=$(echo "$TRACK_DATA" | $JQ_BIN -r '.created_at') + TRACK_TAGS=$(echo "$TRACK_DATA" | $JQ_BIN -r '[.tags[] | select(.[0] == "t") | .[1]] | join(",")') + + # Build track JSON object + TRACK_JSON=$(cat < "${OUTPUT_FILE}.tmp" + mv "${OUTPUT_FILE}.tmp" "$OUTPUT_FILE" +done + +# ============================================ +# Step 4: Flattened tracks file +# ============================================ + +log "Generating flattened tracks file..." + +cat "$ALL_TRACKS_JSON" | $JQ_BIN ' + .[] | { + id: .id, + created_at: .created_at, + title: (.tags[] | select(.[0] == "title") | .[1] // "Untitled"), + artist: (.tags[] | select(.[0] == "artist") | .[1] // "Unknown Artist"), + album: (.tags[] | select(.[0] == "album") | .[1] // ""), + url: (.tags[] | select(.[0] == "url") | .[1] // ""), + image: (.tags[] | select(.[0] == "image") | .[1] // ""), + duration: (.tags[] | select(.[0] == "duration") | .[1] // ""), + tags: [.tags[] | select(.[0] == "t") | .[1]], + d_tag: (.tags[] | select(.[0] == "d") | .[1] // "") + } +' | $JQ_BIN -s '.' > "$FLATTENED_OUTPUT" + +# ============================================ +# Step 5: Save raw data +# ============================================ + +cp "$PLAYLISTS_JSON" "$DATA_DIR/playlists-raw.json" +cp "$ALL_TRACKS_JSON" "$DATA_DIR/tracks-raw.json" + +# ============================================ +# Step 6: Cleanup +# ============================================ + +rm -f "$PLAYLISTS_JSON" "$ALL_TRACKS_JSON" "$TRACK_LOOKUP" + +# ============================================ +# Step 7: Summary +# ============================================ + +echo "" +echo -e "${BLUE}========================================${NC}" +echo -e "${GREEN}📊 Playlist Summary${NC}" +echo -e "${BLUE}========================================${NC}" +echo -e " 📀 Playlists: $PLAYLIST_COUNT" +echo -e " 🎵 Total tracks found: $TRACK_COUNT" +echo -e " 📁 Data saved to: $DATA_DIR/" +echo -e "${BLUE}========================================${NC}" + +# Show playlist names +echo "" +echo -e "${BLUE}📝 Playlist Names:${NC}" +cat "$OUTPUT_FILE" | $JQ_BIN -r '.playlists[].name' 2>/dev/null | while read -r name; do + if [ -n "$name" ]; then + echo " 🎵 $name" + else + echo " ⚠️ (unnamed)" + fi +done \ No newline at end of file diff --git a/themes/one-pager/assets/css/style.css b/themes/one-pager/assets/css/style.css index 7cc3ad4..a4186f1 100644 --- a/themes/one-pager/assets/css/style.css +++ b/themes/one-pager/assets/css/style.css @@ -2093,3 +2093,187 @@ button { } /* ============ END CYBERPUNK FOOTER ============ */ + +/* ============================================ + * PLAYLISTS + * ============================================ */ + +.playlists-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + gap: 2.5rem; +} + +.playlist-card { + background: var(--card-bg); + border: 2px solid var(--accent-grey); + padding: 1.8rem; + transition: all 0.3s ease; + transform: skewX(-2deg); + box-shadow: 8px 8px 0 var(--accent-grey); +} + +.playlist-card:hover { + transform: skewX(0deg) translate(-4px, -4px); + box-shadow: 12px 12px 0 var(--accent-yellow); +} + +.playlist-image { + width: 100%; + height: 180px; + object-fit: cover; + border: 2px solid var(--accent-grey); + margin-bottom: 1rem; + filter: grayscale(30%); + transition: all 0.3s ease; +} + +.playlist-card:hover .playlist-image { + filter: grayscale(0%); +} + +.playlist-info h3 { + font-family: 'Poppins', sans-serif; + font-weight: 700; + font-size: 1.3rem; + letter-spacing: -1px; + margin-bottom: 0.3rem; + color: var(--text-primary); +} + +.playlist-description { + color: var(--text-secondary); + font-size: 0.9rem; + margin-bottom: 0.5rem; +} + +.playlist-track-count { + display: inline-block; + background: var(--accent-yellow); + color: var(--accent-black); + padding: 0.2rem 0.8rem; + font-size: 0.7rem; + font-weight: 700; + letter-spacing: 1px; + border-radius: 4px; + margin-top: 0.5rem; +} + +.playlist-tracks { + margin-top: 1.5rem; + border-top: 2px solid var(--accent-grey); + padding-top: 1rem; +} + +.playlist-tracks h4 { + font-family: 'Poppins', sans-serif; + font-weight: 600; + font-size: 1rem; + color: var(--accent-yellow); + margin-bottom: 1rem; + letter-spacing: 1px; +} + +.tracks-list { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.track-item { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.8rem; + padding: 0.8rem; + background: var(--bg-secondary); + border-radius: 8px; + border-left: 3px solid var(--accent-yellow); + transition: all 0.3s ease; +} + +.track-item:hover { + background: var(--bg-primary); + border-left-color: var(--accent-yellow); +} + +.track-thumbnail { + width: 50px; + height: 50px; + object-fit: cover; + border-radius: 4px; + border: 1px solid var(--accent-grey); +} + +.track-info { + flex: 1; + min-width: 150px; +} + +.track-title { + display: block; + font-family: 'Poppins', sans-serif; + font-weight: 600; + font-size: 0.9rem; + color: var(--text-primary); +} + +.track-artist { + display: block; + font-size: 0.75rem; + color: var(--accent-yellow); +} + +.track-album { + display: block; + font-size: 0.65rem; + color: var(--accent-grey); +} + +.track-duration { + font-size: 0.65rem; + color: var(--accent-grey); + background: var(--tag-bg); + padding: 0.1rem 0.5rem; + border-radius: 3px; +} + +.track-player { + width: 100%; + margin-top: 0.5rem; + border: 1px solid var(--accent-grey); + border-radius: 4px; +} + +.track-item .track-tags { + display: flex; + flex-wrap: wrap; + gap: 0.3rem; + margin-top: 0.3rem; +} + +/* Responsive */ +@media (max-width: 768px) { + .playlists-grid { + grid-template-columns: 1fr; + } + + .playlist-card { + transform: skewX(0deg); + } + + .playlist-card:hover { + transform: translate(-2px, -2px); + } + + .track-item { + flex-direction: column; + align-items: flex-start; + } + + .track-thumbnail { + width: 100%; + height: 100px; + } +} +/* //////// END PLAYLISTS ////////// */ diff --git a/themes/one-pager/layouts/partials/playlists.html b/themes/one-pager/layouts/partials/playlists.html new file mode 100644 index 0000000..8f5c4c9 --- /dev/null +++ b/themes/one-pager/layouts/partials/playlists.html @@ -0,0 +1,90 @@ +{{ $playlistsData := index hugo.Data "playlists" }} +{{ $playlists := index $playlistsData "playlists" }} + +{{ if $playlists }} +
+

Playlists

+ +
+ {{ range $playlistIndex, $playlist := $playlists }} + {{/* Debug: show the type and data */}} + {{/* +
+ Playlist {{ $playlistIndex }}: Type = {{ printf "%T" $playlist }} +
+ */}} + + {{/* Get the actual playlist object - it's the first element of the array */}} + {{ $playlistObj := index $playlist 0 }} + + {{ if $playlistObj }} + {{ $name := index $playlistObj "name" | default "Untitled Playlist" }} + {{ $description := index $playlistObj "description" | default "" }} + {{ $image := index $playlistObj "image" | default "" }} + {{ $tracks := index $playlistObj "tracks" | default slice }} + +
+ {{ if $image }} + {{ $name }} + {{ end }} + +
+

{{ $name }}

+ {{ if $description }} +

{{ $description }}

+ {{ end }} + {{ if $tracks }} + {{ len $tracks }} tracks + {{ end }} +
+ + {{ if $tracks }} +
+

Tracks

+
+ {{ range $track := $tracks }} + {{ $trackTitle := index $track "title" | default "Untitled" }} + {{ $trackArtist := index $track "artist" | default "Unknown Artist" }} + {{ $trackAlbum := index $track "album" | default "" }} + {{ $trackImage := index $track "image" | default "" }} + {{ $trackUrl := index $track "url" | default "" }} + {{ $trackDuration := index $track "duration" | default "" }} + {{ $trackTags := index $track "tags" | default slice }} + +
+ {{ if $trackImage }} + {{ $trackTitle }} + {{ end }} +
+ {{ $trackTitle }} + {{ $trackArtist }} + {{ if $trackAlbum }} + {{ $trackAlbum }} + {{ end }} + {{ if $trackDuration }} + {{ $trackDuration }}s + {{ end }} +
+ {{ if $trackUrl }} + + {{ end }} + {{ if $trackTags }} +
+ {{ range $trackTags }} + {{ . }} + {{ end }} +
+ {{ end }} +
+ {{ end }} +
+
+ {{ end }} +
+ {{ end }} + {{ end }} +
+
+{{ end }} From 9cb2b8337ef7f00b35a4174b4821dca866f4af79 Mon Sep 17 00:00:00 2001 From: sakrecoer Date: Sun, 16 Aug 2026 14:47:51 +0200 Subject: [PATCH 02/10] add the playlist styles --- themes/one-pager/assets/css/style.css | 420 ++++++++++++++++++++++++++ 1 file changed, 420 insertions(+) diff --git a/themes/one-pager/assets/css/style.css b/themes/one-pager/assets/css/style.css index a4186f1..964c708 100644 --- a/themes/one-pager/assets/css/style.css +++ b/themes/one-pager/assets/css/style.css @@ -2277,3 +2277,423 @@ button { } } /* //////// END PLAYLISTS ////////// */ + +/* ============================================ + * PLAYLIST LIST PAGE + * ============================================ */ + +.playlists-list .section-description { + color: var(--text-secondary); + font-size: 1.1rem; + margin-bottom: 3rem; + text-align: center; + opacity: 0.8; +} + +.playlists-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 2.5rem; +} + +.playlist-card { + background: var(--card-bg); + border: 2px solid var(--accent-grey); + overflow: hidden; + transition: all 0.3s ease; + transform: skewX(-2deg); + box-shadow: 8px 8px 0 var(--accent-grey); + position: relative; +} + +.playlist-card:hover { + transform: skewX(0deg) translate(-4px, -4px); + box-shadow: 12px 12px 0 var(--accent-yellow); +} + +.playlist-link { + display: block; + text-decoration: none; + color: inherit; +} + +.playlist-image { + width: 100%; + height: 200px; + object-fit: cover; + border-bottom: 3px solid var(--accent-yellow); + filter: grayscale(30%); + transition: all 0.3s ease; +} + +.playlist-card:hover .playlist-image { + filter: grayscale(0%); +} + +.playlist-info { + padding: 1.5rem; +} + +.playlist-info h2 { + font-family: 'Poppins', sans-serif; + font-weight: 700; + font-size: 1.3rem; + letter-spacing: -1px; + color: var(--text-primary); + margin-bottom: 0.3rem; +} + +.playlist-description { + color: var(--text-secondary); + font-size: 0.9rem; + margin-bottom: 0.5rem; +} + +.playlist-track-count { + display: inline-block; + background: var(--accent-yellow); + color: var(--accent-black); + padding: 0.2rem 0.8rem; + font-size: 0.7rem; + font-weight: 700; + letter-spacing: 1px; + border-radius: 4px; +} + +.playlist-hover-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.6); + opacity: 0; + transition: opacity 0.3s ease; + backdrop-filter: blur(4px); +} + +.playlist-card:hover .playlist-hover-overlay { + opacity: 1; +} + +.playlist-view-btn { + padding: 0.8rem 2rem; + background: var(--accent-yellow); + color: var(--accent-black); + font-family: 'Poppins', sans-serif; + font-weight: 700; + font-size: 0.8rem; + letter-spacing: 2px; + text-transform: uppercase; + border: 2px solid var(--accent-grey); + transform: skewX(-33deg); + transition: all 0.3s ease; +} + +.playlist-view-btn:hover { + transform: skewX(-33deg) scale(1.1); + background: var(--accent-black); + color: var(--accent-yellow); + border-color: var(--accent-yellow); +} + +/* ============================================ + * PLAYLIST SINGLE PAGE + * ============================================ */ + +.playlist-detail { + padding-top: 2rem; +} + +.playlist-header { + margin-bottom: 3rem; + padding: 2rem; + background: var(--bg-secondary); + border: 2px solid var(--accent-grey); + box-shadow: 8px 8px 0 var(--accent-grey); + transform: skewX(-2deg); +} + +.playlist-header-content { + display: flex; + align-items: center; + gap: 2.5rem; + flex-wrap: wrap; + transform: skewX(2deg); +} + +.playlist-header-image { + width: 200px; + height: 200px; + object-fit: cover; + border: 3px solid var(--accent-yellow); + box-shadow: 4px 4px 0 var(--accent-grey); + flex-shrink: 0; +} + +.playlist-header-info h1 { + font-family: 'Poppins', sans-serif; + font-weight: 900; + font-size: 2.8rem; + letter-spacing: -2px; + color: var(--text-primary); + text-transform: uppercase; + text-shadow: 2px 2px 0 var(--accent-yellow); +} + +.playlist-header-info .playlist-description { + font-size: 1.1rem; + color: var(--text-secondary); + margin: 0.5rem 0 1rem 0; +} + +.playlist-meta { + display: flex; + gap: 1.5rem; + flex-wrap: wrap; +} + +.playlist-meta span { + font-size: 0.85rem; + color: var(--accent-grey); + letter-spacing: 1px; +} + +/* Track list */ +.playlist-tracks-list { + display: flex; + flex-direction: column; + gap: 1rem; + margin: 2rem 0; +} + +.playlist-track-item { + display: flex; + align-items: center; + gap: 1rem; + padding: 1rem; + background: var(--card-bg); + border: 2px solid var(--accent-grey); + transition: all 0.3s ease; + transform: skewX(-2deg); + box-shadow: 4px 4px 0 var(--accent-grey); +} + +.playlist-track-item:hover { + transform: skewX(0deg) translate(-2px, -2px); + box-shadow: 6px 6px 0 var(--accent-yellow); +} + +.track-number { + font-family: 'Poppins', sans-serif; + font-weight: 700; + font-size: 1.2rem; + color: var(--accent-grey); + min-width: 30px; + text-align: center; +} + +.track-thumbnail { + width: 60px; + height: 60px; + object-fit: cover; + border: 2px solid var(--accent-grey); + border-radius: 4px; + flex-shrink: 0; +} + +.track-info { + flex: 1; + min-width: 200px; +} + +.track-title { + display: block; + font-family: 'Poppins', sans-serif; + font-weight: 600; + font-size: 1rem; + color: var(--text-primary); +} + +.track-artist { + display: block; + font-size: 0.8rem; + color: var(--accent-yellow); +} + +.track-album { + display: block; + font-size: 0.7rem; + color: var(--accent-grey); +} + +.track-duration { + font-size: 0.7rem; + color: var(--accent-grey); + background: var(--tag-bg); + padding: 0.1rem 0.5rem; + border-radius: 3px; +} + +.track-tags { + display: flex; + flex-wrap: wrap; + gap: 0.3rem; + margin-top: 0.2rem; +} + +.track-player-wrapper { + display: flex; + align-items: center; + gap: 0.5rem; + flex-shrink: 0; +} + +.track-player { + width: 150px; + border: 1px solid var(--accent-grey); + border-radius: 4px; +} + +.track-share-btn { + background: none; + border: 1px solid var(--accent-grey); + color: var(--text-primary); + padding: 0.3rem 0.6rem; + border-radius: 4px; + cursor: pointer; + transition: all 0.3s ease; + font-size: 0.8rem; +} + +.track-share-btn:hover { + border-color: var(--accent-yellow); + color: var(--accent-yellow); + transform: scale(1.05); +} + +/* Play all button */ +.playlist-actions { + text-align: center; + margin: 2rem 0; +} + +.playlist-play-all { + background: var(--accent-yellow); + color: var(--accent-black); + border: 2px solid var(--accent-grey); + padding: 1rem 3rem; + font-family: 'Poppins', sans-serif; + font-weight: 700; + font-size: 1rem; + letter-spacing: 3px; + text-transform: uppercase; + cursor: pointer; + transition: all 0.3s ease; + transform: skewX(-33deg); + box-shadow: 6px 6px 0 var(--accent-grey); +} + +.playlist-play-all:hover { + transform: skewX(-33deg) scale(1.05); + background: var(--accent-black); + color: var(--accent-yellow); + border-color: var(--accent-yellow); + box-shadow: 8px 8px 0 var(--accent-yellow); +} + +/* Back link */ +.playlist-back { + margin-top: 2rem; + text-align: center; +} + +.back-link { + color: var(--accent-grey); + text-decoration: none; + font-family: 'Poppins', sans-serif; + font-weight: 500; + font-size: 0.8rem; + letter-spacing: 2px; + text-transform: uppercase; + transition: all 0.3s ease; + padding: 0.5rem 1.5rem; + border: 1px solid transparent; +} + +.back-link:hover { + color: var(--accent-yellow); + border-color: var(--accent-yellow); +} + +.no-tracks { + text-align: center; + padding: 3rem; + color: var(--text-secondary); + font-size: 1.1rem; +} + +/* Responsive */ +@media (max-width: 768px) { + .playlist-header-content { + flex-direction: column; + text-align: center; + } + + .playlist-header-info h1 { + font-size: 2rem; + } + + .playlist-header-image { + width: 150px; + height: 150px; + } + + .playlist-meta { + justify-content: center; + } + + .playlist-track-item { + flex-wrap: wrap; + gap: 0.5rem; + } + + .track-player-wrapper { + width: 100%; + flex-wrap: wrap; + justify-content: center; + } + + .track-player { + width: 100%; + max-width: 300px; + } + + .playlist-play-all { + padding: 0.8rem 2rem; + font-size: 0.8rem; + } +} + +@media (max-width: 480px) { + .playlist-header-info h1 { + font-size: 1.5rem; + } + + .playlist-header-image { + width: 120px; + height: 120px; + } + + .track-thumbnail { + width: 40px; + height: 40px; + } + + .track-title { + font-size: 0.85rem; + } +} From 3daecd6eeaaae2268087f6ea5a8bec8708b4c5c9 Mon Sep 17 00:00:00 2001 From: sakrecoer Date: Sun, 16 Aug 2026 14:49:45 +0200 Subject: [PATCH 03/10] split up index to fit baseof.html --- themes/one-pager/layouts/_default/baseof.html | 35 +++++++++++++++++ .../layouts/{ => _default}/index.html | 38 ++----------------- themes/one-pager/layouts/partials/seo.html | 1 + 3 files changed, 39 insertions(+), 35 deletions(-) create mode 100644 themes/one-pager/layouts/_default/baseof.html rename themes/one-pager/layouts/{ => _default}/index.html (74%) diff --git a/themes/one-pager/layouts/_default/baseof.html b/themes/one-pager/layouts/_default/baseof.html new file mode 100644 index 0000000..50a2fa7 --- /dev/null +++ b/themes/one-pager/layouts/_default/baseof.html @@ -0,0 +1,35 @@ + + + + + + + {{ partial "seo.html" . }} + + {{ $style := resources.Get "css/style.css" | resources.Minify | fingerprint }} + + + + + + + + {{ partial "header.html" . }} + +
+ + {{ block "main" . }}{{ end }} + +
+ + {{ partial "footer.html" . }} + + + + + {{ $js := resources.Get "js/scripts.js" | resources.Minify | fingerprint }} + + + + + diff --git a/themes/one-pager/layouts/index.html b/themes/one-pager/layouts/_default/index.html similarity index 74% rename from themes/one-pager/layouts/index.html rename to themes/one-pager/layouts/_default/index.html index e98fd97..cd72782 100644 --- a/themes/one-pager/layouts/index.html +++ b/themes/one-pager/layouts/_default/index.html @@ -1,28 +1,4 @@ - - - - - - - {{ partial "seo.html" . }} - - - - - - - - - - {{ $style := resources.Get "css/style.css" | resources.Minify | fingerprint }} - - - - - - {{ partial "header.html" . }} - -
+{{ define "main" }} {{ partial "blog.html" . }} @@ -32,11 +8,7 @@ {{ partial "videos.html" . }} -
- - {{ partial "footer.html" . }} - - +
@@ -96,8 +68,4 @@
- - {{ $js := resources.Get "js/scripts.js" | resources.Minify | fingerprint }} - - - +{{ end }} diff --git a/themes/one-pager/layouts/partials/seo.html b/themes/one-pager/layouts/partials/seo.html index b20bcf0..37ba8ea 100644 --- a/themes/one-pager/layouts/partials/seo.html +++ b/themes/one-pager/layouts/partials/seo.html @@ -88,6 +88,7 @@ + {{ if $picture }} From 1c35e99ed2abc9fb7369a7d1fec1c6287a2cc29f Mon Sep 17 00:00:00 2001 From: sakrecoer Date: Sun, 16 Aug 2026 15:15:41 +0200 Subject: [PATCH 04/10] make nav clickable --- themes/one-pager/assets/js/scripts.js | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/themes/one-pager/assets/js/scripts.js b/themes/one-pager/assets/js/scripts.js index 3e6ecd1..e6b6e5e 100644 --- a/themes/one-pager/assets/js/scripts.js +++ b/themes/one-pager/assets/js/scripts.js @@ -933,20 +933,25 @@ document.addEventListener('DOMContentLoaded', function() { }); }); - // Smooth scrolling for navigation links (both main nav and sticky nav) + // Smooth scrolling for navigation links (only for # anchors) document.querySelectorAll('nav a').forEach(link => { link.addEventListener('click', function(e) { - e.preventDefault(); - const targetId = this.getAttribute('href'); - const targetElement = document.querySelector(targetId); - if (targetElement) { - const navHeight = stickyNav.offsetHeight; - const targetPosition = targetElement.getBoundingClientRect().top + window.pageYOffset - navHeight - 20; - window.scrollTo({ - top: targetPosition, - behavior: 'smooth' - }); + const href = this.getAttribute('href'); + // Only intercept if it's a # anchor link + if (href && href.startsWith('#')) { + e.preventDefault(); + const targetId = href; + const targetElement = document.querySelector(targetId); + if (targetElement) { + const navHeight = stickyNav.offsetHeight; + const targetPosition = targetElement.getBoundingClientRect().top + window.pageYOffset - navHeight - 20; + window.scrollTo({ + top: targetPosition, + behavior: 'smooth' + }); + } } + // If it's a regular link (like /playlists/), let it navigate normally }); }); From f0e8bafc4c5d53b64e5ad4e7285a429c6f739731 Mon Sep 17 00:00:00 2001 From: sakrecoer Date: Sun, 16 Aug 2026 15:15:57 +0200 Subject: [PATCH 05/10] render playlists pages --- .gitignore | 1 + generate-playlist-pages.sh | 67 +++++++++ themes/one-pager/layouts/playlists/list.html | 37 +++++ .../one-pager/layouts/playlists/single.html | 137 ++++++++++++++++++ 4 files changed, 242 insertions(+) create mode 100755 generate-playlist-pages.sh create mode 100644 themes/one-pager/layouts/playlists/list.html create mode 100644 themes/one-pager/layouts/playlists/single.html diff --git a/.gitignore b/.gitignore index e7d733d..6f9b8e5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ data fetch-and-convert.sh deploy.sh hugo.toml +content/playlists diff --git a/generate-playlist-pages.sh b/generate-playlist-pages.sh new file mode 100755 index 0000000..affb3f1 --- /dev/null +++ b/generate-playlist-pages.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +# ============================================ +# Generate Hugo Playlist Pages from JSON +# ============================================ + +DATA_FILE="data/playlists/playlists.json" +CONTENT_DIR="content/playlists" + +# Check if data file exists +if [ ! -f "$DATA_FILE" ]; then + echo "❌ Error: $DATA_FILE not found. Run fetch-playlist.sh first." + exit 1 +fi + +# Create content directory +mkdir -p "$CONTENT_DIR" + +# Create _index.md if it doesn't exist +if [ ! -f "$CONTENT_DIR/_index.md" ]; then + cat > "$CONTENT_DIR/_index.md" << 'EOF' +--- +title: "Playlists" +--- +EOF +fi + +# Clear old playlist pages (keep _index.md) +find "$CONTENT_DIR" -maxdepth 1 -type f -name "*.md" ! -name "_index.md" -delete + +# Process each playlist +cat "$DATA_FILE" | jq -c '.playlists[]' | while read -r playlist; do + NAME=$(echo "$playlist" | jq -r '.name // "Untitled Playlist"') + SLUG=$(echo "$NAME" | tr ' ' '-' | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]//g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//') + + # If slug is empty, use the ID + if [ -z "$SLUG" ] || [ "$SLUG" = "untitled-playlist" ]; then + SLUG=$(echo "$playlist" | jq -r '.id' | head -c 12) + fi + + DESCRIPTION=$(echo "$playlist" | jq -r '.description // ""') + IMAGE=$(echo "$playlist" | jq -r '.image // ""') + TRACKS=$(echo "$playlist" | jq -c '.tracks') + PLAYLIST_ID=$(echo "$playlist" | jq -r '.id') + CREATED_AT=$(echo "$playlist" | jq -r '.created_at') + + # Convert created_at to date + CREATED_DATE=$(date -d "@$CREATED_AT" "+%Y-%m-%d" 2>/dev/null || echo $(date "+%Y-%m-%d")) + + cat > "$CONTENT_DIR/$SLUG.md" < +

Playlists

+

Explore all my curated playlists from the Nostr network

+ +
+ {{ range .Pages }} + {{ $name := .Title }} + {{ $description := .Params.playlist_description | default "" }} + {{ $image := .Params.playlist_image | default "" }} + {{ $tracks := .Params.playlist_tracks | default slice }} + + + {{ end }} +
+ +{{ end }} \ No newline at end of file diff --git a/themes/one-pager/layouts/playlists/single.html b/themes/one-pager/layouts/playlists/single.html new file mode 100644 index 0000000..9c2a107 --- /dev/null +++ b/themes/one-pager/layouts/playlists/single.html @@ -0,0 +1,137 @@ +{{ define "main" }} +{{ $name := .Title }} +{{ $description := .Params.playlist_description | default "" }} +{{ $image := .Params.playlist_image | default "" }} +{{ $tracks := .Params.playlist_tracks | default slice }} +{{ $created_at := .Params.playlist_created_at | default 0 }} + +
+
+
+ {{ if $image }} + {{ $name }} + {{ end }} +
+

{{ $name }}

+ {{ if $description }} +

{{ $description }}

+ {{ end }} +
+ 🎵 {{ len $tracks }} tracks + {{ if $created_at }} + 📅 {{ dateFormat "January 2, 2006" (time $created_at) }} + {{ end }} +
+
+
+
+ +
+ {{ if $tracks }} + {{ range $index, $track := $tracks }} +
+
{{ add $index 1 }}
+ {{ if $track.image }} + {{ $track.title }} + {{ end }} +
+ {{ $track.title | default "Untitled" }} + {{ $track.artist | default "Unknown Artist" }} + {{ if $track.album }} + {{ $track.album }} + {{ end }} + {{ if $track.duration }} + {{ $track.duration }}s + {{ end }} + {{ if $track.tags }} +
+ {{ range $track.tags }} + {{ . }} + {{ end }} +
+ {{ end }} +
+
+ {{ if $track.url }} + + {{ end }} + {{ if $track.id }} + + {{ end }} +
+
+ {{ end }} + {{ else }} +

No tracks found for this playlist.

+ {{ end }} +
+ + {{ if $tracks }} +
+ +
+ {{ end }} + + +
+ + +{{ end }} \ No newline at end of file From 4df2e1fa37a213c51852b22d1f0c4cdd506c13bd Mon Sep 17 00:00:00 2001 From: sakrecoer Date: Sun, 16 Aug 2026 15:16:28 +0200 Subject: [PATCH 06/10] slice up the header --- themes/one-pager/layouts/_default/baseof.html | 3 ++- themes/one-pager/layouts/_default/index.html | 4 ++++ themes/one-pager/layouts/partials/header.html | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/themes/one-pager/layouts/_default/baseof.html b/themes/one-pager/layouts/_default/baseof.html index 50a2fa7..a5bf90d 100644 --- a/themes/one-pager/layouts/_default/baseof.html +++ b/themes/one-pager/layouts/_default/baseof.html @@ -14,7 +14,8 @@ - {{ partial "header.html" . }} + {{ block "header" . }}{{ end }} +
diff --git a/themes/one-pager/layouts/_default/index.html b/themes/one-pager/layouts/_default/index.html index cd72782..5930d41 100644 --- a/themes/one-pager/layouts/_default/index.html +++ b/themes/one-pager/layouts/_default/index.html @@ -1,3 +1,7 @@ +{{ define "header" }} +{{ partial "header.html" . }} +{{ end }} + {{ define "main" }} {{ partial "blog.html" . }} diff --git a/themes/one-pager/layouts/partials/header.html b/themes/one-pager/layouts/partials/header.html index ef94269..c9c7db2 100644 --- a/themes/one-pager/layouts/partials/header.html +++ b/themes/one-pager/layouts/partials/header.html @@ -45,6 +45,7 @@ From 4f6779e9689f5b0af18500a9572f42b10b56bed7 Mon Sep 17 00:00:00 2001 From: sakrecoer Date: Sun, 16 Aug 2026 23:09:07 +0200 Subject: [PATCH 07/10] add playlist concept --- themes/one-pager/assets/css/style.css | 129 ++++--- themes/one-pager/layouts/playlists/list.html | 76 +++- .../one-pager/layouts/playlists/single.html | 341 +++++++++++------- 3 files changed, 363 insertions(+), 183 deletions(-) diff --git a/themes/one-pager/assets/css/style.css b/themes/one-pager/assets/css/style.css index 964c708..6c0eca3 100644 --- a/themes/one-pager/assets/css/style.css +++ b/themes/one-pager/assets/css/style.css @@ -121,25 +121,6 @@ body { opacity: 0.6; } -[data-theme="dark"] .main-header::before { - background: - /* Darker overlay for dark mode */ - linear-gradient( - to bottom, - rgba(0, 0, 0, 0.7) 0%, - rgba(0, 0, 0, 0.4) 40%, - rgba(0, 0, 0, 0.4) 60%, - rgba(0, 0, 0, 0.7) 100% - ), - repeating-linear-gradient( - 33deg, - transparent, - transparent 50px, - var(--accent-yellow) 50px, - var(--accent-yellow) 51px - ); - opacity: 0.7; -} /* Decorative '>' character */ .main-header::after { @@ -156,6 +137,59 @@ body { pointer-events: none; } +/* */ +/* */ +/* */ + +.playlist-header::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 0; + pointer-events: none; + background: + /* Dark overlay for readability */ + linear-gradient( + to bottom, + rgba(0, 0, 0, 0.5) 0%, + rgba(0, 0, 0, 0.25) 40%, + rgba(0, 0, 0, 0.25) 60%, + rgba(0, 0, 0, 0.5) 100% + ), + /* Cyberpunk pattern */ + repeating-linear-gradient( + 33deg, + transparent, + transparent 50px, + var(--accent-yellow) 50px, + var(--accent-yellow) 51px + ); + opacity: 0.6; +} + +/* Decorative '>' character */ +.playlist-header::after { + content: '>'; + position: absolute; + bottom: 40px; + right: 40px; + z-index: 1; + font-family: 'Poppins', sans-serif; + font-weight: 900; + font-size: 4rem; + color: var(--accent-y); + opacity: 0.15; + pointer-events: none; +} + + +/* */ + + + #bannerino { filter: grayscale(50%); position:absolute; @@ -2115,7 +2149,7 @@ button { .playlist-card:hover { transform: skewX(0deg) translate(-4px, -4px); - box-shadow: 12px 12px 0 var(--accent-yellow); + box-shadow: 12px 12px 0 var(--accent-grey); } .playlist-image { @@ -2149,7 +2183,7 @@ button { .playlist-track-count { display: inline-block; - background: var(--accent-yellow); + background: var(--accent-grey); color: var(--accent-black); padding: 0.2rem 0.8rem; font-size: 0.7rem; @@ -2308,7 +2342,7 @@ button { .playlist-card:hover { transform: skewX(0deg) translate(-4px, -4px); - box-shadow: 12px 12px 0 var(--accent-yellow); + box-shadow: 12px 12px 0 var(--accent-grey); } .playlist-link { @@ -2351,8 +2385,8 @@ button { .playlist-track-count { display: inline-block; - background: var(--accent-yellow); - color: var(--accent-black); + background: var(--accent-grey); + color: var(--bg-primary); padding: 0.2rem 0.8rem; font-size: 0.7rem; font-weight: 700; @@ -2372,7 +2406,7 @@ button { background: rgba(0, 0, 0, 0.6); opacity: 0; transition: opacity 0.3s ease; - backdrop-filter: blur(4px); + backdrop-filter: blur(20px); } .playlist-card:hover .playlist-hover-overlay { @@ -2382,22 +2416,22 @@ button { .playlist-view-btn { padding: 0.8rem 2rem; background: var(--accent-yellow); - color: var(--accent-black); + color: var(--bg-primary); font-family: 'Poppins', sans-serif; font-weight: 700; font-size: 0.8rem; letter-spacing: 2px; text-transform: uppercase; - border: 2px solid var(--accent-grey); - transform: skewX(-33deg); + border-radius: 7px; transition: all 0.3s ease; + filter: drop-shadow(0 0 20px var(--accent-yellow)); } .playlist-view-btn:hover { - transform: skewX(-33deg) scale(1.1); - background: var(--accent-black); - color: var(--accent-yellow); - border-color: var(--accent-yellow); + background: var(--accent-yellow); + color: var(--bg-primary); + transition: all .3s ease; + filter: drop-shadow(0 0 20px var(--accent-yellow)); } /* ============================================ @@ -2458,7 +2492,7 @@ button { .playlist-meta span { font-size: 0.85rem; - color: var(--accent-grey); + color: var(--bg-primary); letter-spacing: 1px; } @@ -2484,7 +2518,7 @@ button { .playlist-track-item:hover { transform: skewX(0deg) translate(-2px, -2px); - box-shadow: 6px 6px 0 var(--accent-yellow); + box-shadow: 6px 6px 0 var(--accent-grey); } .track-number { @@ -2553,7 +2587,7 @@ button { } .track-player { - width: 150px; + width: 100%; border: 1px solid var(--accent-grey); border-radius: 4px; } @@ -2582,27 +2616,26 @@ button { } .playlist-play-all { - background: var(--accent-yellow); - color: var(--accent-black); - border: 2px solid var(--accent-grey); - padding: 1rem 3rem; - font-family: 'Poppins', sans-serif; + background: var(--bg-primary); + color: var(--accent-yellow) !important; + border: 2px solid var(--accent-yellow); + padding: .5rem 1.5rem; + font-family: poppins,sans-serif; font-weight: 700; - font-size: 1rem; + font-size: .7rem; letter-spacing: 3px; text-transform: uppercase; cursor: pointer; - transition: all 0.3s ease; - transform: skewX(-33deg); - box-shadow: 6px 6px 0 var(--accent-grey); + transition: all .3s ease; + filter: drop-shadow(0 0 20px var(--accent-yellow)); + animation: glow 3s infinite alternate; } .playlist-play-all:hover { - transform: skewX(-33deg) scale(1.05); - background: var(--accent-black); - color: var(--accent-yellow); + transform: scale(1.05); + background: var(--accent-yellow); + color: var(--bg-primary) !important; border-color: var(--accent-yellow); - box-shadow: 8px 8px 0 var(--accent-yellow); } /* Back link */ diff --git a/themes/one-pager/layouts/playlists/list.html b/themes/one-pager/layouts/playlists/list.html index 5394a76..ad285b8 100644 --- a/themes/one-pager/layouts/playlists/list.html +++ b/themes/one-pager/layouts/playlists/list.html @@ -1,6 +1,76 @@ +{{ define "header" }} + {{ $displayName := "" }} + {{ $name := "" }} + {{ $picture := "" }} + {{ $banner := "" }} + {{ $about := "" }} + {{ $website := "" }} + {{ $nip05 := "" }} + {{ $lud16 := "" }} + + {{ range hugo.Data.profile }} + {{ range . }} + {{ range .tags }} + {{ if eq (index . 0) "display_name" }} + {{ $displayName = index . 1 }} + {{ else if eq (index . 0) "name" }} + {{ $name = index . 1 }} + {{ else if eq (index . 0) "picture" }} + {{ $picture = index . 1 }} + {{ else if eq (index . 0) "banner" }} + {{ $banner = index . 1 }} + {{ else if eq (index . 0) "about" }} + {{ $about = index . 1 }} + {{ else if eq (index . 0) "website" }} + {{ $website = index . 1 }} + {{ else if eq (index . 0) "nip05" }} + {{ $nip05 = index . 1 }} + {{ else if eq (index . 0) "lud16" }} + {{ $lud16 = index . 1 }} + {{ end }} + {{ end }} + {{ end }} + {{ end }} + + + {{ if not $displayName }} + {{ $displayName = $name }} + {{ end }} + + + +
+ + {{ if $banner }} +
+ +
+ {{ end }} + + + + +
+ +
+
+ {{ if $picture }} + {{ $displayName }}'s profile picture' + {{ end }} + +
+
+ +
+ + + +{{ end }} + {{ define "main" }}
-

Playlists

+ +

Playlists

Explore all my curated playlists from the Nostr network

@@ -27,11 +97,11 @@
- ▶ View Playlist + ▶ Listen
{{ end }}
-{{ end }} \ No newline at end of file +{{ end }} diff --git a/themes/one-pager/layouts/playlists/single.html b/themes/one-pager/layouts/playlists/single.html index 9c2a107..ddc2ed0 100644 --- a/themes/one-pager/layouts/playlists/single.html +++ b/themes/one-pager/layouts/playlists/single.html @@ -1,137 +1,214 @@ +{{ define "header" }} + + {{ $displayName := "" }} + {{ $name := "" }} + {{ $picture := "" }} + {{ $banner := "" }} + {{ $about := "" }} + {{ $website := "" }} + {{ $nip05 := "" }} + {{ $lud16 := "" }} + + {{ range hugo.Data.profile }} + {{ range . }} + {{ range .tags }} + {{ if eq (index . 0) "display_name" }} + {{ $displayName = index . 1 }} + {{ else if eq (index . 0) "name" }} + {{ $name = index . 1 }} + {{ else if eq (index . 0) "picture" }} + {{ $picture = index . 1 }} + {{ else if eq (index . 0) "banner" }} + {{ $banner = index . 1 }} + {{ else if eq (index . 0) "about" }} + {{ $about = index . 1 }} + {{ else if eq (index . 0) "website" }} + {{ $website = index . 1 }} + {{ else if eq (index . 0) "nip05" }} + {{ $nip05 = index . 1 }} + {{ else if eq (index . 0) "lud16" }} + {{ $lud16 = index . 1 }} + {{ end }} + {{ end }} + {{ end }} + {{ end }} + + + {{ if not $displayName }} + {{ $displayName = $name }} + {{ end }} + + + + +
+ + {{ if $banner }} +
+ +
+ {{ end }} + + + + +
+ +
+
+ {{ if $picture }} + {{ $displayName }}'s profile picture' + {{ end }} + +
+
+ +
+{{ end }} + + + {{ define "main" }} -{{ $name := .Title }} -{{ $description := .Params.playlist_description | default "" }} -{{ $image := .Params.playlist_image | default "" }} -{{ $tracks := .Params.playlist_tracks | default slice }} -{{ $created_at := .Params.playlist_created_at | default 0 }} + {{ $name := .Title }} + {{ $description := .Params.playlist_description | default "" }} + {{ $image := .Params.playlist_image | default "" }} + {{ $tracks := .Params.playlist_tracks | default slice }} + {{ $created_at := .Params.playlist_created_at | default 0 }} -
-
-
- {{ if $image }} - {{ $name }} - {{ end }} -
-

{{ $name }}

- {{ if $description }} -

{{ $description }}

- {{ end }} -
- 🎵 {{ len $tracks }} tracks - {{ if $created_at }} - 📅 {{ dateFormat "January 2, 2006" (time $created_at) }} - {{ end }} -
-
-
-
- -
- {{ if $tracks }} - {{ range $index, $track := $tracks }} -
-
{{ add $index 1 }}
- {{ if $track.image }} - {{ $track.title }} - {{ end }} -
- {{ $track.title | default "Untitled" }} - {{ $track.artist | default "Unknown Artist" }} - {{ if $track.album }} - {{ $track.album }} - {{ end }} - {{ if $track.duration }} - {{ $track.duration }}s - {{ end }} - {{ if $track.tags }} -
- {{ range $track.tags }} - {{ . }} - {{ end }} -
- {{ end }} -
-
- {{ if $track.url }} - - {{ end }} - {{ if $track.id }} - - {{ end }} -
-
- {{ end }} - {{ else }} -

No tracks found for this playlist.

- {{ end }} -
- - {{ if $tracks }} -
- -
- {{ end }} - - -
+ -function fallbackCopy(url) { - const textArea = document.createElement('textarea'); - textArea.value = url; - document.body.appendChild(textArea); - textArea.select(); - document.execCommand('copy'); - document.body.removeChild(textArea); - const btn = event.target.closest('.track-share-btn'); - const originalText = btn.innerHTML; - btn.innerHTML = '✓'; - setTimeout(() => { - btn.innerHTML = originalText; - }, 2000); -} +
+
+
+ {{ if $image }} + {{ $name }} + {{ end }} +
+

{{ $name }}

+ {{ if $description }} +

{{ $description }}

+ {{ end }} +
+ 🎵 {{ len $tracks }} tracks + {{ if $created_at }} + 📅 {{ dateFormat "January 2, 2006" (time $created_at) }} + {{ end }} +
+
+
+
-function playAllTracks() { - const players = document.querySelectorAll('.track-player'); - if (players.length === 0) return; - - let current = 0; - function playNext() { - if (current >= players.length) return; - const player = players[current]; - player.play(); - player.onended = function() { - current++; - playNext(); - }; - } - playNext(); -} - -{{ end }} \ No newline at end of file +
+ {{ if $tracks }} + {{ range $index, $track := $tracks }} +
+
{{ add $index 1 }}
+ {{ if $track.image }} + {{ $track.title }} + {{ end }} +
+ {{ $track.title | default "Untitled" }} + {{ $track.artist | default "Unknown Artist" }} + {{ if $track.album }} + {{ $track.album }} + {{ end }} + {{ if $track.duration }} + {{ $track.duration }}s + {{ end }} + {{ if $track.tags }} +
+ {{ range $track.tags }} + {{ . }} + {{ end }} +
+ {{ end }} +
+
+ {{ if $track.url }} + + {{ end }} + {{ if $track.id }} + + {{ end }} +
+
+ {{ end }} + {{ else }} +

No tracks found for this playlist.

+ {{ end }} +
+ + {{ if $tracks }} +
+ +
+ {{ end }} + + +
+ + +{{ end }} From 7561d08504c94f32a44464e7c833ae5e5a88e5d4 Mon Sep 17 00:00:00 2001 From: sakrecoer Date: Sun, 16 Aug 2026 23:19:27 +0200 Subject: [PATCH 08/10] update scripts and chain them --- example.deploy.sh | 5 +- example.fetch-playlist.sh | 214 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 example.fetch-playlist.sh diff --git a/example.deploy.sh b/example.deploy.sh index dd17630..ce741ae 100755 --- a/example.deploy.sh +++ b/example.deploy.sh @@ -12,8 +12,11 @@ echo "" >> $LOG_FILE echo "" >> $LOG_FILE cd $SITE_DIR +/usr/bin/rm $SITE_DIR/content/playlist/* /usr/bin/git pull >> $LOG_FILE -/usr/bin/bash fetch-and-convert.sh >> $LOG_FILE +/usr/bin/bash $SITE_DIR/fetch-and-convert.sh >> $LOG_FILE +/usr/bin/bash $SITE_DIR/fetch-playlist.sh >> $LOG_FILE +/usr/bin/bash $SITE_DIR/generate-playlist-pages.sh >> $LOG_FILE /usr/local/bin/hugo build --cleanDestinationDir --minify >> $LOG_FILE # Optional and potentially dangerous: the generator will creates a random file name (fingerprint) for css and js to be picked up when you do changes to it. If you don't remove them, over time your directory will bloat. A new file is only created if there are changes. diff --git a/example.fetch-playlist.sh b/example.fetch-playlist.sh new file mode 100644 index 0000000..15b34df --- /dev/null +++ b/example.fetch-playlist.sh @@ -0,0 +1,214 @@ +#!/bin/bash + +# ============================================ +# Nostr Playlist Fetcher for Hugo +# ============================================ + +# Configuration +PUBKEY="pubkey in hex or nip05" +RELAY="wss://your.relay" +DATA_DIR="data/playlists" +NAK_BIN="nak" +JQ_BIN="jq" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +mkdir -p "$DATA_DIR" + +log() { echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"; } +log_success() { echo -e "${GREEN}✅ $1${NC}"; } +log_warning() { echo -e "${YELLOW}⚠️ $1${NC}"; } +log_error() { echo -e "${RED}❌ $1${NC}"; } + +# ============================================ +# Step 1: Fetch playlist events +# ============================================ + +log "Fetching playlist events (kind 34139) from $RELAY..." + +PLAYLISTS_JSON=$(mktemp) +$NAK_BIN req -k 34139 -a "$PUBKEY" "$RELAY" | $JQ_BIN -s '.' > "$PLAYLISTS_JSON" + +if [ ! -s "$PLAYLISTS_JSON" ]; then + log_error "No playlist data received." + rm -f "$PLAYLISTS_JSON" + exit 1 +fi + +PLAYLIST_COUNT=$(cat "$PLAYLISTS_JSON" | $JQ_BIN 'length') +log_success "Found $PLAYLIST_COUNT playlist(s)" + +# ============================================ +# Step 2: Fetch ALL tracks +# ============================================ + +log "Fetching ALL tracks (kind 36787) from $PUBKEY..." + +ALL_TRACKS_JSON=$(mktemp) +$NAK_BIN req -k 36787 -a "$PUBKEY" "$RELAY" | $JQ_BIN -s '.' > "$ALL_TRACKS_JSON" + +TRACK_COUNT=$(cat "$ALL_TRACKS_JSON" | $JQ_BIN 'length' 2>/dev/null || echo "0") +log_success "Found $TRACK_COUNT total tracks from pubkey" + +# ============================================ +# Step 3: Build playlist data +# ============================================ + +log "Building playlist data structure..." + +OUTPUT_FILE="$DATA_DIR/playlists.json" +FLATTENED_OUTPUT="$DATA_DIR/tracks.json" + +# Create a file with track lookup by d-tag +TRACK_LOOKUP=$(mktemp) +cat "$ALL_TRACKS_JSON" | $JQ_BIN -r ' + .[] | + . as $track | + (.tags[] | select(.[0] == "d") | .[1]) as $d_tag | + "\($d_tag)|\($track | tojson)" +' > "$TRACK_LOOKUP" + +# Build the playlists JSON +echo '{"playlists": []}' > "$OUTPUT_FILE" + +# Process each playlist +cat "$PLAYLISTS_JSON" | $JQ_BIN -c '.[]' | while read -r playlist; do + # Extract playlist metadata + PLAYLIST_ID=$(echo "$playlist" | $JQ_BIN -r '.id') + PLAYLIST_CREATED=$(echo "$playlist" | $JQ_BIN -r '.created_at') + + # FIX: Extract name from "title" tag (not "name") + PLAYLIST_NAME=$(echo "$playlist" | $JQ_BIN -r '.tags[] | select(.[0] == "title") | .[1] // ""') + PLAYLIST_DESC=$(echo "$playlist" | $JQ_BIN -r '.tags[] | select(.[0] == "description") | .[1] // ""') + PLAYLIST_IMAGE=$(echo "$playlist" | $JQ_BIN -r '.tags[] | select(.[0] == "image") | .[1] // ""') + + # Debug: show what was extracted + echo " Playlist: '$PLAYLIST_NAME'" >&2 + + # Get track references from 'a' tags + TRACK_REFS=$(echo "$playlist" | $JQ_BIN -r '.tags[] | select(.[0] == "a") | .[1]') + + # Build tracks array for this playlist + TRACKS_JSON="[]" + + while read -r ref; do + if [ -n "$ref" ]; then + # Extract identifier (the part after the second colon) + IDENTIFIER=$(echo "$ref" | cut -d':' -f3-) + + # Find the track in the lookup + TRACK_DATA=$(grep "^$IDENTIFIER|" "$TRACK_LOOKUP" | head -1 | cut -d'|' -f2-) + + if [ -n "$TRACK_DATA" ] && [ "$TRACK_DATA" != "null" ]; then + # Extract track fields + TRACK_TITLE=$(echo "$TRACK_DATA" | $JQ_BIN -r '.tags[] | select(.[0] == "title") | .[1] // "Untitled"') + TRACK_ARTIST=$(echo "$TRACK_DATA" | $JQ_BIN -r '.tags[] | select(.[0] == "artist") | .[1] // "Unknown Artist"') + TRACK_ALBUM=$(echo "$TRACK_DATA" | $JQ_BIN -r '.tags[] | select(.[0] == "album") | .[1] // ""') + TRACK_URL=$(echo "$TRACK_DATA" | $JQ_BIN -r '.tags[] | select(.[0] == "url") | .[1] // ""') + TRACK_IMAGE=$(echo "$TRACK_DATA" | $JQ_BIN -r '.tags[] | select(.[0] == "image") | .[1] // ""') + TRACK_DURATION=$(echo "$TRACK_DATA" | $JQ_BIN -r '.tags[] | select(.[0] == "duration") | .[1] // ""') + TRACK_ID=$(echo "$TRACK_DATA" | $JQ_BIN -r '.id') + TRACK_CREATED=$(echo "$TRACK_DATA" | $JQ_BIN -r '.created_at') + TRACK_TAGS=$(echo "$TRACK_DATA" | $JQ_BIN -r '[.tags[] | select(.[0] == "t") | .[1]] | join(",")') + + # Build track JSON object + TRACK_JSON=$(cat < "${OUTPUT_FILE}.tmp" + mv "${OUTPUT_FILE}.tmp" "$OUTPUT_FILE" +done + +# ============================================ +# Step 4: Flattened tracks file +# ============================================ + +log "Generating flattened tracks file..." + +cat "$ALL_TRACKS_JSON" | $JQ_BIN ' + .[] | { + id: .id, + created_at: .created_at, + title: (.tags[] | select(.[0] == "title") | .[1] // "Untitled"), + artist: (.tags[] | select(.[0] == "artist") | .[1] // "Unknown Artist"), + album: (.tags[] | select(.[0] == "album") | .[1] // ""), + url: (.tags[] | select(.[0] == "url") | .[1] // ""), + image: (.tags[] | select(.[0] == "image") | .[1] // ""), + duration: (.tags[] | select(.[0] == "duration") | .[1] // ""), + tags: [.tags[] | select(.[0] == "t") | .[1]], + d_tag: (.tags[] | select(.[0] == "d") | .[1] // "") + } +' | $JQ_BIN -s '.' > "$FLATTENED_OUTPUT" + +# ============================================ +# Step 5: Save raw data +# ============================================ + +cp "$PLAYLISTS_JSON" "$DATA_DIR/playlists-raw.json" +cp "$ALL_TRACKS_JSON" "$DATA_DIR/tracks-raw.json" + +# ============================================ +# Step 6: Cleanup +# ============================================ + +rm -f "$PLAYLISTS_JSON" "$ALL_TRACKS_JSON" "$TRACK_LOOKUP" + +# ============================================ +# Step 7: Summary +# ============================================ + +echo "" +echo -e "${BLUE}========================================${NC}" +echo -e "${GREEN}📊 Playlist Summary${NC}" +echo -e "${BLUE}========================================${NC}" +echo -e " 📀 Playlists: $PLAYLIST_COUNT" +echo -e " 🎵 Total tracks found: $TRACK_COUNT" +echo -e " 📁 Data saved to: $DATA_DIR/" +echo -e "${BLUE}========================================${NC}" + +# Show playlist names +echo "" +echo -e "${BLUE}📝 Playlist Names:${NC}" +cat "$OUTPUT_FILE" | $JQ_BIN -r '.playlists[].name' 2>/dev/null | while read -r name; do + if [ -n "$name" ]; then + echo " 🎵 $name" + else + echo " ⚠️ (unnamed)" + fi +done From ec5a6db07fd5116ef040b42b0c1a1618a81e3cad Mon Sep 17 00:00:00 2001 From: sakrecoer Date: Sun, 16 Aug 2026 23:30:52 +0200 Subject: [PATCH 09/10] add defaults to toml --- example.hugo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/example.hugo.toml b/example.hugo.toml index dddcd1c..50039c2 100644 --- a/example.hugo.toml +++ b/example.hugo.toml @@ -16,6 +16,9 @@ disableKinds = ["sitemap", "taxonomy", "term"] footlinks2_url = "https://git.basspistol.org/setto/setto-nostr-web" footlinks3_name = "BPIST" footlinks3_url = "https://basspistol.com/" + footlinks4_name = "Dyne.org" + footlinks4_url = "https://dyne.org" [permalinks] page = "/:contentbasename/" + playlists = "/playlists/:slug/" From ea1321cc0bd758b9168ed884dc61614e08226cb9 Mon Sep 17 00:00:00 2001 From: sakrecoer Date: Sun, 16 Aug 2026 23:33:58 +0200 Subject: [PATCH 10/10] nobody cares about our own settings --- fetch-playlist.sh | 214 ---------------------------------------------- 1 file changed, 214 deletions(-) delete mode 100755 fetch-playlist.sh diff --git a/fetch-playlist.sh b/fetch-playlist.sh deleted file mode 100755 index d71dbed..0000000 --- a/fetch-playlist.sh +++ /dev/null @@ -1,214 +0,0 @@ -#!/bin/bash - -# ============================================ -# Nostr Playlist Fetcher for Hugo -# ============================================ - -# Configuration -PUBKEY="3f11abb2e235da2d4dda5d6deb2f123173476a745a3ca56895a1d0f632a42f40" -RELAY="wss://drops.basspistol.org" -DATA_DIR="data/playlists" -NAK_BIN="nak" -JQ_BIN="jq" - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' - -mkdir -p "$DATA_DIR" - -log() { echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"; } -log_success() { echo -e "${GREEN}✅ $1${NC}"; } -log_warning() { echo -e "${YELLOW}⚠️ $1${NC}"; } -log_error() { echo -e "${RED}❌ $1${NC}"; } - -# ============================================ -# Step 1: Fetch playlist events -# ============================================ - -log "Fetching playlist events (kind 34139) from $RELAY..." - -PLAYLISTS_JSON=$(mktemp) -$NAK_BIN req -k 34139 -a "$PUBKEY" "$RELAY" | $JQ_BIN -s '.' > "$PLAYLISTS_JSON" - -if [ ! -s "$PLAYLISTS_JSON" ]; then - log_error "No playlist data received." - rm -f "$PLAYLISTS_JSON" - exit 1 -fi - -PLAYLIST_COUNT=$(cat "$PLAYLISTS_JSON" | $JQ_BIN 'length') -log_success "Found $PLAYLIST_COUNT playlist(s)" - -# ============================================ -# Step 2: Fetch ALL tracks -# ============================================ - -log "Fetching ALL tracks (kind 36787) from $PUBKEY..." - -ALL_TRACKS_JSON=$(mktemp) -$NAK_BIN req -k 36787 -a "$PUBKEY" "$RELAY" | $JQ_BIN -s '.' > "$ALL_TRACKS_JSON" - -TRACK_COUNT=$(cat "$ALL_TRACKS_JSON" | $JQ_BIN 'length' 2>/dev/null || echo "0") -log_success "Found $TRACK_COUNT total tracks from pubkey" - -# ============================================ -# Step 3: Build playlist data -# ============================================ - -log "Building playlist data structure..." - -OUTPUT_FILE="$DATA_DIR/playlists.json" -FLATTENED_OUTPUT="$DATA_DIR/tracks.json" - -# Create a file with track lookup by d-tag -TRACK_LOOKUP=$(mktemp) -cat "$ALL_TRACKS_JSON" | $JQ_BIN -r ' - .[] | - . as $track | - (.tags[] | select(.[0] == "d") | .[1]) as $d_tag | - "\($d_tag)|\($track | tojson)" -' > "$TRACK_LOOKUP" - -# Build the playlists JSON -echo '{"playlists": []}' > "$OUTPUT_FILE" - -# Process each playlist -cat "$PLAYLISTS_JSON" | $JQ_BIN -c '.[]' | while read -r playlist; do - # Extract playlist metadata - PLAYLIST_ID=$(echo "$playlist" | $JQ_BIN -r '.id') - PLAYLIST_CREATED=$(echo "$playlist" | $JQ_BIN -r '.created_at') - - # FIX: Extract name from "title" tag (not "name") - PLAYLIST_NAME=$(echo "$playlist" | $JQ_BIN -r '.tags[] | select(.[0] == "title") | .[1] // ""') - PLAYLIST_DESC=$(echo "$playlist" | $JQ_BIN -r '.tags[] | select(.[0] == "description") | .[1] // ""') - PLAYLIST_IMAGE=$(echo "$playlist" | $JQ_BIN -r '.tags[] | select(.[0] == "image") | .[1] // ""') - - # Debug: show what was extracted - echo " Playlist: '$PLAYLIST_NAME'" >&2 - - # Get track references from 'a' tags - TRACK_REFS=$(echo "$playlist" | $JQ_BIN -r '.tags[] | select(.[0] == "a") | .[1]') - - # Build tracks array for this playlist - TRACKS_JSON="[]" - - while read -r ref; do - if [ -n "$ref" ]; then - # Extract identifier (the part after the second colon) - IDENTIFIER=$(echo "$ref" | cut -d':' -f3-) - - # Find the track in the lookup - TRACK_DATA=$(grep "^$IDENTIFIER|" "$TRACK_LOOKUP" | head -1 | cut -d'|' -f2-) - - if [ -n "$TRACK_DATA" ] && [ "$TRACK_DATA" != "null" ]; then - # Extract track fields - TRACK_TITLE=$(echo "$TRACK_DATA" | $JQ_BIN -r '.tags[] | select(.[0] == "title") | .[1] // "Untitled"') - TRACK_ARTIST=$(echo "$TRACK_DATA" | $JQ_BIN -r '.tags[] | select(.[0] == "artist") | .[1] // "Unknown Artist"') - TRACK_ALBUM=$(echo "$TRACK_DATA" | $JQ_BIN -r '.tags[] | select(.[0] == "album") | .[1] // ""') - TRACK_URL=$(echo "$TRACK_DATA" | $JQ_BIN -r '.tags[] | select(.[0] == "url") | .[1] // ""') - TRACK_IMAGE=$(echo "$TRACK_DATA" | $JQ_BIN -r '.tags[] | select(.[0] == "image") | .[1] // ""') - TRACK_DURATION=$(echo "$TRACK_DATA" | $JQ_BIN -r '.tags[] | select(.[0] == "duration") | .[1] // ""') - TRACK_ID=$(echo "$TRACK_DATA" | $JQ_BIN -r '.id') - TRACK_CREATED=$(echo "$TRACK_DATA" | $JQ_BIN -r '.created_at') - TRACK_TAGS=$(echo "$TRACK_DATA" | $JQ_BIN -r '[.tags[] | select(.[0] == "t") | .[1]] | join(",")') - - # Build track JSON object - TRACK_JSON=$(cat < "${OUTPUT_FILE}.tmp" - mv "${OUTPUT_FILE}.tmp" "$OUTPUT_FILE" -done - -# ============================================ -# Step 4: Flattened tracks file -# ============================================ - -log "Generating flattened tracks file..." - -cat "$ALL_TRACKS_JSON" | $JQ_BIN ' - .[] | { - id: .id, - created_at: .created_at, - title: (.tags[] | select(.[0] == "title") | .[1] // "Untitled"), - artist: (.tags[] | select(.[0] == "artist") | .[1] // "Unknown Artist"), - album: (.tags[] | select(.[0] == "album") | .[1] // ""), - url: (.tags[] | select(.[0] == "url") | .[1] // ""), - image: (.tags[] | select(.[0] == "image") | .[1] // ""), - duration: (.tags[] | select(.[0] == "duration") | .[1] // ""), - tags: [.tags[] | select(.[0] == "t") | .[1]], - d_tag: (.tags[] | select(.[0] == "d") | .[1] // "") - } -' | $JQ_BIN -s '.' > "$FLATTENED_OUTPUT" - -# ============================================ -# Step 5: Save raw data -# ============================================ - -cp "$PLAYLISTS_JSON" "$DATA_DIR/playlists-raw.json" -cp "$ALL_TRACKS_JSON" "$DATA_DIR/tracks-raw.json" - -# ============================================ -# Step 6: Cleanup -# ============================================ - -rm -f "$PLAYLISTS_JSON" "$ALL_TRACKS_JSON" "$TRACK_LOOKUP" - -# ============================================ -# Step 7: Summary -# ============================================ - -echo "" -echo -e "${BLUE}========================================${NC}" -echo -e "${GREEN}📊 Playlist Summary${NC}" -echo -e "${BLUE}========================================${NC}" -echo -e " 📀 Playlists: $PLAYLIST_COUNT" -echo -e " 🎵 Total tracks found: $TRACK_COUNT" -echo -e " 📁 Data saved to: $DATA_DIR/" -echo -e "${BLUE}========================================${NC}" - -# Show playlist names -echo "" -echo -e "${BLUE}📝 Playlist Names:${NC}" -cat "$OUTPUT_FILE" | $JQ_BIN -r '.playlists[].name' 2>/dev/null | while read -r name; do - if [ -n "$name" ]; then - echo " 🎵 $name" - else - echo " ⚠️ (unnamed)" - fi -done \ No newline at end of file