feat: 新增5部经典下拉选经、TTS重试、PWA可安装
- 新增阿弥陀经/普门品/道德经/清静经/太上感应篇(维基文库底本,OpenCC转简体) - 经典切换改为下拉框(佛/道分组),移动端复用底部面板并显示分组标题 - TTS 请求失败自动重试3次;普门品偈颂拆分修复600字上限问题 - 服务端缓存原子写(.tmp+rename),上限放宽至800字 - PWA:manifest + Service Worker,安装后无地址栏独立窗口 - 偏好记忆/逐字光标/回到诵读处/夜间模式等此前改动一并入库 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -159,6 +159,13 @@ body {
|
||||
.btn-play.playing .icon-play { display: none; }
|
||||
.btn-play.playing .icon-stop { display: block; }
|
||||
|
||||
/* 音频准备中:按钮轻微呼吸脉冲,提示正在合成/合并 */
|
||||
.btn-play.preparing { cursor: wait; animation: preparing-pulse 1.3s ease-in-out infinite; }
|
||||
@keyframes preparing-pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.55; }
|
||||
}
|
||||
|
||||
.player-meta { display: flex; align-items: center; gap: 12px; }
|
||||
.voice-label { font-size: 0.85rem; color: var(--ink-soft); letter-spacing: 0.2em; white-space: nowrap; }
|
||||
|
||||
@@ -179,7 +186,9 @@ body {
|
||||
}
|
||||
#voice-select:focus-visible { outline: 3px solid var(--gold-soft); outline-offset: 1px; }
|
||||
|
||||
#timer-select {
|
||||
#timer-select,
|
||||
#rate-select,
|
||||
#sutra-select {
|
||||
appearance: none;
|
||||
font-family: inherit;
|
||||
font-size: 0.86rem;
|
||||
@@ -194,10 +203,12 @@ body {
|
||||
padding: 6px 26px 6px 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#timer-select:focus-visible { outline: 3px solid var(--gold-soft); outline-offset: 1px; }
|
||||
#timer-select:focus-visible, #rate-select:focus-visible, #sutra-select:focus-visible { outline: 3px solid var(--gold-soft); outline-offset: 1px; }
|
||||
#sutra-select { color: var(--ink); font-size: 0.9rem; }
|
||||
|
||||
/* ===== 字号 / 排版工具(并入控制栏) ===== */
|
||||
.tool-group { display: flex; align-items: center; gap: 8px; }
|
||||
.field-pair { display: flex; align-items: center; gap: 8px; }
|
||||
.tool-name { font-size: 0.8rem; color: var(--ink-soft); letter-spacing: 0.2em; }
|
||||
|
||||
.size-btn, .layout-btn, .sutra-btn, .pager-btn {
|
||||
@@ -234,10 +245,14 @@ body {
|
||||
}
|
||||
.status.error { color: var(--cinnabar); }
|
||||
|
||||
/* 移动端强制换行占位 / 折叠按钮 / 文字标签(桌面端隐藏) */
|
||||
/* 移动端强制换行占位 / 折叠按钮 / 文字标签 / 底部选择器按钮(桌面端隐藏) */
|
||||
.flex-break { display: none; }
|
||||
.expand-btn { display: none; }
|
||||
.play-label, .expand-label { display: none; }
|
||||
.sheet-btn { display: none; }
|
||||
|
||||
/* 定时剩余时间胶囊(仅移动端定时激活时显示,桌面端看状态栏) */
|
||||
.sleep-remaining { display: none; }
|
||||
|
||||
.progress {
|
||||
display: flex;
|
||||
@@ -250,6 +265,7 @@ body {
|
||||
border-radius: 2px;
|
||||
background: rgba(94, 72, 45, 0.15);
|
||||
transition: background 0.3s ease;
|
||||
cursor: pointer; /* 点击跳到对应段开始诵读 */
|
||||
}
|
||||
.progress .dot.done { background: var(--gold); }
|
||||
.progress .dot.now { background: var(--cinnabar); }
|
||||
@@ -327,13 +343,27 @@ body {
|
||||
transition: background 0.25s ease, color 0.25s ease;
|
||||
}
|
||||
.seg:hover { background: rgba(176, 138, 79, 0.14); }
|
||||
/* 诵读中高亮:朱砂底纹 + 左侧竖条 + 下划线,长文中一眼定位 */
|
||||
.seg.playing {
|
||||
background: rgba(158, 43, 37, 0.10);
|
||||
background: rgba(158, 43, 37, 0.12);
|
||||
color: var(--cinnabar-deep);
|
||||
box-shadow: inset 0 -2px 0 var(--cinnabar);
|
||||
box-shadow:
|
||||
inset 3px 0 0 var(--cinnabar),
|
||||
inset 0 -1px 0 var(--cinnabar);
|
||||
padding-left: 8px;
|
||||
}
|
||||
.seg.done { color: var(--ink-soft); }
|
||||
|
||||
/* 逐字光标:当前段内未读字变淡、已读字回朱砂色、光标字挂金线下划 */
|
||||
.seg.playing .ch { color: rgba(92, 83, 71, 0.5); transition: color 0.15s ease; }
|
||||
.seg.playing .ch.read { color: var(--cinnabar-deep); }
|
||||
.seg.playing .ch.cur {
|
||||
color: var(--cinnabar);
|
||||
box-shadow: inset 0 -2px 0 var(--gold);
|
||||
}
|
||||
body.night .seg.playing .ch { color: rgba(176, 160, 132, 0.55); }
|
||||
body.night .seg.playing .ch.read { color: #f0c9c2; }
|
||||
|
||||
/* 竖排:从右往左逐列铺满整宽 */
|
||||
.sutra.vertical {
|
||||
writing-mode: vertical-rl;
|
||||
@@ -348,7 +378,29 @@ body {
|
||||
overflow-y: hidden;
|
||||
letter-spacing: 0.22em;
|
||||
}
|
||||
.sutra.vertical .seg.playing { box-shadow: inset 2px 0 0 var(--cinnabar); }
|
||||
.sutra.vertical .seg.playing { box-shadow: inset 0 3px 0 var(--cinnabar), inset 2px 0 0 var(--cinnabar); padding-left: 1px; }
|
||||
|
||||
/* "回到诵读处"浮动胶囊:当前段滚出视野时出现 */
|
||||
.jump-now {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
bottom: 16px;
|
||||
z-index: 10;
|
||||
font-family: inherit;
|
||||
font-size: 0.78rem;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--cinnabar);
|
||||
background: rgba(255, 252, 245, 0.95);
|
||||
border: 1px solid var(--gold);
|
||||
border-radius: 18px;
|
||||
padding: 8px 14px;
|
||||
cursor: pointer;
|
||||
box-shadow: var(--shadow);
|
||||
transition: transform 0.15s ease;
|
||||
}
|
||||
.jump-now:active { transform: scale(0.96); }
|
||||
.jump-now[hidden] { display: none; }
|
||||
body.night .jump-now { background: rgba(43, 36, 27, 0.92); }
|
||||
|
||||
/* 竖排:品名与正文恢复内联流式排布 */
|
||||
.sutra.vertical .chap {
|
||||
@@ -406,14 +458,14 @@ body {
|
||||
.subtitle { font-size: 0.78rem; letter-spacing: 0.3em; text-indent: 0.3em; margin-top: 4px; }
|
||||
|
||||
/* 控制栏:折叠为一行文字按钮(▶ 播放 长条 + 设置),点"设置"展开全部控件 */
|
||||
.player-card { padding: 8px 10px; }
|
||||
.player-row { gap: 8px; }
|
||||
.player-card { padding: 6px 8px; }
|
||||
.player-row { gap: 6px; }
|
||||
.btn-play {
|
||||
order: 1;
|
||||
flex: 1;
|
||||
width: auto;
|
||||
height: 38px;
|
||||
border-radius: 19px;
|
||||
height: 34px;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -425,12 +477,42 @@ body {
|
||||
.btn-play .play-label { display: inline; }
|
||||
.player-meta { flex: 1; min-width: 0; order: 2; }
|
||||
.flex-break { display: block; order: 3; flex-basis: 100%; height: 0; }
|
||||
.flex-break-2 { order: 6; } /* 展开态第二处换行:经典/字号 与 模式 分两行 */
|
||||
.tool-group { gap: 6px; }
|
||||
.tool-group[aria-label="经典"] { order: 4; }
|
||||
.tool-group[aria-label="字号"] { order: 5; }
|
||||
/* 经典下拉(field-pair 结构)与字号同行展开 */
|
||||
.player-row > .field-pair { order: 4; flex: 1; min-width: 0; }
|
||||
.player-row > .field-pair .sheet-btn, .player-row > .field-pair select { flex: 1; min-width: 0; }
|
||||
.tool-group[aria-label="字号"] { order: 5; flex: 1; justify-content: center; }
|
||||
.tool-group[aria-label="排版"] { order: 6; display: none; } /* 手机端不提供竖排 */
|
||||
.tool-group[aria-label="模式"] { order: 7; }
|
||||
/* 模式组:三个开关等宽一行,定时/语速两个下拉带小标签再占一行 */
|
||||
.tool-group[aria-label="模式"] { order: 7; flex: 1 1 100%; flex-wrap: wrap; }
|
||||
.tool-group[aria-label="模式"] .layout-btn { flex: 1 1 28%; text-align: center; }
|
||||
.tool-group[aria-label="模式"] .field-pair { flex: 1 1 46%; }
|
||||
.tool-group[aria-label="模式"] select { flex: 1; min-width: 0; }
|
||||
.tool-group[aria-label="模式"] .tool-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 0.68rem;
|
||||
color: var(--gold);
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
.status { display: none; } /* 手机端去掉状态文字,只留经书内容 */
|
||||
/* 定时剩余胶囊:折叠/展开态均可见,夹在播放与设置之间 */
|
||||
.sleep-remaining.show {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
order: 2;
|
||||
flex: none;
|
||||
height: 34px;
|
||||
padding: 0 11px;
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--cinnabar);
|
||||
border: 1px solid var(--gold);
|
||||
border-radius: 6px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.player-meta { order: 2; } /* 展开态音色仍排第二位 */
|
||||
|
||||
/* 设置:与播放按钮同行的文字按钮 */
|
||||
.expand-btn {
|
||||
@@ -439,15 +521,15 @@ body {
|
||||
justify-content: center;
|
||||
order: 8;
|
||||
flex: none;
|
||||
height: 38px;
|
||||
padding: 0 16px;
|
||||
height: 34px;
|
||||
padding: 0 14px;
|
||||
font-family: inherit;
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--ink-soft);
|
||||
background: transparent;
|
||||
border: 1px solid var(--hairline);
|
||||
border-radius: 19px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: color 0.15s ease, border-color 0.15s ease;
|
||||
}
|
||||
@@ -456,21 +538,24 @@ body {
|
||||
.expand-btn:hover { border-color: var(--gold); color: var(--ink); }
|
||||
.expand-btn:focus-visible { outline: 3px solid var(--gold-soft); outline-offset: 1px; }
|
||||
.expand-btn.open { color: var(--cinnabar); border-color: var(--cinnabar); }
|
||||
.player-card.expanded .expand-btn { order: 9; flex: 1 1 100%; } /* 展开后"收起"独占一行垫底 */
|
||||
|
||||
/* 折叠态:隐藏音色/经典/字号/排版控件与进度条,只留居中的播放按钮 */
|
||||
.player-card:not(.expanded) .player-meta,
|
||||
.player-card:not(.expanded) .tool-group,
|
||||
.player-card:not(.expanded) .field-pair,
|
||||
.player-card:not(.expanded) .progress,
|
||||
.player-card:not(.expanded) .flex-break { display: none; }
|
||||
.player-card:not(.expanded) .player-row { justify-content: center; }
|
||||
.player-card.expanded { padding-bottom: 12px; }
|
||||
.player-card.expanded { padding-bottom: 8px; }
|
||||
|
||||
.voice-label, .tool-name { display: none; }
|
||||
#voice-select { min-width: 0; width: 100%; }
|
||||
.voice-label { display: none; }
|
||||
.tool-name { display: none; } /* 默认隐藏小标签;模式组内定时/语速需要标签才可读 */
|
||||
#voice-select { min-width: 0; width: 100%; padding: 5px 30px 5px 10px; font-size: 0.82rem; }
|
||||
|
||||
/* 触屏目标 ≥36px,紧凑以容纳一行 */
|
||||
.size-btn { width: 36px; height: 36px; font-size: 0.85rem; }
|
||||
.layout-btn, .sutra-btn, .pager-btn { padding: 8px 10px; font-size: 0.82rem; min-height: 36px; }
|
||||
/* 触屏控件紧凑化 */
|
||||
.size-btn { width: 34px; height: 30px; font-size: 0.8rem; }
|
||||
.layout-btn, .sutra-btn, .pager-btn { padding: 5px 8px; font-size: 0.78rem; min-height: 30px; }
|
||||
|
||||
.sutra-paper { padding: 18px 16px; }
|
||||
.sutra-paper::before { inset: 6px; }
|
||||
@@ -483,4 +568,148 @@ body {
|
||||
.pager { gap: 12px; }
|
||||
.page-info { min-width: 0; font-size: 0.8rem; }
|
||||
.site-footer { padding: 8px 14px; font-size: 0.7rem; }
|
||||
#timer-select, #rate-select { min-height: 30px; padding-top: 4px; padding-bottom: 4px; font-size: 0.78rem; }
|
||||
/* 原生下拉隐藏,改由底部升起面板选择 */
|
||||
#voice-select, #timer-select, #rate-select, #sutra-select { display: none; }
|
||||
.sheet-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 6px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 30px;
|
||||
font-family: inherit;
|
||||
font-size: 0.78rem;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--ink-soft);
|
||||
background: transparent;
|
||||
border: 1px solid var(--hairline);
|
||||
border-radius: 6px;
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.sheet-btn::after {
|
||||
content: "▾";
|
||||
flex: none;
|
||||
font-size: 0.7rem;
|
||||
color: var(--gold);
|
||||
}
|
||||
.sheet-btn-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.progress { margin-top: 6px; }
|
||||
}
|
||||
|
||||
/* ===== 夜间模式:暗色宣纸底,整体降明度保留朱砂鎏金气质 ===== */
|
||||
body.night {
|
||||
--paper: #211c16;
|
||||
--paper-deep: #1a1611;
|
||||
--ink: #e8dcc2;
|
||||
--ink-soft: #b0a084;
|
||||
--cinnabar: #c96a5c;
|
||||
--cinnabar-deep: #a04a3e;
|
||||
--gold: #b9975f;
|
||||
--gold-soft: #5f4e30;
|
||||
--hairline: rgba(190, 158, 105, 0.28);
|
||||
--shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
|
||||
background:
|
||||
radial-gradient(1200px 600px at 50% -8%, rgba(201, 106, 92, 0.06), transparent 60%),
|
||||
radial-gradient(900px 500px at 85% 110%, rgba(185, 151, 95, 0.08), transparent 55%),
|
||||
repeating-linear-gradient(0deg, transparent 0 26px, rgba(190, 158, 105, 0.02) 26px 27px),
|
||||
#211c16;
|
||||
}
|
||||
body.night .panel { background: rgba(43, 36, 27, 0.85); }
|
||||
body.night .sutra-paper {
|
||||
background:
|
||||
radial-gradient(800px 400px at 50% 0%, rgba(185, 151, 95, 0.05), transparent 60%),
|
||||
#292219;
|
||||
}
|
||||
body.night .sutra-paper::before { border-color: rgba(185, 151, 95, 0.22); }
|
||||
body.night .sutra-paper::after { border-color: rgba(185, 151, 95, 0.11); }
|
||||
body.night #voice-select { background-color: rgba(43, 36, 27, 0.9); color: var(--ink); }
|
||||
body.night #timer-select, body.night #rate-select, body.night #sutra-select { background-color: rgba(43, 36, 27, 0.6); }
|
||||
body.night .progress .dot { background: rgba(190, 158, 105, 0.2); }
|
||||
body.night .seg:hover { background: rgba(185, 151, 95, 0.12); }
|
||||
body.night .seg.playing { background: rgba(201, 106, 92, 0.16); color: #f0c9c2; }
|
||||
|
||||
/* ===== 底部升起选择面板(移动端) ===== */
|
||||
.sheet-mask {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 80;
|
||||
background: rgba(26, 20, 12, 0.45);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
.sheet-mask.open { opacity: 1; }
|
||||
.sheet-panel {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 81;
|
||||
background: var(--paper);
|
||||
border-radius: 16px 16px 0 0;
|
||||
box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.18);
|
||||
padding: 8px 0 max(14px, env(safe-area-inset-bottom));
|
||||
transform: translateY(105%);
|
||||
transition: transform 0.26s cubic-bezier(0.2, 0.8, 0.3, 1);
|
||||
max-height: 62vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.sheet-panel.open { transform: translateY(0); }
|
||||
.sheet-grabber {
|
||||
width: 36px;
|
||||
height: 4px;
|
||||
border-radius: 2px;
|
||||
background: var(--hairline);
|
||||
margin: 2px auto 8px;
|
||||
flex: none;
|
||||
}
|
||||
.sheet-title {
|
||||
text-align: center;
|
||||
font-size: 0.78rem;
|
||||
color: var(--ink-soft);
|
||||
letter-spacing: 0.24em;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid var(--hairline);
|
||||
flex: none;
|
||||
}
|
||||
.sheet-opts { overflow-y: auto; }
|
||||
.sheet-opt {
|
||||
padding: 13px 16px;
|
||||
text-align: center;
|
||||
font-size: 1rem;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--ink);
|
||||
border-bottom: 1px solid var(--hairline);
|
||||
cursor: pointer;
|
||||
}
|
||||
.sheet-opt:last-child { border-bottom: none; }
|
||||
/* 面板内的 optgroup 分组标题(佛经 / 道家) */
|
||||
.sheet-group {
|
||||
padding: 10px 16px 4px;
|
||||
text-align: center;
|
||||
font-size: 0.72rem;
|
||||
letter-spacing: 0.3em;
|
||||
color: var(--gold);
|
||||
}
|
||||
.sheet-opt.sel { color: var(--cinnabar); font-weight: 700; }
|
||||
.sheet-opt:active { background: rgba(176, 138, 79, 0.12); }
|
||||
.sheet-cancel {
|
||||
margin: 8px 16px 0;
|
||||
padding: 11px;
|
||||
text-align: center;
|
||||
font-size: 0.85rem;
|
||||
letter-spacing: 0.2em;
|
||||
color: var(--ink-soft);
|
||||
border: 1px solid var(--hairline);
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
flex: none;
|
||||
}
|
||||
.sheet-cancel:active { background: rgba(176, 138, 79, 0.12); }
|
||||
|
||||
Reference in New Issue
Block a user