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:
parent
d3b9bdbfe1
commit
56697a04ed
@ -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 当前不可用。
|
||||
|
||||
### 路径授权
|
||||
|
||||
|
||||
@ -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 权限等手段综合防护。
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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 当前不可用。
|
||||
|
||||
### 路径授权
|
||||
|
||||
|
||||
@ -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 权限等手段综合防护。
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -23,14 +23,15 @@
|
||||
<!-- ───────────────── 顶栏 ───────────────── -->
|
||||
<header class="topbar" id="topbar">
|
||||
<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>
|
||||
</a>
|
||||
</button>
|
||||
<nav class="topnav">
|
||||
<a class="topnav-link" href="#features" data-i18n="nav.features">功能</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="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>
|
||||
</nav>
|
||||
</div>
|
||||
@ -76,8 +77,12 @@
|
||||
</div>
|
||||
<div class="demo-frame-wrap">
|
||||
<div class="demo-frame" id="demoFrame">
|
||||
<!-- 演示壳较重(main.js 2.1MB):不用 loading=lazy(浏览器预载阈值太宽),改为 data-src + IntersectionObserver 严格懒加载,接近视口才注入 src;site.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>
|
||||
<div class="demo-preview" id="demoPreview">
|
||||
<button class="demo-preview-btn" id="demoPreviewBtn" type="button" data-i18n="demo.preview">点击预览</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="demo-note" data-i18n="demo.note">演示环境只含静态数据;实际使用由你自己的模型驱动。</p>
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user