@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
@@ -333,7 +333,13 @@ border-color:color-mix(in srgb,var(--accent-on) 20%,transparent);color:var(--acc
333
333
  rows-2 / rows-3:等高行分区(纵向节奏,替代等高卡片堆叠) */
334
334
  .g-211{grid-template-columns:minmax(0,2fr) minmax(0,1fr) minmax(0,1fr)}
335
335
  .g-121{grid-template-columns:minmax(0,1fr) minmax(0,2fr) minmax(0,1fr)}
336
- .g-quad{grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-rows:minmax(0,1fr);align-items:stretch}
336
+ .g-quad{grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-rows:minmax(min-content,1fr);align-items:stretch}
337
+ .g-half{grid-template-columns:minmax(0,1.05fr) minmax(0,1fr);align-items:stretch}
338
+ @media (max-width:1180px){.g-half{grid-template-columns:1fr}}
339
+ .g-2--equal,.g-3--equal,.g-4--equal{grid-auto-rows:minmax(min-content,1fr);align-items:stretch}
340
+ .grid.g-2>.card,.grid.g-3>.card,.grid.g-4>.card,
341
+ .grid.g-quad>.card,.grid.g-half>.card,.grid.g-2--equal>.card,
342
+ .grid.g-3--equal>.card,.grid.g-4--equal>.card{align-self:stretch}
337
343
  .g-aside{grid-template-columns:minmax(0,1fr) minmax(200px,.36fr);align-items:start}
338
344
  .g-aside__side{position:sticky;top:calc(var(--bar-h) + 16px)}
339
345
  .g-mosaic{grid-template-columns:repeat(3,minmax(0,1fr));
@@ -349,7 +355,7 @@ grid-template-areas:"main main main" "s1 s2 s3"}
349
355
  .kv--2col{grid-template-columns:auto minmax(0,1fr) auto minmax(0,1fr)}
350
356
  /* ── 拼贴栅格 g-bento(Bento / 马赛克):4 列基准 + 跨列/跨行工具类 ──
351
357
  .sp-2/.sp-3/.sp-4 跨列(span),.rw-2 跨行;窄屏自动降列并收敛 span。 */
352
- .g-bento{grid-template-columns:repeat(4,minmax(0,1fr));grid-auto-rows:minmax(118px,auto)}
358
+ .g-bento{grid-template-columns:repeat(4,minmax(0,1fr));grid-auto-rows:minmax(min-content,1fr)}
353
359
  .sp-2{grid-column:span 2}.sp-3{grid-column:span 3}.sp-4{grid-column:span 4}
354
360
  .rw-2{grid-row:span 2}
355
361
  @media (max-width:1180px){.g-bento{grid-template-columns:repeat(2,minmax(0,1fr))}
@@ -384,19 +390,21 @@ background:color-mix(in srgb,var(--bg) 86%,transparent);
384
390
  backdrop-filter:saturate(160%) blur(14px);-webkit-backdrop-filter:saturate(160%) blur(14px);
385
391
  border-bottom:1px solid var(--border-soft);
386
392
  transition:height var(--dur-medium) var(--ease-standard)}
387
- .bar__in{height:100%;display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4)}
388
- .brand{display:flex;align-items:center;gap:var(--sp-3);min-width:0}
393
+ .bar__in{height:100%;display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4);overflow:hidden}
394
+ .brand{display:flex;align-items:center;gap:var(--sp-3);min-width:0;flex:1 1 auto;overflow:hidden}
389
395
  .brand__mark{width:34px;height:34px;flex:none;border-radius:10px;background:var(--accent);
390
396
  color:var(--accent-on);display:grid;place-items:center;font-size:13px;font-weight:700}
397
+ .brand__txt{min-width:0;overflow:hidden;flex:1 1 auto}
391
398
  .brand__title{font-size:var(--fs-sm);font-weight:600;color:var(--text);white-space:nowrap;
392
- overflow:hidden;text-overflow:ellipsis}
399
+ overflow:hidden;text-overflow:ellipsis;max-width:100%}
393
400
  .brand__sub{font-size:var(--fs-xs);color:var(--text-3);white-space:nowrap;overflow:hidden;
394
- text-overflow:ellipsis}
395
- .nav{display:flex;align-items:center;gap:var(--sp-1)}
401
+ text-overflow:ellipsis;max-width:100%}
402
+ .nav{display:flex;align-items:center;gap:var(--sp-1);flex:0 1 auto;min-width:0;overflow:hidden}
396
403
  .nav a{padding:8px 14px;border-radius:var(--r-full);color:var(--text-2);text-decoration:none;
397
404
  font-size:var(--fs-sm);font-weight:500;white-space:nowrap;transition:background-color var(--dur-short),color var(--dur-short)}
398
405
  .nav a:hover{background:var(--surface-2);color:var(--text)}
399
406
  @media (max-width:1180px){.nav{display:none}}
407
+ @media (max-width:1360px){.brand__sub{display:none}}
400
408
  .iconbtn{width:40px;height:40px;flex:none;display:grid;place-items:center;
401
409
  border:1px solid var(--border);border-radius:var(--r-full);background:transparent;
402
410
  color:var(--text-2);cursor:pointer;transition:background-color var(--dur-short),color var(--dur-short)}
@@ -451,12 +459,15 @@ transition:background-color var(--dur-short),box-shadow var(--dur-short),transfo
451
459
  .btn--ghost{background:transparent;color:var(--text);border-color:var(--border)}
