Support dark mode in the frontend

This commit is contained in:
2025-06-09 11:52:26 +09:00
parent 0438c620a8
commit d21c2356f3
3 changed files with 392 additions and 159 deletions

View File

@ -1,4 +1,5 @@
import { Link, Route, Routes, useLocation } from "react-router-dom"; import { Link, Route, Routes, useLocation } from "react-router-dom";
import { useEffect, useState } from "react";
import EpisodeDetail from "./components/EpisodeDetail"; import EpisodeDetail from "./components/EpisodeDetail";
import EpisodeList from "./components/EpisodeList"; import EpisodeList from "./components/EpisodeList";
import FeedDetail from "./components/FeedDetail"; import FeedDetail from "./components/FeedDetail";
@ -8,6 +9,19 @@ import RSSEndpoints from "./components/RSSEndpoints";
function App() { function App() {
const location = useLocation(); const location = useLocation();
const [isDarkMode, setIsDarkMode] = useState(() => {
const saved = localStorage.getItem('darkMode');
return saved ? JSON.parse(saved) : false;
});
useEffect(() => {
document.documentElement.setAttribute('data-theme', isDarkMode ? 'dark' : 'light');
localStorage.setItem('darkMode', JSON.stringify(isDarkMode));
}, [isDarkMode]);
const toggleDarkMode = () => {
setIsDarkMode(!isDarkMode);
};
const isMainPage = [ const isMainPage = [
"/", "/",
"/feeds", "/feeds",
@ -20,9 +34,20 @@ function App() {
{isMainPage && ( {isMainPage && (
<> <>
<div className="header"> <div className="header">
<div className="title">Voice RSS Summary</div> <div className="header-content">
<div className="subtitle"> <div className="header-text">
RSS <div className="title">Voice RSS Summary</div>
<div className="subtitle">
RSS
</div>
</div>
<button
className="theme-toggle"
onClick={toggleDarkMode}
aria-label="テーマを切り替え"
>
{isDarkMode ? '☀️' : '🌙'}
</button>
</div> </div>
</div> </div>

View File

@ -345,20 +345,9 @@ function EpisodeList() {
return ( return (
<div> <div>
<div <div style={{ marginBottom: "20px" }}>
style={{ <div className="episode-header">
marginBottom: "20px", <h2 className="episode-title">
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "15px",
}}
>
<h2>
( (
{searchQuery {searchQuery
? `検索結果: ${filteredEpisodes.length}` ? `検索結果: ${filteredEpisodes.length}`
@ -369,8 +358,8 @@ function EpisodeList() {
: `${episodes.length}`} : `${episodes.length}`}
) )
</h2> </h2>
<div style={{ display: "flex", gap: "10px", alignItems: "center" }}> <div className="episode-meta">
<span style={{ fontSize: "12px", color: "#666" }}> <span className="episode-meta-text">
: {useDatabase ? "データベース" : "XML"} : {useDatabase ? "データベース" : "XML"}
</span> </span>
<button className="btn btn-secondary" onClick={fetchEpisodes}> <button className="btn btn-secondary" onClick={fetchEpisodes}>
@ -379,27 +368,13 @@ function EpisodeList() {
</div> </div>
</div> </div>
<div <div className="episode-search-bar">
style={{
display: "flex",
gap: "10px",
alignItems: "center",
flexWrap: "wrap",
}}
>
<input <input
type="text" type="text"
placeholder="エピソードを検索..." placeholder="エピソードを検索..."
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
style={{ className="episode-search-input"
flex: "1",
minWidth: "200px",
padding: "8px 12px",
fontSize: "14px",
border: "1px solid #ccc",
borderRadius: "4px",
}}
/> />
{searchQuery && ( {searchQuery && (
<button <button
@ -417,13 +392,7 @@ function EpisodeList() {
<select <select
value={selectedCategory} value={selectedCategory}
onChange={(e) => setSelectedCategory(e.target.value)} onChange={(e) => setSelectedCategory(e.target.value)}
style={{ className="episode-category-select"
padding: "8px 12px",
fontSize: "14px",
border: "1px solid #ccc",
borderRadius: "4px",
minWidth: "120px",
}}
> >
<option value=""></option> <option value=""></option>
{categories.map((category) => ( {categories.map((category) => (
@ -434,7 +403,7 @@ function EpisodeList() {
</select> </select>
)} )}
{isSearching && ( {isSearching && (
<span style={{ fontSize: "14px", color: "#666" }}>...</span> <span className="episode-meta-text">...</span>
)} )}
</div> </div>
</div> </div>
@ -456,24 +425,18 @@ function EpisodeList() {
<strong> <strong>
<Link <Link
to={`/episode/${episode.id}`} to={`/episode/${episode.id}`}
style={{ textDecoration: "none", color: "#007bff" }} className="episode-link"
> >
{episode.title} {episode.title}
</Link> </Link>
</strong> </strong>
</div> </div>
{episode.feedTitle && ( {episode.feedTitle && (
<div <div className="episode-feed-info">
style={{
fontSize: "12px",
color: "#666",
marginBottom: "4px",
}}
>
:{" "} :{" "}
<Link <Link
to={`/feeds/${episode.feedId}`} to={`/feeds/${episode.feedId}`}
style={{ color: "#007bff" }} className="episode-link"
> >
{episode.feedTitle} {episode.feedTitle}
</Link> </Link>
@ -481,7 +444,7 @@ function EpisodeList() {
<span <span
style={{ style={{
marginLeft: "8px", marginLeft: "8px",
color: "#999", color: "var(--text-muted)",
fontSize: "11px", fontSize: "11px",
}} }}
> >
@ -492,13 +455,7 @@ function EpisodeList() {
)} )}
{episode.articleTitle && {episode.articleTitle &&
episode.articleTitle !== episode.title && ( episode.articleTitle !== episode.title && (
<div <div className="episode-article-info">
style={{
fontSize: "12px",
color: "#666",
marginBottom: "4px",
}}
>
: <strong>{episode.articleTitle}</strong> : <strong>{episode.articleTitle}</strong>
</div> </div>
)} )}
@ -507,46 +464,26 @@ function EpisodeList() {
href={episode.articleLink} href={episode.articleLink}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
style={{ fontSize: "12px", color: "#666" }} className="episode-article-link"
> >
</a> </a>
)} )}
</td> </td>
<td> <td>
<div <div className="episode-description">
style={{
fontSize: "14px",
maxWidth: "200px",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
{episode.description || "No description"} {episode.description || "No description"}
</div> </div>
{episode.fileSize && ( {episode.fileSize && (
<div <div className="episode-file-size">
style={{
fontSize: "12px",
color: "#666",
marginTop: "4px",
}}
>
{formatFileSize(episode.fileSize)} {formatFileSize(episode.fileSize)}
</div> </div>
)} )}
</td> </td>
<td>{formatDate(episode.createdAt)}</td> <td>{formatDate(episode.createdAt)}</td>
<td> <td>
<div <div className="episode-actions">
style={{ <div className="episode-action-buttons">
display: "flex",
gap: "8px",
flexDirection: "column",
}}
>
<div style={{ display: "flex", gap: "8px" }}>
<button <button
className="btn btn-primary" className="btn btn-primary"
onClick={() => onClick={() =>
@ -593,16 +530,7 @@ function EpisodeList() {
{/* Pagination Controls - only show for database mode */} {/* Pagination Controls - only show for database mode */}
{useDatabase && totalPages > 1 && ( {useDatabase && totalPages > 1 && (
<div <div className="pagination-container">
style={{
marginTop: "20px",
display: "flex",
justifyContent: "center",
alignItems: "center",
gap: "10px",
flexWrap: "wrap",
}}
>
<button <button
className="btn btn-secondary" className="btn btn-secondary"
onClick={() => setCurrentPage(Math.max(1, currentPage - 1))} onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
@ -612,7 +540,7 @@ function EpisodeList() {
</button> </button>
{/* Page numbers */} {/* Page numbers */}
<div style={{ display: "flex", gap: "5px", alignItems: "center" }}> <div className="pagination-pages">
{/* First page */} {/* First page */}
{currentPage > 3 && ( {currentPage > 3 && (
<> <>
@ -635,11 +563,8 @@ function EpisodeList() {
return ( return (
<button <button
key={pageNum} key={pageNum}
className={`btn ${currentPage === pageNum ? "btn-primary" : "btn-secondary"}`} className={`btn pagination-page-btn ${currentPage === pageNum ? "btn-primary" : "btn-secondary"}`}
onClick={() => setCurrentPage(pageNum)} onClick={() => setCurrentPage(pageNum)}
style={{
minWidth: "40px",
}}
> >
{pageNum} {pageNum}
</button> </button>
@ -669,7 +594,7 @@ function EpisodeList() {
</button> </button>
{/* Page size selector */} {/* Page size selector */}
<div style={{ marginLeft: "20px", display: "flex", alignItems: "center", gap: "5px" }}> <div className="pagination-size-selector">
<span style={{ fontSize: "14px" }}>:</span> <span style={{ fontSize: "14px" }}>:</span>
<select <select
value={pageSize} value={pageSize}
@ -677,12 +602,7 @@ function EpisodeList() {
setPageSize(Number.parseInt(e.target.value, 10)); setPageSize(Number.parseInt(e.target.value, 10));
setCurrentPage(1); // Reset to first page when changing page size setCurrentPage(1); // Reset to first page when changing page size
}} }}
style={{ className="pagination-size-select"
padding: "4px 8px",
fontSize: "14px",
border: "1px solid #ccc",
borderRadius: "4px",
}}
> >
<option value={10}>10</option> <option value={10}>10</option>
<option value={20}>20</option> <option value={20}>20</option>

View File

@ -1,3 +1,49 @@
:root {
/* Light theme colors */
--bg-primary: #f5f5f5;
--bg-secondary: #fff;
--bg-tertiary: #f8f9fa;
--text-primary: #333;
--text-secondary: #666;
--text-muted: #999;
--border-color: #e9ecef;
--border-light: #ddd;
--shadow: rgba(0, 0, 0, 0.1);
--accent-primary: #007bff;
--accent-primary-hover: #0056b3;
--accent-danger: #dc3545;
--accent-danger-hover: #c82333;
--accent-secondary: #6c757d;
--accent-secondary-hover: #545b62;
--success-bg: #d4edda;
--success-text: #155724;
--error-bg: #f8d7da;
--error-text: #721c24;
}
[data-theme="dark"] {
/* Dark theme colors */
--bg-primary: #1a1a1a;
--bg-secondary: #2d2d2d;
--bg-tertiary: #3a3a3a;
--text-primary: #e0e0e0;
--text-secondary: #b0b0b0;
--text-muted: #888;
--border-color: #444;
--border-light: #555;
--shadow: rgba(0, 0, 0, 0.3);
--accent-primary: #4dabf7;
--accent-primary-hover: #339af0;
--accent-danger: #f03e3e;
--accent-danger-hover: #e03131;
--accent-secondary: #868e96;
--accent-secondary-hover: #adb5bd;
--success-bg: #2b5a3b;
--success-text: #a3d9a5;
--error-bg: #5a2b2b;
--error-text: #f1aeb5;
}
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
@ -6,9 +52,10 @@
body { body {
font-family: system-ui, -apple-system, sans-serif; font-family: system-ui, -apple-system, sans-serif;
background-color: #f5f5f5; background-color: var(--bg-primary);
color: #333; color: var(--text-primary);
line-height: 1.6; line-height: 1.6;
transition: background-color 0.3s ease, color 0.3s ease;
} }
.container { .container {
@ -18,30 +65,65 @@ body {
} }
.header { .header {
background-color: #fff; background-color: var(--bg-secondary);
border-radius: 8px; border-radius: 8px;
padding: 20px; padding: 20px;
margin-bottom: 20px; margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px var(--shadow);
transition: background-color 0.3s ease, box-shadow 0.3s ease;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
gap: 20px;
}
.header-text {
flex: 1;
} }
.title { .title {
font-size: 24px; font-size: 24px;
font-weight: bold; font-weight: bold;
margin-bottom: 8px; margin-bottom: 8px;
color: var(--text-primary);
} }
.subtitle { .subtitle {
color: #666; color: var(--text-secondary);
font-size: 14px; font-size: 14px;
} }
.theme-toggle {
background: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: 50%;
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 20px;
transition: all 0.3s ease;
flex-shrink: 0;
}
.theme-toggle:hover {
transform: scale(1.1);
background: var(--accent-primary);
color: white;
}
.tabs { .tabs {
display: flex; display: flex;
background-color: #fff; background-color: var(--bg-secondary);
border-radius: 8px; border-radius: 8px;
margin-bottom: 20px; margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px var(--shadow);
transition: background-color 0.3s ease, box-shadow 0.3s ease;
} }
.tab { .tab {
@ -63,19 +145,20 @@ body {
} }
.tab.active { .tab.active {
background-color: #007bff; background-color: var(--accent-primary);
color: white; color: white;
} }
.tab:hover:not(.active) { .tab:hover:not(.active) {
background-color: #f8f9fa; background-color: var(--bg-tertiary);
} }
.content { .content {
background-color: #fff; background-color: var(--bg-secondary);
border-radius: 8px; border-radius: 8px;
padding: 20px; padding: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px var(--shadow);
transition: background-color 0.3s ease, box-shadow 0.3s ease;
} }
.table { .table {
@ -88,16 +171,20 @@ body {
.table td { .table td {
padding: 12px; padding: 12px;
text-align: left; text-align: left;
border-bottom: 1px solid #e9ecef; border-bottom: 1px solid var(--border-color);
transition: border-color 0.3s ease;
} }
.table th { .table th {
background-color: #f8f9fa; background-color: var(--bg-tertiary);
font-weight: 600; font-weight: 600;
color: var(--text-primary);
transition: background-color 0.3s ease;
} }
.table tr:hover { .table tr:hover {
background-color: #f8f9fa; background-color: var(--bg-tertiary);
transition: background-color 0.3s ease;
} }
.btn { .btn {
@ -112,30 +199,30 @@ body {
} }
.btn-primary { .btn-primary {
background-color: #007bff; background-color: var(--accent-primary);
color: white; color: white;
} }
.btn-primary:hover { .btn-primary:hover {
background-color: #0056b3; background-color: var(--accent-primary-hover);
} }
.btn-danger { .btn-danger {
background-color: #dc3545; background-color: var(--accent-danger);
color: white; color: white;
} }
.btn-danger:hover { .btn-danger:hover {
background-color: #c82333; background-color: var(--accent-danger-hover);
} }
.btn-secondary { .btn-secondary {
background-color: #6c757d; background-color: var(--accent-secondary);
color: white; color: white;
} }
.btn-secondary:hover { .btn-secondary:hover {
background-color: #545b62; background-color: var(--accent-secondary-hover);
} }
.form-group { .form-group {
@ -151,15 +238,18 @@ body {
.form-input { .form-input {
width: 100%; width: 100%;
padding: 10px; padding: 10px;
border: 1px solid #ddd; border: 1px solid var(--border-light);
border-radius: 4px; border-radius: 4px;
font-size: 14px; font-size: 14px;
background-color: var(--bg-secondary);
color: var(--text-primary);
transition: all 0.3s ease;
} }
.form-input:focus { .form-input:focus {
outline: none; outline: none;
border-color: #007bff; border-color: var(--accent-primary);
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); box-shadow: 0 0 0 2px rgba(77, 171, 247, 0.25);
} }
.audio-player { .audio-player {
@ -170,29 +260,31 @@ body {
.loading { .loading {
text-align: center; text-align: center;
padding: 40px; padding: 40px;
color: #666; color: var(--text-secondary);
} }
.error { .error {
background-color: #f8d7da; background-color: var(--error-bg);
color: #721c24; color: var(--error-text);
padding: 10px; padding: 10px;
border-radius: 4px; border-radius: 4px;
margin-bottom: 20px; margin-bottom: 20px;
transition: all 0.3s ease;
} }
.success { .success {
background-color: #d4edda; background-color: var(--success-bg);
color: #155724; color: var(--success-text);
padding: 10px; padding: 10px;
border-radius: 4px; border-radius: 4px;
margin-bottom: 20px; margin-bottom: 20px;
transition: all 0.3s ease;
} }
.empty-state { .empty-state {
text-align: center; text-align: center;
padding: 40px; padding: 40px;
color: #666; color: var(--text-secondary);
} }
.feed-item { .feed-item {
@ -200,9 +292,11 @@ body {
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 15px; padding: 15px;
border: 1px solid #e9ecef; border: 1px solid var(--border-color);
border-radius: 4px; border-radius: 4px;
margin-bottom: 10px; margin-bottom: 10px;
background-color: var(--bg-secondary);
transition: all 0.3s ease;
} }
.feed-info { .feed-info {
@ -215,7 +309,7 @@ body {
} }
.feed-url { .feed-url {
color: #666; color: var(--text-secondary);
font-size: 14px; font-size: 14px;
} }
@ -233,14 +327,15 @@ body {
.feed-section { .feed-section {
margin-bottom: 2rem; margin-bottom: 2rem;
padding: 1.5rem; padding: 1.5rem;
background: #f8f9fa; background: var(--bg-tertiary);
border-radius: 8px; border-radius: 8px;
border: 1px solid #e9ecef; border: 1px solid var(--border-color);
transition: all 0.3s ease;
} }
.feed-section h2 { .feed-section h2 {
margin: 0 0 1rem 0; margin: 0 0 1rem 0;
color: #495057; color: var(--text-primary);
font-size: 1.5rem; font-size: 1.5rem;
font-weight: 600; font-weight: 600;
} }
@ -279,10 +374,10 @@ body {
.rss-section h2 { .rss-section h2 {
margin: 0 0 1.5rem 0; margin: 0 0 1.5rem 0;
color: #2c3e50; color: var(--text-primary);
font-size: 1.5rem; font-size: 1.5rem;
font-weight: 600; font-weight: 600;
border-bottom: 2px solid #3498db; border-bottom: 2px solid var(--accent-primary);
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
} }
@ -293,17 +388,17 @@ body {
} }
.rss-endpoint-card { .rss-endpoint-card {
background: white; background: var(--bg-secondary);
border: 1px solid #e1e8ed; border: 1px solid var(--border-color);
border-radius: 12px; border-radius: 12px;
padding: 1.5rem; padding: 1.5rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 8px var(--shadow);
transition: all 0.2s ease; transition: all 0.2s ease;
position: relative; position: relative;
} }
.rss-endpoint-card:hover { .rss-endpoint-card:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
transform: translateY(-2px); transform: translateY(-2px);
} }
@ -333,7 +428,7 @@ body {
.endpoint-header h3 { .endpoint-header h3 {
margin: 0; margin: 0;
color: #2c3e50; color: var(--text-primary);
font-size: 1.2rem; font-size: 1.2rem;
font-weight: 600; font-weight: 600;
flex: 1; flex: 1;
@ -376,14 +471,14 @@ body {
} }
.endpoint-description { .endpoint-description {
color: #666; color: var(--text-secondary);
margin-bottom: 1rem; margin-bottom: 1rem;
line-height: 1.5; line-height: 1.5;
} }
.endpoint-url { .endpoint-url {
background: #f8f9fa; background: var(--bg-tertiary);
border: 1px solid #e9ecef; border: 1px solid var(--border-color);
border-radius: 8px; border-radius: 8px;
padding: 1rem; padding: 1rem;
display: flex; display: flex;
@ -391,6 +486,7 @@ body {
align-items: center; align-items: center;
gap: 1rem; gap: 1rem;
flex-wrap: wrap; flex-wrap: wrap;
transition: all 0.3s ease;
} }
.rss-endpoint-card.main-feed .endpoint-url { .rss-endpoint-card.main-feed .endpoint-url {
@ -401,7 +497,7 @@ body {
.endpoint-url code { .endpoint-url code {
background: none; background: none;
border: none; border: none;
color: #495057; color: var(--text-primary);
font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace; font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
font-size: 0.9rem; font-size: 0.9rem;
word-break: break-all; word-break: break-all;
@ -421,7 +517,7 @@ body {
.copy-btn, .copy-btn,
.open-btn { .open-btn {
background: #3498db; background: var(--accent-primary);
color: white; color: white;
border: none; border: none;
border-radius: 6px; border-radius: 6px;
@ -438,7 +534,7 @@ body {
.copy-btn:hover, .copy-btn:hover,
.open-btn:hover { .open-btn:hover {
background: #2980b9; background: var(--accent-primary-hover);
transform: scale(1.05); transform: scale(1.05);
} }
@ -462,10 +558,11 @@ body {
} }
.usage-info { .usage-info {
background: #f8f9fa; background: var(--bg-tertiary);
border-radius: 12px; border-radius: 12px;
padding: 2rem; padding: 2rem;
margin-top: 3rem; margin-top: 3rem;
transition: background-color 0.3s ease;
} }
.usage-cards { .usage-cards {
@ -476,22 +573,23 @@ body {
} }
.usage-card { .usage-card {
background: white; background: var(--bg-secondary);
border-radius: 8px; border-radius: 8px;
padding: 1.5rem; padding: 1.5rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px var(--shadow);
transition: all 0.3s ease;
} }
.usage-card h3 { .usage-card h3 {
margin: 0 0 1rem 0; margin: 0 0 1rem 0;
color: #2c3e50; color: var(--text-primary);
font-size: 1.1rem; font-size: 1.1rem;
font-weight: 600; font-weight: 600;
} }
.usage-card p { .usage-card p {
margin: 0; margin: 0;
color: #666; color: var(--text-secondary);
line-height: 1.6; line-height: 1.6;
} }
@ -523,3 +621,193 @@ body {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
} }
/* Additional form elements */
select {
background-color: var(--bg-secondary);
color: var(--text-primary);
border: 1px solid var(--border-light);
border-radius: 4px;
transition: all 0.3s ease;
}
select:focus {
outline: none;
border-color: var(--accent-primary);
box-shadow: 0 0 0 2px rgba(77, 171, 247, 0.25);
}
/* Links */
a {
color: var(--accent-primary);
transition: color 0.3s ease;
}
a:hover {
color: var(--accent-primary-hover);
}
/* Form labels */
.form-label {
color: var(--text-primary);
}
/* Tab text color for better readability */
.tab {
color: var(--text-primary);
transition: all 0.3s ease;
}
/* Component-specific styles for better dark theme support */
.episode-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.episode-title {
color: var(--text-primary);
}
.episode-meta {
display: flex;
gap: 10px;
align-items: center;
}
.episode-meta-text {
font-size: 12px;
color: var(--text-secondary);
}
.episode-search-bar {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}
.episode-search-input {
flex: 1;
min-width: 200px;
padding: 8px 12px;
font-size: 14px;
border: 1px solid var(--border-light);
border-radius: 4px;
background-color: var(--bg-secondary);
color: var(--text-primary);
transition: all 0.3s ease;
}
.episode-search-input:focus {
outline: none;
border-color: var(--accent-primary);
box-shadow: 0 0 0 2px rgba(77, 171, 247, 0.25);
}
.episode-category-select {
padding: 8px 12px;
font-size: 14px;
border: 1px solid var(--border-light);
border-radius: 4px;
min-width: 120px;
background-color: var(--bg-secondary);
color: var(--text-primary);
transition: all 0.3s ease;
}
.episode-category-select:focus {
outline: none;
border-color: var(--accent-primary);
box-shadow: 0 0 0 2px rgba(77, 171, 247, 0.25);
}
.episode-actions {
display: flex;
gap: 8px;
flex-direction: column;
}
.episode-action-buttons {
display: flex;
gap: 8px;
}
.episode-link {
text-decoration: none;
color: var(--accent-primary);
transition: color 0.3s ease;
}
.episode-link:hover {
color: var(--accent-primary-hover);
}
.episode-feed-info {
font-size: 12px;
color: var(--text-secondary);
margin-bottom: 4px;
}
.episode-article-info {
font-size: 12px;
color: var(--text-secondary);
margin-bottom: 4px;
}
.episode-article-link {
font-size: 12px;
color: var(--text-secondary);
}
.episode-description {
font-size: 14px;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--text-primary);
}
.episode-file-size {
font-size: 12px;
color: var(--text-secondary);
margin-top: 4px;
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.pagination-pages {
display: flex;
gap: 5px;
align-items: center;
}
.pagination-page-btn {
min-width: 40px;
}
.pagination-size-selector {
margin-left: 20px;
display: flex;
align-items: center;
gap: 5px;
}
.pagination-size-select {
padding: 4px 8px;
font-size: 14px;
border: 1px solid var(--border-light);
border-radius: 4px;
background-color: var(--bg-secondary);
color: var(--text-primary);
transition: all 0.3s ease;
}