skill-forge-hub 0.2.0 → 0.4.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 (42) hide show
  1. package/README.md +28 -6
  2. package/annual-report/SKILL.md +24 -9
  3. package/annual-report/resources/content-rules.md +11 -9
  4. package/annual-report/resources/data-collection.md +3 -3
  5. package/annual-report/resources/date-parsing.md +1 -1
  6. package/annual-report/resources/disk-and-html.md +8 -6
  7. package/annual-report/resources/industry-dictionary.md +3 -1
  8. package/annual-report/resources/reference.html +360 -0
  9. package/annual-report/resources/usage-guide.md +56 -15
  10. package/annual-report/user-config.md +4 -1
  11. package/cli.js +176 -36
  12. package/daily-report/SKILL.md +24 -8
  13. package/daily-report/resources/content-rules.md +34 -30
  14. package/daily-report/resources/data-collection.md +5 -5
  15. package/daily-report/resources/date-parsing.md +1 -1
  16. package/daily-report/resources/disk-and-html.md +4 -2
  17. package/daily-report/resources/industry-dictionary.md +3 -1
  18. package/daily-report/resources/reference.html +617 -0
  19. package/daily-report/resources/usage-guide.md +14 -14
  20. package/daily-report/user-config.md +6 -3
  21. package/magic-deck/SKILL.md +52 -0
  22. package/magic-deck/resources/deck-spec.md +259 -0
  23. package/magic-deck/resources/reference.html +973 -0
  24. package/magic-deck/user-config.md +37 -0
  25. package/meta-forge/SKILL.md +46 -0
  26. package/meta-forge/resources/forge-rules.md +74 -0
  27. package/meta-forge/resources/usage-guide.md +41 -0
  28. package/meta-forge/user-config.md +19 -0
  29. package/package.json +3 -1
  30. package/spec-doc2spec/SKILL.md +17 -8
  31. package/spec-doc2spec/user-config.md +22 -0
  32. package/spec-spec2doc/SKILL.md +17 -8
  33. package/spec-spec2doc/user-config.md +22 -0
  34. package/weekly-report/SKILL.md +24 -8
  35. package/weekly-report/resources/content-rules.md +43 -32
  36. package/weekly-report/resources/data-collection.md +5 -5
  37. package/weekly-report/resources/date-parsing.md +1 -1
  38. package/weekly-report/resources/disk-and-html.md +4 -2
  39. package/weekly-report/resources/industry-dictionary.md +5 -3
  40. package/weekly-report/resources/reference.html +899 -0
  41. package/weekly-report/resources/usage-guide.md +15 -15
  42. package/weekly-report/user-config.md +6 -3
