diora-web/static/css/app.css
mrwnslz c2a9873b0d PDF-Reader: ein Zeichenweg, Seitenmodus neu, Seitensprung für beide Modi (WIP, ungetestet)
- 58-pdf.js: _pdfDraw – eine Zeichnung je Canvas (die vorige wird
  abgebrochen und abgewartet), Pixeldichte, höchstens 16,7 MP; Seiten, die
  gehen, geben auch pdf.js' Seitendaten frei; ein gemeinsamer pdf.js-Worker
  (_pdfLoad) auch für Uploads, Seitenzahl, Hefte und Cover; Inhalts-
  verzeichnis parallel aufgelöst, auch mit Seitenindex als Ziel;
  ResizeObserver: Drehen, Breite, Leisten – neu an derselben Seite.
- 66-pdf-paginated.js: Seitenmodus auf _pdfDraw, Zuschnitt in
  Bildschirmkoordinaten (gedrehte Seiten) und höchstens 2,5× der ganzen
  Seite (eine fast leere Seite wird nicht unendlich groß); nächste und
  vorige Seite vorgezeichnet; Tippen zum Blättern blendet die Leisten
  nicht mehr ein; Wischen nur deutlich seitwärts; Mausrad; Pinch setzt die
  Vorschau zurück; nach dem Blättern wird gespeichert und 'diora-pdf-turn'
  gemeldet (E-Ink-Refresh).
- pdfGoToPage für beide Modi: Inhaltsverzeichnis, Seitenzahl im Kopf,
  Lesezeichen, Tasten (Seitenmodus jetzt auch Leertaste, Bild auf/ab).
- Beim Scrollen wird die aktuelle Seite mitgeführt (Lesezeichen sagten
  immer „Seite 1“).
- Schalter Scrollen | Seiten im PDF-Panel; Zoom-Regler zeichnet erst neu,
  wenn er ruht.
- openBook hängt seine Listener über _readerListen an; closeReader nimmt
  sie ab (vorher sammelten sie sich bei jedem Öffnen an). Abbrechen und
  ein zweites Buch geben das alte Dokument frei; der entschlüsselte
  PDF-Puffer wird nicht mehr gehalten.
- Invertieren dreht die Farbtöne zurück (Fotos keine Negative).
- e2e/fixtures/make_test_pdf.py: Test-PDF mit gemischten Formaten,
  Outline, Links und einer fast leeren Seite.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 17:52:28 +01:00

2082 lines
54 KiB
CSS

