feat: 移动端适配与演示区手动预览 + 沙箱平台口径更新

- 修复窄屏 git clone 块横向溢出撑破整页(hero-clone max-width + code 块内横滚)
- 顶栏:移动端保留 GitHub 入口;Astrion 标题改为星空开关按钮(停 rAF + 隐藏画布,卡顿排查用)
- 演示区:废弃 IntersectionObserver 懒加载,改为「点击预览」手动触发(移动端卡顿定位)
- 文档:沙箱平台口径——macOS/Windows 已实测,Linux 未测试未适配暂不可用(02/05 章中英四份)

Co-authored-by: Astrion powered by Kimi-K3 <astrion-agent@users.noreply.github.com>
This commit is contained in:
JOJO 2026-09-04 11:06:30 +08:00
parent d3b9bdbfe1
commit 56697a04ed
12 changed files with 130 additions and 52 deletions

View File

@ -61,11 +61,11 @@ Astrion 有四组**相互正交**的概念,理解它们是理解整个系统
| 平台 | 实现 | 安全水位 | | 平台 | 实现 | 安全水位 |
|------|------|----------| |------|------|----------|
| **Windows** | WSL2 | **可以做到完全的数据隔离**——命令跑在独立的 WSL2 文件系统中。前提是**先自行安装 WSL2**,未安装时沙箱不可用 | | **Windows** | WSL2 | **已实测:可以做到完全的数据隔离**——命令跑在独立的 WSL2 文件系统中。前提是**先自行安装 WSL2**,未安装时沙箱不可用 |
| **macOS** | sandbox-exec | **白名单读模型,读写都可限制**——进程默认只能读系统目录、工作区与已授权路径,越界读取会被直接拒绝。固有代价:授权路径的祖先目录顶层文件名可被列出(文件内容仍不可读) | | **macOS** | sandbox-exec | **已实测:白名单读模型,读写都可限制**——进程默认只能读系统目录、工作区与已授权路径,越界读取会被直接拒绝。固有代价:授权路径的祖先目录顶层文件名可被列出(文件内容仍不可读) |
| **Linux** | bubblewrap (bwrap) + seccomp | 写入可限制(只读档全局只读),但**读侧仍是全局可读**、尚未对齐白名单;且尚未经过实际测试,不建议在生产环境依赖其隔离性 | | **Linux** | bubblewrap (bwrap) + seccomp | **未测试、未适配、当前不可用**。Linux 服务器上部署多用户服务请使用 Docker 模式(容器隔离在 Linux 宿主上已实测生效),不要依赖宿主机沙箱 |
这是官方对当前安全能力的如实说明:把沙箱当作「防误操作」的手段,三个平台都是可靠的把它当作「防恶意窃取数据」的手段macOS白名单与 WindowsWSL2可以信赖Linux 暂时不行 这是官方对当前安全能力的如实说明:把沙箱当作「防误操作」的手段,macOS 与 Windows 都是可靠的把它当作「防恶意窃取数据」的手段macOS白名单与 WindowsWSL2可以信赖Linux 当前不可用
### 路径授权 ### 路径授权

View File

@ -79,8 +79,8 @@ docker 模式下,用户之间的隔离边界是容器本身:每个用户的
再次强调《核心概念》中的结论,因为它直接影响你该信任沙箱到什么程度: 再次强调《核心概念》中的结论,因为它直接影响你该信任沙箱到什么程度:
- **WindowsWSL2**:可做到完全数据隔离,需先安装 WSL2 - **WindowsWSL2**已实测,可做到完全数据隔离,需先安装 WSL2
- **macOSsandbox-exec**:白名单读模型,写入和读取都可限制——进程默认只能读系统目录、工作区与已授权路径;代价是授权路径的祖先目录顶层文件名可被列出(文件内容仍不可读); - **macOSsandbox-exec**已实测,白名单读模型,写入和读取都可限制——进程默认只能读系统目录、工作区与已授权路径;代价是授权路径的祖先目录顶层文件名可被列出(文件内容仍不可读);
- **Linuxbwrap+seccomp**写入可限制,但读侧仍是全局可读、尚未对齐白名单,且**尚未实测**,不要依赖其隔离性 - **Linuxbwrap+seccomp****未测试、未适配、当前不可用**——Linux 服务器上部署多用户服务请使用 Docker 模式(容器隔离已实测生效),不要依赖宿主机沙箱
无论哪个平台,沙箱的首要价值是**防误操作**涉及真正的敏感数据请叠加路径授权最小化、网络受限、readonly 权限等手段综合防护。 无论哪个平台,沙箱的首要价值是**防误操作**涉及真正的敏感数据请叠加路径授权最小化、网络受限、readonly 权限等手段综合防护。

