Reader: add Sepia reading theme
Add a Dark/Sepia theme toggle to the reader's reading-settings drawer for easier long-form reading. Sepia uses a warm paper background with dark brown text via a :root[data-theme="sepia"] palette in theme.css. Text colour is now stored per theme with theme-specific swatch sets; the old single key migrates into the dark slot. An inline head script applies the saved theme before paint to avoid a flash. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5207da0792
commit
74de3ddee2
@ -13,8 +13,26 @@
|
||||
--success: #6baa6b;
|
||||
--warning: #c8a03a;
|
||||
--error: #c85a3a;
|
||||
--shadow-ring: rgba(255,255,255,0.1);
|
||||
--radius: 6px;
|
||||
--sidebar: 220px;
|
||||
--mono: 'DM Mono', monospace;
|
||||
--serif: 'Libre Baskerville', Georgia, serif;
|
||||
}
|
||||
|
||||
/* ── Sepia reading theme (reader only) ──────────────────────────────────── */
|
||||
:root[data-theme="sepia"] {
|
||||
--bg: #f4ecd8;
|
||||
--surface: #ece2cb;
|
||||
--surface2: #e3d7bb;
|
||||
--border: #d8cbac;
|
||||
--accent: #c17d12;
|
||||
--accent2: #a9690c;
|
||||
--text: #5b4636;
|
||||
--text-dim: #7c6a55;
|
||||
--text-faint: #ab9a82;
|
||||
--success: #4e8a4e;
|
||||
--warning: #9a7714;
|
||||
--error: #b04a2c;
|
||||
--shadow-ring: rgba(0,0,0,0.12);
|
||||
}
|
||||
|
||||
@ -71,7 +71,7 @@
|
||||
.btn-header-bm:hover { background: rgba(255,162,14,0.08); border-color: var(--accent); }
|
||||
.btn-header-series {
|
||||
display: none;
|
||||
color: var(--text-faint); border-color: rgba(255,255,255,0.08);
|
||||
color: var(--text-faint); border-color: var(--border);
|
||||
padding: 0.3rem 0.5rem;
|
||||
}
|
||||
.btn-header-series.active {
|
||||
@ -177,11 +177,26 @@
|
||||
width: 24px; height: 24px; border-radius: 50%;
|
||||
border: 2px solid transparent;
|
||||
cursor: pointer; transition: border-color 0.12s, transform 0.1s;
|
||||
box-shadow: 0 0 0 1px rgba(255,255,255,0.1);
|
||||
box-shadow: 0 0 0 1px var(--shadow-ring);
|
||||
padding: 0;
|
||||
}
|
||||
.colour-swatch:hover { transform: scale(1.15); }
|
||||
.colour-swatch.active { border-color: var(--accent); }
|
||||
.colour-swatches.hidden { display: none; }
|
||||
|
||||
/* ── Theme toggle ── */
|
||||
.theme-options { display: flex; gap: 0.5rem; }
|
||||
.theme-btn {
|
||||
flex: 1;
|
||||
padding: 0.4rem 0.6rem;
|
||||
background: var(--surface2); border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
font-family: var(--mono); font-size: 0.7rem;
|
||||
color: var(--text-dim); cursor: pointer;
|
||||
transition: color 0.12s, border-color 0.12s;
|
||||
}
|
||||
.theme-btn:hover { color: var(--text); border-color: var(--text-faint); }
|
||||
.theme-btn.active { color: var(--accent); border-color: var(--accent); }
|
||||
|
||||
/* ── Viewer ── */
|
||||
#viewer {
|
||||
@ -300,6 +315,16 @@
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
</style>
|
||||
<script>
|
||||
/* Apply saved reading theme before paint to avoid a flash. */
|
||||
(function () {
|
||||
try {
|
||||
if (localStorage.getItem('reader-theme') === 'sepia') {
|
||||
document.documentElement.setAttribute('data-theme', 'sepia');
|
||||
}
|
||||
} catch (e) {}
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@ -327,6 +352,13 @@
|
||||
<div class="settings-overlay" id="settings-overlay" onclick="closeSettings()"></div>
|
||||
<div class="settings-drawer" id="settings-drawer">
|
||||
<div class="settings-drawer-title">Reading settings</div>
|
||||
<div class="settings-row">
|
||||
<div class="settings-label">Theme</div>
|
||||
<div class="theme-options">
|
||||
<button class="theme-btn" data-theme="dark" onclick="applyTheme('dark')">Dark</button>
|
||||
<button class="theme-btn" data-theme="sepia" onclick="applyTheme('sepia')">Sepia</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-row">
|
||||
<div class="settings-label">
|
||||
Content width
|
||||
@ -345,13 +377,20 @@
|
||||
</div>
|
||||
<div class="settings-row">
|
||||
<div class="settings-label">Text colour</div>
|
||||
<div class="colour-swatches">
|
||||
<div class="colour-swatches" data-theme="dark">
|
||||
<button class="colour-swatch" data-colour="#e8e2d9" title="Bright" style="background:#e8e2d9" onclick="applyTextColour('#e8e2d9')"></button>
|
||||
<button class="colour-swatch" data-colour="#d4cec5" title="Warm cream" style="background:#d4cec5" onclick="applyTextColour('#d4cec5')"></button>
|
||||
<button class="colour-swatch" data-colour="#bfb8ae" title="Soft sand" style="background:#bfb8ae" onclick="applyTextColour('#bfb8ae')"></button>
|
||||
<button class="colour-swatch" data-colour="#a9a29a" title="Muted" style="background:#a9a29a" onclick="applyTextColour('#a9a29a')"></button>
|
||||
<button class="colour-swatch" data-colour="#938d86" title="Dim" style="background:#938d86" onclick="applyTextColour('#938d86')"></button>
|
||||
</div>
|
||||
<div class="colour-swatches hidden" data-theme="sepia">
|
||||
<button class="colour-swatch" data-colour="#5b4636" title="Warm brown" style="background:#5b4636" onclick="applyTextColour('#5b4636')"></button>
|
||||
<button class="colour-swatch" data-colour="#4a3a2c" title="Dark cocoa" style="background:#4a3a2c" onclick="applyTextColour('#4a3a2c')"></button>
|
||||
<button class="colour-swatch" data-colour="#6b5746" title="Medium" style="background:#6b5746" onclick="applyTextColour('#6b5746')"></button>
|
||||
<button class="colour-swatch" data-colour="#3a2e24" title="Near black" style="background:#3a2e24" onclick="applyTextColour('#3a2e24')"></button>
|
||||
<button class="colour-swatch" data-colour="#7c6a55" title="Dim" style="background:#7c6a55" onclick="applyTextColour('#7c6a55')"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -452,18 +491,49 @@
|
||||
applyWidth(saved);
|
||||
}
|
||||
|
||||
// ── Text colour ────────────────────────────────────────────────
|
||||
function applyTextColour(hex) {
|
||||
document.documentElement.style.setProperty('--text', hex);
|
||||
localStorage.setItem('reader-text-colour', hex);
|
||||
document.querySelectorAll('.colour-swatch').forEach(el => {
|
||||
el.classList.toggle('active', el.dataset.colour === hex);
|
||||
// ── Theme (Dark / Sepia) ───────────────────────────────────────
|
||||
const THEME_TEXT_DEFAULT = { dark: '#e8e2d9', sepia: '#5b4636' };
|
||||
|
||||
function currentTheme() {
|
||||
return document.documentElement.getAttribute('data-theme') === 'sepia' ? 'sepia' : 'dark';
|
||||
}
|
||||
|
||||
function applyTheme(theme) {
|
||||
if (theme !== 'sepia') theme = 'dark';
|
||||
if (theme === 'dark') {
|
||||
document.documentElement.removeAttribute('data-theme');
|
||||
} else {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
}
|
||||
localStorage.setItem('reader-theme', theme);
|
||||
document.querySelectorAll('.theme-btn').forEach(el => {
|
||||
el.classList.toggle('active', el.dataset.theme === theme);
|
||||
});
|
||||
document.querySelectorAll('.colour-swatches').forEach(el => {
|
||||
el.classList.toggle('hidden', el.dataset.theme !== theme);
|
||||
});
|
||||
loadTextColour();
|
||||
}
|
||||
|
||||
function loadTheme() {
|
||||
applyTheme(localStorage.getItem('reader-theme') || 'dark');
|
||||
}
|
||||
|
||||
// ── Text colour (per theme) ────────────────────────────────────
|
||||
function applyTextColour(hex) {
|
||||
const theme = currentTheme();
|
||||
document.documentElement.style.setProperty('--text', hex);
|
||||
localStorage.setItem('reader-text-colour-' + theme, hex);
|
||||
document.querySelectorAll('.colour-swatches[data-theme="' + theme + '"] .colour-swatch')
|
||||
.forEach(el => el.classList.toggle('active', el.dataset.colour === hex));
|
||||
}
|
||||
|
||||
function loadTextColour() {
|
||||
const saved = localStorage.getItem('reader-text-colour') || '#e8e2d9';
|
||||
applyTextColour(saved);
|
||||
const theme = currentTheme();
|
||||
let saved = localStorage.getItem('reader-text-colour-' + theme);
|
||||
// Migrate the pre-theme single key into the dark slot.
|
||||
if (!saved && theme === 'dark') saved = localStorage.getItem('reader-text-colour');
|
||||
applyTextColour(saved || THEME_TEXT_DEFAULT[theme]);
|
||||
}
|
||||
|
||||
// ── Font size ──────────────────────────────────────────────────
|
||||
@ -639,7 +709,7 @@
|
||||
// ── Init ───────────────────────────────────────────────────────
|
||||
async function init() {
|
||||
loadWidth();
|
||||
loadTextColour();
|
||||
loadTheme();
|
||||
loadFontSize();
|
||||
loadSeriesNav();
|
||||
|
||||
|
||||
@ -1,5 +1,30 @@
|
||||
# Develop Changelog
|
||||
|
||||
## 2026-06-01 — Reader: Sepia reading theme
|
||||
|
||||
### Added
|
||||
- The reader now offers a **Sepia** theme alongside the existing Dark theme, for easier long-form reading (warm paper background with dark brown text instead of light-on-black). New **Theme** toggle (Dark / Sepia) at the top of the reading settings drawer.
|
||||
- `static/theme.css`: added a `:root[data-theme="sepia"]` palette overriding `--bg`, `--surface`, `--surface2`, `--border`, `--accent`, `--accent2`, `--text`, `--text-dim`, `--text-faint`, `--success`, `--warning`, `--error`. Also added a `--shadow-ring` variable (light hairline in dark, dark hairline in sepia) so swatch rings read correctly in both themes.
|
||||
- `templates/reader.html`: theme toggle buttons + `.theme-btn` styling; an inline head script applies the saved theme before paint to avoid a flash; the Text colour row now has two theme-specific swatch sets (light tints for dark, dark brown tints for sepia), toggled by visibility.
|
||||
- JS: `applyTheme()`/`loadTheme()` set the `data-theme` attribute and persist to `localStorage` (`reader-theme`). Text colour is now stored per theme (`reader-text-colour-dark` / `reader-text-colour-sepia`); the old single `reader-text-colour` key is migrated into the dark slot. `init()` calls `loadTheme()` (which loads the active theme's text colour).
|
||||
- Replaced two hard-coded `rgba(255,255,255,...)` values in the reader CSS (swatch ring, series-button border) with theme variables so they don't show as white halos in sepia.
|
||||
|
||||
## 2026-05-31 — Build version in the sidebar
|
||||
|
||||
### Added
|
||||
- The sidebar now shows the running build version at the bottom (e.g. `v0.2.11` for releases, `v0.2.11.3` for dev builds), linking to the changelog page.
|
||||
- New `containers/novela/version.py` exposes `display_version()`. The semantic release version stays the single source in `changelog.py` (`CHANGELOG[0]["version"]`); a `BUILD` segment is appended for dev builds.
|
||||
- `shared_templates.py` registers `app_version` as a Jinja global; `_sidebar.html` renders it as a `.sidebar-version` link styled in `sidebar.css`.
|
||||
- `main.py` adds a `/api/version` endpoint returning `{"version": display_version()}`.
|
||||
|
||||
## 2026-05-31 — Find & Replace: scope option
|
||||
|
||||
### Added
|
||||
- Editor Find & Replace: new **Current chapter only** checkbox in the modal options. When checked, the search/replace runs against the currently open chapter instead of every chapter in the book.
|
||||
- `templates/editor.html`: added the `rp-current` checkbox next to Regex/Case sensitive; modal title changed from "Find & Replace — all chapters" to "Find & Replace" since scope is now selectable.
|
||||
- `static/editor.js` (`replaceInAllChapters()`): reads `rp-current`; builds a `targets` list of either just `currentCh()` or all `chapters`, and iterates that. Shows an error ("No chapter open.") if Current-chapter-only is selected with no open chapter. Result message reads "… in current chapter" for the single-chapter case.
|
||||
- Default is unchecked, so the existing all-chapters behaviour is preserved.
|
||||
|
||||
## 2026-05-10
|
||||
- Reader: subheading/chat styling now also wins when the wrapper contains block elements with their own color rule (e.g. `<div class="subheading"><p>…</p></div>`)
|
||||
- Previously the `<p>`/`<h*>` rules in `reader.html` (`#chapter-content p { color: var(--text); }`, etc.) had the same specificity as `.subheading` and applied more directly to the inner element, so the parent's color was effectively overridden — the wrapped paragraph stayed in the default text color
|
||||
|
||||
Loading…
Reference in New Issue
Block a user