From fdb107f0998c72e864a6b960aa60a5479c83d021 Mon Sep 17 00:00:00 2001 From: sakrecoer Date: Wed, 16 Sep 2026 15:54:32 +0200 Subject: [PATCH] make playlist more better --- themes/one-pager/assets/css/style.css | 104 +++ themes/one-pager/assets/js/scripts.js | 882 +++++++----------- themes/one-pager/layouts/_default/baseof.html | 27 +- themes/one-pager/layouts/_default/index.html | 63 -- .../layouts/partials/music-lightbox.html | 42 + .../one-pager/layouts/partials/nav-strip.html | 30 + .../layouts/partials/photo-lightbox.html | 14 + themes/one-pager/layouts/playlists/list.html | 95 +- .../one-pager/layouts/playlists/single.html | 266 +++--- 9 files changed, 672 insertions(+), 851 deletions(-) create mode 100644 themes/one-pager/layouts/partials/music-lightbox.html create mode 100644 themes/one-pager/layouts/partials/nav-strip.html create mode 100644 themes/one-pager/layouts/partials/photo-lightbox.html diff --git a/themes/one-pager/assets/css/style.css b/themes/one-pager/assets/css/style.css index ad243d7..fda17de 100644 --- a/themes/one-pager/assets/css/style.css +++ b/themes/one-pager/assets/css/style.css @@ -2724,3 +2724,107 @@ button { font-size: 0.85rem; } } +/* ============================================ + * NAV STRIP (sub-page header) + * ============================================ */ + +.nav-strip { + position: sticky; + top: 0; + z-index: 998; + background: rgba(0, 0, 0, 0.55); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border-bottom: 3px solid var(--accent-yellow); + padding: 0.6rem 2rem; +} + +.nav-strip-inner { + max-width: 1200px; + margin: 0 auto; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1.5rem; + flex-wrap: wrap; +} + +.nav-strip-brand { + display: flex; + align-items: center; + gap: 0.8rem; + text-decoration: none; + color: var(--text-primary); + transition: all 0.3s ease; +} + +.nav-strip-brand:hover { + color: var(--accent-yellow); +} + +.nav-strip-avatar { + width: 40px; + height: 40px; + border-radius: 50%; + object-fit: cover; + border: 2px solid var(--accent-yellow); + filter: grayscale(100%); + transition: filter 0.3s ease; +} + +.nav-strip-brand:hover .nav-strip-avatar { + filter: grayscale(0%); +} + +.nav-strip-name { + font-family: 'Poppins', sans-serif; + font-weight: 700; + font-size: 0.9rem; + letter-spacing: -0.5px; + text-transform: uppercase; +} + +.nav-strip-links { + display: flex; + gap: 0.6rem; + flex-wrap: wrap; +} + +.nav-strip-links a { + color: var(--text-primary); + text-decoration: none; + font-family: 'Poppins', sans-serif; + font-weight: 600; + font-size: 0.7rem; + letter-spacing: 2px; + text-transform: uppercase; + padding: 0.4rem 0.9rem; + border: 2px solid transparent; + border-radius: 6px; + transition: all 0.3s ease; + background: rgba(0, 0, 0, 0.3); +} + +.nav-strip-links a:hover { + color: var(--accent-yellow); + border-color: var(--accent-yellow); + transform: scale(1.05); +} + +/* Responsive */ +@media (max-width: 640px) { + .nav-strip { + padding: 0.5rem 1rem; + } + .nav-strip-inner { + gap: 0.6rem; + } + .nav-strip-name { + display: none; + } + .nav-strip-links a { + font-size: 0.6rem; + padding: 0.3rem 0.6rem; + letter-spacing: 1px; + } +} diff --git a/themes/one-pager/assets/js/scripts.js b/themes/one-pager/assets/js/scripts.js index 4c36a1c..90960cc 100644 --- a/themes/one-pager/assets/js/scripts.js +++ b/themes/one-pager/assets/js/scripts.js @@ -2,7 +2,7 @@ // 1. UTILITY FUNCTIONS // ============================================ -// Function to shuffle array (Fisher-Yates) +// Fisher-Yates shuffle function shuffleArray(array) { const shuffled = [...array]; for (let i = shuffled.length - 1; i > 0; i--) { @@ -12,27 +12,48 @@ function shuffleArray(array) { return shuffled; } -// Artist filter list +// Artist whitelist — only tracks by these artists are shown const allowedArtists = [ '徒 Setto セット', 'Settoshi Tonami', 'Sethy Bowoy', 'reSet Sakrecoer', - 'Sakrecoer' + 'Sakrecoer', 'Vigesimal' ]; -// Function to check if a track is by an allowed artist function isAllowedArtist(artist) { if (!artist) return false; const normalizedArtist = artist.trim().toLowerCase(); return allowedArtists.some(allowed => { const normalizedAllowed = allowed.trim().toLowerCase(); return normalizedArtist.includes(normalizedAllowed) || - normalizedAllowed.includes(normalizedArtist); + normalizedAllowed.includes(normalizedArtist); }); } +// Find a track's raw Nostr event by its audio URL — used to look up `content` (lyrics/info) +// Checks homepage data first, then falls back to playlist-page data. +function findRawTrack(url) { + if (!url) return null; + + const fromHome = (window.allMusicData || []).find(t => { + if (!t.tags) return false; + return t.tags.some(tag => tag[0] === 'url' && tag[1] === url); + }); + if (fromHome) return fromHome; + + const fromPlaylist = (window.playlistOriginalTracks || []).find(t => t.url === url); + return fromPlaylist || null; +} + +function formatTime(seconds) { + if (!seconds || isNaN(seconds)) return '0:00'; + const mins = Math.floor(seconds / 60); + const secs = Math.floor(seconds % 60); + return `${mins}:${secs.toString().padStart(2, '0')}`; +} + // ============================================ // 2. PHOTO LIGHTBOX // ============================================ @@ -65,7 +86,6 @@ function openPhotoLightbox(index) { }); if (dateStr) metaText += `📅 ${dateStr}`; - lightboxMeta.textContent = metaText || '📅 No date available'; lightboxCounter.textContent = `${currentPhotoIndex + 1} / ${allPicturesArray.length}`; @@ -92,13 +112,12 @@ function nextPhoto() { function initPhotoLightbox() { const lightbox = document.getElementById('customLightbox'); + if (!lightbox) return; + const lightboxClose = document.getElementById('lightboxClose'); const lightboxPrev = document.getElementById('lightboxPrev'); const lightboxNext = document.getElementById('lightboxNext'); - if (!lightbox) return; - - // Remove old listeners by cloning const newClose = lightboxClose.cloneNode(true); const newPrev = lightboxPrev.cloneNode(true); const newNext = lightboxNext.cloneNode(true); @@ -107,29 +126,23 @@ function initPhotoLightbox() { lightboxPrev.parentNode.replaceChild(newPrev, lightboxPrev); lightboxNext.parentNode.replaceChild(newNext, lightboxNext); - // Add new listeners newClose.addEventListener('click', closePhotoLightbox); newPrev.addEventListener('click', prevPhoto); newNext.addEventListener('click', nextPhoto); - // Click outside to close lightbox.removeEventListener('click', handlePhotoClick); lightbox.addEventListener('click', handlePhotoClick); - // Keyboard controls document.removeEventListener('keydown', handlePhotoKeydown); document.addEventListener('keydown', handlePhotoKeydown); - // Setup photo click listeners document.querySelectorAll('.photo-item img').forEach((img) => { img.removeEventListener('click', img._photoClick); img._photoClick = function(e) { e.preventDefault(); const url = this.src; const fullIndex = allPicturesArray.findIndex(p => p.url === url); - if (fullIndex !== -1) { - openPhotoLightbox(fullIndex); - } + if (fullIndex !== -1) openPhotoLightbox(fullIndex); }; img.addEventListener('click', img._photoClick); }); @@ -139,16 +152,9 @@ function handlePhotoKeydown(e) { const lightbox = document.getElementById('customLightbox'); if (!lightbox || !lightbox.classList.contains('active')) return; - if (e.key === 'Escape') { - e.preventDefault(); - closePhotoLightbox(); - } else if (e.key === 'ArrowLeft') { - e.preventDefault(); - prevPhoto(); - } else if (e.key === 'ArrowRight') { - e.preventDefault(); - nextPhoto(); - } + if (e.key === 'Escape') { e.preventDefault(); closePhotoLightbox(); } + else if (e.key === 'ArrowLeft') { e.preventDefault(); prevPhoto(); } + else if (e.key === 'ArrowRight') { e.preventDefault(); nextPhoto(); } } function handlePhotoClick(e) { @@ -157,7 +163,7 @@ function handlePhotoClick(e) { } // ============================================ -// 3. MUSIC RENDERER +// 3. MUSIC RENDERER (homepage cards) // ============================================ function renderMusicTracks(tracks, containerId, randomize = false) { @@ -167,9 +173,7 @@ function renderMusicTracks(tracks, containerId, randomize = false) { const filteredTracks = tracks.filter(track => { let artist = ''; if (track.tags) { - track.tags.forEach(tag => { - if (tag[0] === 'artist') artist = tag[1]; - }); + track.tags.forEach(tag => { if (tag[0] === 'artist') artist = tag[1]; }); } return isAllowedArtist(artist); }); @@ -179,11 +183,10 @@ function renderMusicTracks(tracks, containerId, randomize = false) { return; } - // Build playlist from ALL filtered tracks + // Build global playlist from all filtered tracks window.musicPlaylist = []; filteredTracks.forEach(track => { let title = '', url = '', image = '', artist = '', album = '', duration = ''; - // ADD THIS: Store the track ID const trackId = track.id || ''; if (track.tags) { @@ -200,9 +203,9 @@ function renderMusicTracks(tracks, containerId, randomize = false) { } if (url) { window.musicPlaylist.push({ - id: trackId, // ADD THIS LINE + id: trackId, title: title || 'Untitled', - url: url, + url, image: image || '', artist: artist || 'Unknown Artist', album: album || '', @@ -212,16 +215,14 @@ function renderMusicTracks(tracks, containerId, randomize = false) { } }); - // Randomize or sort the playlist if (randomize) { window.musicPlaylist = shuffleArray(window.musicPlaylist); } else { window.musicPlaylist.sort((a, b) => (b.created_at || 0) - (a.created_at || 0)); } - // Render only 6 cards + // Only render the first 6 cards const displayTracks = window.musicPlaylist.slice(0, 6); - container.innerHTML = ''; displayTracks.forEach((track, index) => { @@ -229,72 +230,50 @@ function renderMusicTracks(tracks, containerId, randomize = false) { card.className = 'music-card clickable'; card.dataset.playlistIndex = index; - // Click on card opens lightbox (except when clicking on info button) card.addEventListener('click', function(e) { - // Don't trigger if clicking on lyrics-toggle or inside lyrics - if (e.target.closest('.lyrics-toggle') || e.target.closest('.lyrics')) { - return; - } + if (e.target.closest('.lyrics-toggle') || e.target.closest('.lyrics')) return; const idx = parseInt(this.dataset.playlistIndex); - if (!isNaN(idx) && idx >= 0) { - openMusicLightbox(idx); - } + if (!isNaN(idx) && idx >= 0) openMusicLightbox(idx); }); - // Find genre tags from the original track data + // Collect genre tags + info from the raw event let genreTags = []; let content = ''; - let alt = ''; - - // Find the original track data to get tags and content const originalTrack = filteredTracks.find(t => { - let url = ''; - if (t.tags) { - t.tags.forEach(tag => { - if (tag[0] === 'url') url = tag[1]; - }); - } - return url === track.url; + if (!t.tags) return false; + return t.tags.some(tag => tag[0] === 'url' && tag[1] === track.url); }); if (originalTrack && originalTrack.tags) { originalTrack.tags.forEach(tag => { - const key = tag[0]; - const value = tag[1]; - if (key === 't') genreTags.push(value); - if (key === 'alt') alt = value; + if (tag[0] === 't') genreTags.push(tag[1]); }); - content = originalTrack.content || ''; + content = originalTrack.content || ''; } - // Build the card HTML - let cardHTML = ` -
-
- ${track.image ? `${track.title}` : ''} -
- ▶ -
-
-
-

