jianzhihuixiang/alacarte-novel-website/js/app.js

762 lines
36 KiB
JavaScript
Raw 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.

// 章节数据
const chaptersData = [
{ id: 1, title: "第一章:洛兰", subtitle: "初入艾尔文防线", desc: "林克初到艾尔文防线,在洛兰森林遭遇哥布林,被阿甘左救下。", status: "已完结", date: "2026-03-20" },
{ id: 2, title: "第二章:洛兰深处", subtitle: "哥布林头目", desc: "林克在阿甘左指导下修炼剑术,深入洛兰深处击败哥布林头目。", status: "已完结", date: "2026-03-20" },
{ id: 3, title: "第三章:幽暗密林", subtitle: "猫妖的伏击", desc: "林克进入格兰之森深处的幽暗密林,遭遇猫妖伏击,历经苦战突破重围。", status: "已完结", date: "2026-03-23" },
{ id: 4, title: "第四章:幽暗密林深处", subtitle: "猫妖王", desc: "林克再次进入幽暗密林深处,遭遇猫妖王,揭开卡赞诅咒的部分真相。", status: "已完结", date: "2026-03-23" },
{ id: 5, title: "第五章:雷鸣废墟", subtitle: "闪电哥布林", desc: "林克挑战雷鸣废墟,遭遇闪电哥布林,经历雷电危机。", status: "已完结", date: "2026-03-23" },
{ id: 6, title: "第六章:格拉卡", subtitle: "牛头王萨乌塔", desc: "林克进入格拉卡,面对强大的牛头王萨乌塔,激战突破自身极限。", status: "已完结", date: "2026-03-23" },
{ id: 7, title: "第七章:烈焰格拉卡", subtitle: "火女彼诺修", desc: "林克挑战火女彼诺修,她的血之诅咒让战斗更加凶险。", status: "已完结", date: "2026-03-23" },
{ id: 8, title: "第八章:冰霜幽暗密林", subtitle: "冰女克拉赫", desc: "为寻找赛丽亚身世线索,林克前往冰霜幽暗密林挑战冰女克拉赫。", status: "已完结", date: "2026-03-23" },
{ id: 9, title: "第九章:转职之路", subtitle: "选择剑魂", desc: "林克前往赫顿玛尔寻找GSD了解四大转职方向坚定选择剑魂之道。", status: "已完结", date: "2026-03-24" },
{ id: 10, title: "第十章:暗黑雷鸣废墟", subtitle: "转职试炼", desc: "林克在暗黑雷鸣废墟完成转职试炼,在死亡气息中验证自己的剑道。", status: "已完结", date: "2026-03-24" },
{ id: 11, title: "第十一章:剑魂转职仪式", subtitle: "武器大师", desc: "GSD为林克举行正式转职仪式详细讲解剑魂武器大师特性。", status: "已完结", date: "2026-03-24" },
{ id: 12, title: "第十二章:西海岸", subtitle: "天空之城入口", desc: "林克前往西海岸了解天空之城背景GSD决定同行。", status: "已完结", date: "2026-03-24" },
{ id: 13, title: "第十三章:龙人之塔", subtitle: "GSD带队", desc: "GSD亲自带队刷龙人之塔展现'开挂瞎子'的恐怖实力。", status: "已完结", date: "2026-03-24" },
{ id: 14, title: "第十四章:人偶玄关", subtitle: "觉醒技能", desc: "GSD带队刷人偶玄关展现阿修罗觉醒技能'暗天波动眼'。", status: "已完结", date: "2026-03-24" },
{ id: 15, title: "第十五章:石巨人塔", subtitle: "独立挑战", desc: "林克和赛丽亚独立挑战石巨人塔,面对黄金巨人普拉塔尼。", status: "已完结", date: "2026-03-24" },
{ id: 16, title: "第十六章:黑暗玄廊", subtitle: "光明与黑暗", desc: "回赫顿玛尔交任务,探索黑暗玄廊,击败天之驱逐者。", status: "已完结", date: "2026-03-24" },
{ id: 17, title: "第十七章:城主宫殿", subtitle: "光之城主", desc: "面对光之城主赛格哈特,净化光之核心,揭晓晨曦的来历。", status: "已完结", date: "2026-03-24" },
{ id: 18, title: "第十八章:番外·悬空城", subtitle: "光之骑士", desc: "探索天空之城最深处,恢复晨曦的光芒,获得光之骑士称号。", status: "已完结", date: "2026-03-24" },
{ id: 19, title: "第十九章:天帷巨兽·神殿外围", subtitle: "使徒的呼唤", desc: "前往天帷巨兽调查GBL教异变遭遇第二使徒罗特斯的精神控制。", status: "已完结", date: "2026-03-24" },
{ id: 20, title: "第二十章:树精丛林", subtitle: "禁忌实验", desc: "探索GBL教的秘密实验室发现使徒融合实验击败树精王。", status: "已完结", date: "2026-03-24" },
{ id: 21, title: "第二十一章:炼狱", subtitle: "火焰试炼", desc: "穿越夜叉栖息的炼狱,击败夜叉王,晨曦恢复七成。", status: "已完结", date: "2026-03-24" },
{ id: 22, title: "第二十二章:西海岸的闲暇", subtitle: "支线与成长", desc: "返回西海岸休整,完成支线任务,与卡坤互动,学会拔刀斩。", status: "已完结", date: "2026-03-24" },
{ id: 23, title: "第二十三章:极昼", subtitle: "天空之巅", desc: "赛丽亚因精神污染离队林克独自挑战极昼击败EX多尼尔。", status: "已完结", date: "2026-03-24" },
{ id: 24, title: "第二十四章:第一脊椎", subtitle: "圣光降临", desc: "圣骑士艾伦加入队伍,联手击败巨型黑章鱼,晨曦恢复九成。", status: "已完结", date: "2026-03-24" },
{ id: 25, title: "第二十五章:赫顿玛尔的准备", subtitle: "天界科技", desc: "凯丽制造抗精神干扰手环,为最终决战做准备。", status: "已完结", date: "2026-03-24" },
{ id: 26, title: "第二十六章:第二脊椎", subtitle: "使徒决战", desc: "最终BOSS罗特斯晨曦完全觉醒天帷巨兽篇完结。", status: "已完结", date: "2026-03-24" },
{ id: 27, title: "第二十七章:重逢的温柔", subtitle: "赛丽亚线", desc: "林克与赛丽亚久别重逢,两人感情突破,正式确立关系。", status: "已完结", date: "2026-03-24" },
{ id: 28, title: "第二十八章:暗精灵的委托", subtitle: "后宫建立", desc: "莎兰暧昧互动,奥菲利亚表白加入后宫,前往暗黑城新任务。", status: "已完结", date: "2026-03-24" },
{ id: 29, title: "第二十九章:阿法利亚营地", subtitle: "暗黑城篇开启", desc: "抵达阿法利亚营地,接受克伦特委托,后宫修罗场帐篷同眠。", status: "已完结", date: "2026-03-24" },
{ id: 30, title: "第三十章:浅栖之地", subtitle: "寻找摩根", desc: "探索浅栖之地,击败怨恨之摩根,发现夏普伦长老的阴谋。", status: "已完结", date: "2026-03-24" },
{ id: 31, title: "第三十一章:蜘蛛洞穴", subtitle: "猛龙断空斩", desc: "首次实战使用猛龙断空斩击败BOSS艾克洛索突破Lv.50。", status: "已完结", date: "2026-03-24" },
{ id: 32, title: "第三十二章:克伦特的委托", subtitle: "四把钥匙", desc: "返回营地汇报,接受克伦特委托,准备前往暗精灵墓地收集四把钥匙。", status: "已完结", date: "2026-03-24" },
{ id: 33, title: "第三十三章:暗精灵墓地·左翼守卫", subtitle: "奥菲利亚线", desc: "击败火焰骷髅将军获得第一把钥匙,奥菲利亚献身,后宫正式确立。", status: "已完结", date: "2026-03-24" },
{ id: 34, title: "第三十四章:暗精灵墓地·剩余三将军", subtitle: "三战连捷", desc: "连续击败冰冻、速度、剧毒三位骷髅将军,集齐四把钥匙。", status: "已完结", date: "2026-03-25" },
{ id: 35, title: "第三十五章:邪龙斯皮兹", subtitle: "远古邪龙", desc: "用四把钥匙解开封印击败暗精灵墓地最终BOSS邪龙斯皮兹彻底消灭远古邪恶。", status: "已完结", date: "2026-03-25" },
{ id: 36, title: "第三十六章:莎兰的探望", subtitle: "暧昧之夜", desc: "击败邪龙后回到西海岸,莎兰深夜单独约见林克,两人关系更进一步。", status: "已完结", date: "2026-03-25" },
{ id: 37, title: "第三十七章:暗影迷宫·影子剑士", subtitle: "剑士对决", desc: "进入暗影迷宫挑战影子剑士刹影,林克以光之剑对决暗影之剑,最终净化刹影。", status: "已完结", date: "2026-03-25" },
{ id: 38, title: "第三十八章:熔岩穴·泰坦之怒", subtitle: "熔岩巨人", desc: "挑战熔岩穴深处的熔岩巨人泰坦,林克找到核心弱点,以龙之力彻底消灭怪物。", status: "已完结", date: "2026-03-25" },
{ id: 39, title: "第三十九章:暗黑城入口·无头骑士", subtitle: "幻影剑舞", desc: "林克向GSD学习45级技能幻影剑舞挑战无头骑士以新剑技击败强敌。", status: "已完结", date: "2026-03-25" },
{ id: 40, title: "第四十章:万年雪山·冰心少年", subtitle: "冰心查理", desc: "抵达班图族营地,购买新武器细雪之舞,遭遇被冰龙侵蚀的查理,团队配合制服暴走。", status: "已完结", date: "2026-03-25" },
{ id: 41, title: "第四十一章:敏泰的加入", subtitle: "队伍调整", desc: "艾伦退场返回赫顿玛尔,敏泰加入队伍,奥菲利亚转为后勤,新队伍前往利库天井。", status: "已完结", date: "2026-03-25" },
{ id: 42, title: "第四十二章:山脊·野兽师", subtitle: "后宫互动", desc: "挑战山脊BOSS野兽师鲁乌格赛丽亚受伤深夜教导敏泰如何成为林克的女人。", status: "已完结", date: "2026-03-25" },
{ id: 43, title: "第四十三章:番外·雪山温泉", subtitle: "敏泰献身", desc: "班图族温泉中,赛丽亚引导敏泰向林克献身,三人共度温馨之夜。", status: "已完结", date: "2026-03-25" },
{ id: 44, title: "第四十四章:白色废墟", subtitle: "雪魈王", desc: "探索白色废墟,遭遇雪魈、冰精灵、被控制的班图族战士,配合击败雪魈王。", status: "已完结", date: "2026-03-25" },
{ id: 45, title: "第四十五章:冰雪宫殿", subtitle: "冰龙侍从", desc: "深入冰雪宫殿,遭遇冰蜘蛛、冰霜巨人,最终击败冰龙侍从,获得进入巢穴的资格。", status: "已完结", date: "2026-03-25" },
{ id: 46, title: "第四十六章:布万加修炼场", subtitle: "突破", desc: "在布万加修炼场接受训练,林克领悟山岳剑势,赛丽亚点燃心火,敏泰获得母亲骨杖。", status: "已完结", date: "2026-03-25" },
{ id: 47, title: "第四十七章:斯卡萨之巢·龙威", subtitle: "初战冰龙", desc: "首次挑战冰龙斯卡萨,巴卡尔三龙崽背景揭露,不敌撤退,奥尔卡、奥菲利亚、查理赶来支援。", status: "已完结", date: "2026-03-25" },
{ id: 48, title: "第四十八章:斯卡萨之巢·龙陨", subtitle: "雪山篇完结", desc: "全员集结决战冰龙斯卡萨,击破逆鳞,班图族解放,雪山篇落幕。", status: "已完结", date: "2026-03-25" },
{ id: 49, title: "第四十九章:重返赫顿玛尔", subtitle: "休整", desc: "返回赫顿玛尔休整,与艾伦重逢喝酒,莎兰献身,接受诺斯玛尔异变任务。", status: "已完结", date: "2026-03-25" },
{ id: 50, title: "第五十章:诺斯玛尔·盗贼团", subtitle: "帕丽丝加入", desc: "到达诺斯玛尔,遭遇被瘟疫控制的盗贼团,救下街霸帕丽丝,奥菲利亚退场回西海岸。", status: "已完结", date: "2026-03-25" },
{ id: 51, title: "第五十一章:哈穆林·鼠患", subtitle: "鼠王", desc: "深入哈穆林下水道,遭遇变异巨鼠群,配合击败鼠王,获得瘟疫核心晶石。", status: "已完结", date: "2026-03-25" },
{ id: 52, title: "第五十二章:月光酒馆", subtitle: "长三郎", desc: "探索月光酒馆遭遇被控制的冒险者击败BOSS长三郎获得痛苦之村地图。", status: "已完结", date: "2026-03-25" },
{ id: 53, title: "第五十三章:番外·帕丽丝的誓言", subtitle: "帕丽丝献身", desc: "废弃别墅中,帕丽丝向林克表白献身,两人成为真正的恋人,赛丽亚大度接纳。", status: "已完结", date: "2026-03-25" },
{ id: 54, title: "第五十四章:觉醒之路·启程", subtitle: "GSD", desc: "返回赫顿玛尔找GSD接受觉醒试炼准备前往比尔马克帝国试验场挑战机械牛。", status: "已完结", date: "2026-03-25" },
{ id: 55, title: "第五十五章:比尔马克帝国试验场", subtitle: "机械牛", desc: "挑战比尔马克帝国试验场,遭遇各种改造怪物,击败牛头械王获得牛妖之血。", status: "已完结", date: "2026-03-25" },
{ id: 56, title: "第五十六章:王的遗迹·远古五骑士", subtitle: "五骑士", desc: "挑战王的遗迹,依次击败风、守护、冰、火、光五位远古骑士,获得五大印记。", status: "已完结", date: "2026-03-25" },
{ id: 57, title: "第五十七章:心灵试炼·剑圣觉醒", subtitle: "觉醒", desc: "完成心灵试炼,战胜心魔,成功觉醒成为剑圣,获得极·鬼剑术。", status: "已完结", date: "2026-03-25" }
];
// DOM加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
initTheme();
initNavigation();
initParticles();
// 根据页面类型初始化不同功能
const currentPage = window.location.pathname.split('/').pop() || 'index.html';
if (currentPage === 'index.html' || currentPage === '') {
renderLatestChapters();
} else if (currentPage === 'chapters.html') {
renderChaptersList();
setupFilters();
setupSearch();
updateReadingProgress();
} else if (currentPage === 'reader.html') {
initReader();
} else if (currentPage.startsWith('chapter-')) {
// 独立章节页面初始化
initChapterReader();
}
// 初始化回到顶部按钮
initScrollTop();
// 初始化阅读器设置
initReaderSettings();
});
// ==================== 主题切换 ====================
function initTheme() {
const themeToggle = document.getElementById('themeToggle');
if (!themeToggle) return;
// 检查本地存储的主题
const savedTheme = localStorage.getItem('theme') || 'dark';
document.documentElement.setAttribute('data-theme', savedTheme);
updateThemeIcon(savedTheme);
themeToggle.addEventListener('click', () => {
const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
updateThemeIcon(newTheme);
});
}
function updateThemeIcon(theme) {
const themeToggle = document.getElementById('themeToggle');
if (!themeToggle) return;
const icon = themeToggle.querySelector('.theme-icon');
icon.textContent = theme === 'dark' ? '🌙' : '☀️';
}
// ==================== 导航栏 ====================
function initNavigation() {
const menuToggle = document.getElementById('menuToggle');
const navLinks = document.querySelector('.nav-links');
if (menuToggle && navLinks) {
menuToggle.addEventListener('click', () => {
navLinks.classList.toggle('active');
menuToggle.classList.toggle('active');
});
}
// 导航栏滚动效果
let lastScroll = 0;
const navbar = document.querySelector('.navbar');
window.addEventListener('scroll', () => {
const currentScroll = window.pageYOffset;
if (currentScroll > 100) {
navbar.style.background = 'rgba(15, 15, 26, 0.95)';
} else {
navbar.style.background = 'rgba(15, 15, 26, 0.8)';
}
lastScroll = currentScroll;
});
}
// ==================== 粒子效果 ====================
function initParticles() {
const particlesContainer = document.getElementById('particles');
if (!particlesContainer) return;
// 创建粒子
for (let i = 0; i < 30; i++) {
const particle = document.createElement('div');
particle.className = 'particle';
particle.style.cssText = `
position: absolute;
width: ${Math.random() * 4 + 2}px;
height: ${Math.random() * 4 + 2}px;
background: rgba(99, 102, 241, ${Math.random() * 0.3 + 0.1});
border-radius: 50%;
left: ${Math.random() * 100}%;
top: ${Math.random() * 100}%;
animation: particleFloat ${Math.random() * 10 + 10}s ease-in-out infinite;
animation-delay: ${Math.random() * 5}s;
`;
particlesContainer.appendChild(particle);
}
}
// ==================== 首页最新章节 ====================
function renderLatestChapters() {
const container = document.getElementById('latestChapters');
if (!container) return;
// 获取最新的6章
const latestChapters = chaptersData.slice(-6).reverse();
container.innerHTML = latestChapters.map(chapter => `
<a href="reader.html?id=${chapter.id}" class="chapter-card">
<div class="chapter-number">${chapter.id}</div>
<div class="chapter-info">
<h3 class="chapter-title">${chapter.title}</h3>
<p class="chapter-desc">${chapter.desc}</p>
<div class="chapter-meta">
<span>${chapter.date}</span>
<span class="chapter-status">${chapter.status}</span>
</div>
</div>
</a>
`).join('');
}
// ==================== 章节列表页 ====================
function renderChaptersList() {
const container = document.getElementById('chaptersList');
if (!container) return;
// 优先使用从JSON动态加载的数据
const data = window.chaptersData || chaptersData;
container.innerHTML = data.map(chapter => `
<div class="timeline-item" data-chapter="${chapter.id}">
<div class="timeline-marker"></div>
<a href="reader.html?id=${chapter.id}" class="timeline-content">
<div class="timeline-header">
<h3>${chapter.title}</h3>
<span class="timeline-date">${chapter.date}</span>
</div>
<p class="timeline-subtitle">${chapter.subtitle}</p>
<p class="timeline-desc">${chapter.desc}</p>
</a>
</div>
`).join('');
}
function setupFilters() {
const filterTabs = document.querySelectorAll('.filter-tab');
filterTabs.forEach(tab => {
tab.addEventListener('click', () => {
filterTabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
const filter = tab.dataset.filter;
filterChapters(filter);
});
});
}
function filterChapters(filter) {
const items = document.querySelectorAll('.timeline-item');
const data = window.chaptersData || chaptersData;
items.forEach((item, index) => {
let show = true;
if (filter === 'latest') {
show = index >= data.length - 5;
} else if (filter === 'unread') {
const chapterId = parseInt(item.dataset.chapter);
const readChapters = JSON.parse(localStorage.getItem('readChapters') || '[]');
show = !readChapters.includes(chapterId);
}
if (show) {
item.classList.remove('hidden');
item.style.display = '';
} else {
item.classList.add('hidden');
item.style.display = 'none';
}
});
}
function setupSearch() {
const searchInput = document.getElementById('searchInput');
if (!searchInput) return;
searchInput.addEventListener('input', (e) => {
const query = e.target.value.toLowerCase();
const items = document.querySelectorAll('.timeline-item');
items.forEach(item => {
const title = item.querySelector('h3').textContent.toLowerCase();
const desc = item.querySelector('.timeline-desc').textContent.toLowerCase();
const match = title.includes(query) || desc.includes(query);
if (match) {
item.classList.remove('hidden');
item.style.display = '';
} else {
item.classList.add('hidden');
item.style.display = 'none';
}
});
});
}
function updateReadingProgress() {
const readChapters = JSON.parse(localStorage.getItem('readChapters') || '[]');
const data = window.chaptersData || chaptersData;
const progress = Math.round((readChapters.length / data.length) * 100);
const progressFill = document.getElementById('progressFill');
const progressText = document.getElementById('progressText');
if (progressFill) progressFill.style.width = `${progress}%`;
if (progressText) progressText.textContent = `阅读进度 ${progress}%`;
}
// ==================== 阅读器 ====================
function initReader() {
// 获取章节参数
const urlParams = new URLSearchParams(window.location.search);
const chapterId = parseInt(urlParams.get('chapter')) || 1;
// 加载章节内容
loadChapter(chapterId);
// 初始化阅读器设置
initReaderSettings();
// 初始化导航
initReaderNav(chapterId);
// 初始化侧边栏
initSidebar();
// 记录阅读进度
recordReadingProgress(chapterId);
// 自动隐藏头部和底部
initAutoHide();
}
function loadChapter(chapterId) {
const chapter = chaptersData.find(c => c.id === chapterId);
if (!chapter) return;
// 更新标题
document.getElementById('chapterTitle').textContent = chapter.title;
document.title = `${chapter.title} - 阿拉德:剑之回响`;
// 加载内容(模拟)
const contentEl = document.getElementById('chapterContent');
contentEl.innerHTML = `
<div class="chapter-header">
<h1>${chapter.title}</h1>
<p class="chapter-subtitle">${chapter.subtitle}</p>
</div>
<div class="chapter-body">
<p class="placeholder-text">正在从飞书文档加载内容...</p>
<p class="placeholder-text">章节链接:<a href="${getChapterUrl(chapterId)}" target="_blank">在飞书文档中查看</a></p>
</div>
<div class="chapter-footer">
<p>本章完</p>
</div>
`;
}
function getChapterUrl(chapterId) {
const urls = {
1: 'https://www.feishu.cn/docx/KBdwdTd8foLLfSxCOmDcNr1LnKe',
2: 'https://www.feishu.cn/docx/Jt0wdpCwDoq7zKxT2TUc54Hlnmd',
3: 'https://www.feishu.cn/docx/G0jXdqGzPoVLe9x9S9qcPzTJnsc',
4: 'https://www.feishu.cn/docx/IckMdiy7cor3fCxap2ic1D40n4d',
5: 'https://www.feishu.cn/docx/PQBMdL9DQosxe1xsq34czRnFnPc',
6: 'https://www.feishu.cn/docx/XflsdWsoRoyS5Xxg91VcmkysnlK',
7: 'https://www.feishu.cn/docx/XL9YdobRMoaSwXxTe23cpuwynkh',
8: 'https://www.feishu.cn/docx/FJ80dI1A1oCRgIxR6i2cIXESnnd',
9: 'https://www.feishu.cn/docx/L2tydWs0Bo84h4xE3hbcfl8MnPg',
10: 'https://www.feishu.cn/docx/Iu2LdfRg2oEP2zxh3kRcqBJ9n1f',
11: 'https://www.feishu.cn/docx/NEiJdna0Jo1OHEx1s84cbNgBn1e',
12: 'https://www.feishu.cn/docx/L2j3dt7PQodXFKxSuoScT3BZnkc',
13: 'https://www.feishu.cn/docx/WHgFdA6NXoyynwxdLmMcjZaPnfd',
14: 'https://www.feishu.cn/docx/FhoNdTZq8oPs91xrb2Gcg2udnAh'
};
return urls[chapterId] || '#';
}
function initReaderSettings() {
const settingsToggle = document.getElementById('settingsToggle');
const readerSettings = document.getElementById('readerSettings');
const overlay = document.getElementById('overlay');
if (settingsToggle) {
settingsToggle.addEventListener('click', () => {
readerSettings.classList.toggle('active');
overlay.classList.toggle('active');
});
}
if (overlay) {
overlay.addEventListener('click', () => {
readerSettings.classList.remove('active');
overlay.classList.remove('active');
});
}
// 字体大小
const sizeBtns = document.querySelectorAll('.size-btn');
sizeBtns.forEach(btn => {
btn.addEventListener('click', () => {
sizeBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const size = btn.dataset.size;
const sizes = { small: '16px', normal: '18px', large: '20px' };
document.getElementById('chapterContent').style.fontSize = sizes[size];
});
});
// 主题
const themeOptions = document.querySelectorAll('.theme-option');
themeOptions.forEach(option => {
option.addEventListener('click', () => {
themeOptions.forEach(o => o.classList.remove('active'));
option.classList.add('active');
const theme = option.dataset.theme;
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
});
});
}
function initReaderNav(currentId) {
const prevBtn = document.getElementById('prevChapter');
const nextBtn = document.getElementById('nextChapter');
if (prevBtn) {
if (currentId > 1) {
prevBtn.disabled = false;
prevBtn.addEventListener('click', () => {
window.location.href = `reader.html?chapter=${currentId - 1}`;
});
} else {
prevBtn.disabled = true;
}
}
if (nextBtn) {
if (currentId < chaptersData.length) {
nextBtn.disabled = false;
nextBtn.addEventListener('click', () => {
window.location.href = `reader.html?chapter=${currentId + 1}`;
});
} else {
nextBtn.disabled = true;
}
}
}
function initSidebar() {
const openSidebar = document.getElementById('openSidebar');
const sidebarClose = document.getElementById('sidebarClose');
const readerSidebar = document.getElementById('readerSidebar');
const overlay = document.getElementById('overlay');
// 生成侧边栏章节列表
const sidebarChapters = document.getElementById('sidebarChapters');
if (sidebarChapters) {
sidebarChapters.innerHTML = chaptersData.map(chapter => `
<a href="reader.html?chapter=${chapter.id}" class="sidebar-chapter ${chapter.id === getCurrentChapterId() ? 'active' : ''}">
<span class="chapter-num">${chapter.id}</span>
<span class="chapter-name">${chapter.title}</span>
</a>
`).join('');
}
if (openSidebar) {
openSidebar.addEventListener('click', () => {
readerSidebar.classList.add('active');
overlay.classList.add('active');
});
}
if (sidebarClose) {
sidebarClose.addEventListener('click', () => {
readerSidebar.classList.remove('active');
overlay.classList.remove('active');
});
}
}
function getCurrentChapterId() {
const urlParams = new URLSearchParams(window.location.search);
return parseInt(urlParams.get('chapter')) || 1;
}
function recordReadingProgress(chapterId) {
let readChapters = JSON.parse(localStorage.getItem('readChapters') || '[]');
if (!readChapters.includes(chapterId)) {
readChapters.push(chapterId);
localStorage.setItem('readChapters', JSON.stringify(readChapters));
}
localStorage.setItem('lastReadChapter', chapterId);
}
function initAutoHide() {
const header = document.getElementById('readerHeader');
const footer = document.getElementById('readerFooter');
let lastScrollY = window.scrollY;
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking) {
window.requestAnimationFrame(() => {
const currentScrollY = window.scrollY;
if (currentScrollY > lastScrollY && currentScrollY > 100) {
// 向下滚动,隐藏
header?.classList.add('hidden');
footer?.classList.add('hidden');
} else {
// 向上滚动,显示
header?.classList.remove('hidden');
footer?.classList.remove('hidden');
}
lastScrollY = currentScrollY;
ticking = false;
});
ticking = true;
}
});
}
// ==================== 工具函数 ====================
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// ==================== 回到顶部按钮 ====================
function initScrollTop() {
const scrollTopBtn = document.getElementById('scrollTop');
if (!scrollTopBtn) return;
// 点击回到顶部
scrollTopBtn.addEventListener('click', () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
});
// 滚动时显示/隐藏按钮
const toggleVisibility = () => {
if (window.pageYOffset > 300) {
scrollTopBtn.style.opacity = '1';
scrollTopBtn.style.visibility = 'visible';
} else {
scrollTopBtn.style.opacity = '0';
scrollTopBtn.style.visibility = 'hidden';
}
};
// 初始状态隐藏
scrollTopBtn.style.opacity = '0';
scrollTopBtn.style.visibility = 'hidden';
scrollTopBtn.style.transition = 'opacity 0.3s ease, visibility 0.3s ease';
window.addEventListener('scroll', throttle(toggleVisibility, 100));
}
// ==================== 独立章节页面初始化 ====================
function initChapterReader() {
const openSidebar = document.getElementById('openSidebar');
const sidebarClose = document.getElementById('sidebarClose');
const readerSidebar = document.getElementById('readerSidebar');
const overlay = document.getElementById('overlay');
const progressFill = document.getElementById('progressFill');
// 动态计算进度条(基于总章节数)
if (progressFill) {
const totalChapters = chaptersData.length; // 从 chaptersData 获取总章节数
const currentChapterMatch = window.location.pathname.match(/chapter-(\d+)\.html/);
const currentChapter = currentChapterMatch ? parseInt(currentChapterMatch[1]) : 1;
const progress = Math.round((currentChapter / totalChapters) * 100);
progressFill.style.width = progress + '%';
}
if (openSidebar && readerSidebar && overlay) {
openSidebar.addEventListener('click', () => {
readerSidebar.classList.add('active');
overlay.classList.add('active');
});
}
if (sidebarClose && readerSidebar && overlay) {
sidebarClose.addEventListener('click', () => {
readerSidebar.classList.remove('active');
overlay.classList.remove('active');
});
}
if (overlay && readerSidebar) {
overlay.addEventListener('click', () => {
readerSidebar.classList.remove('active');
overlay.classList.remove('active');
});
}
}
// ==================== 阅读器设置 ====================
function initReaderSettings() {
const settingsBtn = document.getElementById('readerSettings');
const settingsPanel = document.getElementById('settingsPanel');
if (!settingsBtn || !settingsPanel) return;
// 切换设置面板显示
settingsBtn.addEventListener('click', (e) => {
e.stopPropagation();
settingsPanel.classList.toggle('active');
});
// 点击外部关闭面板
document.addEventListener('click', (e) => {
if (!settingsPanel.contains(e.target) && e.target !== settingsBtn) {
settingsPanel.classList.remove('active');
}
});
// 初始化主题
initThemeSettings();
// 初始化字体大小
initFontSize();
// 初始化行间距
initLineHeight();
// 初始化阅读宽度
initReadWidth();
}
// 主题设置
function initThemeSettings() {
const themeOptions = document.querySelectorAll('.theme-option');
const savedTheme = localStorage.getItem('readerTheme') || 'dark';
// 应用保存的主题
document.documentElement.setAttribute('data-theme', savedTheme);
// 设置激活状态
themeOptions.forEach(option => {
if (option.dataset.theme === savedTheme) {
option.classList.add('active');
}
option.addEventListener('click', () => {
const theme = option.dataset.theme;
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('readerTheme', theme);
themeOptions.forEach(o => o.classList.remove('active'));
option.classList.add('active');
});
});
}
// 字体大小设置
function initFontSize() {
const decreaseBtn = document.getElementById('fontDecrease');
const increaseBtn = document.getElementById('fontIncrease');
const fontValue = document.getElementById('fontValue');
const content = document.querySelector('.chapter-article');
if (!content) return;
const sizes = ['14px', '16px', '18px', '20px', '22px', '24px'];
let currentSize = localStorage.getItem('readerFontSize') || '18px';
// 应用保存的字体大小
content.style.fontSize = currentSize;
fontValue.textContent = currentSize;
decreaseBtn?.addEventListener('click', () => {
const index = sizes.indexOf(currentSize);
if (index > 0) {
currentSize = sizes[index - 1];
content.style.fontSize = currentSize;
fontValue.textContent = currentSize;
localStorage.setItem('readerFontSize', currentSize);
}
});
increaseBtn?.addEventListener('click', () => {
const index = sizes.indexOf(currentSize);
if (index < sizes.length - 1) {
currentSize = sizes[index + 1];
content.style.fontSize = currentSize;
fontValue.textContent = currentSize;
localStorage.setItem('readerFontSize', currentSize);
}
});
}
// 行间距设置
function initLineHeight() {
const lineHeightBtns = document.querySelectorAll('.line-height-btn');
const content = document.querySelector('.chapter-article');
if (!content) return;
const savedLineHeight = localStorage.getItem('readerLineHeight') || '1.8';
content.style.lineHeight = savedLineHeight;
lineHeightBtns.forEach(btn => {
if (btn.dataset.value === savedLineHeight) {
btn.classList.add('active');
}
btn.addEventListener('click', () => {
const value = btn.dataset.value;
content.style.lineHeight = value;
localStorage.setItem('readerLineHeight', value);
lineHeightBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
});
});
}
// 阅读宽度设置
function initReadWidth() {
const widthBtns = document.querySelectorAll('.width-btn');
const content = document.querySelector('.reader-content');
if (!content) return;
const savedWidth = localStorage.getItem('readerWidth') || 'medium';
applyWidth(content, savedWidth);
widthBtns.forEach(btn => {
if (btn.dataset.width === savedWidth) {
btn.classList.add('active');
}
btn.addEventListener('click', () => {
const width = btn.dataset.width;
applyWidth(content, width);
localStorage.setItem('readerWidth', width);
widthBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
});
});
}
function applyWidth(content, width) {
const widths = {
narrow: '680px',
medium: '800px',
wide: '100%'
};
content.style.maxWidth = widths[width] || widths.medium;
}