openreader/src/app/globals.css
Richard R d90e48aaf3 feat(html): add HTML/TXT/MD sentence and word highlighting, settings, and audiobook export support
Introduce sentence and word highlighting for HTML/TXT/MD documents, including new config options (`htmlHighlightEnabled`, `htmlWordHighlightEnabled`) and synced user preferences. Update the HTML viewer to support these highlights using custom spans, and add corresponding CSS styles. Extend document settings to allow toggling these features. Implement block-level locator sorting and display improvements. Add audiobook export support for HTML documents, including adapter and pipeline integration.

- Add `htmlHighlightEnabled` and `htmlWordHighlightEnabled` to config, user state, and Dexie storage
- Update HTML viewer to apply and manage highlights for sentences and words
- Add CSS for HTML highlight classes and scroll margin
- Extend document settings UI for HTML highlight toggles
- Support HTML audiobook export in modal and pipeline
- Improve block locator sorting and sidebar display for HTML
- Add HTML audiobook adapter and block parsing utilities
2026-05-17 11:14:02 -06:00

364 lines
8.2 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
);
}
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; }
}
/* 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 */
}