From 56697a04ed0d68490e77bdfb78edb1ab7b8f024f Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Fri, 4 Sep 2026 11:06:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=A7=BB=E5=8A=A8=E7=AB=AF=E9=80=82?= =?UTF-8?q?=E9=85=8D=E4=B8=8E=E6=BC=94=E7=A4=BA=E5=8C=BA=E6=89=8B=E5=8A=A8?= =?UTF-8?q?=E9=A2=84=E8=A7=88=20+=20=E6=B2=99=E7=AE=B1=E5=B9=B3=E5=8F=B0?= =?UTF-8?q?=E5=8F=A3=E5=BE=84=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复窄屏 git clone 块横向溢出撑破整页(hero-clone max-width + code 块内横滚) - 顶栏:移动端保留 GitHub 入口;Astrion 标题改为星空开关按钮(停 rAF + 隐藏画布,卡顿排查用) - 演示区:废弃 IntersectionObserver 懒加载,改为「点击预览」手动触发(移动端卡顿定位) - 文档:沙箱平台口径——macOS/Windows 已实测,Linux 未测试未适配暂不可用(02/05 章中英四份) Co-authored-by: Astrion powered by Kimi-K3 --- content/02-core-concepts.md | 8 +-- content/05-execution-security.md | 6 +-- content/en/02-core-concepts.md | 8 +-- content/en/05-execution-security.md | 6 +-- demo/content/02-core-concepts.md | 8 +-- demo/content/05-execution-security.md | 6 +-- demo/content/en/02-core-concepts.md | 8 +-- demo/content/en/05-execution-security.md | 6 +-- demo/index.html | 13 +++-- demo/site-assets/i18n.js | 8 +++ demo/site-assets/site.css | 41 ++++++++++++++- demo/site-assets/site.js | 64 +++++++++++++++++------- 12 files changed, 130 insertions(+), 52 deletions(-) diff --git a/content/02-core-concepts.md b/content/02-core-concepts.md index e10fa16..34b839f 100644 --- a/content/02-core-concepts.md +++ b/content/02-core-concepts.md @@ -61,11 +61,11 @@ Astrion 有四组**相互正交**的概念,理解它们是理解整个系统 | 平台 | 实现 | 安全水位 | |------|------|----------| -| **Windows** | WSL2 | **可以做到完全的数据隔离**——命令跑在独立的 WSL2 文件系统中。前提是**先自行安装 WSL2**,未安装时沙箱不可用 | -| **macOS** | sandbox-exec | **白名单读模型,读写都可限制**——进程默认只能读系统目录、工作区与已授权路径,越界读取会被直接拒绝。固有代价:授权路径的祖先目录顶层文件名可被列出(文件内容仍不可读) | -| **Linux** | bubblewrap (bwrap) + seccomp | 写入可限制(只读档全局只读),但**读侧仍是全局可读**、尚未对齐白名单;且尚未经过实际测试,不建议在生产环境依赖其隔离性 | +| **Windows** | WSL2 | **已实测:可以做到完全的数据隔离**——命令跑在独立的 WSL2 文件系统中。前提是**先自行安装 WSL2**,未安装时沙箱不可用 | +| **macOS** | sandbox-exec | **已实测:白名单读模型,读写都可限制**——进程默认只能读系统目录、工作区与已授权路径,越界读取会被直接拒绝。固有代价:授权路径的祖先目录顶层文件名可被列出(文件内容仍不可读) | +| **Linux** | bubblewrap (bwrap) + seccomp | **未测试、未适配、当前不可用**。Linux 服务器上部署多用户服务请使用 Docker 模式(容器隔离在 Linux 宿主上已实测生效),不要依赖宿主机沙箱 | -这是官方对当前安全能力的如实说明:把沙箱当作「防误操作」的手段,三个平台都是可靠的;把它当作「防恶意窃取数据」的手段,macOS(白名单)与 Windows(WSL2)可以信赖,Linux 暂时不行。 +这是官方对当前安全能力的如实说明:把沙箱当作「防误操作」的手段,macOS 与 Windows 都是可靠的;把它当作「防恶意窃取数据」的手段,macOS(白名单)与 Windows(WSL2)可以信赖,Linux 当前不可用。 ### 路径授权 diff --git a/content/05-execution-security.md b/content/05-execution-security.md index 64ec270..85e0b6b 100644 --- a/content/05-execution-security.md +++ b/content/05-execution-security.md @@ -79,8 +79,8 @@ docker 模式下,用户之间的隔离边界是容器本身:每个用户的 再次强调《核心概念》中的结论,因为它直接影响你该信任沙箱到什么程度: -- **Windows(WSL2)**:可做到完全数据隔离,需先安装 WSL2; -- **macOS(sandbox-exec)**:白名单读模型,写入和读取都可限制——进程默认只能读系统目录、工作区与已授权路径;代价是授权路径的祖先目录顶层文件名可被列出(文件内容仍不可读); -- **Linux(bwrap+seccomp)**:写入可限制,但读侧仍是全局可读、尚未对齐白名单,且**尚未实测**,不要依赖其隔离性。 +- **Windows(WSL2)**:已实测,可做到完全数据隔离,需先安装 WSL2; +- **macOS(sandbox-exec)**:已实测,白名单读模型,写入和读取都可限制——进程默认只能读系统目录、工作区与已授权路径;代价是授权路径的祖先目录顶层文件名可被列出(文件内容仍不可读); +- **Linux(bwrap+seccomp)**:**未测试、未适配、当前不可用**——Linux 服务器上部署多用户服务请使用 Docker 模式(容器隔离已实测生效),不要依赖宿主机沙箱。 无论哪个平台,沙箱的首要价值是**防误操作**;涉及真正的敏感数据,请叠加路径授权最小化、网络受限、readonly 权限等手段综合防护。 diff --git a/content/en/02-core-concepts.md b/content/en/02-core-concepts.md index 5d88548..64bdff1 100644 --- a/content/en/02-core-concepts.md +++ b/content/en/02-core-concepts.md @@ -61,11 +61,11 @@ The execution environment is a **system-level** switch (switchable in host mode | Platform | Implementation | Security level | |------|------|----------| -| **Windows** | WSL2 | **Full data isolation is achievable** — commands run in an independent WSL2 filesystem. Prerequisite: **install WSL2 yourself first**; the sandbox is unavailable without it | -| **macOS** | sandbox-exec | **Whitelist-based read model; both reads and writes can be restricted** — by default a process can only read system directories, the workspace, and authorized paths; out-of-bounds reads are directly denied. Inherent cost: file names at the top level of an authorized path's ancestor directories can be listed (file contents remain unreadable) | -| **Linux** | bubblewrap (bwrap) + seccomp | Writes can be restricted (read-only level is globally read-only), but **reads are still globally readable** and not yet aligned with a whitelist; it has not been tested in practice, so relying on its isolation in production is not recommended | +| **Windows** | WSL2 | **Tested: full data isolation is achievable** — commands run in an independent WSL2 filesystem. Prerequisite: **install WSL2 yourself first**; the sandbox is unavailable without it | +| **macOS** | sandbox-exec | **Tested: whitelist-based read model; both reads and writes can be restricted** — by default a process can only read system directories, the workspace, and authorized paths; out-of-bounds reads are directly denied. Inherent cost: file names at the top level of an authorized path's ancestor directories can be listed (file contents remain unreadable) | +| **Linux** | bubblewrap (bwrap) + seccomp | **Untested, unadapted, and currently unavailable.** For multi-user deployments on Linux servers, use Docker mode instead (container isolation is tested and effective on Linux hosts); do not rely on the host sandbox | -This is the official, candid statement of current security capabilities: as a way to **prevent accidental mistakes**, the sandbox is reliable on all three platforms; as a way to **prevent malicious data theft**, macOS (whitelist) and Windows (WSL2) can be trusted, while Linux cannot — for now. +This is the official, candid statement of current security capabilities: as a way to **prevent accidental mistakes**, the sandbox is reliable on macOS and Windows; as a way to **prevent malicious data theft**, macOS (whitelist) and Windows (WSL2) can be trusted — on Linux the host sandbox is currently unavailable. ### Path Authorization diff --git a/content/en/05-execution-security.md b/content/en/05-execution-security.md index eca91b5..215e8cb 100644 --- a/content/en/05-execution-security.md +++ b/content/en/05-execution-security.md @@ -79,8 +79,8 @@ In docker mode, the isolation boundary between users is the container itself: ea Reiterating the conclusion from the "Core Concepts" chapter, because it directly affects how much you should trust the sandbox: -- **Windows (WSL2)**: full data isolation is achievable, but WSL2 must be installed first; -- **macOS (sandbox-exec)**: whitelist read model; both writes and reads can be restricted—processes can by default only read system directories, the workspace, and authorized paths; the trade-off is that top-level file names in the ancestor directories of authorized paths can be listed (file contents remain unreadable); -- **Linux (bwrap+seccomp)**: writes can be restricted, but the read side is still globally readable, not yet aligned to a whitelist, and **not yet tested in practice**—don't rely on its isolation. +- **Windows (WSL2)**: tested; full data isolation is achievable, but WSL2 must be installed first; +- **macOS (sandbox-exec)**: tested; whitelist read model—both writes and reads can be restricted: processes can by default only read system directories, the workspace, and authorized paths; the trade-off is that top-level file names in the ancestor directories of authorized paths can be listed (file contents remain unreadable); +- **Linux (bwrap+seccomp)**: **untested, unadapted, and currently unavailable**—for multi-user deployments on Linux servers, use Docker mode (container isolation is tested and effective); do not rely on the host sandbox. On any platform, the primary value of the sandbox is **preventing accidental operations**; for genuinely sensitive data, layer path authorization minimization, restricted network, and readonly permissions together for comprehensive protection. diff --git a/demo/content/02-core-concepts.md b/demo/content/02-core-concepts.md index e10fa16..34b839f 100644 --- a/demo/content/02-core-concepts.md +++ b/demo/content/02-core-concepts.md @@ -61,11 +61,11 @@ Astrion 有四组**相互正交**的概念,理解它们是理解整个系统 | 平台 | 实现 | 安全水位 | |------|------|----------| -| **Windows** | WSL2 | **可以做到完全的数据隔离**——命令跑在独立的 WSL2 文件系统中。前提是**先自行安装 WSL2**,未安装时沙箱不可用 | -| **macOS** | sandbox-exec | **白名单读模型,读写都可限制**——进程默认只能读系统目录、工作区与已授权路径,越界读取会被直接拒绝。固有代价:授权路径的祖先目录顶层文件名可被列出(文件内容仍不可读) | -| **Linux** | bubblewrap (bwrap) + seccomp | 写入可限制(只读档全局只读),但**读侧仍是全局可读**、尚未对齐白名单;且尚未经过实际测试,不建议在生产环境依赖其隔离性 | +| **Windows** | WSL2 | **已实测:可以做到完全的数据隔离**——命令跑在独立的 WSL2 文件系统中。前提是**先自行安装 WSL2**,未安装时沙箱不可用 | +| **macOS** | sandbox-exec | **已实测:白名单读模型,读写都可限制**——进程默认只能读系统目录、工作区与已授权路径,越界读取会被直接拒绝。固有代价:授权路径的祖先目录顶层文件名可被列出(文件内容仍不可读) | +| **Linux** | bubblewrap (bwrap) + seccomp | **未测试、未适配、当前不可用**。Linux 服务器上部署多用户服务请使用 Docker 模式(容器隔离在 Linux 宿主上已实测生效),不要依赖宿主机沙箱 | -这是官方对当前安全能力的如实说明:把沙箱当作「防误操作」的手段,三个平台都是可靠的;把它当作「防恶意窃取数据」的手段,macOS(白名单)与 Windows(WSL2)可以信赖,Linux 暂时不行。 +这是官方对当前安全能力的如实说明:把沙箱当作「防误操作」的手段,macOS 与 Windows 都是可靠的;把它当作「防恶意窃取数据」的手段,macOS(白名单)与 Windows(WSL2)可以信赖,Linux 当前不可用。 ### 路径授权 diff --git a/demo/content/05-execution-security.md b/demo/content/05-execution-security.md index 64ec270..85e0b6b 100644 --- a/demo/content/05-execution-security.md +++ b/demo/content/05-execution-security.md @@ -79,8 +79,8 @@ docker 模式下,用户之间的隔离边界是容器本身:每个用户的 再次强调《核心概念》中的结论,因为它直接影响你该信任沙箱到什么程度: -- **Windows(WSL2)**:可做到完全数据隔离,需先安装 WSL2; -- **macOS(sandbox-exec)**:白名单读模型,写入和读取都可限制——进程默认只能读系统目录、工作区与已授权路径;代价是授权路径的祖先目录顶层文件名可被列出(文件内容仍不可读); -- **Linux(bwrap+seccomp)**:写入可限制,但读侧仍是全局可读、尚未对齐白名单,且**尚未实测**,不要依赖其隔离性。 +- **Windows(WSL2)**:已实测,可做到完全数据隔离,需先安装 WSL2; +- **macOS(sandbox-exec)**:已实测,白名单读模型,写入和读取都可限制——进程默认只能读系统目录、工作区与已授权路径;代价是授权路径的祖先目录顶层文件名可被列出(文件内容仍不可读); +- **Linux(bwrap+seccomp)**:**未测试、未适配、当前不可用**——Linux 服务器上部署多用户服务请使用 Docker 模式(容器隔离已实测生效),不要依赖宿主机沙箱。 无论哪个平台,沙箱的首要价值是**防误操作**;涉及真正的敏感数据,请叠加路径授权最小化、网络受限、readonly 权限等手段综合防护。 diff --git a/demo/content/en/02-core-concepts.md b/demo/content/en/02-core-concepts.md index 5d88548..64bdff1 100644 --- a/demo/content/en/02-core-concepts.md +++ b/demo/content/en/02-core-concepts.md @@ -61,11 +61,11 @@ The execution environment is a **system-level** switch (switchable in host mode | Platform | Implementation | Security level | |------|------|----------| -| **Windows** | WSL2 | **Full data isolation is achievable** — commands run in an independent WSL2 filesystem. Prerequisite: **install WSL2 yourself first**; the sandbox is unavailable without it | -| **macOS** | sandbox-exec | **Whitelist-based read model; both reads and writes can be restricted** — by default a process can only read system directories, the workspace, and authorized paths; out-of-bounds reads are directly denied. Inherent cost: file names at the top level of an authorized path's ancestor directories can be listed (file contents remain unreadable) | -| **Linux** | bubblewrap (bwrap) + seccomp | Writes can be restricted (read-only level is globally read-only), but **reads are still globally readable** and not yet aligned with a whitelist; it has not been tested in practice, so relying on its isolation in production is not recommended | +| **Windows** | WSL2 | **Tested: full data isolation is achievable** — commands run in an independent WSL2 filesystem. Prerequisite: **install WSL2 yourself first**; the sandbox is unavailable without it | +| **macOS** | sandbox-exec | **Tested: whitelist-based read model; both reads and writes can be restricted** — by default a process can only read system directories, the workspace, and authorized paths; out-of-bounds reads are directly denied. Inherent cost: file names at the top level of an authorized path's ancestor directories can be listed (file contents remain unreadable) | +| **Linux** | bubblewrap (bwrap) + seccomp | **Untested, unadapted, and currently unavailable.** For multi-user deployments on Linux servers, use Docker mode instead (container isolation is tested and effective on Linux hosts); do not rely on the host sandbox | -This is the official, candid statement of current security capabilities: as a way to **prevent accidental mistakes**, the sandbox is reliable on all three platforms; as a way to **prevent malicious data theft**, macOS (whitelist) and Windows (WSL2) can be trusted, while Linux cannot — for now. +This is the official, candid statement of current security capabilities: as a way to **prevent accidental mistakes**, the sandbox is reliable on macOS and Windows; as a way to **prevent malicious data theft**, macOS (whitelist) and Windows (WSL2) can be trusted — on Linux the host sandbox is currently unavailable. ### Path Authorization diff --git a/demo/content/en/05-execution-security.md b/demo/content/en/05-execution-security.md index eca91b5..215e8cb 100644 --- a/demo/content/en/05-execution-security.md +++ b/demo/content/en/05-execution-security.md @@ -79,8 +79,8 @@ In docker mode, the isolation boundary between users is the container itself: ea Reiterating the conclusion from the "Core Concepts" chapter, because it directly affects how much you should trust the sandbox: -- **Windows (WSL2)**: full data isolation is achievable, but WSL2 must be installed first; -- **macOS (sandbox-exec)**: whitelist read model; both writes and reads can be restricted—processes can by default only read system directories, the workspace, and authorized paths; the trade-off is that top-level file names in the ancestor directories of authorized paths can be listed (file contents remain unreadable); -- **Linux (bwrap+seccomp)**: writes can be restricted, but the read side is still globally readable, not yet aligned to a whitelist, and **not yet tested in practice**—don't rely on its isolation. +- **Windows (WSL2)**: tested; full data isolation is achievable, but WSL2 must be installed first; +- **macOS (sandbox-exec)**: tested; whitelist read model—both writes and reads can be restricted: processes can by default only read system directories, the workspace, and authorized paths; the trade-off is that top-level file names in the ancestor directories of authorized paths can be listed (file contents remain unreadable); +- **Linux (bwrap+seccomp)**: **untested, unadapted, and currently unavailable**—for multi-user deployments on Linux servers, use Docker mode (container isolation is tested and effective); do not rely on the host sandbox. On any platform, the primary value of the sandbox is **preventing accidental operations**; for genuinely sensitive data, layer path authorization minimization, restricted network, and readonly permissions together for comprehensive protection. diff --git a/demo/index.html b/demo/index.html index 568c716..fb03282 100644 --- a/demo/index.html +++ b/demo/index.html @@ -23,14 +23,15 @@
@@ -76,8 +77,12 @@
- + +
+ +