452
460
  .btn--onDark{background:var(--surface);color:var(--text)}
453
461
  .card{background:var(--surface);border:1px solid var(--border-soft);border-radius:var(--r-lg);
454
- padding:clamp(20px,2vw,30px);transition:border-color var(--dur-short),box-shadow var(--dur-short),background-color var(--dur-medium)}
462
+ padding:clamp(20px,2vw,30px);transition:border-color var(--dur-short),box-shadow var(--dur-short),background-color var(--dur-medium);
463
+ display:flex;flex-direction:column;min-width:0}
455
464
  .card:hover{border-color:var(--border);box-shadow:var(--shadow-1)}
456
465
  .card--flat{background:var(--surface-1);border-color:transparent}
457
466
  .card--flat:hover{background:var(--surface-2)}
458
467
  .card--accent{background:var(--accent-soft);border-color:transparent}
459
- .card__hd{display:flex;align-items:center;gap:var(--sp-3);margin-bottom:var(--sp-3)}
468
+ .card__hd{display:flex;align-items:center;gap:var(--sp-3);margin-bottom:var(--sp-3);flex:none}
469
+ .card>.ul,.card>.t-body,.card>.t-sm{flex:1 1 auto}
470
+ .card>.so-what,.card>.tbd-legend,.card>.flagbar{flex:none;margin-top:auto}
460
471
  .card__ico{width:34px;height:34px;flex:none;border-radius:10px;background:var(--accent-soft);
461
472
  color:var(--accent-text);display:grid;place-items:center;font-size:15px;font-weight:600}
462
473
  .card__ico svg{width:18px;height:18px}
@@ -818,8 +829,9 @@ color:var(--accent);font-variant-numeric:tabular-nums;line-height:1;min-width:2.
818
829
  .agenda__a:hover .agenda__go{color:var(--accent);transform:translateX(4px)}
819
830
  .agenda__go svg{width:20px;height:20px;display:block}
820
831
  @media (min-width:900px){
821
- .agenda--2col{display:grid;grid-template-columns:1fr 1fr;column-gap:clamp(20px,3vw,48px)}
822
- .agenda--2col .agenda__i{border-top:1px solid var(--border-soft)}}
832
+ /* R4:列优先(先左列后右列),与 PPTX Agenda 列填充一致——grid 行优先会打乱阅读顺序 */
833
+ .agenda--2col{display:block;column-count:2;column-gap:clamp(20px,3vw,48px)}
834
+ .agenda--2col .agenda__i{break-inside:avoid;border-top:1px solid var(--border-soft)}}
823
835
  /* ── 页码指示(翻页导航) ── */
824
836
  .pager{position:fixed;right:clamp(10px,1.6vw,26px);top:50%;transform:translateY(-50%);
825
837
  z-index:60;display:flex;flex-direction:column;align-items:center;gap:10px}
@@ -930,7 +942,7 @@ font-size:var(--fs-xs);color:var(--text-3);line-height:1.5}
930
942
  /* ── 结论条 .sowhat 已迁入 engine.css(MD3 衬条 · 无 So what 标签)── */
931
943
 
932
944
  /* ── research 组件:半表半图分栏(R7) ── */
933
- .g-half{grid-template-columns:minmax(0,1.05fr) minmax(0,1fr);align-items:start}
945
+ .g-half{grid-template-columns:minmax(0,1.05fr) minmax(0,1fr);align-items:stretch}
934
946
  @media (max-width:1180px){.g-half{grid-template-columns:1fr}}
935
947
 
936
948
  /* ── research 组件:矩阵图(R8) ── */
@@ -1152,7 +1164,7 @@ transition:color .18s,background-color .18s}
1152
1164
  <rect x="80" y="37" width="126" height="15" rx="7.5" class="f-acc"/>
1153
1165
  <text x="450" y="49" text-anchor="end" class="f-txt" font-size="12" font-weight="600">35%</text>
1154
1166
  </svg>
1155
- <div class="exhibit__src">来源:来源名称,YYYY-MM;口径 = 分子 / 分母一行说明</div>
1167
+ <div class="exhibit__src">来源:<真实来源名>,YYYY-MM;口径 = 分子 / 分母一行说明</div>
1156
1168
  </div>
1157
1169
  <ul class="ul rv" style="margin-top:var(--sp-5)">
1158
1170
  <li><strong>解读一。</strong>图表给出的一条关键解读。</li>
@@ -1248,7 +1260,7 @@ transition:color .18s,background-color .18s}
1248
1260
  <text data-anim="fade" x="338" y="180" text-anchor="middle" class="f-txt3" font-size="11">三季度</text>
1249
1261
  <text data-anim="fade" x="458" y="180" text-anchor="middle" class="f-txt3" font-size="11">四季度</text>
1250
1262
  </svg>
1251
- <div class="exhibit__src">来源:来源名称,YYYY-MM;口径说明</div>
1263
+ <div class="exhibit__src">来源:<真实来源名>,YYYY-MM;口径说明</div>
1252
1264
  </div>
1253
1265
  </div>
1254
1266
  <div class="sowhat rv">
@@ -1314,7 +1326,7 @@ transition:color .18s,background-color .18s}
1314
1326
  <text x="270" y="170" text-anchor="middle" class="f-txt3" font-size="11">Q3</text>
1315
1327
  <text x="380" y="170" text-anchor="middle" class="f-txt3" font-size="11">Q4</text>
1316
1328
  </svg>
