From 0a0eaeb712f338a154a5057c59d88141db6c4326 Mon Sep 17 00:00:00 2001 From: snxraven Date: Sun, 22 Jun 2025 19:34:42 -0400 Subject: [PATCH] update --- views/index.ejs | 267 +++++++++++++++++++++++++++++++++++++++--------- views/track.ejs | 215 ++++++++++++++++++++++++++++++++++---- 2 files changed, 416 insertions(+), 66 deletions(-) diff --git a/views/index.ejs b/views/index.ejs index 01a2be6..5dca3c1 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -11,6 +11,9 @@ background-color: black; color: white; font-family: 'Metal Mania', sans-serif; + position: relative; + min-height: 100vh; + padding-bottom: 100px; } .card { @@ -18,20 +21,69 @@ border: 1px solid #444; border-radius: 8px; overflow: hidden; + transition: transform 0.3s ease; + } + + .card:hover { + transform: translateY(-5px); } .card-body { padding: 1.5rem; } + .btn { + background-color: #ff5500; + border-color: #ff5500; + color: white; + font-weight: bold; + transition: background-color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease; + } + + .btn:hover { + background-color: #ff3300; + border-color: #ff3300; + color: white; + } + + .btn:active, + .btn:focus { + background-color: #cc4400; + border-color: #cc4400; + box-shadow: 0 0 8px rgba(255, 85, 0, 0.5); + outline: none; + } + + .btn:disabled, + .btn[disabled] { + background-color: #444; + border-color: #444; + color: #888; + opacity: 1; + cursor: not-allowed; + } + .btn-primary { background-color: #ff5500; border-color: #ff5500; - transition: background-color 0.3s ease; } .btn-primary:hover { background-color: #ff3300; + border-color: #ff3300; + } + + .btn-primary:active, + .btn-primary:focus { + background-color: #cc4400; + border-color: #cc4400; + } + + .btn-primary:disabled, + .btn-primary[disabled] { + background-color: #444; + border-color: #444; + color: #888; } .pagination-btn { @@ -154,35 +206,6 @@ margin-left: -15px; } - .pagination-btn { - margin: 0 10px; - color: white; - font-weight: bold; - } - - .pagination-btn.btn-primary:hover { - background-color: #ff3300; - border-color: #ff3300; - } - - .pagination-btn.btn-primary:disabled, - .pagination-btn.btn-primary[disabled] { - background-color: #444; - border-color: #444; - color: #888; - opacity: 1; - cursor: not-allowed; - } - - .pagination-btn.btn-primary:active, - .pagination-btn.btn-primary:focus { - background-color: #cc4400; - border-color: #cc4400; - box-shadow: 0 0 8px rgba(255, 85, 0, 0.5); - outline: none; - } - - /* Search box styling */ .search-container { position: relative; width: 100%; @@ -231,7 +254,8 @@ transition: background-color 0.2s ease; } - .search-result-item:hover { + .search-result-item:hover, + .search-result-item.selected { background-color: #ff5500; } @@ -239,6 +263,73 @@ color: #aaa; display: block; } + + .loading-spinner { + display: none; + width: 24px; + height: 24px; + border: 3px solid #ff5500; + border-top: 3px solid transparent; + border-radius: 50%; + animation: spin 1s linear infinite; + margin: 0 auto; + } + + .loading-spinner.show { + display: block; + } + + @keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } + } + + footer { + background-color: #1e1e1e; + color: #aaa; + padding: 2rem 0; + text-align: center; + position: absolute; + bottom: 0; + width: 100%; + border-top: 2px solid #ff5500; + } + + footer a { + color: #ff5500; + text-decoration: none; + transition: color 0.3s ease; + } + + footer a:hover { + color: #ff3300; + } + + .back-to-top { + position: fixed; + bottom: 20px; + right: 20px; + background-color: #ff5500; + color: white; + width: 40px; + height: 40px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + text-decoration: none; + opacity: 0; + transition: opacity 0.3s ease, background-color 0.3s ease; + z-index: 1000; + } + + .back-to-top.show { + opacity: 1; + } + + .back-to-top:hover { + background-color: #ff3300; + } @@ -260,7 +351,7 @@ <% } %>
- +
@@ -306,10 +397,21 @@ +
<% } %> + + + + + + + @@ -319,16 +421,21 @@ // Lazy load iframes function lazyLoadIframes(container = document) { const iframes = container.querySelectorAll('iframe[data-src]'); - iframes.forEach(iframe => { - if (!iframe.src && iframe.getBoundingClientRect().top < window.innerHeight) { - iframe.src = iframe.getAttribute('data-src'); - console.log(`Lazy loading iframe for track: ${iframe.closest('[data-slug]').dataset.slug}`); - } - }); + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const iframe = entry.target; + iframe.src = iframe.getAttribute('data-src'); + console.log(`Lazy loading iframe for track: ${iframe.closest('[data-slug]').dataset.slug}`); + observer.unobserve(iframe); + } + }); + }, { rootMargin: '100px' }); + + iframes.forEach(iframe => observer.observe(iframe)); } window.addEventListener('load', () => lazyLoadIframes()); - window.addEventListener('scroll', () => lazyLoadIframes()); // Page cache: { genre: { page: { container, totalPages } } } const pageCache = {}; @@ -346,6 +453,8 @@ socket.on('page_data', ({ genre, tracks, page, totalPages }) => { console.log(`Received page_data for genre: ${genre}, page: ${page}, tracks: ${tracks.length}`); const trackContainer = document.getElementById(`${genre}-tracks`); + const spinner = document.getElementById(`${genre}-spinner`); + spinner.classList.remove('show'); if (!pageCache[genre]) { pageCache[genre] = {}; @@ -408,15 +517,18 @@ socket.on('error', ({ message }) => { console.error('WebSocket error:', message); + document.querySelectorAll('.loading-spinner').forEach(spinner => spinner.classList.remove('show')); }); document.querySelectorAll('.pagination-btn').forEach(button => { button.addEventListener('click', () => { const genre = button.id.split('-')[0]; const page = parseInt(button.dataset.page, 10); + const spinner = document.getElementById(`${genre}-spinner`); console.log(`Button clicked: ${button.id}, genre: ${genre}, page: ${page}`); if (page > 0 && page !== currentPages[genre]) { + spinner.classList.add('show'); if (pageCache[genre] && pageCache[genre][page]) { console.log(`Loading cached page ${page} for genre: ${genre}`); const trackContainer = document.getElementById(`${genre}-tracks`); @@ -437,14 +549,17 @@ currentPages[genre] = page; lazyLoadIframes(pageCache[genre][page].container); + spinner.classList.remove('show'); } else { socket.emit('request_page', { genre, page }); console.log(`Emitted request_page for genre: ${genre}, page: ${page}`); } } else if (page <= 0) { console.error(`Invalid page number: ${page}`); + spinner.classList.remove('show'); } else { console.log(`Already on page ${page} for genre: ${genre}`); + spinner.classList.remove('show'); } }); }); @@ -452,6 +567,7 @@ // Search functionality const searchInput = document.querySelector('.search-input'); const searchResults = document.getElementById('search-results'); + const searchContainer = document.querySelector('.search-container'); let allTracks = []; const genres = ['metal', 'altrock', 'rap', 'lofi', 'edm', 'cuts']; @@ -471,10 +587,9 @@ } } - // Initialize tracks on page load fetchAllTracks(); - // Debounce function to limit search frequency + // Debounce function function debounce(func, wait) { let timeout; return function executedFunction(...args) { @@ -507,6 +622,7 @@ filteredTracks.forEach(track => { const resultItem = document.createElement('div'); resultItem.className = 'search-result-item'; + resultItem.tabIndex = 0; resultItem.innerHTML = ` ${track.title} ${track.genre.charAt(0).toUpperCase() + track.genre.slice(1)} @@ -514,29 +630,84 @@ resultItem.addEventListener('click', () => { window.location.href = `/${track.genre}/track/${track.slug}`; }); + resultItem.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + window.location.href = `/${track.genre}/track/${track.slug}`; + } + }); searchResults.appendChild(resultItem); }); } searchResults.classList.add('show'); + updateSelectedResult(); }, 300); - // Search input event listener - searchInput.addEventListener('input', performSearch); + // Keyboard navigation for search results + function updateSelectedResult() { + const items = searchResults.querySelectorAll('.search-result-item'); + items.forEach((item, index) => { + item.addEventListener('mouseover', () => { + items.forEach(i => i.classList.remove('selected')); + item.classList.add('selected'); + selectedIndex = index; + }); + }); + } - // Hide search results when clicking outside - document.addEventListener('click', (e) => { - if (!searchContainer.contains(e.target)) { - searchResults.classList.remove('show'); + let selectedIndex = -1; + searchInput.addEventListener('keydown', (e) => { + const items = searchResults.querySelectorAll('.search-result-item'); + if (items.length === 0) return; + + if (e.key === 'ArrowDown') { + e.preventDefault(); + selectedIndex = Math.min(selectedIndex + 1, items.length - 1); + items.forEach(i => i.classList.remove('selected')); + items[selectedIndex].classList.add('selected'); + items[selectedIndex].scrollIntoView({ block: 'nearest' }); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + selectedIndex = Math.max(selectedIndex - 1, -1); + items.forEach(i => i.classList.remove('selected')); + if (selectedIndex >= 0) { + items[selectedIndex].classList.add('selected'); + items[selectedIndex].scrollIntoView({ block: 'nearest' }); + } + } else if (e.key === 'Enter' && selectedIndex >= 0) { + e.preventDefault(); + items[selectedIndex].click(); } }); - // Show search results when clicking input + searchInput.addEventListener('input', performSearch); searchInput.addEventListener('focus', () => { if (searchInput.value.trim().length >= 2) { performSearch(); } }); + + document.addEventListener('click', (e) => { + if (!searchContainer.contains(e.target)) { + searchResults.classList.remove('show'); + selectedIndex = -1; + } + }); + + // Back to top button + const backToTop = document.getElementById('back-to-top'); + window.addEventListener('scroll', () => { + if (window.scrollY > 300) { + backToTop.classList.add('show'); + } else { + backToTop.classList.remove('show'); + } + }); + + backToTop.addEventListener('click', (e) => { + e.preventDefault(); + window.scrollTo({ top: 0, behavior: 'smooth' }); + }); diff --git a/views/track.ejs b/views/track.ejs index af54804..c3d1aae 100644 --- a/views/track.ejs +++ b/views/track.ejs @@ -13,6 +13,9 @@ background-color: black; color: white; font-family: 'Metal Mania', sans-serif; + position: relative; + min-height: 100vh; + padding-bottom: 100px; } .card { @@ -20,20 +23,53 @@ border: 1px solid #444; border-radius: 8px; overflow: hidden; + transition: transform 0.3s ease; + } + + .card:hover { + transform: translateY(-5px); } .card-body { padding: 1.5rem; } + .btn { + background-color: #ff5500; + border-color: #ff5500; + color: white; + font-weight: bold; + transition: background-color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease; + } + + .btn:hover { + background-color: #ff3300; + border-color: #ff3300; + color: white; + } + + .btn:active, + .btn:focus { + background-color: #cc4400; + border-color: #cc4400; + box-shadow: 0 0 8px rgba(255, 85, 0, 0.5); + outline: none; + } + .btn-primary { background-color: #ff5500; border-color: #ff5500; - transition: background-color 0.3s ease; } .btn-primary:hover { background-color: #ff3300; + border-color: #ff3300; + } + + .btn-primary:active, + .btn-primary:focus { + background-color: #cc4400; + border-color: #cc4400; } ::-webkit-scrollbar { @@ -59,7 +95,6 @@ scrollbar-color: #4a4a4a #1e1e1e; } - /* Header styling */ .site-header { color: #ff5500; font-size: 2.5rem; @@ -86,7 +121,6 @@ color: #ff3300; } - /* Search box styling */ .search-container { position: relative; width: 100%; @@ -136,7 +170,8 @@ transition: background-color 0.2s ease; } - .search-result-item:hover { + .search-result-item:hover, + .search-result-item.selected { background-color: #ff5500; } @@ -145,11 +180,68 @@ display: block; } - /* Adjust container padding */ .container { padding-top: 30px; padding-bottom: 30px; } + + footer { + background-color: #1e1e1e; + color: #aaa; + padding: 2rem 0; + text-align: center; + position: absolute; + bottom: 0; + width: 100%; + border-top: 2px solid #ff5500; + } + + footer a { + color: #ff5500; + text-decoration: none; + transition: color 0.3s ease; + } + + footer a:hover { + color: #ff3300; + } + + .back-to-top { + position: fixed; + bottom: 20px; + right: 20px; + background-color: #ff5500; + color: white; + width: 40px; + height: 40px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + text-decoration: none; + opacity: 0; + transition: opacity 0.3s ease, background-color 0.3s ease; + z-index: 1000; + } + + .back-to-top.show { + opacity: 1; + } + + .back-to-top:hover { + background-color: #ff3300; + } + + .share-button { + background-color: #ff5500; + border-color: #ff5500; + margin-left: 10px; + } + + .share-button:hover { + background-color: #ff3300; + border-color: #ff3300; + } @@ -157,7 +249,7 @@

