diff --git a/README.md b/README.md
index 329dbb3..2df3d92 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
暖如灯火,静如留白。暖屿以柔和的色调和舒缓的节奏回归内容本身,为记录与阅读留出一方安静从容的角落。
-> 本项目由 AI 开发完成,人工仅进行部分功能测试及问题找寻
+> 本项目使用 AI 辅助开发。开发者已按 Halo 主题规范完成代码审查、构建产物核验、外部资源审计与主要模板的回归检查清单(见[验证与回归测试](#验证与回归测试));主题不加载远程脚本、不动态下载代码、不进行未披露的数据收集。
## 环境要求
@@ -13,14 +13,59 @@
1. **响应式设计** — 适配桌面端与移动端,支持暗色模式,移动端使用动态视口单位适配地址栏
2. **插件兼容性** — 适配 11 款常用 Halo 插件,安装即用;提供页面布局契约,插件前台页面自动复用主题外观
-3. **内置灯箱** — 主题内置 LightGallery 灯箱,支持 EXIF 信息展示;安装 lightgallery.js 插件后自动禁用
+3. **内置灯箱** — 主题内置 LightGallery 灯箱(GPLv3,随主题自托管打包),支持 EXIF 信息展示;安装 lightgallery.js 插件后自动禁用
4. **可配置性** — 17 个设置分组、122 个配置项,几乎所有模块可独立开关
-5. **性能优化** — 服务端字数统计(API 扩展包)、图片懒加载与响应式缩略图、首屏整屏吸附(仅首屏边界接管,内容区保留原生滚动)、Hero 离屏停绘、CSS/HTML 压缩;详见[开发指南 - 性能与滚动顺滑](docs/customize/development.md)
-6. **页面布局契约** — 提供 `templates/layout.html`,插件页面不再回退到 Halo 内置布局
+5. **性能优化** — 服务端字数统计(API 扩展包)、图片懒加载与响应式缩略图、首屏整屏吸附(仅首屏边界接管,内容区保留原生滚动)、Hero 离屏停绘、CSS/HTML 压缩;详见[开发指南 - 性能与滚动顺滑](https://nxxy335.top/WarmIsland/customize/development.html)
+6. **零默认外部请求** — 所有脚本、样式、字体、图标、句子库、主题图标均随主题自托管,默认不向任何第三方发起自动请求;详见[外部资源与隐私](#外部资源与隐私)
+7. **页面布局契约** — 提供 `templates/layout.html`,插件页面不再回退到 Halo 内置布局
## 预览
-
+
+
+> 预览图同时随安装包分发(包根目录 `WarmIsland Preview.webp`),离线查看制品时也可打开。
+
+## 外部资源与隐私
+
+本章节对应 [Halo 应用市场审核指南](https://docs.halo.run/developer-guide/app-store/app-review-guidelines) 1.4 / 4.4.4 / 5.1,逐项说明服务名称、用途、数据类型、默认状态与关闭方法。
+
+### 默认行为
+
+**默认配置下,主题不会自动向任何第三方服务发起请求。** 页面加载的 CSS、JS、字体(LXGW WenKai 本地分片)、图标、主题 Logo、一言句子库全部来自站点自身的 `/themes/warm-island/assets/`,不经过 CDN,也不使用 `preconnect` / `dns-prefetch`。已移除的远程依赖见下方[自托管与许可](#自托管与许可)。
+
+### 逐项说明
+
+| 服务 / 资源 | 用途 | 涉及的数据类型 | 默认状态 | 关闭 / 替换方法 |
+| --- | --- | --- | --- | --- |
+| 主题自身静态资源 `/themes/warm-island/assets/*` | 样式、脚本、字体、图标、Logo、内置句子库 | 无(同域请求,不含用户数据) | 始终启用,渲染必需 | 不可关闭;均为随包自托管文件 |
+| 自定义一言 API `hero_hitokoto_api` | Hero 首屏随机句子 | 无请求体;发起 GET 时第三方可见访客 IP / User-Agent | **关闭**:留空,使用 `assets/data/hitokoto.json` 内置本地句子库 | 主题设置 → Hero 首屏 → 清空「自定义一言 API」 |
+| 自定义 IP 定位主 / 备选 API `welcome_popup_api` / `welcome_popup_fallback_api` | 欢迎弹窗显示「欢迎来自 XX 的朋友」 | **访客 IP** 与第三方返回的粗略地理位置 | **关闭**:两项均留空,弹窗只显示本地欢迎语;且欢迎弹窗开关本身默认关闭 | 主题设置 → 欢迎弹窗 → 关闭开关,或清空两个 API 字段 |
+| 分享按钮:QQ / 微博 / X / Facebook / Telegram | 分享文章到对应平台 | 文章 URL 与标题(写在目标站 URL 参数中) | 默认开启 `qq,wechat,weibo`;**仅在访客点击后**跳转,页面加载时不请求 | 主题设置 → 文章 → 关闭「显示分享按钮」,或在「分享渠道」取消勾选 |
+| 微信分享弹层 | 生成并复制文章链接 | 文章 URL(浏览器本地字符串,不发送) | 与分享按钮一致 | 同上;已移除二维码远程库,改为「复制链接」 |
+| 页脚备案 / Halo / 应用市场外链 | 备案信息、致谢、插件安装提示 | 无(访客主动点击才跳转) | 由站点配置决定,默认仅显示 Halo 致谢 | 主题设置 → 页脚 → 关闭对应显示项 |
+| 插件前台页面(豆瓣、Steam、朋友圈、微信读书等) | 展示插件提供的数据 | 由对应插件决定,主题不发起请求 | 安装并启用对应插件后才出现 | 卸载 / 停用对应插件;主题自动降级隐藏 |
+| 内置 LightGallery 灯箱 | 图片放大预览、EXIF 展示 | 无(本地打包,不联网) | 默认启用 | 安装 lightgallery.js 插件后主题自动让位 |
+
+> 自定义 API 属于**站点管理员主动配置**的可选能力:一旦填写,浏览器会按设置向该第三方发起请求。请自行评估服务商的隐私政策,并在站点隐私说明中补充披露;主题不会代替站点做这项声明。
+
+### 隐私说明(5.1)
+
+| 项目 | 说明 |
+| --- | --- |
+| 收集的数据类型 | 默认配置下**无**。主题不读取、不上传用户信息、站点内容、访问日志或订单数据 |
+| 数据用途 | 仅用于页面渲染;自定义 API 场景下由管理员自行决定 |
+| 保存位置与期限 | 主题不写入数据库、不上传数据、不生成服务端日志。前端仅在访客本机使用 `sessionStorage`(欢迎弹窗「同会话仅显示一次」)与 `localStorage`(点赞状态),可随时清除浏览器站点数据 |
+| 是否传输到第三方 | 默认**否**。仅当管理员显式填写「自定义一言 API」或「自定义 IP 定位 API」时才会向该服务发起请求,其中 IP 定位会暴露访客 IP |
+| 关闭 / 撤销方式 | 见上表;清空相应设置项或关闭对应开关即可立即停止,无需重装主题 |
+| 遥测 / 统计 / 崩溃上报 / 广告 / AI 分析 / 跨站追踪 | **均不存在**。主题不含任何埋点、指纹或第三方统计脚本 |
+
+### 自托管与许可
+
+- **已移除的远程依赖**:`cdn.jsdelivr.net` 预连接与预解析(原出现在全部模板)、PrismJS 1.29.0 核心与 `autoloader`(会按代码语言从 CDN 动态拉取并执行组件)、`qrcode-generator`、默认的一言与 IP 定位接口。
+- **代码高亮**:主题不再自带 Prism.js 运行时。语法高亮请安装 Halo 应用市场的代码高亮插件,或启用「API 扩展包」的服务端代码高亮;主题只提供两套自托管的 `.token` 配色(`assets/vendor/prism-warm.css` / `prism-cold.css`),由「文章 → 代码高亮主题」切换。
+- **字体**:[霞鹜文楷 LXGW WenKai](https://github.com/lxgw/LxgwWenKai) 以 [SIL OFL 1.1](https://openfontlicense.org/) 发布,由 `cn-font-split` 切分为本地 woff2 分片随主题分发;字体来源与版权声明保留在 `public/assets/fonts/LXGWWenKai/result.css` 中,无远程字体请求。
+- **灯箱**:LightGallery 2.9.0 以 GPLv3 发布,本主题同样采用 GPL-3.0,按开源许可条款使用(非商业授权);已去除占位 `licenseKey`,控制台不再出现 `license key is not valid for production use`。
+- **图标**:来源与协议见[图标与字体来源](#图标与字体来源)。
## 已适配插件
@@ -29,7 +74,7 @@
| [瞬间](https://halo.run/store/apps/app-SnwWD) | `PluginMoments` | `/moments` | 瞬间动态页面,支持点赞、评论、标签筛选、顶部封面 |
| [图库](https://halo.run/store/apps/app-BmQJW) | `PluginPhotos` | `/photos` | 图片展示页面,支持分组、灯箱预览 |
| [友情链接](https://halo.run/store/apps/app-hfbQg) | `PluginLinks` | `/links` | 友链展示页面,支持随机排列、随机传送门、评论区开关 |
-| [朋友圈](https://github.com/halo-sigs/plugin-friends) | `plugin-friends` | `/friends` | RSS 订阅朋友圈页面 |
+| [朋友圈](https://www.halo.run/store/apps/app-yISsV) | `plugin-friends` | `/friends` | RSS 订阅朋友圈页面 |
| [装备管理](https://github.com/chengzhongxue/plugin-equipment) | `equipment` | `/equipments` | 装备展示页面,按分组展示,支持封面图 |
| [豆瓣](https://github.com/chengzhongxue/plugin-douban) | `plugin-douban` | `/douban` | 豆瓣记录页面,支持类型/状态/标签筛选,默认音乐;需插件 **v1.2.6+**(SQLite 数据结构) |
| [Steam 信息展示](https://github.com/Tim0x0/halo-plugin-steam) | `PluginSteam` | `/steam` | Steam 游戏库页面,含用户资料、统计数据、最近游玩、游戏库分页 |
@@ -54,16 +99,16 @@
### 首页
-- Hero 首屏:明暗双背景图、大标题/副标题独立配色、一言(支持自动刷新与淡入淡出过渡)、下滑箭头平滑滚动
-- 欢迎弹窗:基于访客 IP 地理定位的欢迎提示,支持 5 种位置、自定义 API 与会话去重开关
+- Hero 首屏:明暗双背景图、大标题/副标题独立配色、一言(默认使用内置本地句子库,可选自定义 API 与自动刷新)、下滑箭头平滑滚动
+- 欢迎弹窗:默认显示纯本地静态欢迎语,支持 5 种位置、会话去重开关;仅在显式配置自定义 IP 定位 API 时才显示访客所在地(见[外部资源与隐私](#外部资源与隐私))
- 滚动吸附:首屏与内容区之间滚轮/触摸自动吸附,进入内容区后恢复原生自由滚动
- 文章卡片:可配置显示字数(依赖 API 扩展包)、阅读量、分类、标签、摘要行数
### 文章
- 目录(TOC):桌面端侧栏 + 移动端抽屉,滚动高亮、展开模式可选
-- 上下篇导航:悬停反馈与方向箭头动画
-- 相关推荐、分享按钮、赞赏、版权声明可独立开关
+- 代码高亮:由代码高亮插件 / API 扩展包输出,主题提供 Warm / Cold 两套本地配色;主题不加载任何远程高亮脚本
+- 上下篇导航、相关推荐、分享按钮、赞赏、版权声明可独立开关;微信分享改为复制文章链接,无需二维码库
- 字数与阅读时间:优先使用 API 扩展包的服务端统计,未安装时前端计算兜底
### 全局
@@ -76,7 +121,7 @@
## 灯箱集成
-主题内置 LightGallery 灯箱,为图片提供放大预览功能,并支持 EXIF 信息展示。
+主题内置 LightGallery 灯箱,为图片提供放大预览功能,并支持 EXIF 信息展示。该库以 GPLv3 发布,本主题以 GPL-3.0 开源,按开源许可条款自托管使用;控制台不会出现商业授权提示。
### lightgallery.js 插件兼容
@@ -98,7 +143,7 @@
3. 安装完成后点击「启用」
> **升级后首页 404「资源不存在 / Template index was not found」**:说明上传的 ZIP 里没有 `templates/`
-> 模板文件(从源码仓库打包时漏了构建步骤)。请换用完整安装包重新上传,详见 [FAQ 安装相关](docs/faq.md)。
+> 模板文件(从源码仓库打包时漏了构建步骤)。请换用完整安装包重新上传,详见 [FAQ 安装相关](https://nxxy335.top/WarmIsland/faq.html)。
>
> **升级后如果整站没了样式**:Halo 页面缓存或前置反向代理里可能还留着上一版本的 HTML,
> 它引用的 CSS 文件名带内容哈希,升级后已被删除,请求会直接 500。
@@ -107,7 +152,7 @@
>
> **升级主题或修改配色后,部分页面不生效**:如果安装了「页面静态缓存」插件,
> 它只随文章变更自动刷新,主题层的变更需要手动点一次「刷新页面缓存」,
-> 详见 [FAQ:升级主题或修改配色后部分页面不生效](docs/faq.md#升级主题或修改配色后部分页面的新样式配色不生效)。
+> 详见 [FAQ:升级主题或修改配色后部分页面不生效](https://nxxy335.top/WarmIsland/faq.html)。
或从源码构建:
@@ -115,11 +160,11 @@
git clone https://git.nxxy335.top/nxxy335/halo-theme-WarmIsland.git
cd halo-theme-WarmIsland
pnpm install
-pnpm release # build → snapshot → package
+pnpm release # build → snapshot → package(含外部资源审计)
```
`pnpm build` 只生成 `templates/`(模板 + 静态资源),不会产出 ZIP;
-安装包由 `pnpm package` 生成到 `dist/warm-island-.zip`。
+安装包由 `pnpm package` 生成到 `dist/warm-island-.zip`。安装包同时包含 `docs/*.md` 与预览图,便于离线查阅上架说明中引用的文档。
## 开发
@@ -136,6 +181,9 @@ pnpm build
# 把本次构建的哈希资源快照进 compat/assets(发版前必做)
pnpm snapshot
+# 外部资源 / 隐私 / README 链接审计(打包时也会自动执行)
+pnpm audit:external
+
# 打包 dist/warm-island-.zip
pnpm package
@@ -145,7 +193,8 @@ pnpm smoke
发版流程:`pnpm release`(等价于 `pnpm build` → `pnpm snapshot` → `pnpm package`),
并把 `compat/assets` 的新增文件一起提交。`pnpm package` 会校验模板、资源目录与版本号一致性,
-产物缺失时直接报错,避免打出不含 `templates/` 的空壳安装包。
+并执行外部资源审计(禁止第三方预连接、远程脚本、动态加载、无效 README 链接、重复大图),
+任一不通过都会直接报错,避免打出不合规或不含 `templates/` 的空壳安装包。
### 目录结构
@@ -155,9 +204,13 @@ pnpm smoke
│ ├── layouts/ # Layout.astro(含页面布局契约渲染模式)
│ ├── pages/ # 页面模板(构建后输出到 templates/)
│ └── styles/ # SCSS 样式(正文样式在 _content.scss,全局生效)
-├── compat/
-│ └── assets/ # 历史版本哈希静态资源,构建时合并进 templates/assets
-├── scripts/ # 构建后置、资源快照、打包、冒烟测试
+├── public/
+│ ├── assets/data/ # 内置本地一言句子库(静态 JSON)
+│ ├── assets/fonts/ # LXGW WenKai 本地字体分片
+│ ├── assets/vendor/ # 自托管的代码高亮配色 CSS
+│ └── assets/logo.png # 主题图标(唯一一份,256×256 压缩版)
+├── compat/assets/ # 历史版本哈希静态资源,构建时合并进 templates/assets
+├── scripts/ # 构建后置、资源快照、打包、外部资源审计、冒烟测试
├── templates/ # 构建产物,Halo 实际加载的模板
├── settings.yaml # 后台设置项定义
└── theme.yaml # 主题清单与自定义页面模板注册
@@ -167,14 +220,34 @@ pnpm smoke
- **静态资源文件名带内容哈希**:升级后旧文件被删除,被缓存的 HTML 仍会请求旧文件名并触发 500,
所以每个版本的哈希文件都要留一份(见 `compat/assets` 与 `pnpm snapshot`)
+- **不得引入远程脚本 / 样式 / 字体 / 预连接**:所有第三方资源必须自托管并标注许可,`pnpm package` 会拦截回归
- **Thymeleaf 字符串字面量必须加单引号**:`th:text="'文本'"`,裸中文遇到逗号/空格/emoji 会导致页面 500
- **`th:attr` 只用简单表达式**(取属性、Elvis 默认值、三元、拼接、等值比较),表达式对象方法调用会被受限求值器拒绝
- **凡是与后台开关绑定的模板表达式都要给默认值**:`?: '默认值'`,配置未落库时属性会被整体移除,功能会静默失效
+- **截取首字符前先判空**:`#strings.substring('', 0, 1)` 会抛字符串越界异常,统一写成 `!#strings.isEmpty(x) ? #strings.substring(x, 0, 1) : '·'`
- **正文样式必须写在全局样式表**(`src/styles/_content.scss`):Astro scoped 样式对 Thymeleaf 运行时注入的内容无效
- **内联脚本统一加 `th:inline="none"`**:避免 Thymeleaf 解析脚本中的 `[[` / `[(` / `${}`
- **新插入的动画元素要强制回流**(读一次 `offsetWidth`):否则初态与终态在同一帧被合并,transition 不触发
- 修改主题外观无需单独维护 `layout.astro`:页面布局契约复用 `Layout.astro`,构建时自动生成
+## 验证与回归测试
+
+自动化覆盖(每次发版执行):
+
+- `pnpm build` 生成全部 21 个模板,构建后置脚本校验所有静态资源引用真实存在
+- `pnpm audit:external` / `pnpm package` 审计第三方远程加载、预连接、动态加载、README 制品链接、Logo 去重
+- `pnpm smoke` 校验点击特效脚本;安装包内容校验模板数量、`theme.yaml` 版本与元数据一致性
+
+上架前建议在真实 Halo ≥ 2.27 实例上做一次人工回归(主题声明为 AI 辅助开发,此项由站点管理员完成并留存结果):
+
+| 页面 | 检查要点 |
+| --- | --- |
+| 首页 | Hero、内置一言、滚动吸附、文章卡片、欢迎弹窗(默认本地文案) |
+| 文章 | 正文 / 代码块配色、目录、分享按钮与微信复制链接、灯箱、点赞 |
+| 分类 / 标签 / 归档 | 列表、分页、空状态、暗色模式 |
+| 友链 / 留言板 / 订阅 | 空字段兜底(作者为空不报错)、表单与分页 |
+| 全局 | 导航栏、页脚、移动端抽屉、深浅色切换、控制台无报错 / 无外部请求 |
+
## 技术栈
- [Halo](https://halo.run) 2.27+
@@ -186,13 +259,15 @@ pnpm smoke
[GPL-3.0](https://www.gnu.org/licenses/gpl-3.0.en.html)
-## 图标来源
+## 图标与字体来源
-本主题使用的图标来自以下开源项目:
+本主题使用的图标与字体来自以下开源项目,均随主题自托管分发、不使用远程 CDN:
| 来源 | 协议 | 用途 |
| --- | --- | --- |
+| [霞鹜文楷 LXGW WenKai](https://github.com/lxgw/LxgwWenKai) | [SIL OFL 1.1](https://openfontlicense.org/) | 全站中文字体(本地 woff2 分片,含保留字体名与版权声明) |
| [Font Awesome](https://fontawesome.com/) Free v7.2.0 | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) / [SIL OFL 1.1](https://scripts.sil.org/OFL) | QQ 分享图标 |
| [Simple Icons](https://simpleicons.org/) | [CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/) | 微博、X、Facebook、Telegram 分享图标 |
| [Feather Icons](https://feathericons.com/) | [MIT](https://opensource.org/licenses/MIT) | 导航、界面交互图标 |
| [Halo](https://halo.run/) | [GPL-3.0](https://www.gnu.org/licenses/gpl-3.0.en.html) | 主题内置图标 |
+| [LightGallery](https://www.lightgalleryjs.com/) 2.9.0 | GPLv3 | 图片灯箱(npm 依赖随构建打包,非远程加载) |
diff --git a/compat/assets/LightGallery.astro_astro_type_script_index_0_lang.BdWU_nY3.js b/compat/assets/LightGallery.astro_astro_type_script_index_0_lang.BdWU_nY3.js
new file mode 100644
index 0000000..722326d
--- /dev/null
+++ b/compat/assets/LightGallery.astro_astro_type_script_index_0_lang.BdWU_nY3.js
@@ -0,0 +1,62 @@
+var I=function(){return I=Object.assign||function(t){for(var e,i=1,s=arguments.length;i";t.append(s)}else t.html(i)})},getSize:function(o,t,e,i){e===void 0&&(e=0);var s=c(o),r=s.attr("data-lg-size")||i;if(r){var n=r.split(",");if(n[1])for(var l=window.innerWidth,g=0;gl){r=h;break}g===n.length-1&&(r=h)}var u=r.split("-"),a=parseInt(u[0],10),m=parseInt(u[1],10),f=t.width(),y=t.height()-e,w=Math.min(f,a),v=Math.min(y,m),C=Math.min(w/a,v/m);return{width:a*C,height:m*C}}},getTransform:function(o,t,e,i,s){if(s){var r=c(o).find("img").first();if(r.get()){var n=t.get().getBoundingClientRect(),l=n.width,g=t.height()-(e+i),h=r.width(),d=r.height(),u=r.style(),a=(l-h)/2-r.offset().left+(parseFloat(u.paddingLeft)||0)+(parseFloat(u.borderLeft)||0)+c(window).scrollLeft()+n.left,m=(g-d)/2-r.offset().top+(parseFloat(u.paddingTop)||0)+(parseFloat(u.borderTop)||0)+c(window).scrollTop()+e,f=h/s.width,y=d/s.height,w="translate3d("+(a*=-1)+"px, "+(m*=-1)+"px, 0) scale3d("+f+", "+y+", 1)";return w}}},getIframeMarkup:function(o,t,e,i,s,r){var n=r?'title="'+r+'"':"";return'
+
+
`},getImgMarkup:function(o,t,e,i,s,r){var n=i?'srcset="'+i+'"':"",l=s?'sizes="'+s+'"':"",g="
',h="";if(r){var d=typeof r=="string"?JSON.parse(r):r;h=d.map(function(u){var a="";return Object.keys(u).forEach(function(m){a+=" "+m+'="'+u[m]+'"'}),""})}return""+h+g},getResponsiveSrc:function(o){for(var t=[],e=[],i="",s=0;sn){i=e[l];break}return i},isImageLoaded:function(o){return!(!o||!o.complete||o.naturalWidth===0)},getVideoPosterMarkup:function(o,t,e,i,s){var r="";s&&s.youtube?r="lg-has-youtube":s&&s.vimeo?r="lg-has-vimeo":r="lg-has-html5";var n=t;return typeof t!="string"&&(n=t.outerHTML),'
+
+ `+n+`
+

+
`},getFocusableElements:function(o){var t=o.querySelectorAll('a[href]:not([disabled]), button:not([disabled]), textarea:not([disabled]), input[type="text"]:not([disabled]), input[type="radio"]:not([disabled]), input[type="checkbox"]:not([disabled]), select:not([disabled])'),e=[].filter.call(t,function(i){var s=window.getComputedStyle(i);return s.display!=="none"&&s.visibility!=="hidden"});return e},getDynamicOptions:function(o,t,e,i){var s=[],r=N(j,t);return[].forEach.call(o,function(n){for(var l={},g=0;g-1&&(u=d),u&&(l[u]=h.value)}}var a=c(n),m=a.find("img").first().attr("alt"),f=a.attr("title"),y=i?a.attr(i):a.find("img").first().attr("src");l.thumb=y,e&&!l.subHtml&&(l.subHtml=f||m||""),l.alt=m||f||"",s.push(l)}),s},isMobile:function(){return/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)},isVideo:function(o,t,e){if(!o){if(t)return{html5:!0};console.error("lightGallery :- data-src is not provided on slide item "+(e+1)+". Please make sure the selector property is properly configured. More info - https://www.lightgalleryjs.com/demos/html-markup/");return}var i=o.match(/\/\/(?:www\.)?youtu(?:\.be|be\.com|be-nocookie\.com)\/(?:watch\?v=|embed\/)?([a-z0-9\-\_\%]+)([\&|?][\S]*)*/i),s=o.match(/\/\/(?:www\.)?(?:player\.)?vimeo.com\/(?:video\/)?([0-9a-z\-_]+)(.*)?/i),r=o.match(/https?:\/\/(.+)?(wistia\.com|wi\.st)\/(medias|embed)\/([0-9a-z\-_]+)(.*)/);if(i)return{youtube:i};if(s)return{vimeo:s};if(r)return{wistia:r}}},$=0,U=(function(){function o(t,e){if(this.lgOpened=!1,this.index=0,this.plugins=[],this.lGalleryOn=!1,this.lgBusy=!1,this.currentItemsInDom=[],this.prevScrollTop=0,this.bodyPaddingRight=0,this.isDummyImageRemoved=!1,this.dragOrSwipeEnabled=!1,this.mediaContainerPosition={top:0,bottom:0},!t)return this;if($++,this.lgId=$,this.el=t,this.LGel=c(t),this.generateSettings(e),this.buildModules(),this.settings.dynamic&&this.settings.dynamicEl!==void 0&&!Array.isArray(this.settings.dynamicEl))throw"When using dynamic mode, you must also define dynamicEl as an Array.";return this.galleryItems=this.getItems(),this.normalizeSettings(),this.init(),this.validateLicense(),this}return o.prototype.generateSettings=function(t){if(this.settings=I(I({},q),t),this.settings.isMobile&&typeof this.settings.isMobile=="function"?this.settings.isMobile():b.isMobile()){var e=I(I({},this.settings.mobileSettings),this.settings.mobileSettings);this.settings=I(I({},this.settings),e)}},o.prototype.normalizeSettings=function(){if(this.settings.slideEndAnimation&&(this.settings.hideControlOnEnd=!1),this.settings.closable||(this.settings.swipeToClose=!1),this.zoomFromOrigin=this.settings.zoomFromOrigin,this.settings.dynamic&&(this.zoomFromOrigin=!1),this.settings.container){var t=this.settings.container;if(typeof t=="function")this.settings.container=t();else if(typeof t=="string"){var e=document.querySelector(t);this.settings.container=e??document.body}}else this.settings.container=document.body;this.settings.preload=Math.min(this.settings.preload,this.galleryItems.length)},o.prototype.init=function(){var t=this;this.addSlideVideoInfo(this.galleryItems),this.buildStructure(),this.LGel.trigger(p.init,{instance:this}),this.settings.keyPress&&this.keyPress(),setTimeout(function(){t.enableDrag(),t.enableSwipe(),t.triggerPosterClick()},50),this.arrow(),this.settings.mousewheel&&this.mousewheel(),this.settings.dynamic||this.openGalleryOnItemClick()},o.prototype.openGalleryOnItemClick=function(){for(var t=this,e=function(r){var n=i.items[r],l=c(n),g=D.generateUUID();l.attr("data-lg-id",g).on("click.lgcustom-item-"+g,function(h){h.preventDefault();var d=t.settings.index||r;t.openGallery(d,n)})},i=this,s=0;s '+this.settings.prevHtml+`
+ "),this.settings.appendSubHtmlTo!==".lg-item"&&(s='');var r="";this.settings.allowMediaOverlap&&(r+="lg-media-overlap ");var n=this.settings.ariaLabelledby?'aria-labelledby="'+this.settings.ariaLabelledby+'"':"",l=this.settings.ariaDescribedby?'aria-describedby="'+this.settings.ariaDescribedby+'"':"",g="lg-container "+this.settings.addClass+" "+(document.body!==this.settings.container?"lg-inline":""),h=this.settings.closable&&this.settings.showCloseIcon?'':"",d=this.settings.showMaximizeIcon?'':"",u=`
+
+
+
+
+
+
+
+ `+d+`
+ `+h+`
+
+ `+(this.settings.appendSubHtmlTo===".lg-outer"?s:"")+`
+
+ `+(this.settings.appendSubHtmlTo===".lg-sub-html"?s:"")+`
+
+
+
+ `;c(this.settings.container).append(u),document.body!==this.settings.container&&c(this.settings.container).css("position","relative"),this.outer=this.getElementById("lg-outer"),this.$lgComponents=this.getElementById("lg-components"),this.$backdrop=this.getElementById("lg-backdrop"),this.$container=this.getElementById("lg-container"),this.$inner=this.getElementById("lg-inner"),this.$content=this.getElementById("lg-content"),this.$toolbar=this.getElementById("lg-toolbar"),this.$backdrop.css("transition-duration",this.settings.backdropDuration+"ms");var a=this.settings.mode+" ";this.manageSingleSlideClassName(),this.settings.enableDrag&&(a+="lg-grab "),this.outer.addClass(a),this.$inner.css("transition-timing-function",this.settings.easing),this.$inner.css("transition-duration",this.settings.speed+"ms"),this.settings.download&&this.$toolbar.append(''),this.counter(),c(window).on("resize.lg.global"+this.lgId+" orientationchange.lg.global"+this.lgId,function(){t.refreshOnResize()}),this.hideBars(),this.manageCloseGallery(),this.toggleMaximize(),this.initModules()}},o.prototype.refreshOnResize=function(){if(this.lgOpened){var t=this.galleryItems[this.index],e=t.__slideVideoInfo;this.mediaContainerPosition=this.getMediaContainerPosition();var i=this.mediaContainerPosition,s=i.top,r=i.bottom;if(this.currentImageSize=b.getSize(this.items[this.index],this.outer,s+r,e&&this.settings.videoMaxSize),e&&this.resizeVideoSlide(this.index,this.currentImageSize),this.zoomFromOrigin&&!this.isDummyImageRemoved){var n=this.getDummyImgStyles(this.currentImageSize);this.outer.find(".lg-current .lg-dummy-img").first().attr("style",n)}this.LGel.trigger(p.containerResize)}},o.prototype.resizeVideoSlide=function(t,e){var i=this.getVideoContStyle(e),s=this.getSlideItem(t);s.find(".lg-video-cont").attr("style",i)},o.prototype.updateSlides=function(t,e){if(this.index>t.length-1&&(this.index=t.length-1),t.length===1&&(this.index=0),!t.length){this.closeGallery();return}var i=this.galleryItems[e].src;this.galleryItems=t,this.updateControls(),this.$inner.empty(),this.currentItemsInDom=[];var s=0;this.galleryItems.some(function(r,n){return r.src===i?(s=n,!0):!1}),this.currentItemsInDom=this.organizeSlideItems(s,-1),this.loadContent(s,!0),this.getSlideItem(s).addClass("lg-current"),this.index=s,this.updateCurrentCounter(s),this.LGel.trigger(p.updateSlides)},o.prototype.getItems=function(){if(this.items=[],this.settings.dynamic)return this.settings.dynamicEl||[];if(this.settings.selector==="this")this.items.push(this.el);else if(this.settings.selector)if(typeof this.settings.selector=="string")if(this.settings.selectWithin){var t=c(this.settings.selectWithin);this.items=t.find(this.settings.selector).get()}else this.items=this.el.querySelectorAll(this.settings.selector);else this.items=this.settings.selector;else this.items=this.el.children;return b.getDynamicOptions(this.items,this.settings.extraProps,this.settings.getCaptionFromTitleOrAlt,this.settings.exThumbImage)},o.prototype.shouldHideScrollbar=function(){return this.settings.hideScrollbar&&document.body===this.settings.container},o.prototype.hideScrollbar=function(){if(this.shouldHideScrollbar()){this.bodyPaddingRight=parseFloat(c("body").style().paddingRight);var t=document.documentElement.getBoundingClientRect(),e=window.innerWidth-t.width;c(document.body).css("padding-right",e+this.bodyPaddingRight+"px"),c(document.body).addClass("lg-overlay-open")}},o.prototype.resetScrollBar=function(){this.shouldHideScrollbar()&&(c(document.body).css("padding-right",this.bodyPaddingRight+"px"),c(document.body).removeClass("lg-overlay-open"))},o.prototype.openGallery=function(t,e){var i=this;if(t===void 0&&(t=this.settings.index),!this.lgOpened){this.lgOpened=!0,this.outer.removeClass("lg-hide-items"),this.hideScrollbar(),this.$container.addClass("lg-show");var s=this.getItemsToBeInsertedToDom(t,t);this.currentItemsInDom=s;var r="";s.forEach(function(a){r=r+('')}),this.$inner.append(r),this.addHtml(t);var n="";this.mediaContainerPosition=this.getMediaContainerPosition();var l=this.mediaContainerPosition,g=l.top,h=l.bottom;this.settings.allowMediaOverlap||this.setMediaContainerPosition(g,h);var d=this.galleryItems[t].__slideVideoInfo;this.zoomFromOrigin&&e&&(this.currentImageSize=b.getSize(e,this.outer,g+h,d&&this.settings.videoMaxSize),n=b.getTransform(e,this.outer,g,h,this.currentImageSize)),(!this.zoomFromOrigin||!n)&&(this.outer.addClass(this.settings.startClass),this.getSlideItem(t).removeClass("lg-complete"));var u=this.settings.zoomFromOrigin?100:this.settings.backdropDuration;setTimeout(function(){i.outer.addClass("lg-components-open")},u),this.index=t,this.LGel.trigger(p.beforeOpen),this.getSlideItem(t).addClass("lg-current"),this.lGalleryOn=!1,this.prevScrollTop=c(window).scrollTop(),setTimeout(function(){if(i.zoomFromOrigin&&n){var a=i.getSlideItem(t);a.css("transform",n),setTimeout(function(){a.addClass("lg-start-progress lg-start-end-progress").css("transition-duration",i.settings.startAnimationDuration+"ms"),i.outer.addClass("lg-zoom-from-image")}),setTimeout(function(){a.css("transform","translate3d(0, 0, 0)")},100)}setTimeout(function(){i.$backdrop.addClass("in"),i.$container.addClass("lg-show-in")},10),setTimeout(function(){i.settings.trapFocus&&document.body===i.settings.container&&i.trapFocus()},i.settings.backdropDuration+50),(!i.zoomFromOrigin||!n)&&setTimeout(function(){i.outer.addClass("lg-visible")},i.settings.backdropDuration),i.slide(t,!1,!1,!1),i.LGel.trigger(p.afterOpen)}),document.body===this.settings.container&&c("html").addClass("lg-on")}},o.prototype.getMediaContainerPosition=function(){if(this.settings.allowMediaOverlap)return{top:0,bottom:0};var t=this.$toolbar.get().clientHeight||0,e=this.outer.find(".lg-components .lg-sub-html").get(),i=this.settings.defaultCaptionHeight||e&&e.clientHeight||0,s=this.outer.find(".lg-thumb-outer").get(),r=s?s.clientHeight:0,n=r+i;return{top:t,bottom:n}},o.prototype.setMediaContainerPosition=function(t,e){t===void 0&&(t=0),e===void 0&&(e=0),this.$content.css("top",t+"px").css("bottom",e+"px")},o.prototype.hideBars=function(){var t=this;setTimeout(function(){t.outer.removeClass("lg-hide-items"),t.settings.hideBarsDelay>0&&(t.outer.on("mousemove.lg click.lg touchstart.lg",function(){t.outer.removeClass("lg-hide-items"),clearTimeout(t.hideBarTimeout),t.hideBarTimeout=setTimeout(function(){t.outer.addClass("lg-hide-items")},t.settings.hideBarsDelay)}),t.outer.trigger("mousemove.lg"))},this.settings.showBarsAfter)},o.prototype.initPictureFill=function(t){if(this.settings.supportLegacyBrowser)try{picturefill({elements:[t.get()]})}catch{console.warn("lightGallery :- If you want srcset or picture tag to be supported for older browser please include picturefil javascript library in your document.")}},o.prototype.counter=function(){if(this.settings.counter){var t=`
+ '+(this.index+1)+` /
+ '+this.galleryItems.length+"
";this.outer.find(this.settings.appendCounterTo).append(t)}},o.prototype.addHtml=function(t){var e,i;if(this.galleryItems[t].subHtmlUrl?i=this.galleryItems[t].subHtmlUrl:e=this.galleryItems[t].subHtml,!i)if(e){var s=e.substring(0,1);if(s==="."||s==="#")try{this.settings.subHtmlSelectorRelative&&!this.settings.dynamic?e=c(this.items).eq(t).find(e).first().html():e=c(e).first().html()}catch{console.warn('Error processing subHtml selector "'+e+'"'),e=""}}else e="";if(this.settings.appendSubHtmlTo!==".lg-item")i?b.fetchCaptionFromUrl(i,this.outer.find(".lg-sub-html"),"replace"):this.outer.find(".lg-sub-html").html(e);else{var r=c(this.getSlideItemId(t));i?b.fetchCaptionFromUrl(i,r,"append"):r.append(''+e+"
")}typeof e<"u"&&e!==null&&(e===""?this.outer.find(this.settings.appendSubHtmlTo).addClass("lg-empty-html"):this.outer.find(this.settings.appendSubHtmlTo).removeClass("lg-empty-html")),this.LGel.trigger(p.afterAppendSubHtml,{index:t})},o.prototype.preload=function(t){for(var e=1;e<=this.settings.preload&&!(e>=this.galleryItems.length-t);e++)this.loadContent(t+e,!1);for(var i=1;i<=this.settings.preload&&!(t-i<0);i++)this.loadContent(t-i,!1)},o.prototype.getDummyImgStyles=function(t){return t?"width:"+t.width+`px;
+ margin-left: -`+t.width/2+`px;
+ margin-top: -`+t.height/2+`px;
+ height:`+t.height+"px":""},o.prototype.getVideoContStyle=function(t){return t?"width:"+t.width+`px;
+ height:`+t.height+"px":""},o.prototype.getDummyImageContent=function(t,e,i){var s;if(this.settings.dynamic||(s=c(this.items).eq(e)),s){var r=void 0;if(this.settings.exThumbImage?r=s.attr(this.settings.exThumbImage):r=s.find("img").first().attr("src"),!r)return"";var n=this.getDummyImgStyles(this.currentImageSize),l=document.createElement("img");return l.alt=i||"",l.src=r,l.className="lg-dummy-img",l.style.cssText=n,t.addClass("lg-first-slide"),this.outer.addClass("lg-first-slide-loading"),l}return""},o.prototype.setImgMarkup=function(t,e,i){var s=this.galleryItems[i],r=s.alt,n=s.srcset,l=s.sizes,g=s.sources,h="",d=r?'alt="'+r+'"':"";this.isFirstSlideWithZoomAnimation()?h=this.getDummyImageContent(e,i,d):h=b.getImgMarkup(i,t,d,n,l,g);var u=document.createElement("picture");u.className="lg-img-wrap",c(u).append(h),e.prepend(u)},o.prototype.onSlideObjectLoad=function(t,e,i,s){var r=t.find(".lg-object").first();b.isImageLoaded(r.get())||e?i():(r.on("load.lg error.lg",function(){i&&i()}),r.on("error.lg",function(){s&&s()}))},o.prototype.onLgObjectLoad=function(t,e,i,s,r,n){var l=this;this.onSlideObjectLoad(t,n,function(){l.triggerSlideItemLoad(t,e,i,s,r)},function(){t.addClass("lg-complete lg-complete_"),t.html(''+l.settings.strings.mediaLoadingFailed+"")})},o.prototype.triggerSlideItemLoad=function(t,e,i,s,r){var n=this,l=this.galleryItems[e],g=r&&this.getSlideType(l)==="video"&&!l.poster?s:0;setTimeout(function(){t.addClass("lg-complete lg-complete_"),n.LGel.trigger(p.slideItemLoad,{index:e,delay:i||0,isFirstSlide:r})},g)},o.prototype.isFirstSlideWithZoomAnimation=function(){return!!(!this.lGalleryOn&&this.zoomFromOrigin&&this.currentImageSize)},o.prototype.addSlideVideoInfo=function(t){var e=this;t.forEach(function(i,s){i.__slideVideoInfo=b.isVideo(i.src,!!i.video,s),i.__slideVideoInfo&&e.settings.loadYouTubePoster&&!i.poster&&i.__slideVideoInfo.youtube&&(i.poster="//img.youtube.com/vi/"+i.__slideVideoInfo.youtube[1]+"/maxresdefault.jpg")})},o.prototype.loadContent=function(t,e){var i=this,s=this.galleryItems[t],r=c(this.getSlideItemId(t)),n=s.poster,l=s.srcset,g=s.sizes,h=s.sources,d=s.src,u=s.video,a=u&&typeof u=="string"?JSON.parse(u):u;if(s.responsive){var m=s.responsive.split(",");d=b.getResponsiveSrc(m)||d}var f=s.__slideVideoInfo,y="",w=!!s.iframe,v=!this.lGalleryOn,C=0;if(v&&(this.zoomFromOrigin&&this.currentImageSize?C=this.settings.startAnimationDuration+10:C=this.settings.backdropDuration+10),!r.hasClass("lg-loaded")){if(f){var P=this.mediaContainerPosition,F=P.top,B=P.bottom,H=b.getSize(this.items[t],this.outer,F+B,f&&this.settings.videoMaxSize);y=this.getVideoContStyle(H)}if(w){var T=b.getIframeMarkup(this.settings.iframeWidth,this.settings.iframeHeight,this.settings.iframeMaxWidth,this.settings.iframeMaxHeight,d,s.iframeTitle);r.prepend(T)}else if(n){var G="",W=v&&this.zoomFromOrigin&&this.currentImageSize;W&&(G=this.getDummyImageContent(r,t,""));var T=b.getVideoPosterMarkup(n,G||"",y,this.settings.strings.playVideo,f);r.prepend(T)}else if(f){var T='';r.prepend(T)}else if(this.setImgMarkup(d,r,t),l||h){var Y=r.find(".lg-object");this.initPictureFill(Y)}(n||f)&&this.LGel.trigger(p.hasVideo,{index:t,src:d,html5Video:a,hasPoster:!!n}),this.LGel.trigger(p.afterAppendSlide,{index:t}),this.lGalleryOn&&this.settings.appendSubHtmlTo===".lg-item"&&this.addHtml(t)}var x=0;C&&!c(document.body).hasClass("lg-from-hash")&&(x=C),this.isFirstSlideWithZoomAnimation()&&(setTimeout(function(){r.removeClass("lg-start-end-progress lg-start-progress").removeAttr("style")},this.settings.startAnimationDuration+100),r.hasClass("lg-loaded")||setTimeout(function(){if(i.getSlideType(s)==="image"){var k=s.alt,R=k?'alt="'+k+'"':"";if(r.find(".lg-img-wrap").append(b.getImgMarkup(t,d,R,l,g,s.sources)),l||h){var Z=r.find(".lg-object");i.initPictureFill(Z)}}(i.getSlideType(s)==="image"||i.getSlideType(s)==="video"&&n)&&(i.onLgObjectLoad(r,t,C,x,!0,!1),i.onSlideObjectLoad(r,!!(f&&f.html5&&!n),function(){i.loadContentOnFirstSlideLoad(t,r,x)},function(){i.loadContentOnFirstSlideLoad(t,r,x)}))},this.settings.startAnimationDuration+100)),r.addClass("lg-loaded"),(!this.isFirstSlideWithZoomAnimation()||this.getSlideType(s)==="video"&&!n)&&this.onLgObjectLoad(r,t,C,x,v,!!(f&&f.html5&&!n)),(!this.zoomFromOrigin||!this.currentImageSize)&&r.hasClass("lg-complete_")&&!this.lGalleryOn&&setTimeout(function(){r.addClass("lg-complete")},this.settings.backdropDuration),this.lGalleryOn=!0,e===!0&&(r.hasClass("lg-complete_")?this.preload(t):r.find(".lg-object").first().on("load.lg error.lg",function(){i.preload(t)}))},o.prototype.loadContentOnFirstSlideLoad=function(t,e,i){var s=this;setTimeout(function(){e.find(".lg-dummy-img").remove(),e.removeClass("lg-first-slide"),s.outer.removeClass("lg-first-slide-loading"),s.isDummyImageRemoved=!0,s.preload(t)},i+300)},o.prototype.getItemsToBeInsertedToDom=function(t,e,i){var s=this;i===void 0&&(i=0);var r=[],n=Math.max(i,3);n=Math.min(n,this.galleryItems.length);var l="lg-item-"+this.lgId+"-"+e;if(this.galleryItems.length<=3)return this.galleryItems.forEach(function(d,u){r.push("lg-item-"+s.lgId+"-"+u)}),r;if(t<(this.galleryItems.length-1)/2){for(var g=t;g>t-n/2&&g>=0;g--)r.push("lg-item-"+this.lgId+"-"+g);for(var h=r.length,g=0;g')}),this.currentItemsInDom.forEach(function(r){s.indexOf(r)===-1&&c("#"+r).remove()}),s},o.prototype.getPreviousSlideIndex=function(){var t=0;try{var e=this.outer.find(".lg-current").first().attr("id");t=parseInt(e.split("-")[3])||0}catch{t=0}return t},o.prototype.setDownloadValue=function(t){if(this.settings.download){var e=this.galleryItems[t],i=e.downloadUrl===!1||e.downloadUrl==="false";if(i)this.outer.addClass("lg-hide-download");else{var s=this.getElementById("lg-download");this.outer.removeClass("lg-hide-download"),s.attr("href",e.downloadUrl||e.src),e.download&&s.attr("download",e.download)}}},o.prototype.makeSlideAnimation=function(t,e,i){var s=this;this.lGalleryOn&&i.addClass("lg-slide-progress"),setTimeout(function(){s.outer.addClass("lg-no-trans"),s.outer.find(".lg-item").removeClass("lg-prev-slide lg-next-slide"),t==="prev"?(e.addClass("lg-prev-slide"),i.addClass("lg-next-slide")):(e.addClass("lg-next-slide"),i.addClass("lg-prev-slide")),setTimeout(function(){s.outer.find(".lg-item").removeClass("lg-current"),e.addClass("lg-current"),s.outer.removeClass("lg-no-trans")},50)},this.lGalleryOn?this.settings.slideDelay:0)},o.prototype.slide=function(t,e,i,s){var r=this,n=this.getPreviousSlideIndex();if(this.currentItemsInDom=this.organizeSlideItems(t,n),!(this.lGalleryOn&&n===t)){var l=this.galleryItems.length;if(!this.lgBusy){this.settings.counter&&this.updateCurrentCounter(t);var g=this.getSlideItem(t),h=this.getSlideItem(n),d=this.galleryItems[t],u=d.__slideVideoInfo;if(this.outer.attr("data-lg-slide-type",this.getSlideType(d)),this.setDownloadValue(t),u){var a=this.mediaContainerPosition,m=a.top,f=a.bottom,y=b.getSize(this.items[t],this.outer,m+f,u&&this.settings.videoMaxSize);this.resizeVideoSlide(t,y)}if(this.LGel.trigger(p.beforeSlide,{prevIndex:n,index:t,fromTouch:!!e,fromThumb:!!i}),this.lgBusy=!0,clearTimeout(this.hideBarTimeout),this.arrowDisable(t),s||(tn&&(s="next")),!e)this.makeSlideAnimation(s,g,h);else{this.outer.find(".lg-item").removeClass("lg-prev-slide lg-current lg-next-slide");var w=void 0,v=void 0;l>2?(w=t-1,v=t+1,(t===0&&n===l-1||t===l-1&&n===0)&&(v=0,w=l-1)):(w=0,v=1),s==="prev"?this.getSlideItem(v).addClass("lg-next-slide"):this.getSlideItem(w).addClass("lg-prev-slide"),g.addClass("lg-current")}this.lGalleryOn?setTimeout(function(){r.loadContent(t,!0),r.settings.appendSubHtmlTo!==".lg-item"&&r.addHtml(t)},this.settings.speed+50+(e?0:this.settings.slideDelay)):this.loadContent(t,!0),setTimeout(function(){r.lgBusy=!1,h.removeClass("lg-slide-progress"),r.LGel.trigger(p.afterSlide,{prevIndex:n,index:t,fromTouch:e,fromThumb:i})},(this.lGalleryOn?this.settings.speed+100:100)+(e?0:this.settings.slideDelay))}this.index=t}},o.prototype.updateCurrentCounter=function(t){this.getElementById("lg-counter-current").html(t+1+"")},o.prototype.updateCounterTotal=function(){this.getElementById("lg-counter-all").html(this.galleryItems.length+"")},o.prototype.getSlideType=function(t){return t.__slideVideoInfo?"video":t.iframe?"iframe":"image"},o.prototype.touchMove=function(t,e,i){var s=e.pageX-t.pageX,r=e.pageY-t.pageY,n=!1;if(this.swipeDirection?n=!0:Math.abs(s)>15?(this.swipeDirection="horizontal",n=!0):Math.abs(r)>15&&(this.swipeDirection="vertical",n=!0),!!n){var l=this.getSlideItem(this.index);if(this.swipeDirection==="horizontal"){i?.preventDefault(),this.outer.addClass("lg-dragging"),this.setTranslate(l,s,0);var g=l.get().offsetWidth,h=g*15/100,d=h-Math.abs(s*10/100);this.setTranslate(this.outer.find(".lg-prev-slide").first(),-g+s-d,0),this.setTranslate(this.outer.find(".lg-next-slide").first(),g+s+d,0)}else if(this.swipeDirection==="vertical"&&this.settings.swipeToClose){i?.preventDefault(),this.$container.addClass("lg-dragging-vertical");var u=1-Math.abs(r)/window.innerHeight;this.$backdrop.css("opacity",u);var a=1-Math.abs(r)/(window.innerWidth*2);this.setTranslate(l,0,r,a,a),Math.abs(r)>100&&this.outer.addClass("lg-hide-items").removeClass("lg-components-open")}}},o.prototype.touchEnd=function(t,e,i){var s=this,r;this.settings.mode!=="lg-slide"&&this.outer.addClass("lg-slide"),setTimeout(function(){s.$container.removeClass("lg-dragging-vertical"),s.outer.removeClass("lg-dragging lg-hide-items").addClass("lg-components-open");var n=!0;if(s.swipeDirection==="horizontal"){r=t.pageX-e.pageX;var l=Math.abs(t.pageX-e.pageX);r<0&&l>s.settings.swipeThreshold?(s.goToNextSlide(!0),n=!1):r>0&&l>s.settings.swipeThreshold&&(s.goToPrevSlide(!0),n=!1)}else if(s.swipeDirection==="vertical")if(r=Math.abs(t.pageY-e.pageY),s.settings.closable&&s.settings.swipeToClose&&r>100){s.closeGallery();return}else s.$backdrop.css("opacity",1);if(s.outer.find(".lg-item").removeAttr("style"),n&&Math.abs(t.pageX-e.pageX)<5){var g=c(i.target);s.isPosterElement(g)&&s.LGel.trigger(p.posterClick)}s.swipeDirection=void 0}),setTimeout(function(){!s.outer.hasClass("lg-dragging")&&s.settings.mode!=="lg-slide"&&s.outer.removeClass("lg-slide")},this.settings.speed+100)},o.prototype.enableSwipe=function(){var t=this,e={},i={},s=!1,r=!1;this.settings.enableSwipe&&(this.$inner.on("touchstart.lg",function(n){t.dragOrSwipeEnabled=!0;var l=t.getSlideItem(t.index);(c(n.target).hasClass("lg-item")||l.get().contains(n.target))&&!t.outer.hasClass("lg-zoomed")&&!t.lgBusy&&n.touches.length===1&&(r=!0,t.touchAction="swipe",t.manageSwipeClass(),e={pageX:n.touches[0].pageX,pageY:n.touches[0].pageY})}),this.$inner.on("touchmove.lg",function(n){r&&t.touchAction==="swipe"&&n.touches.length===1&&(i={pageX:n.touches[0].pageX,pageY:n.touches[0].pageY},t.touchMove(e,i,n),s=!0)}),this.$inner.on("touchend.lg",function(n){if(t.touchAction==="swipe"){if(s)s=!1,t.touchEnd(i,e,n);else if(r){var l=c(n.target);t.isPosterElement(l)&&t.LGel.trigger(p.posterClick)}t.touchAction=void 0,r=!1}}))},o.prototype.enableDrag=function(){var t=this,e={},i={},s=!1,r=!1;this.settings.enableDrag&&(this.outer.on("mousedown.lg",function(n){t.dragOrSwipeEnabled=!0;var l=t.getSlideItem(t.index);(c(n.target).hasClass("lg-item")||l.get().contains(n.target))&&!t.outer.hasClass("lg-zoomed")&&!t.lgBusy&&(n.preventDefault(),t.lgBusy||(t.manageSwipeClass(),e={pageX:n.pageX,pageY:n.pageY},s=!0,t.outer.get().scrollLeft+=1,t.outer.get().scrollLeft-=1,t.outer.removeClass("lg-grab").addClass("lg-grabbing"),t.LGel.trigger(p.dragStart)))}),c(window).on("mousemove.lg.global"+this.lgId,function(n){s&&t.lgOpened&&(r=!0,i={pageX:n.pageX,pageY:n.pageY},t.touchMove(e,i),t.LGel.trigger(p.dragMove))}),c(window).on("mouseup.lg.global"+this.lgId,function(n){if(t.lgOpened){var l=c(n.target);r?(r=!1,t.touchEnd(i,e,n),t.LGel.trigger(p.dragEnd)):t.isPosterElement(l)&&t.LGel.trigger(p.posterClick),s&&(s=!1,t.outer.removeClass("lg-grabbing").addClass("lg-grab"))}}))},o.prototype.triggerPosterClick=function(){var t=this;this.$inner.on("click.lg",function(e){!t.dragOrSwipeEnabled&&t.isPosterElement(c(e.target))&&t.LGel.trigger(p.posterClick)})},o.prototype.manageSwipeClass=function(){var t=this.index+1,e=this.index-1;this.settings.loop&&this.galleryItems.length>2&&(this.index===0?e=this.galleryItems.length-1:this.index===this.galleryItems.length-1&&(t=0)),this.outer.find(".lg-item").removeClass("lg-next-slide lg-prev-slide"),e>-1&&this.getSlideItem(e).addClass("lg-prev-slide"),this.getSlideItem(t).addClass("lg-next-slide")},o.prototype.goToNextSlide=function(t){var e=this,i=this.settings.loop;t&&this.galleryItems.length<3&&(i=!1),this.lgBusy||(this.index+10?(this.index--,this.LGel.trigger(p.beforePrevSlide,{index:this.index,fromTouch:t}),this.slide(this.index,!!t,!1,"prev")):i?(this.index=this.galleryItems.length-1,this.LGel.trigger(p.beforePrevSlide,{index:this.index,fromTouch:t}),this.slide(this.index,!!t,!1,"prev")):this.settings.slideEndAnimation&&!t&&(this.outer.addClass("lg-left-end"),setTimeout(function(){e.outer.removeClass("lg-left-end")},400)))},o.prototype.keyPress=function(){var t=this;c(window).on("keydown.lg.global"+this.lgId,function(e){t.lgOpened&&t.settings.escKey===!0&&e.keyCode===27&&(e.preventDefault(),t.settings.allowMediaOverlap&&t.outer.hasClass("lg-can-toggle")&&t.outer.hasClass("lg-components-open")?t.outer.removeClass("lg-components-open"):t.closeGallery()),t.lgOpened&&t.galleryItems.length>1&&(e.keyCode===37&&(e.preventDefault(),t.goToPrevSlide()),e.keyCode===39&&(e.preventDefault(),t.goToNextSlide()))})},o.prototype.arrow=function(){var t=this;this.getElementById("lg-prev").on("click.lg",function(){t.goToPrevSlide()}),this.getElementById("lg-next").on("click.lg",function(){t.goToNextSlide()})},o.prototype.arrowDisable=function(t){if(!this.settings.loop&&this.settings.hideControlOnEnd){var e=this.getElementById("lg-prev"),i=this.getElementById("lg-next");t+1===this.galleryItems.length?i.attr("disabled","disabled").addClass("disabled"):i.removeAttr("disabled").removeClass("disabled"),t===0?e.attr("disabled","disabled").addClass("disabled"):e.removeAttr("disabled").removeClass("disabled")}},o.prototype.setTranslate=function(t,e,i,s,r){s===void 0&&(s=1),r===void 0&&(r=1),t.css("transform","translate3d("+e+"px, "+i+"px, 0px) scale3d("+s+", "+r+", 1)")},o.prototype.mousewheel=function(){var t=this,e=0;this.outer.on("wheel.lg",function(i){if(!(!i.deltaY||t.galleryItems.length<2)){i.preventDefault();var s=new Date().getTime();s-e<1e3||(e=s,i.deltaY>0?t.goToNextSlide():i.deltaY<0&&t.goToPrevSlide())}})},o.prototype.isSlideElement=function(t){return t.hasClass("lg-outer")||t.hasClass("lg-item")||t.hasClass("lg-img-wrap")||t.hasClass("lg-img-rotate")},o.prototype.isPosterElement=function(t){var e=this.getSlideItem(this.index).find(".lg-video-play-button").get();return t.hasClass("lg-video-poster")||t.hasClass("lg-video-play-button")||e&&e.contains(t.get())},o.prototype.toggleMaximize=function(){var t=this;this.getElementById("lg-maximize").on("click.lg",function(){t.$container.toggleClass("lg-inline"),t.refreshOnResize()})},o.prototype.invalidateItems=function(){for(var t=0;t':"";this.settings.actualSize&&(t+=''),this.core.outer.addClass("lg-use-transition-for-zoom"),this.core.$toolbar.first().append(t)},o.prototype.enableZoom=function(t){var e=this,i=this.settings.enableZoomAfter+t.detail.delay;this.$LG("body").first().hasClass("lg-from-hash")&&t.detail.delay?i=0:this.$LG("body").first().removeClass("lg-from-hash"),this.zoomableTimeout=setTimeout(function(){e.isImageSlide(e.core.index)&&(e.core.getSlideItem(t.detail.index).addClass("lg-zoomable"),t.detail.index===e.core.index&&e.setZoomEssentials())},i+30)},o.prototype.enableZoomOnSlideItemLoad=function(){this.core.LGel.on(S.slideItemLoad+".zoom",this.enableZoom.bind(this))},o.prototype.getDragCords=function(t){return{x:t.pageX,y:t.pageY}},o.prototype.getSwipeCords=function(t){var e=t.touches[0].pageX,i=t.touches[0].pageY;return{x:e,y:i}},o.prototype.getDragAllowedAxises=function(t,e){if(!this.containerRect)return{allowX:!1,allowY:!1};var i=this.core.getSlideItem(this.core.index).find(".lg-image").first().get(),s=0,r=0,n=i.getBoundingClientRect();t?(s=i.offsetHeight*t,r=i.offsetWidth*t):e?(s=n.height+e*n.height,r=n.width+e*n.width):(s=n.height,r=n.width);var l=s>this.containerRect.height,g=r>this.containerRect.width;return{allowX:g,allowY:l}},o.prototype.setZoomEssentials=function(){this.containerRect=this.core.$content.get().getBoundingClientRect()},o.prototype.zoomImage=function(t,e,i,s){if(!(!this.containerRect||Math.abs(e)<=0)){var r=this.containerRect.width/2+this.containerRect.left,n=this.containerRect.height/2+this.containerRect.top+this.scrollTop,l,g;t===1&&(this.positionChanged=!1);var h=this.getDragAllowedAxises(0,e),d=h.allowY,u=h.allowX;this.positionChanged&&(l=this.left/(this.scale-e),g=this.top/(this.scale-e),this.pageX=r-l,this.pageY=n-g,this.positionChanged=!1);var a=this.getPossibleSwipeDragCords(e),m,f,y=r-this.pageX,w=n-this.pageY;if(t-e>1){var v=(t-e)/Math.abs(e);y=(e<0?-y:y)+this.left*(v+(e<0?-1:1)),w=(e<0?-w:w)+this.top*(v+(e<0?-1:1)),m=y/v,f=w/v}else{var v=(t-e)*e;m=y*v,f=w*v}i&&(u?this.isBeyondPossibleLeft(m,a.minX)?m=a.minX:this.isBeyondPossibleRight(m,a.maxX)&&(m=a.maxX):t>1&&(ma.maxX&&(m=a.maxX)),d?this.isBeyondPossibleTop(f,a.minY)?f=a.minY:this.isBeyondPossibleBottom(f,a.maxY)&&(f=a.maxY):t>1&&(fa.maxY&&(f=a.maxY))),this.setZoomStyles({x:m,y:f,scale:t}),this.left=m,this.top=f,s&&this.setZoomImageSize()}},o.prototype.resetImageTranslate=function(t){if(this.isImageSlide(t)){var e=this.core.getSlideItem(t).find(".lg-image").first();this.imageReset=!1,e.removeClass("reset-transition reset-transition-y reset-transition-x"),this.core.outer.removeClass("lg-actual-size"),e.css("width","auto").css("height","auto"),setTimeout(function(){e.removeClass("no-transition")},10)}},o.prototype.setZoomImageSize=function(){var t=this,e=this.core.getSlideItem(this.core.index).find(".lg-image").first();setTimeout(function(){var i=t.getCurrentImageActualSizeScale();t.scale>=i&&(e.addClass("no-transition"),t.imageReset=!0)},A),setTimeout(function(){var i=t.getCurrentImageActualSizeScale();if(t.scale>=i){var s=t.getDragAllowedAxises(t.scale);e.css("width",e.get().naturalWidth+"px").css("height",e.get().naturalHeight+"px"),t.core.outer.addClass("lg-actual-size"),s.allowX&&s.allowY?e.addClass("reset-transition"):s.allowX&&!s.allowY?e.addClass("reset-transition-x"):!s.allowX&&s.allowY&&e.addClass("reset-transition-y")}},A+50)},o.prototype.setZoomStyles=function(t){var e=this.core.getSlideItem(this.core.index).find(".lg-img-wrap").first(),i=this.core.getSlideItem(this.core.index).find(".lg-image").first(),s=this.core.outer.find(".lg-current .lg-dummy-img").first();this.scale=t.scale,i.css("transform","scale3d("+t.scale+", "+t.scale+", 1)"),s.css("transform","scale3d("+t.scale+", "+t.scale+", 1)");var r="translate3d("+t.x+"px, "+t.y+"px, 0)";e.css("transform",r)},o.prototype.setActualSize=function(t,e){var i=this;if(!this.zoomInProgress){this.zoomInProgress=!0;var s=this.core.galleryItems[this.core.index];this.resetImageTranslate(t),setTimeout(function(){if(!(!s.src||i.core.outer.hasClass("lg-first-slide-loading"))){var r=i.getCurrentImageActualSizeScale(),n=i.scale;i.core.outer.hasClass("lg-zoomed")?i.scale=1:i.scale=i.getScale(r),i.setPageCords(e),i.beginZoom(i.scale),i.zoomImage(i.scale,i.scale-n,!0,!0)}},50),setTimeout(function(){i.core.outer.removeClass("lg-grabbing").addClass("lg-grab")},60),setTimeout(function(){i.zoomInProgress=!1},A+110)}},o.prototype.getNaturalWidth=function(t){var e=this.core.getSlideItem(t).find(".lg-image").first(),i=this.core.galleryItems[t].width;return i?parseFloat(i):e.get().naturalWidth},o.prototype.getActualSizeScale=function(t,e){var i,s;return t>=e?(i=t/e,s=i||2):s=1,s},o.prototype.getCurrentImageActualSizeScale=function(){var t=this.core.getSlideItem(this.core.index).find(".lg-image").first(),e=t.get().offsetWidth,i=this.getNaturalWidth(this.core.index)||e;return this.getActualSizeScale(i,e)},o.prototype.getPageCords=function(t){var e={};if(t)e.x=t.pageX||t.touches[0].pageX,e.y=t.pageY||t.touches[0].pageY;else{var i=this.core.$content.get().getBoundingClientRect();e.x=i.width/2+i.left,e.y=i.height/2+this.scrollTop+i.top}return e},o.prototype.setPageCords=function(t){var e=this.getPageCords(t);this.pageX=e.x,this.pageY=e.y},o.prototype.manageActualPixelClassNames=function(){var t=this.core.getElementById("lg-actual-size");t.removeClass(this.settings.actualSizeIcons.zoomIn).addClass(this.settings.actualSizeIcons.zoomOut)},o.prototype.beginZoom=function(t){return this.core.outer.removeClass("lg-zoom-drag-transition lg-zoom-dragging"),t>1?(this.core.outer.addClass("lg-zoomed"),this.manageActualPixelClassNames()):this.resetZoom(),t>1},o.prototype.getScale=function(t){var e=this.getCurrentImageActualSizeScale();return t<1?t=1:t>e&&(t=e),t},o.prototype.init=function(){var t=this;if(this.settings.zoom){this.buildTemplates(),this.enableZoomOnSlideItemLoad();var e=null;this.core.outer.on("dblclick.lg",function(i){t.$LG(i.target).hasClass("lg-image")&&t.setActualSize(t.core.index,i)}),this.core.outer.on("touchstart.lg",function(i){var s=t.$LG(i.target);i.touches.length===1&&s.hasClass("lg-image")&&(e?(clearTimeout(e),e=null,i.preventDefault(),t.setActualSize(t.core.index,i)):e=setTimeout(function(){e=null},300))}),this.core.LGel.on(S.containerResize+".zoom "+S.rotateRight+".zoom "+S.rotateLeft+".zoom "+S.flipHorizontal+".zoom "+S.flipVertical+".zoom",function(){if(!(!t.core.lgOpened||!t.isImageSlide(t.core.index)||t.core.touchAction)){var i=t.core.getSlideItem(t.core.index).find(".lg-img-wrap").first();t.top=0,t.left=0,t.setZoomEssentials(),t.setZoomSwipeStyles(i,{x:0,y:0}),t.positionChanged=!0}}),this.$LG(window).on("scroll.lg.zoom.global"+this.core.lgId,function(){t.core.lgOpened&&(t.scrollTop=t.$LG(window).scrollTop())}),this.core.getElementById("lg-zoom-out").on("click.lg",function(){if(t.isImageSlide(t.core.index)){var i=0;t.imageReset&&(t.resetImageTranslate(t.core.index),i=50),setTimeout(function(){var s=t.scale-t.settings.scale;s<1&&(s=1),t.beginZoom(s),t.zoomImage(s,-t.settings.scale,!0,!t.settings.infiniteZoom)},i)}}),this.core.getElementById("lg-zoom-in").on("click.lg",function(){t.zoomIn()}),this.core.getElementById("lg-actual-size").on("click.lg",function(){t.setActualSize(t.core.index)}),this.core.LGel.on(S.beforeOpen+".zoom",function(){t.core.outer.find(".lg-item").removeClass("lg-zoomable")}),this.core.LGel.on(S.afterOpen+".zoom",function(){t.scrollTop=t.$LG(window).scrollTop(),t.pageX=t.core.outer.width()/2,t.pageY=t.core.outer.height()/2+t.scrollTop,t.scale=1}),this.core.LGel.on(S.afterSlide+".zoom",function(i){var s=i.detail.prevIndex;t.scale=1,t.positionChanged=!1,t.zoomInProgress=!1,t.resetZoom(s),t.resetImageTranslate(s),t.isImageSlide(t.core.index)&&t.setZoomEssentials()}),this.zoomDrag(),this.pinchZoom(),this.zoomSwipe(),this.zoomableTimeout=!1,this.positionChanged=!1,this.zoomInProgress=!1}},o.prototype.zoomIn=function(){if(this.isImageSlide(this.core.index)){var t=this.scale+this.settings.scale;this.settings.infiniteZoom||(t=this.getScale(t)),this.beginZoom(t),this.zoomImage(t,Math.min(this.settings.scale,t-this.scale),!0,!this.settings.infiniteZoom)}},o.prototype.resetZoom=function(t){this.core.outer.removeClass("lg-zoomed lg-zoom-drag-transition");var e=this.core.getElementById("lg-actual-size"),i=this.core.getSlideItem(t!==void 0?t:this.core.index);e.removeClass(this.settings.actualSizeIcons.zoomOut).addClass(this.settings.actualSizeIcons.zoomIn),i.find(".lg-img-wrap").first().removeAttr("style"),i.find(".lg-image").first().removeAttr("style"),this.scale=1,this.left=0,this.top=0,this.setPageCords()},o.prototype.getTouchDistance=function(t){return Math.sqrt((t.touches[0].pageX-t.touches[1].pageX)*(t.touches[0].pageX-t.touches[1].pageX)+(t.touches[0].pageY-t.touches[1].pageY)*(t.touches[0].pageY-t.touches[1].pageY))},o.prototype.pinchZoom=function(){var t=this,e=0,i=!1,s=1,r=0,n=this.core.getSlideItem(this.core.index);this.core.outer.on("touchstart.lg",function(l){if(n=t.core.getSlideItem(t.core.index),!!t.isImageSlide(t.core.index)&&l.touches.length===2){if(l.preventDefault(),t.core.outer.hasClass("lg-first-slide-loading"))return;s=t.scale||1,t.core.outer.removeClass("lg-zoom-drag-transition lg-zoom-dragging"),t.setPageCords(l),t.resetImageTranslate(t.core.index),t.core.touchAction="pinch",e=t.getTouchDistance(l)}}),this.core.$inner.on("touchmove.lg",function(l){if(l.touches.length===2&&t.core.touchAction==="pinch"&&(t.$LG(l.target).hasClass("lg-item")||n.get().contains(l.target))){l.preventDefault();var g=t.getTouchDistance(l),h=e-g;if(!i&&Math.abs(h)>5&&(i=!0),i){r=t.scale;var d=Math.max(1,s+-h*.02);t.scale=Math.round((d+Number.EPSILON)*100)/100;var u=t.scale-r;t.zoomImage(t.scale,Math.round((u+Number.EPSILON)*100)/100,!1,!1)}}}),this.core.$inner.on("touchend.lg",function(l){if(t.core.touchAction==="pinch"&&(t.$LG(l.target).hasClass("lg-item")||n.get().contains(l.target))){if(i=!1,e=0,t.scale<=1)t.resetZoom();else{var g=t.getCurrentImageActualSizeScale();if(t.scale>=g){var h=g-t.scale;h===0&&(h=.01),t.zoomImage(g,h,!1,!0)}t.manageActualPixelClassNames(),t.core.outer.addClass("lg-zoomed")}t.core.touchAction=void 0}})},o.prototype.touchendZoom=function(t,e,i,s,r){var n=e.x-t.x,l=e.y-t.y,g=Math.abs(n)/r+1,h=Math.abs(l)/r+1;g>2&&(g+=1),h>2&&(h+=1),n=n*g,l=l*h;var d=this.core.getSlideItem(this.core.index).find(".lg-img-wrap").first(),u={};u.x=this.left+n,u.y=this.top+l;var a=this.getPossibleSwipeDragCords();(Math.abs(n)>15||Math.abs(l)>15)&&(s&&(this.isBeyondPossibleTop(u.y,a.minY)?u.y=a.minY:this.isBeyondPossibleBottom(u.y,a.maxY)&&(u.y=a.maxY)),i&&(this.isBeyondPossibleLeft(u.x,a.minX)?u.x=a.minX:this.isBeyondPossibleRight(u.x,a.maxX)&&(u.x=a.maxX)),s?this.top=u.y:u.y=this.top,i?this.left=u.x:u.x=this.left,this.setZoomSwipeStyles(d,u),this.positionChanged=!0)},o.prototype.getZoomSwipeCords=function(t,e,i,s,r){var n={};if(s){if(n.y=this.top+(e.y-t.y),this.isBeyondPossibleTop(n.y,r.minY)){var l=r.minY-n.y;n.y=r.minY-l/6}else if(this.isBeyondPossibleBottom(n.y,r.maxY)){var g=n.y-r.maxY;n.y=r.maxY+g/6}}else n.y=this.top;if(i){if(n.x=this.left+(e.x-t.x),this.isBeyondPossibleLeft(n.x,r.minX)){var h=r.minX-n.x;n.x=r.minX-h/6}else if(this.isBeyondPossibleRight(n.x,r.maxX)){var d=n.x-r.maxX;n.x=r.maxX+d/6}}else n.x=this.left;return n},o.prototype.isBeyondPossibleLeft=function(t,e){return t>=e},o.prototype.isBeyondPossibleRight=function(t,e){return t<=e},o.prototype.isBeyondPossibleTop=function(t,e){return t>=e},o.prototype.isBeyondPossibleBottom=function(t,e){return t<=e},o.prototype.isImageSlide=function(t){var e=this.core.galleryItems[t];return this.core.getSlideType(e)==="image"},o.prototype.getPossibleSwipeDragCords=function(t){var e=this.core.getSlideItem(this.core.index).find(".lg-image").first(),i=this.core.mediaContainerPosition.bottom,s=e.get().getBoundingClientRect(),r=s.height,n=s.width;t&&(r=r+t*r,n=n+t*n);var l=(r-this.containerRect.height)/2,g=(this.containerRect.height-r)/2+i,h=(n-this.containerRect.width)/2,d=(this.containerRect.width-n)/2,u={minY:l,maxY:g,minX:h,maxX:d};return u},o.prototype.setZoomSwipeStyles=function(t,e){t.css("transform","translate3d("+e.x+"px, "+e.y+"px, 0)")},o.prototype.zoomSwipe=function(){var t=this,e={},i={},s=!1,r=!1,n=!1,l=new Date,g=new Date,h,d,u=this.core.getSlideItem(this.core.index);this.core.$inner.on("touchstart.lg",function(a){if(t.isImageSlide(t.core.index)&&(u=t.core.getSlideItem(t.core.index),(t.$LG(a.target).hasClass("lg-item")||u.get().contains(a.target))&&a.touches.length===1&&t.core.outer.hasClass("lg-zoomed"))){a.preventDefault(),l=new Date,t.core.touchAction="zoomSwipe",d=t.core.getSlideItem(t.core.index).find(".lg-img-wrap").first();var m=t.getDragAllowedAxises(0);n=m.allowY,r=m.allowX,(r||n)&&(e=t.getSwipeCords(a)),h=t.getPossibleSwipeDragCords(),t.core.outer.addClass("lg-zoom-dragging lg-zoom-drag-transition")}}),this.core.$inner.on("touchmove.lg",function(a){if(a.touches.length===1&&t.core.touchAction==="zoomSwipe"&&(t.$LG(a.target).hasClass("lg-item")||u.get().contains(a.target))){a.preventDefault(),t.core.touchAction="zoomSwipe",i=t.getSwipeCords(a);var m=t.getZoomSwipeCords(e,i,r,n,h);(Math.abs(i.x-e.x)>15||Math.abs(i.y-e.y)>15)&&(s=!0,t.setZoomSwipeStyles(d,m))}}),this.core.$inner.on("touchend.lg",function(a){if(t.core.touchAction==="zoomSwipe"&&(t.$LG(a.target).hasClass("lg-item")||u.get().contains(a.target))){if(a.preventDefault(),t.core.touchAction=void 0,t.core.outer.removeClass("lg-zoom-dragging"),!s)return;s=!1,g=new Date;var m=g.valueOf()-l.valueOf();t.touchendZoom(e,i,r,n,m)}})},o.prototype.zoomDrag=function(){var t=this,e={},i={},s=!1,r=!1,n=!1,l=!1,g,h,d,u;this.core.outer.on("mousedown.lg.zoom",function(a){if(t.isImageSlide(t.core.index)){var m=t.core.getSlideItem(t.core.index);if(t.$LG(a.target).hasClass("lg-item")||m.get().contains(a.target)){g=new Date,u=t.core.getSlideItem(t.core.index).find(".lg-img-wrap").first();var f=t.getDragAllowedAxises(0);l=f.allowY,n=f.allowX,t.core.outer.hasClass("lg-zoomed")&&t.$LG(a.target).hasClass("lg-object")&&(n||l)&&(a.preventDefault(),e=t.getDragCords(a),d=t.getPossibleSwipeDragCords(),s=!0,t.core.outer.removeClass("lg-grab").addClass("lg-grabbing lg-zoom-drag-transition lg-zoom-dragging"))}}}),this.$LG(window).on("mousemove.lg.zoom.global"+this.core.lgId,function(a){if(s){r=!0,i=t.getDragCords(a);var m=t.getZoomSwipeCords(e,i,n,l,d);t.setZoomSwipeStyles(u,m)}}),this.$LG(window).on("mouseup.lg.zoom.global"+this.core.lgId,function(a){if(s){if(h=new Date,s=!1,t.core.outer.removeClass("lg-zoom-dragging"),r&&(e.x!==i.x||e.y!==i.y)){i=t.getDragCords(a);var m=h.valueOf()-g.valueOf();t.touchendZoom(e,i,n,l,m)}r=!1}t.core.outer.removeClass("lg-grabbing").addClass("lg-grab")})},o.prototype.closeGallery=function(){this.resetZoom(),this.zoomInProgress=!1},o.prototype.destroy=function(){this.$LG(window).off(".lg.zoom.global"+this.core.lgId),this.core.LGel.off(".lg.zoom"),this.core.LGel.off(".zoom"),clearTimeout(this.zoomableTimeout),this.zoomableTimeout=!1},o})();var _=function(){return _=Object.assign||function(t){for(var e,i=1,s=arguments.length;i
+
+
+ `;this.core.outer.addClass("lg-has-thumb"),this.settings.appendThumbnailsTo===".lg-components"?this.core.$lgComponents.append(e):this.core.outer.append(e),this.$thumbOuter=this.core.outer.find(".lg-thumb-outer").first(),this.$lgThumb=this.core.outer.find(".lg-thumb").first(),this.settings.animateThumb&&this.core.outer.find(".lg-thumb").css("transition-duration",this.core.settings.speed+"ms").css("width",this.thumbTotalWidth+"px").css("position","relative"),this.setThumbItemHtml(this.core.galleryItems)},o.prototype.enableThumbDrag=function(){var t=this,e={cords:{startX:0,endX:0},isMoved:!1,newTranslateX:0,startTime:new Date,endTime:new Date,touchMoveTime:0},i=!1;this.$thumbOuter.addClass("lg-grab"),this.core.outer.find(".lg-thumb").first().on("mousedown.lg.thumb",function(s){t.thumbTotalWidth>t.thumbOuterWidth&&(s.preventDefault(),e.cords.startX=s.pageX,e.startTime=new Date,t.thumbClickable=!1,i=!0,t.core.outer.get().scrollLeft+=1,t.core.outer.get().scrollLeft-=1,t.$thumbOuter.removeClass("lg-grab").addClass("lg-grabbing"))}),this.$LG(window).on("mousemove.lg.thumb.global"+this.core.lgId,function(s){t.core.lgOpened&&i&&(e.cords.endX=s.pageX,e=t.onThumbTouchMove(e))}),this.$LG(window).on("mouseup.lg.thumb.global"+this.core.lgId,function(){t.core.lgOpened&&(e.isMoved?e=t.onThumbTouchEnd(e):t.thumbClickable=!0,i&&(i=!1,t.$thumbOuter.removeClass("lg-grabbing").addClass("lg-grab")))})},o.prototype.enableThumbSwipe=function(){var t=this,e={cords:{startX:0,endX:0},isMoved:!1,newTranslateX:0,startTime:new Date,endTime:new Date,touchMoveTime:0};this.$lgThumb.on("touchstart.lg",function(i){t.thumbTotalWidth>t.thumbOuterWidth&&(i.preventDefault(),e.cords.startX=i.targetTouches[0].pageX,t.thumbClickable=!1,e.startTime=new Date)}),this.$lgThumb.on("touchmove.lg",function(i){t.thumbTotalWidth>t.thumbOuterWidth&&(i.preventDefault(),e.cords.endX=i.targetTouches[0].pageX,e=t.onThumbTouchMove(e))}),this.$lgThumb.on("touchend.lg",function(){e.isMoved?e=t.onThumbTouchEnd(e):t.thumbClickable=!0})},o.prototype.rebuildThumbnails=function(){var t=this;this.$thumbOuter.addClass("lg-rebuilding-thumbnails"),setTimeout(function(){t.thumbTotalWidth=t.core.galleryItems.length*(t.settings.thumbWidth+t.settings.thumbMargin),t.$lgThumb.css("width",t.thumbTotalWidth+"px"),t.$lgThumb.empty(),t.setThumbItemHtml(t.core.galleryItems),t.animateThumb(t.core.index)},50),setTimeout(function(){t.$thumbOuter.removeClass("lg-rebuilding-thumbnails")},200)},o.prototype.setTranslate=function(t){this.$lgThumb.css("transform","translate3d(-"+t+"px, 0px, 0px)")},o.prototype.getPossibleTransformX=function(t){return t>this.thumbTotalWidth-this.thumbOuterWidth&&(t=this.thumbTotalWidth-this.thumbOuterWidth),t<0&&(t=0),t},o.prototype.animateThumb=function(t){if(this.$lgThumb.css("transition-duration",this.core.settings.speed+"ms"),this.settings.animateThumb){var e=0;switch(this.settings.currentPagerPosition){case"left":e=0;break;case"middle":e=this.thumbOuterWidth/2-this.settings.thumbWidth/2;break;case"right":e=this.thumbOuterWidth-this.settings.thumbWidth}this.translateX=(this.settings.thumbWidth+this.settings.thumbMargin)*t-1-e,this.translateX>this.thumbTotalWidth-this.thumbOuterWidth&&(this.translateX=this.thumbTotalWidth-this.thumbOuterWidth),this.translateX<0&&(this.translateX=0),this.setTranslate(this.translateX)}},o.prototype.onThumbTouchMove=function(t){return t.newTranslateX=this.translateX,t.isMoved=!0,t.touchMoveTime=new Date().valueOf(),t.newTranslateX-=t.cords.endX-t.cords.startX,t.newTranslateX=this.getPossibleTransformX(t.newTranslateX),this.setTranslate(t.newTranslateX),this.$thumbOuter.addClass("lg-dragging"),t},o.prototype.onThumbTouchEnd=function(t){t.isMoved=!1,t.endTime=new Date,this.$thumbOuter.removeClass("lg-dragging");var e=t.endTime.valueOf()-t.startTime.valueOf(),i=t.cords.endX-t.cords.startX,s=Math.abs(i)/e;return s>.15&&t.endTime.valueOf()-t.touchMoveTime<30?(s+=1,s>2&&(s+=1),s=s+s*(Math.abs(i)/this.thumbOuterWidth),this.$lgThumb.css("transition-duration",Math.min(s-1,2)+"settings"),i=i*s,this.translateX=this.getPossibleTransformX(this.translateX-i),this.setTranslate(this.translateX)):this.translateX=t.newTranslateX,Math.abs(t.cords.endX-t.cords.startX)'),this.core.outer.find(".lg-toggle-thumb").first().on("click.lg",function(){t.core.outer.toggleClass("lg-components-open")}))},o.prototype.thumbKeyPress=function(){var t=this;this.$LG(window).on("keydown.lg.thumb.global"+this.core.lgId,function(e){!t.core.lgOpened||!t.settings.toggleThumb||(e.keyCode===38?(e.preventDefault(),t.core.outer.addClass("lg-components-open")):e.keyCode===40&&(e.preventDefault(),t.core.outer.removeClass("lg-components-open")))})},o.prototype.destroy=function(){this.settings.thumbnail&&(this.$LG(window).off(".lg.thumb.global"+this.core.lgId),this.core.LGel.off(".lg.thumb"),this.core.LGel.off(".thumb"),this.$thumbOuter.remove(),this.core.outer.removeClass("lg-has-thumb"))},o})();var M=function(){return M=Object.assign||function(t){for(var e,i=1,s=arguments.length;i',this.core.$toolbar.append(t),this.fullScreen()}},o.prototype.isFullScreen=function(){return document.fullscreenElement||document.mozFullScreenElement||document.webkitFullscreenElement||document.msFullscreenElement},o.prototype.requestFullscreen=function(){var t=document.documentElement;t.requestFullscreen?t.requestFullscreen():t.msRequestFullscreen?t.msRequestFullscreen():t.mozRequestFullScreen?t.mozRequestFullScreen():t.webkitRequestFullscreen&&t.webkitRequestFullscreen()},o.prototype.exitFullscreen=function(){document.exitFullscreen?document.exitFullscreen():document.msExitFullscreen?document.msExitFullscreen():document.mozCancelFullScreen?document.mozCancelFullScreen():document.webkitExitFullscreen&&document.webkitExitFullscreen()},o.prototype.fullScreen=function(){var t=this;this.$LG(document).on("fullscreenchange.lg.global"+this.core.lgId+`
+ webkitfullscreenchange.lg.global`+this.core.lgId+`
+ mozfullscreenchange.lg.global`+this.core.lgId+`
+ MSFullscreenChange.lg.global`+this.core.lgId,function(){t.core.lgOpened&&t.core.outer.toggleClass("lg-fullscreen-on")}),this.core.outer.find(".lg-fullscreen").first().on("click.lg",function(){t.isFullScreen()?t.exitFullscreen():t.requestFullscreen()})},o.prototype.closeGallery=function(){this.isFullScreen()&&this.exitFullscreen()},o.prototype.destroy=function(){this.$LG(document).off("fullscreenchange.lg.global"+this.core.lgId+`
+ webkitfullscreenchange.lg.global`+this.core.lgId+`
+ mozfullscreenchange.lg.global`+this.core.lgId+`
+ MSFullscreenChange.lg.global`+this.core.lgId)},o})(),X=new WeakSet;function rt(o){return!!X.has(o)}function nt(o){X.add(o)}function lt(o,t){if(!o)return t||"";var e=o.split("|");if(e.length<8)return t||"";var i=e[0],s=e[1],r=e[2],n=e[3],l=e[4],g=e[5],h=e[6],d=e[7],u=i||s||r||n||l||g||h;if(!u)return t||"";var a=''+(t||"")+"
";if(a+='',(i||s)&&(a+='
'+(i?i+" ":"")+s+""),r&&(a+='
'+r+""),a+='
',n&&(a+='f/'+n+""),l&&(a+=''+l+"s"),g&&(a+='ISO '+g+""),h){var m=h+"mm";d&&d!==h&&(m+=" (eq. "+d+"mm)"),a+=''+m+""}return a+="
",a}function at(o){var t=o.querySelectorAll(".wi-lightgallery-item[data-exif]");t.forEach(function(e){var i=e.getAttribute("data-exif");if(i){var s=e.getAttribute("data-sub-html")||"",r=lt(i,s);r&&r!==s&&e.setAttribute("data-sub-html",r)}})}function gt(){return{selector:".wi-lightgallery-item",plugins:[tt,it,ot],speed:280,mode:"lg-fade",licenseKey:"GPLv3",download:!1,counter:!0,preload:2,thumbWidth:80,thumbHeight:"60px",thumbMargin:4,zoomFromOrigin:!0,actualSize:!1,showZoomInOutIcons:!0,toggleThumb:!0,allowMediaOverlap:!0,mobileSettings:{controls:!0,showCloseIcon:!0,download:!1}}}function O(o){if(!rt(o)){var t=o.querySelectorAll(".wi-lightgallery-item");t.length!==0&&(at(o),J(o,gt()),nt(o))}}function ht(){return window.innerWidth<768}function E(){ut(),dt(),ct(),document.addEventListener("click",function(o){var t=o.target.closest(".lg-toggle-thumb");if(t){o.stopPropagation();var e=t.closest(".lg-outer");e&&e.classList.toggle("wi-thumb-shown")}},!0),document.addEventListener("click",function(o){var t=o.target.closest(".wi-lightgallery-item");t&&setTimeout(function(){var e=document.querySelector(".lg-outer");e&&!e.classList.contains("wi-thumb-shown")&&!ht()&&e.classList.add("wi-thumb-shown")},350)})}function ut(){var o=[".wi-post__body",".wi-page__body"];o.forEach(function(t){var e=document.querySelectorAll(t);e.forEach(function(i){mt(i),O(i)})})}function dt(){var o=document.querySelector(".wi-photos-page__all-groups");if(o){var t=o.querySelectorAll(".wi-photos-page__link");t.forEach(function(r){r.classList.add("wi-lightgallery-item")}),O(o);return}var e=document.querySelector(".wi-photos-page__grid:not(.wi-photos-page__all-groups .wi-photos-page__grid)");if(e){var i=e.querySelectorAll(".wi-photos-page__link");i.forEach(function(r){r.classList.add("wi-lightgallery-item")}),O(e);return}var s=document.querySelectorAll(".wi-photos-page__grid");s.forEach(function(r){ft(r),O(r)})}function ct(){var o=[".wi-moments-page__media",".wi-moments-page__card-media",".wi-moments-page__masonry-media"];o.forEach(function(t){var e=document.querySelectorAll(t);e.forEach(function(i){pt(i),O(i)})})}function mt(o){var t=o.querySelectorAll("img");t.forEach(function(e){var i=e.getAttribute("src");if(i){var s=e.getAttribute("width"),r=e.getAttribute("height");if(!(s&&parseInt(s)<50||r&&parseInt(r)<50)){var n=(e.getAttribute("class")||"").toLowerCase();if(!/emoji|icon|avatar|logo|badge|smiley/.test(n)&&e.getAttribute("role")!=="presentation"&&e.getAttribute("aria-hidden")!=="true"){var l=e.closest("a");if(l){if(l.getAttribute("target")==="_blank")return;if(l.classList.add("wi-lightgallery-item"),!l.getAttribute("data-src")){var g=l.getAttribute("href")||i;l.setAttribute("data-src",g)}var h=e.getAttribute("alt")||"";h&&!l.getAttribute("data-sub-html")&&l.setAttribute("data-sub-html",h)}else{var d=document.createElement("a");d.setAttribute("href",i),d.setAttribute("data-src",i),d.classList.add("wi-lightgallery-item");var u=e.getAttribute("alt")||"";u&&d.setAttribute("data-sub-html",u),e.parentNode.insertBefore(d,e),d.appendChild(e)}}}}})}function ft(o){var t=o.querySelectorAll(".wi-photos-page__link");t.forEach(function(e){e.classList.add("wi-lightgallery-item")})}function pt(o){var t=o.querySelectorAll("img");t.forEach(function(e){var i=e.getAttribute("src");i&&(e.classList.add("wi-lightgallery-item"),e.getAttribute("data-src")||e.setAttribute("data-src",i))})}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",function(){"requestIdleCallback"in window?requestIdleCallback(E,{timeout:2e3}):setTimeout(E,200)}):"requestIdleCallback"in window?requestIdleCallback(E,{timeout:2e3}):setTimeout(E,200);
diff --git a/compat/assets/post@_@astro.Coo6gXlT.css b/compat/assets/post@_@astro.Coo6gXlT.css
new file mode 100644
index 0000000..39f8f77
--- /dev/null
+++ b/compat/assets/post@_@astro.Coo6gXlT.css
@@ -0,0 +1 @@
+.wi-reading-progress[data-astro-cid-ftxg5n3y]{position:fixed;top:0;left:0;width:0;height:3px;background:var(--accent);z-index:101;transition:width .1s linear}.wi-post[data-astro-cid-ftxg5n3y]{display:grid;gap:1rem;padding-block:1rem;overflow-x:hidden}.wi-post__header[data-astro-cid-ftxg5n3y]{display:grid;gap:var(--space-lg);padding:var(--space-lg) 0 var(--space-md);border-bottom:1px solid var(--rule);text-align:center}.wi-post__cover-wrap[data-astro-cid-ftxg5n3y]{border-radius:12px;overflow:hidden}.wi-post__cover[data-astro-cid-ftxg5n3y]{width:100%;max-height:480px;object-fit:cover;border-radius:12px}.wi-post__title[data-astro-cid-ftxg5n3y]{font-family:var(--font-sans);font-size:clamp(1.8rem,4vw,2.8rem);font-weight:700;line-height:var(--leading-tight);letter-spacing:-.02em;color:var(--ink);margin:0}.wi-page-accent[data-astro-cid-ftxg5n3y]{display:block;width:48px;height:3px;margin:var(--space-sm) auto 0;border-radius:9999px;background:var(--accent)}.wi-post__meta[data-astro-cid-ftxg5n3y]{display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:.4rem;color:var(--ink-3);font-size:var(--text-sm);letter-spacing:var(--tracking-wide)}.wi-post__meta[data-astro-cid-ftxg5n3y] a[data-astro-cid-ftxg5n3y]{color:var(--ink-2);text-decoration:none;transition:color var(--duration-fast) var(--ease-out-quart)}.wi-post__meta[data-astro-cid-ftxg5n3y] a[data-astro-cid-ftxg5n3y]:hover{color:var(--accent)}.wi-post__meta-sep[data-astro-cid-ftxg5n3y]{color:var(--ink-3)}.wi-post__author[data-astro-cid-ftxg5n3y]{display:inline-flex;align-items:center;gap:.35rem}.wi-post__author-avatar[data-astro-cid-ftxg5n3y]{width:20px;height:20px;border-radius:50%;object-fit:cover}.wi-post__meta-categories[data-astro-cid-ftxg5n3y] a[data-astro-cid-ftxg5n3y]+a[data-astro-cid-ftxg5n3y]:before{content:", "}.wi-post__meta-tags[data-astro-cid-ftxg5n3y] a[data-astro-cid-ftxg5n3y]+a[data-astro-cid-ftxg5n3y]:before{content:", "}.wi-post__stats[data-astro-cid-ftxg5n3y]{display:inline-flex;align-items:center;gap:.4rem}.wi-post__stat-item[data-astro-cid-ftxg5n3y]+.wi-post__stat-item[data-astro-cid-ftxg5n3y]:before{content:"·";margin-right:.4rem;color:var(--ink-3)}.wi-post__body[data-astro-cid-ftxg5n3y]{width:100%;font-size:clamp(1.05rem,1rem + .25vw,1.15rem);line-height:1.85;color:#3d3530;overflow-wrap:break-word;word-wrap:break-word}html.dark .wi-post__body[data-astro-cid-ftxg5n3y]{color:#d9d2c9}.wi-toc[data-astro-cid-ftxg5n3y]{display:none}@media(min-width:1280px){.wi-toc[data-astro-cid-ftxg5n3y]{display:block;position:fixed;top:80px;width:220px;max-height:calc(100vh - 100px);overflow-y:auto;z-index:10}}.wi-toc__header[data-astro-cid-ftxg5n3y]{display:flex;align-items:center;padding-bottom:var(--space-sm);margin-bottom:var(--space-sm);border-bottom:1px solid var(--rule)}.wi-toc__title[data-astro-cid-ftxg5n3y]{font-family:var(--font-sans);font-size:var(--text-sm);font-weight:600;color:var(--ink);letter-spacing:var(--tracking-wide)}.wi-toc__nav[data-astro-cid-ftxg5n3y]{display:flex;flex-direction:column;gap:0;overflow:hidden}.wi-toc-fab[data-astro-cid-ftxg5n3y]{display:none;position:fixed;bottom:32px;right:24px;width:44px;height:44px;border-radius:50%;border:1px solid var(--rule);background:var(--bg-raised);color:var(--ink-2);cursor:pointer;z-index:20;align-items:center;justify-content:center;box-shadow:var(--shadow-md);transition:background .2s ease,color .2s ease,border-color .2s ease,opacity .3s ease}.wi-toc-fab[data-astro-cid-ftxg5n3y]:hover{color:var(--accent);border-color:var(--accent)}@media(max-width:1279px){.wi-toc-fab[data-astro-cid-ftxg5n3y]{display:inline-flex;bottom:24px}}@media(max-width:640px){.wi-toc-fab[data-astro-cid-ftxg5n3y]{right:16px;bottom:24px;width:40px;height:40px}.wi-post__share-btn[data-astro-cid-ftxg5n3y]{width:44px;height:44px}.wi-like-btn[data-astro-cid-ftxg5n3y]{padding:.75rem 1.75rem}}.wi-toc-drawer[data-astro-cid-ftxg5n3y]{display:none}.wi-toc-drawer--open[data-astro-cid-ftxg5n3y]{display:block}.wi-toc-drawer__overlay[data-astro-cid-ftxg5n3y]{position:fixed;inset:0;background:#0006;z-index:50}.wi-toc-drawer__panel[data-astro-cid-ftxg5n3y]{position:fixed;bottom:0;left:0;right:0;max-height:60vh;background:var(--bg);border-top:1px solid var(--rule);border-radius:16px 16px 0 0;padding:1.25rem;z-index:51;overflow-y:auto;transform:translateY(0);transition:transform .3s cubic-bezier(.16,1,.3,1)}.wi-toc-drawer__header[data-astro-cid-ftxg5n3y]{display:flex;align-items:center;justify-content:space-between;margin-bottom:.75rem;padding-bottom:.75rem;border-bottom:1px solid var(--rule)}.wi-toc-drawer__title[data-astro-cid-ftxg5n3y]{font-family:var(--font-sans);font-size:var(--text-sm);font-weight:600;color:var(--ink)}.wi-toc-drawer__close[data-astro-cid-ftxg5n3y]{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;background:none;color:var(--ink-3);cursor:pointer}.wi-toc-drawer__close[data-astro-cid-ftxg5n3y]:hover{color:var(--accent)}.wi-toc-drawer__nav[data-astro-cid-ftxg5n3y]{display:flex;flex-direction:column;gap:2px}.wi-post__like[data-astro-cid-ftxg5n3y]{display:flex;justify-content:center;padding-top:0}.wi-like-btn[data-astro-cid-ftxg5n3y]{display:inline-flex;align-items:center;gap:.5rem;padding:.6rem 1.5rem;border:1px solid var(--rule);border-radius:9999px;background:var(--bg-raised);color:var(--ink-2);font-size:var(--text-sm);font-family:var(--font-body);cursor:pointer;transition:background .2s ease,color .2s ease,border-color .2s ease,transform .2s ease}.wi-like-btn[data-astro-cid-ftxg5n3y]:hover{border-color:var(--accent);color:var(--accent);background:color-mix(in srgb,var(--accent) 8%,var(--bg))}.wi-like-btn[data-astro-cid-ftxg5n3y]:active{transform:scale(.95)}.wi-like-btn--liked[data-astro-cid-ftxg5n3y]{border-color:var(--accent);color:var(--accent);background:color-mix(in srgb,var(--accent) 10%,var(--bg));cursor:default}.wi-like-btn--liked[data-astro-cid-ftxg5n3y] .wi-like-btn__icon[data-astro-cid-ftxg5n3y]{fill:var(--accent)}.wi-like-btn__icon[data-astro-cid-ftxg5n3y]{transition:fill .2s ease,transform .3s ease}.wi-like-btn--animate[data-astro-cid-ftxg5n3y] .wi-like-btn__icon[data-astro-cid-ftxg5n3y]{animation:likeHeartbeat .4s ease}@keyframes likeHeartbeat{0%{transform:scale(1)}30%{transform:scale(1.3)}60%{transform:scale(.9)}to{transform:scale(1)}}.wi-post__share[data-astro-cid-ftxg5n3y]{display:flex;align-items:center;justify-content:center;gap:var(--space-md);padding-top:var(--space-md)}.wi-post__share-label[data-astro-cid-ftxg5n3y]{font-size:var(--text-sm);color:var(--ink-3);font-weight:500}.wi-post__share-buttons[data-astro-cid-ftxg5n3y]{display:flex;align-items:center;gap:.5rem}.wi-post__share-btn[data-astro-cid-ftxg5n3y]{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;border-radius:50%;border:1px solid var(--rule);background:var(--bg-raised);color:var(--ink-3);cursor:pointer;text-decoration:none;transition:color .2s ease,border-color .2s ease,background .2s ease,transform .2s ease}.wi-post__share-btn[data-astro-cid-ftxg5n3y]:hover{color:var(--accent);border-color:var(--accent);background:color-mix(in srgb,var(--accent) 6%,var(--bg));transform:translateY(-1px)}.wi-post__share-btn--qq[data-astro-cid-ftxg5n3y]:hover{color:#12b7f5;border-color:#12b7f5;background:color-mix(in srgb,#12b7f5 6%,var(--bg))}.wi-post__share-btn--wechat[data-astro-cid-ftxg5n3y]:hover{color:#07c160;border-color:#07c160;background:color-mix(in srgb,#07c160 6%,var(--bg))}.wi-post__share-btn--weibo[data-astro-cid-ftxg5n3y]:hover{color:#e6162d;border-color:#e6162d;background:color-mix(in srgb,#e6162d 6%,var(--bg))}.wi-post__share-btn--facebook[data-astro-cid-ftxg5n3y]:hover{color:#1877f2;border-color:#1877f2;background:color-mix(in srgb,#1877f2 6%,var(--bg))}.wi-post__share-btn--telegram[data-astro-cid-ftxg5n3y]:hover{color:#08c;border-color:#08c;background:color-mix(in srgb,#0088cc 6%,var(--bg))}.wi-post__share-btn--copied[data-astro-cid-ftxg5n3y]{color:#22c55e;border-color:#22c55e}.wi-post__share-btn--copied[data-astro-cid-ftxg5n3y]:after{content:"✓";font-size:14px;position:absolute}.wi-post__share-btn--copied[data-astro-cid-ftxg5n3y] svg[data-astro-cid-ftxg5n3y]{display:none}.wi-post__share-btn--copy[data-astro-cid-ftxg5n3y]{position:relative}.wi-wechat-qr[data-astro-cid-ftxg5n3y]{display:none;position:fixed;inset:0;z-index:9999;align-items:center;justify-content:center;background:#00000073;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}.wi-wechat-qr--visible[data-astro-cid-ftxg5n3y]{display:flex}.wi-wechat-qr__card[data-astro-cid-ftxg5n3y]{background:var(--bg);border-radius:var(--radius-lg, 24px);padding:var(--space-2xl, 2rem);text-align:center;max-width:300px;width:90%;box-shadow:0 20px 60px #0003;position:relative}.wi-wechat-qr__title[data-astro-cid-ftxg5n3y]{font-size:var(--text-lg);font-weight:600;color:var(--ink);margin-bottom:var(--space-md)}.wi-wechat-qr__url[data-astro-cid-ftxg5n3y]{display:block;width:100%;box-sizing:border-box;margin:0 0 var(--space-sm, .5rem);padding:10px 12px;border:1px solid var(--rule);border-radius:var(--radius-sm, 8px);background:var(--bg-raised);color:var(--ink);font-size:var(--text-sm);text-overflow:ellipsis}.wi-wechat-qr__copy[data-astro-cid-ftxg5n3y]{display:block;width:100%;margin:0 0 var(--space-md);padding:10px 12px;border:none;border-radius:var(--radius-sm, 8px);background:var(--accent);color:#fff;font-size:var(--text-sm);cursor:pointer;transition:opacity .15s ease}.wi-wechat-qr__copy[data-astro-cid-ftxg5n3y]:hover{opacity:.88}.wi-wechat-qr__hint[data-astro-cid-ftxg5n3y]{font-size:var(--text-sm);color:var(--ink-3);line-height:1.6}.wi-wechat-qr__close[data-astro-cid-ftxg5n3y]{position:absolute;top:12px;right:12px;width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;border:none;background:transparent;color:var(--ink-3);cursor:pointer;border-radius:50%;transition:background .15s ease,color .15s ease}.wi-wechat-qr__close[data-astro-cid-ftxg5n3y]:hover{background:var(--bg-raised);color:var(--ink)}.wi-post__comment[data-astro-cid-ftxg5n3y]{width:100%;max-width:600px;margin-inline:auto;content-visibility:auto;contain-intrinsic-size:auto 500px}halo\:comment[data-astro-cid-ftxg5n3y]{display:block;width:100%}.wi-post__nav[data-astro-cid-ftxg5n3y]{display:flex;justify-content:space-between;gap:var(--space-lg);padding-top:var(--space-md);border-top:1px solid var(--rule)}.wi-post__nav-link[data-astro-cid-ftxg5n3y]{display:flex;flex-direction:column;gap:.35rem;text-decoration:none;max-width:48%;padding:.85rem 1.1rem;border-radius:var(--radius-md, 12px);background:transparent;transition:background .2s ease,transform .2s var(--ease-out-quart)}.wi-post__nav-link[data-astro-cid-ftxg5n3y]:hover{background:color-mix(in srgb,var(--accent) 7%,transparent)}.wi-post__nav-link--prev[data-astro-cid-ftxg5n3y]:hover{transform:translate(-3px)}.wi-post__nav-link--next[data-astro-cid-ftxg5n3y]:hover{transform:translate(3px)}.wi-post__nav-label[data-astro-cid-ftxg5n3y]{display:inline-flex;align-items:center;gap:.4rem;font-size:var(--text-xs);color:var(--ink-3);letter-spacing:var(--tracking-wider);text-transform:uppercase}.wi-post__nav-link--next[data-astro-cid-ftxg5n3y]{text-align:right;margin-left:auto}.wi-post__nav-link--next[data-astro-cid-ftxg5n3y] .wi-post__nav-label[data-astro-cid-ftxg5n3y]{justify-content:flex-end}.wi-post__nav-link--empty[data-astro-cid-ftxg5n3y]{visibility:hidden}.wi-post__nav-link--prev[data-astro-cid-ftxg5n3y] .wi-post__nav-label[data-astro-cid-ftxg5n3y]:before{content:"←";font-size:.9em;transition:transform .2s var(--ease-out-quart)}.wi-post__nav-link--next[data-astro-cid-ftxg5n3y] .wi-post__nav-label[data-astro-cid-ftxg5n3y]:after{content:"→";font-size:.9em;transition:transform .2s var(--ease-out-quart)}.wi-post__nav-link--prev[data-astro-cid-ftxg5n3y]:hover .wi-post__nav-label[data-astro-cid-ftxg5n3y]:before{transform:translate(-2px)}.wi-post__nav-link--next[data-astro-cid-ftxg5n3y]:hover .wi-post__nav-label[data-astro-cid-ftxg5n3y]:after{transform:translate(2px)}.wi-post__nav-title[data-astro-cid-ftxg5n3y]{font-family:var(--font-sans);font-size:var(--text-sm);font-weight:600;color:var(--ink-2);line-height:var(--leading-snug);transition:color var(--duration-fast) var(--ease-out-quart)}.wi-post__nav-link[data-astro-cid-ftxg5n3y]:hover .wi-post__nav-title[data-astro-cid-ftxg5n3y]{color:var(--accent)}.wi-post__related[data-astro-cid-ftxg5n3y]{padding-top:var(--space-xl);border-top:1px solid var(--rule);content-visibility:auto;contain-intrinsic-size:auto 500px}.wi-post__related-title[data-astro-cid-ftxg5n3y]{font-family:var(--font-sans);font-size:var(--text-lg);font-weight:700;color:var(--ink);margin-bottom:var(--space-lg)}.wi-post__related-grid[data-astro-cid-ftxg5n3y]{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--space-md)}.wi-post__related-card[data-astro-cid-ftxg5n3y]{display:flex;flex-direction:column;border-radius:12px;overflow:hidden;background:var(--bg-raised);border:1px solid var(--rule);text-decoration:none;color:inherit;transition:transform var(--duration-fast) var(--ease-out-quart),box-shadow var(--duration-fast) var(--ease-out-quart)}.wi-post__related-card[data-astro-cid-ftxg5n3y]:hover{transform:translateY(-2px);box-shadow:var(--shadow-md)}.wi-post__related-cover[data-astro-cid-ftxg5n3y]{aspect-ratio:16 / 9;overflow:hidden}.wi-post__related-cover[data-astro-cid-ftxg5n3y] img[data-astro-cid-ftxg5n3y]{width:100%;height:100%;object-fit:cover;transition:transform var(--duration-fast) var(--ease-out-quart)}.wi-post__related-card[data-astro-cid-ftxg5n3y]:hover .wi-post__related-cover[data-astro-cid-ftxg5n3y] img[data-astro-cid-ftxg5n3y]{transform:scale(1.03)}.wi-post__related-body[data-astro-cid-ftxg5n3y]{padding:var(--space-md);display:flex;flex-direction:column;gap:.25rem;flex:1}.wi-post__related-name[data-astro-cid-ftxg5n3y]{font-family:var(--font-sans);font-size:var(--text-sm);font-weight:600;color:var(--ink);line-height:var(--leading-snug);transition:color var(--duration-fast) var(--ease-out-quart)}.wi-post__related-card[data-astro-cid-ftxg5n3y]:hover .wi-post__related-name[data-astro-cid-ftxg5n3y]{color:var(--accent)}.wi-post__related-date[data-astro-cid-ftxg5n3y]{font-size:var(--text-xs);color:var(--ink-3)}@media(max-width:480px){.wi-post__related-grid[data-astro-cid-ftxg5n3y]{grid-template-columns:1fr}.wi-post__body[data-astro-cid-ftxg5n3y]{font-size:1.05rem;line-height:1.85}.wi-post__title[data-astro-cid-ftxg5n3y]{font-size:clamp(1.6rem,5vw,2.8rem)}}@media(max-width:768px){.wi-post__related-grid[data-astro-cid-ftxg5n3y]{grid-template-columns:1fr}.wi-post__cover-wrap[data-astro-cid-ftxg5n3y]{width:calc(100% + 2rem);margin-inline:-1rem;border-radius:0}.wi-post__cover[data-astro-cid-ftxg5n3y]{border-radius:0;max-height:280px}.wi-post__nav[data-astro-cid-ftxg5n3y]{gap:var(--space-sm)}.wi-post__nav-link[data-astro-cid-ftxg5n3y]{max-width:48%;min-width:0;padding:.6rem .75rem}.wi-post__nav-title[data-astro-cid-ftxg5n3y]{font-size:var(--text-xs);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wi-post__nav-link--next[data-astro-cid-ftxg5n3y]{text-align:right}.wi-post__body[data-astro-cid-ftxg5n3y]{font-size:1.025rem;line-height:1.82}}.wi-toc__group{position:relative}.wi-toc__sub{overflow:hidden;max-height:0;opacity:0;transition:max-height .3s ease,opacity .25s ease}.wi-toc__group--active .wi-toc__sub,.wi-toc__group--expanded .wi-toc__sub,.wi-toc[data-expand=all] .wi-toc__sub{max-height:1600px;opacity:1}.wi-toc__link{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;padding:5px 8px;font-size:var(--text-xs);line-height:var(--leading-snug);color:var(--ink-3);text-decoration:none;border-radius:6px;border-left:2px solid transparent;transition:color .15s ease,background .15s ease,border-color .15s ease}.wi-toc__link:hover{color:var(--accent);background:color-mix(in srgb,var(--accent) 5%,transparent)}.wi-toc__link--active{color:var(--accent);font-weight:600;border-left-color:var(--accent);background:color-mix(in srgb,var(--accent) 8%,transparent)}.wi-toc__link--group{padding-left:8px;padding-right:8px;font-weight:600;color:var(--ink-2);cursor:pointer}.wi-toc__link--h3{padding-left:22px}.wi-toc__link--h4{padding-left:36px}.wi-toc__link--h5{padding-left:50px}.wi-toc__link--h6{padding-left:64px}.wi-toc__sub .wi-toc__link--active{border-left-color:var(--accent)}.wi-toc-drawer__nav .wi-toc__link{font-size:var(--text-sm);padding:6px 0}
diff --git a/docs/changelog.md b/docs/changelog.md
index 51e1a44..43fa8ec 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -2,6 +2,24 @@
## v1.2.5
+### 上架审核整改(v1.2.5 修订)
+
+- 🔒 **默认零第三方自动请求**:移除全部 21 个模板对 `cdn.jsdelivr.net` 的 `preconnect` / `dns-prefetch`
+- 🔒 移除文章页 PrismJS 1.29.0、`autoloader`(会按代码语言从 CDN 动态拉取并执行组件)与
+ `qrcode-generator`;代码高亮改由应用市场代码高亮插件 / API 扩展包提供,主题只保留自托管的
+ Warm / Cold token 配色(微信分享改为「复制链接」)
+- 🔒 一言默认改用内置本地句子库 `assets/data/hitokoto.json`,不再默认请求 `v1.hitokoto.cn`
+- 🔒 欢迎弹窗默认显示本地静态欢迎语,移除内置的 IP 定位主 / 备选第三方接口;IP 定位改为
+ **管理员显式配置**的可选能力(开启时会向该服务发送访客 IP,需自行披露)
+- 📄 README 新增「外部资源与隐私」章节(服务名称 / 用途 / 数据类型 / 默认状态 / 关闭方法 /
+ 遥测声明)与「验证与回归测试」清单;补充 LXGW WenKai 的 OFL 来源与 LightGallery GPLv3 说明
+- 🔗 修正上架说明中的无效链接:文档与预览图改为有效绝对地址并随包分发;朋友圈插件链接改为应用市场地址
+- 🐛 全部 `#strings.substring(..., 0, 1)` 增加空值保护,修复 `friends.html` 作者为空时的字符串越界
+- 🖼️ `logo.png` 由两份 1024×1024(各约 1.5MB)压缩去重为单份 256×256(约 146KB)
+- ✅ LightGallery 去掉占位 `licenseKey`,按 GPLv3 开源许可使用,控制台不再出现授权提示
+- 🧪 新增 `pnpm audit:external` 与打包前置校验:拦截第三方远程加载、预连接、动态加载、
+ README 死链、重复大图;`docs/*.md` 与预览图一并打进安装包
+
### 兼容性与元数据
- ⬆️ Halo 版本要求提升至 **2.27.0+**(页面布局契约、微信读书页面依赖 2.27 的能力)
diff --git a/docs/config/article.md b/docs/config/article.md
index 72d957e..7adbcee 100644
--- a/docs/config/article.md
+++ b/docs/config/article.md
@@ -84,7 +84,7 @@
### 代码高亮主题(article_code_theme)
-WarmIsland 使用 Prism.js 实现代码语法高亮,提供两种主题风格:
+主题不再加载 Prism.js 运行时与 autoloader,**语法高亮由 Halo 应用市场的代码高亮插件或「API 扩展包」服务端高亮输出**。本项只切换主题自托管的两套 `.token` 配色(`assets/vendor/prism-warm.css` / `prism-cold.css`),不产生任何远程请求:
| 值 | 说明 |
| --- | --- |
@@ -100,7 +100,7 @@ article_code_theme: warm
```
::: tip
-代码高亮支持多种编程语言,包括 JavaScript、Python、Java、Go、Rust、YAML、JSON、Bash 等。语言检测基于代码块的语言标记自动完成。
+支持的语言范围取决于所安装的高亮插件(常见的 JavaScript、Python、Java、Go、Rust、YAML、JSON、Bash 等均可覆盖)。语言识别基于代码块的语言标记,建议始终显式标注。
:::
### 目录展开模式(article_toc_expand)
@@ -136,7 +136,7 @@ article_related_title: "猜你喜欢"
| 平台 | 说明 |
| --- | --- |
| QQ | 点击后弹出 QQ 分享窗口,自动携带文章标题和链接 |
-| 微信 | 扫码分享到微信(生成文章完整 URL 的二维码) |
+| 微信 | 弹出文章链接并提供「复制链接」按钮,由访客在微信内分享(不加载二维码库) |
| 微博 | 分享到新浪微博 |
| X (Twitter) | 分享到 X |
| Facebook | 分享到 Facebook |
@@ -157,7 +157,7 @@ article_related_title: "猜你喜欢"
| Telegram | ❌ |
::: info 图标来源
-分享按钮图标来自 [Font Awesome](https://fontawesome.com/)(Free v7.2.0,CC BY 4.0 / SIL OFL 1.1 协议)和 [Simple Icons](https://simpleicons.org/)(CC0 协议)。微信图标点击后会弹出二维码弹窗,用户可使用微信扫码分享。
+分享按钮图标来自 [Font Awesome](https://fontawesome.com/)(Free v7.2.0,CC BY 4.0 / SIL OFL 1.1 协议)和 [Simple Icons](https://simpleicons.org/)(CC0 协议)。微信图标点击后会弹出「复制链接」弹层,用户复制后可在微信内分享;二维码由远程库生成的旧实现已移除,主题不再加载任何第三方脚本。
:::
#### 分享区域标题(article_share_title)
@@ -174,7 +174,7 @@ article_share_channels:
```
::: tip
-分享功能会自动携带文章标题和完整链接,无需手动配置分享内容。微信分享通过二维码实现,二维码内容为文章的完整 URL(包含域名),用户扫码后可直接打开文章。QQ 分享通过弹窗方式打开 QQ 分享页面。移动端会优先显示适合移动端的分享方式。
+分享功能会自动携带文章标题和完整链接,无需手动配置分享内容。微信分享通过「复制链接」实现:弹层显示文章完整 URL(含域名),点击按钮写入剪贴板,全程在浏览器本地完成、不发送外部请求。QQ 分享通过弹窗方式打开 QQ 分享页面。除访客主动点击外,页面加载时不会访问任何第三方平台。
:::
## 阅读增强功能
diff --git a/docs/config/hero.md b/docs/config/hero.md
index 2928453..7bda3dc 100644
--- a/docs/config/hero.md
+++ b/docs/config/hero.md
@@ -10,8 +10,8 @@ Hero 首屏是 WarmIsland 主题的标志性区域,占据首页首屏的全部
| `hero_title` | 文本 | — | 主标题(留空则使用站点标题) |
| `hero_subtitle` | 文本 | — | 副标题(留空则使用站点副标题) |
| `hero_description_mode` | 下拉 | `hitokoto` | 描述文案来源:`none` / `hitokoto` / `custom` |
-| `hero_hitokoto_api` | URL | `https://v1.hitokoto.cn/` | 一言 API 地址 |
-| `hero_hitokoto_categories` | 多选 | `d, i, k` | 一言句子类型 |
+| `hero_hitokoto_api` | URL | 留空 | 自定义一言 API;留空使用主题内置本地句子库(零外部请求) |
+| `hero_hitokoto_categories` | 多选 | `d, i, k` | 句子类型筛选,仅对自定义 API 生效 |
| `hero_custom_description` | 多行文本 | — | 自定义描述文案 |
| `hero_hitokoto_auto_refresh` | 开关 | `false` | 一言自动刷新 |
| `hero_hitokoto_refresh_interval` | 下拉 | `10000` | 一言刷新间隔(秒) |
@@ -49,24 +49,28 @@ hero_subtitle: "记录温暖与灵感"
| 值 | 说明 |
| --- | --- |
| `none` | 不显示描述文案 |
-| `hitokoto` | 从一言 API 实时获取随机句子(默认) |
+| `hitokoto` | 从主题内置本地句子库随机取一条(默认,零外部请求) |
| `custom` | 显示自定义的描述文案 |
### 一言模式(hitokoto)
-选择 `hitokoto` 模式后,Hero 区域会从一言 API 获取随机句子,每次刷新页面都会展示不同的内容。
+选择 `hitokoto` 模式后,Hero 区域**默认读取主题内置的本地句子库** `assets/data/hitokoto.json`
+(随主题自托管的静态 JSON),每次刷新随机展示一条,不依赖任何外部服务,也不会因为第三方接口
+不可用而出现空白。可直接编辑该 JSON 替换成自己喜欢的句子。
-#### 一言 API 地址(hero_hitokoto_api)
+#### 自定义一言 API(hero_hitokoto_api)
-默认使用官方 API `https://v1.hitokoto.cn/`。如果你部署了一言的私有实例,可以修改为自建 API 地址。
+默认**留空**(零外部请求)。如果需要使用在线一言或自建实例,可在此填写 API 地址;
+填写后浏览器才会向该地址发起 GET 请求,第三方可见访客 IP / User-Agent。
+请自行评估服务商隐私政策,并在站点隐私说明中披露,详见 README「外部资源与隐私」。
-::: tip
-自建一言 API 可以提升加载速度并避免第三方服务不稳定的问题。部署方式请参考 [hitokoto 官方文档](https://developer.hitokoto.cn/)。
+::: warning
+这是站点管理员主动开启的可选能力。留空即可随时关闭,无需重装主题。
:::
#### 一言句子类型(hero_hitokoto_categories)
-通过勾选来筛选获取的句子类型,支持多选:
+仅在填写了自定义 API 时生效,用于筛选该接口返回的句子类型,支持多选:
| 值 | 类型 | 说明 |
| --- | --- | --- |
diff --git a/docs/customize/development.md b/docs/customize/development.md
index 542546c..e31812d 100644
--- a/docs/customize/development.md
+++ b/docs/customize/development.md
@@ -54,18 +54,36 @@ pnpm build
### 打包主题
```bash
-pnpm release # 推荐:build → snapshot → package
-# 或分步:pnpm build && pnpm snapshot && pnpm package
+pnpm release # 推荐:build → snapshot → package(含合规审计)
+# 或分步:pnpm build && pnpm snapshot && pnpm audit:external && pnpm package
```
生成 `dist/warm-island-.zip`,可直接在 Halo 后台上传安装。
-ZIP 根目录直接放 `theme.yaml` / `settings.yaml` / `templates/`,不要额外套一层目录。
+ZIP 根目录直接放 `theme.yaml` / `settings.yaml` / `templates/`,不要额外套一层目录;
+`docs/*.md` 与预览图也会一并打包,保证上架说明里的引用在制品中真实存在。
> **`templates/` 被 `.gitignore` 忽略**,克隆仓库后默认不存在。若跳过 `pnpm build`
> 直接 `pnpm package`,过去会产出不含任何模板的空壳包,上传后 Halo 报
> `Template index was not found.` 并让首页 404。现在打包脚本会先校验
> `index.html` 等模板、资源目录与版本号,不满足即报错退出。
+### 上架合规审计
+
+`scripts/audit-external.mjs`(`pnpm audit:external`)在打包前自动执行,对应应用市场审核指南
+1.4 / 4.4.4 / 4.6 / 5.1,检查:
+
+- 模板中不含第三方 `preconnect` / `dns-prefetch` / `preload`
+- 不含 `src` / `href` 指向 http(s) 的远程脚本、样式、图片、媒体
+- 不含硬编码的外部 `fetch()` / 动态 `import()` / CSS `url(https://...)`,
+ 也不含 Prism autoloader 之类的动态加载痕迹与被移除的服务域名
+- README 中的相对链接都能在安装包清单里找到对应文件
+- `templates/logo.png` 不存在(只允许一份压缩后的 `templates/assets/logo.png`)
+
+任一不通过,`pnpm package` 直接 `exit 1`,避免打出不合规制品。
+`` 这类需要访客主动点击的外链是允许的,但必须在 README
+「外部资源与隐私」章节逐项披露。新增第三方资源时,请先自托管到 `public/assets/`
+并在 README 的许可表中登记来源与协议。
+
## 性能与滚动顺滑
首页滚动曾经卡顿,根因不是 DOM 数量,而是「每帧高成本绘制 + 劫持滚动输入」:
@@ -98,60 +116,36 @@ ZIP 根目录直接放 `theme.yaml` / `settings.yaml` / `templates/`,不要额
## 项目结构
+以下按磁盘实际文件列出(`src/pages/` 下每个 `.astro` 对应一个输出模板,共 21 个):
+
```
WarmIsland/
-├── src/ # 源代码
-│ ├── components/ # Astro 组件
-│ │ ├── HeroSection.astro # Hero 首屏
-│ │ ├── PostCard.astro # 文章卡片
-│ │ ├── Pagination.astro # 分页组件
-│ │ ├── TableOfContents.astro # 目录组件
-│ │ ├── ReadingProgress.astro # 阅读进度条
-│ │ ├── LikeButton.astro # 点赞按钮
-│ │ ├── CommentSection.astro # 评论区
-│ │ ├── Footer.astro # 页脚
-│ │ └── ...
-│ ├── layouts/ # 布局组件
-│ │ ├── BaseLayout.astro # 基础布局
-│ │ └── PostLayout.astro # 文章布局
-│ ├── lib/ # 工具库
-│ │ ├── pages.ts # 页面路由定义
-│ │ └── utils.ts # 工具函数
-│ ├── pages/ # 页面入口
-│ │ ├── index.astro # 首页
-│ │ ├── post.astro # 文章详情
-│ │ ├── archives.astro # 归档
-│ │ ├── categories.astro # 分类
-│ │ ├── tags.astro # 标签
-│ │ ├── moments.astro # 瞬间
-│ │ ├── photos.astro # 图库
-│ │ ├── links.astro # 友情链接
-│ │ ├── friends.astro # 朋友圈
-│ │ ├── page.astro # 自定义页面
-│ │ ├── page_messageboard.astro # 留言板
-│ │ ├── douban.astro # 豆瓣
-│ │ ├── equipments.astro # 装备
-│ │ ├── footprints.astro # 足迹
-│ │ └── steam.astro # Steam
-│ └── styles/ # 样式文件
-│ ├── main.scss # 主样式入口
-│ ├── _variables.scss # SCSS 变量
-│ ├── _colors.scss # 颜色定义
-│ ├── _typography.scss # 排版
-│ ├── _spacing.scss # 间距
-│ ├── _animations.scss # 动画
-│ ├── _mixins.scss # 混入
-│ └── components/ # 组件样式
-├── templates/ # Thymeleaf 模板(构建输出)
-├── public/ # 静态资源
-│ └── assets/ # 编译后的 CSS/JS
-├── docs/ # 文档
-├── theme.yaml # 主题元信息
-├── settings.yaml # 主题设置定义
-├── astro.config.mjs # Astro 配置
-├── package.json # 项目依赖
-├── tsconfig.json # TypeScript 配置
-└── vite.config.ts # Vite 配置
+├── src/
+│ ├── components/ # 18 个组件:Navbar / Footer / HeroSection / WelcomePopup /
+│ │ # LightGallery / ClickEffect / ScrollReveal / ThemeSwitcher 等
+│ ├── layouts/ # Layout.astro(含页面布局契约渲染模式)
+│ ├── pages/ # index / post / page / archives / categories / category /
+│ │ # tags / tag / links / friends / moments / moment / photos /
+│ │ # douban / equipments / important-dates / steam / weread /
+│ │ # page_messageboard / page_subscriptions / layout
+│ └── styles/ # main.scss + 12 个 _*.scss 片段
+├── public/
+│ ├── assets/
+│ │ ├── data/ # hitokoto.json 内置本地句子库
+│ │ ├── fonts/ # LXGW WenKai 本地 woff2 分片与 result.css
+│ │ ├── vendor/ # 自托管 prism-warm.css / prism-cold.css
+│ │ └── logo.png # 主题图标(唯一一份,256×256 压缩版)
+│ ├── error/ # 错误页静态资源
+│ └── favicon.ico
+├── compat/assets/ # 历史版本哈希静态资源,构建时合并进 templates/assets
+├── scripts/ # postbuild / snapshot-assets / package / audit-external / smoke-clickfx
+├── docs/ # VitePress 文档站(*.md 随安装包分发)
+├── templates/ # 构建产物(被 .gitignore 忽略)
+├── dist/ # ZIP 安装包(被 .gitignore 忽略)
+├── theme.yaml # 主题清单与自定义页面模板注册
+├── settings.yaml # 后台设置项定义
+├── astro.config.mjs / package.json / pnpm-lock.yaml / tsconfig.json
+└── WarmIsland Preview.webp
```
## 技术栈
@@ -170,7 +164,8 @@ WarmIsland/
|------|------|
| [SCSS](https://sass-lang.com/) | 样式预处理,CSS 变量驱动的配色系统 |
| 原生 JavaScript | 滚动、菜单、主题切换、无限滚动等交互,无前端框架运行时 |
-| [LightGallery](https://www.lightgalleryjs.com/) | 图片灯箱(可选) |
+| [LightGallery](https://www.lightgalleryjs.com/) 2.9.0(GPLv3) | 图片灯箱,默认内置;安装 lightgallery.js 插件后自动让位 |
+| [LXGW WenKai](https://github.com/lxgw/LxgwWenKai)(OFL 1.1) | 本地字体分片,随主题自托管,无远程字体请求 |
| Halo 插件生态 | 评论、代码高亮、服务端字数统计等由 Halo 与可选插件提供 |
### 构建工具
diff --git a/docs/faq.md b/docs/faq.md
index 6e18a25..0b84a1d 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -120,11 +120,24 @@ v1.2.1 起安装包会保留各版本的哈希资源(v1.1.7 之后全部覆盖
安装并启用插件后,页面会自动显示内容。未安装插件时页面会显示友好的提示信息。
-### 一言 API 加载失败?
+### 主题会向第三方服务发请求吗?
-1. 检查网络是否能访问 `https://v1.hitokoto.cn/`
-2. 如果在国内服务器,可能存在网络延迟
-3. 可以在主题设置中更换一言 API 地址为自建镜像
+**默认不会。** 所有脚本、样式、字体、图标、主题 Logo 与一言句子库都随主题自托管,
+页面不预连接任何 CDN,也不加载远程脚本。只有两类例外:
+
+1. 访客**主动点击**分享按钮 / 备案链接 / 应用市场链接时才跳转到对应站点;
+2. 站点管理员在主题设置里**显式填写**了「自定义一言 API」或「自定义 IP 定位 API」——
+ 后者会把访客 IP 发送给该服务,请在站点隐私说明中披露。
+
+完整逐项说明见 README 的「外部资源与隐私」章节。发版前可运行 `pnpm audit:external` 复核。
+
+### 一言不显示,或想换成自己的句子?
+
+1. 主题默认读取内置的 `assets/data/hitokoto.json` 本地句子库,不依赖外部服务;
+ 直接编辑该文件即可替换文案
+2. 想继续使用在线一言,可在主题设置 → Hero 首屏 → 填写「自定义一言 API」;
+ 此时才会产生外部请求,且「句子类型」筛选仅对自定义 API 生效
+3. 若选择「自定义」模式,请在「自定义描述文案」中填写内容
### 文章目录不显示?
@@ -135,9 +148,11 @@ v1.2.1 起安装包会保留各版本的哈希资源(v1.1.7 之后全部覆盖
### 代码高亮不生效?
-1. 确认代码块使用了正确的语言标记(如 \`\`\`python)
-2. Prism.js 支持自动语言检测,但部分语言需要手动标记
-3. 检查浏览器控制台是否有 Prism.js 加载错误
+主题**不再自带 Prism.js 运行时**(含 autoloader),因此不会自动给代码块加语法着色:
+
+1. 安装 Halo 应用市场的代码高亮插件,或启用「API 扩展包」的服务端代码高亮
+2. 主题设置 → 文章 → 「代码高亮主题」仅切换本地自托管的 Warm / Cold `.token` 配色
+3. 确认代码块使用了正确的语言标记(如 \`\`\`python),以便插件识别语言
## 样式相关
diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md
index 44678be..9380e56 100644
--- a/docs/guide/getting-started.md
+++ b/docs/guide/getting-started.md
@@ -64,7 +64,7 @@ WarmIsland(暖屿)是一款为 [Halo](https://halo.run) 打造的博客主
进入 **Hero 首屏** 设置:
- 设置主标题和副标题
-- 选择描述文案来源(一言/自定义/无)
+- 选择描述文案来源(一言 / 自定义 / 无);「一言」默认读取主题内置的本地句子库,不请求外部服务
- 可选上传背景图片
### 第五步:页脚信息
diff --git a/docs/guide/introduction.md b/docs/guide/introduction.md
index 01165df..b123f7d 100644
--- a/docs/guide/introduction.md
+++ b/docs/guide/introduction.md
@@ -35,7 +35,7 @@
- **文章目录** — 桌面端侧边悬浮 + 移动端底部抽屉,支持展开模式选项
- **阅读进度条** — 实时显示阅读进度
-- **代码高亮** — Prism.js 支持,Warm / Cold 双主题
+- **代码高亮** — 由代码高亮插件 / API 扩展包输出,主题提供 Warm / Cold 本地配色,不加载远程脚本
- **图片灯箱** — 点击放大,支持左右切换
- **图片懒加载** — blur-up 渐显效果,优化加载体验
- **点赞功能** — 一键点赞,状态持久化
@@ -71,7 +71,7 @@
### ⚙️ 灵活配置
-13 个配置分组,覆盖站点的方方面面:
+17 个配置分组、120 余个配置项,覆盖站点的方方面面:
- 基础信息、样式、导航栏、Hero 首屏
- 首页、文章、瞬间、图库、友情链接
@@ -81,13 +81,12 @@
## 技术栈
-- **Astro** — 静态站点生成器,编译模板
-- **vite-plugin-halo-theme** — Halo 主题 Vite 插件
+- **Astro 6** — 构建输出 Thymeleaf 模板
- **Thymeleaf** — Halo 使用的服务端模板引擎
-- **Vue 3** — 部分交互组件(搜索、主题切换、光感跟随)
-- **SCSS** — CSS 预处理器
-- **Prism.js** — 代码语法高亮
-- **LightGallery** — 图片灯箱
+- **原生 JavaScript + SCSS** — 交互与样式(无 Vue / 无远程运行时)
+- **LightGallery 2.9.0(GPLv3)** — 图片灯箱,随构建自托管打包
+- **LXGW WenKai(OFL 1.1)** — 本地字体分片,无远程字体请求
+- **代码高亮** — 交由 Halo 应用市场插件提供,主题只自托管配色 CSS
## 适用场景
diff --git a/docs/pages/post.md b/docs/pages/post.md
index 9cdc7df..f2ca9d7 100644
--- a/docs/pages/post.md
+++ b/docs/pages/post.md
@@ -34,10 +34,15 @@
## 代码高亮
-使用 Prism.js 实现代码块语法高亮,支持自动语言检测。可在主题设置中选择两种主题:
+主题**不再自带 Prism.js 运行时**(含按语言从 CDN 动态加载组件的 autoloader)。语法高亮由
+Halo 应用市场的代码高亮插件或「API 扩展包」服务端高亮输出,主题只提供两套自托管的
+`.token` 配色,在「主题设置 → 文章 → 代码高亮主题」中切换:
-- **Warm**:浅色代码高亮,与暖色调设计一致
-- **Cold**:深色代码高亮(Tomorrow Night),适合代码密集型文章
+- **Warm**:浅色代码高亮,与暖色调设计一致(默认)
+- **Cold**:深色代码高亮(Tomorrow 风格),适合代码密集型文章
+
+未安装高亮插件时,代码块仍以等宽字体正常显示,只是没有语法着色;主题不会因此报错,
+也不会发起任何外部请求。
## 图片灯箱
diff --git a/package.json b/package.json
index 46db3f3..6e43369 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,7 @@
"build": "astro build && node scripts/postbuild.mjs",
"snapshot": "node scripts/snapshot-assets.mjs",
"package": "node scripts/package.mjs",
+ "audit:external": "node scripts/audit-external.mjs",
"release": "pnpm run build && pnpm run snapshot && pnpm run package",
"smoke": "node scripts/smoke-clickfx.mjs",
"astro": "astro",
diff --git a/public/assets/data/hitokoto.json b/public/assets/data/hitokoto.json
new file mode 100644
index 0000000..bf22fc4
--- /dev/null
+++ b/public/assets/data/hitokoto.json
@@ -0,0 +1,27 @@
+{
+ "note": "WarmIsland 内置本地一言句子库,纯静态数据,零外部请求。站点可自行编辑本文件替换文案。",
+ "sentences": [
+ { "text": "暖如灯火,静如留白。", "from": "暖屿" },
+ { "text": "慢慢来,比较快。", "from": "暖屿" },
+ { "text": "把日子过成自己喜欢的样子。", "from": "暖屿" },
+ { "text": "风会记得每一朵花的香。", "from": "暖屿" },
+ { "text": "愿你在忙碌里,仍留一扇看云的窗。", "from": "暖屿" },
+ { "text": "写下此刻,便是对时间最轻柔的挽留。", "from": "暖屿" },
+ { "text": "山不争高,水不争先。", "from": "暖屿" },
+ { "text": "有些答案,要走很远才遇见。", "from": "暖屿" },
+ { "text": "夜色温柔,适合读一封长信。", "from": "暖屿" },
+ { "text": "把灯留着,等一个晚归的人。", "from": "暖屿" },
+ { "text": "心有所栖,便是归处。", "from": "暖屿" },
+ { "text": "日常里藏着最不动声色的欢喜。", "from": "暖屿" },
+ { "text": "不必催促春天,花自会开。", "from": "暖屿" },
+ { "text": "走过的路,都会变成身上的光。", "from": "暖屿" },
+ { "text": "一杯热茶,足以安顿一个傍晚。", "from": "暖屿" },
+ { "text": "愿你所念之人,也在念着你。", "from": "暖屿" },
+ { "text": "安静不是空白,是留给自己呼吸。", "from": "暖屿" },
+ { "text": "把复杂留给世界,把简单留给生活。", "from": "暖屿" },
+ { "text": "长路漫漫,慢慢走也算抵达。", "from": "暖屿" },
+ { "text": "今天也值得被认真记下。", "from": "暖屿" },
+ { "text": "微光虽小,足以照亮一隅。", "from": "暖屿" },
+ { "text": "在喧嚣之外,给自己留一座小岛。", "from": "暖屿" }
+ ]
+}
diff --git a/public/assets/logo.png b/public/assets/logo.png
new file mode 100644
index 0000000..a6d2c17
Binary files /dev/null and b/public/assets/logo.png differ
diff --git a/public/assets/vendor/prism-cold.css b/public/assets/vendor/prism-cold.css
new file mode 100644
index 0000000..43b79e7
--- /dev/null
+++ b/public/assets/vendor/prism-cold.css
@@ -0,0 +1,77 @@
+/* WarmIsland 自托管代码配色(Cold / 深色,参考 Prism Tomorrow 配色)
+ - 仅定义 .token.* 语法着色,供 Halo 代码高亮插件或「API 扩展包」服务端高亮输出使用。
+ - 主题本身不再加载 Prism.js / autoloader / 任何远程脚本。
+ - 本文件随主题分发,不含 @import、url()、远程字体或外部请求。
+*/
+pre[class*="language-"],
+code[class*="language-"] {
+ color: #cccccc;
+ background: none;
+ text-shadow: none;
+}
+
+pre[class*="language-"] {
+ background: #2d2d2d;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+}
+
+.token.comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+ color: #999999;
+}
+
+.token.punctuation {
+ color: #cccccc;
+}
+
+.token.property,
+.token.tag,
+.token.boolean,
+.token.number,
+.token.constant,
+.token.symbol,
+.token.deleted {
+ color: #f2777a;
+}
+
+.token.selector,
+.token.attr-name,
+.token.string,
+.token.char,
+.token.builtin,
+.token.inserted {
+ color: #99cc99;
+}
+
+.token.operator,
+.token.entity,
+.token.url,
+.token.variable {
+ color: #f99157;
+}
+
+.token.atrule,
+.token.attr-value,
+.token.keyword {
+ color: #cc99cc;
+}
+
+.token.function,
+.token.class-name {
+ color: #6699cc;
+}
+
+.token.regex,
+.token.important {
+ color: #ffcc66;
+}
+
+.token.bold {
+ font-weight: 600;
+}
+
+.token.italic {
+ font-style: italic;
+}
diff --git a/public/assets/vendor/prism-warm.css b/public/assets/vendor/prism-warm.css
new file mode 100644
index 0000000..3352526
--- /dev/null
+++ b/public/assets/vendor/prism-warm.css
@@ -0,0 +1,77 @@
+/* WarmIsland 自托管代码配色(Warm / 浅色)
+ - 仅定义 .token.* 语法着色,供 Halo 代码高亮插件或「API 扩展包」服务端高亮输出使用。
+ - 主题本身不再加载 Prism.js / autoloader / 任何远程脚本。
+ - 本文件随主题分发,不含 @import、url()、远程字体或外部请求。
+*/
+pre[class*="language-"],
+code[class*="language-"] {
+ color: #4b4038;
+ background: none;
+ text-shadow: none;
+}
+
+pre[class*="language-"] {
+ background: #f7f2ec;
+ border: 1px solid rgba(90, 70, 55, 0.1);
+}
+
+.token.comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+ color: #8c8279;
+}
+
+.token.punctuation {
+ color: #7a6f66;
+}
+
+.token.property,
+.token.tag,
+.token.boolean,
+.token.number,
+.token.constant,
+.token.symbol,
+.token.deleted {
+ color: #b04a3c;
+}
+
+.token.selector,
+.token.attr-name,
+.token.string,
+.token.char,
+.token.builtin,
+.token.inserted {
+ color: #567c4e;
+}
+
+.token.operator,
+.token.entity,
+.token.url,
+.token.variable {
+ color: #8a6b3f;
+}
+
+.token.atrule,
+.token.attr-value,
+.token.keyword {
+ color: #a0527f;
+}
+
+.token.function,
+.token.class-name {
+ color: #3f6e9e;
+}
+
+.token.regex,
+.token.important {
+ color: #bd7423;
+}
+
+.token.bold {
+ font-weight: 600;
+}
+
+.token.italic {
+ font-style: italic;
+}
diff --git a/public/logo.png b/public/logo.png
deleted file mode 100644
index 669cef4..0000000
Binary files a/public/logo.png and /dev/null differ
diff --git a/scripts/audit-external.mjs b/scripts/audit-external.mjs
new file mode 100644
index 0000000..4d10b54
--- /dev/null
+++ b/scripts/audit-external.mjs
@@ -0,0 +1,167 @@
+/**
+ * 外部资源与隐私审计(发版前自动检查,Node 内置模块,无第三方依赖)
+ *
+ * 对应 Halo 应用市场审核指南:
+ * - 1.4 远程执行与动态加载:禁止远程加载核心代码 / 动态拉取并执行组件
+ * - 4.4.4 性能与外部资源:不得预连接外部 CDN、加载远程脚本样式、打包无关大资源
+ * - 4.6 / 2.2:不得出现占位内容、无效链接
+ *
+ * 检查项:
+ * 1. 模板中不得有指向第三方的
+ * 2. 模板中不得有 src/href 直接指向 http(s) 的 script/link/img/iframe/source/video/audio
+ * 3. 模板与静态资源中不得硬编码 fetch("https://...") / import("https://...") / url(https://...)
+ * 4. 不得出现历史上被审核点名的第三方服务域名,或 autoloader 动态加载痕迹
+ * 5. README 中的相对链接必须在安装包清单中真实存在
+ *
+ * 说明: 这类需要访客主动点击的分享 / 备案 / 应用市场外链是允许的,
+ * 不属于「自动外部请求」,但已在 README「外部资源与隐私」章节逐项披露。
+ */
+import fs from "node:fs";
+import path from "node:path";
+import { fileURLToPath, pathToFileURL } from "node:url";
+
+const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
+
+/** 曾经造成未披露外部请求的服务域名,出现即视为回归 */
+const BLOCKED_HOSTS = [
+ "cdn.jsdelivr.net",
+ "unpkg.com",
+ "cdnjs.cloudflare.com",
+ "fonts.googleapis.com",
+ "fonts.gstatic.com",
+ "v1.hitokoto.cn",
+ "myip.ipip.net",
+ "r.inews.qq.com",
+];
+
+const REMOTE_LOAD_RE =
+ /<(?:script|link|img|iframe|source|video|audio)\b[^>]*\b(?:src|href)\s*=\s*["']https?:\/\/[^"']+["']/gi;
+const PRECONNECT_RE =
+ /]*\brel\s*=\s*["'](?:preconnect|dns-prefetch|preload)["'][^>]*\bhref\s*=\s*["']https?:\/\/[^"']+["']/gi;
+const REMOTE_FETCH_RE = /\b(?:fetch|importScripts)\s*\(\s*["']https?:\/\/[^"']+["']/gi;
+const REMOTE_IMPORT_RE = /\bimport\s*\(\s*["']https?:\/\/[^"']+["']/gi;
+const REMOTE_CSS_URL_RE = /url\(\s*["']?https?:\/\/[^)"']+/gi;
+const AUTOLOADER_RE = /\b(?:prism-autoloader|languages_path|Prism\.plugins\.autoloader)\b/gi;
+
+function listFiles(dir, exts, out = []) {
+ if (!fs.existsSync(dir)) return out;
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
+ const abs = path.join(dir, entry.name);
+ if (entry.isDirectory()) {
+ /* 字体目录只有本地 woff2 与版权注释,不参与审计 */
+ if (entry.name === "fonts") continue;
+ listFiles(abs, exts, out);
+ } else if (exts.some((ext) => entry.name.endsWith(ext))) {
+ out.push(abs);
+ }
+ }
+ return out;
+}
+
+export function auditTemplates(templatesDir = path.join(root, "templates")) {
+ const problems = [];
+ const files = listFiles(templatesDir, [".html", ".js", ".css"]);
+ const htmlFiles = files.filter((f) => f.endsWith(".html"));
+ /* 对外报告的模板数只算 templates/ 根目录下的 Halo 页面模板;
+ templates/error/ 下的错误页 HTML 同样参与审计,但不计入模板数。 */
+ const htmlCount = htmlFiles.filter((f) => path.dirname(f) === templatesDir).length;
+
+ if (htmlFiles.length === 0) {
+ problems.push("未找到任何 templates/*.html,构建产物不完整");
+ return { fileCount: 0, htmlCount: 0, problems };
+ }
+
+ for (const file of files) {
+ const rel = path.relative(root, file).replace(/\\/g, "/");
+ const text = fs.readFileSync(file, "utf8");
+ const isHtml = file.endsWith(".html");
+
+ const rules = isHtml
+ ? [
+ ["远程预连接 / 预加载", PRECONNECT_RE],
+ ["远程脚本 / 样式 / 媒体", REMOTE_LOAD_RE],
+ ["硬编码外部 fetch", REMOTE_FETCH_RE],
+ ["远程动态 import", REMOTE_IMPORT_RE],
+ ["autoloader 动态加载", AUTOLOADER_RE],
+ ]
+ : [
+ ["硬编码外部 fetch", REMOTE_FETCH_RE],
+ ["远程动态 import", REMOTE_IMPORT_RE],
+ ["CSS 远程 url()", REMOTE_CSS_URL_RE],
+ ];
+
+ for (const [label, re] of rules) {
+ re.lastIndex = 0;
+ const hit = re.exec(text);
+ if (hit) {
+ problems.push(`${rel}: ${label} → ${hit[0].slice(0, 110)}`);
+ }
+ }
+
+ for (const host of BLOCKED_HOSTS) {
+ if (isHtml && text.includes(host)) {
+ problems.push(`${rel}: 出现已移除的第三方服务域名 ${host}`);
+ }
+ }
+ }
+
+ return { fileCount: files.length, htmlCount, problems };
+}
+
+export function auditReadmeLinks(entryNames, readmePath = path.join(root, "README.md")) {
+ const problems = [];
+ if (!fs.existsSync(readmePath)) return ["README.md 不存在"];
+ const readme = fs.readFileSync(readmePath, "utf8");
+ const linkRe = /!?\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
+ let match;
+ let checked = 0;
+ while ((match = linkRe.exec(readme)) !== null) {
+ const href = match[1];
+ if (/^(?:https?:|mailto:|#)/.test(href)) continue;
+ let target = href.split("#")[0];
+ if (!target) continue;
+ try {
+ target = decodeURIComponent(target);
+ } catch (e) {
+ /* 保留原值 */
+ }
+ target = target.replace(/^\.\//, "");
+ checked++;
+ if (!entryNames.has(target)) {
+ problems.push(`README.md 相对链接在安装包中不存在:${href}(期望 ${target})`);
+ }
+ }
+ return { problems, checked };
+}
+
+function report(label, problems) {
+ if (problems.length === 0) {
+ console.log(`[audit] PASS ${label}`);
+ return 0;
+ }
+ for (const p of problems) console.error(`[audit] FAIL ${label}: ${p}`);
+ return problems.length;
+}
+
+const isCli = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
+if (isCli) {
+ const templates = auditTemplates();
+ let failures = 0;
+ failures += report(
+ `模板外部资源(${templates.htmlCount} 个 HTML / ${templates.fileCount} 个文件)`,
+ templates.problems
+ );
+ /* 独立运行时按当前磁盘上的打包清单口径检查 README 链接 */
+ const distZip = path.join(root, "dist");
+ const zip = fs.existsSync(distZip)
+ ? fs.readdirSync(distZip).filter((f) => f.endsWith(".zip") && f.includes("warm-island"))
+ : [];
+ if (zip.length) {
+ /* 简单口径:zip 存在时仅提示需通过 pnpm package 做完整链接校验 */
+ console.log(`[audit] 提示:README 链接校验在 pnpm package 内执行(发现 ${zip.length} 个安装包)`);
+ } else {
+ console.log("[audit] 提示:README 链接校验在 pnpm package 内执行(当前无安装包)");
+ }
+ console.log(`[audit] 模板审计共 ${failures} 项问题`);
+ process.exit(failures ? 1 : 0);
+}
diff --git a/scripts/package.mjs b/scripts/package.mjs
index 6a679d4..6af8d6c 100644
--- a/scripts/package.mjs
+++ b/scripts/package.mjs
@@ -10,6 +10,7 @@ import fs from "node:fs";
import path from "node:path";
import zlib from "node:zlib";
import { fileURLToPath } from "node:url";
+import { auditTemplates, auditReadmeLinks } from "./audit-external.mjs";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
@@ -131,11 +132,17 @@ const INCLUDE_ROOT = [
"settings.yaml",
"LICENSE",
"README.md",
+ /* 上架说明引用的预览图必须真实存在于制品中(审核指南 4.6 / 2.2) */
+ "WarmIsland Preview.webp",
"pnpm-lock.yaml",
"pnpm-workspace.yaml",
];
const INCLUDE_DIRS = ["templates"];
-const EXCLUDE_DIRS = new Set([".git", "node_modules", "src", "dist", "compat", ".astro"]);
+/* README 引用的 docs/*.md 也要随包分发,避免制品内出现死链 */
+const INCLUDE_DOCS = ["docs"];
+const EXCLUDE_DIRS = new Set([
+ ".git", "node_modules", "src", "dist", "compat", ".astro", ".vitepress",
+]);
/* ---------------- 构建产物前置校验 ----------------
* templates/ 被 .gitignore 忽略,克隆仓库或清理工作区后默认不存在。
@@ -173,14 +180,16 @@ for (const name of INCLUDE_ROOT) {
}
}
-function walk(dir, prefix) {
+function walk(dir, prefix, opts = {}) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const abs = path.join(dir, entry.name);
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
if (entry.isDirectory()) {
if (EXCLUDE_DIRS.has(entry.name)) continue;
- walk(abs, rel);
+ walk(abs, rel, opts);
} else {
+ /* docs 只收 Markdown 源文件,不收 VitePress 构建产物 / 依赖 README */
+ if (opts.mdOnly && !entry.name.endsWith(".md")) continue;
entries.push({ name: rel, data: fs.readFileSync(abs), date: fs.statSync(abs).mtime });
}
}
@@ -190,6 +199,10 @@ for (const dir of INCLUDE_DIRS) {
const full = path.join(root, dir);
if (fs.existsSync(full)) walk(full, dir);
}
+for (const dir of INCLUDE_DOCS) {
+ const full = path.join(root, dir);
+ if (fs.existsSync(full)) walk(full, dir, { mdOnly: true });
+}
if (!entries.some((entry) => entry.name === "templates/index.html")) {
fail("打包清单中缺少 templates/index.html");
@@ -201,6 +214,38 @@ if (pkgVersion !== version) {
fail(`版本号不一致:package.json=${pkgVersion},theme.yaml=${version}`);
}
+/* ---------------- 上架合规前置校验 ----------------
+ * 1. 主题不得出现未披露的第三方自动请求 / 远程动态加载(审核指南 1.4 / 4.4.4)
+ * 2. README 的相对链接必须在制品内真实存在(审核指南 4.6 / 2.2)
+ * 3. logo 只保留一份压缩版,避免打包两份 1.5MB 大图
+ */
+const templateAudit = auditTemplates(templatesDir);
+for (const problem of templateAudit.problems) {
+ console.error(`[package] ${problem}`);
+}
+if (templateAudit.problems.length) {
+ fail("外部资源审计未通过:模板仍存在第三方远程加载 / 预连接");
+}
+
+const linkAudit = auditReadmeLinks(new Set(entries.map((entry) => entry.name)));
+for (const problem of linkAudit.problems) {
+ console.error(`[package] ${problem}`);
+}
+if (linkAudit.problems.length) {
+ fail("README 存在指向制品中不存在文件的相对链接");
+}
+
+if (fs.existsSync(path.join(templatesDir, "logo.png"))) {
+ fail("templates/logo.png 仍存在:应只保留压缩后的 templates/assets/logo.png");
+}
+const logoFile = path.join(templatesDir, "assets", "logo.png");
+if (fs.existsSync(logoFile) && fs.statSync(logoFile).size > 300 * 1024) {
+ fail("templates/assets/logo.png 超过 300KB,请先压缩再打包");
+}
+console.log(
+ `[package] 合规审计通过(${templateAudit.htmlCount} 个模板、${linkAudit.checked} 个相对链接、logo 单份压缩)`
+);
+
const outFile = path.join(root, "dist", `warm-island-${version}.zip`);
buildZip(entries, outFile);
diff --git a/scripts/postbuild.mjs b/scripts/postbuild.mjs
index 3443405..642553a 100644
--- a/scripts/postbuild.mjs
+++ b/scripts/postbuild.mjs
@@ -1,6 +1,6 @@
/**
* 构建后置处理:
- * 1. 复制 logo.png 到 assets(保留历史行为)
+ * 1. 校验主题 logo 存在且只保留一份(public/assets/logo.png → templates/assets/logo.png)
* 2. 合并 compat/assets 下的历史版本静态资源,避免站点/代理缓存中的旧 HTML
* 仍然引用上一版本的哈希文件名时出现 500(FileNotFoundException)
* 3. 校验所有 templates/*.html 引用的静态资源真实存在,缺失即构建失败
@@ -16,11 +16,13 @@ const templatesDir = path.join(root, "templates");
fs.mkdirSync(assetsDir, { recursive: true });
-// 1. logo
-const logoSrc = path.join(templatesDir, "logo.png");
-if (fs.existsSync(logoSrc)) {
- fs.copyFileSync(logoSrc, path.join(assetsDir, "logo.png"));
+// 1. logo:旧实现把 templates/logo.png 再复制一份到 assets,安装包里会有两份 1.5MB 大图。
+// 现在源文件统一放在 public/assets/logo.png(256x256 压缩版),这里只做存在性校验。
+if (!fs.existsSync(path.join(assetsDir, "logo.png"))) {
+ console.error("[postbuild] 未找到 templates/assets/logo.png(public/assets/logo.png 应随构建复制)");
+ process.exit(1);
}
+console.log("[postbuild] logo.png 已去重,仅保留 templates/assets/logo.png");
// 2. 历史资源合并(已存在的同名文件以当前构建为准,不覆盖)
if (fs.existsSync(compatDir)) {
diff --git a/settings.yaml b/settings.yaml
index 703ae2f..901df2c 100644
--- a/settings.yaml
+++ b/settings.yaml
@@ -36,7 +36,7 @@ spec:
id: welcome_popup_enabled
label: 启用欢迎弹窗
value: false
- help: 首页显示"欢迎来自 XX·XX·XX 的朋友"的浮动提示弹窗,基于访客 IP 地理定位
+ help: 首页显示浮动欢迎提示;默认只显示本地静态欢迎语,不发起任何外部请求
- $formkit: select
name: welcome_popup_position
id: welcome_popup_position
@@ -67,16 +67,16 @@ spec:
- $formkit: url
name: welcome_popup_api
id: welcome_popup_api
- label: IP 定位主 API
- value: "https://myip.ipip.net/json"
- help: 返回 JSON 的 IP 定位主接口,默认 IPIP;留空使用默认值。支持 IPIP/腾讯/ipapi.co 等常见格式
+ label: 自定义 IP 定位主 API(可选)
+ value: ""
+ help: 留空则只显示本地欢迎语。填写后会把访客 IP 发送给该第三方服务换取地理位置,需自行在站点隐私说明中披露,详见 README「外部资源与隐私」
if: "$get(welcome_popup_enabled).value === true"
- $formkit: url
name: welcome_popup_fallback_api
id: welcome_popup_fallback_api
- label: IP 定位备选 API
- value: "https://r.inews.qq.com/api/ip2city?otype=json"
- help: 主接口失败时尝试的备选接口,默认腾讯 ip2city;留空则不使用备选
+ label: 自定义 IP 定位备选 API(可选)
+ value: ""
+ help: 留空不使用备选;填写后同样会向第三方发送访客 IP
if: "$get(welcome_popup_enabled).value === true"
- $formkit: switch
name: welcome_popup_show_once
@@ -278,8 +278,9 @@ spec:
value: hitokoto
- $formkit: url
name: hero_hitokoto_api
- label: 一言 API 地址
- value: https://v1.hitokoto.cn/
+ label: 自定义一言 API(可选)
+ value: ""
+ help: 留空使用主题内置的本地句子库(零外部请求);填写后才会向该地址发起请求,需自行在站点隐私说明中披露,详见 README「外部资源与隐私」
- $formkit: checkbox
name: hero_hitokoto_categories
label: 一言句子类型
@@ -501,6 +502,7 @@ spec:
- label: Cold
value: cold
value: warm
+ help: 仅切换主题自托管的本地代码配色样式;语法高亮本身由 Halo 应用市场的代码高亮插件或「API 扩展包」服务端高亮提供,主题不再加载任何远程高亮脚本
- $formkit: switch
name: article_extra_spacing
label: 中英文混排自动加空格
diff --git a/src/components/FriendsSection.astro b/src/components/FriendsSection.astro
index be5e297..91ada56 100644
--- a/src/components/FriendsSection.astro
+++ b/src/components/FriendsSection.astro
@@ -29,7 +29,7 @@
th:unless="${link.spec.logo}"
class="wi-friends__avatar wi-friends__avatar--placeholder"
>
-
+
diff --git a/src/components/HeroSection.astro b/src/components/HeroSection.astro
index e6d8c56..1f793a3 100644
--- a/src/components/HeroSection.astro
+++ b/src/components/HeroSection.astro
@@ -59,7 +59,7 @@
class="hero__description"
th:if="${theme.config?.hero?.hero_description_mode == 'hitokoto'}"
id="wi-hitokoto"
- th:attr="data-api=${theme.config?.hero?.hero_hitokoto_api ?: 'https://v1.hitokoto.cn/'},data-categories=${theme.config?.hero?.hero_hitokoto_categories},data-auto-refresh=${theme.config?.hero?.hero_hitokoto_auto_refresh == true ? 'true' : 'false'},data-refresh-interval=${theme.config?.hero?.hero_hitokoto_refresh_interval ?: 10000}"
+ th:attr="data-api=${theme.config?.hero?.hero_hitokoto_api ?: ''},data-categories=${theme.config?.hero?.hero_hitokoto_categories},data-auto-refresh=${theme.config?.hero?.hero_hitokoto_auto_refresh == true ? 'true' : 'false'},data-refresh-interval=${theme.config?.hero?.hero_hitokoto_refresh_interval ?: 10000}"
>
-
+
diff --git a/src/components/WelcomePopup.astro b/src/components/WelcomePopup.astro
index 4a914da..9fdf883 100644
--- a/src/components/WelcomePopup.astro
+++ b/src/components/WelcomePopup.astro
@@ -5,7 +5,7 @@
class="wi-welcome-popup"
id="wi-welcome-popup"
th:if="${theme.config?.welcome_popup?.welcome_popup_enabled == true}"
- th:attr="data-position=${theme.config?.welcome_popup?.welcome_popup_position ?: 'top-center'},data-duration=${theme.config?.welcome_popup?.welcome_popup_duration ?: 5},data-api=${theme.config?.welcome_popup?.welcome_popup_api},data-fallback-api=${theme.config?.welcome_popup?.welcome_popup_fallback_api},data-show-once=${theme.config?.welcome_popup?.welcome_popup_show_once != false ? 'true' : 'false'}"
+ th:attr="data-position=${theme.config?.welcome_popup?.welcome_popup_position ?: 'top-center'},data-duration=${theme.config?.welcome_popup?.welcome_popup_duration ?: 5},data-api=${theme.config?.welcome_popup?.welcome_popup_api ?: ''},data-fallback-api=${theme.config?.welcome_popup?.welcome_popup_fallback_api ?: ''},data-show-once=${theme.config?.welcome_popup?.welcome_popup_show_once != false ? 'true' : 'false'}"
aria-live="polite"
aria-atomic="true"
>
@@ -16,7 +16,7 @@
-
+