/* =========================================================
diora — dark radio player theme
========================================================= */
/* Self-hosted (no external request, works offline via the service worker's
app-shell cache) — variable font covering weight 200-900 in one file per
style/subset. Latin + Latin Extended only (covers German/English text). */
@font-face {
font-family: 'Source Serif 4';
font-style: normal;
font-weight: 200 900;
font-display: swap;
src: url('/static/fonts/SourceSerif4-normal-latin.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Source Serif 4';
font-style: normal;
font-weight: 200 900;
font-display: swap;
src: url('/static/fonts/SourceSerif4-normal-latin-ext.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
font-family: 'Source Serif 4';
font-style: italic;
font-weight: 200 900;
font-display: swap;
src: url('/static/fonts/SourceSerif4-italic-latin.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Source Serif 4';
font-style: italic;
font-weight: 200 900;
font-display: swap;
src: url('/static/fonts/SourceSerif4-italic-latin-ext.woff2') format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html, body {
height: 100%;
overflow: hidden;
scrollbar-color: var(--raised) var(--bg);
}
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: var(--bg); }
::-webkit-scrollbar-thumb { background: var(--raised); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--border-strong); }
:root {
--bg: #000;
--bg-card: transparent;
--bg-alt: transparent;
--bg-row: transparent;
--bg-row-alt: transparent;
--border: #2a2a2a;
--accent: #e63946;
--accent-hover: #ff4d58;
--green: #2ecc71;
--yellow: #f1c40f;
--font: 'Courier New', Courier, monospace;
--radius: 3px;
--nav-h: 48px;
--bar-h: 72px;
/* contrast scheme — default: white on dark */
--fg: #fff;
--fg-muted: #fff;
--outline: #000;
--text: var(--fg);
--text-muted: var(--fg);
/* The colour scheme (docs/theme.md): dark here, light below. Use only these. */
--muted: #9a9a9a;
--card: #0b0b0b;
--panel: #111;
--hover: #141414;
--raised: #1a1a1a;
--border-strong: #444;
--on-accent: #fff;
--scrim: rgba(0, 0, 0, 0.6);
--shadow: none;
--surface: var(--panel);
color-scheme: dark;
}
html[data-theme="light"] {
--bg: #f6f5f1;
--fg: #17171a;
--fg-muted: #17171a;
--muted: #66666b;
--card: #fff;
--panel: #fff;
--hover: #ecebe6;
--raised: #e4e2dc;
--border: #d9d6ce;
--border-strong: #b4b0a6;
--accent: #d0263a;
--accent-hover: #b51f31;
--on-accent: #fff;
--scrim: rgba(20, 20, 20, 0.35);
--shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
--bg-card: var(--card);
--bg-alt: var(--hover);
/* Outlined text is for wallpapers (body.bright-bg / a dark picture); on plain light, none. */
--outline: transparent;
color-scheme: light;
}
/* inverted: black on white */
body.bright-bg {
--bg: #fff;
--bg-card: #f5f5f5;
--bg-alt: #f0f0f0;
--bg-row: #fafafa;
--bg-row-alt: #f5f5f5;
--fg: #000;
--fg-muted: #000;
--outline: #fff;
--border: #bbb;
}
html, body {
background: var(--bg);
color: var(--fg);
font-family: var(--font);
font-size: 14px;
font-weight: 600;
line-height: 1.5;
min-height: 100vh;
text-shadow:
-1px -1px 0 var(--outline),
1px -1px 0 var(--outline),
-1px 1px 0 var(--outline),
1px 1px 0 var(--outline);
}
/* Elements with their own coloured backgrounds — no outline, no override */
.btn-play,
.btn-amazon,
.btn-lastfm,
.btn-danger,
.btn-primary,
.navbar-brand,
.reader-toast {
text-shadow: none;
}
/* Accent colour stays red regardless of scheme */
.navbar-brand { color: var(--accent); }
.tab-btn.active { color: var(--accent); }
a {
color: var(--accent);
text-decoration: none;
}
a:hover {
color: var(--accent-hover);
text-decoration: underline;
}
/* =========================================================
NAVBAR
========================================================= */
.navbar {
position: sticky;
top: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: space-between;
height: var(--nav-h);
padding: 0 1.5rem;
background: var(--bg);
border-bottom: 1px solid var(--border);
/* Window Controls Overlay: extend navbar into the titlebar area */
padding-left: max(1.5rem, env(titlebar-area-x, 1.5rem));
padding-top: env(titlebar-area-y, 0);
height: calc(var(--nav-h) + env(titlebar-area-y, 0px));
-webkit-app-region: drag;
}
.navbar a, .navbar button, .navbar input, .navbar form {
-webkit-app-region: no-drag;
}
.navbar-brand {
font-size: 1.4rem;
font-weight: bold;
letter-spacing: 0.05em;
color: var(--accent);
text-decoration: none;
}
.navbar-brand:hover {
color: var(--accent-hover);
text-decoration: none;
}
.navbar-links {
display: flex;
align-items: center;
gap: 1rem;
}
.navbar-user {
color: var(--text-muted);
font-size: 0.85rem;
}
/* =========================================================
MAIN CONTENT
========================================================= */
.main-content {
max-width: 1100px;
margin: 0 auto;
padding: calc(var(--nav-h) + 3rem) 1.5rem calc(var(--bar-h) + 2rem);
height: 100%;
overflow-y: auto;
}
/* =========================================================
MESSAGES
========================================================= */
.messages {
margin-bottom: 1rem;
}
.message {
padding: 0.6rem 1rem;
border-radius: var(--radius);
margin-bottom: 0.4rem;
background: var(--bg-alt);
border-left: 3px solid var(--text-muted);
}
.message-error { border-color: var(--accent); }
.message-success { border-color: var(--green); }
.message-warning { border-color: var(--yellow); }
/* =========================================================
NOW PLAYING BAR (fixed at bottom)
========================================================= */
.now-playing-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: var(--bar-h);
background: var(--bg);
border-top: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 1.5rem;
z-index: 200;
gap: 1rem;
}
.now-playing-info {
display: flex;
flex-direction: column;
flex: 1;
min-width: 0;
overflow: hidden;
}
.now-playing-station {
font-size: 0.75rem;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.now-playing-track {
font-size: 0.95rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: var(--text);
}
.now-playing-controls {
display: flex;
align-items: center;
gap: 0.75rem;
flex-shrink: 0;
}
.volume-label {
display: flex;
align-items: center;
gap: 0.4rem;
color: var(--text-muted);
font-size: 0.75rem;
}
.volume-slider {
width: 80px;
accent-color: var(--accent);
cursor: pointer;
}
/* =========================================================
AFFILIATE SECTION
========================================================= */
.affiliate-section {
display: flex;
align-items: center;
gap: 1.25rem;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 1rem;
margin-bottom: 1.5rem;
}
.affiliate-artwork {
width: 80px;
height: 80px;
object-fit: cover;
border-radius: var(--radius);
flex-shrink: 0;
background: var(--bg-alt);
}
.affiliate-info {
display: flex;
flex-direction: column;
gap: 0.2rem;
min-width: 0;
}
.affiliate-track {
font-size: 1rem;
font-weight: bold;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.affiliate-artist {
font-size: 0.85rem;
color: var(--text-muted);
}
.affiliate-album {
font-size: 0.8rem;
color: var(--text-muted);
font-style: italic;
}
.btn-amazon {
display: inline-block;
margin-top: 0.5rem;
background: #ff9900;
color: #000;
padding: 0.3rem 0.8rem;
border-radius: var(--radius);
font-weight: bold;
font-size: 0.8rem;
}
.btn-amazon:hover {
background: #ffad33;
color: #000;
text-decoration: none;
}
/* =========================================================
TABS
========================================================= */
.tabs {
display: flex;
border-bottom: 1px solid var(--border);
margin-bottom: 1.25rem;
gap: 0;
}
/* Haupttab-Leiste: immer sichtbar unterhalb der Navbar */
#tabs {
position: fixed;
top: var(--nav-h);
left: 0;
right: 0;
z-index: 150;
background: var(--bg);
margin-bottom: 0;
padding: 0 0.5rem;
}
.tab-btn {
background: none;
border: none;
border-bottom: 2px solid transparent;
color: var(--text-muted);
cursor: pointer;
font-family: var(--font);
font-size: 0.9rem;
padding: 0.5rem 1.25rem;
transition: color 0.15s, border-color 0.15s;
}
.tab-btn:hover {
color: var(--text);
}
.tab-btn.active {
color: var(--accent);
border-bottom-color: var(--accent);
}
.tab-panel {
min-height: 200px;
}
/* Ambient: the synthesizer's frame fills the room down to the player bar (app.js _ambientSize). */
.ambient-frame {
display: block;
width: 100%;
height: 70vh;
border: 0;
background: var(--bg);
}
.sub-tabs {
border-bottom-color: transparent;
margin-bottom: 1rem;
}
.sub-tabs .tab-btn {
font-size: 0.8rem;
padding: 0.3rem 1rem;
color: var(--text-muted);
}
.sub-tabs .tab-btn.active {
color: var(--accent);
border-bottom-color: var(--accent);
}
/* =========================================================
SEARCH
========================================================= */
.search-bar {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
.search-input {
flex: 1;
background: transparent;
border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--text);
font-family: var(--font);
font-size: 0.9rem;
padding: 0.4rem 0.7rem;
outline: none;
}
.search-input:focus {
border-color: var(--accent);
}
.status-msg {
color: var(--text-muted);
font-size: 0.85rem;
min-height: 1.4em;
margin-bottom: 0.5rem;
}
/* =========================================================
TABLES
========================================================= */
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 0.85rem;
}
.data-table th {
background: var(--bg-alt);
border-bottom: 1px solid var(--border);
color: var(--text-muted);
font-weight: normal;
padding: 0.45rem 0.7rem;
text-align: left;
}
.data-table td {
padding: 0.4rem 0.7rem;
border-bottom: 1px solid var(--border);
vertical-align: middle;
max-width: 220px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.data-table tbody tr:nth-child(odd) { background: transparent; }
.data-table tbody tr:nth-child(even) { background: transparent; }
.data-table tbody tr:hover { background: var(--hover); }
.empty-msg {
color: var(--text-muted);
text-align: center;
padding: 1.5rem !important;
}
.history-time {
color: var(--text-muted);
font-size: 0.75rem;
white-space: nowrap;
}
/* =========================================================
BUTTONS
========================================================= */
.btn {
background: transparent;
border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--text);
cursor: pointer;
font-family: var(--font);
font-size: 0.85rem;
padding: 0.35rem 0.75rem;
transition: background 0.1s, border-color 0.1s;
white-space: nowrap;
}
.btn:hover {
background: var(--hover);
border-color: var(--border-strong);
}
.btn-play {
background: var(--accent);
border-color: var(--accent);
color: var(--on-accent);
font-size: 0.9rem;
padding: 0.4rem 1rem;
}
.btn-play:hover {
background: var(--accent-hover);
border-color: var(--accent-hover);
}
.btn-play.playing {
background: var(--raised);
border-color: var(--border-strong);
color: var(--text);
}
.btn-save {
color: var(--yellow);
border-color: var(--border-strong);
}
.btn-save:hover {
background: #2a2a00;
border-color: var(--yellow);
}
.btn-sm {
font-size: 0.78rem;
padding: 0.25rem 0.5rem;
}
.btn-primary {
background: var(--accent);
border-color: var(--accent);
color: var(--on-accent);
}
.btn-primary:hover {
background: var(--accent-hover);
}
.btn-danger {
background: #300;
border-color: #700;
color: #faa;
}
.btn-danger:hover {
background: #500;
border-color: #a00;
color: #fff;
}
html[data-theme="light"] .btn-danger { background: #fdecee; border-color: #e7a3aa; color: #a3172a; }
html[data-theme="light"] .btn-danger:hover { background: #a3172a; border-color: #a3172a; color: #fff; }
html[data-theme="light"] .btn-save:hover { background: #fbf3d3; }
.btn-lastfm {
background: #d51007;
border-color: #d51007;
color: #fff;
display: inline-block;
padding: 0.4rem 1rem;
border-radius: var(--radius);
font-family: var(--font);
font-size: 0.9rem;
cursor: pointer;
border: 1px solid transparent;
}
.btn-lastfm:hover {
background: #ff1a0e;
color: #fff;
text-decoration: none;
}
.btn-full {
width: 100%;
padding: 0.55rem;
font-size: 0.95rem;
}
.btn-link {
background: none;
border: none;
color: var(--accent);
cursor: pointer;
font-family: var(--font);
font-size: 1rem;
padding: 0;
}
.btn-link:hover {
color: var(--accent-hover);
text-decoration: underline;
}
.btn-icon {
background: none;
border: none;
color: var(--text-muted);
cursor: pointer;
font-size: 1rem;
padding: 0;
line-height: 1;
}
.btn-icon.active {
color: var(--yellow);
}
.btn-icon:hover {
color: var(--yellow);
}
.inline-form {
display: inline;
}
/* =========================================================
AUTH FORMS
========================================================= */
.auth-container {
max-width: 400px;
margin: 3rem auto;
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 2rem;
}
.auth-title {
font-size: 1.4rem;
margin-bottom: 1.5rem;
color: var(--text);
}
.auth-form {
display: flex;
flex-direction: column;
gap: 1rem;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.form-label {
font-size: 0.85rem;
color: var(--text-muted);
}
.auth-form input[type="text"],
.auth-form input[type="password"],
.auth-form input[type="email"] {
background: var(--bg-alt);
border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--text);
font-family: var(--font);
font-size: 0.9rem;
padding: 0.45rem 0.7rem;
outline: none;
width: 100%;
}
.auth-form input:focus {
border-color: var(--accent);
}
.field-errors {
list-style: none;
color: var(--accent);
font-size: 0.8rem;
}
.form-errors {
color: var(--accent);
font-size: 0.85rem;
background: #1a0000;
border: 1px solid #500;
border-radius: var(--radius);
padding: 0.5rem 0.75rem;
}
html[data-theme="light"] .form-errors { background: #fdecee; border-color: #e7a3aa; }
.field-help {
color: var(--text-muted);
font-size: 0.75rem;
}
.auth-alt { margin-top: 1rem; }
.auth-or {
display: flex; align-items: center; gap: 0.6rem;
margin: 0 0 1rem; font-size: 0.8rem; color: var(--text-muted);
}
.auth-or::before, .auth-or::after { content: ''; flex: 1; border-top: 1px solid var(--border, #333); }
.auth-alt .btn-full { display: block; text-align: center; text-decoration: none; }
/* Unlocking the books on a device without the key (js/app/33-unlock.js) */
.unlock-code { font: 600 1.6rem/1.2 ui-monospace, monospace; letter-spacing: 0.15em; text-align: center; margin: 4px 0 14px; }
.unlock-or { font-size: 0.85rem; color: var(--text-muted); margin: 0 0 8px; }
.unlock-status { font-size: 0.85rem; color: var(--text-muted); min-height: 1.2em; margin: 0 0 10px; }
.auth-switch {
margin-top: 1.25rem;
font-size: 0.85rem;
color: var(--text-muted);
text-align: center;
}
/* =========================================================
SETTINGS
========================================================= */
.settings-container {
max-width: 600px;
margin: 2rem auto;
}
.settings-title {
font-size: 1.4rem;
margin-bottom: 1.5rem;
}
.settings-section {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 1.25rem;
margin-bottom: 1.25rem;
}
.settings-section h2 {
font-size: 1rem;
color: var(--text-muted);
margin-bottom: 1rem;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.lastfm-description {
color: var(--text-muted);
font-size: 0.85rem;
margin-bottom: 0.75rem;
}
.connected-status {
margin-bottom: 0.75rem;
font-size: 0.9rem;
}
.checkbox-label {
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
font-size: 0.9rem;
}
.auth-prompt {
color: var(--text-muted);
padding: 1.5rem 0;
}
/* --- WebDAV cloud connections (settings) --- */
.webdav-list {
list-style: none;
margin: 0 0 1rem;
padding: 0;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.webdav-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
flex-wrap: wrap;
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 0.6rem 0.75rem;
}
.webdav-item-info {
display: flex;
flex-direction: column;
gap: 0.15rem;
min-width: 0;
word-break: break-all;
}
.webdav-item-actions {
display: flex;
gap: 0.5rem;
}
.webdav-form {
display: flex;
flex-direction: column;
gap: 0.6rem;
border-top: 1px solid var(--border);
padding-top: 1rem;
}
.webdav-field {
display: flex;
flex-direction: column;
gap: 0.25rem;
font-size: 0.85rem;
color: var(--text-muted);
}
.webdav-field input {
background: var(--bg-alt);
border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--text);
font-family: var(--font);
font-size: 0.9rem;
padding: 0.45rem 0.7rem;
outline: none;
width: 100%;
}
.webdav-field input:focus {
border-color: var(--accent);
}
.webdav-form .btn {
align-self: flex-start;
}
/* Editing a saved connection: a full row under its name and buttons */
.webdav-edit {
flex-basis: 100%;
padding-top: 0.75rem;
}
.webdav-edit[hidden] {
display: none;
}
/* --- Cloud import browser (books tab) --- */
.cloud-browser {
display: flex;
flex-direction: column;
gap: 0.5rem;
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 0.75rem;
margin-bottom: 1rem;
}
.cloud-browser-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
flex-wrap: wrap;
}
.cloud-browser-head select {
background: var(--bg-alt);
border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--text);
font-family: var(--font);
font-size: 0.85rem;
padding: 0.3rem 0.5rem;
outline: none;
}
.cloud-path {
font-size: 0.8rem;
color: var(--text-muted);
word-break: break-all;
}
.cloud-entries {
list-style: none;
margin: 0;
padding: 0;
max-height: 320px;
overflow-y: auto;
display: flex;
flex-direction: column;
}
.cloud-entry {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
padding: 0.4rem 0.25rem;
border-bottom: 1px solid var(--border);
}
.cloud-entry:last-child {
border-bottom: none;
}
.cloud-entry-name {
background: none;
border: none;
color: inherit;
font-family: var(--font);
font-size: 0.9rem;
text-align: left;
padding: 0;
cursor: pointer;
word-break: break-all;
flex: 1;
min-width: 0;
}
.cloud-entry-name:hover {
color: var(--accent);
}
.cloud-entry-size {
font-size: 0.75rem;
color: var(--text-muted);
white-space: nowrap;
}
/* =========================================================
RESPONSIVE
========================================================= */
/* =========================================================
RECOMMENDATIONS
========================================================= */
.recommendations-section {
padding: 12px 16px 4px;
border-bottom: 1px solid var(--border);
}
.recommendations-context {
font-size: 0.75rem;
color: var(--fg);
margin: 0 0 8px;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.recommendations-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.recommendations-list li {
display: flex;
align-items: center;
gap: 6px;
}
.muted {
color: var(--fg);
font-size: 0.8rem;
}
@media (max-width: 600px) {
.main-content {
padding: calc(var(--nav-h) + 3rem) 0.75rem calc(var(--bar-h) + 1.5rem);
}
#tabs {
padding: 0 0.25rem;
}
.now-playing-bar {
flex-direction: column;
height: auto;
padding: 0.5rem 0.75rem;
gap: 0.4rem;
}
.now-playing-info {
width: 100%;
}
.now-playing-controls {
width: 100%;
justify-content: space-between;
}
.volume-label,
.volume-slider,
.volume-num {
display: none;
}
.affiliate-section {
flex-direction: column;
align-items: flex-start;
}
.data-table th:nth-child(3),
.data-table td:nth-child(3),
.data-table th:nth-child(4),
.data-table td:nth-child(4) {
display: none;
}
.auth-container {
margin: 1rem;
padding: 1.25rem;
}
}
.import-bar {
padding: 8px 16px;
display: flex;
align-items: center;
gap: 10px;
border-bottom: 1px solid var(--border);
}
/* =========================================================
TIMER WIDGET
========================================================= */
.timer-widget {
display: flex;
align-items: center;
gap: 6px;
font-variant-numeric: tabular-nums;
margin-left: 8px;
}
.timer-phase {
font-size: 0.7rem;
color: var(--fg);
text-transform: uppercase;
letter-spacing: 0.05em;
width: 36px;
text-align: right;
}
.timer-display {
font-size: 1rem;
font-weight: 600;
letter-spacing: 0.05em;
min-width: 48px;
}
/* =========================================================
DO NOT DISTURB / FOCUS MODE
========================================================= */
body.dnd-mode .navbar,
body.dnd-mode .tabs,
body.dnd-mode .tab-panel,
body.dnd-mode .affiliate-section {
display: none !important;
}
body.dnd-mode .now-playing-bar {
position: fixed;
inset: 0;
height: 100vh;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 24px;
background: transparent;
z-index: 9999;
}
body.dnd-mode.dnd-dark {
background: #000 !important;
background-image: none !important;
}
body.dnd-mode.dnd-dark .now-playing-bar {
background: #000;
}
.dnd-only {
display: none;
}
body.dnd-mode .dnd-only {
display: inline;
}
body.dnd-mode .now-playing-info {
text-align: center;
flex-direction: column;
gap: 12px;
}
body.dnd-mode .now-playing-station {
font-size: 1.6rem;
}
body.dnd-mode .now-playing-track {
font-size: 1rem;
color: var(--fg);
}
body.dnd-mode .timer-display {
font-size: 3.5rem;
}
/* =========================================================
MOOD CHIPS
========================================================= */
.mood-chips {
display: flex;
flex-wrap: wrap;
gap: 6px;
padding: 8px 16px;
border-bottom: 1px solid var(--border);
}
.mood-chip {
background: transparent;
border: 1px solid var(--border);
color: var(--fg);
border-radius: 999px;
padding: 3px 12px;
font-size: 0.78rem;
cursor: pointer;
transition: background 0.15s, border-color 0.15s;
}
.mood-chip:hover {
background: var(--hover);
border-color: var(--border-strong);
}
.focus-today {
font-size: 0.72rem;
color: var(--fg);
margin-left: 8px;
white-space: nowrap;
}
.curated-lists-container {
padding: 12px 16px 0;
}
.curated-section {
margin-bottom: 16px;
}
.curated-label {
font-size: 0.75rem;
color: var(--fg);
text-transform: uppercase;
letter-spacing: 0.06em;
margin-bottom: 6px;
}
.curated-stations {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
.curated-stations li {
display: flex;
align-items: center;
gap: 8px;
}
.curated-name {
font-size: 0.85rem;
color: var(--fg);
}
.btn-delete-history {
background: none;
border: none;
color: var(--muted, #888);
cursor: pointer;
font-size: 0.75rem;
padding: 0 4px;
opacity: 0.5;
}
.btn-delete-history:hover { opacity: 1; color: #e55; }
#donation-hint {
position: fixed;
bottom: 24px;
right: 24px;
background: var(--surface, #1e1e2e);
border: 1px solid var(--border, #333);
border-radius: 8px;
padding: 12px 16px;
display: flex;
align-items: center;
gap: 12px;
font-size: 0.85rem;
box-shadow: 0 4px 16px rgba(0,0,0,0.4);
z-index: 999;
animation: hint-in 0.3s ease;
max-width: 320px;
}
#donation-hint.hiding {
animation: hint-out 0.4s ease forwards;
}
#donation-hint button {
background: none;
border: none;
cursor: pointer;
color: var(--muted, #888);
font-size: 0.8rem;
padding: 0;
flex-shrink: 0;
}
#donation-hint button:hover { color: var(--fg, #fff); }
@keyframes hint-in {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes hint-out {
from { opacity: 1; transform: translateY(0); }
to { opacity: 0; transform: translateY(12px); }
}
.featured-section {
padding: 8px 0 12px;
border-bottom: 1px solid var(--border, #333);
margin-bottom: 12px;
}
.featured-label {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--muted, #888);
margin: 0 0 8px;
}
.featured-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 6px;
}
.featured-list li {
display: flex;
align-items: center;
gap: 8px;
}
.volume-num {
width: 44px;
background: var(--surface, #1e1e2e);
border: 1px solid var(--border, #333);
border-radius: 4px;
color: var(--fg, #fff);
font-size: 0.8rem;
padding: 2px 4px;
text-align: center;
}
.volume-num::-webkit-inner-spin-button,
.volume-num::-webkit-outer-spin-button { display: none; }
.volume-num { -moz-appearance: textfield; }
/* ===== SIDEBAR ===== */
.sidebar-overlay {
position: fixed;
inset: 0;
background: var(--scrim);
z-index: 299;
}
.sidebar {
position: fixed;
top: 0;
right: 0;
width: 400px;
max-width: 100vw;
/* stop above the now-playing bar */
bottom: 72px;
background: var(--surface, #111);
border-left: 1px solid var(--border, #333);
z-index: 300;
display: flex;
flex-direction: column;
transform: translateX(100%);
transition: transform 0.22s ease;
overflow: hidden;
}
.sidebar.open {
transform: translateX(0);
}
.sidebar-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 10px;
padding: 14px 16px 10px;
border-bottom: 1px solid var(--border, #333);
flex-shrink: 0;
}
.sidebar-title {
font-weight: bold;
font-size: 0.95rem;
line-height: 1.3;
}
.sidebar-close {
flex-shrink: 0;
margin-top: -2px;
}
.sidebar-body {
flex: 1;
overflow-y: auto;
padding: 16px;
font-size: 0.88rem;
line-height: 1.6;
}
/* ===== MODAL DIALOG ===== */
.modal-overlay {
position: fixed;
inset: 0;
background: var(--scrim);
z-index: 500;
}
.modal-dialog {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: var(--surface, #111);
border: 1px solid var(--border, #333);
border-radius: var(--radius);
padding: 20px;
width: 360px;
max-width: calc(100vw - 32px);
z-index: 501;
}
.modal-message {
margin: 0 0 16px;
white-space: pre-wrap;
line-height: 1.5;
}
.modal-input { width: 100%; margin-bottom: 16px; }
.modal-actions { display: flex; justify-content: flex-end; gap: 8px; }
/* Style links and basic HTML inside shownotes */
.sidebar-body a { color: var(--accent, #e63946); }
.sidebar-body p { margin: 0 0 10px; }
.sidebar-body ul, .sidebar-body ol { margin: 0 0 10px; padding-left: 20px; }
.sidebar-body h1, .sidebar-body h2, .sidebar-body h3 {
font-size: 0.9rem;
margin: 12px 0 4px;
}
.tts-lang-select {
background: var(--surface, #1e1e2e); color: var(--fg, #fff);
border: 1px solid var(--border, #444); border-radius: 4px;
padding: 2px 4px; font-size: 0.8rem; cursor: pointer;
}
.reader-marker-btn-mobile { display: none; }
@media (max-width: 600px) {
.sidebar { width: 100vw; }
/* Reader auf Mobile: Header kompakter, Fortschrittsfeld versteckt */
.reader-header { padding: 6px 8px; gap: 4px; }
.reader-title { font-size: 13px; }
.reader-header-actions { gap: 4px; }
.reader-progress-wrap { display: none; }
.reader-content { padding: 16px 10px; }
/* On a phone, opening the 150px margin just to reach the marker-mode toggle
eats too much of an already-narrow reading column — surface it directly
in the main header instead (desktop keeps it only in the margin header,
grouped with "Notes", per earlier feedback against back-and-forth). */
.reader-marker-btn-mobile { display: inline-block; }
}
/* =========================================================
Ebook reader + book list + focus station
========================================================= */
/* --- Drop zone --- */
.book-drop-zone {
border: 2px dashed var(--border);
border-radius: var(--radius);
padding: 24px 16px;
text-align: center;
margin: 12px 0;
cursor: pointer;
transition: border-color 0.15s, background 0.15s;
display: flex;
flex-direction: column;
gap: 6px;
align-items: center;
}
.book-drop-zone:hover,
.book-drop-zone.drag-over {
border-color: var(--accent);
background: var(--hover);
}
.book-drop-zone label {
color: var(--accent);
cursor: pointer;
}
.enc-key-prompt {
padding: 24px 0 16px;
}
.offline-notice {
font-size: 13px;
color: var(--accent);
padding: 4px 0 8px;
}
.book-key-bar {
display: flex;
align-items: center;
gap: 8px;
margin-top: 1rem;
padding-top: 0.75rem;
border-top: 1px solid var(--border);
}
/* --- Book list --- */
.book-list {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: 8px;
}
.book-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 10px;
border: 1px solid var(--border);
border-radius: var(--radius);
gap: 12px;
}
.book-item-info {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.book-title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.book-author,
.book-progress {
font-size: 12px;
}
.book-item-meta-row {
display: flex;
align-items: center;
gap: 8px;
}
.book-item-actions {
display: flex;
align-items: center;
gap: 6px;
flex-shrink: 0;
}
.book-read-badge {
font-size: 12px;
font-weight: normal;
}
.book-shelf-badge {
display: inline-block;
font-size: 11px;
padding: 1px 7px;
border: 1px solid var(--border);
border-radius: 10px;
color: var(--muted, #888);
white-space: nowrap;
}
/* --- Per-book "⋮" menu (everything except Open) --- */
.book-item-menu {
position: relative;
}
.book-item-menu-list {
display: none;
flex-direction: column;
gap: 2px;
position: absolute;
right: 0;
top: calc(100% + 4px);
z-index: 20;
min-width: 220px;
background: var(--surface, #111);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 4px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
}
.book-item-menu-list.open {
display: flex;
}
.book-menu-item {
background: none;
border: none;
color: var(--fg);
text-align: left;
padding: 8px 10px;
border-radius: var(--radius);
cursor: pointer;
font: inherit;
font-size: 13px;
white-space: nowrap;
}
.book-menu-item:hover {
background: var(--bg-row, rgba(255, 255, 255, 0.08));
}
.book-menu-item--danger {
color: var(--accent, #e63946);
}
.book-list-filter {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
color: var(--muted, #888);
margin-top: 10px;
cursor: pointer;
}
/* --- Recently-read section (always pinned atop the books view, plain list style) --- */
.book-recent-section {
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 14px;
padding-bottom: 14px;
border-bottom: 1px solid var(--border);
}
.book-recent-title {
font-size: 13px;
font-weight: 600;
margin: 0;
color: var(--muted, #888);
}
/* --- Book folders (single level) --- */
.book-folder-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 8px;
margin-bottom: 10px;
}
.book-folder-tile {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
padding: 12px 8px;
border: 1px solid var(--border);
border-radius: var(--radius);
background: none;
color: var(--fg);
cursor: pointer;
text-align: center;
font: inherit;
}
.book-folder-tile-icon {
font-size: 22px;
}
.book-folder-tile-name {
font-size: 13px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
.book-folder-tile-count {
font-size: 11px;
color: var(--muted, #888);
}
.book-folder-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
/* --- PDF pages --- */
.pdf-page-wrapper {
margin: 0 auto 1rem;
display: flex;
justify-content: center;
max-width: none;
}
.pdf-page {
display: block;
box-shadow: 0 2px 8px rgba(0,0,0,0.25);
}
/* --- Reader overlay --- */
.reader-overlay {
position: fixed;
top: var(--nav-h);
left: 0;
right: 0;
bottom: var(--bar-h);
background: var(--bg);
z-index: 200;
display: flex;
flex-direction: column;
/* The global white text-outline (for readability over a custom background
image elsewhere in the app) is never needed here — the reader always
sits on its own opaque background — and it muddies highlighted text
badly when combined with a colored highlight background. */
text-shadow: none;
}
.reader-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 16px;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
gap: 12px;
}
.reader-title {
font-weight: 700;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
}
.reader-header-actions {
display: flex;
align-items: center;
gap: 10px;
flex-shrink: 0;
}
.reader-progress-wrap {
display: flex;
align-items: center;
gap: 4px;
}
.reader-content {
flex: 1;
overflow-y: scroll;
overflow-x: auto;
position: relative;
padding: 24px 16px;
line-height: 1.8;
font-family: 'Source Serif 4', Georgia, 'Times New Roman', serif;
font-size: 16px;
font-weight: 380;
}
.reader-content > * {
max-width: 65ch;
margin-left: auto;
margin-right: auto;
}
.reader-content p {
margin-bottom: 1em;
}
.reader-content h1, .reader-content h2, .reader-content h3 {
margin: 1.4em 0 0.6em;
font-family: var(--font);
}
/* --- Focus station sidebar --- */
/* --- Radio sidebar --- */
.rsb-nowplaying { margin-bottom: 12px; }
.rsb-station-name { font-weight: 600; }
.rsb-track { font-size: 0.85rem; margin-top: 2px; }
.rsb-controls { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; flex-wrap: wrap; }
.rsb-vol { display: flex; align-items: center; gap: 6px; font-size: 0.85rem; color: var(--muted, #888); }
.rsb-station-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 6px; }
/* --- Table of contents sidebar --- */
.toc-list {
list-style: none;
display: flex;
flex-direction: column;
gap: 2px;
}
.toc-entry {
display: block;
width: 100%;
text-align: left;
padding: 4px 0;
font-size: 13px;
white-space: normal;
word-break: break-word;
}
.toc-entry:hover {
color: var(--accent);
}
/* Focus station pending (playing interrupted) */
#focus-station-btn.focus-pending {
color: var(--accent);
animation: focus-pulse 1.4s ease-in-out infinite;
}
@keyframes focus-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
/* =========================================================
Reader feature additions
========================================================= */
/* Reader themes */
.reader-theme-sepia .reader-content { background:#f5e6c8; color:#3b2a1a; }
.reader-theme-bright .reader-content { background:#fff; color:#111; }
.reader-content > * { max-width:var(--reader-max-width,65ch); margin-left:auto; margin-right:auto; }
.reader-content > .pdf-page-wrapper { max-width: none; margin: 0 auto 1rem; }
/* Inline panels */
.reader-settings-panel, .reader-search-bar {
display:flex; align-items:center; gap:12px; padding:8px 16px;
border-bottom:1px solid var(--border); flex-shrink:0; flex-wrap:wrap; background:var(--bg);
}
.reader-settings-panel input[type="range"] { width:80px; }
/* PDF viewport wrapper — CSS zoom applied here for smooth zoom without re-render */
#pdf-viewport { transform-origin: top left; }
/* PDF inner container — shares origin with canvas so text layer aligns exactly */
.pdf-page-inner { position:relative; display:inline-block; line-height:0; }
/* PDF text layer — sits flush over the canvas inside pdf-page-inner */
.pdf-text-layer { position:absolute; top:0; left:0; width:100%; height:100%; overflow:hidden; pointer-events:none; }
.pdf-text-layer span { position:absolute; color:transparent !important; background:transparent; white-space:pre; cursor:text; pointer-events:auto; user-select:text; -webkit-user-select:text; line-height:1; }
.pdf-text-layer span::selection { background:rgba(0,120,255,0.3); }
.pdf-text-layer span.reader-search-match { background:rgba(241,196,15,.5); }
.pdf-text-layer span.reader-search-match.active { background:rgba(230,57,70,.6); }
/* PDF invert */
/* Dark pages: inverted, and the hues turned back, so photos and coloured figures aren't negatives. */
#reader-overlay.pdf-inverted .pdf-page { filter:invert(1) hue-rotate(180deg); }
/* ---- Immersive reader mode ---- */
.navbar { transition: transform 0.35s ease; }
.now-playing-bar{ transition: transform 0.35s ease; }
.reader-overlay { transition: top 0.35s ease, bottom 0.35s ease; }
.reader-header { overflow: hidden; max-height: 60px;
transition: max-height 0.35s ease, padding-top 0.35s ease, padding-bottom 0.35s ease; }
body.reader-immersive .navbar { transform: translateY(-100%); }
body.reader-immersive .now-playing-bar { transform: translateY(100%); }
body.reader-immersive .reader-overlay { top: 0; bottom: 0; }
body.reader-immersive .reader-header { max-height: 0; padding-top: 0; padding-bottom: 0; }
body.reader-immersive.reader-show-top .navbar { transform: none; }
body.reader-immersive.reader-show-top .reader-overlay { top: var(--nav-h); }
body.reader-immersive.reader-show-top .reader-header { max-height: 60px; padding-top: 8px; padding-bottom: 8px; }
body.reader-immersive.reader-show-bottom .now-playing-bar { transform: none; }
body.reader-immersive.reader-show-bottom .reader-overlay { bottom: var(--bar-h); }
/* Reading aloud: the player bar is its remote control and stays, as in the apps. */
html.nui-tts-on body.reader-immersive .now-playing-bar { transform: none; }
html.nui-tts-on body.reader-immersive .reader-overlay { bottom: var(--bar-h); }
/* PDF paginated */
.reader-content.pdf-paginated { overflow:hidden !important; display:flex; align-items:center; justify-content:center; }
.pdf-paginated .pdf-page-wrapper { margin:0; }
/* PDF loading overlay */
.pdf-loading-overlay {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: var(--scrim);
z-index: 200;
}
.pdf-loading-spinner {
width: 36px;
height: 36px;
border: 3px solid var(--raised);
border-top-color: var(--accent, #e63946);
border-radius: 50%;
animation: pdf-spin 0.7s linear infinite;
}
@keyframes pdf-spin { to { transform: rotate(360deg); } }
.reader-opening { display: flex; flex-direction: column; align-items: center; gap: 16px; }
/* PDF two-page spread */
.pdf-spread-wrapper { display:flex; flex-direction:row; gap:8px; justify-content:center; margin-bottom:1rem; }
.pdf-spread-wrapper .pdf-page-wrapper { margin:0; }
.pdf-spread-cover { margin-bottom:1rem; }
/* Disable text selection during pinch */
#reader-content.pinch-active { user-select:none; -webkit-user-select:none; }
/* Highlight popover */
.highlight-popover { position:fixed; z-index:500; display:flex; gap:6px; background:var(--bg-card,#1a1a1a); border:1px solid var(--border); border-radius:var(--radius); padding:6px 8px; box-shadow:0 4px 16px rgba(0,0,0,.5); }
.hl-color-btn { width:24px; height:24px; border-radius:50%; border:2px solid transparent; cursor:pointer; font-weight:700; font-size:12px; color:#000; line-height:1; }
.hl-color-btn:hover { border-color:var(--fg); }
.hl-note-btn { background:none; border:1px solid var(--border); color:var(--fg); padding:2px 6px; border-radius:var(--radius); cursor:pointer; }
/* Footnote popover */
.footnote-popover { position:fixed; z-index:500; max-width:min(400px,90vw); max-height:220px; overflow-y:auto; background:#1a1a1a; color:#e8e8e8; border:1px solid #444; border-radius:var(--radius); padding:10px 28px 10px 12px; box-shadow:0 4px 20px rgba(0,0,0,.6); font-size:0.88em; line-height:1.55; pointer-events:auto; }
.footnote-popover.reader-theme-sepia { background:#ede0c4; color:#3b2a1a; border-color:#c8b89a; }
.footnote-popover.reader-theme-bright { background:#f0f0f0; color:#111; border-color:#bbb; }
.footnote-popover-close { position:absolute; top:4px; right:6px; background:none; border:none; color:inherit; opacity:0.5; cursor:pointer; font-size:14px; padding:2px 4px; line-height:1; }
.footnote-popover > :first-child { margin-top:0; }
.footnote-popover > :last-child { margin-bottom:0; }
/* ---- Reading in pages (63-pages.js) ----
Only the current chapter is laid out; the others stay in the DOM (block
indices, search, notes, anchors). No free scrolling: the pager moves. */
#reader-content.nui-window > [data-epub-src]:not(.nui-ch-current) { display:none; }
/* Room below the chapter, so its last page can show just the remaining text
(then the page's colour) instead of scrolling back over lines already read. */
#reader-content.nui-window::after { content:""; display:block; height:100vh; }
#reader-content.nui-window { overflow-y:hidden; -ms-overflow-style:none; scrollbar-width:none; }
#reader-content.nui-window::-webkit-scrollbar { display:none; }
/* Masks: the line cut at the page's bottom and whatever sits in the top gap. */
.nui-page-mask { position:fixed; z-index:250; pointer-events:none; display:none; }
/* Footnotes at the foot of the page, like a printed book. */
.nui-page-notes { position:fixed; z-index:255; overflow:hidden; padding:0 0 4px; box-sizing:border-box;
line-height:1.4; font-weight:400; cursor:pointer; }
.nui-pn-rule { width:30%; border-top:1px solid currentColor; margin:2px 0 6px; }
.nui-pn-item { margin:0 0 4px; padding-left:1.6em; text-indent:-1.6em; }
.nui-pn-item p, .nui-pn-item div, .nui-pn-item aside, .nui-pn-item li { display:inline; margin:0; padding:0; text-indent:0; }
.nui-pn-item p + p::before { content:" "; }
.nui-pn-label { display:inline-block; min-width:1.6em; text-indent:0; font-weight:700; }
.nui-pn-item a { color:inherit; text-decoration:none; }
.nui-pn-item img { display:none; }
/* Cut off: say so, a tap shows the whole note. */
.nui-page-notes.nui-pn-cut::after { content:"… mehr"; position:absolute; right:0; bottom:2px; padding-left:8px;
background:inherit; font-weight:700; }
/* Two pages side by side (a spread, 63-pages.js): the right one is a copy of
the chapter, scrolled to the page after the left one. */
#reader-spread-page { display:none; }
#reader-overlay.reader-spread #reader-content,
#reader-overlay.reader-spread #reader-spread-page { flex:1 1 0; min-width:0; }
#reader-overlay.reader-spread #reader-spread-page { display:block; overflow:hidden; box-shadow:inset 1px 0 0 var(--border);
user-select:none; -webkit-user-select:none; cursor:default; }
#reader-spread-page::after { content:""; display:block; height:100vh; }
/* An article in the reader (65-article.js): what only books have stays out. */
#reader-overlay.reader-article-mode .reader-progress-wrap,
#reader-overlay.reader-article-mode #reader-bookmark-btn,
#reader-overlay.reader-article-mode #reader-bm-list-btn,
#reader-overlay.reader-article-mode #reader-toc-btn,
#reader-overlay.reader-article-mode #reader-margin-btn,
#reader-overlay.reader-article-mode #reader-reset-pos-btn,
#reader-overlay.reader-article-mode .marker-mode-btn,
#reader-overlay.reader-article-mode #reader-margin,
#reader-overlay.reader-article-mode .nui-toc { display:none !important; }
.reader-article-actions { display:inline-flex; gap:10px; align-items:center; }
/* The browser's line under the page (the apps have their own footer). */
#reader-page-footer { display:flex; justify-content:space-between; gap:12px; flex-shrink:0; padding:4px 16px 6px;
font-size:12px; color:var(--muted,#888); font-variant-numeric:tabular-nums; }
.reader-theme-sepia #reader-page-footer { background:#f5e6c8; color:#7a6650; }
.reader-theme-bright #reader-page-footer { background:#fff; color:#777; }
/* Page numbers of the printed edition (epub:type="pagebreak") */
.reader-content .epub-pagenum { display:inline-block; font-size:0.62em; line-height:1.3; font-weight:400; font-style:normal; letter-spacing:0; text-transform:none; vertical-align:0.35em; padding:0 0.35em; margin-right:0.4em; border:1px solid currentColor; border-radius:3px; opacity:0.45; user-select:none; }
.reader-content .epub-pagenum:empty { display:none; }
/* Highlight marks */
.reader-no-bold * { font-weight: inherit !important; }
.reader-content img { max-width: 100%; height: auto; display: block; }
/* color:inherit overrides the browser's UA-default `mark { color: black }` —
without it, highlighted text abruptly flips to black regardless of the
reader theme's own text color (jarring on the default dark theme, whose
body text is white). Inheriting keeps highlighted text the same color as
the text around it in every theme; the background tones below are chosen
dark/saturated enough to stay readable specifically against white text. */
.epub-highlight { border-radius:2px; cursor:pointer; color:inherit; }
.epub-highlight[data-color="yellow"] { background:rgba(184,134,11,.55); }
.epub-highlight[data-color="green"] { background:rgba(30,140,90,.55); }
.epub-highlight[data-color="blue"] { background:rgba(41,110,180,.55); }
.epub-highlight[data-color="red"] { background:rgba(190,50,60,.55); }
/* Margin ("page margin") panel — left of the reader content. Highlights without
a note show as a small color dot; anything with note text (freeform notes or
a highlight+note) shows as a readable little "post-it" card, positioned next
to the text height it belongs to. Click empty space to place a new freeform
note anchored to that line. Freeform notes have NO in-text mark — the anchor
is purely positional, only ever visible here in the margin. */
.reader-body-row { display:flex; flex:1; min-height:0; }
.reader-margin {
--reader-margin-w:220px; /* read by app.js _centerReaderColumn */
width:0; flex-shrink:0; overflow:hidden;
display:flex; flex-direction:column;
border-right:1px solid var(--border); background:var(--bg);
transition:width 0.25s ease;
}
.reader-margin.open { width:var(--reader-margin-w); }
/* The text column keeps its place while the margin opens (app.js _centerReaderColumn). */
.reader-content { transition: padding-right 0.25s ease; }
.reader-margin-header {
display:none; align-items:center; justify-content:space-between;
padding:6px 8px; border-bottom:1px solid var(--border); white-space:nowrap; flex-shrink:0;
}
.reader-margin.open .reader-margin-header { display:flex; }
.reader-margin-title { font-size:11px; text-transform:uppercase; letter-spacing:.04em; color:var(--muted,#888); }
.reader-margin-header-actions { display:flex; align-items:center; gap:6px; }
.reader-margin-markers { position:relative; flex:1; overflow:hidden; cursor:crosshair; contain:layout paint; }
/* The cards in book coordinates; scrolling moves this layer (app.js _repositionMarginMarkers). */
.margin-layer { position:absolute; top:0; left:0; right:0; height:0; will-change:transform; }
.margin-note { position:absolute; left:8px; right:8px; cursor:pointer; }
.margin-note-dot { left:8px; right:auto; width:12px; height:12px; border-radius:50%; }
.margin-note-dot[data-color="yellow"] { background:rgba(241,196,15,.85); }
.margin-note-dot[data-color="green"] { background:rgba(46,204,113,.85); }
.margin-note-dot[data-color="blue"] { background:rgba(52,152,219,.85); }
.margin-note-dot[data-color="red"] { background:rgba(230,57,70,.85); }
.margin-note-text {
font-size:11px; line-height:1.4; padding:5px 7px; border-radius:3px;
background:var(--bg-card,#1a1a1a); border-left:3px solid var(--muted,#888);
box-shadow:1px 2px 5px rgba(0,0,0,.3);
white-space:pre-wrap; word-break:break-word;
}
.margin-note-text[data-color="yellow"] { border-left-color:#f1c40f; }
.margin-note-text[data-color="green"] { border-left-color:#2ecc71; }
.margin-note-text[data-color="blue"] { border-left-color:#3498db; }
.margin-note-text[data-color="red"] { border-left-color:#e63946; }
/* Freeform notes get a paper-like tint, like a post-it note pinned to the text */
.margin-note-text.margin-note-freeform {
border-left-color:#e6c229; background:rgba(241,196,15,.13);
}
/* Inline note editor — appears right where the note lives in the margin, so
setting/editing a note never means jumping to a separate panel */
.margin-note-editor { position:absolute; left:8px; right:8px; z-index:1; }
.margin-note-textarea {
width:100%; min-height:52px; font-size:11px; line-height:1.4; padding:5px 7px;
border-radius:3px; border:1px solid var(--accent,#e63946);
background:var(--bg-card,#1a1a1a); color:var(--fg,#eee);
box-shadow:1px 2px 5px rgba(0,0,0,.4); resize:vertical;
}
/* Note bottom sheet — the mobile alternative to the inline margin editor.
Typing a note into a 150px-wide margin column is unpleasant on a phone
keyboard, and getting there first requires opening that column at all, so
on narrow viewports note editing uses this full-width sheet instead (see
_isMobileReader() / _openNoteEditor() in app.js). Existing notes staying
less discoverable on mobile is an accepted tradeoff — reviewing them in
depth is expected to happen on desktop; setting them fast is what matters
here. */
.note-bottom-sheet { position:fixed; inset:0; z-index:650; }
.note-bottom-sheet-backdrop {
position:absolute; inset:0; background:var(--scrim);
opacity:0; transition:opacity .2s ease; pointer-events:none;
}
.note-bottom-sheet.open .note-bottom-sheet-backdrop { opacity:1; pointer-events:auto; }
.note-bottom-sheet-panel {
position:absolute; left:0; right:0; bottom:0;
background:var(--surface,#111); border-top:1px solid var(--border,#333);
border-radius:12px 12px 0 0; padding:12px 14px calc(14px + env(safe-area-inset-bottom));
transform:translateY(100%); transition:transform .25s ease;
display:flex; flex-direction:column; gap:10px;
}
.note-bottom-sheet.open .note-bottom-sheet-panel { transform:translateY(0); }
.note-bottom-sheet-textarea {
width:100%; min-height:110px; font-size:15px; line-height:1.4; padding:8px 10px;
border-radius:var(--radius); border:1px solid var(--border,#333);
background:var(--bg-card,#1a1a1a); color:var(--fg,#eee); resize:vertical;
}
.note-bottom-sheet-actions { display:flex; justify-content:flex-end; gap:8px; }
@media (max-width: 600px) {
.reader-margin { --reader-margin-w:150px; }
.reader-margin-title { display:none; }
}
/* Search matches */
mark.reader-search-match { background:rgba(241,196,15,.6); color:inherit; border-radius:2px; }
mark.reader-search-match.active { background:rgba(230,57,70,.7); }
#rs-search-count { font-size:12px; min-width:50px; }
/* Read-aloud (TTS) */
mark.tts-current { background:rgba(230,57,70,.55); color:inherit; border-radius:2px; }
.tts-bar { position:fixed; bottom:calc(var(--bar-h) + 16px); left:50%; transform:translateX(-50%); display:flex; align-items:center; gap:14px; background:var(--bg-card,#1a1a1a); border:1px solid var(--border); border-radius:var(--radius); padding:8px 16px; box-shadow:0 4px 16px rgba(0,0,0,.5); z-index:600; }
.tts-bar button { background:none; border:none; color:inherit; font-size:16px; cursor:pointer; padding:2px 4px; line-height:1; }
.tts-bar button:hover { opacity:0.7; }
/* Bookmarks sidebar */
.bookmark-entry { display:flex; width:100%; padding:6px 0; font-size:13px; justify-content:space-between; border-bottom:1px solid var(--border); }
/* Toast */
.reader-toast { position:fixed; bottom:calc(var(--bar-h) + 16px); left:50%; transform:translateX(-50%); background:var(--fg); color:var(--bg); padding:6px 14px; border-radius:var(--radius); font-size:13px; z-index:600; animation:toast-fade 2s ease forwards; pointer-events:none; }
@keyframes toast-fade { 0%,70%{opacity:1} 100%{opacity:0} }
.reader-toast-action { display:flex; align-items:center; gap:10px; padding:8px 8px 8px 14px; animation:toast-slide-up 0.2s ease forwards; pointer-events:auto; }
/* The apps: the player bar sits in the reader, above its footer; notices go above both. */
html.nui-reading .reader-toast { bottom:calc(var(--nui-dock-h, 0px) + 40px); }
.reader-toast-btn { background:var(--accent,#e63946); color:var(--on-accent); border:none; border-radius:calc(var(--radius) - 2px); padding:5px 12px; font-family:var(--font); font-size:13px; font-weight:600; cursor:pointer; white-space:nowrap; }
.reader-toast-btn:hover { filter:brightness(1.1); }
@keyframes toast-slide-up { 0%{opacity:0; transform:translate(-50%, 8px);} 100%{opacity:1; transform:translate(-50%, 0);} }
.build-time {
position: fixed;
bottom: 4px;
right: 6px;
font-size: 0.65rem;
color: var(--border-strong);
pointer-events: none;
z-index: 1;
}