@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,383 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* 显示设置面板(§4.103,对齐 Firefly):导航栏齿轮入口的三 Tab 浮层
|
|
4
|
+
* (外观/壁纸/特效),集中管理主题色、壁纸模式、全屏布局、壁纸设置、
|
|
5
|
+
* 透明设置与樱花特效的访客级开关,选择持久化 localStorage(键表见 design.md)。
|
|
6
|
+
*
|
|
7
|
+
* 渲染条件:displaySettings.enable=true(BaseLayout 挂载处判定)。
|
|
8
|
+
* Tab 可见性:无可见项的 Tab 不渲染;仅一个 Tab 有内容时 Tab 栏隐藏。
|
|
9
|
+
* 组显隐:模式相关的组带 data-modes 属性(空格分隔的模式组合,特殊值
|
|
10
|
+
* fullscreen-hero 表示 fullscreen 且布局为 hero),由 app.ts 在模式/布局
|
|
11
|
+
* 切换时刷新显隐。开关/滑块初始态由 app.ts 按 localStorage > 配置默认填充。
|
|
12
|
+
*/
|
|
13
|
+
import config from "virtual:theme/config";
|
|
14
|
+
import { t } from "../i18n/index.js";
|
|
15
|
+
|
|
16
|
+
const ds = config.displaySettings;
|
|
17
|
+
const wp = config.wallpaper;
|
|
18
|
+
const homeText = wp.common.homeText;
|
|
19
|
+
|
|
20
|
+
/* ---- 壁纸 Tab 各组可见性(构建期判定,与 app.ts 运行时刷新口径一致) ---- */
|
|
21
|
+
const hasImages = wp.src.desktop.length > 0;
|
|
22
|
+
/* 壁纸模式组:有图源才有意义 */
|
|
23
|
+
const showMode = ds.wallpaperModeSwitchable && hasImages;
|
|
24
|
+
/* 全屏布局组:仅 fullscreen 模式下显示(运行时由 data-modes 门控) */
|
|
25
|
+
const showFsLayout = ds.fullscreenLayoutSwitchable && hasImages;
|
|
26
|
+
/* 壁纸设置组各项 */
|
|
27
|
+
const wpSubs = Array.isArray(homeText.subtitle)
|
|
28
|
+
? homeText.subtitle
|
|
29
|
+
: homeText.subtitle
|
|
30
|
+
? [homeText.subtitle]
|
|
31
|
+
: [];
|
|
32
|
+
const showHomeText =
|
|
33
|
+
ds.bannerTitleSwitchable &&
|
|
34
|
+
homeText.enable &&
|
|
35
|
+
(Boolean(homeText.title) || wpSubs.length > 0);
|
|
36
|
+
/* 轮播:仅 carousel.enable 时显示(配置关时 DOM 只渲染一张图,访客无图可轮,
|
|
37
|
+
语义=「关闭轮播」,与 Firefly 一致其 switchable 依赖配置开启) */
|
|
38
|
+
const showCarousel =
|
|
39
|
+
ds.bannerCarouselSwitchable &&
|
|
40
|
+
wp.common.carousel.enable &&
|
|
41
|
+
wp.src.desktop.length > 1;
|
|
42
|
+
const wavesCfg =
|
|
43
|
+
typeof wp.common.waves === "boolean"
|
|
44
|
+
? { desktop: wp.common.waves, mobile: wp.common.waves }
|
|
45
|
+
: wp.common.waves;
|
|
46
|
+
const showWaves = ds.wavesSwitchable && (wavesCfg.desktop || wavesCfg.mobile);
|
|
47
|
+
const gradCfg =
|
|
48
|
+
typeof wp.common.gradient === "boolean"
|
|
49
|
+
? { desktop: wp.common.gradient, mobile: wp.common.gradient }
|
|
50
|
+
: wp.common.gradient;
|
|
51
|
+
const showGradient =
|
|
52
|
+
ds.gradientSwitchable && (gradCfg.desktop || gradCfg.mobile);
|
|
53
|
+
const showWpSettings =
|
|
54
|
+
showHomeText || showCarousel || showWaves || showGradient;
|
|
55
|
+
/* 透明设置组:布尔简写展开为三分项同值 */
|
|
56
|
+
const ov =
|
|
57
|
+
typeof ds.overlaySwitchable === "boolean"
|
|
58
|
+
? {
|
|
59
|
+
opacity: ds.overlaySwitchable,
|
|
60
|
+
blur: ds.overlaySwitchable,
|
|
61
|
+
cardOpacity: ds.overlaySwitchable,
|
|
62
|
+
}
|
|
63
|
+
: ds.overlaySwitchable;
|
|
64
|
+
const showOverlaySettings = ov.opacity || ov.blur || ov.cardOpacity;
|
|
65
|
+
/* 各 Tab 可见性 */
|
|
66
|
+
const wallpaperTabVisible =
|
|
67
|
+
showMode || showFsLayout || showWpSettings || showOverlaySettings;
|
|
68
|
+
const appearanceTabVisible = ds.themeColorSwitchable && config.huePanel.enabled;
|
|
69
|
+
const effectsTabVisible = ds.sakuraSwitchable && config.sakura.enabled;
|
|
70
|
+
|
|
71
|
+
interface DsTab {
|
|
72
|
+
id: "appearance" | "wallpaper" | "effects";
|
|
73
|
+
label: string;
|
|
74
|
+
icon: string;
|
|
75
|
+
}
|
|
76
|
+
const tabs: DsTab[] = [];
|
|
77
|
+
if (appearanceTabVisible)
|
|
78
|
+
tabs.push({ id: "appearance", label: t("tabAppearance"), icon: "palette" });
|
|
79
|
+
if (wallpaperTabVisible)
|
|
80
|
+
tabs.push({ id: "wallpaper", label: t("tabWallpaper"), icon: "image" });
|
|
81
|
+
if (effectsTabVisible)
|
|
82
|
+
tabs.push({ id: "effects", label: t("tabEffects"), icon: "star" });
|
|
83
|
+
const showTabBar = tabs.length > 1;
|
|
84
|
+
|
|
85
|
+
/* hue 调色预设(与 Header palette-pop 同一组常量) */
|
|
86
|
+
const presetHues = [0, 30, 60, 100, 140, 170, 210, 250, 290, 330];
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
<div
|
|
90
|
+
class="ds-panel"
|
|
91
|
+
id="displaySettingsPanel"
|
|
92
|
+
hidden
|
|
93
|
+
role="dialog"
|
|
94
|
+
aria-label={t("displaySettings")}
|
|
95
|
+
data-def-fs-layout={wp.fullscreen.layout}
|
|
96
|
+
data-def-opacity={wp.overlay.opacity}
|
|
97
|
+
data-def-blur={wp.overlay.blur}
|
|
98
|
+
data-def-card-opacity={wp.overlay.cardOpacity}
|
|
99
|
+
>
|
|
100
|
+
{
|
|
101
|
+
showTabBar && (
|
|
102
|
+
<div class="ds-tabs" role="tablist">
|
|
103
|
+
{tabs.map((tab, i) => (
|
|
104
|
+
<button
|
|
105
|
+
class={`ds-tab${i === 0 ? " is-active" : ""}`}
|
|
106
|
+
type="button"
|
|
107
|
+
role="tab"
|
|
108
|
+
aria-selected={i === 0 ? "true" : "false"}
|
|
109
|
+
data-tab={tab.id}
|
|
110
|
+
>
|
|
111
|
+
{tab.icon === "palette" && (
|
|
112
|
+
<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="13.5" cy="6.5" r=".5" fill="currentColor" /><circle cx="17.5" cy="10.5" r=".5" fill="currentColor" /><circle cx="8.5" cy="7.5" r=".5" fill="currentColor" /><circle cx="6.5" cy="12.5" r=".5" fill="currentColor" /><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z" /></svg>
|
|
113
|
+
)}
|
|
114
|
+
{tab.icon === "image" && (
|
|
115
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2" /><circle cx="8.5" cy="8.5" r="1.5" /><path d="m21 15-5-5L5 21" /></svg>
|
|
116
|
+
)}
|
|
117
|
+
{tab.icon === "star" && (
|
|
118
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.2 2.2M16.2 16.2l2.2 2.2M5.6 18.4l2.2-2.2M16.2 7.8l2.2-2.2" /></svg>
|
|
119
|
+
)}
|
|
120
|
+
<span>{tab.label}</span>
|
|
121
|
+
</button>
|
|
122
|
+
))}
|
|
123
|
+
</div>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
<div class="ds-body">
|
|
127
|
+
{/* 外观 Tab:迁入现有 hue 调色板(DOM id 保留,app.ts palette 逻辑复用) */}
|
|
128
|
+
{
|
|
129
|
+
appearanceTabVisible && (
|
|
130
|
+
<section
|
|
131
|
+
class="ds-page is-active"
|
|
132
|
+
data-page="appearance"
|
|
133
|
+
role="tabpanel"
|
|
134
|
+
>
|
|
135
|
+
<div class="ds-group">
|
|
136
|
+
<div class="ds-group-title">{t("paletteTitle")}</div>
|
|
137
|
+
<div class="palette-row">
|
|
138
|
+
{presetHues.map((h) => (
|
|
139
|
+
<button
|
|
140
|
+
class="swatch"
|
|
141
|
+
type="button"
|
|
142
|
+
style={`--sw: hsl(${h} var(--accent-s) var(--accent-l))`}
|
|
143
|
+
data-hue={h}
|
|
144
|
+
aria-label={t("hueValue", { n: h })}
|
|
145
|
+
/>
|
|
146
|
+
))}
|
|
147
|
+
</div>
|
|
148
|
+
<input
|
|
149
|
+
class="hue-slider"
|
|
150
|
+
id="hueSlider"
|
|
151
|
+
type="range"
|
|
152
|
+
min="0"
|
|
153
|
+
max="360"
|
|
154
|
+
step="1"
|
|
155
|
+
aria-label={t("paletteTitle")}
|
|
156
|
+
/>
|
|
157
|
+
<div class="palette-foot">
|
|
158
|
+
<span class="hue-badge" id="hueBadge" />
|
|
159
|
+
<button class="palette-reset" id="hueReset" type="button">
|
|
160
|
+
{t("reset")}
|
|
161
|
+
</button>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</section>
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
{/* 壁纸 Tab */}
|
|
168
|
+
{
|
|
169
|
+
wallpaperTabVisible && (
|
|
170
|
+
<section
|
|
171
|
+
class={`ds-page${appearanceTabVisible ? "" : " is-active"}`}
|
|
172
|
+
data-page="wallpaper"
|
|
173
|
+
role="tabpanel"
|
|
174
|
+
>
|
|
175
|
+
{showMode && (
|
|
176
|
+
<div class="ds-group" data-modes="banner fullscreen overlay none">
|
|
177
|
+
<div class="ds-group-title">{t("wallpaperMode")}</div>
|
|
178
|
+
<div class="ds-mode-grid">
|
|
179
|
+
<button class="ds-mode-btn" type="button" data-mode="banner">
|
|
180
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="5" width="18" height="8" rx="2" /><path d="M3 17h18" /></svg>
|
|
181
|
+
<span>{t("modeBanner")}</span>
|
|
182
|
+
</button>
|
|
183
|
+
<button class="ds-mode-btn" type="button" data-mode="fullscreen">
|
|
184
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2" /><circle cx="8.5" cy="8.5" r="1.5" /><path d="m21 15-5-5L5 21" /></svg>
|
|
185
|
+
<span>{t("modeFullscreen")}</span>
|
|
186
|
+
</button>
|
|
187
|
+
<button class="ds-mode-btn" type="button" data-mode="overlay">
|
|
188
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2" /><rect x="6" y="6" width="12" height="12" rx="1.5" fill="currentColor" opacity="0.25" stroke="none" /></svg>
|
|
189
|
+
<span>{t("modeOverlay")}</span>
|
|
190
|
+
</button>
|
|
191
|
+
<button class="ds-mode-btn" type="button" data-mode="none">
|
|
192
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2" /><path d="m5 19 14-14" /></svg>
|
|
193
|
+
<span>{t("modeNone")}</span>
|
|
194
|
+
</button>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
)}
|
|
198
|
+
{showFsLayout && (
|
|
199
|
+
<div class="ds-group" data-modes="fullscreen">
|
|
200
|
+
<div class="ds-group-title">{t("fullscreenLayout")}</div>
|
|
201
|
+
<div class="ds-mode-grid">
|
|
202
|
+
<button class="ds-mode-btn" type="button" data-fs-layout="classic">
|
|
203
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="10" rx="2" /><path d="M5 17h14M5 20h9" /></svg>
|
|
204
|
+
<span>{t("layoutClassic")}</span>
|
|
205
|
+
</button>
|
|
206
|
+
<button class="ds-mode-btn" type="button" data-fs-layout="hero">
|
|
207
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2" /><rect x="6" y="9" width="12" height="9" rx="1.5" fill="currentColor" opacity="0.25" stroke="none" /><path d="M8 6.5h8" /></svg>
|
|
208
|
+
<span>{t("layoutHero")}</span>
|
|
209
|
+
</button>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
)}
|
|
213
|
+
{showWpSettings && (
|
|
214
|
+
<div class="ds-group" data-modes="banner fullscreen">
|
|
215
|
+
<div class="ds-group-title">
|
|
216
|
+
<span>{t("wallpaperSettings")}</span>
|
|
217
|
+
<button
|
|
218
|
+
class="ds-group-reset"
|
|
219
|
+
type="button"
|
|
220
|
+
data-reset-group="wallpaper"
|
|
221
|
+
aria-label={t("reset")}
|
|
222
|
+
title={t("reset")}
|
|
223
|
+
>
|
|
224
|
+
↺
|
|
225
|
+
</button>
|
|
226
|
+
</div>
|
|
227
|
+
{showHomeText && (
|
|
228
|
+
<div class="ds-switch-row">
|
|
229
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7V5h16v2M9 20h6M12 5v15" /></svg>
|
|
230
|
+
<span class="ds-row-label">{t("homeWallpaperTitle")}</span>
|
|
231
|
+
<button
|
|
232
|
+
class="ds-switch"
|
|
233
|
+
type="button"
|
|
234
|
+
role="switch"
|
|
235
|
+
aria-checked="true"
|
|
236
|
+
data-ds-switch="home-text"
|
|
237
|
+
>
|
|
238
|
+
<span class="ds-switch-knob" />
|
|
239
|
+
</button>
|
|
240
|
+
</div>
|
|
241
|
+
)}
|
|
242
|
+
{showCarousel && (
|
|
243
|
+
<div class="ds-switch-row">
|
|
244
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="7" width="15" height="15" rx="2" /><path d="M7 7V5a2 2 0 0 1 2-2h11a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2h-2" /></svg>
|
|
245
|
+
<span class="ds-row-label">{t("wallpaperCarousel")}</span>
|
|
246
|
+
<button
|
|
247
|
+
class="ds-switch"
|
|
248
|
+
type="button"
|
|
249
|
+
role="switch"
|
|
250
|
+
aria-checked="true"
|
|
251
|
+
data-ds-switch="carousel"
|
|
252
|
+
>
|
|
253
|
+
<span class="ds-switch-knob" />
|
|
254
|
+
</button>
|
|
255
|
+
</div>
|
|
256
|
+
)}
|
|
257
|
+
{showWaves && (
|
|
258
|
+
<div class="ds-switch-row">
|
|
259
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 12c2.5-3 5-3 7.5 0s5 3 7.5 0 3.5-2.4 5-1" /><path d="M2 17c2.5-3 5-3 7.5 0s5 3 7.5 0 3.5-2.4 5-1" /></svg>
|
|
260
|
+
<span class="ds-row-label">{t("wavesAnimation")}</span>
|
|
261
|
+
<button
|
|
262
|
+
class="ds-switch"
|
|
263
|
+
type="button"
|
|
264
|
+
role="switch"
|
|
265
|
+
aria-checked="true"
|
|
266
|
+
data-ds-switch="waves"
|
|
267
|
+
>
|
|
268
|
+
<span class="ds-switch-knob" />
|
|
269
|
+
</button>
|
|
270
|
+
</div>
|
|
271
|
+
)}
|
|
272
|
+
{showGradient && (
|
|
273
|
+
<div class="ds-switch-row">
|
|
274
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="16" rx="2" /><path d="M3 14c4 0 6 2 9 2s5-2 9-2" opacity="0.4" /><path d="M3 17h18" opacity="0.7" /></svg>
|
|
275
|
+
<span class="ds-row-label">{t("gradientTransition")}</span>
|
|
276
|
+
<button
|
|
277
|
+
class="ds-switch"
|
|
278
|
+
type="button"
|
|
279
|
+
role="switch"
|
|
280
|
+
aria-checked="true"
|
|
281
|
+
data-ds-switch="gradient"
|
|
282
|
+
>
|
|
283
|
+
<span class="ds-switch-knob" />
|
|
284
|
+
</button>
|
|
285
|
+
</div>
|
|
286
|
+
)}
|
|
287
|
+
</div>
|
|
288
|
+
)}
|
|
289
|
+
{showOverlaySettings && (
|
|
290
|
+
<div class="ds-group" data-modes="overlay fullscreen-hero">
|
|
291
|
+
<div class="ds-group-title">
|
|
292
|
+
<span>{t("overlaySettings")}</span>
|
|
293
|
+
<button
|
|
294
|
+
class="ds-group-reset"
|
|
295
|
+
type="button"
|
|
296
|
+
data-reset-group="overlay"
|
|
297
|
+
aria-label={t("reset")}
|
|
298
|
+
title={t("reset")}
|
|
299
|
+
>
|
|
300
|
+
↺
|
|
301
|
+
</button>
|
|
302
|
+
</div>
|
|
303
|
+
{ov.opacity && (
|
|
304
|
+
<div class="ds-slider-row" data-modes="overlay">
|
|
305
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2" opacity="0.45" /><rect x="3" y="3" width="18" height="18" rx="2" /></svg>
|
|
306
|
+
<span class="ds-row-label">{t("wallpaperOpacity")}</span>
|
|
307
|
+
<input
|
|
308
|
+
class="ds-slider"
|
|
309
|
+
type="range"
|
|
310
|
+
min="0"
|
|
311
|
+
max="100"
|
|
312
|
+
step="1"
|
|
313
|
+
data-ds-slider="opacity"
|
|
314
|
+
aria-label={t("wallpaperOpacity")}
|
|
315
|
+
/>
|
|
316
|
+
<span class="ds-badge" data-ds-badge="opacity" />
|
|
317
|
+
</div>
|
|
318
|
+
)}
|
|
319
|
+
{ov.blur && (
|
|
320
|
+
<div class="ds-slider-row">
|
|
321
|
+
<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="12" cy="12" r="3" /><circle cx="12" cy="12" r="7" opacity="0.5" /><circle cx="12" cy="12" r="10" opacity="0.2" /></svg>
|
|
322
|
+
<span class="ds-row-label">{t("backgroundBlur")}</span>
|
|
323
|
+
<input
|
|
324
|
+
class="ds-slider"
|
|
325
|
+
type="range"
|
|
326
|
+
min="0"
|
|
327
|
+
max="20"
|
|
328
|
+
step="1"
|
|
329
|
+
data-ds-slider="blur"
|
|
330
|
+
aria-label={t("backgroundBlur")}
|
|
331
|
+
/>
|
|
332
|
+
<span class="ds-badge" data-ds-badge="blur" />
|
|
333
|
+
</div>
|
|
334
|
+
)}
|
|
335
|
+
{ov.cardOpacity && (
|
|
336
|
+
<div class="ds-slider-row">
|
|
337
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="6" rx="1.5" opacity="0.4" /><rect x="3" y="13" width="18" height="7" rx="1.5" /></svg>
|
|
338
|
+
<span class="ds-row-label">{t("cardOpacity")}</span>
|
|
339
|
+
<input
|
|
340
|
+
class="ds-slider"
|
|
341
|
+
type="range"
|
|
342
|
+
min="0"
|
|
343
|
+
max="100"
|
|
344
|
+
step="1"
|
|
345
|
+
data-ds-slider="card-opacity"
|
|
346
|
+
aria-label={t("cardOpacity")}
|
|
347
|
+
/>
|
|
348
|
+
<span class="ds-badge" data-ds-badge="card-opacity" />
|
|
349
|
+
</div>
|
|
350
|
+
)}
|
|
351
|
+
</div>
|
|
352
|
+
)}
|
|
353
|
+
</section>
|
|
354
|
+
)
|
|
355
|
+
}
|
|
356
|
+
{/* 特效 Tab */}
|
|
357
|
+
{
|
|
358
|
+
effectsTabVisible && (
|
|
359
|
+
<section
|
|
360
|
+
class={`ds-page${appearanceTabVisible || wallpaperTabVisible ? "" : " is-active"}`}
|
|
361
|
+
data-page="effects"
|
|
362
|
+
role="tabpanel"
|
|
363
|
+
>
|
|
364
|
+
<div class="ds-group">
|
|
365
|
+
<div class="ds-switch-row">
|
|
366
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3c-2 3-5 4-8 4 1.5 4 4 6.5 8 7-1-1.5-1-3 0-4.5M12 3c2 3 5 4 8 4-1.5 4-4 6.5-8 7" /><path d="M12 21c0-4 1-6 3-8" opacity="0.5" /></svg>
|
|
367
|
+
<span class="ds-row-label">{t("sakuraEffect")}</span>
|
|
368
|
+
<button
|
|
369
|
+
class="ds-switch"
|
|
370
|
+
type="button"
|
|
371
|
+
role="switch"
|
|
372
|
+
aria-checked="true"
|
|
373
|
+
data-ds-switch="sakura"
|
|
374
|
+
>
|
|
375
|
+
<span class="ds-switch-knob" />
|
|
376
|
+
</button>
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
379
|
+
</section>
|
|
380
|
+
)
|
|
381
|
+
}
|
|
382
|
+
</div>
|
|
383
|
+
</div>
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
import config from "virtual:theme/config";
|
|
3
3
|
import { t } from "../i18n/index.js";
|
|
4
|
+
import { resolveSiteImage } from "../utils/image.js";
|
|
4
5
|
|
|
5
6
|
const { title, nav, logo } = config;
|
|
7
|
+
/* 三格式图源(§4.97):相对路径相对站点 src/ 构建期解析 */
|
|
8
|
+
const logoSrc = logo ? await resolveSiteImage(logo) : "";
|
|
6
9
|
|
|
7
10
|
/** 外链统一属性(§4.16):新窗口 + noopener */
|
|
8
11
|
function externalAttrs(external?: boolean) {
|
|
@@ -24,6 +27,16 @@ function navKey(href: string): string | null {
|
|
|
24
27
|
|
|
25
28
|
/* hue 调色面板(M3b-b5):预设色板为代码级常量;运行时覆写 --accent-h,s/l 保持配置值 */
|
|
26
29
|
const hueEnabled = config.huePanel.enabled;
|
|
30
|
+
|
|
31
|
+
/* 壁纸控制按钮(§4.98):switchable 模式循环切换 + 视频播放器控制;
|
|
32
|
+
无图源时切换按钮无意义不渲染,无视频源时播放器按钮不渲染(构建期判定) */
|
|
33
|
+
const wp = config.wallpaper;
|
|
34
|
+
const wpSwitchable = wp.switchable && wp.src.desktop.length > 0;
|
|
35
|
+
const wpPlayer = wp.playerEnable && wp.src.playerUrl.length > 0;
|
|
36
|
+
const wpPlayerMulti = wpPlayer && wp.src.playerUrl.length > 1;
|
|
37
|
+
/* 显示设置面板(§4.103):enable 时渲染齿轮入口,调色板/壁纸模式切换并入面板,
|
|
38
|
+
Header 不再渲染对应独立按钮(功能由面板内的同 id 控件接管) */
|
|
39
|
+
const dsEnable = config.displaySettings.enable;
|
|
27
40
|
const presetHues = [0, 30, 60, 100, 140, 170, 210, 250, 290, 330];
|
|
28
41
|
---
|
|
29
42
|
|
|
@@ -31,7 +44,7 @@ const presetHues = [0, 30, 60, 100, 140, 170, 210, 250, 290, 330];
|
|
|
31
44
|
<div class="shell header-inner">
|
|
32
45
|
{/* Logo(§4.20):配置非空时标题左侧渲染,与文字同行 */}
|
|
33
46
|
<a class="site-title" href="/">
|
|
34
|
-
{logo && <img class="nav-logo" src={
|
|
47
|
+
{logo && <img class="nav-logo" src={logoSrc} alt={config.title} />}
|
|
35
48
|
{title}
|
|
36
49
|
</a>
|
|
37
50
|
<nav class="site-nav" id="siteNav" aria-label={t('mainNav')}>
|
|
@@ -114,7 +127,8 @@ const presetHues = [0, 30, 60, 100, 140, 170, 210, 250, 290, 330];
|
|
|
114
127
|
</button>
|
|
115
128
|
)}
|
|
116
129
|
{
|
|
117
|
-
|
|
130
|
+
/* §4.103:显示设置面板开启时,独立调色板并入面板外观 Tab,不再渲染入口 */
|
|
131
|
+
hueEnabled && !dsEnable && (
|
|
118
132
|
<div class="palette-wrap">
|
|
119
133
|
<button class="icon-btn" id="paletteToggle" type="button" aria-label={t('palette')} aria-expanded="false">
|
|
120
134
|
<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="13.5" cy="6.5" r=".5" fill="currentColor"/><circle cx="17.5" cy="10.5" r=".5" fill="currentColor"/><circle cx="8.5" cy="7.5" r=".5" fill="currentColor"/><circle cx="6.5" cy="12.5" r=".5" fill="currentColor"/><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z"/></svg>
|
|
@@ -141,6 +155,46 @@ const presetHues = [0, 30, 60, 100, 140, 170, 210, 250, 290, 330];
|
|
|
141
155
|
</div>
|
|
142
156
|
)
|
|
143
157
|
}
|
|
158
|
+
{
|
|
159
|
+
/* 壁纸模式切换(§4.98):switchable 开启且有图源时渲染,点击循环
|
|
160
|
+
banner→fullscreen→overlay→none 并持久化 localStorage('qy-wallpaper');
|
|
161
|
+
§4.103:显示设置面板开启时并入面板壁纸 Tab,不再渲染入口 */
|
|
162
|
+
}
|
|
163
|
+
{wpSwitchable && !dsEnable && (
|
|
164
|
+
<button class="icon-btn" id="wallpaperModeToggle" type="button" aria-label={t('wpMode')} title={t('wpMode')}>
|
|
165
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="m21 15-5-5L5 21"/></svg>
|
|
166
|
+
</button>
|
|
167
|
+
)}
|
|
168
|
+
{
|
|
169
|
+
/* 显示设置面板入口(§4.103):齿轮按钮,开关 BaseLayout 尾部的三 Tab 浮层 */
|
|
170
|
+
}
|
|
171
|
+
{dsEnable && (
|
|
172
|
+
<button class="icon-btn" id="displaySettingsToggle" type="button" aria-label={t('displaySettings')} aria-expanded="false" title={t('displaySettings')}>
|
|
173
|
+
<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="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1-1.51H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
|
174
|
+
</button>
|
|
175
|
+
)}
|
|
176
|
+
{
|
|
177
|
+
/* 视频壁纸播放器控制(§4.98):播放/暂停 + 多视频时的前后切换;
|
|
178
|
+
play/pause 双图标由 stage[data-video-playing] 控制显隐 */
|
|
179
|
+
}
|
|
180
|
+
{wpPlayer && (
|
|
181
|
+
<>
|
|
182
|
+
{wpPlayerMulti && (
|
|
183
|
+
<button class="icon-btn" id="wpPlayerPrev" type="button" aria-label={t('wpPrev')} title={t('wpPrev')}>
|
|
184
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polygon points="19 20 9 12 19 4 19 20"/><line x1="5" y1="19" x2="5" y2="5"/></svg>
|
|
185
|
+
</button>
|
|
186
|
+
)}
|
|
187
|
+
<button class="icon-btn" id="wpPlayerToggle" type="button" aria-label={t('wpPlay')} title={t('wpPlay')}>
|
|
188
|
+
<svg class="i-play" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
|
189
|
+
<svg class="i-pause" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>
|
|
190
|
+
</button>
|
|
191
|
+
{wpPlayerMulti && (
|
|
192
|
+
<button class="icon-btn" id="wpPlayerNext" type="button" aria-label={t('wpNext')} title={t('wpNext')}>
|
|
193
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polygon points="5 4 15 12 5 20 5 4"/><line x1="19" y1="5" x2="19" y2="19"/></svg>
|
|
194
|
+
</button>
|
|
195
|
+
)}
|
|
196
|
+
</>
|
|
197
|
+
)}
|
|
144
198
|
<button class="icon-btn" id="modeToggle" type="button" aria-label={t('toggleMode')}>
|
|
145
199
|
<svg class="i-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg>
|
|
146
200
|
<svg class="i-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>
|
|
@@ -27,6 +27,7 @@ interface Props {
|
|
|
27
27
|
const { music } = Astro.props;
|
|
28
28
|
|
|
29
29
|
import { t } from "../i18n/index.js";
|
|
30
|
+
import { resolveSiteImage } from "../utils/image.js";
|
|
30
31
|
|
|
31
32
|
// 客户端动态文案(模式切换 / 播放暂停 aria 需要运行时替换)
|
|
32
33
|
const labels = {
|
|
@@ -37,6 +38,15 @@ const labels = {
|
|
|
37
38
|
modeRandom: t("musicModeRandom"),
|
|
38
39
|
noLyrics: t("musicNoLyrics"),
|
|
39
40
|
};
|
|
41
|
+
|
|
42
|
+
// 封面三格式解析(§4.97):构建期预解析整份歌单,data-playlist 序列化与
|
|
43
|
+
// SSR 首曲 <img> 共用解析结果——客户端 loadTrack 切歌时拿到的已是产物 URL
|
|
44
|
+
const resolvedPlaylist = await Promise.all(
|
|
45
|
+
music.playlist.map(async (tr) => ({
|
|
46
|
+
...tr,
|
|
47
|
+
cover: tr.cover ? await resolveSiteImage(tr.cover) : tr.cover,
|
|
48
|
+
})),
|
|
49
|
+
);
|
|
40
50
|
---
|
|
41
51
|
|
|
42
52
|
<div
|
|
@@ -46,7 +56,7 @@ const labels = {
|
|
|
46
56
|
data-position={music.position}
|
|
47
57
|
data-volume={music.volume}
|
|
48
58
|
data-play-mode={music.playMode}
|
|
49
|
-
data-playlist={JSON.stringify(
|
|
59
|
+
data-playlist={JSON.stringify(resolvedPlaylist)}
|
|
50
60
|
data-labels={JSON.stringify(labels)}
|
|
51
61
|
>
|
|
52
62
|
<!-- 收起态悬浮球:封面旋转 / 无封面唱片图标 -->
|
|
@@ -57,8 +67,8 @@ const labels = {
|
|
|
57
67
|
aria-label={t("musicOpen")}
|
|
58
68
|
title={t("musicOpen")}
|
|
59
69
|
>
|
|
60
|
-
{
|
|
61
|
-
<img src={
|
|
70
|
+
{resolvedPlaylist[0]?.cover ? (
|
|
71
|
+
<img src={resolvedPlaylist[0].cover} alt="" loading="lazy" />
|
|
62
72
|
) : (
|
|
63
73
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>
|
|
64
74
|
)}
|
|
@@ -68,8 +78,8 @@ const labels = {
|
|
|
68
78
|
<div class="mp-panel" role="region" aria-label={t("musicOpen")}>
|
|
69
79
|
<div class="mp-head">
|
|
70
80
|
<div class="mp-cover">
|
|
71
|
-
{
|
|
72
|
-
<img src={
|
|
81
|
+
{resolvedPlaylist[0]?.cover ? (
|
|
82
|
+
<img src={resolvedPlaylist[0].cover} alt="" loading="lazy" />
|
|
73
83
|
) : (
|
|
74
84
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>
|
|
75
85
|
)}
|
|
@@ -718,6 +728,16 @@ const labels = {
|
|
|
718
728
|
});
|
|
719
729
|
}
|
|
720
730
|
|
|
731
|
+
/* ---------- widget 联动(§4.100)---------- */
|
|
732
|
+
// 广播播放状态给侧栏音乐 widget(orchid:music-state)
|
|
733
|
+
function emitState() {
|
|
734
|
+
document.dispatchEvent(
|
|
735
|
+
new CustomEvent("orchid:music-state", {
|
|
736
|
+
detail: { idx, playing, mode },
|
|
737
|
+
}),
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
|
|
721
741
|
/* ---------- 核心播放 ---------- */
|
|
722
742
|
function syncModeUI() {
|
|
723
743
|
modeBtn.dataset.mode = mode;
|
|
@@ -728,6 +748,7 @@ const labels = {
|
|
|
728
748
|
mode === "list" ? labels.modeList : mode === "one" ? labels.modeOne : labels.modeRandom;
|
|
729
749
|
modeBtn.title = label;
|
|
730
750
|
modeBtn.setAttribute("aria-label", label);
|
|
751
|
+
emitState();
|
|
731
752
|
}
|
|
732
753
|
|
|
733
754
|
function syncPlayUI() {
|
|
@@ -736,6 +757,7 @@ const labels = {
|
|
|
736
757
|
playBtn.setAttribute("aria-label", playing ? labels.pause : labels.play);
|
|
737
758
|
root.classList.toggle("playing", playing);
|
|
738
759
|
renderPlaylist();
|
|
760
|
+
emitState();
|
|
739
761
|
}
|
|
740
762
|
|
|
741
763
|
function loadTrack(i: number, autoplay: boolean) {
|
|
@@ -759,6 +781,7 @@ const labels = {
|
|
|
759
781
|
lrcActive = -1;
|
|
760
782
|
loadLyrics(tr);
|
|
761
783
|
renderPlaylist();
|
|
784
|
+
emitState();
|
|
762
785
|
if (autoplay) {
|
|
763
786
|
const v = idx; // 竞态防护:捕获本次序号
|
|
764
787
|
audio
|
|
@@ -890,6 +913,23 @@ const labels = {
|
|
|
890
913
|
}
|
|
891
914
|
});
|
|
892
915
|
|
|
916
|
+
// 侧栏音乐 widget 命令(§4.100):toggle / next / prev / playIndex / sync
|
|
917
|
+
document.addEventListener("orchid:music-cmd", (e) => {
|
|
918
|
+
const cmd = (e as CustomEvent).detail as {
|
|
919
|
+
type: string;
|
|
920
|
+
index?: number;
|
|
921
|
+
};
|
|
922
|
+
if (cmd.type === "toggle") togglePlay();
|
|
923
|
+
else if (cmd.type === "next") playNext(false);
|
|
924
|
+
else if (cmd.type === "prev") playPrev();
|
|
925
|
+
else if (cmd.type === "playIndex" && typeof cmd.index === "number") {
|
|
926
|
+
if (cmd.index === idx && playing) audio.pause();
|
|
927
|
+
else loadTrack(cmd.index, true);
|
|
928
|
+
} else if (cmd.type === "sync") {
|
|
929
|
+
emitState();
|
|
930
|
+
}
|
|
931
|
+
});
|
|
932
|
+
|
|
893
933
|
// audio 生命周期
|
|
894
934
|
audio.addEventListener("play", () => {
|
|
895
935
|
playing = true;
|