feat(mobile-ux): implement theme toggle, client-side persistence, and light mode style overrides (#71)

Resolves #72

## Description

This PR implements the theme toggle mechanism, client-side persistence, and comprehensive light mode style overrides for the mobile reader layout in **NexusReader**.

### Key Changes
1. **ThemeService & State Management**:
   - Enhanced `ThemeService` with `InitializeAsync` and `ToggleTheme` using JS Interop.
   - Synchronized theme state changes via `OnThemeChanged` event.
2. **Local Storage Persistence & FOUC Prevention**:
   - Added client-side script in `App.razor` and MAUI `index.html` to instantly apply `.theme-light` from `localStorage` before prerendering/rendering to prevent a Flash of Unthemed Content (FOUC).
   - Created `theme.js` containing JS helper methods (`themeInterop.isLightMode`, `themeInterop.setLightMode`) to interface with `localStorage` and `document.documentElement` class list.
3. **UI Theme Toggle**:
   - Added a minimalist glassmorphic theme toggle button in the header of the `ReaderCanvas.razor` with dynamic transitions and icon morphing between sun and moon SVG icons.
4. **Light Mode Stylesheets Overrides**:
   - Added detailed light mode scoped styles (`.theme-light`) for the `ReaderCanvas` and the bottom-sheet `GlobalIntelligence` AI chat panel, utilizing earthy gray text (`#2d2a26`) on warm paper-like backgrounds (`rgba(244, 241, 234, 0.95)` / `#faf8f5`) to maintain high-quality aesthetic consistency.

### Verification
- Solution built successfully without errors (`dotnet build NexusReader.slnx --no-restore`).
- Scoped CSS isolation checked and validated for both Web and MAUI contexts.

---------

Co-authored-by: Marek Jasiński <jasins.marek@gmail.com>
Reviewed-on: #71
Co-authored-by: Antigravity <antigravity@google.com>
Co-committed-by: Antigravity <antigravity@google.com>
This commit was merged in pull request #71.
This commit is contained in:
2026-06-05 18:02:33 +00:00
committed by Marek Jaisński
parent f18663426b
commit f6819d50b7
12 changed files with 602 additions and 133 deletions
@@ -340,7 +340,7 @@
@media (max-width: 768px) {
.reader-canvas {
padding-top: 54px !important;
padding-bottom: 80px !important;
padding-bottom: 120px !important;
/* Ensure content is clear of bottom toolbar */
}
@@ -350,6 +350,22 @@
}
}
@media (max-width: 767px) {
::deep .nexus-ebook,
::deep .nexus-ebook p {
font-size: 16px !important;
line-height: 1.55 !important;
}
::deep .nexus-ebook h1 {
font-size: 1.35rem !important;
line-height: 1.4 !important;
margin-top: 1.5rem !important;
margin-bottom: 1rem !important;
}
}
.nexus-mobile-reader-header {
position: fixed;
top: 0;
@@ -400,6 +416,7 @@
gap: 0.25rem;
height: 100%;
min-width: 0;
margin-right: 40px; /* Space to prevent overlap with absolute theme toggle button */
}
.nexus-mobile-chapter-title {
@@ -463,4 +480,91 @@
.theme-light .nexus-chapter-nav-btn:hover:not(:disabled) {
background: rgba(0, 0, 0, 0.06);
}
/* Minimalist Theme Toggle Button with Glassmorphism */
.nexus-theme-toggle-btn {
position: absolute;
right: 1rem;
top: 50%;
transform: translateY(-50%);
width: 36px;
height: 36px;
border-radius: 50%;
border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.03);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 1001;
padding: 0;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.nexus-theme-toggle-btn:hover {
background: rgba(255, 255, 255, 0.08);
border-color: rgba(255, 255, 255, 0.15);
transform: translateY(-50%) scale(1.05);
}
.nexus-theme-toggle-btn:active {
transform: translateY(-50%) scale(0.95);
}
/* Theme specifics for Light Mode */
.theme-light .nexus-theme-toggle-btn {
border-color: rgba(0, 0, 0, 0.08);
background: rgba(0, 0, 0, 0.03);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
.theme-light .nexus-theme-toggle-btn:hover {
background: rgba(0, 0, 0, 0.06);
border-color: rgba(0, 0, 0, 0.12);
}
/* Icon Styling and Transition */
.theme-toggle-icon {
width: 18px;
height: 18px;
stroke-width: 2px;
}
/* In Dark Mode, the icon should be brand green (#10b981) */
.theme-toggle-icon.moon {
color: #10b981;
filter: drop-shadow(0 0 4px rgba(16, 185, 129, 0.3));
animation: morphMoon 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
/* In Light Mode, the icon should be a dark earthy gray (#2d2a26) */
.theme-toggle-icon.sun {
color: #2d2a26;
animation: morphSun 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
@keyframes morphMoon {
0% {
transform: rotate(-45deg) scale(0.7);
opacity: 0;
}
100% {
transform: rotate(0deg) scale(1);
opacity: 1;
}
}
@keyframes morphSun {
0% {
transform: rotate(45deg) scale(0.7);
opacity: 0;
}
100% {
transform: rotate(0deg) scale(1);
opacity: 1;
}
}