@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
@@ -332,7 +332,13 @@ border-color:color-mix(in srgb,var(--accent-on) 20%,transparent);color:var(--acc
332
332
  rows-2 / rows-3:等高行分区(纵向节奏,替代等高卡片堆叠) */
333
333
  .g-211{grid-template-columns:minmax(0,2fr) minmax(0,1fr) minmax(0,1fr)}
334
334
  .g-121{grid-template-columns:minmax(0,1fr) minmax(0,2fr) minmax(0,1fr)}
335
- .g-quad{grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-rows:minmax(0,1fr);align-items:stretch}
335
+ .g-quad{grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-rows:minmax(min-content,1fr);align-items:stretch}
336
+ .g-half{grid-template-columns:minmax(0,1.05fr) minmax(0,1fr);align-items:stretch}
337
+ @media (max-width:1180px){.g-half{grid-template-columns:1fr}}
338
+ .g-2--equal,.g-3--equal,.g-4--equal{grid-auto-rows:minmax(min-content,1fr);align-items:stretch}
339
+ .grid.g-2>.card,.grid.g-3>.card,.grid.g-4>.card,
340
+ .grid.g-quad>.card,.grid.g-half>.card,.grid.g-2--equal>.card,
341
+ .grid.g-3--equal>.card,.grid.g-4--equal>.card{align-self:stretch}
336
342
  .g-aside{grid-template-columns:minmax(0,1fr) minmax(200px,.36fr);align-items:start}
337
343
  .g-aside__side{position:sticky;top:calc(var(--bar-h) + 16px)}
338
344
  .g-mosaic{grid-template-columns:repeat(3,minmax(0,1fr));
@@ -348,7 +354,7 @@ grid-template-areas:"main main main" "s1 s2 s3"}
348
354
  .kv--2col{grid-template-columns:auto minmax(0,1fr) auto minmax(0,1fr)}
349
355
  /* ── 拼贴栅格 g-bento(Bento / 马赛克):4 列基准 + 跨列/跨行工具类 ──
350
356
  .sp-2/.sp-3/.sp-4 跨列(span),.rw-2 跨行;窄屏自动降列并收敛 span。 */
351
- .g-bento{grid-template-columns:repeat(4,minmax(0,1fr));grid-auto-rows:minmax(118px,auto)}
357
+ .g-bento{grid-template-columns:repeat(4,minmax(0,1fr));grid-auto-rows:minmax(min-content,1fr)}
352
358
  .sp-2{grid-column:span 2}.sp-3{grid-column:span 3}.sp-4{grid-column:span 4}
353
359
  .rw-2{grid-row:span 2}
354
360
  @media (max-width:1180px){.g-bento{grid-template-columns:repeat(2,minmax(0,1fr))}
@@ -383,19 +389,21 @@ background:color-mix(in srgb,var(--bg) 86%,transparent);
383
389
  backdrop-filter:saturate(160%) blur(14px);-webkit-backdrop-filter:saturate(160%) blur(14px);
384
390
  border-bottom:1px solid var(--border-soft);
385
391
  transition:height var(--dur-medium) var(--ease-standard)}
386
- .bar__in{height:100%;display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4)}
387
- .brand{display:flex;align-items:center;gap:var(--sp-3);min-width:0}
392
+ .bar__in{height:100%;display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4);overflow:hidden}
393
+ .brand{display:flex;align-items:center;gap:var(--sp-3);min-width:0;flex:1 1 auto;overflow:hidden}
388
394
  .brand__mark{width:34px;height:34px;flex:none;border-radius:10px;background:var(--accent);
389
395
  color:var(--accent-on);display:grid;place-items:center;font-size:13px;font-weight:700}
396
+ .brand__txt{min-width:0;overflow:hidden;flex:1 1 auto}
390
397
  .brand__title{font-size:var(--fs-sm);font-weight:600;color:var(--text);white-space:nowrap;
391
- overflow:hidden;text-overflow:ellipsis}
398
+ overflow:hidden;text-overflow:ellipsis;max-width:100%}
392
399
  .brand__sub{font-size:var(--fs-xs);color:var(--text-3);white-space:nowrap;overflow:hidden;
393
- text-overflow:ellipsis}
394
- .nav{display:flex;align-items:center;gap:var(--sp-1)}
400
+ text-overflow:ellipsis;max-width:100%}
401
+ .nav{display:flex;align-items:center;gap:var(--sp-1);flex:0 1 auto;min-width:0;overflow:hidden}
395
402
  .nav a{padding:8px 14px;border-radius:var(--r-full);color:var(--text-2);text-decoration:none;
396
403
  font-size:var(--fs-sm);font-weight:500;white-space:nowrap;transition:background-color var(--dur-short),color var(--dur-short)}
397
404
  .nav a:hover{background:var(--surface-2);color:var(--text)}
398
405
  @media (max-width:1180px){.nav{display:none}}
406
+ @media (max-width:1360px){.brand__sub{display:none}}
399
407
  .iconbtn{width:40px;height:40px;flex:none;display:grid;place-items:center;
400
408
  border:1px solid var(--border);border-radius:var(--r-full);background:transparent;
401
409
  color:var(--text-2);cursor:pointer;transition:background-color var(--dur-short),color var(--dur-short)}
@@ -450,12 +458,15 @@ transition:background-color var(--dur-short),box-shadow var(--dur-short),transfo
450
458
  .btn--ghost{background:transparent;color:var(--text);border-color:var(--border)}
451
459
  .btn--onDark{background:var(--surface);color:var(--text)}
452
460
  .card{background:var(--surface);border:1px solid var(--border-soft);border-radius:var(--r-lg);
453
- padding:clamp(20px,2vw,30px);transition:border-color var(--dur-short),box-shadow var(--dur-short),background-color var(--dur-medium)}
461
+ padding:clamp(20px,2vw,30px);transition:border-color var(--dur-short),box-shadow var(--dur-short),background-color var(--dur-medium);
462
+ display:flex;flex-direction:column;min-width:0}
454
463
  .card:hover{border-color:var(--border);box-shadow:var(--shadow-1)}
