@topmindspace/tms-skills 0.1.17 → 0.1.19

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 (49) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.en.md +3 -3
  3. package/README.md +3 -3
  4. package/package.json +1 -1
  5. package/top-ppt-html/README.en.md +2 -2
  6. package/top-ppt-html/README.md +1 -1
  7. package/top-ppt-html/SKILL.md +4 -4
  8. package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.html +144 -58
  9. package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.html +144 -58
  10. package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.html +144 -58
  11. package/top-ppt-html/assets/examples/2026-09-26-topmind-tms-skills-showcase.html +214 -211
  12. package/top-ppt-html/assets/examples/2026-09-26-topmind-tms-skills-showcase.model.json +33 -39
  13. package/top-ppt-html/assets/pptx-export.js +120 -46
  14. package/top-ppt-html/assets/style-gallery.html +21 -4
  15. package/top-ppt-html/assets/templates/architecture.html +158 -61
  16. package/top-ppt-html/assets/templates/engine.css +23 -11
  17. package/top-ppt-html/assets/templates/presentation.html +158 -63
  18. package/top-ppt-html/assets/templates/research.html +164 -69
  19. package/top-ppt-html/package-lock.json +2 -2
  20. package/top-ppt-html/package.json +1 -1
  21. package/top-ppt-html/references/chart-decision-tree.md +26 -25
  22. package/top-ppt-html/references/components-atoms.md +5 -4
  23. package/top-ppt-html/references/content-rules.md +17 -9
  24. package/top-ppt-html/references/failure-modes.md +31 -3
  25. package/top-ppt-html/references/high-fidelity.md +14 -1
  26. package/top-ppt-html/references/icons.md +2 -2
  27. package/top-ppt-html/references/layout-grammar.md +3 -1
  28. package/top-ppt-html/references/layouts-combo.md +1 -1
  29. package/top-ppt-html/references/layouts-research.md +1 -1
  30. package/top-ppt-html/references/modes.md +1 -1
  31. package/top-ppt-html/references/page-type-matrix.md +10 -6
  32. package/top-ppt-html/references/pptx-export.md +5 -5
  33. package/top-ppt-html/references/tech-design.md +2 -0
  34. package/top-ppt-html/scripts/build_icon_assets.js +92 -0
  35. package/top-ppt-html/scripts/build_pptx.js +226 -71
  36. package/top-ppt-html/scripts/cross_verify.py +84 -1
  37. package/top-ppt-html/scripts/extract_model.py +26 -1
  38. package/top-ppt-html/scripts/icon_lib.js +53 -0
  39. package/top-ppt-html/scripts/icon_raster.js +86 -0
  40. package/top-ppt-html/scripts/layout-constants.json +45 -2
  41. package/top-ppt-html/scripts/lib_layout_regions.js +9 -4
  42. package/top-ppt-html/scripts/model-schema.json +8 -3
  43. package/top-ppt-html/scripts/package_skill.py +1 -0
  44. package/top-ppt-html/scripts/render_from_model.py +254 -37
  45. package/top-ppt-html/scripts/scaffold_report.py +47 -24
  46. package/top-ppt-html/scripts/sync_runtime.py +21 -4
  47. package/top-ppt-html/scripts/test_feedback_gates.py +73 -0
  48. package/top-ppt-html/scripts/validate_pptx.py +162 -3
  49. package/top-ppt-html/scripts/validate_report.py +24 -0
@@ -4,66 +4,51 @@
4
4
  "theme": "light",
5
5
  "title": "TopMind × tms-skills",
6
6
  "subtitle": "HTML + PPT 双交付 · 为正式商务演示而生",
7
- "meta": "产品介绍 · 2026-09-26 · v0.1.17",
7
+ "meta": "产品介绍 · 2026-09-26 · v0.1.19",
8
8
  "agenda": [
9
9
  [
10
10
  "01",
11
- "市面 PPT 技能很多,为何还要 top-ppt-html",
11
+ "为什么是我们",
12
12
  "01 · 为什么是我们"
13
13
  ],
14
14
  [
15
15
  "02",
16
- "tms-skills:整仓可安装的智能体技能",
16
+ "tms-skills",
17
17
  "02 · tms-skills"
18
18
  ],
19
19
  [
20
20
  "03",
21
- "双交付:HTML 即演示,PPTX 可继续编",
21
+ "top-ppt-html",
22
22
  "03 · top-ppt-html"
23
23
  ],
24
24
  [
25
25
  "04",
26
- "模式先定后写:A 演示 · B 研究 · C 架构",
26
+ "三模式",
27
27
  "04 · 三模式"
28
28
  ],
29
29
  [
30
30
  "05",
31
- "交付面三件套:演示 HTML · 可编辑 PPTX · 门禁说明",
31
+ "图表工艺",
32
32
  "05 · 图表工艺"
33
33
  ],
34
34
  [
35
35
  "06",
36
- "0.1.x 能力累进:定位与样张随版本加厚",
37
- "05 · 图表工艺"
38
- ],
39
- [
40
- "07",
41
- "九风格开场即可见,亮暗主题一键贯穿",
36
+ "风格主题",
42
37
  "06 · 风格主题"
43
38
  ],
44
39
  [
45
- "08",
46
- "散装出稿止步,strict 0/0 才算交付",
47
- "07 · 质量门禁"
48
- ],
49
- [
50
- "09",
51
- "从想法到上台:工艺链路如何把主张垫实",
40
+ "07",
41
+ "质量门禁",
52
42
  "07 · 质量门禁"
53
43
  ],
54
44
  [
55
- "10",
56
- "核图优先:排名对比用横向条,一眼分出轻重",
57
- "05 · 图表工艺"
58
- ],
59
- [
60
- "11",
61
- "打开 HTML 即演示:顶栏快捷操作一览",
45
+ "08",
46
+ "Header 工具栏",
62
47
  "08 · Header 工具栏"
63
48
  ],
64
49
  [
65
- "12",
66
- "Fast Mode:跳过问询,不跳过工艺",
50
+ "09",
51
+ "Fast Mode",
67
52
  "09 · Fast Mode"
68
53
  ]
69
54
  ],
@@ -86,7 +71,8 @@
86
71
  "一屏一主张",
87
72
  "标题即行动句,少装饰堆砌"
88
73
  ]
89
- ]
74
+ ],
75
+ "icon": "增长"
90
76
  },
91
77
  {
92
78
  "title": "HTML + PPT 双交付",
@@ -99,7 +85,8 @@
99
85
  "PPTX 按需导出",
100
86
  "版式保真、可继续编辑"
101
87
  ]
102
- ]
88
+ ],
89
+ "icon": "下降"
103
90
  },
104
91
  {
105
92
  "title": "MD3 式克制",
@@ -112,7 +99,8 @@
112
99
  "无 AI chrome",
113
100
  "结论条 tonal 满铺,禁左侧装饰轨"
114
101
  ]
115
- ]
102
+ ],
103
+ "icon": "数据"
116
104
  }
117
105
  ],
118
106
  "soWhat": "产出即上台:同源模型保证 HTML 与 PPTX 一致,图表带数据可核对。"
@@ -135,7 +123,8 @@
135
123
  "同 tag 发版",
136
124
  "安装器与技能整仓对齐"
137
125
  ]
138
- ]
126
+ ],
127
+ "icon": "增长"
139
128
  },
