@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
@@ -330,7 +330,13 @@ border-color:color-mix(in srgb,var(--accent-on) 20%,transparent);color:var(--acc
330
330
  rows-2 / rows-3:等高行分区(纵向节奏,替代等高卡片堆叠) */
331
331
  .g-211{grid-template-columns:minmax(0,2fr) minmax(0,1fr) minmax(0,1fr)}
332
332
  .g-121{grid-template-columns:minmax(0,1fr) minmax(0,2fr) minmax(0,1fr)}
333
- .g-quad{grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-rows:minmax(0,1fr);align-items:stretch}
333
+ .g-quad{grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-rows:minmax(min-content,1fr);align-items:stretch}
334
+ .g-half{grid-template-columns:minmax(0,1.05fr) minmax(0,1fr);align-items:stretch}
335
+ @media (max-width:1180px){.g-half{grid-template-columns:1fr}}
336
+ .g-2--equal,.g-3--equal,.g-4--equal{grid-auto-rows:minmax(min-content,1fr);align-items:stretch}
337
+ .grid.g-2>.card,.grid.g-3>.card,.grid.g-4>.card,
338
+ .grid.g-quad>.card,.grid.g-half>.card,.grid.g-2--equal>.card,
339
+ .grid.g-3--equal>.card,.grid.g-4--equal>.card{align-self:stretch}
334
340
  .g-aside{grid-template-columns:minmax(0,1fr) minmax(200px,.36fr);align-items:start}
335
341
  .g-aside__side{position:sticky;top:calc(var(--bar-h) + 16px)}
336
342
  .g-mosaic{grid-template-columns:repeat(3,minmax(0,1fr));
@@ -346,7 +352,7 @@ grid-template-areas:"main main main" "s1 s2 s3"}
346
352
  .kv--2col{grid-template-columns:auto minmax(0,1fr) auto minmax(0,1fr)}
347
353
  /* ── 拼贴栅格 g-bento(Bento / 马赛克):4 列基准 + 跨列/跨行工具类 ──
348
354
  .sp-2/.sp-3/.sp-4 跨列(span),.rw-2 跨行;窄屏自动降列并收敛 span。 */
349
- .g-bento{grid-template-columns:repeat(4,minmax(0,1fr));grid-auto-rows:minmax(118px,auto)}
355
+ .g-bento{grid-template-columns:repeat(4,minmax(0,1fr));grid-auto-rows:minmax(min-content,1fr)}
350
356
  .sp-2{grid-column:span 2}.sp-3{grid-column:span 3}.sp-4{grid-column:span 4}
351
357
  .rw-2{grid-row:span 2}
352
358
  @media (max-width:1180px){.g-bento{grid-template-columns:repeat(2,minmax(0,1fr))}
@@ -381,19 +387,21 @@ background:color-mix(in srgb,var(--bg) 86%,transparent);
381
387
  backdrop-filter:saturate(160%) blur(14px);-webkit-backdrop-filter:saturate(160%) blur(14px);
382
388
  border-bottom:1px solid var(--border-soft);
383
389
  transition:height var(--dur-medium) var(--ease-standard)}
384
- .bar__in{height:100%;display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4)}
385
- .brand{display:flex;align-items:center;gap:var(--sp-3);min-width:0}
390
+ .bar__in{height:100%;display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4);overflow:hidden}
391
+ .brand{display:flex;align-items:center;gap:var(--sp-3);min-width:0;flex:1 1 auto;overflow:hidden}
386
392
  .brand__mark{width:34px;height:34px;flex:none;border-radius:10px;background:var(--accent);
387
393
  color:var(--accent-on);display:grid;place-items:center;font-size:13px;font-weight:700}
394
+ .brand__txt{min-width:0;overflow:hidden;flex:1 1 auto}
388
395
  .brand__title{font-size:var(--fs-sm);font-weight:600;color:var(--text);white-space:nowrap;
389
- overflow:hidden;text-overflow:ellipsis}
396
+ overflow:hidden;text-overflow:ellipsis;max-width:100%}
390
397
  .brand__sub{font-size:var(--fs-xs);color:var(--text-3);white-space:nowrap;overflow:hidden;
391
- text-overflow:ellipsis}
392
- .nav{display:flex;align-items:center;gap:var(--sp-1)}
398
+ text-overflow:ellipsis;max-width:100%}
399
+ .nav{display:flex;align-items:center;gap:var(--sp-1);flex:0 1 auto;min-width:0;overflow:hidden}
393
400
  .nav a{padding:8px 14px;border-radius:var(--r-full);color:var(--text-2);text-decoration:none;
394
401
  font-size:var(--fs-sm);font-weight:500;white-space:nowrap;transition:background-color var(--dur-short),color var(--dur-short)}
395
402
  .nav a:hover{background:var(--surface-2);color:var(--text)}
396
403
  @media (max-width:1180px){.nav{display:none}}
