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 { useEffect, useState } from "react";
 | 
			
		||||
import EpisodeDetail from "./components/EpisodeDetail";
 | 
			
		||||
import EpisodeList from "./components/EpisodeList";
 | 
			
		||||
import FeedDetail from "./components/FeedDetail";
 | 
			
		||||
@@ -8,6 +9,19 @@ import RSSEndpoints from "./components/RSSEndpoints";
 | 
			
		||||
 | 
			
		||||
function App() {
 | 
			
		||||
  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 = [
 | 
			
		||||
    "/",
 | 
			
		||||
    "/feeds",
 | 
			
		||||
@@ -20,9 +34,20 @@ function App() {
 | 
			
		||||
      {isMainPage && (
 | 
			
		||||
        <>
 | 
			
		||||
          <div className="header">
 | 
			
		||||
            <div className="title">Voice RSS Summary</div>
 | 
			
		||||
            <div className="subtitle">
 | 
			
		||||
              RSS フィードから自動生成された音声ポッドキャスト
 | 
			
		||||
            <div className="header-content">
 | 
			
		||||
              <div className="header-text">
 | 
			
		||||
                <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>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -345,20 +345,9 @@ function EpisodeList() {
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      <div
 | 
			
		||||
        style={{
 | 
			
		||||
          marginBottom: "20px",
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        <div
 | 
			
		||||
          style={{
 | 
			
		||||
            display: "flex",
 | 
			
		||||
            justifyContent: "space-between",
 | 
			
		||||
            alignItems: "center",
 | 
			
		||||
            marginBottom: "15px",
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <h2>
 | 
			
		||||
      <div style={{ marginBottom: "20px" }}>
 | 
			
		||||
        <div className="episode-header">
 | 
			
		||||
          <h2 className="episode-title">
 | 
			
		||||
            エピソード一覧 (
 | 
			
		||||
            {searchQuery
 | 
			
		||||
              ? `検索結果: ${filteredEpisodes.length}件`
 | 
			
		||||
@@ -369,8 +358,8 @@ function EpisodeList() {
 | 
			
		||||
                  : `${episodes.length}件`}
 | 
			
		||||
            )
 | 
			
		||||
          </h2>
 | 
			
		||||
          <div style={{ display: "flex", gap: "10px", alignItems: "center" }}>
 | 
			
		||||
            <span style={{ fontSize: "12px", color: "#666" }}>
 | 
			
		||||
          <div className="episode-meta">
 | 
			
		||||
            <span className="episode-meta-text">
 | 
			
		||||
              データソース: {useDatabase ? "データベース" : "XML"}
 | 
			
		||||
            </span>
 | 
			
		||||
            <button className="btn btn-secondary" onClick={fetchEpisodes}>
 | 
			
		||||
@@ -379,27 +368,13 @@ function EpisodeList() {
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div
 | 
			
		||||
          style={{
 | 
			
		||||
            display: "flex",
 | 
			
		||||
            gap: "10px",
 | 
			
		||||
            alignItems: "center",
 | 
			
		||||
            flexWrap: "wrap",
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
        <div className="episode-search-bar">
 | 
			
		||||
          <input
 | 
			
		||||
            type="text"
 | 
			
		||||
            placeholder="エピソードを検索..."
 | 
			
		||||
            value={searchQuery}
 | 
			
		||||
            onChange={(e) => setSearchQuery(e.target.value)}
 | 
			
		||||
            style={{
 | 
			
		||||
              flex: "1",
 | 
			
		||||
              minWidth: "200px",
 | 
			
		||||
              padding: "8px 12px",
 | 
			
		||||
              fontSize: "14px",
 | 
			
		||||
              border: "1px solid #ccc",
 | 
			
		||||
              borderRadius: "4px",
 | 
			
		||||
            }}
 | 
			
		||||
            className="episode-search-input"
 | 
			
		||||
          />
 | 
			
		||||
          {searchQuery && (
 | 
			
		||||
            <button
 | 
			
		||||
@@ -417,13 +392,7 @@ function EpisodeList() {
 | 
			
		||||
            <select
 | 
			
		||||
              value={selectedCategory}
 | 
			
		||||
              onChange={(e) => setSelectedCategory(e.target.value)}
 | 
			
		||||
              style={{
 | 
			
		||||
                padding: "8px 12px",
 | 
			
		||||
                fontSize: "14px",
 | 
			
		||||
                border: "1px solid #ccc",
 | 
			
		||||
                borderRadius: "4px",
 | 
			
		||||
                minWidth: "120px",
 | 
			
		||||
              }}
 | 
			
		||||
              className="episode-category-select"
 | 
			
		||||
            >
 | 
			
		||||
              <option value="">全カテゴリ</option>
 | 
			
		||||
              {categories.map((category) => (
 | 
			
		||||
@@ -434,7 +403,7 @@ function EpisodeList() {
 | 
			
		||||
            </select>
 | 
			
		||||
          )}
 | 
			
		||||
          {isSearching && (
 | 
			
		||||
            <span style={{ fontSize: "14px", color: "#666" }}>検索中...</span>
 | 
			
		||||
            <span className="episode-meta-text">検索中...</span>
 | 
			
		||||
          )}
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
@@ -456,24 +425,18 @@ function EpisodeList() {
 | 
			
		||||
                  <strong>
 | 
			
		||||
                    <Link
 | 
			
		||||
                      to={`/episode/${episode.id}`}
 | 
			
		||||
                      style={{ textDecoration: "none", color: "#007bff" }}
 | 
			
		||||
                      className="episode-link"
 | 
			
		||||
                    >
 | 
			
		||||
                      {episode.title}
 | 
			
		||||
                    </Link>
 | 
			
		||||
                  </strong>
 | 
			
		||||
                </div>
 | 
			
		||||
                {episode.feedTitle && (
 | 
			
		||||
                  <div
 | 
			
		||||
                    style={{
 | 
			
		||||
                      fontSize: "12px",
 | 
			
		||||
                      color: "#666",
 | 
			
		||||
                      marginBottom: "4px",
 | 
			
		||||
                    }}
 | 
			
		||||
                  >
 | 
			
		||||
                  <div className="episode-feed-info">
 | 
			
		||||
                    フィード:{" "}
 | 
			
		||||
                    <Link
 | 
			
		||||
                      to={`/feeds/${episode.feedId}`}
 | 
			
		||||
                      style={{ color: "#007bff" }}
 | 
			
		||||
                      className="episode-link"
 | 
			
		||||
                    >
 | 
			
		||||
                      {episode.feedTitle}
 | 
			
		||||
                    </Link>
 | 
			
		||||
@@ -481,7 +444,7 @@ function EpisodeList() {
 | 
			
		||||
                      <span
 | 
			
		||||
                        style={{
 | 
			
		||||
                          marginLeft: "8px",
 | 
			
		||||
                          color: "#999",
 | 
			
		||||
                          color: "var(--text-muted)",
 | 
			
		||||
                          fontSize: "11px",
 | 
			
		||||
                        }}
 | 
			
		||||
                      >
 | 
			
		||||
@@ -492,13 +455,7 @@ function EpisodeList() {
 | 
			
		||||
                )}
 | 
			
		||||
                {episode.articleTitle &&
 | 
			
		||||
                  episode.articleTitle !== episode.title && (
 | 
			
		||||
                    <div
 | 
			
		||||
                      style={{
 | 
			
		||||
                        fontSize: "12px",
 | 
			
		||||
                        color: "#666",
 | 
			
		||||
                        marginBottom: "4px",
 | 
			
		||||
                      }}
 | 
			
		||||
                    >
 | 
			
		||||
                    <div className="episode-article-info">
 | 
			
		||||
                      元記事: <strong>{episode.articleTitle}</strong>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  )}
 | 
			
		||||
@@ -507,46 +464,26 @@ function EpisodeList() {
 | 
			
		||||
                    href={episode.articleLink}
 | 
			
		||||
                    target="_blank"
 | 
			
		||||
                    rel="noopener noreferrer"
 | 
			
		||||
                    style={{ fontSize: "12px", color: "#666" }}
 | 
			
		||||
                    className="episode-article-link"
 | 
			
		||||
                  >
 | 
			
		||||
                    元記事を見る
 | 
			
		||||
                  </a>
 | 
			
		||||
                )}
 | 
			
		||||
              </td>
 | 
			
		||||
              <td>
 | 
			
		||||
                <div
 | 
			
		||||
                  style={{
 | 
			
		||||
                    fontSize: "14px",
 | 
			
		||||
                    maxWidth: "200px",
 | 
			
		||||
                    overflow: "hidden",
 | 
			
		||||
                    textOverflow: "ellipsis",
 | 
			
		||||
                    whiteSpace: "nowrap",
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                <div className="episode-description">
 | 
			
		||||
                  {episode.description || "No description"}
 | 
			
		||||
                </div>
 | 
			
		||||
                {episode.fileSize && (
 | 
			
		||||
                  <div
 | 
			
		||||
                    style={{
 | 
			
		||||
                      fontSize: "12px",
 | 
			
		||||
                      color: "#666",
 | 
			
		||||
                      marginTop: "4px",
 | 
			
		||||
                    }}
 | 
			
		||||
                  >
 | 
			
		||||
                  <div className="episode-file-size">
 | 
			
		||||
                    {formatFileSize(episode.fileSize)}
 | 
			
		||||
                  </div>
 | 
			
		||||
                )}
 | 
			
		||||
              </td>
 | 
			
		||||
              <td>{formatDate(episode.createdAt)}</td>
 | 
			
		||||
              <td>
 | 
			
		||||
                <div
 | 
			
		||||
                  style={{
 | 
			
		||||
                    display: "flex",
 | 
			
		||||
                    gap: "8px",
 | 
			
		||||
                    flexDirection: "column",
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  <div style={{ display: "flex", gap: "8px" }}>
 | 
			
		||||
                <div className="episode-actions">
 | 
			
		||||
                  <div className="episode-action-buttons">
 | 
			
		||||
                    <button
 | 
			
		||||
                      className="btn btn-primary"
 | 
			
		||||
                      onClick={() =>
 | 
			
		||||
@@ -593,16 +530,7 @@ function EpisodeList() {
 | 
			
		||||
      
 | 
			
		||||
      {/* Pagination Controls - only show for database mode */}
 | 
			
		||||
      {useDatabase && totalPages > 1 && (
 | 
			
		||||
        <div
 | 
			
		||||
          style={{
 | 
			
		||||
            marginTop: "20px",
 | 
			
		||||
            display: "flex",
 | 
			
		||||
            justifyContent: "center",
 | 
			
		||||
            alignItems: "center",
 | 
			
		||||
            gap: "10px",
 | 
			
		||||
            flexWrap: "wrap",
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
        <div className="pagination-container">
 | 
			
		||||
          <button
 | 
			
		||||
            className="btn btn-secondary"
 | 
			
		||||
            onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
 | 
			
		||||
@@ -612,7 +540,7 @@ function EpisodeList() {
 | 
			
		||||
          </button>
 | 
			
		||||
          
 | 
			
		||||
          {/* Page numbers */}
 | 
			
		||||
          <div style={{ display: "flex", gap: "5px", alignItems: "center" }}>
 | 
			
		||||
          <div className="pagination-pages">
 | 
			
		||||
            {/* First page */}
 | 
			
		||||
            {currentPage > 3 && (
 | 
			
		||||
              <>
 | 
			
		||||
@@ -635,11 +563,8 @@ function EpisodeList() {
 | 
			
		||||
              return (
 | 
			
		||||
                <button
 | 
			
		||||
                  key={pageNum}
 | 
			
		||||
                  className={`btn ${currentPage === pageNum ? "btn-primary" : "btn-secondary"}`}
 | 
			
		||||
                  className={`btn pagination-page-btn ${currentPage === pageNum ? "btn-primary" : "btn-secondary"}`}
 | 
			
		||||
                  onClick={() => setCurrentPage(pageNum)}
 | 
			
		||||
                  style={{
 | 
			
		||||
                    minWidth: "40px",
 | 
			
		||||
                  }}
 | 
			
		||||
                >
 | 
			
		||||
                  {pageNum}
 | 
			
		||||
                </button>
 | 
			
		||||
@@ -669,7 +594,7 @@ function EpisodeList() {
 | 
			
		||||
          </button>
 | 
			
		||||
          
 | 
			
		||||
          {/* Page size selector */}
 | 
			
		||||
          <div style={{ marginLeft: "20px", display: "flex", alignItems: "center", gap: "5px" }}>
 | 
			
		||||
          <div className="pagination-size-selector">
 | 
			
		||||
            <span style={{ fontSize: "14px" }}>表示件数:</span>
 | 
			
		||||
            <select
 | 
			
		||||
              value={pageSize}
 | 
			
		||||
@@ -677,12 +602,7 @@ function EpisodeList() {
 | 
			
		||||
                setPageSize(Number.parseInt(e.target.value, 10));
 | 
			
		||||
                setCurrentPage(1); // Reset to first page when changing page size
 | 
			
		||||
              }}
 | 
			
		||||
              style={{
 | 
			
		||||
                padding: "4px 8px",
 | 
			
		||||
                fontSize: "14px",
 | 
			
		||||
                border: "1px solid #ccc",
 | 
			
		||||
                borderRadius: "4px",
 | 
			
		||||
              }}
 | 
			
		||||
              className="pagination-size-select"
 | 
			
		||||
            >
 | 
			
		||||
              <option value={10}>10</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;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
@@ -6,9 +52,10 @@
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
  font-family: system-ui, -apple-system, sans-serif;
 | 
			
		||||
  background-color: #f5f5f5;
 | 
			
		||||
  color: #333;
 | 
			
		||||
  background-color: var(--bg-primary);
 | 
			
		||||
  color: var(--text-primary);
 | 
			
		||||
  line-height: 1.6;
 | 
			
		||||
  transition: background-color 0.3s ease, color 0.3s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.container {
 | 
			
		||||
@@ -18,30 +65,65 @@ body {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.header {
 | 
			
		||||
  background-color: #fff;
 | 
			
		||||
  background-color: var(--bg-secondary);
 | 
			
		||||
  border-radius: 8px;
 | 
			
		||||
  padding: 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 {
 | 
			
		||||
  font-size: 24px;
 | 
			
		||||
  font-weight: bold;
 | 
			
		||||
  margin-bottom: 8px;
 | 
			
		||||
  color: var(--text-primary);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.subtitle {
 | 
			
		||||
  color: #666;
 | 
			
		||||
  color: var(--text-secondary);
 | 
			
		||||
  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 {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  background-color: #fff;
 | 
			
		||||
  background-color: var(--bg-secondary);
 | 
			
		||||
  border-radius: 8px;
 | 
			
		||||
  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 {
 | 
			
		||||
@@ -63,19 +145,20 @@ body {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tab.active {
 | 
			
		||||
  background-color: #007bff;
 | 
			
		||||
  background-color: var(--accent-primary);
 | 
			
		||||
  color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tab:hover:not(.active) {
 | 
			
		||||
  background-color: #f8f9fa;
 | 
			
		||||
  background-color: var(--bg-tertiary);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.content {
 | 
			
		||||
  background-color: #fff;
 | 
			
		||||
  background-color: var(--bg-secondary);
 | 
			
		||||
  border-radius: 8px;
 | 
			
		||||
  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 {
 | 
			
		||||
@@ -88,16 +171,20 @@ body {
 | 
			
		||||
.table td {
 | 
			
		||||
  padding: 12px;
 | 
			
		||||
  text-align: left;
 | 
			
		||||
  border-bottom: 1px solid #e9ecef;
 | 
			
		||||
  border-bottom: 1px solid var(--border-color);
 | 
			
		||||
  transition: border-color 0.3s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.table th {
 | 
			
		||||
  background-color: #f8f9fa;
 | 
			
		||||
  background-color: var(--bg-tertiary);
 | 
			
		||||
  font-weight: 600;
 | 
			
		||||
  color: var(--text-primary);
 | 
			
		||||
  transition: background-color 0.3s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.table tr:hover {
 | 
			
		||||
  background-color: #f8f9fa;
 | 
			
		||||
  background-color: var(--bg-tertiary);
 | 
			
		||||
  transition: background-color 0.3s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn {
 | 
			
		||||
@@ -112,30 +199,30 @@ body {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-primary {
 | 
			
		||||
  background-color: #007bff;
 | 
			
		||||
  background-color: var(--accent-primary);
 | 
			
		||||
  color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-primary:hover {
 | 
			
		||||
  background-color: #0056b3;
 | 
			
		||||
  background-color: var(--accent-primary-hover);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-danger {
 | 
			
		||||
  background-color: #dc3545;
 | 
			
		||||
  background-color: var(--accent-danger);
 | 
			
		||||
  color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-danger:hover {
 | 
			
		||||
  background-color: #c82333;
 | 
			
		||||
  background-color: var(--accent-danger-hover);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-secondary {
 | 
			
		||||
  background-color: #6c757d;
 | 
			
		||||
  background-color: var(--accent-secondary);
 | 
			
		||||
  color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.btn-secondary:hover {
 | 
			
		||||
  background-color: #545b62;
 | 
			
		||||
  background-color: var(--accent-secondary-hover);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-group {
 | 
			
		||||
@@ -151,15 +238,18 @@ body {
 | 
			
		||||
.form-input {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  padding: 10px;
 | 
			
		||||
  border: 1px solid #ddd;
 | 
			
		||||
  border: 1px solid var(--border-light);
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  background-color: var(--bg-secondary);
 | 
			
		||||
  color: var(--text-primary);
 | 
			
		||||
  transition: all 0.3s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.form-input:focus {
 | 
			
		||||
  outline: none;
 | 
			
		||||
  border-color: #007bff;
 | 
			
		||||
  box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
 | 
			
		||||
  border-color: var(--accent-primary);
 | 
			
		||||
  box-shadow: 0 0 0 2px rgba(77, 171, 247, 0.25);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.audio-player {
 | 
			
		||||
@@ -170,29 +260,31 @@ body {
 | 
			
		||||
.loading {
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  padding: 40px;
 | 
			
		||||
  color: #666;
 | 
			
		||||
  color: var(--text-secondary);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.error {
 | 
			
		||||
  background-color: #f8d7da;
 | 
			
		||||
  color: #721c24;
 | 
			
		||||
  background-color: var(--error-bg);
 | 
			
		||||
  color: var(--error-text);
 | 
			
		||||
  padding: 10px;
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  margin-bottom: 20px;
 | 
			
		||||
  transition: all 0.3s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.success {
 | 
			
		||||
  background-color: #d4edda;
 | 
			
		||||
  color: #155724;
 | 
			
		||||
  background-color: var(--success-bg);
 | 
			
		||||
  color: var(--success-text);
 | 
			
		||||
  padding: 10px;
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  margin-bottom: 20px;
 | 
			
		||||
  transition: all 0.3s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.empty-state {
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  padding: 40px;
 | 
			
		||||
  color: #666;
 | 
			
		||||
  color: var(--text-secondary);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.feed-item {
 | 
			
		||||
@@ -200,9 +292,11 @@ body {
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  padding: 15px;
 | 
			
		||||
  border: 1px solid #e9ecef;
 | 
			
		||||
  border: 1px solid var(--border-color);
 | 
			
		||||
  border-radius: 4px;
 | 
			
		||||
  margin-bottom: 10px;
 | 
			
		||||
  background-color: var(--bg-secondary);
 | 
			
		||||
  transition: all 0.3s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.feed-info {
 | 
			
		||||
@@ -215,7 +309,7 @@ body {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.feed-url {
 | 
			
		||||
  color: #666;
 | 
			
		||||
  color: var(--text-secondary);
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -233,14 +327,15 @@ body {
 | 
			
		||||
.feed-section {
 | 
			
		||||
  margin-bottom: 2rem;
 | 
			
		||||
  padding: 1.5rem;
 | 
			
		||||
  background: #f8f9fa;
 | 
			
		||||
  background: var(--bg-tertiary);
 | 
			
		||||
  border-radius: 8px;
 | 
			
		||||
  border: 1px solid #e9ecef;
 | 
			
		||||
  border: 1px solid var(--border-color);
 | 
			
		||||
  transition: all 0.3s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.feed-section h2 {
 | 
			
		||||
  margin: 0 0 1rem 0;
 | 
			
		||||
  color: #495057;
 | 
			
		||||
  color: var(--text-primary);
 | 
			
		||||
  font-size: 1.5rem;
 | 
			
		||||
  font-weight: 600;
 | 
			
		||||
}
 | 
			
		||||
@@ -279,10 +374,10 @@ body {
 | 
			
		||||
 | 
			
		||||
.rss-section h2 {
 | 
			
		||||
  margin: 0 0 1.5rem 0;
 | 
			
		||||
  color: #2c3e50;
 | 
			
		||||
  color: var(--text-primary);
 | 
			
		||||
  font-size: 1.5rem;
 | 
			
		||||
  font-weight: 600;
 | 
			
		||||
  border-bottom: 2px solid #3498db;
 | 
			
		||||
  border-bottom: 2px solid var(--accent-primary);
 | 
			
		||||
  padding-bottom: 0.5rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -293,17 +388,17 @@ body {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.rss-endpoint-card {
 | 
			
		||||
  background: white;
 | 
			
		||||
  border: 1px solid #e1e8ed;
 | 
			
		||||
  background: var(--bg-secondary);
 | 
			
		||||
  border: 1px solid var(--border-color);
 | 
			
		||||
  border-radius: 12px;
 | 
			
		||||
  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;
 | 
			
		||||
  position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -333,7 +428,7 @@ body {
 | 
			
		||||
 | 
			
		||||
.endpoint-header h3 {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  color: #2c3e50;
 | 
			
		||||
  color: var(--text-primary);
 | 
			
		||||
  font-size: 1.2rem;
 | 
			
		||||
  font-weight: 600;
 | 
			
		||||
  flex: 1;
 | 
			
		||||
@@ -376,14 +471,14 @@ body {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.endpoint-description {
 | 
			
		||||
  color: #666;
 | 
			
		||||
  color: var(--text-secondary);
 | 
			
		||||
  margin-bottom: 1rem;
 | 
			
		||||
  line-height: 1.5;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.endpoint-url {
 | 
			
		||||
  background: #f8f9fa;
 | 
			
		||||
  border: 1px solid #e9ecef;
 | 
			
		||||
  background: var(--bg-tertiary);
 | 
			
		||||
  border: 1px solid var(--border-color);
 | 
			
		||||
  border-radius: 8px;
 | 
			
		||||
  padding: 1rem;
 | 
			
		||||
  display: flex;
 | 
			
		||||
@@ -391,6 +486,7 @@ body {
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  gap: 1rem;
 | 
			
		||||
  flex-wrap: wrap;
 | 
			
		||||
  transition: all 0.3s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.rss-endpoint-card.main-feed .endpoint-url {
 | 
			
		||||
@@ -401,7 +497,7 @@ body {
 | 
			
		||||
.endpoint-url code {
 | 
			
		||||
  background: none;
 | 
			
		||||
  border: none;
 | 
			
		||||
  color: #495057;
 | 
			
		||||
  color: var(--text-primary);
 | 
			
		||||
  font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
 | 
			
		||||
  font-size: 0.9rem;
 | 
			
		||||
  word-break: break-all;
 | 
			
		||||
@@ -421,7 +517,7 @@ body {
 | 
			
		||||
 | 
			
		||||
.copy-btn,
 | 
			
		||||
.open-btn {
 | 
			
		||||
  background: #3498db;
 | 
			
		||||
  background: var(--accent-primary);
 | 
			
		||||
  color: white;
 | 
			
		||||
  border: none;
 | 
			
		||||
  border-radius: 6px;
 | 
			
		||||
@@ -438,7 +534,7 @@ body {
 | 
			
		||||
 | 
			
		||||
.copy-btn:hover,
 | 
			
		||||
.open-btn:hover {
 | 
			
		||||
  background: #2980b9;
 | 
			
		||||
  background: var(--accent-primary-hover);
 | 
			
		||||
  transform: scale(1.05);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -462,10 +558,11 @@ body {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.usage-info {
 | 
			
		||||
  background: #f8f9fa;
 | 
			
		||||
  background: var(--bg-tertiary);
 | 
			
		||||
  border-radius: 12px;
 | 
			
		||||
  padding: 2rem;
 | 
			
		||||
  margin-top: 3rem;
 | 
			
		||||
  transition: background-color 0.3s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.usage-cards {
 | 
			
		||||
@@ -476,22 +573,23 @@ body {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.usage-card {
 | 
			
		||||
  background: white;
 | 
			
		||||
  background: var(--bg-secondary);
 | 
			
		||||
  border-radius: 8px;
 | 
			
		||||
  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 {
 | 
			
		||||
  margin: 0 0 1rem 0;
 | 
			
		||||
  color: #2c3e50;
 | 
			
		||||
  color: var(--text-primary);
 | 
			
		||||
  font-size: 1.1rem;
 | 
			
		||||
  font-weight: 600;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.usage-card p {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  color: #666;
 | 
			
		||||
  color: var(--text-secondary);
 | 
			
		||||
  line-height: 1.6;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -523,3 +621,193 @@ body {
 | 
			
		||||
    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