[{"data":1,"prerenderedAt":886},["ShallowReactive",2],{"docs-nav-zh":3,"docs-search-sections-zh":118,"doc-\u002Fzh\u002Fdocs\u002F3d-preview\u002Frecipes":796,"doc-surround-\u002Fzh\u002Fdocs\u002F3d-preview\u002Frecipes":881},[4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,74,78,82,86,90,94,97,100,103,107,111,115],{"path":5,"title":6,"icon":7},"\u002Fzh\u002Fdocs\u002Fquickstart","快速开始","ri-rocket-line",{"path":9,"title":10,"icon":11},"\u002Fzh\u002Fdocs\u002Fdevtool","本地调试","ri-terminal-box-line",{"path":13,"title":14,"icon":15},"\u002Fzh\u002Fdocs\u002Fatomm","atomm 对象","ri-plug-line",{"path":17,"title":18,"icon":19},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Foverview","总览","ri-book-open-line",{"path":21,"title":22,"icon":23},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Farchitecture","架构规范","ri-braces-line",{"path":25,"title":26,"icon":27},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Finteraction","交互规范","ri-flashlight-line",{"path":29,"title":30,"icon":31},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Frebuild","参数变化与重建","ri-refresh-line",{"path":33,"title":34,"icon":35},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Frendering","渲染质量","ri-sun-line",{"path":37,"title":38,"icon":39},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Fdependencies","依赖管理","ri-stack-line",{"path":41,"title":42,"icon":43},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Frecipes","常见构件实现参考","ri-shape-line",{"path":45,"title":46,"icon":47},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Fchecklist","验收清单","ri-checkbox-circle-line",{"path":49,"title":50,"icon":51},"\u002Fzh\u002Fdocs\u002Fexport","导出能力 Export","ri-download-2-line",{"path":53,"title":54,"icon":55},"\u002Fzh\u002Fdocs\u002Fexport\u002Fsvg-color-spec","SVG 导出颜色规范","ri-drop-line",{"path":57,"title":58,"icon":59},"\u002Fzh\u002Fdocs\u002Fpublish","提交审核与上架","ri-send-plane-line",{"path":61,"title":62,"icon":63},"\u002Fzh\u002Fdocs\u002Ffaq","常见问题","ri-questionnaire-line",{"path":65,"title":66,"icon":67},"\u002Fzh\u002Fdocs\u002Fai\u002Fllms-txt","LLMs.txt","ri-file-text-line",{"path":69,"title":70,"icon":71},"\u002Fzh\u002Fdocs\u002Fai\u002Fskills","Skill 文件","ri-file-code-line",{"path":73,"title":18,"icon":19},"\u002Fzh\u002Fdocs\u002Fdesign\u002Foverview",{"path":75,"title":76,"icon":77},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fplatform","平台约束","ri-shield-check-line",{"path":79,"title":80,"icon":81},"\u002Fzh\u002Fdocs\u002Fdesign\u002Flayout","布局","ri-layout-line",{"path":83,"title":84,"icon":85},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fcomponents","组件","ri-checkbox-multiple-line",{"path":87,"title":88,"icon":89},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fcolor","色彩","ri-palette-line",{"path":91,"title":92,"icon":93},"\u002Fzh\u002Fdocs\u002Fdesign\u002Ftypography","字体","ri-text",{"path":95,"title":96,"icon":43},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fshapes","形状",{"path":98,"title":99,"icon":39},"\u002Fzh\u002Fdocs\u002Fdesign\u002Felevation","高度与深度",{"path":101,"title":102,"icon":27},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fmotion","动效",{"path":104,"title":105,"icon":106},"\u002Fzh\u002Fdocs\u002Fdesign\u002Ficon","图标","ri-apps-2-line",{"path":108,"title":109,"icon":110},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fdirection","方向（RTL）","ri-global-line",{"path":112,"title":113,"icon":114},"\u002Fzh\u002Fdocs\u002Fdesign\u002Faccessibility","无障碍","ri-eye-line",{"path":116,"title":117,"icon":47},"\u002Fzh\u002Fdocs\u002Fdesign\u002Frules","规则清单",[119,123,127,133,136,139,144,149,152,155,160,165,170,175,180,183,187,192,197,200,203,208,213,218,220,223,228,233,238,243,248,250,253,258,263,268,270,273,278,283,288,293,295,299,302,306,309,312,317,322,327,331,334,337,342,348,353,358,363,368,373,376,380,385,390,395,400,405,408,411,416,421,426,431,436,438,441,446,451,456,461,466,471,474,478,483,488,491,495,500,505,510,513,517,522,527,532,535,539,542,545,550,555,560,565,570,575,578,582,587,592,597,602,607,612,617,622,627,632,637,640,644,649,654,659,664,669,674,679,682,686,691,696,699,703,706,710,715,717,721,724,728,733,738,741,745,750,753,757,760,763,768,773,777,781,786,791],{"id":5,"title":6,"titles":120,"content":121,"level":122},[],"把任意技术栈写出的生成器接入 Atomm：创建生成器 → 引入一行 SDK 并接入能力 → 本地实时预览调试 → 提交审核上架。不需要安装任何命令行工具。",1,{"id":124,"title":6,"titles":125,"content":126,"level":122},"\u002Fzh\u002Fdocs\u002Fquickstart#快速开始",[],"把任意技术栈写出的生成器接入 Atomm：创建生成器 → 引入一行 SDK 并接入能力 → 本地实时预览调试 → 提交审核上架。不需要安装任何命令行工具。 纯 HTML、Vite、Vue、React…… 任意技术栈都行。Atomm 通过浏览器端 SDK 与你的应用通信，不限制你的构建方式。",{"id":128,"title":129,"titles":130,"content":131,"level":132},"\u002Fzh\u002Fdocs\u002Fquickstart#端到端流程","端到端流程",[6],"1. 在开发者后台创建生成器 先取一个名称作为公开标识（以小写字母开头，仅含小写字母、数字与连字符 -，创建后不可修改——它会成为运行子域名与访问路径的一部分），创建后进入应用详情页继续完善。这个名称在下一步本地预览与最终上架都会用到。 2. 在应用中引入 SDK 并接入能力 在你的页面里加入一行 script 获得全局 atomm 对象： \u003Cscript src=\"https:\u002F\u002Fstatic-res.makextool.com\u002Fscripts\u002Fjs\u002Fgenerator-sdk\u002Fplatform-sdk.js\">\u003C\u002Fscript> 再按需接入平台能力。以导出为例分两步——放一个导出按钮占位元素（SDK 会就地把它渲染成 Export 按钮），并注册 export 钩子提供结果文件： \u003C!-- 放在页面任意位置，SDK 就地把它渲染成 Export 下拉 -->\n\u003Cdiv data-atomm-export-button>\u003C\u002Fdiv> \u002F\u002F 平台在用户点导出时反拉你的结果文件\natomm.lifecycle.on('export', async () => ({ filename: 'design.glb', blob })) 详见 atomm 对象 与 导出能力。 3. 本地起服务，在浏览器里实时预览验证 本地启动你的服务（例如 http:\u002F\u002Flocalhost:5173），用第 1 步的生成器名称拼出预览地址，把本地地址填进 ?local=（仅支持 localhost \u002F 127.0.0.1 等回环地址）： https:\u002F\u002Fwww.atomm.com\u002Fcreativetools\u002Fcommunity\u002Fgenerator\u002F\u003C生成器名称>?local=http:\u002F\u002Flocalhost:5173\u002F DevTool 会把你的本地应用放进与线上一致的运行环境里实时预览，右侧接入助手面板提示 SDK 是否已引入、列出可接入的平台能力，你可在预览里验证导出等能力是否被正确调用。也可以直接在应用详情页的「本地调试」卡片里填入本地地址一键打开。详见 本地调试。 4. 上传产物、提交审核、上架 在应用详情页配置卡片展示资料、上传打包产物并提交审核。通过后自动上架到 ，用户在生成器聚合页即可直接打开使用，无需安装。详见 提交审核与上架。 html pre.shiki code .sZEs4, html code.shiki .sZEs4{--shiki-default:#E6EDF3}html pre.shiki code .sPWt5, html code.shiki .sPWt5{--shiki-default:#7EE787}html pre.shiki code .sFSAA, html code.shiki .sFSAA{--shiki-default:#79C0FF}html pre.shiki code .s9uIt, html code.shiki .s9uIt{--shiki-default:#A5D6FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sH3jZ, html code.shiki .sH3jZ{--shiki-default:#8B949E}html pre.shiki code .sc3cj, html code.shiki .sc3cj{--shiki-default:#D2A8FF}html pre.shiki code .suJrU, html code.shiki .suJrU{--shiki-default:#FF7B72}",2,{"id":9,"title":10,"titles":134,"content":135,"level":122},[],"无需安装任何命令行工具。本地调试只有两步：引入 SDK，然后用浏览器打开在线 DevTool 加载你的本地服务。",{"id":137,"title":10,"titles":138,"content":135,"level":122},"\u002Fzh\u002Fdocs\u002Fdevtool#本地调试",[],{"id":140,"title":141,"titles":142,"content":143,"level":132},"\u002Fzh\u002Fdocs\u002Fdevtool#_1-引入平台-sdk","1. 引入平台 SDK",[10],"在你的应用页面引入： \u003Cscript src=\"https:\u002F\u002Fstatic-res.makextool.com\u002Fscripts\u002Fjs\u002Fgenerator-sdk\u002Fplatform-sdk.js\">\u003C\u002Fscript> SDK 加载完成后会向平台握手，并在全局注入 atomm 对象（见 atomm 对象）。",{"id":145,"title":146,"titles":147,"content":148,"level":132},"\u002Fzh\u002Fdocs\u002Fdevtool#_2-打开在线-devtool-预览","2. 打开在线 DevTool 预览",[10],"本地启动你的服务后（任意端口），在浏览器打开下面的地址，把本地地址填进 ?local=（仅支持 localhost \u002F 127.0.0.1 等回环地址）： https:\u002F\u002Fwww.atomm.com\u002Fcreativetools\u002Fcommunity\u002Fgenerator\u002F\u003C生成器名称>?local=http:\u002F\u002Flocalhost:5173\u002F 左侧：你的生成器实时预览（本地服务被放进与线上一致的运行环境，能力限制也一致）右侧：两个页签\n模拟器：目前提供「预览语言」——切换会改变 atomm.app.getLocale() 的返回值并重载生成器，用来验证多语言（见 atomm 对象的「语言」一节）。接入助手：\nSDK 整合提示：未引入 platform-sdk.js 时提示「整合未完成」，给出可复制的引入代码与文档链接；SDK 握手成功后自动消除。平台能力清单：列出平台提供的能力（导出 \u002F 下载、atomm 对象等），每项可「查看文档」或「复制接入 Prompt」交给 AI 接入。 dev 预览模式固定为 dev 环境、不扣费、平台能力默认全开，不依赖任何后台数据。 在开发者后台应用详情页的「本地调试」卡片里填入本地地址，即可直接打开上面的预览。 www.atomm.com 走 https。浏览器对 localhost 的 http 地址有混合内容豁免，所以本地 http 服务能正常加载；非 localhost 的 http 地址会被浏览器拦截，请使用 localhost 或为本地服务启用 https。 html pre.shiki code .sZEs4, html code.shiki .sZEs4{--shiki-default:#E6EDF3}html pre.shiki code .sPWt5, html code.shiki .sPWt5{--shiki-default:#7EE787}html pre.shiki code .sFSAA, html code.shiki .sFSAA{--shiki-default:#79C0FF}html pre.shiki code .s9uIt, html code.shiki .s9uIt{--shiki-default:#A5D6FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":13,"title":14,"titles":150,"content":151,"level":122},[],"atomm 是平台 SDK（platform-sdk.js）加载后注入到页面的全局对象，提供平台能力与 UI 工具。在生成器代码中可直接使用。",{"id":153,"title":14,"titles":154,"content":151,"level":122},"\u002Fzh\u002Fdocs\u002Fatomm#atomm-对象",[],{"id":156,"title":157,"titles":158,"content":159,"level":132},"\u002Fzh\u002Fdocs\u002Fatomm#生命周期-atommlifecycle","生命周期 atomm.lifecycle",[14],"通过 lifecycle.on 注册平台生命周期钩子： atomm.lifecycle.on('export', async () => { ... })",{"id":161,"title":162,"titles":163,"content":164,"level":132},"\u002Fzh\u002Fdocs\u002Fatomm#ui-提示-atommui","UI 提示 atomm.ui",[14],"弹出平台全局提示（toast）。参数为对象： type：可选 success \u002F warning \u002F error \u002F info，默认 successmessage：提示文案duration：可选，展示时长，单位秒，0 表示不自动关闭；不传默认 3 秒 toast() 返回本条提示的 id；用 closeToast(id) 主动关闭。duration: 0 的常驻提示必须这样关，否则会一直挂着。 atomm.ui.toast({ type: 'success', message: '已完成' })\n\n\u002F\u002F 指定展示 5 秒\natomm.ui.toast({ type: 'info', message: '正在处理…', duration: 5 })\n\n\u002F\u002F duration: 0 常驻不自动关闭，任务完成后用 closeToast 关掉\nconst id = await atomm.ui.toast({ type: 'info', message: '导出中…', duration: 0 })\n\u002F\u002F …任务完成…\nawait atomm.ui.closeToast(id) 传字符串（如 toast('已完成')）不会生效——平台只读取对象上的 message 与 type，字符串会被当成空消息。ui 命名空间目前提供 toast（返回提示 id）与 closeToast。",{"id":166,"title":167,"titles":168,"content":169,"level":132},"\u002Fzh\u002Fdocs\u002Fatomm#语言-atommapp","语言 atomm.app",[14],"平台当前的界面语言，用来让生成器跟着平台一起本地化。 \u002F\u002F 当前语言短码，必定是受支持的 17 种之一\nconst locale = await atomm.app.getLocale()\n\n\u002F\u002F 全部受支持语言（短码 + 展示名），用于做与平台一致的语言选择器\nconst locales = await atomm.app.getSupportedLocales()\n\u002F\u002F → [{ code: 'zh', name: '简体中文' }, { code: 'en', name: 'English' }, …] 启动时调一次 getLocale() 决定初始语言就够了。这是一次性读取，不是订阅——平台切语言会整页重载，你的应用随之重启并读到新值，不需要自己监听变化。 getLocale() 返回的一定是下表这 17 种之一，但你的应用未必都翻译了。拿到没有对应文案的语言时，回退到英文或你自己的默认语言。 短码展示名语言zh简体中文简体中文enEnglish英语zh-hant繁體中文繁体中文deDeutsch德语esEspañol西班牙语frFrançais法语itItaliano意大利语ja日本語日语ko한국어韩语ruРусский俄语ukУкраїнська乌克兰语slSlovenščina斯洛文尼亚语thไทย泰语plPolski波兰语csČeština捷克语idBahasa Indonesia印尼语viTiếng Việt越南语 表格顺序即 getSupportedLocales() 的返回顺序，「展示名」就是它返回的 name，做语言选择器直接用即可。 开发工具包的「模拟器」里有「预览语言」下拉，切换会改变 getLocale() 的返回值并重载生成器，不用真到平台上切语言。见开发工具包。",{"id":171,"title":172,"titles":173,"content":174,"level":132},"\u002Fzh\u002Fdocs\u002Fatomm#用户-atommuser","用户 atomm.user",[14],"\u002F\u002F 当前是否已登录（只回布尔，不含 token 与个人信息）\nconst loggedIn = await atomm.user.isLoggedIn()\n\n\u002F\u002F 未登录时主动拉起登录弹窗，等用户登录完成后继续；返回最终是否已登录\nif (!loggedIn) {\n  const ok = await atomm.user.login()\n  if (!ok) return \u002F\u002F 用户取消或登录失败\n}\n\u002F\u002F 到这里已登录，继续你的逻辑 login() 会弹出登录窗口，请在用户点击等手势的回调里调用，避免被浏览器弹窗拦截。登录是耗时交互，平台已为它放宽超时（约 5 分钟），无需你自己加超时。",{"id":176,"title":177,"titles":178,"content":179,"level":132},"\u002Fzh\u002Fdocs\u002Fatomm#安全调用","安全调用",[14],"不确定是否运行在平台内时，用可选链避免报错： window.atomm?.ui?.toast?.({ type: 'info', message: 'hi' }) html pre.shiki code .sZEs4, html code.shiki .sZEs4{--shiki-default:#E6EDF3}html pre.shiki code .sc3cj, html code.shiki .sc3cj{--shiki-default:#D2A8FF}html pre.shiki code .s9uIt, html code.shiki .s9uIt{--shiki-default:#A5D6FF}html pre.shiki code .suJrU, html code.shiki .suJrU{--shiki-default:#FF7B72}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sH3jZ, html code.shiki .sH3jZ{--shiki-default:#8B949E}html pre.shiki code .sFSAA, html code.shiki .sFSAA{--shiki-default:#79C0FF}",{"id":17,"title":18,"titles":181,"content":182,"level":122},[],"本章规定了在 Atomm 平台上开发创意工具 \u002F 生成器 3D 预览功能（基于 three.js）时应遵循的技术规范，涵盖架构设计、交互体验、渲染质量、性能与稳定性四个方面，并在末章提供提测验收清单。",{"id":184,"title":18,"titles":185,"content":186,"level":122},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Foverview#总览",[],"本章规定了在 Atomm 平台上开发创意工具 \u002F 生成器 3D 预览功能（基于 three.js）时应遵循的技术规范，涵盖架构设计、交互体验、渲染质量、性能与稳定性四个方面，并在末章提供提测验收清单。 适用范围：所有提供 3D 预览视图的生成器类应用。 同一套要求也写成了给编码 agent 读的单文件 skill（右上角「下载 3D Preview Skill」），直接交给它就行。 下面这个演示就是让 agent 读完该 skill 后做出来的：拖动旋转、滚轮缩放，改右侧参数时 2D 预览、3D 视图与导出的 SVG 共用同一份几何 IR。",{"id":188,"title":189,"titles":190,"content":191,"level":132},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Foverview#规范用语约定","规范用语约定",[18],"用语含义必须强制要求，验收时逐条检查推荐默认应采用，如有充分理由可例外可选视产品需求决定是否实现",{"id":193,"title":194,"titles":195,"content":196,"level":132},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Foverview#是否需要-3d-预览","是否需要 3D 预览",[18],"3D 预览为可选能力。仅当厚度、立体层次、装配关系属于工具的核心展示价值时，才建议实现 3D 预览；对于以平面效果为主的工具，推荐使用 2D SVG 配合程序化材质纹理（如 feTurbulence 木纹滤镜）呈现材质效果，实现成本显著更低。 一旦决定实现 3D 预览，即须完整满足本规范的验收标准。不满足交互与渲染基线的 3D 预览会降低整体产品体验，不予通过验收。",{"id":21,"title":22,"titles":198,"content":199,"level":122},[],"",{"id":201,"title":22,"titles":202,"content":199,"level":122},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Farchitecture#架构规范",[],{"id":204,"title":205,"titles":206,"content":207,"level":132},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Farchitecture#统一几何数据源","统一几何数据源",[22],"必须遵循单一数据流架构： Config（参数对象）→ 纯函数渲染 → 几何中间表示（IR）→ 2D 画布 \u002F 3D 视图 \u002F 导出文件 3D 视图必须与 2D 画布、导出文件消费同一份几何 IR，不得在 3D 侧单独实现一套几何计算逻辑。独立实现将导致预览与导出结果不一致。",{"id":209,"title":210,"titles":211,"content":212,"level":132},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Farchitecture#坐标系约定","坐标系约定",[22],"各渲染空间的坐标系约定如下，必须在文档化的位置统一维护： 空间单位 \u002F 原点 \u002F 轴向IR（规范空间）mm，中心 (0,0)，Y 轴向下（二维，无 Z）3D（three.js）mm，Y 轴向上，Z 为厚度方向 轮廓在 XY 平面构建、沿 +Z 挤出，零件正面朝相机。IR 翻成 Y-up 用镜像不用旋转——整个 content group 设一次 group.scale.y = -1；改用旋转会把厚度换到另一根轴上。 翻错地方的两种表现： 某个零件上下颠倒且是镜像的，周围都对——是这个 mesh 自己又设了一次 scale.y。它已经继承了 group 的翻转，再翻一次就抵消。每个面都渲染成内外颠倒——翻的是几何体（geometry.scale(1, -1, 1)）。three.js 只在物体级别按 matrixWorld 行列式补偿绕序，几何体级别没有补偿。",{"id":214,"title":215,"titles":216,"content":217,"level":132},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Farchitecture#路径转换","路径转换",[22],"文字及复杂图形的 SVG 路径必须通过 new SVGLoader().parse() 与 SVGLoader.createShapes() 转换为 THREE.Shape（该方法按填充规则正确处理镂空与字腔），再经 ExtrudeGeometry 挤出。不得自行实现 SVG 路径解析。",{"id":25,"title":26,"titles":219,"content":199,"level":122},[],{"id":221,"title":26,"titles":222,"content":199,"level":122},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Finteraction#交互规范",[],{"id":224,"title":225,"titles":226,"content":227,"level":132},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Finteraction#相机控制","相机控制",[26],"使用 OrbitControls 时，必须完成以下配置： 启用阻尼：controls.enableDamping = true，并在每帧调用 controls.update()。限制缩放距离：设置 minDistance 与 maxDistance。推荐取值范围为 1.2×R ～ 8×R（R 为模型外接半径），防止相机进入模型内部或远离至不可见。限制俯仰角：设置 maxPolarAngle 略小于 π（推荐 0.95π），避免相机翻转至模型下方。初始取景：相机距离约为 3.2×R，采用小幅俯视角度，保证默认视角下模型完整可见且具有立体感。",{"id":229,"title":230,"titles":231,"content":232,"level":132},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Finteraction#滚轮事件处理","滚轮事件处理",[26],"如自行接管缩放逻辑，必须使用原生非被动事件监听： element.addEventListener('wheel', handler, { passive: false }) 并在处理函数中调用 preventDefault()。框架层的滚轮绑定通常是被动监听（React 的 onWheel、Vue 未显式退出 passive 的 @wheel），无法阻止页面默认滚动行为，会导致用户在 3D 区域缩放时整个页面随之滚动。OrbitControls 自带的滚轮处理已满足此要求。",{"id":234,"title":235,"titles":236,"content":237,"level":132},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Finteraction#环境动效","环境动效",[26],"推荐实现闲置漂浮动效：使用互为非整数倍频率的 Lissajous 摆动，避免出现可感知的循环点；幅度应保持轻微。推荐为拖拽交互接入欠阻尼弹簧模型，避免指针位移与旋转 1:1 直接映射。以效果为准而不是以数值为准：松手后有一次看得见的回弹过冲，约 0.3 秒停稳。（刚度 70 \u002F 阻尼 5.5 是某一种逐帧积分写法下的取值——这两个数只有配上你用的公式才有意义，所以按效果调。）环境动效的变换必须挂载在不随参数重建的持久父节点（rig）上（见参数变化与重建），确保参数变化时动效连续。所有动效必须响应 prefers-reduced-motion 媒体查询，在用户开启减弱动效时全部禁用。",{"id":239,"title":240,"titles":241,"content":242,"level":132},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Finteraction#帧循环","帧循环",[26],"每帧更新（动效、弹簧、controls.update()）必须在 rAF 循环里直接改 Object3D 的变换；框架渲染仅由 Config 变化触发，不得在帧循环里触发框架状态更新（React 的 setState、Vue 的响应式赋值、Svelte 的 store 写入——都会一秒重渲 60 次并掉帧）。页签处于隐藏状态时（document.visibilityState === 'hidden'）rAF 会暂停，所有动效数值都冻结在最后一帧。任何要读这些数值的场景——自动化检查、调试——都得先把页签切到前台，否则读到的是旧值，会误判成动效坏了。",{"id":244,"title":245,"titles":246,"content":247,"level":132},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Finteraction#文本选择","文本选择",[26],"3D 舞台容器必须设置 user-select: none，防止拖拽旋转时选中页面文本。 html pre.shiki code .sZEs4, html code.shiki .sZEs4{--shiki-default:#E6EDF3}html pre.shiki code .sc3cj, html code.shiki .sc3cj{--shiki-default:#D2A8FF}html pre.shiki code .s9uIt, html code.shiki .s9uIt{--shiki-default:#A5D6FF}html pre.shiki code .sFSAA, html code.shiki .sFSAA{--shiki-default:#79C0FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":29,"title":30,"titles":249,"content":199,"level":122},[],{"id":251,"title":30,"titles":252,"content":199,"level":122},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Frebuild#参数变化与重建",[],{"id":254,"title":255,"titles":256,"content":257,"level":132},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Frebuild#重建策略","重建策略",[30],"参数变化触发的几何重建必须经过防抖处理（推荐 100–200ms），保证滑杆连续拖动时不因逐帧重建而掉帧。重建范围必须限定为内容 group 子树；相机位置、OrbitControls 状态、环境动效 rig 均不得因参数变化而重置。",{"id":259,"title":260,"titles":261,"content":262,"level":132},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Frebuild#资源释放","资源释放",[30],"两种生命周期，不得合成同一个 dispose 函数。 每次重建释放本次构建创建的资源： 所有 geometry.dispose()所有 material.dispose()，以及为本次构建创建的纹理 未正确释放会导致用户持续调参过程中 GPU 内存增长，最终页面卡死。 仅在卸载时释放跨重建存活的资源： PMREM 的 RenderTarget 与环境贴图跨重建复用的共享纹理——程序化噪声纹理、与 2D 画布共用的 CanvasTexture（见渲染质量）controls.dispose() 与 renderer.dispose() 两份清单互不容纳对方的条目。每次重建重新生成环境贴图，等于每个防抖周期跑一趟多趟 PMREM 渲染——比你想避免的那个泄漏更贵；每次重建释放共享纹理则更糟——下一帧材质拿到一张死纹理，直接渲染成空白。 注意：遍历内容子树、见到纹理就 dispose 会同时踩中这两个坑——材质引用共享纹理，但并不拥有它。释放你创建的，不要释放你引用的。",{"id":264,"title":265,"titles":266,"content":267,"level":132},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Frebuild#尺寸设备像素比与零尺寸防护","尺寸、设备像素比与零尺寸防护",[30],"renderer.setPixelRatio() 必须封顶——Math.min(window.devicePixelRatio, 2)。在手机或 5K 屏上按 DPR 3 渲染，片元开销是 DPR 2 的 2.25 倍且肉眼无差别，这是「开发机上跑得好好的、到用户那儿掉帧」最常见的原因。响应容器尺寸变化必须三件齐全：camera.aspect、camera.updateProjectionMatrix()、renderer.setSize()。漏了 updateProjectionMatrix() 画面拉伸，漏了 setSize() 还按旧分辨率渲染。组件可能在隐藏页签或折叠面板中以 0×0 尺寸挂载。ResizeObserver 收到 0×0 尺寸时必须忽略并保留既有视图状态，待获得非零尺寸后再执行初始化或自适应，确保页签切换后视图完好。",{"id":33,"title":34,"titles":269,"content":199,"level":122},[],{"id":271,"title":34,"titles":272,"content":199,"level":122},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Frendering#渲染质量",[],{"id":274,"title":275,"titles":276,"content":277,"level":132},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Frendering#光照","光照",[34],"必须配置基于图像的环境光照（IBL），不得仅使用 AmbientLight。推荐方案为内置程序化室内环境，无需外部资源文件、无网络请求与 CSP 限制：const pmrem = new THREE.PMREMGenerator(renderer)\nconst envRT = pmrem.fromScene(new RoomEnvironment()) \u002F\u002F envRT 要留着——卸载时要释放它\nscene.environment = envRT.texture\n环境光强度应适当调低（参考值：scene.environmentIntensity ≈ 0.4；深色部件使用较低的 envMapIntensity）以保持画面对比度，并叠加一盏柔和方向光提供光照方向感。",{"id":279,"title":280,"titles":281,"content":282,"level":132},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Frendering#色调映射","色调映射",[34],"必须启用 ACES 色调映射： renderer.toneMapping = THREE.ACESFilmicToneMapping 该配置可显著改善 MeshStandardMaterial \u002F MeshPhysicalMaterial 的材质质感。",{"id":284,"title":285,"titles":286,"content":287,"level":132},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Frendering#材质","材质",[34],"推荐使用 canvas 生成的程序化噪声纹理，同时作为 map 与 bumpMap，以低成本呈现物理材质感。这张纹理只建一次、跨重建复用；什么时候释放见参数变化与重建。ExtrudeGeometry 的 UV 坐标就是以 mm 为单位的世界坐标，texture.repeat 必须按物理尺寸换算：先定一块纹理该铺多少 mm，再取 repeat = 1 \u002F 铺展mm数（木纹一个循环 45mm 就是 1\u002F45）。写死重复次数的话，零件一改尺寸纹理就跟着缩放了。2D 画布使用的程序化材质 SVG 可渲染至 canvas 后创建 CanvasTexture 供 3D 复用，保证 2D \u002F 3D 材质观感一致。顶面 UV：在模型以原点为中心（世界坐标从 −R 到 +R）的前提下，repeat = 1\u002F(2R) 加 offset = 0.5 正好把这段映射到 0–1。推荐按面分组配置材质数组 [faceMat, sideMat]（group 0 为顶\u002F底面，group 1 为侧面），侧面颜色略深以增强立体感。",{"id":289,"title":290,"titles":291,"content":292,"level":132},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Frendering#hdri-环境可选","HDRI 环境（可选）",[34],"如需更高的环境真实度，可打包一张小尺寸 CC0 授权的 1k .hdr 文件（约 1.5MB）：经 PMREM 处理后作为 scene.environment，原始 equirect 纹理作为 scene.background 并配置适度的 backgroundBlurriness；文件加载期间以 RoomEnvironment 作为即时占位。 实现时需注意以下事项： 俯视相机的视线会指向全景图的天顶 \u002F 地面区域（全景图中最平坦的部分），导致背景退化为单一模糊色块。应通过 backgroundRotation 与 environmentRotation 将地平线区域调整至主体背后，并将默认相机角度设置得更平缓。背景亮度应明显低于主体，保证模型剪影与孔洞清晰可读。 html pre.shiki code .suJrU, html code.shiki .suJrU{--shiki-default:#FF7B72}html pre.shiki code .sFSAA, html code.shiki .sFSAA{--shiki-default:#79C0FF}html pre.shiki code .sZEs4, html code.shiki .sZEs4{--shiki-default:#E6EDF3}html pre.shiki code .sc3cj, html code.shiki .sc3cj{--shiki-default:#D2A8FF}html pre.shiki code .sH3jZ, html code.shiki .sH3jZ{--shiki-default:#8B949E}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"id":37,"title":38,"titles":294,"content":199,"level":122},[],{"id":296,"title":38,"titles":297,"content":298,"level":122},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Fdependencies#依赖管理",[],"必须锁定 three.js 版本。three.js 的 API 存在持续的弃用性变更，已知示例：\nRoomEnvironment 构造函数在 r150 前后移除了 renderer 参数；r180 弃用 RGBELoader，改用 HDRLoader（旧类还能用，但会告警）；r185 弃用 PCFSoftShadowMap，现在会静默退回 PCFShadowMap；色彩空间与色调映射的默认值曾多次调整。升级 three.js 版本后必须检查浏览器控制台，控制台无弃用警告属于验收标准之一。",{"id":41,"title":42,"titles":300,"content":301,"level":122},[],"以下为按需采用的实现方案：",{"id":303,"title":42,"titles":304,"content":305,"level":122},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Frecipes#常见构件实现参考",[],"以下为按需采用的实现方案： 构件实现方案凸起标记 \u002F 文字复用切割件的轮廓路径 → SVGLoader → ExtrudeGeometry，放置于 z = 面板厚度。和其它零件一样挂进 content group，它会继承 group 的 Y 翻转，不要再翻一次薄环 \u002F 描边件按 半径 ± 线宽\u002F2 生成内外轮廓后挤出细环；圆形采用平滑采样，多边形采用 flatten可翻折翻板每条开放切割弧视为一块板，铰链轴取弧线两端点连线（弦）；绕该轴旋转挤出板，旋转方向取使板质心朝 +z 抬升的一侧；基板冲出对应孔，未切割芯保持实体；翻起角度在小集合内循环取值以获得自然观感",{"id":45,"title":46,"titles":307,"content":308,"level":122},[],"提测前逐项自查，全部通过后方可提交评审。",{"id":310,"title":46,"titles":311,"content":308,"level":122},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Fchecklist#验收清单",[],{"id":313,"title":314,"titles":315,"content":316,"level":132},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Fchecklist#交互","交互",[46],"OrbitControls 已启用阻尼；缩放距离与俯仰角已设置上下限 初始取景约 3.2×R、小幅俯视，模型一屏内完整可见 3D 区域内滚轮操作不引发页面滚动 \u002F 缩放 连续拖动滑杆时帧率稳定（几何重建已防抖） 修改任意参数后视角不重置、动效不中断 拖拽具备弹簧回弹；闲置具备轻微漂浮；prefers-reduced-motion 下动效全部禁用 3D 舞台已设置 user-select: none，拖拽不选中页面文本",{"id":318,"title":319,"titles":320,"content":321,"level":132},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Fchecklist#正确性","正确性",[46],"3D 几何与 2D 画布、导出文件源自同一份 IR（修改任一参数，三处同步变化） Y-down → Y-up 翻转只在 content group 上做一次——不是每个 mesh 各翻、也不是翻几何体。文字无镜像、孔位无对称性错位、无内外颠倒的面 每个面板参数在 3D 视图中均产生可见效果，或按模式隐藏并附说明（不允许参数可见但无效）",{"id":323,"title":324,"titles":325,"content":326,"level":132},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Fchecklist#性能与稳定性","性能与稳定性",[46],"setPixelRatio 已封顶（DPR ≤ 2） 持续调参 5 分钟，GPU \u002F JS 内存保持平稳（资源释放完整） 页签隐藏后切回，视图完好，无空白、无视图状态丢失 以 0×0 尺寸挂载后，获得有效尺寸时可正常初始化 容器尺寸变化时 camera.aspect、updateProjectionMatrix()、setSize() 同步更新，画面自适应且无拉伸变形",{"id":328,"title":34,"titles":329,"content":330,"level":132},"\u002Fzh\u002Fdocs\u002F3d-preview\u002Fchecklist#渲染质量",[46],"已配置环境光照（RoomEnvironment 或 HDRI）并叠加方向光，非纯 AmbientLight 已启用 ACES 色调映射 环境贴图与共享纹理只建一次、仅在卸载时释放，未随每次重建重新生成或销毁 顶面 \u002F 侧面材质已分组，侧面颜色略深 浏览器控制台无 three.js 弃用警告",{"id":49,"title":50,"titles":332,"content":333,"level":122},[],"注册 export 生命周期钩子，平台会在用户点击「导出」或「Open in Studio」时调用你的生成器并获取结果文件。返回值统一为 { filename, blob }，支持任意文件格式（图片、SVG、3D 模型、PDF 等）。",{"id":335,"title":50,"titles":336,"content":333,"level":122},"\u002Fzh\u002Fdocs\u002Fexport#导出能力-export",[],{"id":338,"title":339,"titles":340,"content":341,"level":132},"\u002Fzh\u002Fdocs\u002Fexport#放置导出按钮","放置导出按钮",[50],"导出按钮由你在应用内自己放置——在页面任意位置写一个带 data-atomm-export-button 的元素，SDK 会就地把它渲染成平台受控的 Export 按钮（下拉 + 积分提示）。点击只经平台触发： \u003Cdiv data-atomm-export-button>\u003C\u002Fdiv> 点击展开下拉，两项行为不同：下载直接把文件交给用户，不弹任何窗；Open in Studio 会先弹出导出设置弹窗，用户在里面选机型 \u002F 加工模式 \u002F 材料，并为 SVG 的每个颜色分组选加工类型（见SVG 导出颜色规范）。你的 export 钩子会收到 intent，两项各触发一次，可据此返回不同产物。 位置与外层定位由你控制（如 position: fixed 固定在角落）；一页可放多个，各自独立水合。按钮渲染在 Shadow DOM 里做样式隔离；换肤只用文档化的 --atomm-export-* CSS 变量（在该元素或任意祖先上设置，自动继承进按钮）： [data-atomm-export-button] {\n  --atomm-export-bg: #d32f2f; \u002F* 按钮底色 *\u002F\n  --atomm-export-radius: 0; \u002F* 圆角 *\u002F\n  --atomm-export-width: 200px; \u002F* 宽度：px 或 100% 填满容器 *\u002F\n  --atomm-export-height: 40px; \u002F* 高度 *\u002F\n} 变量默认值控什么--atomm-export-bg \u002F -bg-hover \u002F -bg-active#070b10 \u002F #252c36 \u002F #1c2129按钮三态底色--atomm-export-color#fff按钮文字色--atomm-export-widthauto（贴内容）按钮宽度（px 或 100% 填满容器，下限 24px）--atomm-export-height32px按钮高度（px 或 100%，下限 24px）--atomm-export-radius8px圆角--atomm-export-font \u002F -font-sizeInter, system-ui, sans-serif \u002F 14px字体 \u002F 字号--atomm-export-menu-bg \u002F -menu-color \u002F -menu-hover-bg#fff \u002F #111 \u002F #f4f4f5下拉菜单配色--atomm-export-menu-radius \u002F -menu-shadow8px \u002F 0 4px 16px rgba(16,24,40,.12)菜单圆角 \u002F 阴影--atomm-export-z1000下拉浮层层级 仅上表这些 --atomm-export-* 变量是受支持的定制面；按钮内部 class 随时可能变化，请勿依赖。 导出按钮及其下载 \u002F Open in Studio \u002F 积分提示都由 atomm 平台驱动，只有当生成器运行在 atomm 环境里才生效——线上平台，或开发工具的本地预览（?local=）。若脱离 atomm 单独打开生成器（如直接开本地 HTML），按钮可能照常显示，但点击不会产出文件、也不显示计费状态——这是设计如此，并非 bug。请始终用本地预览或线上平台来验证导出。",{"id":343,"title":344,"titles":345,"content":346,"level":347},"\u002Fzh\u002Fdocs\u002Fexport#按钮上的免费次数-积分提示","按钮上的免费次数 \u002F 积分提示",[50,339],"按钮会自动显示当前计费状态（由平台下发，你无需干预）： 形态显示含义免费时段30s 倒计时一次导出成功后赠送的一小段时间，期内导出不计费免费次数免费 3\u002F3剩余 \u002F 总免费次数消耗积分积分币 + 数字免费用尽后每次导出消耗的积分数 本地调试（?local=），点击导出不会真实扣费，刷新页面即重置。",3,{"id":349,"title":350,"titles":351,"content":352,"level":132},"\u002Fzh\u002Fdocs\u002Fexport#注册-export-hook","注册 export hook",[50],"atomm.lifecycle.on('export', async ({ intent }) => {\n  \u002F\u002F intent 表明用户点的是「下载」还是「Open in Studio」；handler 自行读取当前生成结果并产出一个 Blob\n  const blob = await exportCurrentResultAsBlob()\n  if (!blob) {\n    throw new Error('请先生成作品后再下载')\n  }\n\n  return {\n    filename: 'my-generator.glb', \u002F\u002F 含扩展名，决定下载文件名与 Open in Studio 的资产类型\n    blob, \u002F\u002F 任意格式的 Blob；MIME 取自 blob.type\n  }\n})",{"id":354,"title":355,"titles":356,"content":357,"level":347},"\u002Fzh\u002Fdocs\u002Fexport#入参-intent区分下载与-open-in-studio","入参 intent：区分下载与 Open in Studio",[50,350],"下载与 Open in Studio 共用这一个 hook，intent 告诉你用户点的是下拉里哪一项，可据此返回不同产物。\n每项各触发一次；不需区分就两个 intent 都返回同样内容。 intent触发时机通常返回'download'点下拉里的「下载」用户要落盘的全部文件（多份时平台打成一个 zip）'openInStudio'点下拉里的「Open in Studio」只给 Studio 要打开的内容，通常是一份可继续编辑的矢量图 加工类型只能以属性写进 SVG 元素，独立的位图文件无处承载。所以选 Open in Studio 时，平台会把产物里的独立位图（PNG \u002F JPEG \u002F WebP \u002F GIF \u002F BMP）包一层 SVG \u003Cimage>，它才能出现在弹窗的「位图」分组里、被选上加工类型并带给 Studio。包装时的物理尺寸这样定：能从图片里读到分辨率（PNG 的 pHYs、JPEG 的 EXIF \u002F JFIF、BMP 的每米像素数）就按它换算成毫米；读不到就按 SVG\u002FCSS 的 96 PPI 折算。想让尺寸准确，就在导出的图片里写上分辨率元数据。下载永远原样交付你给的字节——不包装、不加属性。开发者说好导出 PNG，用户拿到的就是 PNG。 atomm.lifecycle.on('export', async ({ intent }) => {\n  \u002F\u002F Open in Studio：Studio 里要继续编辑，只给一份矢量图\n  if (intent === 'openInStudio') {\n    return { filename: 'design.svg', blob: await buildSvg() }\n  }\n\n  \u002F\u002F 下载：给全部文件，平台自动打成一个 zip\n  return [\n    { filename: 'cut.svg', blob: cutBlob },\n    { filename: 'engrave.svg', blob: engraveBlob },\n    { filename: 'score.svg', blob: scoreBlob },\n    { filename: 'readme.txt', blob: readmeBlob },\n  ]\n}) 不需要区分时两者返回同样的内容即可；忽略 intent（写成 async () => { ... }）也照常工作。",{"id":359,"title":360,"titles":361,"content":362,"level":132},"\u002Fzh\u002Fdocs\u002Fexport#返回值字段","返回值字段",[50],"字段类型说明filenamestring下载文件名，必须含扩展名（如 design.glb），且不含路径分隔符 \u002F \\blobBlob任意格式的文件内容，单文件 ≤ 100MB；MIME 取自 blob.type，为空时按文件名扩展名兜底",{"id":364,"title":365,"titles":366,"content":367,"level":132},"\u002Fzh\u002Fdocs\u002Fexport#多文件导出可选","多文件导出（可选）",[50],"handler 也可以返回一个文件数组，平台会把它们打成一个 zip 下载： atomm.lifecycle.on('export', async () => [\n  { filename: 'model.glb', blob: glbBlob },\n  { filename: 'preview.png', blob: pngBlob },\n]) 规则说明zip 文件名取数组中第一个文件的主名（model.glb → model.zip）同名文件自动去重为 name (1).ext大小上限所有文件字节总和 ≤ 100MB，超出整体失败数组长度 1直接下载该文件，不套 zipOpen in Studio支持多文件——返回的文件全部交给 Studio 打开，不限制格式；Studio 不支持的格式由 Studio 端提示。多文件导入需 xTool Studio 1.8+；单文件兼容旧版本 单文件仍用 { filename, blob }；多文件用数组。返回数组中任一项不合规（非 Blob \u002F 文件名无扩展名等）整体判为无效。",{"id":369,"title":370,"titles":371,"content":372,"level":132},"\u002Fzh\u002Fdocs\u002Fexport#怎么得到-blob","怎么得到 Blob",[50],"几乎任何导出源都能一行转成 Blob： 你手里的东西转 BlobCanvas（2D 图）canvas.toBlob(cb, 'image\u002Fpng')SVG 字符串 \u002F 文本 \u002F JSONnew Blob([str], { type: 'image\u002Fsvg+xml' })ArrayBuffer \u002F 3D 网格（glb\u002Fstl…）new Blob([buffer], { type: 'model\u002Fgltf-binary' })已有 data URL \u002F 远程 URLawait (await fetch(x)).blob()\u003Cinput type=file> 的 File直接用（File 本就是 Blob） 导出数据统一用 Blob 承载，支持任意格式——Blob 能原样保存任意文件类型的二进制内容。filename 必须带扩展名且不含路径分隔符；文件超过 100MB 或返回值不是合法 { filename, blob } 时，平台会判为无效、导出失败并提示。 下载支持任意格式落盘。「Open in Studio」会把文件交给 xTool Studio 打开——平台不预判格式，Studio 不支持的格式会由 Studio 端自行提示。 导出按钮对所有生成器开放（在应用里放 data-atomm-export-button 即出现）。用户点击导出时若你未注册 export hook，平台会得到「应用未提供下载方法」——提交审核会因此被驳回。凡是支持导出的生成器都必须注册该 hook，可在本地预览中点「导出」自测是否正常产出文件。 html pre.shiki code .sZEs4, html code.shiki .sZEs4{--shiki-default:#E6EDF3}html pre.shiki code .sPWt5, html code.shiki .sPWt5{--shiki-default:#7EE787}html pre.shiki code .sFSAA, html code.shiki .sFSAA{--shiki-default:#79C0FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sQhOw, html code.shiki .sQhOw{--shiki-default:#FFA657}html pre.shiki code .sH3jZ, html code.shiki .sH3jZ{--shiki-default:#8B949E}html pre.shiki code .suJrU, html code.shiki .suJrU{--shiki-default:#FF7B72}html pre.shiki code .sc3cj, html code.shiki .sc3cj{--shiki-default:#D2A8FF}html pre.shiki code .s9uIt, html code.shiki .s9uIt{--shiki-default:#A5D6FF}",{"id":53,"title":54,"titles":374,"content":375,"level":122},[],"平台按颜色识别加工意图：切割用 #FE0002 描边，雕刻用 #2366FF 描边，填充雕刻用 #2366FF 填充。 其余颜色照常导出，只是加工类型要由用户自己指派。",{"id":377,"title":54,"titles":378,"content":379,"level":122},"\u002Fzh\u002Fdocs\u002Fexport\u002Fsvg-color-spec#svg-导出颜色规范",[],"平台按颜色识别加工意图：切割用 #FE0002 描边，雕刻用 #2366FF 描边，填充雕刻用 #2366FF 填充。 其余颜色照常导出，只是加工类型要由用户自己指派。 你画的颜色平台只读不改：颜色只用来识别，写进文件的只有加工类型 data-processing-type。",{"id":381,"title":382,"titles":383,"content":384,"level":132},"\u002Fzh\u002Fdocs\u002Fexport\u002Fsvg-color-spec#两种颜色五个分组","两种颜色，五个分组",[54],"分组怎么画Red line 切割线stroke= Blue line 雕刻线stroke= Fill vector 填充矢量fill= Bitmap 位图\u003Cimage> 元素，与颜色无关Other vector 其他矢量其余可见矢量，按各自的颜色分成多行 同一分组里有多少个元素都只占一行，用户配置一次。用户在下拉里选「Open in Studio」后看到的就是这个弹窗（选「下载」不弹窗，直接落盘）。 机型、加工模式和材料在最上面，它们决定这台机器支持哪些加工类型。每行右侧的下拉就是这一组的加工类型。",{"id":386,"title":387,"titles":388,"content":389,"level":132},"\u002Fzh\u002Fdocs\u002Fexport\u002Fsvg-color-spec#一个完整的例子","一个完整的例子",[54],"你的 export 钩子返回这样一份 SVG： \u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"200\" height=\"100\">\n  \u003Crect x=\"4\" y=\"4\" width=\"192\" height=\"92\" fill=\"none\" stroke=\"#FE0002\"\u002F>\n  \u003Cpath d=\"M20 30h160\" fill=\"none\" stroke=\"#2366FF\"\u002F>\n  \u003Ccircle cx=\"100\" cy=\"65\" r=\"20\" fill=\"#2366FF\"\u002F>\n\u003C\u002Fsvg> 只用到前三组，所以弹窗里是三行——切割线、雕刻线、填充矢量各一个元素。用户各选一次加工类型，确认后你的文件变成： \u003Crect   … stroke=\"#FE0002\" data-processing-type=\"VECTOR_CUTTING\"\u002F>\n\u003Cpath   … stroke=\"#2366FF\" data-processing-type=\"VECTOR_ENGRAVING\"\u002F>\n\u003Ccircle … fill=\"#2366FF\"   data-processing-type=\"FILL_VECTOR_ENGRAVING\"\u002F> 属性逐个写在真正要加工的元素上，不挂在 \u003Cg> 上靠继承。文件里只有加工类型，不含功率、速度这类参数——用户在弹窗里选的材料，由 Studio 应用对应的推荐参数。 到这里就能跑通了。下面三节是细则，导出结果和预期对不上时再查。",{"id":391,"title":392,"titles":393,"content":394,"level":132},"\u002Fzh\u002Fdocs\u002Fexport\u002Fsvg-color-spec#颜色没被识别","颜色没被识别",[54],"先确认写法在支持范围内。每个元素按 inline style → \u003Cstyle> 规则 → 元素属性 的顺序取值，三处都没有才向上继承。这个顺序对齐 SVG 2：presentation attribute 的特异性为 0，任何 CSS 规则都压过它。 下面每一种都会被识别为切割线： \u003Cpath stroke=\"#FE0002\" fill=\"none\"\u002F>\n\u003Cpath stroke=\"#fe0002\" fill=\"none\"\u002F>\n\u003Cpath style=\"stroke:#FE0002\" fill=\"none\"\u002F>\n\u003Cg stroke=\"rgb(254, 0, 2)\" fill=\"none\">\u003Cpath\u002F>\u003C\u002Fg>\n\n\u003Cstyle>.st0{fill:none;stroke:#FE0002}\u003C\u002Fstyle>\n\u003Cpath class=\"st0\"\u002F>\n\n\u003Cdefs>\u003Cstyle>\u003C![CDATA[.str0{stroke:#FE0002;fill:none}]]>\u003C\u002Fstyle>\u003C\u002Fdefs>\n\u003Cpath class=\"str0\"\u002F> \u003Cstyle> 放在文档任意位置都行——\u003Cdefs> 里、甚至写在它修饰的元素之后。支持 CDATA 包裹，支持类 \u002F id \u002F 元素名 \u002F 属性 \u002F 后代选择器与逗号分组，按特异性和文档顺序排优先级。唯一的例外是 !important 不参与排序，内联写法始终优先。 颜色只认这几种写法：#RGB、#RGBA、#RRGGBB、#RRGGBBAA，以及数值形式的 rgb() \u002F rgba()。透明度不参与比对，#FE0002FF、#FE000280、rgba(254,0,2,.5) 都按 #FE0002 归组，半透明的红仍然是切割线。 读不到颜色的四种情况： 外部样式表（\u003Clink> \u002F @import）@media 等条件规则里的声明CSS 变量与 currentColor不认识的写法：具名色、hsl()、带百分号的 rgb() 这些元素不会丢，只是落进「其他矢量」，由用户自己指派加工类型。",{"id":396,"title":397,"titles":398,"content":399,"level":132},"\u002Fzh\u002Fdocs\u002Fexport\u002Fsvg-color-spec#分组和画的不一样","分组和画的不一样",[54],"「其他矢量」按颜色分行。 不符合规范的颜色不会并成一坨，每种颜色各占一行——上面弹窗末尾的绿、紫两行就是这么来的： 归组取色描边优先于填充同色不同写法（#22C55E \u002F rgb(34,197,94)）算同一行跨多个 SVG 文件的同色合并计数每行的加工类型默认为空，平台不替你猜排序：规范色四组在前，其他矢量按元素数从多到少 同时是蓝底和红边的形状会被拆成两个。 fill=\"#2366FF\" 加 stroke=\"#FE0002\" 表达的是两道工序——填充雕刻内部、切割轮廓，但协议规定一个元素只能带一个加工类型： \u003C!-- 你导出的 -->\n\u003Crect fill=\"#2366FF\" stroke=\"#FE0002\"\u002F>\n\n\u003C!-- 平台处理后 -->\n\u003Crect fill=\"#2366FF\" stroke=\"none\" data-processing-type=\"FILL_VECTOR_ENGRAVING\"\u002F>\n\u003Crect fill=\"none\"    stroke=\"#FE0002\" data-processing-type=\"VECTOR_CUTTING\"\u002F> 填充那份插在描边那份前面，保持原本「先填充后描边」的绘制顺序，观感不变。副本不带 id，marker-* 只留在描边那份。这个形状会同时出现在两组里，所以各组元素数之和会比你画的多。 只有两边都是规范色才拆。红边配绿底不拆——绿色不表达加工意图，整个形状按描边归成切割线。",{"id":401,"title":402,"titles":403,"content":404,"level":132},"\u002Fzh\u002Fdocs\u002Fexport\u002Fsvg-color-spec#图形整个没出现","图形整个没出现",[54],"参与分组的元素：path rect circle ellipse line polyline polygon text，以及位图 image。 不参与的： \u003Cdefs> \u003CclipPath> \u003Cmask> \u003Cmarker> \u003Cpattern> \u003Csymbol> 里的图形——它们只是定义，不会被画出来display:none 或 visibility:hidden 的元素，属性、inline style、\u003Cstyle> 里的类三种写法都算fill 与 stroke 都是 none 的元素 \u003Cuse> 是最容易踩的一个。 它画出来的那份图形不会出现在任何分组里，也就拿不到加工类型：\u003Cuse> 本身不是矢量图形，被引用的原件躺在 \u003Cdefs> \u002F \u003Csymbol> 里也不算数。整张图都靠 \u003Cuse> 拼的话，弹窗里一行都不会有。 更隐蔽的是原件本身也画在图上的情形。\u003Cpath id=\"p\" …\u002F> 加 \u003Cuse href=\"#p\" x=\"20\"\u002F>，原件正常成组、拿到加工类型，\u003Cuse> 复制出的那份不带——同一张图「一份被切、一份不被加工」。请直接输出实际图形。 多文件导出时同名分组跨文件合并计数，解析不了的 SVG 只跳过它自己。选「Open in Studio」时，独立的位图文件（PNG \u002F JPEG \u002F WebP \u002F GIF \u002F BMP）会被包成 SVG \u003Cimage>，因此也会出现在「位图」分组里；其余文件原样保留。选「下载」则一律原样交付，不做包装、不写属性。一个分组都产不出时——SVG 里全是上面这些不参与的元素——弹窗不显示「加工类型」那一段。 html pre.shiki code .sZEs4, html code.shiki .sZEs4{--shiki-default:#E6EDF3}html pre.shiki code .sPWt5, html code.shiki .sPWt5{--shiki-default:#7EE787}html pre.shiki code .sFSAA, html code.shiki .sFSAA{--shiki-default:#79C0FF}html pre.shiki code .s9uIt, html code.shiki .s9uIt{--shiki-default:#A5D6FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sH3jZ, html code.shiki .sH3jZ{--shiki-default:#8B949E}",{"id":57,"title":58,"titles":406,"content":407,"level":122},[],"接入能力并在本地预览验证通过后，即可在开发者后台提交上架。不再需要本地打包工具——用你自己的构建方式产出静态文件即可。",{"id":409,"title":58,"titles":410,"content":407,"level":122},"\u002Fzh\u002Fdocs\u002Fpublish#提交审核与上架",[],{"id":412,"title":413,"titles":414,"content":415,"level":132},"\u002Fzh\u002Fdocs\u002Fpublish#_1-准备产物","1. 准备产物",[58],"用你熟悉的方式构建出可直接部署的静态 Web 文件（页面已引入平台 SDK），打包成一个 .zip（不超过 50 MB）。 线上应用同样靠页面里那行 platform-sdk.js 才能接上平台能力。某些构建配置可能让正式入口页漏掉这行 script，导致「本地预览正常、上架后导出失效」。上传前请确认正式产物的入口页就是你本地验证过、且仍引入了 SDK 的那个页面。",{"id":417,"title":418,"titles":419,"content":420,"level":132},"\u002Fzh\u002Fdocs\u002Fpublish#_2-创建应用","2. 创建应用",[58],"在开发者后台点击「创建应用」，先为你的生成器取一个名称作为运行标识（小写字母开头，仅含小写字母、数字与连字符）。该名称会关联线上运行子域名与访问路径，创建后不可修改，请谨慎选择；展示给用户的卡片标题在下一步单独设置。",{"id":422,"title":423,"titles":424,"content":425,"level":132},"\u002Fzh\u002Fdocs\u002Fpublish#_3-配置展示资料","3. 配置展示资料",[58],"进入应用详情页的「应用列表详情」，填写卡片标题、简要描述与封面图，右侧可实时预览应用卡片效果。这些信息决定用户在 Creative Tools 页面看到的样子，改动同样需要走审核。 预览分 Atomm 与 Studio 两个渠道：Atomm 是 4:3 的卡片，Studio 是 1:1 的方形位。只需上传 4:3 主封面，方形位由它自动居中裁剪——切到 Studio 页签就能看到裁出来的样子，主体别贴边就不会被切掉。",{"id":427,"title":428,"titles":429,"content":430,"level":132},"\u002Fzh\u002Fdocs\u002Fpublish#_4-上传代码产物","4. 上传代码产物",[58],"切换到「代码产物」，上传打包好的 .zip（含 index.html 等静态文件）。本地实时调试请在开发阶段通过在线 DevTool 完成，详见 本地调试。",{"id":432,"title":433,"titles":434,"content":435,"level":132},"\u002Fzh\u002Fdocs\u002Fpublish#_5-提交审核","5. 提交审核",[58],"在「提交审核」中，平台会校验展示资料与代码产物是否填写完整；全部就绪后点击「提交审核」，展示资料改动与新产物会汇总为一次「待审核变更」一次性送审。审核结果（通过 \u002F 驳回 + 理由）会通过消息回流到你的后台。 提交后应用进入「审核中」，需等结果返回后才能再次提交新的改动。 通过后应用自动上架，运行地址为 ，用户在生成器聚合页即可直接打开。",{"id":61,"title":62,"titles":437,"content":199,"level":122},[],{"id":439,"title":62,"titles":440,"content":199,"level":122},"\u002Fzh\u002Fdocs\u002Ffaq#常见问题",[],{"id":442,"title":443,"titles":444,"content":445,"level":132},"\u002Fzh\u002Fdocs\u002Ffaq#需要安装命令行工具吗","需要安装命令行工具吗？",[62],"不需要。接入只靠在页面里引入一行 platform-sdk.js；调试用浏览器打开在线 DevTool 加载你的本地服务即可。",{"id":447,"title":448,"titles":449,"content":450,"level":132},"\u002Fzh\u002Fdocs\u002Ffaq#支持哪些技术栈","支持哪些技术栈？",[62],"任意。纯 HTML、Vite、Vue、React…… 只要最终能产出可部署的静态 Web 文件就行。",{"id":452,"title":453,"titles":454,"content":455,"level":132},"\u002Fzh\u002Fdocs\u002Ffaq#本地服务用什么地址为什么我的地址加载不了","本地服务用什么地址？为什么我的地址加载不了？",[62],"预览通过 ?local= 加载你的本地服务（仅支持 localhost \u002F 127.0.0.1 等回环地址）。www.atomm.com 走 https，浏览器只对 localhost 的 http 放行；非 localhost 的 http 地址会被混合内容策略拦截，请使用 localhost 或为本地服务启用 https。",{"id":457,"title":458,"titles":459,"content":460,"level":132},"\u002Fzh\u002Fdocs\u002Ffaq#平台能力不可用-面板提示整合未完成怎么办","平台能力不可用 \u002F 面板提示「整合未完成」怎么办？",[62],"说明页面没有成功加载 platform-sdk.js。检查 script 是否正确引入、本地服务是否可访问并重新加载页面。SDK 握手成功后提示会自动消除。",{"id":462,"title":463,"titles":464,"content":465,"level":132},"\u002Fzh\u002Fdocs\u002Ffaq#生成器名称运行标识能修改吗","生成器名称（运行标识）能修改吗？",[62],"不能。名称会作为运行子域名与访问路径的一部分，关联线上地址，创建后不可更改，请谨慎选择。",{"id":467,"title":468,"titles":469,"content":470,"level":132},"\u002Fzh\u002Fdocs\u002Ffaq#没接入导出能力会怎样","没接入导出能力会怎样？",[62],"导出按钮对所有生成器开放（在应用里放 data-atomm-export-button 即出现）。用户点击导出时若你未注册 export hook，会得到「应用未提供下载方法」，提交审核会因此被驳回。要支持导出就必须注册 export hook，可在本地预览中点「导出」自测是否正常产出文件。",{"id":65,"title":66,"titles":472,"content":473,"level":122},[],"让 Claude Code、Cursor、Copilot 这类 AI 工具直接读懂 atomm 平台的接口与规范，不必你逐条转述。",{"id":475,"title":66,"titles":476,"content":477,"level":122},"\u002Fzh\u002Fdocs\u002Fai\u002Fllms-txt#llmstxt",[],"让 Claude Code、Cursor、Copilot 这类 AI 工具直接读懂 atomm 平台的接口与规范，不必你逐条转述。 llms.txt 是给大语言模型准备的文档索引约定。本站提供两条路径： 路径内容体量\u002Fllms.txt全部文档的索引与链接约 750 token\u002Fllms-full.txt全部英文文档拼成的单文件全文约 3 万 token 此外，任意文档地址末尾加 .md，就能取到该页的纯 markdown，例如  与 。",{"id":479,"title":480,"titles":481,"content":482,"level":132},"\u002Fzh\u002Fdocs\u002Fai\u002Fllms-txt#给哪一个","给哪一个",[66],"agent 能联网：给 \u002Fllms.txt，让它照索引挑需要的页去取，省上下文。想一次给全：给 \u002Fllms-full.txt，省掉二十多次往返，代价是约 3 万 token 的上下文。只问某一章：直接给那一页的 .md 地址。",{"id":484,"title":485,"titles":486,"content":487,"level":132},"\u002Fzh\u002Fdocs\u002Fai\u002Fllms-txt#用法","用法",[66],"把地址贴进对话即可，例如：先读 ，然后按 atomm 的规范帮我给这个生成器接入导出功能。 设计系统与 3D 预览规范另有可直接交给 agent 的单文件版本，见 Skill 文件。",{"id":69,"title":70,"titles":489,"content":490,"level":122},[],"Skill 是一份写给编码 agent 的单文件规范：不铺垫背景，只写该怎么做、什么会被驳回。把文件交给 agent，它照着做，你不必在对话里逐条复述规范。",{"id":492,"title":70,"titles":493,"content":494,"level":122},"\u002Fzh\u002Fdocs\u002Fai\u002Fskills#skill-文件",[],"Skill 是一份写给编码 agent 的单文件规范：不铺垫背景，只写该怎么做、什么会被驳回。把文件交给 agent，它照着做，你不必在对话里逐条复述规范。 本站提供两份，都可直接下载： 文件内容什么时候给design.md设计系统：语义令牌、字体、圆角与间距、组件基线，另有给 agent 的契约与自查清单，DESIGN.md 格式agent 要写生成器界面时3d-preview-skill.mdthree.js 3D 预览规范：架构、交互、渲染质量、验收清单生成器要做 3D 预览时 对应章节的页面右上角也有下载按钮（设计规范、3D 预览）。 design.md 必须和它描述的代码一起到达 agent 手里。 它带着模板的完整地址，以及一条硬规则：动手搭页面之前先把对应模板抓下来。怎么给，取决于你的 agent 能不能抓 URL。 能抓：只给 design.md 一份就够，地址是 。它会读布局页的决策表，自己去抓需要的那份骨架版（换成它选中的布局，例如 ），然后在那个文件上做。抓不了：把 design.md 和匹配你布局的那一份骨架版一起贴进去。没有模板时，规范要求 agent 停下来向你索取，而不是自己编——这是「慢一点」和「错了」的区别。 两条路的结果一样：项目里多出两个文件。 your-project\u002F\n├── design.md                      # 规范，让 agent 先读它\n└── index.html                     # 匹配你生成器的那份骨架版的副本\n                                   #   \u003Cstyle>  令牌 + 组件类，不要重写\n                                   #   结构与类名要一致，控件接什么逻辑你自己定 抓一份模板，不是五份。 五份共用同一份样式表，骨架版的体积大半是它——全给只花上下文，不增加信息。用布局页的决策表挑出基底；后面确实要用到只有另一份才有的区块，到时再取那一份。 不要用设计稿链接或截图代替。截图只带走样子，不带数字——颜色、尺寸、间距全靠估，每次估得还不一样。这些数值模板里都有。",{"id":496,"title":497,"titles":498,"content":499,"level":132},"\u002Fzh\u002Fdocs\u002Fai\u002Fskills#交给-claude-code","交给 Claude Code",[70],"存成 SKILL.md 放进项目的 skills 目录，agent 遇到相关任务时会自行加载： .claude\u002Fskills\u002Fatomm-3d-preview\u002FSKILL.md 3D 预览那份下载下来就叫 SKILL.md，建好目录直接放进去即可。",{"id":501,"title":502,"titles":503,"content":504,"level":132},"\u002Fzh\u002Fdocs\u002Fai\u002Fskills#交给其他工具","交给其他工具",[70],"Cursor、Copilot 等：把文件放进项目（例如 docs\u002Fatomm-3d-preview.md），在对话里 @ 引用；临时用也可以把内容整段贴进对话。",{"id":506,"title":507,"titles":508,"content":509,"level":132},"\u002Fzh\u002Fdocs\u002Fai\u002Fskills#和-llmstxt-的分工","和 LLMs.txt 的分工",[70],"LLMs.txt 是文档全集的索引，解决「agent 不知道 atomm 有什么能力」。Skill 是单件事的规范，解决「agent 知道了，但做出来不合格」。 做 3D 预览时两份都给：先让它按 llms.txt 摸清平台，再让它按 skill 落地。",{"id":73,"title":18,"titles":511,"content":512,"level":122},[],"这套规范有两件东西，一起交给编码 agent：",{"id":514,"title":18,"titles":515,"content":516,"level":122},"\u002Fzh\u002Fdocs\u002Fdesign\u002Foverview#总览",[],"这套规范有两件东西，一起交给编码 agent： 文件是什么怎么用design.md标准 DESIGN.md 格式的单文件规范：令牌、组件规格、设计意图，开头是给 agent 的契约，末尾是自查清单放进项目，让 agent 先读它五类布局模板对应五种生成器布局，每种两份 HTML：骨架版（结构 + 样式表）与可运行版（再加上行为）搭页面结构时读骨架版并复制进项目；想看示例跑起来再取可运行版 下面这些页面给人读，同一套令牌、同一套组件，配上可以摸的演示。改动只发生在一个地方：design.md 是唯一真源，文档页和模板跟着它走。",{"id":518,"title":519,"titles":520,"content":521,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Foverview#为什么是模板不是描述","为什么是模板，不是描述",[18],"以前只有一份文字规范。同一份文件交给不同的 agent，产出时好时坏——因为每个 agent 都在从文字重建一遍 28px 的输入框、属性卡片的标题行、下拉的键盘契约，而重建必然有出入。现在 agent 的任务变成复用：模板里已经有正确的骨架、组件和行为，剩下的是内容，以及你的生成器要额外加的东西。产出因此稳定下来。 模板刻意做成一个文件：没有第二份要一起拷的样式表或脚本，没有相对路径要对，双击就能在浏览器里跑起来。",{"id":523,"title":524,"titles":525,"content":526,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Foverview#骨架皮肤与功能","骨架、皮肤与功能",[18],"规范分三层，design.md 的契约一节把它们分得很清楚： 骨架——必须一致：五种布局与各区宽度、tab \u002F 加工 Tips \u002F 缩放 \u002F 导出的位置、控件高度（24 \u002F 28 \u002F 32 \u002F 40）、圆角（4 \u002F 6 \u002F 8 \u002F 12）、封闭的状态集、每个控件欠的键盘与读屏契约、全部平台约束。这些都以渲染出来的样子为准——用什么框架、什么标签、类名叫什么，是你的事。皮肤——默认值，可替换：全部颜色取值和字号。这里发的取值来自平台自己的令牌导出，所以与平台界面同源；你可以有自己的中性色和强调色，前提是保持浅色、承载含义的文本对比度 4.5:1 以上、控件状态肉眼可辨，并且只改令牌的值，不改令牌的名。功能——你自己的：生成器到底做什么、开放哪些参数、Generate 产出什么、缩放到什么范围、历史里存什么，规范一概不管。可运行版的模板把这些各答了一种，好让示例能跑；那些答案是演示，不是要求。 审核查两样：骨架——上面列的布局、宽度、控件尺寸与行为——和平台约束：导出必须走平台按钮、生成器只能用平台分配的区域、导出物 1:1 实际尺寸。皮肤是建议。",{"id":528,"title":529,"titles":530,"content":531,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Foverview#工作台不是店面","工作台，不是店面",[18],"生成器的参照物是专业创作工具的属性面板：安静的外框、足够工作的密度、绝不装饰。使用者会在一个下午里把同一个生成器跑二十遍，每次只改一个值。界面的价值就是在这种重复频率下的可预期：控件永远在同一位置，数值永远清晰可读，第二十次的过渡和第一次一模一样。 尺寸宽度mm列数随机度35%样式形状圆形圆形方形六边形星形显示刻度一段真实的参数栏：bg-subtlest 的面板上立着白色属性卡片，标题行 12 \u002F 500，字段行 12 \u002F 400，灰底无边框的 28px 输入框靠末端对齐。点标题可以收起卡片，拖滑块数值会跟着变——规范讲的是控件在各个状态下长什么样，摸得到才说得清。 它不是什么：不是营销页、不是仪表盘、不是消费级应用。没有首屏高光，没有渐变，没有玻璃，没有光晕，没有插画空态，没有入场动画。那些属于必须先说服你留下的产品。这一个已经拥有你的注意力了。",{"id":75,"title":76,"titles":533,"content":534,"level":122},[],"这一章不是风格选择，是运行在 atomm 里的条件。审核查的是骨架和这一章：皮肤不照做也能上架，这一章不照做过不了审。",{"id":536,"title":76,"titles":537,"content":538,"level":122},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fplatform#平台约束",[],"这一章不是风格选择，是运行在 atomm 里的条件。审核查的是骨架和这一章：皮肤不照做也能上架，这一章不照做过不了审。 导出必须走平台按钮。 在导出位放 \u003Cdiv data-atomm-export-button>\u003C\u002Fdiv>，并注册 atomm.lifecycle.on('export', …) 钩子。没有导出钩子的提交会被自动驳回。 导出位就是模板放它的地方：有参数栏时在参数栏底部，没有参数栏时在画布的底部末端角。按钮由 SDK 渲染，换肤只能通过文档化的 --atomm-export-* 变量——模板设了宽 100%、高 32px、圆角 6px 和品牌色，正好是 button-primary 的形状。开发期间可以在该位内渲染一个兜底按钮；SDK 存在时以 SDK 为准。详见导出能力。 …属性卡片…Export参数栏底部 16px 内边距里的满宽 32px 按钮。内容滚动时它不动；用户永远不需要去找它。 把导出文件当作面向机器的。 1:1 真实尺寸、mm 单位、以 mm 为坐标的 viewBox。切割线颜色由设备映射到加工方式，不跟随界面配色，见 SVG 导出颜色规范。用于预览的参考图永不进入图纸。 顶栏属于平台——品牌、账号、积分。不要重做它、不要模仿它、不要再加一条。你自己的控件（项目名、保存、历史）放在前置栏的头部。 你渲染在平台分配的区域内。 你遮不住平台外框，也不知道视口尺寸，而层叠上下文完全是你自己的：一层覆层加一层 toast 就够了，z-index 上限是 3。",{"id":79,"title":80,"titles":540,"content":541,"level":122},[],"生成器把两个问题——画布上放什么（左）和它长什么样（右）——组合成五种布局之一。平台自己的生成器覆盖了全部五种；你的生成器从最接近的那一份起步。",{"id":543,"title":80,"titles":544,"content":541,"level":122},"\u002Fzh\u002Fdocs\u002Fdesign\u002Flayout#布局",[],{"id":546,"title":547,"titles":548,"content":549,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Flayout#五类模板","五类模板",[80],"#模板左右适用1layout-1-params.html无320 参数没有东西可挑可生成，用户只调值2layout-2-templates.html320 模板320 参数左侧只有一件事：浏览并选择模板3layout-3-generate.html320 生成，停靠320 参数从一个输入（提示词、风格、图片）出发，再调整4layout-4-floating.html360 生成，悬浮无选中风格后在对话框里配置这次生成，之后没有可调项5layout-5-multi.html64 工具 + 320 面板320 参数左侧有多件事：生成、模板、导入 在 3 和 4 之间拿不定时，问题只有一个：生成之后还要调东西吗？要，选 3。结果即终稿，选 4。 1 无左栏 + 参数栏2 模板 320 + 参数栏3 生成 320 停靠 + 参数栏4 生成 360 悬浮，无参数栏5 工具 64 + 面板 320 + 参数栏五种组合，一副骨架：左边是要挑选或要生成的东西，中间是结果，右边是要调整的参数，导出（深色小块）永远在右下。 每份模板有两种形态。骨架版（\u003Clayout>.skeleton.html）是结构加完整样式表、剥掉脚本块——搭页面结构时读这一份，文件里没有别的东西跟布局和令牌抢注意力。可运行版（同一地址去掉 .skeleton）把行为也内联进来，用来看示例跑起来；里面控件做的事是演示，不是规范。两种都是单个 HTML 文件，没有第二份要一起拷的。 里面每个控件都能用——重置、单位切换、数值字段的拖拽改值、缩放、下拉、收起、加工 Tips 的分步引导、二级页。只有导出按钮是替身，它会说明平台本来会做什么。下面可以直接操作，下载后复制进项目即可。",{"id":551,"title":552,"titles":553,"content":554,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Flayout#区域","区域",[80],"区域宽度装什么平台顶栏64平台自己画的——你不要再加一条工具栏64仅布局 5：每个左侧面板一枚图标左侧栏320，悬浮时 360可添加、可生成的东西：模板、风格、提示词、导入画布侧栏之外的全部预览，加四个覆层：视图 tab、加工 Tips、缩放，以及没有参数栏时的导出参数栏320属性卡片堆，导出固定在底部 侧栏定宽。窗口的变化全部由画布吸收：窗口变宽，参数栏不跟着变宽。 停靠区域之间没有间距也没有圆角，侧栏与画布之间只有一条 1px 的 stroke-default。悬浮栏是唯一例外：白卡片、12px 圆角、shadow-100 阴影，距画布顶、起始、底三边各 16px。 左侧栏可收起为画布左上角一枚 40px 的胶囊（侧栏标题 + 面板图标），画布随之变大。布局 5 里再次点击已选中的工具即可收起面板。 参数栏永远 320px，永远三段： 头部行——起始端单位切换，末端 Reset中间——可滚动的属性卡片堆底部——导出按钮，固定在这里，不用滚动去找 派生结果（用量、零件数、时长）标注在画布上，或放进卡片堆底部的一张卡片。它们不单开一列。",{"id":556,"title":557,"titles":558,"content":559,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Flayout#画布的四个角","画布的四个角",[80],"画布上只有四个覆层，别无其他：视图 tab 在顶部居中，加工 Tips 胶囊在顶部末端，缩放簇在底部起始端。第四个是导出，只在没有参数栏时出现在底部末端。覆层 hover 时不位移。 缩放是自由值，不是固定档位：滚轮与双指捏合连续缩放画布且锚在指针位置，拖拽画布即平移，读数是一个菜单按钮、菜单里列几档作为快捷入口，「适应画布」量画布算比例并把画板归位。菜单列哪几档、范围到哪，由你定。 前置栏可以有二级页：分区的「View all」或栏底的历史行，会把栏内内容替换成一行返回按钮加一个三列 90px 方块的栅格。画布不动，也不弹对话框；Back 是唯一的出口。 2D design3D previewExportTips100%适应画布50%75%100%200%400%Export视图切换是分段控件，不是页签条：两到三个互斥视图（2D design \u002F 3D preview \u002F Export）。超过三个，说明画布承担了太多。顶部末端那枚胶囊只打开加工 Tips 的分步引导，不做别的。",{"id":561,"title":562,"titles":563,"content":564,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Flayout#属性卡片","属性卡片",[80],"参数按对象或按功能分组进属性卡片：白底、stroke-divider 边框、四角 8px 圆角、shadow-100，在 bg-subtlest 面板上以 12px 间距堆叠。一张卡片就是一行标题加它下面的字段行。标题行是整行宽的 \u003Cbutton aria-expanded>，起始端是标题，末端是展开箭头。卡片本身是 \u003Csection>，用 aria-labelledby 指向那行标题。 参数少的卡片保持展开，参数多的可以默认收起。收起只改两样东西：卡片内容的 hidden，和箭头的旋转。没有高度动画。 字段行是 标签 ⋯ 控件，上下 8px、左右 16px 内边距，标签用 text-primary，控件靠末端对齐。取值的类型决定控件：数值用 92px 输入框，选项用 110px 下拉，范围用滑块并在旁边显示数字，开关用开关，颜色用一枚 24px 色块。 StyleShapeCircleCircleSquareHexagonStarStroke widthmmColorFill modeOption AOption BDecorationShow ticks第二张卡片默认收起：标题行仍在，箭头翻转，卡片只剩一行高。点标题展开。",{"id":566,"title":567,"titles":568,"content":569,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Flayout#流程与状态","流程与状态",[80],"布局必须让流程一目了然：挑选或输入 → 配置 → 生成 → 检视 → 导出。实时预览型生成器拖动滑块就重绘，没有「生成」这一步；给它加一个 Generate 按钮是缺陷。 预览有三个状态，三个都要设计： 空——展示示例或提示，不是一块空白矩形计算中——阶段名、数字，以及取消的入口；不是无限转圈完成——缩放、平移、对比、重新生成 错误放在引发它的控件旁边，并给出下一步。屏幕顶部的 toast 是错误被忽略的地方。",{"id":571,"title":572,"titles":573,"content":574,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Flayout#尺寸与滚动","尺寸与滚动",[80],"你的宽度是平台分配的区域，不是视口，所以用容器查询适配。960px 以下三个区域堆叠，画布在先、侧栏在后，导出按钮仍固定在底部。 侧栏在自己内部滚动，用细滚动条。不要出现横向滚动，也不要把一个滚动容器套进另一个。既要填满又要能滚的面板，flex: 1 和 min-height: 0 缺一不可。前者让它长满，后者允许它收缩到比内容矮。少了后者，flex 子项保持默认的 min-height: auto，不肯收缩，于是溢出而不滚动。模板里每个滚动容器都是两条一起写的。",{"id":83,"title":84,"titles":576,"content":577,"level":122},[],"每个可交互元素支持同一套封闭的状态集：默认、hover、按下、focus-visible、禁用、加载中、选中、错误。没有第九种；看起来需要第九种状态的控件，需要的是重新思考。",{"id":579,"title":84,"titles":580,"content":581,"level":122},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fcomponents#组件",[],"每个可交互元素支持同一套封闭的状态集：默认、hover、按下、focus-visible、禁用、加载中、选中、错误。没有第九种；看起来需要第九种状态的控件，需要的是重新思考。 高度是骨架：输入族 28，按钮、分段控件、图标按钮 32，唯一的大号主操作 40，图标底板与色块 24。下面每个组件在模板的 \u003Cstyle> 块里都有同名的类——直接套用是最省的路径，用自己的框架实现出同样的尺寸与状态也一样成立。",{"id":583,"title":584,"titles":585,"content":586,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fcomponents#按钮","按钮",[84],"ExportResetCancelDeleteExportExportGenerateTips上排是六种 32px 外壳（含禁用与加载中），下排是 40px 的大号主操作、纯图标按钮和 Tips 胶囊。 button-primary——近黑、32px，回答大多数问题。平台自己渲染的 Export 就是这个形状button-primary-large——40px 满宽，只给前置栏里唯一的主操作button-secondary——白底描边，hover 时边框加深button-ghost——透明，hover 才出底色 破坏性操作用次级外壳配 red-default 文字加二次确认，绝不用实心红。加载中保持按钮宽度、换成 spinner 并设 aria-busy。 文字是动词（Generate、Export、Reset），不是 OK 或 Submit。纯图标按钮是 8px 圆角的 32px 方块，在画布上为白色。画布顶部末端角的 Tips 按钮是系统里第二个、也是最后一个胶囊：32px、白底、stroke-default 边框、20px 灯泡加文字——它要读作「帮助」，而不是又一个操作。",{"id":588,"title":589,"titles":590,"content":591,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fcomponents#输入族灰底无边框","输入族：灰底，无边框",[84],"默认mmhover · 底色加深mm聚焦 · 白底 + 1px stroke-activemm占位符mm禁用mm错误mm宽度不能大于 300 mm。input-field：28px 高、92px 宽、bg-control 底、无边框、6px 圆角，文字 text-secondary 加等宽数字。错误消息放在字段旁边，不只在 toast 里。单位固定在字段末端，不跟在数值里：10px、text-tertiary、独立元素。这样数字在起始端成行、单位在末端成行，切换 mm 与 inch 时两者各自改写。单位写在值里会随数字变长而移动，在 RTL 双向重排里还会跑到数字前面。 数值字段的初值写在 value 里，并带 inputmode=\"decimal\"。数值字段支持横向拖拽：按住拖动改值，Shift 十倍步进，Alt 微调，位移小于 3px 算点击。用 setPointerCapture 让拖出面板不丢手势，RTL 下方向反转，并保留 Shift + ↑\u002F↓ 作为键盘等价。不要显示原生上下箭头：目标太小，语域也不对。 生成栏里的提示词框是长大了的同一族：8px 圆角、10\u002F12 内边距，底部末端一枚 text-disabled 的字数计数。",{"id":593,"title":594,"titles":595,"content":596,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fcomponents#下拉","下拉",[84],"触发器就是 110px 的输入框，末端一枚 text-tertiary 箭头，列表展开时箭头翻转。列表是浮起的表面，所以与停靠面板不同，它带描边、8px 圆角和 shadow-200。 圆形圆形方形六边形星形点触发器可以收起再展开，点选项即选中。展开时触发器变白底加 1px `stroke-active` 边框——和聚焦是同一个状态——箭头同时翻转。两个互不干扰的信号，每个都能单独读出来。 底色说的是你在哪：指针和键盘共用它，所以永远只有一行有底色。brand-default 的对勾加 500 字重说的是选了哪个，没有底色时它照样成立。上图里圆形是已选项，方形是指针所在。行高亮单独占一档 12%，因为面板上的 6% 与 8% 放到浮层白底上都会消失。 行高 32px、4px 圆角、左右 10px 内边距、12px 文字用 text-primary。列表内边距 6px，不窄于触发器，超过七行开始滚动并用细滚动条。超过三个选项，下拉胜过分段控件；不到三个，分段控件胜过下拉。",{"id":598,"title":599,"titles":600,"content":601,"level":347},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fcomponents#它欠着的那份契约","它欠着的那份契约",[84,594],"原生 \u003Cselect> 把键盘、读屏和平台选择器白送给你。给弹出层定样式，意味着这些要全部自己写一遍。模板的脚本块里已经写好了，叫 upgradeSelects：移植它，不要自己重新推导。 它按渐进增强写，脚本没跑起来时控件同样可用：HTML 里是一个真实的 \u003Cselect>，包在 \u003Cspan class=\"select-field\" data-select> 里。脚本把它隐藏、建出触发器与列表，并让原生元素继续作为取值的唯一真源——表单提交和你自己的 change 监听照旧生效。 1焦点永不离开触发器。aria-activedescendant 指向当前高亮项——把焦点移进列表，正是列表关闭时焦点被丢在 body 上的原因。2触发器 role=\"combobox\" 与 aria-expanded，列表 role=\"listbox\"，每行 role=\"option\" 与 aria-selected。3收起时：↓ ↑ Enter Space Home End 或任意字符都能打开。4展开时：↓↑ 移动，Home\u002FEnd 跳到两端，Enter 或 Space 提交，Escape 不改值关闭，Tab 关闭，外部点击关闭，首字母跳转可用。5关闭时焦点回到触发器；高亮行在列表自己的滚动盒里滚动，而不是让整页滚动。6列表挂到 body 上 position: fixed——留在原地会被滚动的参数栏裁掉——并在滚动与窗口尺寸变化时重新定位。7下方空间不够时翻到触发器上方；RTL 下对齐触发器的末端边。8窄触屏退回原生 \u003Cselect>：否则丢掉系统选择器，而 32px 的行在拇指下太小。以上每一条都实现在模板脚本块的 upgradeSelects 里。它是模板里唯一要留着的部分：其余都是演示接线，你要换成自己的状态。",{"id":603,"title":604,"titles":605,"content":606,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fcomponents#开关","开关",[84],"36 × 20 轨道 · 14px 圆钮 · 位移 16px关闭 stroke-default，开启 brand-default。轨道低于 24px 目标下限，靠字段行的 \u003Clabel for> 提供那个够大的目标——五份模板都是这么接的。轨道和圆钮是画在真实 \u003Cinput type=\"checkbox\"> 上的装饰，每一层都要 pointer-events: none 和 aria-hidden。",{"id":608,"title":609,"titles":610,"content":611,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fcomponents#滑块","滑块",[84],"Corner radius6 mmDensity40%禁用40%绝不单独出现：当前值始终以数字显示在行末，需要精度时再配一个输入框。3px 轨道用 track-off，已填充段用 brand-default，12px 圆钮白心加 2px 品牌色环。步长与范围来自模型的真实约束，被悄悄截断的值是用户看不见的 bug。",{"id":613,"title":614,"titles":615,"content":616,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fcomponents#分段控件","分段控件",[84],"2D design3D previewmminchOption AOption BOption C32px 的 bg-hover 轨道、2px 内边距、6px 圆角；选项 12-medium text-secondary，选中项白底、4px 圆角、shadow-100；相邻的未选中项之间有一条 1px stroke-divider 分隔线。容器 role=\"group\" 带名字，每个选项是 \u003Cbutton aria-pressed>。用于两到三个互斥视图；超过三项改用下拉。它不是页签条，不承担页面导航。",{"id":618,"title":619,"titles":620,"content":621,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fcomponents#缩略图与工具栏","缩略图与工具栏",[84],"Classic circleSquare frameMinimalAntique goldphoto.pngGenerateTemplates模板缩略图是 140px 白卡片：8px 圆角、stroke-divider 边框、shadow-100。内联 SVG 内缩 14px，照片铺满到边。风格块是同一张卡片做到 90px、不带阴影（SVG 内缩 10px）；圆形主体仍用方卡片，只把里面的图裁圆。选中是 2px brand-default 环，叠在卡片上画，内容不会因选中而缩放；hover 是 stroke-default。工具栏 64px 宽，配 40px 底板。选中的工具是 brand-default 底、白图标。",{"id":623,"title":624,"titles":625,"content":626,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fcomponents#由平台供数的两块","由平台供数的两块",[84],"有两块界面的数据来自平台而不是你的生成器：预览所渲染的材质、用户的历史记录。模板把它们做成带占位数据的可用 UI，让它们从第一天就在正确的位置上；能力对你开放后再接数据。 SurfaceExportMy historyLoad moreSurface 是卡片堆底部的一张属性卡片：57px 方块一行四个，选中的那个在方块外 2px 处有一圈 2px 的 text-primary 描边。它只改变预览的渲染，绝不改变导出物。历史是前置栏里的二级页：一行返回、一行三个的 90px 方块、下面 Load more。方块沿用缩略图规则：内联 SVG 内缩 10px，照片铺满。",{"id":628,"title":629,"titles":630,"content":631,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fcomponents#对话框","对话框",[84],"Fabrication Tips1 \u002F 3插图Real-world unitsAll dimensions are physical (mm \u002F inch) and match the exported file 1:1 — what you set is what gets cut.BackNext原生 \u003Cdialog> 配 showModal()：480px 宽、12px 圆角、shadow-300、遮罩 bg-overlay（70% 黑）。视口不够高时正文内部滚动，头部与底部不动。头部 16\u002F24：标题用 16-semibold，旁边一枚 20px text-tertiary 的 info 图标，末端 24px 关闭按钮，下有发丝线。往下是可选的媒体带（bg-control），再往下是底部：起始端分页点，末端按钮。放进对话框的大多数东西其实不该在里面：错误去控件旁，进度去预览区，设置去参数栏。加工 Tips 是对话框的标准用例。 Antique bronze1:1Transparent backgroundReference imageClick or drag an image hereYour photo, logo or artwork becomes the coin face · max 10MBDescribe your design (optional)CancelGenerate生成对话框（布局 4）。深色 bg-inverse 头部复述这次选择：66px 缩略图、用 16-semibold 写的名字，以及每条已定死的事实一枚 tag。正文摞的是表单字段——这是全套规范里唯一让 form-label 站在控件上方而不是旁边的地方，因为 480px 的对话框放得下整宽的拖放区，320px 的侧栏放不下。点一个风格块就打开它，所以块本身绝不发起调用。",{"id":633,"title":634,"titles":635,"content":636,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fcomponents#提示条链接说明文字","提示条、链接、说明文字",[84],"提示条把浅色放在消息后面、实色放在图标上，文字用 text-primary，紧挨它描述的对象（见色彩）。链接用 blue-default 并保留下划线——颜色本身不是链接的暗示。字段下的说明文字是 text-tertiary 的 12-regular，解释一个约束而不是复述标签。 要吝啬。 能放进单位后缀的东西不要写成一句话——20 % 胜过「单块边长的百分比」。结果面板里读得到的东西不要在参数栏重复。理想的终态是参数栏里只剩两三条真正起约束作用的说明。",{"id":87,"title":88,"titles":638,"content":639,"level":122},[],"调色板分两层。原色持有原始取值，是唯一出现 hex 的地方；语义令牌引用原色并说明一个颜色是干什么用的，组件只引用语义层。换肤因此是改十几行，而不是改每个组件。",{"id":641,"title":88,"titles":642,"content":643,"level":122},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fcolor#色彩",[],"调色板分两层。原色持有原始取值，是唯一出现 hex 的地方；语义令牌引用原色并说明一个颜色是干什么用的，组件只引用语义层。换肤因此是改十几行，而不是改每个组件。 整个颜色层都是皮肤：这里的取值是平台默认值，五份模板就带着它们出厂；生成器可以整套替换。不变的是结构——每个令牌扮演的角色，和每个角色必须达到的对比度。",{"id":645,"title":646,"titles":647,"content":648,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fcolor#中性色阶","中性色阶",[88],"neutral-0#ffffff卡片、白色控件neutral-25#fcfcfd停靠面板neutral-50#f6f6f7色块下的底板neutral-100#eeeff1控件底色neutral-200#e7e8ea地面、禁用底色neutral-300#d6d8db描边、关闭态轨道neutral-400#b9bbc0hover 描边、禁用文字neutral-500#85878b三级文本 3.60:1neutral-800#3d3e42二级文本 10.68:1neutral-900#292a2d品牌色neutral-950#171719一级文本 17.90:1十一档近中性灰，点击复制 hex。令牌导出的灰阶共十四档——600、700、1000 属于平台自己的编辑器外框（网格线、投影基色），生成器区域用不到它们。最浅的三档（0 \u002F 25 \u002F 100）承担卡片、面板、控件底色；最深的三档（800 \u002F 900 \u002F 950）承担二级文本、品牌色与一级文本。",{"id":650,"title":651,"titles":652,"content":653,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fcolor#一个中性工作台加一个深色重音","一个中性工作台加一个深色重音",[88],"面板是 bg-subtlest，面板上的卡片是纯白，预览所立的地面是 bg-editor——比面板深三档。地面是房间的地板，面板是立在上面的家具，预览读作放在地板上的物件。控件另取第三种灰 bg-control，比地面浅一档——它是白色表面上的一处凹陷，不是地板本身。 bg-editorbg-defaultbg-subtlest三个表面只差一两档，却把「地面 \u002F 面板 \u002F 卡片」分得清楚。停靠区域之间没有阴影，只有色调和一条 1px 的 stroke-default。 brand-default 是近黑（neutral-900）。它承担主按钮、滑块已填充段、开启状态的开关、工具栏中被选中的工具，以及缩略图的选中描边。hover 提亮一档到 neutral-800，按下压到最深的 neutral-950——按下永远比静止态更深。 emphasize-default（atomm-600）是平台的红，中性外框唯一的例外：每屏最多一个强调操作，只给承担代价或后果的那一个。平台自己的生成器把 Generate 留在 brand-default 上，所以五份模板都不带强调按钮。",{"id":655,"title":656,"titles":657,"content":658,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fcolor#文本四档","文本四档",[88],"text-primary字段标签、卡片标题、取值、标题17.90:1text-secondary输入框与下拉内的文字、白底按钮文字、缩略图标签10.68:1text-tertiary说明文字、「查看全部」、未选中的工具栏项3.60:1text-disabled禁用文字、字数计数1.92:1前两档在系统里的每个表面上都过 4.5:1。text-tertiary 不过——白底 3.60:1，控件底色上 3.13:1。由此推出它的用法：三级文本只装看漏了也没关系的字——说明行、占位符、未选中的标签。绝不装取值，绝不装消息，绝不装决策所依赖的标签。禁用档则本就不该被读。",{"id":660,"title":661,"titles":662,"content":663,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fcolor#描边三档","描边三档",[88],"stroke-divider · 8% 墨色属性卡片的边框、卡片标题下的线、悬浮面板的边缘。发丝线：只分隔，不勾边。stroke-default · neutral-300停靠侧栏的边、次级按钮、hover 时的缩略图、胶囊形的加工 Tips 按钮。stroke-strong · neutral-400所有带 stroke-default 边框之物的 hover 档。输入框静止时没有边框：灰底本身就是可输入的暗示。边框只在聚焦时出现，且是 stroke-active。",{"id":665,"title":666,"titles":667,"content":668,"level":347},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fcolor#三档中性填充","三档中性填充",[88,661],"它们看起来很近，但不可互换： bg-hover · 6%bg-active · 8%bg-highlight · 12%6% 是面板上的 hover 档，8% 是面板上的按下或选中态，12% 是浮起列表里的行高亮。前两档放到浮层的白底上只有 1.10–1.14:1，等于看不见，所以浮起的行有自己的一档——看不见的菜单行等于不能用的菜单行。",{"id":670,"title":671,"titles":672,"content":673,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fcolor#状态","状态",[88],"外框是中性的：需要重量的东西由 brand-default 承担，emphasize-default 则被限量到每屏一次。 参数已按最新模型约束重新计算。文件已导出，尺寸 200 × 120 mm。线宽 0.1 mm 在屏幕上就是一根头发丝，这是正确的。宽度不能大于 300 mm。状态色成对工作：浅色是底，实色是图标，文字始终是 text-primary（在每种浅底上都有 15–17:1）。彩色底上放彩色字是状态提示里最常见的无障碍错误——在这套取值下会直接失败，green-default 与 warning-default 落在自己浅底上只有 2.6–2.7:1。stroke-surround 是 4.76:1 的实心蓝，常常是键盘在哪的唯一线索，自己过 3:1 还有余量。",{"id":675,"title":676,"titles":677,"content":678,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fcolor#换肤","换肤",[88],"如果替换调色板： 保持浅色——地面比面板深，绝不做暗色模式。把每档文本在它所处的每个表面上重算一遍，承载含义的那两档（text-primary、text-secondary）保持 4.5:1 以上。让 stroke-divider \u002F stroke-default \u002F stroke-strong 三档彼此可辨、与底色可辨。改值，不改名。--color-brand-default 这类变量名已经发布给所有生成器，重命名是破坏性变更。",{"id":91,"title":92,"titles":680,"content":681,"level":122},[],"全程 Inter，三个字重，行高取整像素。令牌导出按字号字重给样式命名，不提供语义别名，所以下面这张表就是那层映射：哪类东西用哪个样式。导出的二十个样式里有七个出现在生成器区域内，其余属于平台自己的营销与落地页。",{"id":683,"title":92,"titles":684,"content":685,"level":122},"\u002Fzh\u002Fdocs\u002Fdesign\u002Ftypography#字体",[],"全程 Inter，三个字重，行高取整像素。令牌导出按字号字重给样式命名，不提供语义别名，所以下面这张表就是那层映射：哪类东西用哪个样式。导出的二十个样式里有七个出现在生成器区域内，其余属于平台自己的营销与落地页。 样式字号承载16-semibold16 \u002F 600 \u002F 22仅对话框标题14-medium14 \u002F 500 \u002F 20按钮、侧栏标题（「Templates」「AI Generate」）12-medium12 \u002F 500 \u002F 16属性卡片标题、分段控件选项12-regular12 \u002F 400 \u002F 16字段标签、输入框内取值、下拉文字、说明文字、提示条与对话框正文11-regular11 \u002F 400 \u002F 16缩略图标签、「查看全部」10-medium10 \u002F 500 \u002F 14选中的那个工具栏文字10-regular10 \u002F 400 \u002F 14单位后缀，以及静止态的工具栏文字 16-semiboldFabrication Tips16 \u002F 600 \u002F 2214-mediumGenerate14 \u002F 500 \u002F 2012-mediumSize12 \u002F 500 \u002F 1612-regularCorner radius12 \u002F 400 \u002F 1611-regularClassic circle11 \u002F 400 \u002F 1610-mediumTemplates10 \u002F 500 \u002F 1410-regularmm10 \u002F 400 \u002F 14七个样式按真实字号各排一行，每行装的就是它负责的那类东西。七档里只有两档大于 12px，而 10–12px 的四档靠字重区分、不靠字号——这是 320px 侧栏既读得清又不会越写越高的原因。",{"id":687,"title":688,"titles":689,"content":690,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Ftypography#面板建立在-12px-上","面板建立在 12px 上",[92],"标签、取值、卡片标题共用 12px。卡片内的层级来自字重（卡片标题 500、字段标签 400）和位置（标题行在上、字段行在下），不来自字号。14px 留给两样必须隔着房间也能读到的东西：按钮和侧栏标题。 Templates label · 14 \u002F 500Size field-strong · 12 \u002F 500Width field · 12 \u002F 400mmnote · 12 \u002F 400 · text-tertiaryGenerate label · 14 \u002F 500同一张卡片里三层：卡片标题 12 \u002F 500 \u002F text-primary、字段标签 12 \u002F 400 \u002F text-primary、输入框内取值 12 \u002F 400 \u002F text-secondary。差的是字重和颜色，不是字号——面板因此不会越写越高。 把字段那一档提到 14 会让每行高 4px、一条 320px 的侧栏少放一张卡片。字段标签、取值、说明文字共用 12-regular，所以需要更大的字时是整档上移——换成 14-regular——并接受更高的行。没有办法只调其中一样，这正是它的用意。",{"id":692,"title":693,"titles":694,"content":695,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Ftypography#数字","数字",[92],"取值一律 tabular-nums：等宽数字让一列 92px 输入框里的 200、3、12 各自对齐，改值时数字不会左右跳。单位用 10-regular 档，作为独立元素待在同一个字段的末端，不另开一列。",{"id":95,"title":96,"titles":697,"content":698,"level":122},[],"令牌导出定义了九档圆角，生成器区域用其中六档，每档对应一类东西。这个配对是骨架：换皮肤不改它。（另外三档——tiny 2、xxlarge 16、xxxlarge 24——属于平台自己的表面。）",{"id":700,"title":96,"titles":701,"content":702,"level":122},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fshapes#形状",[],"令牌导出定义了九档圆角，生成器区域用其中六档，每档对应一类东西。这个配对是骨架：换皮肤不改它。（另外三档——tiny 2、xxlarge 16、xxxlarge 24——属于平台自己的表面。） rounded.none（0）——展开状态下属性卡片标题行的下两角（收起时标题行就是整张卡片，四角都圆）rounded.small（4px）——标签、分区标题里的 24px 图标底板、分段控件的选中项rounded.medium（6px）——所有控件：输入框、下拉、按钮、分段轨道、色块rounded.large（8px）——缩略图卡片、缩放簇、图标按钮、收起胶囊、40px 工具底板、属性卡片rounded.xlarge（12px）——对话框与悬浮栏 24 · sm 428 · md 632 · md 640 · lg 8卡片 · lg 8对话框 · xl 12圆角跟着高度走：28 和 32 的控件 6px，40 的大按钮 8px，浮起的容器 8 与 12，属性卡片四角 8px。配对比单个数字重要——40px 的按钮配 6px 看着没做完，配 20px 就成了另一个产品。 rounded.circle 是导出里的 100px，不是 9999px：它只在宽度小于 200px 的元素上才呈胶囊，而这里所有全圆的东西都在这个范围内——36px 的开关轨道、12px 的圆钮、68px 的 Tips 胶囊。它留给真正的圆形：开关圆钮、滑块圆钮、状态点。系统里只有两个胶囊，不会再多：开关轨道，因为圆角矩形的开关会读成复选框；Tips 按钮，它要读作「帮助」而不是又一个操作。其余按钮都是圆角矩形。 圆角是唯一的造型手段。没有缺口、没有斜切、没有装饰性边框、没有渐变描边。",{"id":98,"title":99,"titles":704,"content":705,"level":122},[],"深度先来自表面色调和发丝线，其次才是阴影。停靠区域完全没有阴影：面板（bg-subtlest）、白卡片、灰画布三个色调加 1px 线，已经把它们分开了。阴影只给真正离开平面的东西。",{"id":707,"title":99,"titles":708,"content":709,"level":122},"\u002Fzh\u002Fdocs\u002Fdesign\u002Felevation#高度与深度",[],"深度先来自表面色调和发丝线，其次才是阴影。停靠区域完全没有阴影：面板（bg-subtlest）、白卡片、灰画布三个色调加 1px 线，已经把它们分开了。阴影只给真正离开平面的东西。 令牌取值用于shadow-1000 4px 12px rgba(11,11,13,.06), 0 0 4px rgba(11,11,13,.06)悬浮栏、属性卡片、收起胶囊、缩略图卡片、分段选中项、开关圆钮、滑块圆钮shadow-2000 16px 32px rgba(11,11,13,.06), 0 2px 8px rgba(11,11,13,.06)下拉列表shadow-3000 40px 80px rgba(11,11,13,.06), 0 2px 8px rgba(11,11,13,.06)对话框 shadow-100shadow-200shadow-300只有三档，没有第四档：需要第四档，通常说明有东西本该平贴在面板上，却被抬了起来。每一档都是双层——一层带位移给出方向，一层零位移让白卡片在白面板上也有边——三档共用同一个 6% 的投影色。shadow-100 同时是最小的一档：圆钮和手柄用它，系统里没有更小的东西。阴影从不做动画，hover 时没有任何东西抬起。",{"id":711,"title":712,"titles":713,"content":714,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Felevation#焦点自成一层不参与高度体系","焦点自成一层，不参与高度体系",[99],"键盘焦点是 2px stroke-surround 外框，外扩 2px。这个外扩是承重的：它把焦点环和控件自己的底色隔开，近黑按钮上的焦点环因此和白按钮上的一样清楚。把它设成 0 正是主按钮上焦点环消失的原因。 ExportReset外扩 2pxExport外扩 0，环贴着深底几乎看不见输入族是例外：输入框和下拉以底色变白、画出 1px stroke-active 边框表示聚焦。它们本来就是一个有底色的框，再套一圈会读成两道边界。",{"id":101,"title":102,"titles":716,"content":199,"level":122},[],{"id":718,"title":102,"titles":719,"content":720,"level":122},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fmotion#动效",[],"令牌取值motion.fast150msmotion.base200msmotion.easingcubic-bezier(0.2, 0, 0.38, 1) 动效确认某事发生了变化。它从不承载含义，也从不让用户等待。 1交互反馈——hover、按下、切换、边框变色：fast。2内容过渡——侧栏收起、对话框进出：base。3只动 opacity、transform 与颜色——绝不动布局属性。GPU 正忙着渲染你的预览，动 height 或 width 会掉帧。4没有东西超过 base。没有回弹、没有过冲、没有停留。5生成进度不是动画，是数字和阶段名。6加载时没有入场动画。7prefers-reduced-motion: reduce 把所有时长归零。什么都不会丢，因为没有状态只靠动效传达。属性卡片收起只改 hidden 和箭头的 transform，没有高度动画。分段控件切换只是底色和阴影的 150ms 过渡。本页这些演示块里的每一次状态变化，用的都是上面这三个取值。",{"id":104,"title":105,"titles":722,"content":723,"level":122},[],"图标是内联描边 SVG，绝不用图标字体。",{"id":725,"title":105,"titles":726,"content":727,"level":122},"\u002Fzh\u002Fdocs\u002Fdesign\u002Ficon#图标",[],"图标是内联描边 SVG，绝不用图标字体。 CDN 上的图标字体加载失败的概率并不小（网络策略、沙箱、离线），而失败的样子很难看：纯图标按钮变成空框，或者退化成一枚风格完全不对的替代字形。图标出现得这么频繁，赌一个外部依赖不值得。 展开查看全部放大缩小适应画布收起面板重置生成模板导入关闭已选规格：viewBox=\"0 0 16 16\"（加工 Tips 的灯泡用 20）；fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round。颜色始终来自父级的文本令牌——自带颜色的图标，在下一个状态里就是错的。",{"id":729,"title":730,"titles":731,"content":732,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Ficon#点击区三档","点击区三档",[105],"24 · 下限32 · 默认40 · 工具底板纯图标控件的点击区 24 \u002F 32 \u002F 40，默认 32。24 以下没有档。 里面的图形可以更小：24px 底板与 32px 按钮都装 16px 图形。控件必须看起来比目标小时（开关轨道、滑块、11px 文字链接），保留视觉尺寸，用内边距把命中区补到 24，再用等量负外边距抵掉，排版不动。",{"id":734,"title":735,"titles":736,"content":737,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Ficon#三种图标三种义务","三种图标，三种义务",[105],"装饰性图标紧挨自己的文字标签，加 aria-hidden，免得被读两遍。可交互的纯图标按钮加 aria-label，说明动作而不是图形（「放大」，不是「加号」）。承载状态的图标需要读屏文本——颜色和形状本身什么都传达不了。 方向性图标——箭头、撤销、缩进——需要一个 RTL 可翻转的 class。时钟、对勾、播放、logo、带时间轴的图表永不翻转。能选上下箭头就选上下箭头：它们不需要翻转，属性卡片的展开箭头因此是上下而不是左右。",{"id":108,"title":109,"titles":739,"content":740,"level":122},[],"平台渲染你的生成器时，文档的 dir 跟随用户语言，而受支持的语言里有若干种从右向左书写。镜像不是可选项，也没有第二套样式表：布局要自己翻转。",{"id":742,"title":109,"titles":743,"content":744,"level":122},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fdirection#方向rtl",[],"平台渲染你的生成器时，文档的 dir 跟随用户语言，而受支持的语言里有若干种从右向左书写。镜像不是可选项，也没有第二套样式表：布局要自己翻转。 按现代写法这几乎是免费的。凡是单侧取值的地方一律用逻辑属性——margin-inline-start、padding-inline-end、inset-inline-start、text-align: start；对称的简写不用管；flex 与 grid 自己会镜像，给 RTL 加 flex-row-reverse 是把已经翻过的布局再翻回去。 WidthmmDensity40%Show ticks100%Tips切到 RTL：参数栏换到右边，字段行的标签与控件换边，滑块填充方向反转，开关圆钮走 -16px，缩放簇和加工 Tips 胶囊跟着换角。这些都是界面，都要镜像。画板不动。 画布内容永不镜像。把画板所在的层锁在 direction: ltr，里面的逻辑属性永远解析成左，画板朝向和导出文件的方向就与外面的界面无关。锁到画板为止：画布上的覆层是界面，不是产物，所以它们随一切镜像——RTL 下缩放簇和加工 Tips 胶囊属于镜像后的那一边。把整个画布区域锁死，会把从右向左阅读者的控件搁在错误的边上。",{"id":746,"title":747,"titles":748,"content":749,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Fdirection#逐项要改的地方","逐项要改的地方",[109],"滑块的 webkit 填充用 linear-gradient(to right …)，RTL 要改成 to left。拖拽改值的方向要反转：向起始端拖是增大。由 JS 定位的下拉浮层要对齐触发器的末端边，而不是照抄 left。不要用 inset-inline-start: 50% 加 translateX(-50%) 居中浮层；用 inset-inline: 0; margin-inline: auto。进度条的 transform-origin 要换边。开关圆钮从 inset-inline-start 出发，却用 translateX(16px) 位移；transform 是物理方向，RTL 需要 -16px。数字和它的单位读作一个整体。在 RTL 的段落方向里「6 mm」会被重排成「mm 6」。数值字段没事——它的单位是独立元素、钉在末端——但把两者拼起来的读数（比如滑块旁那个）要加 unicode-bidi: plaintext。任何表示「向前」的 transform 都要自己决定方向。 提交前设 dir=\"rtl\" 把整个流程走一遍。",{"id":112,"title":113,"titles":751,"content":752,"level":122},[],"底线，不是上限：",{"id":754,"title":113,"titles":755,"content":756,"level":122},"\u002Fzh\u002Fdocs\u002Fdesign\u002Faccessibility#无障碍",[],"底线，不是上限： 每个可交互目标至少 24×24px（WCAG 2.2 SC 2.5.8），模板里已经没有例外。宽度也要查，纯图标按钮是正方形的。视觉上刻意更小的——20px 的开关轨道、12px 的滑块圆钮、11px 的文字链接——模板的做法是保留视觉尺寸，用内边距把命中区撑到 24，再用等量负外边距还给版面，行高不变。它们引用的是同一个 --min-target，改一处，全部跟着走。逐对验证对比度，永不假设。 换肤后把关键配对重算一遍，包括控件底色上小字这种最差情况。text-tertiary 按设计就在 AA 线下，所以它绝不承载取值、消息，或决策所依赖的标签。键盘能到达鼠标能到达的一切，且顺序可见。对话框打开时锁住焦点，关闭时把焦点还给触发器。自造的列表框欠着原生 \u003Cselect> 白送的那份契约，见组件。[hidden] 需要恢复。 任何设了 display: flex 或 grid 的组件都会覆盖浏览器默认的 [hidden] { display: none }。在基础样式里发 [hidden] { display: none !important }——收起的面板漏出来，通常就是这个原因。含义永不只靠颜色、形状或动效。 红边框要配消息；spinner 要配文字；选中的缩略图要有描边，不只是底色。",{"id":116,"title":117,"titles":758,"content":759,"level":122},[],"前面各章的结论，压成能逐条核对的形式。第一部分是 design.md 开头给编码 agent 的契约，第二部分是它末尾的自查清单——agent 做完后应该拿这张表自己过一遍，每一条不过都改掉再提交。",{"id":761,"title":117,"titles":762,"content":759,"level":122},"\u002Fzh\u002Fdocs\u002Fdesign\u002Frules#规则清单",[],{"id":764,"title":765,"titles":766,"content":767,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Frules#十条","十条",[117],"1布局从五份模板之一起步。可以改良它，也可以跨布局组合区块——但骨架不变：侧栏定宽，画布吃掉其余空间。2参数栏 320px；参数放在属性卡片里；导出固定在参数栏底部。3视图 tab 在画布顶部居中，加工 Tips按钮在顶部末端，缩放簇在底部起始端。4控件高度只有 28（输入框、下拉）、32（按钮、分段控件、图标按钮）、40（唯一的大号主操作）。5圆角：控件 6、卡片与面板 8、对话框 12。除开关轨道和 Tips 按钮外没有胶囊。6输入框灰底无边框；聚焦时画 1px stroke-active 边框。选中态是 2px 品牌色描边，绝不只靠颜色。7面板文字 12px；只有按钮和侧栏标题是 14px。不写裸 hex，每个颜色都是 --color-* 变量。8导出走 data-atomm-export-button 和 export 钩子。不做自己的顶栏。9图标是内联描边 SVG。每个可交互目标不小于 24×24，且鼠标指针是手型。任何含义都不只靠颜色或动效传达。10没有超过 200ms 的动效，没有回弹，加载时没有入场动画。这十条覆盖了大多数驳回原因。骨架（布局与尺寸，以渲染出来的样子为准）必须一致；皮肤（颜色、字号）是默认值，可整套替换但只改值不改名；生成器做什么功能，规范不管。",{"id":769,"title":770,"titles":771,"content":772,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Frules#自查清单","自查清单",[117],"页面是五种布局之一；每个侧栏宽度是 64 \u002F 320 \u002F 360，参数栏是 320。视图 tab 在画布顶部居中，Tips 胶囊在顶部末端，缩放簇在底部起始端；导出在侧栏底部或画布底部末端角，内容滚动时不动。没有自己的顶栏和第二层覆层；z-index 不超过 3。每个控件高度是 24、28、32 或 40；输入框和下拉 28，按钮 32，唯一的大号主操作 40。每个圆角是 4、6、8、12 或正圆，只有展开状态下属性卡片标题行的下两角是直角；开关轨道和 Tips 按钮是仅有的两个胶囊。输入框静止时灰底无边框，聚焦时白底加 1px stroke-active 边框，宽 92px；下拉 110px。参数在带展开标题的属性卡片里；行是 标签 ⋯ 控件，内边距 8\u002F16。组件 CSS 里没有裸颜色；每个颜色都是 --color-* 变量，:root 里每个 hex 都在调色板中（或在你声明的换肤里）。面板文字 12px，按钮与侧栏标题 14px，缩略图标签 11px，工具栏文字 10px（或你声明的字号体系并一致应用）。选中态有 2px 品牌色描边或对勾，不只靠颜色；每个错误在其控件旁有消息。每个可交互目标至少 24×24；每个纯图标按钮有 aria-label；每个装饰性图标是 aria-hidden。每个响应点击的控件是 cursor: pointer，文本字段是光标，禁用是 not-allowed；纯展示的数值保持默认箭头。图标是内联 SVG 且 stroke: currentColor；没有加载图标字体。滑块以数字显示当前值；开关是真实 checkbox 且装饰层 pointer-events: none；下拉遵守 combobox 契约或使用原生。没有超过 200ms 的动效，加载时没有动画，prefers-reduced-motion 把所有时长归零。存在 data-atomm-export-button 且注册了 export 生命周期钩子；导出文件是 1:1 的 mm 尺寸。dir=\"rtl\" 下流程可用：侧栏、覆层、滑块填充、开关圆钮全部镜像；画布内容不镜像。960px 以下区域堆叠，画布在先，导出保持固定，没有横向滚动。",{"id":774,"title":775,"titles":776,"content":199,"level":132},"\u002Fzh\u002Fdocs\u002Fdesign\u002Frules#要与不要","要与不要",[117],{"id":778,"title":80,"titles":779,"content":780,"level":347},"\u002Fzh\u002Fdocs\u002Fdesign\u002Frules#布局",[117,775],"要选五份模板之一，并保留它的区域与宽度。要把导出放在模板放它的地方——侧栏底部，或画布底部末端。不要加顶栏、加第二层覆层、给派生结果单开一列。不要给实时预览型生成器加 Generate 按钮。",{"id":782,"title":783,"titles":784,"content":785,"level":347},"\u002Fzh\u002Fdocs\u002Fdesign\u002Frules#控件","控件",[117,775],"要把参数分组进带展开标题的属性卡片，行为 标签 ⋯ 控件。要把状态集当作封闭的；看起来需要第九种状态的控件需要的是重新思考。要在替换原生控件之前先写出键盘与读屏契约，然后逐项还上。不要给静止的输入框加边框，不要用原生上下箭头。不要把破坏性按钮填成红色；用次级外壳配 red-default 文字加二次确认。不要让装饰层夺走下方控件的指针事件。",{"id":787,"title":788,"titles":789,"content":790,"level":347},"\u002Fzh\u002Fdocs\u002Fdesign\u002Frules#颜色与字体","颜色与字体",[117,775],"要处处引用 --color-* 变量；组件里出现裸 hex 就是 bug。要让地面（bg-editor）比面板深，白卡片立在面板上。要在换肤后对每个表面重算文本档。不要在导出的档位之间自造字号；要么把字段那一档整体从 12-regular 提到 14-regular，要么不动。不要只靠颜色传达状态——选中的缩略图有描边，错误有消息。",{"id":792,"title":793,"titles":794,"content":795,"level":347},"\u002Fzh\u002Fdocs\u002Fdesign\u002Frules#工程","工程",[117,775],"要给「填满空间且能滚动」的面板同时写 flex: 1 和 min-height: 0——少任何一条就是那个溢出 bug。要把浮层挂到 body 上 position: fixed。要内联描边 SVG 图标并继承颜色。不要指望经典滚动条不占空间；用细的。不要给布局属性（width、height、inset）做动画；opacity、transform 与颜色是安全的那一组。",{"id":797,"title":42,"body":798,"description":301,"extension":875,"icon":43,"meta":876,"navigation":877,"path":41,"seo":878,"stem":879,"__hash__":880},"docs_zh\u002Fzh\u002Fdocs\u002F3d-preview\u002F06.recipes.md",{"type":799,"value":800,"toc":873},"minimark",[801,804,807],[802,803,42],"h1",{"id":42},[805,806,301],"p",{},[808,809,810,823],"table",{},[811,812,813],"thead",{},[814,815,816,820],"tr",{},[817,818,819],"th",{},"构件",[817,821,822],{},"实现方案",[824,825,826,853,865],"tbody",{},[814,827,828,832],{},[829,830,831],"td",{},"凸起标记 \u002F 文字",[829,833,834,835,839,840,843,844,847,848,852],{},"复用切割件的轮廓路径 → ",[836,837,838],"code",{},"SVGLoader"," → ",[836,841,842],{},"ExtrudeGeometry","，放置于 ",[836,845,846],{},"z = 面板厚度","。和其它零件一样挂进 content group，它会继承 group 的 Y 翻转，",[849,850,851],"strong",{},"不要","再翻一次",[814,854,855,858],{},[829,856,857],{},"薄环 \u002F 描边件",[829,859,860,861,864],{},"按 ",[836,862,863],{},"半径 ± 线宽\u002F2"," 生成内外轮廓后挤出细环；圆形采用平滑采样，多边形采用 flatten",[814,866,867,870],{},[829,868,869],{},"可翻折翻板",[829,871,872],{},"每条开放切割弧视为一块板，铰链轴取弧线两端点连线（弦）；绕该轴旋转挤出板，旋转方向取使板质心朝 +z 抬升的一侧；基板冲出对应孔，未切割芯保持实体；翻起角度在小集合内循环取值以获得自然观感",{"title":199,"searchDepth":132,"depth":132,"links":874},[],"md",{},true,{"title":42,"description":301},"zh\u002Fdocs\u002F3d-preview\u002F06.recipes","Y88qQl7jzzNioRZGY1FX4ZDcvUTWizklojLcBJIB5OM",[882,884],{"title":38,"path":37,"stem":883,"children":-1},"zh\u002Fdocs\u002F3d-preview\u002F05.dependencies",{"title":46,"path":45,"stem":885,"children":-1},"zh\u002Fdocs\u002F3d-preview\u002F07.checklist",1789616867646]