@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}
@@ -3577,7 +3589,7 @@ window.REPORT_MODEL = {
3577
3589
 
3578
3590
  /* __TOPPPT_RUNTIME_START__ */
3579
3591
  /* ══ PPTX 导出运行时(assets/pptx-export.js 的内联副本 · 由 scripts/sync_runtime.py 注入,禁止手改) ══ */
3580
- /* __TOPPPT_RUNTIME_SHA__:57dbfb4f2b594c45 */
3592
+ /* __TOPPPT_RUNTIME_SHA__:af6e25f25fd2e116 */
3581
3593
  /* ═══════════════════════════════════════════════════════════════════════════
3582
3594
  * TopPPT HTML· 浏览器端 PPTX 预览运行时
3583
3595
  * 零依赖、随报告单文件分发;OOXML 序列化器(仅供预览,页面不导出文件)。
@@ -3701,7 +3713,8 @@ function regionOf(pageType, slotId, opts) {
3701
3713
  if (type === 'bar' && slot === 'primary') {
3702
3714
  var by = P.chartY || 2.95;
3703
3715
  var bb = opts.bottom != null ? opts.bottom : bottomDefault;
3704
- return { x: MX + (P.chartX || 0), y: by, w: CW - (P.chartW || 0), h: Math.max(1.2, bb - by) };
3716
+ /* chartX/chartW 历史误用已纠正:与 donut/table/hbar/exhibit 同口径,图表盒对齐版心 (MX, CW) */
3717
+ return { x: MX, y: by, w: CW, h: Math.max(1.2, bb - by) };
3705
3718
  }
3706
3719
  if (type === 'bar' && slot === 'hbar') {
3707
3720
  var y0 = P.hbarY0 || 2.6;
@@ -3736,6 +3749,15 @@ function regionOf(pageType, slotId, opts) {
3736
3749
  return { x: MX, y: P.heroY || 2.9, w: CW * (P.heroW || 0.52), h: P.heroH || 1.6,
3737
3750
  dividerX: P.dividerX, metricY0: P.metricY0, metricRowH: P.metricRowH };
3738
3751
  }
3752
+ /* R3:kpi 支撑指标列——落在 dividerX 右侧(默认全宽返回会压住 hero) */
3753
+ if (type === 'kpi' && slot === 'metrics') {
3754
+ var dxK = P.dividerX != null ? P.dividerX : 0.56;
3755
+ return {
3756
+ x: MX + CW * dxK + 0.35, y: P.metricY0 || 2.95,
3757
+ w: CW * (1 - dxK) - 0.5,
3758
+ h: Math.max(0.6, bottomDefault - (P.metricY0 || 2.95)),
3759
+ };
3760
+ }
3739
3761
  if (type === 'comparison' && (slot === 'left' || slot === 'primary' || slot === 'right')) {
3740
3762
  var cg = P.panelGap || 0.4, pw = (CW - cg) / 2;
3741
3763
  if (slot === 'right') return { x: MX + pw + cg, y: P.panelY || 2.55, w: pw, h: P.panelH || 3.4 };
@@ -3768,15 +3790,22 @@ function regionOf(pageType, slotId, opts) {
3768
3790
  }
3769
3791
  if (type === 'image' && slot === 'primary') {
3770
3792
  var iy2 = Math.max(P.y || 2.3, opts.top || 0);
3771
- 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)) };
3793
+ /* R2:有图注时图片不得越过 contentBottomWithNote(图注落注释带,属 annotation self) */
3794
+ var iBottom = (opts.bottom != null) ? opts.bottom
3795
+ : (opts.caption ? (L.contentBottomWithNote || 6.4) : bottomDefault);
3796
+ return { x: MX, y: iy2, w: CW, h: Math.max(1.2, Math.min(P.h || 4.2, iBottom - iy2)) };
3772
3797
  }