140
129
  {
141
130
  "title": "双通道安装",
@@ -148,7 +137,8 @@
148
137
  "GitHub 跟 HEAD",
149
138
  "日常可 clone / 直装"
150
139
  ]
151
- ]
140
+ ],
141
+ "icon": "下降"
152
142
  },
153
143
  {
154
144
  "title": "当前主技能",
@@ -161,10 +151,11 @@
161
151
  "HTML + PPTX",
162
152
  "同源模型 · 双交付"
163
153
  ]
164
- ]
154
+ ],
155
+ "icon": "数据"
165
156
  }
166
157
  ],
167
- "footnote": "包名 @topmindspace/tms-skills · 当前线 0.1.17(latest)",
158
+ "footnote": "包名 @topmindspace/tms-skills · 当前线 0.1.19(latest)",
168
159
  "soWhat": "安装器与技能同仓同 tag——装一次,版本线对齐。"
169
160
  },
170
161
  {
@@ -227,7 +218,8 @@
227
218
  "大气留白",
228
219
  "证据充分但不堆咨询 dump"
229
220
  ]
230
- ]
221
+ ],
222
+ "icon": "增长"
231
223
  },
232
224
  {
233
225
  "title": "B · 研究",
@@ -240,7 +232,8 @@
240
232
  "结论可核对",
241
233
  "数字与口径双向对齐"
242
234
  ]
243
- ]
235
+ ],
236
+ "icon": "下降"
244
237
  },
245
238
  {
246
239
  "title": "C · 架构",
@@ -253,7 +246,8 @@
253
246
  "少字多结构",
254
247
  "一图说清关系"
255
248
  ]
256
- ]
249
+ ],
250
+ "icon": "数据"
257
251
  }
258
252
  ],
259
253
  "footnote": "意图线索:路演/汇报/演讲 → A;调研/对标 → B;拓扑/方案图 → C",
@@ -555,4 +549,4 @@
555
549
  ]
556
550
  ]
557
551
  }