404
+ @media (max-width:1360px){.brand__sub{display:none}}
397
405
  .iconbtn{width:40px;height:40px;flex:none;display:grid;place-items:center;
398
406
  border:1px solid var(--border);border-radius:var(--r-full);background:transparent;
399
407
  color:var(--text-2);cursor:pointer;transition:background-color var(--dur-short),color var(--dur-short)}
@@ -448,12 +456,15 @@ transition:background-color var(--dur-short),box-shadow var(--dur-short),transfo
448
456
  .btn--ghost{background:transparent;color:var(--text);border-color:var(--border)}
449
457
  .btn--onDark{background:var(--surface);color:var(--text)}
450
458
  .card{background:var(--surface);border:1px solid var(--border-soft);border-radius:var(--r-lg);
451
- padding:clamp(20px,2vw,30px);transition:border-color var(--dur-short),box-shadow var(--dur-short),background-color var(--dur-medium)}
459
+ padding:clamp(20px,2vw,30px);transition:border-color var(--dur-short),box-shadow var(--dur-short),background-color var(--dur-medium);
460
+ display:flex;flex-direction:column;min-width:0}
452
461
  .card:hover{border-color:var(--border);box-shadow:var(--shadow-1)}
453
462
  .card--flat{background:var(--surface-1);border-color:transparent}
454
463
  .card--flat:hover{background:var(--surface-2)}
455
464
  .card--accent{background:var(--accent-soft);border-color:transparent}
456
- .card__hd{display:flex;align-items:center;gap:var(--sp-3);margin-bottom:var(--sp-3)}
465
+ .card__hd{display:flex;align-items:center;gap:var(--sp-3);margin-bottom:var(--sp-3);flex:none}
466
+ .card>.ul,.card>.t-body,.card>.t-sm{flex:1 1 auto}
467
+ .card>.so-what,.card>.tbd-legend,.card>.flagbar{flex:none;margin-top:auto}
457
468
  .card__ico{width:34px;height:34px;flex:none;border-radius:10px;background:var(--accent-soft);
458
469
  color:var(--accent-text);display:grid;place-items:center;font-size:15px;font-weight:600}
459
470
  .card__ico svg{width:18px;height:18px}
@@ -815,8 +826,9 @@ color:var(--accent);font-variant-numeric:tabular-nums;line-height:1;min-width:2.
815
826
  .agenda__a:hover .agenda__go{color:var(--accent);transform:translateX(4px)}
816
827
  .agenda__go svg{width:20px;height:20px;display:block}
817
828
  @media (min-width:900px){
818
- .agenda--2col{display:grid;grid-template-columns:1fr 1fr;column-gap:clamp(20px,3vw,48px)}
819
- .agenda--2col .agenda__i{border-top:1px solid var(--border-soft)}}
829
+ /* R4:列优先(先左列后右列),与 PPTX Agenda 列填充一致——grid 行优先会打乱阅读顺序 */
830
+ .agenda--2col{display:block;column-count:2;column-gap:clamp(20px,3vw,48px)}
831
+ .agenda--2col .agenda__i{break-inside:avoid;border-top:1px solid var(--border-soft)}}
820
832
  /* ── 页码指示(翻页导航) ── */
821
833
  .pager{position:fixed;right:clamp(10px,1.6vw,26px);top:50%;transform:translateY(-50%);
822
834
  z-index:60;display:flex;flex-direction:column;align-items:center;gap:10px}
@@ -1139,7 +1151,8 @@ background:var(--surface-2);border-radius:6px;padding:2px 7px;color:var(--text)}
1139
1151
  </div>
1140
1152
  </section>
1141
1153
 
1142
- <!-- ══ 参考资料(外部数据必标 [n],与正文 cite 双向对齐) ══ -->
1154
+ <!-- ══ 参考资料(只列真实来源;无真实来源时整节删除,并去掉正文 [n] 与导航入口) ══ -->
1155
+ <!-- 示例条目(交付时替换为真实机构/报告/文档名,禁止「来源名称」占位串):
1143
1156
  <section class="band band--tint" id="refs">
1144
1157
  <div class="wrap">
1145
1158
  <div class="shead rv">
@@ -1152,16 +1165,24 @@ background:var(--surface-2);border-radius:6px;padding:2px 7px;color:var(--text)}
1152
1165
  <th style="width:14%">时间</th><th>口径说明</th></tr></thead>
1153
1166
  <tbody>
1154
1167
  <tr id="ref-1"><td class="k">[1]</td>
