update
This commit is contained in:
267
views/index.ejs
267
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;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@ -260,7 +351,7 @@
|
||||
<% } %>
|
||||
</ul>
|
||||
<div class="search-container ml-auto">
|
||||
<input type="text" class="search-input" placeholder="Search tracks or lyrics..." aria-label="Search tracks">
|
||||
<input type="text" class="search-input" placeholder="Search tracks or lyrics..." aria-label="Search tracks" autofocus>
|
||||
<div class="search-results" id="search-results"></div>
|
||||
</div>
|
||||
</div>
|
||||
@ -306,10 +397,21 @@
|
||||
<button class="btn btn-primary pagination-btn" id="<%= genre %>-next"
|
||||
data-page="<%= genreTracks[genre].page + 1 %>"
|
||||
<%=genreTracks[genre].page===genreTracks[genre].totalPages ? 'disabled' : '' %>>Next</button>
|
||||
<div class="loading-spinner" id="<%= genre %>-spinner"></div>
|
||||
</div>
|
||||
</section>
|
||||
<% } %>
|
||||
</div>
|
||||
<!--
|
||||
<footer>
|
||||
<p>© 2025 Raven Scott Music. All rights reserved.</p>
|
||||
</footer> -->
|
||||
|
||||
<a href="#" class="back-to-top" id="back-to-top" title="Back to Top">
|
||||
<svg width="24" height="24" fill="white" viewBox="0 0 24 24">
|
||||
<path d="M12 2L3 11h6v10h6V11h6z"/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js"></script>
|
||||
@ -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}
|
||||
<small>${track.genre.charAt(0).toUpperCase() + track.genre.slice(1)}</small>
|
||||
@ -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' });
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
215
views/track.ejs
215
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;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@ -157,7 +249,7 @@
|
||||
<div class="container mt-5">
|
||||
<h1 class="site-header"><a href="/">Raven Scott Music</a></h1>
|
||||
<div class="search-container">
|
||||
<input type="text" class="search-input" placeholder="Search tracks..." aria-label="Search tracks">
|
||||
<input type="text" class="search-input" placeholder="Search tracks..." aria-label="Search tracks" autofocus>
|
||||
<div class="search-results" id="search-results"></div>
|
||||
</div>
|
||||
<div class="card text-center">
|
||||
@ -171,13 +263,25 @@
|
||||
<p class="card-text">
|
||||
<%- (track.description && track.description.trim()) ? track.description.replace(/\n/g, '<br>' )
|
||||
: 'No description available for this track.' %>
|
||||
<br>
|
||||
<a id="back-button" class="btn btn-primary mt-3">Back</a>
|
||||
</p>
|
||||
<div class="mt-3">
|
||||
<a id="back-button" class="btn btn-primary">Back</a>
|
||||
<button class="btn share-button" id="share-button">Share</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <footer>
|
||||
<p>© 2025 Raven Scott Music. All rights reserved.</p>
|
||||
</footer> -->
|
||||
|
||||
<a href="#" class="back-to-top" id="back-to-top" title="Back to Top">
|
||||
<svg width="24" height="24" fill="white" viewBox="0 0 24 24">
|
||||
<path d="M12 2L3 11h6v10h6V11h6z"/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
|
||||
@ -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}
|
||||
<small>${track.genre.charAt(0).toUpperCase() + track.genre.slice(1)}</small>
|
||||
@ -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' });
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
Reference in New Issue
Block a user