@topmindspace/tms-skills 2.0.1 → 2.1.0

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 (95) hide show
  1. package/CHANGELOG.md +145 -134
  2. package/README.md +1 -1
  3. package/bin/tms-skills.js +160 -160
  4. package/package.json +48 -48
  5. package/top-ppt-html/README.md +254 -254
  6. package/top-ppt-html/SKILL.md +111 -111
  7. package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.html +3926 -3926
  8. package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.model.json +167 -167
  9. package/top-ppt-html/assets/examples/2026-09-09-architecture-spectrum.html +3926 -3926
  10. package/top-ppt-html/assets/examples/2026-09-09-architecture-spectrum.model.json +167 -167
  11. package/top-ppt-html/assets/examples/2026-09-09-presentation-apple-mono.html +4325 -4325
  12. package/top-ppt-html/assets/examples/2026-09-09-presentation-apple-mono.model.json +320 -320
  13. package/top-ppt-html/assets/examples/2026-09-09-presentation-brand-red.html +4325 -4325
  14. package/top-ppt-html/assets/examples/2026-09-09-presentation-brand-red.model.json +320 -320
  15. package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.html +4325 -4325
  16. package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.model.json +320 -320
  17. package/top-ppt-html/assets/examples/2026-09-09-research-deep-teal.html +5527 -5527
  18. package/top-ppt-html/assets/examples/2026-09-09-research-deep-teal.model.json +913 -913
  19. package/top-ppt-html/assets/examples/2026-09-09-research-indigo-violet.html +5527 -5527
  20. package/top-ppt-html/assets/examples/2026-09-09-research-indigo-violet.model.json +913 -913
  21. package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.html +5527 -5527
  22. package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.model.json +913 -913
  23. package/top-ppt-html/assets/examples/2026-09-09-research-warm-sand.html +5527 -5527
  24. package/top-ppt-html/assets/examples/2026-09-09-research-warm-sand.model.json +913 -913
  25. package/top-ppt-html/assets/pptx-export.js +1944 -1944
  26. package/top-ppt-html/assets/style-gallery.html +589 -589
  27. package/top-ppt-html/assets/templates/architecture.html +3728 -3728
  28. package/top-ppt-html/assets/templates/engine.css +840 -840
  29. package/top-ppt-html/assets/templates/presentation.html +3738 -3738
  30. package/top-ppt-html/assets/templates/research.html +4017 -4017
  31. package/top-ppt-html/assets/templates/ui.js +520 -520
  32. package/top-ppt-html/evals/prompts.csv +15 -15
  33. package/top-ppt-html/evals/rubric.schema.json +25 -25
  34. package/top-ppt-html/evals/run_evals.py +220 -220
  35. package/top-ppt-html/evals/trace.example.json +16 -16
  36. package/top-ppt-html/package-lock.json +207 -0
  37. package/top-ppt-html/package.json +35 -35
  38. package/top-ppt-html/references/charts-basic.md +624 -624
  39. package/top-ppt-html/references/charts-discipline.md +108 -108
  40. package/top-ppt-html/references/charts-extended.md +482 -482
  41. package/top-ppt-html/references/charts.md +28 -28
  42. package/top-ppt-html/references/components-atoms.md +624 -624
  43. package/top-ppt-html/references/components.md +30 -30
  44. package/top-ppt-html/references/content-rules.md +490 -490
  45. package/top-ppt-html/references/design-system-engine.md +235 -235
  46. package/top-ppt-html/references/design-system.md +478 -478
  47. package/top-ppt-html/references/failure-modes.md +214 -214
  48. package/top-ppt-html/references/high-fidelity.md +127 -127
  49. package/top-ppt-html/references/icons.md +397 -397
  50. package/top-ppt-html/references/industry-benchmark.md +105 -105
  51. package/top-ppt-html/references/infographics-stats.md +308 -308
  52. package/top-ppt-html/references/infographics-structure.md +226 -226
  53. package/top-ppt-html/references/infographics.md +43 -43
  54. package/top-ppt-html/references/layout-grammar.md +315 -315
  55. package/top-ppt-html/references/layouts-architecture.md +108 -108
  56. package/top-ppt-html/references/layouts-combo.md +600 -600
  57. package/top-ppt-html/references/layouts-research.md +160 -160
  58. package/top-ppt-html/references/modes.md +254 -254
  59. package/top-ppt-html/references/outline-design.md +275 -275
  60. package/top-ppt-html/references/playbook.md +266 -266
  61. package/top-ppt-html/references/pptx-export.md +209 -209
  62. package/top-ppt-html/references/reform-plan.md +252 -252
  63. package/top-ppt-html/references/styles.md +370 -370
  64. package/top-ppt-html/references/tech-design.md +138 -138
  65. package/top-ppt-html/scripts/audit_css.py +109 -109
  66. package/top-ppt-html/scripts/audit_docs.py +176 -176
  67. package/top-ppt-html/scripts/audit_skill.py +220 -220
  68. package/top-ppt-html/scripts/audit_styles.py +351 -351
  69. package/top-ppt-html/scripts/build_examples.py +2276 -2276
  70. package/top-ppt-html/scripts/build_pptx.js +2380 -2380
  71. package/top-ppt-html/scripts/capture_theme_overview.js +78 -78
  72. package/top-ppt-html/scripts/checks_html.py +127 -127
  73. package/top-ppt-html/scripts/cross_verify.py +294 -294
  74. package/top-ppt-html/scripts/env_probe.py +158 -158
  75. package/top-ppt-html/scripts/extract_model.py +210 -210
  76. package/top-ppt-html/scripts/extract_snippet.py +374 -374
  77. package/top-ppt-html/scripts/gen_channel_a.js +214 -214
  78. package/top-ppt-html/scripts/layout-constants.json +3377 -3377
  79. package/top-ppt-html/scripts/layout_slots.json +830 -830
  80. package/top-ppt-html/scripts/lib_layout_regions.js +412 -412
  81. package/top-ppt-html/scripts/measure_height.py +178 -178
  82. package/top-ppt-html/scripts/model-schema.json +547 -547
  83. package/top-ppt-html/scripts/negative_tests.py +307 -307
  84. package/top-ppt-html/scripts/package_skill.py +291 -291
  85. package/top-ppt-html/scripts/prepare_images.py +341 -341
  86. package/top-ppt-html/scripts/probe_image_export.py +188 -188
  87. package/top-ppt-html/scripts/quality_gate.py +301 -301
  88. package/top-ppt-html/scripts/regression.py +308 -308
  89. package/top-ppt-html/scripts/render_compare.py +275 -275
  90. package/top-ppt-html/scripts/render_from_model.py +698 -698
  91. package/top-ppt-html/scripts/scaffold_report.py +1054 -1054
  92. package/top-ppt-html/scripts/section-file-map.json +104 -104
  93. package/top-ppt-html/scripts/sync_runtime.py +662 -662
  94. package/top-ppt-html/scripts/validate_pptx.py +1510 -1510
  95. package/top-ppt-html/scripts/validate_report.py +1456 -1456