View File

@ -61,11 +61,11 @@ The execution environment is a **system-level** switch (switchable in host mode
| Platform | Implementation | Security level | | 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 | | **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 | **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) | | **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 | 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 | | **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 ### Path Authorization

View File

@ -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: 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; - **Windows (WSL2)**: tested; 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); - **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)**: 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. - **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. 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.

View File

@ -61,11 +61,11 @@ Astrion 有四组**相互正交**的概念,理解它们是理解整个系统
| 平台 | 实现 | 安全水位 | | 平台 | 实现 | 安全水位 |
|------|------|----------| |------|------|----------|
| **Windows** | WSL2 | **可以做到完全的数据隔离**——命令跑在独立的 WSL2 文件系统中。前提是**先自行安装 WSL2**,未安装时沙箱不可用 | | **Windows** | WSL2 | **已实测:可以做到完全的数据隔离**——命令跑在独立的 WSL2 文件系统中。前提是**先自行安装 WSL2**,未安装时沙箱不可用 |
| **macOS** | sandbox-exec | **白名单读模型,读写都可限制**——进程默认只能读系统目录、工作区与已授权路径,越界读取会被直接拒绝。固有代价:授权路径的祖先目录顶层文件名可被列出(文件内容仍不可读) | | **macOS** | sandbox-exec | **已实测:白名单读模型,读写都可限制**——进程默认只能读系统目录、工作区与已授权路径,越界读取会被直接拒绝。固有代价:授权路径的祖先目录顶层文件名可被列出(文件内容仍不可读) |
| **Linux** | bubblewrap (bwrap) + seccomp | 写入可限制(只读档全局只读),但**读侧仍是全局可读**、尚未对齐白名单;且尚未经过实际测试,不建议在生产环境依赖其隔离性 | | **Linux** | bubblewrap (bwrap) + seccomp | **未测试、未适配、当前不可用**。Linux 服务器上部署多用户服务请使用 Docker 模式(容器隔离在 Linux 宿主上已实测生效),不要依赖宿主机沙箱 |
这是官方对当前安全能力的如实说明:把沙箱当作「防误操作」的手段,三个平台都是可靠的把它当作「防恶意窃取数据」的手段macOS白名单与 WindowsWSL2可以信赖Linux 暂时不行 这是官方对当前安全能力的如实说明:把沙箱当作「防误操作」的手段,macOS 与 Windows 都是可靠的把它当作「防恶意窃取数据」的手段macOS白名单与 WindowsWSL2可以信赖Linux 当前不可用
### 路径授权 ### 路径授权

View File

@ -79,8 +79,8 @@ docker 模式下,用户之间的隔离边界是容器本身:每个用户的
再次强调《核心概念》中的结论,因为它直接影响你该信任沙箱到什么程度: 再次强调《核心概念》中的结论,因为它直接影响你该信任沙箱到什么程度:
- **WindowsWSL2**:可做到完全数据隔离,需先安装 WSL2 - **WindowsWSL2**已实测,可做到完全数据隔离,需先安装 WSL2
- **macOSsandbox-exec**:白名单读模型,写入和读取都可限制——进程默认只能读系统目录、工作区与已授权路径;代价是授权路径的祖先目录顶层文件名可被列出(文件内容仍不可读); - **macOSsandbox-exec**已实测,白名单读模型,写入和读取都可限制——进程默认只能读系统目录、工作区与已授权路径;代价是授权路径的祖先目录顶层文件名可被列出(文件内容仍不可读);
- **Linuxbwrap+seccomp**写入可限制,但读侧仍是全局可读、尚未对齐白名单,且**尚未实测**,不要依赖其隔离性 - **Linuxbwrap+seccomp****未测试、未适配、当前不可用**——Linux 服务器上部署多用户服务请使用 Docker 模式(容器隔离已实测生效),不要依赖宿主机沙箱
无论哪个平台,沙箱的首要价值是**防误操作**涉及真正的敏感数据请叠加路径授权最小化、网络受限、readonly 权限等手段综合防护。 无论哪个平台,沙箱的首要价值是**防误操作**涉及真正的敏感数据请叠加路径授权最小化、网络受限、readonly 权限等手段综合防护。

View File

@ -61,11 +61,11 @@ The execution environment is a **system-level** switch (switchable in host mode
| Platform | Implementation | Security level | | 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 | | **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 | **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) | | **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 | 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 | | **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 ### Path Authorization