1317
- <div class="exhibit__src">来源:来源名称,YYYY-MM;口径说明</div>
1329
+ <div class="exhibit__src">来源:<真实来源名>,YYYY-MM;口径说明</div>
1318
1330
  </div>
1319
1331
  </div>
1320
1332
  <div class="sowhat rv">
@@ -1385,7 +1397,8 @@ transition:color .18s,background-color .18s}
1385
1397
  </div>
1386
1398
  </section>
1387
1399
 
1388
- <!-- ══ 参考资料(外部数据必标 [n],与正文 cite 双向对齐) ══ -->
1400
+ <!-- ══ 参考资料(只列真实来源;无真实来源时整节删除,并去掉正文 [n] 与导航入口) ══ -->
1401
+ <!-- 示例条目(交付时替换为真实机构/报告/文档名,禁止「来源名称」占位串):
1389
1402
  <section class="band band--tint" id="refs">
1390
1403
  <div class="wrap">
1391
1404
  <div class="shead rv">
@@ -1398,16 +1411,24 @@ transition:color .18s,background-color .18s}
1398
1411
  <th style="width:14%">时间</th><th>口径说明</th></tr></thead>
1399
1412
  <tbody>
1400
1413
  <tr id="ref-1"><td class="k">[1]</td>
1401
- <td><a class="ref-link" href="https://example.com" target="_blank" rel="noopener">来源名称
1414
+ <td><a class="ref-link" href="https://www.gartner.com" target="_blank" rel="noopener">Gartner:Agentic AI 技术成熟度曲线
1402
1415
  <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>
1403
- <td>YYYY-MM</td><td>用于哪些数据;口径说明</td></tr>
1404
- <tr id="ref-2"><td class="k">[2]</td><td>无链接的内部来源(纯文本)</td>
1405
- <td>YYYY-MM</td><td>内部实测口径</td></tr>
1416
+ <td>2026-04</td><td>全球企业智能体采用率;口径 = 调研样本企业占比</td></tr>
1406
1417
  </tbody>
1407
1418
  </table>
1408
1419
  </div>
1409
1420
  </div>
1410
1421
  </section>
1422
+ -->
1423
+ <section class="band band--tint" id="refs" hidden data-ref-policy="real-sources-only">
1424
+ <div class="wrap">
1425
+ <div class="shead rv">
1426
+ <div class="t-eyebrow">参考资料</div>
1427
+ <h2 class="t-h1 shead__title">数据来源与口径说明</h2>
1428
+ </div>
1429
+ <p class="t-body rv">(占位节:交付前填入真实来源条目,或整节删除并去掉正文 [n] 与导航「参考资料」入口。禁止「来源名称,时间;口径。」占位条目。)</p>
1430
+ </div>
1431
+ </section>
1411
1432
 
1412
1433
  <!-- ══ 页脚 ══ -->
1413
1434
  <footer class="foot">
@@ -2048,12 +2069,12 @@ window.REPORT_MODEL = {
2048
2069
  ["证据", "引用口径与对照数据,外部数据带 [1]。"],
2049
2070
  ["含义", "这一发现意味着什么,下一步动作。"]],
2050
2071
  "soWhat": "一行含义或建议(≤60 字)。",
2051
- "footnote": "来源:来源名称,YYYY-MM。" },
2072
+ "footnote": "来源:<真实来源名>,YYYY-MM。" },
2052
2073
  { "type": "exhibit", "eyebrow": "02 · 章节名", "title": "行动标题:一句话给出该图的核心结论",
2053
2074
  "exhibitNo": "Exhibit 1",
2054
2075
  "chart": { "type": "hbar", "labels": ["项目一", "项目二"], "values": [76, 35], "max": 100, "unit": "%" },
2055
2076
  "soWhat": "这页证据意味着什么、建议做什么。",
2056
- "footnote": "来源:来源名称,YYYY-MM;口径 = 分子 / 分母一行说明" },
2077
+ "footnote": "来源:<真实来源名>,YYYY-MM;口径 = 分子 / 分母一行说明" },
2057
2078
  { "type": "table", "eyebrow": "03 · 章节名", "title": "行动标题:这张表的核心结论是什么",
2058
2079
  "table": { "head": ["维度", "现状", "目标", "结论"], "rows": [["维度一", "现状描述", "目标描述", "结论一"]] },
2059
2080
  "footnote": "注:表格口径与数据期间说明一行。" },
@@ -2078,7 +2099,7 @@ window.REPORT_MODEL = {
2078
2099
 
2079
2100
  /* __TOPPPT_RUNTIME_START__ */
2080
2101
  /* ══ PPTX 导出运行时(assets/pptx-export.js 的内联副本 · 由 scripts/sync_runtime.py 注入,禁止手改) ══ */
2081
- /* __TOPPPT_RUNTIME_SHA__:57dbfb4f2b594c45 */
2102
+ /* __TOPPPT_RUNTIME_SHA__:af6e25f25fd2e116 */
2082
2103
  /* ═══════════════════════════════════════════════════════════════════════════
2083
2104
  * TopPPT HTML· 浏览器端 PPTX 预览运行时
2084
2105
  * 零依赖、随报告单文件分发;OOXML 序列化器(仅供预览,页面不导出文件)。
@@ -2202,7 +2223,8 @@ function regionOf(pageType, slotId, opts) {
2202
2223
  if (type === 'bar' && slot === 'primary') {
2203
2224
  var by = P.chartY || 2.95;
2204
2225
  var bb = opts.bottom != null ? opts.bottom : bottomDefault;
2205
- return { x: MX + (P.chartX || 0), y: by, w: CW - (P.chartW || 0), h: Math.max(1.2, bb - by) };
2226
+ /* chartX/chartW 历史误用已纠正:与 donut/table/hbar/exhibit 同口径,图表盒对齐版心 (MX, CW) */
2227
+ return { x: MX, y: by, w: CW, h: Math.max(1.2, bb - by) };
2206
2228
  }