@@ -0,0 +1,973 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Reference 骨架 — magic-deck 定稿样例</title>
7
+ <style>
8
+ /* ══════════ 0. 基础 ══════════ */
9
+ *{ margin:0; padding:0; box-sizing:border-box; }
10
+ html,body{ width:100%; height:100%; overflow:hidden;
11
+ font-family:ui-monospace,'SF Mono',SFMono-Regular,Menlo,Consolas,'Microsoft YaHei',monospace; }
12
+ button,select,input{ font-family:inherit; }
13
+ /* ══════════ 1. 四主题 CSS 变量(8 变量齐备) ══════════ */
14
+ :root, [data-theme="tech"]{
15
+ --bg:#0a1929; --bg2:#071525; --panel:rgba(11,30,55,.72); --panel-solid:#0b1e37;
16
+ --text:#bae6fd; --muted:#5b7fa5; --accent:#0ea5e9; --accent2:#14b8a6;
17
+ --border:rgba(14,165,233,.18); --glow:rgba(14,165,233,.35);
18
+ --title-grad:linear-gradient(90deg,#7dd3fc,#0ea5e9 45%,#818cf8);
19
+ --tag-bg:rgba(14,165,233,.12);
20
+ }
21
+ [data-theme="biz"]{
22
+ --bg:#f4f6fb; --bg2:#e9edf5; --panel:rgba(255,255,255,.85); --panel-solid:#ffffff;
23
+ --text:#1e293b; --muted:#64748b; --accent:#2563eb; --accent2:#0891b2;
24
+ --border:rgba(37,99,235,.14); --glow:rgba(37,99,235,.18);
25
+ --title-grad:linear-gradient(90deg,#1e40af,#2563eb 55%,#0891b2);
26
+ --tag-bg:rgba(37,99,235,.08);
27
+ }
28
+ [data-theme="mono"]{
29
+ --bg:#fafafa; --bg2:#f0f0f0; --panel:rgba(255,255,255,.9); --panel-solid:#ffffff;
30
+ --text:#16181d; --muted:#6b7280; --accent:#111111; --accent2:#4b5563;
31
+ --border:rgba(17,17,17,.14); --glow:rgba(17,17,17,.08);
32
+ --title-grad:linear-gradient(90deg,#111,#333 60%,#666);
33
+ --tag-bg:rgba(17,17,17,.06);
34
+ }
35
+ [data-theme="warm"]{
36
+ --bg:#fdf3e7; --bg2:#f7e7d3; --panel:rgba(255,250,243,.85); --panel-solid:#fffaf3;
37
+ --text:#43301f; --muted:#8a7565; --accent:#d97706; --accent2:#ea580c;
38
+ --border:rgba(217,119,6,.18); --glow:rgba(217,119,6,.22);
39
+ --title-grad:linear-gradient(90deg,#b45309,#d97706 50%,#ea580c);
40
+ --tag-bg:rgba(217,119,6,.1);
41
+ }
42
+ /* ══════════ 2. 背景装饰(舞台外围氛围层) ══════════ */
43
+ .bg-glow{ position:fixed; inset:0; z-index:0;
44
+ background:
45
+ radial-gradient(60% 50% at 50% 0%, var(--glow) 0%, transparent 100%),
46
+ radial-gradient(40% 35% at 100% 100%, var(--glow) 0%, transparent 100%),
47
+ var(--bg); }
48
+ .bg-grid{ position:fixed; inset:0; z-index:0; pointer-events:none;
49
+ background-image:linear-gradient(var(--border) 1px,transparent 1px),linear-gradient(90deg,var(--border) 1px,transparent 1px);
50
+ background-size:72px 72px;
51
+ -webkit-mask-image:radial-gradient(circle at center, #000 0%, transparent 78%);
52
+ mask-image:radial-gradient(circle at center, #000 0%, transparent 78%); }
53
+ /* ══════════ 3. 舞台与切换 ══════════ */
54
+ .stage{ position:fixed; inset:0; z-index:1; display:flex; align-items:center; justify-content:center; }
55
+ .deck{ position:relative; width:1280px; height:720px; transform:scale(var(--sc,1)); transform-origin:center center; flex:0 0 auto; }
56
+ .slide{ position:absolute; inset:0; padding:64px 80px; background:var(--bg); display:flex; flex-direction:column;
57
+ opacity:0; visibility:hidden; transform:translateX(36px);
58
+ transition:opacity .45s ease, transform .45s ease, visibility 0s linear .45s; }
59
+ .slide.active{ opacity:1; visibility:visible; transform:translateX(0); transition:opacity .45s ease, transform .45s ease; }
60
+ .slide.dim{ opacity:0; transform:translateX(-36px); }
61
+ /* ══════════ 4. 版式组件(主题变量驱动) ══════════ */
62
+ .s-title{ font-size:40px; font-weight:900; line-height:1.25;
63
+ background:var(--title-grad); -webkit-background-clip:text; background-clip:text;
64
+ color:transparent; text-shadow:var(--glow) 0 0 18px; }
65
+ .s-head{ display:flex; align-items:center; gap:18px; margin-bottom:34px; }
66
+ .s-head::before{ content:''; width:6px; height:34px; border-radius:3px;
67
+ background:linear-gradient(180deg,var(--accent),var(--accent2)); box-shadow:0 0 12px var(--glow); }
68
+ .s-head h2{ font-size:32px; font-weight:800; color:var(--text); }
69
+ .tag{ flex:0 0 auto; margin-left:auto; font-size:13px; padding:5px 14px; border-radius:999px;
70
+ color:var(--accent); background:var(--tag-bg); border:1px solid var(--border); }
71
+ .panel{ background:var(--panel); border:1px solid var(--border); border-radius:14px;
72
+ padding:22px 26px; backdrop-filter:blur(6px); }
73
+ .grid2{ display:grid; grid-template-columns:1fr 1fr; gap:22px; }
74
+ .grid3{ display:grid; grid-template-columns:1fr 1fr 1fr; gap:22px; }
75
+ .grid4{ display:grid; grid-template-columns:repeat(4,1fr); gap:18px; }
76
+ .card{ background:var(--panel); border:1px solid var(--border); border-radius:12px; padding:20px 22px;
77
+ transition:transform .2s ease, box-shadow .2s ease; }
78
+ .card:hover{ transform:translateY(-4px); box-shadow:0 8px 24px var(--glow); }
79
+ .card h4{ font-size:18px; font-weight:700; color:var(--text); margin-bottom:8px; }
80
+ .card h4 .en{ font-size:12px; color:var(--muted); margin-left:8px; font-weight:400; }
81
+ .card p{ font-size:14px; color:var(--muted); line-height:1.7; }
82
+ .bignum{ background:var(--panel); border:1px solid var(--border); border-radius:12px; padding:18px 20px; text-align:center; }
83
+ .bignum b{ display:block; font-size:52px; font-weight:900; background:var(--title-grad);
84
+ -webkit-background-clip:text; background-clip:text; color:transparent; }
85
+ .bignum span{ font-size:13px; color:var(--muted); }
86
+ .toc{ display:flex; flex-direction:column; gap:14px; }
87
+ .toc-item{ display:flex; align-items:center; gap:16px; padding:16px 22px; border-radius:12px;
88
+ background:var(--panel); border:1px solid var(--border); cursor:pointer;
89
+ transition:transform .2s ease; }
90
+ .toc-item:hover{ transform:translateX(8px); }
91
+ .toc-item .no{ width:34px; height:34px; border-radius:8px; display:flex; align-items:center; justify-content:center;
92
+ font-weight:800; color:var(--accent); background:var(--tag-bg); }
93
+ .toc-item .t{ font-size:18px; font-weight:600; color:var(--text); }
94
+ .toc-item .pg{ margin-left:auto; font-size:13px; color:var(--muted); }
95
+ .checklist{ list-style:none; display:flex; flex-direction:column; gap:12px; }
96
+ .checklist li{ position:relative; padding-left:34px; font-size:16px; color:var(--text); line-height:1.6; }
97
+ .checklist li::before{ content:'✓'; position:absolute; left:0; top:2px; width:22px; height:22px;
98
+ display:flex; align-items:center; justify-content:center; font-size:13px; color:#fff; border-radius:6px;
99
+ background:linear-gradient(135deg,var(--accent),var(--accent2)); }
100
+ .theme-card{ display:flex; align-items:center; gap:16px; padding:18px 22px; border-radius:12px;
101
+ background:var(--panel); border:1px solid var(--border); cursor:pointer; }
102
+ .theme-card .sw{ display:flex; gap:6px; }
103
+ .theme-card .sw i{ width:20px; height:20px; border-radius:50%; border:2px solid rgba(255,255,255,.3); }
104
+ .chip{ display:inline-block; font-size:12px; padding:3px 10px; border-radius:999px;
105
+ color:var(--accent); background:var(--tag-bg); border:1px solid var(--border); }
106
+ .cover{ justify-content:center; align-items:flex-start; }
107
+ .logo{ width:76px; height:76px; border-radius:18px; margin-bottom:30px;
108
+ background:linear-gradient(135deg,var(--accent),var(--accent2)); box-shadow:0 0 28px var(--glow); }
109
+ .cover .s-title{ font-size:64px; }
110
+ .sub{ font-size:20px; color:var(--muted); margin-top:22px; letter-spacing:.5px; }
111
+ .sign{ margin-top:auto; font-size:12px; color:var(--muted); letter-spacing:2px; }
112
+ .endpage{ justify-content:center; align-items:center; text-align:center; }
113
+ .endpage .s-title{ font-size:56px; }
114
+ .endpage p{ margin-top:20px; font-size:16px; color:var(--muted); }
115
+ /* ══════════ 5. 五模板运行时骨架(data-template 驱动;模板管版式、主题管配色) ══════════ */
116
+ /* 应用模板 = slide 挂 data-template + 结构包装器重组语义节点;标题自动映射到模板固定位置 */
117
+ /* T1 classic 经典商务:默认骨架,无前缀规则 */
118
+ /* T2 minimal 极简留白:去色条居中大标题、无边框卡片、窄栏要点 */
119
+ [data-template="minimal"] .s-head::before{ display:none; }
120
+ [data-template="minimal"] .s-head{ margin-bottom:28px; }
121
+ [data-template="minimal"] .s-head h2{ width:100%; font-size:44px; font-weight:900; text-align:center;
122
+ background:var(--title-grad); -webkit-background-clip:text; background-clip:text; color:transparent; }
123
+ [data-template="minimal"] .s-head .tag{ display:none; }
124
+ [data-template="minimal"] .s-title{ font-size:44px; text-align:center; }
125
+ [data-template="minimal"] .points{ margin:0 auto; max-width:640px; list-style:none; }
126
+ [data-template="minimal"] .points li{ padding:12px 0; border-bottom:1px solid var(--border);
127
+ font-size:20px; line-height:1.9; color:var(--text); }
128
+ [data-template="minimal"] .card{ background:transparent; border:none; padding:10px 0; box-shadow:none; }
129
+ [data-template="minimal"] .card:hover{ transform:none; box-shadow:none; }
130
+ [data-template="minimal"] .card h4{ font-size:20px; }
131
+ [data-template="minimal"] .bignum{ background:transparent; border:none; }
132
+ [data-template="minimal"] .bignum b{ font-size:88px; }
133
+ [data-template="minimal"] .cover .logo{ display:none; }
134
+ [data-template="minimal"] .cover{ align-items:center; text-align:center; }
135
+ [data-template="minimal"] .cover .sub{ border-top:1px solid var(--border); padding-top:22px; margin-top:30px; }
136
+ [data-template="minimal"] .toc-item{ background:transparent; border:none; border-bottom:1px solid var(--border); border-radius:0; }
137
+ /* T3 magazine 杂志画册:超大标题跨版 + 左文右图分栏(包装器生成)+ 横向大数字带 */
138
+ [data-template="magazine"] .s-title{ font-size:96px; line-height:1.05; }
139
+ [data-template="magazine"] .s-head h2{ font-size:56px; }
140
+ [data-template="magazine"] .card{ border-radius:18px; padding:28px 30px; }
141
+ [data-template="magazine"] .card h4{ font-size:22px; }
142
+ [data-template="magazine"] .card p{ font-size:15px; }
143
+ [data-template="magazine"] .bignum{ background:transparent; border:none; }
144
+ [data-template="magazine"] .bignum b{ font-size:88px; }
145
+ [data-template="magazine"] .cover .s-title{ font-size:96px; }
146
+ .m-split{ display:grid; grid-template-columns:46% 54%; gap:56px; align-items:center; }
147
+ .m-split h3{ font-size:36px; font-weight:800; color:var(--text); margin-bottom:18px; }
148
+ .m-split p{ font-size:16px; color:var(--muted); line-height:1.9; }
149
+ .m-figure{ height:380px; border-radius:18px; display:flex; flex-wrap:wrap; align-items:flex-end;
150
+ align-content:flex-end; gap:10px; padding:20px;
151
+ background:linear-gradient(135deg,var(--accent),var(--accent2)); opacity:.85; }
152
+ .m-strip{ display:flex; justify-content:space-between; margin-top:40px; }
153
+ .m-strip>div{ flex:1; text-align:center; border-left:1px solid var(--border); }
154
+ .m-strip>div:first-child{ border-left:0; }
155
+ .m-strip b{ display:block; font-size:64px; font-weight:900; background:var(--title-grad);
156
+ -webkit-background-clip:text; background-clip:text; color:transparent; }
157
+ .m-strip span{ font-size:14px; color:var(--muted); }
158
+ /* T4 keynote 黑金发布:每页单点大字(包装器生成)+ 环形进度 + 余量小字 */
159
+ [data-template="keynote"] .s-title{ font-size:112px; text-align:center; }
160
+ [data-template="keynote"] .cover{ align-items:center; text-align:center; }
161
+ [data-template="keynote"] .endpage .s-title{ font-size:96px; }
162
+ .k-big{ font-size:64px; font-weight:900; text-align:center; color:var(--text); margin:auto; }
163
+ .k-sub{ font-size:22px; color:var(--muted); text-align:center; margin-top:18px; }
164
+ .k-bar{ width:320px; height:4px; border-radius:2px; margin:40px auto 0;
165
+ background:linear-gradient(90deg,var(--accent),var(--accent2)); }
166
+ .k-ring{ width:120px; height:120px; border-radius:50%; margin:14px auto 0;
167
+ background:conic-gradient(var(--accent) 0 72%, var(--border) 72% 100%);
168
+ display:flex; align-items:center; justify-content:center; }
169
+ .k-ring b{ font-size:34px; color:var(--text); }
170
+ .k-note{ margin:30px auto 0; max-width:560px; text-align:center; font-size:13px; color:var(--muted); line-height:1.9; }
171
+ /* T5 timeline 时间线叙事(结构由包装器生成:卡片标题→节点标题自动映射) */
172
+ .tl{ position:relative; padding-left:32px; }
173
+ .tl::before{ content:''; position:absolute; left:6px; top:8px; bottom:8px; width:3px; background:var(--accent); }
174
+ .tl-item{ position:relative; padding:18px 0 18px 24px; }
175
+ .tl-item::before{ content:''; position:absolute; left:-32px; top:24px; width:14px; height:14px;
176
+ border-radius:50%; background:var(--accent); box-shadow:0 0 0 4px var(--tag-bg); }
177
+ .tl-time{ font-size:24px; font-weight:800; color:var(--accent); }
178
+ .tl-item h4{ font-size:18px; font-weight:700; color:var(--text); margin:6px 0; }
179
+ .tl-item p{ font-size:14px; color:var(--muted); line-height:1.7; }
180
+ /* ══════════ 6. 操作元素(固定样式) ══════════ */
181
+ .toolbar{ position:fixed; right:18px; top:14px; z-index:10;
182
+ display:flex; align-items:center; gap:10px; }
183
+ .group{ display:flex; align-items:center; gap:8px; padding:4px 6px; border-radius:999px;
184
+ background:var(--panel-solid); border:1px solid var(--border); box-shadow:0 4px 16px rgba(0,0,0,.18); }
185
+ .tbtn{ display:inline-flex; align-items:center; justify-content:center; gap:6px;
186
+ padding:7px 12px; line-height:1; font-size:13px; color:var(--text);
187
+ background:transparent; border:1px solid transparent; border-radius:999px; cursor:pointer;
188
+ white-space:nowrap; transition:all .15s ease; }
189
+ .tbtn:hover{ border-color:var(--border); background:var(--tag-bg); }
190
+ .tbtn.on{ color:#fff; background:var(--accent); border-color:var(--accent); }
191
+ .tbtn.theme{ width:16px; height:16px; padding:0; border-radius:50%; border:2px solid rgba(255,255,255,.35); }
192
+ .tbtn.theme.on{ outline:2px solid var(--accent); outline-offset:2px; }
193
+ .tbtn.theme[data-theme="tech"]{ background:#0ea5e9; }
194
+ .tbtn.theme[data-theme="biz"]{ background:#2563eb; }
195
+ .tbtn.theme[data-theme="mono"]{ background:#111; }
196
+ .tbtn.theme[data-theme="warm"]{ background:#d97706; }
197
+ select.tbtn{ appearance:auto; padding:7px 10px; }
198
+ label.tbtn{ cursor:pointer; }
199
+ label.tbtn input[type=checkbox]{ width:14px; height:14px; margin:0; flex:0 0 auto; accent-color:var(--accent); }
200
+ .pageno{ position:fixed; left:18px; bottom:16px; z-index:10; padding:6px 14px; border-radius:999px;
201
+ font-size:13px; color:var(--muted); background:var(--panel-solid); border:1px solid var(--border); }
202
+ .pageno b{ color:var(--accent); }
203
+ .nav{ position:fixed; top:50%; transform:translateY(-50%); z-index:10; width:46px; height:46px;
204
+ border-radius:50%; font-size:22px; color:var(--text); background:var(--panel-solid);
205
+ border:1px solid var(--border); cursor:pointer; opacity:0; transition:all .2s ease; }
206
+ .nav.prev{ left:18px; } .nav.next{ right:18px; }
207
+ body:hover .nav{ opacity:1; }
208
+ .nav:hover{ border-color:var(--accent); color:var(--accent); box-shadow:0 0 14px var(--glow); }
209
+ @media (hover:none){ .nav{ opacity:1; } }
210
+ .hint{ position:fixed; bottom:16px; left:50%; transform:translateX(-50%); z-index:10;
211
+ font-size:11px; color:var(--muted); letter-spacing:.5px; }
212
+ .help-mask{ position:fixed; inset:0; z-index:20; display:none; align-items:center; justify-content:center;
213
+ background:rgba(0,0,0,.55); backdrop-filter:blur(4px); }
214
+ .help-mask.open{ display:flex; }
215
+ .help-panel{ width:560px; max-height:70vh; overflow:auto; padding:30px 34px; border-radius:16px;
216
+ background:var(--panel-solid); border:1px solid var(--border); color:var(--text); }
217
+ .help-panel h3{ font-size:20px; margin-bottom:16px; }
218
+ .help-panel table{ width:100%; border-collapse:collapse; font-size:13px; }
219
+ .help-panel td{ padding:6px 8px; border-bottom:1px solid var(--border); }
220
+ .help-panel kbd{ padding:2px 8px; border-radius:5px; background:var(--tag-bg);
221
+ border:1px solid var(--border); font-family:inherit; }
222
+ .help-note{ margin-top:14px; font-size:12px; color:var(--muted); line-height:1.8; }
223
+ /* ═══ 选页复制面板 ═══ */
224
+ .copy-panel{ width:430px; max-height:72vh; display:flex; flex-direction:column; padding:24px 26px;
225
+ border-radius:16px; background:var(--panel-solid); border:1px solid var(--border); color:var(--text); }
226
+ .copy-panel h3{ font-size:18px; margin:0; }
227
+ .cp-head{ display:flex; align-items:center; justify-content:space-between; margin-bottom:14px; }
228
+ .cp-all{ display:inline-flex; align-items:center; gap:6px; font-size:13px; cursor:pointer; }
229
+ .cp-list{ flex:1; overflow-y:auto; border:1px solid var(--border); border-radius:10px; }
230
+ .cp-item{ display:flex; align-items:center; gap:10px; padding:9px 12px; border-bottom:1px solid var(--border);
231
+ cursor:pointer; font-size:13px; }
232
+ .cp-item:last-child{ border-bottom:none; }
233
+ .cp-item:hover{ background:var(--tag-bg); }
234
+ .cp-item input{ margin:0; flex:0 0 auto; }
235
+ .cp-no{ flex-shrink:0; color:var(--accent); font-weight:600; }
236
+ .cp-preview{ flex:1; min-width:0; color:var(--muted); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
237
+ .cp-foot{ display:flex; justify-content:flex-end; gap:10px; margin-top:14px; }
238
+ .cp-foot .tbtn{ border:1px solid var(--border); background:var(--tag-bg); cursor:pointer; }
239
+ .cp-foot .tbtn.primary{ background:var(--accent); color:var(--bg); border-color:transparent; }
240
+ /* ═══ 模板应用面板 ═══ */
241
+ .tpl-panel{ width:520px; max-height:78vh; display:flex; flex-direction:column; padding:24px 26px;
242
+ border-radius:16px; background:var(--panel-solid); border:1px solid var(--border); color:var(--text); }
243
+ .tpl-panel h3{ font-size:18px; margin:0; }
244
+ .tpl-head-note{ font-size:12px; color:var(--muted); }
245
+ .tpl-cards{ display:grid; grid-template-columns:repeat(2,1fr); gap:10px; margin:14px 0 12px; }
246
+ .tpl-card{ display:flex; align-items:center; gap:12px; padding:12px 14px; border-radius:12px;
247
+ background:var(--panel); border:1px solid var(--border); cursor:pointer; transition:all .15s ease; text-align:left; }
248
+ .tpl-card:hover{ border-color:var(--accent); }
249
+ .tpl-card.on{ border-color:var(--accent); box-shadow:0 0 0 2px var(--tag-bg), 0 0 16px var(--glow); }
250
+ .tpl-card .ic{ width:34px; height:34px; border-radius:9px; flex:0 0 auto; display:flex; align-items:center;
251
+ justify-content:center; font-size:16px; color:#fff; background:linear-gradient(135deg,var(--accent),var(--accent2)); }
252
+ .tpl-card .nm{ display:block; font-size:14px; font-weight:700; }
253
+ .tpl-card .ds{ display:block; font-size:11px; color:var(--muted); margin-top:2px; line-height:1.5; }
254
+ .tpl-scope{ display:flex; gap:8px; margin:2px 0 12px; flex-wrap:wrap; }
255
+ .tpl-scope label{ display:inline-flex; align-items:center; gap:6px; font-size:13px; cursor:pointer; padding:6px 12px;
256
+ border-radius:999px; border:1px solid var(--border); color:var(--muted); }
257
+ .tpl-scope label input{ margin:0; accent-color:var(--accent); }
258
+ .tpl-scope label.sel{ color:var(--accent); border-color:var(--accent); background:var(--tag-bg); }
259
+ #tplList{ display:none; }
260
+ #tplList.show{ display:block; }
261
+ /* ══════════ 7. 全屏放映纯净模式(PPT 放映规范:黑底 + 幻灯充满画面) ══════════ */
262
+ :root:fullscreen{ background:#000; } /* 放映黑底:与 PPT 软件放映一致,比例不匹配处补黑边,不带主题氛围边框 */
263
+ :root:fullscreen .bg-glow,
264
+ :root:fullscreen .bg-grid{ display:none !important; } /* 氛围层只在窗口浏览态,放映时退场 */
265
+ :root:fullscreen .toolbar,
266
+ :root:fullscreen .pageno,
267
+ :root:fullscreen .hint,
268
+ :root:fullscreen .nav,
269
+ :root:fullscreen .help-mask{ display:none !important; }
270
+ </style>
271
+ </head>
272
+ <body>
273
+ <div class="bg-glow"></div>
274
+ <div class="bg-grid"></div>
275
+ <div class="progress" style="position:fixed;top:0;left:0;right:0;height:3px;z-index:10;background:transparent;">
276
+ <div class="fill" id="progFill" style="height:100%;width:0;background:linear-gradient(90deg,var(--accent),var(--accent2));transition:width .3s ease;"></div>
277
+ </div>
278
+ <div class="toolbar">
279
+ <div class="group">
280
+ <button class="tbtn" id="btnTemplate" title="五套版式模板:可应用到全部页面 / 仅本页 / 勾选页面,标题自动映射">🎨 模板</button>
281
+ </div>
282
+ <div class="group" title="主题:点色点整份换肤,点「仅本页」后只改当前页">
283
+ <button class="tbtn theme" data-theme="tech" title="tech 深蓝科技"></button>
284
+ <button class="tbtn theme" data-theme="biz" title="biz 浅色商务"></button>
285
+ <button class="tbtn theme" data-theme="mono" title="mono 极简黑白"></button>
286
+ <button class="tbtn theme" data-theme="warm" title="warm 温暖渐变"></button>
287
+ <button class="tbtn" id="btnScope" title="开启后主题只作用于当前页">⭮ 仅本页</button>
288
+ </div>
289
+ <div class="group">
290
+ <button class="tbtn" id="btnPlay">▶ 自动播放</button>
291
+ <select class="tbtn" id="selInterval">
292
+ <option value="3">3s</option>
293
+ <option value="5" selected>5s</option>
294
+ <option value="8">8s</option>
295
+ <option value="10">10s</option>
296
+ </select>
297
+ <label class="tbtn"><input type="checkbox" id="chkLoop">循环</label>
298
+ <label class="tbtn"><input type="checkbox" id="chkFromStart" checked>从头</label>
299
+ </div>
300
+ <div class="group">
301
+ <button class="tbtn" id="btnCopy">⧉ 复制本页</button>
302
+ <button class="tbtn" id="btnCopyMulti">▦ 选页复制</button>
303
+ <button class="tbtn" id="btnShot">📷 截图本页</button>
304
+ <button class="tbtn" id="btnShotMulti">▦ 选页截图</button>
305
+ <button class="tbtn" id="btnFull">⛶ 全屏</button>
306
+ <button class="tbtn help" id="btnHelp">?</button>
307
+ </div>
308
+ </div>
309
+ <div class="pageno" id="pageNo"><b>1</b> / 9</div>
310
+ <div class="hint">← → 翻页 · 空格下一页 · P 播放 · S 截图 · C 复制 · F 全屏 · ? 操作说明</div>
311
+ <button class="nav prev" id="btnPrev">‹</button>
312
+ <button class="nav next" id="btnNext">›</button>
313
+ <main class="stage"><div class="deck" id="deck">
314
+
315
+ <!-- 页 1 封面(classic 模板) -->
316
+ <section class="slide cover active" data-title="封面">
317
+ <div class="logo"></div>
318
+ <div class="s-title">魔法幻灯<br>Magic Deck</div>
319
+ <div class="sub">HTML 形态 · PPT 体验 · 单文件离线放映</div>
320
+ <div class="sign">GENERATED BY SKILLFORGE</div>
321
+ </section>
322
+
323
+ <!-- 页 2 目录 -->
324
+ <section class="slide" data-title="目录">
325
+ <div class="s-head"><h2>目录</h2><span class="tag">CONTENTS</span></div>
326
+ <div class="toc">
327
+ <div class="toc-item" data-goto="2"><span class="no">01</span><span class="t">经典商务版式</span><span class="pg">第 3 页</span></div>
328
+ <div class="toc-item" data-goto="3"><span class="no">02</span><span class="t">极简留白版式</span><span class="pg">第 4 页</span></div>
329
+ <div class="toc-item" data-goto="4"><span class="no">03</span><span class="t">杂志画册版式</span><span class="pg">第 5 页</span></div>
330
+ <div class="toc-item" data-goto="5"><span class="no">04</span><span class="t">黑金发布版式</span><span class="pg">第 6 页</span></div>
331
+ <div class="toc-item" data-goto="6"><span class="no">05</span><span class="t">时间线叙事版式</span><span class="pg">第 7 页</span></div>
332
+ </div>
333
+ </section>
334
+
335
+ <!-- 页 3 内容页(classic:左色条 + 卡片栅格) -->
336
+ <section class="slide" data-title="经典商务">
337
+ <div class="s-head"><h2>核心能力</h2><span class="tag">CLASSIC</span></div>
338
+ <div class="grid3">
339
+ <div class="card"><h4>单文件交付<span class="en">Single File</span></h4><p>零外部依赖,双击即开,离线可用,分享只需一个 HTML。</p></div>
340
+ <div class="card"><h4>播放引擎<span class="en">Player</span></h4><p>自动播放、循环、间隔可设,从头或当前页续播。</p></div>
341
+ <div class="card"><h4>纯净放映<span class="en">Clean</span></h4><p>全屏时工具栏页码全部退场,只留舞台主体。</p></div>
342
+ </div>
343
+ </section>
344
+
345
+ <!-- 页 4 minimal 极简留白 -->
346
+ <section class="slide" data-title="极简留白" data-template="minimal">
347
+ <div class="s-title" style="margin-bottom:28px;">少即是多</div>
348
+ <ul class="points">
349
+ <li>没有卡片描边,只有文字与留白</li>
350
+ <li>信息密度低,观点反而更突出</li>
351
+ <li>适合观点陈述与开场引导</li>
352
+ </ul>
353
+ </section>
354
+
355
+ <!-- 页 5 magazine 杂志画册(分栏结构由包装器生成) -->
356
+ <section class="slide" data-title="杂志画册" data-template="magazine">
357
+ <div class="s-head"><h2>图与文的黄金分栏</h2><span class="tag">MAGAZINE</span></div>
358
+ <div class="grid2">
359
+ <div class="card"><h4>左文右图分栏<span class="en">Split</span></h4><p>46/54 分栏,图位用主题渐变色块占位,杂志感扑面而来。</p></div>
360
+ <div class="card"><h4>横向大数字带<span class="en">Strip</span></h4><p>数据页的 bignum 自动转成横向大数字带呈现关键指标。</p></div>
361
+ </div>
362
+ </section>
363
+
364
+ <!-- 页 6 keynote 黑金发布(单点大字结构由包装器生成) -->
365
+ <section class="slide" data-title="黑金发布" data-template="keynote">
366
+ <div class="s-head"><h2>一个观点,讲透</h2><span class="tag">KEYNOTE</span></div>
367
+ <div class="grid2">
368
+ <div class="card"><h4>每页一个核心信息<span class="en">Focus</span></h4><p>配合环形进度展示达成率。</p></div>
369
+ <div class="bignum"><b>72%</b><span>达成率</span></div>
370
+ </div>
371
+ </section>
372
+
373
+ <!-- 页 7 timeline 时间线叙事(时间线结构由包装器生成:卡片标题→节点标题) -->
374
+ <section class="slide" data-title="时间线叙事" data-template="timeline">
375
+ <div class="s-head"><h2>里程碑</h2><span class="tag">TIMELINE</span></div>
376
+ <div class="grid3">
377
+ <div class="card"><h4>立项与架构<span class="en">2026 Q1</span></h4><p>完成技术选型与四主题变量体系。</p></div>
378
+ <div class="card"><h4>模板库建设<span class="en">2026 Q2</span></h4><p>五套版式骨架与主题正交组合。</p></div>
379
+ <div class="card"><h4>单页换肤<span class="en">2026 Q3</span></h4><p>整份或单页自由切换主题。</p></div>
380
+ </div>
381
+ </section>
382
+
383
+ <!-- 页 8 数据页(magazine 下 bignum 自动转横向大数字带) -->
384
+ <section class="slide" data-title="数据一览">
385
+ <div class="s-head"><h2>数据一览</h2><span class="tag">DATA</span></div>
386
+ <div class="grid4">
387
+ <div class="bignum"><b>99.9%</b><span>可用性</span></div>
388
+ <div class="bignum"><b>5</b><span>版式模板</span></div>
389
+ <div class="bignum"><b>4</b><span>配色主题</span></div>
390
+ <div class="bignum"><b>0</b><span>外部依赖</span></div>
391
+ </div>
392
+ </section>
393
+
394
+ <!-- 页 9 结束页 -->
395
+ <section class="slide endpage" data-title="结束">
396
+ <div class="s-title">谢谢观看</div>
397
+ <p>按 F 全屏放映 · 按 ? 查看全部热键</p>
398
+ <p class="sign">GENERATED BY SKILLFORGE</p>
399
+ </section>
400
+
401
+ </div></main>
402
+ <div class="help-mask" id="helpMask">
403
+ <div class="help-panel">
404
+ <h3>操作说明</h3>
405
+ <table>
406
+ <tr><td><kbd>←</kbd> <kbd>→</kbd> / <kbd>空格</kbd></td><td>翻页</td></tr>
407
+ <tr><td><kbd>P</kbd></td><td>播放 / 暂停</td></tr>
408
+ <tr><td><kbd>S</kbd></td><td>截图本页(PNG 2x,只含主体)</td></tr>
409
+ <tr><td><kbd>C</kbd></td><td>复制本页文字(只含主体)</td></tr>
410
+ <tr><td><kbd>F</kbd></td><td>全屏放映(操作元素自动退场)</td></tr>
411
+ <tr><td><kbd>1</kbd>-<kbd>9</kbd></td><td>数字直达对应页</td></tr>
412
+ <tr><td><kbd>Home</kbd> / <kbd>End</kbd></td><td>首页 / 末页</td></tr>
413
+ <tr><td><kbd>Esc</kbd></td><td>关弹层 / 退全屏</td></tr>
414
+ </table>
415
+ <div class="help-note">工具栏色点=整份换主题;点「⭮ 仅本页」后色点只改当前页,再点恢复整份。「🎨 模板」=五套版式模板应用到全部页/仅本页/勾选页,标题自动映射,切回经典商务即恢复原排版。截图与复制只含主体内容,绝不包含工具栏/页码/箭头等操作元素;「▦ 选页复制 / 选页截图」可勾选任意页批量执行,截图逐张下载 PNG。</div>
416
+ </div>
417
+ </div>
418
+ <div class="help-mask" id="tplMask">
419
+ <div class="tpl-panel">
420
+ <div class="cp-head">
421
+ <h3>应用模板</h3>
422
+ <span class="tpl-head-note">模板只改版式,内容与配色不动</span>
423
+ </div>
424
+ <div class="tpl-cards" id="tplCards"></div>
425
+ <div class="tpl-scope">
426
+ <label class="sel"><input type="radio" name="tplScope" value="all" checked>全部页面</label>
427
+ <label><input type="radio" name="tplScope" value="current">仅本页</label>
428
+ <label><input type="radio" name="tplScope" value="selected">选择页面</label>
429
+ </div>
430
+ <div class="cp-list" id="tplList"></div>
431
+ <div class="cp-foot">
432
+ <button class="tbtn" id="tplCancel">取消</button>
433
+ <button class="tbtn primary" id="tplDo">应用模板</button>
434
+ </div>
435
+ <div class="help-note">标题自动映射到模板固定位置:timeline 取卡片标题做节点、keynote 取页标题做大观点、magazine 左文右图分栏,切回「经典商务」即恢复原始排版,内容一条不丢。模板与主题正交——换模板后配色保持不变。</div>
436
+ </div>
437
+ </div>
438
+ <div class="help-mask" id="copyMask">
439
+ <div class="copy-panel">
440
+ <div class="cp-head">
441
+ <h3>选择要复制的页面</h3>
442
+ <label class="cp-all"><input type="checkbox" id="cpAll" checked>全选</label>
443
+ </div>
444
+ <div class="cp-list" id="cpList"></div>
445
+ <div class="cp-foot">
446
+ <button class="tbtn" id="cpCancel">取消</button>
447
+ <button class="tbtn primary" id="cpDo">复制所选</button>
448
+ </div>
449
+ </div>
450
+ </div>
451
+ <div class="help-mask" id="shotMask">
452
+ <div class="copy-panel">
453
+ <div class="cp-head">
454
+ <h3>选择要截图的页面</h3>
455
+ <label class="cp-all"><input type="checkbox" id="spAll" checked>全选</label>
456
+ </div>
457
+ <div class="cp-list" id="spList"></div>
458
+ <div class="cp-foot">
459
+ <button class="tbtn" id="spCancel">取消</button>
460
+ <button class="tbtn primary" id="spDo">截图所选</button>
461
+ </div>
462
+ <div class="help-note">逐张下载 PNG(2560×1440 两倍清晰度);浏览器首次会询问「下载多个文件」的许可,点允许即可。</div>
463
+ </div>
464
+ </div>
465
+ <script>
466
+ var slides = Array.prototype.slice.call(document.querySelectorAll('.slide'));
467
+ var N = slides.length;
468
+ var cur = 0, timer = null, playing = false;
469
+ var progFill = document.getElementById('progFill');
470
+ var pageNo = document.getElementById('pageNo');
471
+ var btnPlay = document.getElementById('btnPlay');
472
+ var btnScope = document.getElementById('btnScope');
473
+ var selInterval = document.getElementById('selInterval');
474
+ var chkLoop = document.getElementById('chkLoop');
475
+ var chkFromStart = document.getElementById('chkFromStart');
476
+
477
+ function go(i){
478
+ cur = Math.max(0, Math.min(N - 1, i));
479
+ slides.forEach(function(s, k){
480
+ s.classList.toggle('active', k === cur);
481
+ s.classList.toggle('dim', k !== cur);
482
+ });
483
+ progFill.style.width = ((cur + 1) / N * 100) + '%';
484
+ pageNo.innerHTML = '<b>' + (cur + 1) + '</b> / ' + N;
485
+ if (location.hash !== '#/' + (cur + 1)) history.replaceState(null, '', '#/' + (cur + 1));
486
+ }
487
+ function next(){ if (cur < N - 1) go(cur + 1); else if (chkLoop.checked) go(0); }
488
+ function prev(){ if (cur > 0) go(cur - 1); else if (chkLoop.checked) go(N - 1); }
489
+ function stopPlay(){ if (timer){ clearInterval(timer); timer = null; } playing = false; btnPlay.classList.remove('on'); btnPlay.textContent = '▶ 自动播放'; }
490
+ function startPlay(){
491
+ var sec = parseInt(selInterval.value, 10); if (!isFinite(sec) || sec < 1) sec = 5;
492
+ if (chkFromStart.checked) go(0);
493
+ stopPlay();
494
+ playing = true; btnPlay.classList.add('on'); btnPlay.textContent = '⏸ 暂停';
495
+ timer = setInterval(function(){
496
+ if (cur < N - 1) go(cur + 1); else if (chkLoop.checked) go(0); else stopPlay();
497
+ }, sec * 1000);
498
+ }
499
+ btnPlay.addEventListener('click', function(){ playing ? stopPlay() : startPlay(); });
500
+
501
+ /* ── 主题切换(整份 / 仅本页) ── */
502
+ function setGlobalTheme(name){
503
+ document.documentElement.setAttribute('data-theme', name);
504
+ slides.forEach(function(s){ s.removeAttribute('data-theme'); });
505
+ }
506
+ function setSlideTheme(name){
507
+ slides[cur].setAttribute('data-theme', name);
508
+ }
509
+ Array.prototype.slice.call(document.querySelectorAll('.tbtn.theme')).forEach(function(b){
510
+ b.addEventListener('click', function(){
511
+ btnScope.classList.contains('on') ? setSlideTheme(b.dataset.theme) : setGlobalTheme(b.dataset.theme);
512
+ Array.prototype.slice.call(document.querySelectorAll('.tbtn.theme')).forEach(function(x){ x.classList.remove('on'); });
513
+ b.classList.add('on');
514
+ });
515
+ });
516
+ btnScope.addEventListener('click', function(){
517
+ btnScope.classList.toggle('on');
518
+ btnScope.textContent = btnScope.classList.contains('on') ? '⭮ 本页模式' : '⭮ 仅本页';
519
+ });
520
+
521
+
522
+ /* ── 模板应用(全部/本页/选页;标题自动映射到模板固定位置,内容不丢) ── */
523
+ var TPLS = [
524
+ { id:'classic', name:'经典商务', ic:'▦', ds:'左色条标题 + 卡片栅格,稳重大方' },
525
+ { id:'minimal', name:'极简留白', ic:'▫', ds:'居中大标题 + 无边框要点,安静克制' },
526
+ { id:'magazine', name:'杂志画册', ic:'▤', ds:'超大标题跨版 + 左文右图 + 大数字带' },
527
+ { id:'keynote', name:'黑金发布', ic:'◉', ds:'每页一个核心观点,大字居中 + 环形进度' },
528
+ { id:'timeline', name:'时间线叙事', ic:'☰', ds:'竖线时间轴 + 节点卡片,适合历程回顾' }
529
+ ];
530
+ function tplOf(s){ return s.getAttribute('data-template') || 'classic'; }
531
+ function ensureOrigin(s){ if (!s._origin) s._origin = s.innerHTML; }
532
+ function isSpecialPage(s){
533
+ return s.classList.contains('cover') || s.classList.contains('endpage') || !!s.querySelector('.toc');
534
+ }
535
+ function wrapTimeline(s){
536
+ var cards = s.querySelectorAll('.card, .points li');
537
+ if (!cards.length) return;
538
+ var tl = document.createElement('div'); tl.className = 'tl';
539
+ Array.prototype.forEach.call(cards, function(card){
540
+ var it = document.createElement('div'); it.className = 'tl-item';
541
+ var en = card.querySelector('.en');
542
+ var tm = document.createElement('div'); tm.className = 'tl-time';
543
+ tm.textContent = en ? en.textContent.trim() : '·';
544
+ it.appendChild(tm);
545
+ var h4 = card.querySelector('h4');
546
+ var t4 = document.createElement('h4');
547
+ t4.textContent = h4 ? h4.textContent.replace(en ? en.textContent : '', '').trim() : card.textContent.trim();
548
+ it.appendChild(t4);
549
+ var p = card.querySelector('p');
550
+ if (p){ var np = document.createElement('p'); np.textContent = p.textContent; it.appendChild(np); }
551
+ tl.appendChild(it);
552
+ });
553
+ var host = cards[0].parentNode;
554
+ host.parentNode.insertBefore(tl, host);
555
+ host.parentNode.removeChild(host);
556
+ }
557
+ function wrapKeynote(s){
558
+ var head = s.querySelector('.s-head h2, .s-title');
559
+ var sub = s.querySelector('.card p');
560
+ var num = s.querySelector('.bignum b');
561
+ if (!head && !num) return;
562
+ var kw = document.createElement('div');
563
+ kw.style.cssText = 'flex:1;display:flex;flex-direction:column;justify-content:center;';
564
+ if (head){
565
+ var kb = document.createElement('div'); kb.className = 'k-big';
566
+ kb.textContent = head.textContent; kw.appendChild(kb);
567
+ }
568
+ if (sub){
569
+ var ks = document.createElement('div'); ks.className = 'k-sub';
570
+ ks.textContent = sub.textContent; kw.appendChild(ks);
571
+ }
572
+ var bar = document.createElement('div'); bar.className = 'k-bar'; kw.appendChild(bar);
573
+ if (num){
574
+ var ring = document.createElement('div'); ring.className = 'k-ring';
575
+ var rb = document.createElement('b'); rb.textContent = num.textContent; ring.appendChild(rb);
576
+ kw.appendChild(ring);
577
+ }
578
+ var rest = [];
579
+ Array.prototype.forEach.call(s.querySelectorAll('.card h4'), function(h4){
580
+ rest.push(h4.childNodes[0] ? h4.childNodes[0].textContent.trim() : h4.textContent);
581
+ });
582
+ if (rest.length){
583
+ var kn = document.createElement('div'); kn.className = 'k-note';
584
+ kn.textContent = rest.join(' · '); kw.appendChild(kn);
585
+ }
586
+ while (s.firstChild) s.removeChild(s.firstChild);
587
+ s.appendChild(kw);
588
+ }
589
+ function wrapMagazine(s){
590
+ var head = s.querySelector('.s-head h2');
591
+ var p = s.querySelector('.card p');
592
+ var nums = s.querySelectorAll('.bignum');
593
+ if (!head && !p && !nums.length) return;
594
+ var mw = document.createElement('div');
595
+ mw.style.cssText = 'flex:1;display:flex;flex-direction:column;justify-content:center;';
596
+ var split = document.createElement('div'); split.className = 'm-split';
597
+ var left = document.createElement('div');
598
+ if (head){ var h3 = document.createElement('h3'); h3.textContent = head.textContent; left.appendChild(h3); }
599
+ if (p){ var np = document.createElement('p'); np.textContent = p.textContent; left.appendChild(np); }
600
+ split.appendChild(left);
601
+ var fig = document.createElement('div'); fig.className = 'm-figure';
602
+ Array.prototype.forEach.call(s.querySelectorAll('.card h4'), function(h4){
603
+ var chip = document.createElement('span'); chip.className = 'chip';
604
+ chip.textContent = h4.childNodes[0] ? h4.childNodes[0].textContent.trim() : h4.textContent;
605
+ fig.appendChild(chip);
606
+ });
607
+ if (!fig.children.length){
608
+ var chip = document.createElement('span'); chip.className = 'chip';
609
+ chip.textContent = '主题渐变图位'; fig.appendChild(chip);
610
+ }
611
+ split.appendChild(fig);
612
+ mw.appendChild(split);
613
+ if (nums.length >= 2){
614
+ var strip = document.createElement('div'); strip.className = 'm-strip';
615
+ Array.prototype.forEach.call(nums, function(n){
616
+ var d = document.createElement('div');
617
+ var b = document.createElement('b'); b.textContent = n.querySelector('b').textContent;
618
+ var sp = document.createElement('span'); sp.textContent = n.querySelector('span').textContent;
619
+ d.appendChild(b); d.appendChild(sp); strip.appendChild(d);
620
+ });
621
+ mw.appendChild(strip);
622
+ }
623
+ while (s.firstChild) s.removeChild(s.firstChild);
624
+ s.appendChild(mw);
625
+ }
626
+ function applyTpl(s, tpl){
627
+ ensureOrigin(s);
628
+ s.innerHTML = s._origin;
629
+ if (tpl === 'classic'){ s.removeAttribute('data-template'); return; }
630
+ s.setAttribute('data-template', tpl);
631
+ if (tpl === 'timeline') wrapTimeline(s);
632
+ else if (tpl === 'keynote' && !isSpecialPage(s)) wrapKeynote(s);
633
+ else if (tpl === 'magazine' && !isSpecialPage(s)) wrapMagazine(s);
634
+ }
635
+ function applyScope(tpl, scope, selected){
636
+ var n = 0;
637
+ slides.forEach(function(s, i){
638
+ if (scope === 'current' && i !== cur) return;
639
+ if (scope === 'selected' && selected.indexOf(i) < 0) return;
640
+ applyTpl(s, tpl); n++;
641
+ });
642
+ return n;
643
+ }
644
+ var tplMask = document.getElementById('tplMask');
645
+ var tplCards = document.getElementById('tplCards');
646
+ var tplList = document.getElementById('tplList');
647
+ var tplDo = document.getElementById('tplDo');
648
+ var btnTemplate = document.getElementById('btnTemplate');
649
+ var selTpl = 'classic';
650
+ function closeTplPanel(){ tplMask.classList.remove('open'); }
651
+ function updateTplListVis(){
652
+ var sc = tplMask.querySelector('input[name="tplScope"]:checked');
653
+ tplList.classList.toggle('show', !!(sc && sc.value === 'selected'));
654
+ }
655
+ function updateTplBtn(){
656
+ var sc = tplMask.querySelector('input[name="tplScope"]:checked');
657
+ var n = N;
658
+ if (sc && sc.value === 'selected') n = tplMask.querySelectorAll('.tpl-check:checked').length;
659
+ if (sc && sc.value === 'current') n = 1;
660
+ tplDo.textContent = n ? '应用模板(' + n + ' 页)' : '应用模板';
661
+ }
662
+ function openTplPanel(){
663
+ selTpl = tplOf(slides[cur]);
664
+ var html = '';
665
+ TPLS.forEach(function(tp){
666
+ html += '<div class="tpl-card' + (tp.id === selTpl ? ' on' : '') + '" data-tpl="' + tp.id + '">'
667
+ + '<span class="ic">' + tp.ic + '</span>'
668
+ + '<span><span class="nm">' + tp.name + '</span><span class="ds">' + tp.ds + '</span></span></div>';
669
+ });
670
+ tplCards.innerHTML = html;
671
+ var pageHtml = '';
672
+ for (var i = 0; i < N; i++) {
673
+ pageHtml += '<label class="cp-item"><input type="checkbox" class="tpl-check" value="' + i + '" checked>'
674
+ + '<span class="cp-no">第 ' + (i + 1) + ' 页</span>'
675
+ + '<span class="cp-preview">' + escapeHtml(pagePreview(i)) + '</span></label>';
676
+ }
677
+ tplList.innerHTML = pageHtml;
678
+ tplMask.querySelector('input[name="tplScope"][value="all"]').checked = true;
679
+ updateTplListVis();
680
+ updateTplBtn();
681
+ closeCopyPanel(); closeShotPanel();
682
+ tplMask.classList.add('open');
683
+ }
684
+ function doApplyTpl(){
685
+ var sc = tplMask.querySelector('input[name="tplScope"]:checked');
686
+ if (!sc) return;
687
+ var selected = [];
688
+ if (sc.value === 'selected'){
689
+ Array.prototype.forEach.call(tplMask.querySelectorAll('.tpl-check:checked'), function(box){ selected.push(parseInt(box.value, 10)); });
690
+ if (!selected.length) return;
691
+ }
692
+ var n = applyScope(selTpl, sc.value, selected);
693
+ closeTplPanel();
694
+ btnTemplate.textContent = '✓ 已应用 ' + n + ' 页';
695
+ setTimeout(function(){ btnTemplate.textContent = '🎨 模板'; }, 1500);
696
+ }
697
+ btnTemplate.addEventListener('click', openTplPanel);
698
+ document.getElementById('tplCancel').addEventListener('click', closeTplPanel);
699
+ tplDo.addEventListener('click', doApplyTpl);
700
+ tplCards.addEventListener('click', function(e){
701
+ var c = e.target.closest('.tpl-card');
702
+ if (!c) return;
703
+ selTpl = c.getAttribute('data-tpl');
704
+ Array.prototype.forEach.call(tplCards.children, function(x){ x.classList.remove('on'); });
705
+ c.classList.add('on');
706
+ });
707
+ tplMask.addEventListener('change', function(e){
708
+ if (e.target.name === 'tplScope'){
709
+ Array.prototype.forEach.call(tplMask.querySelectorAll('.tpl-scope label'), function(l){
710
+ l.classList.toggle('sel', l.querySelector('input') === e.target);
711
+ });
712
+ updateTplListVis(); updateTplBtn();
713
+ }
714
+ if (e.target.classList.contains('tpl-check')) updateTplBtn();
715
+ });
716
+ tplMask.addEventListener('click', function(e){ if (e.target === tplMask) closeTplPanel(); });
717
+
718
+ /* ── 翻页 / 键盘 ── */
719
+ document.getElementById('btnPrev').addEventListener('click', prev);
720
+ document.getElementById('btnNext').addEventListener('click', next);
721
+ document.addEventListener('keydown', function(e){
722
+ if (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT') return;
723
+ if (e.repeat) return;
724
+ switch (e.key) {
725
+ case 'ArrowRight': case 'ArrowDown': case 'PageDown': next(); e.preventDefault(); break;
726
+ case 'ArrowLeft': case 'ArrowUp': case 'PageUp': prev(); e.preventDefault(); break;
727
+ case ' ': next(); e.preventDefault(); break;
728
+ case 'Home': go(0); break;
729
+ case 'End': go(N - 1); break;
730
+ case 'p': case 'P': playing ? stopPlay() : startPlay(); break;
731
+ case 's': case 'S': shotPage(); break;
732
+ case 'c': case 'C': copyPage(); break;
733
+ case 'f': case 'F': toggleFull(); break;
734
+ case '?': helpMask.classList.add('open'); break;
735
+ case 'Escape': helpMask.classList.remove('open'); copyMask.classList.remove('open'); shotMask.classList.remove('open'); tplMask.classList.remove('open'); if (document.fullscreenElement) document.exitFullscreen(); break;
736
+ default:
737
+ if (/^[1-9]$/.test(e.key) && parseInt(e.key, 10) <= N) go(parseInt(e.key, 10) - 1);
738
+ }
739
+ });
740
+
741
+ /* ── 页内连接 / 外部链接 / hash 定位 ── */
742
+ document.getElementById('deck').addEventListener('click', function(e){
743
+ var t = e.target.closest('[data-goto]');
744
+ if (t) go(parseInt(t.getAttribute('data-goto'), 10));
745
+ });
746
+ window.addEventListener('hashchange', function(){
747
+ var m = location.hash.match(/^#\/(\d+)$/);
748
+ if (m) go(parseInt(m[1], 10) - 1);
749
+ });
750
+
751
+ /* ── 全屏 ── */
752
+ function toggleFull(){
753
+ if (document.fullscreenElement) document.exitFullscreen();
754
+ else document.documentElement.requestFullscreen();
755
+ }
756
+ document.getElementById('btnFull').addEventListener('click', toggleFull);
757
+
758
+ /* ── 复制本页(只含主体) ── */
759
+ function copyPage(){
760
+ var text = slides[cur].innerText.replace(/\n{2,}/g, '\n').trim();
761
+ var done = function(){ btnCopy.textContent = '✓ 已复制'; setTimeout(function(){ btnCopy.textContent = '⧉ 复制本页'; }, 1500); };
762
+ var btnCopy = document.getElementById('btnCopy');
763
+ if (navigator.clipboard && navigator.clipboard.writeText) {
764
+ navigator.clipboard.writeText(text).then(done).catch(function(){ fallbackCopy(text); done(); });
765
+ } else { fallbackCopy(text); done(); }
766
+ }
767
+ function fallbackCopy(text){
768
+ var ta = document.createElement('textarea');
769
+ ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0';
770
+ document.body.appendChild(ta); ta.select();
771
+ try { document.execCommand('copy'); } catch(e) {}
772
+ document.body.removeChild(ta);
773
+ }
774
+
775
+ /* ── 选页复制(默认全选=复制全部;只含各页主体文字) ── */
776
+ var copyMask = document.getElementById('copyMask');
777
+ var cpList = document.getElementById('cpList');
778
+ var cpAll = document.getElementById('cpAll');
779
+ var cpDo = document.getElementById('cpDo');
780
+ var btnCopyMulti = document.getElementById('btnCopyMulti');
781
+ function escapeHtml(t){ return t.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
782
+ function pagePreview(i){
783
+ var t = (slides[i].innerText || '').replace(/\n{2,}/g, '\n').trim();
784
+ var line = t.split('\n')[0] || '';
785
+ return line.length > 26 ? line.slice(0, 26) + '…' : line;
786
+ }
787
+ function openCopyPanel(){
788
+ var html = '';
789
+ for (var i = 0; i < N; i++) {
790
+ html += '<label class="cp-item"><input type="checkbox" class="cp-check" value="' + i + '" checked>'
791
+ + '<span class="cp-no">第 ' + (i + 1) + ' 页</span>'
792
+ + '<span class="cp-preview">' + escapeHtml(pagePreview(i)) + '</span></label>';
793
+ }
794
+ cpList.innerHTML = html;
795
+ cpAll.checked = true;
796
+ updateCpBtn();
797
+ closeShotPanel(); closeTplPanel();
798
+ copyMask.classList.add('open');
799
+ }
800
+ function updateCpBtn(){
801
+ var n = copyMask.querySelectorAll('.cp-check:checked').length;
802
+ cpDo.textContent = n ? '复制所选(' + n + ' 页)' : '复制所选';
803
+ }
804
+ function closeCopyPanel(){ copyMask.classList.remove('open'); }
805
+ function copySelectedPages(){
806
+ var boxes = copyMask.querySelectorAll('.cp-check:checked');
807
+ var parts = [];
808
+ Array.prototype.forEach.call(boxes, function(box){
809
+ var i = parseInt(box.value, 10);
810
+ var text = slides[i].innerText.replace(/\n{2,}/g, '\n').trim();
811
+ parts.push('【第 ' + (i + 1) + ' 页】\n' + text);
812
+ });
813
+ if (parts.length === 0) return;
814
+ var all = parts.join('\n\n');
815
+ closeCopyPanel();
816
+ var done = function(){ btnCopyMulti.textContent = '✓ 已复制'; setTimeout(function(){ btnCopyMulti.textContent = '▦ 选页复制'; }, 1500); };
817
+ if (navigator.clipboard && navigator.clipboard.writeText) {
818
+ navigator.clipboard.writeText(all).then(done).catch(function(){ fallbackCopy(all); done(); });
819
+ } else { fallbackCopy(all); done(); }
820
+ }
821
+ btnCopyMulti.addEventListener('click', openCopyPanel);
822
+ document.getElementById('cpCancel').addEventListener('click', closeCopyPanel);
823
+ cpDo.addEventListener('click', copySelectedPages);
824
+ cpAll.addEventListener('change', function(){
825
+ var boxes = copyMask.querySelectorAll('.cp-check');
826
+ Array.prototype.forEach.call(boxes, function(box){ box.checked = cpAll.checked; });
827
+ updateCpBtn();
828
+ });
829
+ cpList.addEventListener('change', function(e){
830
+ if (e.target.classList.contains('cp-check')) {
831
+ var boxes = copyMask.querySelectorAll('.cp-check');
832
+ var allChecked = true;
833
+ Array.prototype.forEach.call(boxes, function(box){ if (!box.checked) allChecked = false; });
834
+ cpAll.checked = allChecked;
835
+ updateCpBtn();
836
+ }
837
+ });
838
+ copyMask.addEventListener('click', function(e){ if (e.target === copyMask) closeCopyPanel(); });
839
+
840
+ /* ── 截图(SVG foreignObject → Canvas → PNG 2x,只含主体):单页 + 选页批量共用 renderShot ── */
841
+ function renderShot(i, cb){
842
+ var clone = slides[i].cloneNode(true);
843
+ clone.classList.remove('dim');
844
+ clone.style.position = 'relative'; clone.style.inset = 'auto';
845
+ clone.style.padding = '64px 80px'; clone.style.display = 'flex';
846
+ clone.style.opacity = '1'; clone.style.visibility = 'visible';
847
+ clone.style.transform = 'none'; clone.style.transition = 'none';
848
+ var styleText = Array.prototype.slice.call(document.querySelectorAll('style')).map(function(x){ return x.textContent; }).join('\n');
849
+ var wrap = document.createElement('div');
850
+ wrap.setAttribute('data-theme', document.documentElement.getAttribute('data-theme') || 'tech');
851
+ wrap.style.width = '1280px'; wrap.style.height = '720px';
852
+ wrap.style.background = 'var(--bg)';
853
+ wrap.appendChild(clone);
854
+ var svg = '<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720" viewBox="0 0 1280 720">'
855
+ + '<foreignObject width="1280" height="720">'
856
+ + '<div xmlns="http://www.w3.org/1999/xhtml"><style>' + styleText + '</style>' + wrap.outerHTML + '</div>'
857
+ + '</foreignObject></svg>';
858
+ var img = new Image();
859
+ img.onload = function(){
860
+ var c = document.createElement('canvas'); c.width = 2560; c.height = 1440;
861
+ var ctx = c.getContext('2d');
862
+ ctx.scale(2, 2); ctx.drawImage(img, 0, 0);
863
+ var a = document.createElement('a');
864
+ a.download = 'slide-' + (i + 1) + '-' + (document.documentElement.getAttribute('data-theme') || 'tech') + '.png';
865
+ a.href = c.toDataURL('image/png'); a.click();
866
+ if (cb) cb();
867
+ };
868
+ img.onerror = function(){ if (cb) cb(new Error('shot fail')); };
869
+ img.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg);
870
+ }
871
+ function shotPage(){
872
+ var btnShot = document.getElementById('btnShot');
873
+ renderShot(cur, function(err){
874
+ if (err){ btnShot.textContent = '✗ 失败'; setTimeout(function(){ btnShot.textContent = '📷 截图本页'; }, 1500); }
875
+ else { btnShot.textContent = '✓ 已保存'; setTimeout(function(){ btnShot.textContent = '📷 截图本页'; }, 1500); }
876
+ });
877
+ }
878
+
879
+ /* ── 选页截图(默认全选=逐张下载全部页 PNG,串行接力不挤占) ── */
880
+ var shotMask = document.getElementById('shotMask');
881
+ var spList = document.getElementById('spList');
882
+ var spAll = document.getElementById('spAll');
883
+ var spDo = document.getElementById('spDo');
884
+ var btnShotMulti = document.getElementById('btnShotMulti');
885
+ function openShotPanel(){
886
+ var html = '';
887
+ for (var i = 0; i < N; i++) {
888
+ html += '<label class="cp-item"><input type="checkbox" class="sp-check" value="' + i + '" checked>'
889
+ + '<span class="cp-no">第 ' + (i + 1) + ' 页</span>'
890
+ + '<span class="cp-preview">' + escapeHtml(pagePreview(i)) + '</span></label>';
891
+ }
892
+ spList.innerHTML = html;
893
+ spAll.checked = true;
894
+ updateSpBtn();
895
+ closeCopyPanel(); closeTplPanel();
896
+ shotMask.classList.add('open');
897
+ }
898
+ function updateSpBtn(){
899
+ var n = shotMask.querySelectorAll('.sp-check:checked').length;
900
+ spDo.textContent = n ? '截图所选(' + n + ' 页)' : '截图所选';
901
+ }
902
+ function closeShotPanel(){ shotMask.classList.remove('open'); }
903
+ function shotSelectedPages(){
904
+ var boxes = shotMask.querySelectorAll('.sp-check:checked');
905
+ var idx = [];
906
+ Array.prototype.forEach.call(boxes, function(box){ idx.push(parseInt(box.value, 10)); });
907
+ if (idx.length === 0) return;
908
+ spDo.disabled = true;
909
+ var k = 0;
910
+ var step = function(){
911
+ if (k >= idx.length) {
912
+ closeShotPanel();
913
+ spDo.disabled = false;
914
+ btnShotMulti.textContent = '✓ 已保存 ' + idx.length + ' 页';
915
+ setTimeout(function(){ btnShotMulti.textContent = '▦ 选页截图'; }, 1500);
916
+ return;
917
+ }
918
+ spDo.textContent = '已保存 ' + k + '/' + idx.length + ' 页…';
919
+ renderShot(idx[k], step); k++;
920
+ };
921
+ step();
922
+ }
923
+ btnShotMulti.addEventListener('click', openShotPanel);
924
+ document.getElementById('spCancel').addEventListener('click', closeShotPanel);
925
+ spDo.addEventListener('click', shotSelectedPages);
926
+ spAll.addEventListener('change', function(){
927
+ var boxes = shotMask.querySelectorAll('.sp-check');
928
+ Array.prototype.forEach.call(boxes, function(box){ box.checked = spAll.checked; });
929
+ updateSpBtn();
930
+ });
931
+ spList.addEventListener('change', function(e){
932
+ if (e.target.classList.contains('sp-check')) {
933
+ var boxes = shotMask.querySelectorAll('.sp-check');
934
+ var allChecked = true;
935
+ Array.prototype.forEach.call(boxes, function(box){ if (!box.checked) allChecked = false; });
936
+ spAll.checked = allChecked;
937
+ updateSpBtn();
938
+ }
939
+ });
940
+ shotMask.addEventListener('click', function(e){ if (e.target === shotMask) closeShotPanel(); });
941
+
942
+ /* ── 帮助弹层 / 缩放 ── */
943
+ var helpMask = document.getElementById('helpMask');
944
+ document.getElementById('btnHelp').addEventListener('click', function(){ helpMask.classList.add('open'); });
945
+ helpMask.addEventListener('click', function(e){ if (e.target === helpMask) helpMask.classList.remove('open'); });
946
+
947
+ function fit(){
948
+ document.documentElement.style.setProperty('--sc', Math.min(window.innerWidth / 1280, window.innerHeight / 720));
949
+ }
950
+ window.addEventListener('resize', fit);
951
+
952
+ /* ── 初始化:?theme / ?shot / #/N ── */
953
+ slides.forEach(function(s){
954
+ var tp = s.getAttribute('data-template');
955
+ if (tp && tp !== 'classic') applyTpl(s, tp);
956
+ });
957
+ var themeParam = new URLSearchParams(location.search).get('theme');
958
+ if (['tech','biz','mono','warm'].indexOf(themeParam) > -1) setGlobalTheme(themeParam);
959
+ var tplParam = new URLSearchParams(location.search).get('tpl');
960
+ if (tplParam && TPLS.some(function(t){ return t.id === tplParam; })) applyScope(tplParam, 'all', []);
961
+ var sm = location.hash.match(/^#\/(\d+)$/);
962
+ var shotParam = new URLSearchParams(location.search).get('shot');
963
+ var jumped = false;
964
+ if (sm){ go(parseInt(sm[1], 10) - 1); jumped = true; }
965
+ if (shotParam && shotParam !== 'all' && /^\d+$/.test(shotParam)){
966
+ var p = parseInt(shotParam, 10);
967
+ if (p >= 1 && p <= N){ go(p - 1); jumped = true; }
968
+ }
969
+ fit();
970
+ if (!jumped) go(0);
971
+ </script>
972
+ </body>
973
+ </html>