455
464
  .card--flat{background:var(--surface-1);border-color:transparent}
456
465
  .card--flat:hover{background:var(--surface-2)}
457
466
  .card--accent{background:var(--accent-soft);border-color:transparent}
458
- .card__hd{display:flex;align-items:center;gap:var(--sp-3);margin-bottom:var(--sp-3)}
467
+ .card__hd{display:flex;align-items:center;gap:var(--sp-3);margin-bottom:var(--sp-3);flex:none}
468
+ .card>.ul,.card>.t-body,.card>.t-sm{flex:1 1 auto}
469
+ .card>.so-what,.card>.tbd-legend,.card>.flagbar{flex:none;margin-top:auto}
459
470
  .card__ico{width:34px;height:34px;flex:none;border-radius:10px;background:var(--accent-soft);
460
471
  color:var(--accent-text);display:grid;place-items:center;font-size:15px;font-weight:600}
461
472
  .card__ico svg{width:18px;height:18px}
@@ -817,8 +828,9 @@ color:var(--accent);font-variant-numeric:tabular-nums;line-height:1;min-width:2.
817
828
  .agenda__a:hover .agenda__go{color:var(--accent);transform:translateX(4px)}
818
829
  .agenda__go svg{width:20px;height:20px;display:block}
819
830
  @media (min-width:900px){
820
- .agenda--2col{display:grid;grid-template-columns:1fr 1fr;column-gap:clamp(20px,3vw,48px)}
821
- .agenda--2col .agenda__i{border-top:1px solid var(--border-soft)}}
831
+ /* R4:列优先(先左列后右列),与 PPTX Agenda 列填充一致——grid 行优先会打乱阅读顺序 */
832
+ .agenda--2col{display:block;column-count:2;column-gap:clamp(20px,3vw,48px)}
833
+ .agenda--2col .agenda__i{break-inside:avoid;border-top:1px solid var(--border-soft)}}
822
834
  /* ── 页码指示(翻页导航) ── */
823
835
  .pager{position:fixed;right:clamp(10px,1.6vw,26px);top:50%;transform:translateY(-50%);
824
836
  z-index:60;display:flex;flex-direction:column;align-items:center;gap:10px}
@@ -1135,7 +1147,8 @@ font-weight:600}
1135
1147
  </div>
1136
1148
  </section>
1137
1149
 
1138
- <!-- ══ 参考资料(有外部依据时保留;纯内部方案可整段删除并同步删除正文 cite) ══ -->
1150
+ <!-- ══ 参考资料(只列真实来源;无真实来源时整节删除,并去掉正文 cite 与导航入口) ══ -->
1151
+ <!-- 示例条目(交付时替换为真实机构/报告/文档名,禁止「来源名称」占位串):
1139
1152
  <section class="band band--tint" id="refs">
1140
1153
  <div class="wrap">
1141
1154
  <div class="shead rv">
@@ -1148,14 +1161,24 @@ font-weight:600}
1148
1161
  <th style="width:14%">时间</th><th>口径说明</th></tr></thead>
1149
1162
  <tbody>
1150
1163
  <tr id="ref-1"><td class="k">[1]</td>
1151
- <td><a class="ref-link" href="https://example.com" target="_blank" rel="noopener">来源名称
1164
+ <td><a class="ref-link" href="https://www.nist.gov" target="_blank" rel="noopener">NIST AI Risk Management Framework
1152
1165
  <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>
1153
- <td>YYYY-MM</td><td>用于哪些数据;口径说明</td></tr>
1166
+ <td>2024-07</td><td>智能体安全控制项映射</td></tr>
1154
1167
  </tbody>
1155
1168
  </table>
1156
1169
  </div>
1157
1170
  </div>
1158
1171
  </section>
1172
+ -->
1173
+ <section class="band band--tint" id="refs" hidden data-ref-policy="real-sources-only">
1174
+ <div class="wrap">
1175
+ <div class="shead rv">
1176
+ <div class="t-eyebrow">参考资料</div>
1177
+ <h2 class="t-h1 shead__title">依据与口径说明</h2>
1178
+ </div>
1179
+ <p class="t-body rv">(占位节:交付前填入真实来源条目,或整节删除并去掉正文 cite 与导航「参考资料」入口。禁止「来源名称,时间;口径。」占位条目。)</p>
1180
+ </div>
1181
+ </section>
1159
1182
 
1160
1183
  <!-- ══ 页脚 ══ -->
1161
1184
  <footer class="foot">
@@ -1795,7 +1818,7 @@ window.REPORT_MODEL = {
1795
1818
 
1796
1819
  /* __TOPPPT_RUNTIME_START__ */
1797
1820
  /* ══ PPTX 导出运行时(assets/pptx-export.js 的内联副本 · 由 scripts/sync_runtime.py 注入,禁止手改) ══ */
1798
- /* __TOPPPT_RUNTIME_SHA__:57dbfb4f2b594c45 */
1821
+ /* __TOPPPT_RUNTIME_SHA__:af6e25f25fd2e116 */
1799
1822
  /* ═══════════════════════════════════════════════════════════════════════════
1800
1823
  * TopPPT HTML· 浏览器端 PPTX 预览运行时
1801
1824
  * 零依赖、随报告单文件分发;OOXML 序列化器(仅供预览,页面不导出文件)。
@@ -1919,7 +1942,8 @@ function regionOf(pageType, slotId, opts) {
1919
1942
  if (type === 'bar' && slot === 'primary') {
1920
1943
  var by = P.chartY || 2.95;
1921
1944
  var bb = opts.bottom != null ? opts.bottom : bottomDefault;
1922
- return { x: MX + (P.chartX || 0), y: by, w: CW - (P.chartW || 0), h: Math.max(1.2, bb - by) };
1945
+ /* chartX/chartW 历史误用已纠正:与 donut/table/hbar/exhibit 同口径,图表盒对齐版心 (MX, CW) */
1946
+ return { x: MX, y: by, w: CW, h: Math.max(1.2, bb - by) };
1923
1947
  }