1155
- <td><a class="ref-link" href="https://example.com" target="_blank" rel="noopener">来源名称
1168
+ <td><a class="ref-link" href="https://www.gartner.com" target="_blank" rel="noopener">Gartner:Agentic AI 技术成熟度曲线
1156
1169
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><path d="M15 3h6v6M10 14 21 3"/></svg></a></td>
1157
- <td>YYYY-MM</td><td>用于哪些数据;口径说明</td></tr>
1158
- <tr id="ref-2"><td class="k">[2]</td><td>无链接的内部来源(纯文本)</td>
1159
- <td>YYYY-MM</td><td>内部实测口径</td></tr>
1170
+ <td>2026-04</td><td>全球企业智能体采用率;口径 = 调研样本企业占比</td></tr>
1160
1171
  </tbody>
1161
1172
  </table>
1162
1173
  </div>
1163
1174
  </div>
1164
1175
  </section>
1176
+ -->
1177
+ <section class="band band--tint" id="refs" hidden data-ref-policy="real-sources-only">
1178
+ <div class="wrap">
1179
+ <div class="shead rv">
1180
+ <div class="t-eyebrow">参考资料</div>
1181
+ <h2 class="t-h1 shead__title">数据来源与口径说明</h2>
1182
+ </div>
1183
+ <p class="t-body rv">(占位节:交付前填入真实来源条目,或整节删除并去掉正文 [n] 与导航「参考资料」入口。禁止「来源名称,时间;口径。」占位条目。)</p>
1184
+ </div>
1185
+ </section>
1165
1186
 
1166
1187
  <!-- ══ 页脚 ══ -->
1167
1188
  <footer class="foot">
@@ -1805,7 +1826,7 @@ window.REPORT_MODEL = {
1805
1826
 
1806
1827
  /* __TOPPPT_RUNTIME_START__ */
1807
1828
  /* ══ PPTX 导出运行时(assets/pptx-export.js 的内联副本 · 由 scripts/sync_runtime.py 注入,禁止手改) ══ */
1808
- /* __TOPPPT_RUNTIME_SHA__:57dbfb4f2b594c45 */
1829
+ /* __TOPPPT_RUNTIME_SHA__:af6e25f25fd2e116 */
1809
1830
  /* ═══════════════════════════════════════════════════════════════════════════
1810
1831
  * TopPPT HTML· 浏览器端 PPTX 预览运行时
1811
1832
  * 零依赖、随报告单文件分发;OOXML 序列化器(仅供预览,页面不导出文件)。
@@ -1929,7 +1950,8 @@ function regionOf(pageType, slotId, opts) {
1929
1950
  if (type === 'bar' && slot === 'primary') {
1930
1951
  var by = P.chartY || 2.95;
1931
1952
  var bb = opts.bottom != null ? opts.bottom : bottomDefault;
1932
- return { x: MX + (P.chartX || 0), y: by, w: CW - (P.chartW || 0), h: Math.max(1.2, bb - by) };
1953
+ /* chartX/chartW 历史误用已纠正:与 donut/table/hbar/exhibit 同口径,图表盒对齐版心 (MX, CW) */
1954
+ return { x: MX, y: by, w: CW, h: Math.max(1.2, bb - by) };
1933
1955
  }
1934
1956
  if (type === 'bar' && slot === 'hbar') {
1935
1957
  var y0 = P.hbarY0 || 2.6;
@@ -1964,6 +1986,15 @@ function regionOf(pageType, slotId, opts) {
1964
1986
  return { x: MX, y: P.heroY || 2.9, w: CW * (P.heroW || 0.52), h: P.heroH || 1.6,
1965
1987
  dividerX: P.dividerX, metricY0: P.metricY0, metricRowH: P.metricRowH };
1966
1988
  }
1989
+ /* R3:kpi 支撑指标列——落在 dividerX 右侧(默认全宽返回会压住 hero) */
1990
+ if (type === 'kpi' && slot === 'metrics') {
1991
+ var dxK = P.dividerX != null ? P.dividerX : 0.56;
1992
+ return {
1993
+ x: MX + CW * dxK + 0.35, y: P.metricY0 || 2.95,
1994
+ w: CW * (1 - dxK) - 0.5,
1995
+ h: Math.max(0.6, bottomDefault - (P.metricY0 || 2.95)),
1996
+ };
1997
+ }
1967
1998
  if (type === 'comparison' && (slot === 'left' || slot === 'primary' || slot === 'right')) {
1968
1999
  var cg = P.panelGap || 0.4, pw = (CW - cg) / 2;
1969
2000
  if (slot === 'right') return { x: MX + pw + cg, y: P.panelY || 2.55, w: pw, h: P.panelH || 3.4 };
@@ -1996,15 +2027,22 @@ function regionOf(pageType, slotId, opts) {
1996
2027
  }
1997
2028
  if (type === 'image' && slot === 'primary') {
1998
2029
  var iy2 = Math.max(P.y || 2.3, opts.top || 0);
1999
- 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)) };
2030
+ /* R2:有图注时图片不得越过 contentBottomWithNote(图注落注释带,属 annotation self) */
2031
+ var iBottom = (opts.bottom != null) ? opts.bottom
2032
+ : (opts.caption ? (L.contentBottomWithNote || 6.4) : bottomDefault);
2033
+ return { x: MX, y: iy2, w: CW, h: Math.max(1.2, Math.min(P.h || 4.2, iBottom - iy2)) };
2000
2034
  }
