From 91dd846fdc6a21e8d34303d428ef45a2d54f410b Mon Sep 17 00:00:00 2001 From: Mason Fox Date: Thu, 12 Feb 2026 08:24:30 -0500 Subject: [PATCH 1/4] feat: Add dark mode and auto mode theme support (#28) Add three-way theme toggle (Light/Auto/Dark) with system preference detection, localStorage persistence, and flash-prevention script. Configure Tailwind CSS v4 class-based dark mode and apply dark variants across all 17 component and page files using the zinc palette. --- app/MessageBanner.jsx | 18 ++--- app/components/DualDownloadButton.jsx | 8 +-- app/components/DualSearchResultsList.jsx | 32 ++++----- app/components/Header.jsx | 44 +++++++------ app/components/MobileBottomSheet.jsx | 18 ++--- app/components/ProgressIndicator.jsx | 24 +++---- app/components/SearchForm.jsx | 10 +-- app/components/SearchResultItem.jsx | 22 +++---- app/components/SearchResultsList.jsx | 4 +- app/components/SequentialSearchResults.jsx | 36 +++++----- app/components/ThemeProvider.jsx | 73 +++++++++++++++++++++ app/components/ThemeToggle.jsx | 56 ++++++++++++++++ app/components/TokenManager.jsx | 76 +++++++++++----------- app/components/UserStatsBar.jsx | 50 +++++++------- app/components/WedgeToggleButton.jsx | 2 +- app/layout.js | 22 ++++++- app/login/page.jsx | 12 ++-- app/page.jsx | 13 ++-- app/styles/globals.css | 4 +- package-lock.json | 39 +++++------ tailwind.config.mjs | 1 + 21 files changed, 354 insertions(+), 210 deletions(-) create mode 100644 app/components/ThemeProvider.jsx create mode 100644 app/components/ThemeToggle.jsx diff --git a/app/MessageBanner.jsx b/app/MessageBanner.jsx index 6e5cae6..f882fd6 100644 --- a/app/MessageBanner.jsx +++ b/app/MessageBanner.jsx @@ -5,21 +5,21 @@ export default function MessageBanner({ type = "info", text }) { let bg, border, textColor, icon; switch (type) { case "error": - bg = "bg-red-100"; - border = "border border-red-300"; - textColor = "text-red-900"; + bg = "bg-red-100 dark:bg-red-900/20"; + border = "border border-red-300 dark:border-red-800/40"; + textColor = "text-red-900 dark:text-red-300"; icon = "❌"; break; case "success": - bg = "bg-green-100"; - border = "border border-green-300"; - textColor = "text-green-900"; + bg = "bg-green-100 dark:bg-green-900/20"; + border = "border border-green-300 dark:border-green-800/40"; + textColor = "text-green-900 dark:text-green-300"; icon = "✅"; break; default: - bg = "bg-blue-100"; - border = "border border-blue-300"; - textColor = "text-blue-900"; + bg = "bg-blue-100 dark:bg-blue-900/20"; + border = "border border-blue-300 dark:border-blue-800/40"; + textColor = "text-blue-900 dark:text-blue-300"; icon = "â„šī¸"; } return ( diff --git a/app/components/DualDownloadButton.jsx b/app/components/DualDownloadButton.jsx index 0995590..c5edcc4 100644 --- a/app/components/DualDownloadButton.jsx +++ b/app/components/DualDownloadButton.jsx @@ -18,11 +18,11 @@ export default function DualDownloadButton({ return (
-
+
{/* Wedge toggles when both selected */} {bothSelected && hasWedges && ( -
- Use FL Wedge: +
+ Use FL Wedge: {loading ? ( <> diff --git a/app/components/DualSearchResultsList.jsx b/app/components/DualSearchResultsList.jsx index 1d0f828..8e798e1 100644 --- a/app/components/DualSearchResultsList.jsx +++ b/app/components/DualSearchResultsList.jsx @@ -28,7 +28,7 @@ export default function DualSearchResultsList({ -

Searching both categories...

+

Searching both categories...

); @@ -39,7 +39,7 @@ export default function DualSearchResultsList({ const noBooks = bookResults.length === 0; if (noResults) { - return

No results found for either category. Try a different search...

; + return

No results found for either category. Try a different search...

; } // Calculate progress for desktop @@ -91,7 +91,7 @@ export default function DualSearchResultsList({ @@ -67,4 +71,4 @@ export default function Header({ onTokenUpdate, mamTokenExists }) {
); -} \ No newline at end of file +} diff --git a/app/components/MobileBottomSheet.jsx b/app/components/MobileBottomSheet.jsx index 5fd6c71..30c91d7 100644 --- a/app/components/MobileBottomSheet.jsx +++ b/app/components/MobileBottomSheet.jsx @@ -24,22 +24,22 @@ export default function MobileBottomSheet({ return ( <> {/* Backdrop */} -
+
{/* Content container - single fixed element */}
{/* Progress indicator (only show when not both selected) */} {!bothSelected && ( -
+
- + Step {currentStep} of 2: Select {stepText} - {progress}% + {progress}%
-
+
+
- Use FL Wedge: + Use FL Wedge: {showBookWedge && ( +
+ ))} +
+ ); +} diff --git a/app/components/TokenManager.jsx b/app/components/TokenManager.jsx index 3552269..f9aad30 100644 --- a/app/components/TokenManager.jsx +++ b/app/components/TokenManager.jsx @@ -110,46 +110,46 @@ export default function TokenManager({ onTokenUpdate }) { const isMouseholeMode = tokenData.mouseholeInfo?.enabled; return ( -
-

MAM Token Manager

+
+

MAM Token Manager

{isMouseholeMode ? ( // Mousehole Read-Only View
-
+
- +
-

+

Token Managed by Mousehole

-

+

Your MAM token is dynamically managed by the mousehole service. Token editing is disabled.

-
-

- Status: +

+

+ Status: {tokenData.exists ? 'Token active' : 'Waiting for mousehole...'}

{tokenData.exists && ( <> -

- Token: {tokenData.token} +

+ Token: {tokenData.token}

-

+

Length: {tokenData.fullLength} characters

{tokenData.mouseholeInfo?.lastUpdate && ( -

+

Last updated: {new Date(tokenData.mouseholeInfo.lastUpdate).toLocaleString()} @@ -157,22 +157,22 @@ export default function TokenManager({ onTokenUpdate }) { )} )} -

- Source: {tokenData.mouseholeInfo?.stateFile || tokenData.location} +

+ Source: {tokenData.mouseholeInfo?.stateFile || tokenData.location}

-
-

+

+

About Mousehole Integration:

-
    +
    • Tokens are automatically rotated when your IP changes
    • No manual token updates required
    • Scurry reads the token from mousehole's state file
    -

    - To disable mousehole mode, set MOUSEHOLE_ENABLED=false in your .env file. +

    + To disable mousehole mode, set MOUSEHOLE_ENABLED=false in your .env file.

@@ -180,20 +180,20 @@ export default function TokenManager({ onTokenUpdate }) { // Standard Manual Token Management View <> {/* Current Token Status */} -
+
-

- Status: +

+ Status: {tokenData.exists ? 'Token configured' : 'No token found'}

{tokenData.exists && ( <> -

- Token: {tokenData.token} +

+ Token: {tokenData.token}

-

+

Length: {tokenData.fullLength} characters

@@ -204,14 +204,14 @@ export default function TokenManager({ onTokenUpdate }) {
@@ -224,7 +224,7 @@ export default function TokenManager({ onTokenUpdate }) { {showTokenInput && (
-