View File

@ -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: 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; - **Windows (WSL2)**: tested; 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); - **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)**: 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. - **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. 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.

View File

@ -23,14 +23,15 @@
<!-- ───────────────── 顶栏 ───────────────── --> <!-- ───────────────── 顶栏 ───────────────── -->
<header class="topbar" id="topbar"> <header class="topbar" id="topbar">
<div class="topbar-inner"> <div class="topbar-inner">
<a class="brand" href="/" data-i18n-aria="brand.aria" aria-label="Astrion 首页"> <!-- 标题即星空开关(移动端卡顿排查用):点击关闭/开启背景星空,关闭态见 .stars-off -->
<button class="brand" id="brandToggle" type="button" data-i18n-aria="brand.toggleAria" aria-label="关闭或开启星空背景" data-i18n-attr-title="brand.toggleTitle" title="点击关闭 / 开启星空背景" aria-pressed="true">
<span class="brand-name">Astrion</span> <span class="brand-name">Astrion</span>
</a> </button>
<nav class="topnav"> <nav class="topnav">
<a class="topnav-link" href="#features" data-i18n="nav.features">功能</a> <a class="topnav-link" href="#features" data-i18n="nav.features">功能</a>
<a class="topnav-link" href="#faq">FAQ</a> <a class="topnav-link" href="#faq">FAQ</a>
<a class="topnav-link" href="/docs.html" data-i18n="nav.docs">文档</a> <a class="topnav-link" href="/docs.html" data-i18n="nav.docs">文档</a>
<a class="topnav-link" href="https://github.com/JOJO6618/astrion" target="_blank" rel="noopener">GitHub</a> <a class="topnav-link topnav-link--keep" href="https://github.com/JOJO6618/astrion" target="_blank" rel="noopener">GitHub</a>
<a class="btn btn-primary btn-sm" href="https://agent.cyjai.com">Try Astrion</a> <a class="btn btn-primary btn-sm" href="https://agent.cyjai.com">Try Astrion</a>
</nav> </nav>
</div> </div>
@ -76,8 +77,12 @@
</div> </div>
<div class="demo-frame-wrap"> <div class="demo-frame-wrap">
<div class="demo-frame" id="demoFrame"> <div class="demo-frame" id="demoFrame">
<!-- 演示壳较重main.js 2.1MB):不用 loading=lazy浏览器预载阈值太宽改为 data-src + IntersectionObserver 严格懒加载,接近视口才注入 srcsite.js fitDemo 先撑占位高度避免跳动 --> <!-- 演示壳较重main.js 2.1MB):不做任何自动加载——点「点击预览」才注入 src 开始渲染;
site.js fitDemo 先撑占位高度避免跳动;子文档 ready 后占位层隐藏(.demo-preview--hidden -->
<iframe id="demoIframe" data-src="/demo.html" data-i18n-attr-title="demo.iframeTitle" title="Astrion 界面演示"></iframe> <iframe id="demoIframe" data-src="/demo.html" data-i18n-attr-title="demo.iframeTitle" title="Astrion 界面演示"></iframe>
<div class="demo-preview" id="demoPreview">
<button class="demo-preview-btn" id="demoPreviewBtn" type="button" data-i18n="demo.preview">点击预览</button>
</div>
</div> </div>
</div> </div>
<p class="demo-note" data-i18n="demo.note">演示环境只含静态数据;实际使用由你自己的模型驱动。</p> <p class="demo-note" data-i18n="demo.note">演示环境只含静态数据;实际使用由你自己的模型驱动。</p>

View File

@ -10,6 +10,8 @@ window.SITE_I18N = {
'meta.desc': 'Astrion 是开源、自托管的智能体工作台:在本机处理你的代码与工作,也可以部署到云端提供多人服务,体验一致。', 'meta.desc': 'Astrion 是开源、自托管的智能体工作台:在本机处理你的代码与工作,也可以部署到云端提供多人服务,体验一致。',
'brand.aria': 'Astrion 首页', 'brand.aria': 'Astrion 首页',
'brand.toggleAria': '关闭或开启星空背景',
'brand.toggleTitle': '点击关闭 / 开启星空背景',
'nav.features': '功能', 'nav.features': '功能',
'nav.docs': '文档', 'nav.docs': '文档',
@ -21,6 +23,8 @@ window.SITE_I18N = {
'demo.sub': '下面是真实运行的 Astrion 界面,接着一份静态演示数据。可以点击切换对话、展开工具记录、打开右侧快捷窗口查看子智能体。', 'demo.sub': '下面是真实运行的 Astrion 界面,接着一份静态演示数据。可以点击切换对话、展开工具记录、打开右侧快捷窗口查看子智能体。',
'demo.iframeTitle': 'Astrion 界面演示', 'demo.iframeTitle': 'Astrion 界面演示',
'demo.note': '演示环境只含静态数据;实际使用由你自己的模型驱动。', 'demo.note': '演示环境只含静态数据;实际使用由你自己的模型驱动。',
'demo.preview': '点击预览',
'demo.previewLoading': '加载中…',
'features.title': '围绕「放心交给它做事」设计', 'features.title': '围绕「放心交给它做事」设计',
'f1.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.', '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.aria': 'Astrion home',
'brand.toggleAria': 'Toggle the starry background',
'brand.toggleTitle': 'Click to toggle the starry background',
'nav.features': 'Features', 'nav.features': 'Features',
'nav.docs': 'Docs', '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.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.iframeTitle': 'Astrion interface demo',
'demo.note': 'The demo contains static data only; real usage is driven by your own models.', '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', 'features.title': 'Designed so you can hand it work with confidence',
'f1.title': 'Plan / Ask / Execute', 'f1.title': 'Plan / Ask / Execute',

View File

@ -161,7 +161,9 @@ code {
justify-content: space-between; 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 { width: 26px; height: 26px; color: var(--text-1); display: inline-flex; }
.brand-logo .astrion-avatar { width: 100%; height: 100%; } .brand-logo .astrion-avatar { width: 100%; height: 100%; }
.brand-logo-sm { width: 20px; height: 20px; } .brand-logo-sm { width: 20px; height: 20px; }
@ -229,6 +231,9 @@ code {
gap: 12px; gap: 12px;
height: 42px; height: 42px;
padding: 0 14px; padding: 0 14px;
/* 窄屏兜底块宽不顶破容器code nowrap 长文本会把 min-content 撑出视口
导致整页横向溢出右侧铺不满max-width 截住块宽超出部分交给 code 内部横滚 */
max-width: 100%;
background: var(--bg-panel); background: var(--bg-panel);
border: 1px solid var(--line-strong); border: 1px solid var(--line-strong);
border-radius: 8px; border-radius: 8px;
@ -241,10 +246,16 @@ code {
font-size: 13.5px; font-size: 13.5px;
color: var(--text-1); color: var(--text-1);
white-space: nowrap; white-space: nowrap;
/* 窄屏flex 子项允许收缩min-width:0超出部分块内横滚且不暴露原生滚动条
复制走 JS 固定字符串与可视选区无关横滚不影响复制 */
min-width: 0;
overflow-x: auto;
scrollbar-width: none;
background: none; background: none;
padding: 0; padding: 0;
border-radius: 0; border-radius: 0;
} }
.hero-clone code::-webkit-scrollbar { display: none; }
.hero-clone-icon { .hero-clone-icon {
display: inline-flex; display: inline-flex;
position: relative; position: relative;
@ -325,6 +336,32 @@ code {
opacity: 1; 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 { .demo-note {
max-width: var(--page-max); max-width: var(--page-max);
margin: 14px auto 0; margin: 14px auto 0;
@ -677,6 +714,8 @@ code {
.feature-row--anim { grid-template-columns: minmax(0, 1fr); } .feature-row--anim { grid-template-columns: minmax(0, 1fr); }
.feature-anim { height: 340px; } .feature-anim { height: 340px; }
.topnav-link { display: none; } .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; } .hero { padding-top: calc(var(--topbar-h) + 64px); padding-bottom: 72px; }
} }

View File

@ -1,5 +1,6 @@
/* Astrion /* Astrion
* 1) 多层视差星空无连线无光晕实心星点 + 闪烁 + 极慢漂移 + 鼠标/滚动视差 + 偶发流星 * 1) 多层视差星空无连线无光晕实心星点 + 闪烁 + 极慢漂移 + 鼠标/滚动视差 + 偶发流星
* 顶栏 Astrion 标题 = 星空开关点击切换开/ = rAF + 隐藏画布移动端卡顿排查用
* 2) 顶栏滚动态 * 2) 顶栏滚动态
* 3) FAQ 手风琴平滑展开details + 高度动画 * 3) FAQ 手风琴平滑展开details + 高度动画
* 4) 演示区 iframe 等比缩放内部固定 1400×860 逻辑尺寸 * 4) 演示区 iframe 等比缩放内部固定 1400×860 逻辑尺寸
@ -108,11 +109,14 @@
} }
var lastTs = 0; var lastTs = 0;
var rafId = 0; // 当前 rAF 句柄(星空开关用)
var starsEnabled = true;
function tick(ts) { function tick(ts) {
if (!starsEnabled) return; // 开关关闭:不再排下一帧,循环自然终止
if (document.hidden) { if (document.hidden) {
lastTs = ts; lastTs = ts;
requestAnimationFrame(tick); rafId = requestAnimationFrame(tick);
return; return;
} }
var dt = Math.min((ts - lastTs) / 1000 || 0, 0.05); 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); if (m.life > m.ttl || m.y > H + 60) meteors.splice(i, 1);
} }
requestAnimationFrame(tick); rafId = requestAnimationFrame(tick);
} }
function draw(dt, ts, scrollY) { function draw(dt, ts, scrollY) {
@ -194,10 +198,32 @@
draw(0, 0, 0); // 静态星图 draw(0, 0, 0); // 静态星图
} else { } else {
scheduleMeteor(performance.now() + 6000); // 首颗稍提前,证明存在 scheduleMeteor(performance.now() + 6000); // 首颗稍提前,证明存在
requestAnimationFrame(tick); rafId = requestAnimationFrame(tick);
// 滚动时重绘由 rAF 持续驱动,无需 scroll 监听 // 滚动时重绘由 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) 顶栏滚动态 ═══════════ */ /* ═══════════ 2) 顶栏滚动态 ═══════════ */
var topbar = document.getElementById('topbar'); var topbar = document.getElementById('topbar');
@ -256,24 +282,21 @@
var frame = document.getElementById('demoFrame'); var frame = document.getElementById('demoFrame');
var iframe = document.getElementById('demoIframe'); var iframe = document.getElementById('demoIframe');
var preview = document.getElementById('demoPreview');
var previewBtn = document.getElementById('demoPreviewBtn');
var DEMO_W = 1400; var DEMO_W = 1400;
var DEMO_H = 860; var DEMO_H = 860;
// 严格懒加载iframe 初始无 srcdata-src 占位接近视口才注入——loading=lazy 的 // 手动预览2026-09-04 起,取代原 IntersectionObserver 严格懒加载演示壳较重main.js 2.1MB
// 浏览器预载阈值过宽(几千 px 就开载),演示壳 main.js 2.1MB 不值得提前加载。 // 连「接近视口自动注入」都不做——点「点击预览」才注入 src 开始渲染(卡顿排查期可彻底排除演示区变量)。
if (iframe && iframe.dataset.src) { if (iframe && iframe.dataset.src && previewBtn) {
if ('IntersectionObserver' in window) { previewBtn.addEventListener('click', function () {
var demoIO = new IntersectionObserver(function (entries) { var dict = (window.SITE_I18N && window.SITE_I18N[siteLocale]) || (window.SITE_I18N && window.SITE_I18N['zh-CN']) || {};
if (entries[0].isIntersecting) { previewBtn.textContent = dict['demo.previewLoading'] || '加载中…';
previewBtn.disabled = true;
iframe.src = iframe.dataset.src; iframe.src = iframe.dataset.src;
delete iframe.dataset.src; delete iframe.dataset.src;
demoIO.disconnect(); }, { once: true });
}
}, { rootMargin: '600px' });
demoIO.observe(iframe);
} else {
iframe.src = iframe.dataset.src; // 兜底:无 IO 直接加载
}
} }
// 防白闪iframe 子文档整页导航(新建对话跳 /new浏览器会把 iframe 区域画成不透明白, // 防白闪iframe 子文档整页导航(新建对话跳 /new浏览器会把 iframe 区域画成不透明白,
@ -282,7 +305,10 @@
// 对策:导航期间隐藏 iframe露出 demo-frame 深色面板底),子文档等 data-theme 落地且首帧绘制后 // 对策:导航期间隐藏 iframe露出 demo-frame 深色面板底),子文档等 data-theme 落地且首帧绘制后
// postMessage 通知显示demo.htmlload 事件兜底load 时 module 脚本已执行data-theme 已在)。 // postMessage 通知显示demo.htmlload 事件兜底load 时 module 脚本已执行data-theme 已在)。
if (iframe) { 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) { window.addEventListener('message', function (e) {
if (e.origin === location.origin && e.data === 'astrion-demo-frame-ready') markDemoFrameReady(); if (e.origin === location.origin && e.data === 'astrion-demo-frame-ready') markDemoFrameReady();
}); });