${track.title}

- ${track.artist ? `

${track.artist}

` : ''} - ${track.album ? `

${track.album}

` : ''} -
- ${genreTags.length > 0 ? ` -
- ${genreTags.map(t => `${t}`).join('')} -
- ` : ''} - ${content ? ` - - - ` : ''} + card.innerHTML = ` +
+
+ ${track.image ? `${track.title}` : ''} +
▶
- `; - - card.innerHTML = cardHTML; - container.appendChild(card); +
+

${track.title}

+ ${track.artist ? `

${track.artist}

` : ''} + ${track.album ? `

${track.album}

` : ''} +
+ ${genreTags.length > 0 ? ` +
+ ${genreTags.map(t => `${t}`).join('')} +
+ ` : ''} + ${content ? ` + + + ` : ''} +
+ `; + container.appendChild(card); }); initMusicEventListeners(); @@ -308,18 +287,12 @@ function initMusicEventListeners() { } function handleLyricsToggle(e) { - // Stop the click from bubbling up to the card e.stopPropagation(); - const lyrics = this.nextElementSibling; if (lyrics && lyrics.classList.contains('lyrics')) { - if (lyrics.style.display === 'none') { - lyrics.style.display = 'block'; - this.innerHTML = 'Hide Info'; - } else { - lyrics.style.display = 'none'; - this.innerHTML = 'Show Info'; - } + const hidden = lyrics.style.display === 'none'; + lyrics.style.display = hidden ? 'block' : 'none'; + this.innerHTML = `${hidden ? 'Hide Info' : 'Show Info'}`; } } @@ -331,21 +304,14 @@ function renderRandomPictures(picturesData, containerId, randomize = false) { const container = document.getElementById(containerId); if (!container) return; - // First, flatten all pictures from all events but keep track of which event they belong to let allPictures = []; let allEvents = []; picturesData.forEach(event => { - const imetaTags = []; - event.tags.forEach(tag => { - if (tag[0] === 'imeta') { - imetaTags.push(tag); - } - }); + const imetaTags = event.tags.filter(tag => tag[0] === 'imeta'); if (imetaTags.length > 0) { let firstImage = null; - let eventImages = []; imetaTags.forEach(imetaTag => { let imageUrl = ''; @@ -353,28 +319,20 @@ function renderRandomPictures(picturesData, containerId, randomize = false) { imetaTag.forEach(item => { if (typeof item === 'string') { - if (item.startsWith('url ')) { - imageUrl = item.replace('url ', '').trim(); - } - if (item.startsWith('alt ')) { - alt = item.replace('alt ', '').trim(); - } + if (item.startsWith('url ')) imageUrl = item.replace('url ', '').trim(); + if (item.startsWith('alt ')) alt = item.replace('alt ', '').trim(); } }); if (imageUrl) { const pictureData = { - url: imageUrl, - alt: alt, + url: imageUrl, alt, created_at: event.created_at, pubkey: event.pubkey, event_id: event.id }; - eventImages.push(pictureData); allPictures.push(pictureData); - if (!firstImage) { - firstImage = pictureData; - } + if (!firstImage) firstImage = pictureData; } }); @@ -382,13 +340,11 @@ function renderRandomPictures(picturesData, containerId, randomize = false) { allEvents.push({ event_id: event.id, created_at: event.created_at, - firstImage: firstImage + firstImage }); } } else { - let imageUrl = ''; - let alt = event.content || ''; - + let imageUrl = '', alt = event.content || ''; event.tags.forEach(tag => { if (tag[0] === 'imeta') { const imetaStr = tag[1]; @@ -398,11 +354,9 @@ function renderRandomPictures(picturesData, containerId, randomize = false) { if (altMatch) alt = altMatch[1]; } }); - if (imageUrl) { const pictureData = { - url: imageUrl, - alt: alt, + url: imageUrl, alt, created_at: event.created_at, pubkey: event.pubkey, event_id: event.id @@ -417,24 +371,16 @@ function renderRandomPictures(picturesData, containerId, randomize = false) { } }); - // Store ALL pictures in the global array for the lightbox allPicturesArray = allPictures; - // Sort events by created_at (newest first) const sortedEvents = [...allEvents].sort((a, b) => b.created_at - a.created_at); - - // Select which events to display (6 max) - let displayEvents; - if (randomize) { - const shuffled = shuffleArray([...sortedEvents]); - displayEvents = shuffled.slice(0, 6); - } else { - displayEvents = sortedEvents.slice(0, 6); - } + const displayEvents = randomize + ? shuffleArray([...sortedEvents]).slice(0, 6) + : sortedEvents.slice(0, 6); container.innerHTML = ''; - displayEvents.forEach((event, index) => { + displayEvents.forEach(event => { const picture = event.firstImage; if (!picture) return; @@ -442,8 +388,6 @@ function renderRandomPictures(picturesData, containerId, randomize = false) { item.className = 'photo-item'; item.dataset.image = picture.url; item.dataset.alt = picture.alt; - item.dataset.index = index; - item.dataset.eventId = event.event_id; const date = new Date(picture.created_at * 1000); const dateStr = date.toLocaleDateString('en-US', { @@ -453,14 +397,12 @@ function renderRandomPictures(picturesData, containerId, randomize = false) { item.dataset.pubkey = picture.pubkey || ''; item.innerHTML = ` - ${picture.url ? `${picture.alt}` : ''} -
📅 ${dateStr}
+ ${picture.url ? `${picture.alt}` : ''} +
📅 ${dateStr}
`; - container.appendChild(item); }); - // Initialize photo lightbox initPhotoLightbox(); } @@ -472,14 +414,9 @@ function renderRandomVideos(videosData, containerId, randomize = false) { const container = document.getElementById(containerId); if (!container) return; - let selected; - if (randomize) { - const shuffled = shuffleArray(videosData); - selected = shuffled.slice(0, 2); - } else { - const sorted = [...videosData].sort((a, b) => b.created_at - a.created_at); - selected = sorted.slice(0, 2); - } + const selected = randomize + ? shuffleArray(videosData).slice(0, 2) + : [...videosData].sort((a, b) => b.created_at - a.created_at).slice(0, 2); container.innerHTML = ''; const posterImage = '/images/videoposter.png'; @@ -514,37 +451,246 @@ function renderRandomVideos(videosData, containerId, randomize = false) { }); const wrapperStyle = isVertical - ? 'style="position:relative;padding-bottom:177.78%;height:0;overflow:hidden;background:var(--accent-black);"' - : ''; + ? 'style="position:relative;padding-bottom:177.78%;height:0;overflow:hidden;background:var(--accent-black);"' + : ''; const videoStyle = isVertical - ? 'style="position:absolute;top:0;left:0;width:100%;height:100%;object-fit:cover;"' - : 'style="width:100%;"'; + ? 'style="position:absolute;top:0;left:0;width:100%;height:100%;object-fit:cover;"' + : 'style="width:100%;"'; - const html = ` -
- ${videoUrl ? ` -
- + container.insertAdjacentHTML('beforeend', ` +
+ ${videoUrl ? ` +
+ +
+ ` : ''} +
+

${alt}

+
+ 📅 ${dateStr} + ${dim ? `📐 ${dim}` : ''} +
+
- ` : ''} -
-

${alt}

-
- 📅 ${dateStr} - ${dim ? `📐 ${dim}` : ''} -
-
-
- `; - container.insertAdjacentHTML('beforeend', html); + `); }); } // ============================================ -// 6. REFRESH FUNCTIONS +// 6. MUSIC LIGHTBOX +// ============================================ + +let currentMusicIndex = 0; +let musicPlayer = null; + +function openMusicLightbox(index) { + if (!window.musicPlaylist || window.musicPlaylist.length === 0) return; + + if (musicPlayer) { + musicPlayer.pause(); + musicPlayer.remove(); + musicPlayer = null; + } + + if (index < 0) index = window.musicPlaylist.length - 1; + if (index >= window.musicPlaylist.length) index = 0; + currentMusicIndex = index; + + const track = window.musicPlaylist[currentMusicIndex]; + if (!track) return; + + const lightbox = document.getElementById('musicLightbox'); + const bg = document.getElementById('musicLightboxBg'); + const img = document.getElementById('musicLightboxImage'); + const title = document.getElementById('musicLightboxTitle'); + const artist = document.getElementById('musicLightboxArtist'); + const album = document.getElementById('musicLightboxAlbum'); + const durationDisplay = document.getElementById('musicLightboxDuration'); + const currentTimeEl = document.getElementById('musicLightboxCurrentTime'); + const progressFill = document.getElementById('musicLightboxProgressFill'); + const playIcon2 = document.getElementById('musicPlayIcon2'); + const lyricsDisplay = document.getElementById('musicLightboxLyrics'); + const shareBtn = document.getElementById('musicShareBtn'); + + // --- Share button --- + let feedbackEl = document.getElementById('shareFeedbackPopout'); + if (!feedbackEl) { + feedbackEl = document.createElement('div'); + feedbackEl.id = 'shareFeedbackPopout'; + feedbackEl.className = 'share-feedback'; + feedbackEl.innerHTML = '✓ Copied!'; + document.body.appendChild(feedbackEl); + } + if (shareBtn) { + shareBtn.onclick = function() { + if (!track.id) { + feedbackEl.textContent = '⚠️ No ID'; + feedbackEl.classList.add('visible'); + setTimeout(() => feedbackEl.classList.remove('visible'), 2000); + return; + } + const shareUrl = `https://nostr.basspistol.org/notes/${track.id}`; + + if (navigator.share) { + navigator.share({ + title: track.title || 'Music Track', + text: `Check out "${track.title}" by ${track.artist || 'Unknown Artist'}`, + url: shareUrl + }).catch(err => console.log('Share cancelled or failed:', err)); + } else { + const showCopied = () => { + feedbackEl.innerHTML = '✓ Copied!'; + feedbackEl.classList.add('visible'); + setTimeout(() => feedbackEl.classList.remove('visible'), 2000); + }; + if (navigator.clipboard) { + navigator.clipboard.writeText(shareUrl).then(showCopied).catch(() => { + const ta = document.createElement('textarea'); + ta.value = shareUrl; + document.body.appendChild(ta); + ta.select(); + try { document.execCommand('copy'); showCopied(); } catch(e) {} + document.body.removeChild(ta); + }); + } + } + }; + } + + // --- Album art + info --- + const artUrl = track.image || '/images/default-album.jpg'; + img.src = artUrl; + bg.style.backgroundImage = `url(${artUrl})`; + title.textContent = track.title || 'Untitled'; + artist.textContent = track.artist || 'Unknown Artist'; + album.textContent = track.album || ''; + + // --- Lyrics lookup (works on homepage & playlist pages) --- + let lyricsContent = track.content || ''; + if (!lyricsContent) { + const raw = findRawTrack(track.url); + if (raw && raw.content) lyricsContent = raw.content; + } + if (lyricsContent) { + lyricsDisplay.innerHTML = lyricsContent.replace(/\n/g, '
'); + lyricsDisplay.style.display = 'block'; + } else { + lyricsDisplay.style.display = 'none'; + } + + // --- Progress --- + progressFill.style.width = '0%'; + currentTimeEl.textContent = '0:00'; + durationDisplay.textContent = track.duration ? formatTime(track.duration) : '0:00'; + playIcon2.textContent = '▶'; + + // --- Audio player --- + musicPlayer = new Audio(track.url); + musicPlayer.preload = 'metadata'; + + musicPlayer.addEventListener('loadedmetadata', function() { + durationDisplay.textContent = formatTime(this.duration); + }); + musicPlayer.addEventListener('ended', nextMusicTrack); + musicPlayer.addEventListener('timeupdate', function() { + const percent = (this.currentTime / this.duration) * 100; + progressFill.style.width = `${percent}%`; + currentTimeEl.textContent = formatTime(this.currentTime); + }); + + lightbox.classList.add('active'); + document.body.style.overflow = 'hidden'; + playMusic(); +} + +function playMusic() { + if (!musicPlayer) return; + musicPlayer.play() + .then(() => { document.getElementById('musicPlayIcon2').textContent = '⏸️'; }) + .catch(e => console.log('Playback prevented:', e)); +} + +function pauseMusic() { + if (!musicPlayer) return; + musicPlayer.pause(); + document.getElementById('musicPlayIcon2').textContent = '▶️'; +} + +function toggleMusic() { + if (!musicPlayer) return; + musicPlayer.paused ? playMusic() : pauseMusic(); +} + +function nextMusicTrack() { openMusicLightbox(currentMusicIndex + 1); } +function prevMusicTrack() { openMusicLightbox(currentMusicIndex - 1); } + +function closeMusicLightbox() { + const lightbox = document.getElementById('musicLightbox'); + if (!lightbox) return; + if (musicPlayer) { + musicPlayer.pause(); + musicPlayer.remove(); + musicPlayer = null; + } + lightbox.classList.remove('active'); + document.body.style.overflow = ''; +} + +function initMusicLightbox() { + const lightbox = document.getElementById('musicLightbox'); + if (!lightbox) return; + + const closeBtn = document.getElementById('musicLightboxClose'); + const playBtn2 = document.getElementById('musicLightboxPlayBtn2'); + const prevBtn = document.getElementById('musicLightboxPrev'); + const nextBtn = document.getElementById('musicLightboxNext'); + const progressBar = document.getElementById('musicLightboxProgressBar'); + + const newClose = closeBtn.cloneNode(true); + const newPlayBtn2 = playBtn2.cloneNode(true); + const newPrev = prevBtn.cloneNode(true); + const newNext = nextBtn.cloneNode(true); + const newProgress = progressBar.cloneNode(true); + + closeBtn.parentNode.replaceChild(newClose, closeBtn); + playBtn2.parentNode.replaceChild(newPlayBtn2, playBtn2); + prevBtn.parentNode.replaceChild(newPrev, prevBtn); + nextBtn.parentNode.replaceChild(newNext, nextBtn); + progressBar.parentNode.replaceChild(newProgress, progressBar); + + newClose.addEventListener('click', closeMusicLightbox); + + lightbox.addEventListener('click', function(e) { + if (e.target === lightbox) closeMusicLightbox(); + }); + + newPlayBtn2.addEventListener('click', function(e) { e.stopPropagation(); toggleMusic(); }); + newPrev.addEventListener('click', function(e) { e.stopPropagation(); prevMusicTrack(); }); + newNext.addEventListener('click', function(e) { e.stopPropagation(); nextMusicTrack(); }); + + newProgress.addEventListener('click', function(e) { + if (!musicPlayer) return; + const rect = this.getBoundingClientRect(); + const percent = (e.clientX - rect.left) / rect.width; + musicPlayer.currentTime = percent * musicPlayer.duration; + }); + + document.addEventListener('keydown', function(e) { + const lb = document.getElementById('musicLightbox'); + if (!lb || !lb.classList.contains('active')) return; + + if (e.key === 'Escape') { e.preventDefault(); closeMusicLightbox(); } + else if (e.key === ' ' || e.key === 'Space') { e.preventDefault(); toggleMusic(); } + else if (e.key === 'ArrowLeft') { e.preventDefault(); prevMusicTrack(); } + else if (e.key === 'ArrowRight') { e.preventDefault(); nextMusicTrack(); } + }); +} + +// ============================================ +// 7. REFRESH / RANDOMIZE HELPERS // ============================================ function refreshMusic() { @@ -563,366 +709,38 @@ function refreshVideos() { } // ============================================ -// 7. MUSIC LIGHTBOX -// ============================================ - -let currentMusicIndex = 0; -let musicPlayer = null; - -// Open music lightbox -function openMusicLightbox(index) { - - - if (!window.musicPlaylist || window.musicPlaylist.length === 0) return; - - // Stop any existing playback - if (musicPlayer) { - musicPlayer.pause(); - musicPlayer.remove(); - musicPlayer = null; - } - - if (index < 0) index = window.musicPlaylist.length - 1; - if (index >= window.musicPlaylist.length) index = 0; - currentMusicIndex = index; - - const track = window.musicPlaylist[currentMusicIndex]; - if (!track) return; - - // Get elements - const lightbox = document.getElementById('musicLightbox'); - const bg = document.getElementById('musicLightboxBg'); - const img = document.getElementById('musicLightboxImage'); - const title = document.getElementById('musicLightboxTitle'); - const artist = document.getElementById('musicLightboxArtist'); - const album = document.getElementById('musicLightboxAlbum'); - const durationDisplay = document.getElementById('musicLightboxDuration'); - const currentTimeDisplay = document.getElementById('musicLightboxCurrentTime'); - const progressFill = document.getElementById('musicLightboxProgressFill'); - const playBtn2 = document.getElementById('musicLightboxPlayBtn2'); - const playIcon2 = document.getElementById('musicPlayIcon2'); - // Get the lyrics element - const lyricsDisplay = document.getElementById('musicLightboxLyrics'); - - // Share button - in its own row - const shareBtn = document.getElementById('musicShareBtn'); - - // Create feedback element if it doesn't exist - let feedbackEl = document.getElementById('shareFeedbackPopout'); - if (!feedbackEl) { - feedbackEl = document.createElement('div'); - feedbackEl.id = 'shareFeedbackPopout'; - feedbackEl.className = 'share-feedback'; - feedbackEl.innerHTML = '✓ Copied!'; - document.body.appendChild(feedbackEl); - } - - if (shareBtn) { - shareBtn.onclick = function() { - if (!track.id) { - feedbackEl.textContent = '⚠️ No ID'; - feedbackEl.classList.add('visible'); - setTimeout(() => { - feedbackEl.classList.remove('visible'); - }, 2000); - return; - } - - const shareUrl = `https://nostr.basspistol.org/notes/${track.id}`; - - if (navigator.share) { - navigator.share({ - title: track.title || 'Music Track', - text: `Check out "${track.title}" by ${track.artist || 'Unknown Artist'}`, - url: shareUrl - }).catch(err => { - console.log('Share cancelled or failed:', err); - }); - } else { - navigator.clipboard.writeText(shareUrl).then(() => { - feedbackEl.innerHTML = '✓ Copied!'; - feedbackEl.classList.add('visible'); - setTimeout(() => { - feedbackEl.classList.remove('visible'); - }, 2000); - }).catch(err => { - console.error('Could not copy text: ', err); - const textArea = document.createElement('textarea'); - textArea.value = shareUrl; - document.body.appendChild(textArea); - textArea.select(); - try { - document.execCommand('copy'); - feedbackEl.innerHTML = '✓ Copied!'; - feedbackEl.classList.add('visible'); - setTimeout(() => { - feedbackEl.classList.remove('visible'); - }, 2000); - } catch (err) { - console.error('Fallback copy failed:', err); - feedbackEl.textContent = '⚠️ Copy failed'; - feedbackEl.classList.add('visible'); - setTimeout(() => { - feedbackEl.classList.remove('visible'); - }, 2000); - } - document.body.removeChild(textArea); - }); - } - }; - } - - // Set album art - const artUrl = track.image || '/images/default-album.jpg'; - img.src = artUrl; - bg.style.backgroundImage = `url(${artUrl})`; - - // Set info - title.textContent = track.title || 'Untitled'; - artist.textContent = track.artist || 'Unknown Artist'; - album.textContent = track.album || ''; - - // Set lyrics - find the original track data to get content - let lyricsContent = ''; - // Find the original track data - const originalTrack = window.allMusicData ? window.allMusicData.find(t => { - let url = ''; - if (t.tags) { - t.tags.forEach(tag => { - if (tag[0] === 'url') url = tag[1]; - }); - } - return url === track.url; - }) : null; - - if (originalTrack && originalTrack.content) { - lyricsContent = originalTrack.content; - } - - // Display lyrics if they exist - if (lyricsContent) { - lyricsDisplay.innerHTML = lyricsContent.replace(/\n/g, '
'); - lyricsDisplay.style.display = 'block'; - } else { - lyricsDisplay.style.display = 'none'; - } - - // Reset progress - progressFill.style.width = '0%'; - currentTimeDisplay.textContent = '0:00'; - durationDisplay.textContent = track.duration ? formatTime(track.duration) : '0:00'; - - // Set play buttons to play state - playIcon2.textContent = '▶'; - - // Create audio player - musicPlayer = new Audio(track.url); - musicPlayer.preload = 'metadata'; - - // When metadata loaded, update duration - musicPlayer.addEventListener('loadedmetadata', function() { - durationDisplay.textContent = formatTime(this.duration); - }); - - // Track ended - go to next - musicPlayer.addEventListener('ended', function() { - nextMusicTrack(); - }); - - // Update progress - musicPlayer.addEventListener('timeupdate', function() { - const percent = (this.currentTime / this.duration) * 100; - progressFill.style.width = `${percent}%`; - currentTimeDisplay.textContent = formatTime(this.currentTime); - }); - - // Show lightbox - lightbox.classList.add('active'); - document.body.style.overflow = 'hidden'; - - // Auto-play - playMusic(); -} - -// Play music -function playMusic() { - if (!musicPlayer) return; - - musicPlayer.play() - .then(() => { - document.getElementById('musicPlayIcon2').textContent = '⏸️'; - }) - .catch(e => { - console.log('Playback prevented:', e); - }); -} - -// Pause music -function pauseMusic() { - if (!musicPlayer) return; - musicPlayer.pause(); - document.getElementById('musicPlayIcon2').textContent = '▶️'; -} - -// Toggle play/pause -function toggleMusic() { - if (!musicPlayer) return; - if (musicPlayer.paused) { - playMusic(); - } else { - pauseMusic(); - } -} - -// Next track -function nextMusicTrack() { - openMusicLightbox(currentMusicIndex + 1); -} - -// Previous track -function prevMusicTrack() { - openMusicLightbox(currentMusicIndex - 1); -} - -// Close music lightbox -function closeMusicLightbox() { - const lightbox = document.getElementById('musicLightbox'); - if (!lightbox) return; - - if (musicPlayer) { - musicPlayer.pause(); - musicPlayer.remove(); - musicPlayer = null; - } - - lightbox.classList.remove('active'); - document.body.style.overflow = ''; -} - -// Format time (seconds to MM:SS) -function formatTime(seconds) { - if (!seconds || isNaN(seconds)) return '0:00'; - const mins = Math.floor(seconds / 60); - const secs = Math.floor(seconds % 60); - return `${mins}:${secs.toString().padStart(2, '0')}`; -} - -// Initialize music lightbox event listeners -function initMusicLightbox() { - const lightbox = document.getElementById('musicLightbox'); - const closeBtn = document.getElementById('musicLightboxClose'); - const playBtn2 = document.getElementById('musicLightboxPlayBtn2'); - const prevBtn = document.getElementById('musicLightboxPrev'); - const nextBtn = document.getElementById('musicLightboxNext'); - const progressBar = document.getElementById('musicLightboxProgressBar'); - - if (!lightbox) return; - - // Remove old listeners by cloning - const newClose = closeBtn.cloneNode(true); - const newPlayBtn2 = playBtn2.cloneNode(true); - const newPrev = prevBtn.cloneNode(true); - const newNext = nextBtn.cloneNode(true); - const newProgressBar = progressBar.cloneNode(true); - - closeBtn.parentNode.replaceChild(newClose, closeBtn); - playBtn2.parentNode.replaceChild(newPlayBtn2, playBtn2); - prevBtn.parentNode.replaceChild(newPrev, prevBtn); - nextBtn.parentNode.replaceChild(newNext, nextBtn); - progressBar.parentNode.replaceChild(newProgressBar, progressBar); - - // Close button - newClose.addEventListener('click', closeMusicLightbox); - - // Click outside to close (on the background) - lightbox.addEventListener('click', function(e) { - if (e.target === lightbox) { - closeMusicLightbox(); - } - }); - - // Play button - newPlayBtn2.addEventListener('click', function(e) { - e.stopPropagation(); - toggleMusic(); - }); - - // Previous/Next - newPrev.addEventListener('click', function(e) { - e.stopPropagation(); - prevMusicTrack(); - }); - - newNext.addEventListener('click', function(e) { - e.stopPropagation(); - nextMusicTrack(); - }); - - // Progress bar click to seek - newProgressBar.addEventListener('click', function(e) { - if (!musicPlayer) return; - const rect = this.getBoundingClientRect(); - const x = e.clientX - rect.left; - const percent = x / rect.width; - musicPlayer.currentTime = percent * musicPlayer.duration; - }); - - // Keyboard controls - document.addEventListener('keydown', function(e) { - const lightbox = document.getElementById('musicLightbox'); - if (!lightbox || !lightbox.classList.contains('active')) return; - - if (e.key === 'Escape') { - e.preventDefault(); - closeMusicLightbox(); - } else if (e.key === ' ' || e.key === 'Space') { - e.preventDefault(); - toggleMusic(); - } else if (e.key === 'ArrowLeft') { - e.preventDefault(); - prevMusicTrack(); - } else if (e.key === 'ArrowRight') { - e.preventDefault(); - nextMusicTrack(); - } - }); -} - -// ============================================ -// 8. DOM CONTENT LOADED +// 8. INIT // ============================================ document.addEventListener('DOMContentLoaded', function() { - // Sticky Navigation + // --- Sticky navigation --- const stickyNav = document.getElementById('stickyNav'); const mainHeader = document.querySelector('.main-header'); - let lastScrollY = window.scrollY; let ticking = false; function updateStickyNav() { const scrollY = window.scrollY; const headerHeight = mainHeader ? mainHeader.offsetHeight : 400; - if (scrollY > headerHeight - 100) { - stickyNav.classList.add('visible'); - } else { - stickyNav.classList.remove('visible'); + if (stickyNav) { + if (scrollY > headerHeight - 100) stickyNav.classList.add('visible'); + else stickyNav.classList.remove('visible'); } - lastScrollY = scrollY; } - window.addEventListener('scroll', function() { - if (!ticking) { - window.requestAnimationFrame(function() { - updateStickyNav(); - ticking = false; - }); - ticking = true; - } - }); + if (stickyNav) { + window.addEventListener('scroll', function() { + if (!ticking) { + window.requestAnimationFrame(function() { + updateStickyNav(); + ticking = false; + }); + ticking = true; + } + }); + setTimeout(updateStickyNav, 100); + } - setTimeout(updateStickyNav, 100); - - // Read More toggle for blog posts + // --- Read more toggles --- document.querySelectorAll('.read-more-toggle').forEach(button => { button.addEventListener('click', function() { const content = this.nextElementSibling; @@ -936,45 +754,35 @@ document.addEventListener('DOMContentLoaded', function() { }); }); - // Smooth scrolling for navigation links (only for # anchors) + // --- Smooth scroll for # anchors --- document.querySelectorAll('nav a').forEach(link => { link.addEventListener('click', function(e) { 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); + const targetElement = document.querySelector(href); if (targetElement) { - const navHeight = stickyNav.offsetHeight; + const navHeight = stickyNav ? stickyNav.offsetHeight : 0; const targetPosition = targetElement.getBoundingClientRect().top + window.pageYOffset - navHeight - 20; - window.scrollTo({ - top: targetPosition, - behavior: 'smooth' - }); + window.scrollTo({ top: targetPosition, behavior: 'smooth' }); } } - // If it's a regular link (like /playlists/), let it navigate normally }); }); - // Initialize Music (chronological, not randomized) + // --- Initialize renderers --- if (window.allMusicData && window.allMusicData.length > 0) { renderMusicTracks(window.allMusicData, 'musicGrid', false); } - - // Initialize Pictures (chronological, not randomized) if (window.allPicturesData && window.allPicturesData.length > 0) { renderRandomPictures(window.allPicturesData, 'photoGrid', false); } - - // Initialize Videos (chronological, not randomized) if (window.allVideosData && window.allVideosData.length > 0) { renderRandomVideos(window.allVideosData, 'videoGrid', false); } - // Initialize music lightbox + // --- Initialize the music lightbox controls --- initMusicLightbox(); }); -console.log('✅ Site initialized successfully!'); +console.log('✅ Site initialized successfully!'); \ No newline at end of file diff --git a/themes/one-pager/layouts/_default/baseof.html b/themes/one-pager/layouts/_default/baseof.html index a5bf90d..95b34af 100644 --- a/themes/one-pager/layouts/_default/baseof.html +++ b/themes/one-pager/layouts/_default/baseof.html @@ -12,25 +12,16 @@ + {{ block "header" . }}{{ end }} +
+ {{ block "main" . }}{{ end }} +
+ {{ partial "footer.html" . }} + {{ partial "photo-lightbox.html" . }} + {{ partial "music-lightbox.html" . }} - {{ block "header" . }}{{ end }} - - -
- - {{ block "main" . }}{{ end }} - -
- - {{ partial "footer.html" . }} - - - - - {{ $js := resources.Get "js/scripts.js" | resources.Minify | fingerprint }} - - - + {{ $js := resources.Get "js/scripts.js" | resources.Minify | fingerprint }} + diff --git a/themes/one-pager/layouts/_default/index.html b/themes/one-pager/layouts/_default/index.html index 5930d41..a5b7df2 100644 --- a/themes/one-pager/layouts/_default/index.html +++ b/themes/one-pager/layouts/_default/index.html @@ -5,71 +5,8 @@ {{ define "main" }} {{ partial "blog.html" . }} - {{ partial "music.html" . }} - {{ partial "pictures.html" . }} - {{ partial "videos.html" . }} - -
- ✕ - - -
- -
- - -
-
-
1 / 1
-
- - -
-
- - -
-
- Album art -
- -
-

