From f08147cd805ac47adf5401aaac3948c5f1e79c19 Mon Sep 17 00:00:00 2001 From: Anurup R Krishnan Date: Sat, 24 Jan 2026 08:07:01 +0530 Subject: [PATCH 01/13] flawless reading experience but gotta fix the dropcaps and the constant rendering --- src/components/pages/ReaderView.tsx | 27 ++++++++++++++++++++++-- src/components/reader/ReaderSettings.tsx | 2 +- src/context/SettingsContext.tsx | 8 +++---- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/components/pages/ReaderView.tsx b/src/components/pages/ReaderView.tsx index 1d484f5..7fae8ae 100644 --- a/src/components/pages/ReaderView.tsx +++ b/src/components/pages/ReaderView.tsx @@ -1,3 +1,4 @@ +// original import React, { useState, useEffect, useRef, useCallback } from "react"; import type { Book, Bookmark } from "@/types"; import { useSettings } from "@/context/SettingsContext"; @@ -115,15 +116,17 @@ const ReaderView: React.FC = ({ "color": `${readerForeground} !important`, "background-color": `${readerBackground} !important`, "padding-top": `${pageMargin}px !important`, - "padding-bottom": `${pageMargin}px !important`, - "padding-left": `${continuous ? pageMargin : 0}px !important`, + "padding-bottom": `${pageMargin}px !important`, "padding-left": `${continuous ? pageMargin : 0}px !important`, "padding-right": `${continuous ? pageMargin : 0}px !important`, ...(continuous ? { "max-width": `${maxTextWidth}ch !important`, "margin": "0 auto !important", + "padding-bottom": "2em !important", + } : { "max-width": "none !important", "margin": "0 !important", + "padding-bottom": "2em !important", }), }, "p": { @@ -131,6 +134,8 @@ const ReaderView: React.FC = ({ "font-size": "inherit !important", "line-height": "inherit !important", "color": "inherit !important", + "text-indent": "0 !important", + "margin-bottom": `${paragraphSpacing}px !important`, "text-align": `${textAlignment} !important`, "hyphens": hyphenation ? "auto !important" : "none !important", "-webkit-hyphens": hyphenation ? "auto !important" : "none !important", @@ -442,6 +447,24 @@ const ReaderView: React.FC = ({ if (firstPara) { firstPara.classList.add('first-paragraph'); } + + const body = content.document.body; + + if (continuous) { + const lastElement = body.lastElementChild; + if (lastElement && !lastElement.classList.contains('chapter-end-spacer')) { + const spacer = content.document.createElement('div'); + spacer.className = 'chapter-end-spacer'; + spacer.style.cssText = ` + height: 2em; + width: 100%; + pointer-events: none; + `; + body.appendChild(spacer); + } + } + + }); applyStyles(); diff --git a/src/components/reader/ReaderSettings.tsx b/src/components/reader/ReaderSettings.tsx index 5df6b93..b552dff 100644 --- a/src/components/reader/ReaderSettings.tsx +++ b/src/components/reader/ReaderSettings.tsx @@ -310,7 +310,7 @@ const ReaderSettings: React.FC = () => { label="Width" value={maxTextWidth} min={30} - max={100} + max={150} onChange={setMaxTextWidth} formatValue={(v) => `${v}ch`} /> diff --git a/src/context/SettingsContext.tsx b/src/context/SettingsContext.tsx index cdaa08a..d372e90 100644 --- a/src/context/SettingsContext.tsx +++ b/src/context/SettingsContext.tsx @@ -82,14 +82,14 @@ const SettingsContext = createContext(undefined); const DEFAULTS = { fontSize: 19, - lineHeight: 1.85, + lineHeight: 1.65, textAlignment: "justify" as TextAlignment, fontPairing: "merriweather-georgia", - maxTextWidth: 60, + maxTextWidth: 150, hyphenation: true, pageMargin: 40, - paragraphSpacing: 18, - dropCaps: true, + paragraphSpacing: 17, + dropCaps: false, continuous: false, spread: false, direction: "ltr" as "ltr" | "rtl", From c13efdf261370d53ddae692eb3f021540a40e91f Mon Sep 17 00:00:00 2001 From: Anurup R Krishnan Date: Sat, 24 Jan 2026 08:10:53 +0530 Subject: [PATCH 02/13] brighntess target the document body now not the iframe --- src/components/pages/ReaderView.tsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/components/pages/ReaderView.tsx b/src/components/pages/ReaderView.tsx index 7fae8ae..ea62cdb 100644 --- a/src/components/pages/ReaderView.tsx +++ b/src/components/pages/ReaderView.tsx @@ -115,6 +115,7 @@ const ReaderView: React.FC = ({ "line-height": `${lineHeight} !important`, "color": `${readerForeground} !important`, "background-color": `${readerBackground} !important`, + "filter": `brightness(${brightness}%) grayscale(${grayscale ? 1 : 0}) !important`, "padding-top": `${pageMargin}px !important`, "padding-bottom": `${pageMargin}px !important`, "padding-left": `${continuous ? pageMargin : 0}px !important`, "padding-right": `${continuous ? pageMargin : 0}px !important`, @@ -182,7 +183,7 @@ const ReaderView: React.FC = ({ "margin": "1em auto !important", } }); - }, [fontSize, lineHeight, fontPairing, textAlignment, readerForeground, readerBackground, readerAccent, pageMargin, maxTextWidth, hyphenation, paragraphSpacing, dropCaps, getFontFamily, continuous, grayscale, isDarkBackground]); + }, [fontSize, lineHeight, fontPairing, textAlignment, readerForeground, readerBackground, readerAccent, pageMargin, maxTextWidth, hyphenation, paragraphSpacing, dropCaps, getFontFamily, continuous, grayscale, brightness, isDarkBackground]); // Navigation const goToNextPage = useCallback(() => { @@ -493,6 +494,15 @@ const ReaderView: React.FC = ({ if (isReady) applyStyles(); }, [isReady, applyStyles]); + // Apply global UI filter so header/footer/panels also follow brightness/grayscale + useEffect(() => { + const prev = document.body.style.filter || ""; + document.body.style.filter = `brightness(${brightness}%) grayscale(${grayscale ? 1 : 0})`; + return () => { + document.body.style.filter = prev; + }; + }, [brightness, grayscale]); + // Calculate reading time useEffect(() => { const remaining = Math.max(0, totalPages - currentPage); @@ -590,9 +600,8 @@ const ReaderView: React.FC = ({ ref={containerRef} className={`flex-1 w-full h-full ${continuous ? "overflow-y-auto" : "overflow-hidden"}`} style={{ - filter: `brightness(${brightness}%) grayscale(${grayscale ? 1 : 0})`, - scrollbarWidth: showScrollbar ? 'auto' : 'none', - }} + scrollbarWidth: showScrollbar ? 'auto' : 'none', + }} /> Date: Wed, 18 Feb 2026 19:54:25 +0530 Subject: [PATCH 03/13] refactor layout shell and unify global page margins --- index.css | 324 ++++++++++++++++++++++++++++++++++++---------------- src/App.tsx | 153 ++++++++++++------------- 2 files changed, 302 insertions(+), 175 deletions(-) diff --git a/index.css b/index.css index ef23d92..ca3afad 100644 --- a/index.css +++ b/index.css @@ -6,12 +6,16 @@ :root { --accent: 184 149 108; + /* #b8956c - Antique Gold */ --accent-dark: 212 181 139; + /* #d4b58b - Parchment Gold */ --radius: 16px; - --shadow-color: 0 0 0; - --shadow-elevation-low: 0 1px 2px rgba(var(--shadow-color), 0.04), 0 2px 4px rgba(var(--shadow-color), 0.02); - --shadow-elevation-medium: 0 4px 8px rgba(var(--shadow-color), 0.06), 0 8px 16px rgba(var(--shadow-color), 0.03); - --shadow-elevation-high: 0 8px 16px rgba(var(--shadow-color), 0.08), 0 16px 32px rgba(var(--shadow-color), 0.04); + --shadow-color: 28 24 20; + --shadow-elevation-low: 0 1px 2px rgba(var(--shadow-color), 0.06), 0 2px 4px rgba(var(--shadow-color), 0.04); + --shadow-elevation-medium: 0 4px 8px rgba(var(--shadow-color), 0.08), 0 8px 16px rgba(var(--shadow-color), 0.06); + --shadow-elevation-high: 0 8px 16px rgba(var(--shadow-color), 0.1), 0 16px 32px rgba(var(--shadow-color), 0.08); + --page-max-width: 84rem; + --page-gutter: clamp(1rem, 3vw, 2rem); } .dark { @@ -22,24 +26,33 @@ html { scroll-behavior: smooth; font-size: 16px; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } - + body { overflow-x: hidden; letter-spacing: -0.01em; font-family: 'Inter', system-ui, sans-serif; font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11'; + /* Grainy texture overlay logic handled in main app wrapper for better performance */ } ::selection { - background-color: rgba(184, 149, 108, 0.25); + background-color: rgba(184, 149, 108, 0.3); + color: inherit; } .dark ::selection { - background-color: rgba(212, 181, 139, 0.25); + background-color: rgba(212, 181, 139, 0.3); } - h1, h2, h3, h4, h5, h6 { + h1, + h2, + h3, + h4, + h5, + h6 { font-family: 'Crimson Pro', Georgia, serif; letter-spacing: -0.025em; font-weight: 600; @@ -52,8 +65,8 @@ } *::-webkit-scrollbar { - width: 8px; - height: 8px; + width: 6px; + height: 6px; } *::-webkit-scrollbar-track { @@ -62,17 +75,18 @@ *::-webkit-scrollbar-thumb { background-color: rgba(var(--accent), 0.2); - border-radius: 4px; - border: 2px solid transparent; + border-radius: 99px; + border: 1px solid transparent; background-clip: content-box; + transition: background-color 0.2s; } *::-webkit-scrollbar-thumb:hover { - background-color: rgba(var(--accent), 0.35); + background-color: rgba(var(--accent), 0.4); } .dark *::-webkit-scrollbar-thumb { - background-color: rgba(var(--accent-dark), 0.25); + background-color: rgba(var(--accent-dark), 0.2); } .dark *::-webkit-scrollbar-thumb:hover { @@ -81,6 +95,7 @@ } @layer components { + /* Layout Classes */ .standard-layout { @apply transition-all duration-300 ease-out; @@ -92,12 +107,20 @@ .standard-main { @apply transition-all duration-300 ease-out; + padding-top: clamp(5.25rem, 8vw, 6rem); + padding-bottom: clamp(7rem, 10vw, 8.5rem); } .reader-main { @apply transition-all duration-500 ease-out; } + .page-shell { + width: min(100%, var(--page-max-width)); + margin-inline: auto; + padding-inline: var(--page-gutter); + } + /* Glass Effects */ .glass { @apply bg-white/75 dark:bg-dark-surface/75 backdrop-blur-xl border border-black/[0.06] dark:border-white/[0.06]; @@ -150,12 +173,12 @@ .btn-primary { @apply relative inline-flex items-center justify-center gap-2.5 rounded-2xl px-6 py-3.5 text-sm font-semibold text-white overflow-hidden transition-all duration-200 ease-out; background: linear-gradient(135deg, rgb(var(--accent)) 0%, #d97706 100%); - box-shadow: 0 4px 16px rgba(var(--accent), 0.3), 0 8px 32px rgba(var(--accent), 0.15), inset 0 1px 0 rgba(255,255,255,0.2); + box-shadow: 0 4px 16px rgba(var(--accent), 0.3), 0 8px 32px rgba(var(--accent), 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.2); } .btn-primary:hover { transform: translateY(-2px); - box-shadow: 0 8px 24px rgba(var(--accent), 0.4), 0 16px 48px rgba(var(--accent), 0.2), inset 0 1px 0 rgba(255,255,255,0.2); + box-shadow: 0 8px 24px rgba(var(--accent), 0.4), 0 16px 48px rgba(var(--accent), 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.2); } .btn-primary:active { @@ -165,11 +188,11 @@ .dark .btn-primary { background: linear-gradient(135deg, rgb(var(--accent-dark)) 0%, #f59e0b 100%); - box-shadow: 0 4px 16px rgba(var(--accent-dark), 0.25), 0 8px 32px rgba(var(--accent-dark), 0.12), inset 0 1px 0 rgba(255,255,255,0.15); + box-shadow: 0 4px 16px rgba(var(--accent-dark), 0.25), 0 8px 32px rgba(var(--accent-dark), 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.15); } .dark .btn-primary:hover { - box-shadow: 0 8px 24px rgba(var(--accent-dark), 0.35), 0 16px 48px rgba(var(--accent-dark), 0.18), inset 0 1px 0 rgba(255,255,255,0.15); + box-shadow: 0 8px 24px rgba(var(--accent-dark), 0.35), 0 16px 48px rgba(var(--accent-dark), 0.18), inset 0 1px 0 rgba(255, 255, 255, 0.15); } .btn-secondary { @@ -252,7 +275,7 @@ .toggle-thumb { @apply absolute w-5 h-5 rounded-full bg-white shadow-lg transition-all duration-300 ease-out; left: 4px; - box-shadow: 0 2px 8px rgba(0,0,0,0.15); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); } .toggle.active .toggle-thumb { @@ -260,7 +283,7 @@ } .dark .toggle-thumb { - box-shadow: 0 2px 8px rgba(0,0,0,0.3); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); } /* Range Input Styling */ @@ -310,25 +333,30 @@ } @layer utilities { + /* Layout Utilities */ .container-fluid { - @apply w-full max-w-none px-4 sm:px-6 lg:px-8; + @apply w-full max-w-none; + padding-inline: var(--page-gutter); } .container-narrow { - @apply max-w-4xl mx-auto px-4 sm:px-6 lg:px-8; + @apply max-w-4xl mx-auto; + padding-inline: var(--page-gutter); } .container-wide { - @apply max-w-7xl mx-auto px-4 sm:px-6 lg:px-8; + @apply mx-auto; + width: min(100%, var(--page-max-width)); + padding-inline: var(--page-gutter); } /* Spacing Utilities */ - .space-y-fluid > * + * { + .space-y-fluid>*+* { margin-top: clamp(1rem, 2.5vw, 2rem); } - .space-x-fluid > * + * { + .space-x-fluid>*+* { margin-left: clamp(0.5rem, 1.5vw, 1rem); } @@ -366,7 +394,7 @@ /* Book-specific utilities */ .book-spine-shadow { - box-shadow: inset -8px 0 16px -8px rgba(0,0,0,0.4); + box-shadow: inset -8px 0 16px -8px rgba(0, 0, 0, 0.4); } .book-cover-glow { @@ -379,9 +407,12 @@ /* Scrollbar Utilities */ .scrollbar-hide { - -ms-overflow-style: none; /* IE and Edge */ - scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; + /* IE and Edge */ + scrollbar-width: none; + /* Firefox */ } + .scrollbar-hide::-webkit-scrollbar { display: none; } @@ -389,128 +420,229 @@ /* Enhanced Animations */ @keyframes fadeIn { - from { opacity: 0; } - to { opacity: 1; } + from { + opacity: 0; + } + + to { + opacity: 1; + } } @keyframes fadeOut { - from { opacity: 1; } - to { opacity: 0; } + from { + opacity: 1; + } + + to { + opacity: 0; + } } @keyframes fadeInUp { - from { - opacity: 0; - transform: translateY(24px); + from { + opacity: 0; + transform: translateY(24px); } - to { - opacity: 1; - transform: translateY(0); + + to { + opacity: 1; + transform: translateY(0); } } @keyframes fadeInDown { - from { - opacity: 0; - transform: translateY(-24px); + from { + opacity: 0; + transform: translateY(-24px); } - to { - opacity: 1; - transform: translateY(0); + + to { + opacity: 1; + transform: translateY(0); } } @keyframes scaleIn { - from { - opacity: 0; - transform: scale(0.95); + from { + opacity: 0; + transform: scale(0.95); } - to { - opacity: 1; - transform: scale(1); + + to { + opacity: 1; + transform: scale(1); } } @keyframes slideInLeft { - from { - opacity: 0; - transform: translateX(-32px); + from { + opacity: 0; + transform: translateX(-32px); } - to { - opacity: 1; - transform: translateX(0); + + to { + opacity: 1; + transform: translateX(0); } } @keyframes slideInRight { - from { - opacity: 0; - transform: translateX(32px); + from { + opacity: 0; + transform: translateX(32px); } - to { - opacity: 1; - transform: translateX(0); + + to { + opacity: 1; + transform: translateX(0); } } @keyframes shimmer { - 0% { transform: translateX(-100%); } - 100% { transform: translateX(100%); } + 0% { + transform: translateX(-100%); + } + + 100% { + transform: translateX(100%); + } } @keyframes float { - 0%, 100% { transform: translateY(0px) rotate(0deg); } - 33% { transform: translateY(-10px) rotate(1deg); } - 66% { transform: translateY(-5px) rotate(-1deg); } + + 0%, + 100% { + transform: translateY(0px) rotate(0deg); + } + + 33% { + transform: translateY(-10px) rotate(1deg); + } + + 66% { + transform: translateY(-5px) rotate(-1deg); + } } @keyframes pulse-soft { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.7; } + + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.7; + } } @keyframes gradient-shift { - 0%, 100% { background-position: 0% 50%; } - 50% { background-position: 100% 50%; } + + 0%, + 100% { + background-position: 0% 50%; + } + + 50% { + background-position: 100% 50%; + } } @keyframes bounce-gentle { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(-4px); } + + 0%, + 100% { + transform: translateY(0); + } + + 50% { + transform: translateY(-4px); + } } /* Animation Classes */ -.animate-fadeIn { animation: fadeIn 0.4s ease-out forwards; } -.animate-fadeOut { animation: fadeOut 0.3s ease-out forwards; } -.animate-fadeInUp { animation: fadeInUp 0.5s ease-out forwards; } -.animate-fadeInDown { animation: fadeInDown 0.5s ease-out forwards; } -.animate-scaleIn { animation: scaleIn 0.3s ease-out forwards; } -.animate-slideInLeft { animation: slideInLeft 0.4s ease-out forwards; } -.animate-slideInRight { animation: slideInRight 0.4s ease-out forwards; } -.animate-shimmer { animation: shimmer 2.5s infinite; } -.animate-float { animation: float 6s ease-in-out infinite; } -.animate-pulse-soft { animation: pulse-soft 2.5s ease-in-out infinite; } -.animate-bounce-gentle { animation: bounce-gentle 2s ease-in-out infinite; } +.animate-fadeIn { + animation: fadeIn 0.4s ease-out forwards; +} + +.animate-fadeOut { + animation: fadeOut 0.3s ease-out forwards; +} + +.animate-fadeInUp { + animation: fadeInUp 0.5s ease-out forwards; +} + +.animate-fadeInDown { + animation: fadeInDown 0.5s ease-out forwards; +} + +.animate-scaleIn { + animation: scaleIn 0.3s ease-out forwards; +} + +.animate-slideInLeft { + animation: slideInLeft 0.4s ease-out forwards; +} + +.animate-slideInRight { + animation: slideInRight 0.4s ease-out forwards; +} + +.animate-shimmer { + animation: shimmer 2.5s infinite; +} + +.animate-float { + animation: float 6s ease-in-out infinite; +} + +.animate-pulse-soft { + animation: pulse-soft 2.5s ease-in-out infinite; +} + +.animate-bounce-gentle { + animation: bounce-gentle 2s ease-in-out infinite; +} /* Stagger Delays */ -.stagger-1 { animation-delay: 100ms; } -.stagger-2 { animation-delay: 200ms; } -.stagger-3 { animation-delay: 300ms; } -.stagger-4 { animation-delay: 400ms; } -.stagger-5 { animation-delay: 500ms; } -.stagger-6 { animation-delay: 600ms; } +.stagger-1 { + animation-delay: 100ms; +} + +.stagger-2 { + animation-delay: 200ms; +} + +.stagger-3 { + animation-delay: 300ms; +} + +.stagger-4 { + animation-delay: 400ms; +} + +.stagger-5 { + animation-delay: 500ms; +} + +.stagger-6 { + animation-delay: 600ms; +} /* Reduced Motion Support */ @media (prefers-reduced-motion: reduce) { - .reduce-motion *, - .reduce-motion *::before, + + .reduce-motion *, + .reduce-motion *::before, .reduce-motion *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } - + .reduce-motion .animate-float, .reduce-motion .animate-bounce-gentle, .reduce-motion .animate-pulse-soft { @@ -523,11 +655,11 @@ .card { @apply border-2 border-black/20 dark:border-white/20; } - + .btn-primary { @apply border-2 border-black/20; } - + .input { @apply border-2 border-black/20 dark:border-white/20; } @@ -538,7 +670,7 @@ .no-print { display: none !important; } - + * { background: white !important; color: black !important; diff --git a/src/App.tsx b/src/App.tsx index cc75758..035f958 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,108 +1,91 @@ -import React, { useState, useEffect, useCallback, useMemo } from "react"; -import type { Session } from "@supabase/supabase-js"; -import { Theme, View, Book } from "@/types"; +import React, { useEffect, useCallback, useMemo } from "react"; +import { useUser, useAuth } from "@/hooks/useAuth"; import { useBookLibrary } from "./hooks/useBookLibrary"; import { useReadingStats } from "./hooks/useReadingStats"; import { useSettings } from "@/context/SettingsContext"; +import { useSessionStore } from "@/store/useSessionStore"; +import { useUIStore } from "@/store/useUIStore"; +import { Theme, View, Book } from "@/types"; +import { BookOpen } from "lucide-react"; + import Header from "./components/ui/Header"; import Navigation from "./components/ui/Navigation"; import LibraryGrid from "./components/pages/LibraryGrid"; import ReaderView from "./components/pages/ReaderView"; import SettingsView from "./components/pages/SettingsView"; import StatsView from "./components/pages/StatsView"; -import Auth from "./components/pages/Auth"; -import { supabase } from "./lib/supabase"; -import { BookOpen } from "lucide-react"; +import ClerkAuth from "./components/auth/ClerkAuth"; + +const DISABLE_AUTH = import.meta.env.VITE_DISABLE_AUTH === "true"; const App: React.FC = () => { - const [session, setSession] = useState(null); - const [isAuthLoading, setIsAuthLoading] = useState(true); - const [isGuest, setIsGuest] = useState(false); - const { - dailyGoal, - weeklyGoal, - setDailyGoal, + // Global Stores + const { isGuest, setIsGuest, reset: resetSession } = useSessionStore(); + const { theme, view, selectedBook, searchTerm, setView, setSelectedBook, setSearchTerm, toggleTheme } = useUIStore(); + + // Clerk Hooks + const { isLoaded, isSignedIn, user } = useUser(); + const { signOut } = useAuth(); + + // Settings Context + const { + dailyGoal, + weeklyGoal, + setDailyGoal, setWeeklyGoal, - reduceMotion + reduceMotion } = useSettings(); - useEffect(() => { - let active = true; - const init = async () => { - try { - const { data: { session: s } } = await supabase.auth.getSession(); - if (!active) return; - if (s) { setSession(s); setIsGuest(false); } - } catch (e) { - console.error("Session init failed:", e); - } finally { - if (active) setIsAuthLoading(false); - } - }; - init(); - const { data } = supabase.auth.onAuthStateChange((_e, s) => { - if (!active) return; - setSession(s); - setIsGuest(false); - setIsAuthLoading(false); - }); - return () => { active = false; data.subscription.unsubscribe(); }; - }, []); - - const [theme, setTheme] = useState(Theme.LIGHT); - const [view, setView] = useState(View.LIBRARY); - const [selectedBook, setSelectedBook] = useState(null); - const [searchTerm, setSearchTerm] = useState(""); + // Library & Stats Hooks + // When auth is disabled, always treat as persistent (local storage mode) + const persistent = DISABLE_AUTH ? true : (isSignedIn && !isGuest); - const persistent = !isGuest && Boolean(session); const { books, sortedBooks, recentBooks, favoriteBooks, seriesGroups, addBook, updateBookProgress, toggleFavorite, addBookmark, removeBookmark, sortBy, setSortBy, filterBy, setFilterBy, isLoading: libLoading, + reloadBooks } = useBookLibrary({ persistent }); + const { stats, startSession, endSession } = useReadingStats(books); + // Handlers const handleGuestMode = useCallback(() => { setIsGuest(true); - setSession(null); - setIsAuthLoading(false); - }, []); + }, [setIsGuest]); const handleShowLogin = useCallback(() => { setIsGuest(false); - setSession(null); - }, []); + // Clerk handles the rest (redirects to sign in if we render logic correctly) + }, [setIsGuest]); const handleSignOut = useCallback(async () => { - setIsAuthLoading(true); - try { await supabase.auth.signOut(); } - catch (e) { console.error("Sign out error:", e); } - finally { setSession(null); setIsGuest(false); setIsAuthLoading(false); } - }, []); - - useEffect(() => { - const root = document.documentElement; - root.classList.toggle("dark", theme === Theme.DARK); - root.classList.toggle("reduce-motion", reduceMotion); - - const bgColor = theme === Theme.DARK ? "#0f0e0d" : "#fefcf8"; - document.body.style.backgroundColor = bgColor; - document.body.style.transition = reduceMotion ? "none" : "background-color 0.3s ease"; - }, [theme, reduceMotion]); - - const toggleTheme = useCallback(() => setTheme(t => t === Theme.LIGHT ? Theme.DARK : Theme.LIGHT), []); + if (isGuest) { + setIsGuest(false); + resetSession(); + } else { + await signOut(); + resetSession(); + } + }, [isGuest, setIsGuest, resetSession, signOut]); const handleSelectBook = useCallback((book: Book) => { setSelectedBook(book); setView(View.READER); startSession(book.id); - }, [startSession]); + }, [setSelectedBook, setView, startSession]); const handleCloseReader = useCallback(() => { endSession(0); setView(View.LIBRARY); setSelectedBook(null); - }, [endSession]); + reloadBooks(); + }, [endSession, setView, setSelectedBook, reloadBooks]); + + const handleUpdateGoal = useCallback((d: number, w: number) => { + setDailyGoal(d); + setWeeklyGoal(w); + }, [setDailyGoal, setWeeklyGoal]); const filteredBooks = useMemo(() => { if (!searchTerm) return books; @@ -110,12 +93,19 @@ const App: React.FC = () => { return books.filter(b => b.title.toLowerCase().includes(term) || b.author.toLowerCase().includes(term)); }, [books, searchTerm]); - const handleUpdateGoal = useCallback((d: number, w: number) => { - setDailyGoal(d); - setWeeklyGoal(w); - }, [setDailyGoal, setWeeklyGoal]); + // Theme Effect + useEffect(() => { + const root = document.documentElement; + root.classList.toggle("dark", theme === Theme.DARK); + root.classList.toggle("reduce-motion", reduceMotion); + + const bgColor = theme === Theme.DARK ? "#0f0e0d" : "#fefcf8"; + document.body.style.backgroundColor = bgColor; + document.body.style.transition = reduceMotion ? "none" : "background-color 0.3s ease"; + }, [theme, reduceMotion]); - if (isAuthLoading) { + // Render - Loading State (Clerk) + if (!DISABLE_AUTH && !isLoaded) { return (
@@ -132,14 +122,17 @@ const App: React.FC = () => { ); } - if (!session && !isGuest) return ; + // Render - Auth (skip entirely when auth is disabled) + if (!DISABLE_AUTH && !isSignedIn && !isGuest) { + return ; + } const isReader = view === View.READER; - const layoutClasses = isReader ? "immersive-layout" : "standard-layout"; + // Render - App return ( -
- {/* Enhanced Background Decorations */} +
+ {/* Background Decorations */}
@@ -156,13 +149,15 @@ const App: React.FC = () => { onSearch={setSearchTerm} isGuest={isGuest} onShowLogin={isGuest ? handleShowLogin : undefined} - onSignOut={session ? handleSignOut : undefined} + onSignOut={isSignedIn ? handleSignOut : undefined} + userEmail={user?.primaryEmailAddress?.emailAddress} + userImage={user?.imageUrl} /> )} - {/* Main Content Container */} -
-
+ {/* Main Content */} +
+
{view === View.LIBRARY && ( Date: Wed, 18 Feb 2026 19:58:06 +0530 Subject: [PATCH 04/13] normalize page spacing utilities and remove duplicate settings block --- index.css | 9 +++++ src/components/pages/LibraryGrid.tsx | 33 +++++++--------- src/components/pages/SettingsView.tsx | 54 ++++++--------------------- src/components/pages/StatsView.tsx | 2 +- 4 files changed, 36 insertions(+), 62 deletions(-) diff --git a/index.css b/index.css index ca3afad..3bc32ef 100644 --- a/index.css +++ b/index.css @@ -121,6 +121,15 @@ padding-inline: var(--page-gutter); } + .page-narrow { + @apply max-w-4xl mx-auto; + } + + .page-stack { + @apply space-y-8; + padding-bottom: clamp(3rem, 8vw, 4.5rem); + } + /* Glass Effects */ .glass { @apply bg-white/75 dark:bg-dark-surface/75 backdrop-blur-xl border border-black/[0.06] dark:border-white/[0.06]; diff --git a/src/components/pages/LibraryGrid.tsx b/src/components/pages/LibraryGrid.tsx index 0469f76..90d4907 100644 --- a/src/components/pages/LibraryGrid.tsx +++ b/src/components/pages/LibraryGrid.tsx @@ -93,7 +93,7 @@ const LibraryGrid: React.FC = ({ if (isLoading) { return ( -
+
@@ -151,10 +151,10 @@ const LibraryGrid: React.FC = ({ ); const HorizontalScroll = ({ books: scrollBooks }: { books: Book[] }) => ( -
+
{scrollBooks.map((book) => (
- +
))}
@@ -182,11 +182,10 @@ const LibraryGrid: React.FC = ({ onSelect(opt.value); onClose(); }} - className={`w-full text-left px-3 py-2 text-sm transition-colors ${ - value === opt.value + className={`w-full text-left px-3 py-2 text-sm transition-colors ${value === opt.value ? "text-light-accent dark:text-dark-accent font-medium bg-light-accent/5 dark:bg-dark-accent/5" : "text-light-text dark:text-dark-text hover:bg-black/[0.03] dark:hover:bg-white/[0.03]" - }`} + }`} > {opt.label} @@ -195,7 +194,7 @@ const LibraryGrid: React.FC = ({ ) : null; return ( -
+

Library

@@ -207,22 +206,20 @@ const LibraryGrid: React.FC = ({
diff --git a/src/components/pages/SettingsView.tsx b/src/components/pages/SettingsView.tsx index 4f36e81..15ace99 100644 --- a/src/components/pages/SettingsView.tsx +++ b/src/components/pages/SettingsView.tsx @@ -139,6 +139,7 @@ const SettingsView: React.FC = () => { weeklyGoal, setWeeklyGoal, showStreakReminder, setShowStreakReminder, trackingEnabled, setTrackingEnabled, + showFloatingCapsule, setShowFloatingCapsule, resetToDefaults, } = settings; @@ -336,7 +337,7 @@ const SettingsView: React.FC = () => { }; return ( -
+
{/* Hero Header */}
@@ -393,6 +394,16 @@ const SettingsView: React.FC = () => {
+ {/* Global Interface Toggles */} +
+ +
+ {/* Tab Content */}
{activeTab === "colors" && ( @@ -523,47 +534,6 @@ const SettingsView: React.FC = () => { )} - {activeTab === "shortcuts" && ( - <> -
-
- setKeybinds({ ...keybinds, nextPage: keys })} - /> - setKeybinds({ ...keybinds, prevPage: keys })} - /> - setKeybinds({ ...keybinds, toggleBookmark: keys })} - /> - setKeybinds({ ...keybinds, toggleFullscreen: keys })} - /> - setKeybinds({ ...keybinds, toggleUI: keys })} - /> - setKeybinds({ ...keybinds, close: keys })} - /> -
-

- Click on a shortcut to edit. Press keys to set, Esc to cancel, Backspace to clear. -

-
- - )}
); diff --git a/src/components/pages/StatsView.tsx b/src/components/pages/StatsView.tsx index 5dbd3df..55d9042 100644 --- a/src/components/pages/StatsView.tsx +++ b/src/components/pages/StatsView.tsx @@ -187,7 +187,7 @@ const StatsView: React.FC = ({ stats, dailyGoal, weeklyGoal }) = ] as const; return ( -
+

Stats

From 049ccb1cbd5bb4f58d2ed5958464051eb3ae4950 Mon Sep 17 00:00:00 2001 From: Anurup R Krishnan Date: Wed, 18 Feb 2026 19:58:27 +0530 Subject: [PATCH 05/13] respect mobile safe-area inset for bottom navigation --- src/components/ui/Navigation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ui/Navigation.tsx b/src/components/ui/Navigation.tsx index b109961..ad15268 100644 --- a/src/components/ui/Navigation.tsx +++ b/src/components/ui/Navigation.tsx @@ -79,7 +79,7 @@ const Navigation: React.FC = ({ activeView, onNavigate, isReade }, [activeView, isReaderActive]); return ( -