2207
2229
  if (type === 'bar' && slot === 'hbar') {
2208
2230
  var y0 = P.hbarY0 || 2.6;
@@ -2237,6 +2259,15 @@ function regionOf(pageType, slotId, opts) {
2237
2259
  return { x: MX, y: P.heroY || 2.9, w: CW * (P.heroW || 0.52), h: P.heroH || 1.6,
2238
2260
  dividerX: P.dividerX, metricY0: P.metricY0, metricRowH: P.metricRowH };
2239
2261
  }
2262
+ /* R3:kpi 支撑指标列——落在 dividerX 右侧(默认全宽返回会压住 hero) */
2263
+ if (type === 'kpi' && slot === 'metrics') {
2264
+ var dxK = P.dividerX != null ? P.dividerX : 0.56;
2265
+ return {
2266
+ x: MX + CW * dxK + 0.35, y: P.metricY0 || 2.95,
2267
+ w: CW * (1 - dxK) - 0.5,
2268
+ h: Math.max(0.6, bottomDefault - (P.metricY0 || 2.95)),
2269
+ };
2270
+ }
2240
2271
  if (type === 'comparison' && (slot === 'left' || slot === 'primary' || slot === 'right')) {
2241
2272
  var cg = P.panelGap || 0.4, pw = (CW - cg) / 2;
2242
2273
  if (slot === 'right') return { x: MX + pw + cg, y: P.panelY || 2.55, w: pw, h: P.panelH || 3.4 };
@@ -2269,15 +2300,22 @@ function regionOf(pageType, slotId, opts) {
2269
2300
  }
2270
2301
  if (type === 'image' && slot === 'primary') {
2271
2302
  var iy2 = Math.max(P.y || 2.3, opts.top || 0);
2272
- 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)) };
2303
+ /* R2:有图注时图片不得越过 contentBottomWithNote(图注落注释带,属 annotation self) */
2304
+ var iBottom = (opts.bottom != null) ? opts.bottom
2305
+ : (opts.caption ? (L.contentBottomWithNote || 6.4) : bottomDefault);
2306
+ return { x: MX, y: iy2, w: CW, h: Math.max(1.2, Math.min(P.h || 4.2, iBottom - iy2)) };
2273
2307
  }
2274
2308
  if ((type === 'halftable') && (slot === 'left' || slot === 'primary')) {
2275
2309
  var rs = PT.research || {};
2276
- return { x: MX, y: topDefault, w: CW * (rs.halfTableW || 0.52), h: Math.max(1, bottomDefault - topDefault) };
2310
+ var ht = opts.top != null ? opts.top : topDefault;
2311
+ var hb = opts.bottom != null ? opts.bottom : bottomDefault;
2312
+ return { x: MX, y: ht, w: CW * (rs.halfTableW || 0.52), h: Math.max(1, hb - ht) };
2277
2313
  }
2278
2314
  if ((type === 'halftable') && (slot === 'right' || slot === 'secondary')) {
2279
2315
  var rs2 = PT.research || {};
2280
- return { x: MX + CW * (rs2.halfChartX || 0.57), y: topDefault, w: CW * (rs2.halfChartW || 0.43), h: Math.max(1, bottomDefault - topDefault) };
2316
+ var ht2 = opts.top != null ? opts.top : topDefault;
2317
+ var hb2 = opts.bottom != null ? opts.bottom : bottomDefault;
2318
+ return { x: MX + CW * (rs2.halfChartX || 0.57), y: ht2, w: CW * (rs2.halfChartW || 0.43), h: Math.max(1, hb2 - ht2) };
2281
2319
  }
