stella-coder 5.3.1 → 5.3.3

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 (52) hide show
  1. package/COMMANDS.md +47 -0
  2. package/install-av.bat +17 -0
  3. package/install-stella-pkg.bat +21 -0
  4. package/oneline-av.ps1 +7 -0
  5. package/oneline-stella.ps1 +2 -0
  6. package/package.json +1 -1
  7. package/releases/stella-antivirus/database.mjs +871 -0
  8. package/releases/stella-antivirus/index.mjs +8 -0
  9. package/releases/stella-antivirus/install-av.bat +12 -0
  10. package/releases/stella-antivirus/scanner.mjs +591 -0
  11. package/releases/stella-antivirus/ui.mjs +570 -0
  12. package/releases/stella-antivirus.zip +0 -0
  13. package/releases/stella-coder/README.md +67 -0
  14. package/releases/stella-coder/adb.mjs +200 -0
  15. package/releases/stella-coder/autonomous-agent.mjs +550 -0
  16. package/releases/stella-coder/banner.mjs +46 -0
  17. package/releases/stella-coder/browser-control.mjs +274 -0
  18. package/releases/stella-coder/build.mjs +151 -0
  19. package/releases/stella-coder/charts.mjs +411 -0
  20. package/releases/stella-coder/coding-brain.mjs +753 -0
  21. package/releases/stella-coder/game-engine.mjs +708 -0
  22. package/releases/stella-coder/gdrive-backup.mjs +338 -0
  23. package/releases/stella-coder/git-api.mjs +407 -0
  24. package/releases/stella-coder/gmail.mjs +415 -0
  25. package/releases/stella-coder/home-assistant.mjs +168 -0
  26. package/releases/stella-coder/index.mjs +5810 -0
  27. package/releases/stella-coder/install-stella.bat +12 -0
  28. package/releases/stella-coder/markdown.mjs +100 -0
  29. package/releases/stella-coder/mcp.mjs +296 -0
  30. package/releases/stella-coder/package.json +67 -0
  31. package/releases/stella-coder/presentations.mjs +1106 -0
  32. package/releases/stella-coder/protect.mjs +182 -0
  33. package/releases/stella-coder/screen-monitor.mjs +334 -0
  34. package/releases/stella-coder/sea-config.json +5 -0
  35. package/releases/stella-coder/security.mjs +237 -0
  36. package/releases/stella-coder/subagents.mjs +142 -0
  37. package/releases/stella-coder/telegram-bot.mjs +824 -0
  38. package/releases/stella-coder/tg-server.mjs +116 -0
  39. package/releases/stella-coder/theme.mjs +91 -0
  40. package/releases/stella-coder/tools.mjs +3143 -0
  41. package/releases/stella-coder/web-parser.mjs +229 -0
  42. package/releases/stella-coder/yandex-maps.mjs +426 -0
  43. package/releases/stella-coder.zip +0 -0
  44. package/run-antivirus.bat +4 -0
  45. package/setup-antivirus.bat +64 -0
  46. package/setup-full.bat +65 -0
  47. package/setup-full.ps1 +84 -0
  48. package/setup-stella.bat +46 -0
  49. package/stella-cli/index.mjs +12 -2
  50. package/stella-cli/protect.mjs +182 -0
  51. package/stella.bat +3 -0
  52. package/tests/test-modules.mjs +101 -0
@@ -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
+ }