// ============================================ // 1. UTILITY FUNCTIONS // ============================================ // Function to shuffle array (Fisher-Yates) function shuffleArray(array) { const shuffled = [...array]; for (let i = shuffled.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; } return shuffled; } // Artist filter list const allowedArtists = [ 'εΎ’ Setto γ‚»γƒƒγƒˆ', 'Settoshi Tonami', 'Sethy Bowoy' ]; // 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); }); } // ============================================ // 2. PHOTO LIGHTBOX // ============================================ let allPicturesArray = []; let currentPhotoIndex = 0; function openPhotoLightbox(index) { const lightbox = document.getElementById('customLightbox'); const lightboxImage = document.getElementById('lightboxImage'); const lightboxCaption = document.getElementById('lightboxCaption'); const lightboxMeta = document.getElementById('lightboxMeta'); const lightboxCounter = document.getElementById('lightboxCounter'); if (!lightbox || !lightboxImage || allPicturesArray.length === 0) return; if (index < 0) index = allPicturesArray.length - 1; if (index >= allPicturesArray.length) index = 0; currentPhotoIndex = index; const photo = allPicturesArray[currentPhotoIndex]; lightboxImage.src = photo.url; lightboxImage.alt = photo.alt; lightboxCaption.textContent = photo.alt; let metaText = ''; const date = new Date(photo.created_at * 1000); const dateStr = date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); if (dateStr) metaText += `πŸ“… ${dateStr}`; lightboxMeta.textContent = metaText || 'πŸ“… No date available'; lightboxCounter.textContent = `${currentPhotoIndex + 1} / ${allPicturesArray.length}`; lightbox.classList.add('active'); document.body.style.overflow = 'hidden'; } function closePhotoLightbox() { const lightbox = document.getElementById('customLightbox'); if (!lightbox) return; lightbox.classList.remove('active'); document.body.style.overflow = ''; } function prevPhoto() { if (allPicturesArray.length === 0) return; openPhotoLightbox(currentPhotoIndex - 1); } function nextPhoto() { if (allPicturesArray.length === 0) return; openPhotoLightbox(currentPhotoIndex + 1); } function initPhotoLightbox() { const lightbox = document.getElementById('customLightbox'); 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); lightboxClose.parentNode.replaceChild(newClose, lightboxClose); 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); } }; img.addEventListener('click', img._photoClick); }); } 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(); } } function handlePhotoClick(e) { const lightbox = document.getElementById('customLightbox'); if (e.target === lightbox) closePhotoLightbox(); } // ============================================ // 3. MUSIC RENDERER // ============================================ function renderMusicTracks(tracks, containerId, randomize = false) { const container = document.getElementById(containerId); if (!container) return; const filteredTracks = tracks.filter(track => { let artist = ''; if (track.tags) { track.tags.forEach(tag => { if (tag[0] === 'artist') artist = tag[1]; }); } return isAllowedArtist(artist); }); if (filteredTracks.length === 0) { container.innerHTML = '

No tracks found for the selected artists.

