stella-coder 3.9.2 → 5.0.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 (176) hide show
  1. package/.mcp.json +11 -0
  2. package/.opencode/skills/banner-design/SKILL.md +196 -0
  3. package/.opencode/skills/banner-design/references/banner-sizes-and-styles.md +118 -0
  4. package/.opencode/skills/brand/SKILL.md +97 -0
  5. package/.opencode/skills/brand/references/approval-checklist.md +169 -0
  6. package/.opencode/skills/brand/references/asset-organization.md +157 -0
  7. package/.opencode/skills/brand/references/brand-guideline-template.md +140 -0
  8. package/.opencode/skills/brand/references/color-palette-management.md +186 -0
  9. package/.opencode/skills/brand/references/consistency-checklist.md +94 -0
  10. package/.opencode/skills/brand/references/logo-usage-rules.md +185 -0
  11. package/.opencode/skills/brand/references/messaging-framework.md +85 -0
  12. package/.opencode/skills/brand/references/typography-specifications.md +214 -0
  13. package/.opencode/skills/brand/references/update.md +118 -0
  14. package/.opencode/skills/brand/references/visual-identity.md +96 -0
  15. package/.opencode/skills/brand/references/voice-framework.md +88 -0
  16. package/.opencode/skills/brand/scripts/extract-colors.cjs +341 -0
  17. package/.opencode/skills/brand/scripts/inject-brand-context.cjs +349 -0
  18. package/.opencode/skills/brand/scripts/sync-brand-to-tokens.cjs +248 -0
  19. package/.opencode/skills/brand/scripts/tests/test_sync_brand_to_tokens.py +52 -0
  20. package/.opencode/skills/brand/scripts/validate-asset.cjs +387 -0
  21. package/.opencode/skills/brand/templates/brand-guidelines-starter.md +275 -0
  22. package/.opencode/skills/design/SKILL.md +313 -0
  23. package/.opencode/skills/design/data/cip/deliverables.csv +51 -0
  24. package/.opencode/skills/design/data/cip/industries.csv +21 -0
  25. package/.opencode/skills/design/data/cip/mockup-contexts.csv +21 -0
  26. package/.opencode/skills/design/data/cip/styles.csv +21 -0
  27. package/.opencode/skills/design/data/icon/styles.csv +16 -0
  28. package/.opencode/skills/design/data/logo/colors.csv +56 -0
  29. package/.opencode/skills/design/data/logo/industries.csv +56 -0
  30. package/.opencode/skills/design/data/logo/styles.csv +56 -0
  31. package/.opencode/skills/design/references/banner-sizes-and-styles.md +118 -0
  32. package/.opencode/skills/design/references/cip-deliverable-guide.md +95 -0
  33. package/.opencode/skills/design/references/cip-design.md +121 -0
  34. package/.opencode/skills/design/references/cip-prompt-engineering.md +84 -0
  35. package/.opencode/skills/design/references/cip-style-guide.md +68 -0
  36. package/.opencode/skills/design/references/design-routing.md +207 -0
  37. package/.opencode/skills/design/references/icon-design.md +122 -0
  38. package/.opencode/skills/design/references/logo-color-psychology.md +101 -0
  39. package/.opencode/skills/design/references/logo-design.md +92 -0
  40. package/.opencode/skills/design/references/logo-prompt-engineering.md +158 -0
  41. package/.opencode/skills/design/references/logo-style-guide.md +109 -0
  42. package/.opencode/skills/design/references/slides-copywriting-formulas.md +84 -0
  43. package/.opencode/skills/design/references/slides-create.md +4 -0
  44. package/.opencode/skills/design/references/slides-html-template.md +295 -0
  45. package/.opencode/skills/design/references/slides-layout-patterns.md +137 -0
  46. package/.opencode/skills/design/references/slides-strategies.md +94 -0
  47. package/.opencode/skills/design/references/slides.md +42 -0
  48. package/.opencode/skills/design/references/social-photos-design.md +329 -0
  49. package/.opencode/skills/design/scripts/cip/core.py +215 -0
  50. package/.opencode/skills/design/scripts/cip/generate.py +484 -0
  51. package/.opencode/skills/design/scripts/cip/render-html.py +424 -0
  52. package/.opencode/skills/design/scripts/cip/search.py +127 -0
  53. package/.opencode/skills/design/scripts/icon/generate.py +487 -0
  54. package/.opencode/skills/design/scripts/logo/core.py +175 -0
  55. package/.opencode/skills/design/scripts/logo/generate.py +362 -0
  56. package/.opencode/skills/design/scripts/logo/search.py +114 -0
  57. package/.opencode/skills/design-system/SKILL.md +244 -0
  58. package/.opencode/skills/design-system/data/slide-backgrounds.csv +11 -0
  59. package/.opencode/skills/design-system/data/slide-charts.csv +26 -0
  60. package/.opencode/skills/design-system/data/slide-color-logic.csv +14 -0
  61. package/.opencode/skills/design-system/data/slide-copy.csv +26 -0
  62. package/.opencode/skills/design-system/data/slide-layout-logic.csv +16 -0
  63. package/.opencode/skills/design-system/data/slide-layouts.csv +26 -0
  64. package/.opencode/skills/design-system/data/slide-strategies.csv +16 -0
  65. package/.opencode/skills/design-system/data/slide-typography.csv +15 -0
  66. package/.opencode/skills/design-system/references/component-specs.md +236 -0
  67. package/.opencode/skills/design-system/references/component-tokens.md +214 -0
  68. package/.opencode/skills/design-system/references/primitive-tokens.md +203 -0
  69. package/.opencode/skills/design-system/references/semantic-tokens.md +215 -0
  70. package/.opencode/skills/design-system/references/states-and-variants.md +241 -0
  71. package/.opencode/skills/design-system/references/tailwind-integration.md +251 -0
  72. package/.opencode/skills/design-system/references/token-architecture.md +224 -0
  73. package/.opencode/skills/design-system/scripts/embed-tokens.cjs +99 -0
  74. package/.opencode/skills/design-system/scripts/fetch-background.py +317 -0
  75. package/.opencode/skills/design-system/scripts/generate-slide.py +770 -0
  76. package/.opencode/skills/design-system/scripts/generate-tokens.cjs +205 -0
  77. package/.opencode/skills/design-system/scripts/html-token-validator.py +327 -0
  78. package/.opencode/skills/design-system/scripts/search-slides.py +218 -0
  79. package/.opencode/skills/design-system/scripts/slide-token-validator.py +35 -0
  80. package/.opencode/skills/design-system/scripts/slide_search_core.py +453 -0
  81. package/.opencode/skills/design-system/scripts/tests/test_validate_tokens.py +48 -0
  82. package/.opencode/skills/design-system/scripts/validate-tokens.cjs +246 -0
  83. package/.opencode/skills/design-system/templates/design-tokens-starter.json +143 -0
  84. package/.opencode/skills/slides/SKILL.md +40 -0
  85. package/.opencode/skills/slides/references/copywriting-formulas.md +84 -0
  86. package/.opencode/skills/slides/references/create.md +4 -0
  87. package/.opencode/skills/slides/references/html-template.md +295 -0
  88. package/.opencode/skills/slides/references/layout-patterns.md +137 -0
  89. package/.opencode/skills/slides/references/slide-strategies.md +94 -0
  90. package/.opencode/skills/ui-styling/LICENSE.txt +202 -0
  91. package/.opencode/skills/ui-styling/SKILL.md +324 -0
  92. package/.opencode/skills/ui-styling/references/canvas-design-system.md +320 -0
  93. package/.opencode/skills/ui-styling/references/shadcn-accessibility.md +471 -0
  94. package/.opencode/skills/ui-styling/references/shadcn-components.md +424 -0
  95. package/.opencode/skills/ui-styling/references/shadcn-theming.md +373 -0
  96. package/.opencode/skills/ui-styling/references/tailwind-customization.md +483 -0
  97. package/.opencode/skills/ui-styling/references/tailwind-responsive.md +382 -0
  98. package/.opencode/skills/ui-styling/references/tailwind-utilities.md +455 -0
  99. package/.opencode/skills/ui-styling/scripts/requirements.txt +17 -0
  100. package/.opencode/skills/ui-styling/scripts/shadcn_add.py +308 -0
  101. package/.opencode/skills/ui-styling/scripts/tailwind_config_gen.py +473 -0
  102. package/.opencode/skills/ui-styling/scripts/tests/coverage-ui.json +1 -0
  103. package/.opencode/skills/ui-styling/scripts/tests/requirements.txt +3 -0
  104. package/.opencode/skills/ui-styling/scripts/tests/test_shadcn_add.py +266 -0
  105. package/.opencode/skills/ui-styling/scripts/tests/test_tailwind_config_gen.py +394 -0
  106. package/.opencode/skills/ui-ux-pro-max/SKILL.md +396 -0
  107. package/.opencode/skills/ui-ux-pro-max/data/_sync_all.py +414 -0
  108. package/.opencode/skills/ui-ux-pro-max/data/app-interface.csv +31 -0
  109. package/.opencode/skills/ui-ux-pro-max/data/charts.csv +26 -0
  110. package/.opencode/skills/ui-ux-pro-max/data/colors.csv +193 -0
  111. package/.opencode/skills/ui-ux-pro-max/data/design.csv +1776 -0
  112. package/.opencode/skills/ui-ux-pro-max/data/draft.csv +1779 -0
  113. package/.opencode/skills/ui-ux-pro-max/data/google-fonts.csv +1924 -0
  114. package/.opencode/skills/ui-ux-pro-max/data/icons.csv +106 -0
  115. package/.opencode/skills/ui-ux-pro-max/data/landing.csv +35 -0
  116. package/.opencode/skills/ui-ux-pro-max/data/motion.csv +17 -0
  117. package/.opencode/skills/ui-ux-pro-max/data/products.csv +193 -0
  118. package/.opencode/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  119. package/.opencode/skills/ui-ux-pro-max/data/stacks/angular.csv +51 -0
  120. package/.opencode/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  121. package/.opencode/skills/ui-ux-pro-max/data/stacks/avalonia.csv +57 -0
  122. package/.opencode/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  123. package/.opencode/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  124. package/.opencode/skills/ui-ux-pro-max/data/stacks/javafx.csv +76 -0
  125. package/.opencode/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  126. package/.opencode/skills/ui-ux-pro-max/data/stacks/laravel.csv +51 -0
  127. package/.opencode/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  128. package/.opencode/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +71 -0
  129. package/.opencode/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  130. package/.opencode/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  131. package/.opencode/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  132. package/.opencode/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  133. package/.opencode/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  134. package/.opencode/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  135. package/.opencode/skills/ui-ux-pro-max/data/stacks/threejs.csv +54 -0
  136. package/.opencode/skills/ui-ux-pro-max/data/stacks/uno.csv +60 -0
  137. package/.opencode/skills/ui-ux-pro-max/data/stacks/uwp.csv +56 -0
  138. package/.opencode/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  139. package/.opencode/skills/ui-ux-pro-max/data/stacks/winui.csv +60 -0
  140. package/.opencode/skills/ui-ux-pro-max/data/stacks/wpf.csv +57 -0
  141. package/.opencode/skills/ui-ux-pro-max/data/styles.csv +85 -0
  142. package/.opencode/skills/ui-ux-pro-max/data/typography.csv +75 -0
  143. package/.opencode/skills/ui-ux-pro-max/data/ui-reasoning.csv +162 -0
  144. package/.opencode/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  145. package/.opencode/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-312.pyc +0 -0
  146. package/.opencode/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-312.pyc +0 -0
  147. package/.opencode/skills/ui-ux-pro-max/scripts/core.py +274 -0
  148. package/.opencode/skills/ui-ux-pro-max/scripts/design_system.py +1329 -0
  149. package/.opencode/skills/ui-ux-pro-max/scripts/search.py +127 -0
  150. package/README.md +25 -30
  151. package/STELLA.md +1 -1
  152. package/dist/antimalware/database.mjs +871 -0
  153. package/dist/antimalware/index.mjs +8 -0
  154. package/dist/antimalware/scanner.mjs +591 -0
  155. package/dist/antimalware/ui.mjs +570 -0
  156. package/dist/banner.mjs +46 -0
  157. package/dist/index.mjs +3073 -0
  158. package/dist/markdown.mjs +100 -0
  159. package/dist/sea-config.json +4 -0
  160. package/dist/security.mjs +237 -0
  161. package/dist/theme.mjs +89 -0
  162. package/dist/tools.mjs +3145 -0
  163. package/install-stella.bat +44 -0
  164. package/package.json +5 -4
  165. package/stella-cli/coding-brain.mjs +753 -0
  166. package/stella-cli/index.mjs +931 -15
  167. package/stella-cli/mcp.mjs +296 -0
  168. package/stella-cli/presentations.mjs +1106 -0
  169. package/stella-cli/subagents.mjs +142 -0
  170. package/stella-cli/telegram-bot.mjs +824 -0
  171. package/stella-cli/tg-bg.bat +13 -0
  172. package/stella-cli/tg-server.bat +15 -0
  173. package/stella-cli/tg-server.mjs +116 -0
  174. package/stella-cli/theme.mjs +6 -4
  175. package/create_cats_pptx.py +0 -121
  176. package/hello.py +0 -2