Raven Scott Music

- +
@@ -171,13 +263,25 @@

<%- (track.description && track.description.trim()) ? track.description.replace(/\n/g, '
' ) : 'No description available for this track.' %> -
- Back

+
+ Back + +
+ + + + + + + + @@ -190,10 +294,30 @@ backButton.href = `/${genre}`; console.log(`Set back button href to /${genre}`); } else { - backButton.href = '/'; // Fallback to home page + backButton.href = '/'; console.warn('Genre not detected in URL, falling back to /'); } + // Share button functionality + const shareButton = document.getElementById('share-button'); + shareButton.addEventListener('click', () => { + const shareData = { + title: `<%= track.title %>`, + text: `Check out this track by Raven Scott Music!`, + url: window.location.href + }; + if (navigator.share) { + navigator.share(shareData) + .then(() => console.log('Track shared successfully')) + .catch(err => console.error('Error sharing track:', err)); + } else { + // Fallback: Copy URL to clipboard + navigator.clipboard.writeText(window.location.href) + .then(() => alert('Track URL copied to clipboard!')) + .catch(err => console.error('Error copying URL:', err)); + } + }); + // Search functionality const searchInput = document.querySelector('.search-input'); const searchResults = document.getElementById('search-results'); @@ -217,10 +341,9 @@ } } - // Initialize tracks on page load fetchAllTracks(); - // Debounce function to limit search frequency + // Debounce function function debounce(func, wait) { let timeout; return function executedFunction(...args) { @@ -253,6 +376,7 @@ filteredTracks.forEach(track => { const resultItem = document.createElement('div'); resultItem.className = 'search-result-item'; + resultItem.tabIndex = 0; resultItem.innerHTML = ` ${track.title} ${track.genre.charAt(0).toUpperCase() + track.genre.slice(1)} @@ -260,29 +384,84 @@ resultItem.addEventListener('click', () => { window.location.href = `/${track.genre}/track/${track.slug}`; }); + resultItem.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + window.location.href = `/${track.genre}/track/${track.slug}`; + } + }); searchResults.appendChild(resultItem); }); } searchResults.classList.add('show'); + updateSelectedResult(); }, 300); - // Search input event listener - searchInput.addEventListener('input', performSearch); + // Keyboard navigation for search results + function updateSelectedResult() { + const items = searchResults.querySelectorAll('.search-result-item'); + items.forEach((item, index) => { + item.addEventListener('mouseover', () => { + items.forEach(i => i.classList.remove('selected')); + item.classList.add('selected'); + selectedIndex = index; + }); + }); + } - // Hide search results when clicking outside - document.addEventListener('click', (e) => { - if (!searchContainer.contains(e.target)) { - searchResults.classList.remove('show'); + let selectedIndex = -1; + searchInput.addEventListener('keydown', (e) => { + const items = searchResults.querySelectorAll('.search-result-item'); + if (items.length === 0) return; + + if (e.key === 'ArrowDown') { + e.preventDefault(); + selectedIndex = Math.min(selectedIndex + 1, items.length - 1); + items.forEach(i => i.classList.remove('selected')); + items[selectedIndex].classList.add('selected'); + items[selectedIndex].scrollIntoView({ block: 'nearest' }); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + selectedIndex = Math.max(selectedIndex - 1, -1); + items.forEach(i => i.classList.remove('selected')); + if (selectedIndex >= 0) { + items[selectedIndex].classList.add('selected'); + items[selectedIndex].scrollIntoView({ block: 'nearest' }); + } + } else if (e.key === 'Enter' && selectedIndex >= 0) { + e.preventDefault(); + items[selectedIndex].click(); } }); - // Show search results when clicking input + searchInput.addEventListener('input', performSearch); searchInput.addEventListener('focus', () => { if (searchInput.value.trim().length >= 2) { performSearch(); } }); + + document.addEventListener('click', (e) => { + if (!searchContainer.contains(e.target)) { + searchResults.classList.remove('show'); + selectedIndex = -1; + } + }); + + // Back to top button + const backToTop = document.getElementById('back-to-top'); + window.addEventListener('scroll', () => { + if (window.scrollY > 300) { + backToTop.classList.add('show'); + } else { + backToTop.classList.remove('show'); + } + }); + + backToTop.addEventListener('click', (e) => { + e.preventDefault(); + window.scrollTo({ top: 0, behavior: 'smooth' }); + });