base-paint 0.1.0 → 0.3.0

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 (64) hide show
  1. package/dist/blocks.d.ts +169 -0
  2. package/dist/blocks.d.ts.map +1 -0
  3. package/dist/blocks.js +862 -0
  4. package/dist/blocks.js.map +1 -0
  5. package/dist/charts.d.ts +62 -0
  6. package/dist/charts.d.ts.map +1 -0
  7. package/dist/charts.js +1677 -0
  8. package/dist/charts.js.map +1 -0
  9. package/dist/contract.d.ts.map +1 -1
  10. package/dist/contract.js +7 -1
  11. package/dist/contract.js.map +1 -1
  12. package/dist/controls.d.ts +139 -0
  13. package/dist/controls.d.ts.map +1 -0
  14. package/dist/controls.js +1182 -0
  15. package/dist/controls.js.map +1 -0
  16. package/dist/help.d.ts +24 -0
  17. package/dist/help.d.ts.map +1 -0
  18. package/dist/help.js +604 -0
  19. package/dist/help.js.map +1 -0
  20. package/dist/index.d.ts +21 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +21 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/spec/charts.d.ts +222 -0
  25. package/dist/spec/charts.d.ts.map +1 -0
  26. package/dist/spec/charts.js +35 -0
  27. package/dist/spec/charts.js.map +1 -0
  28. package/dist/spec/controls.d.ts +329 -0
  29. package/dist/spec/controls.d.ts.map +1 -0
  30. package/dist/spec/controls.js +119 -0
  31. package/dist/spec/controls.js.map +1 -0
  32. package/dist/spec/help.d.ts +353 -0
  33. package/dist/spec/help.d.ts.map +1 -0
  34. package/dist/spec/help.js +167 -0
  35. package/dist/spec/help.js.map +1 -0
  36. package/dist/spec/index.d.ts +40 -0
  37. package/dist/spec/index.d.ts.map +1 -0
  38. package/dist/spec/index.js +164 -0
  39. package/dist/spec/index.js.map +1 -0
  40. package/dist/spec/style.d.ts +46 -0
  41. package/dist/spec/style.d.ts.map +1 -0
  42. package/dist/spec/style.js +39 -0
  43. package/dist/spec/style.js.map +1 -0
  44. package/dist/spec/template.d.ts +363 -0
  45. package/dist/spec/template.d.ts.map +1 -0
  46. package/dist/spec/template.js +252 -0
  47. package/dist/spec/template.js.map +1 -0
  48. package/dist/spec/text.d.ts +157 -0
  49. package/dist/spec/text.d.ts.map +1 -0
  50. package/dist/spec/text.js +83 -0
  51. package/dist/spec/text.js.map +1 -0
  52. package/dist/style.d.ts +19 -0
  53. package/dist/style.d.ts.map +1 -1
  54. package/dist/style.js +1091 -0
  55. package/dist/style.js.map +1 -1
  56. package/dist/template.d.ts +27 -0
  57. package/dist/template.d.ts.map +1 -0
  58. package/dist/template.js +362 -0
  59. package/dist/template.js.map +1 -0
  60. package/dist/text.d.ts +19 -0
  61. package/dist/text.d.ts.map +1 -0
  62. package/dist/text.js +448 -0
  63. package/dist/text.js.map +1 -0
  64. package/package.json +5 -3
package/dist/style.js CHANGED
@@ -2,7 +2,22 @@
2
2
  *
3
3
  * 红线:样式只抖 render——改样式只改本文件,link-core/combos/单品包
4
4
  * 禁止自带样式常量。类名前缀统一 STYLE_PREFIX,token 表冻结。
