This commit is contained in:
2025-06-22 19:34:42 -04:00
parent 8523b14aae
commit 0a0eaeb712
2 changed files with 416 additions and 66 deletions

View File

@ -11,6 +11,9 @@
background-color: black; background-color: black;
color: white; color: white;
font-family: 'Metal Mania', sans-serif; font-family: 'Metal Mania', sans-serif;
position: relative;
min-height: 100vh;
padding-bottom: 100px;
} }
.card { .card {
@ -18,20 +21,69 @@
border: 1px solid #444; border: 1px solid #444;
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
transition: transform 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
} }
.card-body { .card-body {
padding: 1.5rem; 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 { .btn-primary {
background-color: #ff5500; background-color: #ff5500;
border-color: #ff5500; border-color: #ff5500;
transition: background-color 0.3s ease;
} }
.btn-primary:hover { .btn-primary:hover {
background-color: #ff3300; 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 { .pagination-btn {
@ -154,35 +206,6 @@
margin-left: -15px; 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 { .search-container {
position: relative; position: relative;
width: 100%; width: 100%;
@ -231,7 +254,8 @@
transition: background-color 0.2s ease; transition: background-color 0.2s ease;
} }
.search-result-item:hover { .search-result-item:hover,
.search-result-item.selected {
background-color: #ff5500; background-color: #ff5500;
} }
@ -239,6 +263,73 @@
color: #aaa; color: #aaa;
display: block; 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> </style>
</head> </head>
@ -260,7 +351,7 @@
<% } %> <% } %>
</ul> </ul>
<div class="search-container ml-auto"> <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 class="search-results" id="search-results"></div>
</div> </div>
</div> </div>
@ -306,10 +397,21 @@
<button class="btn btn-primary pagination-btn" id="<%= genre %>-next" <button class="btn btn-primary pagination-btn" id="<%= genre %>-next"
data-page="<%= genreTracks[genre].page + 1 %>" data-page="<%= genreTracks[genre].page + 1 %>"
<%=genreTracks[genre].page===genreTracks[genre].totalPages ? 'disabled' : '' %>>Next</button> <%=genreTracks[genre].page===genreTracks[genre].totalPages ? 'disabled' : '' %>>Next</button>
<div class="loading-spinner" id="<%= genre %>-spinner"></div>
</div> </div>
</section> </section>
<% } %> <% } %>
</div> </div>
<!--
<footer>
<p>&copy; 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://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://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js"></script>
@ -319,16 +421,21 @@
// Lazy load iframes // Lazy load iframes
function lazyLoadIframes(container = document) { function lazyLoadIframes(container = document) {
const iframes = container.querySelectorAll('iframe[data-src]'); const iframes = container.querySelectorAll('iframe[data-src]');
iframes.forEach(iframe => { const observer = new IntersectionObserver((entries) => {
if (!iframe.src && iframe.getBoundingClientRect().top < window.innerHeight) { entries.forEach(entry => {
iframe.src = iframe.getAttribute('data-src'); if (entry.isIntersecting) {
console.log(`Lazy loading iframe for track: ${iframe.closest('[data-slug]').dataset.slug}`); 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('load', () => lazyLoadIframes());
window.addEventListener('scroll', () => lazyLoadIframes());
// Page cache: { genre: { page: { container, totalPages } } } // Page cache: { genre: { page: { container, totalPages } } }
const pageCache = {}; const pageCache = {};
@ -346,6 +453,8 @@
socket.on('page_data', ({ genre, tracks, page, totalPages }) => { socket.on('page_data', ({ genre, tracks, page, totalPages }) => {
console.log(`Received page_data for genre: ${genre}, page: ${page}, tracks: ${tracks.length}`); console.log(`Received page_data for genre: ${genre}, page: ${page}, tracks: ${tracks.length}`);
const trackContainer = document.getElementById(`${genre}-tracks`); const trackContainer = document.getElementById(`${genre}-tracks`);
const spinner = document.getElementById(`${genre}-spinner`);
spinner.classList.remove('show');
if (!pageCache[genre]) { if (!pageCache[genre]) {
pageCache[genre] = {}; pageCache[genre] = {};
@ -408,15 +517,18 @@
socket.on('error', ({ message }) => { socket.on('error', ({ message }) => {
console.error('WebSocket error:', message); console.error('WebSocket error:', message);
document.querySelectorAll('.loading-spinner').forEach(spinner => spinner.classList.remove('show'));
}); });
document.querySelectorAll('.pagination-btn').forEach(button => { document.querySelectorAll('.pagination-btn').forEach(button => {
button.addEventListener('click', () => { button.addEventListener('click', () => {
const genre = button.id.split('-')[0]; const genre = button.id.split('-')[0];
const page = parseInt(button.dataset.page, 10); const page = parseInt(button.dataset.page, 10);
const spinner = document.getElementById(`${genre}-spinner`);
console.log(`Button clicked: ${button.id}, genre: ${genre}, page: ${page}`); console.log(`Button clicked: ${button.id}, genre: ${genre}, page: ${page}`);
if (page > 0 && page !== currentPages[genre]) { if (page > 0 && page !== currentPages[genre]) {
spinner.classList.add('show');
if (pageCache[genre] && pageCache[genre][page]) { if (pageCache[genre] && pageCache[genre][page]) {
console.log(`Loading cached page ${page} for genre: ${genre}`); console.log(`Loading cached page ${page} for genre: ${genre}`);
const trackContainer = document.getElementById(`${genre}-tracks`); const trackContainer = document.getElementById(`${genre}-tracks`);
@ -437,14 +549,17 @@
currentPages[genre] = page; currentPages[genre] = page;
lazyLoadIframes(pageCache[genre][page].container); lazyLoadIframes(pageCache[genre][page].container);
spinner.classList.remove('show');
} else { } else {
socket.emit('request_page', { genre, page }); socket.emit('request_page', { genre, page });
console.log(`Emitted request_page for genre: ${genre}, page: ${page}`); console.log(`Emitted request_page for genre: ${genre}, page: ${page}`);
} }
} else if (page <= 0) { } else if (page <= 0) {
console.error(`Invalid page number: ${page}`); console.error(`Invalid page number: ${page}`);
spinner.classList.remove('show');
} else { } else {
console.log(`Already on page ${page} for genre: ${genre}`); console.log(`Already on page ${page} for genre: ${genre}`);
spinner.classList.remove('show');
} }
}); });
}); });
@ -452,6 +567,7 @@
// Search functionality // Search functionality
const searchInput = document.querySelector('.search-input'); const searchInput = document.querySelector('.search-input');
const searchResults = document.getElementById('search-results'); const searchResults = document.getElementById('search-results');
const searchContainer = document.querySelector('.search-container');
let allTracks = []; let allTracks = [];
const genres = ['metal', 'altrock', 'rap', 'lofi', 'edm', 'cuts']; const genres = ['metal', 'altrock', 'rap', 'lofi', 'edm', 'cuts'];
@ -471,10 +587,9 @@
} }
} }
// Initialize tracks on page load
fetchAllTracks(); fetchAllTracks();
// Debounce function to limit search frequency // Debounce function
function debounce(func, wait) { function debounce(func, wait) {
let timeout; let timeout;
return function executedFunction(...args) { return function executedFunction(...args) {
@ -507,6 +622,7 @@
filteredTracks.forEach(track => { filteredTracks.forEach(track => {
const resultItem = document.createElement('div'); const resultItem = document.createElement('div');
resultItem.className = 'search-result-item'; resultItem.className = 'search-result-item';
resultItem.tabIndex = 0;
resultItem.innerHTML = ` resultItem.innerHTML = `
${track.title} ${track.title}
<small>${track.genre.charAt(0).toUpperCase() + track.genre.slice(1)}</small> <small>${track.genre.charAt(0).toUpperCase() + track.genre.slice(1)}</small>
@ -514,29 +630,84 @@
resultItem.addEventListener('click', () => { resultItem.addEventListener('click', () => {
window.location.href = `/${track.genre}/track/${track.slug}`; 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.appendChild(resultItem);
}); });
} }
searchResults.classList.add('show'); searchResults.classList.add('show');
updateSelectedResult();
}, 300); }, 300);
// Search input event listener // Keyboard navigation for search results
searchInput.addEventListener('input', performSearch); 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 let selectedIndex = -1;
document.addEventListener('click', (e) => { searchInput.addEventListener('keydown', (e) => {
if (!searchContainer.contains(e.target)) { const items = searchResults.querySelectorAll('.search-result-item');
searchResults.classList.remove('show'); 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', () => { searchInput.addEventListener('focus', () => {
if (searchInput.value.trim().length >= 2) { if (searchInput.value.trim().length >= 2) {
performSearch(); 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> </script>
</body> </body>

View File

@ -13,6 +13,9 @@
background-color: black; background-color: black;
color: white; color: white;
font-family: 'Metal Mania', sans-serif; font-family: 'Metal Mania', sans-serif;
position: relative;
min-height: 100vh;
padding-bottom: 100px;
} }
.card { .card {
@ -20,20 +23,53 @@
border: 1px solid #444; border: 1px solid #444;
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
transition: transform 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
} }
.card-body { .card-body {
padding: 1.5rem; 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 { .btn-primary {
background-color: #ff5500; background-color: #ff5500;
border-color: #ff5500; border-color: #ff5500;
transition: background-color 0.3s ease;
} }
.btn-primary:hover { .btn-primary:hover {
background-color: #ff3300; background-color: #ff3300;
border-color: #ff3300;
}
.btn-primary:active,
.btn-primary:focus {
background-color: #cc4400;
border-color: #cc4400;
} }
::-webkit-scrollbar { ::-webkit-scrollbar {
@ -59,7 +95,6 @@
scrollbar-color: #4a4a4a #1e1e1e; scrollbar-color: #4a4a4a #1e1e1e;
} }
/* Header styling */
.site-header { .site-header {
color: #ff5500; color: #ff5500;
font-size: 2.5rem; font-size: 2.5rem;
@ -86,7 +121,6 @@
color: #ff3300; color: #ff3300;
} }
/* Search box styling */
.search-container { .search-container {
position: relative; position: relative;
width: 100%; width: 100%;
@ -136,7 +170,8 @@
transition: background-color 0.2s ease; transition: background-color 0.2s ease;
} }
.search-result-item:hover { .search-result-item:hover,
.search-result-item.selected {
background-color: #ff5500; background-color: #ff5500;
} }
@ -145,11 +180,68 @@
display: block; display: block;
} }
/* Adjust container padding */
.container { .container {
padding-top: 30px; padding-top: 30px;
padding-bottom: 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> </style>
</head> </head>
@ -157,7 +249,7 @@
<div class="container mt-5"> <div class="container mt-5">
<h1 class="site-header"><a href="/">Raven Scott Music</a></h1> <h1 class="site-header"><a href="/">Raven Scott Music</a></h1>
<div class="search-container"> <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 class="search-results" id="search-results"></div>
</div> </div>
<div class="card text-center"> <div class="card text-center">
@ -171,13 +263,25 @@
<p class="card-text"> <p class="card-text">
<%- (track.description && track.description.trim()) ? track.description.replace(/\n/g, '<br>' ) <%- (track.description && track.description.trim()) ? track.description.replace(/\n/g, '<br>' )
: 'No description available for this track.' %> : 'No description available for this track.' %>
<br>
<a id="back-button" class="btn btn-primary mt-3">Back</a>
</p> </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> </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://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://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> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
@ -190,10 +294,30 @@
backButton.href = `/${genre}`; backButton.href = `/${genre}`;
console.log(`Set back button href to /${genre}`); console.log(`Set back button href to /${genre}`);
} else { } else {
backButton.href = '/'; // Fallback to home page backButton.href = '/';
console.warn('Genre not detected in URL, falling back to /'); 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 // Search functionality
const searchInput = document.querySelector('.search-input'); const searchInput = document.querySelector('.search-input');
const searchResults = document.getElementById('search-results'); const searchResults = document.getElementById('search-results');
@ -217,10 +341,9 @@
} }
} }
// Initialize tracks on page load
fetchAllTracks(); fetchAllTracks();
// Debounce function to limit search frequency // Debounce function
function debounce(func, wait) { function debounce(func, wait) {
let timeout; let timeout;
return function executedFunction(...args) { return function executedFunction(...args) {
@ -253,6 +376,7 @@
filteredTracks.forEach(track => { filteredTracks.forEach(track => {
const resultItem = document.createElement('div'); const resultItem = document.createElement('div');
resultItem.className = 'search-result-item'; resultItem.className = 'search-result-item';
resultItem.tabIndex = 0;
resultItem.innerHTML = ` resultItem.innerHTML = `
${track.title} ${track.title}
<small>${track.genre.charAt(0).toUpperCase() + track.genre.slice(1)}</small> <small>${track.genre.charAt(0).toUpperCase() + track.genre.slice(1)}</small>
@ -260,29 +384,84 @@
resultItem.addEventListener('click', () => { resultItem.addEventListener('click', () => {
window.location.href = `/${track.genre}/track/${track.slug}`; 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.appendChild(resultItem);
}); });
} }
searchResults.classList.add('show'); searchResults.classList.add('show');
updateSelectedResult();
}, 300); }, 300);
// Search input event listener // Keyboard navigation for search results
searchInput.addEventListener('input', performSearch); 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 let selectedIndex = -1;
document.addEventListener('click', (e) => { searchInput.addEventListener('keydown', (e) => {
if (!searchContainer.contains(e.target)) { const items = searchResults.querySelectorAll('.search-result-item');
searchResults.classList.remove('show'); 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', () => { searchInput.addEventListener('focus', () => {
if (searchInput.value.trim().length >= 2) { if (searchInput.value.trim().length >= 2) {
performSearch(); 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> </script>
</body> </body>