synthos 0.10.1 → 0.11.1
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.
- package/README.md +5 -5
- package/default-pages/elevenlabs_effects_studio/chat-history.json +1 -0
- package/default-pages/elevenlabs_effects_studio/page.html +1345 -1363
- package/default-pages/elevenlabs_effects_studio/page.json +13 -11
- package/default-pages/elevenlabs_voice_studio/chat-history.json +1 -0
- package/default-pages/elevenlabs_voice_studio/page.html +782 -801
- package/default-pages/elevenlabs_voice_studio/page.json +13 -11
- package/default-pages/json_tools/chat-history.json +1 -0
- package/default-pages/json_tools/page.html +70 -90
- package/default-pages/json_tools/page.json +12 -10
- package/default-pages/my_notes/chat-history.json +1 -0
- package/default-pages/my_notes/page.html +115 -131
- package/default-pages/my_notes/page.json +14 -12
- package/default-pages/neon_asteroids/chat-history.json +1 -0
- package/default-pages/neon_asteroids/page.html +1777 -1803
- package/default-pages/neon_asteroids/page.json +14 -12
- package/default-pages/oregon_trail/chat-history.json +1 -0
- package/default-pages/oregon_trail/page.html +290 -307
- package/default-pages/oregon_trail/page.json +14 -12
- package/default-pages/solar_explorer/chat-history.json +1 -0
- package/default-pages/solar_explorer/page.html +1929 -1951
- package/default-pages/solar_explorer/page.json +14 -12
- package/default-pages/solar_tutorial/chat-history.json +1 -0
- package/default-pages/solar_tutorial/page.html +464 -478
- package/default-pages/solar_tutorial/page.json +12 -10
- package/default-pages/us_map/chat-history.json +1 -0
- package/default-pages/us_map/page.html +170 -193
- package/default-pages/us_map/page.json +14 -12
- package/default-pages/us_map/page.light.png +0 -0
- package/default-pages/us_map_1850/chat-history.json +1 -0
- package/default-pages/us_map_1850/page.html +302 -326
- package/default-pages/us_map_1850/page.json +14 -12
- package/default-pages/western_cities_1850/chat-history.json +1 -0
- package/default-pages/western_cities_1850/page.html +503 -527
- package/default-pages/western_cities_1850/page.json +14 -12
- package/default-themes/aurora-dawn.v3.css +15 -14
- package/default-themes/aurora-dusk.v3.css +26 -26
- package/default-themes/cosmos-dawn.v3.css +15 -14
- package/default-themes/cosmos-dusk.v3.css +26 -26
- package/default-themes/elemental-dawn.v3.css +200 -0
- package/default-themes/nebula-dawn.v3.css +15 -14
- package/default-themes/nebula-dusk.v3.css +24 -24
- package/default-themes/solar-flare-dawn.v3.css +15 -14
- package/default-themes/solar-flare-dusk.v3.css +26 -26
- package/dist/builders/anthropic.d.ts +26 -2
- package/dist/builders/anthropic.d.ts.map +1 -1
- package/dist/builders/anthropic.js +132 -31
- package/dist/builders/anthropic.js.map +1 -1
- package/dist/builders/claudecode.d.ts +13 -0
- package/dist/builders/claudecode.d.ts.map +1 -0
- package/dist/builders/claudecode.js +253 -0
- package/dist/builders/claudecode.js.map +1 -0
- package/dist/builders/index.d.ts +2 -1
- package/dist/builders/index.d.ts.map +1 -1
- package/dist/builders/index.js +8 -1
- package/dist/builders/index.js.map +1 -1
- package/dist/builders/openai.js +2 -1
- package/dist/builders/openai.js.map +1 -1
- package/dist/builders/types.d.ts +31 -7
- package/dist/builders/types.d.ts.map +1 -1
- package/dist/builders/types.js +60 -28
- package/dist/builders/types.js.map +1 -1
- package/dist/connectors/types.d.ts +8 -0
- package/dist/connectors/types.d.ts.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +13 -6
- package/dist/init.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +161 -14
- package/dist/migrations.js.map +1 -1
- package/dist/models/anthropic.d.ts +1 -0
- package/dist/models/anthropic.d.ts.map +1 -1
- package/dist/models/anthropic.js +129 -29
- package/dist/models/anthropic.js.map +1 -1
- package/dist/models/chainOfThought.d.ts.map +1 -1
- package/dist/models/chainOfThought.js +32 -19
- package/dist/models/chainOfThought.js.map +1 -1
- package/dist/models/index.d.ts +2 -2
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/index.js +2 -1
- package/dist/models/index.js.map +1 -1
- package/dist/models/providers.d.ts +1 -0
- package/dist/models/providers.d.ts.map +1 -1
- package/dist/models/providers.js +12 -4
- package/dist/models/providers.js.map +1 -1
- package/dist/models/types.d.ts +15 -1
- package/dist/models/types.d.ts.map +1 -1
- package/dist/models/types.js.map +1 -1
- package/dist/pages.d.ts +57 -8
- package/dist/pages.d.ts.map +1 -1
- package/dist/pages.js +258 -45
- package/dist/pages.js.map +1 -1
- package/dist/service/createCompletePrompt.d.ts.map +1 -1
- package/dist/service/createCompletePrompt.js +5 -0
- package/dist/service/createCompletePrompt.js.map +1 -1
- package/dist/service/mediaCache.d.ts +36 -0
- package/dist/service/mediaCache.d.ts.map +1 -0
- package/dist/service/mediaCache.js +182 -0
- package/dist/service/mediaCache.js.map +1 -0
- package/dist/service/pageValidator.d.ts +25 -0
- package/dist/service/pageValidator.d.ts.map +1 -0
- package/dist/service/pageValidator.js +315 -0
- package/dist/service/pageValidator.js.map +1 -0
- package/dist/service/server.d.ts.map +1 -1
- package/dist/service/server.js +4 -0
- package/dist/service/server.js.map +1 -1
- package/dist/service/sharedTableSchema.d.ts +73 -0
- package/dist/service/sharedTableSchema.d.ts.map +1 -0
- package/dist/service/sharedTableSchema.js +206 -0
- package/dist/service/sharedTableSchema.js.map +1 -0
- package/dist/service/transformPage.d.ts +49 -11
- package/dist/service/transformPage.d.ts.map +1 -1
- package/dist/service/transformPage.js +354 -241
- package/dist/service/transformPage.js.map +1 -1
- package/dist/service/useApiRoutes.d.ts.map +1 -1
- package/dist/service/useApiRoutes.js +285 -34
- package/dist/service/useApiRoutes.js.map +1 -1
- package/dist/service/useConnectorRoutes.d.ts.map +1 -1
- package/dist/service/useConnectorRoutes.js +170 -32
- package/dist/service/useConnectorRoutes.js.map +1 -1
- package/dist/service/useDataRoutes.d.ts.map +1 -1
- package/dist/service/useDataRoutes.js +59 -2
- package/dist/service/useDataRoutes.js.map +1 -1
- package/dist/service/useExtractRoutes.d.ts +4 -0
- package/dist/service/useExtractRoutes.d.ts.map +1 -0
- package/dist/service/useExtractRoutes.js +304 -0
- package/dist/service/useExtractRoutes.js.map +1 -0
- package/dist/service/usePageRoutes.d.ts +17 -0
- package/dist/service/usePageRoutes.d.ts.map +1 -1
- package/dist/service/usePageRoutes.js +1388 -483
- package/dist/service/usePageRoutes.js.map +1 -1
- package/dist/service/useSharedDataRoutes.d.ts.map +1 -1
- package/dist/service/useSharedDataRoutes.js +54 -2
- package/dist/service/useSharedDataRoutes.js.map +1 -1
- package/dist/settings.d.ts +27 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +40 -1
- package/dist/settings.js.map +1 -1
- package/dist/themes.d.ts +0 -5
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +3 -95
- package/dist/themes.js.map +1 -1
- package/migration-rules/v2-to-v3.md +277 -119
- package/package.json +5 -1
- package/{default-pages/application → required-pages/_shell}/page.html +56 -42
- package/required-pages/_shell/page.json +14 -0
- package/required-pages/_starters/page.html +534 -0
- package/required-pages/_starters/page.json +12 -0
- package/required-pages/builder/page.html +353 -43
- package/required-pages/builder/page.json +12 -10
- package/required-pages/pages/page.html +697 -924
- package/required-pages/pages/page.json +12 -10
- package/required-pages/settings/page.html +1888 -1753
- package/required-pages/settings/page.json +12 -10
- package/required-pages/synthos_apis/page.html +834 -845
- package/required-pages/synthos_apis/page.json +12 -10
- package/required-pages/synthos_scripts/page.html +74 -88
- package/required-pages/synthos_scripts/page.json +12 -10
- package/scripts/append-instructions.py +90 -0
- package/scripts/audit-instructions.py +76 -0
- package/scripts/cleanup-shell-markup.mjs +112 -0
- package/service-connectors/buffer/connector.json +46 -0
- package/service-connectors/canva/connector.json +67 -0
- package/service-connectors/elevenlabs/connector.json +1 -1
- package/src/builders/anthropic.ts +150 -25
- package/src/builders/claudecode.ts +310 -0
- package/src/builders/index.ts +7 -1
- package/src/builders/openai.ts +2 -1
- package/src/builders/types.ts +93 -32
- package/src/connectors/types.ts +8 -0
- package/src/init.ts +13 -7
- package/src/migrations.ts +187 -16
- package/src/models/anthropic.ts +140 -30
- package/src/models/chainOfThought.ts +33 -18
- package/src/models/index.ts +2 -2
- package/src/models/providers.ts +10 -1
- package/src/models/types.ts +21 -1
- package/src/pages.ts +271 -35
- package/src/service/createCompletePrompt.ts +6 -0
- package/src/service/mediaCache.ts +206 -0
- package/src/service/pageValidator.ts +337 -0
- package/src/service/server.ts +4 -0
- package/src/service/sharedTableSchema.ts +236 -0
- package/src/service/transformPage.ts +370 -260
- package/src/service/useApiRoutes.ts +283 -32
- package/src/service/useConnectorRoutes.ts +189 -34
- package/src/service/useDataRoutes.ts +198 -116
- package/src/service/useExtractRoutes.ts +331 -0
- package/src/service/usePageRoutes.ts +1414 -394
- package/src/service/useSharedDataRoutes.ts +184 -109
- package/src/settings.ts +65 -0
- package/src/themes.ts +78 -180
- package/starters/blank_starter/chat-history.json +1 -0
- package/starters/blank_starter/page.dark.png +0 -0
- package/starters/blank_starter/page.html +47 -0
- package/starters/blank_starter/page.json +13 -0
- package/starters/blank_starter/page.light.png +0 -0
- package/starters/calculator_starter/chat-history.json +1 -0
- package/starters/calculator_starter/page.dark.png +0 -0
- package/starters/calculator_starter/page.html +232 -0
- package/starters/calculator_starter/page.json +13 -0
- package/starters/calculator_starter/page.light.png +0 -0
- package/starters/calendar_starter/chat-history.json +1 -0
- package/starters/calendar_starter/page.dark.png +0 -0
- package/starters/calendar_starter/page.html +495 -0
- package/starters/calendar_starter/page.json +13 -0
- package/starters/calendar_starter/page.light.png +0 -0
- package/starters/chat_starter/chat-history.json +1 -0
- package/starters/chat_starter/page.dark.png +0 -0
- package/starters/chat_starter/page.html +351 -0
- package/starters/chat_starter/page.json +13 -0
- package/starters/chat_starter/page.light.png +0 -0
- package/starters/checklist_starter/chat-history.json +1 -0
- package/starters/checklist_starter/page.dark.png +0 -0
- package/starters/checklist_starter/page.html +437 -0
- package/starters/checklist_starter/page.json +13 -0
- package/starters/checklist_starter/page.light.png +0 -0
- package/starters/dashboard_starter/chat-history.json +1 -0
- package/starters/dashboard_starter/page.dark.png +0 -0
- package/starters/dashboard_starter/page.html +195 -0
- package/starters/dashboard_starter/page.json +13 -0
- package/starters/dashboard_starter/page.light.png +0 -0
- package/starters/form_starter/chat-history.json +1 -0
- package/starters/form_starter/page.dark.png +0 -0
- package/starters/form_starter/page.html +313 -0
- package/starters/form_starter/page.json +13 -0
- package/starters/form_starter/page.light.png +0 -0
- package/starters/gallery_starter/chat-history.json +1 -0
- package/starters/gallery_starter/page.dark.png +0 -0
- package/starters/gallery_starter/page.html +418 -0
- package/starters/gallery_starter/page.json +13 -0
- package/starters/gallery_starter/page.light.png +0 -0
- package/starters/generator_starter/chat-history.json +1 -0
- package/starters/generator_starter/page.dark.png +0 -0
- package/starters/generator_starter/page.html +261 -0
- package/starters/generator_starter/page.json +13 -0
- package/starters/generator_starter/page.light.png +0 -0
- package/starters/index.html +538 -0
- package/starters/kanban_starter/chat-history.json +1 -0
- package/starters/kanban_starter/page.dark.png +0 -0
- package/starters/kanban_starter/page.html +432 -0
- package/starters/kanban_starter/page.json +13 -0
- package/starters/kanban_starter/page.light.png +0 -0
- package/starters/presentation_builder/chat-history.json +1 -0
- package/starters/presentation_builder/page.dark.png +0 -0
- package/starters/presentation_builder/page.html +970 -0
- package/starters/presentation_builder/page.json +15 -0
- package/starters/presentation_builder/page.light.png +0 -0
- package/starters/presentation_builder/presentation_voice/voice_config.json +9 -0
- package/starters/pulse_starter/chat-history.json +1 -0
- package/starters/pulse_starter/page.dark.png +0 -0
- package/starters/pulse_starter/page.html +698 -0
- package/starters/pulse_starter/page.json +13 -0
- package/starters/pulse_starter/page.light.png +0 -0
- package/starters/quiz_starter/chat-history.json +1 -0
- package/starters/quiz_starter/page.dark.png +0 -0
- package/starters/quiz_starter/page.html +292 -0
- package/starters/quiz_starter/page.json +13 -0
- package/starters/quiz_starter/page.light.png +0 -0
- package/starters/reference_starter/chat-history.json +1 -0
- package/starters/reference_starter/page.dark.png +0 -0
- package/starters/reference_starter/page.html +250 -0
- package/starters/reference_starter/page.json +13 -0
- package/starters/reference_starter/page.light.png +0 -0
- package/starters/retro_game_starter/chat-history.json +1 -0
- package/starters/retro_game_starter/page.dark.png +0 -0
- package/{default-pages → starters}/retro_game_starter/page.html +1281 -1308
- package/starters/retro_game_starter/page.json +15 -0
- package/starters/retro_game_starter/page.light.png +0 -0
- package/starters/roster_starter/chat-history.json +1 -0
- package/starters/roster_starter/page.dark.png +0 -0
- package/starters/roster_starter/page.html +600 -0
- package/starters/roster_starter/page.json +13 -0
- package/starters/roster_starter/page.light.png +0 -0
- package/starters/server.js +182 -0
- package/starters/start.cmd +1 -0
- package/starters/timeline_starter/chat-history.json +1 -0
- package/starters/timeline_starter/page.dark.png +0 -0
- package/starters/timeline_starter/page.html +446 -0
- package/starters/timeline_starter/page.json +13 -0
- package/starters/timeline_starter/page.light.png +0 -0
- package/starters/tutorial_starter/chat-history.json +1 -0
- package/starters/tutorial_starter/page.dark.png +0 -0
- package/starters/tutorial_starter/page.html +283 -0
- package/starters/tutorial_starter/page.json +13 -0
- package/starters/tutorial_starter/page.light.png +0 -0
- package/static-files/agent.v3.js +122 -0
- package/static-files/connector.v3.js +48 -0
- package/static-files/extract.v3.js +188 -0
- package/static-files/helpers.v3.js +50 -6
- package/static-files/page-bridge.js +114 -0
- package/static-files/page.v3.js +1292 -1290
- package/static-files/script.v3.js +32 -0
- package/static-files/server.v3.js +89 -0
- package/static-files/shell-bridge.v3.js +174 -0
- package/static-files/shell-modals.v3.js +521 -0
- package/static-files/{shell.css → shell.v3.css} +271 -22
- package/static-files/shell.v3.js +1865 -0
- package/static-files/storage.v3.js +176 -0
- package/tests/anthropic.spec.ts +42 -7
- package/tests/builders.spec.ts +70 -2
- package/tests/pageValidator.spec.ts +548 -0
- package/tests/profiles.spec.ts +122 -0
- package/tests/sharedTableSchema.spec.ts +242 -0
- package/tests/transformPage.spec.ts +62 -81
- package/default-pages/application/page.json +0 -10
- package/default-pages/retro_game_starter/page.json +0 -12
- package/default-pages/sidebar_page/page.html +0 -51
- package/default-pages/sidebar_page/page.json +0 -10
- package/default-pages/two-panel_page/page.html +0 -68
- package/default-pages/two-panel_page/page.json +0 -10
|
@@ -0,0 +1,970 @@
|
|
|
1
|
+
<!DOCTYPE html><html lang="en"><head>
|
|
2
|
+
<meta charset="UTF-8">
|
|
3
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
4
|
+
<title>SynthOS - Presentation Builder</title>
|
|
5
|
+
<script id="theme-info" src="/api/theme-info.js" data-locked="true"></script>
|
|
6
|
+
<link id="theme-css" rel="stylesheet" href="/api/theme.css" data-locked="true">
|
|
7
|
+
<style id="presentation-styles">
|
|
8
|
+
/* ── Viewer panel override for presentation layout ── */
|
|
9
|
+
.viewer-panel { justify-content: flex-start; align-items: stretch; overflow: visible; }
|
|
10
|
+
/* ── Presentation-specific layout ── */
|
|
11
|
+
.presentation-container { width: 100%; height: 100%; display: flex; flex-direction: column; padding: 10px 15px; position: relative; }
|
|
12
|
+
.presentation-controls { display: flex; justify-content: center; align-items: center; gap: 15px; padding: 10px; background: var(--pres-controlbar-bg); border-radius: var(--roundedCorner6); margin-top: 8px; border: var(--pres-controlbar-border); box-shadow: var(--pres-controlbar-shadow); order: 2; position: relative; }
|
|
13
|
+
.control-btn { padding: 8px 16px; border: none; border-radius: var(--roundedCorner6); background: var(--primaryButtonBackground); color: var(--primaryButtonText); cursor: pointer; font-size: 13px; font-weight: 600; transition: all .2s ease; box-shadow: var(--elevation4); }
|
|
14
|
+
.control-btn:hover { background: var(--primaryButtonBackgroundHovered); transform: translateY(-1px); box-shadow: var(--elevation8); }
|
|
15
|
+
.control-btn:disabled { opacity: var(--pres-btn-disabled-opacity); cursor: not-allowed; transform: none; background: var(--pres-btn-disabled-bg); color: var(--pres-btn-disabled-text); border: 1px solid var(--pres-btn-disabled-border); box-shadow: none; }
|
|
16
|
+
.present-btn { background: var(--themeTertiary); color: var(--white); }
|
|
17
|
+
.present-btn:hover { background: var(--themeDarkAlt); }
|
|
18
|
+
.pause-btn { background: var(--orange); color: var(--white); }
|
|
19
|
+
.pause-btn:hover { background: var(--orangeLight); }
|
|
20
|
+
.slide-counter { color: var(--bodySubtext); font-size: 14px; font-weight: 600; min-width: 80px; text-align: center; }
|
|
21
|
+
.slide-container { flex: 1; display: flex; justify-content: center; align-items: center; overflow: hidden; order: 1; min-height: 0; }
|
|
22
|
+
.slide { width: 100%; height: 100%; max-width: 900px; max-height: calc(100vh - 200px); background: var(--pres-slide-bg); border-radius: var(--pres-slide-radius); border: var(--pres-slide-border); box-shadow: var(--pres-slide-shadow); padding: 30px; display: none; flex-direction: column; justify-content: center; opacity: 0; transform: translateX(50px); transition: opacity .5s ease, transform .5s ease; overflow-y: auto; }
|
|
23
|
+
.slide.active { display: flex; opacity: 1; transform: translateX(0); }
|
|
24
|
+
.slide-content ul { list-style: none; padding-left: 0; }
|
|
25
|
+
.slide-content li { padding: 6px 0 6px 30px; position: relative; opacity: 0; transform: translateX(-20px); animation: slideIn .5s ease forwards; }
|
|
26
|
+
.slide.active .slide-content li:nth-child(1) { animation-delay: .1s; }
|
|
27
|
+
.slide.active .slide-content li:nth-child(2) { animation-delay: .2s; }
|
|
28
|
+
.slide.active .slide-content li:nth-child(3) { animation-delay: .3s; }
|
|
29
|
+
.slide.active .slide-content li:nth-child(4) { animation-delay: .4s; }
|
|
30
|
+
.slide.active .slide-content li:nth-child(5) { animation-delay: .5s; }
|
|
31
|
+
.slide.active .slide-content li:nth-child(6) { animation-delay: .6s; }
|
|
32
|
+
@keyframes slideIn { to { opacity: 1; transform: translateX(0); } }
|
|
33
|
+
@keyframes boxIn { to { opacity: 1; transform: scale(1); } }
|
|
34
|
+
@keyframes stepIn { to { opacity: 1; transform: translateY(0); } }
|
|
35
|
+
.slide.active .quote-block { animation-delay: .3s; }
|
|
36
|
+
@keyframes quoteIn { to { opacity: 1; } }
|
|
37
|
+
.slide.active .feature-box:nth-child(1) { animation-delay: .2s; }
|
|
38
|
+
.slide.active .feature-box:nth-child(2) { animation-delay: .3s; }
|
|
39
|
+
.slide.active .feature-box:nth-child(3) { animation-delay: .4s; }
|
|
40
|
+
.slide.active .feature-box:nth-child(4) { animation-delay: .5s; }
|
|
41
|
+
@keyframes fadeIn { to { opacity: 1; } }
|
|
42
|
+
.slide.active .final-cta { animation-delay: .5s; }
|
|
43
|
+
@keyframes finalIn { to { opacity: 1; } }
|
|
44
|
+
.slide.active .network-box:nth-child(1) { animation-delay: .2s; }
|
|
45
|
+
.slide.active .network-box:nth-child(2) { animation-delay: .4s; }
|
|
46
|
+
.slide.active .network-box:nth-child(3) { animation-delay: .6s; }
|
|
47
|
+
.slide.active .network-box:nth-child(4) { animation-delay: .8s; }
|
|
48
|
+
.slide.active .step:nth-child(1) { animation-delay: .2s; }
|
|
49
|
+
.slide.active .step:nth-child(2) { animation-delay: .4s; }
|
|
50
|
+
.slide.active .step:nth-child(3) { animation-delay: .6s; }
|
|
51
|
+
.slide.active .step:nth-child(4) { animation-delay: .8s; }
|
|
52
|
+
/* ── TTS indicator ── */
|
|
53
|
+
.tts-indicator { display: none; position: fixed; top: 20px; right: 20px; z-index: 10001; align-items: center; gap: 8px; padding: 8px 16px; background: var(--defaultStateBackground); border: 1px solid var(--neutralLight); border-radius: 25px; font-size: 13px; color: var(--themeTertiary); box-shadow: var(--elevation8); }
|
|
54
|
+
.tts-indicator.active { display: flex; }
|
|
55
|
+
.tts-wave { display: flex; gap: 3px; align-items: center; }
|
|
56
|
+
.tts-wave span { width: 3px; height: 14px; background: var(--themeTertiary); border-radius: 2px; animation: wave 1s ease-in-out infinite; }
|
|
57
|
+
.tts-wave span:nth-child(2) { animation-delay: .1s; }
|
|
58
|
+
.tts-wave span:nth-child(3) { animation-delay: .2s; }
|
|
59
|
+
.tts-wave span:nth-child(4) { animation-delay: .3s; }
|
|
60
|
+
@keyframes wave { 0%, 100% { height: 6px; } 50% { height: 14px; } }
|
|
61
|
+
/* ── Fullscreen mode ── */
|
|
62
|
+
.fullscreen-mode { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 9999; background: var(--bodyBackground); }
|
|
63
|
+
.fullscreen-mode .presentation-controls { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%) translateY(0); z-index: 10000; margin-top: 0; transition: transform .4s ease, opacity .4s ease; }
|
|
64
|
+
.fullscreen-mode .presentation-controls.controls-hidden { transform: translateX(-50%) translateY(calc(100% + 30px)); opacity: 0; pointer-events: none; }
|
|
65
|
+
.fullscreen-mode .slide-container { width: 100%; height: 100%; padding: 0; }
|
|
66
|
+
.fullscreen-mode .slide { width: 100%; height: 100%; max-width: none; max-height: none; box-shadow: none; border: none; border-radius: 0; padding: 60px 80px 100px; }
|
|
67
|
+
.fullscreen-mode .slide-title { font-size: 44px; }
|
|
68
|
+
.fullscreen-mode .slide-content { font-size: 22px; }
|
|
69
|
+
/* ── Voice picker popup ── */
|
|
70
|
+
.voice-picker { display: none; position: absolute; bottom: calc(100% + 8px); right: 0; min-width: 260px; max-width: 320px; background: var(--menuBackground); border: 1px solid var(--neutralLight); border-radius: var(--roundedCorner6); box-shadow: var(--elevation16); z-index: 100; overflow: hidden; }
|
|
71
|
+
.voice-picker.open { display: block; }
|
|
72
|
+
.voice-picker-header { padding: 10px 14px; font-size: 12px; font-weight: 600; color: var(--bodySubtext); text-transform: uppercase; letter-spacing: .5px; border-bottom: 1px solid var(--neutralLight); }
|
|
73
|
+
.voice-picker-list { max-height: 240px; overflow-y: auto; padding: 4px 0; }
|
|
74
|
+
.voice-picker-item { display: flex; align-items: center; gap: 10px; padding: 8px 14px; cursor: pointer; transition: background .15s; font-size: 13px; color: var(--bodyText); }
|
|
75
|
+
.voice-picker-item:hover { background: var(--menuItemBackgroundHovered); }
|
|
76
|
+
.voice-picker-item.selected { background: var(--themeLighter); font-weight: 600; }
|
|
77
|
+
.voice-picker-item.selected::before { content: '\2713'; color: var(--themePrimary); font-weight: 700; margin-right: 2px; }
|
|
78
|
+
.voice-picker-item-name { flex: 1; }
|
|
79
|
+
.voice-picker-item-meta { font-size: 11px; color: var(--bodySubtext); }
|
|
80
|
+
.voice-picker-empty { padding: 16px 14px; text-align: center; font-size: 13px; color: var(--bodySubtext); }
|
|
81
|
+
.voice-picker-empty a { color: var(--link); text-decoration: none; }
|
|
82
|
+
.voice-picker-empty a:hover { color: var(--linkHovered); text-decoration: underline; }
|
|
83
|
+
.voice-picker-footer { padding: 6px 14px 8px; border-top: 1px solid var(--neutralLight); display: flex; justify-content: flex-end; }
|
|
84
|
+
.voice-picker-clear { background: none; border: none; font-size: 12px; color: var(--bodySubtext); cursor: pointer; padding: 4px 8px; border-radius: var(--roundedCorner4); }
|
|
85
|
+
.voice-picker-clear:hover { background: var(--menuItemBackgroundHovered); color: var(--bodyText); }
|
|
86
|
+
/* ── Speaker Notes Modal ── */
|
|
87
|
+
.notes-modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,.45); z-index: 20000; justify-content: center; align-items: center; }
|
|
88
|
+
.notes-modal-overlay.open { display: flex; }
|
|
89
|
+
.notes-modal { background: var(--menuBackground); border-radius: var(--roundedCorner6); box-shadow: var(--elevation16); width: 520px; max-width: 90vw; max-height: 80vh; display: flex; flex-direction: column; overflow: hidden; }
|
|
90
|
+
.notes-modal-header { padding: 16px 20px; font-size: 15px; font-weight: 600; color: var(--bodyText); border-bottom: 1px solid var(--neutralLight); }
|
|
91
|
+
.notes-modal-body { padding: 16px 20px; flex: 1; overflow: auto; }
|
|
92
|
+
.notes-modal-body textarea { width: 100%; min-height: 180px; resize: vertical; border: 1px solid var(--neutralLight); border-radius: var(--roundedCorner4); padding: 10px; font-size: 14px; font-family: inherit; background: var(--inputBackground); color: var(--bodyText); }
|
|
93
|
+
.notes-modal-body textarea:focus { outline: none; border-color: var(--themePrimary); }
|
|
94
|
+
.notes-modal-footer { padding: 12px 20px; border-top: 1px solid var(--neutralLight); display: flex; justify-content: flex-end; gap: 8px; }
|
|
95
|
+
.notes-modal-footer .flm-button { padding: 6px 16px; border: 1px solid var(--neutralLight); border-radius: var(--roundedCorner4); background: var(--defaultStateBackground); color: var(--bodyText); cursor: pointer; font-size: 13px; font-weight: 600; }
|
|
96
|
+
.notes-modal-footer .flm-button:hover { background: var(--menuItemBackgroundHovered); }
|
|
97
|
+
.notes-modal-footer .flm-button--primary { background: var(--primaryButtonBackground); color: var(--primaryButtonText); border-color: transparent; }
|
|
98
|
+
.notes-modal-footer .flm-button--primary:hover { background: var(--primaryButtonBackgroundHovered); }
|
|
99
|
+
/* ── Presentation CSS Classes ── */
|
|
100
|
+
/* Callout boxes (shared structure) */
|
|
101
|
+
.pres-note, .pres-example, .pres-tip, .pres-warning, .pres-quote { padding: 12px 16px; border-radius: var(--pres-card-radius); border-left: 4px solid; margin: 12px 0; }
|
|
102
|
+
.pres-note { background: var(--pres-note-bg); border-left-color: var(--pres-note-accent); color: var(--pres-note-text); }
|
|
103
|
+
.pres-example { background: var(--pres-example-bg); border-left-color: var(--pres-example-accent); color: var(--pres-example-text); }
|
|
104
|
+
.pres-example strong { color: var(--pres-example-accent); }
|
|
105
|
+
.pres-tip { background: var(--pres-tip-bg); border-left-color: var(--pres-tip-accent); color: var(--pres-tip-text); }
|
|
106
|
+
.pres-tip strong { color: var(--pres-tip-accent); }
|
|
107
|
+
.pres-warning { background: var(--pres-warning-bg); border-left-color: var(--pres-warning-accent); color: var(--pres-warning-text); }
|
|
108
|
+
.pres-warning strong { color: var(--pres-warning-accent); }
|
|
109
|
+
.pres-quote { background: var(--pres-quote-bg); border-left-color: var(--pres-quote-accent); color: var(--pres-quote-text); font-style: italic; }
|
|
110
|
+
/* Content cards */
|
|
111
|
+
.pres-card { padding: 16px; background: var(--pres-card-bg); border-radius: var(--pres-card-radius); border: var(--pres-card-border); box-shadow: var(--pres-card-shadow); }
|
|
112
|
+
.pres-card-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin-top: 12px; }
|
|
113
|
+
.pres-card-title { font-weight: 600; color: var(--pres-title-color); margin-bottom: 4px; }
|
|
114
|
+
.pres-card-desc { font-size: 13px; color: var(--pres-muted-color); }
|
|
115
|
+
.pres-card-icon { font-size: 24px; margin-bottom: 8px; }
|
|
116
|
+
/* Steps / Process */
|
|
117
|
+
.pres-steps { display: flex; justify-content: center; gap: 16px; margin-top: 16px; flex-wrap: wrap; }
|
|
118
|
+
.pres-step { padding: 16px; background: var(--pres-card-bg); border-radius: var(--pres-card-radius); text-align: center; flex: 1; max-width: 180px; border: var(--pres-card-border); opacity: 0; transform: translateY(20px); animation: stepIn .5s var(--pres-ease-enter) forwards; }
|
|
119
|
+
.slide.active .pres-step:nth-child(1) { animation-delay: .2s; }
|
|
120
|
+
.slide.active .pres-step:nth-child(2) { animation-delay: .4s; }
|
|
121
|
+
.slide.active .pres-step:nth-child(3) { animation-delay: .6s; }
|
|
122
|
+
.slide.active .pres-step:nth-child(4) { animation-delay: .8s; }
|
|
123
|
+
.pres-step-number { font-size: 24px; font-weight: 700; color: var(--pres-highlight-color); margin-bottom: 8px; }
|
|
124
|
+
.pres-step-title { font-weight: 600; color: var(--pres-title-color); margin-bottom: 4px; }
|
|
125
|
+
.pres-step-desc { font-size: 12px; color: var(--pres-muted-color); }
|
|
126
|
+
/* Code */
|
|
127
|
+
.pres-code { background: var(--defaultStateBackground); border: 1px solid var(--neutralLight); border-radius: var(--pres-card-radius); padding: 12px; font-family: var(--fontFamilyMonospace, 'Courier New', monospace); font-size: 13px; color: var(--pres-code-color); margin-top: 12px; overflow-x: auto; }
|
|
128
|
+
.pres-code-keyword { color: var(--pres-code-keyword); }
|
|
129
|
+
.pres-code-string { color: var(--pres-code-string); }
|
|
130
|
+
/* Data / Charts */
|
|
131
|
+
.pres-chart { --chart-1: var(--pres-chart-1); --chart-2: var(--pres-chart-2); --chart-3: var(--pres-chart-3); --chart-4: var(--pres-chart-4); --chart-5: var(--pres-chart-5); --chart-6: var(--pres-chart-6); }
|
|
132
|
+
.pres-table { width: 100%; border-collapse: collapse; margin-top: 12px; }
|
|
133
|
+
.pres-table th { background: var(--pres-card-bg); color: var(--pres-title-color); font-weight: 600; padding: 10px 12px; text-align: left; border-bottom: 2px solid var(--pres-highlight-color); }
|
|
134
|
+
.pres-table td { padding: 8px 12px; color: var(--pres-body-color); border-bottom: 1px solid var(--neutralLight); }
|
|
135
|
+
.pres-table tr:nth-child(even) td { background: var(--pres-card-bg); }
|
|
136
|
+
.pres-table tr:nth-child(odd) td { background: transparent; }
|
|
137
|
+
.pres-stat { text-align: center; padding: 16px; }
|
|
138
|
+
.pres-stat-value { font-size: 36px; font-weight: 700; color: var(--pres-title-color); }
|
|
139
|
+
.pres-stat-label { font-size: 13px; color: var(--pres-muted-color); margin-top: 4px; }
|
|
140
|
+
/* Layout utilities */
|
|
141
|
+
.pres-two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
|
|
142
|
+
.pres-three-col { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 24px; }
|
|
143
|
+
.pres-center { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; }
|
|
144
|
+
.pres-spacer { height: 16px; }
|
|
145
|
+
/* Animation classes (only activate on .active slides) */
|
|
146
|
+
.pres-fade-in { opacity: 0; }
|
|
147
|
+
.slide.active .pres-fade-in { animation: fadeIn var(--pres-duration-normal) var(--pres-ease-enter) forwards; }
|
|
148
|
+
.pres-slide-in { opacity: 0; transform: translateX(-20px); }
|
|
149
|
+
.slide.active .pres-slide-in { animation: slideIn var(--pres-duration-normal) var(--pres-ease-enter) forwards; }
|
|
150
|
+
.pres-scale-in { opacity: 0; transform: scale(.9); }
|
|
151
|
+
.slide.active .pres-scale-in { animation: boxIn var(--pres-duration-normal) var(--pres-ease-enter) forwards; }
|
|
152
|
+
.pres-step-in { opacity: 0; transform: translateY(20px); }
|
|
153
|
+
.slide.active .pres-step-in { animation: stepIn var(--pres-duration-normal) var(--pres-ease-enter) forwards; }
|
|
154
|
+
.pres-stagger > *:nth-child(1) { animation-delay: calc(var(--pres-stagger-delay) * 1); }
|
|
155
|
+
.pres-stagger > *:nth-child(2) { animation-delay: calc(var(--pres-stagger-delay) * 2); }
|
|
156
|
+
.pres-stagger > *:nth-child(3) { animation-delay: calc(var(--pres-stagger-delay) * 3); }
|
|
157
|
+
.pres-stagger > *:nth-child(4) { animation-delay: calc(var(--pres-stagger-delay) * 4); }
|
|
158
|
+
.pres-stagger > *:nth-child(5) { animation-delay: calc(var(--pres-stagger-delay) * 5); }
|
|
159
|
+
.pres-stagger > *:nth-child(6) { animation-delay: calc(var(--pres-stagger-delay) * 6); }
|
|
160
|
+
/* ── Old class aliases (mapped to new tokens) ── */
|
|
161
|
+
.tip-box { background: var(--pres-tip-bg); border: none; border-left: 4px solid var(--pres-tip-accent); border-radius: var(--pres-card-radius); padding: 12px 16px; margin: 12px 0; color: var(--pres-tip-text); }
|
|
162
|
+
.tip-box strong { color: var(--pres-tip-accent); }
|
|
163
|
+
.quote-block { background: var(--pres-quote-bg); border-left: 3px solid var(--pres-quote-accent); border-radius: 0 var(--pres-card-radius) var(--pres-card-radius) 0; padding: 12px 20px; margin: 12px 0; font-style: italic; color: var(--pres-quote-text); opacity: 0; animation: quoteIn .6s var(--pres-ease-enter) forwards; }
|
|
164
|
+
.feature-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin-top: 12px; }
|
|
165
|
+
.feature-box { padding: 16px; background: var(--pres-card-bg); border-radius: var(--pres-card-radius); border: var(--pres-card-border); opacity: 0; animation: fadeIn .5s var(--pres-ease-enter) forwards; }
|
|
166
|
+
.feature-icon { font-size: 24px; margin-bottom: 8px; }
|
|
167
|
+
.feature-title { font-weight: 600; color: var(--pres-title-color); margin-bottom: 4px; }
|
|
168
|
+
.feature-desc { font-size: 13px; color: var(--pres-muted-color); }
|
|
169
|
+
.steps-container { display: flex; justify-content: center; gap: 16px; margin-top: 16px; flex-wrap: wrap; }
|
|
170
|
+
.step { padding: 16px; background: var(--pres-card-bg); border-radius: var(--pres-card-radius); text-align: center; flex: 1; max-width: 180px; border: var(--pres-card-border); opacity: 0; transform: translateY(20px); animation: stepIn .5s var(--pres-ease-enter) forwards; }
|
|
171
|
+
.step-number { font-size: 24px; font-weight: 700; color: var(--pres-highlight-color); margin-bottom: 8px; }
|
|
172
|
+
.step-title { font-weight: 600; color: var(--pres-title-color); margin-bottom: 4px; }
|
|
173
|
+
.step-desc { font-size: 12px; color: var(--pres-muted-color); }
|
|
174
|
+
.network-boxes { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 12px; }
|
|
175
|
+
.network-box { padding: 12px; background: var(--pres-card-bg); border-radius: var(--pres-card-radius); border: var(--pres-card-border); text-align: center; opacity: 0; transform: scale(.9); animation: boxIn .4s var(--pres-ease-enter) forwards; }
|
|
176
|
+
.network-box-title { font-weight: 600; color: var(--pres-title-color); margin-bottom: 4px; }
|
|
177
|
+
.network-box-desc { font-size: 13px; color: var(--pres-muted-color); }
|
|
178
|
+
.slide-example { margin-top: 16px; padding: 12px 16px; background: var(--pres-example-bg); border-radius: var(--pres-card-radius); border-left: 4px solid var(--pres-example-accent); font-style: italic; color: var(--pres-example-text); }
|
|
179
|
+
.code-block { background: var(--defaultStateBackground); border: 1px solid var(--neutralLight); border-radius: var(--pres-card-radius); padding: 12px; font-family: var(--fontFamilyMonospace, 'Courier New', monospace); font-size: 13px; color: var(--pres-code-color); margin-top: 12px; overflow-x: auto; }
|
|
180
|
+
.code-keyword { color: var(--pres-code-keyword); }
|
|
181
|
+
.code-string { color: var(--pres-code-string); }
|
|
182
|
+
.final-cta { margin-top: 20px; padding: 20px; background: var(--pres-card-bg); border-radius: var(--pres-card-radius); border: var(--pres-card-border); text-align: center; opacity: 0; animation: finalIn .8s var(--pres-ease-enter) forwards; }
|
|
183
|
+
.final-cta p { font-size: 15px; color: var(--pres-body-color); line-height: 1.6; }
|
|
184
|
+
.final-cta p:first-child { color: var(--pres-highlight-color); font-weight: 600; margin-bottom: 8px; }
|
|
185
|
+
.two-column { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
|
|
186
|
+
.column-title { font-size: 15px; font-weight: 600; color: var(--pres-highlight-color); margin-bottom: 8px; }
|
|
187
|
+
.slide-title { font-size: 28px; font-weight: 700; color: var(--pres-title-color); margin-bottom: 20px; text-align: center; }
|
|
188
|
+
.slide-subtitle { font-size: 16px; color: var(--pres-subtitle-color); text-align: center; margin-bottom: 16px; font-style: italic; }
|
|
189
|
+
.slide-content { font-size: 16px; line-height: 1.8; color: var(--pres-body-color); }
|
|
190
|
+
.highlight { color: var(--pres-highlight-color); font-weight: 600; }
|
|
191
|
+
.slide-content li::before { content: '\25B8'; position: absolute; left: 0; color: var(--pres-highlight-color); font-size: 16px; }
|
|
192
|
+
/* ── Presentation Design Tokens (light defaults) ── */
|
|
193
|
+
:root {
|
|
194
|
+
/* Surface & Depth */
|
|
195
|
+
--pres-slide-bg: var(--defaultStateBackground);
|
|
196
|
+
--pres-slide-border: none;
|
|
197
|
+
--pres-slide-shadow: var(--elevation16);
|
|
198
|
+
--pres-slide-glow: transparent;
|
|
199
|
+
--pres-slide-radius: var(--roundedCorner6);
|
|
200
|
+
--pres-controlbar-bg: var(--defaultStateBackground);
|
|
201
|
+
--pres-controlbar-border: 1px solid var(--neutralLight);
|
|
202
|
+
--pres-controlbar-shadow: var(--elevation4);
|
|
203
|
+
/* Card / Box */
|
|
204
|
+
--pres-card-bg: var(--themeLighter);
|
|
205
|
+
--pres-card-border: 1px solid var(--neutralLight);
|
|
206
|
+
--pres-card-shadow: none;
|
|
207
|
+
--pres-card-radius: var(--roundedCorner6);
|
|
208
|
+
/* Callout — Note */
|
|
209
|
+
--pres-note-bg: var(--themeLighterAlt);
|
|
210
|
+
--pres-note-accent: var(--themeTertiary);
|
|
211
|
+
--pres-note-text: var(--bodySubtext);
|
|
212
|
+
/* Callout — Example */
|
|
213
|
+
--pres-example-bg: var(--themeLighterAlt);
|
|
214
|
+
--pres-example-accent: var(--themePrimary);
|
|
215
|
+
--pres-example-text: var(--bodySubtext);
|
|
216
|
+
/* Callout — Tip */
|
|
217
|
+
--pres-tip-bg: var(--successBackground);
|
|
218
|
+
--pres-tip-accent: var(--green);
|
|
219
|
+
--pres-tip-text: var(--bodySubtext);
|
|
220
|
+
/* Callout — Warning */
|
|
221
|
+
--pres-warning-bg: var(--warningBackground);
|
|
222
|
+
--pres-warning-accent: var(--orange);
|
|
223
|
+
--pres-warning-text: var(--bodySubtext);
|
|
224
|
+
/* Callout — Quote */
|
|
225
|
+
--pres-quote-bg: var(--themeLighterAlt);
|
|
226
|
+
--pres-quote-accent: var(--themeTertiary);
|
|
227
|
+
--pres-quote-text: var(--bodySubtext);
|
|
228
|
+
/* Disabled State */
|
|
229
|
+
--pres-btn-disabled-bg: var(--themeLighter);
|
|
230
|
+
--pres-btn-disabled-text: var(--themeTertiary);
|
|
231
|
+
--pres-btn-disabled-border: var(--themeLight);
|
|
232
|
+
--pres-btn-disabled-opacity: 1;
|
|
233
|
+
/* Typography */
|
|
234
|
+
--pres-title-color: var(--themePrimary);
|
|
235
|
+
--pres-subtitle-color: var(--bodySubtext);
|
|
236
|
+
--pres-body-color: var(--bodyText);
|
|
237
|
+
--pres-highlight-color: var(--themeTertiary);
|
|
238
|
+
--pres-muted-color: var(--bodySubtext);
|
|
239
|
+
--pres-code-color: var(--bodySubtext);
|
|
240
|
+
--pres-code-keyword: var(--themeTertiary);
|
|
241
|
+
--pres-code-string: var(--green);
|
|
242
|
+
/* Animation & Motion */
|
|
243
|
+
--pres-ease-enter: var(--easeDecelerate, cubic-bezier(0, 0, 0, 1));
|
|
244
|
+
--pres-ease-exit: var(--easeAccelerate, cubic-bezier(1, 0, 1, 1));
|
|
245
|
+
--pres-ease-move: var(--easeStandard, cubic-bezier(0.8, 0, 0.2, 1));
|
|
246
|
+
--pres-ease-subtle: ease;
|
|
247
|
+
--pres-duration-fast: var(--duration1, 100ms);
|
|
248
|
+
--pres-duration-normal: var(--duration3, 300ms);
|
|
249
|
+
--pres-duration-slow: var(--duration4, 400ms);
|
|
250
|
+
--pres-stagger-delay: 100ms;
|
|
251
|
+
/* Chart & Data Viz */
|
|
252
|
+
--pres-chart-1: var(--themePrimary);
|
|
253
|
+
--pres-chart-2: var(--themeTertiary);
|
|
254
|
+
--pres-chart-3: var(--themeSecondary);
|
|
255
|
+
--pres-chart-4: var(--orange);
|
|
256
|
+
--pres-chart-5: var(--green);
|
|
257
|
+
--pres-chart-6: var(--red);
|
|
258
|
+
--pres-chart-axis: var(--bodySubtext);
|
|
259
|
+
--pres-chart-grid: var(--neutralLight);
|
|
260
|
+
--pres-chart-label: var(--bodyText);
|
|
261
|
+
}
|
|
262
|
+
/* ── Presentation Design Tokens (dark overrides) ── */
|
|
263
|
+
.theme-dark {
|
|
264
|
+
/* Surface & Depth — stronger brand border + dramatic glow (à la Star Trek deck) */
|
|
265
|
+
--pres-slide-border: 1px solid rgba(from var(--themePrimary) r g b / 0.30);
|
|
266
|
+
--pres-slide-shadow: 0 10px 40px rgba(0,0,0,.5), 0 0 30px rgba(from var(--themePrimary) r g b / 0.20);
|
|
267
|
+
--pres-slide-glow: rgba(from var(--themePrimary) r g b / 0.20);
|
|
268
|
+
--pres-slide-radius: 20px;
|
|
269
|
+
--pres-controlbar-bg: rgba(15, 15, 25, 0.80);
|
|
270
|
+
--pres-controlbar-border: 1px solid rgba(from var(--themePrimary) r g b / 0.30);
|
|
271
|
+
--pres-controlbar-shadow: 0 4px 20px rgba(0,0,0,.4);
|
|
272
|
+
/* Card / Box — gradient brand tint for depth */
|
|
273
|
+
--pres-card-bg: linear-gradient(135deg, rgba(from var(--themePrimary) r g b / 0.15) 0%, rgba(from var(--themePrimary) r g b / 0.08) 100%);
|
|
274
|
+
--pres-card-border: 1px solid rgba(from var(--themePrimary) r g b / 0.30);
|
|
275
|
+
--pres-card-shadow: 0 2px 12px rgba(0,0,0,.3), 0 0 8px rgba(from var(--themePrimary) r g b / 0.05);
|
|
276
|
+
--pres-card-radius: 12px;
|
|
277
|
+
/* Callout backgrounds — translucent tints matching accent */
|
|
278
|
+
--pres-note-bg: rgba(from var(--themeTertiary) r g b / 0.12);
|
|
279
|
+
--pres-note-text: var(--bodyText);
|
|
280
|
+
--pres-example-bg: rgba(from var(--themePrimary) r g b / 0.12);
|
|
281
|
+
--pres-example-text: var(--bodyText);
|
|
282
|
+
--pres-tip-bg: rgba(from var(--green) r g b / 0.12);
|
|
283
|
+
--pres-tip-text: var(--bodyText);
|
|
284
|
+
--pres-warning-bg: rgba(from var(--orange) r g b / 0.12);
|
|
285
|
+
--pres-warning-text: var(--bodyText);
|
|
286
|
+
--pres-quote-bg: rgba(from var(--themePrimary) r g b / 0.10);
|
|
287
|
+
--pres-quote-text: var(--bodyText);
|
|
288
|
+
/* Disabled State */
|
|
289
|
+
--pres-btn-disabled-bg: var(--themeDarker);
|
|
290
|
+
--pres-btn-disabled-text: var(--themeTertiary);
|
|
291
|
+
--pres-btn-disabled-border: var(--themeDark);
|
|
292
|
+
/* Chart (brighter accents for dark bg) */
|
|
293
|
+
--pres-chart-4: var(--orangeLight);
|
|
294
|
+
--pres-chart-5: var(--greenLight);
|
|
295
|
+
--pres-chart-6: var(--redLight);
|
|
296
|
+
--pres-chart-grid: var(--neutralSecondary);
|
|
297
|
+
}
|
|
298
|
+
</style>
|
|
299
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/14.1.1/marked.min.js"></script>
|
|
300
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
|
|
301
|
+
<script id="page-info" src="/api/page-info.js?page=presentation-builder"></script>
|
|
302
|
+
</head>
|
|
303
|
+
<body>
|
|
304
|
+
<div class="viewer-panel" id="viewerPanel" data-locked="true">
|
|
305
|
+
<div class="tts-indicator" id="ttsIndicator" data-locked="true">
|
|
306
|
+
<div class="tts-wave"><span></span><span></span><span></span><span></span></div>
|
|
307
|
+
<span>Speaking...</span>
|
|
308
|
+
</div>
|
|
309
|
+
|
|
310
|
+
<div class="presentation-container" id="presentationContainer">
|
|
311
|
+
<div class="slide-container">
|
|
312
|
+
<div class="slide active" id="slide1" data-notes="Welcome to Presentation Builder! This quick tutorial will show you how to create stunning presentations in minutes. Just describe your topic in the chat, and I'll generate a complete deck with animations, speaker notes, and voice narration. Let's get started!">
|
|
313
|
+
<div class="slide-title">Presentation Builder</div>
|
|
314
|
+
<div class="slide-subtitle">Create Beautiful Presentations with AI</div>
|
|
315
|
+
<div class="slide-content" style="text-align:center;margin-top:16px">
|
|
316
|
+
<div class="quote-block">"Describe your topic. Get a stunning presentation."</div>
|
|
317
|
+
<p style="margin-top:16px;font-size:13px;color:var(--bodySubtext)">A Quick Tutorial</p>
|
|
318
|
+
</div>
|
|
319
|
+
</div>
|
|
320
|
+
<div class="slide" id="slide2" data-notes="Using the builder is simple. Type your presentation topic in the chat panel on the left. Be as specific as you like. Include your target audience, the number of slides you want, or any specific points to cover. The more detail you provide, the better your presentation will be.">
|
|
321
|
+
<div class="slide-title">How It Works</div>
|
|
322
|
+
<div class="slide-content">
|
|
323
|
+
<div class="steps-container">
|
|
324
|
+
<div class="step"><div class="step-number">1</div><div class="step-title">Describe</div><div class="step-desc">Type your topic in the chat</div></div>
|
|
325
|
+
<div class="step"><div class="step-number">2</div><div class="step-title">Generate</div><div class="step-desc">AI creates your deck</div></div>
|
|
326
|
+
<div class="step"><div class="step-number">3</div><div class="step-title">Present</div><div class="step-desc">Click Present to go fullscreen</div></div>
|
|
327
|
+
<div class="step"><div class="step-number">4</div><div class="step-title">Refine</div><div class="step-desc">Ask for changes anytime</div></div>
|
|
328
|
+
</div>
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
<div class="slide" id="slide3" data-notes="Every presentation comes packed with features. Smooth animations bring your content to life. Speaker notes are generated for each slide to guide your delivery. Text-to-speech narration reads your notes aloud in presentation mode. And keyboard shortcuts let you navigate effortlessly.">
|
|
332
|
+
<div class="slide-title">Built-In Features</div>
|
|
333
|
+
<div class="slide-content">
|
|
334
|
+
<div class="feature-grid">
|
|
335
|
+
<div class="feature-box"><div class="feature-icon">✨</div><div class="feature-title">Smooth Animations</div><div class="feature-desc">Content animates in beautifully</div></div>
|
|
336
|
+
<div class="feature-box"><div class="feature-icon">📝</div><div class="feature-title">Speaker Notes</div><div class="feature-desc">Detailed notes for each slide</div></div>
|
|
337
|
+
<div class="feature-box"><div class="feature-icon">🔊</div><div class="feature-title">Voice Narration</div><div class="feature-desc">TTS reads notes in present mode</div></div>
|
|
338
|
+
<div class="feature-box"><div class="feature-icon">⌨</div><div class="feature-title">Keyboard Shortcuts</div><div class="feature-desc">Arrow keys, Space, F, Escape</div></div>
|
|
339
|
+
</div>
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
<div class="slide" id="slide4" data-notes="Here are some tips for great prompts. Mention your audience so the tone matches. Specify the number of slides if you have a preference. Include key points you want covered. Ask for comparisons, pros and cons, or specific visualizations. The more context you give, the better the result.">
|
|
343
|
+
<div class="slide-title">Tips for Great Prompts</div>
|
|
344
|
+
<div class="slide-content">
|
|
345
|
+
<ul>
|
|
346
|
+
<li>Mention your <span class="highlight">target audience</span></li>
|
|
347
|
+
<li>Specify the <span class="highlight">number of slides</span> if needed</li>
|
|
348
|
+
<li>Include <span class="highlight">key points</span> to cover</li>
|
|
349
|
+
<li>Request specific <span class="highlight">visualizations</span></li>
|
|
350
|
+
<li>Ask for <span class="highlight">comparisons</span> or analysis</li>
|
|
351
|
+
</ul>
|
|
352
|
+
<div class="pres-example"><strong>Example:</strong> "Create a 10-slide presentation on machine learning for business executives, covering ROI, use cases, and implementation challenges."</div>
|
|
353
|
+
</div>
|
|
354
|
+
</div>
|
|
355
|
+
<div class="slide" id="slide5" data-notes="You're all set! Head back to the chat and describe the presentation you want to create. I'll generate a complete deck with all the styling, animations, and features you've just seen. Let's build something amazing together!">
|
|
356
|
+
<div class="slide-title">Ready to Build!</div>
|
|
357
|
+
<div class="slide-content" style="text-align:center">
|
|
358
|
+
<div class="quote-block">"The best presentations tell a story. Let's tell yours."</div>
|
|
359
|
+
<div class="final-cta">
|
|
360
|
+
<p>Head to the chat panel</p>
|
|
361
|
+
<p><em>Describe your presentation topic and watch the magic happen!</em></p>
|
|
362
|
+
</div>
|
|
363
|
+
</div>
|
|
364
|
+
</div>
|
|
365
|
+
</div>
|
|
366
|
+
|
|
367
|
+
<div class="presentation-controls" id="controls" data-locked="true">
|
|
368
|
+
<button class="control-btn" id="prevBtn" disabled>← Previous</button>
|
|
369
|
+
<span class="slide-counter" id="slideCounter">1 / 5</span>
|
|
370
|
+
<button class="control-btn" id="nextBtn">Next →</button>
|
|
371
|
+
<button class="control-btn pause-btn" id="pauseBtn" style="display:none">⏸ Pause</button>
|
|
372
|
+
<button class="control-btn present-btn" id="presentBtn">🖥 Present</button>
|
|
373
|
+
<button class="control-btn" id="notesBtn" title="Speaker Notes">📝 Notes</button>
|
|
374
|
+
<button class="control-btn" id="voiceSettingsBtn" title="Voice Settings"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:middle"><path d="M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z"/></svg></button>
|
|
375
|
+
|
|
376
|
+
<!-- Voice picker popup -->
|
|
377
|
+
<div class="voice-picker" id="voicePicker">
|
|
378
|
+
<div class="voice-picker-header">Voice</div>
|
|
379
|
+
<div class="voice-picker-list" id="voicePickerList">
|
|
380
|
+
<div class="voice-picker-empty">Loading voices...</div>
|
|
381
|
+
</div>
|
|
382
|
+
<div class="voice-picker-footer">
|
|
383
|
+
<button class="voice-picker-clear" id="voicePickerClearBtn">Disable TTS</button>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
387
|
+
</div>
|
|
388
|
+
|
|
389
|
+
<!-- Speaker Notes Modal -->
|
|
390
|
+
<div class="notes-modal-overlay" id="notesModalOverlay" data-light-dismiss>
|
|
391
|
+
<div class="notes-modal" id="notesModal">
|
|
392
|
+
<div class="notes-modal-header" id="notesModalTitle">Speaker Notes — Slide 1</div>
|
|
393
|
+
<div class="notes-modal-body">
|
|
394
|
+
<textarea id="notesTextarea" placeholder="Enter speaker notes for this slide..."></textarea>
|
|
395
|
+
</div>
|
|
396
|
+
<div class="notes-modal-footer">
|
|
397
|
+
<button class="flm-button" id="notesCancelBtn">Cancel</button>
|
|
398
|
+
<button class="flm-button flm-button--primary" id="notesSaveBtn">Save</button>
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
401
|
+
</div>
|
|
402
|
+
|
|
403
|
+
</div>
|
|
404
|
+
|
|
405
|
+
<div id="instructions" style="display: none;" data-locked="true">You are the Presentation Builder assistant. When the user describes a topic, generate a full slide deck as HTML.
|
|
406
|
+
|
|
407
|
+
IMPORTANT RULES:
|
|
408
|
+
1. Replace ALL slide content inside .slide-container ONLY. Remove existing slides and create new ones. Do NOT touch or remove the .presentation-controls div — it contains the navigation buttons (Previous, Next, Present, Voice, Pause) and must remain intact.
|
|
409
|
+
2. Each slide must follow this structure:
|
|
410
|
+
<div class="slide" id="slideN" data-notes="Speaker notes for TTS narration...">
|
|
411
|
+
<div class="slide-title">Title</div>
|
|
412
|
+
<div class="slide-content">...content...</div>
|
|
413
|
+
</div>
|
|
414
|
+
3. The FIRST slide must have class "slide active", all others just "slide".
|
|
415
|
+
4. Slide IDs must be sequential: slide1, slide2, slide3, etc.
|
|
416
|
+
5. Every slide MUST have a data-notes attribute with 2-3 sentences of speaker notes.
|
|
417
|
+
6. After the slides, update the slide counter: document.getElementById('slideCounter').textContent = '1 / N';
|
|
418
|
+
7. Also update the JS variable: totalSlides = N; currentSlide = 1;
|
|
419
|
+
|
|
420
|
+
AVAILABLE CSS CLASSES for slide content:
|
|
421
|
+
|
|
422
|
+
CALLOUTS: .pres-note, .pres-example, .pres-tip, .pres-warning, .pres-quote
|
|
423
|
+
CARDS: .pres-card, .pres-card-grid, .pres-card-title, .pres-card-desc, .pres-card-icon
|
|
424
|
+
STEPS: .pres-steps, .pres-step, .pres-step-number, .pres-step-title, .pres-step-desc
|
|
425
|
+
CODE: .pres-code, .pres-code-keyword, .pres-code-string
|
|
426
|
+
DATA: .pres-chart, .pres-table, .pres-stat, .pres-stat-value, .pres-stat-label
|
|
427
|
+
LAYOUT: .pres-two-col, .pres-three-col, .pres-center, .pres-spacer
|
|
428
|
+
ANIMATION: .pres-fade-in, .pres-slide-in, .pres-scale-in, .pres-step-in, .pres-stagger
|
|
429
|
+
TYPOGRAPHY: .highlight (inline emphasis)
|
|
430
|
+
|
|
431
|
+
STRUCTURE:
|
|
432
|
+
- .slide-title — themed heading
|
|
433
|
+
- .slide-subtitle — italic subheading
|
|
434
|
+
- .slide-content — main content area
|
|
435
|
+
- .final-cta — call-to-action box
|
|
436
|
+
- ul/li — animated bullet list (auto-animated in active slide)
|
|
437
|
+
|
|
438
|
+
LEGACY (still work but prefer new names above):
|
|
439
|
+
- .tip-box → .pres-tip
|
|
440
|
+
- .quote-block → .pres-quote
|
|
441
|
+
- .feature-grid/.feature-box → .pres-card-grid/.pres-card
|
|
442
|
+
- .steps-container/.step → .pres-steps/.pres-step
|
|
443
|
+
- .network-boxes/.network-box → .pres-card-grid/.pres-card
|
|
444
|
+
- .two-column → .pres-two-col
|
|
445
|
+
- .code-block → .pres-code
|
|
446
|
+
- .slide-example → .pres-example
|
|
447
|
+
|
|
448
|
+
All styles are theme-aware. Do not use inline styles for colors or backgrounds.
|
|
449
|
+
|
|
450
|
+
CONTENT TIPS:
|
|
451
|
+
- Use 5-10 slides for most topics
|
|
452
|
+
- Mix different layouts across slides for visual variety
|
|
453
|
+
- Include a title slide and a conclusion/CTA slide
|
|
454
|
+
- Make speaker notes conversational — they will be read aloud by TTS
|
|
455
|
+
- Use emojis in feature-icon divs for visual appeal
|
|
456
|
+
- Keep bullet points concise (under 10 words each)
|
|
457
|
+
|
|
458
|
+
Use theme tokens (avoid the natural pallet) unless the user requests an alternative color.</div>
|
|
459
|
+
<div id="thoughts" style="display: none;" data-locked="true"></div>
|
|
460
|
+
|
|
461
|
+
<script id="presentation-logic">
|
|
462
|
+
/* ── Dark mode detection ── */
|
|
463
|
+
if (window.__themeInfo?.isDark) document.documentElement.classList.add('theme-dark');
|
|
464
|
+
|
|
465
|
+
/* ── State ── */
|
|
466
|
+
const MIN_SLIDE_DURATION = 10000;
|
|
467
|
+
const TRANSITION_PAUSE = 2000;
|
|
468
|
+
const TTS_TIMEOUT = 30000; // 30s max wait for TTS generation
|
|
469
|
+
let currentSlide = 1;
|
|
470
|
+
let totalSlides = 5;
|
|
471
|
+
let isFullscreen = false;
|
|
472
|
+
let isPaused = false;
|
|
473
|
+
let audioElement = new Audio();
|
|
474
|
+
let audioSlideIdx = -1;
|
|
475
|
+
let resolveAudioPlay = null;
|
|
476
|
+
let readSlides = new Set();
|
|
477
|
+
let audioCache = {};
|
|
478
|
+
let slideStartTime = null;
|
|
479
|
+
let advanceGeneration = 0;
|
|
480
|
+
|
|
481
|
+
/* ── Voice settings state ── */
|
|
482
|
+
let selectedVoiceId = '';
|
|
483
|
+
let selectedVoiceName = '';
|
|
484
|
+
let selectedModel = 'eleven_turbo_v2_5';
|
|
485
|
+
let selectedStability = 0.5;
|
|
486
|
+
let selectedSimilarity = 0.75;
|
|
487
|
+
let selectedStyle = 0;
|
|
488
|
+
let configuredVoices = [];
|
|
489
|
+
|
|
490
|
+
const DATA_TABLE = 'presentation_voice';
|
|
491
|
+
const DATA_ID = 'voice_config';
|
|
492
|
+
const SHARED_VOICES_TABLE = 'elevenlabs_voices';
|
|
493
|
+
|
|
494
|
+
/* ── Focus management ── */
|
|
495
|
+
document.getElementById('viewerPanel').addEventListener('mousedown', function(e) {
|
|
496
|
+
if (e.target.closest('.presentation-controls') || e.target.closest('.voice-picker')) return;
|
|
497
|
+
e.preventDefault();
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
/* ── Utility: timeout-wrapped promise ── */
|
|
501
|
+
function withTimeout(promise, ms) {
|
|
502
|
+
return Promise.race([
|
|
503
|
+
promise,
|
|
504
|
+
new Promise(function(resolve) { setTimeout(function() { resolve(null); }, ms); })
|
|
505
|
+
]);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/* ── TTS via ElevenLabs connector ── */
|
|
509
|
+
async function generateAudio(text) {
|
|
510
|
+
if (!selectedVoiceId) return null;
|
|
511
|
+
try {
|
|
512
|
+
var res = await withTimeout(fetch('/api/connectors', {
|
|
513
|
+
method: 'POST',
|
|
514
|
+
headers: { 'Content-Type': 'application/json' },
|
|
515
|
+
body: JSON.stringify({
|
|
516
|
+
connector: 'elevenlabs',
|
|
517
|
+
method: 'POST',
|
|
518
|
+
path: '/v1/text-to-speech/' + selectedVoiceId,
|
|
519
|
+
headers: { 'Accept': 'audio/mpeg' },
|
|
520
|
+
body: {
|
|
521
|
+
text: text,
|
|
522
|
+
model_id: selectedModel,
|
|
523
|
+
voice_settings: {
|
|
524
|
+
stability: selectedStability,
|
|
525
|
+
similarity_boost: selectedSimilarity,
|
|
526
|
+
style: selectedStyle,
|
|
527
|
+
use_speaker_boost: true
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
})
|
|
531
|
+
}), TTS_TIMEOUT);
|
|
532
|
+
if (!res || !res.ok) return null;
|
|
533
|
+
var blob = await res.blob();
|
|
534
|
+
if (!blob || blob.size < 1000) return null; // too small = likely error
|
|
535
|
+
return URL.createObjectURL(blob);
|
|
536
|
+
} catch (e) {
|
|
537
|
+
console.error('TTS error:', e);
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function showTTSIndicator(show) {
|
|
543
|
+
document.getElementById('ttsIndicator').classList.toggle('active', show);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
async function preGenerateNext(idx) {
|
|
547
|
+
var next = idx + 1;
|
|
548
|
+
if (next > totalSlides || audioCache[next]) return;
|
|
549
|
+
var slide = document.getElementById('slide' + next);
|
|
550
|
+
var notes = slide ? slide.dataset.notes : '';
|
|
551
|
+
if (notes) {
|
|
552
|
+
try {
|
|
553
|
+
var url = await generateAudio(notes);
|
|
554
|
+
if (url) audioCache[next] = url;
|
|
555
|
+
} catch (e) { /* ignore pre-gen failures */ }
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function playAudio(url, gen, slideIdx) {
|
|
560
|
+
return new Promise(resolve => {
|
|
561
|
+
if (gen !== advanceGeneration) { resolve(); return; }
|
|
562
|
+
if (resolveAudioPlay) { resolveAudioPlay(); resolveAudioPlay = null; }
|
|
563
|
+
resolveAudioPlay = resolve;
|
|
564
|
+
audioSlideIdx = slideIdx;
|
|
565
|
+
audioElement.onended = () => { audioSlideIdx = -1; resolveAudioPlay = null; resolve(); };
|
|
566
|
+
audioElement.onerror = () => { audioSlideIdx = -1; resolveAudioPlay = null; resolve(); };
|
|
567
|
+
audioElement.src = url;
|
|
568
|
+
audioElement.play().then(() => {
|
|
569
|
+
if (gen !== advanceGeneration) { audioElement.pause(); resolveAudioPlay = null; resolve(); }
|
|
570
|
+
}).catch(() => { resolveAudioPlay = null; resolve(); });
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
575
|
+
|
|
576
|
+
async function speakAndAdvance(idx) {
|
|
577
|
+
var gen = advanceGeneration;
|
|
578
|
+
if (!isFullscreen || isPaused || readSlides.has(idx)) return;
|
|
579
|
+
slideStartTime = Date.now();
|
|
580
|
+
var slide = document.getElementById('slide' + idx);
|
|
581
|
+
var notes = slide ? slide.dataset.notes : '';
|
|
582
|
+
|
|
583
|
+
if (notes && selectedVoiceId) {
|
|
584
|
+
showTTSIndicator(true);
|
|
585
|
+
|
|
586
|
+
var audioUrl = audioCache[idx] || null;
|
|
587
|
+
if (!audioUrl) {
|
|
588
|
+
try {
|
|
589
|
+
audioUrl = await generateAudio(notes);
|
|
590
|
+
} catch (e) {
|
|
591
|
+
console.error('TTS generation failed for slide ' + idx, e);
|
|
592
|
+
audioUrl = null;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (gen !== advanceGeneration) { showTTSIndicator(false); return; }
|
|
596
|
+
|
|
597
|
+
if (audioUrl) {
|
|
598
|
+
// Start pre-generating next slide audio in background
|
|
599
|
+
preGenerateNext(idx);
|
|
600
|
+
if (currentSlide !== idx) { showTTSIndicator(false); return; }
|
|
601
|
+
await playAudio(audioUrl, gen, idx);
|
|
602
|
+
if (gen !== advanceGeneration) { showTTSIndicator(false); return; }
|
|
603
|
+
if (audioCache[idx]) { URL.revokeObjectURL(audioCache[idx]); delete audioCache[idx]; }
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
showTTSIndicator(false);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Mark slide as read regardless of TTS success
|
|
610
|
+
readSlides.add(idx);
|
|
611
|
+
|
|
612
|
+
// Wait for transition pause
|
|
613
|
+
await sleep(TRANSITION_PAUSE);
|
|
614
|
+
if (gen !== advanceGeneration) return;
|
|
615
|
+
|
|
616
|
+
// Ensure minimum time on slide
|
|
617
|
+
await waitForMinDuration();
|
|
618
|
+
if (gen !== advanceGeneration) return;
|
|
619
|
+
|
|
620
|
+
// Check we're still in presentation mode and not paused
|
|
621
|
+
if (!isPaused && isFullscreen && idx < totalSlides && idx === currentSlide) {
|
|
622
|
+
currentSlide++;
|
|
623
|
+
updateSlideDisplay();
|
|
624
|
+
speakAndAdvance(currentSlide);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
async function waitForMinDuration() {
|
|
629
|
+
if (!slideStartTime) return;
|
|
630
|
+
var elapsed = Date.now() - slideStartTime;
|
|
631
|
+
var remaining = MIN_SLIDE_DURATION - elapsed;
|
|
632
|
+
if (remaining > 0) await sleep(remaining);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/* ── Slide navigation ── */
|
|
636
|
+
function updateSlideDisplay() {
|
|
637
|
+
document.querySelectorAll('.slide').forEach(s => s.classList.remove('active'));
|
|
638
|
+
const el = document.getElementById('slide' + currentSlide);
|
|
639
|
+
if (el) el.classList.add('active');
|
|
640
|
+
document.getElementById('slideCounter').textContent = currentSlide + ' / ' + totalSlides;
|
|
641
|
+
document.getElementById('prevBtn').disabled = currentSlide === 1;
|
|
642
|
+
document.getElementById('nextBtn').disabled = currentSlide === totalSlides;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function updateSlide() {
|
|
646
|
+
updateSlideDisplay();
|
|
647
|
+
if (isFullscreen && !readSlides.has(currentSlide)) speakAndAdvance(currentSlide);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function stopCurrentAudio() {
|
|
651
|
+
advanceGeneration++;
|
|
652
|
+
audioElement.pause();
|
|
653
|
+
audioElement.onended = null;
|
|
654
|
+
audioElement.onerror = null;
|
|
655
|
+
audioElement.removeAttribute('src');
|
|
656
|
+
try { audioElement.load(); } catch(e) {}
|
|
657
|
+
audioSlideIdx = -1;
|
|
658
|
+
if (resolveAudioPlay) { resolveAudioPlay(); resolveAudioPlay = null; }
|
|
659
|
+
showTTSIndicator(false);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function nextSlide() { if (currentSlide < totalSlides) { stopCurrentAudio(); currentSlide++; updateSlide(); } }
|
|
663
|
+
function prevSlide() { if (currentSlide > 1) { stopCurrentAudio(); currentSlide--; updateSlideDisplay(); } }
|
|
664
|
+
|
|
665
|
+
function togglePause() {
|
|
666
|
+
isPaused = !isPaused;
|
|
667
|
+
var btn = document.getElementById('pauseBtn');
|
|
668
|
+
btn.textContent = isPaused ? '\u25B6 Resume' : '\u23F8 Pause';
|
|
669
|
+
if (isPaused) {
|
|
670
|
+
audioElement.pause();
|
|
671
|
+
} else {
|
|
672
|
+
if (audioSlideIdx === currentSlide && audioElement.src) {
|
|
673
|
+
audioElement.play();
|
|
674
|
+
} else if (!readSlides.has(currentSlide)) {
|
|
675
|
+
speakAndAdvance(currentSlide);
|
|
676
|
+
} else if (currentSlide < totalSlides) {
|
|
677
|
+
// If current slide was already read, advance to next
|
|
678
|
+
currentSlide++;
|
|
679
|
+
updateSlideDisplay();
|
|
680
|
+
speakAndAdvance(currentSlide);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function toggleFullscreen() {
|
|
686
|
+
var container = document.getElementById('presentationContainer');
|
|
687
|
+
var btn = document.getElementById('presentBtn');
|
|
688
|
+
var pauseBtn = document.getElementById('pauseBtn');
|
|
689
|
+
if (isFullscreen) {
|
|
690
|
+
container.classList.remove('fullscreen-mode');
|
|
691
|
+
btn.textContent = '\u{1F5A5} Present';
|
|
692
|
+
btn.classList.add('present-btn');
|
|
693
|
+
pauseBtn.style.display = 'none';
|
|
694
|
+
isFullscreen = false;
|
|
695
|
+
stopCurrentAudio();
|
|
696
|
+
isPaused = false;
|
|
697
|
+
if (controlsHideTimer) clearTimeout(controlsHideTimer);
|
|
698
|
+
document.getElementById('controls').classList.remove('controls-hidden');
|
|
699
|
+
if (window.synthos && window.synthos.shell && window.synthos.shell.openBuilder) {
|
|
700
|
+
window.synthos.shell.openBuilder();
|
|
701
|
+
}
|
|
702
|
+
} else {
|
|
703
|
+
if (window.synthos && window.synthos.shell && window.synthos.shell.closeBuilder) {
|
|
704
|
+
window.synthos.shell.closeBuilder();
|
|
705
|
+
}
|
|
706
|
+
container.classList.add('fullscreen-mode');
|
|
707
|
+
btn.textContent = '\u2715 Exit';
|
|
708
|
+
btn.classList.remove('present-btn');
|
|
709
|
+
pauseBtn.style.display = 'inline-block';
|
|
710
|
+
isFullscreen = true;
|
|
711
|
+
isPaused = false;
|
|
712
|
+
pauseBtn.textContent = '\u23F8 Pause';
|
|
713
|
+
// Clear read slides so presentation starts fresh
|
|
714
|
+
readSlides.clear();
|
|
715
|
+
if (!readSlides.has(currentSlide)) speakAndAdvance(currentSlide);
|
|
716
|
+
resetControlsTimer();
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/* ── Fullscreen controls auto-hide ── */
|
|
721
|
+
let controlsHideTimer = null;
|
|
722
|
+
const CONTROLS_HIDE_DELAY = 2500;
|
|
723
|
+
const CONTROLS_SHOW_ZONE = 80; // px from bottom edge
|
|
724
|
+
|
|
725
|
+
function hideControls() {
|
|
726
|
+
if (!isFullscreen) return;
|
|
727
|
+
document.getElementById('controls').classList.add('controls-hidden');
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function showControls() {
|
|
731
|
+
document.getElementById('controls').classList.remove('controls-hidden');
|
|
732
|
+
resetControlsTimer();
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function resetControlsTimer() {
|
|
736
|
+
if (controlsHideTimer) clearTimeout(controlsHideTimer);
|
|
737
|
+
if (!isFullscreen) return;
|
|
738
|
+
controlsHideTimer = setTimeout(hideControls, CONTROLS_HIDE_DELAY);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
document.addEventListener('mousemove', function(e) {
|
|
742
|
+
if (!isFullscreen) return;
|
|
743
|
+
if (e.clientY >= window.innerHeight - CONTROLS_SHOW_ZONE) {
|
|
744
|
+
showControls();
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
// In fullscreen, controls only appear when mouse enters the bottom zone
|
|
749
|
+
|
|
750
|
+
/* ── Keyboard shortcuts ── */
|
|
751
|
+
document.addEventListener('keydown', function(e) {
|
|
752
|
+
var tag = document.activeElement && document.activeElement.tagName;
|
|
753
|
+
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return;
|
|
754
|
+
if (document.activeElement && document.activeElement.isContentEditable) return;
|
|
755
|
+
if (document.getElementById('voicePicker').classList.contains('open')) return;
|
|
756
|
+
if (document.getElementById('notesModalOverlay').classList.contains('open')) return;
|
|
757
|
+
if (e.key === 'ArrowRight' || e.key === ' ') { e.preventDefault(); nextSlide(); }
|
|
758
|
+
if (e.key === 'ArrowLeft') { prevSlide(); }
|
|
759
|
+
if (e.key === 'Escape' && isFullscreen) { toggleFullscreen(); }
|
|
760
|
+
if (e.key === 'f' || e.key === 'F') { toggleFullscreen(); }
|
|
761
|
+
if ((e.key === 'p' || e.key === 'P') && isFullscreen) { togglePause(); }
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
/* ── Voice picker ── */
|
|
765
|
+
function toggleVoicePicker() {
|
|
766
|
+
var picker = document.getElementById('voicePicker');
|
|
767
|
+
if (picker.classList.contains('open')) {
|
|
768
|
+
picker.classList.remove('open');
|
|
769
|
+
} else {
|
|
770
|
+
picker.classList.add('open');
|
|
771
|
+
loadConfiguredVoices();
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Close picker when clicking outside
|
|
776
|
+
document.addEventListener('click', function(e) {
|
|
777
|
+
var picker = document.getElementById('voicePicker');
|
|
778
|
+
if (picker.classList.contains('open') && !e.target.closest('.voice-picker') && !e.target.closest('#voiceSettingsBtn')) {
|
|
779
|
+
picker.classList.remove('open');
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
async function loadConfiguredVoices() {
|
|
784
|
+
var list = document.getElementById('voicePickerList');
|
|
785
|
+
list.innerHTML = '<div class="voice-picker-empty">Loading voices...</div>';
|
|
786
|
+
|
|
787
|
+
try {
|
|
788
|
+
configuredVoices = await synthos.shared.data.list(SHARED_VOICES_TABLE);
|
|
789
|
+
renderVoicePicker();
|
|
790
|
+
} catch (e) {
|
|
791
|
+
list.innerHTML = '<div class="voice-picker-empty">Failed to load voices.</div>';
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function renderVoicePicker() {
|
|
796
|
+
var list = document.getElementById('voicePickerList');
|
|
797
|
+
|
|
798
|
+
if (!configuredVoices || configuredVoices.length === 0) {
|
|
799
|
+
list.innerHTML = '<div class="voice-picker-empty">No voices configured.<br><a href="/pages/elevenlabs_voice_studio">Set up voices in Voice Studio</a></div>';
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
list.innerHTML = configuredVoices.map(function(v) {
|
|
804
|
+
var isSelected = v.voiceId === selectedVoiceId;
|
|
805
|
+
return '<div class="voice-picker-item' + (isSelected ? ' selected' : '') + '" data-id="' + (v.id || '') + '" data-voice-id="' + (v.voiceId || '') + '" data-voice-name="' + (v.voiceName || v.name || '').replace(/"/g, '"') + '" data-model="' + (v.model || 'eleven_turbo_v2_5') + '" data-stability="' + (v.stability != null ? v.stability : 50) + '" data-similarity="' + (v.similarity != null ? v.similarity : 75) + '" data-style="' + (v.style != null ? v.style : 0) + '">' +
|
|
806
|
+
'<span class="voice-picker-item-name">' + (v.name || v.voiceName || 'Unnamed') + '</span>' +
|
|
807
|
+
'<span class="voice-picker-item-meta">' + (v.voiceName || '') + '</span>' +
|
|
808
|
+
'</div>';
|
|
809
|
+
}).join('');
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function selectVoiceFromPicker(el) {
|
|
813
|
+
selectedVoiceId = el.dataset.voiceId;
|
|
814
|
+
selectedVoiceName = el.dataset.voiceName;
|
|
815
|
+
selectedModel = el.dataset.model || 'eleven_turbo_v2_5';
|
|
816
|
+
selectedStability = (parseInt(el.dataset.stability, 10) || 50) / 100;
|
|
817
|
+
selectedSimilarity = (parseInt(el.dataset.similarity, 10) || 75) / 100;
|
|
818
|
+
selectedStyle = (parseInt(el.dataset.style, 10) || 0) / 100;
|
|
819
|
+
|
|
820
|
+
document.querySelectorAll('.voice-picker-item').forEach(function(v) { v.classList.remove('selected'); });
|
|
821
|
+
el.classList.add('selected');
|
|
822
|
+
saveVoiceConfig();
|
|
823
|
+
|
|
824
|
+
// Clear cached audio since voice changed
|
|
825
|
+
readSlides.clear();
|
|
826
|
+
Object.keys(audioCache).forEach(function(k) { URL.revokeObjectURL(audioCache[k]); delete audioCache[k]; });
|
|
827
|
+
|
|
828
|
+
// Close picker after selection
|
|
829
|
+
document.getElementById('voicePicker').classList.remove('open');
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function clearVoice() {
|
|
833
|
+
selectedVoiceId = '';
|
|
834
|
+
selectedVoiceName = '';
|
|
835
|
+
document.querySelectorAll('.voice-picker-item').forEach(function(v) { v.classList.remove('selected'); });
|
|
836
|
+
saveVoiceConfig();
|
|
837
|
+
readSlides.clear();
|
|
838
|
+
Object.keys(audioCache).forEach(function(k) { URL.revokeObjectURL(audioCache[k]); delete audioCache[k]; });
|
|
839
|
+
document.getElementById('voicePicker').classList.remove('open');
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/* ── Persist voice per page ── */
|
|
843
|
+
async function saveVoiceConfig() {
|
|
844
|
+
try {
|
|
845
|
+
await synthos.data.save(DATA_TABLE, {
|
|
846
|
+
id: DATA_ID,
|
|
847
|
+
voiceId: selectedVoiceId,
|
|
848
|
+
voiceName: selectedVoiceName,
|
|
849
|
+
model: selectedModel,
|
|
850
|
+
stability: selectedStability,
|
|
851
|
+
similarity: selectedSimilarity,
|
|
852
|
+
style: selectedStyle
|
|
853
|
+
});
|
|
854
|
+
} catch (e) {
|
|
855
|
+
console.error('Failed to save voice config:', e);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
async function loadVoiceConfig() {
|
|
860
|
+
try {
|
|
861
|
+
var saved = await synthos.data.get(DATA_TABLE, DATA_ID);
|
|
862
|
+
if (saved) {
|
|
863
|
+
selectedVoiceId = saved.voiceId || '';
|
|
864
|
+
selectedVoiceName = saved.voiceName || '';
|
|
865
|
+
selectedModel = saved.model || 'eleven_turbo_v2_5';
|
|
866
|
+
selectedStability = saved.stability != null ? saved.stability : 0.5;
|
|
867
|
+
selectedSimilarity = saved.similarity != null ? saved.similarity : 0.75;
|
|
868
|
+
selectedStyle = saved.style != null ? saved.style : 0;
|
|
869
|
+
}
|
|
870
|
+
} catch (e) {
|
|
871
|
+
// No saved config yet
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// Auto-enable TTS with first voice if ElevenLabs is configured but no voice selected
|
|
875
|
+
if (!selectedVoiceId) {
|
|
876
|
+
try {
|
|
877
|
+
var connectors = await synthos.connector.list({ id: 'elevenlabs' });
|
|
878
|
+
if (connectors && connectors.length > 0) {
|
|
879
|
+
var voices = await synthos.shared.data.list(SHARED_VOICES_TABLE);
|
|
880
|
+
if (voices && voices.length > 0) {
|
|
881
|
+
var first = voices[0];
|
|
882
|
+
selectedVoiceId = first.voiceId || '';
|
|
883
|
+
selectedVoiceName = first.voiceName || first.name || '';
|
|
884
|
+
selectedModel = first.model || 'eleven_turbo_v2_5';
|
|
885
|
+
selectedStability = (first.stability != null ? first.stability : 50) / 100;
|
|
886
|
+
selectedSimilarity = (first.similarity != null ? first.similarity : 75) / 100;
|
|
887
|
+
selectedStyle = (first.style != null ? first.style : 0) / 100;
|
|
888
|
+
saveVoiceConfig();
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
} catch (e) {
|
|
892
|
+
// ElevenLabs not available, TTS stays disabled
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
/* ── Speaker Notes Modal ── */
|
|
898
|
+
function openNotesModal() {
|
|
899
|
+
var slide = document.getElementById('slide' + currentSlide);
|
|
900
|
+
if (!slide) return;
|
|
901
|
+
document.getElementById('notesModalTitle').textContent = 'Speaker Notes \u2014 Slide ' + currentSlide;
|
|
902
|
+
document.getElementById('notesTextarea').value = slide.getAttribute('data-notes') || '';
|
|
903
|
+
document.getElementById('notesModalOverlay').classList.add('open');
|
|
904
|
+
document.getElementById('notesTextarea').focus();
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function closeNotesModal() {
|
|
908
|
+
document.getElementById('notesModalOverlay').classList.remove('open');
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function saveNotes() {
|
|
912
|
+
var slide = document.getElementById('slide' + currentSlide);
|
|
913
|
+
if (!slide) return;
|
|
914
|
+
var value = document.getElementById('notesTextarea').value;
|
|
915
|
+
var pageName = window.pageInfo ? window.pageInfo.name : window.location.pathname.replace(/^\//, '');
|
|
916
|
+
closeNotesModal();
|
|
917
|
+
var overlay = document.getElementById('loadingOverlay');
|
|
918
|
+
if (overlay) overlay.style.display = 'flex';
|
|
919
|
+
fetch('/' + encodeURIComponent(pageName) + '/patch', {
|
|
920
|
+
method: 'POST',
|
|
921
|
+
headers: { 'Content-Type': 'application/json' },
|
|
922
|
+
body: JSON.stringify({
|
|
923
|
+
operations: [{ op: 'set-attr', selector: '#slide' + currentSlide, attr: 'data-notes', value: value }],
|
|
924
|
+
message: 'Speaker notes for slide ' + currentSlide + ' updated'
|
|
925
|
+
})
|
|
926
|
+
}).then(function(r) {
|
|
927
|
+
if (!r.ok) throw new Error('Patch failed');
|
|
928
|
+
return r.json();
|
|
929
|
+
}).then(function(data) {
|
|
930
|
+
document.open();
|
|
931
|
+
document.write(data.html);
|
|
932
|
+
document.close();
|
|
933
|
+
}).catch(function(err) {
|
|
934
|
+
if (overlay) overlay.style.display = 'none';
|
|
935
|
+
console.error('Failed to save notes:', err);
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// Light dismiss — close modal when clicking overlay background
|
|
940
|
+
document.getElementById('notesModalOverlay').addEventListener('click', function(e) {
|
|
941
|
+
if (e.target === this) closeNotesModal();
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
// Ctrl+Enter to save, Escape to cancel in notes modal
|
|
945
|
+
document.addEventListener('keydown', function(e) {
|
|
946
|
+
var overlay = document.getElementById('notesModalOverlay');
|
|
947
|
+
if (!overlay || !overlay.classList.contains('open')) return;
|
|
948
|
+
if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); closeNotesModal(); }
|
|
949
|
+
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { e.preventDefault(); e.stopPropagation(); saveNotes(); }
|
|
950
|
+
}, true);
|
|
951
|
+
|
|
952
|
+
/* ── Wire up control handlers ── */
|
|
953
|
+
document.getElementById('prevBtn').addEventListener('click', prevSlide);
|
|
954
|
+
document.getElementById('nextBtn').addEventListener('click', nextSlide);
|
|
955
|
+
document.getElementById('pauseBtn').addEventListener('click', togglePause);
|
|
956
|
+
document.getElementById('presentBtn').addEventListener('click', toggleFullscreen);
|
|
957
|
+
document.getElementById('notesBtn').addEventListener('click', openNotesModal);
|
|
958
|
+
document.getElementById('voiceSettingsBtn').addEventListener('click', toggleVoicePicker);
|
|
959
|
+
document.getElementById('voicePickerClearBtn').addEventListener('click', clearVoice);
|
|
960
|
+
document.getElementById('notesCancelBtn').addEventListener('click', closeNotesModal);
|
|
961
|
+
document.getElementById('notesSaveBtn').addEventListener('click', saveNotes);
|
|
962
|
+
document.getElementById('voicePickerList').addEventListener('click', function(e) {
|
|
963
|
+
var item = e.target.closest('.voice-picker-item');
|
|
964
|
+
if (item) selectVoiceFromPicker(item);
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
/* ── Init ── */
|
|
968
|
+
loadVoiceConfig();
|
|
969
|
+
</script>
|
|
970
|
+
</body></html>
|