Title

-

Artist

-

Album

- -
- 0:00 -
-
-
- 0:00 -
- -
- - - -
- - -
- -
- - - -
-
-
- {{ end }} diff --git a/themes/one-pager/layouts/partials/music-lightbox.html b/themes/one-pager/layouts/partials/music-lightbox.html new file mode 100644 index 0000000..d128c8f --- /dev/null +++ b/themes/one-pager/layouts/partials/music-lightbox.html @@ -0,0 +1,42 @@ + +
+
+ + +
+
+ Album art +
+ +
+

Title

+

Artist

+

Album

+ +
+ 0:00 +
+
+
+ 0:00 +
+ +
+ + + +
+ +
+ +
+ + +
+
+
\ No newline at end of file diff --git a/themes/one-pager/layouts/partials/nav-strip.html b/themes/one-pager/layouts/partials/nav-strip.html new file mode 100644 index 0000000..55c5d3e --- /dev/null +++ b/themes/one-pager/layouts/partials/nav-strip.html @@ -0,0 +1,30 @@ +{{/* Small persistent navigation strip for sub-pages (playlist list, playlist single, etc.) */}} +{{ $displayName := "" }} +{{ $picture := "" }} + +{{ range hugo.Data.profile }} + {{ range . }} + {{ range .tags }} + {{ if eq (index . 0) "display_name" }}{{ $displayName = index . 1 }}{{ end }} + {{ if eq (index . 0) "picture" }}{{ $picture = index . 1 }}{{ end }} + {{ end }} + {{ end }} +{{ end }} +{{ if not $displayName }} + {{ range hugo.Data.profile }}{{ range . }}{{ range .tags }}{{ if eq (index . 0) "name" }}{{ $displayName = index . 1 }}{{ end }}{{ end }}{{ end }}{{ end }} +{{ end }} + + \ No newline at end of file diff --git a/themes/one-pager/layouts/partials/photo-lightbox.html b/themes/one-pager/layouts/partials/photo-lightbox.html new file mode 100644 index 0000000..9faedea --- /dev/null +++ b/themes/one-pager/layouts/partials/photo-lightbox.html @@ -0,0 +1,14 @@ + +
+ ✕ + + +
+ +
+ + +
+
+
1 / 1
+
\ No newline at end of file diff --git a/themes/one-pager/layouts/playlists/list.html b/themes/one-pager/layouts/playlists/list.html index ad285b8..c38e57b 100644 --- a/themes/one-pager/layouts/playlists/list.html +++ b/themes/one-pager/layouts/playlists/list.html @@ -1,107 +1,42 @@ {{ 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 }} - -
-
- -
- - - + {{ partial "nav-strip.html" . }} {{ end }} {{ define "main" }}
+

