openreader/src/app/globals.css
Richard R 52ec0ad9b4 refactor(ui): modularize PDF loader and range slider visuals
Move PDF layout scan visualization and range slider styles into dedicated CSS
modules, isolating their styles from the global scope. Integrate PdfLayoutScan
component into the PDF viewer loader UI for animated parse progress. Refactor
progress bars to use a reusable progress-fill class with animated sheen effect.
Update range input to use CSS module for precision gauge styling.
2026-06-03 00:15:25 -06:00

454 lines
10 KiB
CSS

@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%); }
}