186 lines
7.4 KiB
Plaintext
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>
|