'; return; } // Build playlist from ALL filtered tracks window.musicPlaylist = []; filteredTracks.forEach(track => { let title = '', url = '', image = '', artist = '', album = '', duration = ''; if (track.tags) { track.tags.forEach(tag => { const key = tag[0]; const value = tag[1]; if (key === 'title') title = value; else if (key === 'url') url = value; else if (key === 'image') image = value; else if (key === 'artist') artist = value; else if (key === 'album') album = value; else if (key === 'duration') duration = parseFloat(value); }); } if (url) { window.musicPlaylist.push({ title: title || 'Untitled', url: url, image: image || '', artist: artist || 'Unknown Artist', album: album || '', duration: duration || 0, created_at: track.created_at || 0 }); } }); // 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 const displayTracks = window.musicPlaylist.slice(0, 6); container.innerHTML = ''; displayTracks.forEach((track, index) => { const card = document.createElement('div'); 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; } const idx = parseInt(this.dataset.playlistIndex); if (!isNaN(idx) && idx >= 0) { openMusicLightbox(idx); } }); // Find genre tags from the original track data 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 (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; }); 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 = cardHTML; container.appendChild(card); }); initMusicEventListeners(); } function initMusicEventListeners() { document.querySelectorAll('.lyrics-toggle').forEach(button => { button.removeEventListener('click', handleLyricsToggle); button.addEventListener('click', handleLyricsToggle); }); } 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'; } } } // ============================================ // 4. PICTURES RENDERER // ============================================ 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); } }); if (imetaTags.length > 0) { let firstImage = null; let eventImages = []; imetaTags.forEach(imetaTag => { let imageUrl = ''; let alt = event.content || ''; 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 (imageUrl) { const pictureData = { url: imageUrl, alt: 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) { allEvents.push({ event_id: event.id, created_at: event.created_at, firstImage: firstImage }); } } else { let imageUrl = ''; let alt = event.content || ''; event.tags.forEach(tag => { if (tag[0] === 'imeta') { const imetaStr = tag[1]; const urlMatch = imetaStr.match(/url ([^\s]+)/); if (urlMatch) imageUrl = urlMatch[1]; const altMatch = imetaStr.match(/alt ([^\s]+)/); if (altMatch) alt = altMatch[1]; } }); if (imageUrl) { const pictureData = { url: imageUrl, alt: alt, created_at: event.created_at, pubkey: event.pubkey, event_id: event.id }; allPictures.push(pictureData); allEvents.push({ event_id: event.id, created_at: event.created_at, firstImage: pictureData }); } } }); // 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); } container.innerHTML = ''; displayEvents.forEach((event, index) => { const picture = event.firstImage; if (!picture) return; const item = document.createElement('div'); 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', { year: 'numeric', month: 'short', day: 'numeric' }); item.dataset.date = dateStr; item.dataset.pubkey = picture.pubkey || ''; item.innerHTML = ` ${picture.url ? `${picture.alt}` : ''}
πŸ“… ${dateStr}
`; container.appendChild(item); }); // Initialize photo lightbox initPhotoLightbox(); } // ============================================ // 5. VIDEOS RENDERER // ============================================ 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); } container.innerHTML = ''; const posterImage = '/images/videoposter.png'; selected.forEach(video => { let videoUrl = '', mimeType = 'video/mp4', alt = video.content || '', dim = ''; let isVertical = false; if (video.tags) { video.tags.forEach(tag => { if (tag[0] === 'imeta') { const imetaStr = tag[1]; const urlMatch = imetaStr.match(/url ([^\s]+)/); if (urlMatch) videoUrl = urlMatch[1]; const mimeMatch = imetaStr.match(/m ([^\s]+)/); if (mimeMatch) mimeType = mimeMatch[1]; const altMatch = imetaStr.match(/alt ([^\s]+)/); if (altMatch) alt = altMatch[1]; const dimMatch = imetaStr.match(/dim ([^\s]+)/); if (dimMatch) { dim = dimMatch[1]; const [width, height] = dim.split('x').map(Number); if (height > width) isVertical = true; } } }); } const date = new Date(video.created_at * 1000); const dateStr = date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); const wrapperStyle = isVertical ? '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%;"'; const html = `
${videoUrl ? `
` : ''}

${alt}

πŸ“… ${dateStr} ${dim ? `πŸ“ ${dim}` : ''}
`; container.insertAdjacentHTML('beforeend', html); }); } // ============================================ // 6. REFRESH FUNCTIONS // ============================================ function refreshMusic() { if (window.allMusicData) { window.musicPlaylist = []; renderMusicTracks(window.allMusicData, 'musicGrid', true); } } function refreshPictures() { if (window.allPicturesData) renderRandomPictures(window.allPicturesData, 'photoGrid', true); } function refreshVideos() { if (window.allVideosData) renderRandomVideos(window.allVideosData, 'videoGrid', true); } // ============================================ // 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'); // 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 || ''; // 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 // ============================================ document.addEventListener('DOMContentLoaded', function() { // 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'); } lastScrollY = scrollY; } window.addEventListener('scroll', function() { if (!ticking) { window.requestAnimationFrame(function() { updateStickyNav(); ticking = false; }); ticking = true; } }); setTimeout(updateStickyNav, 100); // Read More toggle for blog posts document.querySelectorAll('.read-more-toggle').forEach(button => { button.addEventListener('click', function() { const content = this.nextElementSibling; if (content.style.display === 'none') { content.style.display = 'block'; this.innerHTML = 'Read Less'; } else { content.style.display = 'none'; this.innerHTML = 'Read More'; } }); }); // Smooth scrolling for navigation links (both main nav and sticky nav) 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' }); } }); }); // Initialize Music (chronological, not randomized) 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 initMusicLightbox(); }); console.log('βœ… Site initialized successfully!');