@topmindspace/tms-skills 2.0.1 → 2.0.2

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 (98) hide show
  1. package/CHANGELOG.md +139 -134
  2. package/bin/tms-skills.js +160 -160
  3. package/package.json +48 -48
  4. package/top-ppt-html/README.md +254 -254
  5. package/top-ppt-html/SKILL.md +111 -111
  6. package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.html +3873 -3926
  7. package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.model.json +167 -167
  8. package/top-ppt-html/assets/examples/2026-09-09-architecture-spectrum.html +3873 -3926
  9. package/top-ppt-html/assets/examples/2026-09-09-architecture-spectrum.model.json +167 -167
  10. package/top-ppt-html/assets/examples/2026-09-09-presentation-apple-mono.html +4272 -4325
  11. package/top-ppt-html/assets/examples/2026-09-09-presentation-apple-mono.model.json +320 -320
  12. package/top-ppt-html/assets/examples/2026-09-09-presentation-brand-red.html +4272 -4325
  13. package/top-ppt-html/assets/examples/2026-09-09-presentation-brand-red.model.json +320 -320
  14. package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.html +4272 -4325
  15. package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.model.json +320 -320
  16. package/top-ppt-html/assets/examples/2026-09-09-research-deep-teal.html +5474 -5527
  17. package/top-ppt-html/assets/examples/2026-09-09-research-deep-teal.model.json +913 -913
  18. package/top-ppt-html/assets/examples/2026-09-09-research-indigo-violet.html +5474 -5527
  19. package/top-ppt-html/assets/examples/2026-09-09-research-indigo-violet.model.json +913 -913
  20. package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.html +5474 -5527
  21. package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.model.json +913 -913
  22. package/top-ppt-html/assets/examples/2026-09-09-research-warm-sand.html +5474 -5527
  23. package/top-ppt-html/assets/examples/2026-09-09-research-warm-sand.model.json +913 -913
  24. package/top-ppt-html/assets/pptx-export.js +1944 -1944
  25. package/top-ppt-html/assets/style-gallery.html +559 -589
  26. package/top-ppt-html/assets/templates/architecture.html +3675 -3728
  27. package/top-ppt-html/assets/templates/engine.css +787 -840
  28. package/top-ppt-html/assets/templates/presentation.html +3685 -3738
  29. package/top-ppt-html/assets/templates/research.html +3964 -4017
  30. package/top-ppt-html/assets/templates/ui.js +520 -520
  31. package/top-ppt-html/assets/theme-overview-architecture.png +0 -0
  32. package/top-ppt-html/assets/theme-overview-presentation.png +0 -0
  33. package/top-ppt-html/assets/theme-overview-research.png +0 -0
  34. package/top-ppt-html/assets/theme-overview.png +0 -0
  35. package/top-ppt-html/evals/prompts.csv +15 -15
  36. package/top-ppt-html/evals/rubric.schema.json +25 -25
  37. package/top-ppt-html/evals/run_evals.py +220 -220
  38. package/top-ppt-html/evals/trace.example.json +16 -16
  39. package/top-ppt-html/package-lock.json +175 -0
  40. package/top-ppt-html/package.json +30 -35
  41. package/top-ppt-html/references/charts-basic.md +624 -624
  42. package/top-ppt-html/references/charts-discipline.md +108 -108
  43. package/top-ppt-html/references/charts-extended.md +482 -482
  44. package/top-ppt-html/references/charts.md +28 -28
  45. package/top-ppt-html/references/components-atoms.md +624 -624
  46. package/top-ppt-html/references/components.md +30 -30
  47. package/top-ppt-html/references/content-rules.md +490 -490
  48. package/top-ppt-html/references/design-system-engine.md +235 -235
  49. package/top-ppt-html/references/design-system.md +471 -478
  50. package/top-ppt-html/references/failure-modes.md +214 -214
  51. package/top-ppt-html/references/high-fidelity.md +127 -127
  52. package/top-ppt-html/references/icons.md +397 -397
  53. package/top-ppt-html/references/industry-benchmark.md +105 -105
  54. package/top-ppt-html/references/infographics-stats.md +308 -308
  55. package/top-ppt-html/references/infographics-structure.md +226 -226
  56. package/top-ppt-html/references/infographics.md +43 -43
  57. package/top-ppt-html/references/layout-grammar.md +315 -315
  58. package/top-ppt-html/references/layouts-architecture.md +108 -108
  59. package/top-ppt-html/references/layouts-combo.md +600 -600
  60. package/top-ppt-html/references/layouts-research.md +160 -160
  61. package/top-ppt-html/references/modes.md +254 -254
  62. package/top-ppt-html/references/outline-design.md +275 -275
  63. package/top-ppt-html/references/playbook.md +266 -266
  64. package/top-ppt-html/references/pptx-export.md +209 -209
  65. package/top-ppt-html/references/reform-plan.md +252 -252
  66. package/top-ppt-html/references/styles.md +336 -370
  67. package/top-ppt-html/references/tech-design.md +138 -138
  68. package/top-ppt-html/scripts/audit_css.py +109 -109
  69. package/top-ppt-html/scripts/audit_docs.py +176 -176
  70. package/top-ppt-html/scripts/audit_skill.py +220 -220
  71. package/top-ppt-html/scripts/audit_styles.py +293 -351
  72. package/top-ppt-html/scripts/build_examples.py +2276 -2276
  73. package/top-ppt-html/scripts/build_pptx.js +2380 -2380
  74. package/top-ppt-html/scripts/capture_theme_overview.js +78 -78
  75. package/top-ppt-html/scripts/checks_html.py +127 -127
  76. package/top-ppt-html/scripts/cross_verify.py +294 -294
  77. package/top-ppt-html/scripts/env_probe.py +158 -158
  78. package/top-ppt-html/scripts/extract_model.py +210 -210
  79. package/top-ppt-html/scripts/extract_snippet.py +374 -374
  80. package/top-ppt-html/scripts/gen_channel_a.js +214 -214
  81. package/top-ppt-html/scripts/layout-constants.json +3309 -3377
  82. package/top-ppt-html/scripts/layout_slots.json +830 -830
  83. package/top-ppt-html/scripts/lib_layout_regions.js +412 -412
  84. package/top-ppt-html/scripts/measure_height.py +178 -178
  85. package/top-ppt-html/scripts/model-schema.json +547 -547
  86. package/top-ppt-html/scripts/negative_tests.py +307 -307
  87. package/top-ppt-html/scripts/package_skill.py +291 -291
  88. package/top-ppt-html/scripts/prepare_images.py +341 -341
  89. package/top-ppt-html/scripts/probe_image_export.py +188 -188
  90. package/top-ppt-html/scripts/quality_gate.py +301 -301
  91. package/top-ppt-html/scripts/regression.py +307 -308
  92. package/top-ppt-html/scripts/render_compare.py +275 -275
  93. package/top-ppt-html/scripts/render_from_model.py +698 -698
  94. package/top-ppt-html/scripts/scaffold_report.py +1054 -1054
  95. package/top-ppt-html/scripts/section-file-map.json +104 -104
  96. package/top-ppt-html/scripts/sync_runtime.py +662 -662
  97. package/top-ppt-html/scripts/validate_pptx.py +1510 -1510
  98. package/top-ppt-html/scripts/validate_report.py +1456 -1456