@@ -1,520 +1,520 @@
1
- /* ══ TopPPT HTML 公共 UI 脚本(assets/templates/ui.js)══
2
- 由 scripts/sync_runtime.py 注入到 templates/*.html 的 __TOPPPT_UI__ 标记块 · 禁止在各模板手改。
3
- 含:主题切换 / header 工具组(风格下拉·预览·帮助·全屏·折叠)/ 滚动显现 / PPT 翻页页码 /
4
- 图表轻量动效 / PPTX 预览模态(WYSIWYG + 可复制 AI 提示词)/
5
- 快捷键(T 主题 · P 预览 · H 帮助 · F 全屏 · B 收起/展开工具栏)。
6
- 注:三模式为生成时确定的独立形态——此处无模式切换;风格为纯视觉皮肤可实时切换。
7
- 页面不提供 PPTX 导出(浏览器端达不到 strict 硬门禁质量);正式 PPTX 由
8
- 本地智能体走精导通道生成,预览即"交付所见"(同一序列化引擎)+ 提示词引导后续补生成。
9
- 主题记忆按文件隔离(report-theme:<pathname>)且系统偏好不覆盖模板出厂
10
- data-theme;
11
- 翻页引擎:飞行期禁 scroll-snap / 连按直跳 / 滚轮打断 / 模态打开不翻页;
12
- header 全屏按钮(F)与折叠(B,收起成迷你工具条)。 */
13
-
14
- /* ── 主题切换(记忆按文件隔离;出厂 data-theme 优先于系统偏好) ── */
15
- (function(){var r=document.documentElement,b=document.getElementById('themeBtn'),
16
- K='report-theme:'+(location.pathname||'index');
17
- if(!b)return;
18
- function a(t){r.setAttribute('data-theme',t);try{localStorage.setItem(K,t)}catch(e){}
19
- /* 主题切换实时同步模型(预览与导出按当前主题出亮/暗版) */
20
- if(window.REPORT_MODEL)window.REPORT_MODEL.theme=t;}
21
- var s=null;try{s=localStorage.getItem(K)}catch(e){}
22
- if(s==='light'||s==='dark'){a(s)}
23
- else if(!r.getAttribute('data-theme')&&window.matchMedia&&
24
- window.matchMedia('(prefers-color-scheme: dark)').matches){a('dark')}
25
- b.addEventListener('click',function(){a(r.getAttribute('data-theme')==='dark'?'light':'dark')})})();
26
-
27
- /* ── header 全屏按钮(F 键同效) ── */
28
- (function(){
29
- var b=document.getElementById('fsBtn');if(!b)return;
30
- function sync(){var on=!!(document.fullscreenElement||document.webkitFullscreenElement);
31
- document.documentElement.classList.toggle('is-fs',on);
32
- b.title=(on?'退出全屏':'全屏(F)')}
33
- b.addEventListener('click',function(){
34
- var d=document,el=d.documentElement;
35
- if(d.fullscreenElement||d.webkitFullscreenElement){
36
- var ex=d.exitFullscreen||d.webkitExitFullscreen;
37
- if(ex)ex.call(d)}
38
- else{var fn=el.requestFullscreen||el.webkitRequestFullscreen;
39
- if(fn){var p=fn.call(el);if(p&&p.catch)p.catch(function(){})}}
40
- });
41
- ['fullscreenchange','webkitfullscreenchange'].forEach(function(ev){
42
- document.addEventListener(ev,sync)});
43
- sync();
44
- })();
45
-
46
- /* ── header 折叠/展开(收起成迷你工具条 · B 键同效 · 记忆按文件隔离) ── */
47
- (function(){
48
- var b=document.getElementById('barFold');if(!b)return;
49
- var K='report-bar-fold:'+(location.pathname||'index');
50
- function folded(){return document.documentElement.classList.contains('bar--fold')}
51
- function set(v){
52
- document.documentElement.classList.toggle('bar--fold',v);
53
- try{localStorage.setItem(K,v?'1':'0')}catch(e){}
54
- /* --bar-h 收紧会改变 band--fit 高度与锚点,动画结束后重算当前页 */
55
- setTimeout(function(){try{window.dispatchEvent(new Event('resize'))}catch(e){}},320);
56
- }
57
- b.addEventListener('click',function(){set(!folded())});
58
- try{if(localStorage.getItem(K)==='1')set(true)}catch(e){}
59
- })();
60
-
61
- /* ── header 工具组:风格下拉 + 帮助模态 ── */
62
- (function(){
63
- var STYLES=[
64
- ['business-blue','商务蓝','#1a73e8'],['apple-mono','优雅黑白','#1d1d1f'],
65
- ['mckinsey','麦肯锡','#003a70'],['brand-red','品牌红','#d0021b'],
66
- ['warm-sand','暖沙金','#96681f'],['deep-teal','墨绿','#0f6b5c'],
67
- ['graphite-dark','石墨','#0369a1'],['indigo-violet','靛紫','#5b5bd6'],
68
- ['spectrum','彩色','#4563ef']];
69
- var r=document.documentElement;
70
- function syncModel(){if(window.REPORT_MODEL){
71
- window.REPORT_MODEL.style=r.getAttribute('data-style')}}
72
- var seld=document.getElementById('styleSel');
73
- if(seld){
74
- var btn=document.getElementById('styleBtn'),list=document.getElementById('styleList');
75
- STYLES.forEach(function(s){
76
- var b=document.createElement('button');b.type='button';b.className='sitem';
77
- b.setAttribute('data-style',s[0]);b.title=s[1];
78
- b.innerHTML='<span class="swd" style="background:'+s[2]+'"></span>'+s[1];
79
- if(s[0]===r.getAttribute('data-style'))b.classList.add('on');
80
- b.addEventListener('click',function(){
81
- r.setAttribute('data-style',s[0]);
82
- list.querySelectorAll('.sitem').forEach(function(x){x.classList.remove('on')});
83
- b.classList.add('on');syncModel();
84
- seld.classList.remove('open')}); /* MD3 menu 惯例:选中即收起 */
85
- list.appendChild(b)});
86
- btn.addEventListener('click',function(e){e.stopPropagation();
87
- seld.classList.toggle('open')});
88
- document.addEventListener('click',function(e){
89
- if(!seld.contains(e.target))seld.classList.remove('open')});
90
- }
91
- /* 帮助模态(PPT 生成指引:双通道说明 + 可复制提示词) */
92
- var hmodal=document.getElementById('helpModal');
93
- if(hmodal){
94
- var hb=document.getElementById('helpBtn');
95
- var hclose=document.getElementById('helpClose');
96
- var hcopy=document.getElementById('helpCopy');
97
- if(hb)hb.addEventListener('click',function(){hmodal.classList.add('open')});
98
- if(hclose)hclose.addEventListener('click',function(){hmodal.classList.remove('open')});
99
- hmodal.addEventListener('click',function(e){if(e.target===hmodal)hmodal.classList.remove('open')});
100
- if(hcopy)hcopy.addEventListener('click',function(){
101
- var tx=document.getElementById('helpPrompt');
102
- var text=tx?tx.textContent:'';
103
- function done(){hcopy.textContent='已复制 ✓';
104
- setTimeout(function(){hcopy.textContent='复制 AI 提示词'},1600)}
105
- function fallback(){
106
- var ta=document.createElement('textarea');ta.value=text;
107
- document.body.appendChild(ta);ta.select();
108
- try{document.execCommand('copy');done()}catch(e){}ta.remove();}
109
- if(navigator.clipboard&&navigator.clipboard.writeText){
110
- navigator.clipboard.writeText(text).then(done,fallback)}
111
- else fallback();
112
- });
113
- }
114
- })();
115
-
116
- /* ── 滚动显现 ── */
117
- (function(){var i=document.querySelectorAll('.rv');
118
- if(!('IntersectionObserver' in window)){i.forEach(function(e){e.classList.add('in')});return}
119
- var o=new IntersectionObserver(function(es){es.forEach(function(e){
120
- if(e.isIntersecting){e.target.classList.add('in');o.unobserve(e.target)}})},
121
- {threshold:.08,rootMargin:'0px 0px -48px 0px'});i.forEach(function(e){o.observe(e)})})();
122
-
123
- /* ── PPT 翻页:方向键整屏翻页 + 页码指示
124
- 翻页要点:① 飞行期临时禁用 scroll-snap(平滑滚动与吸附互相拉扯是卡顿/回弹根因);
125
- ② 450ms 内连按直跳落位,不反复重启平滑滚动;③ 滚轮/触摸即打断飞行并恢复吸附;
126
- ④ 预览/帮助模态打开时不翻页;⑤ 空格不劫持聚焦中的按钮/链接。 ── */
127
- (function(){
128
- var pages=[].slice.call(document.querySelectorAll('section.band'));
129
- var dots=document.getElementById('pagerDots');
130
- var count=document.getElementById('pagerCount');
131
- if(!pages.length||!dots)return;
132
- var flying=false,pending=-1,lastGo=0,restoreTO=null;
133
- var dotEls=pages.map(function(sec,i){
134
- var d=document.createElement('button');
135
- d.className='pager__dot';d.type='button';
136
- d.setAttribute('aria-label','第 '+(i+1)+' 页');
137
- d.addEventListener('click',function(){go(i)});
138
- dots.appendChild(d);return d;
139
- });
140
- function modalOpen(){return !!document.querySelector('.pmodal.open')}
141
- function pageTop(i){return pages[i].getBoundingClientRect().top+window.scrollY}
142
- function idx(){
143
- var y=window.scrollY+window.innerHeight*0.4,best=0,bd=1e9;
144
- pages.forEach(function(sec,i){
145
- var d=Math.abs(pageTop(i)-y);
146
- if(d<bd){bd=d;best=i}
147
- });
148
- return best;
149
- }
150
- function mark(c){
151
- dotEls.forEach(function(d,i){d.classList.toggle('on',i===c)});
152
- if(count)count.textContent=(c+1)+' / '+pages.length;
153
- }
154
- function snap(on){document.documentElement.style.scrollSnapType=on?'':'none'}
155
- function land(){flying=false;pending=-1;snap(true);mark(idx())}
156
- function interrupt(){if(flying){flying=false;pending=-1;clearTimeout(restoreTO);snap(true)}}
157
- function go(i){
158
- i=Math.max(0,Math.min(pages.length-1,i));
159
- if(flying&&i===pending)return;
160
- var now=Date.now(),instant=(now-lastGo)<450; /* 连按直跳:防平滑滚动反复重启打架 */
161
- lastGo=now;pending=i;flying=true;
162
- mark(i);snap(false);clearTimeout(restoreTO);
163
- try{pages[i].scrollIntoView({behavior:instant?'auto':'smooth',block:'start'})}
164
- catch(e){pages[i].scrollIntoView()}
165
- restoreTO=setTimeout(land,instant?150:900);
166
- }
167
- function paint(){if(flying)return;mark(idx())}
168
- var KEY_NEXT=['ArrowRight','ArrowDown','PageDown',' '];
169
- var KEY_PREV=['ArrowLeft','ArrowUp','PageUp'];
170
- document.addEventListener('keydown',function(e){
171
- var tag=(e.target&&e.target.tagName)||'';
172
- if(/INPUT|TEXTAREA|SELECT/.test(tag))return;
173
- if(e.key===' '&&tag!=='BODY')return; /* 空格留给聚焦中的按钮/链接 */
174
- if(modalOpen())return; /* 预览/帮助模态打开时不翻页 */
175
- var base=(flying&&pending>-1)?pending:idx(); /* 飞行中按基准页继续翻,不回跳 */
176
- if(KEY_NEXT.indexOf(e.key)>-1){e.preventDefault();go(base+1)}
177
- else if(KEY_PREV.indexOf(e.key)>-1){e.preventDefault();go(base-1)}
178
- else if(e.key==='Home'){e.preventDefault();go(0)}
179
- else if(e.key==='End'){e.preventDefault();go(pages.length-1)}
180
- });
181
- /* 用户主动滚动(滚轮/触摸)→ 立即打断飞行并恢复吸附 */
182
- ['wheel','touchstart'].forEach(function(ev){
183
- window.addEventListener(ev,function(){interrupt();paint()},{passive:true});
184
- });
185
- if('onscrollend' in window)window.addEventListener('scrollend',function(){
186
- if(flying)land()});
187
- var raf=null;
188
- window.addEventListener('scroll',function(){
189
- if(raf)return;
190
- raf=requestAnimationFrame(function(){paint();raf=null});
191
- },{passive:true});
192
- window.addEventListener('resize',paint);
193
- paint();
194
- })();
195
-
196
- /* ── 图表轻量动效:进入视口触发 生长/描边/扫入/计数 ── */
197
- (function(){
198
- if(!('IntersectionObserver' in window))return;
199
- function countUp(el){var t=parseFloat(el.dataset.count),s=el.dataset.suffix||'',
200
- d=(el.dataset.count.split('.')[1]||'').length,t0=null;
201
- (function st(ts){if(!t0)t0=ts;var p=Math.min(1,(ts-t0)/900);
202
- el.textContent=(t*p).toFixed(d)+s;if(p<1)requestAnimationFrame(st)})(performance.now());}
203
- var EASE='stroke-dashoffset 1s cubic-bezier(.05,.7,.1,1)';
204
- function all(g,sel){return [].slice.call(g.querySelectorAll(sel))}
205
- var io=new IntersectionObserver(function(es){
206
- var hit=[],draws=[],sweeps=[],counts=[];
207
- es.forEach(function(e){if(!e.isIntersecting)return;
208
- var g=e.target;io.unobserve(g);hit.push(g);
209
- draws=draws.concat(all(g,'[data-draw]'));
210
- sweeps=sweeps.concat(all(g,'[data-sweep]'));
211
- counts=counts.concat(all(g,'[data-count]'));});
212
- if(!hit.length)return;
213
- /* 先集中量(读),再集中写:逐条 getTotalLength 与改样式交替会反复触发强制回流 */
214
- var lens=draws.map(function(p){return p.getTotalLength()});
215
- hit.forEach(function(g){g.classList.add('in')});
216
- draws.forEach(function(p,i){p.style.willChange='stroke-dashoffset';
217
- p.style.strokeDasharray=lens[i];p.style.strokeDashoffset=lens[i]});
218
- sweeps.forEach(function(c){var L=+c.dataset.circ;c.style.willChange='stroke-dashoffset';
219
- c.style.strokeDasharray=L;c.style.strokeDashoffset=L});
220
- counts.forEach(countUp);
221
- requestAnimationFrame(function(){
222
- draws.forEach(function(p){p.style.transition=EASE;p.style.strokeDashoffset=0});
223
- sweeps.forEach(function(c){c.style.transition=EASE;
224
- c.style.strokeDashoffset=(+c.dataset.circ)*(1-(+c.dataset.p))});
225
- });
226
- /* will-change 长期驻留会占用合成层内存,动画结束即撤下 */
227
- setTimeout(function(){draws.concat(sweeps).forEach(function(el){el.style.willChange=''})},1200);
228
- },{threshold:.3});
229
- document.querySelectorAll('.chart,[data-chart]').forEach(function(el){io.observe(el)});
230
- })();
231
-
232
- /* ── research 导航轨高亮(滚动跟随;rail 默认注释不启用,启用后自动生效) ── */
233
- (function(){
234
- var rail=document.getElementById('rail');if(!rail)return;
235
- var as=[].slice.call(rail.querySelectorAll('.rail__a'));
236
- if(!as.length)return;
237
- var raf=null,tops=[];
238
- /* 章节位置缓存:长报告里每帧对每个锚点做 querySelector + 读 offsetTop 会反复触发同步布局 */
239
- function measure(){tops=as.map(function(a){
240
- var s=document.querySelector(a.getAttribute('href'));return s?s.offsetTop:null})}
241
- function paint(){var y=window.scrollY+window.innerHeight*0.4,best=0,bd=1e9;
242
- tops.forEach(function(t,i){if(t==null)return;
243
- var d=Math.abs(t-y);if(d<bd){bd=d;best=i}});
244
- as.forEach(function(a,i){a.classList.toggle('on',i===best)});raf=null}
245
- window.addEventListener('scroll',function(){if(raf)return;
246
- raf=requestAnimationFrame(paint)},{passive:true});
247
- window.addEventListener('resize',function(){measure();paint()});
248
- measure();paint();
249
- })();
250
-
251
- /* ══ PPTX 预览 / 提示词复制 / 快捷键(预览通道 UI 层;与智能体精导同一序列化引擎)
252
- 版式/文本所见即所得;带数据的原生图表在交付 PPTX 中为真 chart part(双击可编辑数据),
253
- 预览侧为形状近似——形态以精导产物为准。 ══ */
254
- (function(){
255
- var PW=13.333,PH=7.5,EMU=914400;
256
- var modal=document.getElementById('pptModal');
257
- var body=document.getElementById('pptBody'),meta=document.getElementById('pptMeta'),
258
- issues=document.getElementById('pptIssues'),
259
- copy=document.getElementById('pptCopy'),closeBtn=document.getElementById('pptClose');
260
- var helpModal=document.getElementById('helpModal');
261
-
262
- var PROMPT=[
263
- '请为这份 HTML 报告生成可编辑 PPTX 并交付:',
264
- '1) 阅读 references/pptx-export.md 与 references/modes.md(三模式独立规则);',
265
- '2) 核对/补全 window.REPORT_MODEL 为与正文严格一致的内容(含 mode/style/theme/title/agenda/sections/closing;theme 为 light 或 dark、与页面 data-theme 一致——PPTX 按此导出亮色版或深色版;29 种页型字段与 30 类 chart.type 见 scripts/model-schema.json;research 关键图表页用 type:"exhibit" 并填 exhibitNo/soWhat/footnote);',
266
- '3) 运行 python scripts/validate_report.py <报告.html> 直到全 PASS(含模型一致性检查);',
267
- '4) 智能体精导并质检(0/0 才交付):python scripts/extract_model.py <报告.html> → NODE_PATH=<pptxgenjs 所在 node_modules> node scripts/build_pptx.js <报告.pptx> --model=<报告.model.json>(如需覆盖模型主题可加 --theme=light|dark) → python scripts/validate_pptx.py <报告.pptx> --strict --model=<报告.model.json>。',
268
- '报告文件:<请填写报告.html 的路径>'
269
- ].join('\n');
270
- var promptEl=document.getElementById('helpPrompt');
271
- if(promptEl)promptEl.textContent=PROMPT;
272
-
273
- function escHtml(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')}
274
-
275
- /* ── slide XML → DOM 缩略(与导出同一序列化输出,所见即所得) ── */
276
- function renderSlide(xml,i,model){
277
- var doc=new DOMParser().parseFromString(xml,'application/xml');
278
- var slide=document.createElement('div');slide.className='pslide';
279
- var no=document.createElement('span');no.className='pslide__no';no.textContent=(i+1);
280
- slide.appendChild(no);
281
- var tree=doc.getElementsByTagName('p:spTree')[0];
282
- if(tree){[].forEach.call(tree.children||tree.childNodes,function(n){
283
- if(n.nodeType!==1)return;
284
- if(n.tagName==='p:sp')renderSp(slide,n);
285
- else if(n.tagName==='p:graphicFrame')renderTable(slide,n);});}
286
- paintImages(slide,i,model);
287
- return slide;
288
- }
289
- /* 把模型里的素材图片填进图位块(预览保真)。
290
- 页序 = 封面 + 大纲(可选) + sections + 收尾 —— 与 slidesOf 同规则。 */
291
- function imageSrcsFor(model,idx){
292
- try{
293
- var secs=(model&&model.sections)||[];
294
- var off=1+((model&&model.agenda&&model.agenda.length)?1:0);
295
- var si=idx-off;
296
- if(si<0||si>=secs.length)return null;
297
- var sec=secs[si]||{},srcs=[],ph=false;
298
- var holders=[sec.image,sec.right&&sec.right.image];
299
- holders.forEach(function(img){
300
- if(!img||typeof img!=='object')return;
301
- if(img.placeholder){ph=true;return}
302
- if(typeof img.src==='string'&&img.src)srcs.push(img.src);
303
- (img.items||[]).forEach(function(it){
304
- if(typeof it==='string'&&it)srcs.push(it);
305
- else if(it&&typeof it.src==='string'&&it.src)srcs.push(it.src);});
306
- });
307
- return {ph:ph,srcs:srcs};
308
- }catch(e){return null}
309
- }
310
- function paintImages(slide,idx,model){
311
- var info=imageSrcsFor(model,idx);if(!info)return;
312
- var slots=[].slice.call(slide.querySelectorAll('[data-el="img"],[data-el="imgph"]'));
313
- if(info.ph&&!info.srcs.length)return; /* 纯占位:保留虚线占位块与标签 */
314
- slots.forEach(function(el,k){
315
- var src=info.srcs[k]||info.srcs[0];if(!src)return;
316
- el.style.backgroundImage='url("'+String(src).replace(/"/g,'%22')+'")';
317
- el.style.backgroundSize='cover';el.style.backgroundPosition='center';
318
- el.style.border='none';el.style.borderRadius='0.14em';
319
- });
320
- }
321
- function geomOf(scope){
322
- var xf=scope.getElementsByTagName('a:xfrm')[0];if(!xf)return null;
323
- var off=xf.getElementsByTagName('a:off')[0],ext=xf.getElementsByTagName('a:ext')[0];
324
- if(!off||!ext)return null;
325
- return{x:+off.getAttribute('x')/EMU,y:+off.getAttribute('y')/EMU,
326
- w:+ext.getAttribute('cx')/EMU,h:+ext.getAttribute('cy')/EMU};
327
- }
328
- function place(el,g){
329
- el.style.left=(g.x/PW*100)+'%';el.style.top=(g.y/PH*100)+'%';
330
- el.style.width=(g.w/PW*100)+'%';el.style.height=(g.h/PH*100)+'%';
331
- }
332
- function fillOf(node){
333
- if(!node)return null;
334
- var f=node.getElementsByTagName('a:solidFill')[0];if(!f)return null;
335
- var c=f.getElementsByTagName('a:srgbClr')[0];
336
- return c?'#'+c.getAttribute('val'):null;
337
- }
338
- function renderSp(slide,sp){
339
- var spPr=sp.getElementsByTagName('p:spPr')[0];if(!spPr)return;
340
- var g=geomOf(spPr);if(!g)return;
341
- var el=document.createElement('div');
342
- /* 图位块标记(img 真实图片 / imgph 配图占位)——预览时用真实图片填充图位,
343
- 让"有配图的页面"在预览里就是交付所见(占位块保持虚线 + 标签)。 */
344
- var nv=sp.getElementsByTagName('p:cNvPr')[0];
345
- var nm=nv?nv.getAttribute('name'):'';
346
- el.setAttribute('data-el',(nm==='img'||nm==='imgph')?nm:'');
347
- place(el,g);
348
- var fill=fillOf(spPr);
349
- if(fill)el.style.background=fill;
350
- var prst=spPr.getElementsByTagName('a:prstGeom')[0];
351
- if(prst){var t=prst.getAttribute('prst');
352
- if(t==='roundRect')el.style.borderRadius='0.14em';
353
- else if(t==='ellipse')el.style.borderRadius='50%';}
354
- var ln=spPr.getElementsByTagName('a:ln')[0];
355
- if(ln&&!fill){var lc=fillOf(ln);if(lc)el.style.border='1px solid '+lc;}
356
- var tx=sp.getElementsByTagName('p:txBody')[0];
357
- if(tx){
358
- var bodyPr=tx.getElementsByTagName('a:bodyPr')[0];
359
- var anchor=(bodyPr&&bodyPr.getAttribute('anchor'))||'t';
360
- el.style.display='flex';el.style.flexDirection='column';
361
- el.style.justifyContent=anchor==='ctr'?'center':(anchor==='b'?'flex-end':'flex-start');
362
- el.style.padding='0.06em 0.08em';el.style.boxSizing='border-box';
363
- [].forEach.call(tx.getElementsByTagName('a:p'),function(p){
364
- var pe=document.createElement('p');
365
- var pPr=p.getElementsByTagName('a:pPr')[0];
366
- var algn=pPr?pPr.getAttribute('algn'):null;
367
- if(algn==='ctr')pe.style.textAlign='center';
368
- else if(algn==='r'||algn==='just')pe.style.textAlign='right';
369
- [].forEach.call(p.getElementsByTagName('a:r'),function(r){
370
- var rPr=r.getElementsByTagName('a:rPr')[0];
371
- var run=document.createElement('span');
372
- run.style.fontSize=(((rPr&&rPr.getAttribute('sz'))||1000)/100)+'em';
373
- if(rPr&&rPr.getAttribute('b'))run.style.fontWeight='700';
374
- var col=rPr?fillOf(rPr):null;if(col)run.style.color=col;
375
- var lat=r.getElementsByTagName('a:latin')[0];
376
- if(lat)run.style.fontFamily=lat.getAttribute('typeface');
377
- var tNode=r.getElementsByTagName('a:t')[0];
378
- run.textContent=tNode?tNode.textContent:'';
379
- pe.appendChild(run);
380
- });
381
- el.appendChild(pe);
382
- });
383
- }
384
- slide.appendChild(el);
385
- }
386
- function renderTable(slide,gf){
387
- var g=geomOf(gf);if(!g)return;
388
- var el=document.createElement('div');el.setAttribute('data-el','');
389
- place(el,g);
390
- var tbl=gf.getElementsByTagName('a:tbl')[0];
391
- if(tbl){
392
- var cols=[].map.call(tbl.getElementsByTagName('a:gridCol'),function(c){return +c.getAttribute('w')});
393
- var tot=cols.reduce(function(a,b){return a+b},0)||1;
394
- var t=document.createElement('table');
395
- t.style.width='100%';t.style.height='100%';t.style.borderCollapse='collapse';
396
- [].forEach.call(tbl.getElementsByTagName('a:tr'),function(tr){
397
- var re=document.createElement('tr');
398
- var tcs=tr.getElementsByTagName('a:tc');
399
- for(var ci=0;ci<cols.length;ci++){
400
- var td=document.createElement('td');
401
- td.style.width=(cols[ci]/tot*100)+'%';td.style.padding='0.08em 0.1em';
402
- var tc=tcs[ci];
403
- if(tc){
404
- var f=fillOf(tc);if(f)td.style.background=f;
405
- var r=tc.getElementsByTagName('a:r')[0];
406
- if(r){
407
- var rPr=r.getElementsByTagName('a:rPr')[0];
408
- var run=document.createElement('span');
409
- run.style.fontSize=(((rPr&&rPr.getAttribute('sz'))||1000)/100)+'em';
410
- if(rPr&&rPr.getAttribute('b'))run.style.fontWeight='700';
411
- var col=rPr?fillOf(rPr):null;if(col)run.style.color=col;
412
- var tNode=r.getElementsByTagName('a:t')[0];
413
- run.textContent=tNode?tNode.textContent:'';
414
- td.appendChild(run);
415
- }
416
- }
417
- re.appendChild(td);
418
- }
419
- t.appendChild(re);
420
- });
421
- el.appendChild(t);
422
- }
423
- slide.appendChild(el);
424
- }
425
- function fitSlides(){
426
- [].forEach.call(body.querySelectorAll('.pslide'),function(s){
427
- s.style.fontSize=(s.clientWidth/PW/72)+'px'; /* 1em = 1pt(按当前缩略宽度) */
428
- });
429
- }
430
- function currentModel(){
431
- var m=window.REPORT_MODEL;
432
- if(m){var r=document.documentElement;
433
- m.style=r.getAttribute('data-style')||m.style;
434
- m.theme=r.getAttribute('data-theme')||m.theme||'light';}
435
- return m;
436
- }
437
-
438
- /* ── 打开模态:WYSIWYG 预览(与通道 B 同一序列化输出)+ 自检结果 + 常显可复制提示词 ── */
439
- function open(){
440
- if(!modal||!body||!meta||!issues)return; /* 预览骨架缺失时优雅降级(不影响其它快捷键) */
441
- var m=currentModel();
442
- var chk=(window.TopPptHtml&&window.TopPptHtml.validateModel)
443
- ?window.TopPptHtml.validateModel(m)
444
- :{ok:false,missing:['预览运行时缺失'],warnings:[],pages:0};
445
- meta.textContent=(m&&m.title?m.title:'未命名报告')+' · '+(m&&m.style||'business-blue')
446
- +' · '+((m&&m.theme)==='dark'?'深色':(m&&m.theme)==='light'?'浅色':'浅色')
447
- +' · '+(m&&m.mode||'presentation')+' · '+(chk.pages||0)+' 页';
448
- body.innerHTML='';
449
- if(window.TopPptHtml&&m&&m.sections){
450
- try{
451
- window.TopPptHtml.slidesXml(m).forEach(function(x,i){body.appendChild(renderSlide(x,i,m))});
452
- }catch(e){body.innerHTML='<div class="t-sm">预览渲染失败:'+escHtml(e.message)+'</div>'}
453
- }else if(!m){
454
- body.innerHTML='<div class="t-sm">报告未内嵌 window.REPORT_MODEL 内容模型,无法预览。请按下方提示词回到 AI 对话补全。</div>';
455
- }
456
- if(chk.ok){
457
- issues.innerHTML='<b>模型自检通过</b>:内容完整('+chk.pages+' 页)。版式与文本与精导一致;'
458
- +'带数据的原生图表在交付 PPTX 中为可编辑数据图表(chart part),预览为形状近似。'
459
- +(chk.warnings.length?'<br>· '+chk.warnings.map(escHtml).join('<br>· '):'')
460
- +'<br>本页面不直接导出 PPTX——复制下方提示词交给 AI,由智能体精导通道生成并过 strict 校验(0/0)后交付。'
461
- +'<div class="pmodal__prompt">'+escHtml(PROMPT)+'</div>';
462
- }else{
463
- issues.innerHTML='<b>模型不完整</b>:'
464
- +chk.missing.map(escHtml).join('、')
465
- +'<br>预览为尽力渲染;复制下方提示词回到 AI 对话补全模型并由智能体精导交付。'
466
- +'<div class="pmodal__prompt">'+escHtml(PROMPT)+'</div>';
467
- }
468
- if(copy)copy.style.display='';
469
- modal.classList.add('open');
470
- requestAnimationFrame(fitSlides);
471
- }
472
- function close(){if(modal)modal.classList.remove('open')}
473
-
474
- /* 按钮 wiring(header 预览 / 帮助) */
475
- var previewBtn=document.getElementById('pptPreviewBtn');
476
- if(previewBtn)previewBtn.addEventListener('click',open);
477
- if(closeBtn)closeBtn.addEventListener('click',close);
478
- if(modal)modal.addEventListener('click',function(e){if(e.target===modal)close()});
479
- if(copy)copy.addEventListener('click',function(){
480
- function done(){copy.textContent='已复制 ✓';
481
- setTimeout(function(){copy.textContent='复制 AI 提示词'},1600)}
482
- function fallback(){
483
- var ta=document.createElement('textarea');ta.value=PROMPT;
484
- document.body.appendChild(ta);ta.select();
485
- try{document.execCommand('copy');done()}catch(e){}ta.remove();}
486
- if(navigator.clipboard&&navigator.clipboard.writeText){
487
- navigator.clipboard.writeText(PROMPT).then(done,fallback)}
488
- else fallback();
489
- });
490
-
491
- /* 快捷键:T 主题 / F 全屏 / B 收起工具栏 / P 预览 / H 帮助 / Esc 关闭 */
492
- document.addEventListener('keydown',function(e){
493
- var tag=(e.target&&e.target.tagName)||'';
494
- if(/INPUT|TEXTAREA|SELECT/.test(tag))return;
495
- if(e.key==='Escape'){
496
- close();
497
- if(helpModal)helpModal.classList.remove('open');
498
- var seld=document.getElementById('styleSel');
499
- if(seld)seld.classList.remove('open');
500
- return;
501
- }
502
- if(e.altKey||e.ctrlKey||e.metaKey)return;
503
- /* 模态打开时屏蔽内容快捷键:预览态整页锁定;帮助态仅 Esc(上方已处理)可用 */
504
- if((modal&&modal.classList.contains('open'))||
505
- (helpModal&&helpModal.classList.contains('open')))return;
506
- var k=e.key.toLowerCase();
507
- if(k==='t'){e.preventDefault();
508
- var tb=document.getElementById('themeBtn');if(tb)tb.click()}
509
- else if(k==='f'){e.preventDefault(); /* 全屏 */
510
- var fb=document.getElementById('fsBtn');if(fb)fb.click()}
511
- else if(k==='b'){e.preventDefault(); /* 收起/展开工具栏 */
512
- var bb=document.getElementById('barFold');if(bb)bb.click()}
513
- else if(k==='p'){e.preventDefault();open()}
514
- else if(k==='h'){e.preventDefault();
515
- if(helpModal)helpModal.classList.add('open')}
516
- });
517
-
518
- window.addEventListener('resize',function(){
519
- if(modal&&modal.classList.contains('open'))fitSlides()},{passive:true});
520
- })();
1
+ /* ══ TopPPT HTML 公共 UI 脚本(assets/templates/ui.js)══
2
+ 由 scripts/sync_runtime.py 注入到 templates/*.html 的 __TOPPPT_UI__ 标记块 · 禁止在各模板手改。
3
+ 含:主题切换 / header 工具组(风格下拉·预览·帮助·全屏·折叠)/ 滚动显现 / PPT 翻页页码 /
4
+ 图表轻量动效 / PPTX 预览模态(WYSIWYG + 可复制 AI 提示词)/
5
+ 快捷键(T 主题 · P 预览 · H 帮助 · F 全屏 · B 收起/展开工具栏)。
6
+ 注:三模式为生成时确定的独立形态——此处无模式切换;风格为纯视觉皮肤可实时切换。
7
+ 页面不提供 PPTX 导出(浏览器端达不到 strict 硬门禁质量);正式 PPTX 由
8
+ 本地智能体走精导通道生成,预览即"交付所见"(同一序列化引擎)+ 提示词引导后续补生成。
9
+ 主题记忆按文件隔离(report-theme:<pathname>)且系统偏好不覆盖模板出厂
10
+ data-theme;
11
+ 翻页引擎:飞行期禁 scroll-snap / 连按直跳 / 滚轮打断 / 模态打开不翻页;
12
+ header 全屏按钮(F)与折叠(B,收起成迷你工具条)。 */
13
+
14
+ /* ── 主题切换(记忆按文件隔离;出厂 data-theme 优先于系统偏好) ── */
15
+ (function(){var r=document.documentElement,b=document.getElementById('themeBtn'),
16
+ K='report-theme:'+(location.pathname||'index');
17
+ if(!b)return;
18
+ function a(t){r.setAttribute('data-theme',t);try{localStorage.setItem(K,t)}catch(e){}
19
+ /* 主题切换实时同步模型(预览与导出按当前主题出亮/暗版) */
20
+ if(window.REPORT_MODEL)window.REPORT_MODEL.theme=t;}
21
+ var s=null;try{s=localStorage.getItem(K)}catch(e){}
22
+ if(s==='light'||s==='dark'){a(s)}
23
+ else if(!r.getAttribute('data-theme')&&window.matchMedia&&
24
+ window.matchMedia('(prefers-color-scheme: dark)').matches){a('dark')}
25
+ b.addEventListener('click',function(){a(r.getAttribute('data-theme')==='dark'?'light':'dark')})})();
26
+
27
+ /* ── header 全屏按钮(F 键同效) ── */
28
+ (function(){
29
+ var b=document.getElementById('fsBtn');if(!b)return;
30
+ function sync(){var on=!!(document.fullscreenElement||document.webkitFullscreenElement);
31
+ document.documentElement.classList.toggle('is-fs',on);
32
+ b.title=(on?'退出全屏':'全屏(F)')}
33
+ b.addEventListener('click',function(){
34
+ var d=document,el=d.documentElement;
35
+ if(d.fullscreenElement||d.webkitFullscreenElement){
36
+ var ex=d.exitFullscreen||d.webkitExitFullscreen;
37
+ if(ex)ex.call(d)}
38
+ else{var fn=el.requestFullscreen||el.webkitRequestFullscreen;
39
+ if(fn){var p=fn.call(el);if(p&&p.catch)p.catch(function(){})}}
40
+ });
41
+ ['fullscreenchange','webkitfullscreenchange'].forEach(function(ev){
42
+ document.addEventListener(ev,sync)});
43
+ sync();
44
+ })();
45
+
46
+ /* ── header 折叠/展开(收起成迷你工具条 · B 键同效 · 记忆按文件隔离) ── */
47
+ (function(){
48
+ var b=document.getElementById('barFold');if(!b)return;
49
+ var K='report-bar-fold:'+(location.pathname||'index');
50
+ function folded(){return document.documentElement.classList.contains('bar--fold')}
51
+ function set(v){
52
+ document.documentElement.classList.toggle('bar--fold',v);
53
+ try{localStorage.setItem(K,v?'1':'0')}catch(e){}
54
+ /* --bar-h 收紧会改变 band--fit 高度与锚点,动画结束后重算当前页 */
55
+ setTimeout(function(){try{window.dispatchEvent(new Event('resize'))}catch(e){}},320);
56
+ }
57
+ b.addEventListener('click',function(){set(!folded())});
58
+ try{if(localStorage.getItem(K)==='1')set(true)}catch(e){}
59
+ })();
60
+
61
+ /* ── header 工具组:风格下拉 + 帮助模态 ── */
62
+ (function(){
63
+ var STYLES=[
64
+ ['business-blue','商务蓝','#1a73e8'],['apple-mono','优雅黑白','#1d1d1f'],
65
+ ['mckinsey','麦肯锡','#003a70'],['brand-red','品牌红','#d0021b'],
66
+ ['warm-sand','暖沙金','#96681f'],['deep-teal','墨绿','#0f6b5c'],
67
+ ['graphite-dark','石墨','#0369a1'],['indigo-violet','靛紫','#5b5bd6'],
68
+ ['spectrum','彩色','#4563ef']];
69
+ var r=document.documentElement;
70
+ function syncModel(){if(window.REPORT_MODEL){
71
+ window.REPORT_MODEL.style=r.getAttribute('data-style')}}
72
+ var seld=document.getElementById('styleSel');
73
+ if(seld){
74
+ var btn=document.getElementById('styleBtn'),list=document.getElementById('styleList');
75
+ STYLES.forEach(function(s){
76
+ var b=document.createElement('button');b.type='button';b.className='sitem';
77
+ b.setAttribute('data-style',s[0]);b.title=s[1];
78
+ b.innerHTML='<span class="swd" style="background:'+s[2]+'"></span>'+s[1];
79
+ if(s[0]===r.getAttribute('data-style'))b.classList.add('on');
80
+ b.addEventListener('click',function(){
81
+ r.setAttribute('data-style',s[0]);
82
+ list.querySelectorAll('.sitem').forEach(function(x){x.classList.remove('on')});
83
+ b.classList.add('on');syncModel();
84
+ seld.classList.remove('open')}); /* MD3 menu 惯例:选中即收起 */
85
+ list.appendChild(b)});
86
+ btn.addEventListener('click',function(e){e.stopPropagation();
87
+ seld.classList.toggle('open')});
88
+ document.addEventListener('click',function(e){
89
+ if(!seld.contains(e.target))seld.classList.remove('open')});
90
+ }
91
+ /* 帮助模态(PPT 生成指引:双通道说明 + 可复制提示词) */
92
+ var hmodal=document.getElementById('helpModal');
93
+ if(hmodal){
94
+ var hb=document.getElementById('helpBtn');
95
+ var hclose=document.getElementById('helpClose');
96
+ var hcopy=document.getElementById('helpCopy');
97
+ if(hb)hb.addEventListener('click',function(){hmodal.classList.add('open')});
98
+ if(hclose)hclose.addEventListener('click',function(){hmodal.classList.remove('open')});
99
+ hmodal.addEventListener('click',function(e){if(e.target===hmodal)hmodal.classList.remove('open')});
100
+ if(hcopy)hcopy.addEventListener('click',function(){
101
+ var tx=document.getElementById('helpPrompt');
102
+ var text=tx?tx.textContent:'';
103
+ function done(){hcopy.textContent='已复制 ✓';
104
+ setTimeout(function(){hcopy.textContent='复制 AI 提示词'},1600)}
105
+ function fallback(){
106
+ var ta=document.createElement('textarea');ta.value=text;
107
+ document.body.appendChild(ta);ta.select();
108
+ try{document.execCommand('copy');done()}catch(e){}ta.remove();}
109
+ if(navigator.clipboard&&navigator.clipboard.writeText){
110
+ navigator.clipboard.writeText(text).then(done,fallback)}
111
+ else fallback();
112
+ });
113
+ }
114
+ })();
115
+
116
+ /* ── 滚动显现 ── */
117
+ (function(){var i=document.querySelectorAll('.rv');
118
+ if(!('IntersectionObserver' in window)){i.forEach(function(e){e.classList.add('in')});return}
119
+ var o=new IntersectionObserver(function(es){es.forEach(function(e){
120
+ if(e.isIntersecting){e.target.classList.add('in');o.unobserve(e.target)}})},
121
+ {threshold:.08,rootMargin:'0px 0px -48px 0px'});i.forEach(function(e){o.observe(e)})})();
122
+
123
+ /* ── PPT 翻页:方向键整屏翻页 + 页码指示
124
+ 翻页要点:① 飞行期临时禁用 scroll-snap(平滑滚动与吸附互相拉扯是卡顿/回弹根因);
125
+ ② 450ms 内连按直跳落位,不反复重启平滑滚动;③ 滚轮/触摸即打断飞行并恢复吸附;
126
+ ④ 预览/帮助模态打开时不翻页;⑤ 空格不劫持聚焦中的按钮/链接。 ── */
127
+ (function(){
128
+ var pages=[].slice.call(document.querySelectorAll('section.band'));
129
+ var dots=document.getElementById('pagerDots');
130
+ var count=document.getElementById('pagerCount');
131
+ if(!pages.length||!dots)return;
132
+ var flying=false,pending=-1,lastGo=0,restoreTO=null;
133
+ var dotEls=pages.map(function(sec,i){
134
+ var d=document.createElement('button');
135
+ d.className='pager__dot';d.type='button';
136
+ d.setAttribute('aria-label','第 '+(i+1)+' 页');
137
+ d.addEventListener('click',function(){go(i)});
138
+ dots.appendChild(d);return d;
139
+ });
140
+ function modalOpen(){return !!document.querySelector('.pmodal.open')}
141
+ function pageTop(i){return pages[i].getBoundingClientRect().top+window.scrollY}
142
+ function idx(){
143
+ var y=window.scrollY+window.innerHeight*0.4,best=0,bd=1e9;
144
+ pages.forEach(function(sec,i){
145
+ var d=Math.abs(pageTop(i)-y);
146
+ if(d<bd){bd=d;best=i}
147
+ });
148
+ return best;
149
+ }
150
+ function mark(c){
151
+ dotEls.forEach(function(d,i){d.classList.toggle('on',i===c)});
152
+ if(count)count.textContent=(c+1)+' / '+pages.length;
153
+ }
154
+ function snap(on){document.documentElement.style.scrollSnapType=on?'':'none'}
155
+ function land(){flying=false;pending=-1;snap(true);mark(idx())}
156
+ function interrupt(){if(flying){flying=false;pending=-1;clearTimeout(restoreTO);snap(true)}}
157
+ function go(i){
158
+ i=Math.max(0,Math.min(pages.length-1,i));
159
+ if(flying&&i===pending)return;
160
+ var now=Date.now(),instant=(now-lastGo)<450; /* 连按直跳:防平滑滚动反复重启打架 */
161
+ lastGo=now;pending=i;flying=true;
162
+ mark(i);snap(false);clearTimeout(restoreTO);
163
+ try{pages[i].scrollIntoView({behavior:instant?'auto':'smooth',block:'start'})}
164
+ catch(e){pages[i].scrollIntoView()}
165
+ restoreTO=setTimeout(land,instant?150:900);
166
+ }
167
+ function paint(){if(flying)return;mark(idx())}
168
+ var KEY_NEXT=['ArrowRight','ArrowDown','PageDown',' '];
169
+ var KEY_PREV=['ArrowLeft','ArrowUp','PageUp'];
170
+ document.addEventListener('keydown',function(e){
171
+ var tag=(e.target&&e.target.tagName)||'';
172
+ if(/INPUT|TEXTAREA|SELECT/.test(tag))return;
173
+ if(e.key===' '&&tag!=='BODY')return; /* 空格留给聚焦中的按钮/链接 */
174
+ if(modalOpen())return; /* 预览/帮助模态打开时不翻页 */
175
+ var base=(flying&&pending>-1)?pending:idx(); /* 飞行中按基准页继续翻,不回跳 */
176
+ if(KEY_NEXT.indexOf(e.key)>-1){e.preventDefault();go(base+1)}
177
+ else if(KEY_PREV.indexOf(e.key)>-1){e.preventDefault();go(base-1)}
178
+ else if(e.key==='Home'){e.preventDefault();go(0)}
179
+ else if(e.key==='End'){e.preventDefault();go(pages.length-1)}
180
+ });
181
+ /* 用户主动滚动(滚轮/触摸)→ 立即打断飞行并恢复吸附 */
182
+ ['wheel','touchstart'].forEach(function(ev){
183
+ window.addEventListener(ev,function(){interrupt();paint()},{passive:true});
184
+ });
185
+ if('onscrollend' in window)window.addEventListener('scrollend',function(){
186
+ if(flying)land()});
187
+ var raf=null;
188
+ window.addEventListener('scroll',function(){
189
+ if(raf)return;
190
+ raf=requestAnimationFrame(function(){paint();raf=null});
191
+ },{passive:true});
192
+ window.addEventListener('resize',paint);
193
+ paint();
194
+ })();
195
+
196
+ /* ── 图表轻量动效:进入视口触发 生长/描边/扫入/计数 ── */
197
+ (function(){
198
+ if(!('IntersectionObserver' in window))return;
199
+ function countUp(el){var t=parseFloat(el.dataset.count),s=el.dataset.suffix||'',
200
+ d=(el.dataset.count.split('.')[1]||'').length,t0=null;
201
+ (function st(ts){if(!t0)t0=ts;var p=Math.min(1,(ts-t0)/900);
202
+ el.textContent=(t*p).toFixed(d)+s;if(p<1)requestAnimationFrame(st)})(performance.now());}
203
+ var EASE='stroke-dashoffset 1s cubic-bezier(.05,.7,.1,1)';
204
+ function all(g,sel){return [].slice.call(g.querySelectorAll(sel))}
205
+ var io=new IntersectionObserver(function(es){
206
+ var hit=[],draws=[],sweeps=[],counts=[];
207
+ es.forEach(function(e){if(!e.isIntersecting)return;
208
+ var g=e.target;io.unobserve(g);hit.push(g);
209
+ draws=draws.concat(all(g,'[data-draw]'));
210
+ sweeps=sweeps.concat(all(g,'[data-sweep]'));
211
+ counts=counts.concat(all(g,'[data-count]'));});
212
+ if(!hit.length)return;
213
+ /* 先集中量(读),再集中写:逐条 getTotalLength 与改样式交替会反复触发强制回流 */
214
+ var lens=draws.map(function(p){return p.getTotalLength()});
215
+ hit.forEach(function(g){g.classList.add('in')});
216
+ draws.forEach(function(p,i){p.style.willChange='stroke-dashoffset';
217
+ p.style.strokeDasharray=lens[i];p.style.strokeDashoffset=lens[i]});
218
+ sweeps.forEach(function(c){var L=+c.dataset.circ;c.style.willChange='stroke-dashoffset';
219
+ c.style.strokeDasharray=L;c.style.strokeDashoffset=L});
220
+ counts.forEach(countUp);
221
+ requestAnimationFrame(function(){
222
+ draws.forEach(function(p){p.style.transition=EASE;p.style.strokeDashoffset=0});
223
+ sweeps.forEach(function(c){c.style.transition=EASE;
224
+ c.style.strokeDashoffset=(+c.dataset.circ)*(1-(+c.dataset.p))});
225
+ });
226
+ /* will-change 长期驻留会占用合成层内存,动画结束即撤下 */
227
+ setTimeout(function(){draws.concat(sweeps).forEach(function(el){el.style.willChange=''})},1200);
228
+ },{threshold:.3});
229
+ document.querySelectorAll('.chart,[data-chart]').forEach(function(el){io.observe(el)});
230
+ })();
231
+
232
+ /* ── research 导航轨高亮(滚动跟随;rail 默认注释不启用,启用后自动生效) ── */
233
+ (function(){
234
+ var rail=document.getElementById('rail');if(!rail)return;
235
+ var as=[].slice.call(rail.querySelectorAll('.rail__a'));
236
+ if(!as.length)return;
237
+ var raf=null,tops=[];
238
+ /* 章节位置缓存:长报告里每帧对每个锚点做 querySelector + 读 offsetTop 会反复触发同步布局 */
239
+ function measure(){tops=as.map(function(a){
240
+ var s=document.querySelector(a.getAttribute('href'));return s?s.offsetTop:null})}
241
+ function paint(){var y=window.scrollY+window.innerHeight*0.4,best=0,bd=1e9;
242
+ tops.forEach(function(t,i){if(t==null)return;
243
+ var d=Math.abs(t-y);if(d<bd){bd=d;best=i}});
244
+ as.forEach(function(a,i){a.classList.toggle('on',i===best)});raf=null}
245
+ window.addEventListener('scroll',function(){if(raf)return;
246
+ raf=requestAnimationFrame(paint)},{passive:true});
247
+ window.addEventListener('resize',function(){measure();paint()});
248
+ measure();paint();
249
+ })();
250
+
251
+ /* ══ PPTX 预览 / 提示词复制 / 快捷键(预览通道 UI 层;与智能体精导同一序列化引擎)
252
+ 版式/文本所见即所得;带数据的原生图表在交付 PPTX 中为真 chart part(双击可编辑数据),
253
+ 预览侧为形状近似——形态以精导产物为准。 ══ */
254
+ (function(){
255
+ var PW=13.333,PH=7.5,EMU=914400;
256
+ var modal=document.getElementById('pptModal');
257
+ var body=document.getElementById('pptBody'),meta=document.getElementById('pptMeta'),
258
+ issues=document.getElementById('pptIssues'),
259
+ copy=document.getElementById('pptCopy'),closeBtn=document.getElementById('pptClose');
260
+ var helpModal=document.getElementById('helpModal');
261
+
262
+ var PROMPT=[
263
+ '请为这份 HTML 报告生成可编辑 PPTX 并交付:',
264
+ '1) 阅读 references/pptx-export.md 与 references/modes.md(三模式独立规则);',
265
+ '2) 核对/补全 window.REPORT_MODEL 为与正文严格一致的内容(含 mode/style/theme/title/agenda/sections/closing;theme 为 light 或 dark、与页面 data-theme 一致——PPTX 按此导出亮色版或深色版;29 种页型字段与 30 类 chart.type 见 scripts/model-schema.json;research 关键图表页用 type:"exhibit" 并填 exhibitNo/soWhat/footnote);',
266
+ '3) 运行 python scripts/validate_report.py <报告.html> 直到全 PASS(含模型一致性检查);',
267
+ '4) 智能体精导并质检(0/0 才交付):python scripts/extract_model.py <报告.html> → NODE_PATH=<pptxgenjs 所在 node_modules> node scripts/build_pptx.js <报告.pptx> --model=<报告.model.json>(如需覆盖模型主题可加 --theme=light|dark) → python scripts/validate_pptx.py <报告.pptx> --strict --model=<报告.model.json>。',
268
+ '报告文件:<请填写报告.html 的路径>'
269
+ ].join('\n');
270
+ var promptEl=document.getElementById('helpPrompt');
271
+ if(promptEl)promptEl.textContent=PROMPT;
272
+
273
+ function escHtml(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')}
274
+
275
+ /* ── slide XML → DOM 缩略(与导出同一序列化输出,所见即所得) ── */
276
+ function renderSlide(xml,i,model){
277
+ var doc=new DOMParser().parseFromString(xml,'application/xml');
278
+ var slide=document.createElement('div');slide.className='pslide';
279
+ var no=document.createElement('span');no.className='pslide__no';no.textContent=(i+1);
280
+ slide.appendChild(no);
281
+ var tree=doc.getElementsByTagName('p:spTree')[0];
282
+ if(tree){[].forEach.call(tree.children||tree.childNodes,function(n){
283
+ if(n.nodeType!==1)return;
284
+ if(n.tagName==='p:sp')renderSp(slide,n);
285
+ else if(n.tagName==='p:graphicFrame')renderTable(slide,n);});}
286
+ paintImages(slide,i,model);
287
+ return slide;
288
+ }
289
+ /* 把模型里的素材图片填进图位块(预览保真)。
290
+ 页序 = 封面 + 大纲(可选) + sections + 收尾 —— 与 slidesOf 同规则。 */
291
+ function imageSrcsFor(model,idx){
292
+ try{
293
+ var secs=(model&&model.sections)||[];
294
+ var off=1+((model&&model.agenda&&model.agenda.length)?1:0);
295
+ var si=idx-off;
296
+ if(si<0||si>=secs.length)return null;
297
+ var sec=secs[si]||{},srcs=[],ph=false;
298
+ var holders=[sec.image,sec.right&&sec.right.image];
299
+ holders.forEach(function(img){
300
+ if(!img||typeof img!=='object')return;
301
+ if(img.placeholder){ph=true;return}
302
+ if(typeof img.src==='string'&&img.src)srcs.push(img.src);
303
+ (img.items||[]).forEach(function(it){
304
+ if(typeof it==='string'&&it)srcs.push(it);
305
+ else if(it&&typeof it.src==='string'&&it.src)srcs.push(it.src);});
306
+ });
307
+ return {ph:ph,srcs:srcs};
308
+ }catch(e){return null}
309
+ }
310
+ function paintImages(slide,idx,model){
311
+ var info=imageSrcsFor(model,idx);if(!info)return;
312
+ var slots=[].slice.call(slide.querySelectorAll('[data-el="img"],[data-el="imgph"]'));
313
+ if(info.ph&&!info.srcs.length)return; /* 纯占位:保留虚线占位块与标签 */
314
+ slots.forEach(function(el,k){
315
+ var src=info.srcs[k]||info.srcs[0];if(!src)return;
316
+ el.style.backgroundImage='url("'+String(src).replace(/"/g,'%22')+'")';
317
+ el.style.backgroundSize='cover';el.style.backgroundPosition='center';
318
+ el.style.border='none';el.style.borderRadius='0.14em';
319
+ });
320
+ }
321
+ function geomOf(scope){
322
+ var xf=scope.getElementsByTagName('a:xfrm')[0];if(!xf)return null;
323
+ var off=xf.getElementsByTagName('a:off')[0],ext=xf.getElementsByTagName('a:ext')[0];
324
+ if(!off||!ext)return null;
325
+ return{x:+off.getAttribute('x')/EMU,y:+off.getAttribute('y')/EMU,
326
+ w:+ext.getAttribute('cx')/EMU,h:+ext.getAttribute('cy')/EMU};
327
+ }
328
+ function place(el,g){
329
+ el.style.left=(g.x/PW*100)+'%';el.style.top=(g.y/PH*100)+'%';
330
+ el.style.width=(g.w/PW*100)+'%';el.style.height=(g.h/PH*100)+'%';
331
+ }
332
+ function fillOf(node){
333
+ if(!node)return null;
334
+ var f=node.getElementsByTagName('a:solidFill')[0];if(!f)return null;
335
+ var c=f.getElementsByTagName('a:srgbClr')[0];
336
+ return c?'#'+c.getAttribute('val'):null;
337
+ }
338
+ function renderSp(slide,sp){
339
+ var spPr=sp.getElementsByTagName('p:spPr')[0];if(!spPr)return;
340
+ var g=geomOf(spPr);if(!g)return;
341
+ var el=document.createElement('div');
342
+ /* 图位块标记(img 真实图片 / imgph 配图占位)——预览时用真实图片填充图位,
343
+ 让"有配图的页面"在预览里就是交付所见(占位块保持虚线 + 标签)。 */
344
+ var nv=sp.getElementsByTagName('p:cNvPr')[0];
345
+ var nm=nv?nv.getAttribute('name'):'';
346
+ el.setAttribute('data-el',(nm==='img'||nm==='imgph')?nm:'');
347
+ place(el,g);
348
+ var fill=fillOf(spPr);
349
+ if(fill)el.style.background=fill;
350
+ var prst=spPr.getElementsByTagName('a:prstGeom')[0];
351
+ if(prst){var t=prst.getAttribute('prst');
352
+ if(t==='roundRect')el.style.borderRadius='0.14em';
353
+ else if(t==='ellipse')el.style.borderRadius='50%';}
354
+ var ln=spPr.getElementsByTagName('a:ln')[0];
355
+ if(ln&&!fill){var lc=fillOf(ln);if(lc)el.style.border='1px solid '+lc;}
356
+ var tx=sp.getElementsByTagName('p:txBody')[0];
357
+ if(tx){
358
+ var bodyPr=tx.getElementsByTagName('a:bodyPr')[0];
359
+ var anchor=(bodyPr&&bodyPr.getAttribute('anchor'))||'t';
360
+ el.style.display='flex';el.style.flexDirection='column';
361
+ el.style.justifyContent=anchor==='ctr'?'center':(anchor==='b'?'flex-end':'flex-start');
362
+ el.style.padding='0.06em 0.08em';el.style.boxSizing='border-box';
363
+ [].forEach.call(tx.getElementsByTagName('a:p'),function(p){
364
+ var pe=document.createElement('p');
365
+ var pPr=p.getElementsByTagName('a:pPr')[0];
366
+ var algn=pPr?pPr.getAttribute('algn'):null;
367
+ if(algn==='ctr')pe.style.textAlign='center';
368
+ else if(algn==='r'||algn==='just')pe.style.textAlign='right';
369
+ [].forEach.call(p.getElementsByTagName('a:r'),function(r){
370
+ var rPr=r.getElementsByTagName('a:rPr')[0];
371
+ var run=document.createElement('span');
372
+ run.style.fontSize=(((rPr&&rPr.getAttribute('sz'))||1000)/100)+'em';
373
+ if(rPr&&rPr.getAttribute('b'))run.style.fontWeight='700';
374
+ var col=rPr?fillOf(rPr):null;if(col)run.style.color=col;
375
+ var lat=r.getElementsByTagName('a:latin')[0];
376
+ if(lat)run.style.fontFamily=lat.getAttribute('typeface');
377
+ var tNode=r.getElementsByTagName('a:t')[0];
378
+ run.textContent=tNode?tNode.textContent:'';
379
+ pe.appendChild(run);
380
+ });
381
+ el.appendChild(pe);
382
+ });
383
+ }
384
+ slide.appendChild(el);
385
+ }
386
+ function renderTable(slide,gf){
387
+ var g=geomOf(gf);if(!g)return;
388
+ var el=document.createElement('div');el.setAttribute('data-el','');
389
+ place(el,g);
390
+ var tbl=gf.getElementsByTagName('a:tbl')[0];
391
+ if(tbl){
392
+ var cols=[].map.call(tbl.getElementsByTagName('a:gridCol'),function(c){return +c.getAttribute('w')});
393
+ var tot=cols.reduce(function(a,b){return a+b},0)||1;
394
+ var t=document.createElement('table');
395
+ t.style.width='100%';t.style.height='100%';t.style.borderCollapse='collapse';
396
+ [].forEach.call(tbl.getElementsByTagName('a:tr'),function(tr){
397
+ var re=document.createElement('tr');
398
+ var tcs=tr.getElementsByTagName('a:tc');
399
+ for(var ci=0;ci<cols.length;ci++){
400
+ var td=document.createElement('td');
401
+ td.style.width=(cols[ci]/tot*100)+'%';td.style.padding='0.08em 0.1em';
402
+ var tc=tcs[ci];
403
+ if(tc){
404
+ var f=fillOf(tc);if(f)td.style.background=f;
405
+ var r=tc.getElementsByTagName('a:r')[0];
406
+ if(r){
407
+ var rPr=r.getElementsByTagName('a:rPr')[0];
408
+ var run=document.createElement('span');
409
+ run.style.fontSize=(((rPr&&rPr.getAttribute('sz'))||1000)/100)+'em';
410
+ if(rPr&&rPr.getAttribute('b'))run.style.fontWeight='700';
411
+ var col=rPr?fillOf(rPr):null;if(col)run.style.color=col;
412
+ var tNode=r.getElementsByTagName('a:t')[0];
413
+ run.textContent=tNode?tNode.textContent:'';
414
+ td.appendChild(run);
415
+ }
416
+ }
417
+ re.appendChild(td);
418
+ }
419
+ t.appendChild(re);
420
+ });
421
+ el.appendChild(t);
422
+ }
423
+ slide.appendChild(el);
424
+ }
425
+ function fitSlides(){
426
+ [].forEach.call(body.querySelectorAll('.pslide'),function(s){
427
+ s.style.fontSize=(s.clientWidth/PW/72)+'px'; /* 1em = 1pt(按当前缩略宽度) */
428
+ });
429
+ }
430
+ function currentModel(){
431
+ var m=window.REPORT_MODEL;
432
+ if(m){var r=document.documentElement;
433
+ m.style=r.getAttribute('data-style')||m.style;
434
+ m.theme=r.getAttribute('data-theme')||m.theme||'light';}
435
+ return m;
436
+ }
437
+
438
+ /* ── 打开模态:WYSIWYG 预览(与通道 B 同一序列化输出)+ 自检结果 + 常显可复制提示词 ── */
439
+ function open(){
440
+ if(!modal||!body||!meta||!issues)return; /* 预览骨架缺失时优雅降级(不影响其它快捷键) */
441
+ var m=currentModel();
442
+ var chk=(window.TopPptHtml&&window.TopPptHtml.validateModel)
443
+ ?window.TopPptHtml.validateModel(m)
444
+ :{ok:false,missing:['预览运行时缺失'],warnings:[],pages:0};
445
+ meta.textContent=(m&&m.title?m.title:'未命名报告')+' · '+(m&&m.style||'business-blue')
446
+ +' · '+((m&&m.theme)==='dark'?'深色':(m&&m.theme)==='light'?'浅色':'浅色')
447
+ +' · '+(m&&m.mode||'presentation')+' · '+(chk.pages||0)+' 页';
448
+ body.innerHTML='';
449
+ if(window.TopPptHtml&&m&&m.sections){
450
+ try{
451
+ window.TopPptHtml.slidesXml(m).forEach(function(x,i){body.appendChild(renderSlide(x,i,m))});
452
+ }catch(e){body.innerHTML='<div class="t-sm">预览渲染失败:'+escHtml(e.message)+'</div>'}
453
+ }else if(!m){
454
+ body.innerHTML='<div class="t-sm">报告未内嵌 window.REPORT_MODEL 内容模型,无法预览。请按下方提示词回到 AI 对话补全。</div>';
455
+ }
456
+ if(chk.ok){
457
+ issues.innerHTML='<b>模型自检通过</b>:内容完整('+chk.pages+' 页)。版式与文本与精导一致;'
458
+ +'带数据的原生图表在交付 PPTX 中为可编辑数据图表(chart part),预览为形状近似。'
459
+ +(chk.warnings.length?'<br>· '+chk.warnings.map(escHtml).join('<br>· '):'')
460
+ +'<br>本页面不直接导出 PPTX——复制下方提示词交给 AI,由智能体精导通道生成并过 strict 校验(0/0)后交付。'
461
+ +'<div class="pmodal__prompt">'+escHtml(PROMPT)+'</div>';
462
+ }else{
463
+ issues.innerHTML='<b>模型不完整</b>:'
464
+ +chk.missing.map(escHtml).join('、')
465
+ +'<br>预览为尽力渲染;复制下方提示词回到 AI 对话补全模型并由智能体精导交付。'
466
+ +'<div class="pmodal__prompt">'+escHtml(PROMPT)+'</div>';
467
+ }
468
+ if(copy)copy.style.display='';
469
+ modal.classList.add('open');
470
+ requestAnimationFrame(fitSlides);
471
+ }
472
+ function close(){if(modal)modal.classList.remove('open')}
473
+
474
+ /* 按钮 wiring(header 预览 / 帮助) */
475
+ var previewBtn=document.getElementById('pptPreviewBtn');
476
+ if(previewBtn)previewBtn.addEventListener('click',open);
477
+ if(closeBtn)closeBtn.addEventListener('click',close);
478
+ if(modal)modal.addEventListener('click',function(e){if(e.target===modal)close()});
479
+ if(copy)copy.addEventListener('click',function(){
480
+ function done(){copy.textContent='已复制 ✓';
481
+ setTimeout(function(){copy.textContent='复制 AI 提示词'},1600)}
482
+ function fallback(){
483
+ var ta=document.createElement('textarea');ta.value=PROMPT;
484
+ document.body.appendChild(ta);ta.select();
485
+ try{document.execCommand('copy');done()}catch(e){}ta.remove();}
486
+ if(navigator.clipboard&&navigator.clipboard.writeText){
487
+ navigator.clipboard.writeText(PROMPT).then(done,fallback)}
488
+ else fallback();
489
+ });
490
+
491
+ /* 快捷键:T 主题 / F 全屏 / B 收起工具栏 / P 预览 / H 帮助 / Esc 关闭 */
492
+ document.addEventListener('keydown',function(e){
493
+ var tag=(e.target&&e.target.tagName)||'';
494
+ if(/INPUT|TEXTAREA|SELECT/.test(tag))return;
495
+ if(e.key==='Escape'){
496
+ close();
497
+ if(helpModal)helpModal.classList.remove('open');
498
+ var seld=document.getElementById('styleSel');
499
+ if(seld)seld.classList.remove('open');
500
+ return;
501
+ }
502
+ if(e.altKey||e.ctrlKey||e.metaKey)return;
503
+ /* 模态打开时屏蔽内容快捷键:预览态整页锁定;帮助态仅 Esc(上方已处理)可用 */
504
+ if((modal&&modal.classList.contains('open'))||
505
+ (helpModal&&helpModal.classList.contains('open')))return;
506
+ var k=e.key.toLowerCase();
507
+ if(k==='t'){e.preventDefault();
508
+ var tb=document.getElementById('themeBtn');if(tb)tb.click()}
509
+ else if(k==='f'){e.preventDefault(); /* 全屏 */
510
+ var fb=document.getElementById('fsBtn');if(fb)fb.click()}
511
+ else if(k==='b'){e.preventDefault(); /* 收起/展开工具栏 */
512
+ var bb=document.getElementById('barFold');if(bb)bb.click()}
513
+ else if(k==='p'){e.preventDefault();open()}
514
+ else if(k==='h'){e.preventDefault();
515
+ if(helpModal)helpModal.classList.add('open')}
516
+ });
517
+
518
+ window.addEventListener('resize',function(){
519
+ if(modal&&modal.classList.contains('open'))fitSlides()},{passive:true});
520
+ })();