@tailwind base; @tailwind components; @tailwind utilities; @plugin "@tailwindcss/typography"; :root, html.light { --background: #ffffff; --foreground: #2d3748; --base: #f7fafc; --offbase: #e2e8f0; --accent: #ef4444; --secondary-accent: #ed6868; --muted: #718096; --prism-gradient: linear-gradient(90deg, #fecaca, #f87171, #ef4444 ); } html.dark { --background: #111111; --foreground: #ededed; --base: #171717; --offbase: #343434; --accent: #f87171; --secondary-accent: #eb6262; --muted: #a3a3a3; --prism-gradient: linear-gradient(90deg, #fca5a5, #fb7185, #f87171 ); } html.ocean { --background: #020617; --foreground: #e2e8f0; --base: #0f172a; --offbase: #1e293b; --accent: #38bdf8; --secondary-accent: #22d3ee; --muted: #94a3b8; --prism-gradient: linear-gradient(90deg, #7dd3fc, #38bdf8, #0ea5e9 ); } html.forest { --background: #0a0f0c; --foreground: #d4e8d0; --base: #111a15; --offbase: #1a2820; --accent: #4ade80; --secondary-accent: #22c55e; --muted: #7c8f85; --prism-gradient: linear-gradient(90deg, #86efac, #4ade80, #22c55e ); } html.sunset { --background: #1a0f0f; --foreground: #ffe4d6; --base: #2c1810; --offbase: #3d1f14; --accent: #ff6b6b; --secondary-accent: #f59e0b; --muted: #bc8f8f; --prism-gradient: linear-gradient(90deg, #fca5a5, #fb7185, #ff6b6b ); } html.sea { --background: #0c1922; --foreground: #e0f2fe; --base: #102c3d; --offbase: #1a3c52; --accent: #06b6d4; --secondary-accent: #0ea5e9; --muted: #7ca7c4; --prism-gradient: linear-gradient(90deg, #67e8f9, #22d3ee, #06b6d4 ); } html.mint { --background: #0f1916; --foreground: #dcfce7; --base: #132d27; --offbase: #1c3d35; --accent: #2dd4bf; --secondary-accent: #10b981; --muted: #75a99c; --prism-gradient: linear-gradient(90deg, #99f6e4, #5eead4, #2dd4bf ); } html.lavender { --background: #faf8ff; --foreground: #3b2e5a; --base: #f3effb; --offbase: #e4daf0; --accent: #7c3aed; --secondary-accent: #a78bfa; --muted: #8e7bab; --prism-gradient: linear-gradient(90deg, #c4b5fd, #a78bfa, #7c3aed ); } html.rose { --background: #fff8f8; --foreground: #4a2c2c; --base: #fef1f1; --offbase: #f5dada; --accent: #e11d48; --secondary-accent: #f472b6; --muted: #b08a8a; --prism-gradient: linear-gradient(90deg, #fda4af, #fb7185, #e11d48 ); } html.sand { --background: #fdfbf7; --foreground: #44392a; --base: #f7f2e8; --offbase: #e8dfc9; --accent: #b45309; --secondary-accent: #d97706; --muted: #9a8b74; --prism-gradient: linear-gradient(90deg, #fcd34d, #f59e0b, #b45309 ); } html.sky { --background: #f6faff; --foreground: #1e3a5f; --base: #edf4fc; --offbase: #d5e3f5; --accent: #2563eb; --secondary-accent: #3b82f6; --muted: #6b8db5; --prism-gradient: linear-gradient(90deg, #93c5fd, #60a5fa, #2563eb ); } html.slate { --background: #e8ecf0; --foreground: #2c3440; --base: #dde2e8; --offbase: #c8ced6; --accent: #5b7a9d; --secondary-accent: #7393b0; --muted: #7a8694; --prism-gradient: linear-gradient(90deg, #a3bdd4, #7393b0, #5b7a9d ); } /* Custom theme: variables are set via inline styles from JS/localStorage. These fallback values are only used before JS hydrates. */ html.custom { --background: #1a1a2e; --foreground: #e0e0e0; --base: #16213e; --offbase: #0f3460; --accent: #e94560; --secondary-accent: #f78da7; --muted: #7f8c8d; --prism-gradient: linear-gradient(90deg, #e94560, #f78da7, #7f8c8d ); } :root { --shadow-rgb: 15 23 42; --surface: var(--base); --surface-solid: color-mix(in srgb, var(--base) 96%, var(--background)); --surface-sunken: color-mix(in srgb, var(--base) 80%, var(--background)); --line: color-mix(in srgb, var(--offbase) 70%, transparent); --line-soft: color-mix(in srgb, var(--offbase) 45%, transparent); --line-strong: var(--offbase); --soft: color-mix(in srgb, var(--foreground) 74%, var(--muted)); --faint: color-mix(in srgb, var(--foreground) 52%, var(--muted)); --accent-wash: color-mix(in srgb, var(--accent) 12%, transparent); --accent-line: color-mix(in srgb, var(--accent) 40%, transparent); --accent-strong: color-mix(in srgb, var(--accent) 88%, var(--foreground)); --danger: #dc2626; --danger-strong: #b91c1c; --danger-wash: color-mix(in srgb, var(--danger) 12%, transparent); --elev-1: 0 1px 2px rgb(var(--shadow-rgb) / 0.10); --elev-2: 0 6px 16px rgb(var(--shadow-rgb) / 0.16); --elev-3: 0 18px 45px rgb(var(--shadow-rgb) / 0.22); --dur-fast: 120ms; --dur-base: 180ms; --dur-slow: 260ms; --ease: cubic-bezier(0.22, 0.61, 0.36, 1); } html.light, html.lavender, html.rose, html.sand, html.sky, html.slate { --shadow-rgb: 15 23 42; } html.dark, html.ocean, html.forest, html.sunset, html.sea, html.mint, html.custom { --shadow-rgb: 0 0 0; } body { color: var(--foreground); background: var(--background); font-family: var(--font-display), -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } h1, h2, h3, h4, h5, h6 { font-family: var(--font-display), system-ui, -apple-system, sans-serif; } /* * PDF page turn smoothing * - Keep a stable paint surface (prevents background flash) * - Fade-in newly rendered canvas */ .pdf-page-stage { /* Ensure we always paint something (matches app theme) while react-pdf swaps canvases */ background: var(--background); position: relative; } .pdf-viewer .react-pdf__Page { /* Make page paints more stable and avoid seeing the app background between renders */ background: var(--background); isolation: isolate; } /* Fade-in the freshly rendered canvas (react-pdf replaces the canvas element). */ .pdf-viewer .react-pdf__Page canvas { display: block; background: var(--background); /* GPU hint; helps reduce flicker on some browsers */ transform: translateZ(0); backface-visibility: hidden; opacity: 0; animation: pdf-canvas-fade-in 140ms ease-out forwards; } @keyframes pdf-canvas-fade-in { from { opacity: 0; } to { opacity: 1; } } @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 1ms !important; animation-iteration-count: 1 !important; scroll-behavior: auto !important; transition-duration: 1ms !important; } } /* Hide react-pdf's default tiny "Loading page..." placeholder to avoid jarring layout shifts. */ .pdf-viewer .react-pdf__message { display: none !important; } /* App shell utility (fullscreen, vertical layout) */ .app-shell { --header-height: 3.25rem; } /* Themed overlay using foreground color without relying on /opacity classes */ .overlay-dim { background-color: color-mix(in srgb, var(--foreground), transparent 75%); } /* Shared floating reader-sidebar sizing (Segments/Settings/Audiobook) */ .reader-sidebar-panel { width: 100%; } @media (min-width: 640px) { .reader-sidebar-panel { width: min( calc(100% - 1.5rem), clamp(360px, 32vw, 480px) ); } } /* Scrollbar styling for better fullscreen experience */ *::-webkit-scrollbar { width: 10px; height: 10px; } *::-webkit-scrollbar-track { background: var(--base); } *::-webkit-scrollbar-thumb { background: var(--offbase); border-radius: 6px; border: 2px solid var(--base); } *::-webkit-scrollbar-thumb:hover { background: var(--muted); } /* Subtle prism animated outline around list items (very light), matches the row's rounded-lg curve exactly */ .prism-outline { position: relative; border: 1px solid transparent; /* do not set border-radius here; let the element's rounded-lg from Tailwind define the curve */ background: linear-gradient(var(--card-fill, var(--offbase)), var(--card-fill, var(--offbase))) padding-box, var(--prism-gradient) border-box; background-clip: padding-box, border-box; background-size: 300% 300%; animation: prism-shift 8s linear infinite, prism-fade 3s ease-in-out infinite; } @keyframes prism-shift { 0% { background-position: 0% 50%; } 100% { background-position: 300% 50%; } } /* Gentle opacity pulse to simulate a "breath" without shadows spilling outside */ @keyframes prism-fade { 0% { opacity: 0.8; } 50% { opacity: 1.0; } 100% { opacity: 0.8; } } /* Block container scroll target — keeps anchor scroll-into-view aligned with the page padding when the reader has top header chrome. */ .openreader-html-block { scroll-margin-top: 4rem; scroll-margin-bottom: 4rem; } /* HTML/TXT/MD reader sentence + word highlights. These are applied by `src/lib/client/html/highlight.ts` wrapping the matched text in spans, so they survive any browser quirks around the (still-spotty) CSS Custom Highlight API. */ .openreader-html-highlight-sentence { background-color: color-mix(in srgb, var(--accent) 22%, transparent); border-radius: 2px; padding: 0 1px; } .openreader-html-highlight-word { background-color: color-mix(in srgb, var(--accent) 55%, transparent); border-radius: 2px; padding: 0 1px; } /* Static prism gradient divider (no animation) */ .prism-divider { width: 100%; height: 0.7px; /* use 2px then feather for a crisper center */ position: relative; border: 0; background: none; opacity: 0.95; } .prism-divider::before { content: ''; position: absolute; inset: 0; background: var(--prism-gradient); /* Feather edges so they appear thinner */ mask: linear-gradient(90deg, transparent 0%, black 8%, black 92%, transparent 100%); -webkit-mask: linear-gradient(90deg, transparent 0%, black 8%, black 92%, transparent 100%); border-radius: 999px; /* subtle rounding to reinforce taper */ } /* Generic determinate progress fill with a moving sheen. Apply to the colored fill element (the one whose width tracks progress); its track should clip. */ .progress-fill { position: relative; overflow: hidden; } .progress-fill::after { content: ''; position: absolute; inset: 0; background: linear-gradient( 90deg, transparent, color-mix(in srgb, var(--background) 55%, transparent), transparent ); transform: translateX(-100%); animation: progress-sheen 2.2s linear infinite; } @keyframes progress-sheen { 0% { transform: translateX(-100%); } /* sweep across at constant speed, then rest off-screen so the loop reset (from the right edge back to the left) is never visible */ 65% { transform: translateX(220%); } 100% { transform: translateX(220%); } }