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:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user