Support dark mode in the frontend
This commit is contained in:
@ -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,11 +34,22 @@ function App() {
|
|||||||
{isMainPage && (
|
{isMainPage && (
|
||||||
<>
|
<>
|
||||||
<div className="header">
|
<div className="header">
|
||||||
|
<div className="header-content">
|
||||||
|
<div className="header-text">
|
||||||
<div className="title">Voice RSS Summary</div>
|
<div className="title">Voice RSS Summary</div>
|
||||||
<div className="subtitle">
|
<div className="subtitle">
|
||||||
RSS フィードから自動生成された音声ポッドキャスト
|
RSS フィードから自動生成された音声ポッドキャスト
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
className="theme-toggle"
|
||||||
|
onClick={toggleDarkMode}
|
||||||
|
aria-label="テーマを切り替え"
|
||||||
|
>
|
||||||
|
{isDarkMode ? '☀️' : '🌙'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="tabs">
|
<div className="tabs">
|
||||||
<Link
|
<Link
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user