2001
2035
  if ((type === 'halftable') && (slot === 'left' || slot === 'primary')) {
2002
2036
  var rs = PT.research || {};
2003
- return { x: MX, y: topDefault, w: CW * (rs.halfTableW || 0.52), h: Math.max(1, bottomDefault - topDefault) };
2037
+ var ht = opts.top != null ? opts.top : topDefault;
2038
+ var hb = opts.bottom != null ? opts.bottom : bottomDefault;
2039
+ return { x: MX, y: ht, w: CW * (rs.halfTableW || 0.52), h: Math.max(1, hb - ht) };
2004
2040
  }
2005
2041
  if ((type === 'halftable') && (slot === 'right' || slot === 'secondary')) {
2006
2042
  var rs2 = PT.research || {};
2007
- return { x: MX + CW * (rs2.halfChartX || 0.57), y: topDefault, w: CW * (rs2.halfChartW || 0.43), h: Math.max(1, bottomDefault - topDefault) };
2043
+ var ht2 = opts.top != null ? opts.top : topDefault;
2044
+ var hb2 = opts.bottom != null ? opts.bottom : bottomDefault;
2045
+ return { x: MX + CW * (rs2.halfChartX || 0.57), y: ht2, w: CW * (rs2.halfChartW || 0.43), h: Math.max(1, hb2 - ht2) };
2008
2046
  }
