base-paint 0.3.1 → 0.3.3

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 (62) hide show
  1. package/README.md +94 -1
  2. package/dist/blocks.d.ts +160 -6
  3. package/dist/blocks.d.ts.map +1 -1
  4. package/dist/blocks.js +1212 -27
  5. package/dist/blocks.js.map +1 -1
  6. package/dist/charts.d.ts +6 -4
  7. package/dist/charts.d.ts.map +1 -1
  8. package/dist/charts.js +426 -67
  9. package/dist/charts.js.map +1 -1
  10. package/dist/controls.d.ts +20 -5
  11. package/dist/controls.d.ts.map +1 -1
  12. package/dist/controls.js +242 -34
  13. package/dist/controls.js.map +1 -1
  14. package/dist/font.d.ts +3 -3
  15. package/dist/font.js +3 -3
  16. package/dist/help.d.ts +3 -3
  17. package/dist/help.d.ts.map +1 -1
  18. package/dist/help.js +32 -15
  19. package/dist/help.js.map +1 -1
  20. package/dist/helpShell.d.ts.map +1 -1
  21. package/dist/helpShell.js +2 -2
  22. package/dist/helpShell.js.map +1 -1
  23. package/dist/index.d.ts +10 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +8 -2
  26. package/dist/index.js.map +1 -1
  27. package/dist/injector.d.ts +1 -1
  28. package/dist/output/saveHtml.d.ts +37 -2
  29. package/dist/output/saveHtml.d.ts.map +1 -1
  30. package/dist/output/saveHtml.js +125 -15
  31. package/dist/output/saveHtml.js.map +1 -1
  32. package/dist/pageShapes.d.ts +117 -0
  33. package/dist/pageShapes.d.ts.map +1 -0
  34. package/dist/pageShapes.js +366 -0
  35. package/dist/pageShapes.js.map +1 -0
  36. package/dist/pageUi.d.ts +37 -0
  37. package/dist/pageUi.d.ts.map +1 -0
  38. package/dist/pageUi.js +237 -0
  39. package/dist/pageUi.js.map +1 -0
  40. package/dist/spec/charts.d.ts +16 -3
  41. package/dist/spec/charts.d.ts.map +1 -1
  42. package/dist/spec/charts.js +1 -1
  43. package/dist/spec/charts.js.map +1 -1
  44. package/dist/spec/controls.d.ts +30 -5
  45. package/dist/spec/controls.d.ts.map +1 -1
  46. package/dist/spec/controls.js +16 -3
  47. package/dist/spec/controls.js.map +1 -1
  48. package/dist/spec/help.d.ts +34 -6
  49. package/dist/spec/help.d.ts.map +1 -1
  50. package/dist/spec/help.js +21 -4
  51. package/dist/spec/help.js.map +1 -1
  52. package/dist/spec/index.d.ts +6 -3
  53. package/dist/spec/index.d.ts.map +1 -1
  54. package/dist/spec/index.js +27 -2
  55. package/dist/spec/index.js.map +1 -1
  56. package/dist/style.d.ts.map +1 -1
  57. package/dist/style.js +279 -47
  58. package/dist/style.js.map +1 -1
  59. package/dist/text.d.ts.map +1 -1
  60. package/dist/text.js +7 -2
  61. package/dist/text.js.map +1 -1
  62. package/package.json +1 -1
package/dist/style.js CHANGED
@@ -60,7 +60,7 @@ function rootBlock() {
60
60
  lines.push('}');
61
61
  return lines.join(LF);
62
62
  }
63
- /** 页级基座(非控件区):**本票不产**。
63
+ /** 页级基础(非控件区):**本票不产**。
64
64
  * 裁定 R7/施工单 B-D2(a):共享页面模板/KPI/表格/回到顶部等**无闭集归属**的样式归 #104
65
65
  * (区块组件 owner),#75 只产 11 token + 8 个闭集样式区;`extraCss` 语义被
66
66
  * 契约 doc:299-300 限死(只许技能作用域 token 覆盖块),**不得**塞共享页面模板的样式。 */