3773
3798
  if ((type === 'halftable') && (slot === 'left' || slot === 'primary')) {
3774
3799
  var rs = PT.research || {};
3775
- return { x: MX, y: topDefault, w: CW * (rs.halfTableW || 0.52), h: Math.max(1, bottomDefault - topDefault) };
3800
+ var ht = opts.top != null ? opts.top : topDefault;
3801
+ var hb = opts.bottom != null ? opts.bottom : bottomDefault;
3802
+ return { x: MX, y: ht, w: CW * (rs.halfTableW || 0.52), h: Math.max(1, hb - ht) };
3776
3803
  }
3777
3804
  if ((type === 'halftable') && (slot === 'right' || slot === 'secondary')) {
3778
3805
  var rs2 = PT.research || {};
3779
- return { x: MX + CW * (rs2.halfChartX || 0.57), y: topDefault, w: CW * (rs2.halfChartW || 0.43), h: Math.max(1, bottomDefault - topDefault) };
3806
+ var ht2 = opts.top != null ? opts.top : topDefault;
3807
+ var hb2 = opts.bottom != null ? opts.bottom : bottomDefault;
3808
+ return { x: MX + CW * (rs2.halfChartX || 0.57), y: ht2, w: CW * (rs2.halfChartW || 0.43), h: Math.max(1, hb2 - ht2) };
3780
3809
  }
