Files
malabar/webnode/views/episode.ejs
2026-03-03 18:03:59 +08:00

186 lines
7.4 KiB
Plaintext

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title><%= currentEpisode.title %></title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<!-- Navbar -->
<nav class="navbar">
<button class="nav-btn" id="translateBtn" title="Toggle Translation">
<svg viewBox="0 0 24 24" fill="none" stroke-width="2">
<path d="M12 20h9"/>
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/>
</svg>
</button>
<span class="nav-title"><%= currentEpisode.title %></span>
<button class="nav-btn" id="menuBtn" title="Episodes">
<svg viewBox="0 0 24 24" fill="none" stroke-width="2">
<path d="M3 12h18"/>
<path d="M3 6h18"/>
<path d="M3 18h18"/>
</svg>
</button>
</nav>
<!-- Main Content -->
<main class="main-content">
<div class="lines-container">
<% if (lines && lines.length > 0) { %>
<% lines.forEach(line => { %>
<div class="line"
data-chinese="<%= escapeHtml(line.chinese) %>"
data-timestamp="<%= escapeHtml(line.timestamp) %>"
data-speaker="<%= escapeHtml(line.speaker) %>">
<div class="english" style="color: <%= colors[line.speaker] || '#777777' %>">
<%= line.english %>
</div>
</div>
<% }) %>
<% } else { %>
<div class="empty-state">
<p>No lines found in this episode.</p>
</div>
<% } %>
</div>
</main>
<!-- Side Menu Overlay -->
<div class="side-menu-overlay" id="sideMenuOverlay"></div>
<!-- Side Menu -->
<aside class="side-menu" id="sideMenu">
<div class="side-menu-header">
<span class="side-menu-title">Episodes</span>
<button class="close-btn" id="closeMenuBtn">
<svg viewBox="0 0 24 24" fill="none" stroke-width="2">
<path d="M18 6L6 18"/>
<path d="M6 6l12 12"/>
</svg>
</button>
</div>
<div class="episode-list">
<% episodes.forEach(ep => { %>
<a href="/episode/<%= ep.id %>"
class="episode-item <%= currentEpisode.id === ep.id ? 'active' : '' %>">
<span class="episode-id"><%= ep.title %></span>
</a>
<% }) %>
</div>
</aside>
<!-- Minimal JavaScript for interactions -->
<script>
(function() {
// DOM Elements
const translateBtn = document.getElementById('translateBtn');
const menuBtn = document.getElementById('menuBtn');
const closeMenuBtn = document.getElementById('closeMenuBtn');
const sideMenu = document.getElementById('sideMenu');
const sideMenuOverlay = document.getElementById('sideMenuOverlay');
const lines = document.querySelectorAll('.line');
let selectedLine = null;
let expandedLine = null;
// Line selection
lines.forEach(line => {
line.addEventListener('click', function(e) {
// Remove selected class from all lines
lines.forEach(l => l.classList.remove('selected'));
// Add selected class to clicked line
this.classList.add('selected');
selectedLine = this;
});
});
// Toggle translation on selected line
translateBtn.addEventListener('click', function() {
if (!selectedLine) {
// If no line selected, select the first one
if (lines.length > 0) {
lines[0].classList.add('selected');
selectedLine = lines[0];
}
}
if (selectedLine) {
const translationCard = selectedLine.querySelector('.translation-card');
if (translationCard) {
// If already expanded, collapse it
translationCard.remove();
expandedLine = null;
} else {
// Collapse any previously expanded line
if (expandedLine) {
const prevCard = expandedLine.querySelector('.translation-card');
if (prevCard) prevCard.remove();
}
// Expand the selected line
const chineseText = selectedLine.dataset.chinese;
const timestamp = selectedLine.dataset.timestamp;
const speaker = selectedLine.dataset.speaker;
if (chineseText) {
const card = document.createElement('div');
card.className = 'translation-card';
card.innerHTML = `
<div class="meta-info">
<span class="meta-timestamp">${escapeHtml(timestamp)}</span>
<span class="meta-speaker">${escapeHtml(speaker)}</span>
</div>
<div class="translation-label">中文</div>
<div class="chinese">${escapeHtml(chineseText)}</div>
`;
selectedLine.appendChild(card);
expandedLine = selectedLine;
// Scroll the expanded card into view smoothly
setTimeout(() => {
card.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}, 100);
}
}
}
});
// Open side menu
function openMenu() {
sideMenu.classList.add('active');
sideMenuOverlay.classList.add('active');
document.body.style.overflow = 'hidden';
}
// Close side menu
function closeMenu() {
sideMenu.classList.remove('active');
sideMenuOverlay.classList.remove('active');
document.body.style.overflow = '';
}
menuBtn.addEventListener('click', openMenu);
closeMenuBtn.addEventListener('click', closeMenu);
sideMenuOverlay.addEventListener('click', closeMenu);
// Helper: Escape HTML to prevent XSS
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Close menu on escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeMenu();
}
});
})();
</script>
</body>
</html>