{{ .Title }}

+ {{ if .Content }} +
{{ .Content }}
+ {{ else }} +

Explore all my curated playlists from the Nostr network

+ {{ end }} -

Playlists

-

Explore all my curated playlists from the Nostr network

- + {{ if .Pages }}
{{ range .Pages }} {{ $name := .Title }} {{ $description := .Params.playlist_description | default "" }} {{ $image := .Params.playlist_image | default "" }} {{ $tracks := .Params.playlist_tracks | default slice }} - {{ end }}
+ {{ else }} +

No playlists found. Run ./fetch-playlist.sh and ./generate-playlist-pages.sh.

+ {{ end }}
-{{ 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 index be39de5..1555f47 100644 --- a/themes/one-pager/layouts/playlists/single.html +++ b/themes/one-pager/layouts/playlists/single.html @@ -1,73 +1,20 @@ {{ define "header" }} - {{ $displayName := "" }} - {{ $name := "" }} - {{ $picture := "" }} - {{ $banner := "" }} - {{ $about := "" }} - {{ $website := "" }} - {{ $nip05 := "" }} - {{ $lud16 := "" }} + {{ $name := .Title }} + {{ $image := .Params.playlist_image | default "" }} - {{ 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 }} + {{/* Small nav strip with avatar + home + playlists links */}} + {{ partial "nav-strip.html" . }} - - {{ if not $displayName }} - {{ $displayName = $name }} - {{ end }} - - - -
- - {{ if $banner }} +
+ {{ if $image }}
-
{{ end }} - - - - -
-
-
- {{ if $picture }} - {{ $displayName }}'s profile picture' - {{ end }} - -
+
-
- - - {{ end }} - - {{ define "main" }} {{ $name := .Title }} {{ $description := .Params.playlist_description | default "" }} @@ -76,10 +23,7 @@ {{ $created_at := .Params.playlist_created_at | default 0 }}
@@ -103,112 +47,128 @@
-
- {{ 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 }} +
+
← Back to All Playlists
+ {{/* FIX: safeJS prevents Hugo from HTML-escaping the JSON */}} + + {{ end }}