1924
1948
  if (type === 'bar' && slot === 'hbar') {
1925
1949
  var y0 = P.hbarY0 || 2.6;
@@ -1954,6 +1978,15 @@ function regionOf(pageType, slotId, opts) {
1954
1978
  return { x: MX, y: P.heroY || 2.9, w: CW * (P.heroW || 0.52), h: P.heroH || 1.6,
1955
1979
  dividerX: P.dividerX, metricY0: P.metricY0, metricRowH: P.metricRowH };
1956
1980
  }
1981
+ /* R3:kpi 支撑指标列——落在 dividerX 右侧(默认全宽返回会压住 hero) */
1982
+ if (type === 'kpi' && slot === 'metrics') {
1983
+ var dxK = P.dividerX != null ? P.dividerX : 0.56;
1984
+ return {
1985
+ x: MX + CW * dxK + 0.35, y: P.metricY0 || 2.95,
1986
+ w: CW * (1 - dxK) - 0.5,
1987
+ h: Math.max(0.6, bottomDefault - (P.metricY0 || 2.95)),
1988
+ };
1989
+ }
1957
1990
  if (type === 'comparison' && (slot === 'left' || slot === 'primary' || slot === 'right')) {
1958
1991
  var cg = P.panelGap || 0.4, pw = (CW - cg) / 2;
1959
1992
  if (slot === 'right') return { x: MX + pw + cg, y: P.panelY || 2.55, w: pw, h: P.panelH || 3.4 };
@@ -1986,15 +2019,22 @@ function regionOf(pageType, slotId, opts) {
1986
2019
  }
1987
2020
  if (type === 'image' && slot === 'primary') {
1988
2021
  var iy2 = Math.max(P.y || 2.3, opts.top || 0);
1989
- 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)) };
2022
+ /* R2:有图注时图片不得越过 contentBottomWithNote(图注落注释带,属 annotation self) */
2023
+ var iBottom = (opts.bottom != null) ? opts.bottom
2024
+ : (opts.caption ? (L.contentBottomWithNote || 6.4) : bottomDefault);
2025
+ return { x: MX, y: iy2, w: CW, h: Math.max(1.2, Math.min(P.h || 4.2, iBottom - iy2)) };
1990
2026
  }
1991
2027
  if ((type === 'halftable') && (slot === 'left' || slot === 'primary')) {
1992
2028
  var rs = PT.research || {};
1993
- return { x: MX, y: topDefault, w: CW * (rs.halfTableW || 0.52), h: Math.max(1, bottomDefault - topDefault) };
2029
+ var ht = opts.top != null ? opts.top : topDefault;
2030
+ var hb = opts.bottom != null ? opts.bottom : bottomDefault;
2031
+ return { x: MX, y: ht, w: CW * (rs.halfTableW || 0.52), h: Math.max(1, hb - ht) };
1994
2032
  }
1995
2033
  if ((type === 'halftable') && (slot === 'right' || slot === 'secondary')) {
1996
2034
  var rs2 = PT.research || {};
1997
- return { x: MX + CW * (rs2.halfChartX || 0.57), y: topDefault, w: CW * (rs2.halfChartW || 0.43), h: Math.max(1, bottomDefault - topDefault) };
2035
+ var ht2 = opts.top != null ? opts.top : topDefault;
2036
+ var hb2 = opts.bottom != null ? opts.bottom : bottomDefault;
2037
+ return { x: MX + CW * (rs2.halfChartX || 0.57), y: ht2, w: CW * (rs2.halfChartW || 0.43), h: Math.max(1, hb2 - ht2) };
1998
2038
  }