2009
2047
  if (type === 'matrix' && slot === 'primary') {
2010
2048
  var rm = PT.research || {};
@@ -2025,7 +2063,7 @@ function regionOf(pageType, slotId, opts) {
2025
2063
  /* __TOPPPT_SCHEMA_START__ */
2026
2064
  /* ── 由 scripts/sync_runtime.py 从 scripts/model-schema.json 注入 · 禁止手改 ── */
2027
2065
  /* DSL schema 单源:浏览器端 validateModel 与 scripts/extract_model.py 消费同一份定义 */
2028
- 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"]}}};
2066
+ 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"]}}};
2029
2067
  /* __TOPPPT_SCHEMA_END__ */
2030
2068
 
2031
2069
  var _mode = 'presentation'; /* 当前模式(slidesOf 里按 model.mode 设置) */
@@ -2316,6 +2354,15 @@ function footnoteLine(sh, text, S) {
2316
2354
  sh.push(txSp(R.x, R.y, R.w, R.h,
2317
2355
  [[{ t: text, sz: 9.5, col: S.faint, font: S.font }]]));
2318
2356
  }
2357
+ /* R2:sec.note 与 sec.footnote 共用注释带末行(footnoteY=6.72)。
2358
+ note.y=6.55 会压进 so-what 带(6.05–6.67)——同页二者必须合并为一行。 */
2359
+ function noteOrFootnote(sh, sec, S) {
2360
+ var parts = [];
2361
+ if (sec && sec.note) parts.push(String(sec.note));
2362
+ if (sec && sec.footnote) parts.push(String(sec.footnote));
2363
+ if (!parts.length) return;
2364
+ footnoteLine(sh, parts.join(' · '), S);
2365
+ }
2319
2366
  /* 待核实清单条(accent 强调色标注 · 与 build_pptx.js / HTML .flagbar 同源) */
2320
2367
  function flagBar(sh, items, S, yTop) {
2321
2368
  var F = PT.flagbar || { hdH: 0.26, rowH: 0.24, maxRows: 3, gap: 0.12 };
@@ -2504,7 +2551,9 @@ function vbarShapes(sh, ec, S, x, y, w, h, dcols) {
2504
2551
  : [{ name: '数值', values: ec.values || [] }];
2505
2552
  var nb = Math.max(1, labels.length);
2506
2553
  var ns = Math.max(1, series.length);
2507
- var baseY = y + h, plotW = w, step = plotW / nb;
2554
+ /* R2:类目标签带必须含在 h 内(原 baseY+0.08 画在图区外,会压进 so-what/来源行) */
2555
+ var labelH = 0.36;
2556
+ var baseY = y + Math.max(0.6, h - labelH), plotW = w, step = plotW / nb;
2508
2557
  var cluster = Math.min(1.1, step * (ns > 1 ? 0.78 : 0.55));
2509
2558
  var bw = cluster / ns;
2510
2559
  var all = [];
@@ -2513,26 +2562,27 @@ function vbarShapes(sh, ec, S, x, y, w, h, dcols) {
2513
2562
  });
2514
2563
  var vmax = ec.max || Math.max.apply(null, all.concat([1]));
2515
2564
  var top = all.length ? Math.max.apply(null, all) : 0;
2565
+ var plotH = Math.max(0.35, baseY - y);
2516
2566
  sh.push(shape('rect', x, baseY, plotW, 0.02, S.line));
2517
2567
  series.forEach(function (se, si) {
2518
2568
  var vals = se.values || [];
2519
2569
  var col = dcols ? dcols[si % 5] : (si === 0 ? S.accent : S.faint);
2520
2570
  vals.forEach(function (v, i) {
2521
2571
  var num = Number(v) || 0;
2522
- var hh = Math.max(0.05, num / vmax * (h - 0.5));
2572
+ var hh = Math.max(0.05, num / vmax * (plotH - 0.35));
2523
2573
  var bx = x + i * step + (step - cluster) / 2 + si * bw;
2524
2574
  var hot = ns === 1 && num === top;
2525
2575
  sh.push(shape('roundRect', bx, baseY - hh, Math.max(0.04, bw * 0.92), hh,
2526
2576
  hot ? (dcols ? dcols[i % 5] : S.accent) : col, { adj: 6000 }));
2527
2577
  if (ns === 1) {
2528
- sh.push(txSp(bx - 0.3, baseY - hh - 0.4, bw + 0.6, 0.32,
2578
+ sh.push(txSp(bx - 0.3, baseY - hh - 0.36, bw + 0.6, 0.30,
2529
2579
  [[{ t: valText(num, ec.unit), sz: 12.5, b: 1, col: hot ? S.accent : S.body, font: S.font }]], { align: 'ctr' }));
2530
2580
  }
2531
2581
  });
2532
2582
  });
2533
2583
  labels.forEach(function (lb, i) {
2534
2584
  var lx = x + i * step;
2535
- sh.push(txSp(lx, baseY + 0.08, step, 0.32,
2585
+ sh.push(txSp(lx, baseY + 0.06, step, labelH - 0.08,
2536
2586
  [[{ t: lb, sz: 10.5, col: S.faint, font: S.font }]], { align: 'ctr' }));
2537
2587
  });
2538
2588
  /* Multi-series legend along top of plot (keeps category labels; avoids blank chart). */
@@ -2555,7 +2605,10 @@ function lineShapes(sh, ec, S, x, y, w, h, dcols) {
2555
2605
  var all = [];
2556
2606
  series.forEach(function (se) { (se.values || []).forEach(function (v) { all.push(Number(v) || 0); }); });
2557
2607
  var vmax = ec.max || Math.max.apply(null, all.concat([1]));
2558
- var plotH = h - 0.5, baseY = y + h;
2608
+ /* R2:类目标签带含在 h 内,禁画到图区外压 so-what */
2609
+ var labelH = 0.36;
2610
+ var baseY = y + Math.max(0.6, h - labelH);
2611
+ var plotH = Math.max(0.35, baseY - y - 0.2);
2559
2612
  sh.push(shape('rect', x, baseY, w, 0.02, S.line));
2560
2613
  var stepX = n > 1 ? w / (n - 1) : 0;
2561
2614
  series.forEach(function (se, si) {
@@ -2575,7 +2628,7 @@ function lineShapes(sh, ec, S, x, y, w, h, dcols) {
2575
2628
  });
2576
2629
  labels.forEach(function (lb, i) {
2577
2630
  var px = x + (n > 1 ? i * stepX : w / 2);
2578
- sh.push(txSp(px - 0.5, baseY + 0.08, 1.0, 0.32,
2631
+ sh.push(txSp(px - 0.5, baseY + 0.06, 1.0, labelH - 0.08,
2579
2632
  [[{ t: lb, sz: 10.5, col: S.faint, font: S.font }]], { align: 'ctr' }));
2580
2633
  });
2581
2634
  }
@@ -3027,25 +3080,44 @@ function slidesOf(model, S) {
3027
3080
  c.push(txSp(MX, CV.subtitleY, CW - 1, CV.subtitleH, [[{ t: model.subtitle || '', sz: 18, col: S.soft, font: S.font }]]));
3028
3081
  slides.push(c);
3029
3082
 
3030
- /* Agenda(>8 条自动双列,行高自适应防溢出;architecture 极简形态可省略 agenda) */
3083
+ /* Agenda(>8 条自动双列;>12 条自动分页;长标题截断+全称入 notes;列优先与 HTML 一致) */
3031
3084
  var items = model.agenda || [];
3032
3085
  if (items.length) {
3033
- var ag = [];
3034
- head(ag, 'AGENDA', '报告大纲');
3035
- var colN = items.length > 8 ? 2 : 1;
3036
- var perCol = Math.ceil(items.length / colN) || 1;
3037
- var rowH = Math.min(1.05, (CONTENT_BOTTOM - CONTENT_TOP - 0.25) / Math.max(1, perCol));
3038
- var numSz = rowH >= 0.85 ? 30 : (rowH >= 0.65 ? 22 : 18);
3039
- var tSz = rowH >= 0.85 ? 16 : 14, dSz = rowH >= 0.85 ? 12 : 11;
3040
- items.forEach(function (it, i) {
3041
- var col = Math.floor(i / perCol), row = i % perCol;
3042
- var x = MX + col * (CW / colN), y = CONTENT_TOP + row * rowH;
3043
- 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 }]]));
3044
- ag.push(txSp(x + 1.0, y + 0.03, CW / colN - 1.1, Math.min(0.95, rowH),
3045
- [[{ t: it[1], sz: tSz, b: 1, col: S.ink, font: S.font }],
3046
- [{ t: it[2] || '', sz: dSz, col: S.body, font: S.font }]], { lineSpacing: tSz + 5 }));
3086
+ var ROWS_PER_PAGE = 12;
3087
+ var agPages = [];
3088
+ for (var ai = 0; ai < items.length; ai += ROWS_PER_PAGE) agPages.push(items.slice(ai, ai + ROWS_PER_PAGE));
3089
+ agPages.forEach(function (pageItems, pi) {
3090
+ var ag = [];
3091
+ var sub = agPages.length > 1 ? '(' + (pi + 1) + '/' + agPages.length + ')' : '';
3092
+ head(ag, 'AGENDA', '报告大纲' + sub);
3093
+ var colN = pageItems.length > 8 ? 2 : 1;
3094
+ var perCol = Math.ceil(pageItems.length / colN) || 1;
3095
+ var rowH = Math.min(1.05, (CONTENT_BOTTOM - CONTENT_TOP - 0.25) / Math.max(1, perCol));
3096
+ var numSz = rowH >= 0.85 ? 30 : (rowH >= 0.65 ? 22 : 18);
3097
+ var tSz = rowH >= 0.85 ? 16 : 14, dSz = rowH >= 0.85 ? 12 : 11;
3098
+ var agNotes = [];
3099
+ pageItems.forEach(function (it, i) {
3100
+ var col = Math.floor(i / perCol), row = i % perCol;
3101
+ var x = MX + col * (CW / colN), y = CONTENT_TOP + row * rowH;
3102
+ var titleBoxW = CW / colN - 1.15;
3103
+ var rawTitle = String(it[1] || '');
3104
+ var titleEstW = estTextH(rawTitle, titleBoxW, tSz, 1.2);
3105
+ var maxTitleH = rowH * 0.55;
3106
+ var title = rawTitle;
3107
+ if (titleEstW > maxTitleH) {
3108
+ var maxChars = Math.max(10, Math.floor(rawTitle.length * (maxTitleH / titleEstW)));
3109
+ if (rawTitle.length > maxChars) {
3110
+ title = rawTitle.slice(0, maxChars - 1) + '…';
3111
+ agNotes.push('条目 ' + it[0] + ' 全称:' + rawTitle);
3112
+ }
3113
+ }
3114
+ 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 }]]));
3115
+ ag.push(txSp(x + 1.0, y + 0.03, titleBoxW, Math.min(0.95, rowH),
3116
+ [[{ t: title, sz: tSz, b: 1, col: S.ink, font: S.font }],
3117
+ [{ t: it[2] || '', sz: dSz, col: S.body, font: S.font }]], { lineSpacing: tSz + 5 }));
3118
+ });
3119
+ slides.push(ag);
3047
3120
  });
3048
- slides.push(ag);
3049
3121
  }
3050
3122
 
3051
3123
  /* 章节页 */
@@ -3131,12 +3203,13 @@ function slidesOf(model, S) {
3131
3203
  { t: p[1], sz: fz - 1, col: S.body, font: S.font }]]));