2282
2320
  if (type === 'matrix' && slot === 'primary') {
2283
2321
  var rm = PT.research || {};
@@ -2298,7 +2336,7 @@ function regionOf(pageType, slotId, opts) {
2298
2336
  /* __TOPPPT_SCHEMA_START__ */
2299
2337
  /* ── 由 scripts/sync_runtime.py 从 scripts/model-schema.json 注入 · 禁止手改 ── */
2300
2338
  /* DSL schema 单源:浏览器端 validateModel 与 scripts/extract_model.py 消费同一份定义 */
2301
- 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"]}}};
2339
+ 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"]}}};
2302
2340
  /* __TOPPPT_SCHEMA_END__ */
2303
2341
 
2304
2342
  var _mode = 'presentation'; /* 当前模式(slidesOf 里按 model.mode 设置) */
@@ -2589,6 +2627,15 @@ function footnoteLine(sh, text, S) {
2589
2627
  sh.push(txSp(R.x, R.y, R.w, R.h,
2590
2628
  [[{ t: text, sz: 9.5, col: S.faint, font: S.font }]]));
2591
2629
  }
2630
+ /* R2:sec.note 与 sec.footnote 共用注释带末行(footnoteY=6.72)。
2631
+ note.y=6.55 会压进 so-what 带(6.05–6.67)——同页二者必须合并为一行。 */
2632
+ function noteOrFootnote(sh, sec, S) {
2633
+ var parts = [];
2634
+ if (sec && sec.note) parts.push(String(sec.note));
2635
+ if (sec && sec.footnote) parts.push(String(sec.footnote));
2636
+ if (!parts.length) return;
2637
+ footnoteLine(sh, parts.join(' · '), S);
2638
+ }
2592
2639
  /* 待核实清单条(accent 强调色标注 · 与 build_pptx.js / HTML .flagbar 同源) */
2593
2640
  function flagBar(sh, items, S, yTop) {
2594
2641
  var F = PT.flagbar || { hdH: 0.26, rowH: 0.24, maxRows: 3, gap: 0.12 };
@@ -2777,7 +2824,9 @@ function vbarShapes(sh, ec, S, x, y, w, h, dcols) {
2777
2824
  : [{ name: '数值', values: ec.values || [] }];
2778
2825
  var nb = Math.max(1, labels.length);
2779
2826
  var ns = Math.max(1, series.length);
2780
- var baseY = y + h, plotW = w, step = plotW / nb;
2827
+ /* R2:类目标签带必须含在 h 内(原 baseY+0.08 画在图区外,会压进 so-what/来源行) */
2828
+ var labelH = 0.36;
2829
+ var baseY = y + Math.max(0.6, h - labelH), plotW = w, step = plotW / nb;
2781
2830
  var cluster = Math.min(1.1, step * (ns > 1 ? 0.78 : 0.55));
2782
2831
  var bw = cluster / ns;
2783
2832
  var all = [];
@@ -2786,26 +2835,27 @@ function vbarShapes(sh, ec, S, x, y, w, h, dcols) {
2786
2835
  });
2787
2836
  var vmax = ec.max || Math.max.apply(null, all.concat([1]));
2788
2837
  var top = all.length ? Math.max.apply(null, all) : 0;
2838
+ var plotH = Math.max(0.35, baseY - y);
2789
2839
  sh.push(shape('rect', x, baseY, plotW, 0.02, S.line));
2790
2840
  series.forEach(function (se, si) {
2791
2841
  var vals = se.values || [];
2792
2842
  var col = dcols ? dcols[si % 5] : (si === 0 ? S.accent : S.faint);
2793
2843
  vals.forEach(function (v, i) {
2794
2844
  var num = Number(v) || 0;
2795
- var hh = Math.max(0.05, num / vmax * (h - 0.5));
2845
+ var hh = Math.max(0.05, num / vmax * (plotH - 0.35));
2796
2846
  var bx = x + i * step + (step - cluster) / 2 + si * bw;
2797
2847
  var hot = ns === 1 && num === top;
2798
2848
  sh.push(shape('roundRect', bx, baseY - hh, Math.max(0.04, bw * 0.92), hh,
2799
2849
  hot ? (dcols ? dcols[i % 5] : S.accent) : col, { adj: 6000 }));
2800
2850
  if (ns === 1) {
2801
- sh.push(txSp(bx - 0.3, baseY - hh - 0.4, bw + 0.6, 0.32,
2851
+ sh.push(txSp(bx - 0.3, baseY - hh - 0.36, bw + 0.6, 0.30,
2802
2852
  [[{ t: valText(num, ec.unit), sz: 12.5, b: 1, col: hot ? S.accent : S.body, font: S.font }]], { align: 'ctr' }));
2803
2853
  }
2804
2854
  });
2805
2855
  });
2806
2856
  labels.forEach(function (lb, i) {
2807
2857
  var lx = x + i * step;
2808
- sh.push(txSp(lx, baseY + 0.08, step, 0.32,
2858
+ sh.push(txSp(lx, baseY + 0.06, step, labelH - 0.08,
2809
2859
  [[{ t: lb, sz: 10.5, col: S.faint, font: S.font }]], { align: 'ctr' }));
2810
2860
  });
2811
2861
  /* Multi-series legend along top of plot (keeps category labels; avoids blank chart). */
@@ -2828,7 +2878,10 @@ function lineShapes(sh, ec, S, x, y, w, h, dcols) {
2828
2878
  var all = [];
2829
2879
  series.forEach(function (se) { (se.values || []).forEach(function (v) { all.push(Number(v) || 0); }); });
2830
2880
  var vmax = ec.max || Math.max.apply(null, all.concat([1]));
2831
- var plotH = h - 0.5, baseY = y + h;
2881
+ /* R2:类目标签带含在 h 内,禁画到图区外压 so-what */
2882
+ var labelH = 0.36;
2883
+ var baseY = y + Math.max(0.6, h - labelH);
2884
+ var plotH = Math.max(0.35, baseY - y - 0.2);
2832
2885
  sh.push(shape('rect', x, baseY, w, 0.02, S.line));
2833
2886
  var stepX = n > 1 ? w / (n - 1) : 0;
2834
2887
  series.forEach(function (se, si) {
@@ -2848,7 +2901,7 @@ function lineShapes(sh, ec, S, x, y, w, h, dcols) {
2848
2901
  });
2849
2902
  labels.forEach(function (lb, i) {
2850
2903
  var px = x + (n > 1 ? i * stepX : w / 2);
2851
- sh.push(txSp(px - 0.5, baseY + 0.08, 1.0, 0.32,
2904
+ sh.push(txSp(px - 0.5, baseY + 0.06, 1.0, labelH - 0.08,
2852
2905
  [[{ t: lb, sz: 10.5, col: S.faint, font: S.font }]], { align: 'ctr' }));
2853
2906
  });
2854
2907
  }
@@ -3300,25 +3353,44 @@ function slidesOf(model, S) {
3300
3353
  c.push(txSp(MX, CV.subtitleY, CW - 1, CV.subtitleH, [[{ t: model.subtitle || '', sz: 18, col: S.soft, font: S.font }]]));
3301
3354
  slides.push(c);
3302
3355
 
3303
- /* Agenda(>8 条自动双列,行高自适应防溢出;architecture 极简形态可省略 agenda) */
3356
+ /* Agenda(>8 条自动双列;>12 条自动分页;长标题截断+全称入 notes;列优先与 HTML 一致) */
3304
3357
  var items = model.agenda || [];
3305
3358
  if (items.length) {
3306
- var ag = [];
3307
- head(ag, 'AGENDA', '报告大纲');
3308
- var colN = items.length > 8 ? 2 : 1;
3309
- var perCol = Math.ceil(items.length / colN) || 1;
3310
- var rowH = Math.min(1.05, (CONTENT_BOTTOM - CONTENT_TOP - 0.25) / Math.max(1, perCol));
3311
- var numSz = rowH >= 0.85 ? 30 : (rowH >= 0.65 ? 22 : 18);
3312
- var tSz = rowH >= 0.85 ? 16 : 14, dSz = rowH >= 0.85 ? 12 : 11;
3313
- items.forEach(function (it, i) {
3314
- var col = Math.floor(i / perCol), row = i % perCol;
3315
- var x = MX + col * (CW / colN), y = CONTENT_TOP + row * rowH;
3316
- 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 }]]));
3317
- ag.push(txSp(x + 1.0, y + 0.03, CW / colN - 1.1, Math.min(0.95, rowH),
3318
- [[{ t: it[1], sz: tSz, b: 1, col: S.ink, font: S.font }],
3319
- [{ t: it[2] || '', sz: dSz, col: S.body, font: S.font }]], { lineSpacing: tSz + 5 }));
3359
+ var ROWS_PER_PAGE = 12;
3360
+ var agPages = [];
3361
+ for (var ai = 0; ai < items.length; ai += ROWS_PER_PAGE) agPages.push(items.slice(ai, ai + ROWS_PER_PAGE));
3362
+ agPages.forEach(function (pageItems, pi) {
3363
+ var ag = [];
3364
+ var sub = agPages.length > 1 ? '(' + (pi + 1) + '/' + agPages.length + ')' : '';
3365
+ head(ag, 'AGENDA', '报告大纲' + sub);
3366
+ var colN = pageItems.length > 8 ? 2 : 1;
3367
+ var perCol = Math.ceil(pageItems.length / colN) || 1;
3368
+ var rowH = Math.min(1.05, (CONTENT_BOTTOM - CONTENT_TOP - 0.25) / Math.max(1, perCol));
3369
+ var numSz = rowH >= 0.85 ? 30 : (rowH >= 0.65 ? 22 : 18);
3370
+ var tSz = rowH >= 0.85 ? 16 : 14, dSz = rowH >= 0.85 ? 12 : 11;
3371
+ var agNotes = [];
3372
+ pageItems.forEach(function (it, i) {
3373
+ var col = Math.floor(i / perCol), row = i % perCol;
3374
+ var x = MX + col * (CW / colN), y = CONTENT_TOP + row * rowH;
3375
+ var titleBoxW = CW / colN - 1.15;
3376
+ var rawTitle = String(it[1] || '');
3377
+ var titleEstW = estTextH(rawTitle, titleBoxW, tSz, 1.2);
3378
+ var maxTitleH = rowH * 0.55;
3379
+ var title = rawTitle;
3380
+ if (titleEstW > maxTitleH) {
3381
+ var maxChars = Math.max(10, Math.floor(rawTitle.length * (maxTitleH / titleEstW)));
3382
+ if (rawTitle.length > maxChars) {
3383
+ title = rawTitle.slice(0, maxChars - 1) + '…';
3384
+ agNotes.push('条目 ' + it[0] + ' 全称:' + rawTitle);
3385
+ }
3386
+ }
3387
+ 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 }]]));
3388
+ ag.push(txSp(x + 1.0, y + 0.03, titleBoxW, Math.min(0.95, rowH),
3389
+ [[{ t: title, sz: tSz, b: 1, col: S.ink, font: S.font }],
3390
+ [{ t: it[2] || '', sz: dSz, col: S.body, font: S.font }]], { lineSpacing: tSz + 5 }));
3391
+ });
3392
+ slides.push(ag);
3320
3393
  });
3321
- slides.push(ag);
3322
3394
  }
3323
3395
 
3324
3396
  /* 章节页 */
@@ -3404,12 +3476,13 @@ function slidesOf(model, S) {
3404
3476
  { t: p[1], sz: fz - 1, col: S.body, font: S.font }]]));