1999
2039
  if (type === 'matrix' && slot === 'primary') {
2000
2040
  var rm = PT.research || {};
@@ -2015,7 +2055,7 @@ function regionOf(pageType, slotId, opts) {
2015
2055
  /* __TOPPPT_SCHEMA_START__ */
2016
2056
  /* ── 由 scripts/sync_runtime.py 从 scripts/model-schema.json 注入 · 禁止手改 ── */
2017
2057
  /* DSL schema 单源:浏览器端 validateModel 与 scripts/extract_model.py 消费同一份定义 */
2018
- 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"]}}};
2058
+ 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"]}}};
2019
2059
  /* __TOPPPT_SCHEMA_END__ */
2020
2060
 
2021
2061
  var _mode = 'presentation'; /* 当前模式(slidesOf 里按 model.mode 设置) */
@@ -2306,6 +2346,15 @@ function footnoteLine(sh, text, S) {
2306
2346
  sh.push(txSp(R.x, R.y, R.w, R.h,
2307
2347
  [[{ t: text, sz: 9.5, col: S.faint, font: S.font }]]));
2308
2348
  }
2349
+ /* R2:sec.note 与 sec.footnote 共用注释带末行(footnoteY=6.72)。
2350
+ note.y=6.55 会压进 so-what 带(6.05–6.67)——同页二者必须合并为一行。 */
2351
+ function noteOrFootnote(sh, sec, S) {
2352
+ var parts = [];
2353
+ if (sec && sec.note) parts.push(String(sec.note));
2354
+ if (sec && sec.footnote) parts.push(String(sec.footnote));
2355
+ if (!parts.length) return;
2356
+ footnoteLine(sh, parts.join(' · '), S);
2357
+ }
2309
2358
  /* 待核实清单条(accent 强调色标注 · 与 build_pptx.js / HTML .flagbar 同源) */
2310
2359
  function flagBar(sh, items, S, yTop) {
2311
2360
  var F = PT.flagbar || { hdH: 0.26, rowH: 0.24, maxRows: 3, gap: 0.12 };
@@ -2494,7 +2543,9 @@ function vbarShapes(sh, ec, S, x, y, w, h, dcols) {
2494
2543
  : [{ name: '数值', values: ec.values || [] }];
2495
2544
  var nb = Math.max(1, labels.length);
2496
2545
  var ns = Math.max(1, series.length);
2497
- var baseY = y + h, plotW = w, step = plotW / nb;
2546
+ /* R2:类目标签带必须含在 h 内(原 baseY+0.08 画在图区外,会压进 so-what/来源行) */
2547
+ var labelH = 0.36;
2548
+ var baseY = y + Math.max(0.6, h - labelH), plotW = w, step = plotW / nb;
2498
2549
  var cluster = Math.min(1.1, step * (ns > 1 ? 0.78 : 0.55));
2499
2550
  var bw = cluster / ns;
2500
2551
  var all = [];
@@ -2503,26 +2554,27 @@ function vbarShapes(sh, ec, S, x, y, w, h, dcols) {
2503
2554
  });
2504
2555
  var vmax = ec.max || Math.max.apply(null, all.concat([1]));
2505
2556
  var top = all.length ? Math.max.apply(null, all) : 0;
2557
+ var plotH = Math.max(0.35, baseY - y);
2506
2558
  sh.push(shape('rect', x, baseY, plotW, 0.02, S.line));
2507
2559
  series.forEach(function (se, si) {
2508
2560
  var vals = se.values || [];
2509
2561
  var col = dcols ? dcols[si % 5] : (si === 0 ? S.accent : S.faint);
2510
2562
  vals.forEach(function (v, i) {
2511
2563
  var num = Number(v) || 0;
2512
- var hh = Math.max(0.05, num / vmax * (h - 0.5));
2564
+ var hh = Math.max(0.05, num / vmax * (plotH - 0.35));
2513
2565
  var bx = x + i * step + (step - cluster) / 2 + si * bw;
2514
2566
  var hot = ns === 1 && num === top;
2515
2567
  sh.push(shape('roundRect', bx, baseY - hh, Math.max(0.04, bw * 0.92), hh,
2516
2568
  hot ? (dcols ? dcols[i % 5] : S.accent) : col, { adj: 6000 }));
2517
2569
  if (ns === 1) {
2518
- sh.push(txSp(bx - 0.3, baseY - hh - 0.4, bw + 0.6, 0.32,
2570
+ sh.push(txSp(bx - 0.3, baseY - hh - 0.36, bw + 0.6, 0.30,
2519
2571
  [[{ t: valText(num, ec.unit), sz: 12.5, b: 1, col: hot ? S.accent : S.body, font: S.font }]], { align: 'ctr' }));
2520
2572
  }
2521
2573
  });
2522
2574
  });
2523
2575
  labels.forEach(function (lb, i) {
2524
2576
  var lx = x + i * step;
2525
- sh.push(txSp(lx, baseY + 0.08, step, 0.32,
2577
+ sh.push(txSp(lx, baseY + 0.06, step, labelH - 0.08,
2526
2578
  [[{ t: lb, sz: 10.5, col: S.faint, font: S.font }]], { align: 'ctr' }));
2527
2579
  });
2528
2580
  /* Multi-series legend along top of plot (keeps category labels; avoids blank chart). */
@@ -2545,7 +2597,10 @@ function lineShapes(sh, ec, S, x, y, w, h, dcols) {
2545
2597
  var all = [];
2546
2598
  series.forEach(function (se) { (se.values || []).forEach(function (v) { all.push(Number(v) || 0); }); });
2547
2599
  var vmax = ec.max || Math.max.apply(null, all.concat([1]));
2548
- var plotH = h - 0.5, baseY = y + h;
2600
+ /* R2:类目标签带含在 h 内,禁画到图区外压 so-what */
2601
+ var labelH = 0.36;
2602
+ var baseY = y + Math.max(0.6, h - labelH);
2603
+ var plotH = Math.max(0.35, baseY - y - 0.2);
2549
2604
  sh.push(shape('rect', x, baseY, w, 0.02, S.line));
2550
2605
  var stepX = n > 1 ? w / (n - 1) : 0;
2551
2606
  series.forEach(function (se, si) {
@@ -2565,7 +2620,7 @@ function lineShapes(sh, ec, S, x, y, w, h, dcols) {
2565
2620
  });
2566
2621
  labels.forEach(function (lb, i) {
2567
2622
  var px = x + (n > 1 ? i * stepX : w / 2);
2568
- sh.push(txSp(px - 0.5, baseY + 0.08, 1.0, 0.32,
2623
+ sh.push(txSp(px - 0.5, baseY + 0.06, 1.0, labelH - 0.08,
2569
2624
  [[{ t: lb, sz: 10.5, col: S.faint, font: S.font }]], { align: 'ctr' }));
2570
2625
  });
2571
2626
  }
@@ -3017,25 +3072,44 @@ function slidesOf(model, S) {
3017
3072
  c.push(txSp(MX, CV.subtitleY, CW - 1, CV.subtitleH, [[{ t: model.subtitle || '', sz: 18, col: S.soft, font: S.font }]]));
3018
3073
  slides.push(c);
3019
3074
 
3020
- /* Agenda(>8 条自动双列,行高自适应防溢出;architecture 极简形态可省略 agenda) */
3075
+ /* Agenda(>8 条自动双列;>12 条自动分页;长标题截断+全称入 notes;列优先与 HTML 一致) */
3021
3076
  var items = model.agenda || [];
3022
3077
  if (items.length) {
3023
- var ag = [];
3024
- head(ag, 'AGENDA', '报告大纲');
3025
- var colN = items.length > 8 ? 2 : 1;
3026
- var perCol = Math.ceil(items.length / colN) || 1;
3027
- var rowH = Math.min(1.05, (CONTENT_BOTTOM - CONTENT_TOP - 0.25) / Math.max(1, perCol));
3028
- var numSz = rowH >= 0.85 ? 30 : (rowH >= 0.65 ? 22 : 18);
3029
- var tSz = rowH >= 0.85 ? 16 : 14, dSz = rowH >= 0.85 ? 12 : 11;
3030
- items.forEach(function (it, i) {
3031
- var col = Math.floor(i / perCol), row = i % perCol;
3032
- var x = MX + col * (CW / colN), y = CONTENT_TOP + row * rowH;
3033
- 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 }]]));
3034
- ag.push(txSp(x + 1.0, y + 0.03, CW / colN - 1.1, Math.min(0.95, rowH),
3035
- [[{ t: it[1], sz: tSz, b: 1, col: S.ink, font: S.font }],
3036
- [{ t: it[2] || '', sz: dSz, col: S.body, font: S.font }]], { lineSpacing: tSz + 5 }));
3078
+ var ROWS_PER_PAGE = 12;
3079
+ var agPages = [];
3080
+ for (var ai = 0; ai < items.length; ai += ROWS_PER_PAGE) agPages.push(items.slice(ai, ai + ROWS_PER_PAGE));
3081
+ agPages.forEach(function (pageItems, pi) {
3082
+ var ag = [];
3083
+ var sub = agPages.length > 1 ? '(' + (pi + 1) + '/' + agPages.length + ')' : '';
3084
+ head(ag, 'AGENDA', '报告大纲' + sub);
3085
+ var colN = pageItems.length > 8 ? 2 : 1;
3086
+ var perCol = Math.ceil(pageItems.length / colN) || 1;
3087
+ var rowH = Math.min(1.05, (CONTENT_BOTTOM - CONTENT_TOP - 0.25) / Math.max(1, perCol));
3088
+ var numSz = rowH >= 0.85 ? 30 : (rowH >= 0.65 ? 22 : 18);
3089
+ var tSz = rowH >= 0.85 ? 16 : 14, dSz = rowH >= 0.85 ? 12 : 11;
3090
+ var agNotes = [];
3091
+ pageItems.forEach(function (it, i) {
3092
+ var col = Math.floor(i / perCol), row = i % perCol;
3093
+ var x = MX + col * (CW / colN), y = CONTENT_TOP + row * rowH;
3094
+ var titleBoxW = CW / colN - 1.15;
3095
+ var rawTitle = String(it[1] || '');
3096
+ var titleEstW = estTextH(rawTitle, titleBoxW, tSz, 1.2);
3097
+ var maxTitleH = rowH * 0.55;
3098
+ var title = rawTitle;
3099
+ if (titleEstW > maxTitleH) {
3100
+ var maxChars = Math.max(10, Math.floor(rawTitle.length * (maxTitleH / titleEstW)));
3101
+ if (rawTitle.length > maxChars) {
3102
+ title = rawTitle.slice(0, maxChars - 1) + '…';
3103
+ agNotes.push('条目 ' + it[0] + ' 全称:' + rawTitle);
3104
+ }
3105
+ }
3106
+ 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 }]]));
3107
+ ag.push(txSp(x + 1.0, y + 0.03, titleBoxW, Math.min(0.95, rowH),
3108
+ [[{ t: title, sz: tSz, b: 1, col: S.ink, font: S.font }],
3109
+ [{ t: it[2] || '', sz: dSz, col: S.body, font: S.font }]], { lineSpacing: tSz + 5 }));
3110
+ });
3111
+ slides.push(ag);
3037
3112
  });
3038
- slides.push(ag);
3039
3113
  }
3040
3114
 
3041
3115
  /* 章节页 */
@@ -3121,12 +3195,13 @@ function slidesOf(model, S) {
3121
3195
  { t: p[1], sz: fz - 1, col: S.body, font: S.font }]]));