3781
3810
  if (type === 'matrix' && slot === 'primary') {
3782
3811
  var rm = PT.research || {};
@@ -3797,7 +3826,7 @@ function regionOf(pageType, slotId, opts) {
3797
3826
  /* __TOPPPT_SCHEMA_START__ */
3798
3827
  /* ── 由 scripts/sync_runtime.py 从 scripts/model-schema.json 注入 · 禁止手改 ── */
3799
3828
  /* DSL schema 单源:浏览器端 validateModel 与 scripts/extract_model.py 消费同一份定义 */
3800
- 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"]}}};
3829
+ 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"]}}};
3801
3830
  /* __TOPPPT_SCHEMA_END__ */
3802
3831
 
3803
3832
  var _mode = 'presentation'; /* 当前模式(slidesOf 里按 model.mode 设置) */
@@ -4088,6 +4117,15 @@ function footnoteLine(sh, text, S) {
4088
4117
  sh.push(txSp(R.x, R.y, R.w, R.h,
4089
4118
  [[{ t: text, sz: 9.5, col: S.faint, font: S.font }]]));
4090
4119
  }
4120
+ /* R2:sec.note 与 sec.footnote 共用注释带末行(footnoteY=6.72)。
4121
+ note.y=6.55 会压进 so-what 带(6.05–6.67)——同页二者必须合并为一行。 */
4122
+ function noteOrFootnote(sh, sec, S) {
4123
+ var parts = [];
4124
+ if (sec && sec.note) parts.push(String(sec.note));
4125
+ if (sec && sec.footnote) parts.push(String(sec.footnote));
4126
+ if (!parts.length) return;
4127
+ footnoteLine(sh, parts.join(' · '), S);
4128
+ }
4091
4129
  /* 待核实清单条(accent 强调色标注 · 与 build_pptx.js / HTML .flagbar 同源) */
4092
4130
  function flagBar(sh, items, S, yTop) {
4093
4131
  var F = PT.flagbar || { hdH: 0.26, rowH: 0.24, maxRows: 3, gap: 0.12 };
@@ -4276,7 +4314,9 @@ function vbarShapes(sh, ec, S, x, y, w, h, dcols) {
4276
4314
  : [{ name: '数值', values: ec.values || [] }];
4277
4315
  var nb = Math.max(1, labels.length);
4278
4316
  var ns = Math.max(1, series.length);
4279
- var baseY = y + h, plotW = w, step = plotW / nb;
4317
+ /* R2:类目标签带必须含在 h 内(原 baseY+0.08 画在图区外,会压进 so-what/来源行) */
4318
+ var labelH = 0.36;
4319
+ var baseY = y + Math.max(0.6, h - labelH), plotW = w, step = plotW / nb;
4280
4320
  var cluster = Math.min(1.1, step * (ns > 1 ? 0.78 : 0.55));
4281
4321
  var bw = cluster / ns;
4282
4322
  var all = [];
@@ -4285,26 +4325,27 @@ function vbarShapes(sh, ec, S, x, y, w, h, dcols) {
4285
4325
  });
4286
4326
  var vmax = ec.max || Math.max.apply(null, all.concat([1]));
4287
4327
  var top = all.length ? Math.max.apply(null, all) : 0;
4328
+ var plotH = Math.max(0.35, baseY - y);
4288
4329
  sh.push(shape('rect', x, baseY, plotW, 0.02, S.line));
4289
4330
  series.forEach(function (se, si) {
4290
4331
  var vals = se.values || [];
4291
4332
  var col = dcols ? dcols[si % 5] : (si === 0 ? S.accent : S.faint);
4292
4333
  vals.forEach(function (v, i) {
4293
4334
  var num = Number(v) || 0;
4294
- var hh = Math.max(0.05, num / vmax * (h - 0.5));
4335
+ var hh = Math.max(0.05, num / vmax * (plotH - 0.35));
4295
4336
  var bx = x + i * step + (step - cluster) / 2 + si * bw;
4296
4337
  var hot = ns === 1 && num === top;
4297
4338
  sh.push(shape('roundRect', bx, baseY - hh, Math.max(0.04, bw * 0.92), hh,
4298
4339
  hot ? (dcols ? dcols[i % 5] : S.accent) : col, { adj: 6000 }));
4299
4340
  if (ns === 1) {
4300
- sh.push(txSp(bx - 0.3, baseY - hh - 0.4, bw + 0.6, 0.32,
4341
+ sh.push(txSp(bx - 0.3, baseY - hh - 0.36, bw + 0.6, 0.30,
4301
4342
  [[{ t: valText(num, ec.unit), sz: 12.5, b: 1, col: hot ? S.accent : S.body, font: S.font }]], { align: 'ctr' }));
4302
4343
  }
4303
4344
  });
4304
4345
  });
4305
4346
  labels.forEach(function (lb, i) {
4306
4347
  var lx = x + i * step;
4307
- sh.push(txSp(lx, baseY + 0.08, step, 0.32,
4348
+ sh.push(txSp(lx, baseY + 0.06, step, labelH - 0.08,
4308
4349
  [[{ t: lb, sz: 10.5, col: S.faint, font: S.font }]], { align: 'ctr' }));
4309
4350
  });
4310
4351
  /* Multi-series legend along top of plot (keeps category labels; avoids blank chart). */
@@ -4327,7 +4368,10 @@ function lineShapes(sh, ec, S, x, y, w, h, dcols) {
4327
4368
  var all = [];
4328
4369
  series.forEach(function (se) { (se.values || []).forEach(function (v) { all.push(Number(v) || 0); }); });
4329
4370
  var vmax = ec.max || Math.max.apply(null, all.concat([1]));
4330
- var plotH = h - 0.5, baseY = y + h;
4371
+ /* R2:类目标签带含在 h 内,禁画到图区外压 so-what */
4372
+ var labelH = 0.36;
4373
+ var baseY = y + Math.max(0.6, h - labelH);
4374
+ var plotH = Math.max(0.35, baseY - y - 0.2);
4331
4375
  sh.push(shape('rect', x, baseY, w, 0.02, S.line));
4332
4376
  var stepX = n > 1 ? w / (n - 1) : 0;
4333
4377
  series.forEach(function (se, si) {
@@ -4347,7 +4391,7 @@ function lineShapes(sh, ec, S, x, y, w, h, dcols) {
4347
4391
  });
4348
4392
  labels.forEach(function (lb, i) {
4349
4393
  var px = x + (n > 1 ? i * stepX : w / 2);
4350
- sh.push(txSp(px - 0.5, baseY + 0.08, 1.0, 0.32,
4394
+ sh.push(txSp(px - 0.5, baseY + 0.06, 1.0, labelH - 0.08,
4351
4395
  [[{ t: lb, sz: 10.5, col: S.faint, font: S.font }]], { align: 'ctr' }));
4352
4396
  });
4353
4397
  }
@@ -4799,25 +4843,44 @@ function slidesOf(model, S) {
4799
4843
  c.push(txSp(MX, CV.subtitleY, CW - 1, CV.subtitleH, [[{ t: model.subtitle || '', sz: 18, col: S.soft, font: S.font }]]));
4800
4844
  slides.push(c);
4801
4845
 
4802
- /* Agenda(>8 条自动双列,行高自适应防溢出;architecture 极简形态可省略 agenda) */
4846
+ /* Agenda(>8 条自动双列;>12 条自动分页;长标题截断+全称入 notes;列优先与 HTML 一致) */
4803
4847
  var items = model.agenda || [];
4804
4848
  if (items.length) {
4805
- var ag = [];
4806
- head(ag, 'AGENDA', '报告大纲');
4807
- var colN = items.length > 8 ? 2 : 1;
4808
- var perCol = Math.ceil(items.length / colN) || 1;
4809
- var rowH = Math.min(1.05, (CONTENT_BOTTOM - CONTENT_TOP - 0.25) / Math.max(1, perCol));
4810
- var numSz = rowH >= 0.85 ? 30 : (rowH >= 0.65 ? 22 : 18);
4811
- var tSz = rowH >= 0.85 ? 16 : 14, dSz = rowH >= 0.85 ? 12 : 11;
4812
- items.forEach(function (it, i) {
4813
- var col = Math.floor(i / perCol), row = i % perCol;
4814
- var x = MX + col * (CW / colN), y = CONTENT_TOP + row * rowH;
4815
- 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 }]]));
4816
- ag.push(txSp(x + 1.0, y + 0.03, CW / colN - 1.1, Math.min(0.95, rowH),
4817
- [[{ t: it[1], sz: tSz, b: 1, col: S.ink, font: S.font }],
4818
- [{ t: it[2] || '', sz: dSz, col: S.body, font: S.font }]], { lineSpacing: tSz + 5 }));
4849
+ var ROWS_PER_PAGE = 12;
4850
+ var agPages = [];
4851
+ for (var ai = 0; ai < items.length; ai += ROWS_PER_PAGE) agPages.push(items.slice(ai, ai + ROWS_PER_PAGE));
4852
+ agPages.forEach(function (pageItems, pi) {
4853
+ var ag = [];
4854
+ var sub = agPages.length > 1 ? '(' + (pi + 1) + '/' + agPages.length + ')' : '';
4855
+ head(ag, 'AGENDA', '报告大纲' + sub);
4856
+ var colN = pageItems.length > 8 ? 2 : 1;
4857
+ var perCol = Math.ceil(pageItems.length / colN) || 1;
4858
+ var rowH = Math.min(1.05, (CONTENT_BOTTOM - CONTENT_TOP - 0.25) / Math.max(1, perCol));
4859
+ var numSz = rowH >= 0.85 ? 30 : (rowH >= 0.65 ? 22 : 18);
4860
+ var tSz = rowH >= 0.85 ? 16 : 14, dSz = rowH >= 0.85 ? 12 : 11;
4861
+ var agNotes = [];
4862
+ pageItems.forEach(function (it, i) {
4863
+ var col = Math.floor(i / perCol), row = i % perCol;
4864
+ var x = MX + col * (CW / colN), y = CONTENT_TOP + row * rowH;
4865
+ var titleBoxW = CW / colN - 1.15;
4866
+ var rawTitle = String(it[1] || '');
4867
+ var titleEstW = estTextH(rawTitle, titleBoxW, tSz, 1.2);
4868
+ var maxTitleH = rowH * 0.55;
4869
+ var title = rawTitle;
4870
+ if (titleEstW > maxTitleH) {
4871
+ var maxChars = Math.max(10, Math.floor(rawTitle.length * (maxTitleH / titleEstW)));
4872
+ if (rawTitle.length > maxChars) {
4873
+ title = rawTitle.slice(0, maxChars - 1) + '…';
4874
+ agNotes.push('条目 ' + it[0] + ' 全称:' + rawTitle);
4875
+ }
4876
+ }
4877
+ 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 }]]));
4878
+ ag.push(txSp(x + 1.0, y + 0.03, titleBoxW, Math.min(0.95, rowH),
4879
+ [[{ t: title, sz: tSz, b: 1, col: S.ink, font: S.font }],
4880
+ [{ t: it[2] || '', sz: dSz, col: S.body, font: S.font }]], { lineSpacing: tSz + 5 }));
4881
+ });
4882
+ slides.push(ag);
4819
4883
  });
4820
- slides.push(ag);
4821
4884
  }
4822
4885
 
4823
4886
  /* 章节页 */
@@ -4903,12 +4966,13 @@ function slidesOf(model, S) {
4903
4966
  { t: p[1], sz: fz - 1, col: S.body, font: S.font }]]));