3132
3204
  });
3133
3205
  });
3206
+ /* R2:verdict 与 soWhat 共用 annotation 槽位——只画一个(verdict 优先) */
3134
3207
  if (sec.verdict) {
3135
3208
  var vR = regOf('exhibit', 'annotation') || { x: MX, y: PT.exhibit.soWhatY, w: CW, h: 0.62 };
3136
- sh.push(shape('rect', vR.x, vR.y, vR.w, vR.h, S.accent));
3209
+ sh.push(shape('rect', vR.x, vR.y, vR.w, vR.h, S.accent, { name: 'tr:soWhat' }));
3137
3210
  sh.push(txSp(vR.x + 0.22, vR.y + 0.06, vR.w - 0.44, vR.h - 0.12,
3138
3211
  [[{ t: '结论 ', sz: 11, b: 1, col: S.onAccent, font: S.font, spc: 150 },
3139
- { t: sec.verdict, sz: 12.5, b: 1, col: S.onAccent, font: S.font }]], { anchor: 'ctr' }));
3212
+ { t: sec.verdict, sz: 12.5, b: 1, col: S.onAccent, font: S.font }]], { anchor: 'ctr', name: 'tr:soWhat' }));
3140
3213
  }
3141
3214
  } else if (type === 'quote') {
3142
3215
  var Q = PT.quote;
@@ -3169,7 +3242,6 @@ function slidesOf(model, S) {
3169
3242
  } else if (type === 'donut') {
3170
3243
  var dcn = sec.chart || {};
3171
3244
  if (dcn.labels && dcn.values) donutChart(sh, dcn, S, dcn.colors || donutColors(model.style, S, model.theme), bodyBottom);
3172
- 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 }]]));
3173
3245
  } else if (type === 'heatmap') {
3174
3246
  var H = PT.heatmap;
3175
3247
  var hR = regOf('heatmap', 'primary', { bottom: bodyBottom }) || { x: MX, y: H.y, w: CW, h: bodyBottom - H.y };
@@ -3316,7 +3388,7 @@ function slidesOf(model, S) {
3316
3388
  } else if (type === 'bar') {
3317
3389
  var ch = sec.chart || {};
3318
3390
  var dcols2 = (ch.colors || dataColors(model.style, model.theme));
3319
- var botY = chartBottom(!!sec.soWhat, !!sec.footnote) - (sec.note ? 0.42 : 0.05);
3391
+ var botY = chartBottom(!!sec.soWhat, !!(sec.footnote || sec.note)) - 0.05;
3320
3392
  var isHBar = ch.type === 'hbar';
3321
3393
  var bRR = regOf('bar', isHBar ? 'hbar' : 'primary', { bottom: botY }) ||
3322
3394
  { x: isHBar ? MX : MX + PT.bar.chartX, y: isHBar ? PT.bar.hbarY0 : PT.bar.chartY,
@@ -3343,13 +3415,12 @@ function slidesOf(model, S) {
3343
3415
  } else {
3344
3416
  chartBlockShapes(sh, ch, S, bx, bRR.y, bw, Math.max(1.5, bRR.h), dcols2);
3345
3417
  }
3346
- 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 }]]));
3347
3418
  } else if (type === 'twocol' || type === 'threecol') {
3348
3419
  var ps2 = sec.paragraphs || [];
3349
3420
  var nCol = (type === 'threecol') ? 3 : 2;
3350
3421
  var gapC = (type === 'threecol') ? PT.research.col3Gap : PT.twocol.colGap;
3351
- var tcR = regOf('twocol', 'primary', { top: bodyY, bottom: bodyBottom }) ||
3352
- { x: MX, y: bodyY, w: CW, h: bodyBottom - bodyY };
3422
+ /* R3:多栏均分用版心全宽(regOf('twocol') 返回单栏宽,当总宽会压扁三栏) */
3423
+ var tcR = { x: MX, y: bodyY, w: CW, h: bodyBottom - bodyY };
3353
3424
  var gC = cols(tcR.w, nCol, gapC);
3354
3425
  var perC = Math.ceil(ps2.length / nCol);
3355
3426
  var availC = tcR.h;
@@ -3545,7 +3616,7 @@ function slidesOf(model, S) {
3545
3616
  var dStart = dgR.y;
3546
3617
  var dEnd = dgR.y + dgR.h;
3547
3618
  var nLay = Math.max(1, lys.length);
3548
- var lh2 = Math.max(0.6, Math.min(D.maxLayerH, (dEnd - dStart) / nLay - D.layerGap));
3619
+ var lh2 = Math.max(0.5, Math.min(D.maxLayerH, (dEnd - dStart) / nLay - D.layerGap));
3549
3620
  lys.forEach(function (lay, li) {
3550
3621
  var y9 = dStart + li * (lh2 + D.layerGap);
3551
3622
  var focus = lay[2] === 'focus';
@@ -3561,9 +3632,26 @@ function slidesOf(model, S) {
3561
3632
  var ntt = isRec(nd) ? nd.t : nd;
3562
3633
  var nds = isRec(nd) ? (nd.d || '') : '';
3563
3634
  sh.push(shape('roundRect', x10, y9, nw2, lh2, ndacc ? S.soft : S.surface, { adj: 4000, line: S.line }));
3564
- sh.push(txSp(x10 + 0.12, y9 + (nds ? 0.12 : lh2 / 2 - 0.2), nw2 - 0.24, 0.38,
3565
- [[{ t: ntt, sz: 12.5, b: 1, col: ndacc ? S.accent : S.ink, font: S.font }]], { anchor: nds ? 't' : 'ctr' }));
3566
- 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 }]]));
3635
+ /* R1: 注解不得「浮回」标题上。标题顶对齐,注解钳在标题下边;空间不足降级单框混排。 */
3636
+ var padXn = 0.12, padYn = 0.10;
3637
+ var innerWn = nw2 - padXn * 2;
3638
+ var titleHn = 0.32, noteHn = 0.34, gapTNn = 0.04;
3639
+ var needHn = titleHn + gapTNn + (nds ? noteHn : 0) + padYn * 2;
3640
+ if (!nds || lh2 >= needHn) {
3641
+ var tYn = y9 + padYn;
3642
+ sh.push(txSp(x10 + padXn, nds ? tYn : y9 + lh2 / 2 - 0.18, innerWn, titleHn,
3643
+ [[{ t: ntt, sz: nds ? 11 : 12.5, b: 1, col: ndacc ? S.accent : S.ink, font: S.font }]],
3644
+ { anchor: 'ctr' }));
3645
+ if (nds) {
3646
+ var nYn = Math.max(tYn + titleHn + gapTNn, y9 + lh2 - padYn - noteHn);
3647
+ sh.push(txSp(x10 + padXn, nYn, innerWn, Math.min(noteHn, y9 + lh2 - padYn - nYn),
3648
+ [[{ t: nds, sz: 10, col: S.faint, font: S.font }]], { anchor: 't' }));
3649
+ }
3650
+ } else {
3651
+ sh.push(txSp(x10 + padXn, y9 + padYn * 0.5, innerWn, lh2 - padYn,
3652
+ [[{ t: ntt, sz: 11, b: 1, col: ndacc ? S.accent : S.ink, font: S.font }],
3653
+ [{ t: nds, sz: 9, col: S.faint, font: S.font }]], { anchor: 't', lineSpacing: 12 }));
3654
+ }
3567
3655
  });