3122
3196
  });
3123
3197
  });
3198
+ /* R2:verdict 与 soWhat 共用 annotation 槽位——只画一个(verdict 优先) */
3124
3199
  if (sec.verdict) {
3125
3200
  var vR = regOf('exhibit', 'annotation') || { x: MX, y: PT.exhibit.soWhatY, w: CW, h: 0.62 };
3126
- sh.push(shape('rect', vR.x, vR.y, vR.w, vR.h, S.accent));
3201
+ sh.push(shape('rect', vR.x, vR.y, vR.w, vR.h, S.accent, { name: 'tr:soWhat' }));
3127
3202
  sh.push(txSp(vR.x + 0.22, vR.y + 0.06, vR.w - 0.44, vR.h - 0.12,
3128
3203
  [[{ t: '结论 ', sz: 11, b: 1, col: S.onAccent, font: S.font, spc: 150 },
3129
- { t: sec.verdict, sz: 12.5, b: 1, col: S.onAccent, font: S.font }]], { anchor: 'ctr' }));
3204
+ { t: sec.verdict, sz: 12.5, b: 1, col: S.onAccent, font: S.font }]], { anchor: 'ctr', name: 'tr:soWhat' }));
3130
3205
  }
3131
3206
  } else if (type === 'quote') {
3132
3207
  var Q = PT.quote;
@@ -3159,7 +3234,6 @@ function slidesOf(model, S) {
3159
3234
  } else if (type === 'donut') {
3160
3235
  var dcn = sec.chart || {};
3161
3236
  if (dcn.labels && dcn.values) donutChart(sh, dcn, S, dcn.colors || donutColors(model.style, S, model.theme), bodyBottom);
3162
- 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 }]]));
3163
3237
  } else if (type === 'heatmap') {
3164
3238
  var H = PT.heatmap;
3165
3239
  var hR = regOf('heatmap', 'primary', { bottom: bodyBottom }) || { x: MX, y: H.y, w: CW, h: bodyBottom - H.y };
@@ -3306,7 +3380,7 @@ function slidesOf(model, S) {
3306
3380
  } else if (type === 'bar') {
3307
3381
  var ch = sec.chart || {};
3308
3382
  var dcols2 = (ch.colors || dataColors(model.style, model.theme));
3309
- var botY = chartBottom(!!sec.soWhat, !!sec.footnote) - (sec.note ? 0.42 : 0.05);
3383
+ var botY = chartBottom(!!sec.soWhat, !!(sec.footnote || sec.note)) - 0.05;
3310
3384
  var isHBar = ch.type === 'hbar';
3311
3385
  var bRR = regOf('bar', isHBar ? 'hbar' : 'primary', { bottom: botY }) ||
3312
3386
  { x: isHBar ? MX : MX + PT.bar.chartX, y: isHBar ? PT.bar.hbarY0 : PT.bar.chartY,
@@ -3333,13 +3407,12 @@ function slidesOf(model, S) {
3333
3407
  } else {
3334
3408
  chartBlockShapes(sh, ch, S, bx, bRR.y, bw, Math.max(1.5, bRR.h), dcols2);
3335
3409
  }
3336
- 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 }]]));
3337
3410
  } else if (type === 'twocol' || type === 'threecol') {
3338
3411
  var ps2 = sec.paragraphs || [];
3339
3412
  var nCol = (type === 'threecol') ? 3 : 2;
3340
3413
  var gapC = (type === 'threecol') ? PT.research.col3Gap : PT.twocol.colGap;
3341
- var tcR = regOf('twocol', 'primary', { top: bodyY, bottom: bodyBottom }) ||
3342
- { x: MX, y: bodyY, w: CW, h: bodyBottom - bodyY };
3414
+ /* R3:多栏均分用版心全宽(regOf('twocol') 返回单栏宽,当总宽会压扁三栏) */
3415
+ var tcR = { x: MX, y: bodyY, w: CW, h: bodyBottom - bodyY };
3343
3416
  var gC = cols(tcR.w, nCol, gapC);
3344
3417
  var perC = Math.ceil(ps2.length / nCol);
3345
3418
  var availC = tcR.h;
@@ -3535,7 +3608,7 @@ function slidesOf(model, S) {
3535
3608
  var dStart = dgR.y;
3536
3609
  var dEnd = dgR.y + dgR.h;
3537
3610
  var nLay = Math.max(1, lys.length);
3538
- var lh2 = Math.max(0.6, Math.min(D.maxLayerH, (dEnd - dStart) / nLay - D.layerGap));
3611
+ var lh2 = Math.max(0.5, Math.min(D.maxLayerH, (dEnd - dStart) / nLay - D.layerGap));
3539
3612
  lys.forEach(function (lay, li) {
3540
3613
  var y9 = dStart + li * (lh2 + D.layerGap);
3541
3614
  var focus = lay[2] === 'focus';
@@ -3551,9 +3624,26 @@ function slidesOf(model, S) {
3551
3624
  var ntt = isRec(nd) ? nd.t : nd;
3552
3625
  var nds = isRec(nd) ? (nd.d || '') : '';
3553
3626
  sh.push(shape('roundRect', x10, y9, nw2, lh2, ndacc ? S.soft : S.surface, { adj: 4000, line: S.line }));
3554
- sh.push(txSp(x10 + 0.12, y9 + (nds ? 0.12 : lh2 / 2 - 0.2), nw2 - 0.24, 0.38,
3555
- [[{ t: ntt, sz: 12.5, b: 1, col: ndacc ? S.accent : S.ink, font: S.font }]], { anchor: nds ? 't' : 'ctr' }));
3556
- 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 }]]));
3627
+ /* R1: 注解不得「浮回」标题上。标题顶对齐,注解钳在标题下边;空间不足降级单框混排。 */
3628
+ var padXn = 0.12, padYn = 0.10;
3629
+ var innerWn = nw2 - padXn * 2;
3630
+ var titleHn = 0.32, noteHn = 0.34, gapTNn = 0.04;
3631
+ var needHn = titleHn + gapTNn + (nds ? noteHn : 0) + padYn * 2;
3632
+ if (!nds || lh2 >= needHn) {
3633
+ var tYn = y9 + padYn;
3634
+ sh.push(txSp(x10 + padXn, nds ? tYn : y9 + lh2 / 2 - 0.18, innerWn, titleHn,
3635
+ [[{ t: ntt, sz: nds ? 11 : 12.5, b: 1, col: ndacc ? S.accent : S.ink, font: S.font }]],
3636
+ { anchor: 'ctr' }));
3637
+ if (nds) {
3638
+ var nYn = Math.max(tYn + titleHn + gapTNn, y9 + lh2 - padYn - noteHn);
3639
+ sh.push(txSp(x10 + padXn, nYn, innerWn, Math.min(noteHn, y9 + lh2 - padYn - nYn),
3640
+ [[{ t: nds, sz: 10, col: S.faint, font: S.font }]], { anchor: 't' }));
3641
+ }
3642
+ } else {
3643
+ sh.push(txSp(x10 + padXn, y9 + padYn * 0.5, innerWn, lh2 - padYn,
3644
+ [[{ t: ntt, sz: 11, b: 1, col: ndacc ? S.accent : S.ink, font: S.font }],
3645
+ [{ t: nds, sz: 9, col: S.faint, font: S.font }]], { anchor: 't', lineSpacing: 12 }));
3646
+ }
3557
3647
  });