5
+ *
6
+ * 本文件同时是 **#75 共享样式资产的唯一产出者**(契约 §3.2/§6.2):
7
+ * `buildStyleSheet()` 产出的 `css` 即 `<!--SHARED-CSS-->` 的填充物
8
+ * (`input.assets.sharedCssText`,契约 `docs/base-paint-contract.md:163`)。
9
+ * 逐条纪律(契约行号):
10
+ * - `:root` 的 11 个 token 逐值取 `CSS_VAR_TOKENS`(doc:273-289),**不自造第二份表**(doc:303);
11
+ * - 8 个样式区取 `CONTROL_STYLE_SECTIONS` 闭集(doc:295),类名自动加 `prefix`;
12
+ * - 产出**不含** `STYLE_FORBIDDEN_TOKENS`(Q14)、**不含**深色区(doc:292);
13
+ * - `charts` 区文本**复用** `charts.ts` 的唯一产出者 `chartsCss`,**不得重述**(#78 结论);
14
+ * - 与既有 `STYLE_TOKENS`(9 个深色 JS token)**并存**、语义互不覆盖(doc:293);
15
+ * - `extraCss` 末尾**原样追加**、**不按技能名分支**(doc:299-300),并执行**三禁强制**
16
+ * (`:root` 改写/Q14 禁入项/深色区 → 抛不导出的 `StyleSheetError`;D3 修订见 §8.11)。
5
17
  */
18
+ import { chartsCss } from './charts.js';
19
+ import { ACTION_BAR_DEFAULTS, TOAST_DEFAULTS } from './spec/index.js';
20
+ import { CONTROL_STYLE_SECTIONS, CSS_VAR_TOKENS, STYLE_FORBIDDEN_TOKENS, STYLE_SHEET_ID } from './spec/style.js';
6
21
  export const STYLE_PREFIX = 'ilife-';
7
22
  export const STYLE_VERSION = '0.1.0';
8
23
  export const STYLE_TOKENS = Object.freeze({
@@ -24,4 +39,1080 @@ export function cx(...names) {
24
39
  export function token(name) {
25
40
  return String(STYLE_TOKENS[name]);
26
41
  }
42
+ /* ── #75 共享样式资产(契约 §3.2/§6.2) ───────────────────────────────── */
43
+ /** 换行(仓库口径:`String.fromCharCode(10)`,不写字面 `\n`)。 */
44
+ const LF = String.fromCharCode(10);
45
+ /** 样式区名 → kebab 类名根(`actionBar` → `action-bar`)。
46
+ * **只用于生成注释头**:真实类名恒取各控件产出器里的字面量(见各区注释),
47
+ * 区名 kebab 与类名根并非一一对应(如 `copyButton` 的类名根是 `copy-btn`、
48
+ * `emptyState` 是 `empty`、`errorReceipt` 是 `error`)——**不得**用本函数臆造类名。 */
49
+ function sectionSlug(section) {
50
+ return section.replace(/[A-Z]/g, (ch) => '-' + ch.toLowerCase());
51
+ }
52
+ /** `:root` 的 11 个冻结 token 块:逐值取 `CSS_VAR_TOKENS`,顺序即常量声明序。
53
+ * 形态与契约 doc:276-288 的 CSS 块一致(`name + ': ' + value` 带空格)。 */
54
+ function rootBlock() {
55
+ const lines = [':root {'];
56
+ for (const name of Object.keys(CSS_VAR_TOKENS)) {
57
+ lines.push(' ' + name + ': ' + CSS_VAR_TOKENS[name] + ';');
58
+ }
59
+ lines.push('}');
60
+ return lines.join(LF);
61
+ }
62
+ /** 页级基座(非控件区):**本票不产**。
63
+ * 裁定 R7/施工单 B-D2(a):页面壳/KPI/表格/回到顶部等**无闭集归属**的样式归 #104
64
+ * (区块组件 owner),#75 只产 11 token + 8 个闭集样式区;`extraCss` 语义被
65
+ * 契约 doc:299-300 限死(只许技能作用域 token 覆盖块),**不得**塞壳层样式。 */
66
+ /** 主色 alpha 派生(`ghostBorderAlpha` 等):RGB 与 `--blue` 同源(`#007aff`)。
67
+ * 这是**派生字面量**、不是第二份 token 表(不新增 token 名,doc:299)。 */
68
+ const BLUE_RGB = '0, 122, 255';
69
+ /** 正文栈(H-06:首位 `"SF Pro Display"`,后接系统兜底+`"Noto Sans SC"`)。
70
+ * **局部 CSS 常量**、不是 token(D-5 纪律:不新增 token 名);等宽栈不动(D-13,各 `font-family: "SF Mono", monospace` 原样保留)。
71
+ * B1 原栈见 `docs/research/benchmark-visual-spec.md:105`(`body`);按 #89 返修方案把 `"SF Pro Display"` 提首位、
72
+ * 尾部补 `"Noto Sans SC"`(修前正文 computed 回落实测,CJK 渲染不变);落点仅 `.ilife-help-shell`(T22:壳层样式归 #104,不得产 `body` 规则)。 */
73
+ const BODY_FONT_STACK = '"SF Pro Display", -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", "Noto Sans SC", sans-serif';
74
+ /** 焦点环(视觉尺 H-20 强制项):`:focus-visible` 覆盖全部可交互控件。 */
75
+ function focusRing(selector) {
76
+ return selector + ':focus-visible {' + LF + ' outline: 2px solid var(--blue);' + LF + ' outline-offset: 2px;' + LF + '}';
77
+ }
78
+ /** 各区 CSS 文本(闭集 `CONTROL_STYLE_SECTIONS` 的**唯一**实现处)。
79
+ * 每区的类名逐条来自产出器实测(注释里给 `file:line`),**不许臆造未被使用的类名**。 */
80
+ const SECTION_BUILDERS = {
81
+ /** toast —— 类名产出者:`src/controls.ts` 的 `renderToast`(静态)与 `buildSharedHelpersJs`
82
+ * 的 `feedback()`(helpers 运行时);两侧结构**同构**(`.toast` > `.toast-body` >
83
+ * `.toast-title-row` + 可选 detail;`.toast-close` 在 body 之外)。 */
84
+ toast: (p) => [
85
+ '.' + p + 'toast-stack {',
86
+ ' position: fixed;',
87
+ ' left: 50%;',
88
+ ' bottom: calc(24px + env(safe-area-inset-bottom, 0px));',
89
+ ' transform: translateX(-50%);',
90
+ ' z-index: 9999;',
91
+ ' display: flex;',
92
+ ' flex-direction: column;',
93
+ ' align-items: center;',
94
+ ' gap: ' + TOAST_DEFAULTS.gapPx + 'px;',
95
+ ' max-width: calc(100vw - 32px);',
96
+ ' pointer-events: none;',
97
+ '}',
98
+ // W1(返修第二轮):此处原有 `flex-wrap: wrap` 是**权宜补丁**(helpers 运行时 DOM 缺 body 包裹
99
+ // 时靠换行分层,代价是把关闭按钮挤到第三行)。根因已由 `controls.ts` 的结构对齐修掉
100
+ // (helpers 与静态产出器/旧层同构:`icon? + .toast-body(> .toast-title-row + detail) + .toast-close`),
101
+ // 补丁一并删除 → 关闭按钮与标题恒同行。
102
+ '.' + p + 'toast {',
103
+ ' display: flex;',
104
+ ' align-items: flex-start;',
105
+ ' gap: 12px;',
106
+ ' box-sizing: border-box;',
107
+ ' min-width: 300px;',
108
+ ' max-width: 480px;',
109
+ ' padding: 13px 14px 13px 16px;',
110
+ ' border: 1px solid var(--line);',
111
+ ' border-radius: 14px;',
112
+ ' background: var(--card);',
113
+ ' color: var(--fg);',
114
+ ' box-shadow: var(--shadow);',
115
+ ' font-size: 12.5px;',
116
+ ' line-height: 1.4;',
117
+ ' font-feature-settings: "tnum";',
118
+ ' pointer-events: auto;',
119
+ ' animation: ' + p + 'toast-in .22s cubic-bezier(.34, 1.56, .64, 1) both;',
120
+ '}',
121
+ // 入场动效(W4):旧层 `.hm-toast{opacity:0;transform:scale(.9)}` + `.hm-toast.show{…}`
122
+ // (`base.js:75`)依赖 JS 加类,新控制器**不加 `.show`**(`ToastController.show` 是方法名,非类)
123
+ // → 改为 **CSS-only** `@keyframes` + `animation`,**不依赖任何 JS 加类**,且默认可见。
124
+ '@keyframes ' + p + 'toast-in {',
125
+ ' from {',
126
+ ' opacity: 0;',
127
+ ' transform: scale(.9) translateY(4px);',
128
+ ' }',
129
+ ' to {',
130
+ ' opacity: 1;',
131
+ ' transform: none;',
132
+ ' }',
133
+ '}',
134
+ '.' + p + 'toast-danger {',
135
+ ' border-color: rgba(255, 59, 48, .38);',
136
+ '}',
137
+ '.' + p + 'toast-icon {',
138
+ ' flex: 0 0 auto;',
139
+ ' padding-top: 1px;',
140
+ ' font-size: 20px;',
141
+ ' line-height: 1;',
142
+ '}',
143
+ // body 是 toast 的**唯一**内容列(静态产出器与 helpers 运行时同构,见 `controls.ts`
144
+ // `TOAST_BODY_CLASS`):`flex: 1 1 auto` 让它吃掉剩余宽度并内部换行,
145
+ // 关闭按钮因此恒与标题同行(W1 判据 `rt_close_top === rt_title_top`)。
146
+ '.' + p + 'toast-body {',
147
+ ' flex: 1 1 auto;',
148
+ ' min-width: 0;',
149
+ '}',
150
+ '.' + p + 'toast-title-row {',
151
+ ' display: flex;',
152
+ ' flex-wrap: wrap;',
153
+ ' align-items: center;',
154
+ ' gap: 8px;',
155
+ '}',
156
+ '.' + p + 'toast-title {',
157
+ ' margin-bottom: 2px;',
158
+ ' color: var(--fg);',
159
+ ' font-size: 12.5px;',
160
+ ' font-weight: 600;',
161
+ ' line-height: 1.4;',
162
+ '}',
163
+ // 运行时详情(helpers 产出 `.ilife-toast-title-detail`,在 `.ilife-toast-body` 内):
164
+ // 与静态侧 `.ilife-toast-detail` **同排版**;此前的 `flex: 1 1 100%` 权宜补丁已随结构对齐删除。
165
+ '.' + p + 'toast-title-detail {',
166
+ ' color: var(--fg2);',
167
+ ' font-size: 11px;',
168
+ ' font-weight: 400;',
169
+ ' line-height: 1.5;',
170
+ '}',
171
+ '.' + p + 'toast-chip {',
172
+ ' flex-shrink: 0;',
173
+ ' padding: 2px 8px;',
174
+ ' border-radius: 999px;',
175
+ ' background: var(--soft);',
176
+ ' color: var(--blue2);',
177
+ ' font-size: 10px;',
178
+ ' font-weight: 700;',
179
+ ' letter-spacing: .02em;',
180
+ ' line-height: 1.6;',
181
+ '}',
182
+ '.' + p + 'toast-chip-ok {',
183
+ ' background: rgba(52, 199, 89, .12);',
184
+ ' color: #1f8f3d;',
185
+ '}',
186
+ '.' + p + 'toast-chip-warn {',
187
+ ' background: rgba(255, 149, 0, .12);',
188
+ ' color: #b25000;',
189
+ '}',
190
+ '.' + p + 'toast-chip-danger {',
191
+ ' background: rgba(255, 59, 48, .12);',
192
+ ' color: #c0392b;',
193
+ '}',
194
+ '.' + p + 'toast-count {',
195
+ ' flex-shrink: 0;',
196
+ ' padding: 2px 8px;',
197
+ ' border-radius: 8px;',
198
+ ' color: var(--fg3);',
199
+ ' font-size: 10px;',
200
+ ' font-weight: 700;',
201
+ ' font-variant-numeric: tabular-nums;',
202
+ '}',
203
+ '.' + p + 'toast-detail {',
204
+ ' color: var(--fg2);',
205
+ ' font-size: 11px;',
206
+ ' line-height: 1.5;',
207
+ '}',
208
+ '.' + p + 'toast-lines {',
209
+ ' color: var(--fg2);',
210
+ ' font-size: 11px;',
211
+ ' line-height: 1.55;',
212
+ ' white-space: pre-wrap;',
213
+ '}',
214
+ '.' + p + 'toast-code {',
215
+ ' margin: 6px 0 0;',
216
+ ' padding: 8px 10px;',
217
+ ' border-radius: 8px;',
218
+ ' background: var(--soft);',
219
+ ' color: var(--fg2);',
220
+ ' font-family: "SF Mono", monospace;',
221
+ ' font-size: 10.5px;',
222
+ ' line-height: 1.5;',
223
+ ' white-space: pre-wrap;',
224
+ ' max-height: 140px;',
225
+ ' overflow: auto;',
226
+ '}',
227
+ '.' + p + 'toast-act {',
228
+ ' margin: 0;',
229
+ ' padding: 2px 4px;',
230
+ ' border: 0;',
231
+ ' background: none;',
232
+ ' color: var(--ok);',
233
+ ' font-family: inherit;',
234
+ ' font-size: 11px;',
235
+ ' font-weight: 600;',
236
+ ' cursor: pointer;',
237
+ '}',
238
+ '.' + p + 'toast-act:active {',
239
+ ' opacity: .7;',
240
+ '}',
241
+ '.' + p + 'toast-close {',
242
+ ' flex-shrink: 0;',
243
+ ' margin: 0 0 0 6px;',
244
+ ' padding: 5px 9px;',
245
+ ' border: 0;',
246
+ ' border-radius: 8px;',
247
+ ' background: var(--soft);',
248
+ ' color: var(--blue2);',
249
+ ' font-family: inherit;',
250
+ ' font-size: 10.5px;',
251
+ ' font-weight: 500;',
252
+ ' white-space: nowrap;',
253
+ ' cursor: pointer;',
254
+ '}',
255
+ focusRing('.' + p + 'toast-act'),
256
+ focusRing('.' + p + 'toast-close'),
257
+ '@media (max-width: ' + TOAST_DEFAULTS.mobileMaxPx + 'px) {',
258
+ ' .' + p + 'toast-stack {',
259
+ ' left: 12px;',
260
+ ' right: 12px;',
261
+ ' transform: none;',
262
+ ' align-items: stretch;',
263
+ ' }',
264
+ ' .' + p + 'toast {',
265
+ ' min-width: 0;',
266
+ ' max-width: none;',
267
+ ' }',
268
+ '}',
269
+ ].join(LF),
270
+ /** actionBar —— 类名产出者 `src/controls.ts:649-695`(`action-bar`/`action-row`/
271
+ * `action-row-ghost`/`action-btn`/`action-btn-<kind>`)。 */
272
+ actionBar: (p) => [
273
+ '.' + p + 'action-bar {',
274
+ ' display: flex;',
275
+ ' flex-direction: column;',
276
+ ' gap: 8px;',
277
+ ' box-sizing: border-box;',
278
+ ' margin: 12px 0;',
279
+ ' max-width: 520px;',
280
+ ' margin-left: auto;',
281
+ ' margin-right: auto;',
282
+ '}',
283
+ '.' + p + 'action-row {',
284
+ ' display: grid;',
285
+ ' grid-template-columns: repeat(' + ACTION_BAR_DEFAULTS.evenRowPairs + ', minmax(0, 1fr));',
286
+ ' gap: 8px;',
287
+ '}',
288
+ '.' + p + 'action-row-ghost {',
289
+ ' grid-template-columns: minmax(0, 1fr);',
290
+ '}',
291
+ '.' + p + 'action-btn {',
292
+ ' display: inline-flex;',
293
+ ' align-items: center;',
294
+ ' justify-content: center;',
295
+ ' min-height: ' + ACTION_BAR_DEFAULTS.minHeightPx + 'px;',
296
+ ' padding: 0 16px;',
297
+ ' border: 1px solid transparent;',
298
+ ' border-radius: 999px;',
299
+ ' background: var(--blue);',
300
+ ' color: var(--card);',
301
+ ' font-family: inherit;',
302
+ ' font-size: ' + ACTION_BAR_DEFAULTS.fontSizePx + 'px;',
303
+ ' font-weight: ' + ACTION_BAR_DEFAULTS.fontWeight + ';',
304
+ ' line-height: 1;',
305
+ ' cursor: pointer;',
306
+ '}',
307
+ '.' + p + 'action-btn-primary {',
308
+ ' background: var(--blue);',
309
+ ' color: var(--card);',
310
+ '}',
311
+ '.' + p + 'action-btn-red {',
312
+ ' background: #ff3b30;',
313
+ ' color: var(--card);',
314
+ '}',
315
+ '.' + p + 'action-btn-ghost {',
316
+ ' border-color: rgba(' + BLUE_RGB + ', ' + ACTION_BAR_DEFAULTS.ghostBorderAlpha + ');',
317
+ ' background: var(--card);',
318
+ ' color: var(--blue);',
319
+ '}',
320
+ focusRing('.' + p + 'action-btn'),
321
+ ].join(LF),
322
+ copyButton: (p) => [
323
+ '.' + p + 'copy-btn {',
324
+ ' display: inline-flex;',
325
+ ' align-items: center;',
326
+ ' justify-content: center;',
327
+ ' gap: 6px;',
328
+ ' min-height: ' + ACTION_BAR_DEFAULTS.minHeightPx + 'px;',
329
+ ' padding: 0 14px;',
330
+ ' border: 1px solid transparent;',
331
+ ' border-radius: 999px;',
332
+ ' background: var(--blue);',
333
+ ' color: var(--card);',
334
+ ' font-family: inherit;',
335
+ ' font-size: ' + ACTION_BAR_DEFAULTS.fontSizePx + 'px;',
336
+ ' font-weight: ' + ACTION_BAR_DEFAULTS.fontWeight + ';',
337
+ ' line-height: 1;',
338
+ ' cursor: pointer;',
339
+ ' transition: transform .45s cubic-bezier(.34, 1.56, .64, 1), background-color .2s ease;',
340
+ '}',
341
+ '.' + p + 'copy-btn-primary {',
342
+ ' background: var(--blue);',
343
+ ' color: var(--card);',
344
+ '}',
345
+ '.' + p + 'copy-btn-ghost {',
346
+ ' border-color: rgba(' + BLUE_RGB + ', ' + ACTION_BAR_DEFAULTS.ghostBorderAlpha + ');',
347
+ ' background: var(--card);',
348
+ ' color: var(--blue);',
349
+ '}',
350
+ '.' + p + 'copy-btn-wide {',
351
+ ' width: 100%;',
352
+ '}',
353
+ '.' + p + 'copy-btn:active {',
354
+ ' transform: scale(.96);',
355
+ '}',
356
+ // W3(H-16 双反馈的 CSS 侧):复制成功态变绿。规格 `docs/visual-spec-help.md:195,197`
357
+ // 「按钮变绿进入 `copied` 态并跑 450ms 弹簧动画」;弹簧 = 基座 `transition: transform .45s
358
+ // cubic-bezier(.34, 1.56, .64, 1)`(B1 `benchmark-visual-spec.md:279,645`),本节只补**变绿**。
359
+ // 类名由**运行时**添加(`copied`,非 `ilife-` 前缀 → 不占样式区命名空间);
360
+ // 本票**不改** helpers 的复制反馈行为,移交落点见契约 §8.11.1 FX-75-11。
361
+ '.' + p + 'copy-btn.copied {',
362
+ ' border-color: var(--ok);',
363
+ ' background: var(--ok);',
364
+ ' color: var(--card);',
365
+ '}',
366
+ focusRing('.' + p + 'copy-btn'),
367
+ ].join(LF),
368
+ /** statusBadge —— 类名产出者 `controls.ts:702-707`。
369
+ * **逐值对齐施工单 B §1.4**:`gap:6px;padding:6px 14px;font-size:13px;font-weight:600;
370
+ * border-radius:999px;width:fit-content`。四组底色/字色**逐值取旧 `.hm-status.{ok,warn,danger,empty}`**
371
+ * (`公共组件/assets/base.css:225-228`;旧值清单亦见施工单 B `:253`)——**实色**背景,非 alpha:
372
+ * ok `#e6f7ec`/`#1f8c3d`、warn `#fff5e0`/`#a25b00`、danger `#fff0ee`/`#a83228`、
373
+ * empty `#f0f0f3`/`var(--fg2)`(empty 字色旧层 `base.css:228` 为 `#6e6e73`=`--fg2` 解析值,底色为实色 `#f0f0f3`)。
374
+ * **返修 W2**:上一轮误用 `#1f8f3d`/`#b25000`(全仓无出处)+ 12% alpha 底色,且注释谎称「沿用旧值」
375
+ * → 已改回旧逐值,注释与实现一致(非 token 硬编码色按 D-5 以 CSS 常量落地,不新增 token 名)。 */
376
+ statusBadge: (p) => [
377
+ '.' + p + 'status-badge {',
378
+ ' display: inline-flex;',
379
+ ' align-items: center;',
380
+ ' gap: 6px;',
381
+ ' padding: 6px 14px;',
382
+ ' border-radius: 999px;',
383
+ ' background: var(--soft);',
384
+ ' color: var(--fg2);',
385
+ ' font-size: 13px;',
386
+ ' font-weight: 600;',
387
+ ' line-height: 1.6;',
388
+ ' width: fit-content;',
389
+ '}',
390
+ '.' + p + 'status-badge-ok {',
391
+ ' background: #e6f7ec;',
392
+ ' color: #1f8c3d;',
393
+ '}',
394
+ '.' + p + 'status-badge-warn {',
395
+ ' background: #fff5e0;',
396
+ ' color: #a25b00;',
397
+ '}',
398
+ '.' + p + 'status-badge-danger {',
399
+ ' background: #fff0ee;',
400
+ ' color: #a83228;',
401
+ '}',
402
+ '.' + p + 'status-badge-empty {',
403
+ ' background: #f0f0f3;',
404
+ ' color: var(--fg2);',
405
+ '}',
406
+ ].join(LF),
407
+ emptyState: (p) => [
408
+ '.' + p + 'empty {',
409
+ ' padding: 48px 20px;',
410
+ ' border: 1px solid var(--line);',
411
+ ' border-radius: 20px;',
412
+ ' background: var(--card);',
413
+ ' text-align: center;',
414
+ ' color: var(--fg2);',
415
+ '}',
416
+ '.' + p + 'empty-icon {',
417
+ ' font-size: 40px;',
418
+ ' line-height: 1.2;',
419
+ ' opacity: .5;',
420
+ '}',
421
+ '.' + p + 'empty-text {',
422
+ ' margin-top: 8px;',
423
+ ' color: var(--fg);',
424
+ ' font-size: 17px;',
425
+ ' font-weight: 600;',
426
+ '}',
427
+ '.' + p + 'empty-hint {',
428
+ ' margin-top: 4px;',
429
+ ' color: var(--fg3);',
430
+ ' font-size: 13px;',
431
+ '}',
432
+ '.' + p + 'empty-action {',
433
+ ' margin-top: 16px;',
434
+ '}',
435
+ ].join(LF),
436
+ /** errorReceipt —— 类名产出者 `src/controls.ts:763-765`。
437
+ * **类名撞车处置(裁定 R6/施工单 B-R4)**:`ilife-error` 亦由
438
+ * `packages/skill-calorie/src/render/html.ts:254`(`cx('error')`,语义为「错误页正文」)产出,
439
+ * 故容器规则**不得**用裸 `.ilife-error` 选择器(会污染 calorie 错误页);改用
440
+ * `:has(> .ilife-error-title)` 精确限定「本组件自己的类组合」——
441
+ * calorie 的 `ilife-error` 节点无该子元素,**不命中**。 */
442
+ errorReceipt: (p) => [
443
+ '.' + p + 'error:has(> .' + p + 'error-title) {',
444
+ ' margin: 12px 0;',
445
+ ' padding: 20px 22px;',
446
+ ' border: 1px solid rgba(255, 59, 48, .28);',
447
+ ' border-radius: 14px;',
448
+ ' background: rgba(255, 59, 48, .06);',
449
+ ' color: var(--fg);',
450
+ '}',
451
+ '.' + p + 'error-title {',
452
+ ' margin-bottom: 14px;',
453
+ ' color: #a83228;',
454
+ ' font-size: 15px;',
455
+ ' font-weight: 700;',
456
+ '}',
457
+ // **旧层两行 grid 语义对齐**(施工单 B §1.6 要求「等价」):旧层 `.hm-actions`
458
+ // `{display:grid;grid-template-columns:1fr 1fr;gap:8px;max-width:520px;margin:0 auto}`(`base.css:81-91`,
459
+ // 其中 `max-width:520px;margin:0 auto` 在 **`:87-89`**)+ `.hm-error .hm-actions{margin-top:0}` 与
460
+ // 相邻 `+ .hm-actions{margin-top:14px}`(两行之间的竖向间距 14px)。
461
+ // A2 返修项⑤实测旧实现 retry 独占 1352px、两个 ghost 仅 78px(单 flex 行)→ 改为
462
+ // grid 2 列(列数取 `ACTION_BAR_DEFAULTS.evenRowPairs`)+ `.ilife-copy-btn-wide` 跨全列。
463
+ // **返修 W5**:上一轮漏了 `max-width:520px;margin:0 auto`(实测 retry 1352/ghost 672,
464
+ // 旧应为 520/256)→ 本节补上;两行间距由 `gap:8px` 改为 `row-gap:14px;column-gap:8px`
465
+ // 以逐值对齐旧层 `+.hm-actions{margin-top:14px}`。
466
+ '.' + p + 'error-actions {',
467
+ ' display: grid;',
468
+ ' grid-template-columns: repeat(' + ACTION_BAR_DEFAULTS.evenRowPairs + ', minmax(0, 1fr));',
469
+ ' row-gap: 14px;',
470
+ ' column-gap: 8px;',
471
+ ' max-width: 520px;',
472
+ ' margin-left: auto;',
473
+ ' margin-right: auto;',
474
+ '}',
475
+ '.' + p + 'error-actions > .' + p + 'copy-btn-wide {',
476
+ ' grid-column: 1 / -1;',
477
+ '}',
478
+ '@media (max-width: ' + TOAST_DEFAULTS.mobileMaxPx + 'px) {',
479
+ ' .' + p + 'error:has(> .' + p + 'error-title) {',
480
+ ' padding: 16px 14px;',
481
+ ' }',
482
+ '}',
483
+ ].join(LF),
484
+ /** charts —— **复用** `src/charts.ts` 的唯一产出者 `chartsCss`(#78 结论;重述即 S1)。 */
485
+ charts: (p) => chartsCss(p),
486
+ /** helpShell —— 类名产出者 `src/help.ts:380-599`(`cls()` = `ilife-help-shell-<suffix>`)
487
+ * + `src/controls.ts` 的 `buildSharedHelpersJs`(#88 S4 运行时注入:卡级复制按钮/搜索/高亮/
488
+ * 回到顶部——后缀同一命名空间,T11 按既有 `cls()` 实参前缀归属)。 */
489
+ helpShell: (p) => [
490
+ '.' + p + 'help-shell {',
491
+ ' display: block;',
492
+ ' max-width: 960px;',
493
+ ' margin: 0 auto;',
494
+ ' padding: 32px 20px 80px;',
495
+ ' color: var(--fg);',
496
+ // H-06 正文栈(局部常量 BODY_FONT_STACK,首位 "SF Pro Display";不新增 token 名,等宽栈不动)。
497
+ ' font-family: ' + BODY_FONT_STACK + ';',
498
+ ' font-feature-settings: "tnum";',
499
+ '}',
500
+ '.' + p + 'help-shell-hero {',
501
+ ' margin-bottom: 20px;',
502
+ '}',
503
+ '.' + p + 'help-shell-eyebrow {',
504
+ ' margin: 0;',
505
+ ' color: var(--blue);',
506
+ ' font-size: 12px;',
507
+ ' font-weight: 600;',
508
+ ' letter-spacing: .08em;',
509
+ ' text-transform: uppercase;',
510
+ '}',
511
+ '.' + p + 'help-shell-title {',
512
+ ' margin: 6px 0 0;',
513
+ ' font-size: 32px;',
514
+ ' font-weight: 700;',
515
+ ' letter-spacing: -.4px;',
516
+ ' line-height: 1.2;',
517
+ '}',
518
+ '.' + p + 'help-shell-subtitle {',
519
+ ' margin: 6px 0 0;',
520
+ ' color: var(--fg2);',
521
+ ' font-size: 15px;',
522
+ '}',
523
+ '.' + p + 'help-shell-lead {',
524
+ ' margin: 4px 0 0;',
525
+ ' color: var(--fg3);',
526
+ ' font-size: 13px;',
527
+ '}',
528
+ '.' + p + 'help-shell-init {',
529
+ ' padding: 16px;',
530
+ ' border: 1px solid var(--line);',
531
+ ' border-radius: 20px;',
532
+ ' background: var(--card);',
533
+ '}',
534
+ '.' + p + 'help-shell-init-title {',
535
+ ' margin: 0;',
536
+ ' font-size: 17px;',
537
+ ' font-weight: 600;',
538
+ '}',
539
+ '.' + p + 'help-shell-init-subtitle {',
540
+ ' margin: 4px 0 0;',
541
+ ' color: var(--fg2);',
542
+ ' font-size: 13px;',
543
+ '}',
544
+ '.' + p + 'help-shell-init-prompt {',
545
+ ' margin: 8px 0 0;',
546
+ ' padding: 12px;',
547
+ ' border-radius: 8px;',
548
+ ' background: var(--soft);',
549
+ ' color: var(--fg2);',
550
+ ' font-family: "SF Mono", monospace;',
551
+ ' font-size: 12px;',
552
+ ' line-height: 1.55;',
553
+ ' white-space: pre-wrap;',
554
+ ' overflow-x: auto;',
555
+ '}',
556
+ '.' + p + 'help-shell-init-steps {',
557
+ ' margin: 8px 0 0;',
558
+ ' padding-left: 20px;',
559
+ ' color: var(--fg2);',
560
+ ' font-size: 13px;',
561
+ '}',
562
+ '.' + p + 'help-shell-init-step {',
563
+ ' margin: 2px 0;',
564
+ '}',
565
+ '.' + p + 'help-shell-tab-bar {',
566
+ ' display: flex;',
567
+ ' flex-wrap: wrap;',
568
+ ' gap: 8px;',
569
+ ' margin: 16px 0;',
570
+ '}',
571
+ '.' + p + 'help-shell-tab {',
572
+ ' display: inline-flex;',
573
+ ' align-items: center;',
574
+ ' gap: 6px;',
575
+ ' padding: 6px 14px;',
576
+ ' border: 1px solid var(--line);',
577
+ ' border-radius: 999px;',
578
+ ' background: var(--card);',
579
+ ' color: var(--fg2);',
580
+ ' font-size: 13px;',
581
+ ' font-weight: 600;',
582
+ ' cursor: pointer;',
583
+ '}',
584
+ '.' + p + 'help-shell-tab-icon {',
585
+ ' font-size: 14px;',
586
+ '}',
587
+ '.' + p + 'help-shell-tab-input {',
588
+ ' position: absolute;',
589
+ ' width: 1px;',
590
+ ' height: 1px;',
591
+ ' margin: -1px;',
592
+ ' padding: 0;',
593
+ ' border: 0;',
594
+ ' overflow: hidden;',
595
+ ' clip: rect(0 0 0 0);',
596
+ ' white-space: nowrap;',
597
+ '}',
598
+ '.' + p + 'help-shell-pages {',
599
+ ' display: block;',
600
+ '}',
601
+ '.' + p + 'help-shell-page {',
602
+ ' display: block;',
603
+ '}',
604
+ '.' + p + 'help-shell-page-body {',
605
+ ' display: none;',
606
+ '}',
607
+ '.' + p + 'help-shell-tab-input:checked + .' + p + 'help-shell-page-body {',
608
+ ' display: block;',
609
+ '}',
610
+ '.' + p + 'help-shell-grid {',
611
+ ' display: grid;',
612
+ ' grid-template-columns: repeat(2, minmax(0, 1fr));',
613
+ ' gap: 12px;',
614
+ '}',
615
+ '.' + p + 'help-shell-card {',
616
+ ' display: block;',
617
+ ' padding: 14px;',
618
+ ' border: 1px solid var(--line);',
619
+ ' border-radius: 14px;',
620
+ ' background: var(--card);',
621
+ ' box-shadow: var(--shadow);',
622
+ '}',
623
+ '.' + p + 'help-shell-card-top {',
624
+ ' display: flex;',
625
+ ' flex-wrap: wrap;',
626
+ ' align-items: center;',
627
+ ' gap: 8px;',
628
+ '}',
629
+ '.' + p + 'help-shell-card-title {',
630
+ ' margin: 8px 0 0;',
631
+ ' font-size: 17px;',
632
+ ' font-weight: 600;',
633
+ '}',
634
+ '.' + p + 'help-shell-chip {',
635
+ ' display: inline-flex;',
636
+ ' align-items: center;',
637
+ ' padding: 1px 8px;',
638
+ ' border-radius: 999px;',
639
+ ' background: var(--soft);',
640
+ ' color: var(--blue2);',
641
+ ' font-size: 11px;',
642
+ ' font-weight: 600;',
643
+ '}',
644
+ '.' + p + 'help-shell-badge {',
645
+ ' display: inline-flex;',
646
+ ' align-items: center;',
647
+ ' padding: 1px 8px;',
648
+ ' border-radius: 999px;',
649
+ ' background: var(--soft);',
650
+ ' color: var(--fg2);',
651
+ ' font-size: 11px;',
652
+ ' font-weight: 600;',
653
+ '}',
654
+ '.' + p + 'help-shell-badge-dev {',
655
+ ' background: rgba(255, 149, 0, .12);',
656
+ ' color: #b25000;',
657
+ '}',
658
+ '.' + p + 'help-shell-cli {',
659
+ ' display: block;',
660
+ ' margin-top: 6px;',
661
+ ' color: var(--fg3);',
662
+ ' font-family: "SF Mono", monospace;',
663
+ ' font-size: 11.5px;',
664
+ '}',
665
+ '.' + p + 'help-shell-fields {',
666
+ ' margin: 8px 0 0;',
667
+ ' padding: 0;',
668
+ ' list-style: none;',
669
+ '}',
670
+ '.' + p + 'help-shell-field {',
671
+ ' display: flex;',
672
+ ' flex-wrap: wrap;',
673
+ ' gap: 6px;',
674
+ ' padding: 6px 0;',
675
+ ' border-top: 1px solid var(--line);',
676
+ ' font-size: 13px;',
677
+ '}',
678
+ '.' + p + 'help-shell-field:first-child {',
679
+ ' border-top: 0;',
680
+ '}',
681
+ '.' + p + 'help-shell-field-label {',
682
+ ' color: var(--fg2);',
683
+ '}',
684
+ '.' + p + 'help-shell-field-value {',
685
+ ' color: var(--fg);',
686
+ ' font-weight: 600;',
687
+ '}',
688
+ '.' + p + 'help-shell-field-hint {',
689
+ ' color: var(--fg3);',
690
+ '}',
691
+ '.' + p + 'help-shell-field-required {',
692
+ ' color: #c0392b;',
693
+ ' font-weight: 600;',
694
+ '}',
695
+ '.' + p + 'help-shell-sheet {',
696
+ ' margin-top: 10px;',
697
+ ' border-top: 1px solid var(--line);',
698
+ '}',
699
+ '.' + p + 'help-shell-sheet-summary {',
700
+ ' padding: 8px 0;',
701
+ ' color: var(--blue);',
702
+ ' font-size: 13px;',
703
+ ' font-weight: 600;',
704
+ ' cursor: pointer;',
705
+ ' list-style: none;',
706
+ '}',
707
+ '.' + p + 'help-shell-sheet-summary::-webkit-details-marker {',
708
+ ' display: none;',
709
+ '}',
710
+ '.' + p + 'help-shell-sheet-summary::after {',
711
+ ' content: "▾";',
712
+ ' display: inline-block;',
713
+ ' margin-left: 6px;',
714
+ ' transition: transform .15s ease;',
715
+ '}',
716
+ '.' + p + 'help-shell-sheet[open] > .' + p + 'help-shell-sheet-summary::after {',
717
+ ' transform: rotate(180deg);',
718
+ '}',
719
+ '.' + p + 'help-shell-sheet-body {',
720
+ ' padding-bottom: 4px;',
721
+ '}',
722
+ '.' + p + 'help-shell-prompt {',
723
+ ' margin: 0;',
724
+ ' padding: 12px;',
725
+ ' border-radius: 8px;',
726
+ ' background: var(--soft);',
727
+ ' color: var(--fg2);',
728
+ ' font-family: "SF Mono", monospace;',
729
+ ' font-size: 12px;',
730
+ ' line-height: 1.55;',
731
+ ' white-space: pre-wrap;',
732
+ ' overflow-x: auto;',
733
+ '}',
734
+ '.' + p + 'help-shell-actions {',
735
+ ' display: flex;',
736
+ ' flex-wrap: wrap;',
737
+ ' gap: 8px;',
738
+ ' margin-top: 10px;',
739
+ '}',
740
+ '.' + p + 'help-shell-btn {',
741
+ ' display: inline-flex;',
742
+ ' align-items: center;',
743
+ ' justify-content: center;',
744
+ ' min-height: ' + ACTION_BAR_DEFAULTS.minHeightPx + 'px;',
745
+ ' padding: 0 14px;',
746
+ ' border: 1px solid transparent;',
747
+ ' border-radius: 999px;',
748
+ ' background: var(--blue);',
749
+ ' color: var(--card);',
750
+ ' font-family: inherit;',
751
+ ' font-size: ' + ACTION_BAR_DEFAULTS.fontSizePx + 'px;',
752
+ ' font-weight: ' + ACTION_BAR_DEFAULTS.fontWeight + ';',
753
+ ' line-height: 1;',
754
+ // #121:H-16「跑 450ms 弹簧动画」——弹簧口径**逐字复用** `copyButton` 基座(B1
755
+ // `benchmark-visual-spec.md:279,645`:`transform .45s cubic-bezier(.34,1.56,.64,1)`),
756
+ // 不新增机制、不新增关键帧;按下态同 `.ilife-copy-btn:active`(scale .96)。
757
+ ' transition: transform .45s cubic-bezier(.34, 1.56, .64, 1), background-color .2s ease;',
758
+ ' cursor: pointer;',
759
+ '}',
760
+ '.' + p + 'help-shell-btn:active {',
761
+ ' transform: scale(.96);',
762
+ '}',
763
+ '.' + p + 'help-shell-btn-prompt,',
764
+ '.' + p + 'help-shell-btn-wakeWord,',
765
+ '.' + p + 'help-shell-btn-params {',
766
+ ' background: var(--blue);',
767
+ ' color: var(--card);',
768
+ '}',
769
+ // #121(H-16 双反馈的 CSS 侧补面):HELP 速查台的复制按钮是 helpShell 命名空间(静态三目标
770
+ // `.ilife-help-shell-btn-*` + 运行时注入的 `.ilife-help-shell-card-copy`),**不含** `.ilife-copy-btn`
771
+ // → #75 的 `.ilife-copy-btn.copied` 命中不到。此处按同一成功色 token `--ok` 补 `copied` 态,
772
+ // 类名逐字 `copied`(运行时加,非 `ilife-` 前缀 → 不占样式区命名空间)。
773
+ '.' + p + 'help-shell-btn.copied,',
774
+ '.' + p + 'help-shell-card-copy.copied {',
775
+ ' border-color: var(--ok);',
776
+ ' background: var(--ok);',
777
+ ' color: var(--card);',
778
+ '}',
779
+ focusRing('.' + p + 'help-shell-btn'),
780
+ '.' + p + 'help-shell-subgroup {',
781
+ ' margin-top: 12px;',
782
+ ' border: 1px solid var(--line);',
783
+ ' border-radius: 14px;',
784
+ ' background: var(--card);',
785
+ '}',
786
+ '.' + p + 'help-shell-subgroup-summary {',
787
+ ' display: flex;',
788
+ ' align-items: center;',
789
+ ' gap: 8px;',
790
+ ' padding: 10px 14px;',
791
+ ' font-size: 14px;',
792
+ ' font-weight: 600;',
793
+ ' cursor: pointer;',
794
+ '}',
795
+ '.' + p + 'help-shell-count {',
796
+ ' color: var(--fg3);',
797
+ ' font-size: 12px;',
798
+ ' font-weight: 600;',
799
+ '}',
800
+ '.' + p + 'help-shell-subgroup-body {',
801
+ ' padding: 0 14px 14px;',
802
+ '}',
803
+ '.' + p + 'help-shell-about-sec {',
804
+ ' margin-top: 20px;',
805
+ '}',
806
+ '.' + p + 'help-shell-about-head {',
807
+ ' margin: 0 0 8px;',
808
+ ' font-size: 17px;',
809
+ ' font-weight: 600;',
810
+ '}',
811
+ '.' + p + 'help-shell-about-list {',
812
+ ' margin: 0;',
813
+ ' padding: 0;',
814
+ ' list-style: none;',
815
+ '}',
816
+ '.' + p + 'help-shell-about-row {',
817
+ ' display: flex;',
818
+ ' flex-wrap: wrap;',
819
+ ' gap: 8px;',
820
+ ' padding: 8px 0;',
821
+ ' border-top: 1px solid var(--line);',
822
+ ' font-size: 13px;',
823
+ '}',
824
+ '.' + p + 'help-shell-about-row:first-child {',
825
+ ' border-top: 0;',
826
+ '}',
827
+ '.' + p + 'help-shell-about-label {',
828
+ ' color: var(--fg2);',
829
+ '}',
830
+ '.' + p + 'help-shell-about-value {',
831
+ ' color: var(--fg);',
832
+ ' font-weight: 600;',
833
+ '}',
834
+ '.' + p + 'help-shell-about-note {',
835
+ ' margin: 8px 0 0;',
836
+ ' color: var(--fg3);',
837
+ ' font-size: 13px;',
838
+ '}',
839
+ '.' + p + 'help-shell-meta {',
840
+ ' margin-top: 20px;',
841
+ '}',
842
+ '.' + p + 'help-shell-meta-block {',
843
+ ' margin-top: 16px;',
844
+ '}',
845
+ '.' + p + 'help-shell-meta-title {',
846
+ ' margin: 0 0 8px;',
847
+ ' font-size: 17px;',
848
+ ' font-weight: 600;',
849
+ '}',
850
+ '.' + p + 'help-shell-meta-html {',
851
+ ' color: var(--fg2);',
852
+ ' font-size: 13px;',
853
+ '}',
854
+ // ── #88 S4:helpers 运行时注入面(`src/controls.ts` 的 `buildSharedHelpersJs`)──────────
855
+ // 类名后缀与 `src/help.ts` 的 `cls()` 同一命名空间(T11 归属由既有实参前缀承担);
856
+ // 只用 11 个冻结 token,不新增 token、不越区、不走技能侧 `extraCss`。
857
+ '.' + p + 'help-shell-tab-search {',
858
+ ' display: flex;',
859
+ ' flex-wrap: wrap;',
860
+ ' align-items: center;',
861
+ ' gap: 8px;',
862
+ ' margin: 0 0 16px;',
863
+ '}',
864
+ '.' + p + 'help-shell-tab-search-input {',
865
+ ' flex: 1 1 200px;',
866
+ ' min-width: 0;',
867
+ ' min-height: 36px;',
868
+ ' padding: 0 14px;',
869
+ ' border: 1px solid var(--line);',
870
+ ' border-radius: 999px;',
871
+ ' background: var(--card);',
872
+ ' color: var(--fg);',
873
+ ' font-family: inherit;',
874
+ ' font-size: 13px;',
875
+ '}',
876
+ focusRing('.' + p + 'help-shell-tab-search-input'),
877
+ '.' + p + 'help-shell-tab-search-clear {',
878
+ ' display: inline-flex;',
879
+ ' align-items: center;',
880
+ ' justify-content: center;',
881
+ ' width: 28px;',
882
+ ' height: 28px;',
883
+ ' padding: 0;',
884
+ ' border: 1px solid var(--line);',
885
+ ' border-radius: 50%;',
886
+ ' background: var(--card);',
887
+ ' color: var(--fg2);',
888
+ ' font-family: inherit;',
889
+ ' font-size: 12px;',
890
+ ' line-height: 1;',
891
+ ' cursor: pointer;',
892
+ '}',
893
+ focusRing('.' + p + 'help-shell-tab-search-clear'),
894
+ '.' + p + 'help-shell-page-hitcount {',
895
+ ' color: var(--fg2);',
896
+ ' font-size: 13px;',
897
+ ' font-weight: 600;',
898
+ '}',
899
+ '.' + p + 'help-shell-card-copy {',
900
+ ' display: inline-flex;',
901
+ ' align-items: center;',
902
+ ' justify-content: center;',
903
+ ' margin-left: auto;',
904
+ ' min-height: 28px;',
905
+ ' padding: 0 12px;',
906
+ ' border: 1px solid var(--line);',
907
+ ' border-radius: 999px;',
908
+ ' background: var(--card);',
909
+ ' color: var(--blue);',
910
+ ' font-family: inherit;',
911
+ ' font-size: 12px;',
912
+ ' font-weight: 600;',
913
+ ' line-height: 1;',
914
+ // #121:弹簧口径同 `copyButton` 基座(H-16「450ms 弹簧」),成功态 `.copied` 见上方 helpShell 区。
915
+ ' transition: transform .45s cubic-bezier(.34, 1.56, .64, 1), background-color .2s ease;',
916
+ ' cursor: pointer;',
917
+ '}',
918
+ '.' + p + 'help-shell-card-copy:active {',
919
+ ' transform: scale(.96);',
920
+ '}',
921
+ focusRing('.' + p + 'help-shell-card-copy'),
922
+ '.' + p + 'help-shell-card-mark {',
923
+ ' padding: 0 2px;',
924
+ // H-10:`4px` ∉ {8,14,20,999px,50%} → 取集合内最近的 `8px`(D-5:CSS 常量直写,不新增 token 名)。
925
+ ' border-radius: 8px;',
926
+ ' background: var(--soft);',
927
+ ' color: var(--blue2);',
928
+ ' font-weight: 600;',
929
+ '}',
930
+ // 搜索态:未命中卡片/子功能组整块隐藏(`hidden` 属性会被本区 `display:block` 覆盖,
931
+ // 故用类名而非属性;类名同样由 helpers 以 `className` 字符串增删,不依赖 `classList`)。
932
+ '.' + p + 'help-shell-card-hidden {',
933
+ ' display: none;',
934
+ '}',
935
+ '.' + p + 'help-shell-subgroup-hidden {',
936
+ ' display: none;',
937
+ '}',
938
+ '.' + p + 'help-shell-field-input {',
939
+ ' flex: 1 1 120px;',
940
+ ' min-width: 0;',
941
+ ' min-height: 32px;',
942
+ ' padding: 0 10px;',
943
+ ' border: 1px solid var(--line);',
944
+ ' border-radius: 8px;',
945
+ ' background: var(--card);',
946
+ ' color: var(--fg);',
947
+ ' font-family: inherit;',
948
+ ' font-size: 13px;',
949
+ '}',
950
+ focusRing('.' + p + 'help-shell-field-input'),
951
+ // 回到顶部(视觉尺 H-19:42px 圆形、fixed bottom/right 24px、初始 opacity:0 +
952
+ // pointer-events:none,scrollY>400 加 `-show`;点击平滑回顶由 helpers 承担)。
953
+ '.' + p + 'help-shell-btn-backtop {',
954
+ ' position: fixed;',
955
+ ' right: 24px;',
956
+ ' bottom: 24px;',
957
+ ' display: inline-flex;',
958
+ ' align-items: center;',
959
+ ' justify-content: center;',
960
+ ' width: 42px;',
961
+ ' height: 42px;',
962
+ ' padding: 0;',
963
+ ' border: 1px solid var(--line);',
964
+ ' border-radius: 50%;',
965
+ ' background: var(--card);',
966
+ ' color: var(--fg);',
967
+ ' font-family: inherit;',
968
+ ' font-size: 18px;',
969
+ ' font-weight: 600;',
970
+ ' line-height: 1;',
971
+ ' box-shadow: var(--shadow);',
972
+ ' opacity: 0;',
973
+ ' pointer-events: none;',
974
+ ' transition: opacity .2s ease;',
975
+ ' z-index: 9998;',
976
+ ' cursor: pointer;',
977
+ '}',
978
+ '.' + p + 'help-shell-btn-backtop-show {',
979
+ ' opacity: 1;',
980
+ ' pointer-events: auto;',
981
+ '}',
982
+ focusRing('.' + p + 'help-shell-btn-backtop'),
983
+ '@media (max-width: 640px) {',
984
+ ' .' + p + 'help-shell {',
985
+ ' padding: 20px 16px 60px;',
986
+ ' }',
987
+ ' .' + p + 'help-shell-title {',
988
+ ' font-size: 26px;',
989
+ ' }',
990
+ ' .' + p + 'help-shell-grid {',
991
+ ' grid-template-columns: minmax(0, 1fr);',
992
+ ' }',
993
+ '}',
994
+ '@media (max-width: 400px) {',
995
+ ' .' + p + 'help-shell-tab-bar {',
996
+ ' gap: 6px;',
997
+ ' }',
998
+ '}',
999
+ '@media (prefers-reduced-motion: reduce) {',
1000
+ ' .' + p + 'copy-btn {',
1001
+ ' transition: none;',
1002
+ ' }',
1003
+ ' .' + p + 'copy-btn:active {',
1004
+ ' transform: none;',
1005
+ ' }',
1006
+ // W4:入场动效在 reduced-motion 下**归零**(含动画本身,不只是时长)。
1007
+ ' .' + p + 'toast {',
1008
+ ' animation: none;',
1009
+ ' }',
1010
+ // #88 S4:回到顶部按钮的透明度过渡同样归零(H-20 覆盖全部可交互控件)。
1011
+ ' .' + p + 'help-shell-btn-backtop {',
1012
+ ' transition: none;',
1013
+ ' }',
1014
+ // #121:helpShell 复制按钮新增的弹簧过渡同样归零(口径同上一行)。
1015
+ ' .' + p + 'help-shell-btn,',
1016
+ ' .' + p + 'help-shell-card-copy {',
1017
+ ' transition: none;',
1018
+ ' }',
1019
+ ' .' + p + 'help-shell-btn:active,',
1020
+ ' .' + p + 'help-shell-card-copy:active {',
1021
+ ' transform: none;',
1022
+ ' }',
1023
+ '}',
1024
+ ].join(LF),
1025
+ };
1026
+ /** 闭集漂移 fail-fast(与 `src/help.ts:90-93` 同口径):`CONTROL_STYLE_SECTIONS`
1027
+ * 新增区名而本文件未补实现时,**导入即报错**,不会静默产出缺区样式表。 */
1028
+ for (const section of CONTROL_STYLE_SECTIONS) {
1029
+ if (SECTION_BUILDERS[section] === undefined) {
1030
+ throw new Error('base-paint/style:CONTROL_STYLE_SECTIONS 闭集缺样式区实现(' + section + ')');
1031
+ }
1032
+ }
1033
+ /** 缺省前缀(`StyleSheetInput.prefix` 非法时回落,见 `normalizePrefix`)。 */
1034
+ function normalizePrefix(input) {
1035
+ const prefix = input === undefined || input === null ? undefined : input.prefix;
1036
+ return typeof prefix === 'string' && prefix !== '' ? prefix : STYLE_PREFIX;
1037
+ }
1038
+ /** `extraCss` 违规错误:**不导出**(与 #74 `TemplateError`/#76 `bad-input` 同口径——
1039
+ * 冻结面 `SPEC_FROZEN_SURFACE` 无该运行时条目,调用方按 `name`/`code` 判定)。
1040
+ * code 三值 = 契约 doc:299-300 三条「不得」的落点:
1041
+ * `extra-css-root`(改写基座 `:root`)/`extra-css-forbidden-token`(Q14 禁入项)/
1042
+ * `extra-css-dark-scheme`(深色区选择器,doc:292)。 */
1043
+ class StyleSheetExtraCssError extends Error {
1044
+ code;
1045
+ constructor(code, message) {
1046
+ super(message);
1047
+ this.name = 'StyleSheetError';
1048
+ this.code = code;
1049
+ }
1050
+ }
1051
+ /** CSS 块注释剥除(`/* … *\/` → 空格):**判定前先剥注释**——注释里的 `:root`/`--pink` 只是说明文字,
1052
+ * 不是「改写基座/引入禁入项」;反之注释**不能**藏住真实声明(`/*c*\/:root{}` 剥后仍命中)。 */
1053
+ function stripCssComments(css) {
1054
+ return css.replace(/\/\*[\s\S]*?\*\//g, ' ');
1055
+ }
1056
+ /** **token 边界**匹配(返修 W6):`--pink` 只匹配完整自定义属性名,`--pinkish`/`--pink-2` **不**命中
1057
+ * (旧实现 `includes(forbidden)` 会把 `--pinkish` 误拦)。边界 = 前一个字符非 `[A-Za-z0-9_-]`、
1058
+ * 后一个字符非 `[A-Za-z0-9_-]`。 */
1059
+ function hasBoundedToken(text, token) {
1060
+ const escaped = token.replace(/[.*+?^${}()|[\]\\-]/g, '\\$&');
1061
+ return new RegExp('(^|[^A-Za-z0-9_-])' + escaped + '(?![A-Za-z0-9_-])').test(text);
1062
+ }
1063
+ /** `extraCss` 三禁强制(**编排者返修裁定:D3 修订**,见契约 §8.11)。
1064
+ *
1065
+ * 理由:契约 doc:299-300 的三条「不得」(不得改写基座值/不得引入 Q14 禁入项/不得引入深色区)
1066
+ * 此前**无任何落点**(D3 原裁定「不校验、纯调用方责任」)→ 本票补最小强制:
1067
+ * - (a) 含 `:root` 选择器 → 改写基座;
1068
+ * - (b) 含 `STYLE_FORBIDDEN_TOKENS`(`--r-xl`/`--pink`);
1069
+ * - (c) 含深色区选择器(`[data-theme`/`prefers-color-scheme: dark`)。
1070
+ * 命中**任一**即抛错;**合法技能作用域覆盖块**(`.ilife-<skill>{--blue:…}`)照常通过。
1071
+ * **返修 W6(误拦两面)**:① 判定前先 `stripCssComments()`(注释里的 `:root` 不再误拦);
1072
+ * ② 禁入 token 改**边界匹配**(`--pinkish` 不再误拦)。**漏拦面**同时登记于契约 §8.11:
1073
+ * 三禁仍是**文本级**判定(不解析 CSS AST),`html{--blue:…}` 这类等价改写仍属调用方责任。
1074
+ * **未知 token 名不强制**(契约未冻结 token 名闭集的判定方式 → 保持调用方责任,记账见 §8.11)。 */
1075
+ function assertExtraCss(extraCss) {
1076
+ if (extraCss === '')
1077
+ return;
1078
+ const code = stripCssComments(extraCss);
1079
+ // `:root` 是伪类名(CSS 伪类名大小写不敏感)→ 用 `/i` 拦住 `:ROOT` 之类的等价写法。
1080
+ if (/:root(?![A-Za-z0-9_-])/i.test(code)) {
1081
+ throw new StyleSheetExtraCssError('extra-css-root', 'buildStyleSheet: extraCss 不得改写基座(命中 `:root` 选择器);技能主题只许用 `.ilife-<skill>` 作用域覆盖块(契约 doc:299)');
1082
+ }
1083
+ for (const forbidden of STYLE_FORBIDDEN_TOKENS) {
1084
+ if (hasBoundedToken(code, forbidden)) {
1085
+ throw new StyleSheetExtraCssError('extra-css-forbidden-token', 'buildStyleSheet: extraCss 不得引入 Q14 禁入 token(命中 ' + forbidden + ',契约 doc:292)');
1086
+ }
1087
+ }
1088
+ if (/\[\s*data-theme/i.test(code) || /prefers-color-scheme\s*:\s*dark/i.test(code)) {
1089
+ throw new StyleSheetExtraCssError('extra-css-dark-scheme', 'buildStyleSheet: extraCss 不得引入深色区选择器(命中 `[data-theme` 或 `prefers-color-scheme: dark`,契约 doc:292)');
1090
+ }
1091
+ }
1092
+ /** 冻结签名:`buildStyleSheet(input?: StyleSheetInput): StyleSheetOutput`。
1093
+ *
1094
+ * **同源**:不按技能名分支;任何技能主题差异只能来自调用方显式传入的 `extraCss`(doc:300)。
1095
+ * `extraCss` **末尾原样追加**(不加工、不转义);并执行**三禁强制**(D3 修订,见 `assertExtraCss`):
1096
+ * 命中 `:root`/`STYLE_FORBIDDEN_TOKENS`/深色区任一 → 抛不导出的 `StyleSheetError`(带 `code`)。 */
1097
+ export const buildStyleSheet = (input) => {
1098
+ const prefix = normalizePrefix(input);
1099
+ const extraCss = input === undefined || input === null || input.extraCss === undefined ? '' : String(input.extraCss);
1100
+ assertExtraCss(extraCss);
1101
+ const parts = [
1102
+ '/* base-paint 共享样式资产 · ' + STYLE_SHEET_ID + ' · v' + STYLE_VERSION + ' */',
1103
+ rootBlock(),
1104
+ ];
1105
+ for (const section of CONTROL_STYLE_SECTIONS) {
1106
+ parts.push('/* ' + sectionSlug(section) + ' */');
1107
+ parts.push(SECTION_BUILDERS[section](prefix));
1108
+ }
1109
+ if (extraCss !== '')
1110
+ parts.push(extraCss);
1111
+ return Object.freeze({
1112
+ css: parts.join(LF),
1113
+ tokens: Object.freeze(Object.keys(CSS_VAR_TOKENS)),
1114
+ prefix,
1115
+ version: STYLE_VERSION,
1116
+ });
1117
+ };
27
1118
  //# sourceMappingURL=style.js.map