Add per-user page tone modes to reader

This commit is contained in:
Codex committed 2026-10-12 23:49:02 +02:00
1 parent 26d6cd59fc
commit 605fa5b9d8
8 files changed
+84 -2

No files matched your search

+6
View File
@@ -430,3 +430,9 @@ body.nobars #bot { transform: translateY(100%); opacity: 0; pointer-events: none
.seg button { padding: 7px 10px; } #bot .btn { padding: 6px 10px; } .wide-only { display: none; }
.clock-panel { bottom: calc(124px + env(safe-area-inset-bottom)); }
}
#canvas[data-page-tone="inverted"] .pg img { filter: invert(1); }
#canvas[data-page-tone="sepia"] .pg img { filter: sepia(.8) brightness(.9); }
#canvas[data-page-tone="dim"] .pg img { filter: brightness(.7); }
#canvas[data-page-tone="extra-dim"] .pg img { filter: brightness(.45); }
.page-tone { white-space: nowrap; }
.page-tone:not([data-page-tone="original"]) { border-color: var(--accent); }
+1
View File
@@ -38,6 +38,7 @@
<button data-mode="page" title="One page at a time">Page</button>
<button data-mode="book" title="Two pages side by side, like an open book">Book</button>
</div>
<button id="page-tone" class="btn page-tone" type="button" title="Change page colors; controls and theme stay the same" aria-label="Page tone: Original. Change page colors">Tone: Original</button>
<button id="shift" class="btn" title="Page pairs look wrong? Shift them by one page">⇄<span class="wide-only"> Shift</span></button>
<div class="seg zoom-controls" role="group" aria-label="Page zoom">
<button id="zoomout" title="Zoom out 5%" aria-label="Zoom out 5%">−</button>
+31
View File
@@ -45,6 +45,36 @@
const shift = new Set(D.progress && D.progress.v === D.version ? D.progress.shift : []);
const zkey = () => (mode === 'flow' && ttb ? 'strip' : mode);
const zoom = () => S.zoom[zkey()] || 1;
const tones = [
{ id: 'original', label: 'Original' },
{ id: 'inverted', label: 'Inverted' },
{ id: 'sepia', label: 'Sepia' },
{ id: 'dim', label: 'Dim' },
{ id: 'extra-dim', label: 'Extra dim' },
];
const toneButton = $('#page-tone');
const toneName = id => tones.find(tone => tone.id === id)?.label || 'Original';
function applyTone(id) {
S.pageTone = tones.some(tone => tone.id === id) ? id : 'original';
canvas.dataset.pageTone = S.pageTone;
toneButton.dataset.pageTone = S.pageTone;
toneButton.textContent = `Tone: ${toneName(S.pageTone)}`;
toneButton.title = `Page images: ${toneName(S.pageTone)}. Click for the next tone; controls and theme stay unchanged.`;
toneButton.setAttribute('aria-label', toneButton.title);
}
applyTone(S.pageTone);
let savedTone = S.pageTone, toneSave = Promise.resolve(), toneChange = 0;
function cycleTone() {
const next = tones[(tones.findIndex(tone => tone.id === S.pageTone) + 1) % tones.length];
applyTone(next.id);
const change = ++toneChange;
toneSave = toneSave.catch(() => {}).then(async () => {
await api('api/settings', { pageTone: next.id });
savedTone = next.id;
}).catch(() => {
if (change === toneChange) { applyTone(savedTone); toast('Could not save page tone'); }
});
}
const pr = D.progress;
if (pr && !(pr.done && pr.p >= pr.n - 1)) {
@@ -571,6 +601,7 @@
ver.hidden = D.versions.length < 2;
ver.onchange = () => { saveProgress(true); location.href = readUrl(D.manga.id, D.chapter.id, ver.value); };
for (const b of modeBtns) b.onclick = () => { if (b.dataset.mode !== mode) setMode(b.dataset.mode, true); };
toneButton.onclick = cycleTone;
$('#shift').onclick = toggleShift;
$('#zoomout').onclick = () => stepZoom(-1);
$('#zoom').onclick = () => setZoom(1);
+9
View File
@@ -31,6 +31,13 @@
h('option', { value: 'page' }, 'Always Page (one page at a time)'),
h('option', { value: 'book' }, 'Always Book (two pages side by side)'));
startMode.value = S.startMode;
const tone = h('select', { onchange: e => save({ pageTone: e.target.value }) },
h('option', { value: 'original' }, 'Original'),
h('option', { value: 'inverted' }, 'Inverted'),
h('option', { value: 'sepia' }, 'Sepia'),
h('option', { value: 'dim' }, 'Dim (70% brightness)'),
h('option', { value: 'extra-dim' }, 'Extra dim (45% brightness)'));
tone.value = S.pageTone;
const LABEL = { flow: 'Flow', page: 'Page', book: 'Book', strip: 'Webtoon flow' };
const zooms = () => Object.keys(LABEL).map(k => `${LABEL[k]} ${Math.round((S.zoom[k] || 1) * 100)}%`).join(' · ');
const zoomText = h('span', { class: 'muted' }, zooms());
@@ -72,6 +79,8 @@
check('skipCovers', 'Skip chapter cover pages'),
check('flowPageGap', 'Show a gap between pages in Flow mode',
'Turn this off for continuous webtoon artwork or text that crosses page edges.'),
h('label', { class: 'field' }, h('span', {}, 'Page tone'), tone,
h('div', { class: 'muted small' }, 'The reader button cycles through these choices. Only page artwork changes; the site theme and controls stay as they are.')),
h('div', { class: 'field' }, h('span', {}, 'Page zoom'), zoomText, ' ',
h('button', { class: 'btn small', onclick: async () => { await save({ zoom: {} }); zoomText.textContent = zooms(); } }, 'Reset to fit'),
h('div', { class: 'muted small' }, '100% = the page fits the screen. Change it in the reader with Ctrl + mouse wheel, a pinch, or the + / - keys.'))));