Files
ai-bot 742da3c539 Add reading settings panel (Aa) + switch default font to Linux system font
- Floating Aa button on left edge, aligned with sidebar-toggle
- Panel: theme (light/paper/green/night), font (system/serif/sans/kai/mono),
  font size (A-/A+), line spacing (compact/cozy/relaxed), reset
- localStorage persistence (toozhao-reading)
- Replaces the old A-/A/A+ font-size control
- Default font: Ubuntu + Noto Sans CJK (Linux system stack) for global,
  headings and posts (was Georgia/Noto Serif SC)
2026-08-11 22:22:41 +10:00

181 lines
6.9 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script src="/js/tag-cloud.js"></script>
<script>
/* =========================================================
阅读设置面板(Aa 按钮)— 参考 Links Reading settings
支持:背景主题 / 字体类型 / 字号 / 行距 / 重置,localStorage 记忆
========================================================= */
(function () {
'use strict';
var LS_KEY = 'toozhao-reading';
var state = { theme: 'light', font: 'system', size: 1.05, spacing: 'cozy' };
var FONTS = {
system: '', /* 默认(Linux 系统字体) */
serif: 'Georgia, "Noto Serif SC", "Source Han Serif SC", "Songti SC", SimSun, serif',
sans: '"Noto Sans SC", "Source Han Sans SC", "Microsoft YaHei", "PingFang SC", "WenQuanYi Micro Hei", sans-serif',
kai: '"KaiTi", "STKaiti", "Kaiti SC", "AR PL UKai CN", serif',
mono: 'SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace'
};
var THEMES = { light: '明亮', paper: '纸感', green: '护眼', night: '夜间' };
var SPACING = {
compact: { label: '紧凑', lh: 1.6 },
cozy: { label: '舒适', lh: 1.9 },
relaxed: { label: '宽松', lh: 2.3 }
};
function loadState() {
try {
var raw = localStorage.getItem(LS_KEY);
if (!raw) return;
var s = JSON.parse(raw);
if (THEMES[s.theme]) state.theme = s.theme;
if (FONTS.hasOwnProperty(s.font)) state.font = s.font;
if (typeof s.size === 'number' && s.size >= 0.8 && s.size <= 1.5) state.size = s.size;
if (SPACING[s.spacing]) state.spacing = s.spacing;
} catch (e) { /* ignore */ }
}
function saveState() {
try { localStorage.setItem(LS_KEY, JSON.stringify(state)); } catch (e) { /* ignore */ }
}
function buildUI() {
if (document.getElementById('reading-toggle')) return;
var toggle = document.createElement('button');
toggle.id = 'reading-toggle';
toggle.className = 'reading-toggle';
toggle.title = '阅读设置';
toggle.textContent = 'Aa';
document.body.appendChild(toggle);
var panel = document.createElement('div');
panel.id = 'reading-panel';
panel.className = 'reading-panel';
// 主题(swatch 全部为硬编码静态 HTML,无用户输入,安全)
var rowT = document.createElement('div');
rowT.className = 'rp-row';
rowT.innerHTML = '<h4>主题</h4><div class="rp-themes">' +
['light', 'paper', 'green', 'night'].map(function (t) {
return '<button class="sw sw-' + t + '" data-theme="' + t + '" title="' + THEMES[t] + '"><span></span>' + THEMES[t] + '</button>';
}).join('') + '</div>';
var rowF = document.createElement('div');
rowF.className = 'rp-row';
rowF.innerHTML = '<h4>字体</h4><div class="rp-fonts">' +
['system', 'serif', 'sans', 'kai', 'mono'].map(function (f) {
var label = { system: '系统', serif: '宋体', sans: '黑体', kai: '楷体', mono: '等宽' }[f];
return '<button data-font="' + f + '">' + label + '</button>';
}).join('') + '</div>';
var rowS = document.createElement('div');
rowS.className = 'rp-row';
rowS.innerHTML = '<h4>字号</h4><div class="rp-size">' +
'<button class="rp-minus" title="减小">A</button><span class="rp-size-val">17px</span><button class="rp-plus" title="增大">A+</button></div>';
var rowSp = document.createElement('div');
rowSp.className = 'rp-row';
rowSp.innerHTML = '<h4>行距</h4><div class="rp-spacing">' +
['compact', 'cozy', 'relaxed'].map(function (s) {
return '<button data-spacing="' + s + '">' + SPACING[s].label + '</button>';
}).join('') + '</div>';
var reset = document.createElement('button');
reset.className = 'rp-reset';
reset.textContent = '↺ 恢复默认';
reset.addEventListener('click', function () {
state = { theme: 'light', font: 'system', size: 1.05, spacing: 'cozy' };
saveState();
apply();
render();
});
panel.appendChild(rowT);
panel.appendChild(rowF);
panel.appendChild(rowS);
panel.appendChild(rowSp);
panel.appendChild(reset);
document.body.appendChild(panel);
toggle.addEventListener('click', function (e) {
e.stopPropagation();
panel.classList.toggle('open');
toggle.classList.toggle('active', panel.classList.contains('open'));
});
panel.addEventListener('click', function (e) { e.stopPropagation(); });
panel.querySelectorAll('.sw').forEach(function (b) {
b.addEventListener('click', function () { state.theme = b.dataset.theme; saveState(); apply(); render(); });
});
panel.querySelectorAll('.rp-fonts button').forEach(function (b) {
b.addEventListener('click', function () { state.font = b.dataset.font; saveState(); apply(); render(); });
});
panel.querySelectorAll('.rp-spacing button').forEach(function (b) {
b.addEventListener('click', function () { state.spacing = b.dataset.spacing; saveState(); apply(); render(); });
});
panel.querySelector('.rp-minus').addEventListener('click', function () {
state.size = Math.max(0.8, +(state.size - 0.05).toFixed(2)); saveState(); apply(); render();
});
panel.querySelector('.rp-plus').addEventListener('click', function () {
state.size = Math.min(1.5, +(state.size + 0.05).toFixed(2)); saveState(); apply(); render();
});
document.addEventListener('click', function (e) {
if (panel.classList.contains('open') && !panel.contains(e.target) && e.target !== toggle) {
panel.classList.remove('open');
toggle.classList.remove('active');
}
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && panel.classList.contains('open')) {
panel.classList.remove('open');
toggle.classList.remove('active');
}
});
}
function apply() {
var body = document.body;
var pb = document.querySelector('.post-body');
body.setAttribute('data-rtheme', state.theme);
if (pb) {
pb.style.fontFamily = FONTS[state.font] || '';
pb.style.fontSize = state.size + 'em';
pb.style.lineHeight = SPACING[state.spacing].lh;
}
}
function render() {
var panel = document.getElementById('reading-panel');
if (!panel) return;
panel.querySelectorAll('.sw').forEach(function (b) {
b.classList.toggle('active', b.dataset.theme === state.theme);
});
panel.querySelectorAll('.rp-fonts button').forEach(function (b) {
b.classList.toggle('active', b.dataset.font === state.font);
});
panel.querySelectorAll('.rp-spacing button').forEach(function (b) {
b.classList.toggle('active', b.dataset.spacing === state.spacing);
});
var val = panel.querySelector('.rp-size-val');
if (val) val.textContent = Math.round(16 * state.size) + 'px';
}
function init() {
loadState();
buildUI();
apply();
render();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
</script>