3558
3648
  if (li < lys.length - 1) {
3559
3649
  var cy2 = y9 + lh2 + D.layerGap / 2;
@@ -3563,10 +3653,14 @@ function slidesOf(model, S) {
3563
3653
  }
3564
3654
  });
3565
3655
  if (sec.legend) {
3566
- var lx2 = MX;
3567
- sec.legend.forEach(function (lg2) {
3568
- sh.push(txSp(lx2, PH - 0.62, 3.2, 0.3, [[{ t: '● ' + lg2, sz: 10, col: S.faint, font: S.font }]]));
3569
- lx2 += 2.0;
3656
+ /* R2:图例进图区底部(与 HTML .arch__legend 同源),禁用 PH-0.62 固定偏移 */
3657
+ var lgH2 = 0.28;
3658
+ var lgY2 = Math.max(dStart, bodyBottom - lgH2 - 0.02);
3659
+ var nLg2 = Math.max(1, sec.legend.length);
3660
+ var step2 = Math.min(2.4, CW / nLg2);
3661
+ sec.legend.forEach(function (lg2, li2) {
3662
+ sh.push(txSp(MX + CW - nLg2 * step2 + li2 * step2, lgY2, step2 - 0.05, lgH2,
3663
+ [[{ t: '● ' + lg2, sz: 10, col: S.faint, font: S.font }]]));
3570
3664
  });
3571
3665
  }
3572
3666
  } else if (type === 'exhibit') {
@@ -3623,8 +3717,10 @@ function slidesOf(model, S) {
3623
3717
  }
3624
3718
  }
3625
3719
  /* research 通用可选件:so-what 结论条 + 页脚来源行 + 待核实条(共用同一槽位) */
3626
- if (sec.soWhat) soWhatBar(sh, sec.soWhat, S);
3627
- if (sec.footnote) footnoteLine(sh, sec.footnote, S);
3720
+ /* R2:verdict 已占 annotation 槽位时跳过 soWhat,禁止同槽双条叠印 */
3721
+ if (sec.soWhat && !sec.verdict) soWhatBar(sh, sec.soWhat, S);
3722
+ /* R2:note 与 footnote 共用注释带末行(禁 note.y=6.55 压 so-what) */
3723
+ noteOrFootnote(sh, sec, S);
3628
3724
  if (flagH) flagBar(sh, flagItems, S, flagY);
3629
3725
  slides.push(sh);
3630
3726
  });
