document.addEventListener('DOMContentLoaded', function() { // Sticky Navigation const stickyNav = document.getElementById('stickyNav'); const mainHeader = document.querySelector('.main-header'); const themeToggle = document.getElementById('themeToggle'); const themeToggleSmall = document.getElementById('themeToggleSmall'); let lastScrollY = window.scrollY; let ticking = false; // Function to update sticky nav visibility function updateStickyNav() { const scrollY = window.scrollY; const headerHeight = mainHeader ? mainHeader.offsetHeight : 400; if (scrollY > headerHeight - 100) { stickyNav.classList.add('visible'); if (themeToggle) { themeToggle.classList.add('hidden'); } } else { stickyNav.classList.remove('visible'); if (themeToggle) { themeToggle.classList.remove('hidden'); } } lastScrollY = scrollY; } // Throttled scroll listener window.addEventListener('scroll', function() { if (!ticking) { window.requestAnimationFrame(function() { updateStickyNav(); ticking = false; }); ticking = true; } }); // Initial check setTimeout(updateStickyNav, 100); // Read More toggle for blog posts const readMoreButtons = document.querySelectorAll('.read-more-toggle'); readMoreButtons.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) { // Calculate offset for sticky nav const navHeight = stickyNav.offsetHeight; const targetPosition = targetElement.getBoundingClientRect().top + window.pageYOffset - navHeight - 20; window.scrollTo({ top: targetPosition, behavior: 'smooth' }); } }); }); // ============ CUSTOM LIGHTBOX ============ 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'); const lightboxClose = document.getElementById('lightboxClose'); const lightboxPrev = document.getElementById('lightboxPrev'); const lightboxNext = document.getElementById('lightboxNext'); let currentPhotos = []; let currentIndex = 0; // Get all photo items const photoItems = document.querySelectorAll('.photo-item'); // Build photo array photoItems.forEach(item => { const image = item.querySelector('img'); const date = item.dataset.date || ''; const pubkey = item.dataset.pubkey || ''; const alt = item.dataset.alt || ''; if (image) { currentPhotos.push({ url: image.src, alt: alt, date: date, pubkey: pubkey }); } }); // Open lightbox function openLightbox(index) { if (index < 0) index = currentPhotos.length - 1; if (index >= currentPhotos.length) index = 0; currentIndex = index; const photo = currentPhotos[currentIndex]; lightboxImage.src = photo.url; lightboxImage.alt = photo.alt; lightboxCaption.textContent = photo.alt; lightboxMeta.textContent = `πŸ“… ${photo.date} | πŸ–ŠοΈ ${photo.pubkey}`; lightboxCounter.textContent = `${currentIndex + 1} / ${currentPhotos.length}`; lightbox.classList.add('active'); document.body.style.overflow = 'hidden'; } // Close lightbox function closeLightbox() { lightbox.classList.remove('active'); document.body.style.overflow = ''; } // Navigate function prevPhoto() { openLightbox(currentIndex - 1); } function nextPhoto() { openLightbox(currentIndex + 1); } // Event listeners for photo clicks photoItems.forEach((item, index) => { const img = item.querySelector('img'); if (img) { img.addEventListener('click', function(e) { e.preventDefault(); openLightbox(index); }); } }); // Event listeners for lightbox controls lightboxClose.addEventListener('click', closeLightbox); lightboxPrev.addEventListener('click', prevPhoto); lightboxNext.addEventListener('click', nextPhoto); // Keyboard controls document.addEventListener('keydown', function(e) { if (!lightbox.classList.contains('active')) return; if (e.key === 'Escape') { closeLightbox(); } else if (e.key === 'ArrowLeft') { prevPhoto(); } else if (e.key === 'ArrowRight') { nextPhoto(); } }); // Click outside image to close lightbox.addEventListener('click', function(e) { if (e.target === lightbox) { closeLightbox(); } }); }); // No external lightbox library needed anymore! console.log('Custom lightbox initialized successfully!'); // 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; // Normalize the artist name for comparison (remove extra spaces, lowercase) const normalizedArtist = artist.trim().toLowerCase(); return allowedArtists.some(allowed => { // Normalize the allowed artist name too const normalizedAllowed = allowed.trim().toLowerCase(); // Check if the artist name contains the allowed artist name or vice versa return normalizedArtist.includes(normalizedAllowed) || normalizedAllowed.includes(normalizedArtist); }); } // Render music tracks (randomized, 6 at a time, filtered by artist) function renderMusicTracks(tracks, containerId) { const container = document.getElementById(containerId); if (!container) return; // Filter tracks by artist const filteredTracks = tracks.filter(track => { // Extract artist from tags let artist = ''; if (track.tags) { track.tags.forEach(tag => { if (tag[0] === 'artist') { artist = tag[1]; } }); } return isAllowedArtist(artist); }); // Check if we have any filtered tracks if (filteredTracks.length === 0) { container.innerHTML = '

No tracks found for the selected artists.

'; return; } // Shuffle and get first 6 const shuffled = filteredTracks.sort(() => 0.5 - Math.random()); const selected = shuffled.slice(0, 6); // Clear container container.innerHTML = ''; // Render each track (same as before) selected.forEach(track => { // Extract data from tags let title = '', url = '', image = '', artist = '', album = '', trackNumber = '', duration = '', released = '', language = '', tags = [], alt = '', content = ''; // Parse tags if (track.tags) { track.tags.forEach(tag => { if (tag[0] === 'title') title = tag[1]; else if (tag[0] === 'url') url = tag[1]; else if (tag[0] === 'image') image = tag[1]; else if (tag[0] === 'artist') artist = tag[1]; else if (tag[0] === 'album') album = tag[1]; else if (tag[0] === 'track_number') trackNumber = tag[1]; else if (tag[0] === 'duration') duration = tag[1]; else if (tag[0] === 'released') released = tag[1]; else if (tag[0] === 'language') language = tag[1]; else if (tag[0] === 't') tags.push(tag[1]); else if (tag[0] === 'alt') alt = tag[1]; }); } content = track.content || ''; // Create music card HTML const card = document.createElement('div'); card.className = 'music-card'; let cardHTML = ` ${image ? `${title}` : ''}

${title || 'Untitled'}

${artist ? `

${artist}

` : ''} ${album ? `

${album}

` : ''}
${url ? `` : ''}
${trackNumber ? `Track ${trackNumber}` : ''} ${duration ? `${duration}s` : ''} ${released ? `${released}` : ''} ${language ? `${language}` : ''}
${tags.map(t => `${t}`).join('')}
${content ? ` ` : ''} `; card.innerHTML = cardHTML; container.appendChild(card); }); // Re-initialize event listeners for new buttons initMusicEventListeners(); } // Initialize music event listeners function initMusicEventListeners() { // Lyrics toggle document.querySelectorAll('.lyrics-toggle').forEach(button => { button.removeEventListener('click', handleLyricsToggle); button.addEventListener('click', handleLyricsToggle); }); } function handleLyricsToggle() { 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'; } } } // Refresh music (randomize) function refreshMusic() { if (window.allMusicData) { renderMusicTracks(window.allMusicData, 'musicGrid'); } } // Call this when page loads document.addEventListener('DOMContentLoaded', function() { if (window.allMusicData && window.allMusicData.length > 0) { renderMusicTracks(window.allMusicData, 'musicGrid'); } }); // ============ CUSTOM BLURHASH DECODER ============ // Based on the official blurhash implementation, but compact const Blurhash = { // Base83 character set chars: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+,;<=>?@[]^_`{|}~', // Decode base83 to integer decode83: function(str) { let value = 0; for (let i = 0; i < str.length; i++) { const c = str[i]; const digit = this.chars.indexOf(c); if (digit === -1) { throw new Error('Invalid base83 character: ' + c); } value = value * 83 + digit; } return value; }, // Decode blurhash to pixel data decode: function(blurhash, width, height, punch = 1) { // Parse metadata const sizeFlag = this.decode83(blurhash[0]); const numX = (sizeFlag % 9) + 1; const numY = Math.floor(sizeFlag / 9) + 1; // Calculate maximum AC component value const maxAC = this.decode83(blurhash[1]) + 1; // Decode DC component const dcValue = this.decode83(blurhash.slice(2, 6)); const dc = this.decodeDC(dcValue); // Decode AC components const ac = []; let acIndex = 6; for (let y = 0; y < numY; y++) { for (let x = 0; x < numX; x++) { if (x === 0 && y === 0) continue; const encoded = this.decode83(blurhash.slice(acIndex, acIndex + 2)); acIndex += 2; const acValue = this.decodeAC(encoded, maxAC * punch); ac.push({ x, y, value: acValue }); } } // Render pixels const pixels = new Uint8ClampedArray(width * height * 4); const size = width * height; // Pre-calculate cos values for performance const cosX = []; const cosY = []; for (let x = 0; x < width; x++) { cosX[x] = Math.cos(Math.PI * x / width); } for (let y = 0; y < height; y++) { cosY[y] = Math.cos(Math.PI * y / height); } for (let i = 0; i < size; i++) { const x = i % width; const y = Math.floor(i / width); let r = dc[0]; let g = dc[1]; let b = dc[2]; for (const acVal of ac) { const basis = cosX[x] * cosY[y]; const value = acVal.value; r += value[0] * basis; g += value[1] * basis; b += value[2] * basis; } const index = i * 4; pixels[index] = this.clamp(r + 128); pixels[index + 1] = this.clamp(g + 128); pixels[index + 2] = this.clamp(b + 128); pixels[index + 3] = 255; } return pixels; }, // Decode DC component decodeDC: function(value) { return [ this.signPow((value >> 16) & 0x3ff, 2), this.signPow((value >> 8) & 0xff, 2), this.signPow(value & 0xff, 2) ]; }, // Decode AC component decodeAC: function(value, maxVal) { const quantR = Math.floor(value / (19 * 19)); const quantG = Math.floor((value / 19) % 19); const quantB = value % 19; return [ this.signPow((quantR - 9) / 9, 2) * maxVal, this.signPow((quantG - 9) / 9, 2) * maxVal, this.signPow((quantB - 9) / 9, 2) * maxVal ]; }, // Sign power function signPow: function(val, exp) { return Math.sign(val) * Math.pow(Math.abs(val), exp); }, // Clamp value to 0-255 clamp: function(value) { return Math.min(255, Math.max(0, value)); }, // Render blurhash to canvas renderToCanvas: function(blurhash, canvas, punch = 1) { if (!blurhash) return false; try { const width = canvas.width; const height = canvas.height; const pixels = this.decode(blurhash, width, height, punch); const ctx = canvas.getContext('2d'); const imageData = ctx.createImageData(width, height); imageData.data.set(pixels); ctx.putImageData(imageData, 0, 0); return true; } catch (e) { console.warn('Failed to decode blurhash:', e); return false; } }, // Get blurhash as data URL toDataURL: function(blurhash, width = 64, height = 64, punch = 1) { const canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; const success = this.renderToCanvas(blurhash, canvas, punch); return success ? canvas.toDataURL('image/png') : null; } }; // ============ RANDOMIZED GALLERIES ============ // 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; } // Render random pictures (6 max) function renderRandomPictures(picturesData, containerId) { const container = document.getElementById(containerId); if (!container) return; // Shuffle and get first 6 const shuffled = shuffleArray(picturesData); const selected = shuffled.slice(0, 6); // Clear container container.innerHTML = ''; // Render each picture selected.forEach(picture => { // Parse imeta tag to get image URL let imageUrl = ''; let alt = picture.content || ''; if (picture.tags) { picture.tags.forEach(tag => { if (tag[0] === 'imeta') { const imetaStr = tag[1]; // Extract URL const urlMatch = imetaStr.match(/url ([^\s]+)/); if (urlMatch) { imageUrl = urlMatch[1]; } // Extract alt const altMatch = imetaStr.match(/alt ([^\s]+)/); if (altMatch) { alt = altMatch[1]; } } }); } // Create picture item const item = document.createElement('div'); item.className = 'photo-item'; item.dataset.image = imageUrl; item.dataset.alt = alt; const date = new Date(picture.created_at * 1000); const dateStr = date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); item.innerHTML = ` ${imageUrl ? ` ${alt} ` : ''}
πŸ“… ${dateStr}
`; container.appendChild(item); }); // Re-initialize the custom lightbox for new images initCustomLightbox(); } // Initialize custom lightbox (call this after any DOM updates) function initCustomLightbox() { const lightbox = document.getElementById('customLightbox'); if (!lightbox) return; const lightboxImage = document.getElementById('lightboxImage'); const lightboxCaption = document.getElementById('lightboxCaption'); const lightboxMeta = document.getElementById('lightboxMeta'); const lightboxCounter = document.getElementById('lightboxCounter'); const lightboxClose = document.getElementById('lightboxClose'); const lightboxPrev = document.getElementById('lightboxPrev'); const lightboxNext = document.getElementById('lightboxNext'); // Get all photo items const photoItems = document.querySelectorAll('.photo-item'); // Build photo array let currentPhotos = []; photoItems.forEach(item => { const image = item.querySelector('img'); const date = item.dataset.date || ''; const pubkey = item.dataset.pubkey || ''; const alt = item.dataset.alt || ''; if (image) { currentPhotos.push({ url: image.src, alt: alt, date: date, pubkey: pubkey }); } }); let currentIndex = 0; // Remove old event listeners by cloning and replacing const newLightboxImage = lightboxImage.cloneNode(true); const newLightboxCaption = lightboxCaption.cloneNode(true); const newLightboxMeta = lightboxMeta.cloneNode(true); const newLightboxCounter = lightboxCounter.cloneNode(true); const newLightboxClose = lightboxClose.cloneNode(true); const newLightboxPrev = lightboxPrev.cloneNode(true); const newLightboxNext = lightboxNext.cloneNode(true); lightboxImage.parentNode.replaceChild(newLightboxImage, lightboxImage); lightboxCaption.parentNode.replaceChild(newLightboxCaption, lightboxCaption); lightboxMeta.parentNode.replaceChild(newLightboxMeta, lightboxMeta); lightboxCounter.parentNode.replaceChild(newLightboxCounter, lightboxCounter); lightboxClose.parentNode.replaceChild(newLightboxClose, lightboxClose); lightboxPrev.parentNode.replaceChild(newLightboxPrev, lightboxPrev); lightboxNext.parentNode.replaceChild(newLightboxNext, lightboxNext); // Update references const newImage = document.getElementById('lightboxImage'); const newCaption = document.getElementById('lightboxCaption'); const newMeta = document.getElementById('lightboxMeta'); const newCounter = document.getElementById('lightboxCounter'); const newClose = document.getElementById('lightboxClose'); const newPrev = document.getElementById('lightboxPrev'); const newNext = document.getElementById('lightboxNext'); // Open lightbox function openLightbox(index) { if (index < 0) index = currentPhotos.length - 1; if (index >= currentPhotos.length) index = 0; currentIndex = index; const photo = currentPhotos[currentIndex]; newImage.src = photo.url; newImage.alt = photo.alt; newCaption.textContent = photo.alt; newMeta.textContent = `πŸ“… ${photo.date} | πŸ–ŠοΈ ${photo.pubkey}`; newCounter.textContent = `${currentIndex + 1} / ${currentPhotos.length}`; lightbox.classList.add('active'); document.body.style.overflow = 'hidden'; } // Close lightbox function closeLightbox() { lightbox.classList.remove('active'); document.body.style.overflow = ''; } // Navigate function prevPhoto() { openLightbox(currentIndex - 1); } function nextPhoto() { openLightbox(currentIndex + 1); } // Event listeners for photo clicks photoItems.forEach((item, index) => { const img = item.querySelector('img'); if (img) { // Remove old listener by cloning const newImg = img.cloneNode(true); img.parentNode.replaceChild(newImg, img); newImg.addEventListener('click', function(e) { e.preventDefault(); openLightbox(index); }); } }); // Event listeners for lightbox controls newClose.addEventListener('click', closeLightbox); newPrev.addEventListener('click', prevPhoto); newNext.addEventListener('click', nextPhoto); // Keyboard controls document.removeEventListener('keydown', handleLightboxKeydown); document.addEventListener('keydown', handleLightboxKeydown); function handleLightboxKeydown(e) { if (!lightbox.classList.contains('active')) return; if (e.key === 'Escape') { closeLightbox(); } else if (e.key === 'ArrowLeft') { prevPhoto(); } else if (e.key === 'ArrowRight') { nextPhoto(); } } // Click outside image to close lightbox.removeEventListener('click', handleLightboxClick); lightbox.addEventListener('click', handleLightboxClick); function handleLightboxClick(e) { if (e.target === lightbox) { closeLightbox(); } } } // Update the DOMContentLoaded event listener document.addEventListener('DOMContentLoaded', function() { // ... (your existing code for theme toggle, sticky nav, etc.) ... // Initialize Music (if you already have this) if (window.allMusicData && window.allMusicData.length > 0) { renderMusicTracks(window.allMusicData, 'musicGrid'); } // Initialize Pictures if (window.allPicturesData && window.allPicturesData.length > 0) { renderRandomPictures(window.allPicturesData, 'photoGrid'); } else { // If no data, still initialize lightbox for static content initCustomLightbox(); } // Initialize Videos if (window.allVideosData && window.allVideosData.length > 0) { renderRandomVideos(window.allVideosData, 'videoGrid'); } }); // Refresh functions function refreshMusic() { if (window.allMusicData) { renderMusicTracks(window.allMusicData, 'musicGrid'); } } function refreshPictures() { if (window.allPicturesData) { renderRandomPictures(window.allPicturesData, 'photoGrid'); } } function refreshVideos() { if (window.allVideosData) { renderRandomVideos(window.allVideosData, 'videoGrid'); } } // Render random videos (2 max) function renderRandomVideos(videosData, containerId) { const container = document.getElementById(containerId); if (!container) return; // Shuffle and get first 2 const shuffled = shuffleArray(videosData); const selected = shuffled.slice(0, 2); // Clear container container.innerHTML = ''; // Static poster image const posterImage = '/images/videoposter.png'; let html = ''; selected.forEach(video => { let videoUrl = ''; let mimeType = 'video/mp4'; let alt = video.content || ''; let 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%;"'; html += `
${videoUrl ? `
` : ''}

${alt}

πŸ“… ${dateStr} ${dim ? `πŸ“ ${dim}` : ''}
`; }); container.innerHTML = html; } // ============ INITIALIZATION ============ document.addEventListener('DOMContentLoaded', function() { // ... (your existing code for theme toggle, sticky nav, etc.) ... // Initialize Music (if you already have this) if (window.allMusicData && window.allMusicData.length > 0) { renderMusicTracks(window.allMusicData, 'musicGrid'); } // Initialize Pictures if (window.allPicturesData && window.allPicturesData.length > 0) { renderRandomPictures(window.allPicturesData, 'photoGrid'); } // Initialize Videos if (window.allVideosData && window.allVideosData.length > 0) { renderRandomVideos(window.allVideosData, 'videoGrid'); } }); // Refresh functions for randomize buttons function refreshMusic() { if (window.allMusicData) { renderMusicTracks(window.allMusicData, 'musicGrid'); } } function refreshPictures() { if (window.allPicturesData) { renderRandomPictures(window.allPicturesData, 'photoGrid'); } } function refreshVideos() { if (window.allVideosData) { renderRandomVideos(window.allVideosData, 'videoGrid'); } }