3405
3477
  });
3406
3478
  });
3479
+ /* R2:verdict 与 soWhat 共用 annotation 槽位——只画一个(verdict 优先) */
3407
3480
  if (sec.verdict) {
3408
3481
  var vR = regOf('exhibit', 'annotation') || { x: MX, y: PT.exhibit.soWhatY, w: CW, h: 0.62 };
3409
- sh.push(shape('rect', vR.x, vR.y, vR.w, vR.h, S.accent));
3482
+ sh.push(shape('rect', vR.x, vR.y, vR.w, vR.h, S.accent, { name: 'tr:soWhat' }));
3410
3483
  sh.push(txSp(vR.x + 0.22, vR.y + 0.06, vR.w - 0.44, vR.h - 0.12,
3411
3484
  [[{ t: '结论 ', sz: 11, b: 1, col: S.onAccent, font: S.font, spc: 150 },
3412
- { t: sec.verdict, sz: 12.5, b: 1, col: S.onAccent, font: S.font }]], { anchor: 'ctr' }));
3485
+ { t: sec.verdict, sz: 12.5, b: 1, col: S.onAccent, font: S.font }]], { anchor: 'ctr', name: 'tr:soWhat' }));
3413
3486
  }
3414
3487
  } else if (type === 'quote') {
3415
3488
  var Q = PT.quote;
@@ -3442,7 +3515,6 @@ function slidesOf(model, S) {
3442
3515
  } else if (type === 'donut') {
3443
3516
  var dcn = sec.chart || {};
3444
3517
  if (dcn.labels && dcn.values) donutChart(sh, dcn, S, dcn.colors || donutColors(model.style, S, model.theme), bodyBottom);
3445
- 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 }]]));
3446
3518
  } else if (type === 'heatmap') {
3447
3519
  var H = PT.heatmap;
3448
3520
  var hR = regOf('heatmap', 'primary', { bottom: bodyBottom }) || { x: MX, y: H.y, w: CW, h: bodyBottom - H.y };
@@ -3589,7 +3661,7 @@ function slidesOf(model, S) {
3589
3661
  } else if (type === 'bar') {
3590
3662
  var ch = sec.chart || {};
3591
3663
  var dcols2 = (ch.colors || dataColors(model.style, model.theme));
3592
- var botY = chartBottom(!!sec.soWhat, !!sec.footnote) - (sec.note ? 0.42 : 0.05);
3664
+ var botY = chartBottom(!!sec.soWhat, !!(sec.footnote || sec.note)) - 0.05;
3593
3665
  var isHBar = ch.type === 'hbar';
3594
3666
  var bRR = regOf('bar', isHBar ? 'hbar' : 'primary', { bottom: botY }) ||
3595
3667
  { x: isHBar ? MX : MX + PT.bar.chartX, y: isHBar ? PT.bar.hbarY0 : PT.bar.chartY,
@@ -3616,13 +3688,12 @@ function slidesOf(model, S) {
3616
3688
  } else {
3617
3689
  chartBlockShapes(sh, ch, S, bx, bRR.y, bw, Math.max(1.5, bRR.h), dcols2);
3618
3690
  }
3619
- 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 }]]));
3620
3691
  } else if (type === 'twocol' || type === 'threecol') {
3621
3692
  var ps2 = sec.paragraphs || [];
3622
3693
  var nCol = (type === 'threecol') ? 3 : 2;
3623
3694
  var gapC = (type === 'threecol') ? PT.research.col3Gap : PT.twocol.colGap;
3624
- var tcR = regOf('twocol', 'primary', { top: bodyY, bottom: bodyBottom }) ||
3625
- { x: MX, y: bodyY, w: CW, h: bodyBottom - bodyY };
3695
+ /* R3:多栏均分用版心全宽(regOf('twocol') 返回单栏宽,当总宽会压扁三栏) */
3696
+ var tcR = { x: MX, y: bodyY, w: CW, h: bodyBottom - bodyY };
3626
3697
  var gC = cols(tcR.w, nCol, gapC);
3627
3698
  var perC = Math.ceil(ps2.length / nCol);
3628
3699
  var availC = tcR.h;
@@ -3818,7 +3889,7 @@ function slidesOf(model, S) {
3818
3889
  var dStart = dgR.y;
3819
3890
  var dEnd = dgR.y + dgR.h;
3820
3891
  var nLay = Math.max(1, lys.length);
3821
- var lh2 = Math.max(0.6, Math.min(D.maxLayerH, (dEnd - dStart) / nLay - D.layerGap));
3892
+ var lh2 = Math.max(0.5, Math.min(D.maxLayerH, (dEnd - dStart) / nLay - D.layerGap));
3822
3893
  lys.forEach(function (lay, li) {
3823
3894
  var y9 = dStart + li * (lh2 + D.layerGap);
3824
3895
  var focus = lay[2] === 'focus';
@@ -3834,9 +3905,26 @@ function slidesOf(model, S) {
3834
3905
  var ntt = isRec(nd) ? nd.t : nd;
3835
3906
  var nds = isRec(nd) ? (nd.d || '') : '';
3836
3907
  sh.push(shape('roundRect', x10, y9, nw2, lh2, ndacc ? S.soft : S.surface, { adj: 4000, line: S.line }));
3837
- sh.push(txSp(x10 + 0.12, y9 + (nds ? 0.12 : lh2 / 2 - 0.2), nw2 - 0.24, 0.38,
3838
- [[{ t: ntt, sz: 12.5, b: 1, col: ndacc ? S.accent : S.ink, font: S.font }]], { anchor: nds ? 't' : 'ctr' }));
3839
- 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 }]]));
3908
+ /* R1: 注解不得「浮回」标题上。标题顶对齐,注解钳在标题下边;空间不足降级单框混排。 */
3909
+ var padXn = 0.12, padYn = 0.10;
3910
+ var innerWn = nw2 - padXn * 2;
3911
+ var titleHn = 0.32, noteHn = 0.34, gapTNn = 0.04;
3912
+ var needHn = titleHn + gapTNn + (nds ? noteHn : 0) + padYn * 2;
3913
+ if (!nds || lh2 >= needHn) {
3914
+ var tYn = y9 + padYn;
3915
+ sh.push(txSp(x10 + padXn, nds ? tYn : y9 + lh2 / 2 - 0.18, innerWn, titleHn,
3916
+ [[{ t: ntt, sz: nds ? 11 : 12.5, b: 1, col: ndacc ? S.accent : S.ink, font: S.font }]],
3917
+ { anchor: 'ctr' }));
3918
+ if (nds) {
3919
+ var nYn = Math.max(tYn + titleHn + gapTNn, y9 + lh2 - padYn - noteHn);
3920
+ sh.push(txSp(x10 + padXn, nYn, innerWn, Math.min(noteHn, y9 + lh2 - padYn - nYn),
3921
+ [[{ t: nds, sz: 10, col: S.faint, font: S.font }]], { anchor: 't' }));
3922
+ }
3923
+ } else {
3924
+ sh.push(txSp(x10 + padXn, y9 + padYn * 0.5, innerWn, lh2 - padYn,
3925
+ [[{ t: ntt, sz: 11, b: 1, col: ndacc ? S.accent : S.ink, font: S.font }],
3926
+ [{ t: nds, sz: 9, col: S.faint, font: S.font }]], { anchor: 't', lineSpacing: 12 }));
3927
+ }
3840
3928
  });