@@ -3794,9 +3890,10 @@ function validateModel(model) {
3794
3890
  }
3795
3891
  var agMax = (M.model && M.model.agendaComfortMax) || 16;
3796
3892
  if ((model.agenda || []).length > agMax) {
3797
- warnings.push('agenda ' + model.agenda.length + ' 条超出单页舒适上限 ' + agMax + ',建议拆分');
3893
+ warnings.push('agenda ' + model.agenda.length + ' 条超出单页舒适上限 ' + agMax + ',将自动分页');
3798
3894
  }
3799
- var pages = 1 + (hasAgenda ? 1 : 0) + secs.length + 1; /* 封面+大纲(可选)+章节+收尾 */
3895
+ var agPageCount = hasAgenda ? Math.ceil(model.agenda.length / 12) : 0;
3896
+ var pages = 1 + agPageCount + secs.length + 1; /* 封面+大纲(可多页)+章节+收尾 */
3800
3897
  return { ok: missing.length === 0, missing: missing, warnings: warnings, pages: pages };
3801
3898
  }
3802
3899
 
@@ -306,7 +306,13 @@ border-color:color-mix(in srgb,var(--accent-on) 20%,transparent);color:var(--acc
306
306
  rows-2 / rows-3:等高行分区(纵向节奏,替代等高卡片堆叠) */
307
307
  .g-211{grid-template-columns:minmax(0,2fr) minmax(0,1fr) minmax(0,1fr)}
308
308
  .g-121{grid-template-columns:minmax(0,1fr) minmax(0,2fr) minmax(0,1fr)}
309
- .g-quad{grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-rows:minmax(0,1fr);align-items:stretch}
309
+ .g-quad{grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-rows:minmax(min-content,1fr);align-items:stretch}
310
+ .g-half{grid-template-columns:minmax(0,1.05fr) minmax(0,1fr);align-items:stretch}
311
+ @media (max-width:1180px){.g-half{grid-template-columns:1fr}}
312
+ .g-2--equal,.g-3--equal,.g-4--equal{grid-auto-rows:minmax(min-content,1fr);align-items:stretch}
313
+ .grid.g-2>.card,.grid.g-3>.card,.grid.g-4>.card,
314
+ .grid.g-quad>.card,.grid.g-half>.card,.grid.g-2--equal>.card,
315
+ .grid.g-3--equal>.card,.grid.g-4--equal>.card{align-self:stretch}
310
316
  .g-aside{grid-template-columns:minmax(0,1fr) minmax(200px,.36fr);align-items:start}
311
317
  .g-aside__side{position:sticky;top:calc(var(--bar-h) + 16px)}
312
318
  .g-mosaic{grid-template-columns:repeat(3,minmax(0,1fr));
@@ -322,7 +328,7 @@ grid-template-areas:"main main main" "s1 s2 s3"}
322
328
  .kv--2col{grid-template-columns:auto minmax(0,1fr) auto minmax(0,1fr)}
323
329
  /* ── 拼贴栅格 g-bento(Bento / 马赛克):4 列基准 + 跨列/跨行工具类 ──
324
330
  .sp-2/.sp-3/.sp-4 跨列(span),.rw-2 跨行;窄屏自动降列并收敛 span。 */
325
- .g-bento{grid-template-columns:repeat(4,minmax(0,1fr));grid-auto-rows:minmax(118px,auto)}
331
+ .g-bento{grid-template-columns:repeat(4,minmax(0,1fr));grid-auto-rows:minmax(min-content,1fr)}
326
332
  .sp-2{grid-column:span 2}.sp-3{grid-column:span 3}.sp-4{grid-column:span 4}
327
333
  .rw-2{grid-row:span 2}
328
334
  @media (max-width:1180px){.g-bento{grid-template-columns:repeat(2,minmax(0,1fr))}
@@ -357,19 +363,21 @@ background:color-mix(in srgb,var(--bg) 86%,transparent);
357
363
  backdrop-filter:saturate(160%) blur(14px);-webkit-backdrop-filter:saturate(160%) blur(14px);
358
364
  border-bottom:1px solid var(--border-soft);
359
365
  transition:height var(--dur-medium) var(--ease-standard)}
360
- .bar__in{height:100%;display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4)}
361
- .brand{display:flex;align-items:center;gap:var(--sp-3);min-width:0}
366
+ .bar__in{height:100%;display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4);overflow:hidden}
367
+ .brand{display:flex;align-items:center;gap:var(--sp-3);min-width:0;flex:1 1 auto;overflow:hidden}
362
368
  .brand__mark{width:34px;height:34px;flex:none;border-radius:10px;background:var(--accent);
363
369
  color:var(--accent-on);display:grid;place-items:center;font-size:13px;font-weight:700}
