cardbuddy 0.12.0 → 0.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +11 -5
  2. package/dist/config.js +4 -4
  3. package/dist/preview-editor/assets/{index-CHuGGIR3.js → index-CjZzK7u7.js} +14 -14
  4. package/dist/preview-editor/index.html +1 -1
  5. package/dist/standalone.js +5 -5
  6. package/dist/vendored/{preview → card-editor}/api.js +1 -1
  7. package/dist/vendored/{preview → card-editor}/components/SvgSymbols.vue +1 -1
  8. package/dist/vendored/server/core/ai-chat.js +32 -5
  9. package/dist/vendored/server/core/nodeModels.js +1 -1
  10. package/dist/vendored/server/core/renderStatus.js +1 -1
  11. package/dist/vendored/skills/card-skill-trainer/SKILL.md +1 -1
  12. package/dist/vendored/skills/dsl-card-ai/SKILL.md +1 -1
  13. package/dist/vendored/skills/dsl-card-generator/references/expression_engine.md +1 -1
  14. package/dist/vendored/skills/dsl-card-generator/references/generation_pitfalls.md +1 -1
  15. package/dist/vendored/skills/dsl-card-generator/references/rules.md +1 -1
  16. package/dist/vendored/skills/dsl-card-generator/references/slices_convention.md +1 -1
  17. package/dist/vendored/skills/dsl-card-generator/scripts/lint_autofix.mjs +1 -1
  18. package/dist/vendored/skills/dsl-card-generator/scripts/lint_card.mjs +2 -2
  19. package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-BCLshIzC.css +1 -0
  20. package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-pP5kTKiI.js +88 -0
  21. package/dist/vendored/skills/dsl-card-generator/vendor/preview/standalone.html +2 -2
  22. package/dist/vendored/skills/finmall-card-sync/SKILL.md +2 -2
  23. package/package.json +1 -1
  24. package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-C9k-07y7.css +0 -1
  25. package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-oFRM4f4V.js +0 -55
  26. /package/dist/vendored/{preview → card-editor}/assets/default-image.png +0 -0
  27. /package/dist/vendored/{preview → card-editor}/assets/svg-symbols.svg +0 -0
  28. /package/dist/vendored/{preview → card-editor}/components/AbsoluteLayout.vue +0 -0
  29. /package/dist/vendored/{preview → card-editor}/components/Button.vue +0 -0
  30. /package/dist/vendored/{preview → card-editor}/components/Canvas.vue +0 -0
  31. /package/dist/vendored/{preview → card-editor}/components/CheckGroup.vue +0 -0
  32. /package/dist/vendored/{preview → card-editor}/components/Column.vue +0 -0
  33. /package/dist/vendored/{preview → card-editor}/components/CustomPlaceholder.vue +0 -0
  34. /package/dist/vendored/{preview → card-editor}/components/Divider.vue +0 -0
  35. /package/dist/vendored/{preview → card-editor}/components/GridLayout.vue +0 -0
  36. /package/dist/vendored/{preview → card-editor}/components/Icon.vue +0 -0
  37. /package/dist/vendored/{preview → card-editor}/components/Image.vue +0 -0
  38. /package/dist/vendored/{preview → card-editor}/components/Input.vue +0 -0
  39. /package/dist/vendored/{preview → card-editor}/components/ListLayout.vue +0 -0
  40. /package/dist/vendored/{preview → card-editor}/components/Map.vue +0 -0
  41. /package/dist/vendored/{preview → card-editor}/components/MultiColumn.vue +0 -0
  42. /package/dist/vendored/{preview → card-editor}/components/MultiText.vue +0 -0
  43. /package/dist/vendored/{preview → card-editor}/components/ProgressSlider.vue +0 -0
  44. /package/dist/vendored/{preview → card-editor}/components/RadioButton.vue +0 -0
  45. /package/dist/vendored/{preview → card-editor}/components/RenderTree.vue +0 -0
  46. /package/dist/vendored/{preview → card-editor}/components/Root.vue +0 -0
  47. /package/dist/vendored/{preview → card-editor}/components/Row.vue +0 -0
  48. /package/dist/vendored/{preview → card-editor}/components/SvgCode.vue +0 -0
  49. /package/dist/vendored/{preview → card-editor}/components/Text.vue +0 -0
  50. /package/dist/vendored/{preview → card-editor}/components/Video.vue +0 -0
  51. /package/dist/vendored/{preview → card-editor}/components/ViewContainer.vue +0 -0
  52. /package/dist/vendored/{preview → card-editor}/components/base.js +0 -0
  53. /package/dist/vendored/{preview → card-editor}/components/icons.js +0 -0
  54. /package/dist/vendored/{preview → card-editor}/components/index.js +0 -0
  55. /package/dist/vendored/{preview → card-editor}/meta/component-meta.json +0 -0
  56. /package/dist/vendored/{preview → card-editor}/meta/directive-meta.json +0 -0
  57. /package/dist/vendored/{preview → card-editor}/meta/style-meta.json +0 -0
  58. /package/dist/vendored/{preview → card-editor}/runtime/StyleParser.js +0 -0
  59. /package/dist/vendored/{preview → card-editor}/runtime/adapter.js +0 -0
  60. /package/dist/vendored/{preview → card-editor}/runtime/assets.js +0 -0
  61. /package/dist/vendored/{preview → card-editor}/runtime/canvas-context.js +0 -0
  62. /package/dist/vendored/{preview → card-editor}/runtime/card-runtime.js +0 -0
  63. /package/dist/vendored/{preview → card-editor}/runtime/evaluate.js +0 -0
  64. /package/dist/vendored/{preview → card-editor}/runtime/property.js +0 -0
  65. /package/dist/vendored/{preview → card-editor}/runtime/selectionOverlay.js +0 -0
  66. /package/dist/vendored/{preview → card-editor}/runtime/toPreview.js +0 -0
@@ -8,7 +8,7 @@
8
8
  <link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16-Cg6Nrosk.png" />
9
9
  <link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32-B_1744sp.png" />
10
10
  <link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon-BQcES9QK.png" />
11
- <script type="module" crossorigin src="/assets/index-CHuGGIR3.js"></script>
11
+ <script type="module" crossorigin src="/assets/index-CjZzK7u7.js"></script>
12
12
  <link rel="stylesheet" crossorigin href="/assets/index-CQScMhyg.css">
13
13
  </head>
14
14
  <body>
@@ -84,7 +84,7 @@ function resolvePreviewRoot(config) {
84
84
 
85
85
  function injectPreviewConfig(html, apiBase) {
86
86
  // 同时注入新旧两个全局名:已构建好的旧 dist/ 前端只认 __CARD_DEV_OFFLINE_BASE__,
87
- // 新前端优先读 __CARD_DEV_PREVIEW_BASE__(见 preview/api.js),升级期间两者都能起。
87
+ // 新前端优先读 __CARD_DEV_PREVIEW_BASE__(见 card-editor/api.js),升级期间两者都能起。
88
88
  const script =
89
89
  `<script>window.__CARD_DEV_PREVIEW_BASE__=${JSON.stringify(apiBase)};` +
90
90
  `window.__CARD_DEV_OFFLINE_BASE__=${JSON.stringify(apiBase)};</script>`
@@ -200,7 +200,7 @@ function openBrowser(url) {
200
200
  * 优先级(决定 SSR 端点 ssrLoadModule('/components/...') 落到哪里):
201
201
  * 1) DSL_EDITOR_SRC 显式设置 → 用该编辑器源码目录(内核开发 / 调试用);
202
202
  * 2) 包内 vendoredRuntimeDir 存在 → 用发布快照(默认、消费者场景,无需持有编辑器源码);
203
- * 3) cwd 下存在 preview/ → 仓库内开发回退(无需先 build 编辑器即可起本地服务);
203
+ * 3) cwd 下存在 card-editor/ → 仓库内开发回退(无需先 build 编辑器即可起本地服务);
204
204
  * 4) 兜底 → 仍用 vendored(不存在则由调用方捕获报错,给出明确提示)。
205
205
  *
206
206
  * 无论哪种,都把 `@` 别名指向运行时根,确保内部 `@/runtime/*`、`@/components/*` 解析一致。