3841
3929
  if (li < lys.length - 1) {
3842
3930
  var cy2 = y9 + lh2 + D.layerGap / 2;
@@ -3846,10 +3934,14 @@ function slidesOf(model, S) {
3846
3934
  }
3847
3935
  });
3848
3936
  if (sec.legend) {
3849
- var lx2 = MX;
3850
- sec.legend.forEach(function (lg2) {
3851
- sh.push(txSp(lx2, PH - 0.62, 3.2, 0.3, [[{ t: '● ' + lg2, sz: 10, col: S.faint, font: S.font }]]));
3852
- lx2 += 2.0;
3937
+ /* R2:图例进图区底部(与 HTML .arch__legend 同源),禁用 PH-0.62 固定偏移 */
3938
+ var lgH2 = 0.28;
3939
+ var lgY2 = Math.max(dStart, bodyBottom - lgH2 - 0.02);
3940
+ var nLg2 = Math.max(1, sec.legend.length);
3941
+ var step2 = Math.min(2.4, CW / nLg2);
3942
+ sec.legend.forEach(function (lg2, li2) {
3943
+ sh.push(txSp(MX + CW - nLg2 * step2 + li2 * step2, lgY2, step2 - 0.05, lgH2,
3944
+ [[{ t: '● ' + lg2, sz: 10, col: S.faint, font: S.font }]]));
3853
3945
  });
3854
3946
  }
3855
3947
  } else if (type === 'exhibit') {
@@ -3906,8 +3998,10 @@ function slidesOf(model, S) {
3906
3998
  }
3907
3999
  }
3908
4000
  /* research 通用可选件:so-what 结论条 + 页脚来源行 + 待核实条(共用同一槽位) */
3909
- if (sec.soWhat) soWhatBar(sh, sec.soWhat, S);
3910
- if (sec.footnote) footnoteLine(sh, sec.footnote, S);
4001
+ /* R2:verdict 已占 annotation 槽位时跳过 soWhat,禁止同槽双条叠印 */
4002
+ if (sec.soWhat && !sec.verdict) soWhatBar(sh, sec.soWhat, S);
4003
+ /* R2:note 与 footnote 共用注释带末行(禁 note.y=6.55 压 so-what) */
4004
+ noteOrFootnote(sh, sec, S);
3911
4005
  if (flagH) flagBar(sh, flagItems, S, flagY);
3912
4006
  slides.push(sh);
3913
4007
  });