4904
4967
  });
4905
4968
  });
4969
+ /* R2:verdict 与 soWhat 共用 annotation 槽位——只画一个(verdict 优先) */
4906
4970
  if (sec.verdict) {
4907
4971
  var vR = regOf('exhibit', 'annotation') || { x: MX, y: PT.exhibit.soWhatY, w: CW, h: 0.62 };
4908
- sh.push(shape('rect', vR.x, vR.y, vR.w, vR.h, S.accent));
4972
+ sh.push(shape('rect', vR.x, vR.y, vR.w, vR.h, S.accent, { name: 'tr:soWhat' }));
4909
4973
  sh.push(txSp(vR.x + 0.22, vR.y + 0.06, vR.w - 0.44, vR.h - 0.12,
4910
4974
  [[{ t: '结论 ', sz: 11, b: 1, col: S.onAccent, font: S.font, spc: 150 },
4911
- { t: sec.verdict, sz: 12.5, b: 1, col: S.onAccent, font: S.font }]], { anchor: 'ctr' }));
4975
+ { t: sec.verdict, sz: 12.5, b: 1, col: S.onAccent, font: S.font }]], { anchor: 'ctr', name: 'tr:soWhat' }));
4912
4976
  }
4913
4977
  } else if (type === 'quote') {
4914
4978
  var Q = PT.quote;
@@ -4941,7 +5005,6 @@ function slidesOf(model, S) {
4941
5005
  } else if (type === 'donut') {
4942
5006
  var dcn = sec.chart || {};
4943
5007
  if (dcn.labels && dcn.values) donutChart(sh, dcn, S, dcn.colors || donutColors(model.style, S, model.theme), bodyBottom);
4944
- 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 }]]));
4945
5008
  } else if (type === 'heatmap') {
4946
5009
  var H = PT.heatmap;
4947
5010
  var hR = regOf('heatmap', 'primary', { bottom: bodyBottom }) || { x: MX, y: H.y, w: CW, h: bodyBottom - H.y };
@@ -5088,7 +5151,7 @@ function slidesOf(model, S) {
5088
5151
  } else if (type === 'bar') {
5089
5152
  var ch = sec.chart || {};
5090
5153
  var dcols2 = (ch.colors || dataColors(model.style, model.theme));
5091
- var botY = chartBottom(!!sec.soWhat, !!sec.footnote) - (sec.note ? 0.42 : 0.05);
5154
+ var botY = chartBottom(!!sec.soWhat, !!(sec.footnote || sec.note)) - 0.05;
5092
5155
  var isHBar = ch.type === 'hbar';
5093
5156
  var bRR = regOf('bar', isHBar ? 'hbar' : 'primary', { bottom: botY }) ||
5094
5157
  { x: isHBar ? MX : MX + PT.bar.chartX, y: isHBar ? PT.bar.hbarY0 : PT.bar.chartY,
@@ -5115,13 +5178,12 @@ function slidesOf(model, S) {
5115
5178
  } else {
5116
5179
  chartBlockShapes(sh, ch, S, bx, bRR.y, bw, Math.max(1.5, bRR.h), dcols2);
5117
5180
  }
5118
- 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 }]]));
5119
5181
  } else if (type === 'twocol' || type === 'threecol') {
5120
5182
  var ps2 = sec.paragraphs || [];
5121
5183
  var nCol = (type === 'threecol') ? 3 : 2;
5122
5184
  var gapC = (type === 'threecol') ? PT.research.col3Gap : PT.twocol.colGap;
5123
- var tcR = regOf('twocol', 'primary', { top: bodyY, bottom: bodyBottom }) ||
5124
- { x: MX, y: bodyY, w: CW, h: bodyBottom - bodyY };
5185
+ /* R3:多栏均分用版心全宽(regOf('twocol') 返回单栏宽,当总宽会压扁三栏) */
5186
+ var tcR = { x: MX, y: bodyY, w: CW, h: bodyBottom - bodyY };
5125
5187
  var gC = cols(tcR.w, nCol, gapC);
5126
5188
  var perC = Math.ceil(ps2.length / nCol);
5127
5189
  var availC = tcR.h;
@@ -5317,7 +5379,7 @@ function slidesOf(model, S) {
5317
5379
  var dStart = dgR.y;
5318
5380
  var dEnd = dgR.y + dgR.h;
5319
5381
  var nLay = Math.max(1, lys.length);
5320
- var lh2 = Math.max(0.6, Math.min(D.maxLayerH, (dEnd - dStart) / nLay - D.layerGap));
5382
+ var lh2 = Math.max(0.5, Math.min(D.maxLayerH, (dEnd - dStart) / nLay - D.layerGap));
5321
5383
  lys.forEach(function (lay, li) {
5322
5384
  var y9 = dStart + li * (lh2 + D.layerGap);
5323
5385
  var focus = lay[2] === 'focus';
@@ -5333,9 +5395,26 @@ function slidesOf(model, S) {
5333
5395
  var ntt = isRec(nd) ? nd.t : nd;
5334
5396
  var nds = isRec(nd) ? (nd.d || '') : '';
5335
5397
  sh.push(shape('roundRect', x10, y9, nw2, lh2, ndacc ? S.soft : S.surface, { adj: 4000, line: S.line }));
5336
- sh.push(txSp(x10 + 0.12, y9 + (nds ? 0.12 : lh2 / 2 - 0.2), nw2 - 0.24, 0.38,
5337
- [[{ t: ntt, sz: 12.5, b: 1, col: ndacc ? S.accent : S.ink, font: S.font }]], { anchor: nds ? 't' : 'ctr' }));
5338
- 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 }]]));
5398
+ /* R1: 注解不得「浮回」标题上。标题顶对齐,注解钳在标题下边;空间不足降级单框混排。 */
5399
+ var padXn = 0.12, padYn = 0.10;
5400
+ var innerWn = nw2 - padXn * 2;
5401
+ var titleHn = 0.32, noteHn = 0.34, gapTNn = 0.04;
5402
+ var needHn = titleHn + gapTNn + (nds ? noteHn : 0) + padYn * 2;
5403
+ if (!nds || lh2 >= needHn) {
5404
+ var tYn = y9 + padYn;
5405
+ sh.push(txSp(x10 + padXn, nds ? tYn : y9 + lh2 / 2 - 0.18, innerWn, titleHn,
5406
+ [[{ t: ntt, sz: nds ? 11 : 12.5, b: 1, col: ndacc ? S.accent : S.ink, font: S.font }]],
5407
+ { anchor: 'ctr' }));
5408
+ if (nds) {
5409
+ var nYn = Math.max(tYn + titleHn + gapTNn, y9 + lh2 - padYn - noteHn);
5410
+ sh.push(txSp(x10 + padXn, nYn, innerWn, Math.min(noteHn, y9 + lh2 - padYn - nYn),
5411
+ [[{ t: nds, sz: 10, col: S.faint, font: S.font }]], { anchor: 't' }));
5412
+ }
5413
+ } else {
5414
+ sh.push(txSp(x10 + padXn, y9 + padYn * 0.5, innerWn, lh2 - padYn,
5415
+ [[{ t: ntt, sz: 11, b: 1, col: ndacc ? S.accent : S.ink, font: S.font }],
5416
+ [{ t: nds, sz: 9, col: S.faint, font: S.font }]], { anchor: 't', lineSpacing: 12 }));
5417
+ }
5339
5418
  });
