Files
ravenscott-rocks/views/index.ejs
2025-06-22 02:59:44 -04:00

368 lines
14 KiB
Plaintext

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Great Scott Music</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<style>
body {
background-color: black;
color: white;
font-family: 'Metal Mania', sans-serif;
}
.card {
background-color: #222;
border: 1px solid #444;
border-radius: 8px;
overflow: hidden;
}
.card-body {
padding: 1.5rem;
}
.btn-primary {
background-color: #ff5500;
border-color: #ff5500;
transition: background-color 0.3s ease;
}
.btn-primary:hover {
background-color: #ff3300;
}
.pagination-btn {
margin: 0 10px;
}
.navbar {
background-color: #1e1e1e;
padding: 1rem 2rem;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
border-bottom: 2px solid #ff5500;
}
.navbar-brand {
color: #ff5500 !important;
font-size: 1.8rem;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 2px;
transition: color 0.3s ease;
}
.navbar-brand:hover {
color: #ff3300 !important;
}
.navbar-nav .nav-item {
margin: 0 1rem;
}
.nav-link {
color: white !important;
font-size: 1.2rem;
text-transform: uppercase;
letter-spacing: 1px;
padding: 0.5rem 1rem !important;
border-radius: 5px;
transition: all 0.3s ease;
position: relative;
}
.nav-link:hover {
color: #ff5500 !important;
background-color: rgba(255, 85, 0, 0.1);
}
.nav-link::after {
content: '';
position: absolute;
width: 0;
height: 2px;
bottom: 0;
left: 50%;
background-color: #ff5500;
transition: all 0.3s ease;
transform: translateX(-50%);
}
.nav-link:hover::after {
width: 80%;
}
.navbar-toggler {
border-color: #ff5500;
}
.navbar-toggler-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23ff5500' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
}
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #1e1e1e;
}
::-webkit-scrollbar-thumb {
background-color: #4a4a4a;
border-radius: 10px;
border: 2px solid #1e1e1e;
}
::-webkit-scrollbar-thumb:hover {
background-color: #555555;
}
* {
scrollbar-width: thin;
scrollbar-color: #4a4a4a #1e1e1e;
}
section {
padding-top: 90px;
margin-top: -90px;
}
/* Page container styling */
.page-container {
display: none;
width: 100%; /* Ensure full width for grid */
}
.page-container.active {
display: block;
}
/* Iframe styling */
iframe {
width: 100%;
height: 166px;
max-width: 100%;
border: none;
border-radius: 4px;
}
/* Ensure row and columns work correctly */
.page-container .row {
display: flex;
flex-wrap: wrap;
margin-right: -15px;
margin-left: -15px;
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark fixed-top">
<a class="navbar-brand" href="https://raven-scott.rocks">Raven Scott Music</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ml-auto">
<% for (const genre in genreTracks) { %>
<li class="nav-item">
<a class="nav-link" href="#<%= genre %>">
<%= genre.charAt(0).toUpperCase() + genre.slice(1) %>
</a>
</li>
<% } %>
</ul>
</div>
</nav>
<div class="container mt-5 pt-5">
<% for (const genre in genreTracks) { %>
<section id="<%= genre %>">
<h2 class="text-center mb-4">
<%= genre.charAt(0).toUpperCase() + genre.slice(1) %> Tracks
</h2>
<div id="<%= genre %>-tracks">
<div class="page-container active" data-page="1" data-total-pages="<%= genreTracks[genre].totalPages %>">
<div class="row">
<% genreTracks[genre].tracks.forEach(track => { %>
<div class="col-md-6 mb-4" data-slug="<%= track.slug %>">
<div class="card text-center">
<div class="card-body">
<h5 class="card-title">
<%= track.title %>
</h5>
<p class="card-text"></p>
<iframe width="100%" height="166" scrolling="no" frameborder="no" allow="autoplay"
data-src="https://w.soundcloud.com/player/?url=<%= track.url %>&color=%23ff5500&auto_play=false&show_artwork=true"
loading="lazy">
</iframe>
<a href="/<%= genre %>/track/<%= track.slug %>" class="btn btn-primary mt-3" target="_blank">More Details</a>
</div>
</div>
</div>
<% }) %>
</div>
</div>
</div>
<div class="text-center mb-4">
<button class="btn btn-primary pagination-btn" id="<%= genre %>-prev" data-page="<%= genreTracks[genre].page - 1 %>" <%= genreTracks[genre].page === 1 ? 'disabled' : '' %>>Previous</button>
<span>Page <%= genreTracks[genre].page %> of <%= genreTracks[genre].totalPages %></span>
<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>
</section>
<% } %>
</div>
<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>
<script src="https://cdn.socket.io/4.5.0/socket.io.min.js"></script>
<script>
// 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}`);
}
});
}
window.addEventListener('load', () => lazyLoadIframes());
window.addEventListener('scroll', () => lazyLoadIframes());
// Page cache: { genre: { page: { container, totalPages } } }
const pageCache = {};
// Track current page per genre
const currentPages = {};
// WebSocket client
const socket = io();
socket.on('connect', () => {
console.log('Connected to WebSocket server');
});
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`);
// Initialize cache for genre
if (!pageCache[genre]) {
pageCache[genre] = {};
}
// Update current page
currentPages[genre] = page;
// Create new page if not cached
if (!pageCache[genre][page]) {
console.log(`Creating new page container for genre: ${genre}, page: ${page}`);
const pageContainer = document.createElement('div');
pageContainer.className = 'page-container';
pageContainer.dataset.page = page;
pageContainer.dataset.totalPages = totalPages;
// Create row for grid
const row = document.createElement('div');
row.className = 'row';
// Build track elements
tracks.forEach(track => {
const trackDiv = document.createElement('div');
trackDiv.className = 'col-md-6 mb-4';
trackDiv.dataset.slug = track.slug;
trackDiv.innerHTML = `
<div class="card text-center">
<div class="card-body">
<h5 class="card-title">${track.title}</h5>
<p class="card-text"></p>
<iframe width="100%" height="166" scrolling="no" frameborder="no" allow="autoplay"
data-src="https://w.soundcloud.com/player/?url=${track.url}&color=%23ff5500&auto_play=false&show_artwork=true"
loading="lazy"></iframe>
<a href="/${genre}/track/${track.slug}" class="btn btn-primary mt-3" target="_blank">More Details</a>
</div>
</div>
`;
row.appendChild(trackDiv);
});
pageContainer.appendChild(row);
trackContainer.appendChild(pageContainer);
pageCache[genre][page] = { container: pageContainer, totalPages };
lazyLoadIframes(pageContainer);
}
// Switch to requested page
const allPages = trackContainer.querySelectorAll('.page-container');
allPages.forEach(p => p.classList.remove('active'));
pageCache[genre][page].container.classList.add('active');
console.log(`Switched to page ${page} for genre: ${genre}`);
// Update pagination controls
const prevButton = document.getElementById(`${genre}-prev`);
const nextButton = document.getElementById(`${genre}-next`);
prevButton.disabled = page === 1;
nextButton.disabled = page === totalPages;
prevButton.dataset.page = page - 1;
nextButton.dataset.page = page + 1;
const pageInfo = document.querySelector(`#${genre}-tracks + .text-center span`);
pageInfo.textContent = `Page ${page} of ${totalPages}`;
lazyLoadIframes(pageCache[genre][page].container);
});
socket.on('error', ({ message }) => {
console.error('WebSocket error:', message);
});
// Pagination button handlers
document.querySelectorAll('.pagination-btn').forEach(button => {
button.addEventListener('click', () => {
const genre = button.id.split('-')[0];
const page = parseInt(button.dataset.page, 10);
console.log(`Button clicked: ${button.id}, genre: ${genre}, page: ${page}`);
if (page > 0 && page !== currentPages[genre]) {
if (pageCache[genre] && pageCache[genre][page]) {
console.log(`Loading cached page ${page} for genre: ${genre}`);
const trackContainer = document.getElementById(`${genre}-tracks`);
const allPages = trackContainer.querySelectorAll('.page-container');
allPages.forEach(p => p.classList.remove('active'));
pageCache[genre][page].container.classList.add('active');
// Update pagination controls
const prevButton = document.getElementById(`${genre}-prev`);
const nextButton = document.getElementById(`${genre}-next`);
const totalPages = parseInt(pageCache[genre][page].totalPages);
prevButton.disabled = page === 1;
nextButton.disabled = page === totalPages;
prevButton.dataset.page = page - 1;
nextButton.dataset.page = page + 1;
const pageInfo = document.querySelector(`#${genre}-tracks + .text-center span`);
pageInfo.textContent = `Page ${page} of ${totalPages}`;
currentPages[genre] = page;
lazyLoadIframes(pageCache[genre][page].container);
} 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}`);
} else {
console.log(`Already on page ${page} for genre: ${genre}`);
}
});
});
</script>
</body>
</html>