`)
+
+### 3. 每个任务需包含
+
+- 任务 ID(T1, T2, ...)
+- 目标文件
+- 具体变更内容描述
+- 依赖的前置任务
+- 验证方式(测试/视觉检查)
+
+## 输出
+
+将计划写入 `.claude/nutui-plan.json`:
+
+```json
+{
+ "component": "组件名",
+ "requirement": "需求简述",
+ "tasks": [
+ {
+ "id": "T1",
+ "title": "任务标题",
+ "file": "目标文件路径",
+ "description": "具体变更内容",
+ "depends": [],
+ "verification": "验证方式",
+ "status": "pending"
+ }
+ ],
+ "checkpoints": [
+ {
+ "after": "T3",
+ "check": "运行测试确认 H5 端无回归"
+ }
+ ]
+}
+```
+
+输出计划摘要给用户确认,提示确认后运行 `/nutui-execute` 开始执行。
+
+$ARGUMENTS
diff --git a/.claude/commands/nutui-review.md b/.claude/commands/nutui-review.md
new file mode 100644
index 0000000000..36fd9ff0d4
--- /dev/null
+++ b/.claude/commands/nutui-review.md
@@ -0,0 +1,84 @@
+# NutUI 组件代码评审
+
+你是 NutUI React 组件库的代码评审专家。对本次变更进行全面评审。
+
+## 输入
+
+1. 读取 `.claude/nutui-analysis.json` 了解需求
+2. 读取 `.claude/nutui-execution-report.json` 了解执行情况
+3. 通过 `git diff` 查看实际代码变更
+
+如果缺少上述文件,直接基于当前 git diff 进行评审。
+
+## 评审维度
+
+### 1. 正确性
+
+- 逻辑是否正确实现了需求
+- 边界条件是否处理
+- 类型安全(TypeScript 类型是否完备)
+
+### 2. 跨端一致性
+
+- H5(`.tsx`)和 Taro(`.taro.tsx`)的变更是否同步
+- Harmony(`.harmony.css`)样式是否同步更新
+- 三端行为是否一致
+
+### 3. 样式规范
+
+- 新增样式变量是否遵循命名规范(`$组件名-属性名`)
+- CSS 变量格式是否正确(`var(--nutui-*, fallback)`)
+- 是否存在硬编码的颜色/尺寸值(应使用变量)
+- 暗黑模式是否生效
+
+### 4. API 兼容性
+
+- Props 变更是否向后兼容
+- 是否有 Breaking Change 未标注
+- 废弃的 API 是否有迁移说明
+
+### 5. 测试覆盖
+
+- 新增/修改的功能是否有测试覆盖
+- 旧测试是否需要更新
+- 运行测试确认通过:`npx vitest run src/packages/
`
+
+### 6. 性能
+
+- 是否有不必要的重渲染
+- 事件监听是否正确清理
+- 大数据量场景是否考虑
+
+## 输出
+
+输出评审报告,按严重程度分级:
+
+- 🔴 **Critical** — 必须修复,阻塞合并
+- 🟡 **Warning** — 建议修复,不阻塞
+- 🔵 **Info** — 优化建议
+
+格式:
+
+```
+## 评审结果:[通过 / 需修改]
+
+### 🔴 Critical
+- [文件:行号] 问题描述
+
+### 🟡 Warning
+- [文件:行号] 问题描述
+
+### 🔵 Info
+- [文件:行号] 优化建议
+
+### 测试结果
+- 测试通过/失败
+
+### 总结
+一句话总结评审结论
+```
+
+如果有 Critical 问题,提示用户修复后重新运行 `/nutui-review`。
+全部通过后,提示可以提交代码。
+
+$ARGUMENTS
diff --git a/.cursor/skills/nutui-build-local-verify/SKILL.md b/.cursor/skills/nutui-build-local-verify/SKILL.md
new file mode 100644
index 0000000000..d498ff7afc
--- /dev/null
+++ b/.cursor/skills/nutui-build-local-verify/SKILL.md
@@ -0,0 +1,72 @@
+---
+name: nutui-build-local-verify
+description: NutUI 比例缩放本地验证——写回 src/packages 下同路径组件 SCSS(跳过 src/packages/**/demo.scss 与 demos);--mirror 写 scale-verify/;不写 build。
+disable-model-invocation: true
+---
+
+# NutUI Build Local Verify
+
+## 在做什么
+
+**只做一步**:用 `scripts/px-to-scale-px-in-component-scss.cjs` 把组件 SCSS 里裸 `px` 转成 `scale-px` 等,并把结果写回磁盘。
+
+**不扫描、不写入**:**`src/packages/<组件名>/demo.scss`**(各组件目录根下的单文件)、`**/demos/**`、路径中含 **`/demo/`**、测试与快照目录下的 `.scss`(与官方 `build.mjs` 里对 `**/demo.scss` 的 ignore 一致)。
+
+- **默认(就地覆盖)**:对每个匹配的 `.scss`,**读、写都是同一路径**——相对 `src/packages` 的路径不变。例如 `src/packages/actionsheet/actionsheet.scss` 转换后仍写回该文件,不会改到别的目录或改名。
+- **`--mirror`**:不写源码;结果写到 **`scale-verify/<与 src/packages 相同的相对路径>`**(例如 `scale-verify/actionsheet/actionsheet.scss`),便于 diff。
+
+之后是否再跑 `pnpm run build`、是否用别的工具核对,由你自行决定;本 skill **不要求** build。
+
+## 覆盖原 SCSS(推荐)
+
+在 **nutui-react 仓库根目录** 执行。**务必先 commit / stash**,用完 `git restore src/packages` 或 `git checkout -- src/packages` 恢复。
+
+若只需还原 **`src/packages/<组件>/demo.scss`**(当前脚本已跳过;若曾被旧版本误改):
+
+```bash
+find src/packages -name 'demo.scss' -exec git restore -- {} \;
+```
+
+**然后**在仓库根执行验证:
+
+```bash
+pnpm run verify-scale
+```
+
+等价:
+
+```bash
+node .cursor/skills/nutui-build-local-verify/scripts/verify-scale-generation.mjs
+```
+
+(`--in-place` / `-i` 与默认等价。)
+
+## 报告
+
+路径:**`scale-verify/report.json`**。覆盖模式下看 `overwriteSource === true`、`changedFileCount`、`changedFiles`。
+
+## 其它命令
+
+```bash
+# 删除仓库根下 scale-verify/ 整目录(含 report;不还原已覆盖的 src/packages)
+node .cursor/skills/nutui-build-local-verify/scripts/verify-scale-generation.mjs --clean
+```
+
+**可选**(只镜像、不覆盖源码):
+
+```bash
+pnpm run verify-scale:mirror
+```
+
+`--mirror` 与 `--in-place` 不能同时使用。
+
+## 核对清单
+
+- [ ] 覆盖前已 git 可回滚
+- [ ] `changedFiles` 抽样无 `scale-px(0px)`、无重复嵌套 `scale-px`
+- [ ] `font-size` / `font` 未被误改(转换器会跳过)
+
+## 给用户的一句话结论
+
+- 脚本跑完 + `changedFileCount` + 列 2~3 个 `changedFiles`
+- **覆盖的是真实源码**时,验证完用 **git 恢复**
diff --git a/.cursor/skills/nutui-build-local-verify/scripts/verify-scale-generation.mjs b/.cursor/skills/nutui-build-local-verify/scripts/verify-scale-generation.mjs
new file mode 100644
index 0000000000..35c5b4777c
--- /dev/null
+++ b/.cursor/skills/nutui-build-local-verify/scripts/verify-scale-generation.mjs
@@ -0,0 +1,136 @@
+#!/usr/bin/env node
+/**
+ * 本地验证:默认就地写回 src/packages 下同一路径的组件 .scss(如 …/actionsheet/actionsheet.scss)。
+ * 跳过 src/packages 下的 demo.scss、demos、测试与快照(与 build.mjs ignore 一致)。
+ * --mirror 只写 scale-verify/;不包含 build;自行 git diff / 恢复即可。
+ */
+import fs from 'node:fs/promises'
+import path from 'path'
+import { createRequire } from 'node:module'
+
+const require = createRequire(import.meta.url)
+const transform = require(path.resolve(process.cwd(), 'scripts/px-to-scale-px-in-component-scss.cjs'))
+
+const repoRoot = process.cwd()
+const packagesRoot = path.resolve(repoRoot, 'src/packages')
+const outRoot = path.resolve(repoRoot, 'scale-verify')
+const reportPath = path.resolve(outRoot, 'report.json')
+
+const argv = new Set(process.argv.slice(2))
+const shouldClean = argv.has('--clean')
+const mirrorMode = argv.has('--mirror')
+/** 默认覆盖 src/packages 原 .scss;传 --mirror 则只写 scale-verify/ */
+const inPlace = !mirrorMode
+
+if (mirrorMode && (argv.has('--in-place') || argv.has('-i'))) {
+ console.error('[scale-verify] 不能同时使用 --mirror 与 --in-place / -i')
+ process.exit(1)
+}
+
+function isScssFile(name) {
+ return name.endsWith('.scss')
+}
+
+function shouldSkip(relPath) {
+ const p = relPath.replaceAll('\\', '/')
+ // 与 build.mjs 的 ignore 一致:**/demo.scss 不参与 px→scale 写回
+ if (path.posix.basename(p) === 'demo.scss') return true
+ if (p.includes('/demo/')) return true
+ if (p.includes('/demos/')) return true
+ if (p.includes('/__test__/')) return true
+ if (p.includes('/__tests__/')) return true
+ if (p.includes('/__snapshots__/')) return true
+ if (p.startsWith('.scale-verify/')) return true
+ return false
+}
+
+async function walkScssFiles(dir, base = dir, list = []) {
+ const entries = await fs.readdir(dir, { withFileTypes: true })
+ for (const entry of entries) {
+ const abs = path.resolve(dir, entry.name)
+ const rel = path.relative(base, abs)
+ if (entry.isDirectory()) {
+ await walkScssFiles(abs, base, list)
+ continue
+ }
+ if (!entry.isFile() || !isScssFile(entry.name)) continue
+ if (shouldSkip(rel)) continue
+ list.push(abs)
+ }
+ return list
+}
+
+async function ensureReportDir() {
+ await fs.mkdir(outRoot, { recursive: true })
+}
+
+async function prepareOutputLayout() {
+ if (shouldClean) {
+ await fs.rm(outRoot, { recursive: true, force: true })
+ console.log('[scale-verify] cleaned:', path.relative(repoRoot, outRoot))
+ return
+ }
+
+ await fs.rm(outRoot, { recursive: true, force: true })
+ await fs.mkdir(outRoot, { recursive: true })
+}
+
+async function main() {
+ await prepareOutputLayout()
+ if (shouldClean) {
+ return
+ }
+
+ const files = await walkScssFiles(packagesRoot)
+ files.sort()
+
+ const changed = []
+ for (const absFile of files) {
+ const rel = path.relative(packagesRoot, absFile)
+ const source = await fs.readFile(absFile, 'utf8')
+ const transformed = transform(source)
+ if (source === transformed) continue
+
+ const targetFile = inPlace ? absFile : path.resolve(outRoot, rel)
+ if (!inPlace) {
+ await fs.mkdir(path.dirname(targetFile), { recursive: true })
+ }
+ await fs.writeFile(targetFile, transformed, 'utf8')
+ changed.push(rel.replaceAll('\\', '/'))
+ }
+
+ await ensureReportDir()
+ const scssWriteRoot = inPlace
+ ? path.relative(repoRoot, packagesRoot).replaceAll('\\', '/')
+ : path.relative(repoRoot, outRoot).replaceAll('\\', '/')
+
+ const report = {
+ generatedAt: new Date().toISOString(),
+ mode: inPlace ? 'in-place' : 'mirror',
+ overwriteSource: inPlace,
+ /** 本次写入的 SCSS 根路径:原地为 src/packages,镜像为仓库根下 scale-verify */
+ scssWriteRoot,
+ /** 镜像模式下的实验目录;原地模式为 null */
+ outDir: inPlace ? null : path.relative(repoRoot, outRoot).replaceAll('\\', '/'),
+ reportPath: path.relative(repoRoot, reportPath).replaceAll('\\', '/'),
+ totalScssFiles: files.length,
+ changedFileCount: changed.length,
+ changedFiles: changed,
+ }
+ await fs.writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8')
+
+ console.log('[scale-verify] mode:', report.mode)
+ if (!inPlace) {
+ console.log('[scale-verify] outDir:', report.outDir)
+ } else {
+ console.log('[scale-verify] wrote into:', path.relative(repoRoot, packagesRoot))
+ }
+ console.log('[scale-verify] totalScssFiles:', report.totalScssFiles)
+ console.log('[scale-verify] changedFileCount:', report.changedFileCount)
+ console.log('[scale-verify] report:', path.relative(repoRoot, reportPath))
+}
+
+main().catch((err) => {
+ console.error('[scale-verify] failed:', err)
+ process.exitCode = 1
+})
diff --git a/.cursor/skills/nutui-proportional-scaling/SKILL.md b/.cursor/skills/nutui-proportional-scaling/SKILL.md
new file mode 100644
index 0000000000..4c2fcc482d
--- /dev/null
+++ b/.cursor/skills/nutui-proportional-scaling/SKILL.md
@@ -0,0 +1,118 @@
+---
+name: nutui-proportional-scaling
+description: >-
+ NutUI React proportional scaling on branch feat_resize: runtime --nut-scale-f /
+ --nut-scale-font / --nut-scale-icon from scale-f.ts (H5) and scale-f.taro.ts
+ (Taro), Sass helpers scale-px / scale-font-px / scale-icon-px and theme font
+ tokens in variables.scss & theme-*.scss; npm run build / build:taro run
+ scripts/px-to-scale-px-in-component-scss.cjs on component SCSS in memory; profiles standard / large / elderly;
+ commit-backed rules e.g. never scale 0px. Use when implementing 多尺寸适配,
+ 等比适配, 大字版, 老年版, scale-px, viewport or native bridge scaling, or
+ editing component SCSS for resize; SCSS: prefer calc($token + Npx) over
+ #{} in calc, use outer calc() when mixing tokens that compile to
+ var(--nutui-*).
+---
+
+# NutUI React 等比适配
+
+## 1. 运行时:谁在写 CSS 变量
+
+- **H5**:`src/utils/scale-f.ts`
+ - `initScaleF(profile?)`:首次计算缩放、`resize` 时 `refreshScaleF`。
+ - `getScaleF`:优先 `jmfe.callNative('DongScreenAdapterPlugin','getScale')`,失败用视口规则。
+ - 视口回退要点:`innerWidth >= 600` 视作 pad,基准乘 `1.2`;`375–600` 间按 `375` 比例,**上限 1.17**(与源码常量一致)。
+- **Taro 侧复用同一套契约**:`src/utils/scale-f.taro.ts`,并从 `src/utils/index.taro.ts` 导出。
+- 写入 `:root` 的变量(与 `variables.scss` 一致):
+ - `--nut-scale-f`:布局/通用 `scale-px`
+ - `--nut-scale-font`:`scale-font-px`、主题 `--nutui-font-size-*`
+ - `--nut-scale-icon`:`scale-icon-px`、图标相关
+
+**档位 `ScaleProfile`**:`standard` | `large` | `elderly`(仅后两者生效额外倍率)。
+**场景倍率**(与 `getSceneRatio` 一致):老年对 `font` / `icon` / `lego` × `1.3`;大字仅对 `font` × `1.15`。
+
+**JS 里算像素**:`calcByProfile(baseValue, { scene, profile?, scale?, device? })` — 用于组件内联样式、画布尺寸等,与 Sass 的 `calc(...* var(--nut-scale-*))` 同一套语义。
+
+---
+
+## 2. 样式层:`variables.scss` 中的函数
+
+```scss
+// 根上默认值见 variables.scss :root
+@function scale-px($size) {
+ @return calc(#{$size} * var(--nut-scale-f, 1));
+}
+@function scale-font-px($size) {
+ @return calc(#{$size} * var(--nut-scale-font, var(--nut-scale-f, 1)));
+}
+@function scale-icon-px($size) {
+ @return calc(#{$size} * var(--nut-scale-icon, var(--nut-scale-f, 1)));
+}
+```
+
+**主题字号档**(`theme-default.scss` / `theme-dark.scss`):`--nutui-font-size-*` 使用 `calc(Npx * var(--nut-scale-font, var(--nut-scale-f, 1)))`,与 **大字/老年** 档位对齐。
+
+### 2.1 `npm run build` / `npm run build:taro` 时的 px → `scale-px`
+
+- 与 `package.json` 中顺序一致:先跑 **`scripts/replace-css-var.js`**,再 **`scripts/build.mjs`** 或 **`scripts/build-taro.mjs`**;上述脚本在读取 **`src/packages/**/\*.scss`(不含 demo)** 后,会经 **`scripts/px-to-scale-px-in-component-scss.cjs`** 在**内存**里把声明值中的裸 **`Npx`** 转为 **`scale-px(Npx)`**(规则见 §3),**不写回\*\*仓库里的组件 SCSS。
+- 源码里可继续手写 **`scale-px` / `scale-font-px` / `scale-icon-px`**;构建不会重复嵌套 `scale-px`。
+- 该脚本对 **`calc(...)` 体内同时含 `$` 与 `/`** 的整段先做占位再替换裸 `px`,避免 postcss-scss 把 **`calc($var / 2)`** 等拆坏;其它 `calc` 内的裸 `Npx` 仍会按规则转为 `scale-px`。
+
+---
+
+## 3. 提交里固化的规范(务必遵守)
+
+### 3.1 「0px 不转换」(`1a35d9b8`)
+
+凡应为 **数值 0** 的尺寸,**不要**写 `scale-px(0px)`,一律 **`0`** 或 **`0px`**(如 padding 某一维、`box-shadow` 偏移、border 为 0、`margin: 0`)。
+否则会得到 `calc(0px * var(--nut-scale-f))`,在部分浏览器或亚像素场景下与纯 `0` 表现不一致。
+
+### 3.2 `line-height`
+
+- **比例行高**(如 `line-height: 1`):不随系数变,用于挤压行盒、图标对齐等 — 与「等比 px」不同维度。
+- **与设计稿 px 绑定的行高**:用与字号一致的档位,通常为 **`scale-font-px`**,或与同一变量体系。
+- 参考历史修复:弹层标题等曾去掉不恰当的固定 `line-height` 以免与大字模式冲突 — 新增时不要给标题随意写死 `line-height: 20px` 类样式,除非走缩放函数或主题变量。
+
+### 3.3 组件 SCSS 迁移模式(`dc4f1e28` / 后续 style 提交)
+
+- 间距、圆角、`border` 粗细、固定宽高(非纯文字):优先 **`scale-px`**。
+- 纯字体大小:`scale-font-px` 或主题已有 `--nutui-font-size-*`。
+- 图标占位:**`scale-icon-px`** 或已有 `--nut-icon-*`。
+- 保持与 **无障碍/大屏** 相关提交协同:同一文件改尺度时,勿回退 `dialog` 等对大字兼容的改动。
+
+### 3.5 组件 `.tsx` 图标尺寸治理(props → class → 变量)
+
+- 对 `@nutui/icons-react` / `@nutui/icons-react-taro`:尽量避免在组件上写死 `size={12}`、`width={16}`、`height={16}`。
+- **推荐模式**:在 `.tsx` 里只加语义化 `className`,到对应 `.scss` 里用变量控制尺寸(优先 `$icon-size-*` 阶梯,或组件专用变量)。
+- 若是内联 `