5340
5419
  if (li < lys.length - 1) {
5341
5420
  var cy2 = y9 + lh2 + D.layerGap / 2;
@@ -5345,10 +5424,14 @@ function slidesOf(model, S) {
5345
5424
  }
5346
5425
  });
5347
5426
  if (sec.legend) {
5348
- var lx2 = MX;
5349
- sec.legend.forEach(function (lg2) {
5350
- sh.push(txSp(lx2, PH - 0.62, 3.2, 0.3, [[{ t: '● ' + lg2, sz: 10, col: S.faint, font: S.font }]]));
5351
- lx2 += 2.0;
5427
+ /* R2:图例进图区底部(与 HTML .arch__legend 同源),禁用 PH-0.62 固定偏移 */
5428
+ var lgH2 = 0.28;
5429
+ var lgY2 = Math.max(dStart, bodyBottom - lgH2 - 0.02);
5430
+ var nLg2 = Math.max(1, sec.legend.length);
5431
+ var step2 = Math.min(2.4, CW / nLg2);
5432
+ sec.legend.forEach(function (lg2, li2) {
5433
+ sh.push(txSp(MX + CW - nLg2 * step2 + li2 * step2, lgY2, step2 - 0.05, lgH2,
5434
+ [[{ t: '● ' + lg2, sz: 10, col: S.faint, font: S.font }]]));
5352
5435
  });
5353
5436
  }
5354
5437
  } else if (type === 'exhibit') {
@@ -5405,8 +5488,10 @@ function slidesOf(model, S) {
5405
5488
  }
5406
5489
  }
5407
5490
  /* research 通用可选件:so-what 结论条 + 页脚来源行 + 待核实条(共用同一槽位) */
5408
- if (sec.soWhat) soWhatBar(sh, sec.soWhat, S);
5409
- if (sec.footnote) footnoteLine(sh, sec.footnote, S);
5491
+ /* R2:verdict 已占 annotation 槽位时跳过 soWhat,禁止同槽双条叠印 */
5492
+ if (sec.soWhat && !sec.verdict) soWhatBar(sh, sec.soWhat, S);
5493
+ /* R2:note 与 footnote 共用注释带末行(禁 note.y=6.55 压 so-what) */
5494
+ noteOrFootnote(sh, sec, S);
5410
5495
  if (flagH) flagBar(sh, flagItems, S, flagY);
5411
5496
  slides.push(sh);
5412
5497
  });
@@ -5576,9 +5661,10 @@ function validateModel(model) {
5576
5661
  }
5577
5662
  var agMax = (M.model && M.model.agendaComfortMax) || 16;
5578
5663
  if ((model.agenda || []).length > agMax) {
5579
- warnings.push('agenda ' + model.agenda.length + ' 条超出单页舒适上限 ' + agMax + ',建议拆分');
5664
+ warnings.push('agenda ' + model.agenda.length + ' 条超出单页舒适上限 ' + agMax + ',将自动分页');
5580
5665
  }
5581
- var pages = 1 + (hasAgenda ? 1 : 0) + secs.length + 1; /* 封面+大纲(可选)+章节+收尾 */
5666
+ var agPageCount = hasAgenda ? Math.ceil(model.agenda.length / 12) : 0;
5667
+ var pages = 1 + agPageCount + secs.length + 1; /* 封面+大纲(可多页)+章节+收尾 */
5582
5668
  return { ok: missing.length === 0, missing: missing, warnings: warnings, pages: pages };
5583
5669
  }
5584
5670