@@ -95,6 +95,13 @@ const SECTION_BUILDERS = {
95
95
  // 时靠换行分层,代价是把关闭按钮挤到第三行)。根因已由 `controls.ts` 的结构对齐修掉
96
96
  // (helpers 与静态产出器/旧层同构:`icon? + .toast-body(> .toast-title-row + detail) + .toast-close`),
97
97
  // 补丁一并删除 → 关闭按钮与标题恒同行。
98
+ // 卡面色(2026-09-12 用户裁定改用**老技能**版):深色毛玻璃,逐值取自
99
+ // 老仓 `公共组件/assets/base.js:75` 的 `.hm-toast` 串——底色 rgba(28,28,30,.94)、
100
+ // 毛玻璃 blur(20px) saturate(180%)、主字 #f0f0f0、投影 0 10px 32px rgba(0,0,0,.32)
101
+ // + 0 0 0 .5px rgba(255,255,255,.08) inset(原样的「无描边、靠内影收边」)。
102
+ // 几何(圆角 14px/内距 13-14-16/min 300/max 480/gap 12)与老仓逐值相同,本次不动。
103
+ // 取字面量而非 token:本卡是**深色面**,不随浅色 token 变——这是本次的唯一例外,记账见
104
+ // `docs/visual-spec-blocks.md` §B-12 的 2026-09-12 裁定行。
98
105
  '.' + p + 'toast {',
99
106
  ' display: flex;',
100
107
  ' align-items: flex-start;',
@@ -103,12 +110,14 @@ const SECTION_BUILDERS = {
103
110
  ' min-width: 300px;',
104
111
  ' max-width: 480px;',
105
112
  ' padding: 13px 14px 13px 16px;',
106
- ' border: 1px solid var(--line);',
107
113
  ' border-radius: 14px;',
108
- ' background: var(--card);',
109
- ' color: var(--fg);',
110
- ' box-shadow: var(--shadow);',
111
- ' font-size: 12.5px;',
114
+ ' background: rgba(28, 28, 30, .94);',
115
+ ' -webkit-backdrop-filter: blur(20px) saturate(180%);',
116
+ ' backdrop-filter: blur(20px) saturate(180%);',
117
+ ' color: #f0f0f0;',
118
+ ' box-shadow: 0 10px 32px rgba(0, 0, 0, .32), 0 0 0 .5px rgba(255, 255, 255, .08) inset;',
119
+ // #567 J4(§5.2 正文 13 吸收 13/12.5)。
120
+ ' font-size: 13px;',
112
121
  ' line-height: 1.4;',
113
122
  ' font-feature-settings: "tnum";',
114
123
  ' pointer-events: auto;',
@@ -127,13 +136,16 @@ const SECTION_BUILDERS = {
127
136
  ' transform: none;',
128
137
  ' }',
129
138
  '}',
139
+ // 失败态:老仓没有卡面描边,失败由「❌ 图标 + danger 徽章」承担;深色面上改由
140
+ // **内影那条 0.5px 细线换成功色系的红**来点一句(不新增描边、不改底色)。
130
141
  '.' + p + 'toast-danger {',
131
- ' border-color: rgba(255, 59, 48, .38);',
142
+ ' box-shadow: 0 10px 32px rgba(0, 0, 0, .32), 0 0 0 .5px rgba(255, 59, 48, .45) inset;',
132
143
  '}',
133
144
  '.' + p + 'toast-icon {',
134
145
  ' flex: 0 0 auto;',
135
146
  ' padding-top: 1px;',
136
- ' font-size: 20px;',
147
+ // #567 J4(§5.2 标题 18 吸收 17/18/20/22):装饰字形 20→18。
148
+ ' font-size: 18px;',
137
149
  ' line-height: 1;',
138
150
  '}',
139
151
  // body 是 toast 的**唯一**内容列(静态产出器与 helpers 运行时同构,见 `controls.ts`
@@ -151,15 +163,16 @@ const SECTION_BUILDERS = {
151
163
  '}',
152
164
  '.' + p + 'toast-title {',
153
165
  ' margin-bottom: 2px;',
154
- ' color: var(--fg);',
155
- ' font-size: 12.5px;',
166
+ ' color: #fff;',
167
+ // #567 J4(§5.2 正文 13 吸收 13/12.5)。
168
+ ' font-size: 13px;',
156
169
  ' font-weight: 600;',
157
170
  ' line-height: 1.4;',
158
171
  '}',
159
172
  // 运行时详情(helpers 产出 `.ilife-toast-title-detail`,在 `.ilife-toast-body` 内):
160
173
  // 与静态侧 `.ilife-toast-detail` **同排版**;此前的 `flex: 1 1 100%` 权宜补丁已随结构对齐删除。
161
174
  '.' + p + 'toast-title-detail {',
162
- ' color: var(--fg2);',
175
+ ' color: #c8c8cc;',
163
176
  ' font-size: 11px;',
164
177
  ' font-weight: 400;',
165
178
  ' line-height: 1.5;',
@@ -168,41 +181,44 @@ const SECTION_BUILDERS = {
168
181
  ' flex-shrink: 0;',
169
182
  ' padding: 2px 8px;',
170
183
  ' border-radius: 999px;',
171
- ' background: var(--soft);',
172
- ' color: var(--blue2);',
173
- ' font-size: 10px;',
184
+ ' background: rgba(255, 255, 255, .12);',
185
+ ' color: #c8c8cc;',
186
+ // #567 J4(§5.2 下限 11:10/10.5/11 一律抬到 11)。
187
+ ' font-size: 11px;',
174
188
  ' font-weight: 700;',
175
189
  ' letter-spacing: .02em;',
176
190
  ' line-height: 1.6;',
177
191
  '}',
178
192
  '.' + p + 'toast-chip-ok {',
179
- ' background: rgba(52, 199, 89, .12);',
180
- ' color: #1f8f3d;',
193
+ ' background: rgba(52, 199, 89, .18);',
194
+ ' color: #4dd96b;',
181
195
  '}',
182
196
  '.' + p + 'toast-chip-warn {',
183
- ' background: rgba(255, 149, 0, .12);',
184
- ' color: #b25000;',
197
+ ' background: rgba(255, 149, 0, .18);',
198
+ ' color: #ffb340;',
185
199
  '}',
186
200
  '.' + p + 'toast-chip-danger {',
187
- ' background: rgba(255, 59, 48, .12);',
188
- ' color: #c0392b;',
201
+ ' background: rgba(255, 59, 48, .18);',
202
+ ' color: #ff6961;',
189
203
  '}',
190
204
  '.' + p + 'toast-count {',
191
205
  ' flex-shrink: 0;',
192
206
  ' padding: 2px 8px;',
193
207
  ' border-radius: 8px;',
194
- ' color: var(--fg3);',
195
- ' font-size: 10px;',
208
+ ' background: rgba(255, 255, 255, .08);',
209
+ ' color: #c8c8cc;',
210
+ // #567 J4(§5.2 下限 11)。
211
+ ' font-size: 11px;',
196
212
  ' font-weight: 700;',
197
213
  ' font-variant-numeric: tabular-nums;',
198
214
  '}',
199
215
  '.' + p + 'toast-detail {',
200
- ' color: var(--fg2);',
216
+ ' color: #c8c8cc;',
201
217
  ' font-size: 11px;',
202
218
  ' line-height: 1.5;',
203
219
  '}',
204
220
  '.' + p + 'toast-lines {',
205
- ' color: var(--fg2);',
221
+ ' color: #c8c8cc;',
206
222
  ' font-size: 11px;',
207
223
  ' line-height: 1.55;',
208
224
  ' white-space: pre-wrap;',
@@ -211,10 +227,11 @@ const SECTION_BUILDERS = {
211
227
  ' margin: 6px 0 0;',
212
228
  ' padding: 8px 10px;',
213
229
  ' border-radius: 8px;',
214
- ' background: var(--soft);',
215
- ' color: var(--fg2);',
230
+ ' background: rgba(0, 0, 0, .32);',
231
+ ' color: #aeb0b8;',
216
232
  ' font-family: "SF Mono", monospace;',
217
- ' font-size: 10.5px;',
233
+ // #567 J4(§5.2 下限 11)。
234
+ ' font-size: 11px;',
218
235
  ' line-height: 1.5;',
219
236
  ' white-space: pre-wrap;',
220
237
  ' max-height: 140px;',
@@ -225,11 +242,13 @@ const SECTION_BUILDERS = {
225
242
  ' padding: 2px 4px;',
226
243
  ' border: 0;',
227
244
  ' background: none;',
228
- ' color: var(--ok);',
245
+ ' color: #4dd96b;',
229
246
  ' font-family: inherit;',
230
247
  ' font-size: 11px;',
231
248
  ' font-weight: 600;',
232
249
  ' cursor: pointer;',
250
+ ' -webkit-tap-highlight-color: transparent;',
251
+ ' touch-action: manipulation;',
233
252
  '}',
234
253
  '.' + p + 'toast-act:active {',
235
254
  ' opacity: .7;',
@@ -240,13 +259,16 @@ const SECTION_BUILDERS = {
240
259
  ' padding: 5px 9px;',
241
260
  ' border: 0;',
242
261
  ' border-radius: 8px;',
243
- ' background: var(--soft);',
244
- ' color: var(--blue2);',
262
+ ' background: rgba(255, 255, 255, .10);',
263
+ ' color: #34c759;',
245
264
  ' font-family: inherit;',
246
- ' font-size: 10.5px;',
265
+ // #567 J4(§5.2 下限 11)。
266
+ ' font-size: 11px;',
247
267
  ' font-weight: 500;',
248
268
  ' white-space: nowrap;',
249
269
  ' cursor: pointer;',
270
+ ' -webkit-tap-highlight-color: transparent;',
271
+ ' touch-action: manipulation;',
250
272
  '}',
251
273
  focusRing('.' + p + 'toast-act'),
252
274
  focusRing('.' + p + 'toast-close'),
@@ -272,9 +294,20 @@ const SECTION_BUILDERS = {
272
294
  ' gap: 8px;',
273
295
  ' box-sizing: border-box;',
274
296
  ' margin: 12px 0;',
297
+ // 旧层冻结值(旧 `base.css:81-91`)。≤552 的窄档内容列不到 520px 时这条不生效;
298
+ // 553–820 的内容列已超 520px(820 实测内容列 780px),须由下方 641 档放开(#427 实测)。
275
299
  ' max-width: 520px;',
276
- ' margin-left: auto;',
277
- ' margin-right: auto;',
300
+ '}',
301
+ // #238(用户 2026-09-13 二次裁定「两个按钮平分宽度」)+ #427 中档修正:
302
+ // 桌面与中档**铺满内容列**,动作条里的整行
303
+ // (`.action-row-ghost` 的 `repeat(2, minmax(0, 1fr))`)随之按内容列平分——两颗按钮各占一半。
304
+ // 放开位取 641(`blocks.ts:1899` 与 `helpShell` 640 档同源的桌面侧补集,不新增断点值;
305
+ // 原 821 位在 641–820 留出 260px 空白,KPI 栅格同档已铺满 780px,见 #427 缺口 1),
306
+ // 故 ≤640 的窄档逐像素不动、≥641 与改前 821 档同为 `max-width: none`(≥821 零变)。
307
+ '@media (min-width: 641px) {',
308
+ ' .' + p + 'action-bar {',
309
+ ' max-width: none;',
310
+ ' }',
278
311
  '}',
279
312
  '.' + p + 'action-row {',
280
313
  ' display: grid;',
@@ -282,7 +315,30 @@ const SECTION_BUILDERS = {
282
315
  ' gap: 8px;',
283
316
  '}',
284
317
  '.' + p + 'action-row-ghost {',
285
- ' grid-template-columns: minmax(0, 1fr);',
318
+ // 复制按钮那一行**平分整行**(#247 用户 2026-09-12 返修):两列而不是单列——单列只在「一行一颗」时
319
+ // 才好看,一旦有第二颗(复制数据 + 复制日志)就变成「一颗铺满、一颗缩成内容宽」。列数取冻结
320
+ // `ACTION_BAR_DEFAULTS.evenRowPairs`(既有「偶数一行 2 个」的口径,不是新拍的数字)。
321
+ ' grid-template-columns: repeat(' + ACTION_BAR_DEFAULTS.evenRowPairs + ', minmax(0, 1fr));',
322
+ '}',
323
+ // #249:窄屏那档菜单**以整行做锚点**(同档 `copyButton` 区把包裹层改成 `position: static`)——
324
+ // 行盒=内容宽,菜单 `left:0; right:0` 落进去就恒在视口内,与那颗按钮落在左格还是右格无关。
325
+ // 只在窄屏挂,桌面档的锚点仍是包裹层(菜单贴按钮右缘,用户已验收的样子不动)。
326
+ '@media (max-width: ' + TOAST_DEFAULTS.mobileMaxPx + 'px) {',
327
+ ' .' + p + 'action-row-ghost {',
328
+ ' position: relative;',
329
+ ' }',
330
+ '}',
331
+ // **#654 后续(负责人 2026-09-16 第二次验收,口径变更)**:#427 的「≤640 单列」**撤回**。
332
+ // 负责人明确「底部应该是并排的两颗:复制数据 + 复制日志」,且同仓参考页(`D:\ilife-验收` 根下
333
+ // 01~05,已验收)**在 390 档也是两列并排** ⇒ ghost 行**各宽档恒两列平分**(回到 #247 口径)。
334
+ // 390 档两列各约 175px 偏窄,由「必须并排」这条更高的要求承担(负责人裁定)。
335
+ // 原规则 `@media (max-width:640px){ .…action-row-ghost{ grid-template-columns: minmax(0,1fr) } }`
336
+ // 已删;`page-finish-427.test.mjs` 的对应断言已按新口径改钉(改成「不许再有单列覆盖」)。
337
+ // #654:ghost 行**只有一颗真按钮**时,让它在整行轨道里铺满——#336 当年靠「补一颗禁用态假按钮」
338
+ // 凑第二格,为的就是保这个几何(不占半格、也不缩成内容宽);现在列数跟着颗数走,假控件不再需要
339
+ // (负责人 2026-09-16 验收:读页面底部那颗点不动的「复制日志」是缺陷)。
340
+ '.' + p + 'action-row-ghost-single > .' + p + 'copy-btn {',
341
+ ' grid-column: 1 / -1;',
286
342
  '}',
287
343
  '.' + p + 'action-btn {',
288
344
  ' display: inline-flex;',
@@ -299,6 +355,8 @@ const SECTION_BUILDERS = {
299
355
  ' font-weight: ' + ACTION_BAR_DEFAULTS.fontWeight + ';',
300
356
  ' line-height: 1;',
301
357
  ' cursor: pointer;',
358
+ ' -webkit-tap-highlight-color: transparent;',
359
+ ' touch-action: manipulation;',
302
360
  '}',
303
361
  '.' + p + 'action-btn-primary {',
304
362
  ' background: var(--blue);',
@@ -313,7 +371,8 @@ const SECTION_BUILDERS = {
313
371
  ' background: var(--card);',
314
372
  ' color: var(--blue2);',
315
373
  '}',
316
- // #179 触控目标:窄屏按钮抬到 44px(桌面 40px 沿用 `ACTION_BAR_DEFAULTS.minHeightPx`,不改冻结值)。
374
+ // #179 触控目标:窄屏按钮抬到 44px。**#525 起两个宽档同值**(`minHeightPx` 40 → 44,见
375
+ // `spec/controls.ts`);本条留在这里只为不动既有选择器与既有媒体查询。
317
376
  '@media (max-width: ' + TOAST_DEFAULTS.mobileMaxPx + 'px) {',
318
377
  ' .' + p + 'action-btn {',
319
378
  ' min-height: 44px;',
@@ -338,6 +397,8 @@ const SECTION_BUILDERS = {
338
397
  ' font-weight: ' + ACTION_BAR_DEFAULTS.fontWeight + ';',
339
398
  ' line-height: 1;',
340
399
  ' cursor: pointer;',
400
+ ' -webkit-tap-highlight-color: transparent;',
401
+ ' touch-action: manipulation;',
341
402
  ' transition: transform .45s cubic-bezier(.34, 1.56, .64, 1), background-color .2s ease;',
342
403
  '}',
343
404
  '.' + p + 'copy-btn-primary {',
@@ -358,7 +419,7 @@ const SECTION_BUILDERS = {
358
419
  ' transform: scale(.96);',
359
420
  '}',
360
421
  // W3(H-16 双反馈的 CSS 侧):复制成功态变绿。规格 `docs/visual-spec-help.md:195,197`
361
- // 「按钮变绿进入 `copied` 态并跑 450ms 弹簧动画」;弹簧 = 基座 `transition: transform .45s
422
+ // 「按钮变绿进入 `copied` 态并跑 450ms 弹簧动画」;弹簧 = 基础 `transition: transform .45s
362
423
  // cubic-bezier(.34, 1.56, .64, 1)`(B1 `benchmark-visual-spec.md:279,645`),本节只补**变绿**。
363
424
  // 类名由**运行时**添加(`copied`,非 `ilife-` 前缀 → 不占样式区命名空间);
364
425
  // 本票**不改** helpers 的复制反馈行为,移交落点见契约 §8.11.1 FX-75-11。
@@ -367,12 +428,172 @@ const SECTION_BUILDERS = {
367
428
  ' background: var(--ok);',
368
429
  ' color: var(--card);',
369
430
  '}',
370
- // #179 触控目标:窄屏按钮抬到 44px(桌面 40px 沿用 `ACTION_BAR_DEFAULTS.minHeightPx`,不改冻结值)。
431
+ // **禁用态必须看得出来**(#525 收口 · 两处公共债之一):读页面没有写库日志,复制区按 #336
432
+ // 自动补的那颗「复制日志」带 `disabled` 属性、可**从 #336 起就没有任何置灰规则**——看着与
433
+ // 旁边那颗能点的按钮一模一样(`t524-改前读数.md` §7.1 第 3 条只核了属性、没核观感)。
434
+ //
435
+ // **#525 第二轮返修(禁用态只看得见、读不清)**:上一版照 HELP 页 `button.copy:disabled`
436
+ // 取 `opacity:.45` 压**整颗**按钮,实测有效文字色被混成 `#91b9e9` 压白底,对比度只有
437
+ // **2.03:1**(`.scratch/t525c/before.json`;读法=把 opacity 混进底色后按 WCAG 相对亮度算)——
438
+ // 连 AA 大字的 3:1 都不到,等于把「禁用」写成了「看不清」。
439
+ // 改法=**不压整颗**,改三处、各自都是一个信号:① 文字色换 `--fg3`;② 底压成 `--soft`;
440
+ // ③ 描边降到 `--line`(「蓝=可点」这条信号只留在可点态)。`opacity` 恒 1(不继承、不叠乘)。
441
+ // 2.03 → 3.47 的两行读数落 `.scratch/t525c/disabled-contrast.json`,探针 `.scratch/t525c/probe.mjs` 可复跑。
442
+ '.' + p + 'copy-btn[disabled],',
443
+ '.' + p + 'copy-btn:disabled {',
444
+ ' opacity: 1;',
445
+ ' border-color: var(--line);',
446
+ ' background: var(--soft);',
447
+ ' color: var(--fg3);',
448
+ ' cursor: not-allowed;',
449
+ '}',
450
+ '.' + p + 'copy-btn[disabled]:active,',
451
+ '.' + p + 'copy-btn:disabled:active {',
452
+ ' transform: none;',
453
+ '}',
454
+ // #179 触控目标:窄屏按钮抬到 44px。**#525 起这条在两个宽档上是同一个值**(`minHeightPx`
455
+ // 由 40 提到 44,见 `spec/controls.ts`)——留在这里是为了不动既有选择器与既有媒体查询;
456
+ // 桌面档不再是 40px。
371
457
  '@media (max-width: ' + TOAST_DEFAULTS.mobileMaxPx + 'px) {',
372
458
  ' .' + p + 'copy-btn {',
373
459
  ' min-height: 44px;',
374
460
  ' }',
375
461
  '}',
462
+ // ── 复制数据的三格式菜单(#247,2026-09-12 用户裁定「恢复老仓原样」)───────────────
463
+ // 逐值取自老仓 `卡路里/templates/crud_receipt.html`(可点版本 `2262fee1~1`)的 `.fmt-menu`/
464
+ // `.fmt-item`/`.fmt-item:hover`/`.fmt-item span` 四串;老仓的 `--border`/`--soft` 分别
465
+ // 对到本仓的 `--line`/`--soft`。**复用本区命名空间**(`CONTROL_STYLE_SECTIONS` 是冻结闭集,
466
+ // 不新增第 9 个区);类名不对 `copy-btn` 用前缀相似的名字(`.copy-menu-btn` 会被
467
+ // `.copy-btn` 的规则一并命中,多出一圈胶囊底),故开合器只带 `copy-btn copy-btn-ghost` 两颗类。
468
+ // **偏离 1 处(记账)**:菜单圆角取 **14px**(老仓是 12px)——#89 R-9 的 H-10 把
469
+ // 全部非图表 CSS 的 `border-radius` 钉在 `{8px,14px,20px,999px,50%}`,12px 会让你越出闭集;
470
+ // 14px 是本仓既有卡片圆角(toast 同值),几何其余逐值不动(`bottom`/`right`/`min-width`/
471
+ // `max-width`/`padding`/投影/项内距与字号全取老仓)。
472
+ '.' + p + 'copy-menu-wrap {',
473
+ // 宽度**跟着轨道走**(#247 用户 2026-09-12 返修):复制数据与复制日志在 ghost 行里**平分整行**
474
+ // (各占一格,见 `.action-row-ghost` 的两列),两颗宽高一致——不再出现「一颗铺满一行、一颗缩成
475
+ // 内容宽」的一胖一瘦。故这里**不写** `justify-self: start`(那会让本层缩成内容宽、连带按钮也缩)。
476
+ ' position: relative;',
477
+ ' display: flex;',
478
+ ' align-items: center;',
479
+ ' width: 100%;',
480
+ '}',
481
+ // 开合器按钮铺满自己那一格:菜单的 `right: 0` 才贴着按钮右缘(== 格子右缘),两格时居中偏右。
482
+ '.' + p + 'copy-menu-wrap > .' + p + 'copy-btn {',
483
+ ' width: 100%;',
484
+ '}',
485
+ // **#525 第二轮·三角用 CSS 画**(用户裁定第 5 条:符号顶替了设计):
486
+ // 可见文字里的 `▾` 已从 `controls.ts` 的 `copyMenuHtml` 删掉(那句「可以选格式」改住 `aria-label`),
487
+ // 这里用 `border` 拼一个向下的小三角 + `rotate(180deg)` 做开合态——**一个字符都不打**,
488
+ // 也就不会被字体/读屏/抓取器当成内容。画的规矩:零宽零高的盒子靠 `border-top` 出形,
489
+ // `margin-left: 6px` 与基础的 `gap: 6px` 同值(文字与三角的间距连两处取值都不新造)。
490
+ // 开合态由运行时加的 `.copy-menu-open` 落色(与菜单同一个类,不新增第二个开关)。
491
+ '.' + p + 'copy-menu-wrap > .' + p + 'copy-btn::after {',
492
+ ' content: "";',
493
+ ' width: 0;',
494
+ ' height: 0;',
495
+ ' margin-left: 6px;',
496
+ ' border-left: 4px solid transparent;',
497
+ ' border-right: 4px solid transparent;',
498
+ ' border-top: 5px solid currentColor;',
499
+ ' transition: transform .16s ease-out;',
500
+ '}',
501
+ '.copy-menu-open.' + p + 'copy-menu-wrap > .' + p + 'copy-btn::after {',
502
+ ' transform: rotate(180deg);',
503
+ '}',
504
+ '.' + p + 'copy-menu {',
505
+ ' position: absolute;',
506
+ ' bottom: calc(100% + 8px);',
507
+ ' right: 0;',
508
+ ' z-index: 20;',
509
+ ' min-width: 200px;',
510
+ // 浮层开合走 opacity(不是 `display`/`hidden`):一是 CSS 过渡不被跳过,二是开合不重排整页。
511
+ // 关着时同时收掉命中,免得看不见的菜单项还能被点到;`visibility` 可过渡 ⇒ 收起仍有淡出。
512
+ ' box-sizing: border-box;',
513
+ ' max-width: calc(100vw - 32px);',
514
+ ' padding: 6px;',
515
+ ' border: 1px solid var(--line);',
516
+ ' border-radius: 14px;',
517
+ ' background: var(--card);',
518
+ ' box-shadow: 0 8px 24px rgba(0, 0, 0, .14);',
519
+ ' opacity: 0;',
520
+ ' visibility: hidden;',
521
+ ' pointer-events: none;',
522
+ ' transition: opacity .16s ease-out, visibility .16s ease-out;',
523
+ '}',
524
+ // 开着的那条:`copy-menu-open` 是**运行时**加的类(与 #121 的 `copied` 同口径——类名不加
525
+ // `ilife-` 前缀,故不占样式区命名空间,也不作为 `ilife-` 类名进跨文件检查);选择器把裸类
526
+ // 排在**前**面,这样从 CSS 里扫出来的 `ilife-` 类名仍只有 `ilife-copy-menu`(T10 ② 认得出产出者)。
527
+ '.copy-menu-open.' + p + 'copy-menu {',
528
+ ' opacity: 1;',
529
+ ' visibility: visible;',
530
+ ' pointer-events: auto;',
531
+ '}',
532
+ '.' + p + 'copy-menu-item {',
533
+ // #152 返修:补最小高——老仓 `.fmt-item` 只写 `padding:10px 12px` + 13px 字,算出来 37px,
534
+ // 低于本仓同区复制按钮的冻结最小值(`spec/controls.ts:308` 的 44px,#525 由 40 改到 44)。44 不另写一个数,
535
+ // 引用同一个定义地;窄屏那档仍由下面的 `min-height:44px` 托底(老仓 `.fmt-item` 是 36px)。
536
+ ' min-height: ' + ACTION_BAR_DEFAULTS.minHeightPx + 'px;',
537
+ ' display: flex;',
538
+ ' justify-content: space-between;',
539
+ ' gap: 14px;',
540
+ ' width: 100%;',
541
+ ' padding: 10px 12px;',
542
+ ' border: none;',
543
+ ' border-radius: 8px;',
544
+ ' background: none;',
545
+ ' color: var(--fg);',
546
+ ' font-family: inherit;',
547
+ ' font-size: 13px;',
548
+ ' text-align: left;',
549
+ ' cursor: pointer;',
550
+ ' -webkit-tap-highlight-color: transparent;',
551
+ ' touch-action: manipulation;',
552
+ '}',
553
+ '.' + p + 'copy-menu-item:hover {',
554
+ ' background: var(--soft);',
555
+ '}',
556
+ // 标签(格式键):显式一条——省得继承菜单项的既有值,也让「CSS 里每个类名都有产出者、
557
+ // 每个产出类名都有 CSS」这条跨文件检查两面都成立(`style.test.mjs` T10)。
558
+ '.' + p + 'copy-menu-item > .' + p + 'copy-menu-label {',
559
+ ' color: var(--fg);',
560
+ ' font-size: 13px;',
561
+ '}',
562
+ // 用途提示:老仓是 `.fmt-item span`(后代选择器,两项 span 都染 11px 灰);本仓显式给类名,
563
+ // 标签那半仍取 13px/`--fg`(上一条)。选择器用 `>` ——本仓跨文件交叉检查按**整段选择器**比对。
564
+ // **返修**:字色由 `--fg3`(#86868b)改为 `--fg2`(#6e6e73)——11px 属正文,AA 要 4.5:1,
565
+ // 前者对白卡片仅 3.62:1、后者 5.07:1;与同批「12px + `--fg3`」那 7 处改用的是同一个做法(#152)。
566
+ '.' + p + 'copy-menu-item > .' + p + 'copy-menu-hint {',
567
+ ' color: var(--fg2);',
568
+ ' font-size: 11px;',
569
+ ' white-space: nowrap;',
570
+ '}',
571
+ // #179 触控目标:窄屏菜单项抬到 44px(与复制按钮同一口径;老仓 `.fmt-item` 是 36px 上下)。
572
+ '@media (max-width: ' + TOAST_DEFAULTS.mobileMaxPx + 'px) {',
573
+ ' .' + p + 'copy-menu-item {',
574
+ ' min-height: 44px;',
575
+ ' }',
576
+ // 窄屏改**贴按钮算**(老仓那句「右对齐视口内,手机不超界」的落法):桌面档按钮靠左,200px 的菜单
577
+ // 贴按钮会有一半越到屏幕左外;窄屏按钮在右半格,故改成
578
+ // 「宽 = 视口 − 左右各 16px、右缘贴按钮右缘」——既恒在视口内,又跟着按钮(滚动时不漂、不挡按钮),
579
+ // 也不会像 `position: fixed` 那样把浮层钉在视口底、压住别的正文。
580
+ //
581
+ // **#249 修正**:原先「宽 = 视口 − 32px + 右缘贴按钮」只在按钮位于**右格**时成立;复制数据排在
582
+ // 复制日志前头(`renderActionBar` 的 ghost 行顺序),窄屏上它在**左格**,菜单右缘贴左格右缘 ⇒
583
+ // 菜单左缘跑到视口外 167px(390 实测 x=-167,三项的格式名全看不见)。修法:窄屏把锚点从**包裹层**
584
+ // 换成**整行**(行在这一档挂 `position: relative`,见 `actionBar` 区);菜单 `left:0; right:0`
585
+ // 就落在行盒(=内容宽 358)里,按钮在哪一格都在视口内,且仍是「跟着按钮走」的绝对定位浮层。
586
+ ' .' + p + 'copy-menu-wrap {',
587
+ ' position: static;',
588
+ ' }',
589
+ ' .' + p + 'copy-menu {',
590
+ ' left: 0;',
591
+ ' right: 0;',
592
+ ' width: auto;',
593
+ ' min-width: 0;',
594
+ ' max-width: none;',
595
+ ' }',
596
+ '}',
376
597
  focusRing('.' + p + 'copy-btn'),
377
598
  ].join(LF),
378
599
  /** statusBadge —— 类名产出者 `controls.ts:702-707`。
@@ -382,7 +603,7 @@ const SECTION_BUILDERS = {
382
603
  * ok `#e6f7ec`/`#1f8c3d`、warn `#fff5e0`/`#a25b00`、danger `#fff0ee`/`#a83228`、
383
604
  * empty `#f0f0f3`/`var(--fg2)`(empty 字色旧层 `base.css:228` 为 `#6e6e73`=`--fg2` 解析值,底色为实色 `#f0f0f3`)。
384
605
  * **返修 W2**:上一轮误用 `#1f8f3d`/`#b25000`(全仓无出处)+ 12% alpha 底色,且注释谎称「沿用旧值」
385
- * → 已改回旧逐值,注释与实现一致(非 token 硬编码色按 D-5 以 CSS 常量落地,不新增 token 名)。 */
606
+ * → 已改回旧逐值,注释与实现一致(非 token 硬编码色按 D-5 以 CSS 常量实施,不新增 token 名)。 */
386
607
  statusBadge: (p) => [
387
608
  '.' + p + 'status-badge {',
388
609
  ' display: inline-flex;',
@@ -424,14 +645,16 @@ const SECTION_BUILDERS = {
424
645
  ' color: var(--fg2);',
425
646
  '}',
426
647
  '.' + p + 'empty-icon {',
427
- ' font-size: 40px;',
648
+ // #567 J4(§5.2 大数 28 吸收 26/28/32/40):装饰图标 40→28。
649
+ ' font-size: 28px;',
428
650
  ' line-height: 1.2;',
429
651
  ' opacity: .5;',
430
652
  '}',
431
653
  '.' + p + 'empty-text {',
432
654
  ' margin-top: 8px;',
433
655
  ' color: var(--fg);',
434
- ' font-size: 17px;',
656
+ // #567 J4(§5.2 标题 18 吸收 17/18/20/22)。
657
+ ' font-size: 18px;',
435
658
  ' font-weight: 600;',
436
659
  '}',
437
660
  '.' + p + 'empty-hint {',
@@ -590,6 +813,7 @@ const SECTION_BUILDERS = {
590
813
  ' font-size: 13px;',
591
814
  ' font-weight: 600;',
592
815
  ' cursor: pointer;',
816
+ ' touch-action: manipulation;',
593
817
  '}',
594
818
  '.' + p + 'help-shell-tab-icon {',
595
819
  ' font-size: 14px;',
@@ -712,6 +936,7 @@ const SECTION_BUILDERS = {
712
936
  ' font-size: 13px;',
713
937
  ' font-weight: 600;',
714
938
  ' cursor: pointer;',
939
+ ' touch-action: manipulation;',
715
940
  ' list-style: none;',
716
941
  '}',
717
942
  '.' + p + 'help-shell-sheet-summary::-webkit-details-marker {',
@@ -761,11 +986,13 @@ const SECTION_BUILDERS = {
761
986
  ' font-size: ' + ACTION_BAR_DEFAULTS.fontSizePx + 'px;',
762
987
  ' font-weight: ' + ACTION_BAR_DEFAULTS.fontWeight + ';',
763
988
  ' line-height: 1;',
764
- // #121:H-16「跑 450ms 弹簧动画」——弹簧口径**逐字复用** `copyButton` 基座(B1
989
+ // #121:H-16「跑 450ms 弹簧动画」——弹簧口径**逐字复用** `copyButton` 基础(B1
765
990
  // `benchmark-visual-spec.md:279,645`:`transform .45s cubic-bezier(.34,1.56,.64,1)`),
766
991
  // 不新增机制、不新增关键帧;按下态同 `.ilife-copy-btn:active`(scale .96)。
767
992
  ' transition: transform .45s cubic-bezier(.34, 1.56, .64, 1), background-color .2s ease;',
768
993
  ' cursor: pointer;',
994
+ ' -webkit-tap-highlight-color: transparent;',
995
+ ' touch-action: manipulation;',
769
996
  '}',
770
997
  '.' + p + 'help-shell-btn:active {',
771
998
  ' transform: scale(.96);',
@@ -801,6 +1028,7 @@ const SECTION_BUILDERS = {
801
1028
  ' font-size: 14px;',
802
1029
  ' font-weight: 600;',
803
1030
  ' cursor: pointer;',
1031
+ ' touch-action: manipulation;',
804
1032
  '}',
805
1033
  '.' + p + 'help-shell-count {',
806
1034
  ' color: var(--fg3);',
@@ -899,6 +1127,7 @@ const SECTION_BUILDERS = {
899
1127
  ' font-size: 12px;',
900
1128
  ' line-height: 1;',
901
1129
  ' cursor: pointer;',
1130
+ ' touch-action: manipulation;',
902
1131
  '}',
903
1132
  focusRing('.' + p + 'help-shell-tab-search-clear'),
904
1133
  '.' + p + 'help-shell-page-hitcount {',
@@ -921,9 +1150,11 @@ const SECTION_BUILDERS = {
921
1150
  ' font-size: 12px;',
922
1151
  ' font-weight: 600;',
923
1152
  ' line-height: 1;',
924
- // #121:弹簧口径同 `copyButton` 基座(H-16「450ms 弹簧」),成功态 `.copied` 见上方 helpShell 区。
1153
+ // #121:弹簧口径同 `copyButton` 基础(H-16「450ms 弹簧」),成功态 `.copied` 见上方 helpShell 区。
925
1154
  ' transition: transform .45s cubic-bezier(.34, 1.56, .64, 1), background-color .2s ease;',
926
1155
  ' cursor: pointer;',
1156
+ ' -webkit-tap-highlight-color: transparent;',
1157
+ ' touch-action: manipulation;',
927
1158
  '}',
928
1159
  '.' + p + 'help-shell-card-copy:active {',
929
1160
  ' transform: scale(.96);',
@@ -984,6 +1215,7 @@ const SECTION_BUILDERS = {
984
1215
  ' transition: opacity .2s ease;',
985
1216
  ' z-index: 9998;',
986
1217
  ' cursor: pointer;',
1218
+ ' touch-action: manipulation;',
987
1219
  '}',
988
1220
  '.' + p + 'help-shell-btn-backtop-show {',
989
1221
  ' opacity: 1;',
@@ -1048,7 +1280,7 @@ function normalizePrefix(input) {
1048
1280
  /** `extraCss` 违规错误:**不导出**(与 #74 `TemplateError`/#76 `bad-input` 同口径——
1049
1281
  * 冻结面 `SPEC_FROZEN_SURFACE` 无该运行时条目,调用方按 `name`/`code` 判定)。
1050
1282
  * code 三值 = 契约 doc:299-300 三条「不得」的落点:
1051
- * `extra-css-root`(改写基座 `:root`)/`extra-css-forbidden-token`(Q14 禁入项)/
1283
+ * `extra-css-root`(改写基础 `:root`)/`extra-css-forbidden-token`(Q14 禁入项)/
1052
1284
  * `extra-css-dark-scheme`(深色区选择器,doc:292)。 */
1053
1285
  class StyleSheetExtraCssError extends Error {
1054
1286
  code;
@@ -1059,7 +1291,7 @@ class StyleSheetExtraCssError extends Error {
1059
1291
  }
1060
1292
  }
1061
1293
  /** CSS 块注释剥除(`/* … *\/` → 空格):**判定前先剥注释**——注释里的 `:root`/`--pink` 只是说明文字,
1062
- * 不是「改写基座/引入禁入项」;反之注释**不能**藏住真实声明(`/*c*\/:root{}` 剥后仍命中)。 */
1294
+ * 不是「改写基础/引入禁入项」;反之注释**不能**藏住真实声明(`/*c*\/:root{}` 剥后仍命中)。 */
1063
1295
  function stripCssComments(css) {
1064
1296
  return css.replace(/\/\*[\s\S]*?\*\//g, ' ');
1065
1297
  }
@@ -1072,9 +1304,9 @@ function hasBoundedToken(text, token) {
1072
1304
  }
1073
1305
  /** `extraCss` 三禁强制(**编排者返修裁定:D3 修订**,见契约 §8.11)。
1074
1306
  *
1075
- * 理由:契约 doc:299-300 的三条「不得」(不得改写基座值/不得引入 Q14 禁入项/不得引入深色区)
1307
+ * 理由:契约 doc:299-300 的三条「不得」(不得改写基础值/不得引入 Q14 禁入项/不得引入深色区)
1076
1308
  * 此前**无任何落点**(D3 原裁定「不校验、纯调用方责任」)→ 本票补最小强制:
1077
- * - (a) 含 `:root` 选择器 → 改写基座;
1309
+ * - (a) 含 `:root` 选择器 → 改写基础;
1078
1310
  * - (b) 含 `STYLE_FORBIDDEN_TOKENS`(`--r-xl`/`--pink`);
1079
1311
  * - (c) 含深色区选择器(`[data-theme`/`prefers-color-scheme: dark`)。
1080
1312
  * 命中**任一**即抛错;**合法技能作用域覆盖块**(`.ilife-<skill>{--blue:…}`)照常通过。
@@ -1088,7 +1320,7 @@ function assertExtraCss(extraCss) {
1088
1320
  const code = stripCssComments(extraCss);
1089
1321
  // `:root` 是伪类名(CSS 伪类名大小写不敏感)→ 用 `/i` 拦住 `:ROOT` 之类的等价写法。
1090
1322
  if (/:root(?![A-Za-z0-9_-])/i.test(code)) {
1091
- throw new StyleSheetExtraCssError('extra-css-root', 'buildStyleSheet: extraCss 不得改写基座(命中 `:root` 选择器);技能主题只许用 `.ilife-<skill>` 作用域覆盖块(契约 doc:299)');
1323
+ throw new StyleSheetExtraCssError('extra-css-root', 'buildStyleSheet: extraCss 不得改写基础(命中 `:root` 选择器);技能主题只许用 `.ilife-<skill>` 作用域覆盖块(契约 doc:299)');
1092
1324
  }
1093
1325
  for (const forbidden of STYLE_FORBIDDEN_TOKENS) {
1094
1326
  if (hasBoundedToken(code, forbidden)) {