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
364 lines
8.2 KiB
CSS
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 */
|
|
}
|