演示环境只含静态数据;实际使用由你自己的模型驱动。

diff --git a/demo/site-assets/i18n.js b/demo/site-assets/i18n.js index 8cd5a7a..5d174dc 100644 --- a/demo/site-assets/i18n.js +++ b/demo/site-assets/i18n.js @@ -10,6 +10,8 @@ window.SITE_I18N = { 'meta.desc': 'Astrion 是开源、自托管的智能体工作台:在本机处理你的代码与工作,也可以部署到云端提供多人服务,体验一致。', 'brand.aria': 'Astrion 首页', + 'brand.toggleAria': '关闭或开启星空背景', + 'brand.toggleTitle': '点击关闭 / 开启星空背景', 'nav.features': '功能', 'nav.docs': '文档', @@ -21,6 +23,8 @@ window.SITE_I18N = { 'demo.sub': '下面是真实运行的 Astrion 界面,接着一份静态演示数据。可以点击切换对话、展开工具记录、打开右侧快捷窗口查看子智能体。', 'demo.iframeTitle': 'Astrion 界面演示', 'demo.note': '演示环境只含静态数据;实际使用由你自己的模型驱动。', + 'demo.preview': '点击预览', + 'demo.previewLoading': '加载中…', 'features.title': '围绕「放心交给它做事」设计', 'f1.title': '计划 / 询问 / 执行', @@ -65,6 +69,8 @@ window.SITE_I18N = { 'meta.desc': 'Astrion is an open-source, self-hosted agent workbench: it handles your code and work on your own machine, or deploys to the cloud for multi-user service — the same experience.', 'brand.aria': 'Astrion home', + 'brand.toggleAria': 'Toggle the starry background', + 'brand.toggleTitle': 'Click to toggle the starry background', 'nav.features': 'Features', 'nav.docs': 'Docs', @@ -76,6 +82,8 @@ window.SITE_I18N = { 'demo.sub': 'Below is the real Astrion interface running on a static demo dataset. Switch conversations, expand tool records, and open the quick panel on the right to inspect sub agents.', 'demo.iframeTitle': 'Astrion interface demo', 'demo.note': 'The demo contains static data only; real usage is driven by your own models.', + 'demo.preview': 'Click to Preview', + 'demo.previewLoading': 'Loading…', 'features.title': 'Designed so you can hand it work with confidence', 'f1.title': 'Plan / Ask / Execute', diff --git a/demo/site-assets/site.css b/demo/site-assets/site.css index b5a0c99..9c8a88b 100644 --- a/demo/site-assets/site.css +++ b/demo/site-assets/site.css @@ -161,7 +161,9 @@ code { justify-content: space-between; } -.brand { display: inline-flex; align-items: center; gap: 10px; } +/* 顶栏标题是 button(星空开关),清掉按钮默认样式;.stars-off 给关闭态一点视觉反馈 */ +.brand { display: inline-flex; align-items: center; gap: 10px; background: none; border: 0; padding: 0; font: inherit; color: var(--text-1); cursor: pointer; } +.brand.stars-off .brand-name { color: var(--text-3); } .brand-logo { width: 26px; height: 26px; color: var(--text-1); display: inline-flex; } .brand-logo .astrion-avatar { width: 100%; height: 100%; } .brand-logo-sm { width: 20px; height: 20px; } @@ -229,6 +231,9 @@ code { gap: 12px; height: 42px; padding: 0 14px; + /* 窄屏兜底:块宽不顶破容器(code 的 nowrap 长文本会把 min-content 撑出视口, + 导致整页横向溢出、右侧铺不满;max-width 截住块宽,超出部分交给 code 内部横滚) */ + max-width: 100%; background: var(--bg-panel); border: 1px solid var(--line-strong); border-radius: 8px; @@ -241,10 +246,16 @@ code { font-size: 13.5px; color: var(--text-1); white-space: nowrap; + /* 窄屏:flex 子项允许收缩(min-width:0),超出部分块内横滚且不暴露原生滚动条; + 复制走 JS 固定字符串,与可视选区无关,横滚不影响复制 */ + min-width: 0; + overflow-x: auto; + scrollbar-width: none; background: none; padding: 0; border-radius: 0; } +.hero-clone code::-webkit-scrollbar { display: none; } .hero-clone-icon { display: inline-flex; position: relative; @@ -325,6 +336,32 @@ code { opacity: 1; } +/* 手动预览占位层:盖在未加载的 iframe 上,居中按钮 + 提示;加载中按钮禁用,ready 后整层隐藏 */ +.demo-preview { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 12px; +} +.demo-preview--hidden { display: none; } +.demo-preview-btn { + height: 42px; + padding: 0 26px; + font: inherit; + font-size: 14px; + color: var(--text-1); + background: var(--bg-panel); + border: 1px solid var(--line-strong); + border-radius: 8px; + cursor: pointer; + transition: border-color 0.15s ease, color 0.15s ease; +} +.demo-preview-btn:hover { border-color: var(--text-2); } +.demo-preview-btn:disabled { cursor: default; color: var(--text-3); } + .demo-note { max-width: var(--page-max); margin: 14px auto 0; @@ -677,6 +714,8 @@ code { .feature-row--anim { grid-template-columns: minmax(0, 1fr); } .feature-anim { height: 340px; } .topnav-link { display: none; } + .topnav-link--keep { display: inline; } /* GitHub 移动端保留:同优先级后定义胜出;刻意不提优先级(如 :not 双类),保住 docs.css .docs-back-home (0,2,0) 的优先级放行设计 */ + .topnav { gap: 18px; } .hero { padding-top: calc(var(--topbar-h) + 64px); padding-bottom: 72px; } } diff --git a/demo/site-assets/site.js b/demo/site-assets/site.js index 5b3c45a..9f02165 100644 --- a/demo/site-assets/site.js +++ b/demo/site-assets/site.js @@ -1,5 +1,6 @@ /* Astrion 官网主页交互 * 1) 多层视差星空(无连线、无光晕:实心星点 + 闪烁 + 极慢漂移 + 鼠标/滚动视差 + 偶发流星) + * 顶栏 Astrion 标题 = 星空开关:点击切换开/关(关 = 停 rAF + 隐藏画布,移动端卡顿排查用) * 2) 顶栏滚动态 * 3) FAQ 手风琴平滑展开(details + 高度动画) * 4) 演示区 iframe 等比缩放(内部固定 1400×860 逻辑尺寸) @@ -108,11 +109,14 @@ } var lastTs = 0; + var rafId = 0; // 当前 rAF 句柄(星空开关用) + var starsEnabled = true; function tick(ts) { + if (!starsEnabled) return; // 开关关闭:不再排下一帧,循环自然终止 if (document.hidden) { lastTs = ts; - requestAnimationFrame(tick); + rafId = requestAnimationFrame(tick); return; } var dt = Math.min((ts - lastTs) / 1000 || 0, 0.05); @@ -139,7 +143,7 @@ if (m.life > m.ttl || m.y > H + 60) meteors.splice(i, 1); } - requestAnimationFrame(tick); + rafId = requestAnimationFrame(tick); } function draw(dt, ts, scrollY) { @@ -194,10 +198,32 @@ draw(0, 0, 0); // 静态星图 } else { scheduleMeteor(performance.now() + 6000); // 首颗稍提前,证明存在 - requestAnimationFrame(tick); + rafId = requestAnimationFrame(tick); // 滚动时重绘由 rAF 持续驱动,无需 scroll 监听 } + // 顶栏标题 = 星空开关(移动端卡顿排查用):点击切换开/关。 + // 关闭必须「停 rAF 循环 + 隐藏画布」同时做——只藏画布仍每帧渲染,对比测试就失去意义。 + function setStarsEnabled(on) { + starsEnabled = on; + canvas.style.display = on ? '' : 'none'; + var btn = document.getElementById('brandToggle'); + if (btn) { + btn.classList.toggle('stars-off', !on); + btn.setAttribute('aria-pressed', on ? 'true' : 'false'); // aria-pressed 语义 = 星空开启 + } + if (on) { + if (!reduceMotion && !rafId) { lastTs = 0; rafId = requestAnimationFrame(tick); } + } else if (rafId) { + cancelAnimationFrame(rafId); + rafId = 0; + } + } + var brandToggle = document.getElementById('brandToggle'); + if (brandToggle) { + brandToggle.addEventListener('click', function () { setStarsEnabled(!starsEnabled); }); + } + /* ═══════════ 2) 顶栏滚动态 ═══════════ */ var topbar = document.getElementById('topbar'); @@ -256,24 +282,21 @@ var frame = document.getElementById('demoFrame'); var iframe = document.getElementById('demoIframe'); + var preview = document.getElementById('demoPreview'); + var previewBtn = document.getElementById('demoPreviewBtn'); var DEMO_W = 1400; var DEMO_H = 860; - // 严格懒加载:iframe 初始无 src(data-src 占位),接近视口才注入——loading=lazy 的 - // 浏览器预载阈值过宽(几千 px 就开载),演示壳 main.js 2.1MB 不值得提前加载。 - if (iframe && iframe.dataset.src) { - if ('IntersectionObserver' in window) { - var demoIO = new IntersectionObserver(function (entries) { - if (entries[0].isIntersecting) { - iframe.src = iframe.dataset.src; - delete iframe.dataset.src; - demoIO.disconnect(); - } - }, { rootMargin: '600px' }); - demoIO.observe(iframe); - } else { - iframe.src = iframe.dataset.src; // 兜底:无 IO 直接加载 - } + // 手动预览(2026-09-04 起,取代原 IntersectionObserver 严格懒加载):演示壳较重(main.js 2.1MB), + // 连「接近视口自动注入」都不做——点「点击预览」才注入 src 开始渲染(卡顿排查期可彻底排除演示区变量)。 + if (iframe && iframe.dataset.src && previewBtn) { + previewBtn.addEventListener('click', function () { + var dict = (window.SITE_I18N && window.SITE_I18N[siteLocale]) || (window.SITE_I18N && window.SITE_I18N['zh-CN']) || {}; + previewBtn.textContent = dict['demo.previewLoading'] || '加载中…'; + previewBtn.disabled = true; + iframe.src = iframe.dataset.src; + delete iframe.dataset.src; + }, { once: true }); } // 防白闪:iframe 子文档整页导航(新建对话跳 /new)时,浏览器会把 iframe 区域画成不透明白, @@ -282,7 +305,10 @@ // 对策:导航期间隐藏 iframe(露出 demo-frame 深色面板底),子文档等 data-theme 落地且首帧绘制后 // postMessage 通知显示(demo.html);load 事件兜底(load 时 module 脚本已执行,data-theme 已在)。 if (iframe) { - var markDemoFrameReady = function () { iframe.classList.add('demo-frame-ready'); }; + var markDemoFrameReady = function () { + iframe.classList.add('demo-frame-ready'); + if (preview) preview.classList.add('demo-preview--hidden'); // 手动预览:子文档就绪后撤掉占位层 + }; window.addEventListener('message', function (e) { if (e.origin === location.origin && e.data === 'astrion-demo-frame-ready') markDemoFrameReady(); });