558
- }
552
+ }
@@ -121,7 +121,8 @@ function regionOf(pageType, slotId, opts) {
121
121
  if (type === 'bar' && slot === 'primary') {
122
122
  var by = P.chartY || 2.95;
123
123
  var bb = opts.bottom != null ? opts.bottom : bottomDefault;
124
- return { x: MX + (P.chartX || 0), y: by, w: CW - (P.chartW || 0), h: Math.max(1.2, bb - by) };
124
+ /* chartX/chartW 历史误用已纠正:与 donut/table/hbar/exhibit 同口径,图表盒对齐版心 (MX, CW) */
125
+ return { x: MX, y: by, w: CW, h: Math.max(1.2, bb - by) };
125
126
  }
126
127
  if (type === 'bar' && slot === 'hbar') {
127
128
  var y0 = P.hbarY0 || 2.6;
@@ -156,6 +157,15 @@ function regionOf(pageType, slotId, opts) {
156
157
  return { x: MX, y: P.heroY || 2.9, w: CW * (P.heroW || 0.52), h: P.heroH || 1.6,
157
158
  dividerX: P.dividerX, metricY0: P.metricY0, metricRowH: P.metricRowH };
158
159
  }
160
+ /* R3:kpi 支撑指标列——落在 dividerX 右侧(默认全宽返回会压住 hero) */
161
+ if (type === 'kpi' && slot === 'metrics') {
162
+ var dxK = P.dividerX != null ? P.dividerX : 0.56;
163
+ return {
164
+ x: MX + CW * dxK + 0.35, y: P.metricY0 || 2.95,
165
+ w: CW * (1 - dxK) - 0.5,
166
+ h: Math.max(0.6, bottomDefault - (P.metricY0 || 2.95)),
167
+ };
168
+ }
159
169
  if (type === 'comparison' && (slot === 'left' || slot === 'primary' || slot === 'right')) {
160
170
  var cg = P.panelGap || 0.4, pw = (CW - cg) / 2;
161
171
  if (slot === 'right') return { x: MX + pw + cg, y: P.panelY || 2.55, w: pw, h: P.panelH || 3.4 };
@@ -188,15 +198,22 @@ function regionOf(pageType, slotId, opts) {
188
198
  }
189
199
  if (type === 'image' && slot === 'primary') {
190
200
  var iy2 = Math.max(P.y || 2.3, opts.top || 0);
191
- return { x: MX, y: iy2, w: CW, h: Math.max(1.2, Math.min(P.h || 4.2, (opts.bottom != null ? opts.bottom : bottomDefault) - iy2)) };
201
+ /* R2:有图注时图片不得越过 contentBottomWithNote(图注落注释带,属 annotation self) */
202
+ var iBottom = (opts.bottom != null) ? opts.bottom
203
+ : (opts.caption ? (L.contentBottomWithNote || 6.4) : bottomDefault);
204
+ return { x: MX, y: iy2, w: CW, h: Math.max(1.2, Math.min(P.h || 4.2, iBottom - iy2)) };
192
205
  }
193
206
  if ((type === 'halftable') && (slot === 'left' || slot === 'primary')) {
194
207
  var rs = PT.research || {};
195
- return { x: MX, y: topDefault, w: CW * (rs.halfTableW || 0.52), h: Math.max(1, bottomDefault - topDefault) };
208
+ var ht = opts.top != null ? opts.top : topDefault;
209
+ var hb = opts.bottom != null ? opts.bottom : bottomDefault;
210
+ return { x: MX, y: ht, w: CW * (rs.halfTableW || 0.52), h: Math.max(1, hb - ht) };
196
211
  }
197
212
  if ((type === 'halftable') && (slot === 'right' || slot === 'secondary')) {
198
213
  var rs2 = PT.research || {};
199
- return { x: MX + CW * (rs2.halfChartX || 0.57), y: topDefault, w: CW * (rs2.halfChartW || 0.43), h: Math.max(1, bottomDefault - topDefault) };
214
+ var ht2 = opts.top != null ? opts.top : topDefault;
215
+ var hb2 = opts.bottom != null ? opts.bottom : bottomDefault;
216
+ return { x: MX + CW * (rs2.halfChartX || 0.57), y: ht2, w: CW * (rs2.halfChartW || 0.43), h: Math.max(1, hb2 - ht2) };
200
217
  }
201
218
  if (type === 'matrix' && slot === 'primary') {
202
219
  var rm = PT.research || {};
@@ -217,7 +234,7 @@ function regionOf(pageType, slotId, opts) {
217
234
  /* __TOPPPT_SCHEMA_START__ */
218
235
  /* ── 由 scripts/sync_runtime.py 从 scripts/model-schema.json 注入 · 禁止手改 ── */
219
236
  /* DSL schema 单源:浏览器端 validateModel 与 scripts/extract_model.py 消费同一份定义 */
220
- var MODEL_SCHEMA = {"version":"0.1","modes":["presentation","research","architecture"],"model":{"required":["title","sections:array","closing.title","closing.points:array"],"optional":["mode","style","theme","subtitle","meta","agenda:array"],"agendaMin":{"presentation":3,"research":3,"architecture":0},"sectionsMin":1,"sectionsRecommended":3,"agendaComfortMax":16},"commonSectionFields":["eyebrow","title","lead","soWhat","footnote","flags","image","exhibitNo","layoutPreset"],"flagsHint":"flags = 待核实/待二次修改条目字符串数组(如 '2027 增速 xx%(口径未定)');渲染为 accent 强调色标注行,提示用户核对。","imageHint":"image = 素材图片/配图占位对象。三选一必填:src(用户图,data: 内联或相对路径,零外链铁律禁 http(s))|items(多图版式:grid/compare/wall,元素 {src?, alt?, caption?, placeholder?})|placeholder:true(无图时出配图占位,原生形状渲染、pictures 不增)。其余可选:layout(full/half/bleed/grid/compare/wall,见 layout-constants.json imageSpec.layouts)、fit(cover 默认 / contain)、caption、alt、hint、ratio。","imageLayoutHint":"layout 与 HTML 版式对应:full=版心全宽图|half=左图右注(points 为右栏注解)|bleed=通栏出血|grid=多图网格(items 2/3/4/6 张)|compare=双图 A/B 对比(items 2 张)|wall=Logo 墙(items 3–12 张小图)。占位符标签文本由双引擎按 imageSpec 拼同一串,保证 A/B 通道逐页文本一致。","chartTypes":["bar","hbar","stack","stackline","line","dualline","area","donut","multidonut","pie","radar","scatter","bubble","waterfall","gauge","pareto","funnel","gantt","vsbar","progress","sparkline","slope","dumbbell","lollipop","dotplot","bulletchart","waffle","radialbar","rose","candlestick"],"chartTypesHint":"chart.type 可取值 = 本表(30 类:16 原生 + 14 形状还原)。其余 6 类复杂信息图(sankey/treemap/boxplot/network/marimekko/streamgraph)是**专属页型**而非 chart.type——它们要按列/段/节点等结构化载荷表达,故走 sections[].type。不变量:charts.registry.types == chartTypes ∪ 上述 6 类页型(sync_runtime.py 校验)。","chartDataTable":{"enum":["notes","inline","appendix","off"],"default":"notes","rule":"非原生图表(registry.pptx=shape)的 dataTable 不得为 off;缺失时按 registry 默认值补齐。"},"pageTypes":{"points":{"label":"要点列表页","modes":["presentation","research","architecture"],"required":["points:array"],"optional":["metrics:array"]},"metrics":{"label":"指标带页(4–6 个核心数字)","modes":["presentation","research"],"required":["metrics:array"]},"kpi":{"label":"大数指标页(hero 大数字 + 支撑指标行)","modes":["presentation","research"],"required":["hero:array"],"optional":["metrics:array"]},"table":{"label":"对比表页","modes":["presentation","research","architecture"],"required":["table.head:array","table.rows:array"],"optional":["table.colW:array"]},"timeline":{"label":"时间线 / 路线页","modes":["presentation","research"],"required":["phases:array"]},"steps":{"label":"步骤条页(N 步横排 + 箭头,可分组)","modes":["presentation","research","architecture"],"required":["steps:array"],"optional":["groups:array","note"]},"bar":{"label":"图表页(chart.type 见 chartTypes;native 通道走原生数据图表,shape 通道走高保真形状还原 + 数据表)","modes":["presentation","research"],"required":["chart.labels:array","chart.values:array"],"optional":["chart.type","chart.series:array","chart.points:array","chart.max","chart.unit","chart.colors:array","chart.start:array","chart.target:array","chart.dataTable","note"]},"donut":{"label":"环形图页(原生 pie 楔形 + 图例)","modes":["presentation","research"],"required":["chart.labels:array","chart.values:array"],"optional":["chart.unit","chart.centerLabel","chart.colors:array","chart.dataTable","note"]},"heatmap":{"label":"热力矩阵页(行 × 列 + 强度色阶)","modes":["presentation","research"],"required":["rowHeads:array","colHeads:array","cells:array"],"optional":["unit","scaleLabel:array","note"]},"bullet":{"label":"达成对比页(实际 vs 目标条)","modes":["presentation","research"],"required":["items:array"],"optional":["unit","max","note"]},"pyramid":{"label":"金字塔页(层级递进,顶层最窄)","modes":["presentation","research","architecture"],"required":["levels:array"],"optional":["note"]},"image":{"label":"素材图片页(full/half/bleed/grid/compare/wall,效果与位置锁定;支持配图占位)","modes":["presentation","research","architecture"],"required":["anyof:image.src:str|image.items:array|image.placeholder"],"optional":["image.caption","image.alt","image.layout","image.fit","image.ratio","image.hint","points:array","note"]},"cards":{"label":"卡片网格页(cards 元素须为 {title, points:[[k,v]…]},不接受元组)","modes":["presentation","research"],"required":["cards:array"],"optional":["columns"]},"split":{"label":"双区组合页(left / right 各可为 要点|图表|表格|图片:type = points(默认)| table | image | 图表类型名;图表/表格字段直接挂在对应侧)","modes":["presentation","research"],"required":["anyof:left.points:array|left.type:str"],"optional":["left.cap","left.image","left.labels:array","left.values:array","left.series:array","left.points:array","left.max","left.unit","left.colors:array","left.dataTable","left.head:array","left.rows:array","left.colW:array","right.type","right.cap","right.image","right.labels:array","right.values:array","right.series:array","right.points:array","right.max","right.unit","right.colors:array","right.dataTable","right.head:array","right.rows:array","right.colW:array"]},"comparison":{"label":"对比页(左右双栏 + 可选结论条)","modes":["presentation","research"],"required":["left.title","left.points:array","right.title","right.points:array"],"optional":["verdict"]},"quote":{"label":"引用 / 金句页(深色全幅)","modes":["presentation","research"],"required":["quote","author"],"optional":["context"]},"diagram":{"label":"分层架构页(architecture 模式走全幅几何)","modes":["presentation","research","architecture"],"required":["layers:array"],"optional":["legend:array"]},"exhibit":{"label":"Exhibit 编号图表页(research R2)","modes":["research"],"required":["chart.labels:array","chart.values:array"],"optional":["exhibitNo","chart.type","chart.series:array","chart.points:array","chart.max","chart.unit","chart.colors:array","chart.dataTable","note"]},"twocol":{"label":"双栏论证页(research R1)","modes":["research"],"required":["paragraphs:array"]},"threecol":{"label":"三栏证据页(research R6)","modes":["research"],"required":["paragraphs:array"]},"halftable":{"label":"半表半图页(research R7)","modes":["research"],"required":["table.head:array","table.rows:array","chart.labels:array","chart.values:array"],"optional":["table.colW:array","chart.type","chart.series:array","chart.points:array","chart.max","chart.unit","chart.colors:array","chart.dataTable","note"]},"matrix":{"label":"矩阵图页(research R8)","modes":["research"],"required":["rowHeads:array","colHeads:array","cells:array"]},"lane":{"label":"泳道页(architecture A2)","modes":["architecture"],"required":["lanes:array"]},"sankey":{"label":"桑基图页(节点-流带,流向与流量)","modes":["research","architecture"],"required":["flows:array"],"optional":["unit","chart.dataTable","note"]},"treemap":{"label":"树图页(面积编码的层级构成)","modes":["research","architecture"],"required":["items:array"],"optional":["unit","chart.dataTable","note"]},"boxplot":{"label":"箱线图页(分布对比:min/q1/median/q3/max)","modes":["research","architecture"],"required":["groups:array"],"optional":["unit","chart.dataTable","note"]},"network":{"label":"关系网络页(节点-边拓扑)","modes":["research","architecture"],"required":["nodes:array","edges:array"],"optional":["chart.dataTable","note"]},"marimekko":{"label":"马赛克图页(列宽 × 列高双重编码)","modes":["research","architecture"],"required":["cols:array","cells:array"],"optional":["unit","chart.dataTable","note"]},"streamgraph":{"label":"流带图页(时间上的构成演变)","modes":["research","architecture"],"required":["series:array"],"optional":["labels:array","chart.dataTable","note"]}}};
237
+ var MODEL_SCHEMA = {"version":"0.1","modes":["presentation","research","architecture"],"model":{"required":["title","sections:array","closing.title","closing.points:array"],"optional":["mode","style","theme","subtitle","meta","agenda:array"],"agendaMin":{"presentation":3,"research":3,"architecture":0},"agendaMax":7,"agendaMaxWithRefs":8,"agendaHint":"agenda 条目 = 章(篇/section),3–7 条;每章可含多页(sections[] 按页建模)。禁止把逐页标题当大纲。sections[].eyebrow 的 `NN ·` 前缀 = 章键,须与某条 agenda 对应;跳转锚点 = 该章第一页。含「参考资料」条目时 +1 仍 ≤8。","sectionsMin":1,"sectionsRecommended":3,"agendaComfortMax":8},"commonSectionFields":["eyebrow","title","lead","soWhat","footnote","flags","image","exhibitNo","layoutPreset"],"mutualExclusionHint":"verdict 与 soWhat 互斥:二者共用 annotation 结论条槽位,同页双填必叠印。comparison 页填 verdict 时不要再填 soWhat(渲染器已自动二选一,但模型侧应只给一个)。","flagsHint":"flags = 待核实/待二次修改条目字符串数组(如 '2027 增速 xx%(口径未定)');渲染为 accent 强调色标注行,提示用户核对。","imageHint":"image = 素材图片/配图占位对象。三选一必填:src(用户图,data: 内联或相对路径,零外链铁律禁 http(s))|items(多图版式:grid/compare/wall,元素 {src?, alt?, caption?, placeholder?})|placeholder:true(无图时出配图占位,原生形状渲染、pictures 不增)。其余可选:layout(full/half/bleed/grid/compare/wall,见 layout-constants.json imageSpec.layouts)、fit(cover 默认 / contain)、caption、alt、hint、ratio。","imageLayoutHint":"layout 与 HTML 版式对应:full=版心全宽图|half=左图右注(points 为右栏注解)|bleed=通栏出血|grid=多图网格(items 2/3/4/6 张)|compare=双图 A/B 对比(items 2 张)|wall=Logo 墙(items 3–12 张小图)。占位符标签文本由双引擎按 imageSpec 拼同一串,保证 A/B 通道逐页文本一致。","chartTypes":["bar","hbar","stack","stackline","line","dualline","area","donut","multidonut","pie","radar","scatter","bubble","waterfall","gauge","pareto","funnel","gantt","vsbar","progress","sparkline","slope","dumbbell","lollipop","dotplot","bulletchart","waffle","radialbar","rose","candlestick"],"chartTypesHint":"chart.type 可取值 = 本表(30 类:16 原生 + 14 形状还原)。其余 6 类复杂信息图(sankey/treemap/boxplot/network/marimekko/streamgraph)是**专属页型**而非 chart.type——它们要按列/段/节点等结构化载荷表达,故走 sections[].type。不变量:charts.registry.types == chartTypes ∪ 上述 6 类页型(sync_runtime.py 校验)。","chartDataTable":{"enum":["notes","inline","appendix","off"],"default":"notes","rule":"非原生图表(registry.pptx=shape)的 dataTable 不得为 off;缺失时按 registry 默认值补齐。"},"pageTypes":{"points":{"label":"要点列表页","modes":["presentation","research","architecture"],"required":["points:array"],"optional":["metrics:array"]},"metrics":{"label":"指标带页(4–6 个核心数字)","modes":["presentation","research"],"required":["metrics:array"]},"kpi":{"label":"大数指标页(hero 大数字 + 支撑指标行)","modes":["presentation","research"],"required":["hero:array"],"optional":["metrics:array"]},"table":{"label":"对比表页","modes":["presentation","research","architecture"],"required":["table.head:array","table.rows:array"],"optional":["table.colW:array"]},"timeline":{"label":"时间线 / 路线页","modes":["presentation","research"],"required":["phases:array"]},"steps":{"label":"步骤条页(N 步横排 + 箭头,可分组)","modes":["presentation","research","architecture"],"required":["steps:array"],"optional":["groups:array","note"]},"bar":{"label":"图表页(chart.type 见 chartTypes;native 通道走原生数据图表,shape 通道走高保真形状还原 + 数据表)","modes":["presentation","research"],"required":["chart.labels:array","chart.values:array"],"optional":["chart.type","chart.series:array","chart.points:array","chart.max","chart.unit","chart.colors:array","chart.start:array","chart.target:array","chart.dataTable","note"]},"donut":{"label":"环形图页(原生 pie 楔形 + 图例)","modes":["presentation","research"],"required":["chart.labels:array","chart.values:array"],"optional":["chart.unit","chart.centerLabel","chart.colors:array","chart.dataTable","note"]},"heatmap":{"label":"热力矩阵页(行 × 列 + 强度色阶)","modes":["presentation","research"],"required":["rowHeads:array","colHeads:array","cells:array"],"optional":["unit","scaleLabel:array","note"]},"bullet":{"label":"达成对比页(实际 vs 目标条)","modes":["presentation","research"],"required":["items:array"],"optional":["unit","max","note"]},"pyramid":{"label":"金字塔页(层级递进,顶层最窄)","modes":["presentation","research","architecture"],"required":["levels:array"],"optional":["note"]},"image":{"label":"素材图片页(full/half/bleed/grid/compare/wall,效果与位置锁定;支持配图占位)","modes":["presentation","research","architecture"],"required":["anyof:image.src:str|image.items:array|image.placeholder"],"optional":["image.caption","image.alt","image.layout","image.fit","image.ratio","image.hint","points:array","note"]},"cards":{"label":"卡片网格页(cards 元素须为 {title, points:[[k,v]…], icon?},不接受元组)","modes":["presentation","research"],"required":["cards:array"],"optional":["columns"],"cardItemHint":"cards[] 元素:{title:str, points:[[k,v]…]|[{t,d}…]|[str…], icon?:str}。icon = 语义图标名(icon_lib.js 键:增长/下降/数据/图表/趋势/占比/表格/效率/成果/安全/权限/检查/风险/团队/流程/计划/智能/洞察/工具/清单),HTML 与 PPTX 同名真导出;缺省按标题语义自动挑选。"},"split":{"label":"双区组合页(left / right 各可为 要点|图表|表格|图片:type = points(默认)| table | image | 图表类型名;图表/表格字段直接挂在对应侧)","modes":["presentation","research"],"required":["anyof:left.points:array|left.type:str"],"optional":["left.cap","left.image","left.labels:array","left.values:array","left.series:array","left.points:array","left.max","left.unit","left.colors:array","left.dataTable","left.head:array","left.rows:array","left.colW:array","right.type","right.cap","right.image","right.labels:array","right.values:array","right.series:array","right.points:array","right.max","right.unit","right.colors:array","right.dataTable","right.head:array","right.rows:array","right.colW:array"]},"comparison":{"label":"对比页(左右双栏 + 可选结论条)","modes":["presentation","research"],"required":["left.title","left.points:array","right.title","right.points:array"],"optional":["verdict"]},"quote":{"label":"引用 / 金句页(深色全幅)","modes":["presentation","research"],"required":["quote","author"],"optional":["context"]},"diagram":{"label":"分层架构页(architecture 模式走全幅几何)","modes":["presentation","research","architecture"],"required":["layers:array"],"optional":["legend:array"]},"exhibit":{"label":"Exhibit 编号图表页(research R2)","modes":["research"],"required":["chart.labels:array","chart.values:array"],"optional":["exhibitNo","chart.type","chart.series:array","chart.points:array","chart.max","chart.unit","chart.colors:array","chart.dataTable","note"]},"twocol":{"label":"双栏论证页(research R1)","modes":["research"],"required":["paragraphs:array"]},"threecol":{"label":"三栏证据页(research R6)","modes":["research"],"required":["paragraphs:array"]},"halftable":{"label":"半表半图页(research R7)","modes":["research"],"required":["table.head:array","table.rows:array","chart.labels:array","chart.values:array"],"optional":["table.colW:array","chart.type","chart.series:array","chart.points:array","chart.max","chart.unit","chart.colors:array","chart.dataTable","note"]},"matrix":{"label":"矩阵图页(research R8)","modes":["research"],"required":["rowHeads:array","colHeads:array","cells:array"]},"lane":{"label":"泳道页(architecture A2)","modes":["architecture"],"required":["lanes:array"]},"sankey":{"label":"桑基图页(节点-流带,流向与流量)","modes":["research","architecture"],"required":["flows:array"],"optional":["unit","chart.dataTable","note"]},"treemap":{"label":"树图页(面积编码的层级构成)","modes":["research","architecture"],"required":["items:array"],"optional":["unit","chart.dataTable","note"]},"boxplot":{"label":"箱线图页(分布对比:min/q1/median/q3/max)","modes":["research","architecture"],"required":["groups:array"],"optional":["unit","chart.dataTable","note"]},"network":{"label":"关系网络页(节点-边拓扑)","modes":["research","architecture"],"required":["nodes:array","edges:array"],"optional":["chart.dataTable","note"]},"marimekko":{"label":"马赛克图页(列宽 × 列高双重编码)","modes":["research","architecture"],"required":["cols:array","cells:array"],"optional":["unit","chart.dataTable","note"]},"streamgraph":{"label":"流带图页(时间上的构成演变)","modes":["research","architecture"],"required":["series:array"],"optional":["labels:array","chart.dataTable","note"]}}};
221
238
  /* __TOPPPT_SCHEMA_END__ */
222
239
 
223
240
  var _mode = 'presentation'; /* 当前模式(slidesOf 里按 model.mode 设置) */
@@ -508,6 +525,15 @@ function footnoteLine(sh, text, S) {
508
525
  sh.push(txSp(R.x, R.y, R.w, R.h,
509
526
  [[{ t: text, sz: 9.5, col: S.faint, font: S.font }]]));
510
527
  }
528
+ /* R2:sec.note 与 sec.footnote 共用注释带末行(footnoteY=6.72)。
529
+ note.y=6.55 会压进 so-what 带(6.05–6.67)——同页二者必须合并为一行。 */
530
+ function noteOrFootnote(sh, sec, S) {
531
+ var parts = [];
532
+ if (sec && sec.note) parts.push(String(sec.note));
533
+ if (sec && sec.footnote) parts.push(String(sec.footnote));
534
+ if (!parts.length) return;
535
+ footnoteLine(sh, parts.join(' · '), S);
536
+ }
511
537
  /* 待核实清单条(accent 强调色标注 · 与 build_pptx.js / HTML .flagbar 同源) */
512
538
  function flagBar(sh, items, S, yTop) {
513
539
  var F = PT.flagbar || { hdH: 0.26, rowH: 0.24, maxRows: 3, gap: 0.12 };
@@ -696,7 +722,9 @@ function vbarShapes(sh, ec, S, x, y, w, h, dcols) {
696
722
  : [{ name: '数值', values: ec.values || [] }];
697
723
  var nb = Math.max(1, labels.length);
698
724
  var ns = Math.max(1, series.length);
699
- var baseY = y + h, plotW = w, step = plotW / nb;
725
+ /* R2:类目标签带必须含在 h 内(原 baseY+0.08 画在图区外,会压进 so-what/来源行) */
726
+ var labelH = 0.36;
727
+ var baseY = y + Math.max(0.6, h - labelH), plotW = w, step = plotW / nb;
700
728
  var cluster = Math.min(1.1, step * (ns > 1 ? 0.78 : 0.55));
701
729
  var bw = cluster / ns;
702
730
  var all = [];
@@ -705,26 +733,27 @@ function vbarShapes(sh, ec, S, x, y, w, h, dcols) {
705
733
  });
706
734
  var vmax = ec.max || Math.max.apply(null, all.concat([1]));
707
735
  var top = all.length ? Math.max.apply(null, all) : 0;
736
+ var plotH = Math.max(0.35, baseY - y);
708
737
  sh.push(shape('rect', x, baseY, plotW, 0.02, S.line));
709
738
  series.forEach(function (se, si) {
710
739
  var vals = se.values || [];
711
740
  var col = dcols ? dcols[si % 5] : (si === 0 ? S.accent : S.faint);
712
741
  vals.forEach(function (v, i) {
713
742
  var num = Number(v) || 0;
714
- var hh = Math.max(0.05, num / vmax * (h - 0.5));
743
+ var hh = Math.max(0.05, num / vmax * (plotH - 0.35));
715
744
  var bx = x + i * step + (step - cluster) / 2 + si * bw;
716
745
  var hot = ns === 1 && num === top;
717
746
  sh.push(shape('roundRect', bx, baseY - hh, Math.max(0.04, bw * 0.92), hh,
718
747
  hot ? (dcols ? dcols[i % 5] : S.accent) : col, { adj: 6000 }));
719
748
  if (ns === 1) {
720
- sh.push(txSp(bx - 0.3, baseY - hh - 0.4, bw + 0.6, 0.32,
749
+ sh.push(txSp(bx - 0.3, baseY - hh - 0.36, bw + 0.6, 0.30,
721
750
  [[{ t: valText(num, ec.unit), sz: 12.5, b: 1, col: hot ? S.accent : S.body, font: S.font }]], { align: 'ctr' }));
722
751
  }
723
752
  });
724
753
  });
725
754
  labels.forEach(function (lb, i) {
726
755
  var lx = x + i * step;
727
- sh.push(txSp(lx, baseY + 0.08, step, 0.32,
756
+ sh.push(txSp(lx, baseY + 0.06, step, labelH - 0.08,
728
757
  [[{ t: lb, sz: 10.5, col: S.faint, font: S.font }]], { align: 'ctr' }));
729
758
  });
730
759
  /* Multi-series legend along top of plot (keeps category labels; avoids blank chart). */
@@ -747,7 +776,10 @@ function lineShapes(sh, ec, S, x, y, w, h, dcols) {
747
776
  var all = [];
748
777
  series.forEach(function (se) { (se.values || []).forEach(function (v) { all.push(Number(v) || 0); }); });
749
778
  var vmax = ec.max || Math.max.apply(null, all.concat([1]));
750
- var plotH = h - 0.5, baseY = y + h;
779
+ /* R2:类目标签带含在 h 内,禁画到图区外压 so-what */
780
+ var labelH = 0.36;
781
+ var baseY = y + Math.max(0.6, h - labelH);
782
+ var plotH = Math.max(0.35, baseY - y - 0.2);
751
783
  sh.push(shape('rect', x, baseY, w, 0.02, S.line));
752
784
  var stepX = n > 1 ? w / (n - 1) : 0;
753
785
  series.forEach(function (se, si) {
@@ -767,7 +799,7 @@ function lineShapes(sh, ec, S, x, y, w, h, dcols) {
767
799
  });
768
800
  labels.forEach(function (lb, i) {
769
801
  var px = x + (n > 1 ? i * stepX : w / 2);
770
- sh.push(txSp(px - 0.5, baseY + 0.08, 1.0, 0.32,
802
+ sh.push(txSp(px - 0.5, baseY + 0.06, 1.0, labelH - 0.08,
771
803
  [[{ t: lb, sz: 10.5, col: S.faint, font: S.font }]], { align: 'ctr' }));
772
804
  });
773
805
  }
@@ -1219,25 +1251,44 @@ function slidesOf(model, S) {
1219
1251
  c.push(txSp(MX, CV.subtitleY, CW - 1, CV.subtitleH, [[{ t: model.subtitle || '', sz: 18, col: S.soft, font: S.font }]]));
1220
1252
  slides.push(c);
1221
1253
 
1222
- /* Agenda(>8 条自动双列,行高自适应防溢出;architecture 极简形态可省略 agenda) */
1254
+ /* Agenda(>8 条自动双列;>12 条自动分页;长标题截断+全称入 notes;列优先与 HTML 一致) */
1223
1255
  var items = model.agenda || [];
1224
1256
  if (items.length) {
1225
- var ag = [];
1226
- head(ag, 'AGENDA', '报告大纲');
1227
- var colN = items.length > 8 ? 2 : 1;
1228
- var perCol = Math.ceil(items.length / colN) || 1;
1229
- var rowH = Math.min(1.05, (CONTENT_BOTTOM - CONTENT_TOP - 0.25) / Math.max(1, perCol));
1230
- var numSz = rowH >= 0.85 ? 30 : (rowH >= 0.65 ? 22 : 18);
1231
- var tSz = rowH >= 0.85 ? 16 : 14, dSz = rowH >= 0.85 ? 12 : 11;
1232
- items.forEach(function (it, i) {
1233
- var col = Math.floor(i / perCol), row = i % perCol;
1234
- var x = MX + col * (CW / colN), y = CONTENT_TOP + row * rowH;
1235
- ag.push(txSp(x, y, 0.9, Math.min(0.9, rowH), [[{ t: it[0], sz: numSz, b: 1, col: S.accent, font: S.font }]]));
1236
- ag.push(txSp(x + 1.0, y + 0.03, CW / colN - 1.1, Math.min(0.95, rowH),
1237
- [[{ t: it[1], sz: tSz, b: 1, col: S.ink, font: S.font }],
1238
- [{ t: it[2] || '', sz: dSz, col: S.body, font: S.font }]], { lineSpacing: tSz + 5 }));
1257
+ var ROWS_PER_PAGE = 12;
1258
+ var agPages = [];
1259
+ for (var ai = 0; ai < items.length; ai += ROWS_PER_PAGE) agPages.push(items.slice(ai, ai + ROWS_PER_PAGE));
1260
+ agPages.forEach(function (pageItems, pi) {
1261
+ var ag = [];
1262
+ var sub = agPages.length > 1 ? '(' + (pi + 1) + '/' + agPages.length + ')' : '';
1263
+ head(ag, 'AGENDA', '报告大纲' + sub);
1264
+ var colN = pageItems.length > 8 ? 2 : 1;
1265
+ var perCol = Math.ceil(pageItems.length / colN) || 1;
1266
+ var rowH = Math.min(1.05, (CONTENT_BOTTOM - CONTENT_TOP - 0.25) / Math.max(1, perCol));
1267
+ var numSz = rowH >= 0.85 ? 30 : (rowH >= 0.65 ? 22 : 18);
1268
+ var tSz = rowH >= 0.85 ? 16 : 14, dSz = rowH >= 0.85 ? 12 : 11;
1269
+ var agNotes = [];
1270
+ pageItems.forEach(function (it, i) {
1271
+ var col = Math.floor(i / perCol), row = i % perCol;
1272
+ var x = MX + col * (CW / colN), y = CONTENT_TOP + row * rowH;
1273
+ var titleBoxW = CW / colN - 1.15;
1274
+ var rawTitle = String(it[1] || '');
1275
+ var titleEstW = estTextH(rawTitle, titleBoxW, tSz, 1.2);
1276
+ var maxTitleH = rowH * 0.55;
1277
+ var title = rawTitle;
1278
+ if (titleEstW > maxTitleH) {
1279
+ var maxChars = Math.max(10, Math.floor(rawTitle.length * (maxTitleH / titleEstW)));
1280
+ if (rawTitle.length > maxChars) {
1281
+ title = rawTitle.slice(0, maxChars - 1) + '…';
1282
+ agNotes.push('条目 ' + it[0] + ' 全称:' + rawTitle);
1283
+ }
1284
+ }
1285
+ ag.push(txSp(x, y, 0.9, Math.min(0.9, rowH), [[{ t: it[0], sz: numSz, b: 1, col: S.accent, font: S.font }]]));
1286
+ ag.push(txSp(x + 1.0, y + 0.03, titleBoxW, Math.min(0.95, rowH),
1287
+ [[{ t: title, sz: tSz, b: 1, col: S.ink, font: S.font }],
1288
+ [{ t: it[2] || '', sz: dSz, col: S.body, font: S.font }]], { lineSpacing: tSz + 5 }));
1289
+ });
1290
+ slides.push(ag);
1239
1291
  });
1240
- slides.push(ag);
1241
1292
  }
1242
1293
 
1243
1294
  /* 章节页 */
@@ -1323,12 +1374,13 @@ function slidesOf(model, S) {
1323
1374
  { t: p[1], sz: fz - 1, col: S.body, font: S.font }]]));
1324
1375
  });
1325
1376
  });
1377
+ /* R2:verdict 与 soWhat 共用 annotation 槽位——只画一个(verdict 优先) */
1326
1378
  if (sec.verdict) {
1327
1379
  var vR = regOf('exhibit', 'annotation') || { x: MX, y: PT.exhibit.soWhatY, w: CW, h: 0.62 };
1328
- sh.push(shape('rect', vR.x, vR.y, vR.w, vR.h, S.accent));
1380
+ sh.push(shape('rect', vR.x, vR.y, vR.w, vR.h, S.accent, { name: 'tr:soWhat' }));
1329
1381
  sh.push(txSp(vR.x + 0.22, vR.y + 0.06, vR.w - 0.44, vR.h - 0.12,
1330
1382
  [[{ t: '结论 ', sz: 11, b: 1, col: S.onAccent, font: S.font, spc: 150 },
1331
- { t: sec.verdict, sz: 12.5, b: 1, col: S.onAccent, font: S.font }]], { anchor: 'ctr' }));
1383
+ { t: sec.verdict, sz: 12.5, b: 1, col: S.onAccent, font: S.font }]], { anchor: 'ctr', name: 'tr:soWhat' }));
1332
1384
  }
1333
1385
  } else if (type === 'quote') {
1334
1386
  var Q = PT.quote;
@@ -1361,7 +1413,6 @@ function slidesOf(model, S) {
1361
1413
  } else if (type === 'donut') {
1362
1414
  var dcn = sec.chart || {};
1363
1415
  if (dcn.labels && dcn.values) donutChart(sh, dcn, S, dcn.colors || donutColors(model.style, S, model.theme), bodyBottom);
1364
- if (sec.note) sh.push(txSp(MX, PT.note.y, CW, PT.note.h, [[{ t: sec.note, sz: 11, col: S.faint, font: S.font }]]));
1365
1416
  } else if (type === 'heatmap') {
1366
1417
  var H = PT.heatmap;
1367
1418
  var hR = regOf('heatmap', 'primary', { bottom: bodyBottom }) || { x: MX, y: H.y, w: CW, h: bodyBottom - H.y };
@@ -1508,7 +1559,7 @@ function slidesOf(model, S) {
1508
1559
  } else if (type === 'bar') {
1509
1560
  var ch = sec.chart || {};
1510
1561
  var dcols2 = (ch.colors || dataColors(model.style, model.theme));
1511
- var botY = chartBottom(!!sec.soWhat, !!sec.footnote) - (sec.note ? 0.42 : 0.05);
1562
+ var botY = chartBottom(!!sec.soWhat, !!(sec.footnote || sec.note)) - 0.05;
1512
1563
  var isHBar = ch.type === 'hbar';
1513
1564
  var bRR = regOf('bar', isHBar ? 'hbar' : 'primary', { bottom: botY }) ||
1514
1565
  { x: isHBar ? MX : MX + PT.bar.chartX, y: isHBar ? PT.bar.hbarY0 : PT.bar.chartY,
@@ -1535,13 +1586,12 @@ function slidesOf(model, S) {
1535
1586
  } else {
1536
1587
  chartBlockShapes(sh, ch, S, bx, bRR.y, bw, Math.max(1.5, bRR.h), dcols2);
1537
1588
  }
1538
- if (sec.note) sh.push(txSp(MX, PT.note.y, CW, PT.note.h, [[{ t: sec.note, sz: 11, col: S.faint, font: S.font }]]));
1539
1589
  } else if (type === 'twocol' || type === 'threecol') {
1540
1590
  var ps2 = sec.paragraphs || [];
1541
1591
  var nCol = (type === 'threecol') ? 3 : 2;
1542
1592
  var gapC = (type === 'threecol') ? PT.research.col3Gap : PT.twocol.colGap;
1543
- var tcR = regOf('twocol', 'primary', { top: bodyY, bottom: bodyBottom }) ||
1544
- { x: MX, y: bodyY, w: CW, h: bodyBottom - bodyY };
1593
+ /* R3:多栏均分用版心全宽(regOf('twocol') 返回单栏宽,当总宽会压扁三栏) */
1594
+ var tcR = { x: MX, y: bodyY, w: CW, h: bodyBottom - bodyY };
1545
1595
  var gC = cols(tcR.w, nCol, gapC);
1546
1596
  var perC = Math.ceil(ps2.length / nCol);
1547
1597
  var availC = tcR.h;
@@ -1737,7 +1787,7 @@ function slidesOf(model, S) {
1737
1787
  var dStart = dgR.y;
1738
1788
  var dEnd = dgR.y + dgR.h;
1739
1789
  var nLay = Math.max(1, lys.length);
1740
- var lh2 = Math.max(0.6, Math.min(D.maxLayerH, (dEnd - dStart) / nLay - D.layerGap));
1790
+ var lh2 = Math.max(0.5, Math.min(D.maxLayerH, (dEnd - dStart) / nLay - D.layerGap));
1741
1791
  lys.forEach(function (lay, li) {
1742
1792
  var y9 = dStart + li * (lh2 + D.layerGap);
1743
1793
  var focus = lay[2] === 'focus';
@@ -1753,9 +1803,26 @@ function slidesOf(model, S) {
1753
1803
  var ntt = isRec(nd) ? nd.t : nd;
1754
1804
  var nds = isRec(nd) ? (nd.d || '') : '';
1755
1805
  sh.push(shape('roundRect', x10, y9, nw2, lh2, ndacc ? S.soft : S.surface, { adj: 4000, line: S.line }));
1756
- sh.push(txSp(x10 + 0.12, y9 + (nds ? 0.12 : lh2 / 2 - 0.2), nw2 - 0.24, 0.38,
1757
- [[{ t: ntt, sz: 12.5, b: 1, col: ndacc ? S.accent : S.ink, font: S.font }]], { anchor: nds ? 't' : 'ctr' }));
1758
- if (nds) sh.push(txSp(x10 + 0.12, y9 + lh2 - 0.5, nw2 - 0.24, 0.4, [[{ t: nds, sz: 10, col: S.faint, font: S.font }]]));
1806
+ /* R1: 注解不得「浮回」标题上。标题顶对齐,注解钳在标题下边;空间不足降级单框混排。 */
1807
+ var padXn = 0.12, padYn = 0.10;
1808
+ var innerWn = nw2 - padXn * 2;
1809
+ var titleHn = 0.32, noteHn = 0.34, gapTNn = 0.04;
1810
+ var needHn = titleHn + gapTNn + (nds ? noteHn : 0) + padYn * 2;
1811
+ if (!nds || lh2 >= needHn) {
1812
+ var tYn = y9 + padYn;
1813
+ sh.push(txSp(x10 + padXn, nds ? tYn : y9 + lh2 / 2 - 0.18, innerWn, titleHn,
1814
+ [[{ t: ntt, sz: nds ? 11 : 12.5, b: 1, col: ndacc ? S.accent : S.ink, font: S.font }]],
1815
+ { anchor: 'ctr' }));
1816
+ if (nds) {
1817
+ var nYn = Math.max(tYn + titleHn + gapTNn, y9 + lh2 - padYn - noteHn);
1818
+ sh.push(txSp(x10 + padXn, nYn, innerWn, Math.min(noteHn, y9 + lh2 - padYn - nYn),
1819
+ [[{ t: nds, sz: 10, col: S.faint, font: S.font }]], { anchor: 't' }));
1820
+ }
1821
+ } else {
1822
+ sh.push(txSp(x10 + padXn, y9 + padYn * 0.5, innerWn, lh2 - padYn,
1823
+ [[{ t: ntt, sz: 11, b: 1, col: ndacc ? S.accent : S.ink, font: S.font }],
1824
+ [{ t: nds, sz: 9, col: S.faint, font: S.font }]], { anchor: 't', lineSpacing: 12 }));
1825
+ }
1759
1826
  });
1760
1827
  if (li < lys.length - 1) {
1761
1828
  var cy2 = y9 + lh2 + D.layerGap / 2;
@@ -1765,10 +1832,14 @@ function slidesOf(model, S) {
1765
1832
  }
1766
1833
  });
1767
1834
  if (sec.legend) {
1768
- var lx2 = MX;
1769
- sec.legend.forEach(function (lg2) {
1770
- sh.push(txSp(lx2, PH - 0.62, 3.2, 0.3, [[{ t: '● ' + lg2, sz: 10, col: S.faint, font: S.font }]]));
1771
- lx2 += 2.0;
1835
+ /* R2:图例进图区底部(与 HTML .arch__legend 同源),禁用 PH-0.62 固定偏移 */
1836
+ var lgH2 = 0.28;
1837
+ var lgY2 = Math.max(dStart, bodyBottom - lgH2 - 0.02);
1838
+ var nLg2 = Math.max(1, sec.legend.length);
1839
+ var step2 = Math.min(2.4, CW / nLg2);
1840
+ sec.legend.forEach(function (lg2, li2) {
1841
+ sh.push(txSp(MX + CW - nLg2 * step2 + li2 * step2, lgY2, step2 - 0.05, lgH2,
1842
+ [[{ t: '● ' + lg2, sz: 10, col: S.faint, font: S.font }]]));
1772
1843
  });
1773
1844
  }
1774
1845
  } else if (type === 'exhibit') {
@@ -1825,8 +1896,10 @@ function slidesOf(model, S) {
1825
1896
  }
1826
1897
  }
1827
1898
  /* research 通用可选件:so-what 结论条 + 页脚来源行 + 待核实条(共用同一槽位) */
1828
- if (sec.soWhat) soWhatBar(sh, sec.soWhat, S);
1829
- if (sec.footnote) footnoteLine(sh, sec.footnote, S);
1899
+ /* R2:verdict 已占 annotation 槽位时跳过 soWhat,禁止同槽双条叠印 */
1900
+ if (sec.soWhat && !sec.verdict) soWhatBar(sh, sec.soWhat, S);
1901
+ /* R2:note 与 footnote 共用注释带末行(禁 note.y=6.55 压 so-what) */
1902
+ noteOrFootnote(sh, sec, S);
1830
1903
  if (flagH) flagBar(sh, flagItems, S, flagY);
1831
1904
  slides.push(sh);
1832
1905
  });
@@ -1996,9 +2069,10 @@ function validateModel(model) {
1996
2069
  }
1997
2070
  var agMax = (M.model && M.model.agendaComfortMax) || 16;
1998
2071
  if ((model.agenda || []).length > agMax) {
1999
- warnings.push('agenda ' + model.agenda.length + ' 条超出单页舒适上限 ' + agMax + ',建议拆分');
2072
+ warnings.push('agenda ' + model.agenda.length + ' 条超出单页舒适上限 ' + agMax + ',将自动分页');
2000
2073
  }
2001
- var pages = 1 + (hasAgenda ? 1 : 0) + secs.length + 1; /* 封面+大纲(可选)+章节+收尾 */
2074
+ var agPageCount = hasAgenda ? Math.ceil(model.agenda.length / 12) : 0;
2075
+ var pages = 1 + agPageCount + secs.length + 1; /* 封面+大纲(可多页)+章节+收尾 */
2002
2076
  return { ok: missing.length === 0, missing: missing, warnings: warnings, pages: pages };
2003
2077
  }
2004
2078
 
@@ -249,7 +249,8 @@ function regionOf(pageType, slotId, opts) {
249
249
  if (type === 'bar' && slot === 'primary') {
250
250
  var by = P.chartY || 2.95;
251
251
  var bb = opts.bottom != null ? opts.bottom : bottomDefault;
252
- return { x: MX + (P.chartX || 0), y: by, w: CW - (P.chartW || 0), h: Math.max(1.2, bb - by) };
252
+ /* chartX/chartW 历史误用已纠正:与 donut/table/hbar/exhibit 同口径,图表盒对齐版心 (MX, CW) */
253
+ return { x: MX, y: by, w: CW, h: Math.max(1.2, bb - by) };
253
254
  }
254
255
  if (type === 'bar' && slot === 'hbar') {
255
256
  var y0 = P.hbarY0 || 2.6;
@@ -284,6 +285,15 @@ function regionOf(pageType, slotId, opts) {
284
285
  return { x: MX, y: P.heroY || 2.9, w: CW * (P.heroW || 0.52), h: P.heroH || 1.6,
285
286
  dividerX: P.dividerX, metricY0: P.metricY0, metricRowH: P.metricRowH };
286
287
  }
288
+ /* R3:kpi 支撑指标列——落在 dividerX 右侧(默认全宽返回会压住 hero) */
289
+ if (type === 'kpi' && slot === 'metrics') {
290
+ var dxK = P.dividerX != null ? P.dividerX : 0.56;
291
+ return {
292
+ x: MX + CW * dxK + 0.35, y: P.metricY0 || 2.95,
293
+ w: CW * (1 - dxK) - 0.5,
294
+ h: Math.max(0.6, bottomDefault - (P.metricY0 || 2.95)),
295
+ };
296
+ }
287
297
  if (type === 'comparison' && (slot === 'left' || slot === 'primary' || slot === 'right')) {
288
298
  var cg = P.panelGap || 0.4, pw = (CW - cg) / 2;
289
299
  if (slot === 'right') return { x: MX + pw + cg, y: P.panelY || 2.55, w: pw, h: P.panelH || 3.4 };
@@ -316,15 +326,22 @@ function regionOf(pageType, slotId, opts) {
316
326
  }
317
327
  if (type === 'image' && slot === 'primary') {
318
328
  var iy2 = Math.max(P.y || 2.3, opts.top || 0);
319
- return { x: MX, y: iy2, w: CW, h: Math.max(1.2, Math.min(P.h || 4.2, (opts.bottom != null ? opts.bottom : bottomDefault) - iy2)) };
329
+ /* R2:有图注时图片不得越过 contentBottomWithNote(图注落注释带,属 annotation self) */
330
+ var iBottom = (opts.bottom != null) ? opts.bottom
331
+ : (opts.caption ? (L.contentBottomWithNote || 6.4) : bottomDefault);
332
+ return { x: MX, y: iy2, w: CW, h: Math.max(1.2, Math.min(P.h || 4.2, iBottom - iy2)) };
320
333
  }
321
334
  if ((type === 'halftable') && (slot === 'left' || slot === 'primary')) {
322
335
  var rs = PT.research || {};
323
- return { x: MX, y: topDefault, w: CW * (rs.halfTableW || 0.52), h: Math.max(1, bottomDefault - topDefault) };
336
+ var ht = opts.top != null ? opts.top : topDefault;
337
+ var hb = opts.bottom != null ? opts.bottom : bottomDefault;
338
+ return { x: MX, y: ht, w: CW * (rs.halfTableW || 0.52), h: Math.max(1, hb - ht) };
324
339
  }
325
340
  if ((type === 'halftable') && (slot === 'right' || slot === 'secondary')) {
326
341
  var rs2 = PT.research || {};
327
- return { x: MX + CW * (rs2.halfChartX || 0.57), y: topDefault, w: CW * (rs2.halfChartW || 0.43), h: Math.max(1, bottomDefault - topDefault) };
342
+ var ht2 = opts.top != null ? opts.top : topDefault;
343
+ var hb2 = opts.bottom != null ? opts.bottom : bottomDefault;
344
+ return { x: MX + CW * (rs2.halfChartX || 0.57), y: ht2, w: CW * (rs2.halfChartW || 0.43), h: Math.max(1, hb2 - ht2) };
328
345
  }
329
346
  if (type === 'matrix' && slot === 'primary') {
330
347
  var rm = PT.research || {};