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](./WarmIsland%20Preview.webp) +![WarmIsland Preview](https://git.nxxy335.top/nxxy335/halo-theme-WarmIsland/raw/branch/main/WarmIsland%20Preview.webp) + +> 预览图同时随安装包分发(包根目录 `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),'
+
+ + `+i+` + + + + + + + +
+ `+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=` + + `;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 @@
-
加载中…
+
欢迎来访,愿你在这里度过一段安静的时光
-
微信扫码分享
- -
打开微信,扫描二维码即可分享
+
微信分享
+ + + +
复制链接后,在微信中发送给好友
@@ -295,37 +297,20 @@ import LightGallery from "../components/LightGallery.astro"; + - - - - - -