cheymin commited on
Commit
e1ae2c6
·
verified ·
1 Parent(s): cf44c14

Upload 136 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .dockerignore +21 -0
  2. .editorconfig +12 -0
  3. .gitattributes +21 -35
  4. .gitignore +43 -0
  5. .husky/pre-commit +10 -0
  6. .prettierignore +29 -0
  7. .prettierrc.json +7 -0
  8. CHANGELOG.md +252 -0
  9. CNAME +1 -0
  10. Dockerfile +12 -61
  11. Dockerfile.static +38 -0
  12. LICENSE +661 -0
  13. README.md +382 -10
  14. assets/README.md +98 -0
  15. assets/menav.svg +6 -0
  16. assets/pinyin-match.js +1 -0
  17. assets/preview_dark.png +3 -0
  18. assets/preview_light.png +3 -0
  19. assets/style.css +33 -0
  20. assets/styles/_animations.css +49 -0
  21. assets/styles/_base.css +238 -0
  22. assets/styles/_cards.css +1365 -0
  23. assets/styles/_content.css +216 -0
  24. assets/styles/_dashboard.css +371 -0
  25. assets/styles/_layout.css +101 -0
  26. assets/styles/_main.css +1448 -0
  27. assets/styles/_modal.css +152 -0
  28. assets/styles/_search.css +306 -0
  29. assets/styles/_sidebar.css +633 -0
  30. assets/styles/_variables.css +147 -0
  31. bookmarks/.gitkeep +2 -0
  32. bookmarks/README.md +134 -0
  33. config/README.md +382 -0
  34. config/update-instructions-20251227.md +114 -0
  35. config/update-instructions-20260102.md +102 -0
  36. config/user/pages/articles.yml +43 -0
  37. config/user/pages/bookmarks.yml +251 -0
  38. config/user/pages/common.yml +143 -0
  39. config/user/pages/content.yml +12 -0
  40. config/user/pages/projects.yml +33 -0
  41. config/user/site.yml +126 -0
  42. content/about.md +65 -0
  43. docker-compose.yml +14 -0
  44. docker/entrypoint-build-and-serve.sh +25 -0
  45. docker/nginx/default.conf +30 -0
  46. licenses/pinyin-match.NOTICE.md +7 -0
  47. package-lock.json +2174 -0
  48. scripts/build-runtime.js +72 -0
  49. scripts/build.js +55 -0
  50. scripts/check.js +49 -0
.dockerignore ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .git
2
+ .github
3
+ .husky
4
+ .vscode
5
+ .idea
6
+
7
+ node_modules
8
+ dist
9
+ dev
10
+ coverage
11
+ .nyc_output
12
+
13
+ docs
14
+ *.log
15
+ npm-debug.log*
16
+
17
+ .specstory
18
+ .spec-workflow
19
+ .serena
20
+ .claude
21
+
.editorconfig ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ indent_style = space
8
+ indent_size = 2
9
+ trim_trailing_whitespace = true
10
+
11
+ [*.md]
12
+ trim_trailing_whitespace = false
.gitattributes CHANGED
@@ -1,35 +1,21 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ * text=auto
2
+
3
+ *.js text eol=lf
4
+ *.json text eol=lf
5
+ *.md text eol=lf
6
+ *.yml text eol=lf
7
+ *.yaml text eol=lf
8
+ *.css text eol=lf
9
+ *.html text eol=lf
10
+ *.sh text eol=lf
11
+
12
+ # Husky hooks 必须使用 LF
13
+ .husky/* text eol=lf
14
+
15
+ *.png binary
16
+ *.jpg binary
17
+ *.jpeg binary
18
+ *.gif binary
19
+ *.ico binary
20
+ assets/preview_dark.png filter=lfs diff=lfs merge=lfs -text
21
+ assets/preview_light.png filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.gitignore ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 依赖相关
2
+ node_modules/
3
+ npm-debug.log*
4
+
5
+ # 构建输出
6
+ dist/
7
+
8
+ # IDE/编辑器配置
9
+ .vscode/
10
+ .idea/
11
+ .specstory
12
+ .cursorindexingignore
13
+ .cursor
14
+ .spec-workflow/
15
+ .serena/
16
+
17
+ # 系统文件
18
+ .DS_Store
19
+ Thumbs.db
20
+
21
+
22
+ # 个人笔记
23
+ docs/
24
+
25
+ # 测试相关文件
26
+ dev/
27
+ jest.config.js
28
+ tests/
29
+ coverage/
30
+ .nyc_output/
31
+ jest-*.json
32
+ jest.results.json
33
+ .eslintcache
34
+ tests/screenshots/
35
+ tests/fixtures/
36
+ *.test.js
37
+ *.spec.js
38
+
39
+ # SpecStory explanation file
40
+ .specstory/.what-is-this.md
41
+ AGENTS.md
42
+ /.claude
43
+ /discord-style-navstation
.husky/pre-commit ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env sh
2
+
3
+ # 进入仓库根目录,避免在子目录执行 git commit 时找不到脚本/依赖
4
+ cd "$(dirname "$0")/.." || exit 1
5
+
6
+ # Windows Git Bash 下如果需要中文输出不乱码,可在个人环境中设置终端编码/locale
7
+ # 这里不强制设置 LANG/LC_ALL,避免在 Linux/macOS 上因 locale 不存在而产生警告
8
+
9
+ # 优先使用项目内的 lint-staged,避免依赖全局 PATH
10
+ ./node_modules/.bin/lint-staged
.prettierignore ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 依赖和构建产物
2
+ node_modules/
3
+ dist/
4
+ dev/
5
+
6
+ # npm lockfile(避免提交时产生大面积格式化 diff)
7
+ package-lock.json
8
+
9
+ # Git 配置文件
10
+ .gitattributes
11
+ .gitignore
12
+
13
+ # Husky hooks(shell 脚本,不需要 Prettier 格式化)
14
+ .husky/
15
+
16
+ # 缓存文件
17
+ *.cache
18
+ .cache/
19
+
20
+ # 日志文件
21
+ *.log
22
+
23
+ # 临时文件
24
+ *.tmp
25
+ *.temp
26
+
27
+ # 文档文件(保留排版灵活性,避免代码示例被格式化)
28
+ **/README.md
29
+ src/**/*.md
.prettierrc.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "printWidth": 100,
3
+ "singleQuote": true,
4
+ "trailingComma": "es5",
5
+ "tabWidth": 2,
6
+ "useTabs": false
7
+ }
CHANGELOG.md ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 更新记录
2
+
3
+ > 本文件维护 MeNav 历史更新记录。README 不再维护"更新记录"章节。
4
+
5
+ ### 2026/01/04
6
+
7
+ **1. 首屏性能优化**
8
+
9
+ - 移除首页副标题固定 Quicksand 外链字体,改为跟随全站字体
10
+ - 字体外链 CSS 支持 `fonts.preload: true`(`preload + onload` 非阻塞加载,含 `<noscript>` 回退)
11
+ - Font Awesome CSS 改为 `preload + onload` 非阻塞加载,降低 render-blocking 影响
12
+ - 构建阶段压缩 `style.css` / `script.js` / `pinyin-match.js`,减少传输体积
13
+
14
+ **2. 安全与部署稳定性**
15
+
16
+ - 链接安全加固:模板与运行时统一校验 URL scheme(不安全链接降级为 `#`),新增 `security.allowedSchemes` 支持显式放行自定义协议
17
+ - 去除外部输入的 `innerHTML` 拼接:分类标题更新/新增分类改用 DOM API 构建,降低注入风险
18
+ - `sync-articles` 对齐 best-effort:同步失败不再以非 0 退出码阻断构建/部署
19
+ - 版本号来源统一:`window.MeNav.version` 不再写死,自动读取构建注入版本(用于扩展/调试识别)
20
+
21
+ **3. 模板图标 helper(Breaking)**
22
+
23
+ - 模板 helper `faviconUrl` 更名为 `faviconV2Url`,避免与站点字段 `sites[].faviconUrl` 同名冲突;如有自定义模板调用 `{{faviconUrl url}}`,需同步改为 `{{faviconV2Url url}}`
24
+
25
+ ### 2026/01/03
26
+
27
+ 关联 Issue:[#31](https://github.com/rbetree/menav/issues/31)
28
+
29
+ **1. favicon 加载优化**
30
+
31
+ - 新增 `icons.region: com | cn` 配置项,允许用户选择优先使用国内源或国外源
32
+ - `com`(默认):优先 gstatic.com,失败回退 gstatic.cn
33
+ - `cn`:优先 gstatic.cn,失败回退 gstatic.com
34
+ - 修改 favicon 加载超时判断机制
35
+ - 自定义 faviconUrl:5秒超时后显示回退图标
36
+ - 自动 favicon:每次尝试3秒超时,最多等待6秒
37
+ - 避免网络慢时长时间显示加载动画
38
+
39
+ ### 2026/01/02
40
+
41
+ 关联 Issue:[#30](https://github.com/rbetree/menav/issues/30)
42
+
43
+ 细节见:[`config/update-instructions-20260102.md`](config/update-instructions-20260102.md)
44
+
45
+ **1. 外部资源可用性**
46
+
47
+ - Font Awesome:bootcdn → cdnjs(Cloudflare),降低被拦截风险
48
+ - favicon:`t3.gstatic.com` 失败自动回退 `t3.gstatic.cn`,提升国内网络可用性
49
+
50
+ **2. 图标模式与站点级覆盖**
51
+
52
+ - 修复 `site.yml -> icons.mode` 配置未生效(构建期提升为顶层 `icons.mode`,供模板/运行时统一读取)
53
+ - 新增站点级图标覆盖:`faviconUrl` / `forceIconMode: favicon | manual`(优先级:`faviconUrl` > `forceIconMode` > 全局 `icons.mode`)
54
+
55
+ **3. 嵌套交互与链接打开**
56
+
57
+ - 恢复二级分组折叠入口(桌面端默认隐藏,悬停/收起态显示,避免界面过密)
58
+ - 多级结构下递归补齐 `sites[].external` 默认值,保证站点链接默认新标签页打开
59
+
60
+ ### 2025/12/27
61
+
62
+ 细节见:[`config/update-instructions-20251227.md`](config/update-instructions-20251227.md)
63
+
64
+ **1. 页面模板差异化改进(Phase 1/Phase 2)**
65
+
66
+ - 首页判定规则调整:`site.yml -> navigation` 第一项即首页(不再依赖 `home` 页面/ID)
67
+ - 模板体系整理:通用 `page` + 特殊页 `projects/articles/bookmarks` + 内置 `search-results`
68
+ - `bookmarks` 标题后追加只读更新时间:`update: YYYY-MM-DD | from: git|mtime`
69
+ - `articles` Phase 2:RSS 聚合文章条目(只读 `data-type="article"`),按 `articles.yml` 分类聚合展示;保留隐藏写回结构避免干扰扩展
70
+ - `projects`:repo 风格卡片(language/stars/forks 自动抓取)+ 可选 GitHub 贡献热力图
71
+
72
+ **2. 工作流与时效性数据刷新**
73
+
74
+ - GitHub Actions 构建前自动执行 `sync-projects` / `sync-articles`
75
+ - 新增 `schedule` 定时触发刷新(cron 使用 UTC,可在 workflow 中调整)
76
+
77
+ **3. 配置与兼容清理(Breaking)**
78
+
79
+ - 移除旧版单文件配置 `config.yml/config.yaml` 回退
80
+ - 移除独立 `navigation.yml` 回退
81
+ - 移除 `pages/home.yml -> 顶层 categories` 与 `home` 子菜单特例
82
+ - `navigation[].active` 不再生效(首页/默认打开页始终由 `navigation` 第一项决定)
83
+
84
+ **4. 配置变更(字段新增/减少)**
85
+
86
+ - 新增:
87
+ - `site.rss.*`:articles RSS 抓取与缓存配置(用于 `npm run sync-articles`)
88
+ - `site.github.*`:projects 热力图与仓库元信息抓取缓存配置(用于 `npm run sync-projects`)
89
+ - `pages/<id>.yml -> template`:页面模板选择(缺省时按回退规则使用 `page`)
90
+ - 说明:
91
+ - “首页”始终由 `site.yml -> navigation` 第一项决定,不要求页面 id 为 `home`
92
+
93
+ ### 2025/12/23
94
+
95
+ **1. 侧边栏与导航交互优化**
96
+
97
+ - 高亮项有子菜单时会自动展开
98
+ - 侧边栏 `logo_text` 左侧展示站点 Logo(复用 `site.favicon`)
99
+
100
+ **2. 卡片层级折叠规则调整**
101
+
102
+ - 仅 1 层分类:一级分类支持下拉/收起
103
+ - 2/3 层分类:仅二级标题支持下拉/收起(一级/三级不提供折叠按钮与交互)
104
+
105
+ **3.页面细节**
106
+
107
+ - 主题蓝调整为 `#7694B9`,统一应用到高亮/渐变/阴���
108
+ - 搜索无结果红色状态图标对齐修复(避免图标位置偏移)
109
+ - `menav.svg` 优化暗色背景可读性(字母颜色加深)
110
+
111
+ ### 2025/11/09
112
+
113
+ **1. 默认配置与文档**
114
+
115
+ - 更新默认配置与项目 Logo,并同步完善 README
116
+
117
+ ### 2025/10/31
118
+
119
+ **1. 书签导入与嵌套结构**
120
+
121
+ - 优化书签转换逻辑与分类嵌套结构
122
+ - 修复书签转换脚本问题,提升稳定性
123
+
124
+ ### 2025/10/24 - 2025/10/27
125
+
126
+ **1. 分类/卡片交互与细节修复**
127
+
128
+ - 为各结构补齐下拉指示与交互,并新增“分类展开/收起”按钮
129
+ - 修复侧边栏切换图标错位、站点卡片悬浮层级遮挡问题
130
+ - 调整卡片间距与 category/group 栏样式效果,移除废弃的 `restructure` 命令
131
+
132
+ ### 2025/10/18
133
+
134
+ **1. 图标模式默认行为变更**
135
+
136
+ - 默认启用 `icons.mode: favicon`,自动根据站点 URL 加载 favicon(失败回退为 Font Awesome 图标)
137
+ - 如需关闭外部请求并完全使用手动图标,请在 `config/user/site.yml` 中设置:
138
+
139
+ ```yaml
140
+ # config/user/site.yml
141
+ icons:
142
+ mode: manual # 关闭 favicon 请求,纯手动图标
143
+ ```
144
+
145
+ ### 2025/10/14
146
+
147
+ **1. 拼音搜索支持**
148
+
149
+ - 支持中文拼音与首字母匹配检索(基于 `pinyin-match`)
150
+
151
+ ### 2025/07/30
152
+
153
+ **1. 链接打开行为一致性**
154
+
155
+ - 统一站点/导航外链为新标签页打开,改善导航体验
156
+
157
+ ### 2025/07/07
158
+
159
+ **1. UI 细节优化**
160
+
161
+ - 侧边栏显示与布局细节优化
162
+ - 明暗主题切换按钮样式改进
163
+ - 欢迎文本与布局对齐优化
164
+
165
+ ### 2025/05/22
166
+
167
+ **1. MeNav 浏览器扩展支持接口**
168
+
169
+ - 注入扩展元信息(`menav-config-data`)并输出 `dist/menav-config.json` 供扩展按需加载(避免把整站配置注入到 `index.html`)
170
+ - 暴露 `window.MeNav` 基础能力与 DOM 数据属性,支持元素精准定位与更新
171
+ - 为扩展推送与页面联动打通基础能力
172
+
173
+ ### 2025/05/16
174
+
175
+ **1. MarksVault 浏览器扩展集成**
176
+
177
+ - 支持与 [MarksVault](https://github.com/rbetree/MarksVault) 浏览器扩展集成
178
+ - 使用扩展自动推送书签文件到 MeNav
179
+ - 自动处理推送的书签文件并更新网站
180
+
181
+ ### 2025/05/09
182
+
183
+ **1. 搜索引擎集成功能**
184
+
185
+ - 集成Google、Bing、百度搜索引擎
186
+ - 通过搜索框图标一键切换不同搜索引擎
187
+ - 用户选择保存在本地,下次访问自动应用
188
+
189
+ ### 2025/05/08
190
+
191
+ **1. Handlebars模板系统重构**
192
+
193
+ - 使用Handlebars模板引擎重构整个前端生成系统
194
+ - 实现模块化、组件化的模板结构,包含layouts、pages和components
195
+ - 改进代码复用,提高可维护性和扩展性
196
+ - 优化HTML生成逻辑,提升性能和代码质量
197
+
198
+ ### 2025/05/04
199
+
200
+ **1. 移除双文件配置支持**
201
+
202
+ - 完全移除了对双文件配置方法的支持
203
+ - 简化了配置加载逻辑,现在仅支持模块化配置
204
+
205
+ ### 2025/05/03
206
+
207
+ **1. 侧边栏收回功能**
208
+
209
+ - 添加侧边栏折叠/展开按钮,位于Logo文本右侧
210
+ - 侧边栏平滑折叠/展开过渡
211
+
212
+ **2. 移动端UI优化**
213
+
214
+ - 修复搜索按钮和侧边栏按钮遮挡问题
215
+ - 点击侧边栏导航项后自动收起侧边栏
216
+
217
+ ### 2025/05/02
218
+
219
+ **1. 模块化配置**
220
+
221
+ - 支持将配置拆分为多个文件,便于管理和维护
222
+ - 引入配置目录结构,分离页面配置
223
+ - 配置统一采用模块化目录结构(`config/user/` / `config/_default/`)
224
+
225
+ ### 2025/05/01
226
+
227
+ **1. 页面布局优化**
228
+
229
+ - 优化了内容区域和侧边栏的间距,确保各种分辨率下内容不会贴近边缘
230
+ - 卡片与边框始终保持合理间距,避免在窄屏设备上与滚动条贴边
231
+ - 调整了搜索结果区域的边距,与常规分类保持样式一致性
232
+
233
+ **2. 网站卡片文本优化**
234
+
235
+ - 为站点卡片标题添加单行文本截断,过长标题显示省略号
236
+ - 为站点描述添加两行限制和省略号,保持卡片布局整洁
237
+ - 添加卡片悬停提示,方便查看完整信息
238
+
239
+ **3. 移动端显示增强**
240
+
241
+ - 优化了移动端卡片尺寸,一屏可显示更多网址
242
+ - 图标大小自适应,在小屏幕上更加紧凑
243
+ - 为不同尺寸移动设备(768px、480px、400px)提供递进式UI优化
244
+ - 减小卡片内边距和元素间距,增加屏幕利用率
245
+
246
+ **4. 书签导入功能**
247
+
248
+ - 支持从Chrome、Firefox和Edge浏览器导入HTML格式书签
249
+ - 自动处理书签文件,解析文件夹结构和链接
250
+ - 图标处理:默认加载站点 favicon;在 manual 模式下保留 Font Awesome 匹配
251
+ - 生成配置文件,无需手动录入即可批量导入网站链接
252
+ - 与GitHub Actions集成,全自动化的导入和部署流程
CNAME ADDED
@@ -0,0 +1 @@
 
 
1
+ home.346247.xyz
Dockerfile CHANGED
@@ -1,71 +1,22 @@
1
- # 使用 Node.js 官方镜像作为基础
2
- FROM node:20-alpine AS builder
3
 
4
- WORKDIR /app
5
-
6
- # 复制项目文件
7
- COPY . .
8
-
9
- # 安装依赖
10
- RUN npm ci --omit=dev || npm install
11
-
12
- # 构建项目(每次启动时执行,但镜像中预先构建一次)
13
- RUN npm run build || echo "Build will run at runtime"
14
-
15
- # ===== 运行阶段 =====
16
- FROM node:20-alpine
17
 
18
  WORKDIR /app
19
 
20
- # 安装 nginx 和运行时依赖
21
- RUN apk add --no-cache nginx
22
 
23
- # 复制构建产物和源码
24
- COPY --from=builder /app /app
25
 
26
- # 创建必要的目录
27
- RUN mkdir -p /app/dist /app/config /app/bookmarks /run/nginx /var/log/nginx
28
-
29
- # 复制 nginx 配置
30
- RUN cat > /etc/nginx/nginx.conf << 'EOF'
31
- pid /tmp/nginx.pid;
32
- events {
33
- worker_connections 1024;
34
- }
35
- http {
36
- include /etc/nginx/mime.types;
37
- default_type application/octet-stream;
38
- access_log /tmp/access.log;
39
- error_log /tmp/error.log;
40
-
41
- server {
42
- listen 7860;
43
- server_name localhost;
44
- root /app/dist;
45
- index index.html;
46
-
47
- location / {
48
- try_files $uri $uri/ /index.html;
49
- }
50
-
51
- location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
52
- expires 1y;
53
- add_header Cache-Control "public, immutable";
54
- }
55
- }
56
- }
57
- EOF
58
 
59
- # 设置环境变量
60
- ENV PORT=7860
61
- ENV MENAV_PORT=7860
62
- ENV MENAV_ENABLE_SYNC=false
63
- ENV MENAV_IMPORT_BOOKMARKS=false
64
 
65
- EXPOSE 7860
66
 
67
- # 启动脚本
68
- COPY entrypoint.sh /entrypoint.sh
69
- RUN chmod +x /entrypoint.sh
70
 
71
- CMD ["/entrypoint.sh"]
 
1
+ # 动态构建版本(默认):容器启动时执行 `npm run build` 生成 dist,再由 nginx 提供静态文件。
 
2
 
3
+ FROM node:22-alpine
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
  WORKDIR /app
6
 
7
+ ENV HUSKY=0
 
8
 
9
+ COPY package.json package-lock.json ./
10
+ RUN npm ci && apk add --no-cache nginx
11
 
12
+ COPY . .
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
+ COPY docker/nginx/default.conf /etc/nginx/http.d/default.conf
15
+ COPY docker/entrypoint-build-and-serve.sh /usr/local/bin/entrypoint-build-and-serve.sh
16
+ RUN chmod +x /usr/local/bin/entrypoint-build-and-serve.sh
 
 
17
 
18
+ EXPOSE 80
19
 
20
+ STOPSIGNAL SIGQUIT
 
 
21
 
22
+ ENTRYPOINT ["/usr/local/bin/entrypoint-build-and-serve.sh"]
Dockerfile.static ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 静态构建版本(可选):构建阶段生成 dist,运行阶段仅 nginx 提供静态文件。
2
+ # 默认 Docker 方案请使用仓库根目录的 Dockerfile(动态构建,可挂载配置并通过重启生效)。
3
+
4
+ FROM node:22-alpine AS builder
5
+
6
+ WORKDIR /app
7
+
8
+ ENV HUSKY=0
9
+
10
+ COPY package.json package-lock.json ./
11
+ RUN npm ci
12
+
13
+ COPY . .
14
+
15
+ ARG MENAV_ENABLE_SYNC=false
16
+ ARG MENAV_IMPORT_BOOKMARKS=false
17
+
18
+ RUN if [ "${MENAV_IMPORT_BOOKMARKS}" = "true" ]; then \
19
+ MENAV_BOOKMARKS_DETERMINISTIC=1 npm run import-bookmarks; \
20
+ fi \
21
+ && if [ "${MENAV_ENABLE_SYNC}" = "true" ]; then \
22
+ npm run build; \
23
+ else \
24
+ PROJECTS_ENABLED=false HEATMAP_ENABLED=false RSS_ENABLED=false npm run build; \
25
+ fi
26
+
27
+ FROM nginx:1.27-alpine AS runtime
28
+
29
+ WORKDIR /usr/share/nginx/html
30
+
31
+ COPY docker/nginx/default.conf /etc/nginx/conf.d/default.conf
32
+ COPY --from=builder /app/dist ./
33
+
34
+ EXPOSE 80
35
+
36
+ STOPSIGNAL SIGQUIT
37
+
38
+ CMD ["nginx", "-g", "daemon off;"]
LICENSE ADDED
@@ -0,0 +1,661 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ GNU AFFERO GENERAL PUBLIC LICENSE
2
+ Version 3, 19 November 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+ Preamble
9
+
10
+ The GNU Affero General Public License is a free, copyleft license for
11
+ software and other kinds of works, specifically designed to ensure
12
+ cooperation with the community in the case of network server software.
13
+
14
+ The licenses for most software and other practical works are designed
15
+ to take away your freedom to share and change the works. By contrast,
16
+ our General Public Licenses are intended to guarantee your freedom to
17
+ share and change all versions of a program--to make sure it remains free
18
+ software for all its users.
19
+
20
+ When we speak of free software, we are referring to freedom, not
21
+ price. Our General Public Licenses are designed to make sure that you
22
+ have the freedom to distribute copies of free software (and charge for
23
+ them if you wish), that you receive source code or can get it if you
24
+ want it, that you can change the software or use pieces of it in new
25
+ free programs, and that you know you can do these things.
26
+
27
+ Developers that use our General Public Licenses protect your rights
28
+ with two steps: (1) assert copyright on the software, and (2) offer
29
+ you this License which gives you legal permission to copy, distribute
30
+ and/or modify the software.
31
+
32
+ A secondary benefit of defending all users' freedom is that
33
+ improvements made in alternate versions of the program, if they
34
+ receive widespread use, become available for other developers to
35
+ incorporate. Many developers of free software are heartened and
36
+ encouraged by the resulting cooperation. However, in the case of
37
+ software used on network servers, this result may fail to come about.
38
+ The GNU General Public License permits making a modified version and
39
+ letting the public access it on a server without ever releasing its
40
+ source code to the public.
41
+
42
+ The GNU Affero General Public License is designed specifically to
43
+ ensure that, in such cases, the modified source code becomes available
44
+ to the community. It requires the operator of a network server to
45
+ provide the source code of the modified version running there to the
46
+ users of that server. Therefore, public use of a modified version, on
47
+ a publicly accessible server, gives the public access to the source
48
+ code of the modified version.
49
+
50
+ An older license, called the Affero General Public License and
51
+ published by Affero, was designed to accomplish similar goals. This is
52
+ a different license, not a version of the Affero GPL, but Affero has
53
+ released a new version of the Affero GPL which permits relicensing under
54
+ this license.
55
+
56
+ The precise terms and conditions for copying, distribution and
57
+ modification follow.
58
+
59
+ TERMS AND CONDITIONS
60
+
61
+ 0. Definitions.
62
+
63
+ "This License" refers to version 3 of the GNU Affero General Public License.
64
+
65
+ "Copyright" also means copyright-like laws that apply to other kinds of
66
+ works, such as semiconductor masks.
67
+
68
+ "The Program" refers to any copyrightable work licensed under this
69
+ License. Each licensee is addressed as "you". "Licensees" and
70
+ "recipients" may be individuals or organizations.
71
+
72
+ To "modify" a work means to copy from or adapt all or part of the work
73
+ in a fashion requiring copyright permission, other than the making of an
74
+ exact copy. The resulting work is called a "modified version" of the
75
+ earlier work or a work "based on" the earlier work.
76
+
77
+ A "covered work" means either the unmodified Program or a work based
78
+ on the Program.
79
+
80
+ To "propagate" a work means to do anything with it that, without
81
+ permission, would make you directly or secondarily liable for
82
+ infringement under applicable copyright law, except executing it on a
83
+ computer or modifying a private copy. Propagation includes copying,
84
+ distribution (with or without modification), making available to the
85
+ public, and in some countries other activities as well.
86
+
87
+ To "convey" a work means any kind of propagation that enables other
88
+ parties to make or receive copies. Mere interaction with a user through
89
+ a computer network, with no transfer of a copy, is not conveying.
90
+
91
+ An interactive user interface displays "Appropriate Legal Notices"
92
+ to the extent that it includes a convenient and prominently visible
93
+ feature that (1) displays an appropriate copyright notice, and (2)
94
+ tells the user that there is no warranty for the work (except to the
95
+ extent that warranties are provided), that licensees may convey the
96
+ work under this License, and how to view a copy of this License. If
97
+ the interface presents a list of user commands or options, such as a
98
+ menu, a prominent item in the list meets this criterion.
99
+
100
+ 1. Source Code.
101
+
102
+ The "source code" for a work means the preferred form of the work
103
+ for making modifications to it. "Object code" means any non-source
104
+ form of a work.
105
+
106
+ A "Standard Interface" means an interface that either is an official
107
+ standard defined by a recognized standards body, or, in the case of
108
+ interfaces specified for a particular programming language, one that
109
+ is widely used among developers working in that language.
110
+
111
+ The "System Libraries" of an executable work include anything, other
112
+ than the work as a whole, that (a) is included in the normal form of
113
+ packaging a Major Component, but which is not part of that Major
114
+ Component, and (b) serves only to enable use of the work with that
115
+ Major Component, or to implement a Standard Interface for which an
116
+ implementation is available to the public in source code form. A
117
+ "Major Component", in this context, means a major essential component
118
+ (kernel, window system, and so on) of the specific operating system
119
+ (if any) on which the executable work runs, or a compiler used to
120
+ produce the work, or an object code interpreter used to run it.
121
+
122
+ The "Corresponding Source" for a work in object code form means all
123
+ the source code needed to generate, install, and (for an executable
124
+ work) run the object code and to modify the work, including scripts to
125
+ control those activities. However, it does not include the work's
126
+ System Libraries, or general-purpose tools or generally available free
127
+ programs which are used unmodified in performing those activities but
128
+ which are not part of the work. For example, Corresponding Source
129
+ includes interface definition files associated with source files for
130
+ the work, and the source code for shared libraries and dynamically
131
+ linked subprograms that the work is specifically designed to require,
132
+ such as by intimate data communication or control flow between those
133
+ subprograms and other parts of the work.
134
+
135
+ The Corresponding Source need not include anything that users
136
+ can regenerate automatically from other parts of the Corresponding
137
+ Source.
138
+
139
+ The Corresponding Source for a work in source code form is that
140
+ same work.
141
+
142
+ 2. Basic Permissions.
143
+
144
+ All rights granted under this License are granted for the term of
145
+ copyright on the Program, and are irrevocable provided the stated
146
+ conditions are met. This License explicitly affirms your unlimited
147
+ permission to run the unmodified Program. The output from running a
148
+ covered work is covered by this License only if the output, given its
149
+ content, constitutes a covered work. This License acknowledges your
150
+ rights of fair use or other equivalent, as provided by copyright law.
151
+
152
+ You may make, run and propagate covered works that you do not
153
+ convey, without conditions so long as your license otherwise remains
154
+ in force. You may convey covered works to others for the sole purpose
155
+ of having them make modifications exclusively for you, or provide you
156
+ with facilities for running those works, provided that you comply with
157
+ the terms of this License in conveying all material for which you do
158
+ not control copyright. Those thus making or running the covered works
159
+ for you must do so exclusively on your behalf, under your direction
160
+ and control, on terms that prohibit them from making any copies of
161
+ your copyrighted material outside their relationship with you.
162
+
163
+ Conveying under any other circumstances is permitted solely under
164
+ the conditions stated below. Sublicensing is not allowed; section 10
165
+ makes it unnecessary.
166
+
167
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168
+
169
+ No covered work shall be deemed part of an effective technological
170
+ measure under any applicable law fulfilling obligations under article
171
+ 11 of the WIPO copyright treaty adopted on 20 December 1996, or
172
+ similar laws prohibiting or restricting circumvention of such
173
+ measures.
174
+
175
+ When you convey a covered work, you waive any legal power to forbid
176
+ circumvention of technological measures to the extent such circumvention
177
+ is effected by exercising rights under this License with respect to
178
+ the covered work, and you disclaim any intention to limit operation or
179
+ modification of the work as a means of enforcing, against the work's
180
+ users, your or third parties' legal rights to forbid circumvention of
181
+ technological measures.
182
+
183
+ 4. Conveying Verbatim Copies.
184
+
185
+ You may convey verbatim copies of the Program's source code as you
186
+ receive it, in any medium, provided that you conspicuously and
187
+ appropriately publish on each copy an appropriate copyright notice;
188
+ keep intact all notices stating that this License and any
189
+ non-permissive terms added in accord with section 7 apply to the code;
190
+ keep intact all notices of the absence of any warranty; and give all
191
+ recipients a copy of this License along with the Program.
192
+
193
+ You may charge any price or no price for each copy that you convey,
194
+ and you may offer support or warranty protection for a fee.
195
+
196
+ 5. Conveying Modified Source Versions.
197
+
198
+ You may convey a work based on the Program, or the modifications to
199
+ produce it from the Program, in the form of source code under the
200
+ terms of section 4, provided that you also meet all of these conditions:
201
+
202
+ a) The work must carry prominent notices stating that you modified
203
+ it, and giving a relevant date.
204
+
205
+ b) The work must carry prominent notices stating that it is
206
+ released under this License and any conditions added under section
207
+ 7. This requirement modifies the requirement in section 4 to
208
+ "keep intact all notices".
209
+
210
+ c) You must license the entire work, as a whole, under this
211
+ License to anyone who comes into possession of a copy. This
212
+ License will therefore apply, along with any applicable section 7
213
+ additional terms, to the whole of the work, and all its parts,
214
+ regardless of how they are packaged. This License gives no
215
+ permission to license the work in any other way, but it does not
216
+ invalidate such permission if you have separately received it.
217
+
218
+ d) If the work has interactive user interfaces, each must display
219
+ Appropriate Legal Notices; however, if the Program has interactive
220
+ interfaces that do not display Appropriate Legal Notices, your
221
+ work need not make them do so.
222
+
223
+ A compilation of a covered work with other separate and independent
224
+ works, which are not by their nature extensions of the covered work,
225
+ and which are not combined with it such as to form a larger program,
226
+ in or on a volume of a storage or distribution medium, is called an
227
+ "aggregate" if the compilation and its resulting copyright are not
228
+ used to limit the access or legal rights of the compilation's users
229
+ beyond what the individual works permit. Inclusion of a covered work
230
+ in an aggregate does not cause this License to apply to the other
231
+ parts of the aggregate.
232
+
233
+ 6. Conveying Non-Source Forms.
234
+
235
+ You may convey a covered work in object code form under the terms
236
+ of sections 4 and 5, provided that you also convey the
237
+ machine-readable Corresponding Source under the terms of this License,
238
+ in one of these ways:
239
+
240
+ a) Convey the object code in, or embodied in, a physical product
241
+ (including a physical distribution medium), accompanied by the
242
+ Corresponding Source fixed on a durable physical medium
243
+ customarily used for software interchange.
244
+
245
+ b) Convey the object code in, or embodied in, a physical product
246
+ (including a physical distribution medium), accompanied by a
247
+ written offer, valid for at least three years and valid for as
248
+ long as you offer spare parts or customer support for that product
249
+ model, to give anyone who possesses the object code either (1) a
250
+ copy of the Corresponding Source for all the software in the
251
+ product that is covered by this License, on a durable physical
252
+ medium customarily used for software interchange, for a price no
253
+ more than your reasonable cost of physically performing this
254
+ conveying of source, or (2) access to copy the
255
+ Corresponding Source from a network server at no charge.
256
+
257
+ c) Convey individual copies of the object code with a copy of the
258
+ written offer to provide the Corresponding Source. This
259
+ alternative is allowed only occasionally and noncommercially, and
260
+ only if you received the object code with such an offer, in accord
261
+ with subsection 6b.
262
+
263
+ d) Convey the object code by offering access from a designated
264
+ place (gratis or for a charge), and offer equivalent access to the
265
+ Corresponding Source in the same way through the same place at no
266
+ further charge. You need not require recipients to copy the
267
+ Corresponding Source along with the object code. If the place to
268
+ copy the object code is a network server, the Corresponding Source
269
+ may be on a different server (operated by you or a third party)
270
+ that supports equivalent copying facilities, provided you maintain
271
+ clear directions next to the object code saying where to find the
272
+ Corresponding Source. Regardless of what server hosts the
273
+ Corresponding Source, you remain obligated to ensure that it is
274
+ available for as long as needed to satisfy these requirements.
275
+
276
+ e) Convey the object code using peer-to-peer transmission, provided
277
+ you inform other peers where the object code and Corresponding
278
+ Source of the work are being offered to the general public at no
279
+ charge under subsection 6d.
280
+
281
+ A separable portion of the object code, whose source code is excluded
282
+ from the Corresponding Source as a System Library, need not be
283
+ included in conveying the object code work.
284
+
285
+ A "User Product" is either (1) a "consumer product", which means any
286
+ tangible personal property which is normally used for personal, family,
287
+ or household purposes, or (2) anything designed or sold for incorporation
288
+ into a dwelling. In determining whether a product is a consumer product,
289
+ doubtful cases shall be resolved in favor of coverage. For a particular
290
+ product received by a particular user, "normally used" refers to a
291
+ typical or common use of that class of product, regardless of the status
292
+ of the particular user or of the way in which the particular user
293
+ actually uses, or expects or is expected to use, the product. A product
294
+ is a consumer product regardless of whether the product has substantial
295
+ commercial, industrial or non-consumer uses, unless such uses represent
296
+ the only significant mode of use of the product.
297
+
298
+ "Installation Information" for a User Product means any methods,
299
+ procedures, authorization keys, or other information required to install
300
+ and execute modified versions of a covered work in that User Product from
301
+ a modified version of its Corresponding Source. The information must
302
+ suffice to ensure that the continued functioning of the modified object
303
+ code is in no case prevented or interfered with solely because
304
+ modification has been made.
305
+
306
+ If you convey an object code work under this section in, or with, or
307
+ specifically for use in, a User Product, and the conveying occurs as
308
+ part of a transaction in which the right of possession and use of the
309
+ User Product is transferred to the recipient in perpetuity or for a
310
+ fixed term (regardless of how the transaction is characterized), the
311
+ Corresponding Source conveyed under this section must be accompanied
312
+ by the Installation Information. But this requirement does not apply
313
+ if neither you nor any third party retains the ability to install
314
+ modified object code on the User Product (for example, the work has
315
+ been installed in ROM).
316
+
317
+ The requirement to provide Installation Information does not include a
318
+ requirement to continue to provide support service, warranty, or updates
319
+ for a work that has been modified or installed by the recipient, or for
320
+ the User Product in which it has been modified or installed. Access to a
321
+ network may be denied when the modification itself materially and
322
+ adversely affects the operation of the network or violates the rules and
323
+ protocols for communication across the network.
324
+
325
+ Corresponding Source conveyed, and Installation Information provided,
326
+ in accord with this section must be in a format that is publicly
327
+ documented (and with an implementation available to the public in
328
+ source code form), and must require no special password or key for
329
+ unpacking, reading or copying.
330
+
331
+ 7. Additional Terms.
332
+
333
+ "Additional permissions" are terms that supplement the terms of this
334
+ License by making exceptions from one or more of its conditions.
335
+ Additional permissions that are applicable to the entire Program shall
336
+ be treated as though they were included in this License, to the extent
337
+ that they are valid under applicable law. If additional permissions
338
+ apply only to part of the Program, that part may be used separately
339
+ under those permissions, but the entire Program remains governed by
340
+ this License without regard to the additional permissions.
341
+
342
+ When you convey a copy of a covered work, you may at your option
343
+ remove any additional permissions from that copy, or from any part of
344
+ it. (Additional permissions may be written to require their own
345
+ removal in certain cases when you modify the work.) You may place
346
+ additional permissions on material, added by you to a covered work,
347
+ for which you have or can give appropriate copyright permission.
348
+
349
+ Notwithstanding any other provision of this License, for material you
350
+ add to a covered work, you may (if authorized by the copyright holders of
351
+ that material) supplement the terms of this License with terms:
352
+
353
+ a) Disclaiming warranty or limiting liability differently from the
354
+ terms of sections 15 and 16 of this License; or
355
+
356
+ b) Requiring preservation of specified reasonable legal notices or
357
+ author attributions in that material or in the Appropriate Legal
358
+ Notices displayed by works containing it; or
359
+
360
+ c) Prohibiting misrepresentation of the origin of that material, or
361
+ requiring that modified versions of such material be marked in
362
+ reasonable ways as different from the original version; or
363
+
364
+ d) Limiting the use for publicity purposes of names of licensors or
365
+ authors of the material; or
366
+
367
+ e) Declining to grant rights under trademark law for use of some
368
+ trade names, trademarks, or service marks; or
369
+
370
+ f) Requiring indemnification of licensors and authors of that
371
+ material by anyone who conveys the material (or modified versions of
372
+ it) with contractual assumptions of liability to the recipient, for
373
+ any liability that these contractual assumptions directly impose on
374
+ those licensors and authors.
375
+
376
+ All other non-permissive additional terms are considered "further
377
+ restrictions" within the meaning of section 10. If the Program as you
378
+ received it, or any part of it, contains a notice stating that it is
379
+ governed by this License along with a term that is a further
380
+ restriction, you may remove that term. If a license document contains
381
+ a further restriction but permits relicensing or conveying under this
382
+ License, you may add to a covered work material governed by the terms
383
+ of that license document, provided that the further restriction does
384
+ not survive such relicensing or conveying.
385
+
386
+ If you add terms to a covered work in accord with this section, you
387
+ must place, in the relevant source files, a statement of the
388
+ additional terms that apply to those files, or a notice indicating
389
+ where to find the applicable terms.
390
+
391
+ Additional terms, permissive or non-permissive, may be stated in the
392
+ form of a separately written license, or stated as exceptions;
393
+ the above requirements apply either way.
394
+
395
+ 8. Termination.
396
+
397
+ You may not propagate or modify a covered work except as expressly
398
+ provided under this License. Any attempt otherwise to propagate or
399
+ modify it is void, and will automatically terminate your rights under
400
+ this License (including any patent licenses granted under the third
401
+ paragraph of section 11).
402
+
403
+ However, if you cease all violation of this License, then your
404
+ license from a particular copyright holder is reinstated (a)
405
+ provisionally, unless and until the copyright holder explicitly and
406
+ finally terminates your license, and (b) permanently, if the copyright
407
+ holder fails to notify you of the violation by some reasonable means
408
+ prior to 60 days after the cessation.
409
+
410
+ Moreover, your license from a particular copyright holder is
411
+ reinstated permanently if the copyright holder notifies you of the
412
+ violation by some reasonable means, this is the first time you have
413
+ received notice of violation of this License (for any work) from that
414
+ copyright holder, and you cure the violation prior to 30 days after
415
+ your receipt of the notice.
416
+
417
+ Termination of your rights under this section does not terminate the
418
+ licenses of parties who have received copies or rights from you under
419
+ this License. If your rights have been terminated and not permanently
420
+ reinstated, you do not qualify to receive new licenses for the same
421
+ material under section 10.
422
+
423
+ 9. Acceptance Not Required for Having Copies.
424
+
425
+ You are not required to accept this License in order to receive or
426
+ run a copy of the Program. Ancillary propagation of a covered work
427
+ occurring solely as a consequence of using peer-to-peer transmission
428
+ to receive a copy likewise does not require acceptance. However,
429
+ nothing other than this License grants you permission to propagate or
430
+ modify any covered work. These actions infringe copyright if you do
431
+ not accept this License. Therefore, by modifying or propagating a
432
+ covered work, you indicate your acceptance of this License to do so.
433
+
434
+ 10. Automatic Licensing of Downstream Recipients.
435
+
436
+ Each time you convey a covered work, the recipient automatically
437
+ receives a license from the original licensors, to run, modify and
438
+ propagate that work, subject to this License. You are not responsible
439
+ for enforcing compliance by third parties with this License.
440
+
441
+ An "entity transaction" is a transaction transferring control of an
442
+ organization, or substantially all assets of one, or subdividing an
443
+ organization, or merging organizations. If propagation of a covered
444
+ work results from an entity transaction, each party to that
445
+ transaction who receives a copy of the work also receives whatever
446
+ licenses to the work the party's predecessor in interest had or could
447
+ give under the previous paragraph, plus a right to possession of the
448
+ Corresponding Source of the work from the predecessor in interest, if
449
+ the predecessor has it or can get it with reasonable efforts.
450
+
451
+ You may not impose any further restrictions on the exercise of the
452
+ rights granted or affirmed under this License. For example, you may
453
+ not impose a license fee, royalty, or other charge for exercise of
454
+ rights granted under this License, and you may not initiate litigation
455
+ (including a cross-claim or counterclaim in a lawsuit) alleging that
456
+ any patent claim is infringed by making, using, selling, offering for
457
+ sale, or importing the Program or any portion of it.
458
+
459
+ 11. Patents.
460
+
461
+ A "contributor" is a copyright holder who authorizes use under this
462
+ License of the Program or a work on which the Program is based. The
463
+ work thus licensed is called the contributor's "contributor version".
464
+
465
+ A contributor's "essential patent claims" are all patent claims
466
+ owned or controlled by the contributor, whether already acquired or
467
+ hereafter acquired, that would be infringed by some manner, permitted
468
+ by this License, of making, using, or selling its contributor version,
469
+ but do not include claims that would be infringed only as a
470
+ consequence of further modification of the contributor version. For
471
+ purposes of this definition, "control" includes the right to grant
472
+ patent sublicenses in a manner consistent with the requirements of
473
+ this License.
474
+
475
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
476
+ patent license under the contributor's essential patent claims, to
477
+ make, use, sell, offer for sale, import and otherwise run, modify and
478
+ propagate the contents of its contributor version.
479
+
480
+ In the following three paragraphs, a "patent license" is any express
481
+ agreement or commitment, however denominated, not to enforce a patent
482
+ (such as an express permission to practice a patent or covenant not to
483
+ sue for patent infringement). To "grant" such a patent license to a
484
+ party means to make such an agreement or commitment not to enforce a
485
+ patent against the party.
486
+
487
+ If you convey a covered work, knowingly relying on a patent license,
488
+ and the Corresponding Source of the work is not available for anyone
489
+ to copy, free of charge and under the terms of this License, through a
490
+ publicly available network server or other readily accessible means,
491
+ then you must either (1) cause the Corresponding Source to be so
492
+ available, or (2) arrange to deprive yourself of the benefit of the
493
+ patent license for this particular work, or (3) arrange, in a manner
494
+ consistent with the requirements of this License, to extend the patent
495
+ license to downstream recipients. "Knowingly relying" means you have
496
+ actual knowledge that, but for the patent license, your conveying the
497
+ covered work in a country, or your recipient's use of the covered work
498
+ in a country, would infringe one or more identifiable patents in that
499
+ country that you have reason to believe are valid.
500
+
501
+ If, pursuant to or in connection with a single transaction or
502
+ arrangement, you convey, or propagate by procuring conveyance of, a
503
+ covered work, and grant a patent license to some of the parties
504
+ receiving the covered work authorizing them to use, propagate, modify
505
+ or convey a specific copy of the covered work, then the patent license
506
+ you grant is automatically extended to all recipients of the covered
507
+ work and works based on it.
508
+
509
+ A patent license is "discriminatory" if it does not include within
510
+ the scope of its coverage, prohibits the exercise of, or is
511
+ conditioned on the non-exercise of one or more of the rights that are
512
+ specifically granted under this License. You may not convey a covered
513
+ work if you are a party to an arrangement with a third party that is
514
+ in the business of distributing software, under which you make payment
515
+ to the third party based on the extent of your activity of conveying
516
+ the work, and under which the third party grants, to any of the
517
+ parties who would receive the covered work from you, a discriminatory
518
+ patent license (a) in connection with copies of the covered work
519
+ conveyed by you (or copies made from those copies), or (b) primarily
520
+ for and in connection with specific products or compilations that
521
+ contain the covered work, unless you entered into that arrangement,
522
+ or that patent license was granted, prior to 28 March 2007.
523
+
524
+ Nothing in this License shall be construed as excluding or limiting
525
+ any implied license or other defenses to infringement that may
526
+ otherwise be available to you under applicable patent law.
527
+
528
+ 12. No Surrender of Others' Freedom.
529
+
530
+ If conditions are imposed on you (whether by court order, agreement or
531
+ otherwise) that contradict the conditions of this License, they do not
532
+ excuse you from the conditions of this License. If you cannot convey a
533
+ covered work so as to satisfy simultaneously your obligations under this
534
+ License and any other pertinent obligations, then as a consequence you may
535
+ not convey it at all. For example, if you agree to terms that obligate you
536
+ to collect a royalty for further conveying from those to whom you convey
537
+ the Program, the only way you could satisfy both those terms and this
538
+ License would be to refrain entirely from conveying the Program.
539
+
540
+ 13. Remote Network Interaction; Use with the GNU General Public License.
541
+
542
+ Notwithstanding any other provision of this License, if you modify the
543
+ Program, your modified version must prominently offer all users
544
+ interacting with it remotely through a computer network (if your version
545
+ supports such interaction) an opportunity to receive the Corresponding
546
+ Source of your version by providing access to the Corresponding Source
547
+ from a network server at no charge, through some standard or customary
548
+ means of facilitating copying of software. This Corresponding Source
549
+ shall include the Corresponding Source for any work covered by version 3
550
+ of the GNU General Public License that is incorporated pursuant to the
551
+ following paragraph.
552
+
553
+ Notwithstanding any other provision of this License, you have
554
+ permission to link or combine any covered work with a work licensed
555
+ under version 3 of the GNU General Public License into a single
556
+ combined work, and to convey the resulting work. The terms of this
557
+ License will continue to apply to the part which is the covered work,
558
+ but the work with which it is combined will remain governed by version
559
+ 3 of the GNU General Public License.
560
+
561
+ 14. Revised Versions of this License.
562
+
563
+ The Free Software Foundation may publish revised and/or new versions of
564
+ the GNU Affero General Public License from time to time. Such new versions
565
+ will be similar in spirit to the present version, but may differ in detail to
566
+ address new problems or concerns.
567
+
568
+ Each version is given a distinguishing version number. If the
569
+ Program specifies that a certain numbered version of the GNU Affero General
570
+ Public License "or any later version" applies to it, you have the
571
+ option of following the terms and conditions either of that numbered
572
+ version or of any later version published by the Free Software
573
+ Foundation. If the Program does not specify a version number of the
574
+ GNU Affero General Public License, you may choose any version ever published
575
+ by the Free Software Foundation.
576
+
577
+ If the Program specifies that a proxy can decide which future
578
+ versions of the GNU Affero General Public License can be used, that proxy's
579
+ public statement of acceptance of a version permanently authorizes you
580
+ to choose that version for the Program.
581
+
582
+ Later license versions may give you additional or different
583
+ permissions. However, no additional obligations are imposed on any
584
+ author or copyright holder as a result of your choosing to follow a
585
+ later version.
586
+
587
+ 15. Disclaimer of Warranty.
588
+
589
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590
+ APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591
+ HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592
+ OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594
+ PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595
+ IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596
+ ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597
+
598
+ 16. Limitation of Liability.
599
+
600
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602
+ THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603
+ GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604
+ USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605
+ DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606
+ PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607
+ EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608
+ SUCH DAMAGES.
609
+
610
+ 17. Interpretation of Sections 15 and 16.
611
+
612
+ If the disclaimer of warranty and limitation of liability provided
613
+ above cannot be given local legal effect according to their terms,
614
+ reviewing courts shall apply local law that most closely approximates
615
+ an absolute waiver of all civil liability in connection with the
616
+ Program, unless a warranty or assumption of liability accompanies a
617
+ copy of the Program in return for a fee.
618
+
619
+ END OF TERMS AND CONDITIONS
620
+
621
+ How to Apply These Terms to Your New Programs
622
+
623
+ If you develop a new program, and you want it to be of the greatest
624
+ possible use to the public, the best way to achieve this is to make it
625
+ free software which everyone can redistribute and change under these terms.
626
+
627
+ To do so, attach the following notices to the program. It is safest
628
+ to attach them to the start of each source file to most effectively
629
+ state the exclusion of warranty; and each file should have at least
630
+ the "copyright" line and a pointer to where the full notice is found.
631
+
632
+ <one line to give the program's name and a brief idea of what it does.>
633
+ Copyright (C) <year> <name of author>
634
+
635
+ This program is free software: you can redistribute it and/or modify
636
+ it under the terms of the GNU Affero General Public License as published by
637
+ the Free Software Foundation, either version 3 of the License, or
638
+ (at your option) any later version.
639
+
640
+ This program is distributed in the hope that it will be useful,
641
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
642
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643
+ GNU Affero General Public License for more details.
644
+
645
+ You should have received a copy of the GNU Affero General Public License
646
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
647
+
648
+ Also add information on how to contact you by electronic and paper mail.
649
+
650
+ If your software can interact with users remotely through a computer
651
+ network, you should also make sure that it provides a way for users to
652
+ get its source. For example, if your program is a web application, its
653
+ interface could display a "Source" link that leads users to an archive
654
+ of the code. There are many ways you could offer source, and different
655
+ solutions will be better for different programs; see section 13 for the
656
+ specific requirements.
657
+
658
+ You should also get your employer (if you work as a programmer) or school,
659
+ if any, to sign a "copyright disclaimer" for the program, if necessary.
660
+ For more information on this, and how to apply and follow the GNU AGPL, see
661
+ <https://www.gnu.org/licenses/>.
README.md CHANGED
@@ -1,10 +1,382 @@
1
- ---
2
- title: Me
3
- emoji: 🐠
4
- colorFrom: yellow
5
- colorTo: blue
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div align="center">
2
+ <img src="assets/menav.svg" alt="MeNav" width="120">
3
+ <h1>MeNav</h1>
4
+ <p>
5
+ 一个轻量的个人导航网站
6
+ </p>
7
+ </div>
8
+
9
+ [![License](https://img.shields.io/badge/License-AGPL--3.0-blue.svg)](https://www.gnu.org/licenses/agpl-3.0.html)
10
+ [![GitHub stars](https://img.shields.io/github/stars/rbetree/menav)](https://github.com/rbetree/menav/stargazers)
11
+ [![GitHub forks](https://img.shields.io/github/forks/rbetree/menav)](https://github.com/rbetree/menav/network/members)
12
+
13
+ 📋 静态一键部署 | ⚡ 自动化构建 | 🔖 支持书签导入
14
+
15
+ > MeNav 是一个轻量级、高度可定制的个人导航网站生成器,让您轻松创建属于自己的导航主页。无需数据库和后端服务,完全静态部署,支持一键 Fork 部署到 GitHub Pages,还可以从浏览器书签一键导入网站。配合 [MarksVault](https://github.com/rbetree/MarksVault) 浏览器扩展,更支持书签自动同步和导航站自动更新。
16
+
17
+ 如果觉得项目有用,欢迎 Star/Fork 支持,谢谢!
18
+
19
+ [直接开始部署>>](#部署方式)
20
+
21
+ ## 预览
22
+
23
+ [在线访问](https://rbetree.github.io/menav/)
24
+
25
+ <p align="center">
26
+ <img src="assets/preview_light.png" alt="明亮主题预览" width="48%">
27
+ <img src="assets/preview_dark.png" alt="黑暗主题预览" width="48%">
28
+ </p>
29
+
30
+ ## 特点
31
+
32
+ - 简洁美观的响应式布局设计
33
+ - 集成外部搜索引擎
34
+ - 分类展示网站链接
35
+ - 模块化配置
36
+ - 支持从浏览器导入书签
37
+ - 支持2-4层级的多层级嵌套结构
38
+ - 与 [MarksVault](https://github.com/rbetree/MarksVault) 浏览器扩展集成,支持自动推送书签
39
+ - 可部署到GitHub Pages或任何类似的CI/CD服务
40
+
41
+ > 历史更新记录已迁移至 [`CHANGELOG.md`](CHANGELOG.md),README 不再维护该部分。
42
+
43
+
44
+ ## 技术栈
45
+
46
+ - HTML5 + CSS3
47
+ - JavaScript (原生)
48
+ - Handlebars 模板引擎
49
+ - Google Favicon API + Font Awesome 图标
50
+
51
+ ## 项目结构
52
+
53
+ ```text
54
+ menav/
55
+ ├── src/ # 生成器、书签处理、前端脚本(入口:src/generator.js)
56
+ ├── templates/ # Handlebars 模板(layouts/pages/components)
57
+ ├── config/ # 模块化配置
58
+ ├── assets/ # 静态资源
59
+ ├── bookmarks/ # 书签导入相关
60
+ └── dist/ # 构建产物
61
+ ```
62
+
63
+ ## 文档导航
64
+
65
+ - 历史更新记录(README 不再维护):[`CHANGELOG.md`](CHANGELOG.md)
66
+ - 更新说明2025/12/27(兼容性移除 / 迁移指南):[`config/update-instructions-20251227.md`](config/update-instructions-20251227.md)
67
+ - 配置系统(完全替换策略、目录结构、示例):[`config/README.md`](config/README.md)
68
+ - 书签导入(格式要求、流程、常见问题):[`bookmarks/README.md`](bookmarks/README.md)
69
+ - 模板系统(组件、回退、数据流):[`templates/README.md`](templates/README.md)
70
+ - 源码结构(各脚本职责):[`src/README.md`](src/README.md)
71
+ - Handlebars helpers(模板辅助函数):[`src/helpers/README.md`](src/helpers/README.md)
72
+ - 静态资源(样式/图片等):[`assets/README.md`](assets/README.md)
73
+
74
+ ## 快速开始
75
+
76
+ 用于本地开发预览与构建静态站点;在线部署见 [部署方式](#部署方式)。
77
+
78
+ <details>
79
+ <summary>点击展开</summary>
80
+
81
+ 通过以下步骤快速设置您的个人导航站:
82
+
83
+ 1. 克隆仓库
84
+
85
+ ```bash
86
+ git clone https://github.com/rbetree/menav.git
87
+ cd menav
88
+ ```
89
+
90
+ 2. 安装依赖
91
+
92
+ ```bash
93
+ # 安装依赖
94
+ npm install
95
+ ```
96
+
97
+ (本仓库的 GitHub Actions/CI 已改为使用 `npm ci`,以获得更稳定、可复现的依赖安装(基于 `package-lock.json`);本地开发可继续使用 `npm install`,也可直接使用 `npm ci`。)
98
+
99
+ 3. 完成配置(见[设置配置文件](#设置配置文件))
100
+ 4. 导入书签(可选)
101
+ - 将浏览器导出的HTML格式书签文件放入`bookmarks`目录
102
+ - 运行书签处理命令:
103
+
104
+ ```bash
105
+ npm run import-bookmarks
106
+ ```
107
+
108
+ - 若希望生成结果保持确定性(便于版本管理,减少时间戳导致的无意义 diff):
109
+
110
+ ```bash
111
+ MENAV_BOOKMARKS_DETERMINISTIC=1 npm run import-bookmarks
112
+ ```
113
+
114
+ - 系统会自动将书签转换为配置文件保存到`config/user/pages/bookmarks.yml`
115
+
116
+ - **注意**:`npm run dev`命令不会自动处理书签文件,必须先手动运行上述命令
117
+ - `npm run dev` 默认会刷新 `articles/projects` 的联网缓存(若你希望离线启动,请使用 `npm run dev:offline`)
118
+
119
+ 5. 构建
120
+
121
+ ```bash
122
+ # 启动开发服务器
123
+ npm run dev
124
+ ```
125
+
126
+ ```bash
127
+ # 离线启动开发服务器(不刷新联网缓存)
128
+ npm run dev:offline
129
+ ```
130
+
131
+ ```bash
132
+ # 生成静态HTML文件
133
+ npm run build
134
+ ```
135
+
136
+ 构建后的文件位于`dist`目录
137
+
138
+ 6. 提交前检查(推荐)
139
+
140
+ ```bash
141
+ # 一键检查(语法检查 + 单元测试 + 构建)
142
+ npm run check
143
+ ```
144
+
145
+ (可选)格式化代码:
146
+
147
+ ```bash
148
+ npm run format
149
+ ```
150
+
151
+ </details>
152
+
153
+ ## 部署方式
154
+
155
+ 用于将生成的静态站点发布到 服务器 or CI/CD;本地构建见 [快速开始](#快速开始)。
156
+
157
+ ### 快速部署到GitHub Pages(推荐)
158
+
159
+ <details>
160
+ <summary>点击展开</summary>
161
+
162
+ #### 第一步:前置设置
163
+
164
+ 1. Fork仓库:
165
+ - 点击右上角的 Fork 按钮复制此仓库到您的账号
166
+
167
+ 2. 启用Actions:
168
+ - 进入fork后的仓库
169
+ - 点击顶部的 "Actions" 标签页
170
+ - 点击绿色按钮 "I understand my workflows, go ahead and enable them"
171
+
172
+ 3. 配置Pages:
173
+ - 进入仓库的 Settings -> Pages
174
+ - 在 "Build and deployment" 部分
175
+ - Source: 选择 "GitHub Actions"
176
+
177
+ #### 第二步:自定义配置
178
+
179
+ 创建个人配置文件:
180
+
181
+ - **重要:** 始终创建自己的用户配置文件,不要直接修改默认配置文件
182
+ - 完成配置文件(见[设置配置文件](#设置配置文件))
183
+ - 提交您的配置文件到仓库
184
+
185
+ #### 第三步:等待自动部署
186
+
187
+ - GitHub Actions会自动检测您的更改
188
+ - 构建并部署您的网站
189
+ - 部署完成后,您可以在 Settings -> Pages 中找到您的网站地址
190
+ - 站点内容的“时效性数据”(RSS 文章聚合、projects 仓库统计)会由部署工作流在构建前自动刷新
191
+ - 也支持定时刷新:默认每天 UTC 02:00 触发一次(GitHub Actions cron 使用 UTC;北京时间=UTC+8,可在 `.github/workflows/deploy.yml` 中调整 `schedule.cron`)
192
+
193
+ **重要: Sync fork后需要手动触发工作流**:
194
+
195
+ - 当您使用GitHub界面上的"Sync fork"按钮同步本仓库的更新后
196
+ - GitHub Actions工作流不会自动运行
197
+ - 您需要手动触发构建流程:
198
+ - 进入 Actions 标签页
199
+ - 选择左侧的 "Build and Deploy" 工作流
200
+ - 点击 "Run workflow" 按钮
201
+
202
+ </details>
203
+
204
+ ### Docker 部署(可选)
205
+
206
+ <details>
207
+ <summary>点击展开</summary>
208
+
209
+ 仓库已内置 `docker-compose.yml`,并提供 GHCR 预构建镜像;两种方式都建议统一使用 Docker Compose。
210
+
211
+ > 说明:容器每次启动都会在容器内执行 `npm run build` 生成 `dist/`,然后用 nginx 提供静态文件。
212
+ >
213
+ > 请在仓库根目录执行(需要 `config/_default` 等文件)。
214
+
215
+ #### 方式 A:使用预构建镜像(推荐,免本地构建)
216
+
217
+ ```bash
218
+ docker compose pull
219
+ docker compose up -d --no-build
220
+ ```
221
+
222
+ #### 方式 B:本地构建镜像(适合二次开发/改源码)
223
+
224
+ ```bash
225
+ docker compose up -d --build
226
+ ```
227
+
228
+ 默认访问地址:`http://localhost:8080`
229
+
230
+ #### 可选参数(环境变量)
231
+
232
+ ```bash
233
+ MENAV_PORT=80 MENAV_ENABLE_SYNC=true MENAV_IMPORT_BOOKMARKS=true docker compose up -d --no-build
234
+ ```
235
+
236
+ - `MENAV_PORT`:宿主机端口(默认 `8080`)
237
+ - `MENAV_ENABLE_SYNC`:启动构建时是否联网执行 `sync-*`(默认 `false`,更稳定)
238
+ - `MENAV_IMPORT_BOOKMARKS`:启动构建前是否执行 `npm run import-bookmarks`(默认 `false`)
239
+
240
+ #### 配置与更新
241
+
242
+ - 配置目录挂载在 `./config`,个人配置按“完全替换策略”建议:将 `config/_default/` 完整复制到 `config/user/` 再修改(见 [设置配置文件](#设置配置文件) 与 `config/README.md`)。
243
+ - 如需导入书签:将浏览器导出的书签 HTML 放到 `./bookmarks/`,并设置 `MENAV_IMPORT_BOOKMARKS=true` 后重启容器。
244
+ - 修改配置/书签后生效方式(触发重新构建):
245
+
246
+ ```bash
247
+ docker compose restart menav
248
+ ```
249
+
250
+ </details>
251
+
252
+ ### 部署到服务器
253
+
254
+ <details>
255
+ <summary>点击展开</summary>
256
+
257
+ 如果您想部署到自己的Web服务器,只需要以下几个步骤:
258
+
259
+ 1. 构建静态网站:
260
+
261
+ ```bash
262
+ npm run build
263
+ ```
264
+
265
+ 2. 复制构建结果:
266
+ - 所有生成的静态文件都位于 `dist` 目录中
267
+ - 将 `dist` 目录中的所有文件复制到您的Web服务器根目录
268
+
269
+ 3. 配置Web服务器:
270
+ - 确保服务器配置为提供静态文件
271
+ - 对于Apache: 在网站根目录中已有正确的 .htaccess 文件
272
+ - 对于Nginx: 添加以下配置到您的server块:
273
+
274
+ ```nginx
275
+ server {
276
+ listen 80;
277
+ server_name your-domain.com;
278
+ root /path/to/dist;
279
+ index index.html;
280
+
281
+ location / {
282
+ try_files $uri $uri/ /404.html;
283
+ }
284
+ }
285
+ ```
286
+
287
+ 4. 更新配置:
288
+ - 如果您想在服务器上更新网站,只需重复上述步骤1-2
289
+ - 或者设置自动部署流程,例如使用GitLab CI/CD或Jenkins
290
+
291
+ </details>
292
+
293
+ ### 其他CI/CD托管选项
294
+
295
+ <details>
296
+ <summary>点击展开</summary>
297
+
298
+ 除了GitHub Pages外,您还可以使用其他各种CI/CD服务部署MeNav:
299
+
300
+ **如 Vercel / Netlify / Cloudflare Pages**:
301
+
302
+ - 连接您的GitHub仓库
303
+ - 设置构建命令为`npm run build`
304
+ - 设置输出目录为`dist`
305
+
306
+ Vercel 部署:
307
+
308
+ 1. 登录 Vercel,点击 `Add New...` → `Project`
309
+ 2. 选择 `Import Git Repository`,连接并选择你的 MeNav 仓库
310
+ 3. 构建配置(一般选择 `Other` 或保持默认自动识别即可):
311
+ - `Build Command`: `npm run build`
312
+ - `Output Directory`: `dist`
313
+ - `Install Command`(可选,但更稳定):`npm ci`
314
+ 4. 点击 `Deploy`,等待完成后用 Vercel 分配的域名/自定义域名访问
315
+
316
+ **如果您只使用第三方平台部署(不使用 GitHub Pages)**:
317
+
318
+ 为避免 GitHub Actions 中的 Pages 配置错误,您可以禁用 GitHub Pages 部署步骤:
319
+
320
+ 1. 进入仓库的 Settings -> Secrets and variables -> Actions
321
+ 2. 点击 "Variables" 标签页
322
+ 3. 点击 "New repository variable"
323
+ 4. 名称填写:`ENABLE_GITHUB_PAGES`
324
+ 5. 值填写:`false`
325
+ 6. 点击 "Add variable"
326
+
327
+ 设置后,GitHub Actions 仍会自动构建网站(包括书签处理、RSS 同步等),但会跳过 GitHub Pages 部署步骤,避免报错。第三方平台(如 Vercel/Cloudflare Pages)会自动检测到代码变化并部署。
328
+
329
+ > 如果你希望在构建时刷新“时效性数据”(RSS 文章聚合、projects 仓库统计),请将构建命令改为:
330
+ >
331
+ > ```bash
332
+ > npm ci && npm run sync-projects && npm run sync-articles && npm run build
333
+ > ```
334
+ >
335
+ > 说明:`sync-*` 会联网抓取并写入 `dev/` 缓存(仓库默认 gitignore);同步脚本为 best-effort,失败不会阻断后续 `build`。
336
+ >
337
+ > 备注:`dev/` 只用于构建过程的中间缓存,默认不会被提交到仓库;部署时也只会上传 `dist/`,不会包含 `dev/`。
338
+
339
+ > **书签转换依赖 GitHub Actions**
340
+ > 如果需要使用书签自动推送功能,必须先在 GitHub 仓库中启用 GitHub Actions
341
+ >
342
+ > **部署流程**:
343
+ >
344
+ > ```
345
+ > 1. 上传书签 → 2. GitHub Actions 处理 → 3. 使用处理完成的代码在 GitHub Pages 自动部署
346
+ > ↓
347
+ > 4. 其他 CI/CD 托管平台检测到变化 → 5. 使用处理完成的代码自动部署
348
+ > ```
349
+
350
+ 无论选择哪种部署方式,请确保创建并使用您自己的配置文件,而不是直接修改默认配置。
351
+
352
+ </details>
353
+
354
+ ## 设置配置文件
355
+
356
+ MeNav 使用模块化配置方式,将配置分散到多个 YAML 文件中,更易于管理和维护。
357
+
358
+ 完整说明请直接看:[`config/README.md`](config/README.md)(以该文档为准)。
359
+
360
+ > **🔔 重要提示:** 请务必在`config/user/`目录下创建并使用您自己的配置文件,不要直接修改默认配置文件,以便后续更新项目时不会丢失您的个性化设置。
361
+
362
+ 在加载配置时遵循以下优先级顺序:
363
+
364
+ 1. `config/user/` (用户配置)(优先级最高)
365
+ 2. `config/_default/` (默认配置)
366
+
367
+ **注意:** 采用完全替换策略,而非合并。系统会选择存在的用户配置,完全忽略默认配置。
368
+
369
+ ### 最小可用配置(快速指引)
370
+
371
+ - 首次使用建议先完整复制 `config/_default/` 到 `config/user/`,再按需修改(因为配置采用“完全替换”策略,不会从默认配置补齐缺失项)。
372
+ - 至少需要有 `config/user/site.yml`(缺失时构建会直接报错退出,避免生成空白站点)。
373
+
374
+ ## 书签导入功能
375
+
376
+ MeNav 支持从浏览器导入书签,快速批量添加网站链接;也支持与 MarksVault 扩展集成自动同步。
377
+
378
+ 完整说明请直接看:[`bookmarks/README.md`](bookmarks/README.md)(以该文档为准)。
379
+
380
+ ## Star-History
381
+
382
+ [![Star History Chart](https://api.star-history.com/svg?repos=rbetree/menav&type=Date)](https://www.star-history.com/#rbetree/menav&Date)
assets/README.md ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MeNav 资源目录
2
+
3
+ ## 目录
4
+
5
+ - [目录概述](#目录概述)
6
+ - [资源列表](#资源列表)
7
+ - [CSS 模块化架构](#css-模块化架构)
8
+ - [添加新资源](#添加新资源)
9
+
10
+ ## 目录概述
11
+
12
+ `assets` 目录包含 MeNav 项目所需的所有静态资源文件,包括样式表、图标、图片等。这些资源文件直接被复制到生成的站点中,用于网站的展示和功能实现。
13
+
14
+ ## 资源列表
15
+
16
+ 目录包含以下主要资源:
17
+
18
+ - **style.css**: 样式入口文件
19
+ - 通过 `@import` 聚合所有模块化 CSS
20
+ - 构建时由 esbuild 合并压缩为单文件
21
+
22
+ - **styles/**: CSS 模块目录(详见下方)
23
+
24
+ - **menav.svg**: 网站图标和项目logo
25
+ - 显示在浏览器标签页、书签和收藏夹中
26
+ - SVG格式,包含黑色字母"M"和蓝色向上箭头设计
27
+
28
+ - **preview_light.png / preview_dark.png**: 主题预览图
29
+ - 用于 README 文档展示
30
+
31
+ ## CSS 模块化架构
32
+
33
+ 样式采用模块化组织,构建时自动合并:
34
+
35
+ ```
36
+ assets/
37
+ ├── style.css # 入口文件(@import 聚合)
38
+ └── styles/
39
+ ├── _variables.css # CSS 变量、主题色
40
+ ├── _base.css # 全局重置、滚动条
41
+ ├── _animations.css # @keyframes 动画
42
+ ├── _layout.css # 页面容器布局
43
+ ├── _sidebar.css # 侧边栏组件
44
+ ├── _search.css # 搜索框组件
45
+ ├── _cards.css # 卡片组件(站点卡片、tooltip)
46
+ ├── _modal.css # 模态框、表单
47
+ ├── _content.css # Markdown 内容页
48
+ ├── _dashboard.css # 仪表盘(时钟/Todo)
49
+ └── _main.css # 兜底样式(分类、热力图、响应式)
50
+ ```
51
+
52
+ ### 模块说明
53
+
54
+ | 模块 | 职责 |
55
+ |------|------|
56
+ | `_variables.css` | CSS 变量、深色/浅色主题、间距/圆角系统 |
57
+ | `_base.css` | 全局重置、滚动条、遮罩层、主题切换按钮 |
58
+ | `_animations.css` | 所有 `@keyframes` 定义 |
59
+ | `_layout.css` | 页面容器、欢迎区域、模板布局 |
60
+ | `_sidebar.css` | 侧边栏、导航项、折叠状态、子菜单 |
61
+ | `_search.css` | 搜索框、引擎下拉、快捷键提示 |
62
+ | `_cards.css` | 站点卡片、网格布局、tooltip、编辑按钮 |
63
+ | `_modal.css` | 模态框、表单控件、按钮样式 |
64
+ | `_content.css` | Markdown 渲染(标题、代码块、表格等) |
65
+ | `_dashboard.css` | 仪表盘网格、时钟卡片、Todo 列表 |
66
+ | `_main.css` | 分类层级、GitHub 热力图、全局响应式 |
67
+
68
+ ### 构建流程
69
+
70
+ 构建时 esbuild 会:
71
+ 1. 解析 `style.css` 中的 `@import` 语句
72
+ 2. 合并所有模块为单文件
73
+ 3. 压缩输出到 `dist/style.css`
74
+
75
+ > **开发提示**:修改 CSS 后运行 `npm run build` 或 `npm run dev` 查看效果。
76
+
77
+ ## 添加新资源
78
+
79
+ ### 文件命名规范
80
+
81
+ - 使用小写字母和连字符 (`-`)
82
+ - 避免使用空格和特殊字符
83
+ - 名称应清晰表达文件用途
84
+
85
+ ### CSS 扩展指南
86
+
87
+ 1. **找到合适的模块**:根据功能选择对应的 `_*.css` 文件
88
+ 2. **遵循命名规范**:使用 BEM 风格或现有选择器模式
89
+ 3. **添加响应式支持**:在同一模块内添加 `@media` 查询
90
+ 4. **变量优先**:优先使用 `_variables.css` 中定义的变量
91
+
92
+ ### 图片优化
93
+
94
+ - 压缩图片以减小文件大小
95
+ - 使用 PNG、JPG、WebP 等 web 友好格式
96
+ - 考虑添加合适的分辨率版本
97
+
98
+ 添加新资源后,构建系统会自动将这些文件复制到生成的网站中。
assets/menav.svg ADDED
assets/pinyin-match.js ADDED
@@ -0,0 +1 @@
 
 
1
+ !function(n,i){"object"==typeof exports&&"undefined"!=typeof module?module.exports=i():"function"==typeof define&&define.amd?define(i):(n="undefined"!=typeof globalThis?globalThis:n||self).PinyinMatch=i()}(this,(function(){"use strict";function n(n,i,a){return(i=function(n){var i=function(n,i){if("object"!=typeof n||!n)return n;var a=n[Symbol.toPrimitive];if(void 0!==a){var u=a.call(n,i||"default");if("object"!=typeof u)return u;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===i?String:Number)(n)}(n,"string");return"symbol"==typeof i?i:i+""}(i))in n?Object.defineProperty(n,i,{value:a,enumerable:!0,configurable:!0,writable:!0}):n[i]=a,n}var i=[],a={},u={};function e(n){for(var i=[],a=n.length,u=[],e=0;a>=e;e++)u.push(!0);return o(0,n,[],i,u),i}function o(n,a,u,e,r){var t=a.length;if(n!==t)for(var g=function(){var t=a.substring(n,h+1),g=!1;if(i.some((function(n){return 0===n.indexOf(t)}))&&!a[h+1]&&r[h+1]){if(1===t.length)u.push(t);else{var f=[];i.forEach((function(n){0===n.indexOf(t)&&f.push(n)})),u.push(f)}g=!0}else-1!==i.indexOf(t)&&r[h+1]&&(u.push(t),g=!0);if(g){var l=e.length;o(h+1,a,u,e,r),e.length===l&&(r[h+1]=!1),u.pop()}},h=n;t>h;h++)g();else e.push(u.join(" "))}function r(n,i,a,u){if(!n)return!1;var e=n.split(" ");return e.forEach((function(n){n.length>0&&u&&e.push(n.charAt(0))})),a?e.some((function(n){return 0===n.indexOf(i)})):-1!==e.indexOf(i)}var t=["lü","lüe","nü","nüe"];function g(i,o){if(!i||!o)return!1;i=i.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g,""),o=function(n){var i=n.replace(/\s+/g,"").toLowerCase();return t.some((function(n){return i.includes(n)}))&&(i=i.replace("ü","v")),i.normalize("NFD").replace(/[\u0300-\u036f]/g,"")}(o);var r=i.indexOf(o);if(-1!==r)return[r,r+o.length-1];var g=h(i.split(""),[o.split("")],o);if(g)return g;var f,l,c=function(n){for(var i=[],u=0,e=n.length;e>u;u++){var o=n.charAt(u);i.push(a[o]||o)}return i}(i);return h(c,u[o]||(l=[],e(f=o).forEach((function(n){var i=n.split(" "),a=i.length-1;i[a].indexOf(",")?i[a].split(",").forEach((function(n){i.splice(a,1,n),l.push(JSON.parse(JSON.stringify(i)))})):l.push(i)})),0!==l.length&&l[0].length===f.length||l.push(f.split("")),u=n({},f,l),l),o)}function h(n,i,a){for(var u=0;n.length>u;u++)for(var e=0;i.length>e;e++){var o=i[e],t=o.length,g=t===a.length,h=!0,f=0,l=0,c=0;if(n.length>=t){for(;o.length>f;f++)if(0===f&&" "===n[u+f+l])l+=1,f-=1;else if(" "===n[u+f+c])c+=1,f-=1;else if(!r(n[u+f+c],o[f],!n[u+f+1]||!o[f+1],g)){h=!1;break}if(h)return[u+l,c+u+f-1]}}return!1}var f={match:function(n){var u={},e=["ju","jun","jue","juan","qu","qun","que","xuan","xu","xue","yu","yuan","yue","yun","nve","lve"],o=["lv","lve","nv","nve"];return Object.keys(n).forEach((function(a){if(u[a]=n[a],i.push(a),e.includes(a)){var r=-1!==(t=a).indexOf("u")?t.replace("u","v"):t.replace("v","u");u[r]=n[a],i.push(r)}var t;if(o.includes(a)){var g=a.replace("v","ü");u[g]=n[a],i.push(g)}})),a=function(n){var i={};for(var a in n)for(var u=n[a],e=0,o=u.length;o>e;e++)i[u[e]]=i[u[e]]?i[u[e]]+" "+a:a;return i}(u),g}({a:"阿啊呵腌嗄吖锕",e:"额阿俄恶鹅遏鄂厄饿峨扼娥鳄哦蛾噩愕讹锷垩婀鹗萼谔莪腭锇颚呃阏屙苊轭",ai:"爱埃艾碍癌哀挨矮隘蔼唉皑哎霭捱暧嫒嗳瑷嗌锿砹",ei:"诶",xi:"系西席息希习吸喜细析戏洗悉锡溪惜稀袭夕洒晰昔牺腊烯熙媳栖膝隙犀蹊硒兮熄曦禧嬉玺奚汐徙羲铣淅嘻歙熹矽蟋郗唏皙隰樨浠忾蜥檄郄翕阋鳃舾屣葸螅咭粞觋欷僖醯鼷裼穸饩舄禊诶菥蓰",yi:"一以已意议义益亿易医艺食依移衣异伊仪宜射遗疑毅谊亦疫役忆抑尾乙译翼蛇溢椅沂泄逸蚁夷邑怡绎彝裔姨熠贻矣屹颐倚诣胰奕翌疙弈轶蛾驿壹猗臆弋铱旖漪迤佚翊诒怿痍懿饴峄揖眙镒仡黟肄咿翳挹缢呓刈咦嶷羿钇殪荑薏蜴镱噫癔苡悒嗌瘗衤佾埸圯舣酏劓",an:"安案按岸暗鞍氨俺胺铵谙庵黯鹌桉埯犴揞厂广",han:"厂汉韩含旱寒汗涵函喊憾罕焊翰邯撼瀚憨捍酣悍鼾邗颔蚶晗菡旰顸犴焓撖",ang:"昂仰盎肮",ao:"奥澳傲熬凹鳌敖遨鏖袄坳翱嗷拗懊岙螯骜獒鏊艹媪廒聱",wa:"瓦挖娃洼袜蛙凹哇佤娲呙腽",yu:"于与育余预域予遇奥语誉玉鱼雨渔裕愈娱欲吁舆宇羽逾豫郁寓吾狱喻御浴愉禹俞邪榆愚渝尉淤虞屿峪粥驭瑜禺毓钰隅芋熨瘀迂煜昱汩於臾盂聿竽萸妪腴圄谕觎揄龉谀俣馀庾妤瘐鬻欤鹬阈嵛雩鹆圉蜮伛纡窬窳饫蓣狳肀舁蝓燠",niu:"牛纽扭钮拗妞忸狃",o:"哦噢喔",ba:"把八巴拔伯吧坝爸霸罢芭跋扒叭靶疤笆耙鲅粑岜灞钯捌菝魃茇",pa:"怕帕爬扒趴琶啪葩耙杷钯筢",pi:"被批副否皮坏辟啤匹披疲罢僻毗坯脾譬劈媲屁琵邳裨痞癖陂丕枇噼霹吡纰砒铍淠郫埤濞睥芘蚍圮鼙罴蜱疋貔仳庀擗甓陴",bi:"比必币笔毕秘避闭佛辟壁弊彼逼碧鼻臂蔽拂泌璧庇痹毙弼匕鄙陛裨贲敝蓖吡篦纰俾铋毖筚荸薜婢哔跸濞秕荜愎睥妣芘箅髀畀滗狴萆嬖襞舭",bai:"百白败摆伯拜柏佰掰呗擘捭稗",bo:"波博播勃拨薄佛伯玻搏柏泊舶剥��卜驳簿脖膊簸菠礴箔铂亳钵帛擘饽跛钹趵檗啵鹁擗踣",bei:"北被备倍背杯勃贝辈悲碑臂卑悖惫蓓陂钡狈呗焙碚褙庳鞴孛鹎邶鐾",ban:"办版半班般板颁伴搬斑扮拌扳瓣坂阪绊钣瘢舨癍",pan:"判盘番潘攀盼拚畔胖叛拌蹒磐爿蟠泮袢襻丬",bin:"份宾频滨斌彬濒殡缤鬓槟摈膑玢镔豳髌傧",bang:"帮邦彭旁榜棒膀镑绑傍磅蚌谤梆浜蒡",pang:"旁庞乓磅螃彷滂逄耪胖",beng:"泵崩蚌蹦迸绷甭嘣甏堋",bao:"报保包宝暴胞薄爆炮饱抱堡剥鲍曝葆瀑豹刨褒雹孢苞煲褓趵鸨龅勹",bu:"不部步布补捕堡埔卜埠簿哺怖钚卟瓿逋晡醭钸",pu:"普暴铺浦朴堡葡谱埔扑仆蒲曝瀑溥莆圃璞濮菩蹼匍噗氆攵镨攴镤",mian:"面棉免绵缅勉眠冕娩腼渑湎沔黾宀眄",po:"破繁坡迫颇朴泊婆泼魄粕鄱珀陂叵笸泺皤钋钷",fan:"反范犯繁饭泛翻凡返番贩烦拚帆樊藩矾梵蕃钒幡畈蘩蹯燔",fu:"府服副负富复福夫妇幅付扶父符附腐赴佛浮覆辅傅伏抚赋辐腹弗肤阜袱缚甫氟斧孚敷俯拂俘咐腑孵芙涪釜脯茯馥宓绂讣呋罘麸蝠匐芾蜉跗凫滏蝮驸绋蚨砩桴赙菔呒趺苻拊阝鲋怫稃郛莩幞祓艴黻黼鳆",ben:"本奔苯笨夯贲锛畚坌",feng:"风丰封峰奉凤锋冯逢缝蜂枫疯讽烽俸沣酆砜葑唪",bian:"变便边编遍辩鞭辨贬匾扁卞汴辫砭苄蝙鳊弁窆笾煸褊碥忭缏",pian:"便片篇偏骗翩扁骈胼蹁谝犏缏",zhen:"镇真针圳振震珍阵诊填侦臻贞枕桢赈祯帧甄斟缜箴疹砧榛鸩轸稹溱蓁胗椹朕畛浈",biao:"表标彪镖裱飚膘飙镳婊骠飑杓髟鳔灬瘭",piao:"票朴漂飘嫖瓢剽缥殍瞟骠嘌莩螵",huo:"和活或货获火伙惑霍祸豁嚯藿锪蠖钬耠镬夥灬劐攉",bie:"别鳖憋瘪蹩",min:"民敏闽闵皿泯岷悯珉抿黾缗玟愍苠鳘",fen:"分份纷奋粉氛芬愤粪坟汾焚酚吩忿棼玢鼢瀵偾鲼",bing:"并病兵冰屏饼炳秉丙摒柄槟禀枋邴冫",geng:"更耕颈庚耿梗埂羹哽赓绠鲠",fang:"方放房防访纺芳仿坊妨肪邡舫彷枋鲂匚钫",xian:"现先县见线限显险献鲜洗宪纤陷闲贤仙衔掀咸嫌掺羡弦腺痫娴舷馅酰铣冼涎暹籼锨苋蚬跹岘藓燹鹇氙莶霰跣猃彡祆筅",fou:"不否缶",ca:"拆擦嚓礤",cha:"查察差茶插叉刹茬楂岔诧碴嚓喳姹杈汊衩搽槎镲苴檫馇锸猹",cai:"才采财材菜彩裁蔡猜踩睬",can:"参残餐灿惨蚕掺璨惭粲孱骖黪",shen:"信深参身神什审申甚沈伸慎渗肾绅莘呻婶娠砷蜃哂椹葚吲糁渖诜谂矧胂",cen:"参岑涔",san:"三参散伞叁糁馓毵",cang:"藏仓苍沧舱臧伧",zang:"藏脏葬赃臧奘驵",chen:"称陈沈沉晨琛臣尘辰衬趁忱郴宸谌碜嗔抻榇伧谶龀肜",cao:"草操曹槽糙嘈漕螬艚屮",ce:"策测册侧厕栅恻",ze:"责则泽择侧咋啧仄箦赜笮舴昃迮帻",zhai:"债择齐宅寨侧摘窄斋祭翟砦瘵哜",dao:"到道导岛倒刀盗稻蹈悼捣叨祷焘氘纛刂帱忉",ceng:"层曾蹭噌",zha:"查扎炸诈闸渣咋乍榨楂札栅眨咤柞喳喋铡蚱吒怍砟揸痄哳齄",chai:"差拆柴钗豺侪虿瘥",ci:"次此差词辞刺瓷磁兹慈茨赐祠伺雌疵鹚糍呲粢",zi:"资自子字齐咨滋仔姿紫兹孜淄籽梓鲻渍姊吱秭恣甾孳訾滓锱辎趑龇赀眦缁呲笫谘嵫髭茈粢觜耔",cuo:"措错磋挫搓撮蹉锉厝嵯痤矬瘥脞鹾",chan:"产单阐崭缠掺禅颤铲蝉搀潺蟾馋忏婵孱觇廛谄谗澶骣羼躔蒇冁",shan:"山单善陕闪衫擅汕扇掺珊禅删膳缮赡鄯栅煽姗跚鳝嬗潸讪舢苫疝掸膻钐剡蟮芟埏彡骟",zhan:"展战占站崭粘湛沾瞻颤詹斩盏辗绽毡栈蘸旃谵搌",xin:"新心信辛欣薪馨鑫芯锌忻莘昕衅歆囟忄镡",lian:"联连练廉炼脸莲恋链帘怜涟敛琏镰濂楝鲢殓潋裢裣臁奁莶蠊蔹",chang:"场长厂常偿昌唱畅倡尝肠敞倘猖娼淌裳徜昶怅嫦菖鲳阊伥苌氅惝鬯",zhang:"长张章障涨掌帐胀彰丈仗漳樟账杖璋嶂仉瘴蟑獐幛鄣嫜",chao:"超朝潮炒钞抄巢吵剿绰嘲晁焯耖怊",zhao:"着照招找召朝赵兆昭肇罩钊沼嘲爪诏濯啁棹笊",zhou:"调州周洲舟骤轴昼宙粥皱肘咒帚胄绉纣妯啁诌繇碡籀酎荮",che:"车彻撤尺扯澈掣坼砗屮",ju:"车局据具举且居剧巨聚渠距句拒俱柜菊拘炬桔惧矩鞠驹锯踞咀瞿枸掬沮莒橘飓疽钜趄踽遽琚龃椐苣裾榘狙倨榉苴讵雎锔窭鞫犋屦醵",cheng:"成程城承称盛抢乘诚呈净惩撑澄秤橙骋逞瞠丞晟铛埕塍蛏柽铖酲裎枨",rong:"容荣融绒溶蓉熔戎榕茸冗嵘肜狨蝾",sheng:"生声升胜盛乘圣剩牲甸省绳笙甥嵊晟渑眚",deng:"等登邓灯澄凳瞪蹬噔磴嶝镫簦戥",zhi:"制之治质职只志至指织支值知识直致执置止植纸拓智殖秩旨址滞氏枝芝脂帜汁肢挚稚酯掷峙炙栉侄芷窒咫吱趾痔蜘郅桎雉祉郦陟痣蛭帙枳踯徵胝栀贽祗豸鸷摭轵卮轾彘觯絷跖埴夂黹忮骘膣踬",zheng:"政正证争整征郑丁症挣蒸睁铮筝拯峥怔诤狰徵钲",tang:"堂唐糖汤塘躺趟倘棠烫淌膛搪镗傥螳溏帑羰樘醣螗耥铴瑭",chi:"持吃池迟赤驰尺斥齿翅匙痴耻炽侈弛叱啻坻眙嗤墀哧茌豉敕笞饬踟蚩柢媸魑���褫彳鸱螭瘛眵傺",shi:"是时实事市十使世施式势视识师史示石食始士失适试什泽室似诗饰殖释驶氏硕逝湿蚀狮誓拾尸匙仕柿矢峙侍噬嗜栅拭嘘屎恃轼虱耆舐莳铈谥炻豕鲥饣螫酾筮埘弑礻蓍鲺贳",qi:"企其起期气七器汽奇齐启旗棋妻弃揭枝歧欺骑契迄亟漆戚岂稽岐琦栖缉琪泣乞砌祁崎绮祺祈凄淇杞脐麒圻憩芪伎俟畦耆葺沏萋骐鳍綦讫蕲屺颀亓碛柒啐汔綮萁嘁蛴槭欹芑桤丌蜞",chuai:"揣踹啜搋膪",tuo:"托脱拓拖妥驼陀沱鸵驮唾椭坨佗砣跎庹柁橐乇铊沲酡鼍箨柝",duo:"多度夺朵躲铎隋咄堕舵垛惰哆踱跺掇剁柁缍沲裰哚隳",xue:"学血雪削薛穴靴谑噱鳕踅泶彐",chong:"重种充冲涌崇虫宠忡憧舂茺铳艟",chou:"筹抽绸酬愁丑臭仇畴稠瞅踌惆俦瘳雠帱",qiu:"求球秋丘邱仇酋裘龟囚遒鳅虬蚯泅楸湫犰逑巯艽俅蝤赇鼽糗",xiu:"修秀休宿袖绣臭朽锈羞嗅岫溴庥馐咻髹鸺貅",chu:"出处础初助除储畜触楚厨雏矗橱锄滁躇怵绌搐刍蜍黜杵蹰亍樗憷楮",tuan:"团揣湍疃抟彖",zhui:"追坠缀揣椎锥赘惴隹骓缒",chuan:"传川船穿串喘椽舛钏遄氚巛舡",zhuan:"专转传赚砖撰篆馔啭颛",yuan:"元员院原源远愿园援圆缘袁怨渊苑宛冤媛猿垣沅塬垸鸳辕鸢瑗圜爰芫鼋橼螈眢箢掾",cuan:"窜攒篡蹿撺爨汆镩",chuang:"创床窗闯幢疮怆",zhuang:"装状庄壮撞妆幢桩奘僮戆",chui:"吹垂锤炊椎陲槌捶棰",chun:"春纯醇淳唇椿蠢鹑朐莼肫蝽",zhun:"准屯淳谆肫窀",cu:"促趋趣粗簇醋卒蹴猝蹙蔟殂徂",dun:"吨顿盾敦蹲墩囤沌钝炖盹遁趸砘礅",qu:"区去取曲趋渠趣驱屈躯衢娶祛瞿岖龋觑朐蛐癯蛆苣阒诎劬蕖蘧氍黢蠼璩麴鸲磲",xu:"需许续须序徐休蓄畜虚吁绪叙旭邪恤墟栩絮圩婿戌胥嘘浒煦酗诩朐盱蓿溆洫顼勖糈砉醑",chuo:"辍绰戳淖啜龊踔辶",zu:"组族足祖租阻卒俎诅镞菹",ji:"济机其技基记计系期际及集级几给积极己纪即继击既激绩急奇吉季齐疾迹鸡剂辑籍寄挤圾冀亟寂暨脊跻肌稽忌饥祭缉棘矶汲畸姬藉瘠骥羁妓讥稷蓟悸嫉岌叽伎鲫诘楫荠戟箕霁嵇觊麂畿玑笈犄芨唧屐髻戢佶偈笄跽蒺乩咭赍嵴虮掎齑殛鲚剞洎丌墼蕺彐芰哜",cong:"从丛匆聪葱囱琮淙枞骢苁璁",zong:"总从综宗纵踪棕粽鬃偬枞腙",cou:"凑辏腠楱",cui:"衰催崔脆翠萃粹摧璀瘁悴淬啐隹毳榱",wei:"为位委未维卫围违威伟危味微唯谓伪慰尾魏韦胃畏帷喂巍萎蔚纬潍尉渭惟薇苇炜圩娓诿玮崴桅偎逶倭猥囗葳隗痿猬涠嵬韪煨艉隹帏闱洧沩隈鲔軎",cun:"村存寸忖皴",zuo:"作做座左坐昨佐琢撮祚柞唑嘬酢怍笮阼胙",zuan:"钻纂攥缵躜",da:"大达打答搭沓瘩惮嗒哒耷鞑靼褡笪怛妲",dai:"大代带待贷毒戴袋歹呆隶逮岱傣棣怠殆黛甙埭诒绐玳呔迨",tai:"台太态泰抬胎汰钛苔薹肽跆邰鲐酞骀炱",ta:"他它她拓塔踏塌榻沓漯獭嗒挞蹋趿遢铊鳎溻闼",dan:"但单石担丹胆旦弹蛋淡诞氮郸耽殚惮儋眈疸澹掸膻啖箪聃萏瘅赕",lu:"路六陆录绿露鲁卢炉鹿禄赂芦庐碌麓颅泸卤潞鹭辘虏璐漉噜戮鲈掳橹轳逯渌蓼撸鸬栌氇胪镥簏舻辂垆",tan:"谈探坦摊弹炭坛滩贪叹谭潭碳毯瘫檀痰袒坍覃忐昙郯澹钽锬",ren:"人任认仁忍韧刃纫饪妊荏稔壬仞轫亻衽",jie:"家结解价界接节她届介阶街借杰洁截姐揭捷劫戒皆竭桔诫楷秸睫藉拮芥诘碣嗟颉蚧孑婕疖桀讦疥偈羯袷哜喈卩鲒骱",yan:"研严验演言眼烟沿延盐炎燕岩宴艳颜殷彦掩淹阎衍铅雁咽厌焰堰砚唁焉晏檐蜒奄俨腌妍谚兖筵焱偃闫嫣鄢湮赝胭琰滟阉魇酽郾恹崦芫剡鼹菸餍埏谳讠厣罨",dang:"当党档荡挡宕砀铛裆凼菪谠",tao:"套讨跳陶涛逃桃萄淘掏滔韬叨洮啕绦饕鼗",tiao:"条调挑跳迢眺苕窕笤佻啁粜髫铫祧龆蜩鲦",te:"特忑忒铽慝",de:"的地得德底锝",dei:"得",di:"的地第提低底抵弟迪递帝敌堤蒂缔滴涤翟娣笛棣荻谛狄邸嘀砥坻诋嫡镝碲骶氐柢籴羝睇觌",ti:"体提题弟替梯踢惕剔蹄棣啼屉剃涕锑倜悌逖嚏荑醍绨鹈缇裼",tui:"推退弟腿褪颓蜕忒煺",you:"有由又优游油友右邮尤忧幼犹诱悠幽佑釉柚铀鱿囿酉攸黝莠猷蝣疣呦蚴莸莜铕宥繇卣牖鼬尢蚰侑",dian:"电点店典奠甸碘淀殿垫颠滇癫巅惦掂癜玷佃踮靛钿簟坫阽",tian:"天田添填甜甸恬腆佃舔钿阗忝殄畋栝掭",zhu:"主术住注助属逐宁著筑驻朱珠祝猪诸柱竹铸株瞩嘱贮煮烛苎褚蛛拄铢洙竺蛀渚伫杼侏澍诛茱箸炷躅翥潴邾槠舳橥丶瘃麈疰",nian:"年念酿辗碾廿捻撵拈蔫鲶埝鲇辇黏",diao:"调掉雕吊钓刁貂凋碉鲷叼铫铞",yao:"要么约药邀摇耀腰遥姚窑瑶咬尧钥谣肴夭侥吆疟妖幺杳舀窕窈曜鹞爻繇徭轺铫鳐崾珧",die:"跌叠蝶迭碟爹谍牒耋佚喋堞瓞鲽垤揲蹀",she:"设社摄涉射折舍蛇拾舌奢慑赦赊佘麝歙畲厍猞揲滠",ye:"业也夜叶射野液冶喝页爷耶邪咽椰烨掖拽曳晔谒腋噎揶靥邺铘揲",xie:"些解协写血叶��械鞋胁斜携懈契卸谐泄蟹邪歇泻屑挟燮榭蝎撷偕亵楔颉缬邂鲑瀣勰榍薤绁渫廨獬躞",zhe:"喆这者着著浙折哲蔗遮辙辄柘锗褶蜇蛰鹧谪赭摺乇磔螫",ding:"定订顶丁鼎盯钉锭叮仃铤町酊啶碇腚疔玎耵",diu:"丢铥",ting:"听庭停厅廷挺亭艇婷汀铤烃霆町蜓葶梃莛",dong:"动东董冬洞懂冻栋侗咚峒氡恫胴硐垌鸫岽胨",tong:"同通统童痛铜桶桐筒彤侗佟潼捅酮砼瞳恸峒仝嗵僮垌茼",zhong:"中重种众终钟忠仲衷肿踵冢盅蚣忪锺舯螽夂",dou:"都斗读豆抖兜陡逗窦渎蚪痘蔸钭篼",du:"度都独督读毒渡杜堵赌睹肚镀渎笃竺嘟犊妒牍蠹椟黩芏髑",duan:"断段短端锻缎煅椴簖",dui:"对队追敦兑堆碓镦怼憝",rui:"瑞兑锐睿芮蕊蕤蚋枘",yue:"月说约越乐跃兑阅岳粤悦曰钥栎钺樾瀹龠哕刖",tun:"吞屯囤褪豚臀饨暾氽",hui:"会回挥汇惠辉恢徽绘毁慧灰贿卉悔秽溃荟晖彗讳诲珲堕诙蕙晦睢麾烩茴喙桧蛔洄浍虺恚蟪咴隳缋哕",wu:"务物无五武午吴舞伍污乌误亡恶屋晤悟吾雾芜梧勿巫侮坞毋诬呜钨邬捂鹜兀婺妩於戊鹉浯蜈唔骛仵焐芴鋈庑鼯牾怃圬忤痦迕杌寤阢",ya:"亚压雅牙押鸭呀轧涯崖邪芽哑讶鸦娅衙丫蚜碣垭伢氩桠琊揠吖睚痖疋迓岈砑",he:"和合河何核盖贺喝赫荷盒鹤吓呵苛禾菏壑褐涸阂阖劾诃颌嗬貉曷翮纥盍",wo:"我握窝沃卧挝涡斡渥幄蜗喔倭莴龌肟硪",en:"恩摁蒽",n:"嗯唔",er:"而二尔儿耳迩饵洱贰铒珥佴鸸鲕",fa:"发法罚乏伐阀筏砝垡珐",quan:"全权券泉圈拳劝犬铨痊诠荃醛蜷颧绻犭筌鬈悛辁畎",fei:"费非飞肥废菲肺啡沸匪斐蜚妃诽扉翡霏吠绯腓痱芾淝悱狒榧砩鲱篚镄",pei:"配培坏赔佩陪沛裴胚妃霈淠旆帔呸醅辔锫",ping:"平评凭瓶冯屏萍苹乒坪枰娉俜鲆",fo:"佛",hu:"和护户核湖互乎呼胡戏忽虎沪糊壶葫狐蝴弧瑚浒鹄琥扈唬滹惚祜囫斛笏芴醐猢怙唿戽槲觳煳鹕冱瓠虍岵鹱烀轷",ga:"夹咖嘎尬噶旮伽尕钆尜",ge:"个合各革格歌哥盖隔割阁戈葛鸽搁胳舸疙铬骼蛤咯圪镉颌仡硌嗝鬲膈纥袼搿塥哿虼",ha:"哈蛤铪",xia:"下夏峡厦辖霞夹虾狭吓侠暇遐瞎匣瑕唬呷黠硖罅狎瘕柙",gai:"改该盖概溉钙丐芥赅垓陔戤",hai:"海还害孩亥咳骸骇氦嗨胲醢",gan:"干感赶敢甘肝杆赣乾柑尴竿秆橄矸淦苷擀酐绀泔坩旰疳澉",gang:"港钢刚岗纲冈杠缸扛肛罡戆筻",jiang:"将强江港奖讲降疆蒋姜浆匠酱僵桨绛缰犟豇礓洚茳糨耩",hang:"行航杭巷夯吭桁沆绗颃",gong:"工公共供功红贡攻宫巩龚恭拱躬弓汞蚣珙觥肱廾",hong:"红宏洪轰虹鸿弘哄烘泓訇蕻闳讧荭黉薨",guang:"广光逛潢犷胱咣桄",qiong:"穷琼穹邛茕筇跫蛩銎",gao:"高告搞稿膏糕镐皋羔锆杲郜睾诰藁篙缟槁槔",hao:"好号毫豪耗浩郝皓昊皋蒿壕灏嚎濠蚝貉颢嗥薅嚆",li:"理力利立里李历例离励礼丽黎璃厉厘粒莉梨隶栗荔沥犁漓哩狸藜罹篱鲤砺吏澧俐骊溧砾莅锂笠蠡蛎痢雳俪傈醴栎郦俚枥喱逦娌鹂戾砬唳坜疠蜊黧猁鬲粝蓠呖跞疬缡鲡鳢嫠詈悝苈篥轹",jia:"家加价假佳架甲嘉贾驾嫁夹稼钾挟拮迦伽颊浃枷戛荚痂颉镓笳珈岬胛袈郏葭袷瘕铗跏蛱恝哿",luo:"啰落罗络洛逻螺锣骆萝裸漯烙摞骡咯箩珞捋荦硌雒椤镙跞瘰泺脶猡倮蠃",ke:"可科克客刻课颗渴壳柯棵呵坷恪苛咳磕珂稞瞌溘轲窠嗑疴蝌岢铪颏髁蚵缂氪骒钶锞",qia:"卡恰洽掐髂袷咭葜",gei:"给",gen:"根跟亘艮哏茛",hen:"很狠恨痕哏",gou:"构购够句沟狗钩拘勾苟垢枸篝佝媾诟岣彀缑笱鞲觏遘",kou:"口扣寇叩抠佝蔻芤眍筘",gu:"股古顾故固鼓骨估谷贾姑孤雇辜菇沽咕呱锢钴箍汩梏痼崮轱鸪牯蛊诂毂鹘菰罟嘏臌觚瞽蛄酤牿鲴",pai:"牌排派拍迫徘湃俳哌蒎",gua:"括挂瓜刮寡卦呱褂剐胍诖鸹栝呙",tou:"钭投头透偷愉骰亠",guai:"怪拐乖",kuai:"会快块筷脍蒯侩浍郐蒉狯哙",guan:"关管观馆官贯冠惯灌罐莞纶棺斡矜倌鹳鳏盥掼涫",wan:"万完晚湾玩碗顽挽弯蔓丸莞皖宛婉腕蜿惋烷琬畹豌剜纨绾脘菀芄箢",ne:"呢哪呐讷疒",gui:"规贵归轨桂柜圭鬼硅瑰跪龟匮闺诡癸鳜桧皈鲑刽晷傀眭妫炅庋簋刿宄匦",jun:"军均俊君峻菌竣钧骏龟浚隽郡筠皲麇捃",jiong:"窘炯迥炅冂扃",jue:"决绝角觉掘崛诀獗抉爵嚼倔厥蕨攫珏矍蹶谲镢鳜噱桷噘撅橛孓觖劂爝",gun:"滚棍辊衮磙鲧绲丨",hun:"婚混魂浑昏棍珲荤馄诨溷阍",guo:"国过果郭锅裹帼涡椁囗蝈虢聒埚掴猓崞蜾呙馘",hei:"黑嘿嗨",kan:"看刊勘堪坎砍侃嵌槛瞰阚龛戡凵莰",heng:"衡横恒亨哼珩桁蘅",mo:"万没么模末冒莫摩墨默磨摸漠脉膜魔沫陌抹寞蘑摹蓦馍茉嘿谟秣蟆貉嫫镆殁耱嬷麽瘼貊貘",peng:"鹏朋彭膨蓬碰苹棚捧亨烹篷澎抨硼怦砰嘭蟛堋",hou:"后候厚侯猴喉吼逅篌糇骺後鲎瘊堠",hua:"化华划话花画滑哗豁骅桦猾铧砉",huai:"怀坏淮徊槐踝",huan:"还环换欢患缓唤焕幻痪桓寰涣宦垸洹浣豢奂郇圜獾鲩鬟萑逭漶锾缳擐",xun:"讯训迅孙寻询循旬巡汛勋逊熏徇浚殉驯鲟薰荀浔洵峋埙巽郇醺恂荨窨蕈曛獯",huang:"黄荒煌皇凰慌晃潢谎惶簧璜恍幌湟蝗磺隍徨遑肓篁鳇蟥癀",nai:"能乃奶耐奈鼐萘氖柰佴艿",luan:"乱卵滦峦鸾栾銮挛孪脔娈",qie:"切且契窃茄砌锲怯伽惬妾趄挈郄箧慊",jian:"建间件见坚检健监减简艰践兼鉴键渐柬剑尖肩舰荐箭浅剪俭碱茧奸歼拣捡煎贱溅槛涧堑笺谏饯锏缄睑謇蹇腱菅翦戬毽笕犍硷鞯牮枧湔鲣囝裥踺搛缣鹣蒹谫僭戋趼楗",nan:"南难男楠喃囡赧腩囝蝻",qian:"前千钱签潜迁欠纤牵浅遣谦乾铅歉黔谴嵌倩钳茜虔堑钎骞阡掮钤扦芊犍荨仟芡悭缱佥愆褰凵肷岍搴箝慊椠",qiang:"强抢疆墙枪腔锵呛羌蔷襁羟跄樯戕嫱戗炝镪锖蜣",xiang:"向项相想乡象响香降像享箱羊祥湘详橡巷翔襄厢镶飨饷缃骧芗庠鲞葙蟓",jiao:"教交较校角觉叫脚缴胶轿郊焦骄浇椒礁佼蕉娇矫搅绞酵剿嚼饺窖跤蛟侥狡姣皎茭峤铰醮鲛湫徼鹪僬噍艽挢敫",zhuo:"着著缴桌卓捉琢灼浊酌拙茁涿镯淖啄濯焯倬擢斫棹诼浞禚",qiao:"桥乔侨巧悄敲俏壳雀瞧翘窍峭锹撬荞跷樵憔鞘橇峤诮谯愀鞒硗劁缲",xiao:"小效销消校晓笑肖削孝萧俏潇硝宵啸嚣霄淆哮筱逍姣箫骁枭哓绡蛸崤枵魈",si:"司四思斯食私死似丝饲寺肆撕泗伺嗣祀厮驷嘶锶俟巳蛳咝耜笥纟糸鸶缌澌姒汜厶兕",kai:"开凯慨岂楷恺揩锴铠忾垲剀锎蒈",jin:"进金今近仅紧尽津斤禁锦劲晋谨筋巾浸襟靳瑾烬缙钅矜觐堇馑荩噤廑妗槿赆衿卺",qin:"亲勤侵秦钦琴禽芹沁寝擒覃噙矜嗪揿溱芩衾廑锓吣檎螓",jing:"经京精境竞景警竟井惊径静劲敬净镜睛晶颈荆兢靖泾憬鲸茎腈菁胫阱旌粳靓痉箐儆迳婧肼刭弪獍",ying:"应营影英景迎映硬盈赢颖婴鹰荧莹樱瑛蝇萦莺颍膺缨瀛楹罂荥萤鹦滢蓥郢茔嘤璎嬴瘿媵撄潆",jiu:"就究九酒久救旧纠舅灸疚揪咎韭玖臼柩赳鸠鹫厩啾阄桕僦鬏",zui:"最罪嘴醉咀蕞觜",juan:"卷捐圈眷娟倦绢隽镌涓鹃鄄蠲狷锩桊",suan:"算酸蒜狻",yun:"员运云允孕蕴韵酝耘晕匀芸陨纭郧筠恽韫郓氲殒愠昀菀狁",qun:"群裙逡麇",ka:"卡喀咖咔咯佧胩",kang:"康抗扛慷炕亢糠伉钪闶",keng:"坑铿吭",kao:"考靠烤拷铐栲尻犒",ken:"肯垦恳啃龈裉",yin:"因引银印音饮阴隐姻殷淫尹荫吟瘾寅茵圻垠鄞湮蚓氤胤龈窨喑铟洇狺夤廴吲霪茚堙",kong:"空控孔恐倥崆箜",ku:"苦库哭酷裤枯窟挎骷堀绔刳喾",kua:"跨夸垮挎胯侉",kui:"亏奎愧魁馈溃匮葵窥盔逵睽馗聩喟夔篑岿喹揆隗傀暌跬蒉愦悝蝰",kuan:"款宽髋",kuang:"况矿框狂旷眶匡筐邝圹哐贶夼诳诓纩",que:"确却缺雀鹊阙瘸榷炔阕悫",kun:"困昆坤捆琨锟鲲醌髡悃阃",kuo:"扩括阔廓蛞",la:"拉落垃腊啦辣蜡喇剌旯砬邋瘌",lai:"来莱赖睐徕籁涞赉濑癞崃疠铼",lan:"兰览蓝篮栏岚烂滥缆揽澜拦懒榄斓婪阑褴罱啉谰镧漤",lin:"林临邻赁琳磷淋麟霖鳞凛拎遴蔺吝粼嶙躏廪檩啉辚膦瞵懔",lang:"浪朗郎廊狼琅榔螂阆锒莨啷蒗稂",liang:"量两粮良辆亮梁凉谅粱晾靓踉莨椋魉墚",lao:"老劳落络牢捞涝烙姥佬崂唠酪潦痨醪铑铹栳耢",mu:"目模木亩幕母牧莫穆姆墓慕牟牡募睦缪沐暮拇姥钼苜仫毪坶",le:"了乐勒肋叻鳓嘞仂泐",lei:"类累雷勒泪蕾垒磊擂镭肋羸耒儡嫘缧酹嘞诔檑",sui:"随岁虽碎尿隧遂髓穗绥隋邃睢祟濉燧谇眭荽",lie:"列烈劣裂猎冽咧趔洌鬣埒捩躐",leng:"冷愣棱楞塄",ling:"领令另零灵龄陵岭凌玲铃菱棱伶羚苓聆翎泠瓴囹绫呤棂蛉酃鲮柃",lia:"俩",liao:"了料疗辽廖聊寥缪僚燎缭撂撩嘹潦镣寮蓼獠钌尥鹩",liu:"流刘六留柳瘤硫溜碌浏榴琉馏遛鎏骝绺镏旒熘鹨锍",lun:"论轮伦仑纶沦抡囵",lv:"率律旅绿虑履吕铝屡氯缕滤侣驴榈闾偻褛捋膂稆",lou:"楼露漏陋娄搂篓喽镂偻瘘髅耧蝼嵝蒌",mao:"贸毛矛冒貌茂茅帽猫髦锚懋袤牦卯铆耄峁瑁蟊茆蝥旄泖昴瞀",long:"龙隆弄垄笼拢聋陇胧珑窿茏咙砻垅泷栊癃",nong:"农浓弄脓侬哝",shuang:"双爽霜孀泷",shu:"术书数属树输束述署熟殊蔬舒疏鼠淑叔暑枢墅俞曙抒竖蜀薯梳戍恕孰沭赎庶漱塾倏澍纾姝菽黍腧秫毹殳疋摅",shuai:"率衰帅摔甩蟀",lve:"略掠锊",ma:"么马吗摩麻码妈玛嘛骂抹蚂唛蟆犸杩",me:"么麽",mai:"买卖麦迈脉埋霾荬劢",man:"满慢曼漫埋蔓瞒蛮鳗馒幔谩螨熳缦镘颟墁鞔嫚",mi:"米密秘迷弥蜜谜觅靡泌眯麋猕谧咪糜宓汨醚嘧弭脒冖幂祢縻蘼芈糸敉",men:"们门闷瞒汶扪焖懑鞔钔",mang:"忙盲茫芒氓莽蟒邙硭漭",meng:"蒙盟梦猛孟萌氓朦锰檬勐懵蟒蜢虻黾蠓艨甍艋瞢礞",miao:"苗秒妙描庙瞄缪渺淼藐缈邈鹋杪眇喵",mou:"某谋牟缪眸哞鍪蛑侔厶",miu:"缪谬",mei:"美没每煤梅媒枚妹眉魅霉昧媚玫酶镁湄寐莓袂楣糜嵋镅浼猸鹛",wen:"文问闻稳温纹吻蚊雯紊瘟汶韫刎璺玟阌",mie:"灭蔑篾乜咩蠛",ming:"明名命鸣铭冥茗溟酩瞑螟暝",na:"内南那纳拿哪娜钠呐捺衲镎肭",nei:"内那哪馁",nuo:"难诺挪娜糯懦傩喏搦锘",ruo:"若弱偌箬",nang:"囊馕囔曩攮",nao:"脑闹恼挠瑙淖孬垴铙桡呶硇猱蛲",ni:"你尼呢泥疑拟逆倪妮腻匿霓溺旎昵坭铌鲵伲怩睨猊",nen:"嫩恁",neng:"能",nin:"您恁",niao:"鸟尿溺袅脲茑嬲",nie:"摄聂捏涅镍孽捻蘖啮蹑嗫臬镊颞乜陧",niang:"娘酿",ning:"宁凝拧泞柠咛狞佞聍甯",nu:"努怒奴弩驽帑孥胬",nv:"女钕衄恧",ru:"入如女乳儒辱汝茹褥孺濡蠕嚅缛溽铷洳薷襦颥蓐",nuan:"暖",nve:"虐疟",re:"热若惹喏",ou:"区欧偶殴呕禺藕讴鸥瓯沤耦怄",pao:"跑炮泡抛刨袍咆疱庖狍匏脬",pou:"剖掊裒",pen:"喷盆湓",pie:"瞥撇苤氕丿",pin:"品贫聘频拼拚颦姘嫔榀牝",se:"色塞瑟涩啬穑铯槭",qing:"情青清请亲轻庆倾顷卿晴氢擎氰罄磬蜻箐鲭綮苘黥圊檠謦",zan:"赞暂攒堑昝簪糌瓒錾趱拶",shao:"少绍召烧稍邵哨韶捎勺梢鞘芍苕劭艄筲杓潲",sao:"扫骚嫂梢缫搔瘙臊埽缲鳋",sha:"沙厦杀纱砂啥莎刹杉傻煞鲨霎嗄痧裟挲铩唼歃",xuan:"县选宣券旋悬轩喧玄绚渲璇炫萱癣漩眩暄煊铉楦泫谖痃碹揎镟儇",ran:"然染燃冉苒髯蚺",rang:"让壤攘嚷瓤穰禳",rao:"绕扰饶娆桡荛",reng:"仍扔",ri:"日",rou:"肉柔揉糅鞣蹂",ruan:"软阮朊",run:"润闰",sa:"萨洒撒飒卅仨脎",suo:"所些索缩锁莎梭琐嗦唆唢娑蓑羧挲桫嗍睃",sai:"思赛塞腮噻鳃",shui:"说水税谁睡氵",sang:"桑丧嗓搡颡磉",sen:"森",seng:"僧",shai:"筛晒",shang:"上商尚伤赏汤裳墒晌垧觞殇熵绱",xing:"行省星腥猩惺兴刑型形邢饧醒幸杏性姓陉荇荥擤悻硎",shou:"收手受首售授守寿瘦兽狩绶艏扌",shuo:"说数硕烁朔铄妁槊蒴搠",su:"速素苏诉缩塑肃俗宿粟溯酥夙愫簌稣僳谡涑蔌嗉觫",shua:"刷耍唰",shuan:"栓拴涮闩",shun:"顺瞬舜吮",song:"送松宋讼颂耸诵嵩淞怂悚崧凇忪竦菘",sou:"艘搜擞嗽嗖叟馊薮飕嗾溲锼螋瞍",sun:"损孙笋荪榫隼狲飧",teng:"腾疼藤滕誊",tie:"铁贴帖餮萜",tu:"土突图途徒涂吐屠兔秃凸荼钍菟堍酴",wai:"外歪崴",wang:"王望往网忘亡旺汪枉妄惘罔辋魍",weng:"翁嗡瓮蓊蕹",zhua:"抓挝爪",yang:"样养央阳洋扬杨羊详氧仰秧痒漾疡泱殃恙鸯徉佯怏炀烊鞅蛘",xiong:"雄兄熊胸凶匈汹芎",yo:"哟唷",yong:"用永拥勇涌泳庸俑踊佣咏雍甬镛臃邕蛹恿慵壅痈鳙墉饔喁",za:"杂扎咱砸咋匝咂拶",zai:"在再灾载栽仔宰哉崽甾",zao:"造早遭枣噪灶燥糟凿躁藻皂澡蚤唣",zei:"贼",zen:"怎谮",zeng:"增曾综赠憎锃甑罾缯",zhei:"这",zou:"走邹奏揍诹驺陬楱鄹鲰",zhuai:"转拽",zun:"尊遵鳟樽撙",dia:"嗲",nou:"耨"})};return f}));
assets/preview_dark.png ADDED

Git LFS Details

  • SHA256: a47b980857d9b64ff5eafaaca6c22b0a2de0dc07b33544cc5afdbdb0237d7edd
  • Pointer size: 131 Bytes
  • Size of remote file: 413 kB
assets/preview_light.png ADDED

Git LFS Details

  • SHA256: 1f2c430eea96f46f35907ca5b2b73a39a6314cbab3df2e7d655856561dd7c9a6
  • Pointer size: 131 Bytes
  • Size of remote file: 442 kB
assets/style.css ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ============================================
2
+ Menav Style System - Modular Entry Point
3
+ ============================================
4
+
5
+ 模块化 CSS 架构:
6
+ - 本文件作为入口,通过 @import 聚合所有模块
7
+ - esbuild bundle 模式会在构建时自动合并
8
+ - 模块导入顺序很重要!变量必须第一个导入
9
+
10
+ ============================================ */
11
+
12
+ /* 1. 基础层:变量和全局样式必须最先加载 */
13
+ @import './styles/_variables.css';
14
+ @import './styles/_base.css';
15
+ @import './styles/_animations.css';
16
+
17
+ /* 2. 布局层 */
18
+ @import './styles/_layout.css';
19
+ @import './styles/_sidebar.css';
20
+
21
+ /* 3. 组件层 */
22
+ @import './styles/_search.css';
23
+ @import './styles/_cards.css';
24
+ @import './styles/_modal.css';
25
+
26
+ /* 4. 页面层 */
27
+ @import './styles/_content.css';
28
+ @import './styles/_dashboard.css';
29
+
30
+ /* 5. 兜底样式(包含分类层级、GitHub热力图、响应式等)
31
+ 注意:此文件包含所有未独立拆分的样式,部分选择器可能与上方模块重复
32
+ esbuild 会按导入顺序合并,后导入的样式优先级更高 */
33
+ @import './styles/_main.css';
assets/styles/_animations.css ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ============================================
2
+ Animations & Keyframes
3
+ ============================================ */
4
+
5
+ @keyframes glow {
6
+ from {
7
+ filter: drop-shadow(0 0 2px rgba(118, 148, 185, 0.2))
8
+ drop-shadow(0 0 4px rgba(168, 85, 247, 0.2));
9
+ }
10
+
11
+ to {
12
+ filter: drop-shadow(0 0 4px rgba(118, 148, 185, 0.4))
13
+ drop-shadow(0 0 8px rgba(168, 85, 247, 0.4));
14
+ }
15
+ }
16
+
17
+ @keyframes fadeIn {
18
+ from {
19
+ opacity: 0;
20
+ transform: translateY(10px);
21
+ }
22
+
23
+ to {
24
+ opacity: 1;
25
+ transform: translateY(0);
26
+ }
27
+ }
28
+
29
+ @keyframes modalFadeIn {
30
+ from {
31
+ opacity: 0;
32
+ }
33
+
34
+ to {
35
+ opacity: 1;
36
+ }
37
+ }
38
+
39
+ @keyframes modalContentShow {
40
+ from {
41
+ opacity: 0;
42
+ transform: scale(0.95);
43
+ }
44
+
45
+ to {
46
+ opacity: 1;
47
+ transform: scale(1);
48
+ }
49
+ }
assets/styles/_base.css ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ============================================
2
+ Base Styles & Global Reset
3
+ ============================================ */
4
+
5
+ /* 可访问性:视觉隐藏但保留屏幕阅读器可读 */
6
+ .visually-hidden {
7
+ position: absolute !important;
8
+ width: 1px;
9
+ height: 1px;
10
+ padding: 0;
11
+ margin: -1px;
12
+ overflow: hidden;
13
+ clip: rect(0, 0, 0, 0);
14
+ white-space: nowrap;
15
+ border: 0;
16
+ }
17
+
18
+ /* 主题切换按钮 - 调整为 iOS 风格 */
19
+ .theme-toggle {
20
+ position: fixed;
21
+ bottom: var(--spacing-xl);
22
+ right: var(--spacing-xl);
23
+ width: 2.5rem;
24
+ height: 2.5rem;
25
+ border-radius: var(--radius-lg);
26
+ background: rgba(var(--card-bg-rgb), 0.65);
27
+ border: 1px solid var(--border-color);
28
+ backdrop-filter: blur(12px);
29
+ -webkit-backdrop-filter: blur(12px);
30
+ color: var(--text-color);
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: center;
34
+ cursor: pointer;
35
+ transition: all var(--transition-normal);
36
+ transition-timing-function: var(--transition-bounce);
37
+ z-index: 100;
38
+ box-shadow: 0 4px 16px var(--shadow-color);
39
+ }
40
+
41
+ .theme-toggle:hover {
42
+ transform: translateY(-2px);
43
+ background: rgba(var(--card-bg-rgb), 0.75);
44
+ box-shadow: 0 6px 20px var(--shadow-color);
45
+ color: var(--accent-color);
46
+ }
47
+
48
+ .theme-toggle:active {
49
+ transform: translateY(0);
50
+ box-shadow: 0 2px 8px var(--shadow-color);
51
+ }
52
+
53
+ .theme-toggle i {
54
+ font-size: 18px;
55
+ }
56
+
57
+ /* 全局样式 */
58
+ * {
59
+ margin: 0;
60
+ padding: 0;
61
+ box-sizing: border-box;
62
+ }
63
+
64
+ /* 全局 Focus Visible 样式 */
65
+ :focus-visible {
66
+ outline: 2px solid var(--accent-color);
67
+ outline-offset: 2px;
68
+ }
69
+
70
+ /* 导航项 Focus 样式 */
71
+ .nav-item:focus-visible,
72
+ .submenu-item:focus-visible,
73
+ .site-card:focus-visible,
74
+ .theme-toggle:focus-visible,
75
+ .category-toggle:focus-visible,
76
+ .menu-toggle:focus-visible,
77
+ .search-toggle:focus-visible {
78
+ outline: 2px solid var(--accent-color);
79
+ outline-offset: 2px;
80
+ z-index: 10;
81
+ }
82
+
83
+ /* 通用滚动条样式 */
84
+ .custom-scrollbar {
85
+ scrollbar-width: thin;
86
+ /* Firefox */
87
+ scrollbar-color: var(--scrollbar-color) transparent;
88
+ /* Firefox */
89
+ }
90
+
91
+ /* Webkit滚动条样式(Chrome, Safari, Edge等) */
92
+ .custom-scrollbar::-webkit-scrollbar {
93
+ width: 7px;
94
+ /* 统一滚动条宽度 */
95
+ }
96
+
97
+ .custom-scrollbar::-webkit-scrollbar-track {
98
+ background: transparent;
99
+ }
100
+
101
+ .custom-scrollbar::-webkit-scrollbar-thumb {
102
+ background-color: var(--scrollbar-color);
103
+ /* 使用变量 */
104
+ border-radius: 4px;
105
+ }
106
+
107
+ .custom-scrollbar::-webkit-scrollbar-thumb:hover {
108
+ background-color: var(--scrollbar-hover-color);
109
+ /* 使用变量 */
110
+ }
111
+
112
+ /* 防止滚动条导致的布局偏移 */
113
+ html {
114
+ overflow-y: hidden;
115
+ /* 改为hidden,移除强制显示的滚动条 */
116
+ scrollbar-width: thin;
117
+ /* Firefox */
118
+ /* 明确 rem 基准字号:便于用 rem 统一管理字号(1rem = 16px) */
119
+ font-size: 16px;
120
+ }
121
+
122
+ /* 搜索高亮样式 */
123
+ .highlight {
124
+ background-color: var(--highlight-bg);
125
+ border-radius: var(--radius-sm);
126
+ padding: 0 2px;
127
+ font-weight: bold;
128
+ color: var(--text-color);
129
+ }
130
+
131
+ body {
132
+ font-family: var(
133
+ --font-body,
134
+ system-ui,
135
+ -apple-system,
136
+ 'Segoe UI',
137
+ Roboto,
138
+ 'Noto Sans',
139
+ 'Helvetica Neue',
140
+ Arial,
141
+ sans-serif
142
+ );
143
+ font-weight: var(--font-weight-body, normal);
144
+ line-height: 1.6;
145
+ background-color: var(--bg-color);
146
+ color: var(--text-color);
147
+ min-height: var(--app-height, 100vh);
148
+ overflow: hidden;
149
+ /* 防止body滚动 */
150
+ padding-right: 0 !important;
151
+ /* 防止滚动条导致的布局偏移 */
152
+ transition:
153
+ background-color 0.3s ease,
154
+ color 0.3s ease;
155
+ }
156
+
157
+ /* 布局 */
158
+ .layout {
159
+ display: flex;
160
+ min-height: var(--app-height, 100vh);
161
+ position: relative;
162
+ z-index: 1;
163
+ overflow: hidden;
164
+ /* 防止layout滚动 */
165
+ opacity: 0;
166
+ transition: opacity 0.3s ease;
167
+ }
168
+
169
+ /* 确保加载后立即显示 */
170
+ body.loaded .layout {
171
+ opacity: 1;
172
+ }
173
+
174
+ /* 移动端基础样式 */
175
+ .mobile-buttons {
176
+ display: none;
177
+ position: fixed;
178
+ top: var(--spacing-md);
179
+ top: calc(env(safe-area-inset-top) + var(--spacing-md));
180
+ left: 0;
181
+ right: 0;
182
+ width: 100%;
183
+ padding: 0 var(--spacing-md);
184
+ padding-left: calc(env(safe-area-inset-left) + var(--spacing-md));
185
+ padding-right: calc(env(safe-area-inset-right) + var(--spacing-md));
186
+ justify-content: space-between;
187
+ z-index: 910;
188
+ pointer-events: none;
189
+ }
190
+
191
+ .menu-toggle,
192
+ .search-toggle {
193
+ background: var(--sidebar-bg);
194
+ border: none;
195
+ color: var(--text-color);
196
+ width: 2.5rem;
197
+ height: 2.5rem;
198
+ border-radius: var(--radius-md);
199
+ display: flex;
200
+ align-items: center;
201
+ justify-content: center;
202
+ cursor: pointer;
203
+ transition: all 0.25s var(--transition-bounce);
204
+ box-shadow: 0 2px 8px var(--shadow-color);
205
+ pointer-events: auto;
206
+ }
207
+
208
+ .menu-toggle:hover,
209
+ .search-toggle:hover {
210
+ background: var(--secondary-bg);
211
+ transform: translateY(-2px);
212
+ box-shadow: 0 4px 12px var(--shadow-color);
213
+ }
214
+
215
+ .menu-toggle:active,
216
+ .search-toggle:active {
217
+ transform: translateY(0);
218
+ }
219
+
220
+ /* 遮罩层 */
221
+ .overlay {
222
+ position: fixed;
223
+ top: 0;
224
+ left: 0;
225
+ width: 100%;
226
+ height: 100%;
227
+ background: rgba(0, 0, 0, 0.5);
228
+ opacity: 0;
229
+ visibility: hidden;
230
+ transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
231
+ z-index: 950;
232
+ /* 调整遮罩层z-index,处于按钮与弹出面板之间 */
233
+ }
234
+
235
+ .overlay.active {
236
+ opacity: 1;
237
+ visibility: visible;
238
+ }
assets/styles/_cards.css ADDED
@@ -0,0 +1,1365 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Cards Component */
2
+
3
+ .site-card {
4
+ display: flex;
5
+ align-items: center;
6
+ padding: 1rem;
7
+ /* More generous padding */
8
+ gap: 0.8rem;
9
+ background-color: var(--site-card-bg-gradient-1);
10
+ /* Solid color */
11
+ border-radius: var(--radius-lg);
12
+ /* Larger radius */
13
+ text-decoration: none;
14
+ color: var(--text-color);
15
+ transition:
16
+ transform var(--transition-normal),
17
+ box-shadow var(--transition-normal),
18
+ background-color var(--transition-normal);
19
+ /* Spring transition */
20
+ position: relative;
21
+ overflow: hidden;
22
+ border: 0.5px solid var(--border-color);
23
+ /* Subtle separator */
24
+ /* Remove hard shadow by default for cleaner look, only adding on hover or using very subtle depth */
25
+ }
26
+
27
+ .site-card:hover {
28
+ transform: translateY(-2px) scale(1.01);
29
+ /* Subtle scale */
30
+ background-color: var(--site-card-hover-bg);
31
+ box-shadow: 0 8px 20px var(--shadow-color);
32
+ /* Deep soft shadow */
33
+ z-index: 2;
34
+ border-color: transparent;
35
+ /* Merge with shadow */
36
+ }
37
+
38
+ /* 分类样式 */
39
+ .category {
40
+ background: linear-gradient(145deg, var(--card-bg-gradient-1), var(--card-bg-gradient-2));
41
+ border-radius: var(--radius-xl);
42
+ padding: 1rem;
43
+ margin: 0 auto 1.2rem auto;
44
+ width: 100%;
45
+ max-width: var(--page-max-width);
46
+ position: relative;
47
+ z-index: 1;
48
+ opacity: 1;
49
+ box-shadow: 0 4px 20px var(--shadow-color);
50
+ border: 1px solid var(--border-color);
51
+ transition:
52
+ background var(--transition-normal),
53
+ box-shadow var(--transition-normal);
54
+ }
55
+
56
+ /* 分类标题容器 */
57
+ .category-header {
58
+ border-radius: var(--radius-md);
59
+ padding: 0.4rem;
60
+ margin: -0.4rem -0.4rem 0.8rem -0.4rem;
61
+ transition: all var(--transition-normal);
62
+ }
63
+
64
+ /* 标题前图标固定宽度:避免不同图标宽度导致标题文本不对齐 */
65
+ .category-header [data-editable='category-name'] > i,
66
+ .group-header [data-editable='group-name'] > i {
67
+ width: 1.25em;
68
+ min-width: 1.25em;
69
+ text-align: center;
70
+ flex: 0 0 1.25em;
71
+ }
72
+
73
+ /* 分组标题容器:与分类保持一致的悬浮动效基础 */
74
+ .group-header {
75
+ border-radius: var(--radius-md);
76
+ transition: all var(--transition-normal);
77
+ }
78
+
79
+ /* 仅可折叠的标题显示交互态 */
80
+ .category-header[data-toggle='category'],
81
+ .group-header[data-toggle='group'] {
82
+ cursor: pointer;
83
+ user-select: none;
84
+ }
85
+
86
+ .category-header[data-toggle='category']:hover {
87
+ transform: translateY(-2px);
88
+ background: linear-gradient(145deg, rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0.01));
89
+ }
90
+
91
+ .group-header[data-toggle='group']:hover {
92
+ transform: translateY(-2px);
93
+ background: linear-gradient(145deg, rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0.01));
94
+ }
95
+
96
+ .category-header[data-toggle='category']:active {
97
+ transform: translateY(0);
98
+ }
99
+
100
+ .group-header[data-toggle='group']:active {
101
+ transform: translateY(0);
102
+ }
103
+
104
+ .category h2 {
105
+ font-size: 1.2rem;
106
+ margin-bottom: 0;
107
+ color: var(--text-bright);
108
+ display: flex;
109
+ align-items: center;
110
+ gap: 0.8rem;
111
+ letter-spacing: 0.3px;
112
+ transition: color 0.3s ease;
113
+ }
114
+
115
+ .category h2 > i {
116
+ color: var(--accent-color);
117
+ font-size: 1.3rem;
118
+ transition: all 0.3s ease;
119
+ }
120
+
121
+ .category-header[data-toggle='category']:hover h2 > i {
122
+ transform: scale(1.1);
123
+ color: var(--accent-hover);
124
+ }
125
+
126
+ /* 多层级嵌套样式 - 扁平化设计 */
127
+
128
+ /* 通用重置:移除所有嵌套层级的卡片背景和边框 */
129
+ .category-level-2,
130
+ .category-level-3,
131
+ .category-level-4,
132
+ .group-level-3,
133
+ .group-level-4 {
134
+ background: none;
135
+ border: none;
136
+ box-shadow: none;
137
+ padding: 0;
138
+ width: 100%;
139
+ margin: 0;
140
+ }
141
+
142
+ /* 嵌套层级指示线 (Hierarchy Indicator A) */
143
+ .category-level-2::before,
144
+ .category-level-3::before,
145
+ .group-level-3::before,
146
+ .category-level-4::before,
147
+ .group-level-4::before {
148
+ content: '';
149
+ position: absolute;
150
+ left: 0;
151
+ top: 0;
152
+ bottom: 0;
153
+ width: 2px;
154
+ background-color: var(--border-color);
155
+ opacity: 0.6;
156
+ }
157
+
158
+ /* 层级2: 子分类 */
159
+ .category-level-2 {
160
+ margin-top: 0;
161
+ margin-bottom: 0;
162
+ padding-left: 1rem;
163
+ border-left: none;
164
+ position: relative;
165
+ }
166
+
167
+ /* 层级2: 标题样式 */
168
+ .category-level-2 .category-header {
169
+ margin: 0 -0.5rem 1rem -0.5rem;
170
+ padding: 0.5rem;
171
+ background: none;
172
+ border-radius: var(--radius-md);
173
+ }
174
+
175
+ .category-level-2 .category-header h3 {
176
+ font-size: 1.1rem;
177
+ font-weight: 600;
178
+ color: var(--text-bright);
179
+ display: flex;
180
+ align-items: center;
181
+ gap: 0.8rem;
182
+ }
183
+
184
+ .category-level-2 .category-header h3 > i {
185
+ color: var(--accent-color);
186
+ font-size: 1.2rem;
187
+ opacity: 0.9;
188
+ }
189
+
190
+ /* 层级3: 分组 */
191
+ .group-level-3,
192
+ .category-level-3 {
193
+ margin-top: 0;
194
+ margin-bottom: 0;
195
+ padding-left: 1rem;
196
+ position: relative;
197
+ }
198
+
199
+ /* 层级3: 标题样式 */
200
+ .group-level-3 .group-header,
201
+ .category-level-3 .category-header {
202
+ margin: 0 0 0.8rem 0;
203
+ padding: 0.3rem 0;
204
+ background: none;
205
+ }
206
+
207
+ .group-level-3 .group-header h4,
208
+ .category-level-3 .category-header h4 {
209
+ font-size: 1rem;
210
+ font-weight: 500;
211
+ color: var(--text-color);
212
+ display: flex;
213
+ align-items: center;
214
+ gap: 0.6rem;
215
+ }
216
+
217
+ .group-level-3 .group-header h4 i,
218
+ .category-level-3 .category-header h4 i {
219
+ color: var(--text-muted);
220
+ font-size: 1rem;
221
+ }
222
+
223
+ /* 层级4: 子分组 */
224
+ .group-level-4,
225
+ .category-level-4 {
226
+ margin-top: 0;
227
+ margin-bottom: 0;
228
+ padding-left: 1rem;
229
+ position: relative;
230
+ }
231
+
232
+ /* 嵌套层级间距:仅在同级相邻时增加间距,避免首项被额外下推 */
233
+ .subcategories-container > .category-level-2 + .category-level-2 {
234
+ margin-top: 1rem;
235
+ }
236
+
237
+ .groups-container > .group-level-3 + .group-level-3,
238
+ .groups-container > .category-level-3 + .category-level-3 {
239
+ margin-top: 0.8rem;
240
+ }
241
+
242
+ .subgroups-container > .group-level-4 + .group-level-4,
243
+ .subcategories-container > .category-level-4 + .category-level-4 {
244
+ margin-top: 0.6rem;
245
+ }
246
+
247
+ /* 层级4: 标题样式 */
248
+ .group-level-4 .group-header,
249
+ .category-level-4 .category-header {
250
+ margin: 0 0 0.6rem 0;
251
+ padding: 0.2rem 0;
252
+ background: none;
253
+ }
254
+
255
+ .group-level-4 .group-header h5,
256
+ .category-level-4 .category-header h5 {
257
+ font-size: 0.9rem;
258
+ font-weight: 500;
259
+ color: var(--text-muted);
260
+ display: flex;
261
+ align-items: center;
262
+ gap: 0.5rem;
263
+ }
264
+
265
+ .group-level-4 .group-header h5 i,
266
+ .category-level-4 .category-header h5 i {
267
+ font-size: 0.9rem;
268
+ opacity: 0.7;
269
+ }
270
+
271
+ /* 移除悬停时的缩放效果,保持简洁 */
272
+ .category-level-2 .category-header:hover h3 > i,
273
+ .group-level-3 .group-header:hover h4 i,
274
+ .category-level-3 .category-header:hover h4 i,
275
+ .group-level-4 .group-header:hover h5 i,
276
+ .category-level-4 .category-header:hover h5 i {
277
+ transform: none;
278
+ }
279
+
280
+ /* 切换图标样式 */
281
+ .category-header .toggle-icon,
282
+ .group-header .toggle-icon {
283
+ display: inline-flex;
284
+ align-items: center;
285
+ justify-content: center;
286
+ width: 20px;
287
+ height: 20px;
288
+ margin-left: auto;
289
+ color: var(--text-muted);
290
+ font-size: 0.9rem;
291
+ }
292
+
293
+ .category-header .toggle-icon i,
294
+ .group-header .toggle-icon i {
295
+ transition:
296
+ transform 0.3s ease,
297
+ color 0.3s ease;
298
+ transform: rotate(0deg);
299
+ }
300
+
301
+ /* 展开态:图标旋转 180°(类似参考样式1) */
302
+ .category:not(.collapsed) > .category-header .toggle-icon i,
303
+ .group:not(.collapsed) > .group-header .toggle-icon i {
304
+ transform: rotate(180deg);
305
+ color: var(--text-bright);
306
+ }
307
+
308
+ .category-header[data-toggle='category']:hover .toggle-icon i,
309
+ .group-header[data-toggle='group']:hover .toggle-icon i {
310
+ color: var(--accent-color);
311
+ }
312
+
313
+ /* 分类/分组折叠图标:桌面端默认隐藏,悬停/收起时显示,避免按钮过多 */
314
+ @media (hover: hover) and (pointer: fine) {
315
+ .category-header .toggle-icon,
316
+ .group-header .toggle-icon {
317
+ opacity: 0;
318
+ transition: opacity 0.2s ease;
319
+ }
320
+
321
+ .category-header[data-toggle='category']:hover .toggle-icon,
322
+ .category.collapsed > .category-header .toggle-icon,
323
+ .group-header[data-toggle='group']:hover .toggle-icon,
324
+ .group.collapsed > .group-header .toggle-icon {
325
+ opacity: 1;
326
+ }
327
+ }
328
+
329
+ /* 展开/折叠动画 */
330
+ .category-content,
331
+ .group-content {
332
+ overflow: visible;
333
+ transition:
334
+ max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1),
335
+ opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);
336
+ max-height: 5000px;
337
+ opacity: 1;
338
+ }
339
+
340
+ .category.collapsed .category-content,
341
+ .group.collapsed .group-content {
342
+ overflow: hidden;
343
+ max-height: 0;
344
+ opacity: 0;
345
+ margin-top: 0;
346
+ }
347
+
348
+ /* 收起状态下调整header的下边距 */
349
+ .category.collapsed > .category-header {
350
+ margin-bottom: -0.5rem;
351
+ }
352
+
353
+ .category-level-2.collapsed > .category-header {
354
+ margin-bottom: 0;
355
+ border-bottom: none;
356
+ }
357
+
358
+ .group-level-3.collapsed > .group-header,
359
+ .category-level-3.collapsed > .category-header {
360
+ margin-bottom: 0;
361
+ }
362
+
363
+ .group-level-4.collapsed > .group-header,
364
+ .category-level-4.collapsed > .category-header {
365
+ margin-bottom: 0;
366
+ }
367
+
368
+ /* 收起态默认向下,无需额外旋转(保持 0deg) */
369
+
370
+ /* 空内容提示 */
371
+ .empty-content {
372
+ color: var(--text-muted);
373
+ font-style: italic;
374
+ text-align: center;
375
+ padding: 1rem;
376
+ font-size: 0.9rem;
377
+ }
378
+
379
+ /* 子容器样式 */
380
+ .subcategories-container,
381
+ .groups-container {
382
+ width: 100%;
383
+ }
384
+
385
+ /* 当分类同时包含子分类和站点时的样式优化 */
386
+ .category-content .subcategories-container + .sites-grid {
387
+ margin-top: 1.2rem;
388
+ padding-top: 1rem;
389
+ border-top: 1px solid var(--border-color);
390
+ }
391
+
392
+ /* 当分类同时包含分组和站点时的样式优化 */
393
+ .category-content .groups-container + .sites-grid {
394
+ margin-top: 1.2rem;
395
+ padding-top: 1rem;
396
+ border-top: 1px solid var(--border-color);
397
+ }
398
+
399
+ /* 子分类容器底部间距调整 */
400
+ .category-content .subcategories-container:not(:last-child),
401
+ .category-content .groups-container:not(:last-child) {
402
+ margin-bottom: 0.6rem;
403
+ }
404
+
405
+ /* 确保嵌套的网站网格正确显示 */
406
+ .category-level-2 .sites-grid,
407
+ .group-level-3 .sites-grid,
408
+ .category-level-3 .sites-grid,
409
+ .group-level-4 .sites-grid,
410
+ .category-level-4 .sites-grid {
411
+ margin-top: 0;
412
+ gap: 0.75rem;
413
+ /* 保持与顶层一致的网格布局 */
414
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
415
+ }
416
+
417
+ /* 响应式设计 - 嵌套结构 */
418
+ @media (max-width: 768px) {
419
+ .category-level-2 {
420
+ padding-left: 0.75rem;
421
+ }
422
+
423
+ .group-level-3,
424
+ .category-level-3 {
425
+ padding-left: 0.75rem;
426
+ }
427
+
428
+ .group-level-4,
429
+ .category-level-4 {
430
+ padding-left: 0.75rem;
431
+ }
432
+
433
+ .subcategories-container > .category-level-2 + .category-level-2 {
434
+ margin-top: 0.8rem;
435
+ }
436
+
437
+ .groups-container > .group-level-3 + .group-level-3,
438
+ .groups-container > .category-level-3 + .category-level-3 {
439
+ margin-top: 0.7rem;
440
+ }
441
+
442
+ .subgroups-container > .group-level-4 + .group-level-4,
443
+ .subcategories-container > .category-level-4 + .category-level-4 {
444
+ margin-top: 0.55rem;
445
+ }
446
+
447
+ .category-level-2 .sites-grid,
448
+ .group-level-3 .sites-grid,
449
+ .category-level-3 .sites-grid,
450
+ .group-level-4 .sites-grid,
451
+ .category-level-4 .sites-grid {
452
+ grid-template-columns: repeat(2, minmax(0, 1fr));
453
+ gap: var(--spacing-sm);
454
+ }
455
+ }
456
+
457
+ @media (max-width: 480px) {
458
+ .category {
459
+ margin-left: 0.5rem;
460
+ margin-right: 0.5rem;
461
+ padding: 1rem;
462
+ }
463
+
464
+ .category-level-2,
465
+ .group-level-3,
466
+ .category-level-3 {
467
+ margin-left: 0;
468
+ padding-left: 0.75rem;
469
+ width: 100%;
470
+ }
471
+
472
+ .group-level-4,
473
+ .category-level-4 {
474
+ margin-left: 0;
475
+ padding-left: 0.75rem;
476
+ width: 100%;
477
+ }
478
+
479
+ .category-level-2 .sites-grid,
480
+ .group-level-3 .sites-grid,
481
+ .category-level-3 .sites-grid,
482
+ .group-level-4 .sites-grid,
483
+ .category-level-4 .sites-grid {
484
+ grid-template-columns: repeat(2, minmax(0, 1fr));
485
+ gap: 0.5rem;
486
+ }
487
+ }
488
+
489
+ /* 网站卡片网格 */
490
+ .sites-grid {
491
+ display: grid;
492
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
493
+ gap: 0.75rem;
494
+ position: relative;
495
+ z-index: 1;
496
+ width: 100%;
497
+ }
498
+
499
+ /* projects:GitHub 热力图(github-calendar.js,底部展示) */
500
+ .page-template-projects .gh-heatmap-category {
501
+ /* 外层已复用一级分类卡片(.category),这里仅保留热力图内部布局/色阶 */
502
+ --gh-text: var(--text-color);
503
+ --gh-text-muted: var(--text-muted);
504
+ --gh-level-0: rgba(255, 255, 255, 0.08);
505
+ --gh-level-1: rgba(55, 178, 77, 0.35);
506
+ --gh-level-2: rgba(55, 178, 77, 0.55);
507
+ --gh-level-3: rgba(55, 178, 77, 0.75);
508
+ --gh-level-4: rgba(55, 178, 77, 0.95);
509
+ --gh-radius: 3px;
510
+
511
+ margin: 0 auto 1.2rem auto;
512
+ }
513
+
514
+ .page-template-projects .gh-heatmap-wrapper {
515
+ margin-top: 0;
516
+ }
517
+
518
+ /* 浅色主题:更接近 GitHub 原色阶 */
519
+ html.theme-preload .page-template-projects .gh-heatmap-category,
520
+ body.light-theme .page-template-projects .gh-heatmap-category {
521
+ /* 浅色主题下,空格子需要比背景更明显一点 */
522
+ --gh-level-0: #d8dee4;
523
+ --gh-level-1: #9be9a8;
524
+ --gh-level-2: #40c463;
525
+ --gh-level-3: #30a14e;
526
+ --gh-level-4: #216e39;
527
+ }
528
+
529
+ /* 标题中的用户名(@xxx)更弱化一点,像副标题 */
530
+ .page-template-projects .gh-heatmap-username {
531
+ color: var(--text-muted);
532
+ font-weight: 400;
533
+ margin-left: 0.35rem;
534
+ }
535
+
536
+ /* 使用一级分类标题的排版节奏,略收紧热力图区域 */
537
+ .page-template-projects .gh-heatmap-category .category-header {
538
+ margin-bottom: 0.8rem;
539
+ }
540
+
541
+ /* 标题行:左侧标题 + 右侧 legend(不占内容区高度) */
542
+ .page-template-projects .gh-heatmap-category .gh-heatmap-header {
543
+ display: flex;
544
+ align-items: center;
545
+ justify-content: space-between;
546
+ gap: 0.8rem;
547
+ }
548
+
549
+ .page-template-projects .gh-heatmap-category .gh-heatmap-header h2 {
550
+ margin: 0;
551
+ }
552
+
553
+ /* 让 legend 与标题体系保持一致:放在标题区右侧 */
554
+ .page-template-projects .gh-heatmap-category .gh-legend {
555
+ justify-content: flex-end;
556
+ margin: 0;
557
+ }
558
+
559
+ .page-template-projects .gh-header {
560
+ display: none;
561
+ }
562
+
563
+ .page-template-projects .gh-legend {
564
+ display: flex;
565
+ align-items: center;
566
+ gap: 4px;
567
+ font-size: 0.75rem;
568
+ color: var(--gh-text-muted);
569
+ white-space: nowrap;
570
+ }
571
+
572
+ /* heatmap:移动端标题/legend 文本简写 */
573
+ .gh-text-mobile {
574
+ display: none;
575
+ }
576
+
577
+ @media (max-width: 480px) {
578
+ .gh-text-desktop {
579
+ display: none;
580
+ }
581
+
582
+ .gh-text-mobile {
583
+ display: inline;
584
+ }
585
+
586
+ .page-template-projects .gh-heatmap-category .gh-heatmap-header {
587
+ flex-wrap: wrap;
588
+ }
589
+
590
+ .page-template-projects .gh-heatmap-category .gh-legend {
591
+ gap: 2px;
592
+ font-size: 0.7rem;
593
+ }
594
+
595
+ .page-template-projects .gh-legend-item {
596
+ width: 8px;
597
+ height: 8px;
598
+ }
599
+ }
600
+
601
+ .page-template-projects .gh-legend-item {
602
+ width: 10px;
603
+ height: 10px;
604
+ border-radius: 2px;
605
+ }
606
+
607
+ .page-template-projects .gh-legend .level-0 {
608
+ background-color: var(--gh-level-0);
609
+ }
610
+
611
+ .page-template-projects .gh-legend .level-1 {
612
+ background-color: var(--gh-level-1);
613
+ }
614
+
615
+ .page-template-projects .gh-legend .level-2 {
616
+ background-color: var(--gh-level-2);
617
+ }
618
+
619
+ .page-template-projects .gh-legend .level-3 {
620
+ background-color: var(--gh-level-3);
621
+ }
622
+
623
+ .page-template-projects .gh-legend .level-4 {
624
+ background-color: var(--gh-level-4);
625
+ }
626
+
627
+ /* github-calendar 注入的内容容器 */
628
+ .page-template-projects .gh-calendar {
629
+ border: none !important;
630
+ min-height: 0;
631
+ width: 100%;
632
+ /* 构建期注入:避免 flex 居中导致内容超宽“撑破卡片” */
633
+ display: block;
634
+ }
635
+
636
+ /* 顶部统计标题:XXX contributions in the last year */
637
+ .page-template-projects .gh-calendar #js-contribution-activity-description {
638
+ /* 注意:全局 .category h2 会把 h2 设为 flex,导致“看起来左对齐”。这里强制回退为 block。 */
639
+ display: block;
640
+ width: 100%;
641
+ text-align: center;
642
+ margin: 0 0 12px 0;
643
+ }
644
+
645
+ /* 外层包裹:强制占满卡片宽度,滚动发生在内部 */
646
+ .page-template-projects .gh-calendar .graph-before-activity-overview {
647
+ width: 100%;
648
+ }
649
+
650
+ /* 允许移动端横向滚动查看(GitHub 风格) */
651
+ .page-template-projects .gh-calendar .js-calendar-graph {
652
+ width: 100%;
653
+ /* 构建期注入 GitHub 原生 table:滚动交给内层容器,避免裁剪 */
654
+ display: block;
655
+ }
656
+
657
+ /* GitHub 原生 markup:通常在 .js-calendar-graph 内有一个 div 设置 overflow-x */
658
+ .page-template-projects .gh-calendar .js-calendar-graph > div {
659
+ width: 100%;
660
+ max-width: 100%;
661
+ overflow-x: auto;
662
+ overflow-y: hidden;
663
+ -webkit-overflow-scrolling: touch;
664
+ /* 由 table 的 margin:auto 来实现“可居中 + 可滚动时左对齐起始列” */
665
+ display: block;
666
+ padding-bottom: 10px;
667
+ }
668
+
669
+ .page-template-projects .gh-calendar .js-calendar-graph-svg {
670
+ width: 100%;
671
+ height: auto;
672
+ }
673
+
674
+ /* 覆盖每个方块的颜色(依赖 github-calendar 的 data-level) */
675
+ .page-template-projects .gh-calendar .day {
676
+ rx: var(--gh-radius);
677
+ ry: var(--gh-radius);
678
+ outline: none;
679
+ }
680
+
681
+ .page-template-projects .gh-calendar .day[data-level='0'] {
682
+ fill: var(--gh-level-0);
683
+ }
684
+
685
+ .page-template-projects .gh-calendar .day[data-level='1'] {
686
+ fill: var(--gh-level-1);
687
+ }
688
+
689
+ .page-template-projects .gh-calendar .day[data-level='2'] {
690
+ fill: var(--gh-level-2);
691
+ }
692
+
693
+ .page-template-projects .gh-calendar .day[data-level='3'] {
694
+ fill: var(--gh-level-3);
695
+ }
696
+
697
+ .page-template-projects .gh-calendar .day[data-level='4'] {
698
+ fill: var(--gh-level-4);
699
+ }
700
+
701
+ .page-template-projects .gh-calendar text {
702
+ fill: var(--gh-text-muted);
703
+ font-size: 10px;
704
+ }
705
+
706
+ /* 去掉库自带 footer(更简洁) */
707
+ .page-template-projects .gh-calendar .contrib-footer {
708
+ display: none !important;
709
+ }
710
+
711
+ .page-template-projects .gh-calendar.gh-calendar-error {
712
+ color: var(--gh-text-muted);
713
+ font-size: 0.85rem;
714
+ }
715
+
716
+ /* github-calendar(HTML table 版):适配 ContributionCalendar-* 类名 */
717
+ .page-template-projects .gh-calendar table {
718
+ /* 让热力图在卡片内尽量铺满宽度;需要时仍可横向滚动 */
719
+ /* 关键:避免 table 按列拉伸导致 day 变成长方形 */
720
+ display: table;
721
+ /* 不强制占满:保持自然宽度,并在容器内居中 */
722
+ width: max-content;
723
+ min-width: max-content;
724
+ max-width: none;
725
+ table-layout: auto;
726
+ border-collapse: separate;
727
+ border-spacing: 5px !important;
728
+ /* table 未超宽时居中;超宽时 margin auto 会退化为 0(滚动起始列左对齐) */
729
+ margin: 0 auto;
730
+ }
731
+
732
+ /* GitHub 原生 table:thead 行内写死 height:15px,需强制更高以避免月份 label 压到格子 */
733
+ .page-template-projects .gh-calendar table thead tr {
734
+ height: 20px !important;
735
+ }
736
+
737
+ /* GitHub 原生 table 的月份 label 使用 absolute 定位,需要为 thead 行预留高度,避免与格子重叠 */
738
+ .page-template-projects .gh-calendar .ContributionCalendar-label {
739
+ height: 16px;
740
+ position: relative;
741
+ padding-bottom: 4px;
742
+ font-size: 10px;
743
+ line-height: 10px;
744
+ font-weight: 400;
745
+ vertical-align: bottom;
746
+ }
747
+
748
+ @media (max-width: 600px) {
749
+ /* 移动端:格子更紧凑,减少横向滚动压力 */
750
+ .page-template-projects .gh-calendar table {
751
+ border-spacing: 3px !important;
752
+ }
753
+
754
+ .page-template-projects .gh-calendar .ContributionCalendar-day {
755
+ width: 12px;
756
+ height: 12px;
757
+ min-width: 12px;
758
+ min-height: 12px;
759
+ }
760
+
761
+ .page-template-projects .gh-calendar #js-contribution-activity-description {
762
+ font-size: 0.95rem;
763
+ }
764
+ }
765
+
766
+ /* GitHub 原生 footer:保留“Learn how we count contributions”,隐藏右侧自带 legend(避免与自定义 legend 重复) */
767
+ .page-template-projects .gh-calendar .float-right.color-fg-muted.d-flex.flex-items-center {
768
+ display: none !important;
769
+ }
770
+
771
+ /* 按需求:去除 “Learn how we count contributions” */
772
+ .page-template-projects .gh-calendar .float-left {
773
+ display: none !important;
774
+ }
775
+
776
+ /* Link--muted 目前不展示(float-left 已隐藏);保留基础链接色由下方 a 规则兜底 */
777
+
778
+ .page-template-projects .gh-calendar .ContributionCalendar-day {
779
+ /* 固定正方形,防止被 table 列宽拉伸 */
780
+ width: 15px;
781
+ height: 15px;
782
+ min-width: 15px;
783
+ min-height: 15px;
784
+ aspect-ratio: 1 / 1;
785
+ border-radius: var(--gh-radius);
786
+ background-color: var(--gh-level-0);
787
+ }
788
+
789
+ .page-template-projects .gh-calendar .ContributionCalendar-day[data-level='0'] {
790
+ background-color: var(--gh-level-0) !important;
791
+ }
792
+
793
+ .page-template-projects .gh-calendar .ContributionCalendar-day[data-level='1'] {
794
+ background-color: var(--gh-level-1) !important;
795
+ }
796
+
797
+ .page-template-projects .gh-calendar .ContributionCalendar-day[data-level='2'] {
798
+ background-color: var(--gh-level-2) !important;
799
+ }
800
+
801
+ .page-template-projects .gh-calendar .ContributionCalendar-day[data-level='3'] {
802
+ background-color: var(--gh-level-3) !important;
803
+ }
804
+
805
+ .page-template-projects .gh-calendar .ContributionCalendar-day[data-level='4'] {
806
+ background-color: var(--gh-level-4) !important;
807
+ }
808
+
809
+ /* a11y 跳转链接:视觉隐藏(保留可访问性) */
810
+ .page-template-projects .gh-calendar a[href^='#year-list'],
811
+ .page-template-projects .gh-calendar a[href*='year-list'],
812
+ .page-template-projects .gh-calendar a[href*='contributions-year'],
813
+ /* GitHub 注入的 "Skip to contributions year list"(常见为 show-on-focus) */
814
+ .page-template-projects .gh-calendar a.show-on-focus {
815
+ position: absolute;
816
+ width: 1px;
817
+ height: 1px;
818
+ padding: 0;
819
+ margin: -1px;
820
+ overflow: hidden;
821
+ clip: rect(0, 0, 0, 0);
822
+ white-space: nowrap;
823
+ border: 0;
824
+ }
825
+
826
+ /* 隐藏库自带 footer/legend,避免与自定义 legend 重复 */
827
+ .page-template-projects .gh-calendar .ContributionCalendar-footer,
828
+ .page-template-projects .gh-calendar .contrib-footer,
829
+ .page-template-projects .gh-calendar .legend,
830
+ /* GitHub 原生 legend 容器(float-right + flex) */
831
+ .page-template-projects .gh-calendar .float-right.color-fg-muted.d-flex.flex-items-center {
832
+ display: none !important;
833
+ }
834
+
835
+ /* 修复星期/月份 label 的意外高亮(如 Wed 蓝底) */
836
+ .page-template-projects .gh-calendar .ContributionCalendar-label {
837
+ background: transparent !important;
838
+ background-color: transparent !important;
839
+ color: var(--gh-text-muted) !important;
840
+ }
841
+
842
+ /* 组件内链接样式:去掉默认紫色 */
843
+ .page-template-projects .gh-calendar a {
844
+ color: var(--gh-text-muted);
845
+ text-decoration: none;
846
+ }
847
+
848
+ .page-template-projects .gh-calendar a:hover {
849
+ color: var(--gh-text);
850
+ text-decoration: underline;
851
+ }
852
+
853
+ /* Mobile text toggling for Heatmap Header:已在上方 heatmap 区域定义,避免重复 */
854
+
855
+ /* projects:旧版 GitHub 热力图(ghchart 图片)已弃用,改用 github-calendar.js */
856
+
857
+ .page-template-projects .sites-grid {
858
+ /* projects:桌面端固定 3 列(避免 auto-fill 在中等宽度下退化为 2 列) */
859
+ grid-template-columns: repeat(3, minmax(0, 1fr));
860
+ gap: 24px;
861
+ }
862
+
863
+ @media (max-width: 1024px) {
864
+ .page-template-projects .sites-grid {
865
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
866
+ }
867
+ }
868
+
869
+ @media (max-width: 600px) {
870
+ .page-template-projects .sites-grid {
871
+ grid-template-columns: 1fr;
872
+ gap: 0.75rem;
873
+ }
874
+ }
875
+
876
+ /* projects:代码仓库风卡片 */
877
+ .site-card.site-card-repo {
878
+ position: relative;
879
+ display: flex;
880
+ flex-direction: column;
881
+ align-items: stretch;
882
+ padding: 1.1rem 1.1rem 1rem;
883
+ gap: 0;
884
+ }
885
+
886
+ .site-card.site-card-repo:hover {
887
+ transform: translateY(-4px);
888
+ }
889
+
890
+ .site-card.site-card-repo .repo-header {
891
+ display: flex;
892
+ align-items: center;
893
+ gap: 0.75rem;
894
+ margin-bottom: 12px;
895
+ min-width: 0;
896
+ }
897
+
898
+ .site-card.site-card-repo .repo-icon {
899
+ font-size: 1.15rem;
900
+ color: var(--nav-item-color);
901
+ opacity: 0.85;
902
+ transition:
903
+ color 0.3s ease,
904
+ opacity 0.3s ease;
905
+ flex: 0 0 auto;
906
+ }
907
+
908
+ .site-card.site-card-repo:hover .repo-icon {
909
+ color: var(--accent-color);
910
+ opacity: 1;
911
+ }
912
+
913
+ .site-card.site-card-repo .repo-title {
914
+ font-size: 1rem;
915
+ font-weight: 600;
916
+ white-space: nowrap;
917
+ overflow: hidden;
918
+ text-overflow: ellipsis;
919
+ }
920
+
921
+ .site-card.site-card-repo .repo-desc {
922
+ font-size: 0.9rem;
923
+ color: var(--nav-item-color);
924
+ opacity: 0.85;
925
+ line-height: 1.5;
926
+ flex-grow: 1;
927
+ margin: 0 0 16px 0;
928
+ display: -webkit-box;
929
+ -webkit-line-clamp: 3;
930
+ -webkit-box-orient: vertical;
931
+ overflow: hidden;
932
+ position: relative;
933
+ /* Ensure tooltip positioning context */
934
+ }
935
+
936
+ .site-card.site-card-repo .repo-stats {
937
+ display: flex;
938
+ align-items: center;
939
+ gap: 16px;
940
+ padding-top: 12px;
941
+ border-top: 1px solid rgba(255, 255, 255, 0.06);
942
+ font-size: 0.8rem;
943
+ color: var(--nav-item-color);
944
+ opacity: 0.85;
945
+ }
946
+
947
+ .site-card.site-card-repo .stat-item {
948
+ display: flex;
949
+ align-items: center;
950
+ gap: 6px;
951
+ min-width: 0;
952
+ }
953
+
954
+ .site-card.site-card-repo .lang-dot {
955
+ width: 10px;
956
+ height: 10px;
957
+ border-radius: 50%;
958
+ display: inline-block;
959
+ }
960
+
961
+ @media (max-width: 600px) {
962
+ .welcome-section-with-side {
963
+ align-items: flex-start;
964
+ }
965
+
966
+ .welcome-section-with-side .welcome-section-side {
967
+ width: 100%;
968
+ }
969
+
970
+ /* 旧版 heatmap-container/heatmap-img 已弃用 */
971
+ }
972
+
973
+ /* 网站卡片样式 */
974
+ .site-card {
975
+ background: linear-gradient(
976
+ 145deg,
977
+ var(--site-card-bg-gradient-1),
978
+ var(--site-card-bg-gradient-2)
979
+ );
980
+ border-radius: var(--radius-lg);
981
+ padding: 0.75rem 0.9rem;
982
+ text-decoration: none;
983
+ color: inherit;
984
+ transition:
985
+ background var(--transition-normal),
986
+ transform var(--transition-normal),
987
+ box-shadow var(--transition-normal),
988
+ border-color var(--transition-normal);
989
+ display: flex;
990
+ align-items: center;
991
+ gap: 0.6rem;
992
+ text-align: left;
993
+ backface-visibility: hidden;
994
+ transform: translateZ(0);
995
+ will-change: transform;
996
+ max-width: 100%;
997
+ position: relative;
998
+ box-shadow: 0 4px 16px var(--shadow-color);
999
+ border: 1px solid var(--border-color);
1000
+ z-index: 2;
1001
+ overflow: hidden;
1002
+ }
1003
+
1004
+ /* 网站卡片变体:projects 大卡片 */
1005
+ .site-card.site-card-large {
1006
+ padding: 1.1rem 1.2rem;
1007
+ gap: 0.9rem;
1008
+ }
1009
+
1010
+ .site-card.site-card-large .site-card-icon {
1011
+ width: 3.1rem;
1012
+ height: 3.1rem;
1013
+ }
1014
+
1015
+ .site-card.site-card-large h3 {
1016
+ font-size: 1rem;
1017
+ font-weight: 600;
1018
+ }
1019
+
1020
+ .site-card.site-card-large p {
1021
+ font-size: 0.9rem;
1022
+ }
1023
+
1024
+ /* Phase 2:articles 页面隐藏“扩展写回结构”,避免与文章条目渲染混淆 */
1025
+ .menav-extension-shadow {
1026
+ display: none;
1027
+ }
1028
+
1029
+ /* articles:文章元信息(日期 + 来源) */
1030
+ .site-card[data-type='article'] .site-card-meta {
1031
+ margin: 0 0 8px 0;
1032
+ font-size: 0.75rem;
1033
+ color: var(--nav-item-color);
1034
+ opacity: 0.9;
1035
+ display: flex;
1036
+ align-items: center;
1037
+ gap: 0.4rem;
1038
+ }
1039
+
1040
+ .site-card[data-type='article'] .site-card-meta-sep {
1041
+ opacity: 0.8;
1042
+ }
1043
+
1044
+ /* articles:文章卡片布局(首行:图标+标题;下方:时间/来源 + 简介 全宽对齐) */
1045
+ .site-card[data-type='article'] {
1046
+ display: block;
1047
+ padding: 1rem 1.1rem;
1048
+ }
1049
+
1050
+ .site-card[data-type='article'] .article-card-header {
1051
+ display: flex;
1052
+ align-items: flex-start;
1053
+ gap: 0.75rem;
1054
+ }
1055
+
1056
+ .site-card[data-type='article'] .article-card-title {
1057
+ min-width: 0;
1058
+ }
1059
+
1060
+ .site-card[data-type='article'] .article-card-body {
1061
+ margin-top: 0.55rem;
1062
+ }
1063
+
1064
+ .site-card[data-type='article'] h3 {
1065
+ margin: 0;
1066
+ }
1067
+
1068
+ .site-card[data-type='article'] p {
1069
+ margin: 0;
1070
+ }
1071
+
1072
+ /* articles:桌面端网格固定 3 列(避免 auto-fill 在大屏上过多列导致阅读密度过高) */
1073
+ .page-template-articles .sites-grid {
1074
+ grid-template-columns: repeat(3, minmax(0, 1fr));
1075
+ }
1076
+
1077
+ @media (max-width: 1024px) {
1078
+ .page-template-articles .sites-grid {
1079
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
1080
+ }
1081
+ }
1082
+
1083
+ @media (max-width: 480px) {
1084
+ .page-template-articles .sites-grid {
1085
+ grid-template-columns: 1fr;
1086
+ gap: 0.5rem;
1087
+ }
1088
+ }
1089
+
1090
+ /* articles:标题/描述允许两行显示(更适合多列宽卡片,也适用于搜索结果页) */
1091
+ .site-card[data-type='article'] h3 {
1092
+ white-space: normal;
1093
+ display: -webkit-box;
1094
+ -webkit-line-clamp: 2;
1095
+ -webkit-box-orient: vertical;
1096
+ }
1097
+
1098
+ .site-card[data-type='article'] p {
1099
+ white-space: normal;
1100
+ display: -webkit-box;
1101
+ -webkit-line-clamp: 2;
1102
+ -webkit-box-orient: vertical;
1103
+ }
1104
+
1105
+ .site-card:hover {
1106
+ transform: translateY(-3px);
1107
+ background: var(--site-card-hover-bg);
1108
+ border-color: var(--border-color);
1109
+ }
1110
+
1111
+ .site-card-icon {
1112
+ flex-shrink: 0;
1113
+ width: 2.5rem;
1114
+ height: 2.5rem;
1115
+ border-radius: var(--radius-md);
1116
+ background: rgba(var(--card-bg-rgb), 0.35);
1117
+ border: 1px solid var(--border-color);
1118
+ display: flex;
1119
+ align-items: center;
1120
+ justify-content: center;
1121
+ transition:
1122
+ transform 0.3s ease,
1123
+ background-color 0.3s ease;
1124
+ }
1125
+
1126
+ .site-card .site-icon {
1127
+ font-size: 1.4rem;
1128
+ color: var(--accent-color);
1129
+ transition: color 0.3s ease;
1130
+ }
1131
+
1132
+ .site-card:hover .site-card-icon {
1133
+ transform: scale(1.06);
1134
+ }
1135
+
1136
+ .site-card:hover .site-icon {
1137
+ color: var(--accent-hover);
1138
+ }
1139
+
1140
+ /* 网站卡片 favicon 图片样式,与图标尺寸保持一致 */
1141
+ .site-card .favicon-icon {
1142
+ display: inline-block;
1143
+ width: 1.8rem;
1144
+ height: 1.8rem;
1145
+ border-radius: var(--radius-sm);
1146
+ object-fit: cover;
1147
+ transition:
1148
+ transform 0.3s ease,
1149
+ box-shadow 0.3s ease;
1150
+ }
1151
+
1152
+ .site-card .icon-placeholder,
1153
+ .site-card .icon-fallback {
1154
+ display: inline-block;
1155
+ width: 1.8rem;
1156
+ height: 1.8rem;
1157
+ border-radius: var(--radius-sm);
1158
+ flex-shrink: 0;
1159
+ vertical-align: middle;
1160
+ text-align: center;
1161
+ line-height: 1.8rem;
1162
+ font-size: 1.5rem;
1163
+ transition:
1164
+ transform 0.3s ease,
1165
+ box-shadow 0.3s ease;
1166
+ color: var(--accent-color);
1167
+ }
1168
+
1169
+ /* 确保图标容器在加载过程中保持固定尺寸 */
1170
+ .site-card .icon-container {
1171
+ display: inline-block;
1172
+ width: 1.8rem;
1173
+ height: 1.8rem;
1174
+ position: relative;
1175
+ vertical-align: middle;
1176
+ }
1177
+
1178
+ .site-card .icon-container .favicon-icon,
1179
+ .site-card .icon-container .icon-placeholder,
1180
+ .site-card .icon-container .icon-fallback {
1181
+ position: absolute;
1182
+ top: 0;
1183
+ left: 0;
1184
+ width: 100%;
1185
+ height: 100%;
1186
+ margin-bottom: 0;
1187
+ }
1188
+
1189
+ /* 优化图标切换动画 */
1190
+ .site-card .icon-container .favicon-icon {
1191
+ opacity: 0;
1192
+ transition: opacity 0.3s ease;
1193
+ }
1194
+
1195
+ .site-card .icon-container .favicon-icon.loaded {
1196
+ opacity: 1;
1197
+ }
1198
+
1199
+ .site-card .icon-container .favicon-icon.error {
1200
+ display: none;
1201
+ }
1202
+
1203
+ .site-card .icon-container .icon-placeholder {
1204
+ opacity: 1;
1205
+ transition: opacity 0.3s ease;
1206
+ }
1207
+
1208
+ .site-card .icon-container .icon-placeholder.hidden {
1209
+ opacity: 0;
1210
+ pointer-events: none;
1211
+ }
1212
+
1213
+ .site-card .icon-container .icon-fallback {
1214
+ opacity: 0;
1215
+ transition: opacity 0.3s ease;
1216
+ }
1217
+
1218
+ .site-card .icon-container .icon-fallback.visible {
1219
+ opacity: 1;
1220
+ }
1221
+
1222
+ .site-card:hover .icon-placeholder,
1223
+ .site-card:hover .icon-fallback {
1224
+ color: var(--accent-hover);
1225
+ }
1226
+
1227
+ .site-card-content {
1228
+ flex: 1;
1229
+ min-width: 0;
1230
+ overflow: hidden;
1231
+ }
1232
+
1233
+ .site-card h3 {
1234
+ font-size: 1rem;
1235
+ margin-bottom: 0.25rem;
1236
+ color: var(--text-bright);
1237
+ font-weight: 500;
1238
+ letter-spacing: 0.3px;
1239
+ transition: color 0.3s ease;
1240
+ white-space: nowrap;
1241
+ overflow: hidden;
1242
+ text-overflow: ellipsis;
1243
+ max-width: 100%;
1244
+ }
1245
+
1246
+ .site-card p {
1247
+ font-size: 0.9rem;
1248
+ color: var(--nav-item-color);
1249
+ margin: 0;
1250
+ line-height: 1.4;
1251
+ transition: color 0.3s ease;
1252
+ white-space: nowrap;
1253
+ overflow: hidden;
1254
+ text-overflow: ellipsis;
1255
+ width: 100%;
1256
+ position: relative;
1257
+ /* Ensure tooltip positioning context */
1258
+ }
1259
+
1260
+ /* Tooltip styles */
1261
+ /* Tooltip styles */
1262
+ .site-card p[data-tooltip],
1263
+ .site-card .repo-desc[data-tooltip] {
1264
+ cursor: default;
1265
+ /* Indicate interactivity */
1266
+ }
1267
+
1268
+ .custom-tooltip {
1269
+ position: fixed;
1270
+ background: rgba(47, 48, 53, 0.95);
1271
+ /* Fallback dark */
1272
+ background: rgba(var(--card-bg-rgb), 0.95);
1273
+ color: var(--text-bright);
1274
+ padding: 0.5rem 0.8rem;
1275
+ border-radius: var(--radius-md);
1276
+ box-shadow: 0 4px 12px var(--shadow-color);
1277
+ border: 1px solid var(--border-color);
1278
+ font-size: 0.85rem;
1279
+ white-space: normal;
1280
+ line-height: 1.4;
1281
+ z-index: 9999;
1282
+ pointer-events: none;
1283
+ opacity: 0;
1284
+ transition: opacity 0.2s ease-out;
1285
+ backdrop-filter: blur(4px);
1286
+ -webkit-backdrop-filter: blur(4px);
1287
+ max-width: 300px;
1288
+ word-break: break-word;
1289
+ }
1290
+
1291
+ .custom-tooltip.visible {
1292
+ opacity: 1;
1293
+ }
1294
+
1295
+ /* 添加编辑按钮 */
1296
+ .edit-buttons {
1297
+ position: absolute;
1298
+ top: 0.5rem;
1299
+ right: 0.5rem;
1300
+ display: flex;
1301
+ gap: 0.5rem;
1302
+ opacity: 0;
1303
+ transition: opacity 0.3s ease;
1304
+ }
1305
+
1306
+ .site-card:hover .edit-buttons {
1307
+ opacity: 1;
1308
+ }
1309
+
1310
+ .edit-btn,
1311
+ .delete-btn {
1312
+ background: none;
1313
+ border: none;
1314
+ color: var(--text-muted);
1315
+ cursor: pointer;
1316
+ padding: 0.3rem;
1317
+ border-radius: 4px;
1318
+ transition: all 0.3s ease;
1319
+ }
1320
+
1321
+ .edit-btn:hover,
1322
+ .delete-btn:hover {
1323
+ color: var(--text-bright);
1324
+ background-color: var(--secondary-bg);
1325
+ }
1326
+
1327
+ /* 添加网站按钮 */
1328
+ .add-site-btn {
1329
+ background: linear-gradient(
1330
+ 145deg,
1331
+ var(--site-card-bg-gradient-1),
1332
+ var(--site-card-bg-gradient-2)
1333
+ );
1334
+ border: 2px dashed var(--border-color);
1335
+ border-radius: var(--radius-lg);
1336
+ padding: 1.5rem;
1337
+ color: var(--text-muted);
1338
+ cursor: pointer;
1339
+ transition: all var(--transition-normal);
1340
+ transition-timing-function: var(--transition-bounce);
1341
+ display: flex;
1342
+ flex-direction: column;
1343
+ align-items: center;
1344
+ justify-content: center;
1345
+ min-height: 180px;
1346
+ box-shadow: 0 4px 16px var(--shadow-color);
1347
+ }
1348
+
1349
+ .add-site-btn:hover {
1350
+ background: linear-gradient(145deg, var(--secondary-bg), var(--site-card-bg-gradient-1));
1351
+ border-color: var(--accent-color);
1352
+ color: var(--text-bright);
1353
+ transform: translateY(-2px);
1354
+ box-shadow: 0 6px 20px var(--shadow-color);
1355
+ }
1356
+
1357
+ .add-site-btn i {
1358
+ font-size: 2.2rem;
1359
+ margin-bottom: 0.8rem;
1360
+ transition: transform 0.3s ease;
1361
+ }
1362
+
1363
+ .add-site-btn:hover i {
1364
+ transform: scale(1.1);
1365
+ }
assets/styles/_content.css ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Markdown Content Page */
2
+
3
+ /* ------------------------------------------------------------------
4
+ Markdown Content Styling (GitHub-like) - Scoped to .content-page
5
+ ------------------------------------------------------------------ */
6
+
7
+ /* Increase inner spacing for content pages to separate text from card border */
8
+ .content-category .content-page {
9
+ padding-left: var(--spacing-sm);
10
+ padding-right: var(--spacing-sm);
11
+ }
12
+
13
+ @media (max-width: 480px) {
14
+ .content-category .content-page {
15
+ padding-left: var(--spacing-xs);
16
+ padding-right: var(--spacing-xs);
17
+ }
18
+ }
19
+
20
+ .content-page {
21
+ font-family: var(
22
+ --font-body,
23
+ -apple-system,
24
+ BlinkMacSystemFont,
25
+ 'Segoe UI',
26
+ 'Noto Sans',
27
+ Helvetica,
28
+ Arial,
29
+ sans-serif,
30
+ 'Apple Color Emoji',
31
+ 'Segoe UI Emoji'
32
+ );
33
+ font-size: 16px;
34
+ line-height: 1.6;
35
+ color: var(--text-color);
36
+ word-wrap: break-word;
37
+ padding-bottom: 2rem;
38
+ }
39
+
40
+ .content-page h1,
41
+ .content-page h2,
42
+ .content-page h3,
43
+ .content-page h4,
44
+ .content-page h5,
45
+ .content-page h6 {
46
+ margin-top: 24px;
47
+ margin-bottom: 16px;
48
+ font-weight: 600;
49
+ line-height: 1.25;
50
+ color: var(--text-bright);
51
+ }
52
+
53
+ .content-page h1 {
54
+ font-size: 2em;
55
+ padding-bottom: 0.3em;
56
+ border-bottom: 1px solid var(--border-color);
57
+ }
58
+
59
+ .content-page h2 {
60
+ font-size: 1.5em;
61
+ padding-bottom: 0.3em;
62
+ border-bottom: 1px solid var(--border-color);
63
+ }
64
+
65
+ .content-page h3 {
66
+ font-size: 1.25em;
67
+ }
68
+
69
+ .content-page h4 {
70
+ font-size: 1em;
71
+ }
72
+
73
+ .content-page h5 {
74
+ font-size: 0.875em;
75
+ }
76
+
77
+ .content-page h6 {
78
+ font-size: 0.85em;
79
+ color: var(--text-muted);
80
+ }
81
+
82
+ .content-page p {
83
+ margin-top: 0;
84
+ margin-bottom: 16px;
85
+ }
86
+
87
+ .content-page blockquote {
88
+ margin: 0 0 16px;
89
+ padding: 0 1em;
90
+ color: var(--text-muted);
91
+ border-left: 0.25em solid var(--border-color);
92
+ background-color: transparent;
93
+ }
94
+
95
+ .content-page ul,
96
+ .content-page ol {
97
+ margin-top: 0;
98
+ margin-bottom: 16px;
99
+ padding-left: 2em;
100
+ }
101
+
102
+ .content-page li + li {
103
+ margin-top: 0.25em;
104
+ }
105
+
106
+ /* Inline code */
107
+ .content-page code {
108
+ font-family:
109
+ ui-monospace,
110
+ SFMono-Regular,
111
+ SF Mono,
112
+ Menlo,
113
+ Consolas,
114
+ Liberation Mono,
115
+ monospace;
116
+ font-size: 85%;
117
+ padding: 0.2em 0.4em;
118
+ margin: 0;
119
+ border-radius: var(--radius-sm);
120
+ background-color: rgba(127, 127, 127, 0.15);
121
+ color: var(--text-bright);
122
+ }
123
+
124
+ /* Block code (pre) */
125
+ .content-page pre {
126
+ padding: 16px;
127
+ overflow: auto;
128
+ font-size: 85%;
129
+ line-height: 1.45;
130
+ /* 代码块背景需要与内容卡片区分开:避免与 card-bg 过于接近 */
131
+ background-color: rgba(127, 127, 127, 0.08);
132
+ border-radius: var(--radius-md);
133
+ margin-top: 0;
134
+ margin-bottom: 16px;
135
+ border: 1px solid var(--border-color);
136
+ }
137
+
138
+ .content-page pre code {
139
+ background-color: transparent;
140
+ padding: 0;
141
+ font-size: 100%;
142
+ color: inherit;
143
+ word-break: normal;
144
+ border-radius: 0;
145
+ }
146
+
147
+ .content-page hr {
148
+ height: 0.25em;
149
+ padding: 0;
150
+ margin: 24px 0;
151
+ background-color: var(--border-color);
152
+ border: 0;
153
+ }
154
+
155
+ /* Tables */
156
+ .content-page table {
157
+ border-spacing: 0;
158
+ border-collapse: collapse;
159
+ margin-top: 0;
160
+ margin-bottom: 16px;
161
+ display: block;
162
+ width: max-content;
163
+ max-width: 100%;
164
+ overflow: auto;
165
+ }
166
+
167
+ .content-page table th,
168
+ .content-page table td {
169
+ padding: 6px 13px;
170
+ border: 1px solid var(--border-color);
171
+ }
172
+
173
+ .content-page table th {
174
+ font-weight: 600;
175
+ background-color: rgba(var(--card-bg-rgb), 0.3);
176
+ }
177
+
178
+ .content-page table tr {
179
+ background-color: transparent;
180
+ border-top: 1px solid var(--border-color);
181
+ }
182
+
183
+ .content-page table tr:nth-child(2n) {
184
+ background-color: rgba(127, 127, 127, 0.04);
185
+ }
186
+
187
+ .content-page img {
188
+ max-width: 100%;
189
+ box-sizing: content-box;
190
+ background-color: transparent;
191
+ border-radius: var(--radius-sm);
192
+ }
193
+
194
+ .content-page a {
195
+ color: var(--accent-color);
196
+ text-decoration: none;
197
+ }
198
+
199
+ .content-page a:hover {
200
+ text-decoration: underline;
201
+ }
202
+
203
+ /* Task lists */
204
+ .content-page ul.contains-task-list {
205
+ list-style-type: none;
206
+ padding-left: 0;
207
+ }
208
+
209
+ .content-page .task-list-item input {
210
+ margin-right: 0.5em;
211
+ vertical-align: middle;
212
+ }
213
+
214
+ /* GitHub Calendar Fixes (HTML Table Version)
215
+ * 注:此处样式已合并到上方「projects:GitHub 热力图(github-calendar.js)」区域,避免重复维护。
216
+ */
assets/styles/_dashboard.css ADDED
@@ -0,0 +1,371 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Dashboard (Clock & Todo) */
2
+
3
+ /* =========================================
4
+ Home Dashboard Styles
5
+ ========================================= */
6
+
7
+ .dashboard-grid {
8
+ display: grid;
9
+ grid-template-columns: 1fr;
10
+ /* Mobile first: stack vertically */
11
+ gap: var(--spacing-xl);
12
+ margin-bottom: var(--spacing-xl);
13
+ }
14
+
15
+ @media (min-width: 992px) {
16
+ .dashboard-grid {
17
+ grid-template-columns: 5fr 7fr;
18
+ /* Desktop: 5/12 - 7/12 ratio like 1.html */
19
+ align-items: stretch;
20
+ /* Stretch to align bottoms */
21
+ gap: var(--spacing-lg);
22
+ /* Reduced gap */
23
+ margin-bottom: var(--spacing-lg);
24
+ /* Reduced margin */
25
+ }
26
+ }
27
+
28
+ /* --- Left Column: Welcome & Clock --- */
29
+ .dashboard-intro {
30
+ display: flex;
31
+ flex-direction: column;
32
+ height: 100%;
33
+ /* Match grid row height */
34
+ gap: var(--spacing-md);
35
+ /* Reduced gap */
36
+ justify-content: space-between;
37
+ /* Spread welcome and clock */
38
+ }
39
+
40
+ .dashboard-welcome {
41
+ display: flex;
42
+ flex-direction: column;
43
+ gap: var(--spacing-md);
44
+ }
45
+
46
+ .welcome-title {
47
+ font-size: 3.5rem;
48
+ font-family: var(--font-heading, var(--font-body, sans-serif));
49
+ font-weight: 800;
50
+ /* Extra Bold */
51
+ font-style: normal;
52
+ /* Remove italic */
53
+ text-transform: uppercase;
54
+ /* Pop Style */
55
+ letter-spacing: -0.03em;
56
+ /* Tight spacing */
57
+ color: var(--text-color);
58
+ /* Use primary text color, accent reserved for highlights */
59
+ line-height: 0.95;
60
+ margin: 0;
61
+ padding-left: var(--spacing-xl);
62
+ /* Shift right */
63
+ }
64
+
65
+ .welcome-subtitle {
66
+ font-size: 0.9rem;
67
+ letter-spacing: 0.1em;
68
+ text-transform: uppercase;
69
+ color: var(--accent-color);
70
+ /* Accent color for subtitle */
71
+ font-weight: 600;
72
+ margin-top: 1rem;
73
+ padding-left: var(--spacing-xl);
74
+ opacity: 0.9;
75
+ }
76
+
77
+ @media (min-width: 768px) {
78
+ .welcome-title {
79
+ font-size: 3.5rem;
80
+ padding-left: 2rem;
81
+ /* Reduced from 5rem */
82
+ }
83
+
84
+ .welcome-subtitle {
85
+ font-size: 1.125rem;
86
+ padding-left: 2rem;
87
+ }
88
+ }
89
+
90
+ /* Clock Card - Apple Style: Flat + Soft Shadow */
91
+ .dashboard-clock-card {
92
+ position: relative;
93
+ background-color: var(--card-bg-gradient-1);
94
+ /* Solid color, no gradient needed for flat look */
95
+ border: 0.5px solid var(--border-color);
96
+ /* Very subtle border */
97
+ border-radius: var(--radius-xl);
98
+ display: flex;
99
+ flex-direction: column;
100
+ justify-content: space-between;
101
+ flex: 1;
102
+ box-shadow: 0 8px 24px var(--shadow-color);
103
+ /* Soft, diffused shadow */
104
+ transition:
105
+ transform var(--transition-normal),
106
+ box-shadow var(--transition-normal);
107
+ overflow: hidden;
108
+ padding: var(--spacing-xl);
109
+ /* More padding for spatial luxury */
110
+ }
111
+
112
+ body.light-theme .dashboard-clock-card {
113
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
114
+ /* Lighter shadow for light mode */
115
+ }
116
+
117
+ .clock-header {
118
+ display: flex;
119
+ justify-content: space-between;
120
+ align-items: flex-start;
121
+ z-index: 1;
122
+ }
123
+
124
+ .clock-meta {
125
+ display: flex;
126
+ flex-direction: column;
127
+ }
128
+
129
+ .clock-main {
130
+ align-self: flex-end;
131
+ /* Time at bottom right */
132
+ z-index: 1;
133
+ }
134
+
135
+ .clock-time {
136
+ font-size: 3.5rem;
137
+ /* Reduced from 4.5rem to prevent overflow */
138
+ font-weight: 700;
139
+ color: var(--text-bright);
140
+ line-height: 1;
141
+ font-family: var(--font-body);
142
+ letter-spacing: -0.02em;
143
+ }
144
+
145
+ .clock-greeting {
146
+ font-size: 0.85rem;
147
+ color: var(--accent-color);
148
+ font-weight: 600;
149
+ text-transform: uppercase;
150
+ letter-spacing: 0.1em;
151
+ margin-bottom: 0.2rem;
152
+ }
153
+
154
+ .clock-week {
155
+ font-size: 0.75rem;
156
+ color: var(--text-muted);
157
+ border: 1px solid var(--border-color);
158
+ padding: 2px 8px;
159
+ border-radius: 12px;
160
+ }
161
+
162
+ .clock-date {
163
+ font-size: 1.25rem;
164
+ /* Reduced from 1.5rem */
165
+ color: var(--text-muted);
166
+ font-family: var(--font-body);
167
+ }
168
+
169
+ /* --- Right Column: Stats / Todo --- */
170
+ .dashboard-stats {
171
+ height: 100%;
172
+ min-height: 300px;
173
+ }
174
+
175
+ .dashboard-panel {
176
+ background-color: var(--card-bg-gradient-1);
177
+ border: 1px solid var(--border-color);
178
+ border-radius: var(--radius-xl);
179
+ display: flex;
180
+ height: 310px;
181
+ /* Increased from 280px to align with clock card better */
182
+ overflow: hidden;
183
+ box-shadow: 4px 4px 0px 0px rgba(255, 255, 255, 0.15);
184
+ transition: box-shadow 0.2s ease;
185
+ }
186
+
187
+ body.light-theme .dashboard-panel {
188
+ box-shadow: 4px 4px 0px 0px rgba(0, 0, 0, 0.2);
189
+ }
190
+
191
+ .panel-sidebar {
192
+ width: 4rem;
193
+ background-color: var(--sidebar-bg);
194
+ border-right: 4px solid var(--border-color);
195
+ display: flex;
196
+ flex-direction: column;
197
+ justify-content: space-between;
198
+ /* Icon at top, button at bottom */
199
+ align-items: center;
200
+ padding: var(--spacing-md) 0;
201
+ }
202
+
203
+ .panel-static-icon {
204
+ color: var(--text-muted);
205
+ font-size: 1.5rem;
206
+ padding: 0.5rem;
207
+ }
208
+
209
+ .panel-icon-btn {
210
+ background: none;
211
+ border: none;
212
+ color: var(--text-muted);
213
+ font-size: 1.5rem;
214
+ cursor: pointer;
215
+ padding: 0.5rem;
216
+ border-radius: var(--radius-md);
217
+ transition: color 0.2s;
218
+ }
219
+
220
+ .panel-icon-btn:hover {
221
+ color: var(--accent-color);
222
+ }
223
+
224
+ .panel-content-wrapper {
225
+ flex: 1;
226
+ display: flex;
227
+ flex-direction: column;
228
+ overflow: hidden;
229
+ height: 100%;
230
+ }
231
+
232
+ .panel-content {
233
+ flex: 1;
234
+ padding: var(--spacing-lg);
235
+ overflow-y: auto;
236
+ display: flex;
237
+ flex-direction: column;
238
+ gap: var(--spacing-md);
239
+ }
240
+
241
+ /* Todo Items */
242
+ .todo-item {
243
+ display: flex;
244
+ align-items: center;
245
+ gap: var(--spacing-md);
246
+ padding: 4px 0;
247
+ /* Tighter spacing */
248
+ cursor: pointer;
249
+ transition: opacity 0.2s;
250
+ opacity: 1;
251
+ position: relative;
252
+ }
253
+
254
+ .todo-item:hover .todo-delete-btn {
255
+ opacity: 1;
256
+ pointer-events: auto;
257
+ }
258
+
259
+ .todo-checkbox {
260
+ width: 1.25rem;
261
+ height: 1.25rem;
262
+ border: 2px solid var(--text-muted);
263
+ border-radius: var(--radius-sm);
264
+ display: flex;
265
+ align-items: center;
266
+ justify-content: center;
267
+ flex-shrink: 0;
268
+ transition: all 0.2s;
269
+ }
270
+
271
+ .todo-checkbox.checked {
272
+ background-color: var(--accent-color);
273
+ border-color: var(--accent-color);
274
+ color: white;
275
+ }
276
+
277
+ .todo-checkbox.checked span {
278
+ font-size: 0.8rem;
279
+ }
280
+
281
+ .todo-text {
282
+ font-size: 1.1rem;
283
+ color: var(--text-bright);
284
+ flex: 1;
285
+ white-space: nowrap;
286
+ overflow: hidden;
287
+ text-overflow: ellipsis;
288
+ }
289
+
290
+ .todo-item.done .todo-text {
291
+ text-decoration: line-through;
292
+ opacity: 0.5;
293
+ }
294
+
295
+ /* Delete Button */
296
+ .todo-delete-btn {
297
+ opacity: 0;
298
+ pointer-events: none;
299
+ background: none;
300
+ border: none;
301
+ color: var(--error-color);
302
+ padding: 0 0.5rem;
303
+ cursor: pointer;
304
+ transition: opacity 0.2s;
305
+ font-size: 0.85rem;
306
+ }
307
+
308
+ /* Todo Input Area */
309
+ .todo-input-container {
310
+ padding-top: var(--spacing-sm);
311
+ border-top: 1px solid var(--border-color);
312
+ display: flex;
313
+ align-items: center;
314
+ gap: 0.5rem;
315
+ width: 100%;
316
+ /* Ensure full width */
317
+ margin-top: auto;
318
+ /* Push to bottom if flex child */
319
+ }
320
+
321
+ .todo-input {
322
+ flex: 1;
323
+ background: transparent;
324
+ border: none;
325
+ border-bottom: 2px solid var(--border-color);
326
+ color: var(--text-bright);
327
+ padding: 0.4rem 0;
328
+ font-size: 1rem;
329
+ font-family: var(--font-body);
330
+ }
331
+
332
+ .todo-input:focus {
333
+ outline: none;
334
+ border-bottom-color: var(--accent-color);
335
+ }
336
+
337
+ .todo-add-btn-small {
338
+ background: none;
339
+ border: none;
340
+ color: var(--accent-color);
341
+ cursor: pointer;
342
+ font-size: 1.2rem;
343
+ }
344
+
345
+ /* Light Mode Overrides for Specific Dashboard Colors if needed */
346
+
347
+ @media (max-width: 768px) {
348
+ /* 移动端隐藏首页的时间卡片和Todo面板 */
349
+ .dashboard-clock-card,
350
+ .dashboard-stats {
351
+ display: none !important;
352
+ }
353
+
354
+ /* 调整移动端首页欢迎语布局 */
355
+ .dashboard-intro {
356
+ height: auto;
357
+ gap: 0;
358
+ margin-bottom: var(--spacing-md);
359
+ }
360
+
361
+ /* 适当减小欢迎语字号 */
362
+ .welcome-title {
363
+ font-size: 2.5rem;
364
+ padding-left: 1rem;
365
+ }
366
+
367
+ .welcome-subtitle {
368
+ padding-left: 1rem;
369
+ margin-top: 0.5rem;
370
+ }
371
+ }
assets/styles/_layout.css ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ============================================
2
+ Page Layout & Containers
3
+ ============================================ */
4
+
5
+ /* 页面容器 */
6
+ .page {
7
+ position: relative;
8
+ width: 100%;
9
+ display: none;
10
+ flex-direction: column;
11
+ align-items: center;
12
+ padding-top: 2rem;
13
+ padding-left: 0.5rem;
14
+ padding-right: 0.5rem;
15
+ }
16
+
17
+ .page.active {
18
+ display: flex;
19
+ }
20
+
21
+ /* 页面模板容器(friends/articles/projects 等) */
22
+ .page-template {
23
+ width: 100%;
24
+ max-width: var(--page-max-width);
25
+ margin: 0 auto;
26
+ }
27
+
28
+ /* 欢迎区域 - Design A (Minimalist) */
29
+ .welcome-section {
30
+ width: 100%;
31
+ max-width: var(--page-max-width);
32
+ margin: 0 auto 1.2rem auto;
33
+ padding: 0 var(--spacing-lg);
34
+ text-align: left;
35
+ position: relative;
36
+ z-index: 5;
37
+ display: flex;
38
+ justify-content: space-between;
39
+ align-items: flex-end;
40
+ flex-wrap: wrap;
41
+ gap: var(--spacing-md);
42
+ }
43
+
44
+ .welcome-section-main {
45
+ flex: 1;
46
+ min-width: 220px;
47
+ }
48
+
49
+ .welcome-section-side {
50
+ flex: 0 0 auto;
51
+ }
52
+
53
+ .welcome-section h2 {
54
+ font-size: 1.75rem;
55
+ color: var(--text-bright);
56
+ margin-bottom: 0.25rem;
57
+ letter-spacing: 0.5px;
58
+ transition: color 0.3s ease;
59
+ }
60
+
61
+ .welcome-section h3 {
62
+ font-family: var(--font-body);
63
+ font-weight: 400;
64
+ font-size: 1rem;
65
+ margin-bottom: 0.5rem;
66
+ letter-spacing: 0.3px;
67
+ color: var(--text-muted);
68
+ position: relative;
69
+ display: block;
70
+ }
71
+
72
+ .welcome-section h3::before {
73
+ display: none;
74
+ }
75
+
76
+ .welcome-section .subtitle {
77
+ color: var(--text-muted);
78
+ font-size: 0.95rem;
79
+ line-height: 1.5;
80
+ transition: color 0.3s ease;
81
+ }
82
+
83
+ /* bookmarks:标题后追加"更新时间"小字 */
84
+ .welcome-title-row {
85
+ display: flex;
86
+ align-items: baseline;
87
+ flex-wrap: wrap;
88
+ gap: 0.6rem;
89
+ margin-bottom: 0.5rem;
90
+ }
91
+
92
+ .welcome-title-row h2 {
93
+ margin: 0;
94
+ }
95
+
96
+ .page-updated-inline {
97
+ color: var(--text-muted);
98
+ font-size: 0.9rem;
99
+ opacity: 0.85;
100
+ white-space: nowrap;
101
+ }
assets/styles/_main.css ADDED
@@ -0,0 +1,1448 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ============================================
2
+ Main CSS - 分类层级、热力图、响应式
3
+ ============================================
4
+
5
+ 此文件包含以下未独立拆分的样式:
6
+ - 分类层级样式 (.category, .category-level-*, .group-*)
7
+ - GitHub 热力图 (.gh-*)
8
+ - 全局响应式设计 (@media)
9
+ - 搜索结果区域 (#search-results)
10
+
11
+ ============================================ */
12
+
13
+ /* 分类样式 */
14
+ .category {
15
+ background: linear-gradient(145deg, var(--card-bg-gradient-1), var(--card-bg-gradient-2));
16
+ border-radius: var(--radius-xl);
17
+ padding: 1rem;
18
+ margin: 0 auto 1.2rem auto;
19
+ width: 100%;
20
+ max-width: var(--page-max-width);
21
+ position: relative;
22
+ z-index: 1;
23
+ opacity: 1;
24
+ box-shadow: 0 4px 20px var(--shadow-color);
25
+ border: 1px solid var(--border-color);
26
+ transition:
27
+ background var(--transition-normal),
28
+ box-shadow var(--transition-normal);
29
+ }
30
+
31
+ /* 分类标题容器 */
32
+ .category-header {
33
+ border-radius: var(--radius-md);
34
+ padding: 0.4rem;
35
+ margin: -0.4rem -0.4rem 0.8rem -0.4rem;
36
+ transition: all var(--transition-normal);
37
+ }
38
+
39
+ /* 标题前图标固定宽度:避免不同图标宽度导致标题文本不对齐 */
40
+ .category-header [data-editable='category-name'] > i,
41
+ .group-header [data-editable='group-name'] > i {
42
+ width: 1.25em;
43
+ min-width: 1.25em;
44
+ text-align: center;
45
+ flex: 0 0 1.25em;
46
+ }
47
+
48
+ /* 分组标题容器:与分类保持一致的悬浮动效基础 */
49
+ .group-header {
50
+ border-radius: var(--radius-md);
51
+ transition: all var(--transition-normal);
52
+ }
53
+
54
+ /* 仅可折叠的标题显示交互态 */
55
+ .category-header[data-toggle='category'],
56
+ .group-header[data-toggle='group'] {
57
+ cursor: pointer;
58
+ user-select: none;
59
+ }
60
+
61
+ .category-header[data-toggle='category']:hover {
62
+ transform: translateY(-2px);
63
+ background: linear-gradient(145deg, rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0.01));
64
+ }
65
+
66
+ .group-header[data-toggle='group']:hover {
67
+ transform: translateY(-2px);
68
+ background: linear-gradient(145deg, rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0.01));
69
+ }
70
+
71
+ .category-header[data-toggle='category']:active {
72
+ transform: translateY(0);
73
+ }
74
+
75
+ .group-header[data-toggle='group']:active {
76
+ transform: translateY(0);
77
+ }
78
+
79
+ .category h2 {
80
+ font-size: 1.2rem;
81
+ margin-bottom: 0;
82
+ color: var(--text-bright);
83
+ display: flex;
84
+ align-items: center;
85
+ gap: 0.8rem;
86
+ letter-spacing: 0.3px;
87
+ transition: color 0.3s ease;
88
+ }
89
+
90
+ .category h2 > i {
91
+ color: var(--accent-color);
92
+ font-size: 1.3rem;
93
+ transition: all 0.3s ease;
94
+ }
95
+
96
+ .category-header[data-toggle='category']:hover h2 > i {
97
+ transform: scale(1.1);
98
+ color: var(--accent-hover);
99
+ }
100
+
101
+ /* 多层级嵌套样式 - 扁平化设计 */
102
+
103
+ /* 通用重置:移除所有嵌套层级的卡片背景和边框 */
104
+ .category-level-2,
105
+ .category-level-3,
106
+ .category-level-4,
107
+ .group-level-3,
108
+ .group-level-4 {
109
+ background: none;
110
+ border: none;
111
+ box-shadow: none;
112
+ padding: 0;
113
+ width: 100%;
114
+ margin: 0;
115
+ }
116
+
117
+ /* 嵌套层级指示线 (Hierarchy Indicator A) */
118
+ .category-level-2::before,
119
+ .category-level-3::before,
120
+ .group-level-3::before,
121
+ .category-level-4::before,
122
+ .group-level-4::before {
123
+ content: '';
124
+ position: absolute;
125
+ left: 0;
126
+ top: 0;
127
+ bottom: 0;
128
+ width: 2px;
129
+ background-color: var(--border-color);
130
+ opacity: 0.6;
131
+ }
132
+
133
+ /* 层级2: 子分类 */
134
+ .category-level-2 {
135
+ margin-top: 0;
136
+ margin-bottom: 0;
137
+ padding-left: 1rem;
138
+ border-left: none;
139
+ position: relative;
140
+ }
141
+
142
+ /* 层级2: 标题样式 */
143
+ .category-level-2 .category-header {
144
+ margin: 0 -0.5rem 1rem -0.5rem;
145
+ padding: 0.5rem;
146
+ background: none;
147
+ border-radius: var(--radius-md);
148
+ }
149
+
150
+ .category-level-2 .category-header h3 {
151
+ font-size: 1.1rem;
152
+ font-weight: 600;
153
+ color: var(--text-bright);
154
+ display: flex;
155
+ align-items: center;
156
+ gap: 0.8rem;
157
+ }
158
+
159
+ .category-level-2 .category-header h3 > i {
160
+ color: var(--accent-color);
161
+ font-size: 1.2rem;
162
+ opacity: 0.9;
163
+ }
164
+
165
+ /* 层级3: 分组 */
166
+ .group-level-3,
167
+ .category-level-3 {
168
+ margin-top: 0;
169
+ margin-bottom: 0;
170
+ padding-left: 1rem;
171
+ position: relative;
172
+ }
173
+
174
+ /* 层级3: 标题样式 */
175
+ .group-level-3 .group-header,
176
+ .category-level-3 .category-header {
177
+ margin: 0 0 0.8rem 0;
178
+ padding: 0.3rem 0;
179
+ background: none;
180
+ }
181
+
182
+ .group-level-3 .group-header h4,
183
+ .category-level-3 .category-header h4 {
184
+ font-size: 1rem;
185
+ font-weight: 500;
186
+ color: var(--text-color);
187
+ display: flex;
188
+ align-items: center;
189
+ gap: 0.6rem;
190
+ }
191
+
192
+ .group-level-3 .group-header h4 i,
193
+ .category-level-3 .category-header h4 i {
194
+ color: var(--text-muted);
195
+ font-size: 1rem;
196
+ }
197
+
198
+ /* 层级4: 子分组 */
199
+ .group-level-4,
200
+ .category-level-4 {
201
+ margin-top: 0;
202
+ margin-bottom: 0;
203
+ padding-left: 1rem;
204
+ position: relative;
205
+ }
206
+
207
+ /* 嵌套层级间距:仅在同级相邻时增加间距,避免首项被额外下推 */
208
+ .subcategories-container > .category-level-2 + .category-level-2 {
209
+ margin-top: 1rem;
210
+ }
211
+
212
+ .groups-container > .group-level-3 + .group-level-3,
213
+ .groups-container > .category-level-3 + .category-level-3 {
214
+ margin-top: 0.8rem;
215
+ }
216
+
217
+ .subgroups-container > .group-level-4 + .group-level-4,
218
+ .subcategories-container > .category-level-4 + .category-level-4 {
219
+ margin-top: 0.6rem;
220
+ }
221
+
222
+ /* 层级4: 标题样式 */
223
+ .group-level-4 .group-header,
224
+ .category-level-4 .category-header {
225
+ margin: 0 0 0.6rem 0;
226
+ padding: 0.2rem 0;
227
+ background: none;
228
+ }
229
+
230
+ .group-level-4 .group-header h5,
231
+ .category-level-4 .category-header h5 {
232
+ font-size: 0.9rem;
233
+ font-weight: 500;
234
+ color: var(--text-muted);
235
+ display: flex;
236
+ align-items: center;
237
+ gap: 0.5rem;
238
+ }
239
+
240
+ .group-level-4 .group-header h5 i,
241
+ .category-level-4 .category-header h5 i {
242
+ font-size: 0.9rem;
243
+ opacity: 0.7;
244
+ }
245
+
246
+ /* 移除悬停时的缩放效果,保持简洁 */
247
+ .category-level-2 .category-header:hover h3 > i,
248
+ .group-level-3 .group-header:hover h4 i,
249
+ .category-level-3 .category-header:hover h4 i,
250
+ .group-level-4 .group-header:hover h5 i,
251
+ .category-level-4 .category-header:hover h5 i {
252
+ transform: none;
253
+ }
254
+
255
+ /* 切换图标样式 */
256
+ .category-header .toggle-icon,
257
+ .group-header .toggle-icon {
258
+ display: inline-flex;
259
+ align-items: center;
260
+ justify-content: center;
261
+ width: 20px;
262
+ height: 20px;
263
+ margin-left: auto;
264
+ color: var(--text-muted);
265
+ font-size: 0.9rem;
266
+ }
267
+
268
+ .category-header .toggle-icon i,
269
+ .group-header .toggle-icon i {
270
+ transition:
271
+ transform 0.3s ease,
272
+ color 0.3s ease;
273
+ transform: rotate(0deg);
274
+ }
275
+
276
+ /* 展开态:图标旋转 180°(类似参考样式1) */
277
+ .category:not(.collapsed) > .category-header .toggle-icon i,
278
+ .group:not(.collapsed) > .group-header .toggle-icon i {
279
+ transform: rotate(180deg);
280
+ color: var(--text-bright);
281
+ }
282
+
283
+ .category-header[data-toggle='category']:hover .toggle-icon i,
284
+ .group-header[data-toggle='group']:hover .toggle-icon i {
285
+ color: var(--accent-color);
286
+ }
287
+
288
+ /* 分类/分组折叠图标:桌面端默认隐藏,悬停/收起时显示,避免按钮过多 */
289
+ @media (hover: hover) and (pointer: fine) {
290
+ .category-header .toggle-icon,
291
+ .group-header .toggle-icon {
292
+ opacity: 0;
293
+ transition: opacity 0.2s ease;
294
+ }
295
+
296
+ .category-header[data-toggle='category']:hover .toggle-icon,
297
+ .category.collapsed > .category-header .toggle-icon,
298
+ .group-header[data-toggle='group']:hover .toggle-icon,
299
+ .group.collapsed > .group-header .toggle-icon {
300
+ opacity: 1;
301
+ }
302
+ }
303
+
304
+ /* 展开/折叠动画 */
305
+ .category-content,
306
+ .group-content {
307
+ overflow: visible;
308
+ transition:
309
+ max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1),
310
+ opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);
311
+ max-height: 5000px;
312
+ opacity: 1;
313
+ }
314
+
315
+ .category.collapsed .category-content,
316
+ .group.collapsed .group-content {
317
+ overflow: hidden;
318
+ max-height: 0;
319
+ opacity: 0;
320
+ margin-top: 0;
321
+ }
322
+
323
+ /* 收起状态下调整header的下边距 */
324
+ .category.collapsed > .category-header {
325
+ margin-bottom: -0.5rem;
326
+ }
327
+
328
+ .category-level-2.collapsed > .category-header {
329
+ margin-bottom: 0;
330
+ border-bottom: none;
331
+ }
332
+
333
+ .group-level-3.collapsed > .group-header,
334
+ .category-level-3.collapsed > .category-header {
335
+ margin-bottom: 0;
336
+ }
337
+
338
+ .group-level-4.collapsed > .group-header,
339
+ .category-level-4.collapsed > .category-header {
340
+ margin-bottom: 0;
341
+ }
342
+
343
+ /* 收起态默认向下,无需额外旋转(保持 0deg) */
344
+
345
+ /* 空内容提示 */
346
+ .empty-content {
347
+ color: var(--text-muted);
348
+ font-style: italic;
349
+ text-align: center;
350
+ padding: 1rem;
351
+ font-size: 0.9rem;
352
+ }
353
+
354
+ /* 子容器样式 */
355
+ .subcategories-container,
356
+ .groups-container {
357
+ width: 100%;
358
+ }
359
+
360
+ /* 当分类同时包含子分类和站点时的样式优化 */
361
+ .category-content .subcategories-container + .sites-grid {
362
+ margin-top: 1.2rem;
363
+ padding-top: 1rem;
364
+ border-top: 1px solid var(--border-color);
365
+ }
366
+
367
+ /* 当分类同时包含分组和站点时的样式优化 */
368
+ .category-content .groups-container + .sites-grid {
369
+ margin-top: 1.2rem;
370
+ padding-top: 1rem;
371
+ border-top: 1px solid var(--border-color);
372
+ }
373
+
374
+ /* 子分类容器底部间距调整 */
375
+ .category-content .subcategories-container:not(:last-child),
376
+ .category-content .groups-container:not(:last-child) {
377
+ margin-bottom: 0.6rem;
378
+ }
379
+
380
+ /* 确保嵌套的网站网格正确显示 */
381
+ .category-level-2 .sites-grid,
382
+ .group-level-3 .sites-grid,
383
+ .category-level-3 .sites-grid,
384
+ .group-level-4 .sites-grid,
385
+ .category-level-4 .sites-grid {
386
+ margin-top: 0;
387
+ gap: 0.75rem;
388
+ /* 保持与顶层一致的网格布局 */
389
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
390
+ }
391
+
392
+ /* 响应式设计 - 嵌套结构 */
393
+ @media (max-width: 768px) {
394
+ .category-level-2 {
395
+ padding-left: 0.75rem;
396
+ }
397
+
398
+ .group-level-3,
399
+ .category-level-3 {
400
+ padding-left: 0.75rem;
401
+ }
402
+
403
+ .group-level-4,
404
+ .category-level-4 {
405
+ padding-left: 0.75rem;
406
+ }
407
+
408
+ .subcategories-container > .category-level-2 + .category-level-2 {
409
+ margin-top: 0.8rem;
410
+ }
411
+
412
+ .groups-container > .group-level-3 + .group-level-3,
413
+ .groups-container > .category-level-3 + .category-level-3 {
414
+ margin-top: 0.7rem;
415
+ }
416
+
417
+ .subgroups-container > .group-level-4 + .group-level-4,
418
+ .subcategories-container > .category-level-4 + .category-level-4 {
419
+ margin-top: 0.55rem;
420
+ }
421
+
422
+ .category-level-2 .sites-grid,
423
+ .group-level-3 .sites-grid,
424
+ .category-level-3 .sites-grid,
425
+ .group-level-4 .sites-grid,
426
+ .category-level-4 .sites-grid {
427
+ grid-template-columns: repeat(2, minmax(0, 1fr));
428
+ gap: var(--spacing-sm);
429
+ }
430
+ }
431
+
432
+ @media (max-width: 480px) {
433
+ .category {
434
+ margin-left: 0.5rem;
435
+ margin-right: 0.5rem;
436
+ padding: 1rem;
437
+ }
438
+
439
+ .category-level-2,
440
+ .group-level-3,
441
+ .category-level-3 {
442
+ margin-left: 0;
443
+ padding-left: 0.75rem;
444
+ width: 100%;
445
+ }
446
+
447
+ .group-level-4,
448
+ .category-level-4 {
449
+ margin-left: 0;
450
+ padding-left: 0.75rem;
451
+ width: 100%;
452
+ }
453
+
454
+ .category-level-2 .sites-grid,
455
+ .group-level-3 .sites-grid,
456
+ .category-level-3 .sites-grid,
457
+ .group-level-4 .sites-grid,
458
+ .category-level-4 .sites-grid {
459
+ grid-template-columns: repeat(2, minmax(0, 1fr));
460
+ gap: 0.5rem;
461
+ }
462
+ }
463
+
464
+ /* projects:GitHub 热力图(github-calendar.js,底部展示) */
465
+ .page-template-projects .gh-heatmap-category {
466
+ /* 外层已复用一级分类卡片(.category),这里仅保留热力图内部布局/色阶 */
467
+ --gh-text: var(--text-color);
468
+ --gh-text-muted: var(--text-muted);
469
+ --gh-level-0: rgba(255, 255, 255, 0.08);
470
+ --gh-level-1: rgba(55, 178, 77, 0.35);
471
+ --gh-level-2: rgba(55, 178, 77, 0.55);
472
+ --gh-level-3: rgba(55, 178, 77, 0.75);
473
+ --gh-level-4: rgba(55, 178, 77, 0.95);
474
+ --gh-radius: 3px;
475
+
476
+ margin: 0 auto 1.2rem auto;
477
+ }
478
+
479
+ .page-template-projects .gh-heatmap-wrapper {
480
+ margin-top: 0;
481
+ }
482
+
483
+ /* 浅色主题:更接近 GitHub 原色阶 */
484
+ html.theme-preload .page-template-projects .gh-heatmap-category,
485
+ body.light-theme .page-template-projects .gh-heatmap-category {
486
+ /* 浅色主题下,空格子需要比背景更明显一点 */
487
+ --gh-level-0: #d8dee4;
488
+ --gh-level-1: #9be9a8;
489
+ --gh-level-2: #40c463;
490
+ --gh-level-3: #30a14e;
491
+ --gh-level-4: #216e39;
492
+ }
493
+
494
+ /* 标题中的用户名(@xxx)更弱化一点,像副标题 */
495
+ .page-template-projects .gh-heatmap-username {
496
+ color: var(--text-muted);
497
+ font-weight: 400;
498
+ margin-left: 0.35rem;
499
+ }
500
+
501
+ /* 使用一级分类标题的排版节奏,略收紧热力图区域 */
502
+ .page-template-projects .gh-heatmap-category .category-header {
503
+ margin-bottom: 0.8rem;
504
+ }
505
+
506
+ /* 标题行:左侧标题 + 右侧 legend(不占内容区高度) */
507
+ .page-template-projects .gh-heatmap-category .gh-heatmap-header {
508
+ display: flex;
509
+ align-items: center;
510
+ justify-content: space-between;
511
+ gap: 0.8rem;
512
+ }
513
+
514
+ .page-template-projects .gh-heatmap-category .gh-heatmap-header h2 {
515
+ margin: 0;
516
+ }
517
+
518
+ /* 让 legend 与标题体系保持一致:放在标题区右侧 */
519
+ .page-template-projects .gh-heatmap-category .gh-legend {
520
+ justify-content: flex-end;
521
+ margin: 0;
522
+ }
523
+
524
+ .page-template-projects .gh-header {
525
+ display: none;
526
+ }
527
+
528
+ .page-template-projects .gh-legend {
529
+ display: flex;
530
+ align-items: center;
531
+ gap: 4px;
532
+ font-size: 0.75rem;
533
+ color: var(--gh-text-muted);
534
+ white-space: nowrap;
535
+ }
536
+
537
+ /* heatmap:移动端标题/legend 文本简写 */
538
+ .gh-text-mobile {
539
+ display: none;
540
+ }
541
+
542
+ @media (max-width: 480px) {
543
+ .gh-text-desktop {
544
+ display: none;
545
+ }
546
+
547
+ .gh-text-mobile {
548
+ display: inline;
549
+ }
550
+
551
+ .page-template-projects .gh-heatmap-category .gh-heatmap-header {
552
+ flex-wrap: wrap;
553
+ }
554
+
555
+ .page-template-projects .gh-heatmap-category .gh-legend {
556
+ gap: 2px;
557
+ font-size: 0.7rem;
558
+ }
559
+
560
+ .page-template-projects .gh-legend-item {
561
+ width: 8px;
562
+ height: 8px;
563
+ }
564
+ }
565
+
566
+ .page-template-projects .gh-legend-item {
567
+ width: 10px;
568
+ height: 10px;
569
+ border-radius: 2px;
570
+ }
571
+
572
+ .page-template-projects .gh-legend .level-0 {
573
+ background-color: var(--gh-level-0);
574
+ }
575
+
576
+ .page-template-projects .gh-legend .level-1 {
577
+ background-color: var(--gh-level-1);
578
+ }
579
+
580
+ .page-template-projects .gh-legend .level-2 {
581
+ background-color: var(--gh-level-2);
582
+ }
583
+
584
+ .page-template-projects .gh-legend .level-3 {
585
+ background-color: var(--gh-level-3);
586
+ }
587
+
588
+ .page-template-projects .gh-legend .level-4 {
589
+ background-color: var(--gh-level-4);
590
+ }
591
+
592
+ /* github-calendar 注入的内容容器 */
593
+ .page-template-projects .gh-calendar {
594
+ border: none !important;
595
+ min-height: 0;
596
+ width: 100%;
597
+ /* 构建期注入:避免 flex 居中导致内容超宽“撑破卡片” */
598
+ display: block;
599
+ }
600
+
601
+ /* 顶部统计标题:XXX contributions in the last year */
602
+ .page-template-projects .gh-calendar #js-contribution-activity-description {
603
+ /* 注意:全局 .category h2 会把 h2 设为 flex,导致“看起来左对齐”。这里强制回退为 block。 */
604
+ display: block;
605
+ width: 100%;
606
+ text-align: center;
607
+ margin: 0 0 12px 0;
608
+ }
609
+
610
+ /* 外层包裹:强制占满卡片宽度,滚动发生在内部 */
611
+ .page-template-projects .gh-calendar .graph-before-activity-overview {
612
+ width: 100%;
613
+ }
614
+
615
+ /* 允许移动端横向滚动查看(GitHub 风格) */
616
+ .page-template-projects .gh-calendar .js-calendar-graph {
617
+ width: 100%;
618
+ /* 构建期注入 GitHub 原生 table:滚动交给内层容器,避免裁剪 */
619
+ display: block;
620
+ }
621
+
622
+ /* GitHub 原生 markup:通常在 .js-calendar-graph 内有一个 div 设置 overflow-x */
623
+ .page-template-projects .gh-calendar .js-calendar-graph > div {
624
+ width: 100%;
625
+ max-width: 100%;
626
+ overflow-x: auto;
627
+ overflow-y: hidden;
628
+ -webkit-overflow-scrolling: touch;
629
+ /* 由 table 的 margin:auto 来实现“可居中 + 可滚动时左对齐起始列” */
630
+ display: block;
631
+ padding-bottom: 10px;
632
+ }
633
+
634
+ .page-template-projects .gh-calendar .js-calendar-graph-svg {
635
+ width: 100%;
636
+ height: auto;
637
+ }
638
+
639
+ /* 覆盖每个方块的颜色(依赖 github-calendar 的 data-level) */
640
+ .page-template-projects .gh-calendar .day {
641
+ rx: var(--gh-radius);
642
+ ry: var(--gh-radius);
643
+ outline: none;
644
+ }
645
+
646
+ .page-template-projects .gh-calendar .day[data-level='0'] {
647
+ fill: var(--gh-level-0);
648
+ }
649
+
650
+ .page-template-projects .gh-calendar .day[data-level='1'] {
651
+ fill: var(--gh-level-1);
652
+ }
653
+
654
+ .page-template-projects .gh-calendar .day[data-level='2'] {
655
+ fill: var(--gh-level-2);
656
+ }
657
+
658
+ .page-template-projects .gh-calendar .day[data-level='3'] {
659
+ fill: var(--gh-level-3);
660
+ }
661
+
662
+ .page-template-projects .gh-calendar .day[data-level='4'] {
663
+ fill: var(--gh-level-4);
664
+ }
665
+
666
+ .page-template-projects .gh-calendar text {
667
+ fill: var(--gh-text-muted);
668
+ font-size: 10px;
669
+ }
670
+
671
+ /* 去掉库自带 footer(更简洁) */
672
+ .page-template-projects .gh-calendar .contrib-footer {
673
+ display: none !important;
674
+ }
675
+
676
+ .page-template-projects .gh-calendar.gh-calendar-error {
677
+ color: var(--gh-text-muted);
678
+ font-size: 0.85rem;
679
+ }
680
+
681
+ /* github-calendar(HTML table 版):适配 ContributionCalendar-* 类名 */
682
+ .page-template-projects .gh-calendar table {
683
+ /* 让热力图在卡片内尽量铺满宽度;需要时仍可横向滚动 */
684
+ /* 关键:避免 table 按列拉伸导致 day 变成长方形 */
685
+ display: table;
686
+ /* 不强制占满:保持自然宽度,并在容器内居中 */
687
+ width: max-content;
688
+ min-width: max-content;
689
+ max-width: none;
690
+ table-layout: auto;
691
+ border-collapse: separate;
692
+ border-spacing: 5px !important;
693
+ /* table 未超宽时居中;超宽时 margin auto 会退化为 0(滚动起始列左对齐) */
694
+ margin: 0 auto;
695
+ }
696
+
697
+ /* GitHub 原生 table:thead 行内写死 height:15px,需强制更高以避免月份 label 压到格子 */
698
+ .page-template-projects .gh-calendar table thead tr {
699
+ height: 20px !important;
700
+ }
701
+
702
+ /* GitHub 原生 table 的月份 label 使用 absolute 定位,需要为 thead 行预留高度,避免与格子重叠 */
703
+ .page-template-projects .gh-calendar .ContributionCalendar-label {
704
+ height: 16px;
705
+ position: relative;
706
+ padding-bottom: 4px;
707
+ font-size: 10px;
708
+ line-height: 10px;
709
+ font-weight: 400;
710
+ vertical-align: bottom;
711
+ }
712
+
713
+ @media (max-width: 600px) {
714
+ /* 移动端:格子更紧凑,减少横向滚动压力 */
715
+ .page-template-projects .gh-calendar table {
716
+ border-spacing: 3px !important;
717
+ }
718
+
719
+ .page-template-projects .gh-calendar .ContributionCalendar-day {
720
+ width: 12px;
721
+ height: 12px;
722
+ min-width: 12px;
723
+ min-height: 12px;
724
+ }
725
+
726
+ .page-template-projects .gh-calendar #js-contribution-activity-description {
727
+ font-size: 0.95rem;
728
+ }
729
+ }
730
+
731
+ /* GitHub 原生 footer:保留“Learn how we count contributions”,隐藏右侧自带 legend(避免与自定义 legend 重复) */
732
+ .page-template-projects .gh-calendar .float-right.color-fg-muted.d-flex.flex-items-center {
733
+ display: none !important;
734
+ }
735
+
736
+ /* 按需求:去除 “Learn how we count contributions” */
737
+ .page-template-projects .gh-calendar .float-left {
738
+ display: none !important;
739
+ }
740
+
741
+ /* Link--muted 目前不展示(float-left 已隐藏);保留基础链接色由下方 a 规则兜底 */
742
+
743
+ .page-template-projects .gh-calendar .ContributionCalendar-day {
744
+ /* 固定正方形,防止被 table 列宽拉伸 */
745
+ width: 15px;
746
+ height: 15px;
747
+ min-width: 15px;
748
+ min-height: 15px;
749
+ aspect-ratio: 1 / 1;
750
+ border-radius: var(--gh-radius);
751
+ background-color: var(--gh-level-0);
752
+ }
753
+
754
+ .page-template-projects .gh-calendar .ContributionCalendar-day[data-level='0'] {
755
+ background-color: var(--gh-level-0) !important;
756
+ }
757
+
758
+ .page-template-projects .gh-calendar .ContributionCalendar-day[data-level='1'] {
759
+ background-color: var(--gh-level-1) !important;
760
+ }
761
+
762
+ .page-template-projects .gh-calendar .ContributionCalendar-day[data-level='2'] {
763
+ background-color: var(--gh-level-2) !important;
764
+ }
765
+
766
+ .page-template-projects .gh-calendar .ContributionCalendar-day[data-level='3'] {
767
+ background-color: var(--gh-level-3) !important;
768
+ }
769
+
770
+ .page-template-projects .gh-calendar .ContributionCalendar-day[data-level='4'] {
771
+ background-color: var(--gh-level-4) !important;
772
+ }
773
+
774
+ /* a11y 跳转链接:视觉隐藏(保留可访问性) */
775
+ .page-template-projects .gh-calendar a[href^='#year-list'],
776
+ .page-template-projects .gh-calendar a[href*='year-list'],
777
+ .page-template-projects .gh-calendar a[href*='contributions-year'] {
778
+ /* GitHub 注入的 "Skip to contributions year list"(常见为 show-on-focus) */
779
+ position: absolute;
780
+ width: 1px;
781
+ height: 1px;
782
+ padding: 0;
783
+ margin: -1px;
784
+ overflow: hidden;
785
+ clip: rect(0, 0, 0, 0);
786
+ white-space: nowrap;
787
+ border: 0;
788
+ }
789
+
790
+ /* 响应式设计 */
791
+ @media (max-width: 1200px) {
792
+ .welcome-section {
793
+ padding: 0 var(--spacing-lg);
794
+ margin-bottom: 2rem;
795
+ }
796
+
797
+ .category {
798
+ max-width: 1100px;
799
+ margin: 0 auto 2.5rem auto;
800
+ }
801
+ }
802
+
803
+ @media (max-width: 768px) {
804
+ .mobile-buttons {
805
+ display: flex;
806
+ }
807
+
808
+ :root {
809
+ /* 与移动端搜索框高度更贴合(搜索框更高一些,菜单按钮同步放大) */
810
+ --mobile-top-button-size: 2.9rem;
811
+ }
812
+
813
+ .menu-toggle {
814
+ width: var(--mobile-top-button-size);
815
+ height: var(--mobile-top-button-size);
816
+ background: rgba(var(--card-bg-rgb), 0.65);
817
+ border: 1px solid var(--border-color);
818
+ border-radius: var(--radius-lg);
819
+ backdrop-filter: blur(12px);
820
+ -webkit-backdrop-filter: blur(12px);
821
+ box-shadow: 0 4px 16px var(--shadow-color);
822
+ }
823
+
824
+ /* 移动端:右下角磁贴与侧边栏按钮磁贴统一风格 */
825
+ .theme-toggle,
826
+ .category-toggle {
827
+ width: var(--mobile-top-button-size);
828
+ height: var(--mobile-top-button-size);
829
+ background: rgba(var(--card-bg-rgb), 0.65);
830
+ border: 1px solid var(--border-color);
831
+ border-radius: var(--radius-lg);
832
+ backdrop-filter: blur(12px);
833
+ -webkit-backdrop-filter: blur(12px);
834
+ box-shadow: 0 4px 16px var(--shadow-color);
835
+ transition: all var(--transition-normal);
836
+ transition-timing-function: var(--transition-bounce);
837
+ }
838
+
839
+ .theme-toggle:hover,
840
+ .category-toggle:hover {
841
+ transform: translateY(-2px);
842
+ background: rgba(var(--card-bg-rgb), 0.75);
843
+ box-shadow: 0 6px 20px var(--shadow-color);
844
+ color: var(--accent-color);
845
+ }
846
+
847
+ .theme-toggle:active,
848
+ .category-toggle:active {
849
+ transform: translateY(0);
850
+ box-shadow: 0 2px 8px var(--shadow-color);
851
+ }
852
+
853
+ main.content {
854
+ margin-left: 0;
855
+ width: 100vw;
856
+ max-width: 100vw;
857
+ padding-top: calc(var(--spacing-md) + var(--mobile-top-button-size) + var(--spacing-sm));
858
+ padding-top: calc(
859
+ env(safe-area-inset-top) + var(--spacing-md) + var(--mobile-top-button-size) +
860
+ var(--spacing-sm)
861
+ );
862
+ /* 缩短分类卡片与页面边缘的左右留白,扩大分类卡片可用宽度 */
863
+ padding-left: calc(var(--spacing-sm) + var(--spacing-xs));
864
+ padding-right: calc(var(--spacing-sm) + var(--spacing-xs));
865
+ }
866
+
867
+ .sidebar {
868
+ transform: translateX(-100%);
869
+ box-shadow: none;
870
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
871
+ max-width: 100vw;
872
+ overflow-x: hidden;
873
+ }
874
+
875
+ .sidebar .logo {
876
+ padding-top: 1.5rem;
877
+ display: flex;
878
+ align-items: center;
879
+ height: 60px;
880
+ }
881
+
882
+ /* 移动端下隐藏侧边栏折叠按钮 */
883
+ .sidebar-toggle {
884
+ display: none;
885
+ }
886
+
887
+ .sidebar.active {
888
+ transform: translateX(0);
889
+ box-shadow: 2px 0 10px var(--shadow-color);
890
+ z-index: 1000;
891
+ /* 增加侧边栏激活时的z-index,确保显示在按钮之上 */
892
+ }
893
+
894
+ /* 重置移动端下的侧边栏展开状态 */
895
+ .sidebar.collapsed {
896
+ width: var(--sidebar-width);
897
+ }
898
+
899
+ .sidebar.collapsed .logo h1,
900
+ .sidebar.collapsed .nav-item .nav-text,
901
+ .sidebar.collapsed .nav-item .external-icon {
902
+ opacity: 1;
903
+ transform: none;
904
+ width: auto;
905
+ }
906
+
907
+ .sidebar.collapsed .sidebar-footer {
908
+ height: auto;
909
+ padding: 1rem 1.2rem;
910
+ visibility: visible;
911
+ pointer-events: auto;
912
+ border-top: 1px solid var(--border-color);
913
+ }
914
+
915
+ .sidebar.collapsed .sidebar-social {
916
+ padding: 0.2rem 1.2rem 0.8rem;
917
+ flex-direction: row;
918
+ }
919
+
920
+ .sidebar.collapsed .nav-item {
921
+ padding: 0.6rem 0.8rem;
922
+ justify-content: flex-start;
923
+ }
924
+
925
+ .sidebar.collapsed .nav-item .icon-container {
926
+ margin-right: 1rem;
927
+ }
928
+
929
+ /* 移动端:搜索框常驻显示(与侧边栏按钮同一行,无需“悬浮磁贴”) */
930
+ .search-container {
931
+ position: fixed;
932
+ top: var(--spacing-md);
933
+ top: calc(env(safe-area-inset-top) + var(--spacing-md));
934
+ /* 给左侧菜单按钮留出空间 */
935
+ left: calc(var(--spacing-md) + var(--mobile-top-button-size) + var(--spacing-sm));
936
+ right: var(--spacing-md);
937
+ left: calc(
938
+ env(safe-area-inset-left) + var(--spacing-md) + var(--mobile-top-button-size) +
939
+ var(--spacing-sm)
940
+ );
941
+ right: calc(env(safe-area-inset-right) + var(--spacing-md));
942
+ width: auto;
943
+ padding: 0;
944
+ margin-bottom: 0;
945
+ box-shadow: none;
946
+ z-index: 900;
947
+ }
948
+
949
+ .search-box {
950
+ max-width: 100%;
951
+ }
952
+
953
+ .search-box input {
954
+ padding: 0.8rem 3rem 0.8rem 1rem;
955
+ font-size: 0.95rem;
956
+ }
957
+
958
+ .search-box::after {
959
+ right: 0.8rem;
960
+ }
961
+
962
+ .search-shortcut-hint {
963
+ right: 1.2rem;
964
+ font-size: 0.72rem;
965
+ padding: 0.1rem 0.35rem;
966
+ }
967
+
968
+ .search-engine-button {
969
+ width: 104px;
970
+ flex: 0 0 104px;
971
+ padding: 0 0.6rem;
972
+ }
973
+
974
+ .sidebar .logo h1,
975
+ .sidebar .nav-item span {
976
+ opacity: 1;
977
+ display: block;
978
+ }
979
+
980
+ /* 欢迎区域样式 */
981
+ .welcome-section {
982
+ padding: 0 1rem;
983
+ margin-top: 1rem;
984
+ /* 增加顶部间距 */
985
+ }
986
+
987
+ .page {
988
+ padding-left: 0.15rem;
989
+ padding-right: 0.15rem;
990
+ }
991
+
992
+ .welcome-section h2 {
993
+ font-size: 1.5rem;
994
+ }
995
+
996
+ .welcome-section h3 {
997
+ font-size: 1rem;
998
+ background: none;
999
+ -webkit-background-clip: border-box;
1000
+ background-clip: border-box;
1001
+ animation: none;
1002
+ color: var(--text-muted);
1003
+ }
1004
+
1005
+ /* 移动端分类切换按钮 */
1006
+ .category-toggle {
1007
+ bottom: 4rem;
1008
+ bottom: calc(env(safe-area-inset-bottom) + 4rem);
1009
+ right: 1rem;
1010
+ right: calc(env(safe-area-inset-right) + 1rem);
1011
+ }
1012
+
1013
+ /* 移动端:隐藏搜索按钮(未删除,仅隐藏;搜索框常驻) */
1014
+ .search-toggle {
1015
+ display: none;
1016
+ }
1017
+
1018
+ /* 分类样式优化 */
1019
+ .category {
1020
+ margin: 0 auto var(--spacing-lg) auto;
1021
+ padding: var(--spacing-md);
1022
+ width: 100%;
1023
+ }
1024
+
1025
+ .sites-grid {
1026
+ gap: var(--spacing-sm);
1027
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1028
+ }
1029
+
1030
+ .site-card {
1031
+ /* 移动端保持与客户端一致:横排卡片(图标在左,文本左对齐) */
1032
+ flex-direction: row;
1033
+ align-items: center;
1034
+ text-align: left;
1035
+ padding: 0.75rem 0.65rem;
1036
+ gap: 0.6rem;
1037
+ }
1038
+
1039
+ .site-card-icon {
1040
+ width: 2.2rem;
1041
+ height: 2.2rem;
1042
+ }
1043
+
1044
+ .site-card .site-icon {
1045
+ font-size: 1.5rem;
1046
+ }
1047
+
1048
+ .site-card .favicon-icon,
1049
+ .site-card .icon-placeholder,
1050
+ .site-card .icon-fallback,
1051
+ .site-card .icon-container {
1052
+ width: 1.5rem;
1053
+ height: 1.5rem;
1054
+ }
1055
+
1056
+ .site-card .icon-placeholder,
1057
+ .site-card .icon-fallback {
1058
+ line-height: 1.5rem;
1059
+ font-size: 1.3rem;
1060
+ }
1061
+
1062
+ .site-card-content {
1063
+ text-align: left;
1064
+ }
1065
+
1066
+ .site-card h3 {
1067
+ font-size: 1rem;
1068
+ margin-bottom: 0.25rem;
1069
+ white-space: nowrap;
1070
+ overflow: hidden;
1071
+ text-overflow: ellipsis;
1072
+ max-width: 100%;
1073
+ }
1074
+
1075
+ .site-card p {
1076
+ font-size: 0.9rem;
1077
+ line-height: 1.4;
1078
+ white-space: nowrap;
1079
+ overflow: hidden;
1080
+ text-overflow: ellipsis;
1081
+ }
1082
+
1083
+ /* 在移动端的主题切换按钮 */
1084
+ .theme-toggle {
1085
+ bottom: 1rem;
1086
+ bottom: calc(env(safe-area-inset-bottom) + 1rem);
1087
+ right: 1rem;
1088
+ right: calc(env(safe-area-inset-right) + 1rem);
1089
+ }
1090
+
1091
+ .sidebar .submenu {
1092
+ margin-left: 1rem;
1093
+ }
1094
+
1095
+ .sidebar.active .submenu-item {
1096
+ padding: 0.5rem 0.6rem;
1097
+ }
1098
+
1099
+ /* 确保移动设备上子菜单不会出现漏出问题 */
1100
+ .sidebar.collapsed .submenu {
1101
+ display: none;
1102
+ }
1103
+ }
1104
+
1105
+ @media (max-width: 480px) {
1106
+ .welcome-section {
1107
+ padding: 0 1rem;
1108
+ margin-bottom: 1rem;
1109
+ }
1110
+
1111
+ .category {
1112
+ margin: 0 auto 1.3rem auto;
1113
+ padding: 0.95rem;
1114
+ width: 100%;
1115
+ }
1116
+
1117
+ .search-container {
1118
+ left: calc(var(--spacing-md) + var(--mobile-top-button-size) + var(--spacing-sm));
1119
+ right: var(--spacing-md);
1120
+ left: calc(
1121
+ env(safe-area-inset-left) + var(--spacing-md) + var(--mobile-top-button-size) +
1122
+ var(--spacing-sm)
1123
+ );
1124
+ right: calc(env(safe-area-inset-right) + var(--spacing-md));
1125
+ }
1126
+
1127
+ .page {
1128
+ padding-top: 1rem;
1129
+ padding-left: 0.1rem;
1130
+ padding-right: 0.1rem;
1131
+ }
1132
+
1133
+ .sites-grid {
1134
+ gap: 0.5rem;
1135
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1136
+ }
1137
+
1138
+ .site-card {
1139
+ padding: 0.75rem 0.5rem;
1140
+ gap: 0.5rem;
1141
+ }
1142
+
1143
+ .site-card-icon {
1144
+ width: 2rem;
1145
+ height: 2rem;
1146
+ }
1147
+
1148
+ .site-card .site-icon {
1149
+ font-size: 1.3rem;
1150
+ }
1151
+
1152
+ .site-card .favicon-icon,
1153
+ .site-card .icon-placeholder,
1154
+ .site-card .icon-fallback,
1155
+ .site-card .icon-container {
1156
+ width: 1.35rem;
1157
+ height: 1.35rem;
1158
+ }
1159
+
1160
+ .site-card .icon-placeholder,
1161
+ .site-card .icon-fallback {
1162
+ line-height: 1.35rem;
1163
+ font-size: 1.2rem;
1164
+ }
1165
+
1166
+ .site-card h3 {
1167
+ font-size: 1rem;
1168
+ margin-bottom: 0.3rem;
1169
+ white-space: nowrap;
1170
+ overflow: hidden;
1171
+ text-overflow: ellipsis;
1172
+ max-width: 100%;
1173
+ }
1174
+
1175
+ .site-card p {
1176
+ font-size: 0.9rem;
1177
+ line-height: 1.2;
1178
+ white-space: nowrap;
1179
+ overflow: hidden;
1180
+ text-overflow: ellipsis;
1181
+ }
1182
+ }
1183
+
1184
+ @media (max-width: 400px) {
1185
+ .category {
1186
+ padding: 0.85rem;
1187
+ margin: 0 0.05rem 1.2rem 0.05rem;
1188
+ width: calc(100% - 0.1rem);
1189
+ }
1190
+
1191
+ .sites-grid {
1192
+ gap: 0.4rem;
1193
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1194
+ }
1195
+
1196
+ .site-card {
1197
+ padding: 0.6rem 0.4rem;
1198
+ gap: 0.45rem;
1199
+ }
1200
+
1201
+ .site-card-icon {
1202
+ width: 1.9rem;
1203
+ height: 1.9rem;
1204
+ }
1205
+
1206
+ .site-card .site-icon {
1207
+ font-size: 1.2rem;
1208
+ }
1209
+
1210
+ .site-card .favicon-icon,
1211
+ .site-card .icon-placeholder,
1212
+ .site-card .icon-fallback,
1213
+ .site-card .icon-container {
1214
+ width: 1.25rem;
1215
+ height: 1.25rem;
1216
+ }
1217
+
1218
+ .site-card .icon-placeholder,
1219
+ .site-card .icon-fallback {
1220
+ line-height: 1.25rem;
1221
+ font-size: 1.1rem;
1222
+ }
1223
+
1224
+ .site-card h3 {
1225
+ font-size: 1rem;
1226
+ margin-bottom: 0.25rem;
1227
+ white-space: nowrap;
1228
+ overflow: hidden;
1229
+ text-overflow: ellipsis;
1230
+ max-width: 100%;
1231
+ }
1232
+
1233
+ .site-card p {
1234
+ font-size: 0.9rem;
1235
+ line-height: 1.15;
1236
+ white-space: nowrap;
1237
+ overflow: hidden;
1238
+ text-overflow: ellipsis;
1239
+ }
1240
+ }
1241
+
1242
+ /* 搜索结果页面 */
1243
+ #search-results {
1244
+ position: relative;
1245
+ width: 100%;
1246
+ display: none;
1247
+ flex-direction: column;
1248
+ align-items: center;
1249
+ /* 保持在搜索框之下,避免滚动时覆盖 sticky 的搜索容器 */
1250
+ z-index: 1;
1251
+ transform: none !important;
1252
+ /* 确保没有变换 */
1253
+ min-height: 400px;
1254
+ /* 确保最小高度,防止内容过少时的布局跳动 */
1255
+ }
1256
+
1257
+ #search-results.active {
1258
+ display: flex;
1259
+ animation: fadeIn 0.3s ease-out forwards;
1260
+ }
1261
+
1262
+ /* 搜索结果区域 */
1263
+ .search-section {
1264
+ width: 100%;
1265
+ max-width: var(--page-max-width);
1266
+ margin: 0 auto 2.5rem auto;
1267
+ position: relative;
1268
+ z-index: 1;
1269
+ transform: none !important;
1270
+ opacity: 1 !important;
1271
+ }
1272
+
1273
+ /* 确保搜索结果中的网格有正确的间距 */
1274
+ .search-section .sites-grid {
1275
+ margin-top: 1rem;
1276
+ }
1277
+
1278
+ /* 搜索结果页:按来源页面复用对应网格规则(方案 2:复用原卡片 DOM) */
1279
+ #search-results [data-section='projects'] .sites-grid {
1280
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
1281
+ gap: 24px;
1282
+ }
1283
+
1284
+ #search-results [data-section='articles'] .sites-grid {
1285
+ grid-template-columns: repeat(4, minmax(0, 1fr));
1286
+ }
1287
+
1288
+ @media (max-width: 1024px) {
1289
+ #search-results [data-section='articles'] .sites-grid {
1290
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
1291
+ }
1292
+ }
1293
+
1294
+ @media (max-width: 480px) {
1295
+ #search-results [data-section='articles'] .sites-grid {
1296
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1297
+ gap: 0.5rem;
1298
+ }
1299
+ }
1300
+
1301
+ /* 确保搜索结果中的卡片样式一致 */
1302
+ .search-section .site-card {
1303
+ max-width: 100%;
1304
+ }
1305
+
1306
+ /* 加载中动画 */
1307
+ .page {
1308
+ opacity: 0;
1309
+ transition: opacity 0.3s ease;
1310
+ }
1311
+
1312
+ .page.active {
1313
+ opacity: 1;
1314
+ }
1315
+
1316
+ /* 模态框动画 */
1317
+ @keyframes modalFadeIn {
1318
+ from {
1319
+ opacity: 0;
1320
+ }
1321
+
1322
+ to {
1323
+ opacity: 1;
1324
+ }
1325
+ }
1326
+
1327
+ @keyframes modalContentShow {
1328
+ from {
1329
+ opacity: 0;
1330
+ transform: scale(0.9);
1331
+ }
1332
+
1333
+ to {
1334
+ opacity: 1;
1335
+ transform: scale(1);
1336
+ }
1337
+ }
1338
+
1339
+ .sites-grid {
1340
+ transition: gap 0.3s ease;
1341
+ }
1342
+
1343
+ /* 侧边栏底部:社交图标(位于 sidebar-footer 上方) */
1344
+ .sidebar-social {
1345
+ grid-area: social;
1346
+ padding: 0.2rem 1.2rem 0.8rem;
1347
+ display: flex;
1348
+ justify-content: center;
1349
+ flex-wrap: wrap;
1350
+ gap: 0.9rem;
1351
+ }
1352
+
1353
+ .social-icon {
1354
+ display: inline-flex;
1355
+ align-items: center;
1356
+ justify-content: center;
1357
+ padding: 0.35rem;
1358
+ border-radius: var(--radius-full);
1359
+ color: var(--nav-item-color);
1360
+ text-decoration: none;
1361
+ transition:
1362
+ color var(--transition-fast),
1363
+ transform var(--transition-fast);
1364
+ }
1365
+
1366
+ .social-icon:hover {
1367
+ color: var(--accent-color);
1368
+ transform: translateY(-1px);
1369
+ }
1370
+
1371
+ .social-icon:active {
1372
+ transform: translateY(0);
1373
+ }
1374
+
1375
+ .social-icon:focus-visible {
1376
+ outline: 2px solid rgba(118, 148, 185, 0.35);
1377
+ outline-offset: 2px;
1378
+ }
1379
+
1380
+ .social-icon i {
1381
+ width: auto;
1382
+ font-size: 1.2rem;
1383
+ }
1384
+
1385
+ /* 侧边栏底部:版权信息 */
1386
+ .sidebar-footer {
1387
+ grid-area: footer;
1388
+ padding: 1rem 1.2rem;
1389
+ padding-bottom: calc(1rem + env(safe-area-inset-bottom));
1390
+ text-align: center;
1391
+ color: var(--text-muted);
1392
+ font-size: 0.85rem;
1393
+ border-top: 1px solid var(--border-color);
1394
+ background-color: var(--sidebar-bg);
1395
+ /* 使用变量 */
1396
+ transition:
1397
+ background-color 0.3s ease,
1398
+ color 0.3s ease,
1399
+ opacity 0.3s ease;
1400
+ }
1401
+
1402
+ .sidebar-footer .copyright {
1403
+ margin: 0;
1404
+ }
1405
+
1406
+ .sidebar.collapsed .sidebar-social {
1407
+ padding: 0.2rem 0.5rem 0.8rem;
1408
+ flex-direction: column;
1409
+ align-items: center;
1410
+ gap: 0.6rem;
1411
+ }
1412
+
1413
+ .copyright a {
1414
+ color: var(--accent-color);
1415
+ text-decoration: none;
1416
+ transition: all 0.3s ease;
1417
+ }
1418
+
1419
+ .copyright a:hover {
1420
+ color: var(--accent-hover);
1421
+ text-decoration: underline;
1422
+ }
1423
+
1424
+ /* 导航项包装器 - 包含导航项和子菜单 */
1425
+ .nav-item-wrapper {
1426
+ position: relative;
1427
+ display: flex;
1428
+ flex-direction: column;
1429
+ width: 100%;
1430
+ }
1431
+
1432
+ /* 子菜单容器 */
1433
+ .submenu {
1434
+ max-height: 0;
1435
+ overflow: hidden;
1436
+ transition: max-height 0.3s ease;
1437
+ margin-left: 1.5rem;
1438
+ opacity: 0;
1439
+ visibility: hidden;
1440
+ }
1441
+
1442
+ /* 子菜单展开状态 */
1443
+ .nav-item-wrapper.expanded .submenu {
1444
+ max-height: none;
1445
+ overflow: visible;
1446
+ opacity: 1;
1447
+ visibility: visible;
1448
+ }
assets/styles/_modal.css ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Modal & Forms */
2
+
3
+ /* 模态框样式 */
4
+ .modal {
5
+ position: fixed;
6
+ top: 0;
7
+ left: 0;
8
+ width: 100%;
9
+ height: 100%;
10
+ background-color: rgba(0, 0, 0, 0.6);
11
+ display: none;
12
+ justify-content: center;
13
+ align-items: center;
14
+ z-index: 1000;
15
+ }
16
+
17
+ .modal.active {
18
+ display: flex;
19
+ animation: modalFadeIn 0.3s ease-out forwards;
20
+ }
21
+
22
+ .modal-content {
23
+ background-color: var(--sidebar-bg);
24
+ border-radius: var(--radius-xl);
25
+ padding: var(--spacing-xl);
26
+ width: 90%;
27
+ max-width: 520px;
28
+ position: relative;
29
+ box-shadow: 0 8px 32px var(--shadow-color);
30
+ transform: scale(0.95);
31
+ opacity: 0;
32
+ animation: modalContentShow 0.3s ease-out forwards;
33
+ transition: background-color 0.3s ease;
34
+ }
35
+
36
+ .modal-header {
37
+ display: flex;
38
+ justify-content: space-between;
39
+ align-items: center;
40
+ margin-bottom: 2rem;
41
+ }
42
+
43
+ .modal-header h3 {
44
+ color: var(--text-bright);
45
+ font-size: 1.3rem;
46
+ letter-spacing: 0.3px;
47
+ font-weight: 500;
48
+ transition: color 0.3s ease;
49
+ }
50
+
51
+ .close-modal {
52
+ background: none;
53
+ border: none;
54
+ color: var(--text-muted);
55
+ cursor: pointer;
56
+ font-size: 1.5rem;
57
+ padding: var(--spacing-sm);
58
+ transition: all var(--transition-normal);
59
+ border-radius: var(--radius-md);
60
+ }
61
+
62
+ .close-modal:hover {
63
+ color: var(--text-bright);
64
+ background-color: var(--secondary-bg);
65
+ }
66
+
67
+ /* 表单样式 */
68
+ .site-form {
69
+ display: flex;
70
+ flex-direction: column;
71
+ gap: 1.2rem;
72
+ }
73
+
74
+ .form-group {
75
+ display: flex;
76
+ flex-direction: column;
77
+ gap: 0.6rem;
78
+ }
79
+
80
+ .form-group label {
81
+ color: var(--text-muted);
82
+ font-size: 0.95rem;
83
+ letter-spacing: 0.2px;
84
+ transition: color 0.3s ease;
85
+ }
86
+
87
+ .form-group input,
88
+ .form-group select {
89
+ background-color: var(--secondary-bg);
90
+ border: 1px solid transparent;
91
+ border-radius: var(--radius-md);
92
+ padding: var(--spacing-md) var(--spacing-lg);
93
+ color: var(--text-color);
94
+ font-size: 1rem;
95
+ transition: all var(--transition-normal);
96
+ box-shadow: 0 2px 6px var(--shadow-color);
97
+ }
98
+
99
+ .form-group input:focus,
100
+ .form-group select:focus {
101
+ outline: none;
102
+ background-color: var(--secondary-bg);
103
+ border-color: var(--accent-color);
104
+ box-shadow: 0 2px 8px rgba(118, 148, 185, 0.15);
105
+ }
106
+
107
+ .form-group input::placeholder {
108
+ color: var(--text-muted);
109
+ }
110
+
111
+ .form-actions {
112
+ display: flex;
113
+ justify-content: flex-end;
114
+ gap: 1rem;
115
+ margin-top: 2rem;
116
+ }
117
+
118
+ .btn {
119
+ padding: var(--spacing-md) var(--spacing-xl);
120
+ border: none;
121
+ border-radius: var(--radius-md);
122
+ cursor: pointer;
123
+ font-size: 1rem;
124
+ font-weight: 500;
125
+ letter-spacing: 0.3px;
126
+ transition: all var(--transition-normal);
127
+ transition-timing-function: var(--transition-bounce);
128
+ box-shadow: 0 2px 6px var(--shadow-color);
129
+ }
130
+
131
+ .btn-primary {
132
+ background-color: var(--accent-color);
133
+ color: var(--white-color);
134
+ }
135
+
136
+ .btn-primary:hover {
137
+ background-color: var(--accent-hover);
138
+ transform: translateY(-2px);
139
+ box-shadow: 0 4px 12px rgba(118, 148, 185, 0.2);
140
+ }
141
+
142
+ .btn-secondary {
143
+ background-color: var(--secondary-bg);
144
+ color: var(--text-muted);
145
+ }
146
+
147
+ .btn-secondary:hover {
148
+ background-color: var(--secondary-bg);
149
+ color: var(--text-bright);
150
+ transform: translateY(-2px);
151
+ box-shadow: 0 4px 12px var(--shadow-color);
152
+ }
assets/styles/_search.css ADDED
@@ -0,0 +1,306 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ============================================
2
+ Search Box Component
3
+ ============================================ */
4
+
5
+ /* 搜索框容器 - 固定在顶部 */
6
+ .search-container {
7
+ width: 100%;
8
+ display: flex;
9
+ justify-content: center;
10
+ padding: 0 2rem;
11
+ margin-bottom: 1rem;
12
+ position: sticky;
13
+ top: 0;
14
+ /* 搜索框必须始终位于页面内容之上,避免搜索结果卡片滚动时遮挡 */
15
+ z-index: 200;
16
+ }
17
+
18
+ /* 分类切换按钮 */
19
+ .category-toggle {
20
+ position: fixed;
21
+ bottom: 5rem;
22
+ right: var(--spacing-xl);
23
+ width: 2.5rem;
24
+ height: 2.5rem;
25
+ border-radius: var(--radius-lg);
26
+ background: rgba(var(--card-bg-rgb), 0.65);
27
+ border: 1px solid var(--border-color);
28
+ backdrop-filter: blur(12px);
29
+ -webkit-backdrop-filter: blur(12px);
30
+ color: var(--text-color);
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: center;
34
+ cursor: pointer;
35
+ transition: all var(--transition-normal);
36
+ transition-timing-function: var(--transition-bounce);
37
+ z-index: 100;
38
+ box-shadow: 0 4px 16px var(--shadow-color);
39
+ }
40
+
41
+ .category-toggle:hover {
42
+ transform: translateY(-2px);
43
+ background: rgba(var(--card-bg-rgb), 0.75);
44
+ box-shadow: 0 6px 20px var(--shadow-color);
45
+ color: var(--accent-color);
46
+ }
47
+
48
+ .category-toggle:active {
49
+ transform: translateY(0);
50
+ box-shadow: 0 2px 8px var(--shadow-color);
51
+ }
52
+
53
+ .category-toggle i {
54
+ font-size: 18px;
55
+ }
56
+
57
+ /* 搜索框 */
58
+ .search-box {
59
+ position: relative;
60
+ width: 100%;
61
+ max-width: 600px;
62
+ display: flex;
63
+ align-items: stretch;
64
+ --search-status-right: 0.9rem;
65
+ --search-hint-right: 1.6rem;
66
+ background: rgba(var(--card-bg-rgb), 0.65);
67
+ border: 1px solid var(--border-color);
68
+ backdrop-filter: blur(12px);
69
+ -webkit-backdrop-filter: blur(12px);
70
+ border-radius: var(--radius-lg);
71
+ box-shadow: 0 4px 16px var(--shadow-color);
72
+ transition:
73
+ background var(--transition-normal),
74
+ border-color var(--transition-normal),
75
+ box-shadow var(--transition-normal);
76
+ }
77
+
78
+ .search-box:focus-within {
79
+ border-color: rgba(var(--accent-rgb), 0.55);
80
+ box-shadow:
81
+ 0 0 0 1px rgba(var(--accent-rgb), 0.28),
82
+ 0 0 18px rgba(var(--accent-rgb), 0.18),
83
+ 0 6px 24px var(--shadow-color);
84
+ }
85
+
86
+ .search-box::after {
87
+ content: '';
88
+ position: absolute;
89
+ right: var(--search-status-right);
90
+ top: 50%;
91
+ transform: translateY(-50%);
92
+ width: 6px;
93
+ height: 6px;
94
+ border-radius: 50%;
95
+ opacity: 0;
96
+ transition: all var(--transition-normal);
97
+ pointer-events: none;
98
+ }
99
+
100
+ .search-box.has-results::after {
101
+ background-color: var(--success-color);
102
+ opacity: 1;
103
+ }
104
+
105
+ .search-box.no-results::after {
106
+ background-color: var(--error-color);
107
+ opacity: 1;
108
+ }
109
+
110
+ .search-box input {
111
+ flex: 1;
112
+ min-width: 0;
113
+ width: 100%;
114
+ padding: var(--spacing-md) calc(var(--spacing-lg) + 4.8rem) var(--spacing-md) var(--spacing-md);
115
+ border: none;
116
+ border-radius: 0 var(--radius-lg) var(--radius-lg) 0;
117
+ background-color: transparent;
118
+ color: var(--text-color);
119
+ font-family: inherit;
120
+ font-weight: inherit;
121
+ font-size: 1rem;
122
+ transition:
123
+ background-color var(--transition-normal),
124
+ color var(--transition-normal);
125
+ box-shadow: none;
126
+ }
127
+
128
+ .search-box input:focus {
129
+ outline: none;
130
+ background-color: rgba(var(--card-bg-rgb), 0.25);
131
+ box-shadow: none;
132
+ }
133
+
134
+ .search-box input::placeholder {
135
+ color: var(--text-muted);
136
+ font-family: inherit;
137
+ font-weight: inherit;
138
+ }
139
+
140
+ .search-shortcut-hint {
141
+ position: absolute;
142
+ top: 50%;
143
+ right: var(--search-hint-right);
144
+ transform: translateY(-50%);
145
+ padding: 0.1rem 0.4rem;
146
+ border: 1px solid var(--border-color);
147
+ border-radius: var(--radius-md);
148
+ background: rgba(var(--card-bg-rgb), 0.25);
149
+ font-size: 0.78rem;
150
+ line-height: 1.2;
151
+ color: var(--text-muted);
152
+ opacity: 0.65;
153
+ pointer-events: none;
154
+ user-select: none;
155
+ }
156
+
157
+ .search-box:focus-within .search-shortcut-hint {
158
+ opacity: 0.85;
159
+ }
160
+
161
+ /* 搜索引擎前缀按钮(方案B:输入框前缀一体化) */
162
+ .search-engine-button {
163
+ display: flex;
164
+ align-items: center;
165
+ gap: 0.5rem;
166
+ padding: 0 0.75rem;
167
+ width: 120px;
168
+ flex: 0 0 120px;
169
+ border: none;
170
+ border-right: 1px solid var(--border-color);
171
+ border-radius: var(--radius-lg) 0 0 var(--radius-lg);
172
+ background: transparent;
173
+ color: var(--text-muted);
174
+ cursor: pointer;
175
+ font: inherit;
176
+ transition:
177
+ background var(--transition-normal),
178
+ color var(--transition-normal),
179
+ transform var(--transition-normal);
180
+ }
181
+
182
+ .search-engine-button:hover {
183
+ background: rgba(var(--card-bg-rgb), 0.25);
184
+ }
185
+
186
+ .search-engine-button:focus-visible {
187
+ outline: 2px solid var(--accent-color);
188
+ outline-offset: 2px;
189
+ }
190
+
191
+ .search-box:focus-within .search-engine-button {
192
+ color: var(--accent-color);
193
+ }
194
+
195
+ .search-engine-icon {
196
+ display: grid;
197
+ place-items: center;
198
+ height: 1.2em;
199
+ width: 1.2em;
200
+ min-width: 1.2em;
201
+ font-size: 1.25rem;
202
+ line-height: 1;
203
+ text-align: center;
204
+ flex: 0 0 1.2em;
205
+ }
206
+
207
+ .search-engine-icon.search-engine-icon-svg {
208
+ font-size: 1.25rem;
209
+ }
210
+
211
+ .search-engine-icon.search-engine-icon-svg svg {
212
+ width: 100%;
213
+ height: 100%;
214
+ display: block;
215
+ }
216
+
217
+ .search-engine-label {
218
+ flex: 1;
219
+ min-width: 0;
220
+ white-space: nowrap;
221
+ overflow: hidden;
222
+ text-overflow: ellipsis;
223
+ font-size: 0.95rem;
224
+ }
225
+
226
+ .search-box.dropdown-open .search-engine-button {
227
+ background: rgba(var(--card-bg-rgb), 0.25);
228
+ }
229
+
230
+ /* 搜索引擎下拉菜单 */
231
+ .search-engine-dropdown {
232
+ position: absolute;
233
+ top: calc(100% + 6px);
234
+ left: 0;
235
+ background: rgba(var(--card-bg-rgb), 0.9);
236
+ backdrop-filter: blur(12px);
237
+ -webkit-backdrop-filter: blur(12px);
238
+ border-radius: var(--radius-md);
239
+ box-shadow: 0 4px 15px var(--shadow-color);
240
+ display: none;
241
+ z-index: 100;
242
+ padding: 0.35rem;
243
+ border: 1px solid var(--border-color);
244
+ min-width: 190px;
245
+ flex-direction: column;
246
+ gap: 0.25rem;
247
+ }
248
+
249
+ .search-engine-dropdown.active {
250
+ display: flex;
251
+ animation: fadeIn 0.2s ease-out forwards;
252
+ }
253
+
254
+ .search-engine-option {
255
+ display: flex;
256
+ align-items: center;
257
+ justify-content: flex-start;
258
+ gap: 0.6rem;
259
+ width: 100%;
260
+ height: 40px;
261
+ padding: 0 0.75rem;
262
+ border: none;
263
+ border-radius: var(--radius-md);
264
+ cursor: pointer;
265
+ transition: all 0.2s ease;
266
+ background: transparent;
267
+ color: var(--text-color);
268
+ font: inherit;
269
+ }
270
+
271
+ .search-engine-option:hover {
272
+ background: rgba(var(--card-bg-rgb), 0.22);
273
+ }
274
+
275
+ .search-engine-option:focus-visible {
276
+ outline: 2px solid var(--accent-color);
277
+ outline-offset: 2px;
278
+ }
279
+
280
+ .search-engine-option.active {
281
+ background-color: var(--secondary-bg);
282
+ color: var(--text-bright);
283
+ }
284
+
285
+ .search-engine-option i {
286
+ display: grid;
287
+ place-items: center;
288
+ position: static;
289
+ transform: none;
290
+ font-size: 1.25rem;
291
+ width: 1.35em;
292
+ height: 1.35em;
293
+ line-height: 1;
294
+ text-align: center;
295
+ flex: 0 0 1.35em;
296
+ }
297
+
298
+ .search-engine-option i.search-engine-option-svg svg {
299
+ width: 100%;
300
+ height: 100%;
301
+ display: block;
302
+ }
303
+
304
+ .search-engine-option-label {
305
+ font-size: 0.95rem;
306
+ }
assets/styles/_sidebar.css ADDED
@@ -0,0 +1,633 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ============================================
2
+ Sidebar Component
3
+ ============================================ */
4
+
5
+ /* 侧边栏样式 */
6
+ .sidebar {
7
+ width: var(--sidebar-width);
8
+ background-color: var(--sidebar-bg);
9
+ position: fixed;
10
+ top: 0;
11
+ left: 0;
12
+ bottom: 0;
13
+ box-shadow: 2px 0 10px var(--shadow-color);
14
+ z-index: 100;
15
+ -webkit-backface-visibility: hidden;
16
+ backface-visibility: hidden;
17
+ transform: translateZ(0);
18
+ height: var(--app-height, 100vh);
19
+ display: grid;
20
+ grid-template-rows: auto 1fr auto auto;
21
+ grid-template-areas:
22
+ 'header'
23
+ 'content'
24
+ 'social'
25
+ 'footer';
26
+ scrollbar-width: thin;
27
+ scrollbar-color: var(--scrollbar-color) transparent;
28
+ overflow-y: hidden;
29
+ transition: background-color var(--transition-normal);
30
+ }
31
+
32
+ /* 侧边栏折叠状态 */
33
+ .sidebar.collapsed {
34
+ width: var(--sidebar-collapsed-width);
35
+ overflow-x: hidden;
36
+ }
37
+
38
+ /* 优化侧边栏折叠时的Logo部分 */
39
+ .sidebar.collapsed .logo {
40
+ padding: 1.2rem 0.5rem 0.6rem;
41
+ justify-content: center;
42
+ display: flex;
43
+ align-items: center;
44
+ height: 3.75rem;
45
+ /* 确保与展开状态高度一致 */
46
+ margin-bottom: 0.8rem;
47
+ /* 收起态同样拉开与按钮的间距 */
48
+ }
49
+
50
+ /* 调整折叠侧边栏的部分元素间距 */
51
+ .sidebar.collapsed .nav-section {
52
+ gap: 2px;
53
+ }
54
+
55
+ /* 折叠状态下隐藏底部版权区域(不占位) */
56
+ .sidebar.collapsed .sidebar-footer {
57
+ padding: 0;
58
+ height: 0;
59
+ min-height: 0;
60
+ margin: 0;
61
+ overflow: hidden;
62
+ border: none;
63
+ }
64
+
65
+ /* 侧边栏头部区域 */
66
+ .sidebar .logo {
67
+ grid-area: header;
68
+ padding: 1.2rem 1.2rem 0.6rem;
69
+ /* 调整上下padding更紧凑 */
70
+ display: flex;
71
+ align-items: center;
72
+ overflow: hidden;
73
+ /* 防止内容溢出 */
74
+ position: relative;
75
+ /* 添加相对定位,作为按钮的参考 */
76
+ height: 3.75rem;
77
+ /* 固定高度 60px */
78
+ margin-bottom: 0.8rem;
79
+ /* 与下方按钮区域拉开间距 */
80
+ transition: padding 0.3s ease;
81
+ /* 添加padding过渡,避免突变 */
82
+ }
83
+
84
+ .logo-brand {
85
+ display: flex;
86
+ align-items: center;
87
+ gap: 0.6rem;
88
+ min-width: 0;
89
+ flex: 1;
90
+ padding-right: 2.2rem;
91
+ /* 预留右侧折叠按钮空间 */
92
+ }
93
+
94
+ .logo-brand h1 {
95
+ padding-left: 0;
96
+ }
97
+
98
+ .logo-image {
99
+ width: 26px;
100
+ height: 26px;
101
+ flex-shrink: 0;
102
+ }
103
+
104
+ .sidebar.collapsed .logo-image {
105
+ display: none;
106
+ }
107
+
108
+ .logo h1 {
109
+ font-size: 1.4rem;
110
+ color: var(--text-bright);
111
+ margin-bottom: 0;
112
+ padding-left: 0.5rem;
113
+ letter-spacing: 0.5px;
114
+ transition:
115
+ opacity 0.3s ease,
116
+ transform 0.3s ease;
117
+ white-space: nowrap;
118
+ overflow: hidden;
119
+ text-overflow: ellipsis;
120
+ flex: 1;
121
+ }
122
+
123
+ /* 侧边栏折叠按钮 */
124
+ .sidebar-toggle {
125
+ background: transparent;
126
+ border: none;
127
+ color: var(--accent-color);
128
+ height: 28px;
129
+ width: 28px;
130
+ border-radius: 50%;
131
+ display: flex;
132
+ align-items: center;
133
+ justify-content: center;
134
+ cursor: pointer;
135
+ transition: background 0.3s ease;
136
+ /* 只过渡背景色,移除all避免位置过渡 */
137
+ padding: 0;
138
+ flex-shrink: 0;
139
+ /* 防止按钮被压缩 */
140
+ position: absolute;
141
+ /* 在两种状态下都使用绝对定位 */
142
+ right: 1.2rem;
143
+ /* 展开状态下固定在右侧 */
144
+ top: 60%;
145
+ transform: translateY(-50%);
146
+ /* 垂直居中 */
147
+ }
148
+
149
+ .sidebar-toggle .toggle-icon {
150
+ font-size: 0.9rem;
151
+ transition: transform 0.3s ease;
152
+ display: flex;
153
+ align-items: center;
154
+ justify-content: center;
155
+ width: 100%;
156
+ height: 100%;
157
+ }
158
+
159
+ .sidebar-toggle:hover {
160
+ background: var(--secondary-bg);
161
+ }
162
+
163
+ .sidebar-toggle:active {
164
+ background: var(--secondary-bg);
165
+ }
166
+
167
+ /* 收起状态下按钮居中 */
168
+ .sidebar.collapsed .sidebar-toggle {
169
+ left: 50%;
170
+ /* 水平居中 */
171
+ right: auto;
172
+ /* 移除右侧定位 */
173
+ transform: translate(-50%, -50%);
174
+ /* 同时水平和垂直居中 */
175
+ }
176
+
177
+ .sidebar.collapsed .sidebar-toggle:hover {
178
+ background: var(--secondary-bg);
179
+ }
180
+
181
+ .sidebar.collapsed .sidebar-toggle:active {
182
+ background: var(--secondary-bg);
183
+ }
184
+
185
+ /* 侧边栏折叠状态下的按钮图标旋转180度 */
186
+ .sidebar.collapsed .toggle-icon {
187
+ transform: rotate(180deg);
188
+ }
189
+
190
+ /* 侧边栏内容区域:导航项常驻显示,分类列表在独立面板内滚动 */
191
+ .sidebar-content {
192
+ grid-area: content;
193
+ min-height: 0;
194
+ /* 允许在 CSS Grid 内正确收缩与滚动,避免把 footer 挤出可视区域 */
195
+ overflow: hidden;
196
+ /* 导航项常驻显示,分类列表在独立面板内滚动 */
197
+ padding: 0 1.2rem;
198
+ display: flex;
199
+ flex-direction: column;
200
+ gap: 0.6rem;
201
+ /* 从1rem减小到0.6rem */
202
+ }
203
+
204
+ .sidebar-content::-webkit-scrollbar {
205
+ display: none;
206
+ /* Webkit browsers(默认不滚动,保留兼容) */
207
+ }
208
+
209
+ /* 展开态:子菜单不在导航项内部展开,改在独立面板展示(避免把"页面列表"挤出首屏) */
210
+ .sidebar:not(.collapsed) .nav-item-wrapper > .submenu {
211
+ display: none;
212
+ }
213
+
214
+ /* 侧边栏:页面分类面板(容器本身可滚动,隐藏滚动条) */
215
+ .sidebar-submenu-panel {
216
+ flex: 1 1 auto;
217
+ min-height: 0;
218
+ overflow-y: auto;
219
+ padding: 0 0 0.6rem;
220
+ /* 移除顶部 padding,避免标题上方出现缝隙 */
221
+ scrollbar-width: none;
222
+ /* Firefox 隐藏滚动条 */
223
+ opacity: 0;
224
+ visibility: hidden;
225
+ pointer-events: none;
226
+ transition:
227
+ opacity 0.18s ease,
228
+ visibility 0s linear 0.18s;
229
+ }
230
+
231
+ .sidebar-submenu-panel:empty {
232
+ display: none;
233
+ }
234
+
235
+ .sidebar.submenu-panel-visible .sidebar-submenu-panel:not(:empty) {
236
+ opacity: 1;
237
+ visibility: visible;
238
+ pointer-events: auto;
239
+ transition:
240
+ opacity 0.18s ease,
241
+ visibility 0s linear 0s;
242
+ }
243
+
244
+ .sidebar-submenu-panel::-webkit-scrollbar {
245
+ display: none;
246
+ /* Webkit 浏览器隐藏滚动条 */
247
+ }
248
+
249
+ /* 面板内的 submenu 始终可见(不依赖 wrapper.expanded) */
250
+ .sidebar-submenu-panel .submenu {
251
+ max-height: none;
252
+ overflow: visible;
253
+ opacity: 1;
254
+ visibility: visible;
255
+ margin-left: 0;
256
+ transition: none;
257
+ }
258
+
259
+ /* 折叠状态下的内容区域调整 */
260
+ .sidebar.collapsed .sidebar-content {
261
+ padding: 0 0.5rem;
262
+ overflow-y: auto;
263
+ overflow-x: hidden;
264
+ scrollbar-width: none;
265
+ /* 隐藏滚动条 */
266
+ }
267
+
268
+ .sidebar.collapsed .sidebar-content::-webkit-scrollbar {
269
+ display: none;
270
+ /* 隐藏WebKit浏览器的滚动条 */
271
+ }
272
+
273
+ .sidebar.collapsed .sidebar-submenu-panel {
274
+ display: none;
275
+ }
276
+
277
+ /* 收起侧边栏时,禁止当前激活页面的目录直接显示在按钮下方 */
278
+ .sidebar.collapsed .nav-item-wrapper > .submenu {
279
+ display: none;
280
+ }
281
+
282
+ /* 子菜单标题:默认隐藏(在导航栏内折叠时) */
283
+ .submenu-header {
284
+ display: none;
285
+ font-size: 0.85rem;
286
+ font-weight: 500;
287
+ text-transform: uppercase;
288
+ color: var(--text-muted);
289
+ letter-spacing: 0.5px;
290
+ padding: 0.8rem 1rem 0.6rem;
291
+ margin-bottom: 0.2rem;
292
+ border-bottom: 1px solid var(--border-color);
293
+ transition:
294
+ color var(--transition-normal),
295
+ background-color var(--transition-normal),
296
+ border-bottom-color var(--transition-normal);
297
+ opacity: 0.8;
298
+ }
299
+
300
+ /* 当子菜单在面板中显示时:显示标题并固定在顶部 */
301
+ .sidebar-submenu-panel .submenu-header {
302
+ display: block;
303
+ position: sticky;
304
+ top: 0;
305
+ margin-top: 0;
306
+ margin-bottom: 0.2rem;
307
+ /* 覆盖基础样式的 margin-bottom */
308
+ background-color: var(--sidebar-bg);
309
+ opacity: 1;
310
+ /* 覆盖基础样式的半透明,确保背景完全不透明 */
311
+ z-index: 1;
312
+ /* 确保标题在滚动时覆盖下方内容 */
313
+ }
314
+
315
+ /* 子菜单项样式优化 */
316
+ .submenu-item {
317
+ display: flex;
318
+ align-items: center;
319
+ gap: 0.8rem;
320
+ padding: 0.6rem 1rem;
321
+ color: var(--nav-item-color);
322
+ text-decoration: none;
323
+ border-radius: var(--radius-md);
324
+ transition: all var(--transition-normal);
325
+ margin-bottom: 2px;
326
+ font-size: 0.95rem;
327
+ position: relative;
328
+ overflow: hidden;
329
+ }
330
+
331
+ .submenu-item i {
332
+ width: 1.4rem;
333
+ text-align: center;
334
+ font-size: 1rem;
335
+ opacity: 0.8;
336
+ transition: transform var(--transition-normal);
337
+ }
338
+
339
+ .submenu-item span {
340
+ flex: 1;
341
+ white-space: nowrap;
342
+ overflow: hidden;
343
+ text-overflow: ellipsis;
344
+ }
345
+
346
+ /* 悬浮状态 */
347
+ .submenu-item:hover {
348
+ background-color: var(--secondary-bg);
349
+ color: var(--text-bright);
350
+ padding-left: 1.2rem;
351
+ /* 悬浮时轻微右移增加动感 */
352
+ }
353
+
354
+ .submenu-item:hover i {
355
+ transform: scale(1.1);
356
+ color: var(--accent-color);
357
+ opacity: 1;
358
+ }
359
+
360
+ /* 激活状态 */
361
+ .submenu-item.active {
362
+ background-color: rgba(var(--accent-rgb), 0.15);
363
+ color: var(--accent-color);
364
+ font-weight: 500;
365
+ }
366
+
367
+ .submenu-item.active i {
368
+ color: var(--accent-color);
369
+ opacity: 1;
370
+ }
371
+
372
+ /* 在激活状态左侧添加指示条 */
373
+ .submenu-item.active::before {
374
+ content: '';
375
+ position: absolute;
376
+ left: 0;
377
+ top: 50%;
378
+ transform: translateY(-50%);
379
+ height: 60%;
380
+ width: 3px;
381
+ background-color: var(--accent-color);
382
+ border-radius: 0 2px 2px 0;
383
+ }
384
+
385
+ /* 折叠状态下的Logo文本 */
386
+ .sidebar.collapsed .logo h1 {
387
+ opacity: 0;
388
+ transform: translateX(-20px);
389
+ width: 0;
390
+ visibility: hidden;
391
+ /* 确保完全隐藏,防止干扰布局 */
392
+ pointer-events: none;
393
+ /* 禁用交互,避免影响布局 */
394
+ }
395
+
396
+ /* 导航区域样式 */
397
+ .nav-section {
398
+ display: flex;
399
+ flex-direction: column;
400
+ gap: 0;
401
+ /* wrapper 之间不需要 gap,由 nav-item 的 margin 控制 */
402
+ flex: 0 0 auto;
403
+ /* 不伸缩,根据内容大小 */
404
+ overflow: visible;
405
+ /* 导航项不滚动,保持常驻显示 */
406
+ }
407
+
408
+ .section-title {
409
+ font-size: 1rem;
410
+ color: var(--accent-color);
411
+ padding: 0.4rem 0.5rem;
412
+ /* 减小上下padding */
413
+ margin-bottom: 0.2rem;
414
+ /* 增大与下方按钮组的间距 */
415
+ display: flex;
416
+ align-items: center;
417
+ gap: 0.5rem;
418
+ transition: color 0.3s ease;
419
+ }
420
+
421
+ .section-title i {
422
+ font-size: 1.2rem;
423
+ }
424
+
425
+ /* 调整侧边栏折叠状态下的章节标题 */
426
+ .sidebar.collapsed .section-title {
427
+ justify-content: center;
428
+ /* 统一与展开态的垂直间距 */
429
+ padding: 0.4rem 0;
430
+ text-align: center;
431
+ margin-bottom: 0.2rem;
432
+ /* 与展开态保持一致且更大 */
433
+ }
434
+
435
+ .sidebar.collapsed .section-title i {
436
+ margin: 0 auto;
437
+ font-size: 1.2rem;
438
+ }
439
+
440
+ /* 折叠状态下的导航项布局优化 */
441
+ .sidebar.collapsed .nav-section {
442
+ gap: 0.4rem;
443
+ align-items: center;
444
+ }
445
+
446
+ .sidebar.collapsed .nav-item {
447
+ padding: 0;
448
+ justify-content: center;
449
+ width: 2.75rem;
450
+ /* 增大按钮方块尺寸 44px */
451
+ height: 2.75rem;
452
+ /* 增大按钮方块尺寸 44px */
453
+ text-align: center;
454
+ margin-left: auto;
455
+ margin-right: auto;
456
+ border-radius: var(--radius-md);
457
+ /* 略增圆角 */
458
+ display: flex;
459
+ align-items: center;
460
+ box-sizing: border-box;
461
+ }
462
+
463
+ .sidebar.collapsed .nav-item i {
464
+ font-size: 1.25rem;
465
+ width: auto;
466
+ margin: 0;
467
+ padding: 0;
468
+ }
469
+
470
+ .sidebar.collapsed .nav-item .icon-container {
471
+ margin: 0;
472
+ width: 100%;
473
+ height: 100%;
474
+ display: flex;
475
+ justify-content: center;
476
+ align-items: center;
477
+ }
478
+
479
+ /* 折叠状态下的导航项文本 */
480
+ .sidebar.collapsed .nav-item .nav-text,
481
+ .sidebar.collapsed .nav-item .external-icon {
482
+ opacity: 0;
483
+ transform: translateX(-10px);
484
+ width: 0;
485
+ display: none;
486
+ /* 完全移除,防止干扰布局 */
487
+ visibility: hidden;
488
+ }
489
+
490
+ .nav-item {
491
+ display: flex;
492
+ align-items: center;
493
+ height: 44px;
494
+ padding: 0 0.9rem;
495
+ margin-bottom: 0.4rem;
496
+ /* 导航按钮之间的间距 */
497
+ color: var(--nav-item-color);
498
+ text-decoration: none;
499
+ border-radius: var(--radius-md);
500
+ transition:
501
+ background-color var(--transition-normal),
502
+ color var(--transition-normal),
503
+ box-shadow var(--transition-normal);
504
+ position: relative;
505
+ }
506
+
507
+ .nav-item-wrapper:last-child .nav-item {
508
+ margin-bottom: 0;
509
+ /* 最后一个导航按钮不需要底部间距 */
510
+ }
511
+
512
+ .nav-item .icon-container {
513
+ width: 24px;
514
+ height: 100%;
515
+ display: flex;
516
+ justify-content: center;
517
+ align-items: center;
518
+ margin-right: var(--spacing-md);
519
+ transition: margin var(--transition-normal);
520
+ }
521
+
522
+ .nav-item .nav-text {
523
+ flex: 1;
524
+ transition:
525
+ opacity var(--transition-normal),
526
+ transform var(--transition-normal);
527
+ white-space: nowrap;
528
+ overflow: hidden;
529
+ }
530
+
531
+ .nav-item .external-icon {
532
+ font-size: 0.9rem;
533
+ opacity: 0.6;
534
+ margin-left: 0.5rem;
535
+ transition: all var(--transition-normal);
536
+ }
537
+
538
+ .nav-item:hover {
539
+ background-color: var(--secondary-bg);
540
+ color: var(--text-bright);
541
+ }
542
+
543
+ .nav-item:hover .external-icon {
544
+ opacity: 1;
545
+ transform: translateX(2px);
546
+ }
547
+
548
+ .nav-item.active {
549
+ background-color: var(--secondary-bg);
550
+ color: var(--text-bright);
551
+ }
552
+
553
+ .nav-item i {
554
+ width: 20px;
555
+ text-align: center;
556
+ }
557
+
558
+ /* 折叠状态下:底部区域不可见且不可交互 */
559
+ .sidebar.collapsed .sidebar-footer {
560
+ visibility: hidden;
561
+ pointer-events: none;
562
+ }
563
+
564
+ /* 主内容区域 - 修复滚动条问题 */
565
+ main.content {
566
+ flex: 1;
567
+ margin-left: var(--sidebar-width);
568
+ padding: 1.5rem 1rem;
569
+ background-color: var(--bg-color);
570
+ position: relative;
571
+ height: var(--app-height, 100vh);
572
+ /* 固定高度(移动端避免 100vh 问题) */
573
+ overflow-y: auto;
574
+ /* 使用auto替代scroll,只在需要时显示滚动条 */
575
+ overflow-x: hidden;
576
+ width: calc(100vw - var(--sidebar-width));
577
+ display: flex;
578
+ flex-direction: column;
579
+ align-items: center;
580
+ /* 防止"有无滚动条"导致内容横向平移(支持的浏览器会稳定预留滚动条槽位) */
581
+ scrollbar-gutter: stable;
582
+ /* 自定义滚动条颜色(Firefox) */
583
+ scrollbar-width: thin;
584
+ scrollbar-color: var(--scrollbar-color) transparent;
585
+ }
586
+
587
+ /* 自定义滚动条(Chromium / Safari) */
588
+ main.content::-webkit-scrollbar {
589
+ width: 8px;
590
+ }
591
+
592
+ main.content::-webkit-scrollbar-track {
593
+ background: transparent;
594
+ }
595
+
596
+ main.content::-webkit-scrollbar-thumb {
597
+ background-color: var(--scrollbar-color);
598
+ border-radius: var(--radius-full);
599
+ /* 用透明边框制造"内边距",更贴合卡片阴影风格 */
600
+ border: 2px solid transparent;
601
+ background-clip: content-box;
602
+ }
603
+
604
+ main.content::-webkit-scrollbar-thumb:hover {
605
+ background-color: var(--scrollbar-hover-color);
606
+ }
607
+
608
+ /* 回退:不支持 scrollbar-gutter 的浏览器,强制始终显示滚动条以避免横向抖动 */
609
+ @supports not (scrollbar-gutter: stable) {
610
+ main.content {
611
+ overflow-y: scroll;
612
+ }
613
+ }
614
+
615
+ /* 优化内容区域在侧边栏折叠状态下的边距 */
616
+ body main.content.expanded {
617
+ margin-left: var(--sidebar-collapsed-width);
618
+ width: calc(100vw - var(--sidebar-collapsed-width));
619
+ }
620
+
621
+ /* 仅在交互时启用布局相关过渡,避免首帧闪烁 */
622
+ .with-anim .sidebar {
623
+ transition:
624
+ width 0.3s ease,
625
+ background-color 0.3s ease;
626
+ }
627
+
628
+ .with-anim main.content {
629
+ transition:
630
+ background-color 0.3s ease,
631
+ margin-left 0.3s ease,
632
+ width 0.3s ease;
633
+ }
assets/styles/_variables.css ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ============================================
2
+ CSS Variables & Theme System
3
+ ============================================ */
4
+
5
+ /* 主题颜色变量 - Apple Design System */
6
+ :root {
7
+ /* 深色主题 (iOS Dark Mode Inspired) */
8
+ --bg-color: #000000;
9
+ /* Pure Black background */
10
+ --sidebar-bg: #1c1c1e;
11
+ /* System Gray 6 */
12
+ --secondary-bg: #2c2c2e;
13
+ /* System Gray 5 */
14
+
15
+ /* 卡片背景 - 仪表盘卡片 */
16
+ --card-bg-gradient-1: #1c1c1e;
17
+ --card-bg-gradient-2: #1c1c1e;
18
+
19
+ /* 站点卡片 - 提升亮度以区分层级 (Optimize: Use System Gray 5 for cards on black bg) */
20
+ --site-card-bg-gradient-1: #2c2c2e;
21
+ --site-card-bg-gradient-2: #2c2c2e;
22
+ --site-card-hover-bg: #3a3a3c;
23
+ /* System Gray 4 for hover */
24
+
25
+ /* 文字颜色 */
26
+ --text-color: #ffffff;
27
+ --text-muted: #8e8e93;
28
+ /* System Gray */
29
+ --text-bright: #ffffff;
30
+
31
+ /* 边框与阴影 */
32
+ --border-color: rgba(255, 255, 255, 0.12);
33
+ --shadow-color: rgba(0, 0, 0, 0.3);
34
+
35
+ /* 功能色 */
36
+ --highlight-bg: rgba(118, 148, 185, 0.3);
37
+ /* Restore original Highlight */
38
+ --scrollbar-color: rgba(255, 255, 255, 0.2);
39
+ --scrollbar-hover-color: rgba(255, 255, 255, 0.3);
40
+
41
+ /* 恢复原有强调色 (Restore Original Accent) */
42
+ --accent-color: #7694b9;
43
+ --accent-hover: #6684a9;
44
+ --accent-rgb: 118, 148, 185;
45
+
46
+ --nav-item-color: #98989d;
47
+ --success-color: #30d158;
48
+ --error-color: #ff453a;
49
+ --white-color: #ffffff;
50
+
51
+ /* 恢复原有渐变 (Restore Original Gradient) */
52
+ --gradient-color: linear-gradient(135deg, #7694b9 0%, #a855f7 50%, #ff6b6b 100%);
53
+ --gradient-color-simple: linear-gradient(135deg, #7694b9 0%, #a855f7 100%);
54
+
55
+ --sidebar-width: 240px;
56
+ --sidebar-collapsed-width: 60px;
57
+ --app-height: 100vh;
58
+
59
+ /* Spacing System */
60
+ --spacing-xs: 0.25rem;
61
+ --spacing-sm: 0.5rem;
62
+ --spacing-md: 1rem;
63
+ --spacing-lg: 1.5rem;
64
+ --spacing-xl: 2rem;
65
+ --spacing-2xl: 3rem;
66
+
67
+ --page-max-width: 1300px;
68
+
69
+ /* UI Tuning - Apple Style (Kept) */
70
+ --radius-sm: 6px;
71
+ --radius-md: 10px;
72
+ --radius-lg: 14px;
73
+ --radius-xl: 20px;
74
+ --radius-full: 9999px;
75
+
76
+ /* Transitions - Kept iOS Spring */
77
+ --transition-fast: 0.2s ease;
78
+ --transition-normal: 0.35s cubic-bezier(0.25, 1, 0.5, 1);
79
+ --transition-slow: 0.5s cubic-bezier(0.25, 1, 0.5, 1);
80
+ --transition-bounce: cubic-bezier(0.175, 0.885, 0.32, 1.275);
81
+
82
+ --card-bg-rgb: 28, 28, 30;
83
+ }
84
+
85
+ /* 浅色主题 - 恢复原有配色 (Restored Morandi/Warm) */
86
+ html.theme-preload,
87
+ body.light-theme {
88
+ --bg-color: #e0e0d8;
89
+ --sidebar-bg: #f0f0eb;
90
+ --secondary-bg: #e6e6e1;
91
+ /* Slightly darker than sidebar for hover state */
92
+ --card-bg-gradient-1: #f0f0eb;
93
+ --card-bg-gradient-2: #e9e9e4;
94
+ --site-card-bg-gradient-1: #ffffff;
95
+ --site-card-bg-gradient-2: #f4f5f0;
96
+ --site-card-hover-bg: linear-gradient(145deg, #fafaf8, #eef0eb);
97
+
98
+ --text-color: #333333;
99
+ --text-muted: #666666;
100
+ --text-bright: #000000;
101
+
102
+ --border-color: rgba(0, 0, 0, 0.08);
103
+ --shadow-color: rgba(0, 0, 0, 0.1);
104
+
105
+ --highlight-bg: rgba(118, 148, 185, 0.15);
106
+ --scrollbar-color: rgba(0, 0, 0, 0.1);
107
+ --scrollbar-hover-color: rgba(0, 0, 0, 0.2);
108
+
109
+ --accent-color: #7694b9;
110
+ --accent-hover: #6684a9;
111
+ --accent-rgb: 118, 148, 185;
112
+
113
+ --nav-item-color: #666666;
114
+ --success-color: #4caf50;
115
+ --error-color: #f44336;
116
+ --white-color: #ffffff;
117
+
118
+ /* Restore original gradients */
119
+ --gradient-color: linear-gradient(135deg, #7694b9 0%, #a855f7 50%, #ff6b6b 100%);
120
+ --gradient-color-simple: linear-gradient(135deg, #7694b9 0%, #a855f7 100%);
121
+
122
+ --card-bg-rgb: 240, 240, 235;
123
+ }
124
+
125
+ /* 预加载主题 - 在JS完全加载前显示正确的主题 */
126
+ html.theme-preload body {
127
+ background-color: #e0e0d8;
128
+ color: #333333;
129
+ }
130
+
131
+ /* 预加载侧边栏状态 - 在JS完全加载前显示正确的侧边栏宽度 */
132
+ html.sidebar-collapsed-preload .sidebar {
133
+ width: var(--sidebar-collapsed-width);
134
+ }
135
+
136
+ html.sidebar-collapsed-preload main.content {
137
+ margin-left: var(--sidebar-collapsed-width);
138
+ }
139
+
140
+ /* 控制页面预加载状态 */
141
+ html.preload .layout {
142
+ opacity: 0;
143
+ }
144
+
145
+ html.preload * {
146
+ transition: none !important;
147
+ }
bookmarks/.gitkeep ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # 此文件用于保持bookmarks目录在Git仓库中存在
2
+ # 即使目录为空,也不会被删除
bookmarks/README.md ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MeNav 书签目录
2
+
3
+ ## 目录
4
+
5
+ - [目录概述](#目录概述)
6
+ - [书签导入功能](#书签导入功能)
7
+ - [配置加载优先级(完全替换)](#配置加载优先级完全替换)
8
+ - [MarksVault 扩展集成](#marksvault-扩展集成)
9
+ - [书签导入流程](#书签导入流程)
10
+ - [确定性输出](#确定性输出)
11
+ - [支持的浏览器](#支持的浏览器)
12
+ - [书签格式要求](#书签格式要求)
13
+ - [文件处理机制](#文件处理机制)
14
+
15
+ ## 目录概述
16
+
17
+ `bookmarks` 目录是 MeNav 项目中用于存放浏览器导出的书签文件的专用目录。该目录与书签导入功能直接关联,用于自动将浏览器书签转换为 MeNav 配置文件,从而快速生成个人导航站点。
18
+
19
+ ## 书签导入功能
20
+
21
+ 书签导入功能允许用户:
22
+
23
+ - 从浏览器导出书签为 HTML 文件
24
+ - 将书签文件放入此目录
25
+ - 通过自动处理将书签转换为网站配置
26
+ - 无需手动编辑即可批量导入网站链接
27
+
28
+ 这一功能极大简化了网站内容的初始设置过程,特别适合需要迁移大量书签的用户。
29
+
30
+ ## 配置加载优先级(完全替换)
31
+
32
+ 书签页配置同样遵循项目的“完全替换”策略:系统只会选择一套配置目录加载,不会把 `user` 与 `_default` 混合合并。
33
+
34
+ - 若存在 `config/user/`:书签页配置应位于 `config/user/pages/bookmarks.yml`(通常由导入脚本生成)
35
+ - 否则:使用 `config/_default/pages/bookmarks.yml`(默认示例)
36
+
37
+ > 提示:一旦创建 `config/user/`,`config/_default/` 会被完全忽略,因此不要指望从默认配置“兜底补齐缺失项”。
38
+
39
+ ## MarksVault 扩展集成
40
+
41
+ [MarksVault](https://github.com/rbetree/MarksVault) 浏览器扩展可与 MeNav 集成,实现书签自动同步:
42
+
43
+ - **一键推送书签**:扩展将书签 HTML 文件推送到仓库的 `bookmarks/` 目录
44
+ - **自动化处理**:GitHub Actions 检测到书签文件后自动运行导入脚本生成配置
45
+ - **自动清理**:处理完成后会删除已导入的 HTML 文件,避免重复处理
46
+
47
+ ## 书签导入流程
48
+
49
+ 完整的书签导入流程如下:
50
+
51
+ 1. 在浏览器中导出书签为 HTML 文件
52
+ 2. 将导出的书签文件放入 `bookmarks` 目录
53
+ 3. 运行书签处理工具:
54
+ ```bash
55
+ npm run import-bookmarks
56
+ ```
57
+ - 若 `config/user/` 不存在,导入脚本会先从 `config/_default/` 初始化一份用户配置(因为配置采用“完全替换”策略,需要完整配置才能正常生成站点)。
58
+ (可选)若希望生成结果保持确定性(便于版本管理,减少时间戳导致的无意义 diff):
59
+ ```bash
60
+ MENAV_BOOKMARKS_DETERMINISTIC=1 npm run import-bookmarks
61
+ ```
62
+ 4. 系统自动解析书签文件内容
63
+ 5. 根据书签文件夹结构生成分类
64
+ 6. 生成配置文件保存到 `config/user/pages/bookmarks.yml`
65
+ 7. 构建网站应用新配置:
66
+ ```bash
67
+ npm run build
68
+ ```
69
+
70
+ > **重要说明**:在本地开发中,`npm run dev` 命令**不会**自动处理书签文件。您必须先手动运行 `npm run import-bookmarks` 命令处理书签,然后再运行 `npm run dev` 查看效果。这与 GitHub Actions 中的自动处理流程不同,请务必注意。
71
+
72
+ ## 确定性输出
73
+
74
+ 默认情况下,导入脚本会在生成的 `bookmarks.yml` 顶部写入时间戳注释,导致每次导入都会产生 diff。
75
+
76
+ 若你希望生成结果尽量稳定(只有书签内容变化才产生 diff),可使用环境变量开启确定性输出:
77
+
78
+ ```bash
79
+ MENAV_BOOKMARKS_DETERMINISTIC=1 npm run import-bookmarks
80
+ ```
81
+
82
+ ## 支持的浏览器
83
+
84
+ MeNav 书签导入功能支持从以下浏览器导出的书签文件:
85
+
86
+ - **Chrome** - 通过书签管理器导出
87
+ - **Firefox** - 通过书签库导出
88
+ - **Edge** - 通过收藏夹导出
89
+ - **Safari** - 通过书签菜单导出
90
+ - 其他支持标准 HTML 书签格式的浏览器
91
+
92
+ ## 书签格式要求
93
+
94
+ 导入的书签文件需满足以下要求:
95
+
96
+ - 文件格式:HTML(标准网络书签格式)
97
+ - 文件编码:UTF-8
98
+ - 文件结构:包含 `<DL>`、`<DT>` 和 `<A>` 标签的标准书签结构
99
+ - 文件大小:建议不超过 5MB(约数千个书签)
100
+ - 支持处理位于文件夹内的书签
101
+ - 支持处理不在任何文件夹内的根路径书签(自动归入"根目录书签"分类)
102
+ - 空文件夹会被忽略
103
+
104
+ ## 文件处理机制
105
+
106
+ 书签处理器 (`src/bookmark-processor.js`) 对书签文件进行以下处理:
107
+
108
+ 1. **解析文件结构**:
109
+ - 读取书签 HTML 文件
110
+ - 解析 DOM 结构获取书签层次
111
+ - 提取文件夹和链接信息
112
+
113
+ 2. **分类提取**:
114
+ - 将书签文件夹转换为网站分类
115
+ - 提取链接URL、标题和添加日期
116
+
117
+ 3. **根路径书签处理**:
118
+ - **自动分类**:根路径书签会被自动归入名为"根目录书签"的特殊分类
119
+ - **显示位置**:该分类始终位于所有其他分类之前(第一位)
120
+ - **图标标识**:使用星标图标(fas fa-star)
121
+ - **条件生成**:只有存在根路径书签��才会创建该分类
122
+ - **自动映射**:根路径书签同样支持基于URL的自动图标映射功能
123
+
124
+ 4. **图标分配**:
125
+ - 根据URL自动匹配合适的 Font Awesome 图标
126
+ - 为每个链接和分类分配图标
127
+ - 当 `icons.mode: favicon` 时,页面通常会优先显示站点 favicon;配置里的 `icon` 主要用于回退显示或在 `icons.mode: manual` 时使用(详见 `config/README.md`)
128
+
129
+ 5. **配置生成**:
130
+ - 创建符合 MeNav 配置格式的 YAML 文件
131
+ - 按层级组织分类和链接
132
+ - 应用自动生成的元数据
133
+
134
+ 将书签放入此目录后,您可以立即利用 MeNav 的书签处理功能,快速将书签转化为个性化导航站点。
config/README.md ADDED
@@ -0,0 +1,382 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MeNav 配置目录
2
+
3
+ ## 目录
4
+
5
+ - [目录概述](#目录概述)
6
+ - [配置目录结构](#配置目录结构)
7
+ - [配置加载机制](#配置加载机制)
8
+ - [推荐用法](#推荐用法)
9
+ - [模块化配置文件](#模块化配置文件)
10
+ - [网站基础配置](#网站基础配置)
11
+ - [页面配置](#页面配置)
12
+ - [配置详解](#配置详解)
13
+ - [site.yml 常用字段](#siteyml-常用字段)
14
+ - [pages/ 页面配置](#pages-页面配置)
15
+ - [多层级嵌套配置(2-4层)](#多层级嵌套配置2-4层)
16
+ - [配置优先级](#配置优先级)
17
+ - [配置示例](#配置示例)
18
+ - [最佳实践](#最佳实践)
19
+
20
+ ## 目录概述
21
+
22
+ `config` 目录包含 MeNav 项目的所有配置文件,采用模块化的 YAML 格式组织。这些配置文件定义了网站的内容、结构、布局和功能,是定制个人导航站的核心。
23
+
24
+ ## 配置目录结构
25
+
26
+ 配置系统采用分层结构,清晰分离默认配置和用户配置:
27
+
28
+ ```
29
+ config/
30
+ ├── _default/ # 默认配置目录
31
+ │ ├── site.yml # 默认网站基础配置(含导航配置)
32
+ │ └── pages/ # 默认页面配置
33
+ │ ├── common.yml # 示例:默认首页(navigation 第一项)
34
+ │ ├── projects.yml # 项目页
35
+ │ ├── articles.yml # 文章页
36
+ │ └── bookmarks.yml # 书签页
37
+ └── user/ # 用户配置目录(覆盖默认配置)
38
+ ├── site.yml # 用户自定义网站配置(含导航配置)
39
+ └── pages/ # 用户自定义页面配置
40
+ ├── common.yml # 示例:与 navigation 第一项对应
41
+ └── ...
42
+ ```
43
+
44
+ ## 配置加载机制
45
+
46
+ MeNav 配置系统采用“完全替换”策略(不合并),按以下优先级选择**唯一**的一套配置目录:
47
+
48
+ 1. 若存在 `config/user/`,则只加载该目录下的配置,并**完全忽略** `config/_default/`
49
+ 2. 否则加载 `config/_default/` 作为默认配置
50
+
51
+ 也就是说:`config/user/` 一旦存在,就需要包含一套完整的配置(例如 `site.yml` 与必要的 `pages/*.yml`),系统不会把缺失部分从默认配置补齐。
52
+
53
+ ## 推荐用法
54
+
55
+ 为避免文档与配置字段长期不同步,建议按以下方式使用与维护:
56
+
57
+ 1. **首次使用**:完整复制 `config/_default/` 到 `config/user/`,再按需修改。
58
+ 2. **字段与结构的权威参考**:
59
+ - 全局配置:[`_default/site.yml`](_default/site.yml)
60
+ - 页面配置:[`_default/pages/`](_default/pages/)
61
+ 3. **多层级嵌套书签示例**:[`_default/pages/bookmarks.yml`](_default/pages/bookmarks.yml)(包含2层、3层、4层结构示例;`subgroups` 可参考下方说明或由导入脚本生成)
62
+
63
+ ## 模块化配置文件
64
+
65
+ ### 网站基础配置
66
+
67
+ `site.yml` 定义网站的基本信息和全局设置:
68
+
69
+ - 网站标题、描述和关键词
70
+ - 作者信息和版权声明
71
+ - 字体配置、图标模式等全局设置
72
+ - 全局元数据和站点参数
73
+ - 个人资料和社交媒体链接
74
+ - 导航菜单配置(侧边栏导航项、页面标题和图标、页面顺序和可见性)
75
+
76
+ > **注意**:导航配置仅支持写在 `site.yml` 的 `navigation` 字段中。
77
+
78
+ ### 页面配置
79
+
80
+ `pages/` 目录下的配置文件定义各个页面的内容:
81
+
82
+ - `common.yml`: 示例首页(本质上是普通页面;首页由 navigation 第一项决定,不要求必须叫 home)
83
+ - `projects.yml`: 项目展示配置
84
+ - `articles.yml`: 文章列表配置
85
+ - `bookmarks.yml`: 书签页面配置
86
+ - 其他自定义页面配置(可按需新增/删除;与 `site.yml -> navigation[].id` 对应)
87
+
88
+ ## 配置详解
89
+
90
+ 本章节用于补齐“怎么配才是对的”这类细节说明。为了避免示例长期过时,字段与结构的权威参考始终以默认配置为准:
91
+
92
+ - 全局配置:[`_default/site.yml`](_default/site.yml)
93
+ - 页面配置:[`_default/pages/`](_default/pages/)
94
+
95
+ ### site.yml 常用字段
96
+
97
+ `site.yml` 中字段较多,以下是常用项的解释与注意点(完整字段请以默认配置为准):
98
+
99
+ 1. **基础信息**
100
+ - `title`:站点标题
101
+ - `description`:站点描述(SEO/分享)
102
+ - `author`:作者/署名
103
+ - `favicon`、`logo_text`:站点图标与左上角 Logo 文本
104
+
105
+ 2. **图标模式(隐私相关)**
106
+ - `icons.mode: favicon | manual`
107
+ - `favicon`:会请求第三方服务(Google)获取站点 favicon,失败自动回退到 Font Awesome 图标
108
+ - `manual`:完全使用手动 Font Awesome 图标,不发起外部请求(适合内网/离线/隐私敏感场景)
109
+ - `icons.region: com | cn`(默认 `com`)
110
+ - `com`:优先使用 `gstatic.com`(国际版),失败后回退到 `gstatic.cn`(中国版)
111
+ - `cn`:优先使用 `gstatic.cn`(中国版),失败后回退到 `gstatic.com`(国际版)
112
+ - 说明:如果你在中国大陆且访问 gstatic.com 较慢,建议设置为 `cn` 以提升图标加载速度
113
+ - 站点级覆盖(可选,写在 `pages/*.yml` 的每个 `sites[]` 节点上):
114
+ - `faviconUrl`:为单个站点指定图标链接(可远程或本地相对路径;本地建议以 `assets/` 开头,构建会复制到 `dist/` 同路径),优先级最高
115
+ - `forceIconMode: favicon | manual`:强制该站点使用指定模式(不设置则跟随全局 `icons.mode`)
116
+ - 优先级:`faviconUrl` > `forceIconMode` > 全局 `icons.mode`
117
+ - 示例:
118
+ ```yml
119
+ sites:
120
+ - name: 'Ant Design'
121
+ url: 'https://ant.design/'
122
+ icon: 'fas fa-th'
123
+ forceIconMode: manual # 强制使用手动图标,绕过 favicon 默认"地球"图标
124
+ - name: 'Example'
125
+ url: 'https://example.com/'
126
+ faviconUrl: 'https://example.com/favicon.png' # 单站点自定义 favicon
127
+ ```
128
+
129
+ 3. **安全策略(链接白名单)**
130
+ - `security.allowedSchemes`:允许在页面中渲染为可点击链接的 URL scheme 白名单
131
+ - 默认仅允许:`http/https/mailto/tel` + 所有相对链接(`#`、`/`、`./`、`../`、`?` 开头)
132
+ - 其他 scheme 会被安全降级为 `#` 并输出告警;如需支持 `obsidian://`、`vscode://` 等协议,可在此显式放行
133
+
134
+ 4. **字体**
135
+ - `fonts`:单一字体配置项,用于设置全站基础字体(`body` 等)
136
+ - 支持 `source: css | google | system`(分别表示第三方 CSS、Google Fonts、系统字体)
137
+ - 可选 `fonts.preload: true`:用 `preload + onload` 的方式非阻塞加载外链字体 CSS(更利于首屏性能)
138
+ - 首页副标题(渐变发光样式)使用全站基础字体(跟随 `fonts` 配置)
139
+
140
+ 5. **主题(默认明暗模式)**
141
+ - `theme.mode: dark | light | system`
142
+ - `dark/light`:首屏默认主题;用户点击按钮切换后会写入 localStorage 并覆盖该默认值
143
+ - `system`:跟随系统 `prefers-color-scheme`;用户手动切换后同样会写入 localStorage 并停止跟随
144
+
145
+ 6. **顶部欢迎信息与社交链接**
146
+ - `profile`:首页顶部欢迎信息
147
+ - `social`:侧边栏底部社交链接
148
+ - `profile.title` / `profile.subtitle`:分别对应首页顶部主标题与副标题
149
+
150
+ 7. **导航**
151
+ - `navigation[]`:页面入口列表,`id` 需唯一,并与 `pages/<id>.yml` 对应(例如 `id: common` 对应 `pages/common.yml`)
152
+ - 默认首页由 `navigation` 数组顺序决定:**第一项即为首页(默认打开页)**,不再使用 `active` 字段
153
+ - 图标使用 Font Awesome 类名字符串(例如 `fas fa-home`、`fab fa-github`)
154
+ - 导航显示顺序与数组顺序一致,可通过调整数组顺序改变导航顺序
155
+
156
+ 8. **RSS(articles Phase 2)**
157
+ - `rss.*`:仅用于 `npm run sync-articles`(联网抓取 RSS/Atom 并写入缓存)
158
+ - `npm run build` 默认不联网;无缓存时 `articles` 页面会回退到 Phase 1 的站点入口展示
159
+ - articles 页面会按 `articles.yml` 的分类进行聚合展示:某分类下配置的来源站点,其文章会显示在该分类下
160
+ - 抓取条数默认:每个来源站点抓取最新 8 篇(可通过 `site.yml -> rss.articles.perSite` 或 `RSS_ARTICLES_PER_SITE` 调整)
161
+ - 默认配置已将 `rss.cacheDir` 设为 `dev`(仓库默认 gitignore),避免误提交缓存文件;可按需改为自定义目录
162
+ - GitHub Pages 部署工作流会在构建前自动执行 `npm run sync-articles`,并支持定时触发(默认每天 UTC 02:00;可在 `.github/workflows/deploy.yml` 调整)
163
+
164
+ 9. **GitHub(projects 热力图,可选)**
165
+ - `github.username`:你的 GitHub 用户名(用于 projects 页面标题栏右侧贡献热力图)
166
+ - `github.heatmapColor`:热力图主题色(不带 `#`,例如 `339af0`)
167
+ - `github.cacheDir`:projects 仓库元信息缓存目录(默认 `dev`,仓库默认 gitignore)
168
+ - projects 仓库统计信息(language/stars/forks)由 `npm run sync-projects` 自动抓取并写入缓存;`npm run build` 默认不联网
169
+ - GitHub Pages 部署工作流会在构建前自动执行 `npm run sync-projects`,并支持定时触发(默认每天 UTC 02:00;可在 `.github/workflows/deploy.yml` 调整)
170
+
171
+ ### pages/ 页面配置
172
+
173
+ 页面配置位于 `pages/*.yml`,每个文件对应一个页面内容,文件名与导航 `id` 对应:
174
+
175
+ - `pages/common.yml`:示例首页(通常是 `categories -> sites`)
176
+ - `pages/projects.yml` / `articles.yml`:示例页面(可按需删改)
177
+ - `pages/bookmarks.yml`:书签页(通常由导入脚本生成,也可以手动维护)
178
+
179
+ > 提示:自定义页面时,先在 `site.yml` 的 `navigation` 中增加一个 `id`,再创建同名的 `pages/<id>.yml`。
180
+ >
181
+ > 支持“可删除”:如果 `navigation` 中存在某个页面 `id`,但 `pages/<id>.yml` 不存在,构建仍会生成该页面(标题回退为导航名称、分类为空、模板默认使用通用 `page`)。
182
+ >
183
+ > 站点描述建议简洁(例如不超过 30 个字符),以保证卡片展示更美观。
184
+
185
+ #### 通用 page 页面配置(推��,用于 friends 等普通页面)
186
+
187
+ 对不需要特殊渲染的页面(例如“友链/朋友”页),建议使用通用 `page` 模板,并保持 `categories -> sites`(可选更深层级):
188
+
189
+ ```yaml
190
+ title: 示例页面
191
+ subtitle: 示例副标题
192
+ template: page
193
+
194
+ categories:
195
+ - name: 示例分类
196
+ icon: fas fa-folder
197
+ sites:
198
+ - name: 示例站点
199
+ url: https://example.com
200
+ icon: fas fa-link
201
+ description: 示例描述
202
+ ```
203
+
204
+ 兼容说明:
205
+
206
+ - 若历史配置仍使用顶层 `sites`(旧结构),系统会自动映射为一个分类容器以保持页面结构一致(当前仅对 friends/articles 提供该兼容)。
207
+
208
+ #### 内容页(template: content)
209
+
210
+ 内容页用于承载“关于 / 帮助 / 使用说明 / 更新日志 / 迁移指南 / 隐私说明”等纯文本内容。
211
+
212
+ 配置要点:
213
+
214
+ - `template: content`
215
+ - `content.file`:指向本地 Markdown 文件路径(推荐放在 `content/` 下)
216
+ - Markdown 会在**构建期**渲染为 HTML(不是运行时 fetch)
217
+ - 当前约束:
218
+ - 禁止 raw HTML(避免 XSS)
219
+ - 禁止图片(`![]()` 不会输出 `<img>`;本期不支持图片/附件)
220
+ - 链接会按 URL scheme 白名单策略处理:
221
+ - 默认允许:`http/https/mailto/tel` + 所有相对链接(`#`、`/`、`./`、`../`、`?` 开头)
222
+ - 其他 scheme 会被安全降级为 `#`(可用 `site.yml -> security.allowedSchemes` 显式放行)
223
+
224
+ 示例(以 about 页面为例):
225
+
226
+ ```yml
227
+ # config/user/pages/about.yml
228
+ title: 关于
229
+ subtitle: 项目说明
230
+ template: content
231
+
232
+ content:
233
+ file: content/about.md
234
+ ```
235
+
236
+ 对应内容文件:
237
+
238
+ ```text
239
+ content/about.md
240
+ ```
241
+
242
+ ### 多层级嵌套配置(2-4层)
243
+
244
+ 书签与分类支持 2~4 层嵌套,用于更好组织大量站点。建议直接参考默认示例:
245
+
246
+ - 多层级结构示例:[`_default/pages/bookmarks.yml`](_default/pages/bookmarks.yml)(包含2层、3层、4层结构示例)
247
+
248
+ 层级命名约定(自顶向下):
249
+
250
+ 1. `categories`:顶层分类
251
+ 2. `subcategories`:子分类
252
+ 3. `groups`:分组
253
+ 4. `subgroups`:子分组
254
+ 5. `sites`:站点(叶子节点)
255
+
256
+ 若你需要第 4 层(`subgroups`),结构示例(片段):
257
+
258
+ ```yaml
259
+ categories:
260
+ - name: 示例分类
261
+ subcategories:
262
+ - name: 示例子分类
263
+ groups:
264
+ - name: 示例分组
265
+ subgroups:
266
+ - name: 示例子分组
267
+ sites:
268
+ - name: 示例站点
269
+ url: https://example.com
270
+ ```
271
+
272
+ #### 向后兼容性
273
+
274
+ - 原有二层结构(`categories -> sites`)无需修改即可继续使用
275
+ - 系统会自动识别层级结构并匹配对应的模板/样式
276
+ - 允许在同一份配置中混用不同层级(例如某些分类是二层,某些分类是三/四层)
277
+
278
+ ## 配置优先级
279
+
280
+ MeNav 配置系统采用“完全替换”策略:只会选择一套目录加载,不会把 `user` 与 `_default` 混合合并。
281
+
282
+ - 若存在 `config/user/`:只加载 `config/user/`,并**完全忽略** `config/_default/`
283
+ - 否则:加载 `config/_default/`
284
+
285
+ 在“同一套目录”内,各文件的关系是:
286
+
287
+ - `site.yml`:站点全局配置(包含 `navigation` 等)
288
+ - `pages/*.yml`:各页面配置(文件名需与 `navigation.id` 对应)
289
+
290
+ ## 配置示例
291
+
292
+ ### 网站配置示例 (site.yml)
293
+
294
+ ```yaml
295
+ # 网站基本信息
296
+ title: '我的个人导航'
297
+ description: '个人收藏的网站导航页'
298
+ keywords: '导航,网址,书签,个人主页'
299
+
300
+ # 个人资料配置
301
+ profile:
302
+ title: '个人导航站'
303
+ subtitle: '我收藏的精选网站'
304
+
305
+ # 字体:全站基础字体
306
+ fonts:
307
+ source: css
308
+ cssUrl: 'https://fontsapi.zeoseven.com/292/main/result.css'
309
+ preload: true
310
+ family: 'LXGW WenKai'
311
+ weight: normal
312
+
313
+ # 社交媒体链接
314
+ social:
315
+ - name: 'GitHub'
316
+ url: 'https://github.com/username'
317
+ icon: 'fab fa-github'
318
+ - name: 'Twitter'
319
+ url: 'https://twitter.com/username'
320
+ icon: 'fab fa-twitter'
321
+
322
+ # 导航配置
323
+ navigation:
324
+ - name: '常用'
325
+ icon: 'fas fa-star'
326
+ id: 'common'
327
+ - name: '项目'
328
+ icon: 'fas fa-project-diagram'
329
+ id: 'projects'
330
+ - name: '文章'
331
+ icon: 'fas fa-book'
332
+ id: 'articles'
333
+ - name: '书签'
334
+ icon: 'fas fa-bookmark'
335
+ id: 'bookmarks'
336
+ ```
337
+
338
+ ### 通用页面配置示例(例如 common.yml)
339
+
340
+ ```yaml
341
+ # 页面分类配置
342
+ categories:
343
+ - name: '常用工具'
344
+ icon: 'fas fa-tools'
345
+ sites:
346
+ - name: 'Google'
347
+ url: 'https://www.google.com'
348
+ description: '全球最大的搜索引擎'
349
+ icon: 'fab fa-google'
350
+ - name: 'GitHub'
351
+ url: 'https://github.com'
352
+ description: '代码托管平台'
353
+ icon: 'fab fa-github'
354
+
355
+ - name: '学习资源'
356
+ icon: 'fas fa-graduation-cap'
357
+ sites:
358
+ - name: 'MDN Web Docs'
359
+ url: 'https://developer.mozilla.org'
360
+ description: 'Web开发技术文档'
361
+ icon: 'fab fa-firefox-browser'
362
+ ```
363
+
364
+ ## 最佳实践
365
+
366
+ 1. **目录结构**:
367
+ - 总是在 `user/` 目录下创建您的配置
368
+ - 不要直接修改 `_default/` 中的文件
369
+
370
+ 2. **文件命名**:
371
+ - 遵循现有的文件命名约定
372
+ - 自定义页面配置应使用有意义的名称
373
+
374
+ 3. **配置管理**:
375
+ - 利用模块化结构分类管理配置
376
+ - 首次使用建议先完整复制 `config/_default/` 到 `config/user/`,再按需修改
377
+ - 定期备份您的用户配置
378
+
379
+ 4. **配置验证**:
380
+ - 修改配置后运行 `npm run check`(语法检查 + 单测 + 构建)
381
+ - 需要本地预览时运行 `npm run dev`(命令入口见 [`../README.md#快速开始`](../README.md#快速开始))
382
+ - 确保 YAML 语法正确无误
config/update-instructions-20251227.md ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 更新说明(2025-12-27)
2
+
3
+ 本文档用于说明“页面模板差异化改进”相关改动中,**配置层面的新增字段、减少字段与迁移要点**。内容与 [`README.md`](../README.md) 的“2025/12/27 更新记录”保持一致。
4
+
5
+ 最后更新:2025-12-27
6
+
7
+ ---
8
+
9
+ ## 1. 新增/扩展的配置字段
10
+
11
+ ### 1.1 `site.rss.*`(articles RSS 聚合 / 缓存)
12
+
13
+ 用途:为 `articles` 页面提供 RSS/Atom 文章聚合数据,供 `npm run sync-articles` 联网抓取并写入缓存;`npm run build` 默认不联网,只读取缓存渲染。
14
+
15
+ 关键字段(默认示例见 `config/_default/site.yml`):
16
+
17
+ - `site.rss.enabled`:是否启用 RSS 抓取能力
18
+ - `site.rss.cacheDir`:缓存目录(建议 `dev/`,仓库默认 gitignore)
19
+ - `site.rss.fetch.*`:抓取参数(超时、并发、重试、重定向等)
20
+ - `site.rss.articles.*`:抓取条数与摘要长度(例如每站点最多 8 篇)
21
+
22
+ 说明:
23
+
24
+ - RSS 抓取只影响 `articles` Phase 2(文章条目只读展示),不会影响扩展对“来源站点(sites)”的写回能力(构建会保留影子写回结构)。
25
+
26
+ ---
27
+
28
+ ### 1.2 `site.github.*`(projects 仓库元信息 + 热力图)
29
+
30
+ 用途:
31
+
32
+ - projects 卡片可展示仓库元信息(language/stars/forks 等,只读),由 `npm run sync-projects` 联网抓取并写入缓存。
33
+ - projects 标题区右侧可选展示 GitHub 贡献热力图。
34
+
35
+ 关键字段(默认示例见 `config/_default/site.yml`):
36
+
37
+ - `site.github.username`:GitHub 用户名;为空则不展示热力图
38
+ - `site.github.heatmapColor`:热力图主题色(不带 `#`,如 `339af0`)
39
+ - `site.github.cacheDir`:仓库元信息缓存目录(建议 `dev/`)
40
+
41
+ 说明:
42
+
43
+ - 仓库元信息来自 GitHub API,属于“时效性数据”,不会写回到 `pages/projects.yml`。
44
+
45
+ ---
46
+
47
+ ### 1.3 `pages/<id>.yml -> template`(页面模板选择)
48
+
49
+ 用途:指定页面使用的模板(对应 `templates/pages/<template>.hbs`,不含扩展名)。
50
+
51
+ 行为规则:
52
+
53
+ - 若 `template` 缺省:优先尝试同名模板(`templates/pages/<pageId>.hbs`),不存在则回退到通用 `page` 模板。
54
+ - `bookmarks/projects/articles` 等特殊页建议显式配置 `template`,以减少误解。
55
+
56
+ ---
57
+
58
+ ## 2. 减少/不再支持的配置方式(Breaking)
59
+
60
+ ### 2.1 根目录单文件配置 `config.yml` / `config.yaml`
61
+
62
+ 当前版本不再回退读取根目录 `config.yml`/`config.yaml`。
63
+
64
+ 迁移要点:
65
+
66
+ - 使用模块化配置目录:`config/user/`(优先级最高,完全替换)或 `config/_default/`(默认示例)。
67
+ - 推荐迁移方式:复制 `config/_default/` → `config/user/`,再按需修改 `site.yml` 与 `pages/*.yml`。
68
+
69
+ ---
70
+
71
+ ### 2.2 独立 `navigation.yml`
72
+
73
+ 当前版本仅从 `site.yml -> navigation` 读取导航配置,不再读取 `navigation.yml`。
74
+
75
+ 迁移要点:
76
+
77
+ - 将原 `navigation.yml` 的数组内容移动到 `config/user/site.yml` 的 `navigation:` 字段下。
78
+
79
+ ---
80
+
81
+ ### 2.3 `pages/home.yml -> 顶层 categories` 与 `home` 子菜单特例
82
+
83
+ 当前版本不再维护“首页固定叫 `home`”的遗留逻辑(例如把 `pages/home.yml` 的分类提升到顶层 `config.categories`)。
84
+
85
+ 迁移要点:
86
+
87
+ - 不要依赖固定页面 id `home`。
88
+ - 首页始终由 `site.yml -> navigation` 的**第一项**决定;其分类内容应写在对应的 `pages/<homePageId>.yml` 中。
89
+
90
+ ---
91
+
92
+ ### 2.4 `navigation[].active` 不再生效(首页不再靠 active 指定)
93
+
94
+ 历史版本可能通过 `navigation[].active` 指定“默认打开页/首页”。
95
+
96
+ 当前版本:
97
+
98
+ - 首页/默认打开页始终由 `site.yml -> navigation` 的**第一项**决定
99
+ - `active` 字段将被忽略(即使写了也不会生效)
100
+
101
+ 迁移要点:
102
+
103
+ - 通过调整 `navigation` 数组顺序来设置首页(把希望作为首页的页面放到第一项)。
104
+
105
+ ---
106
+
107
+ ## 3. 与更新记录的对应关系(快速索引)
108
+
109
+ - 首页判定规则:`site.yml -> navigation` 第一项
110
+ - 模板体系:`pages/<id>.yml -> template`(缺省回退 `page`)
111
+ - bookmarks 更新时间:构建期注入(不需要新增配置字段)
112
+ - articles RSS:`site.rss.*` + `npm run sync-articles`
113
+ - projects 元信息/热力图:`site.github.*` + `npm run sync-projects`
114
+ - 兼容清理:移除 `config.yml/config.yaml`、`navigation.yml`、`home` 特例
config/update-instructions-20260102.md ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 更新说明(2026-01-02)
2
+
3
+ 本文档用于说明 Issue #30(外部资源/图标/嵌套交互)相关改动中,**配置层面的新增字段、行为变更与迁移要点**。
4
+
5
+ 关联 Issue:https://github.com/rbetree/menav/issues/30
6
+
7
+ 最后更新:2026-01-02
8
+
9
+ ---
10
+
11
+ ## 1. 新增/扩展的配置字段
12
+
13
+ ### 1.1 `site.yml -> icons.mode`(站点卡片图标模式 / 隐私)
14
+
15
+ 用途:控制“站点卡片图标”的全局渲染方式。
16
+
17
+ 取值:
18
+
19
+ - `favicon`:根据站点 URL 通过第三方服务加载站点 favicon(失败时回退到 Font Awesome 图标)
20
+ - `manual`:始终使用配置中的 Font Awesome 图标类名(不发起 favicon 外部请求)
21
+
22
+ 注意:
23
+
24
+ - 该配置位于 `site.yml` 的 `icons:` 节点下(默认示例见 `config/_default/site.yml`)。
25
+ - 配置目录采用“完全替换”策略:若启用 `config/user/`,需要在 `config/user/site.yml` 中设置该字段才会生效。
26
+ - 切换后需要重新生成页面(`npm run build` / `npm run dev`)才能影响生成的 HTML。
27
+
28
+ 示例:
29
+
30
+ ```yml
31
+ # config/user/site.yml
32
+ icons:
33
+ mode: manual
34
+ ```
35
+
36
+ ---
37
+
38
+ ### 1.2 `pages/*.yml -> sites[].faviconUrl`(站点级自定义图标链接)
39
+
40
+ 用途:为单个站点指定图标链接(可远程或本地相对路径),用于兜底“favicon 服务返回默认图标/网络不可达”等情况。
41
+
42
+ 说明:
43
+
44
+ - `faviconUrl` 优先级最高:一旦设置,将直接使用该图片链接渲染图标。
45
+ - 本地路径建议以 `assets/` 开头;构建时会复制到 `dist/` 同路径,便于离线/内网使用。
46
+
47
+ 示例:
48
+
49
+ ```yml
50
+ sites:
51
+ - name: '内部系统'
52
+ url: 'https://intranet.example/'
53
+ faviconUrl: 'assets/icons/intranet.png'
54
+ ```
55
+
56
+ ---
57
+
58
+ ### 1.3 `pages/*.yml -> sites[].forceIconMode`(站点级强制图标模式)
59
+
60
+ 用途:强制该站点使用指定模式(不设置则跟随全局 `icons.mode`)。
61
+
62
+ 取值:
63
+
64
+ - `favicon`:强制走 favicon(外部请求)
65
+ - `manual`:强制走手动图标(不发起 favicon 外部请求)
66
+
67
+ 优先级:
68
+
69
+ - `faviconUrl` > `forceIconMode` > 全局 `icons.mode`
70
+
71
+ 示例:
72
+
73
+ ```yml
74
+ sites:
75
+ - name: 'Ant Design'
76
+ url: 'https://ant.design/'
77
+ icon: 'fas fa-th'
78
+ forceIconMode: manual
79
+ ```
80
+
81
+ ---
82
+
83
+ ## 2. 行为变更与修复要点(无需迁移字段)
84
+
85
+ ### 2.1 `icons.mode` 全局切换修复
86
+
87
+ 修复点:此前 `site.yml` 中的 `icons` 没有被提升为顶层 `icons`,导致模板与运行时读取到的仍是默认 `favicon`。本次已修复,`site.yml -> icons.mode` 会被模板/运行时统一读取生效。
88
+
89
+ ### 2.2 favicon 双域名回退(`.com` → `.cn`)
90
+
91
+ 修复点:favicon 默认使用 `t3.gstatic.com`,失败时自动切换 `t3.gstatic.cn` 重试一次,提升国内网络可用性。
92
+
93
+ ### 2.3 多级结构站点新标签页打开一致性
94
+
95
+ 修复点:多级结构(`subcategories/groups/subgroups`)下站点默认值未递归补齐,导致 `external` 为 `undefined` 时不输出 `target="_blank"`。本次已在生成阶段递归补齐 `sites[].external` 默认 `true`(显式 `external: false` 保持同页打开)。
96
+
97
+ ---
98
+
99
+ ## 3. 迁移建议(从旧版本升级)
100
+
101
+ 1. 若使用 `config/user/`:请在 `config/user/site.yml` 中设置 `icons.mode`,然后执行 `npm run build` 重新生成页面。
102
+ 2. 若遇到某些站点 favicon 始终显示默认图标:建议对该站点配置 `forceIconMode: manual`(使用 Font Awesome)或提供 `faviconUrl` 指向可靠图片(远程或本地 `assets/`)。
config/user/pages/articles.yml ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 默认页面配置(请勿直接修改)。
2
+ # 建议复制到 config/user/pages/articles.yml 并按需调整。
3
+ title: 技术文章 # 页面标题
4
+ subtitle: RSS 聚合文章列表 # 页面副标题
5
+
6
+ # 指定使用的模板文件名,现有页面模板可见 templates/pages(不含 .hbs)
7
+ template: articles
8
+
9
+ # 当存在 RSS 缓存时,页面将优先渲染“文章条目卡片”(只读)。
10
+ # - 本处的站点列表作为“来源站点”输入(url 填站点首页)
11
+ # - 显示时会将“该分类下配置的站点”抓取到的文章聚合展示在该分类下
12
+ # 重要:url 应填写“站点首页 URL”(不是某一篇文章链接),系统会自动发现 RSS/Atom。
13
+ categories:
14
+ - name: 个人博客
15
+ icon: fas fa-rss
16
+ sites:
17
+ - name: 阮一峰的网络日志
18
+ icon: fas fa-pen
19
+ description: 技术文章与随笔
20
+ url: https://www.ruanyifeng.com/blog/
21
+ - name: Coolzr's Blog
22
+ icon: fas fa-pen
23
+ description: 偶尔会写点什么
24
+ url: https://blog.rzlnb.top/
25
+ - name: 天仙子
26
+ icon: fas fa-pen
27
+ description: tianxianzi
28
+ url: https://www.tianxianzi.me/
29
+ - name: pseudoyu
30
+ icon: fas fa-pen
31
+ description: pseudoyu
32
+ url: https://www.pseudoyu.com/
33
+ - name: 官方博客
34
+ icon: fas fa-rss
35
+ sites:
36
+ - name: GitHub Blog
37
+ icon: fab fa-github
38
+ description: GitHub 官方博客(工程/产品/安全)
39
+ url: https://github.blog/
40
+ - name: Cloudflare Blog
41
+ icon: fas fa-cloud
42
+ description: Cloudflare 工程与安全博客
43
+ url: https://blog.cloudflare.com/
config/user/pages/bookmarks.yml ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 默认页面配置(请勿直接修改)。
2
+ # 建议复制到 config/user/pages/bookmarks.yml 并按需调整。
3
+ # 说明:该页面通常由“书签导入工具”自动生成,手工修改时请保持字段结构一致。
4
+ title: 书签
5
+ subtitle: bookmarks
6
+
7
+ # 指定使用的模板文件名,现有页面模板可见 templates/pages(不含 .hbs)
8
+ # 提示:bookmarks 模板页面标题区会自动显示“内容更新:YYYY-MM-DD(git|mtime)”,无需额外配置
9
+ template: bookmarks
10
+
11
+ categories:
12
+ - name: '常用网站'
13
+ icon: 'fas fa-star'
14
+ sites:
15
+ - name: 'GitHub'
16
+ url: 'https://github.com/'
17
+ icon: 'fab fa-github'
18
+ description: '代码托管平台'
19
+ - name: 'Stack Overflow'
20
+ url: 'https://stackoverflow.com/'
21
+ icon: 'fab fa-stack-overflow'
22
+ description: '程序员问答社区'
23
+ - name: 'MDN Web Docs'
24
+ url: 'https://developer.mozilla.org/'
25
+ icon: 'fas fa-book'
26
+ description: 'Web开发文档'
27
+
28
+ - name: '社交媒体'
29
+ icon: 'fas fa-share-alt'
30
+ groups:
31
+ - name: '国际平台'
32
+ icon: 'fas fa-globe'
33
+ sites:
34
+ - name: 'Twitter'
35
+ url: 'https://twitter.com/'
36
+ icon: 'fab fa-twitter'
37
+ description: '微博客社交平台'
38
+ - name: 'LinkedIn'
39
+ url: 'https://www.linkedin.com/'
40
+ icon: 'fab fa-linkedin'
41
+ description: '职业社交网络'
42
+ - name: 'Facebook'
43
+ url: 'https://www.facebook.com/'
44
+ icon: 'fab fa-facebook'
45
+ description: '社交网络服务'
46
+ - name: '国内平台'
47
+ icon: 'fas fa-map-marker-alt'
48
+ sites:
49
+ - name: '微博'
50
+ url: 'https://weibo.com/'
51
+ icon: 'fas fa-comment'
52
+ description: '中文社交媒体平台'
53
+ - name: '知乎'
54
+ url: 'https://www.zhihu.com/'
55
+ icon: 'fas fa-question-circle'
56
+ description: '中文问答社区'
57
+ - name: 'B站'
58
+ url: 'https://www.bilibili.com/'
59
+ icon: 'fas fa-video'
60
+ description: '弹幕视频网站'
61
+
62
+ - name: '技术资源'
63
+ icon: 'fas fa-laptop-code'
64
+ subcategories:
65
+ - name: '前端开发'
66
+ icon: 'fas fa-code'
67
+ groups:
68
+ - name: '框架库'
69
+ icon: 'fas fa-cube'
70
+ sites:
71
+ - name: 'React'
72
+ url: 'https://reactjs.org/'
73
+ icon: 'fab fa-react'
74
+ description: 'React官方文档'
75
+ - name: 'Vue.js'
76
+ url: 'https://vuejs.org/'
77
+ icon: 'fab fa-vuejs'
78
+ description: 'Vue.js官方文档'
79
+ - name: 'Angular'
80
+ url: 'https://angular.io/'
81
+ icon: 'fab fa-angular'
82
+ description: 'Angular官方文档'
83
+ - name: '状态管理'
84
+ icon: 'fas fa-database'
85
+ sites:
86
+ - name: 'Redux'
87
+ url: 'https://redux.js.org/'
88
+ icon: 'fas fa-database'
89
+ description: 'Redux状态管理'
90
+ - name: 'Vuex'
91
+ url: 'https://vuex.vuejs.org/'
92
+ icon: 'fas fa-database'
93
+ description: 'Vue状态管理'
94
+ - name: 'MobX'
95
+ url: 'https://mobx.js.org/'
96
+ icon: 'fas fa-react'
97
+ description: '响应式状态管理'
98
+ - name: '构建工具'
99
+ icon: 'fas fa-tools'
100
+ sites:
101
+ - name: 'Webpack'
102
+ url: 'https://webpack.js.org/'
103
+ icon: 'fas fa-cube'
104
+ description: '模块打包工具'
105
+ - name: 'Vite'
106
+ url: 'https://vitejs.dev/'
107
+ icon: 'fas fa-bolt'
108
+ description: '下一代前端构建工具'
109
+ - name: 'Rollup'
110
+ url: 'https://rollupjs.org/'
111
+ icon: 'fas fa-compress'
112
+ description: '模块打包器'
113
+ - name: '后端开发'
114
+ icon: 'fas fa-server'
115
+ groups:
116
+ - name: 'Node.js生态'
117
+ icon: 'fab fa-node-js'
118
+ sites:
119
+ - name: 'Express'
120
+ url: 'https://expressjs.com/'
121
+ icon: 'fas fa-server'
122
+ description: 'Node.js Web框架'
123
+ - name: 'Koa'
124
+ url: 'https://koajs.com/'
125
+ icon: 'fas fa-leaf'
126
+ forceIconMode: manual
127
+ description: '下一代Node.js框架'
128
+ - name: 'NestJS'
129
+ url: 'https://nestjs.com/'
130
+ icon: 'fas fa-home'
131
+ description: 'Node.js企业级框架'
132
+ - name: 'Python框架'
133
+ icon: 'fab fa-python'
134
+ sites:
135
+ - name: 'Django'
136
+ url: 'https://www.djangoproject.com/'
137
+ icon: 'fab fa-python'
138
+ description: 'Python Web��架'
139
+ - name: 'Flask'
140
+ url: 'https://flask.palletsprojects.com/'
141
+ icon: 'fas fa-flask'
142
+ description: 'Python微框架'
143
+ - name: 'FastAPI'
144
+ url: 'https://fastapi.tiangolo.com/'
145
+ icon: 'fas fa-bolt'
146
+ description: '现代Python Web框架'
147
+
148
+ - name: '设计资源'
149
+ icon: 'fas fa-palette'
150
+ subcategories:
151
+ - name: 'UI设计工具'
152
+ icon: 'fas fa-paint-brush'
153
+ groups:
154
+ - name: '原型设计'
155
+ icon: 'fas fa-drafting-compass'
156
+ sites:
157
+ - name: 'Figma'
158
+ url: 'https://www.figma.com/'
159
+ icon: 'fab fa-figma'
160
+ description: '协作式UI设计工具'
161
+ - name: 'Sketch'
162
+ url: 'https://www.sketch.com/'
163
+ icon: 'fab fa-sketch'
164
+ description: 'Mac平台UI设计工具'
165
+ - name: 'Adobe XD'
166
+ url: 'https://www.adobe.com/products/xd.html'
167
+ icon: 'fab fa-adobe'
168
+ description: 'Adobe UI设计工具'
169
+ - name: '设计系统'
170
+ icon: 'fas fa-th-large'
171
+ sites:
172
+ - name: 'Ant Design'
173
+ url: 'https://ant.design/'
174
+ icon: 'fas fa-th'
175
+ forceIconMode: manual
176
+ description: '企业级UI设计语言'
177
+ - name: 'Material Design'
178
+ url: 'https://material.io/design'
179
+ icon: 'fas fa-cube'
180
+ description: 'Google设计系统'
181
+ - name: 'Bootstrap'
182
+ url: 'https://getbootstrap.com/'
183
+ icon: 'fab fa-bootstrap'
184
+ description: '响应式CSS框架'
185
+ - name: '视觉资源'
186
+ icon: 'fas fa-image'
187
+ groups:
188
+ - name: '图标库'
189
+ icon: 'fas fa-icons'
190
+ sites:
191
+ - name: 'Font Awesome'
192
+ url: 'https://fontawesome.com/'
193
+ icon: 'fab fa-font-awesome'
194
+ description: '图标库'
195
+ - name: 'Iconfont'
196
+ url: 'https://www.iconfont.cn/'
197
+ icon: 'fas fa-icons'
198
+ description: '阿里巴巴图标库'
199
+ - name: 'Feather Icons'
200
+ url: 'https://feathericons.com/'
201
+ icon: 'fas fa-feather'
202
+ description: '简洁的图标库'
203
+ - name: '配色方案'
204
+ icon: 'fas fa-palette'
205
+ sites:
206
+ - name: 'Coolors'
207
+ url: 'https://coolors.co/'
208
+ icon: 'fas fa-palette'
209
+ description: '在线配色方案生成器'
210
+ - name: 'Adobe Color'
211
+ url: 'https://color.adobe.com/'
212
+ icon: 'fab fa-adobe'
213
+ description: 'Adobe配色工具'
214
+ - name: 'Paletton'
215
+ url: 'https://paletton.com/'
216
+ icon: 'fas fa-palette'
217
+ description: '配色方案设计工具'
218
+
219
+ - name: '开发工具'
220
+ icon: 'fas fa-tools'
221
+ groups:
222
+ - name: '代码编辑器'
223
+ icon: 'fas fa-code'
224
+ sites:
225
+ - name: 'Visual Studio Code'
226
+ url: 'https://code.visualstudio.com/'
227
+ icon: 'fas fa-code'
228
+ description: '微软代码编辑器'
229
+ - name: 'Sublime Text'
230
+ url: 'https://www.sublimetext.com/'
231
+ icon: 'fas fa-file-code'
232
+ description: '轻量级代码编辑器'
233
+ - name: 'WebStorm'
234
+ url: 'https://www.jetbrains.com/webstorm/'
235
+ icon: 'fab fa-js'
236
+ description: 'JetBrains前端IDE'
237
+ - name: '版本控制'
238
+ icon: 'fas fa-code-branch'
239
+ sites:
240
+ - name: 'GitHub'
241
+ url: 'https://github.com/'
242
+ icon: 'fab fa-github'
243
+ description: '代码托管平台'
244
+ - name: 'GitLab'
245
+ url: 'https://gitlab.com/'
246
+ icon: 'fab fa-gitlab'
247
+ description: 'Git代码管理平台'
248
+ - name: 'Bitbucket'
249
+ url: 'https://bitbucket.org/'
250
+ icon: 'fab fa-bitbucket'
251
+ description: 'Atlassian代码托管'
config/user/pages/common.yml ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 默认页面配置(请勿直接修改)。
2
+ # 建议复制到 config/user/pages/common.yml 并按需调整。
3
+ title: 常用网站 # 页面标题
4
+ subtitle: Common website # 页面副标题
5
+
6
+ # 指定使用的模板文件名,现有页面模板可见 templates/pages(不含 .hbs)
7
+ # 说明:推荐使用通用模板 page;首页由“导航第一项”决定
8
+ template: page
9
+
10
+ # 页面分类与站点列表
11
+ categories:
12
+ - name: 置顶
13
+ icon: fas fa-star # 分类图标
14
+ sites:
15
+ - name: Linux.do # 站点名称
16
+ url: https://linux.do/ # http/https URL(favicon 模式将尝试加载站点图标)
17
+ icon: fab fa-linux # 手动图标:manual 模式使用;favicon 模式下作为回退
18
+ description: 新的理想型社区 # 站点描述
19
+ - name: Menav
20
+ url: https://rbetree.github.io/menav
21
+ icon: fas fa-star
22
+ description: 个人导航站
23
+ faviconUrl: assets/menav.svg
24
+ - name: Google
25
+ url: https://www.google.com
26
+ icon: fab fa-google
27
+ description: 全球最大的搜索引擎
28
+ - name: GitHub
29
+ url: https://www.github.com
30
+ icon: fab fa-github
31
+ description: 代码托管平台
32
+ - name: Stack Overflow
33
+ url: https://stackoverflow.com
34
+ icon: fab fa-stack-overflow
35
+ description: 程序员问答社区
36
+ - name: ChatGPT
37
+ url: https://chat.openai.com
38
+ icon: fas fa-robot
39
+ description: AI智能助手
40
+ - name: YouTube
41
+ url: https://www.youtube.com
42
+ icon: fab fa-youtube
43
+ description: 视频分享平台
44
+ - name: Twitter
45
+ url: https://twitter.com
46
+ icon: fab fa-twitter
47
+ description: 社交媒体平台
48
+ - name: Reddit
49
+ url: https://www.reddit.com
50
+ icon: fab fa-reddit
51
+ description: 社区讨论平台
52
+ - name: 学习资源
53
+ icon: fas fa-graduation-cap
54
+ sites:
55
+ - name: 哔哩哔哩
56
+ url: https://www.bilibili.com
57
+ icon: fas fa-play-circle
58
+ description: 视频学习平台
59
+ - name: 掘金
60
+ url: https://juejin.cn
61
+ icon: fas fa-book
62
+ description: 高质量技术社区
63
+ - name: LeetCode
64
+ url: https://leetcode.cn
65
+ icon: fas fa-code
66
+ description: 算法刷题平台
67
+ - name: 设计资源
68
+ icon: fas fa-palette
69
+ sites:
70
+ - name: Figma
71
+ url: https://www.figma.com
72
+ icon: fab fa-figma
73
+ description: 在线设计工具
74
+ - name: Dribbble
75
+ url: https://dribbble.com
76
+ icon: fab fa-dribbble
77
+ description: 设计师社区
78
+ - name: IconFont
79
+ url: https://www.iconfont.cn
80
+ icon: fas fa-icons
81
+ description: 图标资源库
82
+ - name: Adobe XD
83
+ url: https://www.adobe.com/products/xd.html
84
+ icon: fab fa-adobe
85
+ description: UI/UX设计工具
86
+ - name: Sketch
87
+ url: https://www.sketch.com
88
+ icon: fas fa-pencil-ruler
89
+ description: 矢量设计工具
90
+ - name: Canva
91
+ url: https://www.canva.com
92
+ icon: fas fa-paint-brush
93
+ description: 在线平面设计
94
+ - name: 在线工具
95
+ icon: fas fa-wrench
96
+ sites:
97
+ - name: JSON Editor
98
+ url: https://jsoneditoronline.org
99
+ icon: fas fa-code-branch
100
+ description: JSON在线编辑器
101
+ - name: Can I Use
102
+ url: https://caniuse.com
103
+ icon: fas fa-browser
104
+ description: 浏览器兼容性查询
105
+ - name: TinyPNG
106
+ url: https://tinypng.com
107
+ icon: fas fa-compress
108
+ description: 图片压缩工具
109
+ - name: Carbon
110
+ url: https://carbon.now.sh
111
+ icon: fas fa-code
112
+ description: 代码图片生成器
113
+ - name: Excalidraw
114
+ url: https://excalidraw.com
115
+ icon: fas fa-pencil-alt
116
+ description: 手绘风格图表工具
117
+ - name: 云服务平台
118
+ icon: fas fa-cloud
119
+ sites:
120
+ - name: Cloudflare
121
+ url: https://www.cloudflare.com
122
+ icon: fas fa-cloud
123
+ description: CDN与安全服务
124
+ - name: Vercel
125
+ url: https://vercel.com
126
+ icon: fas fa-server
127
+ description: 前端部署平台
128
+ - name: Netlify
129
+ url: https://www.netlify.com
130
+ icon: fas fa-globe
131
+ description: 静态网站托管
132
+ - name: AWS
133
+ url: https://aws.amazon.com
134
+ icon: fab fa-aws
135
+ description: 亚马逊云服务
136
+ - name: Azure
137
+ url: https://azure.microsoft.com
138
+ icon: fab fa-microsoft
139
+ description: 微软云平台
140
+ - name: Google Cloud
141
+ url: https://cloud.google.com
142
+ icon: fab fa-google
143
+ description: 谷歌云平台
config/user/pages/content.yml ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 默认页面配置(请勿直接修改)。
2
+ # 建议复制到 config/user/pages/content.yml 并按需调整。
3
+ title: 关于
4
+ subtitle: 项目说明
5
+
6
+ # 指定使用的模板文件名,现有页面模板可见 templates/pages(不含 .hbs)
7
+ # 内容页模板(templates/pages/content.hbs)
8
+ template: content
9
+
10
+ # 本期仅支持文件模式:读取本地 markdown 文件
11
+ content:
12
+ file: content/about.md
config/user/pages/projects.yml ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 默认页面配置(请勿直接修改)。
2
+ # 建议复制到 config/user/pages/projects.yml 并按需调整。
3
+ title: 项目 # 页面标题
4
+ subtitle: 项目展示 # 页面副标题
5
+
6
+ # 指定使用的模板文件名,现有页面模板可见 templates/pages(不含 .hbs)
7
+ template: projects
8
+
9
+ # 页面分类与站点列表
10
+ #
11
+ # projects 模板采用“代码仓库风”卡片(repo 风格)。
12
+ # 统计信息(language/stars/forks)为自动获取数据:
13
+ # - 运行 `npm run sync-projects` 会联网抓取 GitHub 仓库信息,并写入 dev/ 缓存(仓库默认 gitignore)
14
+ # - `npm run build` 默认不联网;缓存缺失时卡片仅展示标题与描述
15
+ categories:
16
+ - name: 个人项目
17
+ icon: fas fa-code # 分类图标(Font Awesome)
18
+ sites:
19
+ - name: MeNav
20
+ icon: fab fa-github # 手动图标(manual 模式显示;favicon 模式下作为回退)
21
+ description: 一键部署的个人导航站生成器,支持书签导入与自动构建,轻松整理展示您的网络收藏 # 站点描述
22
+ url: https://github.com/rbetree/menav
23
+ - name: MarksVault
24
+ icon: fab fa-github
25
+ description: 一个强大的浏览器扩展,用于智能管理、整理和安全备份您的书签数据
26
+ url: 'https://github.com/rbetree/MarksVault'
27
+ - name: star
28
+ icon: fas fa-star
29
+ sites:
30
+ - name: CLIProxyAPI
31
+ icon: fab fa-github
32
+ description: Wrap Gemini CLI, Antigravity, ChatGPT Codex, Claude Code, Qwen Code, iFlow as an OpenAI/Gemini/Claude/Codex compatible API service, allowing you to enjoy the free Gemini 2.5 Pro, GPT 5, Claude, Qwen model through API
33
+ url: 'https://github.com/router-for-me/CLIProxyAPI'
config/user/site.yml ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 默认配置(请勿直接修改)。
2
+ # 建议复制到 config/user/site.yml 并按需调整;用户配置采用“完全替换”策略,将覆盖默认配置。
3
+
4
+ # 网站基本信息
5
+ title: MeNav
6
+ description: Personal Navigation Station
7
+ author: rbetree
8
+ favicon: menav.svg
9
+ logo_text: MeNav
10
+
11
+ icons:
12
+ # 站点卡片图标模式:
13
+ # - favicon:自动根据 URL 加载站点 favicon(失败时回退到 Font Awesome 图标)
14
+ # - manual:始终使用手动指定的 Font Awesome 图标(不发起外部请求)
15
+ # 隐私提示:启用 favicon 模式会请求第三方服务以获取图标,可能将站点 URL 发送给服务商(详见 README"隐私说明")。
16
+ mode: favicon # 可选: favicon | manual(默认 favicon)
17
+
18
+ # favicon 服务区域选择(仅在 mode: favicon 时生效):
19
+ # - com:优先使用 gstatic.com(国际版),失败后回退到 gstatic.cn(中国版)
20
+ # - cn:优先使用 gstatic.cn(中国版),失败后回退到 gstatic.com(国际版)
21
+ # 说明:如果你在中国大陆且访问 gstatic.com 较慢,建议设置为 cn 以提升图标加载速度
22
+ region: cn # 可选: com | cn(默认 com)
23
+
24
+ # 安全策略(可选):链接 URL scheme 白名单
25
+ # - 默认允许:http/https/mailto/tel + 所有相对链接(# / ./ ../ ?)
26
+ # - 其他 scheme 会在页面中安全降级为 # 并输出告警(避免 javascript: 等危险链接变成可点击)
27
+ # - 如需支持 obsidian://、vscode:// 等自定义协议,可在此显式放行
28
+ security:
29
+ allowedSchemes:
30
+ - http
31
+ - https
32
+ - mailto
33
+ - tel
34
+ # 示例:
35
+ # allowedSchemes: [http, https, mailto, tel, obsidian, vscode]
36
+
37
+ # 主题设置:默认明暗模式(可选)
38
+ # - mode: dark | light | system
39
+ # - dark/light:首屏默认主题;用户点击按钮切换后会写入 localStorage 并覆盖该默认值
40
+ # - system:跟随系统 prefers-color-scheme;用户手动切换后同样会写入 localStorage 并停止跟随
41
+ theme:
42
+ mode: dark # 可选: dark | light | system(默认 dark)
43
+
44
+ # 字体设置:全站基础字体
45
+ # - source: css | google | system
46
+ # - css: 通过 cssUrl 引入第三方字体 CSS
47
+ # - google: 通过 Google Fonts 加载 family(weight 建议 100~900)
48
+ # - system: 只使用本地/系统字体,不额外发起请求
49
+ fonts:
50
+ source: css
51
+ cssUrl: 'https://fontsapi.zeoseven.com/292/main/result.css'
52
+ preload: true # 可选:使用 preload+onload 的方式非阻塞加载字体 CSS(更利于首屏性能)
53
+ family: LXGW WenKai
54
+ weight: normal
55
+
56
+ # 示例:切换到 Google Fonts
57
+ # fonts:
58
+ # source: google
59
+ # family: "Noto Sans SC"
60
+ # weight: 400
61
+
62
+ # 个人资料:显示在首页顶部的欢迎信息
63
+ profile:
64
+ # 注意:首页(导航第一项)标题区优先使用 profile.title/profile.subtitle
65
+ # 因此建议把首页希望展示的文案写在这里,避免“改了 pages/<首页id>.yml 但首页不生效”的误会
66
+ title: Hello,
67
+ subtitle: Welcome to My Navigation
68
+
69
+ # RSS(Phase 2):用于 articles 页面文章聚合
70
+ # 说明:
71
+ # - `npm run build` 默认不联网;仅 `npm run sync-articles` 会联网抓取并写入缓存
72
+ # - 缓存目录建议放在 dev/(仓库默认 gitignore),避免误提交
73
+ rss:
74
+ enabled: true
75
+ cacheDir: dev
76
+ fetch:
77
+ timeoutMs: 10000 # 单请求超时(毫秒)
78
+ totalTimeoutMs: 60000 # 全流程总超时(毫秒)
79
+ concurrency: 5 # 并发抓取站点数
80
+ maxRetries: 1 # 单站点重试次数(best-effort)
81
+ maxRedirects: 3 # 最大重定向次数
82
+ articles:
83
+ perSite: 8 # 单站点最多抓取条数
84
+ total: 50 # 全站聚合上限
85
+ summaryMaxLength: 200 # 摘要最大长度(字符)
86
+
87
+ # GitHub:用于 projects 页面右侧“贡献热力图”(可选)
88
+ # - username:你的 GitHub 用户名(例如 torvalds)
89
+ # - heatmapColor:热力图主题色(不带 #,例如 339af0)
90
+ github:
91
+ username: 'rbetree' # 你的 GitHub 用户名(例如 torvalds;为空则 projects 页不展示热力图)
92
+ heatmapColor: 37b24d
93
+ cacheDir: dev # projects 仓库元信息缓存目录(默认 dev,仓库默认 gitignore)
94
+
95
+ # 社交媒体链接:显示在侧边栏底部;可按需增删
96
+ social:
97
+ - name: GitHub
98
+ url: https://github.com
99
+ icon: fab fa-github
100
+ - name: Telegram
101
+ url: https://t.me
102
+ icon: fab fa-telegram
103
+ - name: Twitter
104
+ url: https://twitter.com
105
+ icon: fab fa-twitter
106
+ - name: Steam
107
+ url: https://steam.com
108
+ icon: fab fa-steam
109
+
110
+ # 导航配置(顺序第一项即首页/默认打开页)
111
+ navigation:
112
+ - name: 常用 # 菜单名称
113
+ icon: fas fa-star # Font Awesome 图标类
114
+ id: common # 页面标识符(唯一,需与 pages/<id>.yml 对应)
115
+ - name: 项目
116
+ icon: fas fa-project-diagram
117
+ id: projects
118
+ - name: 文章
119
+ icon: fas fa-book
120
+ id: articles
121
+ - name: 书签
122
+ icon: fas fa-bookmark
123
+ id: bookmarks
124
+ - name: 关于
125
+ icon: fas fa-file-alt
126
+ id: content
content/about.md ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 关于 MeNav
2
+
3
+ MeNav 是一个用于生成**个人导航站**的项目:
4
+
5
+ - **构建期**:使用 Node.js 作为静态站点生成器(SSG),把配置与内容渲染为 `dist/`。
6
+ - **运行时**:输出一份轻量的浏览器 runtime,用于页面交互与增强。
7
+
8
+ 这页用于放置项目的说明与使用要点(你也可以改成自己的“关于”页面)。
9
+
10
+ ## 适合谁
11
+
12
+ - 想要一个**可自托管、可版本管理**的导航页/起始页
13
+ - 希望用 **YAML + Markdown** 管理站点结构与内容
14
+ - 更偏好“生成静态文件再部署”,而不是运行时依赖服务端
15
+
16
+ ## 快速开始
17
+
18
+ ```bash
19
+ npm install
20
+ npm run dev
21
+ ```
22
+
23
+ - `npm run dev`:本地开发(生成站点并启动本地服务)
24
+ - `npm run build`:生成生产构建(输出到 `dist/`)
25
+
26
+ ## 项目结构(常用)
27
+
28
+ - `src/generator/`:构建期生成器(Node.js)
29
+ - `src/runtime/`:浏览器 runtime(最终会被打包到 `dist/script.js`)
30
+ - `templates/`:Handlebars 模板
31
+ - `config/`:站点配置(YAML)
32
+ - `content/`:内容页(Markdown),例如本文件
33
+ - `dist/`:构建输出(可直接部署)
34
+ - `dev/`:网络缓存(gitignored)
35
+
36
+ ## 配置说明(概念)
37
+
38
+ - MeNav 的站点配置以 `config/` 下的 YAML 为主。
39
+ - **注意**:如果存在 `config/user/`,它会**完全替换** `config/_default/`(不是 merge)。
40
+
41
+ ## 内容页(Markdown)说明
42
+
43
+ - 内容页的 Markdown 会在**构建期**渲染为 HTML。
44
+ - 内容页通常用于:关于、帮助、使用说明、更新记录等。
45
+
46
+ ## 安全与链接处理
47
+
48
+ MeNav 对链接会做安全处理(例如限制危险的 URL scheme),以降低把不安全链接渲染到页面上的风险。
49
+
50
+ 如果你在导航数据或内容页里粘贴了外部链接,建议优先使用 `https://`。
51
+
52
+ ## 部署
53
+
54
+ `npm run build` 后将生成的 `dist/` 部署到任意静态站点托管即可(例如 Nginx、GitHub Pages、Cloudflare Pages 等)。
55
+
56
+ ## 维护建议
57
+
58
+ - 把你的配置、内容页都纳入 git 版本管理
59
+ - 变更后跑一遍:
60
+
61
+ ```bash
62
+ npm run check
63
+ ```
64
+
65
+ (会依次执行语法检查、测试与构建)
docker-compose.yml ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ services:
2
+ menav:
3
+ container_name: menav
4
+ image: ${MENAV_IMAGE:-ghcr.io/rbetree/menav:latest}
5
+ build: .
6
+ environment:
7
+ MENAV_ENABLE_SYNC: ${MENAV_ENABLE_SYNC:-false}
8
+ MENAV_IMPORT_BOOKMARKS: ${MENAV_IMPORT_BOOKMARKS:-false}
9
+ ports:
10
+ - '${MENAV_PORT:-8080}:80'
11
+ volumes:
12
+ - ./config:/app/config
13
+ - ./bookmarks:/app/bookmarks
14
+ restart: unless-stopped
docker/entrypoint-build-and-serve.sh ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ set -eu
3
+
4
+ echo "[menav] starting dynamic build mode"
5
+
6
+ if [ "${MENAV_IMPORT_BOOKMARKS:-false}" = "true" ]; then
7
+ echo "[menav] importing bookmarks before build"
8
+ MENAV_BOOKMARKS_DETERMINISTIC=1 npm run import-bookmarks
9
+ fi
10
+
11
+ if [ "${MENAV_ENABLE_SYNC:-false}" = "true" ]; then
12
+ echo "[menav] building with sync enabled"
13
+ npm run build
14
+ else
15
+ echo "[menav] building with sync disabled"
16
+ PROJECTS_ENABLED=false HEATMAP_ENABLED=false RSS_ENABLED=false npm run build
17
+ fi
18
+
19
+ echo "[menav] syncing dist to nginx web root"
20
+ mkdir -p /usr/share/nginx/html
21
+ rm -rf /usr/share/nginx/html/*
22
+ cp -a /app/dist/. /usr/share/nginx/html/
23
+
24
+ echo "[menav] serving dist with nginx"
25
+ exec nginx -g 'daemon off;'
docker/nginx/default.conf ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ server {
2
+ listen 80;
3
+ server_name _;
4
+
5
+ root /usr/share/nginx/html;
6
+ index index.html;
7
+
8
+ charset utf-8;
9
+
10
+ location = / {
11
+ try_files /index.html =404;
12
+ add_header Cache-Control "no-store";
13
+ }
14
+
15
+ location ~* \.(?:css|js|mjs|json|ico|png|jpe?g|gif|svg|webp|txt|xml|map)$ {
16
+ try_files $uri =404;
17
+ access_log off;
18
+ add_header Cache-Control "public, max-age=3600";
19
+ }
20
+
21
+ location ~* \.html$ {
22
+ try_files $uri =404;
23
+ add_header Cache-Control "no-store";
24
+ }
25
+
26
+ location / {
27
+ try_files $uri $uri/ /404.html;
28
+ add_header Cache-Control "no-store";
29
+ }
30
+ }
licenses/pinyin-match.NOTICE.md ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ Third-party notice: pinyin-match
2
+
3
+ - File: assets/pinyin-match.js (minified vendor script)
4
+ - Source: To be confirmed; replace with upstream repo URL and exact license.
5
+ - License: To be added (likely MIT). Include the full license text once verified.
6
+
7
+ Vendored to enable offline builds. This notice will be replaced with the exact license details once confirmed.
package-lock.json ADDED
@@ -0,0 +1,2174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "menav",
3
+ "version": "1.3.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "menav",
9
+ "version": "1.3.0",
10
+ "license": "AGPL-3.0-only",
11
+ "dependencies": {
12
+ "handlebars": "^4.7.9",
13
+ "js-yaml": "^4.1.1",
14
+ "markdown-it": "^14.1.1",
15
+ "rss-parser": "^3.13.0"
16
+ },
17
+ "devDependencies": {
18
+ "esbuild": "^0.28.0",
19
+ "husky": "^9.1.7",
20
+ "lint-staged": "^16.4.0",
21
+ "prettier": "^3.8.3",
22
+ "serve": "^14.2.6"
23
+ }
24
+ },
25
+ "node_modules/@esbuild/aix-ppc64": {
26
+ "version": "0.28.0",
27
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz",
28
+ "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==",
29
+ "cpu": [
30
+ "ppc64"
31
+ ],
32
+ "dev": true,
33
+ "license": "MIT",
34
+ "optional": true,
35
+ "os": [
36
+ "aix"
37
+ ],
38
+ "engines": {
39
+ "node": ">=18"
40
+ }
41
+ },
42
+ "node_modules/@esbuild/android-arm": {
43
+ "version": "0.28.0",
44
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz",
45
+ "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==",
46
+ "cpu": [
47
+ "arm"
48
+ ],
49
+ "dev": true,
50
+ "license": "MIT",
51
+ "optional": true,
52
+ "os": [
53
+ "android"
54
+ ],
55
+ "engines": {
56
+ "node": ">=18"
57
+ }
58
+ },
59
+ "node_modules/@esbuild/android-arm64": {
60
+ "version": "0.28.0",
61
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz",
62
+ "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==",
63
+ "cpu": [
64
+ "arm64"
65
+ ],
66
+ "dev": true,
67
+ "license": "MIT",
68
+ "optional": true,
69
+ "os": [
70
+ "android"
71
+ ],
72
+ "engines": {
73
+ "node": ">=18"
74
+ }
75
+ },
76
+ "node_modules/@esbuild/android-x64": {
77
+ "version": "0.28.0",
78
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz",
79
+ "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==",
80
+ "cpu": [
81
+ "x64"
82
+ ],
83
+ "dev": true,
84
+ "license": "MIT",
85
+ "optional": true,
86
+ "os": [
87
+ "android"
88
+ ],
89
+ "engines": {
90
+ "node": ">=18"
91
+ }
92
+ },
93
+ "node_modules/@esbuild/darwin-arm64": {
94
+ "version": "0.28.0",
95
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz",
96
+ "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==",
97
+ "cpu": [
98
+ "arm64"
99
+ ],
100
+ "dev": true,
101
+ "license": "MIT",
102
+ "optional": true,
103
+ "os": [
104
+ "darwin"
105
+ ],
106
+ "engines": {
107
+ "node": ">=18"
108
+ }
109
+ },
110
+ "node_modules/@esbuild/darwin-x64": {
111
+ "version": "0.28.0",
112
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz",
113
+ "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==",
114
+ "cpu": [
115
+ "x64"
116
+ ],
117
+ "dev": true,
118
+ "license": "MIT",
119
+ "optional": true,
120
+ "os": [
121
+ "darwin"
122
+ ],
123
+ "engines": {
124
+ "node": ">=18"
125
+ }
126
+ },
127
+ "node_modules/@esbuild/freebsd-arm64": {
128
+ "version": "0.28.0",
129
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz",
130
+ "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==",
131
+ "cpu": [
132
+ "arm64"
133
+ ],
134
+ "dev": true,
135
+ "license": "MIT",
136
+ "optional": true,
137
+ "os": [
138
+ "freebsd"
139
+ ],
140
+ "engines": {
141
+ "node": ">=18"
142
+ }
143
+ },
144
+ "node_modules/@esbuild/freebsd-x64": {
145
+ "version": "0.28.0",
146
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz",
147
+ "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==",
148
+ "cpu": [
149
+ "x64"
150
+ ],
151
+ "dev": true,
152
+ "license": "MIT",
153
+ "optional": true,
154
+ "os": [
155
+ "freebsd"
156
+ ],
157
+ "engines": {
158
+ "node": ">=18"
159
+ }
160
+ },
161
+ "node_modules/@esbuild/linux-arm": {
162
+ "version": "0.28.0",
163
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz",
164
+ "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==",
165
+ "cpu": [
166
+ "arm"
167
+ ],
168
+ "dev": true,
169
+ "license": "MIT",
170
+ "optional": true,
171
+ "os": [
172
+ "linux"
173
+ ],
174
+ "engines": {
175
+ "node": ">=18"
176
+ }
177
+ },
178
+ "node_modules/@esbuild/linux-arm64": {
179
+ "version": "0.28.0",
180
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz",
181
+ "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==",
182
+ "cpu": [
183
+ "arm64"
184
+ ],
185
+ "dev": true,
186
+ "license": "MIT",
187
+ "optional": true,
188
+ "os": [
189
+ "linux"
190
+ ],
191
+ "engines": {
192
+ "node": ">=18"
193
+ }
194
+ },
195
+ "node_modules/@esbuild/linux-ia32": {
196
+ "version": "0.28.0",
197
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz",
198
+ "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==",
199
+ "cpu": [
200
+ "ia32"
201
+ ],
202
+ "dev": true,
203
+ "license": "MIT",
204
+ "optional": true,
205
+ "os": [
206
+ "linux"
207
+ ],
208
+ "engines": {
209
+ "node": ">=18"
210
+ }
211
+ },
212
+ "node_modules/@esbuild/linux-loong64": {
213
+ "version": "0.28.0",
214
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz",
215
+ "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==",
216
+ "cpu": [
217
+ "loong64"
218
+ ],
219
+ "dev": true,
220
+ "license": "MIT",
221
+ "optional": true,
222
+ "os": [
223
+ "linux"
224
+ ],
225
+ "engines": {
226
+ "node": ">=18"
227
+ }
228
+ },
229
+ "node_modules/@esbuild/linux-mips64el": {
230
+ "version": "0.28.0",
231
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz",
232
+ "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==",
233
+ "cpu": [
234
+ "mips64el"
235
+ ],
236
+ "dev": true,
237
+ "license": "MIT",
238
+ "optional": true,
239
+ "os": [
240
+ "linux"
241
+ ],
242
+ "engines": {
243
+ "node": ">=18"
244
+ }
245
+ },
246
+ "node_modules/@esbuild/linux-ppc64": {
247
+ "version": "0.28.0",
248
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz",
249
+ "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==",
250
+ "cpu": [
251
+ "ppc64"
252
+ ],
253
+ "dev": true,
254
+ "license": "MIT",
255
+ "optional": true,
256
+ "os": [
257
+ "linux"
258
+ ],
259
+ "engines": {
260
+ "node": ">=18"
261
+ }
262
+ },
263
+ "node_modules/@esbuild/linux-riscv64": {
264
+ "version": "0.28.0",
265
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz",
266
+ "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==",
267
+ "cpu": [
268
+ "riscv64"
269
+ ],
270
+ "dev": true,
271
+ "license": "MIT",
272
+ "optional": true,
273
+ "os": [
274
+ "linux"
275
+ ],
276
+ "engines": {
277
+ "node": ">=18"
278
+ }
279
+ },
280
+ "node_modules/@esbuild/linux-s390x": {
281
+ "version": "0.28.0",
282
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz",
283
+ "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==",
284
+ "cpu": [
285
+ "s390x"
286
+ ],
287
+ "dev": true,
288
+ "license": "MIT",
289
+ "optional": true,
290
+ "os": [
291
+ "linux"
292
+ ],
293
+ "engines": {
294
+ "node": ">=18"
295
+ }
296
+ },
297
+ "node_modules/@esbuild/linux-x64": {
298
+ "version": "0.28.0",
299
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz",
300
+ "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==",
301
+ "cpu": [
302
+ "x64"
303
+ ],
304
+ "dev": true,
305
+ "license": "MIT",
306
+ "optional": true,
307
+ "os": [
308
+ "linux"
309
+ ],
310
+ "engines": {
311
+ "node": ">=18"
312
+ }
313
+ },
314
+ "node_modules/@esbuild/netbsd-arm64": {
315
+ "version": "0.28.0",
316
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz",
317
+ "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==",
318
+ "cpu": [
319
+ "arm64"
320
+ ],
321
+ "dev": true,
322
+ "license": "MIT",
323
+ "optional": true,
324
+ "os": [
325
+ "netbsd"
326
+ ],
327
+ "engines": {
328
+ "node": ">=18"
329
+ }
330
+ },
331
+ "node_modules/@esbuild/netbsd-x64": {
332
+ "version": "0.28.0",
333
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz",
334
+ "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==",
335
+ "cpu": [
336
+ "x64"
337
+ ],
338
+ "dev": true,
339
+ "license": "MIT",
340
+ "optional": true,
341
+ "os": [
342
+ "netbsd"
343
+ ],
344
+ "engines": {
345
+ "node": ">=18"
346
+ }
347
+ },
348
+ "node_modules/@esbuild/openbsd-arm64": {
349
+ "version": "0.28.0",
350
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz",
351
+ "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==",
352
+ "cpu": [
353
+ "arm64"
354
+ ],
355
+ "dev": true,
356
+ "license": "MIT",
357
+ "optional": true,
358
+ "os": [
359
+ "openbsd"
360
+ ],
361
+ "engines": {
362
+ "node": ">=18"
363
+ }
364
+ },
365
+ "node_modules/@esbuild/openbsd-x64": {
366
+ "version": "0.28.0",
367
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz",
368
+ "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==",
369
+ "cpu": [
370
+ "x64"
371
+ ],
372
+ "dev": true,
373
+ "license": "MIT",
374
+ "optional": true,
375
+ "os": [
376
+ "openbsd"
377
+ ],
378
+ "engines": {
379
+ "node": ">=18"
380
+ }
381
+ },
382
+ "node_modules/@esbuild/openharmony-arm64": {
383
+ "version": "0.28.0",
384
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz",
385
+ "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==",
386
+ "cpu": [
387
+ "arm64"
388
+ ],
389
+ "dev": true,
390
+ "license": "MIT",
391
+ "optional": true,
392
+ "os": [
393
+ "openharmony"
394
+ ],
395
+ "engines": {
396
+ "node": ">=18"
397
+ }
398
+ },
399
+ "node_modules/@esbuild/sunos-x64": {
400
+ "version": "0.28.0",
401
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz",
402
+ "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==",
403
+ "cpu": [
404
+ "x64"
405
+ ],
406
+ "dev": true,
407
+ "license": "MIT",
408
+ "optional": true,
409
+ "os": [
410
+ "sunos"
411
+ ],
412
+ "engines": {
413
+ "node": ">=18"
414
+ }
415
+ },
416
+ "node_modules/@esbuild/win32-arm64": {
417
+ "version": "0.28.0",
418
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz",
419
+ "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==",
420
+ "cpu": [
421
+ "arm64"
422
+ ],
423
+ "dev": true,
424
+ "license": "MIT",
425
+ "optional": true,
426
+ "os": [
427
+ "win32"
428
+ ],
429
+ "engines": {
430
+ "node": ">=18"
431
+ }
432
+ },
433
+ "node_modules/@esbuild/win32-ia32": {
434
+ "version": "0.28.0",
435
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz",
436
+ "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==",
437
+ "cpu": [
438
+ "ia32"
439
+ ],
440
+ "dev": true,
441
+ "license": "MIT",
442
+ "optional": true,
443
+ "os": [
444
+ "win32"
445
+ ],
446
+ "engines": {
447
+ "node": ">=18"
448
+ }
449
+ },
450
+ "node_modules/@esbuild/win32-x64": {
451
+ "version": "0.28.0",
452
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz",
453
+ "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==",
454
+ "cpu": [
455
+ "x64"
456
+ ],
457
+ "dev": true,
458
+ "license": "MIT",
459
+ "optional": true,
460
+ "os": [
461
+ "win32"
462
+ ],
463
+ "engines": {
464
+ "node": ">=18"
465
+ }
466
+ },
467
+ "node_modules/@zeit/schemas": {
468
+ "version": "2.36.0",
469
+ "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz",
470
+ "integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==",
471
+ "dev": true,
472
+ "license": "MIT"
473
+ },
474
+ "node_modules/ajv": {
475
+ "version": "8.18.0",
476
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
477
+ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
478
+ "dev": true,
479
+ "license": "MIT",
480
+ "dependencies": {
481
+ "fast-deep-equal": "^3.1.3",
482
+ "fast-uri": "^3.0.1",
483
+ "json-schema-traverse": "^1.0.0",
484
+ "require-from-string": "^2.0.2"
485
+ },
486
+ "funding": {
487
+ "type": "github",
488
+ "url": "https://github.com/sponsors/epoberezkin"
489
+ }
490
+ },
491
+ "node_modules/ansi-align": {
492
+ "version": "3.0.1",
493
+ "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
494
+ "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==",
495
+ "dev": true,
496
+ "license": "ISC",
497
+ "dependencies": {
498
+ "string-width": "^4.1.0"
499
+ }
500
+ },
501
+ "node_modules/ansi-align/node_modules/ansi-regex": {
502
+ "version": "5.0.1",
503
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
504
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
505
+ "dev": true,
506
+ "license": "MIT",
507
+ "engines": {
508
+ "node": ">=8"
509
+ }
510
+ },
511
+ "node_modules/ansi-align/node_modules/emoji-regex": {
512
+ "version": "8.0.0",
513
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
514
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
515
+ "dev": true,
516
+ "license": "MIT"
517
+ },
518
+ "node_modules/ansi-align/node_modules/string-width": {
519
+ "version": "4.2.3",
520
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
521
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
522
+ "dev": true,
523
+ "license": "MIT",
524
+ "dependencies": {
525
+ "emoji-regex": "^8.0.0",
526
+ "is-fullwidth-code-point": "^3.0.0",
527
+ "strip-ansi": "^6.0.1"
528
+ },
529
+ "engines": {
530
+ "node": ">=8"
531
+ }
532
+ },
533
+ "node_modules/ansi-align/node_modules/strip-ansi": {
534
+ "version": "6.0.1",
535
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
536
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
537
+ "dev": true,
538
+ "license": "MIT",
539
+ "dependencies": {
540
+ "ansi-regex": "^5.0.1"
541
+ },
542
+ "engines": {
543
+ "node": ">=8"
544
+ }
545
+ },
546
+ "node_modules/ansi-escapes": {
547
+ "version": "7.2.0",
548
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz",
549
+ "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==",
550
+ "dev": true,
551
+ "license": "MIT",
552
+ "dependencies": {
553
+ "environment": "^1.0.0"
554
+ },
555
+ "engines": {
556
+ "node": ">=18"
557
+ },
558
+ "funding": {
559
+ "url": "https://github.com/sponsors/sindresorhus"
560
+ }
561
+ },
562
+ "node_modules/ansi-regex": {
563
+ "version": "6.1.0",
564
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
565
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
566
+ "dev": true,
567
+ "license": "MIT",
568
+ "engines": {
569
+ "node": ">=12"
570
+ },
571
+ "funding": {
572
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
573
+ }
574
+ },
575
+ "node_modules/ansi-styles": {
576
+ "version": "6.2.1",
577
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
578
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
579
+ "dev": true,
580
+ "license": "MIT",
581
+ "engines": {
582
+ "node": ">=12"
583
+ },
584
+ "funding": {
585
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
586
+ }
587
+ },
588
+ "node_modules/arch": {
589
+ "version": "2.2.0",
590
+ "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz",
591
+ "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==",
592
+ "dev": true,
593
+ "funding": [
594
+ {
595
+ "type": "github",
596
+ "url": "https://github.com/sponsors/feross"
597
+ },
598
+ {
599
+ "type": "patreon",
600
+ "url": "https://www.patreon.com/feross"
601
+ },
602
+ {
603
+ "type": "consulting",
604
+ "url": "https://feross.org/support"
605
+ }
606
+ ],
607
+ "license": "MIT"
608
+ },
609
+ "node_modules/arg": {
610
+ "version": "5.0.2",
611
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
612
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
613
+ "dev": true,
614
+ "license": "MIT"
615
+ },
616
+ "node_modules/argparse": {
617
+ "version": "2.0.1",
618
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
619
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
620
+ "license": "Python-2.0"
621
+ },
622
+ "node_modules/balanced-match": {
623
+ "version": "1.0.2",
624
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
625
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
626
+ "dev": true,
627
+ "license": "MIT"
628
+ },
629
+ "node_modules/boxen": {
630
+ "version": "7.0.0",
631
+ "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz",
632
+ "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==",
633
+ "dev": true,
634
+ "license": "MIT",
635
+ "dependencies": {
636
+ "ansi-align": "^3.0.1",
637
+ "camelcase": "^7.0.0",
638
+ "chalk": "^5.0.1",
639
+ "cli-boxes": "^3.0.0",
640
+ "string-width": "^5.1.2",
641
+ "type-fest": "^2.13.0",
642
+ "widest-line": "^4.0.1",
643
+ "wrap-ansi": "^8.0.1"
644
+ },
645
+ "engines": {
646
+ "node": ">=14.16"
647
+ },
648
+ "funding": {
649
+ "url": "https://github.com/sponsors/sindresorhus"
650
+ }
651
+ },
652
+ "node_modules/brace-expansion": {
653
+ "version": "1.1.12",
654
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
655
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
656
+ "dev": true,
657
+ "license": "MIT",
658
+ "dependencies": {
659
+ "balanced-match": "^1.0.0",
660
+ "concat-map": "0.0.1"
661
+ }
662
+ },
663
+ "node_modules/bytes": {
664
+ "version": "3.0.0",
665
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
666
+ "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
667
+ "dev": true,
668
+ "license": "MIT",
669
+ "engines": {
670
+ "node": ">= 0.8"
671
+ }
672
+ },
673
+ "node_modules/camelcase": {
674
+ "version": "7.0.1",
675
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz",
676
+ "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==",
677
+ "dev": true,
678
+ "license": "MIT",
679
+ "engines": {
680
+ "node": ">=14.16"
681
+ },
682
+ "funding": {
683
+ "url": "https://github.com/sponsors/sindresorhus"
684
+ }
685
+ },
686
+ "node_modules/chalk": {
687
+ "version": "5.0.1",
688
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz",
689
+ "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==",
690
+ "dev": true,
691
+ "license": "MIT",
692
+ "engines": {
693
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
694
+ },
695
+ "funding": {
696
+ "url": "https://github.com/chalk/chalk?sponsor=1"
697
+ }
698
+ },
699
+ "node_modules/chalk-template": {
700
+ "version": "0.4.0",
701
+ "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz",
702
+ "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==",
703
+ "dev": true,
704
+ "license": "MIT",
705
+ "dependencies": {
706
+ "chalk": "^4.1.2"
707
+ },
708
+ "engines": {
709
+ "node": ">=12"
710
+ },
711
+ "funding": {
712
+ "url": "https://github.com/chalk/chalk-template?sponsor=1"
713
+ }
714
+ },
715
+ "node_modules/chalk-template/node_modules/ansi-styles": {
716
+ "version": "4.3.0",
717
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
718
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
719
+ "dev": true,
720
+ "license": "MIT",
721
+ "dependencies": {
722
+ "color-convert": "^2.0.1"
723
+ },
724
+ "engines": {
725
+ "node": ">=8"
726
+ },
727
+ "funding": {
728
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
729
+ }
730
+ },
731
+ "node_modules/chalk-template/node_modules/chalk": {
732
+ "version": "4.1.2",
733
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
734
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
735
+ "dev": true,
736
+ "license": "MIT",
737
+ "dependencies": {
738
+ "ansi-styles": "^4.1.0",
739
+ "supports-color": "^7.1.0"
740
+ },
741
+ "engines": {
742
+ "node": ">=10"
743
+ },
744
+ "funding": {
745
+ "url": "https://github.com/chalk/chalk?sponsor=1"
746
+ }
747
+ },
748
+ "node_modules/chalk-template/node_modules/has-flag": {
749
+ "version": "4.0.0",
750
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
751
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
752
+ "dev": true,
753
+ "license": "MIT",
754
+ "engines": {
755
+ "node": ">=8"
756
+ }
757
+ },
758
+ "node_modules/chalk-template/node_modules/supports-color": {
759
+ "version": "7.2.0",
760
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
761
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
762
+ "dev": true,
763
+ "license": "MIT",
764
+ "dependencies": {
765
+ "has-flag": "^4.0.0"
766
+ },
767
+ "engines": {
768
+ "node": ">=8"
769
+ }
770
+ },
771
+ "node_modules/cli-boxes": {
772
+ "version": "3.0.0",
773
+ "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
774
+ "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==",
775
+ "dev": true,
776
+ "license": "MIT",
777
+ "engines": {
778
+ "node": ">=10"
779
+ },
780
+ "funding": {
781
+ "url": "https://github.com/sponsors/sindresorhus"
782
+ }
783
+ },
784
+ "node_modules/cli-cursor": {
785
+ "version": "5.0.0",
786
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
787
+ "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
788
+ "dev": true,
789
+ "license": "MIT",
790
+ "dependencies": {
791
+ "restore-cursor": "^5.0.0"
792
+ },
793
+ "engines": {
794
+ "node": ">=18"
795
+ },
796
+ "funding": {
797
+ "url": "https://github.com/sponsors/sindresorhus"
798
+ }
799
+ },
800
+ "node_modules/cli-truncate": {
801
+ "version": "5.1.1",
802
+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz",
803
+ "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==",
804
+ "dev": true,
805
+ "license": "MIT",
806
+ "dependencies": {
807
+ "slice-ansi": "^7.1.0",
808
+ "string-width": "^8.0.0"
809
+ },
810
+ "engines": {
811
+ "node": ">=20"
812
+ },
813
+ "funding": {
814
+ "url": "https://github.com/sponsors/sindresorhus"
815
+ }
816
+ },
817
+ "node_modules/cli-truncate/node_modules/string-width": {
818
+ "version": "8.1.0",
819
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz",
820
+ "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==",
821
+ "dev": true,
822
+ "license": "MIT",
823
+ "dependencies": {
824
+ "get-east-asian-width": "^1.3.0",
825
+ "strip-ansi": "^7.1.0"
826
+ },
827
+ "engines": {
828
+ "node": ">=20"
829
+ },
830
+ "funding": {
831
+ "url": "https://github.com/sponsors/sindresorhus"
832
+ }
833
+ },
834
+ "node_modules/clipboardy": {
835
+ "version": "3.0.0",
836
+ "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz",
837
+ "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==",
838
+ "dev": true,
839
+ "license": "MIT",
840
+ "dependencies": {
841
+ "arch": "^2.2.0",
842
+ "execa": "^5.1.1",
843
+ "is-wsl": "^2.2.0"
844
+ },
845
+ "engines": {
846
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
847
+ },
848
+ "funding": {
849
+ "url": "https://github.com/sponsors/sindresorhus"
850
+ }
851
+ },
852
+ "node_modules/color-convert": {
853
+ "version": "2.0.1",
854
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
855
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
856
+ "dev": true,
857
+ "license": "MIT",
858
+ "dependencies": {
859
+ "color-name": "~1.1.4"
860
+ },
861
+ "engines": {
862
+ "node": ">=7.0.0"
863
+ }
864
+ },
865
+ "node_modules/color-convert/node_modules/color-name": {
866
+ "version": "1.1.4",
867
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
868
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
869
+ "dev": true,
870
+ "license": "MIT"
871
+ },
872
+ "node_modules/colorette": {
873
+ "version": "2.0.20",
874
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
875
+ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
876
+ "dev": true,
877
+ "license": "MIT"
878
+ },
879
+ "node_modules/commander": {
880
+ "version": "14.0.3",
881
+ "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
882
+ "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==",
883
+ "dev": true,
884
+ "license": "MIT",
885
+ "engines": {
886
+ "node": ">=20"
887
+ }
888
+ },
889
+ "node_modules/compressible": {
890
+ "version": "2.0.18",
891
+ "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
892
+ "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
893
+ "dev": true,
894
+ "license": "MIT",
895
+ "dependencies": {
896
+ "mime-db": ">= 1.43.0 < 2"
897
+ },
898
+ "engines": {
899
+ "node": ">= 0.6"
900
+ }
901
+ },
902
+ "node_modules/compression": {
903
+ "version": "1.8.1",
904
+ "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
905
+ "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
906
+ "dev": true,
907
+ "license": "MIT",
908
+ "dependencies": {
909
+ "bytes": "3.1.2",
910
+ "compressible": "~2.0.18",
911
+ "debug": "2.6.9",
912
+ "negotiator": "~0.6.4",
913
+ "on-headers": "~1.1.0",
914
+ "safe-buffer": "5.2.1",
915
+ "vary": "~1.1.2"
916
+ },
917
+ "engines": {
918
+ "node": ">= 0.8.0"
919
+ }
920
+ },
921
+ "node_modules/compression/node_modules/bytes": {
922
+ "version": "3.1.2",
923
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
924
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
925
+ "dev": true,
926
+ "license": "MIT",
927
+ "engines": {
928
+ "node": ">= 0.8"
929
+ }
930
+ },
931
+ "node_modules/concat-map": {
932
+ "version": "0.0.1",
933
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
934
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
935
+ "dev": true,
936
+ "license": "MIT"
937
+ },
938
+ "node_modules/content-disposition": {
939
+ "version": "0.5.2",
940
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
941
+ "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==",
942
+ "dev": true,
943
+ "license": "MIT",
944
+ "engines": {
945
+ "node": ">= 0.6"
946
+ }
947
+ },
948
+ "node_modules/cross-spawn": {
949
+ "version": "7.0.6",
950
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
951
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
952
+ "dev": true,
953
+ "license": "MIT",
954
+ "dependencies": {
955
+ "path-key": "^3.1.0",
956
+ "shebang-command": "^2.0.0",
957
+ "which": "^2.0.1"
958
+ },
959
+ "engines": {
960
+ "node": ">= 8"
961
+ }
962
+ },
963
+ "node_modules/debug": {
964
+ "version": "2.6.9",
965
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
966
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
967
+ "dev": true,
968
+ "license": "MIT",
969
+ "dependencies": {
970
+ "ms": "2.0.0"
971
+ }
972
+ },
973
+ "node_modules/deep-extend": {
974
+ "version": "0.6.0",
975
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
976
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
977
+ "dev": true,
978
+ "license": "MIT",
979
+ "engines": {
980
+ "node": ">=4.0.0"
981
+ }
982
+ },
983
+ "node_modules/eastasianwidth": {
984
+ "version": "0.2.0",
985
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
986
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
987
+ "dev": true,
988
+ "license": "MIT"
989
+ },
990
+ "node_modules/emoji-regex": {
991
+ "version": "9.2.2",
992
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
993
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
994
+ "dev": true,
995
+ "license": "MIT"
996
+ },
997
+ "node_modules/entities": {
998
+ "version": "2.2.0",
999
+ "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
1000
+ "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
1001
+ "license": "BSD-2-Clause",
1002
+ "funding": {
1003
+ "url": "https://github.com/fb55/entities?sponsor=1"
1004
+ }
1005
+ },
1006
+ "node_modules/environment": {
1007
+ "version": "1.1.0",
1008
+ "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
1009
+ "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
1010
+ "dev": true,
1011
+ "license": "MIT",
1012
+ "engines": {
1013
+ "node": ">=18"
1014
+ },
1015
+ "funding": {
1016
+ "url": "https://github.com/sponsors/sindresorhus"
1017
+ }
1018
+ },
1019
+ "node_modules/esbuild": {
1020
+ "version": "0.28.0",
1021
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz",
1022
+ "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==",
1023
+ "dev": true,
1024
+ "hasInstallScript": true,
1025
+ "license": "MIT",
1026
+ "bin": {
1027
+ "esbuild": "bin/esbuild"
1028
+ },
1029
+ "engines": {
1030
+ "node": ">=18"
1031
+ },
1032
+ "optionalDependencies": {
1033
+ "@esbuild/aix-ppc64": "0.28.0",
1034
+ "@esbuild/android-arm": "0.28.0",
1035
+ "@esbuild/android-arm64": "0.28.0",
1036
+ "@esbuild/android-x64": "0.28.0",
1037
+ "@esbuild/darwin-arm64": "0.28.0",
1038
+ "@esbuild/darwin-x64": "0.28.0",
1039
+ "@esbuild/freebsd-arm64": "0.28.0",
1040
+ "@esbuild/freebsd-x64": "0.28.0",
1041
+ "@esbuild/linux-arm": "0.28.0",
1042
+ "@esbuild/linux-arm64": "0.28.0",
1043
+ "@esbuild/linux-ia32": "0.28.0",
1044
+ "@esbuild/linux-loong64": "0.28.0",
1045
+ "@esbuild/linux-mips64el": "0.28.0",
1046
+ "@esbuild/linux-ppc64": "0.28.0",
1047
+ "@esbuild/linux-riscv64": "0.28.0",
1048
+ "@esbuild/linux-s390x": "0.28.0",
1049
+ "@esbuild/linux-x64": "0.28.0",
1050
+ "@esbuild/netbsd-arm64": "0.28.0",
1051
+ "@esbuild/netbsd-x64": "0.28.0",
1052
+ "@esbuild/openbsd-arm64": "0.28.0",
1053
+ "@esbuild/openbsd-x64": "0.28.0",
1054
+ "@esbuild/openharmony-arm64": "0.28.0",
1055
+ "@esbuild/sunos-x64": "0.28.0",
1056
+ "@esbuild/win32-arm64": "0.28.0",
1057
+ "@esbuild/win32-ia32": "0.28.0",
1058
+ "@esbuild/win32-x64": "0.28.0"
1059
+ }
1060
+ },
1061
+ "node_modules/eventemitter3": {
1062
+ "version": "5.0.1",
1063
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
1064
+ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
1065
+ "dev": true,
1066
+ "license": "MIT"
1067
+ },
1068
+ "node_modules/execa": {
1069
+ "version": "5.1.1",
1070
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
1071
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
1072
+ "dev": true,
1073
+ "license": "MIT",
1074
+ "dependencies": {
1075
+ "cross-spawn": "^7.0.3",
1076
+ "get-stream": "^6.0.0",
1077
+ "human-signals": "^2.1.0",
1078
+ "is-stream": "^2.0.0",
1079
+ "merge-stream": "^2.0.0",
1080
+ "npm-run-path": "^4.0.1",
1081
+ "onetime": "^5.1.2",
1082
+ "signal-exit": "^3.0.3",
1083
+ "strip-final-newline": "^2.0.0"
1084
+ },
1085
+ "engines": {
1086
+ "node": ">=10"
1087
+ },
1088
+ "funding": {
1089
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
1090
+ }
1091
+ },
1092
+ "node_modules/fast-deep-equal": {
1093
+ "version": "3.1.3",
1094
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
1095
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
1096
+ "dev": true,
1097
+ "license": "MIT"
1098
+ },
1099
+ "node_modules/fast-uri": {
1100
+ "version": "3.1.0",
1101
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
1102
+ "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
1103
+ "dev": true,
1104
+ "funding": [
1105
+ {
1106
+ "type": "github",
1107
+ "url": "https://github.com/sponsors/fastify"
1108
+ },
1109
+ {
1110
+ "type": "opencollective",
1111
+ "url": "https://opencollective.com/fastify"
1112
+ }
1113
+ ],
1114
+ "license": "BSD-3-Clause"
1115
+ },
1116
+ "node_modules/get-east-asian-width": {
1117
+ "version": "1.4.0",
1118
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
1119
+ "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
1120
+ "dev": true,
1121
+ "license": "MIT",
1122
+ "engines": {
1123
+ "node": ">=18"
1124
+ },
1125
+ "funding": {
1126
+ "url": "https://github.com/sponsors/sindresorhus"
1127
+ }
1128
+ },
1129
+ "node_modules/get-stream": {
1130
+ "version": "6.0.1",
1131
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
1132
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
1133
+ "dev": true,
1134
+ "license": "MIT",
1135
+ "engines": {
1136
+ "node": ">=10"
1137
+ },
1138
+ "funding": {
1139
+ "url": "https://github.com/sponsors/sindresorhus"
1140
+ }
1141
+ },
1142
+ "node_modules/handlebars": {
1143
+ "version": "4.7.9",
1144
+ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz",
1145
+ "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==",
1146
+ "license": "MIT",
1147
+ "dependencies": {
1148
+ "minimist": "^1.2.5",
1149
+ "neo-async": "^2.6.2",
1150
+ "source-map": "^0.6.1",
1151
+ "wordwrap": "^1.0.0"
1152
+ },
1153
+ "bin": {
1154
+ "handlebars": "bin/handlebars"
1155
+ },
1156
+ "engines": {
1157
+ "node": ">=0.4.7"
1158
+ },
1159
+ "optionalDependencies": {
1160
+ "uglify-js": "^3.1.4"
1161
+ }
1162
+ },
1163
+ "node_modules/human-signals": {
1164
+ "version": "2.1.0",
1165
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
1166
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
1167
+ "dev": true,
1168
+ "license": "Apache-2.0",
1169
+ "engines": {
1170
+ "node": ">=10.17.0"
1171
+ }
1172
+ },
1173
+ "node_modules/husky": {
1174
+ "version": "9.1.7",
1175
+ "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
1176
+ "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==",
1177
+ "dev": true,
1178
+ "license": "MIT",
1179
+ "bin": {
1180
+ "husky": "bin.js"
1181
+ },
1182
+ "engines": {
1183
+ "node": ">=18"
1184
+ },
1185
+ "funding": {
1186
+ "url": "https://github.com/sponsors/typicode"
1187
+ }
1188
+ },
1189
+ "node_modules/ini": {
1190
+ "version": "1.3.8",
1191
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
1192
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
1193
+ "dev": true,
1194
+ "license": "ISC"
1195
+ },
1196
+ "node_modules/is-docker": {
1197
+ "version": "2.2.1",
1198
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
1199
+ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
1200
+ "dev": true,
1201
+ "license": "MIT",
1202
+ "bin": {
1203
+ "is-docker": "cli.js"
1204
+ },
1205
+ "engines": {
1206
+ "node": ">=8"
1207
+ },
1208
+ "funding": {
1209
+ "url": "https://github.com/sponsors/sindresorhus"
1210
+ }
1211
+ },
1212
+ "node_modules/is-fullwidth-code-point": {
1213
+ "version": "3.0.0",
1214
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
1215
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
1216
+ "dev": true,
1217
+ "license": "MIT",
1218
+ "engines": {
1219
+ "node": ">=8"
1220
+ }
1221
+ },
1222
+ "node_modules/is-port-reachable": {
1223
+ "version": "4.0.0",
1224
+ "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz",
1225
+ "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==",
1226
+ "dev": true,
1227
+ "license": "MIT",
1228
+ "engines": {
1229
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
1230
+ },
1231
+ "funding": {
1232
+ "url": "https://github.com/sponsors/sindresorhus"
1233
+ }
1234
+ },
1235
+ "node_modules/is-stream": {
1236
+ "version": "2.0.1",
1237
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
1238
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
1239
+ "dev": true,
1240
+ "license": "MIT",
1241
+ "engines": {
1242
+ "node": ">=8"
1243
+ },
1244
+ "funding": {
1245
+ "url": "https://github.com/sponsors/sindresorhus"
1246
+ }
1247
+ },
1248
+ "node_modules/is-wsl": {
1249
+ "version": "2.2.0",
1250
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
1251
+ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
1252
+ "dev": true,
1253
+ "license": "MIT",
1254
+ "dependencies": {
1255
+ "is-docker": "^2.0.0"
1256
+ },
1257
+ "engines": {
1258
+ "node": ">=8"
1259
+ }
1260
+ },
1261
+ "node_modules/isexe": {
1262
+ "version": "2.0.0",
1263
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
1264
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
1265
+ "dev": true,
1266
+ "license": "ISC"
1267
+ },
1268
+ "node_modules/js-yaml": {
1269
+ "version": "4.1.1",
1270
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
1271
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
1272
+ "license": "MIT",
1273
+ "dependencies": {
1274
+ "argparse": "^2.0.1"
1275
+ },
1276
+ "bin": {
1277
+ "js-yaml": "bin/js-yaml.js"
1278
+ }
1279
+ },
1280
+ "node_modules/json-schema-traverse": {
1281
+ "version": "1.0.0",
1282
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
1283
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
1284
+ "dev": true,
1285
+ "license": "MIT"
1286
+ },
1287
+ "node_modules/linkify-it": {
1288
+ "version": "5.0.0",
1289
+ "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
1290
+ "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
1291
+ "license": "MIT",
1292
+ "dependencies": {
1293
+ "uc.micro": "^2.0.0"
1294
+ }
1295
+ },
1296
+ "node_modules/lint-staged": {
1297
+ "version": "16.4.0",
1298
+ "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.4.0.tgz",
1299
+ "integrity": "sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==",
1300
+ "dev": true,
1301
+ "license": "MIT",
1302
+ "dependencies": {
1303
+ "commander": "^14.0.3",
1304
+ "listr2": "^9.0.5",
1305
+ "picomatch": "^4.0.3",
1306
+ "string-argv": "^0.3.2",
1307
+ "tinyexec": "^1.0.4",
1308
+ "yaml": "^2.8.2"
1309
+ },
1310
+ "bin": {
1311
+ "lint-staged": "bin/lint-staged.js"
1312
+ },
1313
+ "engines": {
1314
+ "node": ">=20.17"
1315
+ },
1316
+ "funding": {
1317
+ "url": "https://opencollective.com/lint-staged"
1318
+ }
1319
+ },
1320
+ "node_modules/listr2": {
1321
+ "version": "9.0.5",
1322
+ "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz",
1323
+ "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==",
1324
+ "dev": true,
1325
+ "license": "MIT",
1326
+ "dependencies": {
1327
+ "cli-truncate": "^5.0.0",
1328
+ "colorette": "^2.0.20",
1329
+ "eventemitter3": "^5.0.1",
1330
+ "log-update": "^6.1.0",
1331
+ "rfdc": "^1.4.1",
1332
+ "wrap-ansi": "^9.0.0"
1333
+ },
1334
+ "engines": {
1335
+ "node": ">=20.0.0"
1336
+ }
1337
+ },
1338
+ "node_modules/listr2/node_modules/emoji-regex": {
1339
+ "version": "10.6.0",
1340
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
1341
+ "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
1342
+ "dev": true,
1343
+ "license": "MIT"
1344
+ },
1345
+ "node_modules/listr2/node_modules/string-width": {
1346
+ "version": "7.2.0",
1347
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
1348
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
1349
+ "dev": true,
1350
+ "license": "MIT",
1351
+ "dependencies": {
1352
+ "emoji-regex": "^10.3.0",
1353
+ "get-east-asian-width": "^1.0.0",
1354
+ "strip-ansi": "^7.1.0"
1355
+ },
1356
+ "engines": {
1357
+ "node": ">=18"
1358
+ },
1359
+ "funding": {
1360
+ "url": "https://github.com/sponsors/sindresorhus"
1361
+ }
1362
+ },
1363
+ "node_modules/listr2/node_modules/wrap-ansi": {
1364
+ "version": "9.0.2",
1365
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
1366
+ "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
1367
+ "dev": true,
1368
+ "license": "MIT",
1369
+ "dependencies": {
1370
+ "ansi-styles": "^6.2.1",
1371
+ "string-width": "^7.0.0",
1372
+ "strip-ansi": "^7.1.0"
1373
+ },
1374
+ "engines": {
1375
+ "node": ">=18"
1376
+ },
1377
+ "funding": {
1378
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
1379
+ }
1380
+ },
1381
+ "node_modules/log-update": {
1382
+ "version": "6.1.0",
1383
+ "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
1384
+ "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
1385
+ "dev": true,
1386
+ "license": "MIT",
1387
+ "dependencies": {
1388
+ "ansi-escapes": "^7.0.0",
1389
+ "cli-cursor": "^5.0.0",
1390
+ "slice-ansi": "^7.1.0",
1391
+ "strip-ansi": "^7.1.0",
1392
+ "wrap-ansi": "^9.0.0"
1393
+ },
1394
+ "engines": {
1395
+ "node": ">=18"
1396
+ },
1397
+ "funding": {
1398
+ "url": "https://github.com/sponsors/sindresorhus"
1399
+ }
1400
+ },
1401
+ "node_modules/log-update/node_modules/emoji-regex": {
1402
+ "version": "10.6.0",
1403
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
1404
+ "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
1405
+ "dev": true,
1406
+ "license": "MIT"
1407
+ },
1408
+ "node_modules/log-update/node_modules/string-width": {
1409
+ "version": "7.2.0",
1410
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
1411
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
1412
+ "dev": true,
1413
+ "license": "MIT",
1414
+ "dependencies": {
1415
+ "emoji-regex": "^10.3.0",
1416
+ "get-east-asian-width": "^1.0.0",
1417
+ "strip-ansi": "^7.1.0"
1418
+ },
1419
+ "engines": {
1420
+ "node": ">=18"
1421
+ },
1422
+ "funding": {
1423
+ "url": "https://github.com/sponsors/sindresorhus"
1424
+ }
1425
+ },
1426
+ "node_modules/log-update/node_modules/wrap-ansi": {
1427
+ "version": "9.0.2",
1428
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
1429
+ "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
1430
+ "dev": true,
1431
+ "license": "MIT",
1432
+ "dependencies": {
1433
+ "ansi-styles": "^6.2.1",
1434
+ "string-width": "^7.0.0",
1435
+ "strip-ansi": "^7.1.0"
1436
+ },
1437
+ "engines": {
1438
+ "node": ">=18"
1439
+ },
1440
+ "funding": {
1441
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
1442
+ }
1443
+ },
1444
+ "node_modules/markdown-it": {
1445
+ "version": "14.1.1",
1446
+ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz",
1447
+ "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==",
1448
+ "license": "MIT",
1449
+ "dependencies": {
1450
+ "argparse": "^2.0.1",
1451
+ "entities": "^4.4.0",
1452
+ "linkify-it": "^5.0.0",
1453
+ "mdurl": "^2.0.0",
1454
+ "punycode.js": "^2.3.1",
1455
+ "uc.micro": "^2.1.0"
1456
+ },
1457
+ "bin": {
1458
+ "markdown-it": "bin/markdown-it.mjs"
1459
+ }
1460
+ },
1461
+ "node_modules/markdown-it/node_modules/entities": {
1462
+ "version": "4.5.0",
1463
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
1464
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
1465
+ "license": "BSD-2-Clause",
1466
+ "engines": {
1467
+ "node": ">=0.12"
1468
+ },
1469
+ "funding": {
1470
+ "url": "https://github.com/fb55/entities?sponsor=1"
1471
+ }
1472
+ },
1473
+ "node_modules/mdurl": {
1474
+ "version": "2.0.0",
1475
+ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
1476
+ "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
1477
+ "license": "MIT"
1478
+ },
1479
+ "node_modules/merge-stream": {
1480
+ "version": "2.0.0",
1481
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
1482
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
1483
+ "dev": true,
1484
+ "license": "MIT"
1485
+ },
1486
+ "node_modules/mime-db": {
1487
+ "version": "1.54.0",
1488
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
1489
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
1490
+ "dev": true,
1491
+ "license": "MIT",
1492
+ "engines": {
1493
+ "node": ">= 0.6"
1494
+ }
1495
+ },
1496
+ "node_modules/mime-types": {
1497
+ "version": "2.1.18",
1498
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
1499
+ "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
1500
+ "dev": true,
1501
+ "license": "MIT",
1502
+ "dependencies": {
1503
+ "mime-db": "~1.33.0"
1504
+ },
1505
+ "engines": {
1506
+ "node": ">= 0.6"
1507
+ }
1508
+ },
1509
+ "node_modules/mime-types/node_modules/mime-db": {
1510
+ "version": "1.33.0",
1511
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
1512
+ "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==",
1513
+ "dev": true,
1514
+ "license": "MIT",
1515
+ "engines": {
1516
+ "node": ">= 0.6"
1517
+ }
1518
+ },
1519
+ "node_modules/mimic-fn": {
1520
+ "version": "2.1.0",
1521
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
1522
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
1523
+ "dev": true,
1524
+ "license": "MIT",
1525
+ "engines": {
1526
+ "node": ">=6"
1527
+ }
1528
+ },
1529
+ "node_modules/mimic-function": {
1530
+ "version": "5.0.1",
1531
+ "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
1532
+ "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
1533
+ "dev": true,
1534
+ "license": "MIT",
1535
+ "engines": {
1536
+ "node": ">=18"
1537
+ },
1538
+ "funding": {
1539
+ "url": "https://github.com/sponsors/sindresorhus"
1540
+ }
1541
+ },
1542
+ "node_modules/minimatch": {
1543
+ "version": "3.1.5",
1544
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
1545
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
1546
+ "dev": true,
1547
+ "license": "ISC",
1548
+ "dependencies": {
1549
+ "brace-expansion": "^1.1.7"
1550
+ },
1551
+ "engines": {
1552
+ "node": "*"
1553
+ }
1554
+ },
1555
+ "node_modules/minimist": {
1556
+ "version": "1.2.8",
1557
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
1558
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
1559
+ "license": "MIT",
1560
+ "funding": {
1561
+ "url": "https://github.com/sponsors/ljharb"
1562
+ }
1563
+ },
1564
+ "node_modules/ms": {
1565
+ "version": "2.0.0",
1566
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
1567
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
1568
+ "dev": true,
1569
+ "license": "MIT"
1570
+ },
1571
+ "node_modules/negotiator": {
1572
+ "version": "0.6.4",
1573
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
1574
+ "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
1575
+ "dev": true,
1576
+ "license": "MIT",
1577
+ "engines": {
1578
+ "node": ">= 0.6"
1579
+ }
1580
+ },
1581
+ "node_modules/neo-async": {
1582
+ "version": "2.6.2",
1583
+ "resolved": "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz",
1584
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
1585
+ "license": "MIT"
1586
+ },
1587
+ "node_modules/npm-run-path": {
1588
+ "version": "4.0.1",
1589
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
1590
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
1591
+ "dev": true,
1592
+ "license": "MIT",
1593
+ "dependencies": {
1594
+ "path-key": "^3.0.0"
1595
+ },
1596
+ "engines": {
1597
+ "node": ">=8"
1598
+ }
1599
+ },
1600
+ "node_modules/on-headers": {
1601
+ "version": "1.1.0",
1602
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
1603
+ "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
1604
+ "dev": true,
1605
+ "license": "MIT",
1606
+ "engines": {
1607
+ "node": ">= 0.8"
1608
+ }
1609
+ },
1610
+ "node_modules/onetime": {
1611
+ "version": "5.1.2",
1612
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
1613
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
1614
+ "dev": true,
1615
+ "license": "MIT",
1616
+ "dependencies": {
1617
+ "mimic-fn": "^2.1.0"
1618
+ },
1619
+ "engines": {
1620
+ "node": ">=6"
1621
+ },
1622
+ "funding": {
1623
+ "url": "https://github.com/sponsors/sindresorhus"
1624
+ }
1625
+ },
1626
+ "node_modules/path-is-inside": {
1627
+ "version": "1.0.2",
1628
+ "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
1629
+ "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==",
1630
+ "dev": true,
1631
+ "license": "(WTFPL OR MIT)"
1632
+ },
1633
+ "node_modules/path-key": {
1634
+ "version": "3.1.1",
1635
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
1636
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
1637
+ "dev": true,
1638
+ "license": "MIT",
1639
+ "engines": {
1640
+ "node": ">=8"
1641
+ }
1642
+ },
1643
+ "node_modules/path-to-regexp": {
1644
+ "version": "3.3.0",
1645
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz",
1646
+ "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==",
1647
+ "dev": true,
1648
+ "license": "MIT"
1649
+ },
1650
+ "node_modules/picomatch": {
1651
+ "version": "4.0.4",
1652
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
1653
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
1654
+ "dev": true,
1655
+ "license": "MIT",
1656
+ "engines": {
1657
+ "node": ">=12"
1658
+ },
1659
+ "funding": {
1660
+ "url": "https://github.com/sponsors/jonschlinkert"
1661
+ }
1662
+ },
1663
+ "node_modules/prettier": {
1664
+ "version": "3.8.3",
1665
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz",
1666
+ "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==",
1667
+ "dev": true,
1668
+ "license": "MIT",
1669
+ "bin": {
1670
+ "prettier": "bin/prettier.cjs"
1671
+ },
1672
+ "engines": {
1673
+ "node": ">=14"
1674
+ },
1675
+ "funding": {
1676
+ "url": "https://github.com/prettier/prettier?sponsor=1"
1677
+ }
1678
+ },
1679
+ "node_modules/punycode.js": {
1680
+ "version": "2.3.1",
1681
+ "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
1682
+ "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
1683
+ "license": "MIT",
1684
+ "engines": {
1685
+ "node": ">=6"
1686
+ }
1687
+ },
1688
+ "node_modules/range-parser": {
1689
+ "version": "1.2.0",
1690
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
1691
+ "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==",
1692
+ "dev": true,
1693
+ "license": "MIT",
1694
+ "engines": {
1695
+ "node": ">= 0.6"
1696
+ }
1697
+ },
1698
+ "node_modules/rc": {
1699
+ "version": "1.2.8",
1700
+ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
1701
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
1702
+ "dev": true,
1703
+ "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
1704
+ "dependencies": {
1705
+ "deep-extend": "^0.6.0",
1706
+ "ini": "~1.3.0",
1707
+ "minimist": "^1.2.0",
1708
+ "strip-json-comments": "~2.0.1"
1709
+ },
1710
+ "bin": {
1711
+ "rc": "cli.js"
1712
+ }
1713
+ },
1714
+ "node_modules/registry-auth-token": {
1715
+ "version": "3.3.2",
1716
+ "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz",
1717
+ "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==",
1718
+ "dev": true,
1719
+ "license": "MIT",
1720
+ "dependencies": {
1721
+ "rc": "^1.1.6",
1722
+ "safe-buffer": "^5.0.1"
1723
+ }
1724
+ },
1725
+ "node_modules/registry-url": {
1726
+ "version": "3.1.0",
1727
+ "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz",
1728
+ "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==",
1729
+ "dev": true,
1730
+ "license": "MIT",
1731
+ "dependencies": {
1732
+ "rc": "^1.0.1"
1733
+ },
1734
+ "engines": {
1735
+ "node": ">=0.10.0"
1736
+ }
1737
+ },
1738
+ "node_modules/require-from-string": {
1739
+ "version": "2.0.2",
1740
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
1741
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
1742
+ "dev": true,
1743
+ "license": "MIT",
1744
+ "engines": {
1745
+ "node": ">=0.10.0"
1746
+ }
1747
+ },
1748
+ "node_modules/restore-cursor": {
1749
+ "version": "5.1.0",
1750
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
1751
+ "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
1752
+ "dev": true,
1753
+ "license": "MIT",
1754
+ "dependencies": {
1755
+ "onetime": "^7.0.0",
1756
+ "signal-exit": "^4.1.0"
1757
+ },
1758
+ "engines": {
1759
+ "node": ">=18"
1760
+ },
1761
+ "funding": {
1762
+ "url": "https://github.com/sponsors/sindresorhus"
1763
+ }
1764
+ },
1765
+ "node_modules/restore-cursor/node_modules/onetime": {
1766
+ "version": "7.0.0",
1767
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
1768
+ "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
1769
+ "dev": true,
1770
+ "license": "MIT",
1771
+ "dependencies": {
1772
+ "mimic-function": "^5.0.0"
1773
+ },
1774
+ "engines": {
1775
+ "node": ">=18"
1776
+ },
1777
+ "funding": {
1778
+ "url": "https://github.com/sponsors/sindresorhus"
1779
+ }
1780
+ },
1781
+ "node_modules/restore-cursor/node_modules/signal-exit": {
1782
+ "version": "4.1.0",
1783
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
1784
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
1785
+ "dev": true,
1786
+ "license": "ISC",
1787
+ "engines": {
1788
+ "node": ">=14"
1789
+ },
1790
+ "funding": {
1791
+ "url": "https://github.com/sponsors/isaacs"
1792
+ }
1793
+ },
1794
+ "node_modules/rfdc": {
1795
+ "version": "1.4.1",
1796
+ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
1797
+ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
1798
+ "dev": true,
1799
+ "license": "MIT"
1800
+ },
1801
+ "node_modules/rss-parser": {
1802
+ "version": "3.13.0",
1803
+ "resolved": "https://registry.npmjs.org/rss-parser/-/rss-parser-3.13.0.tgz",
1804
+ "integrity": "sha512-7jWUBV5yGN3rqMMj7CZufl/291QAhvrrGpDNE4k/02ZchL0npisiYYqULF71jCEKoIiHvK/Q2e6IkDwPziT7+w==",
1805
+ "license": "MIT",
1806
+ "dependencies": {
1807
+ "entities": "^2.0.3",
1808
+ "xml2js": "^0.5.0"
1809
+ }
1810
+ },
1811
+ "node_modules/safe-buffer": {
1812
+ "version": "5.2.1",
1813
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1814
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
1815
+ "dev": true,
1816
+ "funding": [
1817
+ {
1818
+ "type": "github",
1819
+ "url": "https://github.com/sponsors/feross"
1820
+ },
1821
+ {
1822
+ "type": "patreon",
1823
+ "url": "https://www.patreon.com/feross"
1824
+ },
1825
+ {
1826
+ "type": "consulting",
1827
+ "url": "https://feross.org/support"
1828
+ }
1829
+ ],
1830
+ "license": "MIT"
1831
+ },
1832
+ "node_modules/sax": {
1833
+ "version": "1.4.3",
1834
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz",
1835
+ "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==",
1836
+ "license": "BlueOak-1.0.0"
1837
+ },
1838
+ "node_modules/serve": {
1839
+ "version": "14.2.6",
1840
+ "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.6.tgz",
1841
+ "integrity": "sha512-QEjUSA+sD4Rotm1znR8s50YqA3kYpRGPmtd5GlFxbaL9n/FdUNbqMhxClqdditSk0LlZyA/dhud6XNRTOC9x2Q==",
1842
+ "dev": true,
1843
+ "license": "MIT",
1844
+ "dependencies": {
1845
+ "@zeit/schemas": "2.36.0",
1846
+ "ajv": "8.18.0",
1847
+ "arg": "5.0.2",
1848
+ "boxen": "7.0.0",
1849
+ "chalk": "5.0.1",
1850
+ "chalk-template": "0.4.0",
1851
+ "clipboardy": "3.0.0",
1852
+ "compression": "1.8.1",
1853
+ "is-port-reachable": "4.0.0",
1854
+ "serve-handler": "6.1.7",
1855
+ "update-check": "1.5.4"
1856
+ },
1857
+ "bin": {
1858
+ "serve": "build/main.js"
1859
+ },
1860
+ "engines": {
1861
+ "node": ">= 14"
1862
+ }
1863
+ },
1864
+ "node_modules/serve-handler": {
1865
+ "version": "6.1.7",
1866
+ "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.7.tgz",
1867
+ "integrity": "sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==",
1868
+ "dev": true,
1869
+ "license": "MIT",
1870
+ "dependencies": {
1871
+ "bytes": "3.0.0",
1872
+ "content-disposition": "0.5.2",
1873
+ "mime-types": "2.1.18",
1874
+ "minimatch": "3.1.5",
1875
+ "path-is-inside": "1.0.2",
1876
+ "path-to-regexp": "3.3.0",
1877
+ "range-parser": "1.2.0"
1878
+ }
1879
+ },
1880
+ "node_modules/shebang-command": {
1881
+ "version": "2.0.0",
1882
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
1883
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
1884
+ "dev": true,
1885
+ "license": "MIT",
1886
+ "dependencies": {
1887
+ "shebang-regex": "^3.0.0"
1888
+ },
1889
+ "engines": {
1890
+ "node": ">=8"
1891
+ }
1892
+ },
1893
+ "node_modules/shebang-regex": {
1894
+ "version": "3.0.0",
1895
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
1896
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
1897
+ "dev": true,
1898
+ "license": "MIT",
1899
+ "engines": {
1900
+ "node": ">=8"
1901
+ }
1902
+ },
1903
+ "node_modules/signal-exit": {
1904
+ "version": "3.0.7",
1905
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
1906
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
1907
+ "dev": true,
1908
+ "license": "ISC"
1909
+ },
1910
+ "node_modules/slice-ansi": {
1911
+ "version": "7.1.2",
1912
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz",
1913
+ "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==",
1914
+ "dev": true,
1915
+ "license": "MIT",
1916
+ "dependencies": {
1917
+ "ansi-styles": "^6.2.1",
1918
+ "is-fullwidth-code-point": "^5.0.0"
1919
+ },
1920
+ "engines": {
1921
+ "node": ">=18"
1922
+ },
1923
+ "funding": {
1924
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
1925
+ }
1926
+ },
1927
+ "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": {
1928
+ "version": "5.1.0",
1929
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
1930
+ "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
1931
+ "dev": true,
1932
+ "license": "MIT",
1933
+ "dependencies": {
1934
+ "get-east-asian-width": "^1.3.1"
1935
+ },
1936
+ "engines": {
1937
+ "node": ">=18"
1938
+ },
1939
+ "funding": {
1940
+ "url": "https://github.com/sponsors/sindresorhus"
1941
+ }
1942
+ },
1943
+ "node_modules/source-map": {
1944
+ "version": "0.6.1",
1945
+ "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz",
1946
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
1947
+ "license": "BSD-3-Clause",
1948
+ "engines": {
1949
+ "node": ">=0.10.0"
1950
+ }
1951
+ },
1952
+ "node_modules/string-argv": {
1953
+ "version": "0.3.2",
1954
+ "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
1955
+ "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==",
1956
+ "dev": true,
1957
+ "license": "MIT",
1958
+ "engines": {
1959
+ "node": ">=0.6.19"
1960
+ }
1961
+ },
1962
+ "node_modules/string-width": {
1963
+ "version": "5.1.2",
1964
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
1965
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
1966
+ "dev": true,
1967
+ "license": "MIT",
1968
+ "dependencies": {
1969
+ "eastasianwidth": "^0.2.0",
1970
+ "emoji-regex": "^9.2.2",
1971
+ "strip-ansi": "^7.0.1"
1972
+ },
1973
+ "engines": {
1974
+ "node": ">=12"
1975
+ },
1976
+ "funding": {
1977
+ "url": "https://github.com/sponsors/sindresorhus"
1978
+ }
1979
+ },
1980
+ "node_modules/strip-ansi": {
1981
+ "version": "7.1.0",
1982
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
1983
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
1984
+ "dev": true,
1985
+ "license": "MIT",
1986
+ "dependencies": {
1987
+ "ansi-regex": "^6.0.1"
1988
+ },
1989
+ "engines": {
1990
+ "node": ">=12"
1991
+ },
1992
+ "funding": {
1993
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
1994
+ }
1995
+ },
1996
+ "node_modules/strip-final-newline": {
1997
+ "version": "2.0.0",
1998
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
1999
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
2000
+ "dev": true,
2001
+ "license": "MIT",
2002
+ "engines": {
2003
+ "node": ">=6"
2004
+ }
2005
+ },
2006
+ "node_modules/strip-json-comments": {
2007
+ "version": "2.0.1",
2008
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
2009
+ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
2010
+ "dev": true,
2011
+ "license": "MIT",
2012
+ "engines": {
2013
+ "node": ">=0.10.0"
2014
+ }
2015
+ },
2016
+ "node_modules/tinyexec": {
2017
+ "version": "1.0.4",
2018
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz",
2019
+ "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==",
2020
+ "dev": true,
2021
+ "license": "MIT",
2022
+ "engines": {
2023
+ "node": ">=18"
2024
+ }
2025
+ },
2026
+ "node_modules/type-fest": {
2027
+ "version": "2.19.0",
2028
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
2029
+ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
2030
+ "dev": true,
2031
+ "license": "(MIT OR CC0-1.0)",
2032
+ "engines": {
2033
+ "node": ">=12.20"
2034
+ },
2035
+ "funding": {
2036
+ "url": "https://github.com/sponsors/sindresorhus"
2037
+ }
2038
+ },
2039
+ "node_modules/uc.micro": {
2040
+ "version": "2.1.0",
2041
+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
2042
+ "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
2043
+ "license": "MIT"
2044
+ },
2045
+ "node_modules/uglify-js": {
2046
+ "version": "3.19.3",
2047
+ "resolved": "https://registry.npmmirror.com/uglify-js/-/uglify-js-3.19.3.tgz",
2048
+ "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
2049
+ "license": "BSD-2-Clause",
2050
+ "optional": true,
2051
+ "bin": {
2052
+ "uglifyjs": "bin/uglifyjs"
2053
+ },
2054
+ "engines": {
2055
+ "node": ">=0.8.0"
2056
+ }
2057
+ },
2058
+ "node_modules/update-check": {
2059
+ "version": "1.5.4",
2060
+ "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz",
2061
+ "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==",
2062
+ "dev": true,
2063
+ "license": "MIT",
2064
+ "dependencies": {
2065
+ "registry-auth-token": "3.3.2",
2066
+ "registry-url": "3.1.0"
2067
+ }
2068
+ },
2069
+ "node_modules/vary": {
2070
+ "version": "1.1.2",
2071
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
2072
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
2073
+ "dev": true,
2074
+ "license": "MIT",
2075
+ "engines": {
2076
+ "node": ">= 0.8"
2077
+ }
2078
+ },
2079
+ "node_modules/which": {
2080
+ "version": "2.0.2",
2081
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
2082
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
2083
+ "dev": true,
2084
+ "license": "ISC",
2085
+ "dependencies": {
2086
+ "isexe": "^2.0.0"
2087
+ },
2088
+ "bin": {
2089
+ "node-which": "bin/node-which"
2090
+ },
2091
+ "engines": {
2092
+ "node": ">= 8"
2093
+ }
2094
+ },
2095
+ "node_modules/widest-line": {
2096
+ "version": "4.0.1",
2097
+ "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz",
2098
+ "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==",
2099
+ "dev": true,
2100
+ "license": "MIT",
2101
+ "dependencies": {
2102
+ "string-width": "^5.0.1"
2103
+ },
2104
+ "engines": {
2105
+ "node": ">=12"
2106
+ },
2107
+ "funding": {
2108
+ "url": "https://github.com/sponsors/sindresorhus"
2109
+ }
2110
+ },
2111
+ "node_modules/wordwrap": {
2112
+ "version": "1.0.0",
2113
+ "resolved": "https://registry.npmmirror.com/wordwrap/-/wordwrap-1.0.0.tgz",
2114
+ "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
2115
+ "license": "MIT"
2116
+ },
2117
+ "node_modules/wrap-ansi": {
2118
+ "version": "8.1.0",
2119
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
2120
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
2121
+ "dev": true,
2122
+ "license": "MIT",
2123
+ "dependencies": {
2124
+ "ansi-styles": "^6.1.0",
2125
+ "string-width": "^5.0.1",
2126
+ "strip-ansi": "^7.0.1"
2127
+ },
2128
+ "engines": {
2129
+ "node": ">=12"
2130
+ },
2131
+ "funding": {
2132
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
2133
+ }
2134
+ },
2135
+ "node_modules/xml2js": {
2136
+ "version": "0.5.0",
2137
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
2138
+ "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
2139
+ "license": "MIT",
2140
+ "dependencies": {
2141
+ "sax": ">=0.6.0",
2142
+ "xmlbuilder": "~11.0.0"
2143
+ },
2144
+ "engines": {
2145
+ "node": ">=4.0.0"
2146
+ }
2147
+ },
2148
+ "node_modules/xmlbuilder": {
2149
+ "version": "11.0.1",
2150
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
2151
+ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
2152
+ "license": "MIT",
2153
+ "engines": {
2154
+ "node": ">=4.0"
2155
+ }
2156
+ },
2157
+ "node_modules/yaml": {
2158
+ "version": "2.8.2",
2159
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
2160
+ "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
2161
+ "dev": true,
2162
+ "license": "ISC",
2163
+ "bin": {
2164
+ "yaml": "bin.mjs"
2165
+ },
2166
+ "engines": {
2167
+ "node": ">= 14.6"
2168
+ },
2169
+ "funding": {
2170
+ "url": "https://github.com/sponsors/eemeli"
2171
+ }
2172
+ }
2173
+ }
2174
+ }
scripts/build-runtime.js ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const path = require('node:path');
2
+ const fs = require('node:fs');
3
+
4
+ const { createLogger, isVerbose, startTimer } = require('../src/generator/utils/logger');
5
+
6
+ const log = createLogger('bundle');
7
+
8
+ function ensureDir(dirPath) {
9
+ if (!fs.existsSync(dirPath)) {
10
+ fs.mkdirSync(dirPath, { recursive: true });
11
+ }
12
+ }
13
+
14
+ async function main() {
15
+ let esbuild;
16
+ try {
17
+ esbuild = require('esbuild');
18
+ } catch (error) {
19
+ log.error('未找到 esbuild,请先执行 npm install。');
20
+ process.exitCode = 1;
21
+ return;
22
+ }
23
+
24
+ const projectRoot = path.resolve(__dirname, '..');
25
+ const entry = path.join(projectRoot, 'src', 'runtime', 'index.js');
26
+ const outFile = path.join(projectRoot, 'dist', 'script.js');
27
+
28
+ if (!fs.existsSync(entry)) {
29
+ log.error('运行时入口不存在', { path: path.relative(projectRoot, entry) });
30
+ process.exitCode = 1;
31
+ return;
32
+ }
33
+
34
+ ensureDir(path.dirname(outFile));
35
+
36
+ try {
37
+ const elapsedMs = startTimer();
38
+ const result = await esbuild.build({
39
+ entryPoints: [entry],
40
+ outfile: outFile,
41
+ bundle: true,
42
+ platform: 'browser',
43
+ format: 'iife',
44
+ target: ['es2018'],
45
+ sourcemap: false,
46
+ minify: true,
47
+ legalComments: 'none',
48
+ metafile: true,
49
+ logLevel: 'silent',
50
+ });
51
+
52
+ const ms = elapsedMs();
53
+ const outputs =
54
+ result && result.metafile && result.metafile.outputs ? result.metafile.outputs : null;
55
+ const outKey = outputs ? Object.keys(outputs).find((k) => k.endsWith('dist/script.js')) : '';
56
+ const bytes = outKey && outputs && outputs[outKey] ? outputs[outKey].bytes : 0;
57
+
58
+ const meta = { ms };
59
+ if (bytes) meta.bytes = bytes;
60
+ log.ok('输出 dist/script.js', meta);
61
+ } catch (error) {
62
+ log.error('构建 dist/script.js 失败', {
63
+ message: error && error.message ? error.message : String(error),
64
+ });
65
+ if (isVerbose() && error && error.stack) {
66
+ console.error(error.stack);
67
+ }
68
+ process.exitCode = 1;
69
+ }
70
+ }
71
+
72
+ main();
scripts/build.js ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const path = require('node:path');
2
+ const { spawnSync } = require('node:child_process');
3
+
4
+ const { createLogger, isVerbose, startTimer } = require('../src/generator/utils/logger');
5
+
6
+ const log = createLogger('build');
7
+
8
+ function runNode(scriptPath) {
9
+ const result = spawnSync(process.execPath, [scriptPath], { stdio: 'inherit' });
10
+ return result && Number.isFinite(result.status) ? result.status : 1;
11
+ }
12
+
13
+ async function main() {
14
+ const elapsedMs = startTimer();
15
+ log.info('开始', { version: process.env.npm_package_version });
16
+
17
+ const repoRoot = path.resolve(__dirname, '..');
18
+
19
+ const cleanExit = runNode(path.join(repoRoot, 'scripts', 'clean.js'));
20
+ if (cleanExit !== 0) {
21
+ log.error('clean 失败', { exit: cleanExit });
22
+ process.exitCode = cleanExit;
23
+ return;
24
+ }
25
+
26
+ // best-effort:同步失败不阻断 build
27
+ const syncProjectsExit = runNode(path.join(repoRoot, 'scripts', 'sync-projects.js'));
28
+ if (syncProjectsExit !== 0)
29
+ log.warn('sync-projects 异常退出,已继续(best-effort)', { exit: syncProjectsExit });
30
+
31
+ const syncHeatmapExit = runNode(path.join(repoRoot, 'scripts', 'sync-heatmap.js'));
32
+ if (syncHeatmapExit !== 0)
33
+ log.warn('sync-heatmap 异常退出,已继续(best-effort)', { exit: syncHeatmapExit });
34
+
35
+ const syncArticlesExit = runNode(path.join(repoRoot, 'scripts', 'sync-articles.js'));
36
+ if (syncArticlesExit !== 0)
37
+ log.warn('sync-articles 异常退出,已继续(best-effort)', { exit: syncArticlesExit });
38
+
39
+ const generatorExit = runNode(path.join(repoRoot, 'src', 'generator.js'));
40
+ if (generatorExit !== 0) {
41
+ log.error('generate 失败', { exit: generatorExit });
42
+ process.exitCode = generatorExit;
43
+ return;
44
+ }
45
+
46
+ log.ok('完成', { ms: elapsedMs(), dist: 'dist/' });
47
+ }
48
+
49
+ if (require.main === module) {
50
+ main().catch((error) => {
51
+ log.error('构建失败', { message: error && error.message ? error.message : String(error) });
52
+ if (isVerbose() && error && error.stack) console.error(error.stack);
53
+ process.exitCode = 1;
54
+ });
55
+ }
scripts/check.js ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const path = require('node:path');
2
+ const { spawnSync } = require('node:child_process');
3
+
4
+ const { createLogger, isVerbose, startTimer } = require('../src/generator/utils/logger');
5
+
6
+ const log = createLogger('check');
7
+
8
+ function runNode(scriptPath) {
9
+ const result = spawnSync(process.execPath, [scriptPath], { stdio: 'inherit' });
10
+ return result && Number.isFinite(result.status) ? result.status : 1;
11
+ }
12
+
13
+ async function main() {
14
+ const elapsedMs = startTimer();
15
+ log.info('开始', { version: process.env.npm_package_version });
16
+
17
+ const repoRoot = path.resolve(__dirname, '..');
18
+
19
+ const lintExit = runNode(path.join(repoRoot, 'scripts', 'lint.js'));
20
+ if (lintExit !== 0) {
21
+ log.error('lint 失败', { exit: lintExit });
22
+ process.exitCode = lintExit;
23
+ return;
24
+ }
25
+
26
+ const testExit = runNode(path.join(repoRoot, 'scripts', 'test.js'));
27
+ if (testExit !== 0) {
28
+ log.error('test 失败', { exit: testExit });
29
+ process.exitCode = testExit;
30
+ return;
31
+ }
32
+
33
+ const buildExit = runNode(path.join(repoRoot, 'scripts', 'build.js'));
34
+ if (buildExit !== 0) {
35
+ log.error('build 失败', { exit: buildExit });
36
+ process.exitCode = buildExit;
37
+ return;
38
+ }
39
+
40
+ log.ok('完成', { ms: elapsedMs() });
41
+ }
42
+
43
+ if (require.main === module) {
44
+ main().catch((error) => {
45
+ log.error('执行失败', { message: error && error.message ? error.message : String(error) });
46
+ if (isVerbose() && error && error.stack) console.error(error.stack);
47
+ process.exitCode = 1;
48
+ });
49
+ }