@@ -1,412 +1,412 @@
1
- /**
2
- * TopPPT HTML · 页型布局区域 IR(双通道几何收敛起点)
3
- *
4
- * 从 layout-constants.json + layout_slots.json 派生「语义槽位 → 英寸矩形」。
5
- * B 通道(build_pptx.js)直接 require;A 通道消费同一 LC 常量(数字同源),
6
- * 本模块是把「槽位语义」从散落坐标收拢成可测接口的第一步。
7
- *
8
- * 纪律:改几何只改 layout-constants.json;改槽位只改 layout_slots.json。
9
- * 新增页型先扩 layout_slots.json,再在此补 regionOf 分支,最后写双引擎。
10
- */
11
- 'use strict';
12
-
13
- const fs = require('fs');
14
- const path = require('path');
15
-
16
- const ROOT = path.join(__dirname, '..');
17
- const LC = JSON.parse(fs.readFileSync(path.join(__dirname, 'layout-constants.json'), 'utf8'));
18
- // 布局 IR:优先 layout-constants.layoutSlots(v9 单源合并);否则回落 layout_slots.json
19
- let SLOTS = LC.layoutSlots;
20
- if (!SLOTS || !SLOTS.pageTypes) {
21
- SLOTS = JSON.parse(fs.readFileSync(path.join(__dirname, 'layout_slots.json'), 'utf8'));
22
- }
23
-
24
- const INFO_TYPES = {
25
- sankey: 1, treemap: 1, boxplot: 1, network: 1, marimekko: 1, streamgraph: 1,
26
- };
27
-
28
- function pageBox() {
29
- const p = LC.page;
30
- return { pw: p.pw, ph: p.ph, mx: p.mx, cw: p.pw - 2 * p.mx };
31
- }
32
-
33
- /** 内容区垂直范围(含注释层预留) */
34
- function contentBand(opts) {
35
- const L = LC.pageTypes.layout;
36
- const C = LC.pageTypes.common;
37
- const withNote = !!(opts && opts.withNote);
38
- const top = (opts && opts.top != null) ? opts.top : C.bodyY;
39
- const bottom = withNote ? L.contentBottomWithNote : L.contentBottom;
40
- return { top, bottom, h: Math.max(0, bottom - top) };
41
- }
42
-
43
- /**
44
- * 语义槽位 → {x,y,w,h}(英寸)
45
- * 未识别页型/槽位时返回 null(调用方回落自身逻辑,不中断)。
46
- */
47
- function regionOf(pageType, slotId, opts) {
48
- const type = String(pageType || 'points');
49
- const slot = String(slotId || '');
50
- const { mx, cw } = pageBox();
51
- const C = LC.pageTypes.common;
52
- const L = LC.pageTypes.layout;
53
- const PT = LC.pageTypes[type] || {};
54
-
55
- // ── chrome:页头 ──
56
- if (slot === 'head' || slot === 'chrome') {
57
- return {
58
- x: mx, y: C.headEyebrowY, w: cw,
59
- h: Math.max(0.4, (C.headRuleY || 1.95) - C.headEyebrowY),
60
- titleY: C.headTitleY, ruleY: C.headRuleY, leadY: C.leadY, bodyY: C.bodyY,
61
- };
62
- }
63
-
64
- // ── annotation:so-what / 来源 / note ──
65
- if (slot === 'annotation' || slot === 'soWhat') {
66
- const y = (LC.pageTypes.exhibit && LC.pageTypes.exhibit.soWhatY != null)
67
- ? LC.pageTypes.exhibit.soWhatY : 6.05;
68
- return { x: mx, y, w: cw, h: 0.62 };
69
- }
70
- if (slot === 'footnote') {
71
- const y = (LC.pageTypes.exhibit && LC.pageTypes.exhibit.footnoteY != null)
72
- ? LC.pageTypes.exhibit.footnoteY
73
- : ((LC.pageTypes.note && LC.pageTypes.note.y) || 6.55);
74
- return { x: mx, y, w: cw - 1.2, h: (LC.pageTypes.note && LC.pageTypes.note.h) || 0.32 };
75
- }
76
-
77
- // ── 页型主从区域 ──
78
- if (type === 'points') {
79
- const band = contentBand(opts);
80
- if (slot === 'primary' || slot === 'list') {
81
- return { x: mx, y: PT.listY || band.top, w: cw, h: Math.max(0.8, band.bottom - (PT.listY || band.top)) };
82
- }
83
- if (slot === 'metrics') {
84
- return { x: mx, y: PT.metricY || 4.7, w: cw, h: Math.max(0.6, L.contentBottom - (PT.metricY || 4.7)) };
85
- }
86
- }
87
-
88
- if (type === 'metrics') {
89
- if (slot === 'primary') {
90
- return {
91
- x: mx, y: PT.bandY || 3.1, w: cw, h: PT.cardH || 2.3,
92
- valY: PT.valY, valH: PT.valH, capY: PT.capY, capH: PT.capH,
93
- gap: PT.gap, maxW: PT.maxW,
94
- };
95
- }
96
- }
97
-
98
- if (type === 'table') {
99
- if (slot === 'primary') {
100
- const y = PT.y || L.contentTop;
101
- return {
102
- x: mx, y, w: cw,
103
- h: Math.max(1, ((opts && opts.bottom) || L.contentBottom) - y),
104
- rowH: PT.rowH, rowHMin: PT.rowHMin, headRowH: PT.headRowH,
105
- };
106
- }
107
- }
108
-
109
- if (type === 'bar') {
110
- if (slot === 'primary') {
111
- const y = PT.chartY || 2.95;
112
- const bottom = (opts && opts.bottom != null) ? opts.bottom
113
- : ((opts && opts.withNote) ? L.contentBottomWithNote : L.contentBottom);
114
- return { x: mx + (PT.chartX || 0), y, w: cw - (PT.chartW || 0), h: Math.max(1.2, bottom - y) };
115
- }
116
- if (slot === 'hbar') {
117
- const y0 = PT.hbarY0 || 2.6;
118
- const y1 = (opts && opts.bottom != null) ? opts.bottom : (PT.hbarY1 || 6.25);
119
- return { x: mx, y: y0, w: cw, h: Math.max(1.2, y1 - y0) };
120
- }
121
- }
122
-
123
- if (type === 'donut') {
124
- if (slot === 'primary') {
125
- const y = PT.chartY || 2.95;
126
- const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
127
- return { x: mx, y, w: cw, h: Math.max(1.2, bottom - y) };
128
- }
129
- }
130
-
131
- if (type === 'exhibit') {
132
- if (slot === 'primary') {
133
- const y = PT.chartY || 2.95;
134
- const h = Math.max(1.2, (L.contentBottomWithNote - y));
135
- return { x: mx, y, w: cw, h, badgeY: PT.badgeY };
136
- }
137
- if (slot === 'secondary') {
138
- return { x: mx, y: PT.badgeY || L.contentTop, w: cw * 0.32, h: 0.4 };
139
- }
140
- }
141
-
142
- if (type === 'points') {
143
- const band = contentBand(opts);
144
- if (slot === 'primary' || slot === 'list') {
145
- return {
146
- x: mx, y: PT.listY || band.top, w: cw,
147
- h: Math.max(0.8, (opts && opts.bottom != null ? opts.bottom : band.bottom) - (PT.listY || band.top)),
148
- rowH: PT.rowH, rowHMin: PT.rowHMin, markSize: PT.markSize,
149
- };
150
- }
151
- if (slot === 'metrics') {
152
- return {
153
- x: mx, y: PT.metricY || 4.7, w: cw,
154
- h: Math.max(0.6, L.contentBottom - (PT.metricY || 4.7)),
155
- metricW: PT.metricW, metricValH: PT.metricValH, metricCapY: PT.metricCapY,
156
- };
157
- }
158
- }
159
-
160
- if (type === 'split') {
161
- const SP = PT;
162
- const top = SP.chartY || L.contentTop;
163
- const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
164
- if (slot === 'left') {
165
- return {
166
- x: mx, y: top, w: cw * (SP.leftW || 0.55),
167
- h: Math.max(1.2, bottom - top),
168
- tableY: SP.tableY, tableRowH: SP.tableRowH, rowH: SP.rowH, capY: SP.capY,
169
- };
170
- }
171
- if (slot === 'right') {
172
- const rx = mx + cw * (SP.rightX != null ? SP.rightX : 0.585);
173
- return {
174
- x: rx, y: top, w: cw * (SP.rightW || 0.415),
175
- h: Math.max(1.2, bottom - top),
176
- tableY: SP.tableY, tableRowH: SP.tableRowH, rowH: SP.rowH, capY: SP.capY,
177
- };
178
- }
179
- }
180
-
181
- if (type === 'twocol') {
182
- const gap = PT.colGap || 0.5;
183
- const colW = (cw - gap) / 2;
184
- const top = (opts && opts.top != null) ? opts.top : L.contentTop;
185
- const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
186
- if (slot === 'primary' || slot === 'left') {
187
- return { x: mx, y: top, w: colW, h: Math.max(1, bottom - top), gap, colW };
188
- }
189
- if (slot === 'secondary' || slot === 'right') {
190
- return { x: mx + colW + gap, y: top, w: colW, h: Math.max(1, bottom - top), gap, colW };
191
- }
192
- }
193
-
194
- if (type === 'diagram' || type === 'lane') {
195
- if (slot === 'primary') {
196
- const arch = LC.pageTypes.arch || {};
197
- const y = (opts && opts.mode === 'architecture' && arch.fullStartY != null)
198
- ? arch.fullStartY
199
- : (PT.bodyStartY || 2.9);
200
- const b = (opts && opts.mode === 'architecture' && arch.fullEndY != null)
201
- ? arch.fullEndY
202
- : ((opts && opts.bottom != null) ? opts.bottom : L.contentBottom);
203
- return {
204
- x: mx, y, w: cw, h: Math.max(1.2, b - y),
205
- layerBarW: PT.layerBarW, layerGap: PT.layerGap, maxLayerH: PT.maxLayerH,
206
- laneH: arch.laneH, laneHeadW: arch.laneHeadW, laneGap: arch.laneGap,
207
- stepGap: arch.stepGap, stepMaxW: arch.stepMaxW,
208
- };
209
- }
210
- }
211
-
212
- // ── 剩余高频页型 ──
213
- if (type === 'kpi') {
214
- if (slot === 'primary' || slot === 'hero') {
215
- return {
216
- x: mx, y: PT.heroY || 2.9, w: cw * (PT.heroW || 0.52), h: PT.heroH || 1.6,
217
- dividerX: PT.dividerX, metricY0: PT.metricY0, metricRowH: PT.metricRowH,
218
- };
219
- }
220
- if (slot === 'metrics') {
221
- const dx = PT.dividerX != null ? PT.dividerX : 0.56;
222
- return {
223
- x: mx + cw * dx + 0.35, y: PT.metricY0 || 2.95,
224
- w: cw * (1 - dx) - 0.5, h: Math.max(0.6, L.contentBottom - (PT.metricY0 || 2.95)),
225
- };
226
- }
227
- }
228
-
229
- if (type === 'comparison') {
230
- const gap = PT.panelGap || 0.4;
231
- const panelW = (cw - gap) / 2;
232
- const y = PT.panelY || 2.55;
233
- const h = PT.panelH || 3.4;
234
- if (slot === 'primary' || slot === 'left') {
235
- return { x: mx, y, w: panelW, h, titleH: PT.titleH, rowH: PT.rowH };
236
- }
237
- if (slot === 'right' || slot === 'secondary') {
238
- return { x: mx + panelW + gap, y, w: panelW, h, titleH: PT.titleH, rowH: PT.rowH };
239
- }
240
- }
241
-
242
- if (type === 'quote') {
243
- if (slot === 'primary') {
244
- return {
245
- x: mx + 1.05, y: PT.textY || 2.7, w: cw - 2.1, h: PT.textH || 2.3,
246
- markY: PT.markY, authorY: PT.authorY, contextY: PT.contextY,
247
- };
248
- }
249
- }
250
-
251
- if (type === 'heatmap') {
252
- if (slot === 'primary') {
253
- const y = PT.y || 2.6;
254
- const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
255
- return {
256
- x: mx, y, w: cw, h: Math.max(1, bottom - y),
257
- labelW: PT.labelW, headH: PT.headH, cellH: PT.cellH, cellGap: PT.cellGap,
258
- };
259
- }
260
- }
261
-
262
- if (type === 'bullet') {
263
- if (slot === 'primary') {
264
- const y = PT.y || 2.6;
265
- const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
266
- return {
267
- x: mx, y, w: cw, h: Math.max(1, bottom - y),
268
- rowH: PT.rowH, labelW: PT.labelW, valW: PT.valW, barH: PT.barH,
269
- };
270
- }
271
- }
272
-
273
- if (type === 'pyramid') {
274
- if (slot === 'primary') {
275
- const y = PT.y || 2.35;
276
- const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
277
- return {
278
- x: mx, y, w: cw, h: Math.max(1, bottom - y),
279
- rowH: PT.rowH, gap: PT.gap, minW: PT.minW, labelW: PT.labelW,
280
- };
281
- }
282
- }
283
-
284
- if (type === 'steps') {
285
- if (slot === 'primary') {
286
- const y = PT.y || 3.05;
287
- return {
288
- x: mx, y, w: cw, h: PT.barH || 1.35,
289
- gap: PT.gap, arrowW: PT.arrowW, numH: PT.numH, titleH: PT.titleH,
290
- pad: PT.pad, maxPerRow: PT.maxPerRow,
291
- };
292
- }
293
- }
294
-
295
- if (type === 'cards') {
296
- if (slot === 'primary') {
297
- const y = PT.startY || 3.0;
298
- const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
299
- return { x: mx, y, w: cw, h: Math.max(1, bottom - y), gap: PT.gap, maxH: PT.maxH, titleH: PT.titleH };
300
- }
301
- }
302
-
303
- if (type === 'timeline') {
304
- if (slot === 'primary') {
305
- return {
306
- x: mx, y: PT.labelY || 2.95, w: cw,
307
- h: Math.max(1, L.contentBottom - (PT.labelY || 2.95)),
308
- axisY: PT.axisY, dotY: PT.dotY, dotR: PT.dotR, nameY: PT.nameY,
309
- descY: PT.descY, descH: PT.descH,
310
- };
311
- }
312
- }
313
-
314
- if (type === 'image') {
315
- if (slot === 'primary') {
316
- const y = Math.max(PT.y || 2.3, (opts && opts.top) || 0);
317
- const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
318
- const capH = (opts && opts.caption) ? 0.32 : 0;
319
- return {
320
- x: mx, y, w: cw, h: Math.max(1.2, Math.min(PT.h || 4.2, bottom - y - capH)),
321
- halfW: PT.halfW, halfGap: PT.halfGap, capY: PT.capY, capH: PT.capH || 0.3,
322
- radius: PT.radius, gridGap: PT.gridGap, gridCols: PT.gridCols,
323
- };
324
- }
325
- }
326
-
327
- if (type === 'halftable') {
328
- if (slot === 'primary' || slot === 'left') {
329
- const r = LC.pageTypes.research || {};
330
- const top = (opts && opts.top != null) ? opts.top : L.contentTop;
331
- const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
332
- return { x: mx, y: top, w: cw * (r.halfTableW || 0.52), h: Math.max(1, bottom - top) };
333
- }
334
- if (slot === 'secondary' || slot === 'right') {
335
- const r = LC.pageTypes.research || {};
336
- const top = (opts && opts.top != null) ? opts.top : L.contentTop;
337
- const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
338
- return {
339
- x: mx + cw * (r.halfChartX || 0.57), y: top,
340
- w: cw * (r.halfChartW || 0.43), h: Math.max(1, bottom - top),
341
- };
342
- }
343
- }
344
-
345
- if (type === 'matrix') {
346
- if (slot === 'primary') {
347
- const r = LC.pageTypes.research || {};
348
- const y = r.matrixY || 2.3;
349
- const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
350
- return {
351
- x: mx, y, w: cw, h: Math.max(1, bottom - y),
352
- labelW: r.matrixLabelW, cellH: r.matrixCellH,
353
- };
354
- }
355
- }
356
-
357
- if (type === 'threecol') {
358
- const r = LC.pageTypes.research || {};
359
- const gap = r.col3Gap || 0.38;
360
- const colW = (cw - 2 * gap) / 3;
361
- const top = (opts && opts.top != null) ? opts.top : L.contentTop;
362
- const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
363
- if (slot === 'primary' || slot === 'left') {
364
- return { x: mx, y: top, w: colW, h: Math.max(1, bottom - top), gap, colW, cols: 3 };
365
- }
366
- if (slot === 'middle') {
367
- return { x: mx + colW + gap, y: top, w: colW, h: Math.max(1, bottom - top), gap, colW, cols: 3 };
368
- }
369
- if (slot === 'right' || slot === 'secondary') {
370
- return { x: mx + 2 * (colW + gap), y: top, w: colW, h: Math.max(1, bottom - top), gap, colW, cols: 3 };
371
- }
372
- }
373
-
374
- // 信息图页型:内容区主图
375
- if (INFO_TYPES[type] && (slot === 'primary')) {
376
- const y = PT.y || L.contentTop;
377
- const h = PT.h || 4.1;
378
- const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
379
- return { x: mx, y, w: cw, h: Math.max(1.2, Math.min(h, bottom - y)) };
380
- }
381
-
382
- // 默认:内容区全宽
383
- if (slot === 'primary' || slot === 'secondary' || slot === 'left' || slot === 'right') {
384
- const band = contentBand(opts);
385
- return { x: mx, y: band.top, w: cw, h: band.h };
386
- }
387
- return null;
388
- }
389
-
390
- /** 槽位是否在 layout_slots.json 中声明为 required */
391
- function slotRequired(pageType, slotId) {
392
- const def = (SLOTS.pageTypes || {})[pageType];
393
- if (!def) return false;
394
- const s = (def.slots || []).find(x => x.id === slotId);
395
- return !!(s && s.required);
396
- }
397
-
398
- /** 列出页型已声明槽位 id */
399
- function slotIds(pageType) {
400
- const def = (SLOTS.pageTypes || {})[pageType];
401
- return (def && def.slots) ? def.slots.map(s => s.id) : [];
402
- }
403
-
404
- module.exports = {
405
- LC,
406
- SLOTS,
407
- pageBox,
408
- contentBand,
409
- regionOf,
410
- slotRequired,
411
- slotIds,
412
- };
1
+ /**
2
+ * TopPPT HTML · 页型布局区域 IR(双通道几何收敛起点)
3
+ *
4
+ * 从 layout-constants.json + layout_slots.json 派生「语义槽位 → 英寸矩形」。
5
+ * B 通道(build_pptx.js)直接 require;A 通道消费同一 LC 常量(数字同源),
6
+ * 本模块是把「槽位语义」从散落坐标收拢成可测接口的第一步。
7
+ *
8
+ * 纪律:改几何只改 layout-constants.json;改槽位只改 layout_slots.json。
9
+ * 新增页型先扩 layout_slots.json,再在此补 regionOf 分支,最后写双引擎。
10
+ */
11
+ 'use strict';
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ const ROOT = path.join(__dirname, '..');
17
+ const LC = JSON.parse(fs.readFileSync(path.join(__dirname, 'layout-constants.json'), 'utf8'));
18
+ // 布局 IR:优先 layout-constants.layoutSlots(v9 单源合并);否则回落 layout_slots.json
19
+ let SLOTS = LC.layoutSlots;
20
+ if (!SLOTS || !SLOTS.pageTypes) {
21
+ SLOTS = JSON.parse(fs.readFileSync(path.join(__dirname, 'layout_slots.json'), 'utf8'));
22
+ }
23
+
24
+ const INFO_TYPES = {
25
+ sankey: 1, treemap: 1, boxplot: 1, network: 1, marimekko: 1, streamgraph: 1,
26
+ };
27
+
28
+ function pageBox() {
29
+ const p = LC.page;
30
+ return { pw: p.pw, ph: p.ph, mx: p.mx, cw: p.pw - 2 * p.mx };
31
+ }
32
+
33
+ /** 内容区垂直范围(含注释层预留) */
34
+ function contentBand(opts) {
35
+ const L = LC.pageTypes.layout;
36
+ const C = LC.pageTypes.common;
37
+ const withNote = !!(opts && opts.withNote);
38
+ const top = (opts && opts.top != null) ? opts.top : C.bodyY;
39
+ const bottom = withNote ? L.contentBottomWithNote : L.contentBottom;
40
+ return { top, bottom, h: Math.max(0, bottom - top) };
41
+ }
42
+
43
+ /**
44
+ * 语义槽位 → {x,y,w,h}(英寸)
45
+ * 未识别页型/槽位时返回 null(调用方回落自身逻辑,不中断)。
46
+ */
47
+ function regionOf(pageType, slotId, opts) {
48
+ const type = String(pageType || 'points');
49
+ const slot = String(slotId || '');
50
+ const { mx, cw } = pageBox();
51
+ const C = LC.pageTypes.common;
52
+ const L = LC.pageTypes.layout;
53
+ const PT = LC.pageTypes[type] || {};
54
+
55
+ // ── chrome:页头 ──
56
+ if (slot === 'head' || slot === 'chrome') {
57
+ return {
58
+ x: mx, y: C.headEyebrowY, w: cw,
59
+ h: Math.max(0.4, (C.headRuleY || 1.95) - C.headEyebrowY),
60
+ titleY: C.headTitleY, ruleY: C.headRuleY, leadY: C.leadY, bodyY: C.bodyY,
61
+ };
62
+ }
63
+
64
+ // ── annotation:so-what / 来源 / note ──
65
+ if (slot === 'annotation' || slot === 'soWhat') {
66
+ const y = (LC.pageTypes.exhibit && LC.pageTypes.exhibit.soWhatY != null)
67
+ ? LC.pageTypes.exhibit.soWhatY : 6.05;
68
+ return { x: mx, y, w: cw, h: 0.62 };
69
+ }
70
+ if (slot === 'footnote') {
71
+ const y = (LC.pageTypes.exhibit && LC.pageTypes.exhibit.footnoteY != null)
72
+ ? LC.pageTypes.exhibit.footnoteY
73
+ : ((LC.pageTypes.note && LC.pageTypes.note.y) || 6.55);
74
+ return { x: mx, y, w: cw - 1.2, h: (LC.pageTypes.note && LC.pageTypes.note.h) || 0.32 };
75
+ }
76
+
77
+ // ── 页型主从区域 ──
78
+ if (type === 'points') {
79
+ const band = contentBand(opts);
80
+ if (slot === 'primary' || slot === 'list') {
81
+ return { x: mx, y: PT.listY || band.top, w: cw, h: Math.max(0.8, band.bottom - (PT.listY || band.top)) };
82
+ }
83
+ if (slot === 'metrics') {
84
+ return { x: mx, y: PT.metricY || 4.7, w: cw, h: Math.max(0.6, L.contentBottom - (PT.metricY || 4.7)) };
85
+ }
86
+ }
87
+
88
+ if (type === 'metrics') {
89
+ if (slot === 'primary') {
90
+ return {
91
+ x: mx, y: PT.bandY || 3.1, w: cw, h: PT.cardH || 2.3,
92
+ valY: PT.valY, valH: PT.valH, capY: PT.capY, capH: PT.capH,
93
+ gap: PT.gap, maxW: PT.maxW,
94
+ };
95
+ }
96
+ }
97
+
98
+ if (type === 'table') {
99
+ if (slot === 'primary') {
100
+ const y = PT.y || L.contentTop;
101
+ return {
102
+ x: mx, y, w: cw,
103
+ h: Math.max(1, ((opts && opts.bottom) || L.contentBottom) - y),
104
+ rowH: PT.rowH, rowHMin: PT.rowHMin, headRowH: PT.headRowH,
105
+ };
106
+ }
107
+ }
108
+
109
+ if (type === 'bar') {
110
+ if (slot === 'primary') {
111
+ const y = PT.chartY || 2.95;
112
+ const bottom = (opts && opts.bottom != null) ? opts.bottom
113
+ : ((opts && opts.withNote) ? L.contentBottomWithNote : L.contentBottom);
114
+ return { x: mx + (PT.chartX || 0), y, w: cw - (PT.chartW || 0), h: Math.max(1.2, bottom - y) };
115
+ }
116
+ if (slot === 'hbar') {
117
+ const y0 = PT.hbarY0 || 2.6;
118
+ const y1 = (opts && opts.bottom != null) ? opts.bottom : (PT.hbarY1 || 6.25);
119
+ return { x: mx, y: y0, w: cw, h: Math.max(1.2, y1 - y0) };
120
+ }
121
+ }
122
+
123
+ if (type === 'donut') {
124
+ if (slot === 'primary') {
125
+ const y = PT.chartY || 2.95;
126
+ const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
127
+ return { x: mx, y, w: cw, h: Math.max(1.2, bottom - y) };
128
+ }
129
+ }
130
+
131
+ if (type === 'exhibit') {
132
+ if (slot === 'primary') {
133
+ const y = PT.chartY || 2.95;
134
+ const h = Math.max(1.2, (L.contentBottomWithNote - y));
135
+ return { x: mx, y, w: cw, h, badgeY: PT.badgeY };
136
+ }
137
+ if (slot === 'secondary') {
138
+ return { x: mx, y: PT.badgeY || L.contentTop, w: cw * 0.32, h: 0.4 };
139
+ }
140
+ }
141
+
142
+ if (type === 'points') {
143
+ const band = contentBand(opts);
144
+ if (slot === 'primary' || slot === 'list') {
145
+ return {
146
+ x: mx, y: PT.listY || band.top, w: cw,
147
+ h: Math.max(0.8, (opts && opts.bottom != null ? opts.bottom : band.bottom) - (PT.listY || band.top)),
148
+ rowH: PT.rowH, rowHMin: PT.rowHMin, markSize: PT.markSize,
149
+ };
150
+ }
151
+ if (slot === 'metrics') {
152
+ return {
153
+ x: mx, y: PT.metricY || 4.7, w: cw,
154
+ h: Math.max(0.6, L.contentBottom - (PT.metricY || 4.7)),
155
+ metricW: PT.metricW, metricValH: PT.metricValH, metricCapY: PT.metricCapY,
156
+ };
157
+ }
158
+ }
159
+
160
+ if (type === 'split') {
161
+ const SP = PT;
162
+ const top = SP.chartY || L.contentTop;
163
+ const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
164
+ if (slot === 'left') {
165
+ return {
166
+ x: mx, y: top, w: cw * (SP.leftW || 0.55),
167
+ h: Math.max(1.2, bottom - top),
168
+ tableY: SP.tableY, tableRowH: SP.tableRowH, rowH: SP.rowH, capY: SP.capY,
169
+ };
170
+ }
171
+ if (slot === 'right') {
172
+ const rx = mx + cw * (SP.rightX != null ? SP.rightX : 0.585);
173
+ return {
174
+ x: rx, y: top, w: cw * (SP.rightW || 0.415),
175
+ h: Math.max(1.2, bottom - top),
176
+ tableY: SP.tableY, tableRowH: SP.tableRowH, rowH: SP.rowH, capY: SP.capY,
177
+ };
178
+ }
179
+ }
180
+
181
+ if (type === 'twocol') {
182
+ const gap = PT.colGap || 0.5;
183
+ const colW = (cw - gap) / 2;
184
+ const top = (opts && opts.top != null) ? opts.top : L.contentTop;
185
+ const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
186
+ if (slot === 'primary' || slot === 'left') {
187
+ return { x: mx, y: top, w: colW, h: Math.max(1, bottom - top), gap, colW };
188
+ }
189
+ if (slot === 'secondary' || slot === 'right') {
190
+ return { x: mx + colW + gap, y: top, w: colW, h: Math.max(1, bottom - top), gap, colW };
191
+ }
192
+ }
193
+
194
+ if (type === 'diagram' || type === 'lane') {
195
+ if (slot === 'primary') {
196
+ const arch = LC.pageTypes.arch || {};
197
+ const y = (opts && opts.mode === 'architecture' && arch.fullStartY != null)
198
+ ? arch.fullStartY
199
+ : (PT.bodyStartY || 2.9);
200
+ const b = (opts && opts.mode === 'architecture' && arch.fullEndY != null)
201
+ ? arch.fullEndY
202
+ : ((opts && opts.bottom != null) ? opts.bottom : L.contentBottom);
203
+ return {
204
+ x: mx, y, w: cw, h: Math.max(1.2, b - y),
205
+ layerBarW: PT.layerBarW, layerGap: PT.layerGap, maxLayerH: PT.maxLayerH,
206
+ laneH: arch.laneH, laneHeadW: arch.laneHeadW, laneGap: arch.laneGap,
207
+ stepGap: arch.stepGap, stepMaxW: arch.stepMaxW,
208
+ };
209
+ }
210
+ }
211
+
212
+ // ── 剩余高频页型 ──
213
+ if (type === 'kpi') {
214
+ if (slot === 'primary' || slot === 'hero') {
215
+ return {
216
+ x: mx, y: PT.heroY || 2.9, w: cw * (PT.heroW || 0.52), h: PT.heroH || 1.6,
217
+ dividerX: PT.dividerX, metricY0: PT.metricY0, metricRowH: PT.metricRowH,
218
+ };
219
+ }
220
+ if (slot === 'metrics') {
221
+ const dx = PT.dividerX != null ? PT.dividerX : 0.56;
222
+ return {
223
+ x: mx + cw * dx + 0.35, y: PT.metricY0 || 2.95,
224
+ w: cw * (1 - dx) - 0.5, h: Math.max(0.6, L.contentBottom - (PT.metricY0 || 2.95)),
225
+ };
226
+ }
227
+ }
228
+
229
+ if (type === 'comparison') {
230
+ const gap = PT.panelGap || 0.4;
231
+ const panelW = (cw - gap) / 2;
232
+ const y = PT.panelY || 2.55;
233
+ const h = PT.panelH || 3.4;
234
+ if (slot === 'primary' || slot === 'left') {
235
+ return { x: mx, y, w: panelW, h, titleH: PT.titleH, rowH: PT.rowH };
236
+ }
237
+ if (slot === 'right' || slot === 'secondary') {
238
+ return { x: mx + panelW + gap, y, w: panelW, h, titleH: PT.titleH, rowH: PT.rowH };
239
+ }
240
+ }
241
+
242
+ if (type === 'quote') {
243
+ if (slot === 'primary') {
244
+ return {
245
+ x: mx + 1.05, y: PT.textY || 2.7, w: cw - 2.1, h: PT.textH || 2.3,
246
+ markY: PT.markY, authorY: PT.authorY, contextY: PT.contextY,
247
+ };
248
+ }
249
+ }
250
+
251
+ if (type === 'heatmap') {
252
+ if (slot === 'primary') {
253
+ const y = PT.y || 2.6;
254
+ const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
255
+ return {
256
+ x: mx, y, w: cw, h: Math.max(1, bottom - y),
257
+ labelW: PT.labelW, headH: PT.headH, cellH: PT.cellH, cellGap: PT.cellGap,
258
+ };
259
+ }
260
+ }
261
+
262
+ if (type === 'bullet') {
263
+ if (slot === 'primary') {
264
+ const y = PT.y || 2.6;
265
+ const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
266
+ return {
267
+ x: mx, y, w: cw, h: Math.max(1, bottom - y),
268
+ rowH: PT.rowH, labelW: PT.labelW, valW: PT.valW, barH: PT.barH,
269
+ };
270
+ }
271
+ }
272
+
273
+ if (type === 'pyramid') {
274
+ if (slot === 'primary') {
275
+ const y = PT.y || 2.35;
276
+ const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
277
+ return {
278
+ x: mx, y, w: cw, h: Math.max(1, bottom - y),
279
+ rowH: PT.rowH, gap: PT.gap, minW: PT.minW, labelW: PT.labelW,
280
+ };
281
+ }
282
+ }
283
+
284
+ if (type === 'steps') {
285
+ if (slot === 'primary') {
286
+ const y = PT.y || 3.05;
287
+ return {
288
+ x: mx, y, w: cw, h: PT.barH || 1.35,
289
+ gap: PT.gap, arrowW: PT.arrowW, numH: PT.numH, titleH: PT.titleH,
290
+ pad: PT.pad, maxPerRow: PT.maxPerRow,
291
+ };
292
+ }
293
+ }
294
+
295
+ if (type === 'cards') {
296
+ if (slot === 'primary') {
297
+ const y = PT.startY || 3.0;
298
+ const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
299
+ return { x: mx, y, w: cw, h: Math.max(1, bottom - y), gap: PT.gap, maxH: PT.maxH, titleH: PT.titleH };
300
+ }
301
+ }
302
+
303
+ if (type === 'timeline') {
304
+ if (slot === 'primary') {
305
+ return {
306
+ x: mx, y: PT.labelY || 2.95, w: cw,
307
+ h: Math.max(1, L.contentBottom - (PT.labelY || 2.95)),
308
+ axisY: PT.axisY, dotY: PT.dotY, dotR: PT.dotR, nameY: PT.nameY,
309
+ descY: PT.descY, descH: PT.descH,
310
+ };
311
+ }
312
+ }
313
+
314
+ if (type === 'image') {
315
+ if (slot === 'primary') {
316
+ const y = Math.max(PT.y || 2.3, (opts && opts.top) || 0);
317
+ const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
318
+ const capH = (opts && opts.caption) ? 0.32 : 0;
319
+ return {
320
+ x: mx, y, w: cw, h: Math.max(1.2, Math.min(PT.h || 4.2, bottom - y - capH)),
321
+ halfW: PT.halfW, halfGap: PT.halfGap, capY: PT.capY, capH: PT.capH || 0.3,
322
+ radius: PT.radius, gridGap: PT.gridGap, gridCols: PT.gridCols,
323
+ };
324
+ }
325
+ }
326
+
327
+ if (type === 'halftable') {
328
+ if (slot === 'primary' || slot === 'left') {
329
+ const r = LC.pageTypes.research || {};
330
+ const top = (opts && opts.top != null) ? opts.top : L.contentTop;
331
+ const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
332
+ return { x: mx, y: top, w: cw * (r.halfTableW || 0.52), h: Math.max(1, bottom - top) };
333
+ }
334
+ if (slot === 'secondary' || slot === 'right') {
335
+ const r = LC.pageTypes.research || {};
336
+ const top = (opts && opts.top != null) ? opts.top : L.contentTop;
337
+ const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
338
+ return {
339
+ x: mx + cw * (r.halfChartX || 0.57), y: top,
340
+ w: cw * (r.halfChartW || 0.43), h: Math.max(1, bottom - top),
341
+ };
342
+ }
343
+ }
344
+
345
+ if (type === 'matrix') {
346
+ if (slot === 'primary') {
347
+ const r = LC.pageTypes.research || {};
348
+ const y = r.matrixY || 2.3;
349
+ const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
350
+ return {
351
+ x: mx, y, w: cw, h: Math.max(1, bottom - y),
352
+ labelW: r.matrixLabelW, cellH: r.matrixCellH,
353
+ };
354
+ }
355
+ }
356
+
357
+ if (type === 'threecol') {
358
+ const r = LC.pageTypes.research || {};
359
+ const gap = r.col3Gap || 0.38;
360
+ const colW = (cw - 2 * gap) / 3;
361
+ const top = (opts && opts.top != null) ? opts.top : L.contentTop;
362
+ const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
363
+ if (slot === 'primary' || slot === 'left') {
364
+ return { x: mx, y: top, w: colW, h: Math.max(1, bottom - top), gap, colW, cols: 3 };
365
+ }
366
+ if (slot === 'middle') {
367
+ return { x: mx + colW + gap, y: top, w: colW, h: Math.max(1, bottom - top), gap, colW, cols: 3 };
368
+ }
369
+ if (slot === 'right' || slot === 'secondary') {
370
+ return { x: mx + 2 * (colW + gap), y: top, w: colW, h: Math.max(1, bottom - top), gap, colW, cols: 3 };
371
+ }
372
+ }
373
+
374
+ // 信息图页型:内容区主图
375
+ if (INFO_TYPES[type] && (slot === 'primary')) {
376
+ const y = PT.y || L.contentTop;
377
+ const h = PT.h || 4.1;
378
+ const bottom = (opts && opts.bottom != null) ? opts.bottom : L.contentBottom;
379
+ return { x: mx, y, w: cw, h: Math.max(1.2, Math.min(h, bottom - y)) };
380
+ }
381
+
382
+ // 默认:内容区全宽
383
+ if (slot === 'primary' || slot === 'secondary' || slot === 'left' || slot === 'right') {
384
+ const band = contentBand(opts);
385
+ return { x: mx, y: band.top, w: cw, h: band.h };
386
+ }
387
+ return null;
388
+ }
389
+
390
+ /** 槽位是否在 layout_slots.json 中声明为 required */
391
+ function slotRequired(pageType, slotId) {
392
+ const def = (SLOTS.pageTypes || {})[pageType];
393
+ if (!def) return false;
394
+ const s = (def.slots || []).find(x => x.id === slotId);
395
+ return !!(s && s.required);
396
+ }
397
+
398
+ /** 列出页型已声明槽位 id */
399
+ function slotIds(pageType) {
400
+ const def = (SLOTS.pageTypes || {})[pageType];
401
+ return (def && def.slots) ? def.slots.map(s => s.id) : [];
402
+ }
403
+
404
+ module.exports = {
405
+ LC,
406
+ SLOTS,
407
+ pageBox,
408
+ contentBand,
409
+ regionOf,
410
+ slotRequired,
411
+ slotIds,
412
+ };