370
+ .brand__txt{min-width:0;overflow:hidden;flex:1 1 auto}
364
371
  .brand__title{font-size:var(--fs-sm);font-weight:600;color:var(--text);white-space:nowrap;
365
- overflow:hidden;text-overflow:ellipsis}
372
+ overflow:hidden;text-overflow:ellipsis;max-width:100%}
366
373
  .brand__sub{font-size:var(--fs-xs);color:var(--text-3);white-space:nowrap;overflow:hidden;
367
- text-overflow:ellipsis}
368
- .nav{display:flex;align-items:center;gap:var(--sp-1)}
374
+ text-overflow:ellipsis;max-width:100%}
375
+ .nav{display:flex;align-items:center;gap:var(--sp-1);flex:0 1 auto;min-width:0;overflow:hidden}
369
376
  .nav a{padding:8px 14px;border-radius:var(--r-full);color:var(--text-2);text-decoration:none;
370
377
  font-size:var(--fs-sm);font-weight:500;white-space:nowrap;transition:background-color var(--dur-short),color var(--dur-short)}
371
378
  .nav a:hover{background:var(--surface-2);color:var(--text)}
372
379
  @media (max-width:1180px){.nav{display:none}}
380
+ @media (max-width:1360px){.brand__sub{display:none}}
373
381
  .iconbtn{width:40px;height:40px;flex:none;display:grid;place-items:center;
374
382
  border:1px solid var(--border);border-radius:var(--r-full);background:transparent;
375
383
  color:var(--text-2);cursor:pointer;transition:background-color var(--dur-short),color var(--dur-short)}
@@ -424,12 +432,15 @@ transition:background-color var(--dur-short),box-shadow var(--dur-short),transfo
424
432
  .btn--ghost{background:transparent;color:var(--text);border-color:var(--border)}
425
433
  .btn--onDark{background:var(--surface);color:var(--text)}
426
434
  .card{background:var(--surface);border:1px solid var(--border-soft);border-radius:var(--r-lg);
427
- padding:clamp(20px,2vw,30px);transition:border-color var(--dur-short),box-shadow var(--dur-short),background-color var(--dur-medium)}
435
+ padding:clamp(20px,2vw,30px);transition:border-color var(--dur-short),box-shadow var(--dur-short),background-color var(--dur-medium);
436
+ display:flex;flex-direction:column;min-width:0}
428
437
  .card:hover{border-color:var(--border);box-shadow:var(--shadow-1)}
429
438
  .card--flat{background:var(--surface-1);border-color:transparent}
430
439
  .card--flat:hover{background:var(--surface-2)}
431
440
  .card--accent{background:var(--accent-soft);border-color:transparent}
432
- .card__hd{display:flex;align-items:center;gap:var(--sp-3);margin-bottom:var(--sp-3)}
441
+ .card__hd{display:flex;align-items:center;gap:var(--sp-3);margin-bottom:var(--sp-3);flex:none}
442
+ .card>.ul,.card>.t-body,.card>.t-sm{flex:1 1 auto}
443
+ .card>.so-what,.card>.tbd-legend,.card>.flagbar{flex:none;margin-top:auto}
433
444
  .card__ico{width:34px;height:34px;flex:none;border-radius:10px;background:var(--accent-soft);
434
445
  color:var(--accent-text);display:grid;place-items:center;font-size:15px;font-weight:600}
435
446
  .card__ico svg{width:18px;height:18px}
@@ -791,8 +802,9 @@ color:var(--accent);font-variant-numeric:tabular-nums;line-height:1;min-width:2.
791
802
  .agenda__a:hover .agenda__go{color:var(--accent);transform:translateX(4px)}
792
803
  .agenda__go svg{width:20px;height:20px;display:block}
793
804
  @media (min-width:900px){
794
- .agenda--2col{display:grid;grid-template-columns:1fr 1fr;column-gap:clamp(20px,3vw,48px)}
795
- .agenda--2col .agenda__i{border-top:1px solid var(--border-soft)}}
805
+ /* R4:列优先(先左列后右列),与 PPTX Agenda 列填充一致——grid 行优先会打乱阅读顺序 */
806
+ .agenda--2col{display:block;column-count:2;column-gap:clamp(20px,3vw,48px)}
807
+ .agenda--2col .agenda__i{break-inside:avoid;border-top:1px solid var(--border-soft)}}
796
808
  /* ── 页码指示(翻页导航) ── */
797
809
  .pager{position:fixed;right:clamp(10px,1.6vw,26px);top:50%;transform:translateY(-50%);
798
810
  z-index:60;display:flex;flex-direction:column;align-items:center;gap:10px}