@@ -213,8 +213,8 @@ function resolveRuntimeRoot(config) {
213
213
  if (fs.existsSync(config.vendoredRuntimeDir)) {
214
214
  return { root: config.vendoredRuntimeDir, aliasAt: config.vendoredRuntimeDir }
215
215
  }
216
- if (fs.existsSync(config.repoPreviewDir)) {
217
- return { root: config.repoPreviewDir, aliasAt: config.repoPreviewDir }
216
+ if (fs.existsSync(config.repoEditorDir)) {
217
+ return { root: config.repoEditorDir, aliasAt: config.repoEditorDir }
218
218
  }
219
219
  // 兜底:vendored(即便不存在,createEmbeddedVite 会抛清晰错误)
220
220
  return { root: config.vendoredRuntimeDir, aliasAt: config.vendoredRuntimeDir }
@@ -228,7 +228,7 @@ async function createEmbeddedVite(config) {
228
228
  logLevel: 'warn',
229
229
  }
230
230
  const { root, aliasAt } = resolveRuntimeRoot(config)
231
- // 若运行时根下存在 vite.config(如 DSL_EDITOR_SRC 指向完整仓库 preview),优先复用其配置,
231
+ // 若运行时根下存在 vite.config(如 DSL_EDITOR_SRC 指向完整仓库 card-editor),优先复用其配置,
232
232
  // 保证 vue 插件 + 全部别名与编辑器完全一致;否则用最小配置(vue 插件 + @ 别名)。
233
233
  const repoConfig = path.join(root, 'vite.config.mjs')
234
234
  if (fs.existsSync(repoConfig)) {
@@ -225,7 +225,7 @@ export const subscribeCodeBridge = makeSseSubscriber(['code-bridge-changed', 'co
225
225
 
226
226
  /**
227
227
  * 订阅本地服务日志流(event: service-log)。
228
- * 载荷 { lines: [{ seq, t, text }] },text 为含 ANSI 的原始终端行,由 preview/ansi.js 还原配色。
228
+ * 载荷 { lines: [{ seq, t, text }] },text 为含 ANSI 的原始终端行,由 card-editor/ansi.js 还原配色。
229
229
  *
230
230
  * 连接开销说明:每条订阅各占一个 EventSource(浏览器对同源的并发连接有限),
231
231
  * 因此服务日志采用「面板打开才订阅、关闭即退订」的懒连接策略(见 DebugPanel),
@@ -1,6 +1,6 @@
1
1
  <!--
2
2
  官方 module 1992 / 511。
3
- 从 sprite 里按 id 取图标;sprite 文件(svg-symbols.svg)由仓库直接维护在 preview/assets/。
3
+ 从 sprite 里按 id 取图标;sprite 文件(svg-symbols.svg)由仓库直接维护在 card-editor/assets/。
4
4
  官方只有 IE 才 inline 注入 sprite,现代浏览器直接用外链 `#id`,这里保持一致。
5
5
  -->
6
6
  <template>
@@ -272,13 +272,14 @@ const CARD_PATCH_TOOL = {
272
272
  type: 'function',
273
273
  function: {
274
274
  name: 'card_patch',
275
- description: '按 JSON 点路径(数组用数字段)对卡片 schema/data 做最小修改并写回;大卡必用本工具,禁止用 card_write 整包重写。路径必须先用 card_grep 拿真实路径——schema 的节点树是 view.components[].slots[].components[](不是 view.children)。路径中段不存在会直接报错(不再静默新建分支),确要新建传 create:true。仓库根下走 card_write 管线(带历史快照与校验)。',
275
+ description: '按 JSON 点路径(数组用数字段)对卡片 schema/data 做最小修改并写回;大卡必用本工具,禁止用 card_write 整包重写。路径必须先用 card_grep 拿真实路径——schema 的节点树是 view.components[].slots[].components[](不是 view.children)。路径中段不存在会直接报错(不再静默新建分支),确要新建传 create:true。patches(改/增)与 deletes(删)至少给一个,可只删不改(清理无用样式项、多余节点)。仓库根下走 card_write 管线(带历史快照与校验)。',
276
276
  parameters: {
277
277
  type: 'object',
278
278
  properties: {
279
279
  id: { type: 'string', description: '卡片 id' },
280
280
  patches: {
281
281
  type: 'array',
282
+ description: '要写入的 {path,value} 列表;只删除时可省略(此时必须给 deletes)',
282
283
  items: {
283
284
  type: 'object',
284
285
  properties: {
@@ -288,11 +289,11 @@ const CARD_PATCH_TOOL = {
288
289
  required: ['path', 'value'],
289
290
  },
290
291
  },
291
- deletes: { type: 'array', items: { type: 'string' }, description: '要删除的 JSON 点路径' },
292
+ deletes: { type: 'array', items: { type: 'string' }, description: '要删除的 JSON 点路径;可单独使用(只删不改)' },
292
293
  target: { type: 'string', enum: ['schema', 'data'], description: '默认 schema' },
293
294
  create: { type: 'boolean', description: '路径中段不存在时是否新建分支(默认 false = 响亮报错)' },
294
295
  },
295
- required: ['id', 'patches'],
296
+ required: ['id'],
296
297
  },
297
298
  },
298
299
  }
@@ -352,12 +353,38 @@ function applyByPath(doc, p, setter, { create = false } = {}) {
352
353
 
353
354
  async function cardPatch(root, args, dispatch, { allowDispatch = false } = {}) {
354
355
  const { id, patches = [], deletes = [], target = 'schema' } = args || {}
355
- if (!Array.isArray(patches) || !patches.length) throw new Error('缺少 patches')
356
+ if (!Array.isArray(patches)) throw new Error('patches 必须是数组')
357
+ if (!Array.isArray(deletes)) throw new Error('deletes 必须是数组')
358
+ // 「只删不改」是合法操作(清理无用样式项 / 多余节点);真正的错误是两个都空 —— 空补丁什么都不做,
359
+ // 却会照样走一遍 card_write 管线、平白多存一份历史快照,所以在这里响亮拒掉。
360
+ if (!patches.length && !deletes.length) throw new Error('patches 与 deletes 至少给一个(空补丁无意义)')
356
361
  const file = path.join(root, 'src', 'cards', String(id || ''), target === 'data' ? 'data.json' : 'schema.json')
357
362
  const doc = JSON.parse(fs.readFileSync(file, 'utf8'))
358
363
  const create = args.create === true
359
364
  for (const pt of patches) applyByPath(doc, pt.path, (obj, k) => (obj[k] = pt.value), { create })
360
- for (const d of deletes) applyByPath(doc, d, (obj, k) => delete obj[k], { create })
365
+ // 删数组元素必须 splice:早先用 delete obj[k],在数组上只留一个 null 空洞
366
+ // (properties 变成 [null, {...}]),补丁报「成功」却把 schema 写成稀疏数组,渲染与校验随后就崩。
367
+ // 同一父数组里删多项时按原始下标从大到小删 —— splice 会让后面的索引左移,升序删会误删邻居。
368
+ const orderedDeletes = [...deletes].sort((a, b) => {
369
+ const sa = String(a).split('.')
370
+ const sb = String(b).split('.')
371
+ const pa = sa.slice(0, -1).join('.')
372
+ const pb = sb.slice(0, -1).join('.')
373
+ if (pa !== pb) return pa < pb ? -1 : 1
374
+ const na = Number(sa[sa.length - 1])
375
+ const nb = Number(sb[sb.length - 1])
376
+ return Number.isFinite(na) && Number.isFinite(nb) ? nb - na : 0
377
+ })
378
+ for (const d of orderedDeletes)
379
+ applyByPath(
380
+ doc,
381
+ d,
382
+ (obj, k) => {
383
+ if (Array.isArray(obj)) obj.splice(Number(k), 1)
384
+ else delete obj[k]
385
+ },
386
+ { create },
387
+ )
361
388
  // 工程根:走 card_write 管线(历史快照 + validate/render_status);其它根(测试/自定义)直接落盘。
362
389
  // 仓库内运行由「root === 仓库根」判定;cardbuddy 发布态快照根在包内、永不等于工程根,
363
390
  // 故由宿主显式 allowDispatch 开启(它已把 CARD_ROOT 指到同一个工程根,dispatch 落点一致)。
@@ -9,7 +9,7 @@ import path from 'node:path'
9
9
  * 无需解析原始 DSL + component-meta。
10
10
  *
11
11
  * 与 /__render-status 一致:复用 dev server 的 vite 实例做 ssrLoadModule(HMR 友好,
12
- * 改 preview/runtime 免重启);首次请求有编译冷启动属正常。
12
+ * 改 card-editor/runtime 免重启);首次请求有编译冷启动属正常。
13
13
  */
14
14
 
15
15
  /**
@@ -74,7 +74,7 @@ export async function renderStatusFor(viteServer, id, dir) {
74
74
  }
75
75
 
76
76
  // 每次请求都走 ssrLoadModule:模块未失效时 Vite 直接返回缓存(无重编译),
77
- // 被 HMR 失效后自动返回新版 —— 兼顾性能与「改 preview/runtime 免重启」。
77
+ // 被 HMR 失效后自动返回新版 —— 兼顾性能与「改 card-editor/runtime 免重启」。
78
78
  const { default: RenderTree } = await viteServer.ssrLoadModule('/components/RenderTree.vue')
79
79
  const { default: Adapter } = await viteServer.ssrLoadModule('/runtime/adapter.js')
80
80
  const { createCardRuntime } = await viteServer.ssrLoadModule('/runtime/card-runtime.js')
@@ -110,7 +110,7 @@ DSL_CARD_SLICES_DIR=<training-run>/slices
110
110
  > **字体掩膜口径(r201 起)**:非标字体字形带的掩膜扩宽比不再硬编码,取自 `training/reports/font-metrics.json`
111
111
  > ——由 `training/tools/probe_font_metrics.mjs`(playwright `measureText` 实测设计稿字体栈的渲染宽 / 标注宽比)产出。
112
112
  > 取值顺序:逐样本比例 → 同 fontSize 档中位数 → overall 中位数 → 文件缺失回退 `1.35`。
113
- > 换字体栈、改 `preview/styles/app.css` 或改掩膜逻辑后**必须重跑探针**(`node training/tools/probe_font_metrics.mjs`),
113
+ > 换字体栈、改 `card-editor/styles/app.css` 或改掩膜逻辑后**必须重跑探针**(`node training/tools/probe_font_metrics.mjs`),
114
114
  > 否则 pixelDiff 口径漂移、轮间与锚点不可比。
115
115
 
116
116
  ### 轮级晋级门(round-level promotion gate)
@@ -61,7 +61,7 @@ dsl-card 项目的 AI 技能层。它是**编排层**:把卡片编辑任务拆
61
61
  }
62
62
  ```
63
63
 
64
- - **定位要改的字段**:递归 `components` 找到目标节点(按 `className` 或 `componentId`),再按 `itemKey` 找到字段。`itemType` 告诉你控件类型(13 种,来自 `preview/meta/component-meta.json`):
64
+ - **定位要改的字段**:递归 `components` 找到目标节点(按 `className` 或 `componentId`),再按 `itemKey` 找到字段。`itemType` 告诉你控件类型(13 种,来自 `card-editor/meta/component-meta.json`):
65
65
  - **输入类**:`Input`(文本输入)/ `InputNumber`(数字输入)/ `Textarea`(多行文本)
66
66
  - **选择类**:`Select`(下拉枚举)/ `CustomSelect`(自定义下拉)/ `TabSelect`(Tab 切换选择)/ `IconSelect`(图标选择)
67
67
  - **颜色类**:`Color`(颜色选择器)
@@ -17,7 +17,7 @@
17
17
  | "2" | 图片/文件值 | 资源路径 | 不需要 |
18
18
 
19
19
  - `itemValueFrom:"1"` 的属性**必须**带 `itemDefaultValue` 字段:渲染时先取默认值占位,表达式登记到 vars[点号路径],applyVars 时再求值;求值失败则保持占位值。缺了就渲染空。
20
- - **⚠️ 占位值取自 meta,不是 schema 里写的那个**(2026-09-09 实测):`Property` 由 `new Property(meta)` 构造,`itemDefaultValue` 来自 `preview/meta/*.json`;`onRestore` 不会用 schema 实例里的 `itemDefaultValue` 覆盖它,`onExport` 再把 meta 的值写回 schema(`property.js` 变量绑定分支注释即「值先取 meta 默认值」)。所以 **schema.json 里的 `itemDefaultValue` 只是 meta 默认值的序列化副本,作者改它不产生任何渲染兜底效果**。
20
+ - **⚠️ 占位值取自 meta,不是 schema 里写的那个**(2026-09-09 实测):`Property` 由 `new Property(meta)` 构造,`itemDefaultValue` 来自 `card-editor/meta/*.json`;`onRestore` 不会用 schema 实例里的 `itemDefaultValue` 覆盖它,`onExport` 再把 meta 的值写回 schema(`property.js` 变量绑定分支注释即「值先取 meta 默认值」)。所以 **schema.json 里的 `itemDefaultValue` 只是 meta 默认值的序列化副本,作者改它不产生任何渲染兜底效果**。
21
21
  - 实测:把装饰条 `rect.width` 绑定为 `$title.length * 17`、schema 里 `itemDefaultValue` 写 `"102"`,当 `$title` 为 null/缺失时 `evalExpr` 抛错被 applyVars 逐项 catch,渲染出的是 `width:;`(空)而非 `102px`——因为 `style-meta.json` 里 width 的 `itemDefaultValue` 是 `""`。
22
22
  - 推论:**绑定表达式不要指望 itemDefaultValue 兜底**。要么保证被引用变量一定存在(声明进 `dictionary.inputParams` 并给 `defaultValue`),要么在表达式内部自兜底(如 `($title || '').length * 17`、`$foo || 默认值`——Proxy 把未定义 `$` 变量解析成 `undefined` 而不抛错,所以 `||` 兜底可用;只有对 null/undefined 取 `.length` 这类才会抛错走 catch)。
23
23
  - 写 schema 时 `itemDefaultValue` 保持与 meta 一致(多数为空串),与 `assetAllocRadar` / `loanQuery` / `fundTypeSelect` 等导出卡的既有形态对齐。
@@ -420,7 +420,7 @@ gen-card-test 第一轮就把收支占比条写成了静态 `43%` / `57%` 的 `D
420
420
  - 验收:Playwright `getBoundingClientRect` 检查 Row 容器 `width ≈ 父级 width`;左右子节点 `right - left` 之和接近容器宽、中间留空区域明显存在。
421
421
  - **lint 触发条件(`r19-space-between-no-width` WARN,2026-09-09 收紧去误报)**:只在「宽度**不确定**且父容器**不拉伸**」时报,两种情况不报——
422
422
  1. **自身已给确定宽度**:`rect.width` 为 `100%` 或定宽 px。定宽时容器宽度可定,`space-between` 在该宽度内正常生效(如 `transferPlan` 两个 `width:"100"` 的日期胶囊并排在横向 Row 里,各自把日期文本与日历图标推到 100px 两端)。此时按铁律改成 `100%` 反而会让两个胶囊各占满父级而互相溢出。
423
- 2. **父容器纵向流且交叉轴 stretch**:`Column`/`Root`/`SingleColumn` 的 CSS 已强制 `flex-direction:column`(见 `preview/styles/components.css` 的 `.ce_column`/`.ce_root`/`.ce_singlecolumn`),`align-items` 缺省即 `stretch`,子 Row 未设宽也会被拉伸到父内容宽,`space-between` 已生效。
423
+ 2. **父容器纵向流且交叉轴 stretch**:`Column`/`Root`/`SingleColumn` 的 CSS 已强制 `flex-direction:column`(见 `card-editor/styles/components.css` 的 `.ce_column`/`.ce_root`/`.ce_singlecolumn`),`align-items` 缺省即 `stretch`,子 Row 未设宽也会被拉伸到父内容宽,`space-between` 已生效。
424
424
  只有「未设宽/auto 且父容器是横向流(Row/MultiColumn/GridLayout/ListLayout 项等)或父级 `align-items` 非 stretch」才真的收缩到内容宽——这才是本条要拦的缺陷。
425
425
  - **⚠️ 别为消 R19 WARN 盲目补 `width:"100%"`**:该节点若同时是 `flexShrink:"0"`,补完立刻撞 **R22 `r22-width100-flexshrink0`(ERROR)**;若其并列子节点又全是 `flexShrink:0`,还会撞 **R35 `r35-multi-child-all-shrink0`(ERROR)**。即「用 1 条 WARN 换 2 条 ERROR」,棘轮(error 只减不增)会直接失败。生成期按铁律显式写 `width:"100%"` 时,必须同时给该节点 `flexShrink:"1"`(R22)并让至少一个子节点可压缩(R35);存量卡若已被父级 stretch 撑满(渲染无缺陷),**不要**为了消 WARN 去补宽度。
426
426
 
@@ -80,7 +80,7 @@
80
80
  | **非 UI 变更** | `dictionary.inputParams`(增删改 / defaultValue / mocks)、绑定表达式 `itemValue`(`itemValueFrom:"1"`)的**取值逻辑**、`eventactions[].action.*.code` / `compiled`、`data.json`、事件与 JSAPI 调用、skill 文档与脚本 | §六 三层 **+ 针对性单元断言**;**不做视觉验证** |
81
81
 
82
82
  - **单元断言怎么做**(比截图更快、更准、可穷举入参):
83
- - 表达式 → 用宿主工程的表达式求值器(本项目 `preview/runtime/evaluate.js` 导出 `evalExpr(expr, scope)`)直接比对返回字符串;多组入参(number / 字符串 / 脏值 / 缺省)逐个断言。
83
+ - 表达式 → 用宿主工程的表达式求值器(本项目 `card-editor/runtime/evaluate.js` 导出 `evalExpr(expr, scope)`)直接比对返回字符串;多组入参(number / 字符串 / 脏值 / 缺省)逐个断言。
84
84
  - customAction → 抽出 `code` 与 `compiled` 两份,`new Function(src).call(fakeThis)` 跑;`fakeThis` 只需 `state` / `data` / `setData(s){this.state=s}` / `addEventListener(){}` 四个桩,断言跑完的 `state`。两份都要跑,防止只改了一份。
85
85
  - **理由**:拉浏览器、等首帧、截图比对的成本远高于收益。非 UI 变更的画面本就不该变——截图相同不产生新信息,截图不同反而说明改错了。
86
86
  - **仍按 UI 变更处理的边界**:改动的**副作用会改变布局几何**时——文案长度变化引起换行 / 挤压 / 溢出、`width:"100%"` 节点内容变长、v-show 条件改动改变可见节点集合从而改变容器高度。注意这与「绑定值算得对不对」是两件事:后者一律用单元断言判定,不靠读像素。
@@ -140,7 +140,7 @@ sketch-spec-to-card 产出的 spec 中,**slice 元素的 `name`(图层路径
140
140
  | v3 变体(CLI 直组装)生成 Image 时引用 `/slices/` | `scripts/spec_to_schema_v3.mjs`(`imageFromSlice(sliceEl, sem.slicesExports)`,由 `extractSemantics` 透传 `spec.slices`) |
141
141
  | 拷贝被引用切图到 `src/slices` | `scripts/generate_cards.mjs`(`collectSlicesToSrcSlices`,仅 referenced) |
142
142
  | 本地 `/slices/*` 静态服务(自包含) | `scripts/render_card.mjs`(临时 HTTP 中间件,直接读 `slicesDir`) |
143
- | ③ 阶段:上传切图 + 写映射 + 改写引用 | `skills/finmall-card-sync/scripts/slice_upload.mjs`(`uploadSlices`);HTTP 端点 `server/core/sliceUpload.js`(`POST /__slice-upload`);编辑器入口 `preview/App.vue`(`uploadCardSlices`);MCP `card_slice_upload` |
143
+ | ③ 阶段:上传切图 + 写映射 + 改写引用 | `skills/finmall-card-sync/scripts/slice_upload.mjs`(`uploadSlices`);HTTP 端点 `server/core/sliceUpload.js`(`POST /__slice-upload`);编辑器入口 `card-editor/App.vue`(`uploadCardSlices`);MCP `card_slice_upload` |
144
144
  | 语义名判定 + 内容 sha256 去重 + 引用改写(共享实现) | `scripts/lib/slice_dedupe.mjs`(`checkSliceName` / `buildContentIndex` / `sha256File` / `duplicatePeers` / `rewriteSliceRefs` / `collectSliceRefStrings`) |
145
145
  | 落地时同内容去重(不重复导入 + 改写引用回写) | `scripts/expand_ir.mjs`(`syncReferencedSlices`,已 export)、`scripts/iterate.mjs`(一条龙闭环内调用同一函数,支持 `--slices-dir`)、`scripts/generate_cards.mjs`(`collectSlicesToSrcSlices`) |
146
146
  | 命名 / 重复告警(lint,WARN) | `scripts/lint_card.mjs`(`r59-slice-semantic-name` / `r60-slice-duplicate-content`) |
@@ -102,7 +102,7 @@ function setFlexShrink(node, value) {
102
102
  existing.itemValue = String(value)
103
103
  return true
104
104
  }
105
- // itemId/itemKey 与 preview/meta/style-meta.json 的 flex 组保持一致(005 = flexShrink)
105
+ // itemId/itemKey 与 card-editor/meta/style-meta.json 的 flex 组保持一致(005 = flexShrink)
106
106
  children.push({ itemId: '005', itemKey: 'flexShrink', itemValueFrom: '0', itemValue: String(value) })
107
107
  return true
108
108
  }
@@ -716,7 +716,7 @@ export function lint(schema, data) {
716
716
  // 1. 自身已给确定宽度(100% 或定宽 px,如 transferPlan 两个 100px 日期胶囊并排在横向 Row 里)
717
717
  // → 容器宽度可定,space-between 在该宽度内正常生效,不是缺陷;改成 100% 反而会溢出。
718
718
  // 2. 自身未设宽但父容器是纵向流且交叉轴 stretch(Column/Root/SingleColumn 的 CSS 已强制
719
- // flex-direction:column,align-items 缺省即 stretch,见 preview/styles/components.css)
719
+ // flex-direction:column,align-items 缺省即 stretch,见 card-editor/styles/components.css)
720
720
  // → 子 Row 本就被拉伸到父内容宽,space-between 已生效,无需显式补 width:100%。
721
721
  // (补了反而撞 r22-width100-flexshrink0 / r35-multi-child-all-shrink0 两条 ERROR。)
722
722
  // 只有「未设宽/auto 且父容器不拉伸」——典型是嵌在横向 Row 里——才会收缩到内容宽、
@@ -967,7 +967,7 @@ export function lint(schema, data) {
967
967
  severity: 'error',
968
968
  code: 'unknown-style-child',
969
969
  componentId: cid,
970
- message: `${n.className} "${cid}" 的 style.${sg.itemKey} 写了不存在的键 "${ck}" —— 渲染器静默丢弃(itemId 落空)。该组合法键:${Object.keys(allowed).join(' / ')}(键名以 preview/meta/style-meta.json 原文为准,禁自造 / 缩写)`,
970
+ message: `${n.className} "${cid}" 的 style.${sg.itemKey} 写了不存在的键 "${ck}" —— 渲染器静默丢弃(itemId 落空)。该组合法键:${Object.keys(allowed).join(' / ')}(键名以 card-editor/meta/style-meta.json 原文为准,禁自造 / 缩写)`,
971
971
  })
972
972
  }
973
973
  }
@@ -0,0 +1 @@
1
+ .ce_map_view[data-v-b297e8af]{background-position:50%;background-repeat:no-repeat;background-size:100% 100%;width:100%;height:100%;min-height:160px;position:relative}.ce_map_grid[data-v-b297e8af]{width:100%;height:100%;position:absolute;inset:0}.ce_map_center[data-v-b297e8af]{pointer-events:none;flex-direction:column;align-items:center;display:flex;position:absolute;top:50%;left:50%;transform:translate(-50%,-100%)}.ce_map_pin[data-v-b297e8af]{filter:drop-shadow(0 2px 3px #00000040)}.ce_map_markname[data-v-b297e8af]{color:#333;white-space:nowrap;text-overflow:ellipsis;background:#ffffffe6;border-radius:4px;max-width:160px;margin-top:2px;padding:2px 6px;font-size:12px;line-height:1.3;overflow:hidden}.ce_multicolumn[data-v-cc3a8cee]{box-sizing:border-box;min-height:24px}.ce_divider{background-color:#00000026;width:100%;height:1px}.ce_image{aspect-ratio:16/9;display:flex}.ce_image_fill{background-position:50%;background-repeat:no-repeat;background-size:100% 100%;width:100%;height:100%}.ce_input{border:0;width:100%}.ce_input:focus{outline:none}.ce_text{text-overflow:ellipsis;box-orient:vertical;word-wrap:break-word;word-break:break-all;white-space:pre-line;text-align:left;-webkit-box-orient:vertical;width:fit-content;display:-webkit-box;display:-ms-flexbox;overflow:hidden}.ce_multitext{width:fit-content;display:flex}.ce_svgsymbols,.ce_svgcode{fill:#000;width:100%;height:100%}.ce_svgcode svg{width:100%;height:100%;fill:inherit}.ce_icon{font-size:0}.ce_button{-webkit-user-select:none;user-select:none;background-color:#0000;border:0}.ce_button.ce_button_disabled{color:#9a9a9a!important;background-color:#e6e6e6!important;border:none!important}.ce_map{position:relative}.ce_map .ce_map_view{background-position:50%;background-repeat:no-repeat;background-size:100% 100%;width:100%;height:100%}.ce_map .ce_map_view svg{width:100%;height:100%}.ce_map .ce_map_placeholder{z-index:1000;width:100%;height:100%;position:absolute;inset:0}.ce_svgcheckstyle{width:100%;height:100%}.ce_check_group .ce_check_list{flex-wrap:wrap;display:flex;overflow:hidden}.ce_check_group .ce_check_item{cursor:pointer;align-items:center;display:flex}.ce_check_group .ce_check_icon{flex:none;font-size:0}.ce_check_group .ce_check_text{text-overflow:ellipsis;white-space:nowrap;flex:auto;overflow:hidden}.ce_svgradiostyle{width:100%;height:100%}.ce_radio_button .ce_radio_item{cursor:pointer;align-items:center;display:flex}.ce_radio_button .ce_radio_icon{font-size:0}.ce_slider{align-items:center;display:flex}.ce_slider .ce_slider_rect{width:100%;position:relative}.ce_slider .ce_slider_bar{width:0;height:100%;position:absolute;top:0;left:0}.ce_slider .ce_slider_con{position:absolute;top:50%;left:0;transform:translateY(-50%)translate(-50%)}.ce_slider .ce_slider_block{background-repeat:no-repeat;background-size:100% 100%}.ce_slider .ce_hover,.ce_slider .ce_dragging{transform:scale(1.2)}.ce_video{position:relative}.ce_video .ce_video_elem{width:100%;height:100%}.ce_video .ce_video_poster{cursor:pointer;width:100%;height:100%;position:absolute;top:0;left:0}.ce_viewcontainer{width:100%}.ce_root{color:#000;flex-direction:column;line-height:1.5;display:flex;position:relative;overflow:hidden}.ce_row{display:flex}.ce_column{flex-direction:column;max-width:100%;display:flex}.ce_singlecolumn{flex-direction:column;width:100%;display:flex}.ce_multicolumn{justify-content:space-between;width:100%;display:flex}.ce_listlayout{width:100%}.ce_listlayout .ce_listlayout_con{box-sizing:border-box;flex-direction:column;width:100%;display:flex}.ce_listlayout .ce_listlayout_item{width:100%;display:flex}.ce_listlayout .ce_listlayout_horizontal{flex-flow:row;width:100%;height:100%;display:flex;overflow:auto hidden}.ce_listlayout .ce_listlayout_horizontal::-webkit-scrollbar{display:none}.ce_listlayout .ce_listlayout_horizontal .ce_listlayout_item{width:auto;height:100%;display:flex}.ce_gridlayout{flex-wrap:wrap;width:100%;display:flex}.ce_gridlayout .ce_gridlayout_item{width:100%;display:flex}.ce_absolutelayout{flex-direction:column;display:flex;position:relative}.ce_absolutelayout>*{z-index:1;position:absolute}.ce_absolutelayout_bottomright>*{z-index:1;position:static}:root{--bg-app:#0e0f13;--bg-bar:#15171c;--bg-panel:#181b21;--bg-stage:#0b0c0f;--bg-input:#1b1e25;--bg-hover:#232733;--bg-active:#2b2f3d;--bg-pop:#1c1f26f5;--bg-soft:#0f1219;--border:#2a2e38;--border-strong:#353b48;--text:#e8eaf0;--text-dim:#949aab;--text-faint:#626a7d;--accent:#6b83f7;--accent-2:#5a6ff0;--accent-soft:#6b83f724;--accent-border:#6b83f773;--accent-hover:#8195f9;--danger:#f87171;--danger-soft:#f8717124;--danger-hover:#ef4444;--warn:#fbbf24;--warn-soft:#fbbf2424;--warn-border:#fbbf2466;--warn-hover:#f59e0b;--on-warn:#0f1115;--ok:#34d399;--ok-soft:#34d39929;--ok-border:#34d39973;--ok-strong:#34d399;--ok-solid:#17734a;--on-ok:#fff;--info:#6b83f7;--info-soft:#6b83f724;--info-border:#6b83f773;--on-accent:#0f1115;--stream-shine:#fff;--radius:8px;--radius-sm:6px;--shadow-pop:#0000008c;--shadow-card:#00000080;--shadow-drawer:#00000073;--shadow-toast:#00000047;--scrim:#0000008c;--scrim-soft:#00000073;--scrim-faint:#0006;--scrim-modal:#080a108c;--scrim-modal-blur:2px;--bar-grad:linear-gradient(180deg, #171a20, #131519);--splitter:#5b6cff;--splitter-grip:#ffffff47;--overlay-tint:#ffffff08;--overlay-tint-strong:#ffffff47;--err-item-fg:#ffb3a3;--api-item-fg:#bcd3ff;--api-item-border:#7c8cff33;--api-item-bg:#384c890f;--copy-ok:#8fe39a;--tag-ok-fg:#1a7f37;--tag-ok-bg:#1a7f3724;--tag-warn-fg:#b58105;--tag-warn-bg:#b5810529;--log-warn-fg:#e0b341;--log-warn-bg:#e0b34124;--log-warn-border:#e0b34159;--scroll-thumb:#949aab47;--scroll-thumb-hover:#949aab73;--scroll-thumb-idle:#949aab24;--jt-key:#9db4ff;--jt-string:#8fe39a;--jt-number:#f0b86e;--jt-boolean:#f08e8e;--jt-null:#626a7d;--jt-faint:#8b93a7;--jt-ring:#9db4ff40;--jt-del-fg:#f08e8e;--jt-del-bg:#f0646426;--jt-add-bg:#9db4ff1a;--st-label:#ccc;--st-id:#8b93a7;--st-badge-bg:#6b83f7;--st-badge-fg:#fff;--st-hl-bg:#6b83f747;--code-comment:#6b7280;--code-punc:#9ca3af;--code-prop:#f0883e;--code-str:#7ee787;--code-num:#79c0ff;--code-kw:#ff7b72;--code-fn:#d2a8ff;--code-var:#ffa657;--code-binding:#56d4dd;--type-string:var(--code-str);--type-number:var(--code-num);--type-boolean:var(--code-kw);--type-array:var(--code-fn);--type-object:var(--code-prop);--space-0:0;--space-1:4px;--space-2:8px;--space-3:12px;--space-4:16px;--space-5:20px;--space-6:24px;--space-7:32px;--space-8:40px;--space-9:48px;--fs-xs:11px;--lh-xs:16px;--fs-sm:12px;--lh-sm:18px;--fs-base:13px;--lh-base:20px;--fs-md:14px;--lh-md:22px;--fs-lg:15px;--lh-lg:22px;--fs-xl:16px;--lh-xl:24px;--fs-2xl:20px;--lh-2xl:28px;--radius-xs:4px;--radius-md:8px;--radius-lg:10px;--radius-xl:12px;--radius-2xl:16px;--radius-pill:999px;--ctrl-xs:24px;--ctrl-sm:28px;--ctrl-md:32px;--ctrl-lg:36px;--ctrl-xl:40px;--row-h:28px;--icon-xs:12px;--icon-sm:14px;--icon-md:16px;--icon-lg:20px;--icon-xl:24px;--stroke:1.75;--hairline:1px;--shadow-1:0 1px 2px var(--shadow-card);--shadow-2:0 4px 12px var(--shadow-card);--shadow-3:0 8px 24px var(--shadow-card);--shadow-4:0 20px 64px var(--shadow-pop);--shadow-5:-20px 0 32px var(--shadow-drawer);--z-base:1;--z-sticky:20;--z-overlay-low:50;--z-overlay:60;--z-pop:70;--z-panel:100;--z-modal:1000;--z-deviceui:1100;--z-appmodal:2000;--z-toast-app:2100}@media (resolution>=2x),(-webkit-device-pixel-ratio>=2){:root{--hairline:.5px}}:root[data-theme=light]{--bg-app:#f4f6fa;--bg-bar:#fff;--bg-panel:#fff;--bg-stage:#e9edf4;--bg-input:#f6f8fb;--bg-hover:#f1f4f9;--bg-active:#e4e9f2;--bg-pop:#fffffffa;--bg-soft:#eef2f8;--border:#e2e8f0;--border-strong:#cbd5e1;--text:#1e293b;--text-dim:#475569;--text-faint:#5f6b7d;--accent:#4f6bf2;--accent-2:#3d57d9;--accent-soft:#4f6bf21a;--accent-border:#4f6bf259;--accent-hover:#3d57d9;--danger:#ef4444;--danger-soft:#ef44441a;--danger-hover:#dc2626;--warn:#f59e0b;--warn-soft:#f59e0b1f;--warn-border:#f59e0b66;--warn-hover:#d97706;--on-warn:#0f1115;--ok:#10b981;--ok-soft:#10b9811f;--ok-border:#10b98166;--ok-strong:#047857;--ok-solid:#047857;--on-ok:#fff;--info:#4f6bf2;--info-soft:#4f6bf21a;--info-border:#4f6bf259;--on-accent:#fff;--stream-shine:#3142b8;--radius:8px;--radius-sm:6px;--shadow-pop:#0f172a24;--shadow-card:#0f172a1a;--shadow-drawer:#0f172a1f;--shadow-toast:#0f172a1a;--scrim:#0f172a66;--scrim-soft:#0f172a4d;--scrim-faint:#0f172a38;--bar-grad:linear-gradient(180deg, #fff, #f6f8fc);--splitter:#4f6bf2;--splitter-grip:#0f172a2e;--overlay-tint:#0f172a08;--overlay-tint-strong:#0f172a14;--err-item-fg:#b91c1c;--api-item-fg:#3d57d9;--api-item-border:#4f6bf238;--api-item-bg:#4f6bf20f;--copy-ok:#047857;--tag-ok-fg:#047857;--tag-ok-bg:#10b9811a;--tag-warn-fg:#b45309;--tag-warn-bg:#f59e0b1a;--log-warn-fg:#b45309;--log-warn-bg:#f59e0b1a;--log-warn-border:#f59e0b59;--scroll-thumb:#0f172a29;--scroll-thumb-hover:#0f172a47;--scroll-thumb-idle:#0f172a14;--jt-key:#953800;--jt-string:#0a3069;--jt-number:#116329;--jt-boolean:#cf222e;--jt-null:#57606a;--jt-faint:#6b7280;--jt-ring:#4f6bf247;--jt-del-fg:#cf222e;--jt-del-bg:#cf222e1f;--jt-add-bg:#4f6bf21a;--st-label:#1e293b;--st-id:#64748b;--st-badge-bg:#4f6bf2;--st-badge-fg:#fff;--st-hl-bg:#4f6bf224;--code-comment:#6e7781;--code-punc:#6e7781;--code-prop:#953800;--code-str:#0a3069;--code-num:#0550ae;--code-kw:#cf222e;--code-fn:#8250df;--code-var:#953800;--code-binding:#0d9488;--type-string:#116329;--type-boolean:#a40e26;--type-array:#6639ba}*{box-sizing:border-box;outline:none}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;color:inherit;margin:0}html,body{background:var(--bg-app);height:100%;color:var(--text);-webkit-font-smoothing:antialiased;margin:0;font-family:PingFang SC,-apple-system,BlinkMacSystemFont,Segoe UI,Microsoft YaHei,sans-serif}#app{height:100vh;overflow:hidden}@supports ((-moz-appearance:none)){*{scrollbar-width:thin;scrollbar-color:var(--scroll-thumb) transparent}}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-thumb{background:var(--scroll-thumb-idle);border-radius:8px;transition:background-color .2s}:has(:hover)::-webkit-scrollbar-thumb{background:var(--scroll-thumb)}.st-expr:hover .st-expr__editor::-webkit-scrollbar-thumb{background:var(--scroll-thumb)}.st-code:hover .st-code__editor::-webkit-scrollbar-thumb{background:var(--scroll-thumb)}.debug__scope-raw:hover .debug__raw-editor::-webkit-scrollbar-thumb{background:var(--scroll-thumb)}.dlg:hover .dlg__msg::-webkit-scrollbar-thumb{background:var(--scroll-thumb)}::-webkit-scrollbar-thumb:hover{background:var(--scroll-thumb-hover)}::-webkit-scrollbar-thumb:active{background:var(--scroll-thumb-hover)}::-webkit-scrollbar-track{background:0 0}.app{background:var(--bg-app);flex-direction:column;height:100vh;display:flex}.app__bar{background:var(--bar-grad);border-bottom:var(--hairline) solid var(--border);flex:none;align-items:center;gap:12px;height:48px;padding:0 16px;display:flex}.app__logo{color:var(--text);white-space:nowrap;align-items:center;gap:8px;font-size:15px;font-weight:600;display:flex}.app__logo-icon{width:var(--icon-xl);height:var(--icon-xl);display:block}.app__logo-version{font-size:var(--fs-xs);letter-spacing:.02em;color:var(--text-faint);font-weight:500;line-height:1}.app__btn{height:var(--ctrl-md);background:var(--bg-input);color:var(--text);border:1px solid var(--border-strong);cursor:pointer;white-space:nowrap;border-radius:8px;flex:none;align-items:center;gap:6px;padding:0 12px;font-size:13px;transition:background .15s,border-color .15s,transform 50ms;display:inline-flex}.app__btn:hover:not(:disabled){background:var(--bg-hover);border-color:var(--accent-border);color:var(--accent)}.app__btn:active:not(:disabled){transform:translateY(1px)}.app__btn:disabled{opacity:.5;cursor:default}.app__btn--icon{height:var(--ctrl-md);aspect-ratio:1;justify-content:center;padding:0}.app__btn--active{background:var(--accent-soft);color:var(--accent);border-color:var(--accent-border)}.app__bar .app__btn--icon{height:var(--ctrl-sm)}.app__btn--remote{height:var(--ctrl-sm);background:var(--accent-soft);color:var(--accent);border-color:var(--accent-border);padding:0 10px;font-size:12px}.app__btn--remote:hover:not(:disabled){background:var(--accent);color:var(--on-accent);border-color:var(--accent)}.app__btn--remote.app__btn--warn{background:var(--warn-soft);color:var(--warn);border-color:var(--warn-border)}.app__btn--remote.app__btn--warn:hover:not(:disabled){background:var(--warn);color:var(--on-accent);border-color:var(--warn)}.app__remote-menu{display:inline-flex;position:relative}.app__remote-menu .app__btn--remote{gap:6px}.app__icon--chev{transition:transform .15s}.app__remote-item--save:hover{background:var(--accent-soft);color:var(--accent)}.app__remote-item--upload:hover{background:var(--warn-soft);color:var(--warn)}.app__remote-item--sync:hover{background:var(--ok-soft);color:var(--ok)}.app__op-group{display:inline-flex}.app__op-group>.app__btn{background:var(--bg-input);color:var(--text);border:1px solid var(--border);height:var(--ctrl-sm);border-radius:0;margin-left:-1px;padding:0 10px;font-size:12px}.app__op-group>.app__btn:first-child{border-top-left-radius:var(--radius-sm,6px);border-bottom-left-radius:var(--radius-sm,6px);margin-left:0}.app__op-group>.app__btn:last-child{border-top-right-radius:var(--radius-sm,6px);border-bottom-right-radius:var(--radius-sm,6px)}.app__op-group>.app__btn:hover:not(:disabled){z-index:var(--z-base);border-color:var(--border-strong);background:var(--bg-hover);position:relative}.app__op-group>.app__btn:disabled{opacity:.5;cursor:default}.app__btn--history-save.is-dirty:disabled{background:var(--bg-input);color:var(--text);border-color:var(--border-strong);box-shadow:none;opacity:.5;cursor:default}.app__btn--history-save.is-dirty:not(:disabled){background:var(--accent);color:var(--on-accent);border-color:var(--accent)}.app__btn--history-save.is-dirty:not(:disabled):hover{background:var(--accent-2);border-color:var(--accent-2)}.app__bar-actions{align-items:center;gap:12px;margin-left:auto;display:flex}@media (width<=1180px){.app__op-group>.app__btn{padding:0 8px}.app__op-group>.app__btn>span{display:none}.app__btn--remote{padding:0 8px}.app__btn--remote>span{display:none}}@media (width<=1024px){.app__bar{gap:8px;padding:0 10px}.app__bar-actions{gap:8px}.app__logo-version{display:none}.app__badge{padding:2px 6px}.app__badge-text{display:none}.csel-combo{min-width:120px}}.app__dev-svc{display:inline-flex;position:relative}.app__btn--warn{background:var(--warn-soft);color:var(--warn);border-color:var(--warn-border)}.app__btn--connected{background:var(--ok-soft);color:var(--ok);border-color:var(--ok-border)}.app__btn--connected:hover:not(:disabled){background:var(--ok-soft);color:var(--ok-strong);border-color:var(--ok)}.app__btn--reconnecting{background:var(--warn-soft);color:var(--warn);border-color:var(--warn-border)}.app__btn--disconnected{background:var(--danger-soft);color:var(--danger);border-color:var(--danger)}.app__dev-svc-mask{z-index:var(--z-overlay);position:fixed;inset:0}.app__dev-svc-panel{z-index:var(--z-pop);background:var(--bg-panel);border:1px solid var(--border-strong);border-radius:var(--radius-lg);width:300px;box-shadow:var(--shadow-3);color:var(--text);text-align:left;transform-origin:100% 0;position:absolute;top:calc(100% + 8px);right:0;overflow:hidden}.app__dev-svc-head{border-bottom:var(--hairline) solid var(--border);justify-content:space-between;align-items:center;gap:8px;padding:7px 8px 7px 12px;display:flex}.app__dev-svc-title{color:var(--text);font-size:12px;font-weight:600}.app__dev-svc-close{width:22px;height:22px;color:var(--text-faint);cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;font-size:14px;line-height:1;transition:background .15s,color .15s;display:inline-flex}.app__dev-svc-close:before{content:"✕"}.app__dev-svc-close:hover{background:var(--bg-hover);color:var(--text)}.app__dev-svc-body{flex-direction:column;gap:12px;padding:12px;display:flex}.app__dev-svc-row{color:var(--text-dim);justify-content:space-between;align-items:center;gap:8px;font-size:11px;display:flex}.app__dev-svc-row--col{flex-direction:column;align-items:stretch;gap:6px}.app__dev-svc-row input[type=text],.app__dev-svc-row input[type=password]{width:100%;min-width:0;height:var(--ctrl-sm);background:var(--bg-input);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--text);outline:none;padding:0 10px;font-family:inherit;font-size:12px;transition:border-color .15s,box-shadow .15s}.app__dev-svc-row input[type=text]:focus,.app__dev-svc-row input[type=password]:focus{border-color:var(--accent-border);box-shadow:0 0 0 2px var(--accent-soft)}.app__dev-svc-row input::placeholder{color:var(--text-faint)}.app__dev-svc-row .switch{flex:none}.app__dev-svc-hint{border-radius:var(--radius-sm);background:var(--warn-soft);color:var(--warn);margin:0;padding:6px 8px;font-size:11px;line-height:1.55}.app__dev-svc-actions{justify-content:flex-end;gap:8px;display:flex}.app__nosvc-banner{color:var(--warn,#e0a23a);background:var(--warn-soft,#e0a23a1f);padding:8px 14px;font-size:13px}.app__nosvc-banner--disconnected{color:var(--danger,#ef4444);background:var(--danger-soft,#ef44441f)}.app__extreload-banner{color:var(--info,#0079f2);background:var(--info-soft,#0079f21f)}.theme-toggle{color:var(--text-dim);cursor:pointer;-webkit-user-select:none;user-select:none;background:0 0;border:none;align-items:center;gap:7px;padding:0;font-size:12px;transition:color .15s;display:inline-flex}.theme-toggle:hover{color:var(--text)}.theme-toggle__track{background:var(--bg-input);border:1px solid var(--border-strong);border-radius:999px;width:36px;height:20px;transition:background .18s,border-color .18s;position:relative}.theme-toggle__thumb{background:var(--text-faint);border-radius:50%;justify-content:center;align-items:center;width:16px;height:16px;transition:transform .18s,background .18s;display:flex;position:absolute;top:50%;left:2px;transform:translateY(-50%)}.theme-toggle__icon{width:11px;height:11px;color:var(--bg-app)}:root[data-theme=light] .theme-toggle__track{background:var(--accent-soft);border-color:var(--accent-border)}:root[data-theme=light] .theme-toggle__thumb{background:var(--accent);transform:translateY(-50%)translate(16px)}:root[data-theme=light] .theme-toggle__icon{color:var(--on-accent)}.switch{cursor:pointer;vertical-align:middle;background:0 0;border:none;margin:0;padding:0;line-height:0;display:inline-flex}.switch--disabled{cursor:not-allowed;opacity:.5}.switch__track{background:var(--bg-input);border:1px solid var(--border-strong);box-sizing:border-box;border-radius:999px;width:36px;height:20px;transition:background .18s,border-color .18s;display:inline-block;position:relative}.switch__thumb{background:var(--text-faint);border-radius:50%;width:16px;height:16px;transition:transform .18s,background .18s;position:absolute;top:50%;left:2px;transform:translateY(-50%)}.switch--on .switch__track{background:var(--accent);border-color:var(--accent)}.switch--on .switch__thumb{background:var(--on-accent);transform:translateY(-50%)translate(16px)}.app__badge{border:1px solid var(--border-strong);background:var(--bg-input);color:var(--text-dim);-webkit-user-select:none;user-select:none;white-space:nowrap;border-radius:999px;flex:none;align-items:center;gap:5px;padding:2px 9px;font-size:11px;font-weight:600;display:inline-flex}.app__badge-dot{background:var(--text-faint);border-radius:50%;flex:none;width:7px;height:7px}.app__badge--ok{color:var(--ok);border-color:var(--ok-border);background:var(--ok-soft)}.app__badge--ok .app__badge-dot{background:var(--ok)}.app__badge--error{color:var(--danger);border-color:var(--danger-soft);background:var(--danger-soft)}.app__badge--error .app__badge-dot{background:var(--danger)}.app__badge--pending{color:var(--warn);border-color:var(--warn-border);background:var(--warn-soft)}.app__badge--pending .app__badge-dot{background:var(--warn);animation:1s ease-in-out infinite app-badge-pulse}.app__badge--nopython,.app__badge--fail{color:var(--text-faint);border-color:var(--border);background:var(--bg-input)}.app__badge--nopython .app__badge-dot,.app__badge--fail .app__badge-dot{background:var(--text-faint)}@keyframes app-badge-pulse{0%,to{opacity:1}50%{opacity:.3}}.app__badge.is-celebrating{animation:.5s app-badge-pop}@keyframes app-badge-pop{0%{transform:scale(1)}35%{transform:scale(1.18)}to{transform:scale(1)}}.app__confetti{pointer-events:none;z-index:9999;position:fixed}.app__confetti-bit{background:linear-gradient(135deg, #fffffff2 0%, var(--c,#52c41a) 42%, color-mix(in srgb, var(--c,#52c41a) 62%, #000) 100%);clip-path:var(--clip,polygon(50% 0%, 0% 100%, 100% 100%));opacity:var(--o,.9);filter:drop-shadow(0 0 1px #0000002e);border-radius:0;animation:.7s ease-out forwards app-confetti-fly;position:absolute;top:0;left:0;transform:translate(-50%,-50%)}@keyframes app-confetti-fly{0%{opacity:var(--o,.9);transform:translate(calc(-50% + var(--sx,0px)), calc(-50% + var(--sy,0px))) rotate(0deg) scale(.4)}to{opacity:0;transform:translate(calc(-50% + var(--sx,0px) + var(--tx)), calc(-50% + var(--sy,0px) + var(--ty))) rotate(var(--rot)) scale(1)}}.app__icon{width:15px;height:15px;display:block}.app__icon--spin{animation:.8s linear infinite app__spin}@keyframes app__spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.app__stage{flex:auto;min-height:0;display:flex;position:relative}.workside{border-left:var(--hairline) solid var(--border);flex-direction:column;flex:auto;min-width:0;min-height:0;display:flex}.workside__bar{flex:none;order:2}.workside__panels{flex-direction:column;flex:none;order:1;display:flex}.workside__cols{flex:auto;min-width:0;min-height:0;display:flex;position:relative}.phone{background:radial-gradient(circle at 30% 20%, #7c8cff0d, transparent 60%), var(--bg-stage);flex-direction:column;flex:none;justify-content:center;align-items:center;min-height:0;display:flex;overflow:hidden}.phone__frame{flex:auto;justify-content:center;align-items:center;width:100%;min-height:0;display:flex;position:relative;overflow:hidden}.phone__scaler{flex-direction:column;width:100%;height:100%;display:flex;position:relative}.splitter{top:0;bottom:0;left:calc(var(--preview-width) - 3px);cursor:col-resize;touch-action:none;width:6px;z-index:var(--z-sticky);transition:background .15s;position:absolute}.splitter--api{left:auto;right:calc(var(--api-panel-width,360px) - 3px)}.splitter:after{content:"";background:var(--splitter);opacity:0;width:3px;transition:opacity .15s;position:absolute;top:0;bottom:0;left:1.5px}.splitter:hover:after,.splitter.is-dragging:after{opacity:1}.phone--dragging,.phone--dragging *{-webkit-user-select:none!important;user-select:none!important;cursor:col-resize!important}.canvas-bar{border-bottom:var(--hairline) solid var(--border);flex:none;align-items:center;gap:4px;width:100%;height:32px;padding:0 8px;display:flex}.canvas-bar__spacer{flex:auto}.canvas-bar__chip{height:24px;color:var(--text-dim);white-space:nowrap;cursor:pointer;border-radius:5px;align-items:center;gap:6px;padding:0 8px;font-size:11.5px;transition:background .15s,color .15s;display:inline-flex}.canvas-bar__chip:hover,.dd--open>.dd__trigger--slot .canvas-bar__chip{background:var(--bg-hover);color:var(--text)}.canvas-bar__chev{width:10px;height:10px;color:var(--text-faint)}.canvas-bar__icn{width:13px;height:13px;color:var(--text-faint);flex:none}.canvas-bar__swatch{border:1px solid var(--border-strong);border-radius:3px;flex:none;width:10px;height:10px}.canvas-bar__swatch--white{background:#fff}.canvas-bar__swatch--gray{background:#f0f0f0}.canvas-bar__swatch--black{background:#1c1c1e}.canvas-bar__swatch--checker{background-color:#fff;background-image:linear-gradient(45deg,#cfcfcf 25%,#0000 25% 75%,#cfcfcf 75%),linear-gradient(45deg,#cfcfcf 25%,#0000 25% 75%,#cfcfcf 75%);background-position:0 0,3px 3px;background-size:6px 6px}.canvas-bar__size{font-variant-numeric:tabular-nums;color:var(--text-faint);white-space:nowrap;padding:0 4px;font-family:SF Mono,JetBrains Mono,Menlo,Consolas,monospace;font-size:11px}.canvas-bar__iconbtn{width:24px;height:24px;color:var(--text-faint);cursor:pointer;background:0 0;border:none;border-radius:5px;flex:none;justify-content:center;align-items:center;padding:0;transition:background .15s,color .15s;display:inline-flex}.canvas-bar__iconbtn:hover:not(:disabled){background:var(--bg-hover);color:var(--accent)}.canvas-bar__iconbtn:disabled{opacity:.4;cursor:default}.canvas-bar__iconbtn.is-loading svg{animation:.8s linear infinite canvas-bar-spin}@keyframes canvas-bar-spin{to{transform:rotate(360deg)}}.canvas-bar--tight .canvas-bar__size,.canvas-bar--tight .canvas-bar__txt{display:none}.statusbar__conn-icon{flex:none;width:14px;height:14px}.statusbar .app__btn--connected,.statusbar .app__btn--reconnecting,.statusbar .app__btn--disconnected,.statusbar .app__btn--warn{background:0 0;border:none;padding:0 10px}.statusbar .app__btn--reconnecting{color:var(--warn)}.statusbar .app__btn--disconnected{color:var(--danger)}.statusbar .app__btn--warn{color:var(--warn)}.statusbar .app__btn--connected{color:var(--text-dim)}.statusbar .app__btn--connected:hover:not(:disabled){background:var(--bg-hover);color:var(--text);border-color:#0000}button.statusbar__conn{cursor:pointer;font:inherit}.statusbar__conn:hover{background:var(--bg-hover)}.vc-sketch-presets{border-top-color:var(--border)!important}.phone-viewport{width:100%;min-height:0;box-shadow:0 12px 48px var(--shadow-pop);cursor:default;flex:auto;position:relative}.phone__screen{scrollbar-width:none;-ms-overflow-style:none;width:100%;height:100%;padding:16px;position:relative;overflow:hidden auto}.phone__screen--bg-checker{background-color:#fff;background-image:linear-gradient(45deg,#cfcfcf 25%,#0000 25% 75%,#cfcfcf 75%),linear-gradient(45deg,#cfcfcf 25%,#0000 25% 75%,#cfcfcf 75%);background-position:0 0,8px 8px;background-size:16px 16px}.phone__screen--bg-white{background:#fff}.phone__screen--bg-gray{background:#f0f0f0}.phone__screen--bg-black{background:#1c1c1e}.phone__screen::-webkit-scrollbar{display:none}.ce-sel-overlay{pointer-events:none;box-sizing:border-box;z-index:var(--z-overlay-low);background:#4285f42e;position:absolute}.ce-guides{pointer-events:none;z-index:var(--z-overlay-low);position:absolute;top:0;left:0}.ce-guide{background:#68ffff;position:absolute}.ce-guide--v{width:1px;height:100%;top:0;transform:translate(-.5px)}.ce-guide--h{width:100%;height:1px;left:0;transform:translateY(-.5px)}.debug{min-width:var(--debug-min,33.333vw);background:var(--bg-panel);flex-direction:column;flex:auto;display:flex}.apipanel{width:var(--api-panel-width,360px);background:var(--bg-panel);border-left:var(--hairline) solid var(--border);flex-direction:column;flex:none;min-width:0;max-width:100%;min-height:0;display:flex}.apipanel__head{border-bottom:var(--hairline) solid var(--border);flex:none;align-items:center;gap:8px;height:40px;padding:0 12px;display:flex}.apipanel__title{color:var(--text);white-space:nowrap;font-size:13px;font-weight:600}.apipanel__spacer{flex:auto}.apipanel__btn{width:28px;height:28px;color:var(--text-dim);cursor:pointer;background:0 0;border:1px solid #0000;border-radius:8px;flex:none;justify-content:center;align-items:center;padding:0;transition:background .15s,color .15s,border-color .15s,transform 50ms;display:inline-flex}.apipanel__btn:hover:not(:disabled){background:var(--bg-hover);border-color:var(--border);color:var(--text)}.apipanel__btn:active:not(:disabled){transform:translateY(1px)}.apipanel__btn:disabled{opacity:.5;cursor:default}.apipanel__body{flex:auto;padding:8px 12px;overflow:auto}.empty-state{color:var(--text-faint);flex-direction:column;justify-content:center;align-items:center;gap:12px;padding:56px 16px;display:flex}.empty-state__badge{background:var(--bg-hover);border:1px dashed var(--border-strong);width:48px;height:48px;color:var(--text-faint);border-radius:50%;justify-content:center;align-items:center;display:inline-flex}.empty-state__badge svg{width:20px;height:20px}.empty-state__hint{max-width:240px;color:var(--text-faint);text-align:center;margin:0;font-size:11px;line-height:1.6}.debug__tabs{border-bottom:var(--hairline) solid var(--border);display:flex}.debug__tab{height:39px;color:var(--text-dim);cursor:pointer;background:0 0;border:none;flex:1;padding:10px 8px;font-size:13px;transition:color .15s;position:relative}.debug__tab:hover{color:var(--text)}.debug__tab--active{color:var(--accent)}.debug__tab--active:after{content:"";background:var(--accent);height:2px;position:absolute;bottom:-1px;left:20%;right:20%}.debug__count{background:var(--bg-hover);min-width:16px;color:var(--text-dim);border-radius:999px;margin-left:5px;padding:0 5px;font-size:12px;display:inline-block}.debug__body{flex:auto;position:relative;overflow:auto}.debug__meta{flex-direction:column;min-height:100%;margin:0;padding:8px 12px 0;display:flex}.debug__empty{color:var(--text-faint);text-align:center;font-size:13px}.debug__meta-list{flex-direction:column;gap:8px;margin:0;display:flex}.debug__meta-row{grid-template-columns:72px 1fr;align-items:baseline;gap:8px;font-size:13px;display:grid}.debug__meta-row dt{color:var(--text-faint);margin:0}.debug__meta-row dd{color:var(--text);word-break:break-all;margin:0}.debug__meta-row code{font-family:var(--mono,ui-monospace, monospace);background:var(--bg-hover);border-radius:4px;padding:1px 5px;font-size:12px}.debug__meta-muted{color:var(--text-faint);font-size:12px}.debug__meta-err{color:var(--danger);font-size:12px}.debug__meta-hint{background:var(--bg-panel);border-top:var(--hairline) solid var(--border);color:var(--text-faint);margin:auto -12px 0;padding:10px 12px;font-size:12px;line-height:1.6;position:sticky;bottom:0}.statepanel{background:var(--bg-panel);border-top:var(--hairline) solid var(--border-strong);flex-direction:column;flex:none;height:240px;min-height:120px;max-height:60vh;display:flex;position:relative;overflow:hidden}.statepanel__head{height:var(--ctrl-lg);border-bottom:var(--hairline) solid var(--border);color:var(--text);flex:none;align-items:center;gap:8px;padding:0 12px;font-size:13px;display:flex}.panelhead__edge{cursor:row-resize;-webkit-user-select:none;user-select:none;height:6px;z-index:var(--z-sticky);position:absolute;top:0;left:0;right:0}.panelhead__edge:after{content:"";background:var(--splitter);opacity:0;height:3px;transition:opacity .15s;position:absolute;top:0;left:0;right:0}.panelhead__edge:hover:after,.panelhead__edge.is-dragging:after{opacity:1}.statepanel__title{letter-spacing:.2px;font-weight:600}.statepanel__title code{font-family:var(--mono,ui-monospace, SFMono-Regular, Menlo, monospace);color:var(--text-dim);background:var(--bg-hover);border-radius:4px;padding:1px 5px;font-size:12px}.statepanel__close{width:24px;height:24px;color:var(--text-dim);cursor:pointer;border-radius:var(--radius-xs);background:0 0;border:none;justify-content:center;align-items:center;margin-left:auto;padding:0;transition:background .15s,color .15s;display:inline-flex}.statepanel__close:hover{background:var(--bg-hover);color:var(--text)}.statepanel__close svg{display:block}.statepanel__tree{flex:auto;padding:8px 12px 10px;overflow:auto}.debug__tag{border-radius:999px;padding:1px 8px;font-size:12px;line-height:1.6;display:inline-block}.debug__tag--ok{color:var(--ok-strong);background:var(--ok-soft)}.debug__tag--warn{color:var(--tag-warn-fg);background:var(--tag-warn-bg)}.debug__tag--err{color:var(--danger);background:var(--danger-soft)}.debug__list{margin:0;padding:0;list-style:none}.debug__item{word-break:normal;overflow-wrap:anywhere;border-radius:8px;margin-bottom:6px;padding:9px 11px;font-size:12px;line-height:1.6}.debug__item--err{border-left:2px solid var(--danger);border-bottom:var(--hairline) solid var(--border);color:var(--err-item-fg);word-break:normal;background:0 0;border-radius:0;grid-template-columns:52px 84px minmax(0,1fr);align-items:baseline;gap:8px;margin-bottom:0;padding:3px 12px 3px 10px;font-size:11px;line-height:1.45;display:grid}.debug__item--err:last-child{border-bottom:none}.debug__item--err:hover{background:var(--danger-soft)}.debug__item--err .debug__kind{color:var(--danger);text-overflow:ellipsis;white-space:nowrap;opacity:1;font-family:SF Mono,JetBrains Mono,Menlo,Consolas,monospace;font-size:10px;overflow:hidden}.err__msg{word-break:break-word;min-width:0}.debug__item--api{border:1px solid var(--api-item-border);color:var(--api-item-fg);background:var(--api-item-bg);margin-bottom:8px}.debug__item--api:last-child{margin-bottom:0}.debug__item--api .debug__kind{font-family:SF Mono,JetBrains Mono,Menlo,Consolas,monospace;font-size:12px;line-height:1.6}.debug__kind{opacity:.85;font-weight:600}.debug__api-row{justify-content:space-between;align-items:center;gap:8px;display:flex}.debug__copy-ic{width:24px;height:22px;color:var(--text-faint);cursor:pointer;background:0 0;border:1px solid #0000;border-radius:6px;flex:none;justify-content:center;align-items:center;padding:0;transition:background .15s,color .15s,border-color .15s;display:inline-flex}.debug__copy-ic:hover{background:var(--bg-hover);border-color:var(--border);color:var(--text)}.debug__copy-ok{color:var(--copy-ok);font-size:14px;line-height:1}.apt{color:var(--text-dim);margin-top:4px;font-family:SF Mono,JetBrains Mono,Menlo,Consolas,monospace;font-size:12px;line-height:1.6}.apt__list{margin:0;padding:0;list-style:none}.apt__node{position:relative}.apt__branch,.apt__leaf{cursor:pointer;border-radius:4px;align-items:baseline;gap:4px;padding:1px 4px;display:flex}.apt .jt__colon{margin-left:-4px}.apt .jt__key{flex-shrink:0}.apt .jt__val,.apt .jt__preview{min-width:0}.apt__branch:hover,.apt__leaf:hover{background:var(--bg-hover)}.apt__caret{text-align:center;width:12px;color:var(--text-faint);-webkit-user-select:none;user-select:none;flex:none;font-size:9px;transition:transform .15s;transform:rotate(0)}.apt__caret--open{color:var(--accent);transform:rotate(90deg)}.apt__caret--leaf{visibility:hidden}.apt__children{border-left:var(--hairline) solid var(--border);margin-left:4px;padding-left:16px}.apt__close{color:var(--text-faint);padding:1px 4px 1px 20px}.debug__scope-wrap{flex-direction:column;min-height:0;display:flex;position:absolute;inset:0}.debug__scope-foot{background:var(--bg-bar);border:1px solid var(--border);box-shadow:0 6px 18px var(--shadow-toast);border-radius:10px;justify-content:flex-end;align-items:center;gap:8px;padding:6px;display:flex;position:absolute;bottom:10px;right:10px}.debug__copied{color:var(--accent);margin:0 4px 0 8px;font-size:12px}.debug__copy{background:var(--bg-input);height:26px;color:var(--text-dim);border:1px solid var(--border-strong);cursor:pointer;border-radius:7px;align-items:center;gap:5px;padding:0 10px;font-size:12px;transition:background .15s,border-color .15s,color .15s;display:inline-flex}.debug__copy:hover{background:var(--bg-hover);border-color:var(--accent-border);color:var(--text)}.debug__copy-icn{flex:none;width:13px;height:13px}.debug__copy--primary:hover{background:var(--ok-soft);border-color:var(--ok-border);color:var(--ok)}.debug__copy--warn:hover{background:var(--warn-soft);border-color:var(--warn-border);color:var(--warn)}.debug__sep{width:var(--hairline);background:var(--border-strong);height:16px;margin:0 2px}.debug__scope-wrap--raw{flex-direction:column;min-height:0;display:flex}.debug__scope-raw{flex-direction:column;flex:auto;min-height:0;display:flex}.debug__raw-editor{background:var(--bg-input);width:100%;min-height:160px;color:var(--text);tab-size:2;white-space:pre;outline:none;flex:auto;padding:12px 14px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;line-height:1.6;overflow:auto}.debug__raw-editor .token.property,.debug__raw-editor .token.tag,.debug__raw-editor .token.boolean,.debug__raw-editor .token.number,.debug__raw-editor .token.constant,.debug__raw-editor .token.symbol,.vars-dlg__code .token.property,.vars-dlg__code .token.tag,.vars-dlg__code .token.boolean,.vars-dlg__code .token.number,.vars-dlg__code .token.constant,.vars-dlg__code .token.symbol{color:var(--code-prop)}.debug__raw-editor .token.selector,.debug__raw-editor .token.attr-name,.debug__raw-editor .token.string,.debug__raw-editor .token.char,.debug__raw-editor .token.builtin,.vars-dlg__code .token.selector,.vars-dlg__code .token.attr-name,.vars-dlg__code .token.string,.vars-dlg__code .token.char,.vars-dlg__code .token.builtin{color:var(--code-str)}.debug__raw-editor .token.operator,.debug__raw-editor .token.entity,.debug__raw-editor .token.url,.vars-dlg__code .token.operator,.vars-dlg__code .token.entity,.vars-dlg__code .token.url{color:var(--code-num)}.debug__raw-editor .token.punctuation,.vars-dlg__code .token.punctuation{color:var(--code-punc)}.debug__raw-editor .token.keyword,.debug__raw-editor .token.null,.vars-dlg__code .token.keyword,.vars-dlg__code .token.null{color:var(--code-kw)}.debug__raw-editor .token.function,.debug__raw-editor .token.class-name,.vars-dlg__code .token.function,.vars-dlg__code .token.class-name{color:var(--code-fn)}.raw-err-bar{color:var(--danger,#ef4444);background:var(--danger-soft,#ef44441f);flex:none;align-items:center;gap:6px;padding:4px 10px;font-size:11px;line-height:1.5;display:flex}.raw-err-bar__icn{flex:0 0 12px;width:12px;height:12px}.raw-err-bar__msg{flex:auto;min-width:0}.raw-err-bar__close{width:18px;height:18px;color:inherit;opacity:.7;cursor:pointer;background:0 0;border:none;border-radius:4px;flex:none;justify-content:center;align-items:center;padding:0;font-size:11px;line-height:1;transition:opacity .12s;display:inline-flex}.raw-err-bar__close:hover{opacity:1}.vars{flex-direction:column;height:100%;min-height:0;display:flex;position:relative}.vars__list{flex-direction:column;flex:auto;gap:2px;min-height:0;margin:0;padding:8px 8px 52px;list-style:none;display:flex;overflow-y:auto}.vars__row{-webkit-user-select:none;user-select:none;border-radius:6px;align-items:center;gap:6px;padding:4px 6px;transition:background .12s;display:flex}.vars__row:hover{background:var(--bg-hover)}.vars__key{text-overflow:ellipsis;white-space:nowrap;max-width:46%;color:var(--text);flex:none;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;overflow:hidden}.vars__type{border-radius:var(--radius-pill);background:var(--bg-active);color:var(--text-dim);flex:none;padding:1px 6px;font-size:10px;line-height:1.5}.vars__type--string{color:var(--type-string);background:color-mix(in srgb, var(--type-string) 14%, transparent)}.vars__type--number{color:var(--type-number);background:color-mix(in srgb, var(--type-number) 14%, transparent)}.vars__type--boolean{color:var(--type-boolean);background:color-mix(in srgb, var(--type-boolean) 14%, transparent)}.vars__type--array{color:var(--type-array);background:color-mix(in srgb, var(--type-array) 14%, transparent)}.vars__type--object{color:var(--type-object);background:color-mix(in srgb, var(--type-object) 14%, transparent)}.vars__mock{background:var(--warn-soft);width:14px;height:14px;color:var(--warn);border-radius:50%;flex:none;justify-content:center;align-items:center;font-size:9px;line-height:1;display:inline-flex}.vars__name{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--text-faint);flex:auto;font-size:11px;overflow:hidden}.vars__ops{opacity:0;flex:none;gap:2px;transition:opacity .12s;display:inline-flex}.vars__row:hover .vars__ops,.vars__row:focus-within .vars__ops{opacity:1}.vars__op{width:20px;height:20px;color:var(--text-faint);cursor:pointer;background:0 0;border:none;border-radius:4px;justify-content:center;align-items:center;padding:0;transition:background .12s,color .12s;display:inline-flex}.vars__op svg{width:13px;height:13px;display:block}.vars__op:hover{background:var(--bg-active);color:var(--text)}.vars__op--del:hover{background:var(--danger-soft);color:var(--danger)}.vars__op:disabled,.vars__op:disabled:hover{color:var(--text-faint);opacity:.35;cursor:default;background:0 0}.vars__op .debug__copy-ok{font-size:12px}.vars__fabs{background:var(--bg-bar);border:1px solid var(--border);box-shadow:0 6px 18px var(--shadow-toast);border-radius:10px;align-items:center;gap:8px;padding:6px;display:flex;position:absolute;bottom:10px;right:10px}.vars__mockhead{border-bottom:var(--hairline) solid var(--border);flex:none;align-items:center;gap:8px;padding:5px 10px;display:flex}.vars__back{background:var(--bg-bar);border:1px solid var(--border-strong);height:24px;color:var(--text);cursor:pointer;border-radius:6px;flex:none;align-items:center;gap:4px;padding:0 9px 0 7px;font-size:12px;font-weight:500;transition:background .15s,color .15s,border-color .15s;display:inline-flex}.vars__back:hover{background:var(--accent-soft);border-color:var(--accent-border);color:var(--accent)}.vars__back svg{width:12px;height:12px}.vars__mocktitle{color:var(--text-faint);font-size:11px}.ni-panel.vars-dlg{width:560px;height:auto;min-height:0;max-height:80vh}.vars-dlg .ni-field-label i{color:var(--danger);margin-right:2px;font-style:normal}.ni-field-row--top{align-items:flex-start}.ni-field-row--top .ni-field-label{line-height:var(--ctrl-sm)}.vars-dlg__ctrl{flex:auto;min-width:0}.vars-type-pick{align-items:center;gap:8px;min-width:0;display:flex}.vars-type-pick .vars__type{flex:none}.vars-type-pick__name{white-space:nowrap;text-overflow:ellipsis;min-width:3em;color:var(--text);flex:none;overflow:hidden}.vars-dlg__code,.vars-dlg__str{min-height:171.6px;max-height:264px}.vars-dlg__code{background:var(--bg-input);border:1px solid var(--border-strong);min-width:0;color:var(--text);tab-size:2;white-space:pre;border-radius:6px;outline:none;flex:auto;padding:8px 12px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;line-height:1.6;transition:border-color .15s;overflow:auto}.vars-dlg__code:focus{border-color:var(--accent)}.ni-input.vars-dlg__str{resize:vertical;height:auto;padding:8px 12px;line-height:1.6;overflow:auto}.vars-dlg__desc{resize:vertical;height:auto;min-height:90.8px;max-height:140px;line-height:1.6}.vars-dlg__err{color:var(--danger);margin:2px 0 0;font-size:11px}.ni-panel.newcard-dlg{width:420px;height:auto;min-height:0;max-height:80vh}.newcard-dlg .ni-field-label i{color:var(--danger);margin-right:2px;font-style:normal}.newcard-dlg .ni-body{padding:14px 0}.newcard-dlg .ni-scroll{padding:0 16px;overflow:visible}.newcard-dlg .ni-field-row:last-of-type{margin-bottom:0}.newcard-dlg__err{color:var(--danger);margin:12px 0 0;font-size:11px}.newcard-dlg .ni-input.newcard-dlg__input--dup,.newcard-dlg .ni-input.newcard-dlg__input--dup:focus{border-color:var(--danger)}.vars__mockhint{color:var(--text-faint);margin-left:auto;font-size:11px}.statusbar{background:var(--bg-bar);border-top:var(--hairline) solid var(--border);height:28px;color:var(--text-dim);-webkit-user-select:none;user-select:none;flex:none;order:3;align-items:stretch;padding:0 2px;font-size:12px;display:flex}.statusbar__spacer{flex:auto}.statusbar__item{white-space:nowrap;cursor:default;align-items:center;gap:5px;height:100%;padding:0 10px;display:inline-flex}.statusbar__item--right{color:var(--text-faint)}.statusbar__item--prob{cursor:pointer;color:var(--text-dim);background:0 0;border:none;border-radius:0;font-weight:400}.statusbar__item--prob:hover{color:var(--text)}.statusbar__item--prob.is-error,.statusbar__item--prob.is-error:hover{color:var(--danger)}.statusbar__item--prob.is-ok{color:var(--text-faint);cursor:default}.statusbar__item--prob.is-ok:hover{color:var(--text-faint)}.statusbar__icon{font-size:14px;line-height:1}.statusbar__icon.is-dirty{color:var(--warn)}.errpanel{background:var(--bg-panel);border-top:var(--hairline) solid var(--border-strong);flex-direction:column;flex:none;height:240px;min-height:120px;max-height:60vh;display:flex;position:relative;overflow:hidden}.errpanel__head{height:var(--ctrl-lg);border-bottom:var(--hairline) solid var(--border);color:var(--text);flex:none;align-items:center;gap:8px;padding:0 12px;font-size:13px;display:flex}.errpanel__title{letter-spacing:.2px;font-weight:600}.errpanel__actions{align-items:center;gap:6px;margin-left:auto;display:flex}.errpanel__iconbtn{width:24px;height:24px;color:var(--text-dim);cursor:pointer;border-radius:var(--radius-xs);background:0 0;border:none;justify-content:center;align-items:center;padding:0;transition:background .15s,color .15s;display:inline-flex}.errpanel__iconbtn:hover:not(:disabled){background:var(--bg-hover);color:var(--text)}.errpanel__iconbtn svg{display:block}.errpanel__iconbtn:disabled{opacity:.35;cursor:not-allowed}.errpanel__clear:hover:not(:disabled){color:var(--danger)}.errpanel__tabs{flex:none;align-self:stretch;align-items:stretch;gap:2px;margin-left:4px;display:flex}.errpanel__tab{height:100%;color:var(--text-dim);cursor:pointer;background:0 0;border:none;align-items:center;gap:5px;padding:0 10px;font-size:12px;transition:color .15s;display:inline-flex;position:relative}.errpanel__tab:hover{color:var(--text)}.errpanel__tab--active{color:var(--accent)}.errpanel__tab--active:after{content:"";background:var(--accent);height:2px;position:absolute;bottom:-1px;left:20%;right:20%}.errpanel__tab-count{background:var(--bg-hover);min-width:16px;color:var(--text-faint);text-align:center;border-radius:999px;padding:0 5px;font-size:11px;line-height:15px}.errpanel__tab--active .errpanel__tab-count{background:var(--accent-soft);color:var(--accent)}.errpanel__tab-count.is-hit,.errpanel__tab--active .errpanel__tab-count.is-hit{background:var(--danger-soft);color:var(--danger)}.errpanel__body{flex:auto;padding:4px 0 6px;overflow:auto}.errpanel__body .empty-state{gap:8px;padding:20px 16px}.errpanel__body .empty-state__badge{width:40px;height:40px}.errpanel__body .empty-state__badge svg{width:18px;height:18px}.errpanel__body .empty-state__hint{max-width:460px}.errpanel__body--term{color:#e8eaf0;background:#14161c;padding:0}.svclog{padding:6px 0 10px;font-family:SF Mono,JetBrains Mono,Menlo,Consolas,monospace;font-size:11.5px;line-height:1.55}.svclog__line{white-space:pre-wrap;word-break:break-word;border-left:2px solid #0000;min-height:1.55em;padding:0 12px;display:flex}.svclog__line:hover{background:#ffffff0b}.svclog__line--ok{border-left-color:#34d3998c}.svclog__line--warn{border-left-color:var(--warn);background:#fbbf2412}.svclog__line--error{border-left-color:var(--danger);background:#f8717114}.svclog__text{min-width:0}.svclog__meta{color:var(--text-faint);white-space:nowrap;align-items:center;gap:5px;margin-left:8px;font-size:11px;display:inline-flex}.svclog__dot{background:var(--text-faint);border-radius:50%;width:6px;height:6px;margin-left:4px}.svclog__dot.is-live{background:var(--ok);box-shadow:0 0 0 2px var(--ok-soft)}.svclog__follow.is-on{color:var(--accent)}.errpanel__iconbtn.is-done{color:var(--ok)}.debug__list--log{flex-direction:column;display:flex}.log__item{border-bottom:var(--hairline) solid var(--border);grid-template-columns:52px 44px minmax(0,1fr);align-items:baseline;gap:8px;padding:2px 12px;display:grid}.log__item:last-child{border-bottom:none}.log__item:hover{background:var(--bg-input)}.log__time,.err__time{font-variant-numeric:tabular-nums;color:var(--text-faint);font-family:SF Mono,JetBrains Mono,Menlo,Consolas,monospace;font-size:10px}.log__tag{text-transform:uppercase;letter-spacing:.3px;color:var(--text-dim);background:var(--bg-hover);border:none;border-radius:3px;justify-self:start;padding:0 5px;font-family:SF Mono,JetBrains Mono,Menlo,Consolas,monospace;font-size:10px;font-weight:600;line-height:14px}.log__tag--info{color:var(--accent);background:var(--accent-soft)}.log__tag--warn{color:var(--log-warn-fg);background:var(--log-warn-bg)}.log__tag--error{color:var(--danger);background:var(--danger-soft)}.log__content{min-width:0;color:var(--text);flex-wrap:wrap;flex:auto;align-items:flex-start;gap:3px 8px;font-family:SF Mono,JetBrains Mono,Menlo,Consolas,monospace;font-size:11px;line-height:1.45;display:flex}.log__val{white-space:pre-wrap;word-break:break-word}.log__json{flex:100%;padding:0}.log__json .apt{margin-top:0;font-size:11px;line-height:1.4}.log__json .apt__branch--root{cursor:pointer;border-radius:4px}.log__json .apt__list--nested{border-left:var(--hairline) solid var(--border);margin-left:4px;padding-left:14px}.log__empty{color:var(--text-faint)}.alert-layer{z-index:var(--z-modal);position:absolute;inset:0}.alert-mask{background:#0000008c;padding:16px;display:flex;position:absolute;inset:0}.alert-mask--bottom{justify-content:center;align-items:flex-end}.alert-mask--top{justify-content:center;align-items:flex-start}.alert-mask--center{justify-content:center;align-items:center}.alert-mask--left{justify-content:flex-start;align-items:center}.alert-mask--right{justify-content:flex-end;align-items:center}.alert-panel{background:#fff;border-radius:12px;width:100%;max-width:100%;max-height:92vh;animation:.2s ease-out alert-pop;overflow:hidden auto;box-shadow:0 18px 60px #00000080}@keyframes alert-pop{0%{opacity:0;transform:scale(.96)translateY(8px)}to{opacity:1;transform:scale(1)translateY(0)}}.alert-mask--center .alert-panel,.alert-mask--left .alert-panel,.alert-mask--right .alert-panel{border-radius:12px}.device-ui{z-index:var(--z-deviceui);pointer-events:none;position:absolute;inset:0}.dlg-mask{pointer-events:auto;background:#00000073;justify-content:center;align-items:center;display:flex;position:absolute;inset:0}.dlg{text-align:center;background:#fff;border-radius:12px;width:288px;max-width:82%;padding:24px 20px 0;animation:.2s ease-out dlg-pop;box-shadow:0 12px 40px #00000059}@keyframes dlg-pop{0%{opacity:0;transform:scale(.94)}to{opacity:1;transform:scale(1)}}.dlg__title{color:#333;font-size:16px;font-weight:600}.dlg__msg{color:#666;text-align:left;word-break:break-all;white-space:pre-wrap;max-height:320px;margin-top:10px;font-size:14px;line-height:1.5;overflow-y:auto}.dlg__title+.dlg__msg{margin-top:12px}.dlg__btns{border-top:var(--hairline) solid #e8e8e8;margin:18px -20px 0;display:flex}.dlg__btn{cursor:pointer;background:0 0;border:none;flex:1 1 0;min-width:0;padding:14px 0;font-size:15px}.dlg__btn--ok{color:#1677ff;font-weight:500}.dlg__btn--cancel{color:#1677ff;border-right:var(--hairline) solid #e8e8e8}.dlg__btn--cancel.dlg__btn--gray{color:#888}.dtoast{color:#fff;text-align:center;box-sizing:border-box;pointer-events:none;background:#000000b3;border-radius:10px;flex-direction:column;justify-content:center;align-items:center;gap:8px;min-width:0;max-width:76%;padding:10px 14px;font-size:14px;line-height:20px;display:flex;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.dtoast--icon{min-width:96px;padding:16px}.dtoast__icon{flex:none;justify-content:center;align-items:center;width:36px;height:36px;display:inline-flex}.dtoast__icon-svg{fill:none;stroke:#fff;stroke-width:2.4px;stroke-linecap:round;stroke-linejoin:round;width:28px;height:28px}.dtoast__msg{word-break:break-all}.apptoast{z-index:var(--z-toast-app);color:#fff;pointer-events:none;background:#111111d1;border-radius:10px;align-items:center;gap:8px;max-width:76%;padding:12px 20px;font-size:14px;line-height:1.4;display:inline-flex;position:fixed;top:56px;left:50%;transform:translate(-50%);box-shadow:0 6px 24px #00000047}.apptoast__icon{background:var(--text-faint);border-radius:50%;flex:none;justify-content:center;align-items:center;width:18px;height:18px;display:inline-flex}.apptoast--ok .apptoast__icon{background:var(--ok)}.apptoast--err .apptoast__icon{background:var(--danger)}.apptoast__icon-svg{fill:none;stroke:#fff;stroke-width:2.6px;stroke-linecap:round;stroke-linejoin:round;width:12px;height:12px}.apptoast__msg{word-break:break-all}.apptoast-fade-enter-active,.apptoast-fade-leave-active{transition:opacity .2s,transform .2s}.apptoast-fade-enter,.apptoast-fade-leave-to{opacity:0;transform:translate(-50%,-8px)}.dlg-fade-enter-active,.dlg-fade-leave-active,.toast-fade-enter-active,.toast-fade-leave-active{transition:opacity .18s}.dlg-fade-enter,.dlg-fade-leave-to,.toast-fade-enter,.toast-fade-leave-to{opacity:0}.pk-mask{pointer-events:auto;background:#0006;align-items:flex-end;display:flex;position:absolute;inset:0}.pk-sheet{background:#fff;border-radius:12px 12px 0 0;width:100%;animation:.2s ease-out pk-up}@keyframes pk-up{0%{transform:translateY(100%)}to{transform:translateY(0)}}.pk-bar{box-sizing:border-box;border-bottom:var(--hairline) solid #eef0f2;justify-content:space-between;align-items:center;height:44px;padding:0 16px;display:flex}.pk-bar__title{color:#333;font-size:15px;font-weight:600}.pk-bar__btn{cursor:pointer;background:0 0;border:none;padding:4px 2px;font-size:15px}.pk-bar__btn--cancel{color:#888}.pk-bar__btn--ok{color:#1677ff;font-weight:600}.pk-cols{height:230px;display:flex;position:relative;overflow:hidden}.pk-col{flex:1 1 0;min-width:0;height:100%;position:relative}.pk-col__band{border-top:var(--hairline) solid #d6e4ff;border-bottom:var(--hairline) solid #d6e4ff;pointer-events:none;background:#1677ff14;height:40px;position:absolute;top:50%;left:0;right:0;transform:translateY(-50%)}.pk-col__list{-webkit-overflow-scrolling:touch;height:100%;padding:95px 0;overflow-y:auto}.pk-item{text-align:center;color:#333;cursor:pointer;white-space:nowrap;text-overflow:ellipsis;height:40px;padding:0 6px;font-size:16px;line-height:40px;overflow:hidden}.pk-item--on{color:#1677ff;font-weight:600}.pk-cols--wheel .pk-col{cursor:grab;touch-action:none}.pk-cols--wheel.is-dragging{-webkit-user-select:none;user-select:none;cursor:grabbing}.pk-cols--wheel .pk-col__list{scrollbar-width:none;-ms-overflow-style:none}.pk-cols--wheel .pk-col__list::-webkit-scrollbar{display:none}.picker-fade-enter-active,.picker-fade-leave-active{transition:opacity .18s}.picker-fade-enter,.picker-fade-leave-to{opacity:0}.rp-mask{z-index:var(--z-appmodal);background:var(--scrim-modal);-webkit-backdrop-filter:blur(var(--scrim-modal-blur));backdrop-filter:blur(var(--scrim-modal-blur));justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.rp-card{background:var(--bg-panel);border:1px solid var(--border);width:300px;max-width:calc(100vw - 32px);box-shadow:0 18px 50px var(--shadow-card);color:var(--text);border-radius:14px;overflow:hidden}.rp-head{border-bottom:var(--hairline) solid var(--border);justify-content:space-between;align-items:center;padding:12px 14px;display:flex}.rp-title{font-size:14px;font-weight:600}.rp-x{width:26px;height:26px;color:var(--text-dim);cursor:pointer;background:0 0;border:none;border-radius:7px;font-size:20px;line-height:1;transition:background .15s,color .15s}.rp-x:hover{background:var(--bg-hover);color:var(--text)}.rp-body{flex-direction:column;align-items:center;padding:18px 16px 16px;display:flex}.rp-qr-wrap{box-sizing:border-box;background:#fff;border-radius:10px;justify-content:center;align-items:center;width:220px;height:220px;padding:8px;display:flex;position:relative}.rp-qr{width:204px;height:204px;display:block}.rp-qr--loading{width:100%;height:100%;color:var(--text-placeholder);justify-content:center;align-items:center;font-size:14px;display:flex}.rp-qr--blurred{filter:blur(7px);opacity:.55;transition:filter .25s,opacity .25s}.rp-qr-loading{background:#ffffff80;border-radius:12px;flex-direction:column;justify-content:center;align-items:center;gap:10px;display:flex;position:absolute;inset:0}.rp-spinner{border:3px solid #10131a2e;border-top-color:var(--accent);border-radius:50%;width:30px;height:30px;animation:.8s linear infinite rp-spin}.rp-loading-text{color:var(--text-dim);font-size:13px}@keyframes rp-spin{to{transform:rotate(360deg)}}.rp-tip{color:var(--text-dim);text-align:center;margin:12px 0 10px;font-size:12.5px}.rp-url{align-items:center;gap:8px;width:100%;display:flex}.rp-url-text{min-width:0;color:var(--text-dim);background:var(--bg-input);border:1px solid var(--border);text-overflow:ellipsis;white-space:nowrap;border-radius:7px;flex:auto;padding:6px 8px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;overflow:hidden}.rp-copy{border:1px solid var(--accent-border);background:var(--accent);color:var(--on-accent);cursor:pointer;border-radius:7px;flex:none;padding:6px 10px;font-size:12px;transition:background .15s,opacity .15s}.rp-copy:hover:not(:disabled){background:var(--accent-hover)}.rp-copy:disabled{opacity:.5;cursor:default}.app--chrome-off .app__stage{justify-content:center}.app--chrome-off .phone-viewport{box-shadow:none}.app--chrome-off .phone__screen{width:375px;margin:0 auto;padding:0}.rp-warn{color:var(--warn);text-align:center;margin:10px 0 0;font-size:11.5px;line-height:1.5}.login-mask{z-index:var(--z-appmodal);background:var(--scrim-modal);-webkit-backdrop-filter:blur(var(--scrim-modal-blur));backdrop-filter:blur(var(--scrim-modal-blur));justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.login-modal{background:var(--bg-panel);border:1px solid var(--border);width:320px;max-width:calc(100vw - 32px);box-shadow:0 18px 50px var(--shadow-card);color:var(--text);box-sizing:border-box;border-radius:14px;padding:18px 18px 16px}.login-modal__title{margin-bottom:6px;font-size:15px;font-weight:600}.login-modal__desc{color:var(--text-dim);margin-bottom:14px;font-size:12px;line-height:1.5}.login-modal__row{align-items:center;gap:10px;margin-bottom:12px;display:flex}.login-modal__label{color:var(--text-dim);flex:0 0 36px;font-size:13px}.login-modal__input{min-width:0;height:34px;color:var(--text);background:var(--bg-input);border:1px solid var(--border);box-sizing:border-box;border-radius:8px;outline:none;flex:auto;padding:0 10px;font-size:13px}.login-modal__input:focus{border-color:var(--accent)}.login-modal__error{color:var(--danger);margin-bottom:10px;font-size:12px}.login-modal__actions{justify-content:flex-end;gap:10px;margin-top:4px;display:flex}.login-modal__btn{border:1px solid var(--border);cursor:pointer;border-radius:8px;padding:7px 14px;font-size:13px;transition:background .15s,opacity .15s,border-color .15s}.login-modal__btn--ghost{color:var(--text-dim);background:0 0}.login-modal__btn--ghost:hover:not(:disabled){background:var(--bg-hover);color:var(--text)}.login-modal__btn--primary{border-color:var(--accent-border);background:var(--accent);color:var(--on-accent)}.login-modal__btn--primary:hover:not(:disabled){background:var(--accent-hover)}.login-modal__btn:disabled{opacity:.5;cursor:default}.confirm-mask{z-index:var(--z-appmodal);background:var(--scrim-modal);-webkit-backdrop-filter:blur(var(--scrim-modal-blur));backdrop-filter:blur(var(--scrim-modal-blur));justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.confirm-modal{background:var(--bg-panel);border:1px solid var(--border);width:360px;max-width:calc(100vw - 32px);box-shadow:0 18px 50px var(--shadow-card);color:var(--text);box-sizing:border-box;border-radius:14px;padding:18px 18px 16px}.confirm-modal__title{align-items:center;gap:8px;margin-bottom:8px;font-size:15px;font-weight:600;display:flex}.confirm-modal__title.is-danger{color:var(--danger)}.confirm-modal__title.is-warn{color:var(--warn)}.confirm-modal__icon{flex:none;width:18px;height:18px}.confirm-modal__icon :is(path,line,polyline,circle,rect){vector-effect:non-scaling-stroke}.confirm-modal__desc{color:var(--text-dim);white-space:pre-line;margin-bottom:16px;font-size:12.5px;line-height:1.6}.confirm-modal__actions{justify-content:flex-end;gap:10px;display:flex}.confirm-modal__btn{border:1px solid var(--border);cursor:pointer;border-radius:8px;padding:7px 16px;font-size:13px;transition:background .15s,opacity .15s,border-color .15s}.confirm-modal__btn--ghost{color:var(--text-dim);background:0 0}.confirm-modal__btn--ghost:hover{background:var(--bg-hover);color:var(--text)}.confirm-modal__btn--primary{border-color:var(--accent-border);background:var(--accent);color:var(--on-accent)}.confirm-modal__btn--primary:hover{background:var(--accent-hover)}.confirm-modal__btn--danger{border-color:var(--danger);background:var(--danger);color:var(--on-accent)}.confirm-modal__btn--danger:hover{background:var(--danger-hover)}.confirm-modal__btn--warn{border-color:var(--warn);background:var(--warn);color:var(--on-warn)}.confirm-modal__btn--warn:hover{background:var(--warn-hover)}.pop-enter-active{transition:opacity .15s,transform .26s cubic-bezier(.34,1.56,.64,1)}.pop-leave-active{transition:opacity .12s,transform .12s}.pop-enter-from,.pop-leave-to{opacity:0;transform:translateY(6px)scale(.95)}.modal-enter-active,.modal-leave-active{transition:opacity .2s}.modal-leave-active{transition:opacity .16s}.modal-enter-from,.modal-leave-to{opacity:0}.modal-enter-active .modal-card{transition:transform .34s cubic-bezier(.34,1.56,.64,1)}.modal-leave-active .modal-card{transition:transform .16s}.modal-enter-from .modal-card{transform:translateY(14px)scale(.94)}.modal-leave-to .modal-card{transform:translateY(6px)scale(.98)}html,body,#app{width:100%;height:100%;margin:0;padding:0}.card-preview{background:#fff;justify-content:center;align-items:flex-start;width:100%;min-height:100%;display:flex}.card-preview .phone__screen{height:auto;min-height:100%;margin:0 auto;padding:0;overflow:hidden}