@@ -0,0 +1,1106 @@
1
+ import fs from "node:fs"
2
+ import path from "node:path"
3
+ import { execSync } from "node:child_process"
4
+
5
+ // Stella Presentations — генератор красивых презентаций
6
+
7
+ const PRESENTATION_THEMES = {
8
+ modern: {
9
+ name: "Modern Dark",
10
+ primary: "#22c55e",
11
+ secondary: "#3b82f6",
12
+ background: "#0a0a0f",
13
+ surface: "#111118",
14
+ text: "#f8fafc",
15
+ muted: "#94a3b8",
16
+ accent: "#22c55e",
17
+ },
18
+ elegant: {
19
+ name: "Elegant Light",
20
+ primary: "#1e293b",
21
+ secondary: "#64748b",
22
+ background: "#ffffff",
23
+ surface: "#f8fafc",
24
+ text: "#0f172a",
25
+ muted: "#64748b",
26
+ accent: "#3b82f6",
27
+ },
28
+ creative: {
29
+ name: "Creative Purple",
30
+ primary: "#8b5cf6",
31
+ secondary: "#ec4899",
32
+ background: "#0f0a1a",
33
+ surface: "#1a1025",
34
+ text: "#f8fafc",
35
+ muted: "#a78bfa",
36
+ accent: "#8b5cf6",
37
+ },
38
+ minimal: {
39
+ name: "Minimal Clean",
40
+ primary: "#000000",
41
+ secondary: "#525252",
42
+ background: "#ffffff",
43
+ surface: "#fafafa",
44
+ text: "#171717",
45
+ muted: "#737373",
46
+ accent: "#000000",
47
+ },
48
+ tech: {
49
+ name: "Tech Blue",
50
+ primary: "#0ea5e9",
51
+ secondary: "#06b6d4",
52
+ background: "#0c1222",
53
+ surface: "#131c31",
54
+ text: "#f1f5f9",
55
+ muted: "#64748b",
56
+ accent: "#0ea5e9",
57
+ },
58
+ }
59
+
60
+ const SLIDE_TEMPLATES = {
61
+ title: (content, theme) => `
62
+ <!DOCTYPE html>
63
+ <html lang="ru">
64
+ <head>
65
+ <meta charset="UTF-8">
66
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
67
+ <title>${content.title}</title>
68
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
69
+ <style>
70
+ * { margin: 0; padding: 0; box-sizing: border-box; }
71
+ body {
72
+ font-family: 'Inter', sans-serif;
73
+ background: ${theme.background};
74
+ color: ${theme.text};
75
+ min-height: 100vh;
76
+ display: flex;
77
+ align-items: center;
78
+ justify-content: center;
79
+ }
80
+ .slide {
81
+ width: 1280px;
82
+ height: 720px;
83
+ padding: 80px;
84
+ display: flex;
85
+ flex-direction: column;
86
+ justify-content: center;
87
+ position: relative;
88
+ overflow: hidden;
89
+ }
90
+ .slide::before {
91
+ content: '';
92
+ position: absolute;
93
+ top: -50%;
94
+ right: -50%;
95
+ width: 100%;
96
+ height: 100%;
97
+ background: radial-gradient(circle, ${theme.accent}15 0%, transparent 70%);
98
+ }
99
+ .slide::after {
100
+ content: '';
101
+ position: absolute;
102
+ bottom: -30%;
103
+ left: -30%;
104
+ width: 80%;
105
+ height: 80%;
106
+ background: radial-gradient(circle, ${theme.secondary}10 0%, transparent 70%);
107
+ }
108
+ .content { position: relative; z-index: 1; }
109
+ h1 {
110
+ font-size: 72px;
111
+ font-weight: 800;
112
+ line-height: 1.1;
113
+ margin-bottom: 24px;
114
+ background: linear-gradient(135deg, ${theme.text} 0%, ${theme.muted} 100%);
115
+ -webkit-background-clip: text;
116
+ -webkit-text-fill-color: transparent;
117
+ }
118
+ .subtitle {
119
+ font-size: 28px;
120
+ color: ${theme.muted};
121
+ font-weight: 400;
122
+ max-width: 600px;
123
+ }
124
+ .author {
125
+ position: absolute;
126
+ bottom: 60px;
127
+ left: 80px;
128
+ font-size: 18px;
129
+ color: ${theme.muted};
130
+ }
131
+ .date {
132
+ position: absolute;
133
+ bottom: 60px;
134
+ right: 80px;
135
+ font-size: 16px;
136
+ color: ${theme.muted};
137
+ }
138
+ .accent-line {
139
+ width: 80px;
140
+ height: 4px;
141
+ background: linear-gradient(90deg, ${theme.accent}, ${theme.secondary});
142
+ border-radius: 2px;
143
+ margin-bottom: 32px;
144
+ }
145
+ </style>
146
+ </head>
147
+ <body>
148
+ <div class="slide">
149
+ <div class="content">
150
+ <div class="accent-line"></div>
151
+ <h1>${content.title}</h1>
152
+ <p class="subtitle">${content.subtitle || ''}</p>
153
+ </div>
154
+ <div class="author">${content.author || ''}</div>
155
+ <div class="date">${content.date || new Date().toLocaleDateString('ru-RU')}</div>
156
+ </div>
157
+ </body>
158
+ </html>`,
159
+
160
+ content: (content, theme) => `
161
+ <!DOCTYPE html>
162
+ <html lang="ru">
163
+ <head>
164
+ <meta charset="UTF-8">
165
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
166
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
167
+ <style>
168
+ * { margin: 0; padding: 0; box-sizing: border-box; }
169
+ body {
170
+ font-family: 'Inter', sans-serif;
171
+ background: ${theme.background};
172
+ color: ${theme.text};
173
+ min-height: 100vh;
174
+ display: flex;
175
+ align-items: center;
176
+ justify-content: center;
177
+ }
178
+ .slide {
179
+ width: 1280px;
180
+ height: 720px;
181
+ padding: 80px;
182
+ display: flex;
183
+ flex-direction: column;
184
+ justify-content: center;
185
+ position: relative;
186
+ }
187
+ .slide-number {
188
+ position: absolute;
189
+ top: 40px;
190
+ right: 60px;
191
+ font-size: 14px;
192
+ color: ${theme.muted};
193
+ }
194
+ h2 {
195
+ font-size: 48px;
196
+ font-weight: 700;
197
+ margin-bottom: 40px;
198
+ color: ${theme.text};
199
+ }
200
+ .content-grid {
201
+ display: grid;
202
+ grid-template-columns: 1fr 1fr;
203
+ gap: 40px;
204
+ }
205
+ .content-item {
206
+ padding: 24px;
207
+ background: ${theme.surface};
208
+ border-radius: 16px;
209
+ border: 1px solid ${theme.muted}20;
210
+ }
211
+ .content-item h3 {
212
+ font-size: 20px;
213
+ font-weight: 600;
214
+ margin-bottom: 12px;
215
+ color: ${theme.accent};
216
+ }
217
+ .content-item p {
218
+ font-size: 16px;
219
+ line-height: 1.6;
220
+ color: ${theme.muted};
221
+ }
222
+ .bullet-list {
223
+ list-style: none;
224
+ padding: 0;
225
+ }
226
+ .bullet-list li {
227
+ font-size: 20px;
228
+ line-height: 1.8;
229
+ padding-left: 24px;
230
+ position: relative;
231
+ color: ${theme.text};
232
+ }
233
+ .bullet-list li::before {
234
+ content: '';
235
+ position: absolute;
236
+ left: 0;
237
+ top: 50%;
238
+ transform: translateY(-50%);
239
+ width: 8px;
240
+ height: 8px;
241
+ background: ${theme.accent};
242
+ border-radius: 50%;
243
+ }
244
+ </style>
245
+ </head>
246
+ <body>
247
+ <div class="slide">
248
+ <div class="slide-number">${content.slideNumber || ''}</div>
249
+ <h2>${content.title}</h2>
250
+ ${content.items ? `
251
+ <ul class="bullet-list">
252
+ ${content.items.map(item => `<li>${item}</li>`).join('\n ')}
253
+ </ul>` : ''}
254
+ ${content.grid ? `
255
+ <div class="content-grid">
256
+ ${content.grid.map(item => `
257
+ <div class="content-item">
258
+ <h3>${item.title}</h3>
259
+ <p>${item.text}</p>
260
+ </div>`).join('')}
261
+ </div>` : ''}
262
+ </div>
263
+ </body>
264
+ </html>`,
265
+
266
+ twoColumn: (content, theme) => `
267
+ <!DOCTYPE html>
268
+ <html lang="ru">
269
+ <head>
270
+ <meta charset="UTF-8">
271
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
272
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
273
+ <style>
274
+ * { margin: 0; padding: 0; box-sizing: border-box; }
275
+ body {
276
+ font-family: 'Inter', sans-serif;
277
+ background: ${theme.background};
278
+ color: ${theme.text};
279
+ min-height: 100vh;
280
+ display: flex;
281
+ align-items: center;
282
+ justify-content: center;
283
+ }
284
+ .slide {
285
+ width: 1280px;
286
+ height: 720px;
287
+ padding: 80px;
288
+ display: flex;
289
+ gap: 60px;
290
+ position: relative;
291
+ }
292
+ .column {
293
+ flex: 1;
294
+ display: flex;
295
+ flex-direction: column;
296
+ justify-content: center;
297
+ }
298
+ .left-column {
299
+ border-right: 1px solid ${theme.muted}30;
300
+ padding-right: 60px;
301
+ }
302
+ h2 {
303
+ font-size: 42px;
304
+ font-weight: 700;
305
+ margin-bottom: 24px;
306
+ line-height: 1.2;
307
+ }
308
+ .description {
309
+ font-size: 18px;
310
+ line-height: 1.8;
311
+ color: ${theme.muted};
312
+ }
313
+ .stats {
314
+ display: flex;
315
+ flex-direction: column;
316
+ gap: 32px;
317
+ }
318
+ .stat-item {
319
+ text-align: center;
320
+ padding: 24px;
321
+ background: ${theme.surface};
322
+ border-radius: 12px;
323
+ }
324
+ .stat-value {
325
+ font-size: 48px;
326
+ font-weight: 700;
327
+ color: ${theme.accent};
328
+ }
329
+ .stat-label {
330
+ font-size: 14px;
331
+ color: ${theme.muted};
332
+ margin-top: 8px;
333
+ text-transform: uppercase;
334
+ letter-spacing: 1px;
335
+ }
336
+ </style>
337
+ </head>
338
+ <body>
339
+ <div class="slide">
340
+ <div class="column left-column">
341
+ <h2>${content.title}</h2>
342
+ <p class="description">${content.description}</p>
343
+ </div>
344
+ <div class="column">
345
+ <div class="stats">
346
+ ${content.stats ? content.stats.map(stat => `
347
+ <div class="stat-item">
348
+ <div class="stat-value">${stat.value}</div>
349
+ <div class="stat-label">${stat.label}</div>
350
+ </div>`).join('') : ''}
351
+ </div>
352
+ </div>
353
+ </div>
354
+ </body>
355
+ </html>`,
356
+
357
+ quote: (content, theme) => `
358
+ <!DOCTYPE html>
359
+ <html lang="ru">
360
+ <head>
361
+ <meta charset="UTF-8">
362
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
363
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
364
+ <style>
365
+ * { margin: 0; padding: 0; box-sizing: border-box; }
366
+ body {
367
+ font-family: 'Inter', sans-serif;
368
+ background: ${theme.background};
369
+ color: ${theme.text};
370
+ min-height: 100vh;
371
+ display: flex;
372
+ align-items: center;
373
+ justify-content: center;
374
+ }
375
+ .slide {
376
+ width: 1280px;
377
+ height: 720px;
378
+ padding: 80px;
379
+ display: flex;
380
+ flex-direction: column;
381
+ justify-content: center;
382
+ align-items: center;
383
+ text-align: center;
384
+ position: relative;
385
+ }
386
+ .quote-mark {
387
+ font-size: 120px;
388
+ color: ${theme.accent};
389
+ opacity: 0.3;
390
+ line-height: 1;
391
+ margin-bottom: -40px;
392
+ }
393
+ .quote-text {
394
+ font-size: 36px;
395
+ font-weight: 500;
396
+ line-height: 1.6;
397
+ max-width: 900px;
398
+ margin-bottom: 40px;
399
+ }
400
+ .author {
401
+ font-size: 18px;
402
+ color: ${theme.muted};
403
+ }
404
+ .accent-line {
405
+ width: 60px;
406
+ height: 3px;
407
+ background: ${theme.accent};
408
+ margin-bottom: 40px;
409
+ }
410
+ </style>
411
+ </head>
412
+ <body>
413
+ <div class="slide">
414
+ <div class="quote-mark">"</div>
415
+ <div class="accent-line"></div>
416
+ <p class="quote-text">${content.quote}</p>
417
+ <p class="author">— ${content.author || 'Unknown'}</p>
418
+ </div>
419
+ </body>
420
+ </html>`,
421
+
422
+ conclusion: (content, theme) => `
423
+ <!DOCTYPE html>
424
+ <html lang="ru">
425
+ <head>
426
+ <meta charset="UTF-8">
427
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
428
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
429
+ <style>
430
+ * { margin: 0; padding: 0; box-sizing: border-box; }
431
+ body {
432
+ font-family: 'Inter', sans-serif;
433
+ background: ${theme.background};
434
+ color: ${theme.text};
435
+ min-height: 100vh;
436
+ display: flex;
437
+ align-items: center;
438
+ justify-content: center;
439
+ }
440
+ .slide {
441
+ width: 1280px;
442
+ height: 720px;
443
+ padding: 80px;
444
+ display: flex;
445
+ flex-direction: column;
446
+ justify-content: center;
447
+ align-items: center;
448
+ text-align: center;
449
+ position: relative;
450
+ overflow: hidden;
451
+ }
452
+ .slide::before {
453
+ content: '';
454
+ position: absolute;
455
+ top: 50%;
456
+ left: 50%;
457
+ transform: translate(-50%, -50%);
458
+ width: 600px;
459
+ height: 600px;
460
+ background: radial-gradient(circle, ${theme.accent}20 0%, transparent 70%);
461
+ }
462
+ .content { position: relative; z-index: 1; }
463
+ h2 {
464
+ font-size: 64px;
465
+ font-weight: 800;
466
+ margin-bottom: 24px;
467
+ background: linear-gradient(135deg, ${theme.text} 0%, ${theme.accent} 100%);
468
+ -webkit-background-clip: text;
469
+ -webkit-text-fill-color: transparent;
470
+ }
471
+ .subtitle {
472
+ font-size: 24px;
473
+ color: ${theme.muted};
474
+ margin-bottom: 48px;
475
+ }
476
+ .cta-buttons {
477
+ display: flex;
478
+ gap: 24px;
479
+ }
480
+ .cta-button {
481
+ padding: 16px 32px;
482
+ border-radius: 12px;
483
+ font-size: 18px;
484
+ font-weight: 600;
485
+ text-decoration: none;
486
+ transition: all 0.3s ease;
487
+ }
488
+ .cta-primary {
489
+ background: ${theme.accent};
490
+ color: ${theme.background};
491
+ }
492
+ .cta-secondary {
493
+ background: transparent;
494
+ color: ${theme.text};
495
+ border: 2px solid ${theme.muted}40;
496
+ }
497
+ </style>
498
+ </head>
499
+ <body>
500
+ <div class="slide">
501
+ <div class="content">
502
+ <h2>${content.title || 'Спасибо!'}</h2>
503
+ <p class="subtitle">${content.subtitle || 'Вопросы?'}</p>
504
+ <div class="cta-buttons">
505
+ ${content.cta ? content.cta.map(btn => `
506
+ <a class="cta-button ${btn.primary ? 'cta-primary' : 'cta-secondary'}">${btn.text}</a>`).join('') : ''}
507
+ </div>
508
+ </div>
509
+ </div>
510
+ </body>
511
+ </html>`,
512
+ }
513
+
514
+ export function generatePresentation(config) {
515
+ const {
516
+ title,
517
+ author,
518
+ slides,
519
+ theme = "modern",
520
+ outputDir = "presentation",
521
+ } = config
522
+
523
+ const selectedTheme = PRESENTATION_THEMES[theme] || PRESENTATION_THEMES.modern
524
+ const output = path.resolve(process.cwd(), outputDir)
525
+
526
+ fs.mkdirSync(output, { recursive: true })
527
+
528
+ const htmlSlides = []
529
+
530
+ // Title slide
531
+ htmlSlides.push(SLIDE_TEMPLATES.title({
532
+ title,
533
+ author,
534
+ subtitle: slides[0]?.subtitle || '',
535
+ date: new Date().toLocaleDateString('ru-RU'),
536
+ }, selectedTheme))
537
+
538
+ // Content slides
539
+ slides.forEach((slide, index) => {
540
+ const template = SLIDE_TEMPLATES[slide.type] || SLIDE_TEMPLATES.content
541
+ htmlSlides.push(template({
542
+ ...slide,
543
+ slideNumber: `${index + 1} / ${slides.length}`,
544
+ }, selectedTheme))
545
+ })
546
+
547
+ // Conclusion slide
548
+ htmlSlides.push(SLIDE_TEMPLATES.conclusion({
549
+ title: "Спасибо!",
550
+ subtitle: "Вопросы?",
551
+ cta: [
552
+ { text: "Начать использовать", primary: true },
553
+ { text: "Узнать больше", primary: false },
554
+ ],
555
+ }, selectedTheme))
556
+
557
+ // Create index.html
558
+ const indexHtml = createPresentationIndex(title, htmlSlides, selectedTheme)
559
+ fs.writeFileSync(path.join(output, "index.html"), indexHtml)
560
+
561
+ // Create individual slide files
562
+ htmlSlides.forEach((html, index) => {
563
+ fs.writeFileSync(path.join(output, `slide-${String(index + 1).padStart(2, '0')}.html`), html)
564
+ })
565
+
566
+ // Create README
567
+ fs.writeFileSync(path.join(output, "README.md"), `# ${title}
568
+
569
+ ## Презентация
570
+
571
+ Эта презентация содержит ${htmlSlides.length} слайдов.
572
+
573
+ ### Просмотр
574
+
575
+ Откройте \`index.html\` в браузере для просмотра презентации.
576
+
577
+ ### Навигация
578
+
579
+ - Используйте клавиши ← → для навигации
580
+ - Нажмите F11 для полноэкранного режима
581
+
582
+ ### Тема
583
+
584
+ ${selectedTheme.name}
585
+
586
+ ### Цвета
587
+
588
+ - Primary: ${selectedTheme.primary}
589
+ - Secondary: ${selectedTheme.secondary}
590
+ - Accent: ${selectedTheme.accent}
591
+
592
+ ---
593
+ Создано с помощью Stella Coder
594
+ `)
595
+
596
+ return {
597
+ success: true,
598
+ outputDir: output,
599
+ slidesCount: htmlSlides.length,
600
+ theme: selectedTheme.name,
601
+ }
602
+ }
603
+
604
+ function createPresentationIndex(title, slides, theme) {
605
+ return `<!DOCTYPE html>
606
+ <html lang="ru">
607
+ <head>
608
+ <meta charset="UTF-8">
609
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
610
+ <title>${title}</title>
611
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
612
+ <style>
613
+ * { margin: 0; padding: 0; box-sizing: border-box; }
614
+ body {
615
+ font-family: 'Inter', sans-serif;
616
+ background: ${theme.background};
617
+ overflow: hidden;
618
+ }
619
+ .presentation {
620
+ width: 100vw;
621
+ height: 100vh;
622
+ display: flex;
623
+ align-items: center;
624
+ justify-content: center;
625
+ }
626
+ .slide-container {
627
+ width: 1280px;
628
+ height: 720px;
629
+ transform-origin: center center;
630
+ position: relative;
631
+ }
632
+ .slide {
633
+ position: absolute;
634
+ top: 0;
635
+ left: 0;
636
+ width: 100%;
637
+ height: 100%;
638
+ opacity: 0;
639
+ transform: translateX(100px);
640
+ transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
641
+ pointer-events: none;
642
+ }
643
+ .slide.active {
644
+ opacity: 1;
645
+ transform: translateX(0);
646
+ pointer-events: auto;
647
+ }
648
+ .slide.exit {
649
+ opacity: 0;
650
+ transform: translateX(-100px);
651
+ }
652
+ .controls {
653
+ position: fixed;
654
+ bottom: 30px;
655
+ left: 50%;
656
+ transform: translateX(-50%);
657
+ display: flex;
658
+ gap: 12px;
659
+ z-index: 1000;
660
+ }
661
+ .control-btn {
662
+ width: 48px;
663
+ height: 48px;
664
+ border-radius: 12px;
665
+ border: 1px solid ${theme.muted}30;
666
+ background: ${theme.surface};
667
+ color: ${theme.text};
668
+ cursor: pointer;
669
+ display: flex;
670
+ align-items: center;
671
+ justify-content: center;
672
+ transition: all 0.2s ease;
673
+ font-size: 18px;
674
+ }
675
+ .control-btn:hover {
676
+ background: ${theme.accent};
677
+ color: ${theme.background};
678
+ transform: scale(1.1);
679
+ }
680
+ .control-btn:active {
681
+ transform: scale(0.95);
682
+ }
683
+ .progress {
684
+ position: fixed;
685
+ bottom: 0;
686
+ left: 0;
687
+ height: 4px;
688
+ background: linear-gradient(90deg, ${theme.accent}, ${theme.secondary});
689
+ transition: width 0.3s ease;
690
+ }
691
+ .slide-counter {
692
+ position: fixed;
693
+ bottom: 40px;
694
+ right: 30px;
695
+ font-size: 14px;
696
+ color: ${theme.muted};
697
+ }
698
+ .slide-thumbnails {
699
+ position: fixed;
700
+ bottom: 100px;
701
+ left: 50%;
702
+ transform: translateX(-50%);
703
+ display: flex;
704
+ gap: 8px;
705
+ opacity: 0;
706
+ transition: opacity 0.3s ease;
707
+ }
708
+ .slide-thumbnails.visible {
709
+ opacity: 1;
710
+ }
711
+ .thumbnail {
712
+ width: 80px;
713
+ height: 45px;
714
+ border: 2px solid ${theme.muted}30;
715
+ border-radius: 6px;
716
+ cursor: pointer;
717
+ transition: all 0.2s ease;
718
+ overflow: hidden;
719
+ }
720
+ .thumbnail:hover {
721
+ border-color: ${theme.accent};
722
+ transform: scale(1.1);
723
+ }
724
+ .thumbnail.active {
725
+ border-color: ${theme.accent};
726
+ box-shadow: 0 0 10px ${theme.accent}40;
727
+ }
728
+ .thumbnail iframe {
729
+ width: 1280px;
730
+ height: 720px;
731
+ transform: scale(0.0625);
732
+ transform-origin: top left;
733
+ pointer-events: none;
734
+ }
735
+ .slide-number {
736
+ position: fixed;
737
+ top: 30px;
738
+ left: 50%;
739
+ transform: translateX(-50%);
740
+ font-size: 14px;
741
+ color: ${theme.muted};
742
+ background: ${theme.surface}80;
743
+ padding: 8px 16px;
744
+ border-radius: 20px;
745
+ backdrop-filter: blur(10px);
746
+ }
747
+ .minimap {
748
+ position: fixed;
749
+ top: 30px;
750
+ right: 30px;
751
+ display: flex;
752
+ flex-direction: column;
753
+ gap: 4px;
754
+ }
755
+ .minimap-dot {
756
+ width: 8px;
757
+ height: 8px;
758
+ border-radius: 50%;
759
+ background: ${theme.muted}40;
760
+ cursor: pointer;
761
+ transition: all 0.2s ease;
762
+ }
763
+ .minimap-dot:hover {
764
+ background: ${theme.accent};
765
+ transform: scale(1.5);
766
+ }
767
+ .minimap-dot.active {
768
+ background: ${theme.accent};
769
+ }
770
+ </style>
771
+ </head>
772
+ <body>
773
+ <div class="presentation">
774
+ <div class="slide-container" id="slideContainer">
775
+ ${slides.map((slide, i) => `
776
+ <div class="slide ${i === 0 ? 'active' : ''}" id="slide${i + 1}">
777
+ <iframe src="slide-${String(i + 1).padStart(2, '0')}.html" width="1280" height="720" frameborder="0" style="border: none;"></iframe>
778
+ </div>`).join('')}
779
+ </div>
780
+ </div>
781
+
782
+ <div class="controls">
783
+ <button class="control-btn" onclick="prevSlide()" title="Предыдущий слайд">←</button>
784
+ <button class="control-btn" onclick="nextSlide()" title="Следующий слайд">→</button>
785
+ <button class="control-btn" onclick="toggleFullscreen()" title="Полноэкранный режим">⛶</button>
786
+ <button class="control-btn" onclick="toggleThumbnails()" title="Показать превью">📷</button>
787
+ <button class="control-btn" onclick="toggleMinimap()" title="Мини-карта">🗺</button>
788
+ </div>
789
+
790
+ <div class="progress" id="progress"></div>
791
+ <div class="slide-counter" id="slideCounter">1 / ${slides.length}</div>
792
+ <div class="slide-number" id="slideNumber">1</div>
793
+
794
+ <div class="slide-thumbnails" id="thumbnails">
795
+ ${slides.map((slide, i) => `
796
+ <div class="thumbnail ${i === 0 ? 'active' : ''}" onclick="goToSlide(${i + 1})">
797
+ <iframe src="slide-${String(i + 1).padStart(2, '0')}.html" width="1280" height="720" frameborder="0"></iframe>
798
+ </div>`).join('')}
799
+ </div>
800
+
801
+ <div class="minimap" id="minimap">
802
+ ${slides.map((slide, i) => `
803
+ <div class="minimap-dot ${i === 0 ? 'active' : ''}" onclick="goToSlide(${i + 1})" title="Слайд ${i + 1}"></div>`).join('')}
804
+ </div>
805
+
806
+ <script>
807
+ let currentSlide = 1;
808
+ const totalSlides = ${slides.length};
809
+ let thumbnailsVisible = false;
810
+ let minimapVisible = false;
811
+
812
+ function showSlide(n, direction = 'next') {
813
+ const currentEl = document.getElementById('slide' + currentSlide);
814
+ const nextEl = document.getElementById('slide' + n);
815
+
816
+ if (currentEl) {
817
+ currentEl.classList.remove('active');
818
+ currentEl.classList.add('exit');
819
+ setTimeout(() => currentEl.classList.remove('exit'), 500);
820
+ }
821
+
822
+ if (nextEl) {
823
+ nextEl.style.transform = direction === 'next' ? 'translateX(100px)' : 'translateX(-100px)';
824
+ setTimeout(() => {
825
+ nextEl.classList.add('active');
826
+ nextEl.style.transform = '';
827
+ }, 50);
828
+ }
829
+
830
+ currentSlide = n;
831
+ updateUI();
832
+ }
833
+
834
+ function updateUI() {
835
+ document.getElementById('slideCounter').textContent = currentSlide + ' / ' + totalSlides;
836
+ document.getElementById('slideNumber').textContent = currentSlide;
837
+ document.getElementById('progress').style.width = ((currentSlide / totalSlides) * 100) + '%';
838
+
839
+ // Update thumbnails
840
+ document.querySelectorAll('.thumbnail').forEach((t, i) => {
841
+ t.classList.toggle('active', i + 1 === currentSlide);
842
+ });
843
+
844
+ // Update minimap
845
+ document.querySelectorAll('.minimap-dot').forEach((d, i) => {
846
+ d.classList.toggle('active', i + 1 === currentSlide);
847
+ });
848
+ }
849
+
850
+ function nextSlide() {
851
+ if (currentSlide < totalSlides) {
852
+ showSlide(currentSlide + 1, 'next');
853
+ }
854
+ }
855
+
856
+ function prevSlide() {
857
+ if (currentSlide > 1) {
858
+ showSlide(currentSlide - 1, 'prev');
859
+ }
860
+ }
861
+
862
+ function goToSlide(n) {
863
+ if (n >= 1 && n <= totalSlides) {
864
+ showSlide(n, n > currentSlide ? 'next' : 'prev');
865
+ }
866
+ }
867
+
868
+ function toggleFullscreen() {
869
+ if (!document.fullscreenElement) {
870
+ document.documentElement.requestFullscreen();
871
+ } else {
872
+ document.exitFullscreen();
873
+ }
874
+ }
875
+
876
+ function toggleThumbnails() {
877
+ thumbnailsVisible = !thumbnailsVisible;
878
+ document.getElementById('thumbnails').classList.toggle('visible', thumbnailsVisible);
879
+ }
880
+
881
+ function toggleMinimap() {
882
+ minimapVisible = !minimapVisible;
883
+ document.getElementById('minimap').style.display = minimapVisible ? 'flex' : 'none';
884
+ }
885
+
886
+ // Keyboard navigation
887
+ document.addEventListener('keydown', (e) => {
888
+ if (e.key === 'ArrowRight' || e.key === ' ') nextSlide();
889
+ if (e.key === 'ArrowLeft') prevSlide();
890
+ if (e.key === 'f') toggleFullscreen();
891
+ if (e.key === 't') toggleThumbnails();
892
+ if (e.key === 'm') toggleMinimap();
893
+ if (e.key === 'Home') goToSlide(1);
894
+ if (e.key === 'End') goToSlide(totalSlides);
895
+ });
896
+
897
+ // Touch support
898
+ let touchStartX = 0;
899
+ document.addEventListener('touchstart', (e) => {
900
+ touchStartX = e.touches[0].clientX;
901
+ });
902
+
903
+ document.addEventListener('touchend', (e) => {
904
+ const touchEndX = e.changedTouches[0].clientX;
905
+ const diff = touchStartX - touchEndX;
906
+
907
+ if (Math.abs(diff) > 50) {
908
+ if (diff > 0) nextSlide();
909
+ else prevSlide();
910
+ }
911
+ });
912
+
913
+ // Scale to fit
914
+ function scalePresentation() {
915
+ const container = document.getElementById('slideContainer');
916
+ const scaleX = window.innerWidth / 1280;
917
+ const scaleY = window.innerHeight / 720;
918
+ const scale = Math.min(scaleX, scaleY) * 0.9;
919
+ container.style.transform = 'scale(' + scale + ')';
920
+ }
921
+
922
+ window.addEventListener('resize', scalePresentation);
923
+ scalePresentation();
924
+ </script>
925
+ </body>
926
+ </html>`
927
+ }
928
+
929
+ export function createPresentationFromTopic(topic, options = {}) {
930
+ const {
931
+ theme = "modern",
932
+ slidesCount = 8,
933
+ author = "",
934
+ } = options
935
+
936
+ // Generate slide content based on topic
937
+ const slides = generateSlideContent(topic, slidesCount)
938
+
939
+ return generatePresentation({
940
+ title: topic,
941
+ author,
942
+ slides,
943
+ theme,
944
+ outputDir: `presentation-${topic.toLowerCase().replace(/\s+/g, '-')}`,
945
+ })
946
+ }
947
+
948
+ function generateSlideContent(topic, count) {
949
+ const slides = []
950
+
951
+ // Introduction slide
952
+ slides.push({
953
+ type: "content",
954
+ title: "Введение",
955
+ items: [
956
+ `Что такое ${topic}`,
957
+ `Актуальность темы`,
958
+ `Цели и задачи работы`,
959
+ `Методы исследования`,
960
+ ],
961
+ })
962
+
963
+ // Main content slides
964
+ const sections = [
965
+ { title: "Основные понятия", items: ["Определения", "Классификация", "Примеры"] },
966
+ { title: "Анализ", items: ["Сравнение", "Преимущества", "Недостатки"] },
967
+ { title: "Применение", items: ["Области использования", "Кейсы", "Практика"] },
968
+ { title: "Технологии", items: ["Современные подходы", "Инструменты", "Решения"] },
969
+ { title: "Результаты", items: ["Достижения", "Выводы", "Перспективы"] },
970
+ ]
971
+
972
+ for (let i = 0; i < Math.min(count - 3, sections.length); i++) {
973
+ slides.push({
974
+ type: "content",
975
+ title: sections[i].title,
976
+ items: sections[i].items,
977
+ })
978
+ }
979
+
980
+ // Quote slide
981
+ slides.push({
982
+ type: "quote",
983
+ quote: `${topic} — это ключ к успешному развитию в современном мире.`,
984
+ author: "Исследователь",
985
+ })
986
+
987
+ // Conclusion slide
988
+ slides.push({
989
+ type: "content",
990
+ title: "Заключение",
991
+ items: [
992
+ "Основные выводы",
993
+ "Практическая значимость",
994
+ "Рекомендации",
995
+ "Направления дальнейших исследований",
996
+ ],
997
+ })
998
+
999
+ return slides
1000
+ }
1001
+
1002
+ export const PRESENTATION_COMMANDS = {
1003
+ "/presentation": "создать презентацию из темы",
1004
+ "/presentation-theme": "выбрать тему оформления",
1005
+ "/presentation-list": "показать доступные темы",
1006
+ }
1007
+
1008
+ export const AVAILABLE_THEMES = Object.entries(PRESENTATION_THEMES).map(([key, value]) => ({
1009
+ id: key,
1010
+ name: value.name,
1011
+ colors: { primary: value.primary, accent: value.accent },
1012
+ }))
1013
+
1014
+ export function exportToPDF(presentationDir) {
1015
+ const indexPath = path.join(presentationDir, "index.html")
1016
+ if (!fs.existsSync(indexPath)) {
1017
+ throw new Error("Индексный файл не найден")
1018
+ }
1019
+
1020
+ // Create print-friendly version
1021
+ const printHtml = createPrintVersion(presentationDir)
1022
+ const printPath = path.join(presentationDir, "print.html")
1023
+ fs.writeFileSync(printPath, printHtml)
1024
+
1025
+ return {
1026
+ success: true,
1027
+ printPath,
1028
+ message: "Версия для печати создана. Используйте Ctrl+P в браузере для экспорта в PDF.",
1029
+ }
1030
+ }
1031
+
1032
+ function createPrintVersion(presentationDir) {
1033
+ const files = fs.readdirSync(presentationDir).filter(f => f.startsWith("slide-") && f.endsWith(".html"))
1034
+
1035
+ return `<!DOCTYPE html>
1036
+ <html lang="ru">
1037
+ <head>
1038
+ <meta charset="UTF-8">
1039
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1040
+ <title>Версия для печати</title>
1041
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
1042
+ <style>
1043
+ * { margin: 0; padding: 0; box-sizing: border-box; }
1044
+ body {
1045
+ font-family: 'Inter', sans-serif;
1046
+ background: #ffffff;
1047
+ color: #000000;
1048
+ }
1049
+ .slide {
1050
+ width: 1280px;
1051
+ height: 720px;
1052
+ margin: 20px auto;
1053
+ border: 1px solid #e5e7eb;
1054
+ page-break-after: always;
1055
+ position: relative;
1056
+ overflow: hidden;
1057
+ }
1058
+ .slide:last-child {
1059
+ page-break-after: avoid;
1060
+ }
1061
+ .slide-number {
1062
+ position: absolute;
1063
+ bottom: 20px;
1064
+ right: 20px;
1065
+ font-size: 12px;
1066
+ color: #9ca3af;
1067
+ }
1068
+ @media print {
1069
+ body {
1070
+ background: white;
1071
+ }
1072
+ .slide {
1073
+ border: none;
1074
+ margin: 0;
1075
+ page-break-after: always;
1076
+ }
1077
+ }
1078
+ </style>
1079
+ </head>
1080
+ <body>
1081
+ ${files.map((file, i) => `
1082
+ <div class="slide">
1083
+ <iframe src="${file}" width="1280" height="720" frameborder="0" style="border: none;"></iframe>
1084
+ <div class="slide-number">${i + 1} / ${files.length}</div>
1085
+ </div>`).join('')}
1086
+ </body>
1087
+ </html>`
1088
+ }
1089
+
1090
+ export function createSpeakerNotes(presentationDir, notes) {
1091
+ const notesPath = path.join(presentationDir, "speaker-notes.md")
1092
+
1093
+ let content = "# Заметки докладчика\n\n"
1094
+
1095
+ notes.forEach((note, i) => {
1096
+ content += `## Слайд ${i + 1}\n\n`
1097
+ content += `${note}\n\n`
1098
+ })
1099
+
1100
+ fs.writeFileSync(notesPath, content)
1101
+
1102
+ return {
1103
+ success: true,
1104
+ notesPath,
1105
+ }
1106
+ }