789 lines
26 KiB
JavaScript
789 lines
26 KiB
JavaScript
// ============================================
|
|
// 1. UTILITY FUNCTIONS
|
|
// ============================================
|
|
|
|
// Fisher-Yates shuffle
|
|
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 whitelist — only tracks by these artists are shown
|
|
const allowedArtists = [
|
|
'徒 Setto セット',
|
|
'Settoshi Tonami',
|
|
'Sethy Bowoy',
|
|
'reSet Sakrecoer',
|
|
'Sakrecoer',
|
|
'Vigesimal',
|
|
'Settoshi Tonami'
|
|
];
|
|
|
|
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);
|
|
});
|
|
}
|
|
|
|
// 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
|
|
// ============================================
|
|
|
|
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');
|
|
if (!lightbox) return;
|
|
|
|
const lightboxClose = document.getElementById('lightboxClose');
|
|
const lightboxPrev = document.getElementById('lightboxPrev');
|
|
const lightboxNext = document.getElementById('lightboxNext');
|
|
|
|
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);
|
|
|
|
newClose.addEventListener('click', closePhotoLightbox);
|
|
newPrev.addEventListener('click', prevPhoto);
|
|
newNext.addEventListener('click', nextPhoto);
|
|
|
|
lightbox.removeEventListener('click', handlePhotoClick);
|
|
lightbox.addEventListener('click', handlePhotoClick);
|
|
|
|
document.removeEventListener('keydown', handlePhotoKeydown);
|
|
document.addEventListener('keydown', handlePhotoKeydown);
|
|
|
|
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 (homepage cards)
|
|
// ============================================
|
|
|
|
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 = '<p style="text-align:center;padding:2rem;color:var(--text-secondary);font-family:\'Poppins\',sans-serif;">No tracks found for the selected artists.</p>';
|
|
return;
|
|
}
|
|
|
|
// Build global playlist from all filtered tracks
|
|
window.musicPlaylist = [];
|
|
filteredTracks.forEach(track => {
|
|
let title = '', url = '', image = '', artist = '', album = '', duration = '';
|
|
const trackId = track.id || '';
|
|
|
|
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({
|
|
id: trackId,
|
|
title: title || 'Untitled',
|
|
url,
|
|
image: image || '',
|
|
artist: artist || 'Unknown Artist',
|
|
album: album || '',
|
|
duration: duration || 0,
|
|
created_at: track.created_at || 0
|
|
});
|
|
}
|
|
});
|
|
|
|
if (randomize) {
|
|
window.musicPlaylist = shuffleArray(window.musicPlaylist);
|
|
} else {
|
|
window.musicPlaylist.sort((a, b) => (b.created_at || 0) - (a.created_at || 0));
|
|
}
|
|
|
|
// Only render the first 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;
|
|
|
|
card.addEventListener('click', function(e) {
|
|
if (e.target.closest('.lyrics-toggle') || e.target.closest('.lyrics')) return;
|
|
const idx = parseInt(this.dataset.playlistIndex);
|
|
if (!isNaN(idx) && idx >= 0) openMusicLightbox(idx);
|
|
});
|
|
|
|
// Collect genre tags + info from the raw event
|
|
let genreTags = [];
|
|
let content = '';
|
|
const originalTrack = filteredTracks.find(t => {
|
|
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 => {
|
|
if (tag[0] === 't') genreTags.push(tag[1]);
|
|
});
|
|
content = originalTrack.content || '';
|
|
}
|
|
|
|
card.innerHTML = `
|
|
<div class="music-card-inner">
|
|
<div class="music-image-wrapper">
|
|
${track.image ? `<img src="${track.image}" alt="${track.title}" class="music-image" loading="lazy">` : ''}
|
|
<div class="music-play-overlay"><span class="music-play-btn">▶</span></div>
|
|
</div>
|
|
<div class="music-info-header">
|
|
<h4>${track.title}</h4>
|
|
${track.artist ? `<p class="music-artist">${track.artist}</p>` : ''}
|
|
${track.album ? `<p class="music-album">${track.album}</p>` : ''}
|
|
</div>
|
|
${genreTags.length > 0 ? `
|
|
<div class="music-tags">
|
|
${genreTags.map(t => `<span class="tag"><span>${t}</span></span>`).join('')}
|
|
</div>
|
|
` : ''}
|
|
${content ? `
|
|
<button class="lyrics-toggle"><span>Show Info</span></button>
|
|
<div class="lyrics" style="display: none;">${content.replace(/\n/g, '<br>')}</div>
|
|
` : ''}
|
|
</div>
|
|
`;
|
|
container.appendChild(card);
|
|
});
|
|
|
|
initMusicEventListeners();
|
|
}
|
|
|
|
function initMusicEventListeners() {
|
|
document.querySelectorAll('.lyrics-toggle').forEach(button => {
|
|
button.removeEventListener('click', handleLyricsToggle);
|
|
button.addEventListener('click', handleLyricsToggle);
|
|
});
|
|
}
|
|
|
|
function handleLyricsToggle(e) {
|
|
e.stopPropagation();
|
|
const lyrics = this.nextElementSibling;
|
|
if (lyrics && lyrics.classList.contains('lyrics')) {
|
|
const hidden = lyrics.style.display === 'none';
|
|
lyrics.style.display = hidden ? 'block' : 'none';
|
|
this.innerHTML = `<span>${hidden ? 'Hide Info' : 'Show Info'}</span>`;
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// 4. PICTURES RENDERER
|
|
// ============================================
|
|
|
|
function renderRandomPictures(picturesData, containerId, randomize = false) {
|
|
const container = document.getElementById(containerId);
|
|
if (!container) return;
|
|
|
|
let allPictures = [];
|
|
let allEvents = [];
|
|
|
|
picturesData.forEach(event => {
|
|
const imetaTags = event.tags.filter(tag => tag[0] === 'imeta');
|
|
|
|
if (imetaTags.length > 0) {
|
|
let firstImage = null;
|
|
|
|
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,
|
|
created_at: event.created_at,
|
|
pubkey: event.pubkey,
|
|
event_id: event.id
|
|
};
|
|
allPictures.push(pictureData);
|
|
if (!firstImage) firstImage = pictureData;
|
|
}
|
|
});
|
|
|
|
if (firstImage) {
|
|
allEvents.push({
|
|
event_id: event.id,
|
|
created_at: event.created_at,
|
|
firstImage
|
|
});
|
|
}
|
|
} else {
|
|
let imageUrl = '', 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,
|
|
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
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
allPicturesArray = allPictures;
|
|
|
|
const sortedEvents = [...allEvents].sort((a, b) => b.created_at - a.created_at);
|
|
const displayEvents = randomize
|
|
? shuffleArray([...sortedEvents]).slice(0, 6)
|
|
: sortedEvents.slice(0, 6);
|
|
|
|
container.innerHTML = '';
|
|
|
|
displayEvents.forEach(event => {
|
|
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;
|
|
|
|
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 ? `<img src="${picture.url}" alt="${picture.alt}" loading="lazy">` : ''}
|
|
<div class="photo-date"><span>📅 ${dateStr}</span></div>
|
|
`;
|
|
container.appendChild(item);
|
|
});
|
|
|
|
initPhotoLightbox();
|
|
}
|
|
|
|
// ============================================
|
|
// 5. VIDEOS RENDERER
|
|
// ============================================
|
|
|
|
function renderRandomVideos(videosData, containerId, randomize = false) {
|
|
const container = document.getElementById(containerId);
|
|
if (!container) return;
|
|
|
|
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';
|
|
|
|
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%;"';
|
|
|
|
container.insertAdjacentHTML('beforeend', `
|
|
<div class="video-item">
|
|
${videoUrl ? `
|
|
<div class="video-wrapper" ${wrapperStyle}>
|
|
<video controls preload="none" poster="${posterImage}" ${videoStyle}>
|
|
<source src="${videoUrl}" type="${mimeType}">
|
|
Your browser does not support the video tag.
|
|
</video>
|
|
</div>
|
|
` : ''}
|
|
<div class="video-info">
|
|
<p class="video-caption">${alt}</p>
|
|
<div class="video-meta">
|
|
<span class="video-date">📅 ${dateStr}</span>
|
|
${dim ? `<span class="video-dim">📐 ${dim}</span>` : ''}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`);
|
|
});
|
|
}
|
|
|
|
// ============================================
|
|
// 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 = '<span class="checkmark">✓</span> 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 = '<span class="checkmark">✓</span> 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, '<br>');
|
|
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() {
|
|
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);
|
|
}
|
|
|
|
// ============================================
|
|
// 8. INIT
|
|
// ============================================
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// --- Sticky navigation ---
|
|
const stickyNav = document.getElementById('stickyNav');
|
|
const mainHeader = document.querySelector('.main-header');
|
|
let ticking = false;
|
|
|
|
function updateStickyNav() {
|
|
const scrollY = window.scrollY;
|
|
const headerHeight = mainHeader ? mainHeader.offsetHeight : 400;
|
|
if (stickyNav) {
|
|
if (scrollY > headerHeight - 100) stickyNav.classList.add('visible');
|
|
else stickyNav.classList.remove('visible');
|
|
}
|
|
}
|
|
|
|
if (stickyNav) {
|
|
window.addEventListener('scroll', function() {
|
|
if (!ticking) {
|
|
window.requestAnimationFrame(function() {
|
|
updateStickyNav();
|
|
ticking = false;
|
|
});
|
|
ticking = true;
|
|
}
|
|
});
|
|
setTimeout(updateStickyNav, 100);
|
|
}
|
|
|
|
// --- Read more toggles ---
|
|
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 = '<span>Read Less</span>';
|
|
} else {
|
|
content.style.display = 'none';
|
|
this.innerHTML = '<span>Read More</span>';
|
|
}
|
|
});
|
|
});
|
|
|
|
// --- Smooth scroll for # anchors ---
|
|
document.querySelectorAll('nav a').forEach(link => {
|
|
link.addEventListener('click', function(e) {
|
|
const href = this.getAttribute('href');
|
|
if (href && href.startsWith('#')) {
|
|
e.preventDefault();
|
|
const targetElement = document.querySelector(href);
|
|
if (targetElement) {
|
|
const navHeight = stickyNav ? stickyNav.offsetHeight : 0;
|
|
const targetPosition = targetElement.getBoundingClientRect().top + window.pageYOffset - navHeight - 20;
|
|
window.scrollTo({ top: targetPosition, behavior: 'smooth' });
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// --- Initialize renderers ---
|
|
if (window.allMusicData && window.allMusicData.length > 0) {
|
|
renderMusicTracks(window.allMusicData, 'musicGrid', false);
|
|
}
|
|
if (window.allPicturesData && window.allPicturesData.length > 0) {
|
|
renderRandomPictures(window.allPicturesData, 'photoGrid', false);
|
|
}
|
|
if (window.allVideosData && window.allVideosData.length > 0) {
|
|
renderRandomVideos(window.allVideosData, 'videoGrid', false);
|
|
}
|
|
|
|
// --- Initialize the music lightbox controls ---
|
|
initMusicLightbox();
|
|
});
|
|
|
|
console.log('✅ Site initialized successfully!');
|