3568
3656
  if (li < lys.length - 1) {
3569
3657
  var cy2 = y9 + lh2 + D.layerGap / 2;
@@ -3573,10 +3661,14 @@ function slidesOf(model, S) {
3573
3661
  }
3574
3662
  });
3575
3663
  if (sec.legend) {
3576
- var lx2 = MX;
3577
- sec.legend.forEach(function (lg2) {
3578
- sh.push(txSp(lx2, PH - 0.62, 3.2, 0.3, [[{ t: '● ' + lg2, sz: 10, col: S.faint, font: S.font }]]));
3579
- lx2 += 2.0;
3664
+ /* R2:图例进图区底部(与 HTML .arch__legend 同源),禁用 PH-0.62 固定偏移 */
3665
+ var lgH2 = 0.28;
3666
+ var lgY2 = Math.max(dStart, bodyBottom - lgH2 - 0.02);
3667
+ var nLg2 = Math.max(1, sec.legend.length);
3668
+ var step2 = Math.min(2.4, CW / nLg2);
3669
+ sec.legend.forEach(function (lg2, li2) {
3670
+ sh.push(txSp(MX + CW - nLg2 * step2 + li2 * step2, lgY2, step2 - 0.05, lgH2,
3671
+ [[{ t: '● ' + lg2, sz: 10, col: S.faint, font: S.font }]]));
3580
3672
  });
3581
3673
  }
3582
3674
  } else if (type === 'exhibit') {
@@ -3633,8 +3725,10 @@ function slidesOf(model, S) {
3633
3725
  }
3634
3726
  }
3635
3727
  /* research 通用可选件:so-what 结论条 + 页脚来源行 + 待核实条(共用同一槽位) */
3636
- if (sec.soWhat) soWhatBar(sh, sec.soWhat, S);
3637
- if (sec.footnote) footnoteLine(sh, sec.footnote, S);
3728
+ /* R2:verdict 已占 annotation 槽位时跳过 soWhat,禁止同槽双条叠印 */
3729
+ if (sec.soWhat && !sec.verdict) soWhatBar(sh, sec.soWhat, S);
3730
+ /* R2:note 与 footnote 共用注释带末行(禁 note.y=6.55 压 so-what) */
3731
+ noteOrFootnote(sh, sec, S);
3638
3732
  if (flagH) flagBar(sh, flagItems, S, flagY);
3639
3733
  slides.push(sh);
3640
3734
  });
@@ -3804,9 +3898,10 @@ function validateModel(model) {
3804
3898
  }
3805
3899
  var agMax = (M.model && M.model.agendaComfortMax) || 16;
3806
3900
  if ((model.agenda || []).length > agMax) {
3807
- warnings.push('agenda ' + model.agenda.length + ' 条超出单页舒适上限 ' + agMax + ',建议拆分');
3901
+ warnings.push('agenda ' + model.agenda.length + ' 条超出单页舒适上限 ' + agMax + ',将自动分页');
3808
3902
  }
3809
- var pages = 1 + (hasAgenda ? 1 : 0) + secs.length + 1; /* 封面+大纲(可选)+章节+收尾 */
3903
+ var agPageCount = hasAgenda ? Math.ceil(model.agenda.length / 12) : 0;
3904
+ var pages = 1 + agPageCount + secs.length + 1; /* 封面+大纲(可多页)+章节+收尾 */
3810
3905
  return { ok: missing.length === 0, missing: missing, warnings: warnings, pages: pages };
3811
3906
  }
3812
3907