@@ -4077,9 +4171,10 @@ function validateModel(model) {
4077
4171
  }
4078
4172
  var agMax = (M.model && M.model.agendaComfortMax) || 16;
4079
4173
  if ((model.agenda || []).length > agMax) {
4080
- warnings.push('agenda ' + model.agenda.length + ' 条超出单页舒适上限 ' + agMax + ',建议拆分');
4174
+ warnings.push('agenda ' + model.agenda.length + ' 条超出单页舒适上限 ' + agMax + ',将自动分页');
4081
4175
  }
4082
- var pages = 1 + (hasAgenda ? 1 : 0) + secs.length + 1; /* 封面+大纲(可选)+章节+收尾 */
4176
+ var agPageCount = hasAgenda ? Math.ceil(model.agenda.length / 12) : 0;
4177
+ var pages = 1 + agPageCount + secs.length + 1; /* 封面+大纲(可多页)+章节+收尾 */
4083
4178
  return { ok: missing.length === 0, missing: missing, warnings: warnings, pages: pages };
4084
4179
  }
4085
4180
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "top-ppt-html",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "top-ppt-html",
9
- "version": "0.1.17",
9
+ "version": "0.1.19",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "pptxgenjs": "^4.0.0"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "top-ppt-html",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "private": true,
5
5
  "description": "TopPPT HTML:正式商务演示双交付。单文件 HTML + 版式保真可编辑 PPTX;MD3 密度克制;布局骨架 P1–P12;模型单写;strict 0/0 交付。",
6
6
  "license": "MIT",