diff --git a/app.js b/app.js index b1f100e..1d0a1c1 100644 --- a/app.js +++ b/app.js @@ -10,6 +10,7 @@ const SUTRAS = [ name: "心经", title: "般若波罗蜜多心经", seal: "心经", + cat: "佛经", translator: "唐三藏法师 玄奘 译", source: "故宫博物院", sourceUrl: "https://www.dpm.org.cn/lemmas/243222.html", @@ -34,6 +35,7 @@ const SUTRAS = [ name: "金刚经", title: "金刚般若波罗蜜经", seal: "金刚经", + cat: "佛经", translator: "姚秦三藏法师 鸠摩罗什 译", source: "维基文库 · 大正新修大藏经", sourceUrl: "https://zh.wikisource.org/wiki/金剛般若波羅蜜經_(鳩摩羅什)", @@ -77,6 +79,208 @@ const SUTRAS = [ { h: "应化非真分第三十二", g: "应现设化,亦非真实", t: "“须菩提!若有人以满无量阿僧祇世界七宝,持用布施;若有善男子、善女人,发菩提心者,持于此经,乃至四句偈等,受持读诵,为人演说,其福胜彼。云何为人演说?不取于相,如如不动。何以故?一切有为法,如梦幻泡影,如露亦如电,应作如是观。”佛说是经已,长老须菩提及诸比丘、比丘尼、优婆塞、优婆夷、一切世间天、人、阿修罗,闻佛所说,皆大欢喜,信受奉行。" }, ], }, + { + id: "amitabha", + name: "阿弥陀经", + title: "佛说阿弥陀经", + seal: "弥陀经", + cat: "佛经", + translator: "姚秦三藏法师 鸠摩罗什 译", + source: "维基文库 · 大正新修大藏经", + sourceUrl: "https://zh.wikisource.org/wiki/佛說阿彌陀經", + pageSize: 5, // 每页 5 段 + segments: [ + { t: "如是我闻:一时,佛在舍卫国衹树给孤独园,与大比丘僧千二百五十人俱,皆是大阿罗汉,众所知识。长老舍利弗、摩诃目犍连、摩诃迦叶、摩诃迦旃延、摩诃俱𫄨罗、离婆多、周利槃陀伽、难陀、阿难陀、罗睺罗、憍梵波提、宾头卢颇罗堕、迦留陀夷、摩诃劫宾那、薄拘罗、阿那楼驮,如是等诸大弟子。并诸菩萨摩诃萨——文殊师利法王子、阿逸多菩萨、干陀诃提菩萨、常精进菩萨——与如是等诸大菩萨,及释提桓因等无量诸天大众俱。" }, + { t: "尔时,佛告长老舍利弗:「从是西方过十万亿佛土,有世界名曰极乐。其土有佛,号阿弥陀,今现在说法。舍利弗!彼土何故名为极乐?其国众生无有众苦,但受诸乐,故名极乐。又舍利弗!极乐国土,七重栏楯、七重罗网、七重行树,皆是四宝周匝围绕,是故彼国名为极乐。" }, + { t: "又舍利弗!极乐国土有七宝池,八功德水充满其中,池底纯以金沙布地。四边阶道,金、银、琉璃、玻璃合成;上有楼阁,亦以金、银、琉璃、玻璃、砗磲、赤珠、玛瑙而严饰之。池中莲华,大如车轮,青色青光,黄色黄光,赤色赤光,白色白光,微妙香洁。舍利弗!极乐国土成就如是功德庄严。" }, + { t: "又舍利弗!彼佛国土,常作天乐,黄金为地,昼夜六时,雨天曼陀罗华。其土众生,常以清旦,各以衣裓盛众妙华,供养他方十万亿佛;即以食时,还到本国,饭食经行。舍利弗!极乐国土成就如是功德庄严。" }, + { t: "复次舍利弗!彼国常有种种奇妙杂色之鸟——白鹤、孔雀、鹦鹉、舍利、迦陵频伽、共命之鸟。是诸众鸟,昼夜六时出和雅音,其音演畅五根、五力、七菩提分、八圣道分如是等法。其土众生闻是音已,皆悉念佛、念法、念僧。舍利弗!汝勿谓此鸟实是罪报所生。所以者何?彼佛国土无三恶道。舍利弗!其佛国土尚无三恶道之名,何况有实?是诸众鸟,皆是阿弥陀佛欲令法音宣流,变化所作。舍利弗!彼佛国土微风吹动,诸宝行树及宝罗网出微妙音,譬如百千种乐同时俱作,闻是音者,自然皆生念佛念法念僧之心。舍利弗!其佛国土成就如是功德庄严。" }, + { t: "舍利弗,于汝意云何?彼佛何故号阿弥陀?舍利弗!彼佛光明无量,照十方国无所障碍,是故号为阿弥陀。又舍利弗!彼佛寿命及其人民,无量无边阿僧祇劫,故名阿弥陀。舍利弗!阿弥陀佛成佛以来,于今十劫。又舍利弗!彼佛有无量无边声闻弟子,皆阿罗汉,非是算数之所能知;诸菩萨众,亦复如是。舍利弗!彼佛国土成就如是功德庄严。" }, + { t: "又舍利弗!极乐国土众生生者,皆是阿鞞跋致。其中多有一生补处,其数甚多,非是算数所能知之,但可以无量无边阿僧祇说。舍利弗!众生闻者,应当发愿,愿生彼国。所以者何?得与如是诸上善人俱会一处。" }, + { t: "舍利弗!不可以少善根福德因缘得生彼国。舍利弗!若有善男子、善女人,闻说阿弥陀佛,执持名号,若一日、若二日、若三日、若四日、若五日、若六日、若七日,一心不乱。其人临命终时,阿弥陀佛与诸圣众,现在其前。是人终时,心不颠倒,即得往生阿弥陀佛极乐国土。舍利弗!我见是利,故说此言。若有众生闻是说者,应当发愿生彼国土。" }, + { t: "舍利弗!如我今者,赞叹阿弥陀佛不可思议功德之利;东方亦有阿閦鞞佛、须弥相佛、大须弥佛、须弥光佛、妙音佛,如是等恒河沙数诸佛,各于其国出广长舌相,遍覆三千大千世界,说诚实言:『汝等众生,当信是称赞不可思议功德一切诸佛所护念经。』" }, + { t: "舍利弗!南方世界有日月灯佛、名闻光佛、大燄肩佛、须弥灯佛、无量精进佛,如是等恒河沙数诸佛,各于其国出广长舌相,遍覆三千大千世界,说诚实言:『汝等众生,当信是称赞不可思议功德一切诸佛所护念经。』" }, + { t: "舍利弗!西方世界有无量寿佛、无量相佛、无量幢佛、大光佛、大明佛、宝相佛、净光佛,如是等恒河沙数诸佛,各于其国出广长舌相,遍覆三千大千世界,说诚实言:『汝等众生,当信是称赞不可思议功德一切诸佛所护念经。』" }, + { t: "舍利弗!北方世界有燄肩佛、最胜音佛、难沮佛、日生佛、网明佛,如是等恒河沙数诸佛,各于其国出广长舌相,遍覆三千大千世界,说诚实言:『汝等众生,当信是称赞不可思议功德一切诸佛所护念经。』" }, + { t: "舍利弗!下方世界有师子佛、名闻佛、名光佛、达摩佛、法幢佛、持法佛,如是等恒河沙数诸佛,各于其国出广长舌相,遍覆三千大千世界,说诚实言:『汝等众生,当信是称赞不可思议功德一切诸佛所护念经。』" }, + { t: "舍利弗!上方世界有梵音佛、宿王佛、香上佛、香光佛、大燄肩佛、杂色宝华严身佛、娑罗树王佛、宝华德佛、见一切义佛、如须弥山佛,如是等恒河沙数诸佛,各于其国出广长舌相,遍覆三千大千世界,说诚实言:『汝等众生,当信是称赞不可思议功德一切诸佛所护念经。』" }, + { t: "舍利弗!于汝意云何?何故名为『一切诸佛所护念经』?舍利弗!若有善男子、善女人,闻是经受持者,及闻诸佛名者;是诸善男子、善女人,皆为一切诸佛之所护念,皆得不退转于阿耨多罗三藐三菩提。是故舍利弗!汝等皆当信受我语及诸佛所说。" }, + { t: "舍利弗!若有人已发愿、今发愿、当发愿,欲生阿弥陀佛国者;是诸人等,皆得不退转于阿耨多罗三藐三菩提,于彼国土若已生、若今生、若当生。是故舍利弗!诸善男子、善女人若有信者,应当发愿生彼国土。" }, + { t: "舍利弗!如我今者,称赞诸佛不可思议功德;彼诸佛等,亦称赞我不可思议功德,而作是言:『释迦牟尼佛能为甚难希有之事,能于娑婆国土五浊恶世,劫浊、见浊、烦恼浊、众生浊、命浊中,得阿耨多罗三藐三菩提,为诸众生说是一切世间难信之法。』舍利弗!当知我于五浊恶世行此难事,得阿耨多罗三藐三菩提,为一切世间说此难信之法,是为甚难!」" }, + { t: "佛说此经已,舍利弗及诸比丘,一切世间天、人、阿修罗等,闻佛所说,欢喜信受,作礼而去。" }, + ], + }, + { + id: "pumen", + name: "普门品", + title: "观世音菩萨普门品", + seal: "普门品", + cat: "佛经", + translator: "姚秦鸠摩罗什 译长行 · 隋阇那崛多 译重颂", + source: "维基文库 · 大正新修大藏经", + sourceUrl: "https://zh.wikisource.org/wiki/妙法蓮華經觀世音菩薩普門品經", + pageSize: 6, // 每页 6 段 + segments: [ + { h: "妙法莲华经观世音菩萨普门品第二十五", g: "姚秦鸠摩罗什译长行 · 隋阇那崛多译重颂", t: "尔时,无尽意菩萨即从座起,偏袒右肩,合掌向佛,而作是言:「世尊!观世音菩萨,以何因缘名观世音?」" }, + { t: "佛告无尽意菩萨:「善男子!若有无量百千万亿众生受诸苦恼,闻是观世音菩萨,一心称名,观世音菩萨即时观其音声,皆得解脱。若有持是观世音菩萨名者,设入大火,火不能烧,由是菩萨威神力故。若为大水所漂,称其名号,即得浅处。若有百千万亿众生,为求金、银、琉璃、砗磲、玛瑙、珊瑚、琥珀、真珠等宝,入于大海,假使黑风吹其船舫,飘堕罗刹鬼国,其中若有,乃至一人,称观世音菩萨名者,是诸人等皆得解脱罗刹之难。以是因缘,名观世音。若复有人临当被害,称观世音菩萨名者,彼所执刀杖寻段段坏,而得解脱。若三千大千国土,满中夜叉、罗刹,欲来恼人,闻其称观世音菩萨名者,是诸恶鬼,尚不能以恶眼视之,况复加害。设复有人,若有罪、若无罪,杻械、枷锁检系其身,称观世音菩萨名者,皆悉断坏,即得解脱。若三千大千国土,满中怨贼,有一商主,将诸商人,赍持重宝、经过险路,其中一人作是唱言:『诸善男子!勿得恐怖,汝等应当一心称观世音菩萨名号。是菩萨能以无畏施于众生,汝等若称名者,于此怨贼当得解脱。』众商人闻,俱发声言:『南无观世音菩萨。』称其名故,即得解脱。" }, + { t: "「无尽意!观世音菩萨摩诃萨,威神之力巍巍如是。若有众生多于淫欲,常念恭敬观世音菩萨,便得离欲。若多瞋恚,常念恭敬观世音菩萨,便得离瞋。若多愚痴,常念恭敬观世音菩萨,便得离痴。" }, + { t: "「无尽意!观世音菩萨有如是等大威神力,多所饶益,是故众生常应心念。若有女人,设欲求男,礼拜供养观世音菩萨,便生福德智慧之男;设欲求女,便生端正有相之女。宿殖德本,众人爱敬。" }, + { t: "「无尽意!观世音菩萨有如是力,若有众生,恭敬礼拜观世音菩萨,福不唐捐,是故众生皆应受持观世音菩萨名号。" }, + { t: "「无尽意!若有人受持六十二亿恒河沙菩萨名字,复尽形供养饮食、衣服、卧具、医药。于汝意云何?是善男子、善女人,功德多不?」" }, + { t: "无尽意言:「甚多,世尊!」" }, + { t: "佛言:「若复有人受持观世音菩萨名号,乃至一时礼拜、供养,是二人福,正等无异,于百千万亿劫不可穷尽。无尽意!受持观世音菩萨名号,得如是无量无边福德之利。」" }, + { t: "无尽意菩萨白佛言:「世尊!观世音菩萨,云何游此娑婆世界?云何而为众生说法?方便之力,其事云何?」" }, + { t: "佛告无尽意菩萨:「善男子!若有国土众生,应以佛身得度者,观世音菩萨即现佛身而为说法;应以辟支佛身得度者,即现辟支佛身而为说法;应以声闻身得度者,即现声闻身而为说法;应以梵王身得度者,即现梵王身而为说法;应以帝释身得度者,即现帝释身而为说法;应以自在天身得度者,即现自在天身而为说法;应以大自在天身得度者,即现大自在天身而为说法;应以天大将军身得度者,即现天大将军身而为说法;应以毗沙门身得度者,即现毗沙门身而为说法;应以小王身得度者,即现小王身而为说法;应以长者身得度者,即现长者身而为说法;应以居士身得度者,即现居士身而为说法;应以宰官身得度者,即现宰官身而为说法;应以婆罗门身得度者,即现婆罗门身而为说法;应以比丘、比丘尼、优婆塞、优婆夷身得度者,即现比丘、比丘尼、优婆塞、优婆夷身而为说法;应以长者、居士、宰官、婆罗门妇女身得度者,即现妇女身而为说法;应以童男、童女身得度者,即现童男、童女身而为说法;应以天、龙、夜叉、干闼婆、阿修罗、迦楼罗、紧那罗、摩睺罗伽、人非人等身得度者,即皆现之而为说法;应以执金刚身得度者,即现执金刚身而为说法。" }, + { t: "「无尽意!是观世音菩萨成就如是功德,以种种形,游诸国土,度脱众生。是故汝等,应当一心供养观世音菩萨。是观世音菩萨摩诃萨,于怖畏急难之中能施无畏,是故此娑婆世界,皆号之为施无畏者。」" }, + { t: "无尽意菩萨白佛言:「世尊!我今当供养观世音菩萨。」即解颈众宝珠、璎珞,价值百千两金,而以与之,作是言:「仁者!受此法施珍宝璎珞。」时观世音菩萨不肯受之。无尽意复白观世音菩萨言:「仁者!愍我等故,受此璎珞。」" }, + { t: "尔时佛告观世音菩萨:「当愍此无尽意菩萨及四众,天、龙、夜叉、干闼婆、阿修罗、迦楼罗、紧那罗、摩睺罗伽、人非人等故,受是璎珞。」" }, + { t: "即时观世音菩萨愍诸四众,及于天、龙、人非人等,受其璎珞,分作二分,一分奉释迦牟尼佛,一分奉多宝佛塔。" }, + { t: "「无尽意,观世音菩萨有如是自在神力,游于娑婆世界。」" }, + { t: "尔时无尽意菩萨以偈问曰:" }, + { t: "世尊妙相具,我今重问彼,佛子何因缘,名为观世音。具足妙相尊,偈答无尽意。汝听观音行,善应诸方所,弘誓深如海,历劫不思议,侍多千亿佛,发大清净愿。我为汝略说,闻名及见身,心念不空过,能灭诸有苦。假使兴害意,推落大火坑,念彼观音力,火坑变成池。或漂流巨海,龙鱼诸鬼难,念彼观音力,波浪不能没。或在须弥峰、为人所推堕,念彼观音力,如日虚空住。或被恶人逐,堕落金刚山,念彼观音力,不能损一毛。或值怨贼绕,各执刀加害,念彼观音力,咸即起慈心。或遭王难苦,临刑欲寿终,念彼观音力,刀寻段段坏。或囚禁枷锁,手足被杻械,念彼观音力,释然得解脱。咒诅诸毒药、所欲害身者,念彼观音力,还著于本人。或遇恶罗刹、毒龙诸鬼等,念彼观音力,时悉不敢害。若恶兽围绕,利牙爪可怖,念彼观音力,疾走无边方。蚖蛇及蝮蝎,气毒烟火燃,念彼观音力,寻声自回去。云雷鼓掣电,降雹澍大雨,念彼观音力,应时得消散。" }, + { t: "众生被困厄,无量苦逼身,观音妙智力,能救世间苦。具足神通力,广修智方便,十方诸国土,无刹不现身。种种诸恶趣,地狱鬼畜生,生老病死苦,以渐悉令灭。真观清净观,广大智慧观,悲观及慈观,常愿常瞻仰。无垢清净光、慧日破诸暗,能伏灾风火,普明照世间。悲体戒雷震,慈意妙大云,澍甘露法雨,灭除烦恼焰。诤讼经官处,怖畏军阵中,念彼观音力,众怨悉退散。妙音观世音、梵音海潮音,胜彼世间音,是故须常念,念念勿生疑。观世音净圣,于苦恼死厄、能为作依怙。具一切功德,慈眼视众生,福聚海无量,是故应顶礼。" }, + { t: "尔时持地菩萨即从座起,前白佛言:「世尊!若有众生,闻是观世音菩萨品自在之业,普门示现神通力者,当知是人功德不少。」" }, + { t: "佛说是普门品时,众中八万四千众生,皆发无等等阿耨多罗三藐三菩提心。" }, + ], + }, + { + id: "daodejing", + name: "道德经", + title: "道德经", + seal: "道德经", + cat: "道家", + translator: "春秋 · 老子 著(王弼本)", + source: "维基文库", + sourceUrl: "https://zh.wikisource.org/wiki/道德經_(王弼本)", + pageSize: 9, // 81 章,每页 9 章 + segments: [ + { h: "第一章", t: "道可道,非常道,名可名,非常名;无名,天地之始,有名,万物之母。故常无欲,以观其妙,常有欲,以观其徼;此两者,同出而异名,同谓之玄。玄之又玄,众妙之门。" }, + { h: "第二章", t: "天下皆知美之为美,斯恶已。皆知善之为善,斯不善已。故有无相生,难易相成,长短相较,高下相倾,音声相和,前后相随。是以圣人处无为之事,行不言之教;万物作焉而不辞,生而不有,为而不恃,功成而弗居。夫唯弗居,是以不去。" }, + { h: "第三章", t: "不尚贤,使民不争;不贵难得之货,使民不为盗;不见可欲,使民心不乱。是以圣人之治,虚其心,实其腹,弱其志,强其骨。常使民无知无欲。使夫智者不敢为也。为无为,则无不治。" }, + { h: "第四章", t: "道冲而用之或不盈,渊兮似万物之宗;挫其锐,解其纷,和其光,同其尘,湛兮似或存。吾不知谁之子,象帝之先。" }, + { h: "第五章", t: "天地不仁,以万物为刍狗;圣人不仁,以百姓为刍狗。天地之间,其犹橐籥乎?虚而不屈,动而愈出。多言数穷,不如守中。" }, + { h: "第六章", t: "谷神不死,是谓玄牝。玄牝之门,是谓天地根。緜緜若存,用之不勤。" }, + { h: "第七章", t: "天长地久。天地所以能长且久者,以其不自生,故能长生。是以圣人后其身而身先;外其身而身存。非以其无私邪,故能成其私。" }, + { h: "第八章", t: "上善若水。水善利万物而不争,处众人之所恶,故几于道。居善地,心善渊,与善仁,言善信,正善治,事善能,动善时。夫唯不争,故无尤。" }, + { h: "第九章", t: "持而盈之,不如其已;揣而棁之,不可长保。金玉满堂,莫之能守;富贵而骄,自遗其咎。功遂身退,天之道。" }, + { h: "第十章", t: "载营魄抱一,能无离乎?专气致柔,能婴儿乎?涤除玄览,能无疵乎?爱国治民,能无知乎?天门开阖,能为雌乎?明白四达,能无为乎?生之,畜之。生而不有,为而不恃,长而不宰,是谓玄德。" }, + { h: "第十一章", t: "三十辐共一毂,当其无,有车之用。埏埴以为器,当其无,有器之用。凿户牖以为室,当其无,有室之用。故有之以为利,无之以为用。" }, + { h: "第十二章", t: "五色令人目盲,五音令人耳聋,五味令人口爽,驰骋畋猎令人心发狂,难得之货令人行妨。是以圣人为腹不为目,故去彼取此。" }, + { h: "第十三章", t: "宠辱若惊,贵大患若身。何谓宠辱若惊?宠为下,得之若惊,失之若惊,是谓宠辱若惊。何谓贵大患若身?吾所以有大患者,为吾有身,及吾无身,吾有何患?故贵以身为天下,若可寄天下;爱以身为天下,若可托天下。" }, + { h: "第十四章", t: "视之不见名曰夷,听之不闻名曰希,搏之不得名曰微。此三者,不可致诘,故混而为一。其上不皦,其下不昧。绳绳不可名,复归于无物。是谓无状之状,无物之象,是谓惚恍。迎之不见其首,随之不见其后。执古之道,以御今之有。能知古始,是谓道纪。" }, + { h: "第十五章", t: "古之善为士者,微妙玄通,深不可识。夫唯不可识,故强为之容。豫焉若冬涉川,犹兮若畏四邻,俨兮其若容,涣兮若冰之将释,敦兮其若朴,旷兮其若谷,混兮其若浊。孰能浊以静之徐清?孰能安以久动之徐生?保此道者不欲盈,夫唯不盈,故能蔽不新成。" }, + { h: "第十六章", t: "致虚极,守静笃。万物并作,吾以观复。夫物芸芸,各复归其根。归根曰静,是谓复命。复命曰常,知常曰明。不知常,妄作凶。知常容,容乃公,公乃全,全乃天,天乃道,道乃久,没身不殆。" }, + { h: "第十七章", t: "太上,下知有之,其次,亲而誉之,其次,畏之,其次,侮之。信不足焉,有不信焉。悠兮其贵言,功成事遂,百姓皆谓:我自然。" }, + { h: "第十八章", t: "大道废,有仁义;智慧出,有大伪;六亲不和,有孝慈;国家昏乱,有忠臣。" }, + { h: "第十九章", t: "绝圣弃智,民利百倍;绝仁弃义,民复孝慈;绝巧弃利,盗贼无有。此三者以为文不足,故令有所属﹕见素抱朴,少私寡欲。" }, + { h: "第二十章", t: "绝学无忧,唯之与阿,相去几何?善之与恶,相去若何?人之所畏,不可不畏。荒兮其未央哉﹗众人熙熙,如享太牢,如春登台。我独泊兮其未兆,如婴儿之未孩;儽儽兮若无所归。众人皆有余,而我独若遗。我愚人之心也哉﹗沌沌兮,俗人昭昭,我独昏昏。俗人察察,我独闷闷。澹兮其若海,飂兮若无止。众人皆有以,而我独顽似鄙。我独异于人,而贵食母。" }, + { h: "第二十一章", t: "孔德之容,惟道是从。道之为物,惟恍惟惚。惚兮恍兮,其中有象;恍兮惚兮,其中有物。窈兮冥兮,其中有精;其精甚真,其中有信。自今及古,其名不去,以阅众甫。吾何以知众甫之状哉?以此。" }, + { h: "第二十二章", t: "曲则全,枉则直,洼则盈,敝则新,少则得,多则惑。是以圣人抱一为天下式。不自见故明,不自是故彰,不自伐故有功,不自矜故长。夫唯不争,故天下莫能与之争。古之所谓曲则全者,岂虚言哉!诚全而归之。" }, + { h: "第二十三章", t: "希言自然。故飘风不终朝,骤雨不终日。孰为此者?天地。天地尚不能久,而况于人乎?故从事于道者,道者同于道,德者同于德,失者同于失。同于道者,道亦乐得之;同于德者,德亦乐得之;同于失者,失亦乐得之。信不足焉,有不信焉。" }, + { h: "第二十四章", t: "企者不立,跨者不行,自见者不明,自是者不彰,自伐者无功,自矜者不长。其在道也,曰余食赘行。物或恶之,故有道者不处。" }, + { h: "第二十五章", t: "有物混成,先天地生。寂兮寥兮,独立而不改,周行而不殆,可以为天下母。吾不知其名,字之曰道,强为之名曰大。大曰逝,逝曰远,远曰反。故道大,天大,地大,王亦大。域中有四大,而王居其一焉。人法地,地法天,天法道,道法自然。" }, + { h: "第二十六章", t: "重为轻根,静为躁君。是以圣人终日行不离辎重。虽有荣观,燕处超然。奈何万乘之主,而以身轻天下?轻则失本,躁则失君。" }, + { h: "第二十七章", t: "善行无辙迹,善言无瑕讁;善数不用筹策;善闭无关楗而不可开,善结无绳约而不可解。是以圣人常善救人,故无弃人;常善救物,故无弃物,是谓袭明。故善人者,不善人之师;不善人者,善人之资。不贵其师,不爱其资,虽智大迷,是谓要妙。" }, + { h: "第二十八章", t: "知其雄,守其雌,为天下谿。为天下谿,常德不离,复归于婴儿。知其白,守其黑,为天下式。为天下式,常德不忒,复归于无极。知其荣,守其辱,为天下谷,常德乃足,复归于朴。朴散则为器,圣人用之,则为官长,故大制不割。" }, + { h: "第二十九章", t: "将欲取天下而为之,吾见其不得已。天下神器,不可为也,为者败之,执者失之。故物或行或随,或歔或吹。或强或羸,或挫或隳。是以圣人去甚,去奢,去泰。" }, + { h: "第三十章", t: "以道佐人主者,不以兵强天下。其事好还。师之所处,荆棘生焉。大军之后,必有凶年。善有果而已,不敢以取强。果而勿矜,果而勿伐,果而勿骄。果而不得已,果而勿强。物壮则老,是谓不道,不道早已。" }, + { h: "第三十一章", t: "夫佳兵者,不祥之器,物或恶之,故有道者不处。君子居则贵左,用兵则贵右。兵者不祥之器,非君子之器,不得已而用之,恬淡为上。胜而不美,而美之者,是乐杀人。夫乐杀人者,则不可以得志于天下矣。吉事尚左,凶事尚右。偏将军居左,上将军居右,言以丧礼处之。杀人之众,以哀悲泣之,战胜,以丧礼处之。" }, + { h: "第三十二章", t: "道常无名,朴虽小,天下莫能臣也。侯王若能守之,万物将自宾。天地相合,以降甘露,民莫之令而自均。始制有名,名亦既有,夫亦将知止,知止所以不殆。譬道之在天下,犹川谷之于江海。" }, + { h: "第三十三章", t: "知人者智,自知者明。胜人者有力,自胜者强。知足者富。强行者有志。不失其所者久。死而不亡者寿。" }, + { h: "第三十四章", t: "大道泛兮,其可左右。万物恃之而生而不辞,功成不名有。衣养万物而不为主,常无欲,可名于小;万物归焉而不为主,可名为大。以其终不自为大,故能成其大。" }, + { h: "第三十五章", t: "执大象,天下往。往而不害,安平太。乐与饵,过客止。道之出口,淡乎其无味,视之不足见,听之不足闻,用之不足既。" }, + { h: "第三十六章", t: "将欲歙之,必固张之;将欲弱之,必固强之;将欲废之,必固兴之;将欲夺之,必固与之。是谓微明。柔弱胜刚强。鱼不可脱于渊,国之利器不可以示人。" }, + { h: "第三十七章", t: "道常无为而无不为。侯王若能守之,万物将自化。化而欲作,吾将镇之以无名之朴。无名之朴,夫亦将无欲。不欲以静,天下将自定。# 老子《道德经》下篇" }, + { h: "第三十八章", t: "上德不德,是以有德;下德不失德,是以无德。上德无为而无以为,下德为之而有以为。上仁为之而无以为,上义为之而有以为。上礼为之而莫之应,则攘臂而扔之。故失道而后德,失德而后仁,失仁而后义,失义而后礼。夫礼者,忠信之薄,而乱之首。前识者,道之华,而愚之始。是以大丈夫处其厚,不居其薄;处其实,不居其华。故去彼取此。" }, + { h: "第三十九章", t: "昔之得一者,天得一以清,地得一以宁,神得一以灵,谷得一以盈,万物得一以生,侯王得一以为天下贞。其致之,天无以清将恐裂,地无以宁将恐发,神无以灵将恐歇,谷无以盈将恐竭,万物无以生将恐灭,侯王无以贵高将恐蹶。故贵以贱为本,高以下为基。是以侯王自称孤﹑寡﹑不谷。此非以贱为本邪?非乎?故致数舆无舆,不欲琭琭如玉,珞珞如石。" }, + { h: "第四十章", t: "反者道之动,弱者道之用。天下万物生于有,有生于无。" }, + { h: "第四十一章", t: "上士闻道,勤而行之;中士闻道,若存若亡;下士闻道,大笑之。不笑不足以为道。故建言有之﹕明道若昧,进道若退,夷道若颣,上德若谷,大白若辱,广德若不足,建德若偷,质真若渝,大方无隅,大器晚成,大音希声,大象无形,道隐无名。夫唯道,善贷且成。" }, + { h: "第四十二章", t: "道生一,一生二,二生三,三生万物。万物负阴而抱阳,冲气以为和。人之所恶,唯孤﹑寡﹑不谷,而王公以为称。故物或损之而益,或益之而损。人之所教,我亦教之。强梁者不得其死,吾将以为教父。" }, + { h: "第四十三章", t: "天下之至柔,驰骋天下之至坚。无有入无间,吾是以知无为之有益。不言之教,无为之益,天下希及之。" }, + { h: "第四十四章", t: "名与身孰亲?身与货孰多?得与亡孰病?是故甚爱必大费,多藏必厚亡,知足不辱,知止不殆,可以长久。" }, + { h: "第四十五章", t: "大成若缺,其用不弊。大盈若冲,其用不穷。大直若屈,其用不居。大巧若拙,其用不辍。大辩若讷,其用不差。躁胜寒,静胜热。清静为天下正。" }, + { h: "第四十六章", t: "天下有道,却走马以粪。天下无道,戎马生于郊。祸莫大于不知足;咎莫大于欲得。故知足之足,常足矣。" }, + { h: "第四十七章", t: "不出户,知天下;不闚牖,见天道。其出弥远,其知弥少。是以圣人不行而知,不见而名,不为而成。" }, + { h: "第四十八章", t: "为学日益,为道日损。损之又损,以至于无为。无为而无不为。取天下常以无事,及其有事,不足以取天下。" }, + { h: "第四十九章", t: "圣人无常心,以百姓心为心。善者,吾善之;不善者,吾亦善之,德善。信者,吾信之;不信者,吾亦信之,德信。圣人在天下歙歙,为天下浑其心,百姓皆注其耳目, 圣人皆孩之。" }, + { h: "第五十章", t: "出生入死。生之徒,十有三;死之徒,十有三;人之生,动之死地,亦十有三。夫何故?以其生生之厚。盖闻善摄生者,陆行不遇兕虎,入军不被甲兵;兕无所投其角,虎无所措其爪,兵无所容其刃。夫何故?以其无死地。" }, + { h: "第五十一章", t: "道生之,德畜之,物形之,势成之。是以万物莫不尊道而贵德。道之尊,德之贵,夫莫之命而常自然。故道生之,德畜之。长之育之,亭之毒之,盖之覆之。生而不有,为而不恃,长而不宰。是谓玄德。" }, + { h: "第五十二章", t: "天下有始,以为天下母。既得其母,以知其子,既知其子,复守其母,没身不殆。塞其兑,闭其门,终身不勤。开其兑,济其事,终身不救。见小曰明,守柔曰强。用其光,复归其明,无遗身殃,是为习常。" }, + { h: "第五十三章", t: "使我介然有知,行于大道,唯施是畏。大道甚夷,而民好径。朝甚除,田甚芜,仓甚虚;服文彩,带利剑,厌饮食,财货有余;是为盗夸。非道也哉!" }, + { h: "第五十四章", t: "善建者不拔,善抱者不脱,子孙以祭祀不辍。修之于身,其德乃真;修之于家,其德乃余;修之于乡,其德乃长;修之于国,其德乃丰;修之于天下,其德乃普。故以身观身,以家观家,以乡观乡,以国观国,以天下观天下。吾何以知天下然哉?以此。" }, + { h: "第五十五章", t: "含德之厚,比于赤子。蜂虿虺蛇不螫,猛兽不据,攫鸟不搏。骨弱筋柔而握固。未知牝牡之合而全作,精之至也。终日号而不嗄,和之至也。知和曰常,知常曰明。益生曰祥。心使气曰强。物壮则老,谓之不道,不道早已。" }, + { h: "第五十六章", t: "知者不言,言者不知。塞其兑,闭其门,挫其锐,解其分,和其光,同其尘,是谓玄同。故不可得而亲,不可得而疏;不可得而利,不可得而害;不可得而贵,不可得而贱。故为天下贵。" }, + { h: "第五十七章", t: "以正治国,以奇用兵,以无事取天下。吾何以知其然哉?以此。天下多忌讳,而民弥贫;民多利器,国家滋昬;人多伎巧,奇物滋起;法令滋彰,盗贼多有。故圣人云﹕「我无为而民自化,我好静而民自正,我无事而民自富,我无欲而民自朴。」" }, + { h: "第五十八章", t: "其政闷闷,其民淳淳;其政察察,其民缺缺。祸兮福之所倚,福兮祸之所伏。孰知其极?其无正。正复为奇,善复为妖。人之迷,其日固久。是以圣人方而不割,廉而不刿,直而不肆,光而不燿。" }, + { h: "第五十九章", t: "治人事天,莫若啬。夫唯啬,是谓早服;早服谓之重积德;重积德则无不克,无不克则莫知其极;莫知其极,可以有国;有国之母,可以长久;是谓深根固柢,长生久视之道。" }, + { h: "第六十章", t: "治大国,若烹小鲜。以道莅天下,其鬼不神;非其鬼不神,其神不伤人;非其神不伤人,圣人亦不伤人。夫两不相伤,故德交归焉。" }, + { h: "第六十一章", t: "大国者下流,天下之交。天下之牝,牝常以静胜牡,以静为下。故大国以下小国,则取小国;小国以下大国,则取大国。故或下以取,或下而取。大国不过欲兼畜人,小国不过欲入事人。夫两者各得其所欲,大者宜为下。" }, + { h: "第六十二章", t: "道者万物之奥。善人之宝,不善人之所保。美言可以市,尊行可以加人。人之不善,何弃之有?故立天子,置三公,虽有拱璧以先驷马,不如坐进此道。古之所以贵此道者何?不曰以求得,有罪以免邪?故为天下贵。" }, + { h: "第六十三章", t: "为无为,事无事,味无味。大小多少,报怨以德。图难于其易,为大于其细;天下难事必作于易,天下大事必作于细。是以圣人终不为大,故能成其大。夫轻诺必寡信,多易必多难。是以圣人犹难之,故终无难矣" }, + { h: "第六十四章", t: "其安易持,其未兆易谋。其脆易泮,其微易散。为之于未有,治之于未乱。合抱之木,生于毫末;九层之台,起于累土;千里之行,始于足下。为者败之,执者失之。是以圣人无为故无败,无执故无失。民之从事,常于几成而败之。慎终如始,则无败事。是以圣人欲不欲,不贵难得之货;学不学,复众人之所过。以辅万物之自然,而不敢为。" }, + { h: "第六十五章", t: "古之善为道者,非以明民,将以愚之。民之难治,以其智多。故以智治国,国之贼,不以智治国,国之福。知此两者亦稽式。常知稽式,是谓玄德。玄德深矣,远矣,与物反矣,然后乃至大顺。" }, + { h: "第六十六章", t: "江海所以能为百谷王者,以其善下之,故能为百谷王。是以欲上民,必以言下之。欲先民,必以身后之。是以圣人处上而民不重,处前而民不害。是以天下乐推而不厌,以其不争,故天下莫能与之争。" }, + { h: "第六十七章", t: "天下皆谓我道大,似不肖。夫唯大,故似不肖。若肖,久矣其细也夫!我有三宝,持而保之。一曰慈,二曰俭,三曰不敢为天下先。 慈故能勇,俭故能广,不敢为天下先,故能成器长。今舍慈且勇,舍俭且广,舍后且先,死矣! 夫慈以战则胜,以守则固。天将救之,以慈卫之。" }, + { h: "第六十八章", t: "善为士者不武,善战者不怒,善胜敌者不与,善用人者为之下,是谓不争之德,是谓用人之力,是谓配天古之极。" }, + { h: "第六十九章", t: "用兵有言﹕「吾不敢为主而为客,不敢进寸而退尺。」是谓行无行,攘无臂,扔无敌,执无兵。 祸莫大于轻敌,轻敌几丧吾宝。故抗兵相加,哀者胜矣。" }, + { h: "第七十章", t: "吾言甚易知,甚易行。天下莫能知,莫能行。言有宗,事有君。夫唯无知,是以不我知。知我者希,则我者贵。是以圣人被褐怀玉。" }, + { h: "第七十一章", t: "知不知上,不知知病。夫唯病病,是以不病。圣人不病,以其病病,是以不病。" }, + { h: "第七十二章", t: "民不畏威,则大威至。无狎其所居,无厌其所生。夫唯不厌,是以不厌。是以圣人自知不自见,自爱不自贵;故去彼取此。" }, + { h: "第七十三章", t: "勇于敢则杀,勇于不敢则活。此两者,或利或害。天之所恶,孰知其故?是以圣人犹难之。天之道,不争而善胜,不言而善应,不召而自来,𦈎然而善谋。天网恢恢,疏而不失。" }, + { h: "第七十四章", t: "民不畏死,奈何以死惧之?若使民常畏死,而为奇者,吾得执而杀之,孰敢?常有司杀者杀,夫代司杀者杀,是谓代大匠斲。 夫代大匠斲者,希有不伤其手矣。" }, + { h: "第七十五章", t: "民之饥,以其上食税之多,是以饥。民之难治,以其上之有为,是以难治。民之轻死,以其上求生之厚,是以轻死。夫唯无以生为者,是贤于贵生。" }, + { h: "第七十六章", t: "人之生也柔弱,其死也坚强。 万物草木之生也柔脆,其死也枯槁。 故坚强者死之徒,柔弱者生之徒。是以兵强则不胜,木强则兵。强大处下,柔弱处上。" }, + { h: "第七十七章", t: "天之道,其犹张弓与?高者抑之,下者举之;有余者损之,不足者补之。天之道,损有余而补不足。人之道则不然,损不足以奉有余。 孰能有余以奉天下?唯有道者。 是以圣人为而不恃,功成而不处,其不欲见贤。" }, + { h: "第七十八章", t: "天下莫柔弱于水,而攻坚强者,莫之能胜,其无以易之。弱之胜强,柔之胜刚,天下莫不知莫能行。是以圣人云﹕「受国之垢,是谓社稷主;受国不祥,是为天下王。」正言若反。" }, + { h: "第七十九章", t: "和大怨,必有余怨,安可以为善?是以圣人执左契,而不责于人。有德司契,无德司彻。天道无亲,常与善人。" }, + { h: "第八十章", t: "小国寡民,使有什伯之器而不用,使民重死而不远徙。虽有舟舆,无所乘之;虽有甲兵,无所陈之。使人复结绳而用之,甘其食,美其服,安其居,乐其俗。邻国相望,鸡犬之声相闻,民至老死,不相往来。" }, + { h: "第八十一章", t: "信言不美,美言不信。善者不辩,辩者不善。 知者不博,博者不知。 圣人不积,既以为人己愈有,既以与人己愈多。天之道,利而不害;圣人之道,为而不争。" }, + ], + }, + { + id: "qingjing", + name: "清静经", + title: "太上老君说常清静经", + seal: "清静经", + cat: "道家", + translator: "正统道藏本", + source: "维基文库 · 正统道藏", + sourceUrl: "https://zh.wikisource.org/wiki/太上老君說常清靜經", + pageSize: 6, // 一屏读完 + segments: [ + { t: "老君曰:大道无形,生育天地;大道无情,运行日月;大道无名,长养万物;吾不知其名,强名曰道。夫道者:有清有浊,有动有静;天清地浊,天动地静;男清女浊,男动女静。降本流末,而生万物。清者浊之源,动者静之基。人能常清静,天地悉皆归。" }, + { t: "夫人神好清,而心扰之;人心好静,而欲牵之。常能遣其欲而心自静,澄其心而神自清。自然六欲不生,三毒消灭。所以不能者,为心未澄,欲未遣也。能遣之者,内观于心,心无其心;外观于形,形无其形;远观于物,物无其物。三者既悟,唯见于空。观空亦空,空无所空;所空既无,无无亦无;无无既无,湛然常寂;寂无所寂,欲岂能生?欲既不生,即是真静。真常应物,真常得性;常应常静,常清静矣。如此清静,渐入真道;既入真道,名谓得道,虽名得道,实无所得;为化众生,名谓得道;能悟之者,可传圣道。" }, + { t: "老君曰:上士无争,下士好争;上德不德,下德执德;执著之者,不名道德。众生所以不得真道者,为有妄心。既有妄心,即惊其神;既惊其神,即著万物;既著万物,即生贪求;既生贪求,即是烦恼;烦恼妄想,忧苦身心;便遭浊辱,流浪生死,常沉苦海,永失真道。真常之道,悟者自得。得悟道者,常清静矣。" }, + { t: "仙人葛玄曰:吾得真道,曾诵此经万遍。此经是天人所习,不传下士。吾昔受之于东华帝君。东华帝君受之于金阙帝君,金阙帝君受之于西王母。西王母皆口口相传,不记文字。吾今于世书而录之。上士悟之,升为天官。中士修之,南宫列仙。下士得之,在世长年,游行三界,升入金门。" }, + { t: "左玄真人曰:学道之士,持诵此经,即得十天善神拥护其人。然后玉符宝神,金液链形,形神俱妙,与道合真。" }, + { t: "正一真人曰:人家有此经,悟解之者,灾障不干,众圣护门,神升上界,朝拜高尊。功满德就,相感帝君。诵持不退,身腾紫云。" }, + ], + }, + { + id: "ganying", + name: "感应篇", + title: "太上感应篇", + seal: "感应篇", + cat: "道家", + translator: "通行本", + source: "维基文库", + sourceUrl: "https://zh.wikisource.org/wiki/太上感應篇", + pageSize: 4, // 每页 4 段 + segments: [ + { t: "太上曰:「祸福无门,惟人自召;善恶之报,如影随形。」" }, + { t: "是以天地有司过之神,依人所犯轻重,以夺人算。算减则贫耗,多逢忧患;人皆恶之,刑祸随之,吉庆避之,恶星灾之;算尽则死。又有三台、北斗神君,在人头上,录人罪恶,夺其纪算。又有三尸神,在人身中,每到庚申日,辄上诣天曹,言人罪过。月晦之日,灶神亦然。凡人有过,大则夺纪,小则夺算。其过大小,有数百事,欲求长生者,先须避之。" }, + { t: "是道则进,非道则退。不履邪径,不欺暗室;积德累功,慈心于物;忠孝友悌,正己化人;矜孤恤寡,敬老怀幼;昆虫草木,犹不可伤。宜悯人之凶,乐人之善;济人之急,救人之危。见人之得,如己之得;见人之失,如己之失。不彰人短,不炫己长;遏恶扬善,推多取少。受辱不怨,受宠若惊。施恩不求报,与人不追悔。" }, + { t: "所谓善人,人皆敬之,天道佑之,福禄随之,众邪远之,神灵卫之;所作必成,神仙可冀。欲求天仙者,当立一千三百善;欲求地仙者,当立三百善。" }, + { t: "苟或非义而动,背理而行。以恶为能,忍作残害;阴贼良善,暗侮君亲;慢其先生,叛其所事;诳诸无识,谤诸同学;虚诬诈伪,攻讦宗亲;刚强不仁,狠戾自用;是非不当,向背乖宜;虐下取功,谄上希旨;受恩不感,念怨不休;轻蔑天民,扰乱国政;赏及非义,刑及无辜;杀人取财,倾人取位;诛降戮服,贬正排贤;凌孤逼寡,弃法受赂;以直为曲,以曲为直;入轻为重,见杀加怒;知过不改,知善不为;自罪引他,壅塞方术;讪谤圣贤,侵凌道德。" }, + { t: "射飞逐走,发蛰惊栖;填穴覆巢,伤胎破卵;愿人有失,毁人成功;危人自安,减人自益;以恶易好,以私废公,窃人之能,蔽人之善;形人之丑,讦人之私;耗人货财,离人骨肉;侵人所爱,助人为非;逞志作威,辱人求胜;败人苗稼,破人婚姻;苟富而骄,苟免无耻;认恩推过,嫁祸卖恶;沽买虚誉,包贮险心;挫人所长,护己所短;乘威迫胁,纵暴杀伤;无故剪裁,非礼烹宰;散弃五谷,劳扰众生;破人之家,取其财宝;决水放火,以害民居;紊乱规模,以败人功;损人器物,以穷人用。" }, + { t: "见他荣贵,愿他流贬;见他富有,愿他破散;见他色美,起心私之;负他货财,愿他身死;干求不遂,便生咒恨;见他失便,便说他过;见他体相不具而笑之,见他才能可称而抑之。" }, + { t: "埋蛊厌人,用药杀树;恚怒师傅,抵触父兄;强取强求,好侵好夺;掳掠致富,巧诈求迁;赏罚不平,逸乐过节;苛虐其下,恐吓于他;怨天尤人,呵风骂雨;鬪合争讼,妄逐朋党;用妻妾语,违父母训;得新忘故,口是心非;贪冒于财,欺罔其上;造作恶语,谗毁平人;毁人称直,骂神称正;弃顺效逆,背亲向疏;指天地以证鄙怀,引神明而鉴猥事。" }, + { t: "施与后悔,假借不还;分外营求,力上施设;淫欲过度,心毒貌慈;秽食𫗪人,左道惑众;短尺狭度,轻秤小升;以伪杂真,采取奸利;压良为贱,谩蓦愚人;贪婪无厌,咒诅求直。" }, + { t: "嗜酒悖乱,骨肉忿争;男不忠良,女不柔顺;不和其室,不敬其夫;每好矜夸,常行妒忌;无行于妻子,失礼于舅姑;轻慢先灵,违逆上命;作为无益,怀挟外心;自咒咒他,偏憎偏爱;越井越灶,跳食跳人;损子堕胎,行多隐僻;晦腊歌舞,朔旦号怒;对北涕唾及溺,对灶吟咏及哭;又以灶火烧香,秽柴作食;夜起裸露,八节行刑;唾流星,指虹霓;辄指三光,久视日月;春月燎猎,对北恶骂,无故杀龟打蛇……如是等罪,司命随其轻重,夺其纪算。算尽则死;死有余责,乃殃及子孙。" }, + { t: "又诸横取人财者,乃计其妻子、家口以当之,渐至死丧。若不死丧,则有水火盗贼、遗亡器物、疾病口舌诸事,以当妄取之值。又枉杀人者,是易刀兵而相杀也。取非义之财者,譬如漏脯救饥,鸩酒止渴;非不暂饱,死亦及之。" }, + { t: "夫心起于善,善虽未为,而吉神已随之;或心起于恶,恶虽未为,而凶神已随之。其有曾行恶事,后自改悔,诸恶莫作,众善奉行,久久必获吉庆;所谓转祸为福也。故吉人语善、视善、行善,一日有三善,三年天必降之福。凶人语恶、视恶、行恶,一日有三恶,三年天必降之祸。胡不勉而行之?" }, + ], + }, ]; // ===== 可选音色(模型 qwen3-tts-flash) ===== @@ -84,13 +288,14 @@ const VOICE_GROUPS = [ { label: "诵读音色", voices: [ - ["Seren", "Seren 塞伦 · 温润女声"], - ["Ethan", "Ethan 伊森 · 阳光男声"], + ["Seren", "Seren 塞伦 · 女声"], + ["Ethan", "Ethan 伊森 · 男声"], ], }, ]; const sutraEl = document.getElementById("sutra"); +const sutraSelect = document.getElementById("sutra-select"); const voiceSelect = document.getElementById("voice-select"); const btnPlay = document.getElementById("btn-play"); const statusEl = document.getElementById("status"); @@ -105,6 +310,10 @@ const btnNext = document.getElementById("btn-next"); const btnLoop = document.getElementById("btn-loop"); const btnBgm = document.getElementById("btn-bgm"); const timerSelect = document.getElementById("timer-select"); +const rateSelect = document.getElementById("rate-select"); +const btnNight = document.getElementById("btn-night"); +const sleepRemainEl = document.getElementById("sleep-remaining"); +const btnJumpNow = document.getElementById("btn-jump-now"); const pageInfo = document.getElementById("page-info"); /** 音频缓存:key = `${经id}|${音色}|${段序号}`,value = Blob */ @@ -117,8 +326,27 @@ let bgmOn = false; // 背景音乐开关 let sleepEndAt = 0; // 定时停止的截止时间戳(毫秒),0 = 未开启 let sleepTimer = null; // 定时器句柄(前台兜底;后台由 onTimeUpdate 判定截止时间) let loopAutoByTimer = false; // 循环是否为设定定时时自动打开的(是则定时结束一并关回) + +// ===== 用户偏好记忆(localStorage):经、音色、字号、循环、配乐、语速、夜间 ===== +const PREFS_KEY = "fj.prefs.v1"; +const prefs = (() => { + try { + return JSON.parse(localStorage.getItem(PREFS_KEY)) || {}; + } catch { + return {}; + } +})(); +/** 写一条偏好并落盘;localStorage 不可用时静默降级为仅内存。 */ +function savePref(key, value) { + prefs[key] = value; + try { + localStorage.setItem(PREFS_KEY, JSON.stringify(prefs)); + } catch { /* 隐私模式等场景忽略 */ } +} let segTimes = []; // 合并音频中各段的起始秒 let currentSeg = -1; // 正在朗读的段号 +let lastUserScroll = 0; // 用户最近一次手动滚动经文的时间戳(用于暂缓跟读强拽) +let cursorRaf = null; // 逐字光标 rAF 句柄 // ===== 背景音乐:Web Audio 实时合成颂钵泛音 drone(零资源文件、无缝循环) ===== let bgmCtx = null; @@ -411,17 +639,61 @@ VOICE_GROUPS.forEach((group) => { }); voiceSelect.value = "Seren"; -// ===== 状态 / 高亮 ===== -function setStatus(text, isError = false) { - statusEl.textContent = text; - statusEl.classList.toggle("error", isError); +// ===== 经典下拉:按 cat 分组(佛经 / 道家) ===== +{ + const cats = [...new Set(SUTRAS.map((s) => s.cat || "其他"))]; + cats.forEach((cat) => { + const group = sutraSelect.appendChild(Object.assign(document.createElement("optgroup"), { label: cat })); + SUTRAS.filter((s) => (s.cat || "其他") === cat).forEach((s) => { + const opt = document.createElement("option"); + opt.value = s.id; + opt.textContent = s.name; + group.appendChild(opt); + }); + }); } +// ===== 状态 / 高亮 ===== +let statusBase = ""; // 状态主干文字;定时开启时自动追加剩余倒计时 +function setStatus(text, isError = false) { + statusBase = text; + statusEl.classList.toggle("error", isError); + renderStatus(); +} + +/** 渲染状态条:主干 + 定时剩余 mm:ss;移动端同步到播放旁的剩余胶囊。 */ +function renderStatus() { + let remain = ""; + if (sleepEndAt) { + const s = Math.max(0, Math.ceil((sleepEndAt - Date.now()) / 1000)); + remain = `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`; + } + statusEl.textContent = statusBase + (remain ? ` · ${remain} 后停` : ""); + sleepRemainEl.textContent = remain ? `⏱ ${remain}` : ""; + sleepRemainEl.classList.toggle("show", !!sleepEndAt); +} +setInterval(renderStatus, 1000); + function setPlayingUI(on) { playing = on; btnPlay.classList.toggle("playing", on); btnPlay.setAttribute("aria-label", on ? "停止" : "播放"); btnPlay.querySelector(".play-label").textContent = on ? "停 止" : "播 放"; + if ("mediaSession" in navigator) navigator.mediaSession.playbackState = on ? "playing" : "none"; +} + +/** 更新锁屏/控制中心元数据:经名 + 音色 + 印章封面,并接管播放控制按键。 */ +function updateMediaSession() { + if (!("mediaSession" in navigator) || !sutra) return; + navigator.mediaSession.metadata = new MediaMetadata({ + title: `《${sutra.name}》诵读`, + artist: currentVoice(), + album: sutra.title, + artwork: [ + { src: "icon-180.png", sizes: "180x180", type: "image/png" }, + { src: "icon.svg", sizes: "any", type: "image/svg+xml" }, + ], + }); } function highlight(index) { @@ -434,22 +706,57 @@ function highlight(index) { d.classList.toggle("now", i === index); d.classList.toggle("done", index >= 0 && i < index); }); - // 跟读滚动:把正在朗读的段落平滑移到显示区中央 + // 跟读滚动:定位到光标所在行(而非整段中心); + // 用户刚手动滚动过(6 秒内)则不打断阅读,改由"回到诵读处"按钮兜底 const active = segEls.find((el) => Number(el.dataset.index) === index); - active?.scrollIntoView({ behavior: "smooth", block: "center", inline: "center" }); + const userBusy = playing && Date.now() - lastUserScroll < 6000; + if (active && !userBusy) { + const target = active.querySelector(".ch.cur") || active; + target.scrollIntoView({ behavior: reduceMotionMQ.matches ? "auto" : "smooth", block: "center", inline: "center" }); + } + requestAnimationFrame(updateJumpNow); +} + +/** 诵读光标是否在经文视区内;不在则显示"回到诵读处"浮动按钮。 */ +function updateJumpNow() { + if (!playing || currentSeg < 0) { + btnJumpNow.hidden = true; + return; + } + const active = segEls.find((el) => Number(el.dataset.index) === currentSeg); + if (!active) { + btnJumpNow.hidden = false; // 当前段不在本页,提示可跳回 + return; + } + const target = active.querySelector(".ch.cur") || active; + const r = target.getBoundingClientRect(); + const c = sutraEl.getBoundingClientRect(); + btnJumpNow.hidden = r.top >= c.top && r.bottom <= c.bottom; } function clearHighlight() { - segEls.forEach((el) => el.classList.remove("playing", "done")); + segEls.forEach((el) => { + el.classList.remove("playing", "done"); + el.querySelectorAll(".ch").forEach((s) => s.classList.remove("read", "cur")); + delete el.dataset.cursorN; + }); dots.forEach((d) => d.classList.remove("now", "done")); + if (cursorRaf) { + cancelAnimationFrame(cursorRaf); + cursorRaf = null; + } } +const reduceMotionMQ = matchMedia("(prefers-reduced-motion: reduce)"); + // ===== 渲染 ===== function renderDots() { progressEl.innerHTML = ""; - dots = segments.map(() => { + dots = segments.map((_, i) => { const d = document.createElement("span"); d.className = "dot"; + d.title = `第 ${i + 1} 段,点击起读`; + d.addEventListener("click", () => playFrom(i)); progressEl.appendChild(d); return d; }); @@ -527,6 +834,7 @@ function selectSutra(id) { if (!next || next === sutra) return; stopPlayback(); sutra = next; + savePref("sutra", id); segments = sutra.segments; pageCount = Math.ceil(segments.length / sutra.pageSize); @@ -540,9 +848,8 @@ function selectSutra(id) { src.textContent = sutra.source; src.href = sutra.sourceUrl; - document.querySelectorAll(".sutra-btn").forEach((b) => { - b.classList.toggle("active", b.dataset.sutra === id); - }); + sutraSelect.value = id; + sheetSyncFns.get(sutraSelect)?.(); renderDots(); renderPage(0); @@ -554,6 +861,7 @@ function selectSutra(id) { function stopPlayback() { playToken += 1; setPlayingUI(false); + btnPlay.classList.remove("preparing"); if (inflight) { inflight.abort(); inflight = null; @@ -563,11 +871,14 @@ function stopPlayback() { audioEl.onended = null; stopBgm(); clearHighlight(); + btnJumpNow.hidden = true; currentSeg = -1; } /** * 获取指定段的音频 Blob,命中缓存则直接返回。 + * 失败自动重试(最多 3 次,间隔 1s/2s 递增),吸收服务重启等瞬时 502; + * 用户停止播放导致的 AbortError 不重试。 * @param {number} index 段序号 * @returns {Promise} 音频数据 */ @@ -575,24 +886,32 @@ async function fetchSegment(index) { const key = `${sutra.id}|${currentVoice()}|${index}`; if (audioCache.has(key)) return audioCache.get(key); - inflight = new AbortController(); - try { - const resp = await fetch("/api/tts", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ text: segments[index].t, voice: currentVoice() }), - signal: inflight.signal, - }); - if (!resp.ok) { - const err = await resp.json().catch(() => ({})); - throw new Error(err.error || `HTTP ${resp.status}`); + let lastErr = null; + for (let attempt = 0; attempt < 3; attempt += 1) { + if (attempt > 0) await new Promise((r) => setTimeout(r, attempt * 1000)); + inflight = new AbortController(); + try { + const resp = await fetch("/api/tts", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ text: segments[index].t, voice: currentVoice() }), + signal: inflight.signal, + }); + if (!resp.ok) { + const err = await resp.json().catch(() => ({})); + throw new Error(err.error || `HTTP ${resp.status}`); + } + const blob = await resp.blob(); + audioCache.set(key, blob); + return blob; + } catch (err) { + if (err.name === "AbortError") throw err; // 主动停止,不重试 + lastErr = err; + } finally { + inflight = null; } - const blob = await resp.blob(); - audioCache.set(key, blob); - return blob; - } finally { - inflight = null; } + throw lastErr; } /** @@ -667,6 +986,65 @@ async function buildCombined(blobs) { return { url: URL.createObjectURL(new Blob([out], { type: "audio/wav" })), times }; } +/** 把段落文字逐字包成 .ch span(仅当前诵读段需要,幂等)。 */ +function wrapChars(el) { + if (el.dataset.wrapped) return; + const frag = document.createDocumentFragment(); + for (const c of el.textContent) { + const s = document.createElement("span"); + s.className = "ch"; + s.textContent = c; + frag.appendChild(s); + } + el.innerHTML = ""; + el.appendChild(frag); + el.dataset.wrapped = "1"; +} + +/** + * 逐字光标:按音频当前时间在当前段内按比例推进, + * 已读字染朱砂、当前字挂金色下划线光标。TTS 无逐字时间戳,按语速均匀估算。 + */ +function cursorTick() { + if (!playing || !segTimes.length) { + cursorRaf = null; + return; + } + const t = audioEl.currentTime; + let i = segTimes.length - 1; + while (i > 0 && segTimes[i] > t) i -= 1; + const active = segEls.find((el) => Number(el.dataset.index) === i); + if (active) { + wrapChars(active); + const chs = active.querySelectorAll(".ch"); + const segDur = (i + 1 < segTimes.length ? segTimes[i + 1] - 0.8 : audioEl.duration - 3) - segTimes[i]; + const n = Math.min(chs.length, Math.floor(((t - segTimes[i]) / Math.max(segDur, 0.1)) * chs.length)); + const prev = Number(active.dataset.cursorN ?? -1); + if (n !== prev) { + const lo = Math.max(0, Math.min(n, prev)); + const hi = Math.max(n, prev); + for (let k = lo; k <= hi && k < chs.length; k += 1) chs[k].classList.toggle("read", k < n); + active.dataset.cursorN = n; + const old = active.querySelector(".ch.cur"); + old?.classList.remove("cur"); + if (n < chs.length) chs[n].classList.add("cur"); + } + cursorTick._n = (cursorTick._n || 0) + 1; + if (cursorTick._n % 15 === 0) { + // 光标行跟随:光标走出视区 25%~75% 舒适带时,平滑滚回中部 + if (Date.now() - lastUserScroll >= 6000 && chs[n]) { + const r = chs[n].getBoundingClientRect(); + const c = sutraEl.getBoundingClientRect(); + if (r.top < c.top + c.height * 0.25 || r.bottom > c.top + c.height * 0.75) { + chs[n].scrollIntoView({ behavior: reduceMotionMQ.matches ? "auto" : "smooth", block: "center", inline: "center" }); + } + } + updateJumpNow(); + } + } + cursorRaf = requestAnimationFrame(cursorTick); +} + /** 播放进度回调:按当前时间定位段号,高亮并自动翻页滚动;顺带判定定时停止。 */ function onTimeUpdate() { if (sleepEndAt && Date.now() >= sleepEndAt) { @@ -692,6 +1070,9 @@ async function playFrom(index) { stopPlayback(); const token = ++playToken; setPlayingUI(true); + // 准备阶段按钮脉冲 + 文字提示(移动端没有状态栏,这条反馈很关键) + btnPlay.classList.add("preparing"); + btnPlay.querySelector(".play-label").textContent = "准备中"; setStatus("正在准备音频…"); const blobs = new Array(segments.length); @@ -725,12 +1106,17 @@ async function playFrom(index) { }; audioEl.currentTime = built.times[index]; await audioEl.play(); + btnPlay.classList.remove("preparing"); + btnPlay.querySelector(".play-label").textContent = "停 止"; if (bgmOn) startBgm(); // play() 成功后起步背景音乐(用户手势上下文内) + updateMediaSession(); + cursorRaf = requestAnimationFrame(cursorTick); // 启动逐字光标 setStatus(`正在朗读 · ${currentVoice()}`); } catch (err) { if (err.name === "AbortError") return; // 已被停止/新播放取代 setStatus(`语音合成失败:${err.message}`, true); setPlayingUI(false); + btnPlay.classList.remove("preparing"); clearHighlight(); } } @@ -747,6 +1133,7 @@ function setLoopMode(on) { btnLoop.addEventListener("click", () => { setLoopMode(!loopMode); loopAutoByTimer = false; // 用户手动干预后以手动状态为准 + savePref("loop", loopMode); setStatus(loopMode ? "循环诵读:开" : "循环诵读:关"); }); @@ -755,6 +1142,7 @@ function fireSleepTimer() { sleepEndAt = 0; sleepTimer = null; timerSelect.value = "0"; + sheetSyncFns.get(timerSelect)?.(); if (loopAutoByTimer) { loopAutoByTimer = false; setLoopMode(false); @@ -796,11 +1184,28 @@ btnBgm.addEventListener("click", () => { bgmOn = !bgmOn; btnBgm.classList.toggle("active", bgmOn); btnBgm.setAttribute("aria-pressed", String(bgmOn)); + savePref("bgm", bgmOn); if (bgmOn && playing) startBgm(); if (!bgmOn) stopBgm(); setStatus(bgmOn ? "背景音乐:开" : "背景音乐:关"); }); +/** 语速切换:直接改已合并音频的 playbackRate,立即生效。 */ +rateSelect.addEventListener("change", () => { + audioEl.playbackRate = Number(rateSelect.value); + savePref("rate", rateSelect.value); + setStatus(`语速:${rateSelect.options[rateSelect.selectedIndex].text}`); +}); + +/** 夜间模式:暗色宣纸配色,睡前诵读不刺眼。 */ +btnNight.addEventListener("click", () => { + const on = document.body.classList.toggle("night"); + btnNight.classList.toggle("active", on); + btnNight.setAttribute("aria-pressed", String(on)); + btnNight.textContent = on ? "日间" : "夜间"; + savePref("night", on); +}); + btnPlay.addEventListener("click", () => { if (playing || inflight) { stopPlayback(); @@ -812,6 +1217,7 @@ btnPlay.addEventListener("click", () => { voiceSelect.addEventListener("change", () => { if (playing || inflight) stopPlayback(); + savePref("voice", voiceSelect.value); setStatus(`已切换音色:${voiceSelect.value},点击播放开始诵读`); }); @@ -826,9 +1232,25 @@ btnExpand.addEventListener("click", () => { btnPrev.addEventListener("click", () => gotoPage(currentPage - 1)); btnNext.addEventListener("click", () => gotoPage(currentPage + 1)); -document.querySelectorAll(".sutra-btn").forEach((btn) => { - btn.addEventListener("click", () => selectSutra(btn.dataset.sutra)); +// 手动滚轮/触摸滚动经文:记录时间,6 秒内跟读滚动让位给读者 +["wheel", "touchmove"].forEach((ev) => { + sutraEl.addEventListener(ev, () => { lastUserScroll = Date.now(); }, { passive: true }); }); +// 任何滚动(含自动滚动)都刷新"回到诵读处"按钮的可见性 +sutraEl.addEventListener("scroll", () => { if (playing) updateJumpNow(); }, { passive: true }); +btnJumpNow.addEventListener("click", () => { + lastUserScroll = 0; + let active = segEls.find((el) => Number(el.dataset.index) === currentSeg); + if (!active && currentSeg >= 0) { + renderPage(pageOf(currentSeg)); // 当前段不在本页,先翻过去 + active = segEls.find((el) => Number(el.dataset.index) === currentSeg); + } + const target = active?.querySelector(".ch.cur") || active; + target?.scrollIntoView({ behavior: "smooth", block: "center", inline: "center" }); + btnJumpNow.hidden = true; +}); + +sutraSelect.addEventListener("change", () => selectSutra(sutraSelect.value)); // 键盘 ← → 翻页(下拉框聚焦时不拦截) document.addEventListener("keydown", (e) => { @@ -838,12 +1260,17 @@ document.addEventListener("keydown", (e) => { }); // ===== 字号与排版 ===== +/** 应用字号档位并同步按钮高亮。 */ +function setFontSize(size) { + if (!["sm", "md", "lg"].includes(size)) return; + document.querySelectorAll(".size-btn").forEach((b) => b.classList.toggle("active", b.dataset.size === size)); + sutraEl.classList.remove("size-sm", "size-md", "size-lg"); + sutraEl.classList.add(`size-${size}`); +} document.querySelectorAll(".size-btn").forEach((btn) => { btn.addEventListener("click", () => { - document.querySelectorAll(".size-btn").forEach((b) => b.classList.remove("active")); - btn.classList.add("active"); - sutraEl.classList.remove("size-sm", "size-md", "size-lg"); - sutraEl.classList.add(`size-${btn.dataset.size}`); + setFontSize(btn.dataset.size); + savePref("size", btn.dataset.size); }); }); @@ -862,8 +1289,133 @@ mobileMQ.addEventListener("change", () => { } }); -// ===== 初始化(支持 #jingang / #vertical 锚点;手机端忽略竖排) ===== +// ===== 移动端底部升起选择器:原生 select 保留为状态源,手机上改由面板选择 ===== +const sheetSyncFns = new Map(); // select → 同步按钮文字的函数 +let sheetEls = null; // 当前打开的面板 {mask, panel} + +/** 关闭底部选择面板(若开着)。 */ +function closeSheet() { + if (!sheetEls) return; + const { mask, panel } = sheetEls; + sheetEls = null; + mask.classList.remove("open"); + panel.classList.remove("open"); + setTimeout(() => { mask.remove(); panel.remove(); }, 220); +} + +/** 打开底部升起面板:列出 select 全部选项,点选后同步并派发 change。 */ +function openSheet(select, title) { + closeSheet(); + const mask = document.createElement("div"); + mask.className = "sheet-mask"; + const panel = document.createElement("div"); + panel.className = "sheet-panel"; + panel.setAttribute("role", "dialog"); + panel.setAttribute("aria-label", title); + panel.innerHTML = `
${title}
`; + const opts = panel.querySelector(".sheet-opts"); + let lastGroup = null; // optgroup 分组标题(如"佛经/道家"),无分组时不渲染 + [...select.options].forEach((opt, i) => { + const group = opt.parentElement?.tagName === "OPTGROUP" ? opt.parentElement.label : null; + if (group && group !== lastGroup) { + lastGroup = group; + const gh = document.createElement("div"); + gh.className = "sheet-group"; + gh.textContent = group; + opts.appendChild(gh); + } + const item = document.createElement("div"); + item.className = `sheet-opt${i === select.selectedIndex ? " sel" : ""}`; + item.textContent = opt.textContent; + item.setAttribute("role", "option"); + item.addEventListener("click", () => { + select.value = opt.value; + select.dispatchEvent(new Event("change")); + closeSheet(); + }); + opts.appendChild(item); + }); + const cancel = document.createElement("div"); + cancel.className = "sheet-cancel"; + cancel.textContent = "取 消"; + cancel.addEventListener("click", closeSheet); + panel.appendChild(cancel); + mask.addEventListener("click", closeSheet); + document.body.appendChild(mask); + document.body.appendChild(panel); + sheetEls = { mask, panel }; + requestAnimationFrame(() => { + mask.classList.add("open"); + panel.classList.add("open"); + }); +} + +/** 为 select 生成伪下拉按钮(仅移动端显示),点击打开底部面板。 */ +function attachSheet(select, title) { + if (!select) return; + const btn = document.createElement("button"); + btn.type = "button"; + btn.className = "sheet-btn"; + btn.setAttribute("aria-haspopup", "dialog"); + const text = document.createElement("span"); + text.className = "sheet-btn-text"; + const sync = () => { + text.textContent = select.options[select.selectedIndex]?.textContent || ""; + }; + sync(); + sheetSyncFns.set(select, sync); + select.addEventListener("change", sync); + btn.appendChild(text); + select.insertAdjacentElement("afterend", btn); + btn.addEventListener("click", () => openSheet(select, title)); +} +attachSheet(sutraSelect, "选择经典"); +attachSheet(voiceSelect, "诵读音色"); +attachSheet(timerSelect, "定时停止"); +attachSheet(rateSelect, "诵读语速"); + +// 锁屏/控制中心按键:上一段/下一段、暂停、停止 +if ("mediaSession" in navigator) { + navigator.mediaSession.setActionHandler("play", () => { if (!playing && !inflight) playFrom(Math.max(currentSeg, 0)); }); + navigator.mediaSession.setActionHandler("pause", () => stopPlayback()); + navigator.mediaSession.setActionHandler("stop", () => stopPlayback()); + navigator.mediaSession.setActionHandler("previoustrack", () => playFrom(Math.max(currentSeg - 1, 0))); + navigator.mediaSession.setActionHandler("nexttrack", () => { if (currentSeg + 1 < segments.length) playFrom(currentSeg + 1); }); +} + +// ===== PWA:注册 Service Worker,使"添加到主屏幕"后无地址栏独立运行 ===== +if ("serviceWorker" in navigator) { + window.addEventListener("load", () => navigator.serviceWorker.register("/sw.js").catch(() => {})); +} + +// ===== 初始化:恢复偏好(hash 锚点优先于记忆的经典/排版) ===== +if (prefs.voice && [...voiceSelect.options].some((o) => o.value === prefs.voice)) voiceSelect.value = prefs.voice; +sheetSyncFns.get(voiceSelect)?.(); +if (prefs.size) setFontSize(prefs.size); +if (prefs.loop) setLoopMode(true); +if (prefs.bgm) { + bgmOn = true; + btnBgm.classList.add("active"); + btnBgm.setAttribute("aria-pressed", "true"); +} +if (prefs.rate && [...rateSelect.options].some((o) => o.value === String(prefs.rate))) { + rateSelect.value = prefs.rate; + audioEl.playbackRate = Number(prefs.rate); + sheetSyncFns.get(rateSelect)?.(); +} +if (prefs.night) { + document.body.classList.add("night"); + btnNight.classList.add("active"); + btnNight.setAttribute("aria-pressed", "true"); + btnNight.textContent = "日间"; +} + if (location.hash.includes("vertical") && !mobileMQ.matches) { btnLayout.click(); } -selectSutra(location.hash.includes("jingang") ? "jingang" : "xinjing"); +// URL hash 可直达某部经(如 #jingang、#daodejing),优先于记忆的经典 +const hashId = location.hash.replace(/^#/, ""); +const initSutraId = SUTRAS.some((s) => s.id === hashId) + ? hashId + : (SUTRAS.some((s) => s.id === prefs.sutra) ? prefs.sutra : "xinjing"); +selectSutra(initSutraId); diff --git a/deploy.sh b/deploy.sh index 286c690..19b7811 100755 --- a/deploy.sh +++ b/deploy.sh @@ -14,7 +14,7 @@ HOST=154.8.162.18 USER=root DEST=/opt/fj # 注意:不同步 .env —— 服务器端 PORT=8018 与本地开发配置不同,避免覆盖。 -FILES=(index.html style.css app.js server.py icon.svg icon-180.png README.md run.md) +FILES=(index.html style.css app.js server.py sw.js manifest.json icon.svg icon-180.png icon-192.png icon-512.png README.md run.md) SSH_OPTS="-o StrictHostKeyChecking=no -o ConnectTimeout=15" SSH_CMD="ssh $SSH_OPTS" diff --git a/icon-192.png b/icon-192.png new file mode 100644 index 0000000..619056c Binary files /dev/null and b/icon-192.png differ diff --git a/icon-512.png b/icon-512.png new file mode 100644 index 0000000..71886d3 Binary files /dev/null and b/icon-512.png differ diff --git a/index.html b/index.html index 181ce5a..733e5a1 100644 --- a/index.html +++ b/index.html @@ -3,16 +3,23 @@ + + 心经 - + + + + + +
@@ -32,10 +39,10 @@ 播 放 -
- 经典 - - + +
+ +
@@ -48,20 +55,32 @@
+
- - - + +
+
+ + +