From 178f6fa5e56cc94dd5d9cbc143c911d073148658 Mon Sep 17 00:00:00 2001 From: knight Date: Tue, 4 Nov 2025 23:07:59 -0500 Subject: [PATCH] Add dark mode support to Python app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implement CSS custom properties for theming - Add comprehensive light and dark color schemes - Create theme toggle button in header - Add theme persistence with localStorage - Support system color scheme preference - Smooth transitions between themes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- static/app.js | 38 +++++++++++ static/index.html | 7 ++- static/style.css | 157 ++++++++++++++++++++++++++++++++++------------ 3 files changed, 161 insertions(+), 41 deletions(-) diff --git a/static/app.js b/static/app.js index 240d41c..e894239 100644 --- a/static/app.js +++ b/static/app.js @@ -1,4 +1,42 @@ (() => { + // Theme management + const themeToggle = document.getElementById("themeToggle"); + const prefersDark = window.matchMedia("(prefers-color-scheme: dark)"); + + function getTheme() { + const saved = localStorage.getItem("theme"); + if (saved) return saved; + return prefersDark.matches ? "dark" : "light"; + } + + function setTheme(theme) { + document.documentElement.setAttribute("data-theme", theme); + localStorage.setItem("theme", theme); + if (themeToggle) { + themeToggle.textContent = theme === "dark" ? "☀️ Light Mode" : "🌙 Dark Mode"; + } + } + + function toggleTheme() { + const current = document.documentElement.getAttribute("data-theme") || "light"; + setTheme(current === "dark" ? "light" : "dark"); + } + + // Initialize theme + setTheme(getTheme()); + + // Listen for theme toggle + if (themeToggle) { + themeToggle.addEventListener("click", toggleTheme); + } + + // Listen for system theme changes + prefersDark.addEventListener("change", (e) => { + if (!localStorage.getItem("theme")) { + setTheme(e.matches ? "dark" : "light"); + } + }); + let qs = new URLSearchParams(window.location.search); const qInput = document.getElementById("q"); const channelDropdown = document.getElementById("channelDropdown"); diff --git a/static/index.html b/static/index.html index 822346e..d978cba 100644 --- a/static/index.html +++ b/static/index.html @@ -9,7 +9,12 @@
-

This Little Corner — Elastic Search

+
+

This Little Corner — Elastic Search

+ +

Enter a phrase to query title, description, and transcript text.

diff --git a/static/style.css b/static/style.css index 6eade4a..ff7c39d 100644 --- a/static/style.css +++ b/static/style.css @@ -1,7 +1,79 @@ +/* Light theme (default) */ +:root { + --color-bg-primary: #ffffff; + --color-bg-secondary: #f5f5f5; + --color-bg-tertiary: #fafafa; + --color-bg-button: #f0f0f0; + --color-bg-button-hover: #e8e8e8; + --color-bg-highlight: #ffe58a; + --color-bg-focus: #fff3cd; + --color-bg-focus-border: #ffc107; + --color-bg-timestamp: #e8f4ff; + --color-bg-timestamp-hover: #cce5ff; + --color-bg-hover: #f0f7ff; + + --color-text-primary: #222; + --color-text-secondary: #666; + --color-text-tertiary: #444; + --color-text-quaternary: #333; + --color-text-muted: #999; + --color-text-inverse: #fff; + + --color-border-primary: #ccc; + --color-border-secondary: #ddd; + --color-border-tertiary: #ececec; + --color-border-focus: #e1e1e1; + + --color-link: #0366d6; + --color-badge: #0b6efd; + --color-chart-stroke: #ccc; + --color-chart-stroke-secondary: #fff; + + --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.08); + --shadow-dropdown: 0 2px 6px rgba(0, 0, 0, 0.12); +} + +/* Dark theme */ +[data-theme="dark"] { + --color-bg-primary: #1a1a1a; + --color-bg-secondary: #2a2a2a; + --color-bg-tertiary: #252525; + --color-bg-button: #333333; + --color-bg-button-hover: #3d3d3d; + --color-bg-highlight: #6b5a00; + --color-bg-focus: #4a4200; + --color-bg-focus-border: #7a6b00; + --color-bg-timestamp: #1a3a52; + --color-bg-timestamp-hover: #2a4a62; + --color-bg-hover: #1a2a3a; + + --color-text-primary: #e0e0e0; + --color-text-secondary: #a0a0a0; + --color-text-tertiary: #b8b8b8; + --color-text-quaternary: #cccccc; + --color-text-muted: #888888; + --color-text-inverse: #ffffff; + + --color-border-primary: #444444; + --color-border-secondary: #3a3a3a; + --color-border-tertiary: #2f2f2f; + --color-border-focus: #3f3f3f; + + --color-link: #58a6ff; + --color-badge: #2f81f7; + --color-chart-stroke: #555555; + --color-chart-stroke-secondary: #2a2a2a; + + --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.3); + --shadow-dropdown: 0 2px 6px rgba(0, 0, 0, 0.4); +} + body { font-family: Arial, sans-serif; margin: 24px; - color: #222; + color: var(--color-text-primary); + background-color: var(--color-bg-primary); + transition: background-color 0.3s ease, color 0.3s ease; } header { @@ -25,11 +97,11 @@ header { .channel-dropdown summary { list-style: none; cursor: pointer; - border: 1px solid #ccc; + border: 1px solid var(--color-border-primary); border-radius: 4px; padding: 6px 8px; - background: #fff; - color: #222; + background: var(--color-bg-primary); + color: var(--color-text-primary); display: inline-flex; align-items: center; min-height: 32px; @@ -51,12 +123,12 @@ header { .channel-options { margin-top: 4px; padding: 8px; - border: 1px solid #ccc; + border: 1px solid var(--color-border-primary); border-radius: 0 0 4px 4px; - background: #fff; + background: var(--color-bg-primary); max-height: 240px; overflow-y: auto; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12); + box-shadow: var(--shadow-dropdown); min-width: 220px; width: max(220px, 100%); } @@ -80,12 +152,12 @@ button { } .muted { - color: #666; + color: var(--color-text-secondary); font-size: 12px; } #results .item { - border-bottom: 1px solid #ddd; + border-bottom: 1px solid var(--color-border-secondary); padding: 12px 0; } @@ -105,10 +177,10 @@ button { .summary-right { flex: 1 1 0%; min-width: 0; - background: #f5f5f5; + background: var(--color-bg-secondary); padding: 12px; border-radius: 8px; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08); + box-shadow: var(--shadow-card); } #metrics { @@ -138,11 +210,15 @@ button { #frequencyChart .axis path, #frequencyChart .axis line { - stroke: #ccc; + stroke: var(--color-chart-stroke); +} + +#frequencyChart .axis text { + fill: var(--color-text-primary); } #frequencyChart .freq-layer rect { - stroke: #fff; + stroke: var(--color-chart-stroke-secondary); stroke-width: 0.5px; } @@ -152,7 +228,7 @@ button { flex-wrap: wrap; gap: 8px; font-size: 12px; - color: #444; + color: var(--color-text-tertiary); } .freq-legend-item { @@ -169,7 +245,7 @@ button { } .transcript { - background: #fafafa; + background: var(--color-bg-tertiary); padding: 8px; margin-top: 6px; max-height: 200px; @@ -186,13 +262,13 @@ button { .highlight-row { padding: 4px 0; - border-bottom: 1px solid #ececec; + border-bottom: 1px solid var(--color-border-tertiary); cursor: pointer; transition: background 0.2s; } .highlight-row:hover { - background: #f0f7ff; + background: var(--color-bg-hover); } .highlight-row:last-child { @@ -210,7 +286,8 @@ button { } mark { - background: #ffe58a; + background: var(--color-bg-highlight); + color: var(--color-text-primary); padding: 0 2px; } @@ -223,8 +300,8 @@ mark { } .badge { - background: #0b6efd; - color: #fff; + background: var(--color-badge); + color: var(--color-text-inverse); border-radius: 999px; padding: 2px 8px; font-size: 12px; @@ -233,29 +310,29 @@ mark { .transcript-toggle { margin-top: 8px; padding: 6px 12px; - background: #f0f0f0; - border: 1px solid #ccc; + background: var(--color-bg-button); + border: 1px solid var(--color-border-primary); border-radius: 4px; cursor: pointer; font-size: 13px; - color: #0366d6; + color: var(--color-link); transition: background 0.2s; } .transcript-toggle:hover { - background: #e8e8e8; + background: var(--color-bg-button-hover); } .transcript-toggle:disabled { cursor: not-allowed; - color: #999; + color: var(--color-text-muted); } .full-transcript { margin-top: 12px; padding: 12px; - background: #fafafa; - border: 1px solid #e1e1e1; + background: var(--color-bg-tertiary); + border: 1px solid var(--color-border-focus); border-radius: 4px; max-height: 400px; overflow-y: auto; @@ -264,7 +341,7 @@ mark { .transcript-segment { margin-bottom: 12px; padding-bottom: 8px; - border-bottom: 1px solid #ececec; + border-bottom: 1px solid var(--color-border-tertiary); transition: background 0.3s, padding 0.3s; border-radius: 4px; } @@ -275,49 +352,49 @@ mark { } .transcript-segment.focused { - background: #fff3cd; + background: var(--color-bg-focus); padding: 8px; - border: 2px solid #ffc107; + border: 2px solid var(--color-bg-focus-border); animation: pulse-highlight 1s ease-in-out; } @keyframes pulse-highlight { 0%, 100% { - background: #fff3cd; + background: var(--color-bg-focus); } 50% { - background: #ffe69c; + opacity: 0.8; } } .timestamp-link { display: inline-block; - color: #0366d6; + color: var(--color-link); text-decoration: none; font-weight: bold; font-size: 11px; font-family: monospace; margin-right: 8px; padding: 2px 6px; - background: #e8f4ff; + background: var(--color-bg-timestamp); border-radius: 3px; transition: background 0.2s; } .timestamp-link:hover { - background: #cce5ff; + background: var(--color-bg-timestamp-hover); text-decoration: underline; } .transcript-text { - color: #333; + color: var(--color-text-quaternary); line-height: 1.5; } .transcript-header { font-weight: bold; margin-bottom: 8px; - color: #444; + color: var(--color-text-tertiary); display: flex; align-items: center; justify-content: space-between; @@ -325,16 +402,16 @@ mark { .transcript-close { cursor: pointer; - color: #666; + color: var(--color-text-secondary); font-size: 18px; padding: 0 4px; } .transcript-close:hover { - color: #000; + color: var(--color-text-primary); } .loading-text { - color: #666; + color: var(--color-text-secondary); font-style: italic; }