feat(manual): add the illustrated Web Blender HTML user manual

A single-page 28-chapter manual generated from manual.json covers the
11 workspaces, all React editors, OPFS save/reopen, explicit local
downloads, import/export routes, preferences/keymap and the capability
matrix. It reuses verified course screenshots and adds 14 fresh
Chromium captures for shell, storage, area, camera, preferences, IO and
troubleshooting chapters.

Manual-driven fixes: Save As now states OPFS-only, GLTF import is
restored, quick toolbar honors its preference, startup workspace is
applied, Properties tabs jump to sections, Outliner fallback search
filters, and the editor-header ⋮ opens the area menu. Help and the
tutorial landing link the new manual; the PDF remains as an archive.

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
mes123456
2026-11-13 11:58:40 +08:00
parent aa8cc1313e
commit b932db7642
37 changed files with 1996 additions and 16 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

View File

@@ -0,0 +1,239 @@
{
"schemaVersion": 1,
"generatedAt": "2026-11-13T03:58:17.817Z",
"version": "1.0.0",
"lastVerified": "2026-11-13",
"sourceCommit": "aa8cc1313",
"images": [
{
"src": "/tutorial/full/steps/M01-interface.jpg",
"file": "/tutorial/full/steps/M01-interface.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M01-file-menu.jpg",
"file": "/tutorial/full/steps/M01-file-menu.jpg",
"exists": true
},
{
"src": "/manuals/images/ch01-first-save.jpg",
"file": "/manuals/images/ch01-first-save.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M02-operator-search.jpg",
"file": "/tutorial/full/steps/M02-operator-search.jpg",
"exists": true
},
{
"src": "/manuals/images/ch03-recent.jpg",
"file": "/manuals/images/ch03-recent.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M14-opfs-save.jpg",
"file": "/tutorial/full/steps/M14-opfs-save.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M14-reopen.jpg",
"file": "/tutorial/full/steps/M14-reopen.jpg",
"exists": true
},
{
"src": "/manuals/images/ch05-recovery.jpg",
"file": "/manuals/images/ch05-recovery.jpg",
"exists": true
},
{
"src": "/manuals/images/ch06-area-menu.jpg",
"file": "/manuals/images/ch06-area-menu.jpg",
"exists": true
},
{
"src": "/manuals/images/ch06-area-split.jpg",
"file": "/manuals/images/ch06-area-split.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M02-layout.jpg",
"file": "/tutorial/full/steps/M02-layout.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M03-mesh-selection.jpg",
"file": "/tutorial/full/steps/M03-mesh-selection.jpg",
"exists": true
},
{
"src": "/manuals/images/ch07-camera-view.jpg",
"file": "/manuals/images/ch07-camera-view.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M02-selection.jpg",
"file": "/tutorial/full/steps/M02-selection.jpg",
"exists": true
},
{
"src": "/manuals/images/ch08-outliner.jpg",
"file": "/manuals/images/ch08-outliner.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M04-properties.jpg",
"file": "/tutorial/full/steps/M04-properties.jpg",
"exists": true
},
{
"src": "/manuals/images/ch09-properties-tabs.jpg",
"file": "/manuals/images/ch09-properties-tabs.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M04-modifier-stack.jpg",
"file": "/tutorial/full/steps/M04-modifier-stack.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M03-workspace.jpg",
"file": "/tutorial/full/steps/M03-workspace.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M03-edit-mode.jpg",
"file": "/tutorial/full/steps/M03-edit-mode.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M06-uv-workspace.jpg",
"file": "/tutorial/full/steps/M06-uv-workspace.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M06-unwrap.jpg",
"file": "/tutorial/full/steps/M06-unwrap.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M05-shader-workspace.jpg",
"file": "/tutorial/full/steps/M05-shader-workspace.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M05-asset-library.jpg",
"file": "/tutorial/full/steps/M05-asset-library.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M07-painted-stroke.jpg",
"file": "/tutorial/full/steps/M07-painted-stroke.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M07-texture-paint.jpg",
"file": "/tutorial/full/steps/M07-texture-paint.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M12-geometry-nodes.jpg",
"file": "/tutorial/full/steps/M12-geometry-nodes.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M12-node-added.jpg",
"file": "/tutorial/full/steps/M12-node-added.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M13-vertices.jpg",
"file": "/tutorial/full/steps/M13-vertices.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M10-animation-workspace.jpg",
"file": "/tutorial/full/steps/M10-animation-workspace.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M10-keyframe.jpg",
"file": "/tutorial/full/steps/M10-keyframe.jpg",
"exists": true
},
{
"src": "/manuals/images/ch21-physics.jpg",
"file": "/manuals/images/ch21-physics.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M08-render-workspace.jpg",
"file": "/tutorial/full/steps/M08-render-workspace.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M08-pbr-panel.jpg",
"file": "/tutorial/full/steps/M08-pbr-panel.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M09-render-result.jpg",
"file": "/tutorial/full/steps/M09-render-result.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M11-compositor.jpg",
"file": "/tutorial/full/steps/M11-compositor.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M11-executed.jpg",
"file": "/tutorial/full/steps/M11-executed.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M11-nodes-added.jpg",
"file": "/tutorial/full/steps/M11-nodes-added.jpg",
"exists": true
},
{
"src": "/manuals/images/ch25-import-menu.jpg",
"file": "/manuals/images/ch25-import-menu.jpg",
"exists": true
},
{
"src": "/manuals/images/ch25-export-menu.jpg",
"file": "/manuals/images/ch25-export-menu.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M13-filtered.jpg",
"file": "/tutorial/full/steps/M13-filtered.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M15-scripting.jpg",
"file": "/tutorial/full/steps/M15-scripting.jpg",
"exists": true
},
{
"src": "/tutorial/full/steps/M15-console.jpg",
"file": "/tutorial/full/steps/M15-console.jpg",
"exists": true
},
{
"src": "/manuals/images/ch27-preferences.jpg",
"file": "/manuals/images/ch27-preferences.jpg",
"exists": true
},
{
"src": "/manuals/images/ch27-keymap.jpg",
"file": "/manuals/images/ch27-keymap.jpg",
"exists": true
},
{
"src": "/manuals/images/ch28-help.jpg",
"file": "/manuals/images/ch28-help.jpg",
"exists": true
}
]
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,145 @@
{
"schemaVersion": 1,
"chapters": [
{
"id": "ch01",
"title": "快速开始:首次加载、打开工程与第一次保存",
"summary": "从浏览器打开 Web Blender,等待 WASM 就绪,载入练习工程并把工程保存到 OPFS。"
},
{
"id": "ch02",
"title": "界面总览:工作区、编辑器、状态栏与命令搜索",
"summary": "认识 11 个工作区、区域、状态栏和 F3 操作搜索,知道在哪里找命令与反馈。"
},
{
"id": "ch03",
"title": "文件与工程:新建、打开、最近项目、另存为与保存副本",
"summary": "掌握当前 Web 版的工程文件入口,理解另存为写 OPFS、保存副本才下载本地副本。"
},
{
"id": "ch04",
"title": "浏览器存储:OPFS 保存、重开与手动本地下载",
"summary": "理解 OPFS 工程布局、最近项目索引、恢复点与手动下载的边界。"
},
{
"id": "ch05",
"title": "撤销、自动恢复点、恢复与诊断",
"summary": "使用撤销/重做、浏览器自动恢复点、最近项目修复和诊断报告处理错误。"
},
{
"id": "ch06",
"title": "工作区与 Area 布局:切换、⋮ 菜单、分割与持久化",
"summary": "用区域右上角选择器切换编辑器,用 ⋮ 菜单分割/合并/交换/最大化区域,并保存布局。"
},
{
"id": "ch07",
"title": "3D 视口:导航、选择、变换与相机视图",
"summary": "使用鼠标与键盘导航视口,选择对象/网格元素,执行变换并进入相机视图或锁定相机。"
},
{
"id": "ch08",
"title": "Outliner:层级、选择、可见性、重命名与拖放",
"summary": "在大纲视图中选择对象、展开层级、切换可见性、重命名、remap 数据和拖放父子/集合。"
},
{
"id": "ch09",
"title": "Properties:对象、修改器、材质与场景属性",
"summary": "使用右侧属性面板查看和编辑变换、层级、修改器、材质、相机、灯光、世界、渲染和物理设置。"
},
{
"id": "ch10",
"title": "对象模式:创建、复制、重命名、父子与 Join",
"summary": "创建基础体、复制/链接复制、重命名、删除、父子关系和 Join 合并。"
},
{
"id": "ch11",
"title": "网格编辑与雕刻范围",
"summary": "进入编辑模式执行选择/挤出/倒角/环切/细分,并在雕刻模式使用有限的笔刷与重构。"
},
{
"id": "ch12",
"title": "修改器栈:添加、排序、参数、可见性与应用",
"summary": "在 Modifier Stack 中添加 Bevel/Boolean/Mirror/Array 等修改器并管理堆栈。"
},
{
"id": "ch13",
"title": "非网格对象:曲线、曲面、文本、字体、融球与 Grease Pencil",
"summary": "从添加菜单创建曲线/文本/融球/Grease Pencil,编辑控制点或文本,导入本地字体。"
},
{
"id": "ch14",
"title": "UV 编辑:选择、展开、打包、变换与缝合",
"summary": "进入可写 UV 区域,全选/变换/展开/打包/缝合 UV,并保存重开。"
},
{
"id": "ch15",
"title": "材质与 Shader Editor:Principled、图像与预览",
"summary": "创建/分配材质,在节点画布编辑 Principled 与图像节点,预览并打包外部贴图。"
},
{
"id": "ch16",
"title": "PBR/RE 资产库、Append/Link 与 packed 图像",
"summary": "从 PBR/RE 资产库加载模型/材质,理解 Append/Link 与 packed 资源。"
},
{
"id": "ch17",
"title": "Texture Paint:画笔、撤销/重做、Make Local 与 OPFS",
"summary": "在 packed 图像上绘制,一次笔画一次提交,撤销/重做并保存重开;库链接图先本地化。"
},
{
"id": "ch18",
"title": "Geometry Nodes 与 Spreadsheet",
"summary": "在画布中添加 allowlist 节点、连线、编辑 socket、提交图,并在 Spreadsheet 检查数据。"
},
{
"id": "ch19",
"title": "Timeline、Dope Sheet 与关键帧动画",
"summary": "跳帧、插入/删除 Loc/Rot/Scale 关键帧、播放并管理 Action。"
},
{
"id": "ch20",
"title": "Graph Editor、NLA 与驱动器",
"summary": "在 Graph Editor 查看/拖动关键帧、切换插值;用 NLA 表单管理轨道/条带和驱动器表达式。"
},
{
"id": "ch21",
"title": "物理、粒子、点缓存与模拟限制",
"summary": "查看并编辑 Soft Body/Rigid Body/Cloth/Fluid/Particle 设置,理解 Web 播放与求解限制。"
},
{
"id": "ch22",
"title": "相机、灯光、世界与渲染属性",
"summary": "编辑相机镜头/DoF、灯光类型/能量、世界环境、色彩管理和渲染输出。"
},
{
"id": "ch23",
"title": "PBR 渲染、Render Result 与手动 PNG 下载",
"summary": "设置分辨率/相机/曝光/位移,执行浏览器 PBR 渲染,检查结果并手动下载 PNG。"
},
{
"id": "ch24",
"title": "Compositor:节点画布、执行与保存",
"summary": "创建 RGB/Exposure/Blur/Mix/Alpha Over/Transform/Viewer/Composite 节点,执行 CPU 合成并提交。"
},
{
"id": "ch25",
"title": "导入导出矩阵:GLB/GLTF/OBJ/STL/PLY、原生导出与 CSV",
"summary": "按可用性导入/导出模型与数据,明确浏览器本地、原生/F3、不可用三种路径。"
},
{
"id": "ch26",
"title": "脚本:Text Editor、Python Console 与 Info",
"summary": "编辑/运行文本脚本,使用原生 Python 控制台与 Info 状态,理解脚本策略。"
},
{
"id": "ch27",
"title": "Preferences、Keymap、快捷栏与启动工作区",
"summary": "使用分栏偏好设置、OPFS 保存、可改键位、快捷栏显隐和启动工作区。"
},
{
"id": "ch28",
"title": "故障排查、能力边界与离线/版本说明",
"summary": "按状态栏/诊断/支持矩阵定位问题,理解 OPFS、浏览器、原生服务和格式限制。"
}
]
}

View File

@@ -0,0 +1,19 @@
:root{color-scheme:dark;--bg:#191b1a;--panel:#232624;--panel-2:#2b2f2c;--line:#414743;--text:#e7eae7;--muted:#a8b0a9;--accent:#79a98a;--accent-2:#d6b36a;--danger:#c9836f}
*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:var(--bg);color:var(--text);font:15px/1.75 "Noto Sans SC",system-ui,-apple-system,"Segoe UI",sans-serif}a{color:#9bc7a9}
.manual-topbar{position:sticky;top:0;z-index:30;display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:8px 14px;background:#202321f2;border-bottom:1px solid var(--line);backdrop-filter:blur(6px)}
.manual-brand{font-weight:700;color:#eef3ee;text-decoration:none}.manual-menu-toggle{display:none;width:30px;height:28px;background:#343a35;color:#eee;border-radius:4px}
.manual-search{display:flex;align-items:center;gap:5px;flex:1 1 240px;max-width:520px;color:var(--muted);font-size:12px}.manual-search input{flex:1;min-width:0;padding:5px 8px;color:var(--text);background:#151716;border:1px solid #4d554f;border-radius:4px}
.manual-search-status{color:var(--accent-2);font-size:12px}.manual-topbar nav{display:flex;gap:12px;margin-left:auto;font-size:12px}.manual-topbar nav a{color:var(--muted);text-decoration:none}
.manual-layout{display:grid;grid-template-columns:300px minmax(0,1fr);align-items:start}.manual-sidebar{position:sticky;top:49px;max-height:calc(100vh - 49px);overflow:auto;padding:14px 10px;border-right:1px solid var(--line);background:#1e211f}
.manual-nav-group{margin-bottom:14px}.manual-nav-group>strong{display:block;margin:0 0 4px 7px;color:var(--accent-2);font-size:11px;letter-spacing:.08em}.manual-nav a{display:flex;gap:7px;padding:5px 8px;border-radius:4px;color:#cbd2cc;text-decoration:none;font-size:12px;line-height:1.35}.manual-nav a:hover{background:#2b302c}.manual-nav a span{flex:none;color:#8b9b8f;font-family:monospace}.manual-nav a[hidden]{display:none}.manual-nav a.active{background:#355343;color:#eaf3ec}
.manual-main{min-width:0;padding:24px clamp(16px,4vw,52px) 70px;overflow-wrap:anywhere}.manual-main p,.manual-main li{overflow-wrap:anywhere;word-break:break-word}.manual-hero{max-width:1050px;margin:0 auto 26px;padding:26px clamp(16px,3vw,30px);background:linear-gradient(135deg,#24302a,#242625 65%,#2f2a20);border:1px solid var(--line);border-radius:8px}.manual-kicker{color:var(--accent-2);font-size:12px;letter-spacing:.06em}.manual-hero h1{margin:6px 0 10px;font-size:clamp(28px,4vw,44px)}.manual-policy{margin-top:14px;padding:10px 14px;border-left:3px solid var(--accent-2);background:#292a25;font-size:13px}.manual-policy strong{color:#e6d1a4}.manual-policy ul{margin:4px 0 0;padding-left:18px}
.manual-chapter,.manual-appendix{max-width:1050px;margin:0 auto 28px;padding:22px clamp(16px,3vw,30px);background:var(--panel);border:1px solid var(--line);border-radius:8px}.manual-chapter h2,.manual-appendix h2{margin:4px 0 8px;line-height:1.3}.manual-chapter-kicker{margin:0;color:var(--accent-2);font-size:11px;letter-spacing:.06em}.manual-summary{color:#cdd4ce}.manual-meta{color:var(--muted);font-size:12px}
.manual-block{margin:16px 0}.manual-block h3{margin:0 0 5px;font-size:14px;color:#cfe0d4}.manual-block ul{margin:0;padding-left:20px}.manual-limit{border-left:3px solid var(--danger);padding-left:12px}
.manual-steps{margin:18px 0 0;padding:0;list-style:none;counter-reset:step}.manual-steps>li{position:relative;margin:0 0 24px;padding:0 0 0 38px}.manual-steps>li::before{counter-increment:step;content:counter(step);position:absolute;left:0;top:0;display:grid;place-items:center;width:26px;height:26px;border-radius:50%;background:#355343;color:#e8f2ec;font-size:12px;font-weight:700}.manual-steps h3{margin:2px 0 6px;font-size:14px;color:#e6ece7}.manual-steps p{margin:0 0 8px}
figure{margin:10px 0 0}.manual-steps img{display:block;width:100%;height:auto;border:1px solid var(--line);border-radius:6px;background:#111}figcaption{margin-top:5px;color:#8f9a92;font-size:11px}
.manual-callout{margin:8px 0;padding:8px 11px;background:#2c302a;border-left:3px solid var(--accent);border-radius:3px}.manual-callout.limit{border-left-color:var(--danger)}.manual-callout.warning{border-left-color:var(--accent-2)}
.manual-chapter-nav{display:flex;justify-content:space-between;margin-top:20px;padding-top:10px;border-top:1px solid var(--line);font-size:12px}.manual-chapter-nav a{color:var(--accent);text-decoration:none}
.manual-table-wrap{width:100%;max-width:100%;overflow-x:auto}.manual-table-wrap table{width:100%;table-layout:fixed;border-collapse:collapse;font-size:12px}.manual-table-wrap td,.manual-table-wrap th{padding:6px 8px;text-align:left;border:1px solid #454c47;vertical-align:top;overflow-wrap:anywhere;word-break:break-word}.manual-table-wrap th{background:#313732}.status{padding:1px 6px;border-radius:8px;font-size:10px;white-space:nowrap}.status.usable{background:#2f513d;color:#d8f0de}.status.partial{background:#514a2d;color:#f1e1ae}.status.native-only{background:#3b4554;color:#d7e3f5}.status.unsupported{background:#4c3331;color:#f1d4ce}
.manual-footer{max-width:1050px;margin:20px auto 0;padding-top:14px;border-top:1px solid var(--line);color:#8f9a92;font-size:11px}
@media(max-width:900px){.manual-topbar nav{display:none}.manual-menu-toggle{display:block}.manual-layout{grid-template-columns:1fr}.manual-sidebar{position:fixed;left:0;top:49px;bottom:0;z-index:25;width:280px;transform:translateX(-100%);transition:transform .15s}.manual-sidebar.open{transform:none;box-shadow:20px 0 40px #0008}.manual-main{padding-top:14px}}
@media print{.manual-topbar,.manual-sidebar,.manual-menu-toggle,.manual-chapter-nav,.manual-search-status{display:none!important}.manual-layout{display:block}.manual-main{padding:0}.manual-chapter,.manual-appendix{break-inside:avoid;border:0;padding:0;margin:0 0 18px}.manual-hero{break-after:page}figure img{max-height:420px;object-fit:contain} }

View File

@@ -0,0 +1,38 @@
(() => {
const sidebar = document.querySelector('.manual-sidebar');
const toggle = document.querySelector('.manual-menu-toggle');
const search = document.querySelector('.manual-search input');
const status = document.querySelector('.manual-search-status');
const links = Array.from(document.querySelectorAll('.manual-nav a[data-nav-id]'));
const articles = Array.from(document.querySelectorAll('.manual-chapter'));
const apply = () => {
const query = (search?.value || '').trim().toLowerCase();
let matches = 0;
for (const link of links) {
const article = document.querySelector('#' + CSS.escape(link.dataset.navId));
const text = (article?.dataset.search || link.textContent || '').toLowerCase();
const hit = !query || text.includes(query) || (link.textContent || '').toLowerCase().includes(query);
link.hidden = !hit;
if (hit) matches += 1;
}
for (const article of articles) {
const text = (article.dataset.search || '').toLowerCase();
article.hidden = Boolean(query) && !text.includes(query);
}
if (status) status.textContent = query ? matches + ' 个匹配' : '';
};
search?.addEventListener('input', apply);
toggle?.addEventListener('click', () => sidebar?.classList.toggle('open'));
window.addEventListener('keydown', (event) => {
if (event.key === '/' && document.activeElement !== search) { event.preventDefault(); search?.focus(); }
if (event.key === 'Escape' && search) { search.value = ''; apply(); search.blur(); }
});
const observer = 'IntersectionObserver' in window ? new IntersectionObserver((entries) => {
for (const entry of entries) {
if (!entry.isIntersecting) continue;
const id = entry.target.id;
for (const link of links) link.classList.toggle('active', link.dataset.navId === id);
}
}, { rootMargin: '-30% 0px -60% 0px' }) : null;
for (const article of articles) observer?.observe(article);
})();