@windcoder/astro-theme-orchid 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/package.json +1 -1
- package/scripts/generate-lqips.mjs +234 -20
- package/src/components/AlbumCard.astro +4 -1
- package/src/components/BaseLayout.astro +208 -64
- package/src/components/DisplaySettings.astro +383 -0
- package/src/components/Header.astro +56 -2
- package/src/components/MusicPlayer.astro +45 -5
- package/src/components/MusicWidget.astro +265 -0
- package/src/components/PhotoCard.astro +13 -1
- package/src/components/PostCard.astro +4 -1
- package/src/components/SakuraEffect.astro +126 -28
- package/src/components/Sidebar.astro +28 -7
- package/src/i18n/en.ts +36 -0
- package/src/i18n/ja.ts +213 -0
- package/src/i18n/ko.ts +212 -0
- package/src/i18n/resolve.ts +10 -1
- package/src/i18n/ru.ts +213 -0
- package/src/i18n/zh-CN.ts +37 -0
- package/src/i18n/zh-TW.ts +208 -0
- package/src/index.ts +13 -0
- package/src/plugins/rehype.ts +66 -2
- package/src/routes/friends.astro +10 -2
- package/src/routes/page/[page].astro +3 -0
- package/src/routes/search.astro +24 -0
- package/src/routes/sponsor.astro +17 -4
- package/src/schema.ts +363 -71
- package/src/scripts/app.ts +805 -37
- package/src/styles/global.css +839 -36
- package/src/utils/gallery.ts +3 -1
- package/src/utils/image.ts +79 -0
- package/src/utils/lqip.ts +21 -12
- package/src/utils/thumb.ts +26 -0
- package/src/vite.ts +95 -8
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
// 独立搜索页(§4.104):静态外壳,查询与分页全部在客户端完成(app.ts「独立搜索页」段)。
|
|
3
|
+
// 结果来自 Pagefind 构建期索引;URL 携带 ?q= 关键词与 ?page= 页码,可直接到达与分享。
|
|
4
|
+
// 整页 pagefindIgnore:搜索页自身不进索引,避免搜出无内容空壳页。
|
|
5
|
+
import BaseLayout from "virtual:theme/components/BaseLayout.astro";
|
|
6
|
+
import { t } from "../i18n/index.js";
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<BaseLayout title={t('search')} page="search" pagefindIgnore>
|
|
10
|
+
<header class="archive-head reveal">
|
|
11
|
+
<h1 class="page-title">{t('search')}</h1>
|
|
12
|
+
</header>
|
|
13
|
+
<div class="search-page reveal">
|
|
14
|
+
<div class="search-page-bar">
|
|
15
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
|
16
|
+
<input id="searchPageInput" type="search" placeholder={t('search')} autocomplete="off" enterkeyhint="search" aria-label={t('search')} />
|
|
17
|
+
</div>
|
|
18
|
+
{/* 初始文案由 SSR 输出,客户端按查询状态改写(加载中/结果数/无结果/索引不可用) */}
|
|
19
|
+
<p class="search-page-meta" id="searchPageMeta">{t('searchTypeSomething')}</p>
|
|
20
|
+
<div class="search-page-results" id="searchPageResults"></div>
|
|
21
|
+
{/* 分页条复用列表页 .pagination 样式,内容由客户端按结果总数渲染 */}
|
|
22
|
+
<nav class="pagination" id="searchPagePager" aria-label={t('pagination')} hidden></nav>
|
|
23
|
+
</div>
|
|
24
|
+
</BaseLayout>
|
package/src/routes/sponsor.astro
CHANGED
|
@@ -2,10 +2,23 @@
|
|
|
2
2
|
import BaseLayout from "virtual:theme/components/BaseLayout.astro";
|
|
3
3
|
import config from "virtual:theme/config";
|
|
4
4
|
import { t } from "../i18n/index.js";
|
|
5
|
+
import { resolveSiteImage } from "../utils/image.js";
|
|
5
6
|
|
|
6
7
|
const cfg = config.sponsor;
|
|
7
8
|
const methods = cfg.methods.filter((m) => m.enabled);
|
|
8
9
|
const sponsors = cfg.sponsors;
|
|
10
|
+
|
|
11
|
+
// 收款码/打赏者头像三格式解析(§4.97):空串跳过
|
|
12
|
+
const methodImgs = await Promise.all(
|
|
13
|
+
methods.map((m) =>
|
|
14
|
+
m.image ? resolveSiteImage(m.image) : Promise.resolve(""),
|
|
15
|
+
),
|
|
16
|
+
);
|
|
17
|
+
const sponsorAvatars = await Promise.all(
|
|
18
|
+
sponsors.map((s) =>
|
|
19
|
+
s.avatar ? resolveSiteImage(s.avatar) : Promise.resolve(""),
|
|
20
|
+
),
|
|
21
|
+
);
|
|
9
22
|
---
|
|
10
23
|
|
|
11
24
|
<BaseLayout title={cfg.title} page="sponsor">
|
|
@@ -25,7 +38,7 @@ const sponsors = cfg.sponsors;
|
|
|
25
38
|
{
|
|
26
39
|
methods.length > 0 && (
|
|
27
40
|
<div class="sponsor-grid reveal">
|
|
28
|
-
{methods.map((m) => (
|
|
41
|
+
{methods.map((m, i) => (
|
|
29
42
|
<div class="sponsor-method card">
|
|
30
43
|
<div class="sm-name">
|
|
31
44
|
{m.icon && <span class="sm-icon">{m.icon}</span>}
|
|
@@ -33,7 +46,7 @@ const sponsors = cfg.sponsors;
|
|
|
33
46
|
</div>
|
|
34
47
|
{m.description && <p class="sm-desc">{m.description}</p>}
|
|
35
48
|
{m.image ? (
|
|
36
|
-
<img class="sponsor-qr" src={
|
|
49
|
+
<img class="sponsor-qr" src={methodImgs[i]} alt={t('qrAlt', { name: m.name })} loading="lazy" />
|
|
37
50
|
) : m.link ? (
|
|
38
51
|
<a class="btn sm-link" href={m.link} target="_blank" rel="noopener">
|
|
39
52
|
{t('goSponsor')}
|
|
@@ -58,10 +71,10 @@ const sponsors = cfg.sponsors;
|
|
|
58
71
|
</h3>
|
|
59
72
|
{sponsors.length > 0 ? (
|
|
60
73
|
<div class="sponsor-list">
|
|
61
|
-
{sponsors.map((s) => (
|
|
74
|
+
{sponsors.map((s, i) => (
|
|
62
75
|
<div class="sponsor-item">
|
|
63
76
|
{s.avatar ? (
|
|
64
|
-
<img class="sponsor-item-avatar" src={
|
|
77
|
+
<img class="sponsor-item-avatar" src={sponsorAvatars[i]} alt={s.name} loading="lazy" />
|
|
65
78
|
) : (
|
|
66
79
|
<span class="sponsor-item-avatar avatar-fallback">{s.name.slice(0, 1)}</span>
|
|
67
80
|
)}
|
package/src/schema.ts
CHANGED
|
@@ -37,6 +37,206 @@ const sidebarWidgetBase = z.object({
|
|
|
37
37
|
specificConfig: z.object({}).default({}),
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* 统一壁纸体系(§4.98):原 banner(§4.42)+ background(§4.92)合并为单段配置,
|
|
42
|
+
* 四种模式 html[data-wallpaper-mode] 驱动:banner 横幅 / fullscreen 全屏首屏 /
|
|
43
|
+
* overlay 覆盖透明垫底 / none 纯色不渲染。
|
|
44
|
+
* 图源遵循三格式约定(§4.97):"/" 开头按 public 路径原样输出;http(s)://、//、data:
|
|
45
|
+
* 外链原样输出;其余相对路径相对站点 src/ 目录解析(构建期查表,支持 assets 内图片)。
|
|
46
|
+
* 提取为模块级 const 以便使用函数默认值 .default(() => wallpaperSchema.parse({})):
|
|
47
|
+
* Zod 的 .default(x) 在输入缺失时直接取 x、不做二次 parse(v3/v4 行为一致),
|
|
48
|
+
* 本段嵌套深达 4 层,最外层再写一份完整字面量维护成本过高;改用 parse({}) 在
|
|
49
|
+
* 运行期递归补齐全部内层默认值(内层各段仍按惯例写显式完整字面量)。
|
|
50
|
+
*/
|
|
51
|
+
const wallpaperSchema = z.object({
|
|
52
|
+
/** 壁纸模式:banner 横幅 / fullscreen 全屏首屏 / overlay 覆盖透明垫底 / none 纯色 */
|
|
53
|
+
mode: z.enum(["banner", "fullscreen", "overlay", "none"]).default("banner"),
|
|
54
|
+
/**
|
|
55
|
+
* 是否允许访客在导航栏循环切换壁纸模式(banner→fullscreen→overlay→none),
|
|
56
|
+
* 选择持久化到 localStorage('qy-wallpaper'),优先于本配置的 mode。
|
|
57
|
+
*/
|
|
58
|
+
switchable: z.boolean().default(false),
|
|
59
|
+
/** 视频壁纸播放器总开关:false 时不渲染 video 元素与播放控制按钮 */
|
|
60
|
+
playerEnable: z.boolean().default(false),
|
|
61
|
+
/** 壁纸图源与视频源(三格式约定见 §4.97) */
|
|
62
|
+
src: z
|
|
63
|
+
.object({
|
|
64
|
+
/** 桌面端图源:多图数组构建期随机取一或开启轮播;空数组 = 无壁纸图 */
|
|
65
|
+
desktop: z.array(z.string()).default([]),
|
|
66
|
+
/** 移动端图源(<768px):留空数组则移动端复用 desktop 层全宽显示 */
|
|
67
|
+
mobile: z.array(z.string()).default([]),
|
|
68
|
+
/** 视频源列表(playerEnable 开启时生效) */
|
|
69
|
+
playerUrl: z.array(z.string()).default([]),
|
|
70
|
+
})
|
|
71
|
+
.default({ desktop: [], mobile: [], playerUrl: [] }),
|
|
72
|
+
/** 各模式共用的行为配置 */
|
|
73
|
+
common: z
|
|
74
|
+
.object({
|
|
75
|
+
/** 壁纸蒙层不透明度(0-1),压暗壁纸保证前景可读 */
|
|
76
|
+
dimOpacity: z.number().min(0).max(1).default(0.2),
|
|
77
|
+
/** 暗色模式蒙层不透明度(0-1),暗色下压更暗 */
|
|
78
|
+
darkDimOpacity: z.number().min(0).max(1).default(0.65),
|
|
79
|
+
/** 视频播放顺序:order 按列表顺序 / random 随机 */
|
|
80
|
+
playerMode: z.enum(["order", "random"]).default("random"),
|
|
81
|
+
/** 首页壁纸文案:banner/fullscreen 叠加渲染在壁纸上;无壁纸时复用为独立 hero 区块 */
|
|
82
|
+
homeText: z
|
|
83
|
+
.object({
|
|
84
|
+
/** 文案总开关:false 时标题与副句都不渲染 */
|
|
85
|
+
enable: z.boolean().default(true),
|
|
86
|
+
/** 主标题,<em> 标签内文字以强调色渲染;留空不渲染 */
|
|
87
|
+
title: z.string().default(""),
|
|
88
|
+
/** 主标题字号(css 长度) */
|
|
89
|
+
titleSize: z.string().default("3.8rem"),
|
|
90
|
+
/** 副句:单条或多条(打字机逐条打字;关闭打字机时多条构建期随机取一条静态展示) */
|
|
91
|
+
subtitle: z.union([z.string(), z.array(z.string())]).default([]),
|
|
92
|
+
/** 副句字号(css 长度) */
|
|
93
|
+
subtitleSize: z.string().default("1.5rem"),
|
|
94
|
+
/** 打字机效果(作用于副句) */
|
|
95
|
+
typewriter: z
|
|
96
|
+
.object({
|
|
97
|
+
/** 是否启用打字机(false 时副句静态展示) */
|
|
98
|
+
enable: z.boolean().default(true),
|
|
99
|
+
/** 打字速度 ms/字素 */
|
|
100
|
+
speed: z.number().int().min(10).default(100),
|
|
101
|
+
/** 删除速度 ms/字素 */
|
|
102
|
+
deleteSpeed: z.number().int().min(10).default(50),
|
|
103
|
+
/** 打完一条后的停留 ms */
|
|
104
|
+
pauseTime: z.number().int().min(0).default(2000),
|
|
105
|
+
})
|
|
106
|
+
.default({
|
|
107
|
+
enable: true,
|
|
108
|
+
speed: 100,
|
|
109
|
+
deleteSpeed: 50,
|
|
110
|
+
pauseTime: 2000,
|
|
111
|
+
}),
|
|
112
|
+
})
|
|
113
|
+
.default({
|
|
114
|
+
enable: true,
|
|
115
|
+
title: "",
|
|
116
|
+
titleSize: "3.8rem",
|
|
117
|
+
subtitle: [],
|
|
118
|
+
subtitleSize: "1.5rem",
|
|
119
|
+
typewriter: {
|
|
120
|
+
enable: true,
|
|
121
|
+
speed: 100,
|
|
122
|
+
deleteSpeed: 50,
|
|
123
|
+
pauseTime: 2000,
|
|
124
|
+
},
|
|
125
|
+
}),
|
|
126
|
+
/** 导航栏随壁纸的透明形态 */
|
|
127
|
+
navbar: z
|
|
128
|
+
.object({
|
|
129
|
+
/**
|
|
130
|
+
* semi 顶部半透明+滚动后实底 / semifull 顶部全透明+滚动后半透明 /
|
|
131
|
+
* full 恒全透明;壁纸不可见(none 模式或 homeOnly 下非首页)时强制实底
|
|
132
|
+
*/
|
|
133
|
+
transparentMode: z.enum(["semi", "semifull", "full"]).default("semi"),
|
|
134
|
+
/** 透明态是否启用背景模糊 */
|
|
135
|
+
enableBlur: z.boolean().default(true),
|
|
136
|
+
/** 透明态背景模糊半径 px */
|
|
137
|
+
blur: z.number().min(0).max(20).default(5),
|
|
138
|
+
})
|
|
139
|
+
.default({ transparentMode: "semi", enableBlur: true, blur: 5 }),
|
|
140
|
+
/** 底部波浪装饰:true 全端开启 / false 关闭 / 对象分端控制(默认移动端关闭参考站惯例) */
|
|
141
|
+
waves: z
|
|
142
|
+
.union([
|
|
143
|
+
z.boolean(),
|
|
144
|
+
z.object({
|
|
145
|
+
desktop: z.boolean().default(true),
|
|
146
|
+
mobile: z.boolean().default(true),
|
|
147
|
+
}),
|
|
148
|
+
])
|
|
149
|
+
.default(false),
|
|
150
|
+
/** 底部渐变装饰:true 全端开启 / false 关闭 / 对象分端控制 + 高度 */
|
|
151
|
+
gradient: z
|
|
152
|
+
.union([
|
|
153
|
+
z.boolean(),
|
|
154
|
+
z.object({
|
|
155
|
+
desktop: z.boolean().default(true),
|
|
156
|
+
mobile: z.boolean().default(true),
|
|
157
|
+
/** 渐变层高度(css 长度) */
|
|
158
|
+
height: z.string().default("10%"),
|
|
159
|
+
}),
|
|
160
|
+
])
|
|
161
|
+
.default(false),
|
|
162
|
+
/** 多图轮播(单图无效果);视频播放中自动暂停轮播 */
|
|
163
|
+
carousel: z
|
|
164
|
+
.object({
|
|
165
|
+
/**
|
|
166
|
+
* false:构建期随机取一张(零 JS);true:全列表渲染 + JS 定时轮换。
|
|
167
|
+
* 轮播仅在 banner/fullscreen/overlay 三种图片模式生效。
|
|
168
|
+
*/
|
|
169
|
+
enable: z.boolean().default(false),
|
|
170
|
+
/** 轮换间隔 ms */
|
|
171
|
+
interval: z.number().int().min(1000).default(5000),
|
|
172
|
+
/** 过渡效果:fade 淡入淡出 / zoom 缩放 / slide 滑动 / kenburns 缓慢推近 */
|
|
173
|
+
transitionEffect: z
|
|
174
|
+
.enum(["fade", "zoom", "slide", "kenburns"])
|
|
175
|
+
.default("fade"),
|
|
176
|
+
})
|
|
177
|
+
.default({ enable: false, interval: 5000, transitionEffect: "fade" }),
|
|
178
|
+
})
|
|
179
|
+
.default({
|
|
180
|
+
dimOpacity: 0.2,
|
|
181
|
+
darkDimOpacity: 0.65,
|
|
182
|
+
playerMode: "random",
|
|
183
|
+
homeText: {
|
|
184
|
+
enable: true,
|
|
185
|
+
title: "",
|
|
186
|
+
titleSize: "3.8rem",
|
|
187
|
+
subtitle: [],
|
|
188
|
+
subtitleSize: "1.5rem",
|
|
189
|
+
typewriter: {
|
|
190
|
+
enable: true,
|
|
191
|
+
speed: 100,
|
|
192
|
+
deleteSpeed: 50,
|
|
193
|
+
pauseTime: 2000,
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
navbar: { transparentMode: "semi", enableBlur: true, blur: 5 },
|
|
197
|
+
waves: false,
|
|
198
|
+
gradient: false,
|
|
199
|
+
carousel: { enable: false, interval: 5000, transitionEffect: "fade" },
|
|
200
|
+
}),
|
|
201
|
+
/** banner 模式专属(横幅形态) */
|
|
202
|
+
banner: z
|
|
203
|
+
.object({
|
|
204
|
+
/** 横幅高度(vh),默认 60:壁纸从导航栏顶铺满至分类导航条处渐隐,非窄条 */
|
|
205
|
+
height: z.number().int().min(10).max(90).default(60),
|
|
206
|
+
/** true 仅首页显示;false 全站显示(overlay 天然全站,忽略此项) */
|
|
207
|
+
homeOnly: z.boolean().default(true),
|
|
208
|
+
/** 图片 object-position(center / center 30% 等) */
|
|
209
|
+
position: z.string().default("center"),
|
|
210
|
+
})
|
|
211
|
+
.default({ height: 60, homeOnly: true, position: "center" }),
|
|
212
|
+
/** fullscreen 模式专属 */
|
|
213
|
+
fullscreen: z
|
|
214
|
+
.object({
|
|
215
|
+
/** 图片 object-position(center / center 30% 等) */
|
|
216
|
+
position: z.string().default("center"),
|
|
217
|
+
/**
|
|
218
|
+
* 全屏布局(§4.103):classic 经典 = 滚下后正文区纯色背景(默认,旧行为);
|
|
219
|
+
* hero = 滚下后正文区无标题文字,壁纸 fixed 铺满 + 模糊渐变过渡,
|
|
220
|
+
* 卡片浮于模糊壁纸上(复用 overlay 的 blur/cardOpacity 参数)
|
|
221
|
+
*/
|
|
222
|
+
layout: z.enum(["classic", "hero"]).default("classic"),
|
|
223
|
+
})
|
|
224
|
+
.default({ position: "center", layout: "classic" }),
|
|
225
|
+
/** overlay 模式专属:fixed 垫底,正文卡片半透明透出壁纸 */
|
|
226
|
+
overlay: z
|
|
227
|
+
.object({
|
|
228
|
+
/** 壁纸层 z-index(默认 -1,位于 body 背景之上、内容之下) */
|
|
229
|
+
zIndex: z.number().default(-1),
|
|
230
|
+
/** 壁纸整体不透明度(0-1),配合蒙层控制透出强度 */
|
|
231
|
+
opacity: z.number().min(0).max(1).default(0.8),
|
|
232
|
+
/** 壁纸模糊半径 px(0-20),模糊时自动轻微放大防止边缘露底 */
|
|
233
|
+
blur: z.number().min(0).max(20).default(0),
|
|
234
|
+
/** 正文卡片背景不透明度(0-1):越小壁纸透出越多 */
|
|
235
|
+
cardOpacity: z.number().min(0).max(1).default(0.5),
|
|
236
|
+
})
|
|
237
|
+
.default({ zIndex: -1, opacity: 0.8, blur: 0, cardOpacity: 0.5 }),
|
|
238
|
+
});
|
|
239
|
+
|
|
40
240
|
/**
|
|
41
241
|
* 主题配置 schema(astro.config.ts 中 defineTheme({...}) 的入参)
|
|
42
242
|
* 后续新增配置项一律给 default,保证老站点升级后无需改动配置
|
|
@@ -54,7 +254,7 @@ export const themeConfigSchema = z.object({
|
|
|
54
254
|
* 文章页由 frontmatter tags 覆盖。留空则不输出该 meta。
|
|
55
255
|
*/
|
|
56
256
|
keywords: z.array(z.string()).default([]),
|
|
57
|
-
/** 导航栏 Logo(§4.20
|
|
257
|
+
/** 导航栏 Logo(§4.20):三格式图源(§4.97:public / 外链原样,相对路径相对站点 src/ 解析),留空只渲染文字标题 */
|
|
58
258
|
logo: z.string().default(""),
|
|
59
259
|
/** 作者名 */
|
|
60
260
|
author: z.string().default(""),
|
|
@@ -76,61 +276,12 @@ export const themeConfigSchema = z.object({
|
|
|
76
276
|
*/
|
|
77
277
|
pageWidth: z.number().int().min(800).max(1920).default(1600),
|
|
78
278
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
|
|
83
|
-
banner: z
|
|
84
|
-
.object({
|
|
85
|
-
/** 总开关:false 时即使配了 src/quote 也不渲染 */
|
|
86
|
-
enabled: z.boolean().default(true),
|
|
87
|
-
/** 图源(public 路径或外链):单图 URL 或多图数组(构建期随机取一);不配置则无图 */
|
|
88
|
-
src: z.union([z.string(), z.array(z.string())]).optional(),
|
|
89
|
-
/** banner 高度(vh) */
|
|
90
|
-
height: z.number().int().min(10).max(90).default(32),
|
|
91
|
-
/** 黑蒙层不透明度(0-1),深浅模式同色,保证文字可读 */
|
|
92
|
-
dimOpacity: z.number().min(0).max(1).default(0.2),
|
|
93
|
-
/** 图片 object-position(center / center 30% 等) */
|
|
94
|
-
position: z.string().default("center"),
|
|
95
|
-
/** true 仅首页显示,false 全站显示 */
|
|
96
|
-
homeOnly: z.boolean().default(true),
|
|
97
|
-
/** 主句,<em> 标签内文字会以强调色渲染;留空不渲染 hero 文案 */
|
|
98
|
-
quote: z.string().default(""),
|
|
99
|
-
/** 副句 */
|
|
100
|
-
sub: z.string().default(""),
|
|
101
|
-
})
|
|
102
|
-
.default({
|
|
103
|
-
enabled: true,
|
|
104
|
-
height: 32,
|
|
105
|
-
dimOpacity: 0.2,
|
|
106
|
-
position: "center",
|
|
107
|
-
homeOnly: true,
|
|
108
|
-
quote: "",
|
|
109
|
-
sub: "",
|
|
110
|
-
}),
|
|
111
|
-
/**
|
|
112
|
-
* 全站背景壁纸(§4.92):body 级 fixed 视口背景,与顶部 banner(§4.42)相互独立。
|
|
113
|
-
* 经遮罩压暗后从页缝与卡片间隙透出;正文卡片保持不透明保证可读。
|
|
279
|
+
* 统一壁纸体系(§4.98,breaking:替代原 banner §4.42 + background §4.92 两段)。
|
|
280
|
+
* banner 横幅 / fullscreen 全屏 / overlay 覆盖透明 / none 纯色四模式,
|
|
281
|
+
* 详见模块级 wallpaperSchema 注释。函数默认值在 wallpaper 键整体缺失时
|
|
282
|
+
* 递归补齐全部内层默认值。
|
|
114
283
|
*/
|
|
115
|
-
|
|
116
|
-
.object({
|
|
117
|
-
/** 总开关:false 时即使配了 src 也不渲染(默认关) */
|
|
118
|
-
enabled: z.boolean().default(false),
|
|
119
|
-
/** 图源(public 路径或外链):单图 URL 或多图数组(构建期随机取一) */
|
|
120
|
-
src: z.union([z.string(), z.array(z.string())]).optional(),
|
|
121
|
-
/** 浅色模式遮罩不透明度(0-1),压暗壁纸保证正文可读 */
|
|
122
|
-
dimOpacity: z.number().min(0).max(1).default(0.45),
|
|
123
|
-
/** 暗色模式遮罩不透明度(0-1),暗色下压更暗 */
|
|
124
|
-
darkDimOpacity: z.number().min(0).max(1).default(0.65),
|
|
125
|
-
/** 壁纸模糊半径 px(0-20):模糊时自动轻微放大防止边缘露底 */
|
|
126
|
-
blur: z.number().min(0).max(20).default(0),
|
|
127
|
-
})
|
|
128
|
-
.default({
|
|
129
|
-
enabled: false,
|
|
130
|
-
dimOpacity: 0.45,
|
|
131
|
-
darkDimOpacity: 0.65,
|
|
132
|
-
blur: 0,
|
|
133
|
-
}),
|
|
284
|
+
wallpaper: wallpaperSchema.default(() => wallpaperSchema.parse({})),
|
|
134
285
|
/**
|
|
135
286
|
* 番组追番页(§4.93):构建期从 bgm.tv v0 API 拉取用户「在看」动画收藏,
|
|
136
287
|
* 渲染静态 /bangumi 页。enabled 与 userId 同时满足才注入路由(默认关)。
|
|
@@ -255,7 +406,7 @@ export const themeConfigSchema = z.object({
|
|
|
255
406
|
*/
|
|
256
407
|
profile: z
|
|
257
408
|
.object({
|
|
258
|
-
/**
|
|
409
|
+
/** 头像图(三格式图源 §4.97;留空渲染昵称首字 fallback) */
|
|
259
410
|
avatar: z.string().default(""),
|
|
260
411
|
name: z.string().default(""),
|
|
261
412
|
bio: z.string().default(""),
|
|
@@ -384,6 +535,63 @@ export const themeConfigSchema = z.object({
|
|
|
384
535
|
adaptExternalImages: z.boolean().default(false),
|
|
385
536
|
})
|
|
386
537
|
.default({ adaptExternalImages: false }),
|
|
538
|
+
/**
|
|
539
|
+
* 显示设置面板(§4.103,对齐 Firefly):导航栏齿轮入口的三 Tab 浮层
|
|
540
|
+
* (外观/壁纸/特效),集中管理主题色、壁纸模式、全屏布局、壁纸设置、
|
|
541
|
+
* 透明设置与樱花特效的访客级开关,选择持久化 localStorage。
|
|
542
|
+
* enable=false(默认)不渲染齿轮与面板,维持 Header 独立调色板/壁纸切换按钮。
|
|
543
|
+
* 各 switchable 控制面板内对应项是否对访客可见;无可见项的 Tab 自动隐藏。
|
|
544
|
+
* 注意 default 必须写显式完整字面量:Zod v4 的 .default(x) 在输入缺失时
|
|
545
|
+
* 直接取 x、不做二次 parse,内层字段默认值不会补齐。
|
|
546
|
+
*/
|
|
547
|
+
displaySettings: z
|
|
548
|
+
.object({
|
|
549
|
+
/** 总开关:false(默认)不渲染齿轮入口与面板,维持现有独立按钮 */
|
|
550
|
+
enable: z.boolean().default(false),
|
|
551
|
+
/** 外观 Tab:主题色(迁入现有 hue 调色板) */
|
|
552
|
+
themeColorSwitchable: z.boolean().default(true),
|
|
553
|
+
/** 壁纸 Tab:壁纸模式四选一 */
|
|
554
|
+
wallpaperModeSwitchable: z.boolean().default(true),
|
|
555
|
+
/** 壁纸 Tab:全屏布局(经典/Hero),仅 fullscreen 模式下显示 */
|
|
556
|
+
fullscreenLayoutSwitchable: z.boolean().default(true),
|
|
557
|
+
/** 壁纸 Tab:首页壁纸标题开关(需 homeText.enable 且有文案) */
|
|
558
|
+
bannerTitleSwitchable: z.boolean().default(true),
|
|
559
|
+
/** 壁纸 Tab:壁纸轮播开关(需 carousel.enable,访客只能关不能开) */
|
|
560
|
+
bannerCarouselSwitchable: z.boolean().default(true),
|
|
561
|
+
/** 壁纸 Tab:水波纹动画开关(需 waves 配置开启) */
|
|
562
|
+
wavesSwitchable: z.boolean().default(true),
|
|
563
|
+
/** 壁纸 Tab:渐变过渡开关(需 gradient 配置开启) */
|
|
564
|
+
gradientSwitchable: z.boolean().default(true),
|
|
565
|
+
/**
|
|
566
|
+
* 壁纸 Tab:透明设置滑块组。布尔 = 总开关;对象分项控制。
|
|
567
|
+
* opacity 滑块仅 overlay 模式显示;blur/cardOpacity 在 overlay
|
|
568
|
+
* 与 fullscreen-hero 布局下都生效。
|
|
569
|
+
*/
|
|
570
|
+
overlaySwitchable: z
|
|
571
|
+
.union([
|
|
572
|
+
z.boolean(),
|
|
573
|
+
z.object({
|
|
574
|
+
opacity: z.boolean().default(true),
|
|
575
|
+
blur: z.boolean().default(true),
|
|
576
|
+
cardOpacity: z.boolean().default(true),
|
|
577
|
+
}),
|
|
578
|
+
])
|
|
579
|
+
.default({ opacity: true, blur: true, cardOpacity: true }),
|
|
580
|
+
/** 特效 Tab:樱花特效开关(需 sakura.enabled) */
|
|
581
|
+
sakuraSwitchable: z.boolean().default(true),
|
|
582
|
+
})
|
|
583
|
+
.default({
|
|
584
|
+
enable: false,
|
|
585
|
+
themeColorSwitchable: true,
|
|
586
|
+
wallpaperModeSwitchable: true,
|
|
587
|
+
fullscreenLayoutSwitchable: true,
|
|
588
|
+
bannerTitleSwitchable: true,
|
|
589
|
+
bannerCarouselSwitchable: true,
|
|
590
|
+
wavesSwitchable: true,
|
|
591
|
+
gradientSwitchable: true,
|
|
592
|
+
overlaySwitchable: { opacity: true, blur: true, cardOpacity: true },
|
|
593
|
+
sakuraSwitchable: true,
|
|
594
|
+
}),
|
|
387
595
|
/**
|
|
388
596
|
* 代码高亮(§4.61):Expressive Code(Astro 生态标准方案)接管代码块渲染,
|
|
389
597
|
* 替代 §4.17 自研 shiki 管线与 §4.37 自研折叠。light/dark 为 EC 支持的 Shiki
|
|
@@ -511,7 +719,7 @@ export const themeConfigSchema = z.object({
|
|
|
511
719
|
z.object({
|
|
512
720
|
title: z.string(),
|
|
513
721
|
site: z.string().url(),
|
|
514
|
-
/**
|
|
722
|
+
/** 头像图(三格式图源 §4.97;留空用字头像) */
|
|
515
723
|
image: z.string().default(""),
|
|
516
724
|
desc: z.string().default(""),
|
|
517
725
|
tags: z.array(z.string()).default([]),
|
|
@@ -534,7 +742,7 @@ export const themeConfigSchema = z.object({
|
|
|
534
742
|
/**
|
|
535
743
|
* 侧栏 widget 体系(P2 侧栏改造):
|
|
536
744
|
* - widgets 数组即渲染顺序,可自由增删/排序/开关;类型:
|
|
537
|
-
* announcement / categories / tags / friends / calendar / toc / siteInfo / advertisement / profile
|
|
745
|
+
* announcement / categories / tags / friends / calendar / toc / siteInfo / advertisement / profile / music
|
|
538
746
|
* - 公共配置(开关/显隐/位置)配置在数组项上;组件专属参数统一收进 item 的
|
|
539
747
|
* specificConfig 入口(§4.52,对齐参考项目):tags.specificConfig.count、
|
|
540
748
|
* friends.specificConfig.count/random、calendar.specificConfig.postTooltip
|
|
@@ -562,7 +770,7 @@ export const themeConfigSchema = z.object({
|
|
|
562
770
|
type: z.literal("tags"),
|
|
563
771
|
specificConfig: z
|
|
564
772
|
.object({ count: z.number().int().min(0).default(12) })
|
|
565
|
-
.default({}),
|
|
773
|
+
.default({ count: 12 }),
|
|
566
774
|
}),
|
|
567
775
|
/**
|
|
568
776
|
* 友链 widget:specificConfig.count 展示数量;random=true 构建期随机取样,
|
|
@@ -575,7 +783,7 @@ export const themeConfigSchema = z.object({
|
|
|
575
783
|
count: z.number().int().min(1).default(3),
|
|
576
784
|
random: z.boolean().default(false),
|
|
577
785
|
})
|
|
578
|
-
.default({}),
|
|
786
|
+
.default({ count: 3, random: false }),
|
|
579
787
|
}),
|
|
580
788
|
sidebarWidgetBase.extend({
|
|
581
789
|
type: z.literal("calendar"),
|
|
@@ -584,9 +792,14 @@ export const themeConfigSchema = z.object({
|
|
|
584
792
|
/** 日历下方文章列表条目悬停显示全文 title(§4.41);默认关 */
|
|
585
793
|
postTooltip: z.boolean().default(false),
|
|
586
794
|
})
|
|
587
|
-
.default({}),
|
|
795
|
+
.default({ postTooltip: false }),
|
|
588
796
|
}),
|
|
589
797
|
sidebarWidgetBase.extend({ type: z.literal("toc") }),
|
|
798
|
+
/**
|
|
799
|
+
* 音乐 widget(§4.100):与 MusicPlayer 共用 config.music 歌单,
|
|
800
|
+
* 播放器未启用或歌单为空时不渲染;播放状态经 CustomEvent 联动
|
|
801
|
+
*/
|
|
802
|
+
sidebarWidgetBase.extend({ type: z.literal("music") }),
|
|
590
803
|
sidebarWidgetBase.extend({ type: z.literal("siteInfo") }),
|
|
591
804
|
sidebarWidgetBase.extend({ type: z.literal("advertisement") }),
|
|
592
805
|
sidebarWidgetBase.extend({ type: z.literal("profile") }),
|
|
@@ -595,9 +808,20 @@ export const themeConfigSchema = z.object({
|
|
|
595
808
|
.default([
|
|
596
809
|
{ type: "announcement" },
|
|
597
810
|
{ type: "categories", position: "sticky" },
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
{
|
|
811
|
+
// 注意:schema default 值不会二次 parse(default 原样返回),
|
|
812
|
+
// 因此数组项的 specificConfig 必须显式写全,运行时才有值
|
|
813
|
+
{
|
|
814
|
+
type: "tags",
|
|
815
|
+
position: "sticky",
|
|
816
|
+
specificConfig: { count: 12 },
|
|
817
|
+
},
|
|
818
|
+
{ type: "friends", specificConfig: { count: 3, random: false } },
|
|
819
|
+
{
|
|
820
|
+
type: "calendar",
|
|
821
|
+
hideOnMobile: true,
|
|
822
|
+
position: "sticky",
|
|
823
|
+
specificConfig: { postTooltip: false },
|
|
824
|
+
},
|
|
601
825
|
{
|
|
602
826
|
type: "toc",
|
|
603
827
|
postOnly: true,
|
|
@@ -613,9 +837,14 @@ export const themeConfigSchema = z.object({
|
|
|
613
837
|
widgets: [
|
|
614
838
|
{ type: "announcement" },
|
|
615
839
|
{ type: "categories", position: "sticky" },
|
|
616
|
-
{ type: "tags", position: "sticky" },
|
|
617
|
-
{ type: "friends" },
|
|
618
|
-
{
|
|
840
|
+
{ type: "tags", position: "sticky", specificConfig: { count: 12 } },
|
|
841
|
+
{ type: "friends", specificConfig: { count: 3, random: false } },
|
|
842
|
+
{
|
|
843
|
+
type: "calendar",
|
|
844
|
+
hideOnMobile: true,
|
|
845
|
+
position: "sticky",
|
|
846
|
+
specificConfig: { postTooltip: false },
|
|
847
|
+
},
|
|
619
848
|
{ type: "toc", postOnly: true, hideOnMobile: true, position: "sticky" },
|
|
620
849
|
{ type: "siteInfo" },
|
|
621
850
|
{ type: "advertisement" },
|
|
@@ -637,7 +866,7 @@ export const themeConfigSchema = z.object({
|
|
|
637
866
|
name: z.string(),
|
|
638
867
|
/** 文本/emoji 角标(主题不内置图标依赖) */
|
|
639
868
|
icon: z.string().default(""),
|
|
640
|
-
/**
|
|
869
|
+
/** 收款码图片(三格式图源 §4.97) */
|
|
641
870
|
image: z.string().default(""),
|
|
642
871
|
/** 跳转链接(爱发电/ko-fi 等) */
|
|
643
872
|
link: z.string().default(""),
|
|
@@ -651,6 +880,7 @@ export const themeConfigSchema = z.object({
|
|
|
651
880
|
.array(
|
|
652
881
|
z.object({
|
|
653
882
|
name: z.string(),
|
|
883
|
+
/** 头像图(三格式图源 §4.97;留空用字头像) */
|
|
654
884
|
avatar: z.string().default(""),
|
|
655
885
|
amount: z.string().default(""),
|
|
656
886
|
date: z.string().default(""),
|
|
@@ -822,7 +1052,7 @@ export const themeConfigSchema = z.object({
|
|
|
822
1052
|
.object({
|
|
823
1053
|
enabled: z.boolean().default(false),
|
|
824
1054
|
title: z.string().default("广告"),
|
|
825
|
-
/**
|
|
1055
|
+
/** 广告图(三格式图源 §4.97,留空跳过) */
|
|
826
1056
|
image: z.string().default(""),
|
|
827
1057
|
/** 点击广告图跳转的链接(留空则图不可点) */
|
|
828
1058
|
imageLink: z.string().default(""),
|
|
@@ -1050,6 +1280,7 @@ export const themeConfigSchema = z.object({
|
|
|
1050
1280
|
/**
|
|
1051
1281
|
* favicon 图标列表(§4.30):数组非空时替换默认 /favicon.svg;
|
|
1052
1282
|
* theme 指定 "light"/"dark" 时按系统深浅色经 media 切换(可配双图标)。
|
|
1283
|
+
* src 仅支持 public 路径 / 外链(不参与 src/assets 三格式解析,ico 无优化需求)。
|
|
1053
1284
|
*/
|
|
1054
1285
|
favicon: z
|
|
1055
1286
|
.array(
|
|
@@ -1079,13 +1310,74 @@ export const themeConfigSchema = z.object({
|
|
|
1079
1310
|
* 樱花飘落特效(§4.30):全站 canvas 粒子(程序化花瓣,零贴图资产),
|
|
1080
1311
|
* prefers-reduced-motion 自动停用,页签隐藏暂停。
|
|
1081
1312
|
*/
|
|
1313
|
+
/**
|
|
1314
|
+
* 樱花飘落特效(§4.30 / §4.101 八维参数对齐 Firefly):全站 canvas 粒子
|
|
1315
|
+
* (程序化花瓣,零贴图资产),prefers-reduced-motion 自动停用,页签隐藏暂停。
|
|
1316
|
+
* 注意:外层 default 必须全量展开嵌套值——zod 的 default 原样返回不二次 parse,
|
|
1317
|
+
* 写 `.default({})` 会导致深层字段运行时缺失。
|
|
1318
|
+
*/
|
|
1082
1319
|
sakura: z
|
|
1083
1320
|
.object({
|
|
1084
1321
|
enabled: z.boolean().default(false),
|
|
1085
1322
|
/** 同屏花瓣数 */
|
|
1086
1323
|
density: z.number().int().min(1).max(200).default(24),
|
|
1324
|
+
/** 飘落轮数:-1 无限循环;>=1 时落满轮数后不再重生,花瓣清空即停止动画 */
|
|
1325
|
+
limitTimes: z.number().int().min(-1).default(-1),
|
|
1326
|
+
/** canvas 层级(z-index),默认 100(低于 Header/悬浮球) */
|
|
1327
|
+
zIndex: z.number().int().default(100),
|
|
1328
|
+
/** 花瓣尺寸倍数区间(相对基准尺寸随机取值) */
|
|
1329
|
+
size: z
|
|
1330
|
+
.object({
|
|
1331
|
+
min: z.number().min(0.1).default(0.5),
|
|
1332
|
+
max: z.number().min(0.1).default(1.1),
|
|
1333
|
+
})
|
|
1334
|
+
.default({ min: 0.5, max: 1.1 }),
|
|
1335
|
+
/** 花瓣透明度区间 */
|
|
1336
|
+
opacity: z
|
|
1337
|
+
.object({
|
|
1338
|
+
min: z.number().min(0).max(1).default(0.3),
|
|
1339
|
+
max: z.number().min(0).max(1).default(0.9),
|
|
1340
|
+
})
|
|
1341
|
+
.default({ min: 0.3, max: 0.9 }),
|
|
1342
|
+
/** 运动速度:horizontal 水平漂移 px/帧(负值向左)、vertical 下落 px/帧、rotation 每帧自转弧度、fadeSpeed 临终淡出速度(不应大于 opacity.min) */
|
|
1343
|
+
speed: z
|
|
1344
|
+
.object({
|
|
1345
|
+
horizontal: z
|
|
1346
|
+
.object({
|
|
1347
|
+
min: z.number().default(-1.7),
|
|
1348
|
+
max: z.number().default(-1.2),
|
|
1349
|
+
})
|
|
1350
|
+
.default({ min: -1.7, max: -1.2 }),
|
|
1351
|
+
vertical: z
|
|
1352
|
+
.object({
|
|
1353
|
+
min: z.number().default(1.5),
|
|
1354
|
+
max: z.number().default(2.2),
|
|
1355
|
+
})
|
|
1356
|
+
.default({ min: 1.5, max: 2.2 }),
|
|
1357
|
+
rotation: z.number().default(0.03),
|
|
1358
|
+
fadeSpeed: z.number().default(0.03),
|
|
1359
|
+
})
|
|
1360
|
+
.default({
|
|
1361
|
+
horizontal: { min: -1.7, max: -1.2 },
|
|
1362
|
+
vertical: { min: 1.5, max: 2.2 },
|
|
1363
|
+
rotation: 0.03,
|
|
1364
|
+
fadeSpeed: 0.03,
|
|
1365
|
+
}),
|
|
1087
1366
|
})
|
|
1088
|
-
.default({
|
|
1367
|
+
.default({
|
|
1368
|
+
enabled: false,
|
|
1369
|
+
density: 24,
|
|
1370
|
+
limitTimes: -1,
|
|
1371
|
+
zIndex: 100,
|
|
1372
|
+
size: { min: 0.5, max: 1.1 },
|
|
1373
|
+
opacity: { min: 0.3, max: 0.9 },
|
|
1374
|
+
speed: {
|
|
1375
|
+
horizontal: { min: -1.7, max: -1.2 },
|
|
1376
|
+
vertical: { min: 1.5, max: 2.2 },
|
|
1377
|
+
rotation: 0.03,
|
|
1378
|
+
fadeSpeed: 0.03,
|
|
1379
|
+
},
|
|
1380
|
+
}),
|
|
1089
1381
|
/**
|
|
1090
1382
|
* 音乐播放器(§4.36):local 模式,自研播放内核(audio + LRC 解析),
|
|
1091
1383
|
* 根节点 transition:persist 跨页不断播。meting 在线模式预留后续扩展。
|
|
@@ -1110,7 +1402,7 @@ export const themeConfigSchema = z.object({
|
|
|
1110
1402
|
artist: z.string().default(""),
|
|
1111
1403
|
/** 音频地址(public 路径或外链) */
|
|
1112
1404
|
url: z.string(),
|
|
1113
|
-
/**
|
|
1405
|
+
/** 封面图(三格式图源 §4.97,可选,缺省渲染唱片图标) */
|
|
1114
1406
|
cover: z.string().optional(),
|
|
1115
1407
|
/** 歌词:.lrc 文件路径或内联 LRC 文本(可选,缺省无歌词) */
|
|
1116
1408
|
lrc: z.string().optional(),
|