@jotx-labs/editor 2.4.193 → 2.4.204
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/dist/components/BlockMenu.d.ts.map +1 -1
- package/dist/components/BlockMenu.js +61 -7
- package/dist/components/BlockMenu.js.map +1 -1
- package/dist/components/ButtonNodeView.d.ts +1 -0
- package/dist/components/ButtonNodeView.d.ts.map +1 -1
- package/dist/components/ButtonNodeView.js +34 -13
- package/dist/components/ButtonNodeView.js.map +1 -1
- package/dist/components/ButtonStripNodeView.d.ts +13 -0
- package/dist/components/ButtonStripNodeView.d.ts.map +1 -0
- package/dist/components/ButtonStripNodeView.js +96 -0
- package/dist/components/ButtonStripNodeView.js.map +1 -0
- package/dist/components/ChartNodeView.d.ts.map +1 -1
- package/dist/components/ChartNodeView.js +504 -203
- package/dist/components/ChartNodeView.js.map +1 -1
- package/dist/components/ColumnNodeView.d.ts +6 -0
- package/dist/components/ColumnNodeView.d.ts.map +1 -0
- package/dist/components/ColumnNodeView.js +13 -0
- package/dist/components/ColumnNodeView.js.map +1 -0
- package/dist/components/ColumnsNodeView.d.ts +14 -0
- package/dist/components/ColumnsNodeView.d.ts.map +1 -0
- package/dist/components/ColumnsNodeView.js +122 -0
- package/dist/components/ColumnsNodeView.js.map +1 -0
- package/dist/components/DocumentHeader/DocumentHeader.d.ts.map +1 -1
- package/dist/components/DocumentHeader/DocumentHeader.js +1 -0
- package/dist/components/DocumentHeader/DocumentHeader.js.map +1 -1
- package/dist/components/DocumentHeader/ExportMenu.d.ts +2 -2
- package/dist/components/DocumentHeader/ExportMenu.d.ts.map +1 -1
- package/dist/components/DocumentHeader/ExportMenu.js +129 -9
- package/dist/components/DocumentHeader/ExportMenu.js.map +1 -1
- package/dist/components/JotxEditor.d.ts.map +1 -1
- package/dist/components/JotxEditor.js +52 -0
- package/dist/components/JotxEditor.js.map +1 -1
- package/dist/components/MathNodeView.d.ts.map +1 -1
- package/dist/components/MathNodeView.js +10 -1
- package/dist/components/MathNodeView.js.map +1 -1
- package/dist/components/PresentationToolbar.d.ts +13 -0
- package/dist/components/PresentationToolbar.d.ts.map +1 -0
- package/dist/components/PresentationToolbar.js +14 -0
- package/dist/components/PresentationToolbar.js.map +1 -0
- package/dist/components/PresentationView.d.ts +18 -0
- package/dist/components/PresentationView.d.ts.map +1 -0
- package/dist/components/PresentationView.js +77 -0
- package/dist/components/PresentationView.js.map +1 -0
- package/dist/components/SlashMenu.d.ts.map +1 -1
- package/dist/components/SlashMenu.js +16 -0
- package/dist/components/SlashMenu.js.map +1 -1
- package/dist/components/SlidePropertiesNodeView.d.ts +3 -0
- package/dist/components/SlidePropertiesNodeView.d.ts.map +1 -0
- package/dist/components/SlidePropertiesNodeView.js +87 -0
- package/dist/components/SlidePropertiesNodeView.js.map +1 -0
- package/dist/extensions/ButtonNode.d.ts.map +1 -1
- package/dist/extensions/ButtonNode.js +13 -9
- package/dist/extensions/ButtonNode.js.map +1 -1
- package/dist/extensions/ButtonStripNode.d.ts +8 -0
- package/dist/extensions/ButtonStripNode.d.ts.map +1 -0
- package/dist/extensions/ButtonStripNode.js +51 -0
- package/dist/extensions/ButtonStripNode.js.map +1 -0
- package/dist/extensions/ColumnNode.d.ts +11 -0
- package/dist/extensions/ColumnNode.d.ts.map +1 -0
- package/dist/extensions/ColumnNode.js +46 -0
- package/dist/extensions/ColumnNode.js.map +1 -0
- package/dist/extensions/ColumnsNode.d.ts +7 -0
- package/dist/extensions/ColumnsNode.d.ts.map +1 -0
- package/dist/extensions/ColumnsNode.js +56 -0
- package/dist/extensions/ColumnsNode.js.map +1 -0
- package/dist/extensions/SlideMarkerPlugin.d.ts +9 -0
- package/dist/extensions/SlideMarkerPlugin.d.ts.map +1 -0
- package/dist/extensions/SlideMarkerPlugin.js +78 -0
- package/dist/extensions/SlideMarkerPlugin.js.map +1 -0
- package/dist/extensions/SlidePropertiesNode.d.ts +8 -0
- package/dist/extensions/SlidePropertiesNode.d.ts.map +1 -0
- package/dist/extensions/SlidePropertiesNode.js +74 -0
- package/dist/extensions/SlidePropertiesNode.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -1
- package/dist/index.js.map +1 -1
- package/dist/styles/ButtonStripNodeView.css +134 -0
- package/dist/styles/ChartNodeView.css +9 -1
- package/dist/styles/ColumnsNodeView.css +89 -0
- package/dist/styles/PresentationToolbar.css +65 -0
- package/dist/styles/ReadonlyBlockRenderer.css +5 -5
- package/dist/styles/SlidePropertiesNodeView.css +116 -0
- package/dist/utils/PresentationConverter.d.ts +41 -0
- package/dist/utils/PresentationConverter.d.ts.map +1 -0
- package/dist/utils/PresentationConverter.js +1506 -0
- package/dist/utils/PresentationConverter.js.map +1 -0
- package/package.json +65 -57
- package/src/styles/ButtonStripNodeView.css +134 -0
- package/src/styles/ChartNodeView.css +9 -1
- package/src/styles/ColumnsNodeView.css +89 -0
- package/src/styles/PresentationToolbar.css +65 -0
- package/src/styles/ReadonlyBlockRenderer.css +5 -5
- package/src/styles/SlidePropertiesNodeView.css +116 -0
|
@@ -0,0 +1,1506 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* PresentationConverter — Converts jotx ProseMirror document to reveal.js HTML
|
|
4
|
+
*
|
|
5
|
+
* This is a PURE LOGIC module with zero platform dependencies.
|
|
6
|
+
* Works in both VS Code webview and web app contexts.
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT: Uses Tiptap node type names (bulletList, orderedList, taskList,
|
|
9
|
+
* tableRow, tableCell, tableHeader, etc.) — NOT jotx block type names.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.splitDocIntoSlides = splitDocIntoSlides;
|
|
13
|
+
exports.countSlides = countSlides;
|
|
14
|
+
exports.generateRevealHTML = generateRevealHTML;
|
|
15
|
+
const DEFAULT_PROPS = {
|
|
16
|
+
theme: 'dark',
|
|
17
|
+
transition: 'slide',
|
|
18
|
+
footertext: '',
|
|
19
|
+
footerlogo: '',
|
|
20
|
+
logoalign: 'left',
|
|
21
|
+
ratio: '16:9',
|
|
22
|
+
fontfamily: 'inter',
|
|
23
|
+
autoanimate: false,
|
|
24
|
+
shownumbers: true
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Walk a ProseMirror JSON document and split into slides at H1/divider boundaries
|
|
28
|
+
*/
|
|
29
|
+
function splitDocIntoSlides(doc) {
|
|
30
|
+
const content = doc?.content || [];
|
|
31
|
+
let properties = { ...DEFAULT_PROPS };
|
|
32
|
+
const slides = [];
|
|
33
|
+
let currentBlocks = [];
|
|
34
|
+
let currentTitle = '';
|
|
35
|
+
let hasFirstH1 = false;
|
|
36
|
+
for (const node of content) {
|
|
37
|
+
// Extract slideproperties — not a slide
|
|
38
|
+
if (node.type === 'slideproperties') {
|
|
39
|
+
properties = {
|
|
40
|
+
theme: node.attrs?.theme || DEFAULT_PROPS.theme,
|
|
41
|
+
transition: node.attrs?.transition || DEFAULT_PROPS.transition,
|
|
42
|
+
footertext: node.attrs?.footertext || DEFAULT_PROPS.footertext,
|
|
43
|
+
footerlogo: node.attrs?.footerlogo || DEFAULT_PROPS.footerlogo,
|
|
44
|
+
logoalign: node.attrs?.logoalign || DEFAULT_PROPS.logoalign,
|
|
45
|
+
ratio: node.attrs?.ratio || DEFAULT_PROPS.ratio,
|
|
46
|
+
fontfamily: node.attrs?.fontfamily || DEFAULT_PROPS.fontfamily,
|
|
47
|
+
autoanimate: node.attrs?.autoanimate ?? DEFAULT_PROPS.autoanimate,
|
|
48
|
+
shownumbers: node.attrs?.shownumbers ?? DEFAULT_PROPS.shownumbers
|
|
49
|
+
};
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
// H1 = new slide boundary
|
|
53
|
+
if (node.type === 'heading' && node.attrs?.level === 1) {
|
|
54
|
+
// Flush previous slide
|
|
55
|
+
if (currentBlocks.length > 0 || hasFirstH1) {
|
|
56
|
+
slides.push({
|
|
57
|
+
title: currentTitle,
|
|
58
|
+
html: blocksToHTML(currentBlocks),
|
|
59
|
+
isTitle: !hasFirstH1
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
hasFirstH1 = true;
|
|
63
|
+
currentTitle = extractText(node);
|
|
64
|
+
currentBlocks = [];
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
// Divider = explicit slide break
|
|
68
|
+
if (node.type === 'divider' || node.type === 'horizontalRule') {
|
|
69
|
+
if (currentBlocks.length > 0 || hasFirstH1) {
|
|
70
|
+
slides.push({
|
|
71
|
+
title: currentTitle,
|
|
72
|
+
html: blocksToHTML(currentBlocks)
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
currentTitle = '';
|
|
76
|
+
currentBlocks = [];
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
currentBlocks.push(node);
|
|
80
|
+
}
|
|
81
|
+
// Flush remaining content
|
|
82
|
+
if (currentBlocks.length > 0 || hasFirstH1) {
|
|
83
|
+
slides.push({
|
|
84
|
+
title: currentTitle,
|
|
85
|
+
html: blocksToHTML(currentBlocks)
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
// If no H1 was found, create a single slide with all content
|
|
89
|
+
if (slides.length === 0 && content.length > 0) {
|
|
90
|
+
slides.push({
|
|
91
|
+
title: 'Untitled',
|
|
92
|
+
html: blocksToHTML(content.filter((n) => n.type !== 'slideproperties')),
|
|
93
|
+
isTitle: true
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return { slides, properties };
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Count slides in a ProseMirror doc (for the toolbar indicator)
|
|
100
|
+
*/
|
|
101
|
+
function countSlides(doc) {
|
|
102
|
+
const content = doc?.content || [];
|
|
103
|
+
let count = 0;
|
|
104
|
+
let hasContent = false;
|
|
105
|
+
for (const node of content) {
|
|
106
|
+
if (node.type === 'slideproperties')
|
|
107
|
+
continue;
|
|
108
|
+
if (node.type === 'heading' && node.attrs?.level === 1) {
|
|
109
|
+
count++;
|
|
110
|
+
hasContent = true;
|
|
111
|
+
}
|
|
112
|
+
else if ((node.type === 'divider' || node.type === 'horizontalRule') && hasContent) {
|
|
113
|
+
count++;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
hasContent = true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return Math.max(count, hasContent ? 1 : 0);
|
|
120
|
+
}
|
|
121
|
+
/** Font family registry — maps dropdown values to Google Fonts import + CSS stack */
|
|
122
|
+
const FONT_MAP = {
|
|
123
|
+
'inter': { googleImport: 'Inter:wght@300;400;500;600;700', cssStack: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" },
|
|
124
|
+
'roboto': { googleImport: 'Roboto:wght@300;400;500;700', cssStack: "'Roboto', -apple-system, BlinkMacSystemFont, sans-serif" },
|
|
125
|
+
'poppins': { googleImport: 'Poppins:wght@300;400;500;600;700', cssStack: "'Poppins', -apple-system, BlinkMacSystemFont, sans-serif" },
|
|
126
|
+
'montserrat': { googleImport: 'Montserrat:wght@300;400;500;600;700', cssStack: "'Montserrat', -apple-system, BlinkMacSystemFont, sans-serif" },
|
|
127
|
+
'opensans': { googleImport: 'Open+Sans:wght@300;400;500;600;700', cssStack: "'Open Sans', -apple-system, BlinkMacSystemFont, sans-serif" },
|
|
128
|
+
'lato': { googleImport: 'Lato:wght@300;400;700', cssStack: "'Lato', -apple-system, BlinkMacSystemFont, sans-serif" },
|
|
129
|
+
'playfair': { googleImport: 'Playfair+Display:wght@400;500;600;700', cssStack: "'Playfair Display', Georgia, 'Times New Roman', serif" },
|
|
130
|
+
'raleway': { googleImport: 'Raleway:wght@300;400;500;600;700', cssStack: "'Raleway', -apple-system, BlinkMacSystemFont, sans-serif" },
|
|
131
|
+
'outfit': { googleImport: 'Outfit:wght@300;400;500;600;700', cssStack: "'Outfit', -apple-system, BlinkMacSystemFont, sans-serif" },
|
|
132
|
+
'spacegrotesk': { googleImport: 'Space+Grotesk:wght@300;400;500;600;700', cssStack: "'Space Grotesk', -apple-system, BlinkMacSystemFont, sans-serif" },
|
|
133
|
+
};
|
|
134
|
+
function getFontConfig(fontKey) {
|
|
135
|
+
return FONT_MAP[fontKey] || FONT_MAP['inter'];
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Generate complete reveal.js HTML document from slides
|
|
139
|
+
*/
|
|
140
|
+
function generateRevealHTML(slides, properties, revealJsPath, revealCssPath) {
|
|
141
|
+
const revealJs = revealJsPath || 'https://cdn.jsdelivr.net/npm/reveal.js@5/dist/reveal.esm.js';
|
|
142
|
+
const revealCss = revealCssPath || 'https://cdn.jsdelivr.net/npm/reveal.js@5/dist/reveal.css';
|
|
143
|
+
const themeCss = getThemeCSS(properties.theme);
|
|
144
|
+
const fontConfig = getFontConfig(properties.fontfamily);
|
|
145
|
+
let width = 1280;
|
|
146
|
+
let height = 720;
|
|
147
|
+
if (properties.ratio === '4:3') {
|
|
148
|
+
width = 1024;
|
|
149
|
+
height = 768;
|
|
150
|
+
}
|
|
151
|
+
else if (properties.ratio === '16:10') {
|
|
152
|
+
width = 1280;
|
|
153
|
+
height = 800;
|
|
154
|
+
}
|
|
155
|
+
const sections = slides.map((slide, i) => {
|
|
156
|
+
const titleHtml = slide.title
|
|
157
|
+
? `<h1>${escapeHtml(slide.title)}</h1><div class="slide-header-line"></div>`
|
|
158
|
+
: '';
|
|
159
|
+
const logoSrc = properties.logoDataUri || properties.footerlogo;
|
|
160
|
+
const logoAlign = properties.logoalign || 'left';
|
|
161
|
+
const logoHtml = logoSrc ? `<img src="${logoSrc}" alt="Logo" style="max-height:24px;vertical-align:middle;" />` : '';
|
|
162
|
+
const textHtml = properties.footertext ? `<span>${escapeHtml(properties.footertext)}</span>` : '';
|
|
163
|
+
// 2-zone layout: logo on logoalign side, text on opposite side
|
|
164
|
+
// Slide numbers handled by Reveal.js (centered via CSS)
|
|
165
|
+
const leftContent = logoAlign === 'left' ? logoHtml : textHtml;
|
|
166
|
+
const rightContent = logoAlign === 'left' ? textHtml : logoHtml;
|
|
167
|
+
const footerHtml = (logoSrc || properties.footertext)
|
|
168
|
+
? `<div class="slide-footer"><div class="slide-footer-line"></div><div class="slide-footer-content"><div class="footer-left">${leftContent}</div><div class="footer-right">${rightContent}</div></div></div>`
|
|
169
|
+
: '';
|
|
170
|
+
return `<section data-transition="${properties.transition}">${titleHtml}<div class="slide-body">${slide.html}</div>${footerHtml}</section>`;
|
|
171
|
+
}).join('\n');
|
|
172
|
+
return `<!DOCTYPE html>
|
|
173
|
+
<html>
|
|
174
|
+
<head>
|
|
175
|
+
<meta charset="utf-8">
|
|
176
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
177
|
+
<title>Presentation</title>
|
|
178
|
+
<link rel="stylesheet" href="${revealCss}">
|
|
179
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16/dist/katex.min.css">
|
|
180
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
181
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
182
|
+
<link href="https://fonts.googleapis.com/css2?family=${fontConfig.googleImport}&display=swap" rel="stylesheet">
|
|
183
|
+
<style>
|
|
184
|
+
${themeCss}
|
|
185
|
+
|
|
186
|
+
/* ─── Base Font ────────────────────────────────── */
|
|
187
|
+
html, body { background: inherit; margin: 0; padding: 0; }
|
|
188
|
+
body, .reveal, .reveal * {
|
|
189
|
+
font-family: ${fontConfig.cssStack} !important;
|
|
190
|
+
}
|
|
191
|
+
.reveal pre, .reveal pre *, .reveal code, .reveal code * {
|
|
192
|
+
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', 'JetBrains Mono', Menlo, Monaco, 'Courier New', monospace !important;
|
|
193
|
+
}
|
|
194
|
+
.mermaid, .mermaid * {
|
|
195
|
+
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', 'JetBrains Mono', Menlo, Monaco, 'Courier New', monospace !important;
|
|
196
|
+
}
|
|
197
|
+
/* Flowchart & general labels */
|
|
198
|
+
.mermaid .nodeLabel, .mermaid .edgeLabel, .mermaid .label {
|
|
199
|
+
font-size: 11px !important;
|
|
200
|
+
}
|
|
201
|
+
/* Class diagram — title and body */
|
|
202
|
+
.mermaid .classTitle, .mermaid .classTitleText {
|
|
203
|
+
font-size: 11px !important;
|
|
204
|
+
}
|
|
205
|
+
.mermaid .classText, .mermaid .classText tspan {
|
|
206
|
+
font-size: 10px !important;
|
|
207
|
+
}
|
|
208
|
+
/* Sequence diagram */
|
|
209
|
+
.mermaid .actor, .mermaid .messageText, .mermaid .labelText, .mermaid .loopText {
|
|
210
|
+
font-size: 11px !important;
|
|
211
|
+
}
|
|
212
|
+
/* Keep SVG viewBox fitting */
|
|
213
|
+
.mermaid svg {
|
|
214
|
+
max-width: 100%;
|
|
215
|
+
height: auto;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/* ─── Slide Layout ──────────────────────────────── */
|
|
219
|
+
.reveal section {
|
|
220
|
+
text-align: left;
|
|
221
|
+
padding: 40px 60px;
|
|
222
|
+
height: 100%;
|
|
223
|
+
display: flex !important;
|
|
224
|
+
flex-direction: column;
|
|
225
|
+
}
|
|
226
|
+
.reveal section h1 {
|
|
227
|
+
font-size: 2em;
|
|
228
|
+
margin: 0 0 0 0;
|
|
229
|
+
flex-shrink: 0;
|
|
230
|
+
}
|
|
231
|
+
.slide-header-line {
|
|
232
|
+
height: 3px;
|
|
233
|
+
background: linear-gradient(90deg, var(--accent-color, #89b4fa), transparent);
|
|
234
|
+
margin: 8px 0 20px 0;
|
|
235
|
+
border-radius: 2px;
|
|
236
|
+
flex-shrink: 0;
|
|
237
|
+
}
|
|
238
|
+
.slide-body {
|
|
239
|
+
flex: 1;
|
|
240
|
+
overflow: hidden;
|
|
241
|
+
}
|
|
242
|
+
.slide-footer {
|
|
243
|
+
position: absolute;
|
|
244
|
+
bottom: 40px;
|
|
245
|
+
left: 60px;
|
|
246
|
+
right: 60px;
|
|
247
|
+
flex-shrink: 0;
|
|
248
|
+
}
|
|
249
|
+
.slide-footer-line {
|
|
250
|
+
display: block;
|
|
251
|
+
width: 100%;
|
|
252
|
+
height: 1px;
|
|
253
|
+
margin-bottom: 8px;
|
|
254
|
+
border: none;
|
|
255
|
+
background-color: var(--accent-color, #94e2d5) !important;
|
|
256
|
+
opacity: 0.35;
|
|
257
|
+
}
|
|
258
|
+
.slide-footer-content {
|
|
259
|
+
display: flex;
|
|
260
|
+
justify-content: space-between;
|
|
261
|
+
align-items: center;
|
|
262
|
+
font-size: 0.65em;
|
|
263
|
+
opacity: 0.6;
|
|
264
|
+
}
|
|
265
|
+
.footer-left, .footer-right {
|
|
266
|
+
flex: 1;
|
|
267
|
+
display: flex;
|
|
268
|
+
align-items: center;
|
|
269
|
+
}
|
|
270
|
+
.footer-left { justify-content: flex-start; }
|
|
271
|
+
.footer-right { justify-content: flex-end; }
|
|
272
|
+
|
|
273
|
+
/* Reveal.js slide number — centered at bottom */
|
|
274
|
+
.reveal .slide-number {
|
|
275
|
+
right: auto !important;
|
|
276
|
+
left: 50% !important;
|
|
277
|
+
transform: translateX(-50%) !important;
|
|
278
|
+
bottom: 20px !important;
|
|
279
|
+
font-size: 14px !important;
|
|
280
|
+
color: inherit !important;
|
|
281
|
+
opacity: 0.5 !important;
|
|
282
|
+
font-variant-numeric: tabular-nums;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* ─── Typography ────────────────────────────────── */
|
|
286
|
+
.reveal h2 { font-size: 1.4em; margin-top: 0.3em; }
|
|
287
|
+
.reveal h3 { font-size: 1.1em; margin-top: 0.3em; }
|
|
288
|
+
.reveal p { font-size: 0.85em; line-height: 1.6; margin: 0.3em 0; }
|
|
289
|
+
|
|
290
|
+
/* ─── Lists ─────────────────────────────────────── */
|
|
291
|
+
.reveal ul, .reveal ol {
|
|
292
|
+
display: block;
|
|
293
|
+
margin: 0.3em 0 0.3em 1.2em;
|
|
294
|
+
font-size: 0.85em;
|
|
295
|
+
line-height: 1.6;
|
|
296
|
+
}
|
|
297
|
+
.reveal ul ul, .reveal ol ol, .reveal ul ol, .reveal ol ul {
|
|
298
|
+
margin: 0.1em 0 0.1em 1em;
|
|
299
|
+
font-size: 0.95em;
|
|
300
|
+
}
|
|
301
|
+
.reveal li { margin: 0.15em 0; }
|
|
302
|
+
|
|
303
|
+
/* ─── Checklist ─────────────────────────────────── */
|
|
304
|
+
.reveal .checklist {
|
|
305
|
+
list-style: none;
|
|
306
|
+
padding-left: 0;
|
|
307
|
+
font-size: 0.85em;
|
|
308
|
+
}
|
|
309
|
+
.reveal .checklist li {
|
|
310
|
+
display: flex;
|
|
311
|
+
align-items: center;
|
|
312
|
+
gap: 8px;
|
|
313
|
+
margin: 0.2em 0;
|
|
314
|
+
}
|
|
315
|
+
.reveal .checklist .check-icon {
|
|
316
|
+
font-size: 1.1em;
|
|
317
|
+
flex-shrink: 0;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/* ─── Code Blocks ───────────────────────────────── */
|
|
321
|
+
.reveal pre {
|
|
322
|
+
width: 100%;
|
|
323
|
+
font-size: 0.7em;
|
|
324
|
+
margin: 0.4em 0;
|
|
325
|
+
}
|
|
326
|
+
.reveal pre code {
|
|
327
|
+
padding: 16px 20px;
|
|
328
|
+
border-radius: 8px;
|
|
329
|
+
max-height: 420px;
|
|
330
|
+
line-height: 1.5;
|
|
331
|
+
background: var(--code-bg, #313244) !important;
|
|
332
|
+
color: var(--code-color, #cdd6f4) !important;
|
|
333
|
+
}
|
|
334
|
+
.reveal code:not(pre code) {
|
|
335
|
+
background: var(--code-bg, #313244);
|
|
336
|
+
padding: 2px 6px;
|
|
337
|
+
border-radius: 4px;
|
|
338
|
+
font-size: 0.9em;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/* ─── Tables ────────────────────────────────────── */
|
|
342
|
+
.reveal table {
|
|
343
|
+
margin: 0.5em 0;
|
|
344
|
+
font-size: 0.75em;
|
|
345
|
+
border-collapse: collapse;
|
|
346
|
+
width: auto;
|
|
347
|
+
}
|
|
348
|
+
.reveal th, .reveal td {
|
|
349
|
+
padding: 8px 16px;
|
|
350
|
+
border: 1px solid var(--table-border, rgba(255,255,255,0.15));
|
|
351
|
+
text-align: left;
|
|
352
|
+
}
|
|
353
|
+
.reveal th {
|
|
354
|
+
background: var(--table-header-bg, rgba(255,255,255,0.08));
|
|
355
|
+
font-weight: 600;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/* ─── Blockquote ────────────────────────────────── */
|
|
359
|
+
.reveal blockquote {
|
|
360
|
+
width: 90%;
|
|
361
|
+
padding: 12px 20px;
|
|
362
|
+
margin: 0.5em 0;
|
|
363
|
+
border-left: 4px solid var(--accent-color, #89b4fa);
|
|
364
|
+
background: rgba(255,255,255,0.04);
|
|
365
|
+
font-style: italic;
|
|
366
|
+
font-size: 0.85em;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/* ─── Callouts ──────────────────────────────────── */
|
|
370
|
+
.callout {
|
|
371
|
+
padding: 14px 18px;
|
|
372
|
+
border-radius: 6px;
|
|
373
|
+
margin: 0.5em 0;
|
|
374
|
+
font-size: 0.8em;
|
|
375
|
+
line-height: 1.5;
|
|
376
|
+
border-left: 4px solid;
|
|
377
|
+
}
|
|
378
|
+
.callout-info { border-color: #89b4fa; background: rgba(137,180,250,0.1); }
|
|
379
|
+
.callout-warning { border-color: #f9e2af; background: rgba(249,226,175,0.1); }
|
|
380
|
+
.callout-success { border-color: #a6e3a1; background: rgba(166,227,161,0.1); }
|
|
381
|
+
.callout-danger { border-color: #f38ba8; background: rgba(243,139,168,0.1); }
|
|
382
|
+
.callout-note { border-color: #cba6f7; background: rgba(203,166,247,0.1); }
|
|
383
|
+
|
|
384
|
+
/* ─── Mermaid ───────────────────────────────────── */
|
|
385
|
+
.mermaid {
|
|
386
|
+
margin: 0.5em 0;
|
|
387
|
+
text-align: center;
|
|
388
|
+
overflow: visible;
|
|
389
|
+
}
|
|
390
|
+
.mermaid svg {
|
|
391
|
+
max-height: 60vh;
|
|
392
|
+
height: auto;
|
|
393
|
+
margin: 0 auto;
|
|
394
|
+
display: block;
|
|
395
|
+
overflow: visible;
|
|
396
|
+
}
|
|
397
|
+
.mermaid .node rect, .mermaid .node polygon {
|
|
398
|
+
rx: 4 !important;
|
|
399
|
+
ry: 4 !important;
|
|
400
|
+
}
|
|
401
|
+
.mermaid .nodeLabel {
|
|
402
|
+
white-space: normal !important;
|
|
403
|
+
word-wrap: break-word !important;
|
|
404
|
+
overflow-wrap: break-word !important;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/* ─── Math (KaTeX) ──────────────────────────────── */
|
|
408
|
+
.math-block {
|
|
409
|
+
margin: 0.5em 0;
|
|
410
|
+
text-align: center;
|
|
411
|
+
font-size: 1.2em;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/* ─── Charts ────────────────────────────────────── */
|
|
415
|
+
.chart-container {
|
|
416
|
+
display: flex;
|
|
417
|
+
justify-content: center;
|
|
418
|
+
margin: 0.5em 0;
|
|
419
|
+
}
|
|
420
|
+
.chart-container canvas {
|
|
421
|
+
max-height: 380px;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/* ─── Images ────────────────────────────────────── */
|
|
425
|
+
.reveal img:not(.mermaid img) {
|
|
426
|
+
max-width: 80%;
|
|
427
|
+
max-height: 380px;
|
|
428
|
+
border-radius: 6px;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/* ─── Properties Table ──────────────────────────── */
|
|
432
|
+
.properties-table {
|
|
433
|
+
font-size: 0.8em;
|
|
434
|
+
}
|
|
435
|
+
.properties-table td:first-child {
|
|
436
|
+
font-weight: 600;
|
|
437
|
+
white-space: nowrap;
|
|
438
|
+
padding-right: 24px;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/* ─── Columns ───────────────────────────────────── */
|
|
442
|
+
.slide-columns {
|
|
443
|
+
display: flex;
|
|
444
|
+
gap: 20px;
|
|
445
|
+
}
|
|
446
|
+
.slide-columns > div {
|
|
447
|
+
flex: 1;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/* ─── Grid Cards ────────────────────────────────── */
|
|
451
|
+
.slide-grid {
|
|
452
|
+
display: grid;
|
|
453
|
+
gap: 12px;
|
|
454
|
+
margin: 0.5em 0;
|
|
455
|
+
}
|
|
456
|
+
.slide-card {
|
|
457
|
+
padding: 16px;
|
|
458
|
+
border-radius: 8px;
|
|
459
|
+
border: 1px solid rgba(255,255,255,0.1);
|
|
460
|
+
}
|
|
461
|
+
.card-icon { font-size: 1.5em; margin-bottom: 6px; }
|
|
462
|
+
.card-title { font-weight: 600; font-size: 0.85em; }
|
|
463
|
+
.card-value { font-size: 1.4em; font-weight: 700; margin-top: 4px; }
|
|
464
|
+
.slide-card-blue { background: rgba(137,180,250,0.15); border-color: rgba(137,180,250,0.3); }
|
|
465
|
+
.slide-card-green { background: rgba(166,227,161,0.15); border-color: rgba(166,227,161,0.3); }
|
|
466
|
+
.slide-card-red { background: rgba(243,139,168,0.15); border-color: rgba(243,139,168,0.3); }
|
|
467
|
+
.slide-card-yellow { background: rgba(249,226,175,0.15); border-color: rgba(249,226,175,0.3); }
|
|
468
|
+
.slide-card-purple { background: rgba(203,166,247,0.15); border-color: rgba(203,166,247,0.3); }
|
|
469
|
+
.slide-card-teal { background: rgba(148,226,213,0.15); border-color: rgba(148,226,213,0.3); }
|
|
470
|
+
.slide-card-gray { background: rgba(186,194,222,0.1); border-color: rgba(186,194,222,0.2); }
|
|
471
|
+
|
|
472
|
+
/* ─── Buttons ───────────────────────────────────── */
|
|
473
|
+
.slide-button {
|
|
474
|
+
display: inline-block;
|
|
475
|
+
padding: 8px 20px;
|
|
476
|
+
border-radius: 6px;
|
|
477
|
+
font-size: 0.8em;
|
|
478
|
+
font-weight: 600;
|
|
479
|
+
cursor: default;
|
|
480
|
+
}
|
|
481
|
+
.slide-button-primary { background: #89b4fa; color: #1e1e2e; }
|
|
482
|
+
.slide-button-secondary { background: #45475a; color: #cdd6f4; }
|
|
483
|
+
.slide-button-outline { background: transparent; border: 2px solid #89b4fa; color: #89b4fa; }
|
|
484
|
+
.slide-button-ghost { background: transparent; color: #89b4fa; }
|
|
485
|
+
.slide-button-danger { background: #f38ba8; color: #1e1e2e; }
|
|
486
|
+
.slide-buttonstrip {
|
|
487
|
+
display: flex;
|
|
488
|
+
gap: 10px;
|
|
489
|
+
margin: 0.3em 0;
|
|
490
|
+
}
|
|
491
|
+
.slide-buttonstrip .slide-button { flex: 1; text-align: center; }
|
|
492
|
+
|
|
493
|
+
/* ─── Toggle ────────────────────────────────────── */
|
|
494
|
+
.slide-toggle {
|
|
495
|
+
margin: 0.4em 0;
|
|
496
|
+
border: 1px solid rgba(255,255,255,0.1);
|
|
497
|
+
border-radius: 6px;
|
|
498
|
+
overflow: hidden;
|
|
499
|
+
}
|
|
500
|
+
.toggle-header {
|
|
501
|
+
padding: 8px 14px;
|
|
502
|
+
font-weight: 600;
|
|
503
|
+
font-size: 0.85em;
|
|
504
|
+
background: rgba(255,255,255,0.05);
|
|
505
|
+
}
|
|
506
|
+
.toggle-content { padding: 8px 14px; }
|
|
507
|
+
|
|
508
|
+
/* ─── LinkCard ──────────────────────────────────── */
|
|
509
|
+
.slide-linkcard {
|
|
510
|
+
padding: 12px 16px;
|
|
511
|
+
border: 1px solid rgba(255,255,255,0.12);
|
|
512
|
+
border-radius: 8px;
|
|
513
|
+
margin: 0.4em 0;
|
|
514
|
+
font-size: 0.8em;
|
|
515
|
+
overflow: hidden;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/* ─── Image Caption ─────────────────────────────── */
|
|
519
|
+
.image-caption {
|
|
520
|
+
font-size: 0.75em;
|
|
521
|
+
opacity: 0.6;
|
|
522
|
+
margin-top: 4px;
|
|
523
|
+
text-align: center;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/* ─── Block Title (math/chart/mermaid) ─────────── */
|
|
527
|
+
.block-title {
|
|
528
|
+
font-size: 0.7em;
|
|
529
|
+
font-weight: 600;
|
|
530
|
+
opacity: 0.7;
|
|
531
|
+
margin-bottom: 4px;
|
|
532
|
+
text-transform: uppercase;
|
|
533
|
+
letter-spacing: 0.05em;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/* ─── Links & Misc ──────────────────────────────── */
|
|
537
|
+
.slide-link {
|
|
538
|
+
font-size: 0.85em;
|
|
539
|
+
margin: 0.2em 0;
|
|
540
|
+
}
|
|
541
|
+
.slide-link a { color: var(--accent-color, #89b4fa); }
|
|
542
|
+
.slide-datetime {
|
|
543
|
+
font-size: 0.85em;
|
|
544
|
+
padding: 2px 8px;
|
|
545
|
+
background: rgba(255,255,255,0.06);
|
|
546
|
+
border-radius: 4px;
|
|
547
|
+
}
|
|
548
|
+
.slide-jotxlink {
|
|
549
|
+
font-size: 0.85em;
|
|
550
|
+
color: var(--accent-color, #89b4fa);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/* ─── Print / PDF Export (16:9 Landscape Slides) ── */
|
|
554
|
+
@page {
|
|
555
|
+
size: 1280px 720px landscape;
|
|
556
|
+
margin: 0;
|
|
557
|
+
}
|
|
558
|
+
@media print {
|
|
559
|
+
body { margin: 0; padding: 0; }
|
|
560
|
+
.reveal { position: static !important; }
|
|
561
|
+
.reveal .slides { position: static !important; overflow: visible !important; }
|
|
562
|
+
.reveal .slides section {
|
|
563
|
+
display: flex !important;
|
|
564
|
+
flex-direction: column;
|
|
565
|
+
width: 1280px !important;
|
|
566
|
+
height: 720px !important;
|
|
567
|
+
page-break-after: always;
|
|
568
|
+
page-break-inside: avoid;
|
|
569
|
+
box-sizing: border-box;
|
|
570
|
+
padding: 40px 60px !important;
|
|
571
|
+
margin: 0 !important;
|
|
572
|
+
position: static !important;
|
|
573
|
+
transform: none !important;
|
|
574
|
+
opacity: 1 !important;
|
|
575
|
+
visibility: visible !important;
|
|
576
|
+
}
|
|
577
|
+
.reveal .slides section:last-child { page-break-after: auto; }
|
|
578
|
+
.reveal .controls, .reveal .progress, .reveal .slide-number { display: none !important; }
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/* ─── Controls Visibility ──────────────────── */
|
|
582
|
+
.reveal .controls { color: var(--accent-color, #94e2d5) !important; opacity: 0.9; }
|
|
583
|
+
.reveal .controls button { color: var(--accent-color, #94e2d5) !important; font-size: 1.2em; }
|
|
584
|
+
.reveal .controls button:hover { color: #fff !important; opacity: 1; }
|
|
585
|
+
.reveal .progress span { background: var(--accent-color, #94e2d5) !important; opacity: 0.8; }
|
|
586
|
+
.reveal .slide-number { color: var(--accent-color, #94e2d5) !important; font-size: 0.8em; opacity: 0.6; background: transparent !important; }
|
|
587
|
+
|
|
588
|
+
/* ─── Floating Toolbar ──────────────────────── */
|
|
589
|
+
.slide-toolbar {
|
|
590
|
+
position: fixed;
|
|
591
|
+
bottom: 20px;
|
|
592
|
+
left: 50%;
|
|
593
|
+
transform: translateX(-50%);
|
|
594
|
+
display: flex;
|
|
595
|
+
gap: 2px;
|
|
596
|
+
align-items: center;
|
|
597
|
+
padding: 4px 10px;
|
|
598
|
+
background: rgba(0, 0, 0, 0.65);
|
|
599
|
+
backdrop-filter: blur(12px);
|
|
600
|
+
-webkit-backdrop-filter: blur(12px);
|
|
601
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
602
|
+
border-radius: 20px;
|
|
603
|
+
z-index: 100;
|
|
604
|
+
opacity: 0;
|
|
605
|
+
transition: opacity 0.3s ease;
|
|
606
|
+
}
|
|
607
|
+
.slide-toolbar:hover, .slide-toolbar.visible { opacity: 1; }
|
|
608
|
+
body:hover .slide-toolbar { opacity: 0.7; }
|
|
609
|
+
body:hover .slide-toolbar:hover { opacity: 1; }
|
|
610
|
+
.toolbar-btn {
|
|
611
|
+
width: 32px;
|
|
612
|
+
height: 32px;
|
|
613
|
+
border: none;
|
|
614
|
+
background: transparent;
|
|
615
|
+
color: rgba(255, 255, 255, 0.7);
|
|
616
|
+
cursor: pointer;
|
|
617
|
+
border-radius: 50%;
|
|
618
|
+
display: flex;
|
|
619
|
+
align-items: center;
|
|
620
|
+
justify-content: center;
|
|
621
|
+
transition: all 0.2s;
|
|
622
|
+
position: relative;
|
|
623
|
+
padding: 0;
|
|
624
|
+
}
|
|
625
|
+
.toolbar-btn svg { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
|
|
626
|
+
.toolbar-btn:hover { background: rgba(255, 255, 255, 0.15); color: #fff; }
|
|
627
|
+
.toolbar-btn.active { background: var(--accent-color, #94e2d5); color: #1e1e2e; }
|
|
628
|
+
.toolbar-btn .tooltip {
|
|
629
|
+
position: absolute;
|
|
630
|
+
bottom: 38px;
|
|
631
|
+
left: 50%;
|
|
632
|
+
transform: translateX(-50%);
|
|
633
|
+
background: rgba(0, 0, 0, 0.85);
|
|
634
|
+
color: #fff;
|
|
635
|
+
padding: 3px 8px;
|
|
636
|
+
border-radius: 5px;
|
|
637
|
+
font-size: 10px;
|
|
638
|
+
white-space: nowrap;
|
|
639
|
+
opacity: 0;
|
|
640
|
+
pointer-events: none;
|
|
641
|
+
transition: opacity 0.2s;
|
|
642
|
+
}
|
|
643
|
+
.toolbar-btn:hover .tooltip { opacity: 1; }
|
|
644
|
+
.toolbar-divider {
|
|
645
|
+
width: 1px;
|
|
646
|
+
height: 20px;
|
|
647
|
+
background: rgba(255, 255, 255, 0.18);
|
|
648
|
+
margin: 0 3px;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/* ─── Pen picker popup ─────────────────────── */
|
|
652
|
+
.pen-picker {
|
|
653
|
+
position: absolute;
|
|
654
|
+
bottom: 42px;
|
|
655
|
+
left: 50%;
|
|
656
|
+
transform: translateX(-50%);
|
|
657
|
+
background: rgba(0, 0, 0, 0.85);
|
|
658
|
+
border: 1px solid rgba(255,255,255,0.15);
|
|
659
|
+
border-radius: 10px;
|
|
660
|
+
padding: 8px;
|
|
661
|
+
display: none;
|
|
662
|
+
z-index: 200;
|
|
663
|
+
}
|
|
664
|
+
.pen-picker.open { display: flex; flex-direction: column; gap: 6px; }
|
|
665
|
+
.pen-colors { display: flex; gap: 4px; }
|
|
666
|
+
.pen-color-dot {
|
|
667
|
+
width: 20px; height: 20px;
|
|
668
|
+
border-radius: 50%;
|
|
669
|
+
border: 2px solid transparent;
|
|
670
|
+
cursor: pointer;
|
|
671
|
+
transition: border-color 0.15s;
|
|
672
|
+
}
|
|
673
|
+
.pen-color-dot:hover, .pen-color-dot.active { border-color: #fff; }
|
|
674
|
+
.pen-widths { display: flex; gap: 4px; align-items: center; }
|
|
675
|
+
.pen-width-btn {
|
|
676
|
+
width: 24px; height: 24px;
|
|
677
|
+
border-radius: 50%;
|
|
678
|
+
border: 1px solid rgba(255,255,255,0.3);
|
|
679
|
+
background: transparent;
|
|
680
|
+
color: #fff;
|
|
681
|
+
font-size: 9px;
|
|
682
|
+
cursor: pointer;
|
|
683
|
+
display: flex; align-items: center; justify-content: center;
|
|
684
|
+
}
|
|
685
|
+
.pen-width-btn:hover, .pen-width-btn.active { background: rgba(255,255,255,0.2); border-color: #fff; }
|
|
686
|
+
|
|
687
|
+
/* ─── Chalkboard canvas ─────────────────────── */
|
|
688
|
+
.chalkboard-canvas {
|
|
689
|
+
position: fixed;
|
|
690
|
+
top: 0;
|
|
691
|
+
left: 0;
|
|
692
|
+
width: 100vw;
|
|
693
|
+
height: 100vh;
|
|
694
|
+
z-index: 50;
|
|
695
|
+
cursor: crosshair;
|
|
696
|
+
display: none;
|
|
697
|
+
}
|
|
698
|
+
.chalkboard-active .chalkboard-canvas { display: block; }
|
|
699
|
+
.chalkboard-active .reveal .controls { display: none !important; }
|
|
700
|
+
|
|
701
|
+
/* ─── Laser Pointer ─────────────────────────── */
|
|
702
|
+
.laser-pointer {
|
|
703
|
+
position: fixed;
|
|
704
|
+
width: 12px;
|
|
705
|
+
height: 12px;
|
|
706
|
+
border-radius: 50%;
|
|
707
|
+
background: radial-gradient(circle, #ff0000 30%, rgba(255,0,0,0.4) 70%);
|
|
708
|
+
box-shadow: 0 0 8px 3px rgba(255, 0, 0, 0.5);
|
|
709
|
+
pointer-events: none;
|
|
710
|
+
z-index: 9999;
|
|
711
|
+
display: none;
|
|
712
|
+
transform: translate(-50%, -50%);
|
|
713
|
+
}
|
|
714
|
+
.laser-active .laser-pointer { display: block; }
|
|
715
|
+
.laser-active { cursor: none !important; }
|
|
716
|
+
|
|
717
|
+
@media print {
|
|
718
|
+
.slide-toolbar, .laser-pointer, .chalkboard-canvas, .pen-picker { display: none !important; }
|
|
719
|
+
}
|
|
720
|
+
</style>
|
|
721
|
+
</head>
|
|
722
|
+
<body>
|
|
723
|
+
<div class="reveal">
|
|
724
|
+
<div class="slides">
|
|
725
|
+
${sections}
|
|
726
|
+
</div>
|
|
727
|
+
</div>
|
|
728
|
+
|
|
729
|
+
<!-- Laser pointer element -->
|
|
730
|
+
<div class="laser-pointer" id="laserDot"></div>
|
|
731
|
+
|
|
732
|
+
<!-- Chalkboard canvas -->
|
|
733
|
+
<canvas class="chalkboard-canvas" id="chalkboardCanvas"></canvas>
|
|
734
|
+
|
|
735
|
+
<!-- Floating Toolbar -->
|
|
736
|
+
<div class="slide-toolbar" id="slideToolbar">
|
|
737
|
+
<button class="toolbar-btn" id="btnPrev" title="Previous"><svg viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg><span class="tooltip">Previous (←)</span></button>
|
|
738
|
+
<button class="toolbar-btn" id="btnNext" title="Next"><svg viewBox="0 0 24 24"><polyline points="9 6 15 12 9 18"/></svg><span class="tooltip">Next (→)</span></button>
|
|
739
|
+
<div class="toolbar-divider"></div>
|
|
740
|
+
<button class="toolbar-btn" id="btnFullscreen" title="Fullscreen"><svg viewBox="0 0 24 24"><path d="M8 3H5a2 2 0 00-2 2v3m18 0V5a2 2 0 00-2-2h-3m0 18h3a2 2 0 002-2v-3M3 16v3a2 2 0 002 2h3"/></svg><span class="tooltip">Fullscreen (F)</span></button>
|
|
741
|
+
<button class="toolbar-btn" id="btnOverview" title="Overview"><svg viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg><span class="tooltip">Overview (O)</span></button>
|
|
742
|
+
<div class="toolbar-divider"></div>
|
|
743
|
+
<button class="toolbar-btn" id="btnChalkboard" title="Draw"><svg viewBox="0 0 24 24"><path d="M17 3a2.83 2.83 0 114 4L7.5 20.5 2 22l1.5-5.5L17 3z"/></svg><span class="tooltip">Draw (C)</span></button>
|
|
744
|
+
<button class="toolbar-btn" id="btnLaser" title="Laser"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M12 2v4m0 12v4m-10-10h4m12 0h4m-3.5-7.5l-2.8 2.8m-7.4 7.4l-2.8 2.8m0-13l2.8 2.8m7.4 7.4l2.8 2.8"/></svg><span class="tooltip">Laser (L)</span></button>
|
|
745
|
+
<div class="toolbar-divider"></div>
|
|
746
|
+
<button class="toolbar-btn" id="btnPrint" title="Print/PDF"><svg viewBox="0 0 24 24"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 01-2-2v-5a2 2 0 012-2h16a2 2 0 012 2v5a2 2 0 01-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg><span class="tooltip">Print / PDF</span></button>
|
|
747
|
+
</div>
|
|
748
|
+
|
|
749
|
+
<!-- Pen Picker (for chalkboard) -->
|
|
750
|
+
<div class="pen-picker" id="penPicker">
|
|
751
|
+
<div class="pen-colors">
|
|
752
|
+
<div class="pen-color-dot active" data-color="#f38ba8" style="background:#f38ba8"></div>
|
|
753
|
+
<div class="pen-color-dot" data-color="#a6e3a1" style="background:#a6e3a1"></div>
|
|
754
|
+
<div class="pen-color-dot" data-color="#89b4fa" style="background:#89b4fa"></div>
|
|
755
|
+
<div class="pen-color-dot" data-color="#f9e2af" style="background:#f9e2af"></div>
|
|
756
|
+
<div class="pen-color-dot" data-color="#cba6f7" style="background:#cba6f7"></div>
|
|
757
|
+
<div class="pen-color-dot" data-color="#ffffff" style="background:#ffffff"></div>
|
|
758
|
+
</div>
|
|
759
|
+
<div class="pen-widths">
|
|
760
|
+
<button class="pen-width-btn" data-width="2">S</button>
|
|
761
|
+
<button class="pen-width-btn active" data-width="3">M</button>
|
|
762
|
+
<button class="pen-width-btn" data-width="6">L</button>
|
|
763
|
+
<button class="pen-width-btn" data-width="10">XL</button>
|
|
764
|
+
</div>
|
|
765
|
+
</div>
|
|
766
|
+
|
|
767
|
+
<!-- Mermaid for diagrams -->
|
|
768
|
+
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
|
|
769
|
+
<!-- KaTeX for math -->
|
|
770
|
+
<script src="https://cdn.jsdelivr.net/npm/katex@0.16/dist/katex.min.js"></script>
|
|
771
|
+
<!-- Chart.js for charts -->
|
|
772
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
|
|
773
|
+
<!-- Highlight.js for code syntax highlighting -->
|
|
774
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
|
|
775
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
|
776
|
+
|
|
777
|
+
<script type="module">
|
|
778
|
+
import Reveal from '${revealJs}';
|
|
779
|
+
|
|
780
|
+
// Initialize mermaid with proper sizing and padding
|
|
781
|
+
mermaid.initialize({
|
|
782
|
+
startOnLoad: false,
|
|
783
|
+
theme: '${properties.theme === 'light' || properties.theme === 'minimal' || properties.theme === 'rose' || properties.theme === 'solarized' ? 'default' : 'dark'}',
|
|
784
|
+
securityLevel: 'loose',
|
|
785
|
+
flowchart: { useMaxWidth: true, htmlLabels: true, curve: 'basis', padding: 20, nodeSpacing: 50, rankSpacing: 50, wrappingWidth: 200 },
|
|
786
|
+
sequence: { useMaxWidth: true, actorMargin: 60, actorFontSize: 11, messageFontSize: 11, noteFontSize: 10, wrap: true, wrapPadding: 10 },
|
|
787
|
+
classDiagram: { useMaxWidth: true, titleTopMargin: 10 },
|
|
788
|
+
themeVariables: {
|
|
789
|
+
fontSize: '11px',
|
|
790
|
+
fontFamily: 'Inter, sans-serif',
|
|
791
|
+
classFontSize: '11px'
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
// Post-render: enforce font size on all Mermaid SVG text elements
|
|
796
|
+
function fixMermaidFonts() {
|
|
797
|
+
document.querySelectorAll('.mermaid svg text, .mermaid svg tspan').forEach(t => {
|
|
798
|
+
const cur = parseFloat(t.getAttribute('font-size') || t.style.fontSize || '16');
|
|
799
|
+
if (cur > 12) { t.setAttribute('font-size', '11'); t.style.fontSize = '11px'; }
|
|
800
|
+
});
|
|
801
|
+
document.querySelectorAll('.mermaid svg foreignObject div, .mermaid svg foreignObject span').forEach(el => {
|
|
802
|
+
const cur = parseFloat(getComputedStyle(el).fontSize || '16');
|
|
803
|
+
if (cur > 12) el.style.fontSize = '11px';
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
Reveal.initialize({
|
|
808
|
+
hash: false,
|
|
809
|
+
transition: '${properties.transition}',
|
|
810
|
+
width: ${width},
|
|
811
|
+
height: ${height},
|
|
812
|
+
margin: 0,
|
|
813
|
+
minScale: 0.2,
|
|
814
|
+
maxScale: 2.0,
|
|
815
|
+
center: false,
|
|
816
|
+
controls: true,
|
|
817
|
+
controlsLayout: 'bottom-right',
|
|
818
|
+
progress: true,
|
|
819
|
+
slideNumber: ${properties.shownumbers}, // Centered via CSS override
|
|
820
|
+
keyboard: true,
|
|
821
|
+
overview: true,
|
|
822
|
+
help: true,
|
|
823
|
+
});${properties.autoanimate ? `
|
|
824
|
+
|
|
825
|
+
// Fragment animations — make each element appear one-by-one
|
|
826
|
+
Reveal.on('ready', function() {
|
|
827
|
+
document.querySelectorAll('.slide-body').forEach(function(body) {
|
|
828
|
+
Array.from(body.children).forEach(function(child) {
|
|
829
|
+
if (!child.classList.contains('slide-footer') && !child.classList.contains('slide-footer-line')) {
|
|
830
|
+
child.classList.add('fragment', 'fade-up');
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
});
|
|
834
|
+
Reveal.sync(); // tell Reveal about new fragments
|
|
835
|
+
});` : ''}
|
|
836
|
+
|
|
837
|
+
// ── Toolbar wiring ──
|
|
838
|
+
const laserDot = document.getElementById('laserDot');
|
|
839
|
+
const chalkCanvas = document.getElementById('chalkboardCanvas');
|
|
840
|
+
const ctx = chalkCanvas ? chalkCanvas.getContext('2d') : null;
|
|
841
|
+
const penPicker = document.getElementById('penPicker');
|
|
842
|
+
let laserActive = false;
|
|
843
|
+
let chalkActive = false;
|
|
844
|
+
let drawing = false;
|
|
845
|
+
let penColor = '#f38ba8';
|
|
846
|
+
let penWidth = 3;
|
|
847
|
+
|
|
848
|
+
// Resize canvas to match window
|
|
849
|
+
function resizeCanvas() {
|
|
850
|
+
if (chalkCanvas) { chalkCanvas.width = window.innerWidth; chalkCanvas.height = window.innerHeight; }
|
|
851
|
+
}
|
|
852
|
+
resizeCanvas();
|
|
853
|
+
window.addEventListener('resize', resizeCanvas);
|
|
854
|
+
|
|
855
|
+
function toggleFullscreen() {
|
|
856
|
+
const el = document.documentElement;
|
|
857
|
+
const exitFs = document.exitFullscreen || document.webkitExitFullscreen;
|
|
858
|
+
const fsEl = document.fullscreenElement || document.webkitFullscreenElement;
|
|
859
|
+
if (!fsEl) {
|
|
860
|
+
const req = el.requestFullscreen || el.webkitRequestFullscreen;
|
|
861
|
+
if (req) req.call(el).catch(() => { alert('Fullscreen blocked by browser. Use F11 or Cmd+Ctrl+F for fullscreen.'); });
|
|
862
|
+
} else {
|
|
863
|
+
if (exitFs) exitFs.call(document).catch(() => {});
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
function toggleLaser() {
|
|
867
|
+
laserActive = !laserActive;
|
|
868
|
+
if (chalkActive && laserActive) toggleChalkboard();
|
|
869
|
+
document.body.classList.toggle('laser-active', laserActive);
|
|
870
|
+
document.getElementById('btnLaser')?.classList.toggle('active', laserActive);
|
|
871
|
+
}
|
|
872
|
+
function toggleChalkboard() {
|
|
873
|
+
chalkActive = !chalkActive;
|
|
874
|
+
if (laserActive && chalkActive) toggleLaser();
|
|
875
|
+
document.body.classList.toggle('chalkboard-active', chalkActive);
|
|
876
|
+
document.getElementById('btnChalkboard')?.classList.toggle('active', chalkActive);
|
|
877
|
+
if (penPicker) penPicker.classList.toggle('open', chalkActive);
|
|
878
|
+
}
|
|
879
|
+
function clearChalkboard() {
|
|
880
|
+
if (ctx && chalkCanvas) { ctx.clearRect(0, 0, chalkCanvas.width, chalkCanvas.height); }
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// Pen color/width picker
|
|
884
|
+
document.querySelectorAll('.pen-color-dot').forEach(dot => {
|
|
885
|
+
dot.addEventListener('click', () => {
|
|
886
|
+
document.querySelectorAll('.pen-color-dot').forEach(d => d.classList.remove('active'));
|
|
887
|
+
dot.classList.add('active');
|
|
888
|
+
penColor = dot.getAttribute('data-color') || '#f38ba8';
|
|
889
|
+
if (ctx) ctx.strokeStyle = penColor;
|
|
890
|
+
});
|
|
891
|
+
});
|
|
892
|
+
document.querySelectorAll('.pen-width-btn').forEach(btn => {
|
|
893
|
+
btn.addEventListener('click', () => {
|
|
894
|
+
document.querySelectorAll('.pen-width-btn').forEach(b => b.classList.remove('active'));
|
|
895
|
+
btn.classList.add('active');
|
|
896
|
+
penWidth = parseInt(btn.getAttribute('data-width') || '3');
|
|
897
|
+
if (ctx) ctx.lineWidth = penWidth;
|
|
898
|
+
});
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
// Chalkboard drawing
|
|
902
|
+
if (chalkCanvas && ctx) {
|
|
903
|
+
ctx.strokeStyle = penColor;
|
|
904
|
+
ctx.lineWidth = penWidth;
|
|
905
|
+
ctx.lineCap = 'round';
|
|
906
|
+
ctx.lineJoin = 'round';
|
|
907
|
+
chalkCanvas.addEventListener('mousedown', (e) => {
|
|
908
|
+
if (!chalkActive) return;
|
|
909
|
+
drawing = true;
|
|
910
|
+
ctx.strokeStyle = penColor;
|
|
911
|
+
ctx.lineWidth = penWidth;
|
|
912
|
+
ctx.beginPath();
|
|
913
|
+
ctx.moveTo(e.clientX, e.clientY);
|
|
914
|
+
});
|
|
915
|
+
chalkCanvas.addEventListener('mousemove', (e) => {
|
|
916
|
+
if (!drawing) return;
|
|
917
|
+
ctx.lineTo(e.clientX, e.clientY);
|
|
918
|
+
ctx.stroke();
|
|
919
|
+
});
|
|
920
|
+
chalkCanvas.addEventListener('mouseup', () => { drawing = false; });
|
|
921
|
+
chalkCanvas.addEventListener('mouseleave', () => { drawing = false; });
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// Keyboard shortcuts
|
|
925
|
+
document.addEventListener('keydown', (e) => {
|
|
926
|
+
if (e.key === 'f' || e.key === 'F') toggleFullscreen();
|
|
927
|
+
if (e.key === 'l' || e.key === 'L') toggleLaser();
|
|
928
|
+
if (e.key === 'c' || e.key === 'C') toggleChalkboard();
|
|
929
|
+
if ((e.key === 'd' || e.key === 'D') && chalkActive) clearChalkboard();
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
// Mouse tracking for laser
|
|
933
|
+
document.addEventListener('mousemove', (e) => {
|
|
934
|
+
if (laserActive && laserDot) {
|
|
935
|
+
laserDot.style.left = e.clientX + 'px';
|
|
936
|
+
laserDot.style.top = e.clientY + 'px';
|
|
937
|
+
}
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
// Toolbar button handlers
|
|
941
|
+
document.getElementById('btnPrev')?.addEventListener('click', () => Reveal.prev());
|
|
942
|
+
document.getElementById('btnNext')?.addEventListener('click', () => Reveal.next());
|
|
943
|
+
document.getElementById('btnFullscreen')?.addEventListener('click', toggleFullscreen);
|
|
944
|
+
document.getElementById('btnOverview')?.addEventListener('click', () => Reveal.toggleOverview());
|
|
945
|
+
document.getElementById('btnChalkboard')?.addEventListener('click', toggleChalkboard);
|
|
946
|
+
document.getElementById('btnLaser')?.addEventListener('click', toggleLaser);
|
|
947
|
+
document.getElementById('btnPrint')?.addEventListener('click', () => { window.print(); });
|
|
948
|
+
|
|
949
|
+
// ── Render mermaid, KaTeX, Charts, Code highlighting after reveal initializes ──
|
|
950
|
+
Reveal.on('ready', async () => {
|
|
951
|
+
// Render mermaid
|
|
952
|
+
const mermaidEls = document.querySelectorAll('.mermaid');
|
|
953
|
+
for (let i = 0; i < mermaidEls.length; i++) {
|
|
954
|
+
const el = mermaidEls[i];
|
|
955
|
+
let code = el.textContent || '';
|
|
956
|
+
code = code.replace(/\\\\n/g, String.fromCharCode(10)).trim();
|
|
957
|
+
if (!code) continue;
|
|
958
|
+
try {
|
|
959
|
+
const { svg } = await mermaid.render('mermaid-' + i, code);
|
|
960
|
+
el.innerHTML = svg;
|
|
961
|
+
} catch (e) {
|
|
962
|
+
el.innerHTML = '<pre style="color:#f38ba8;font-size:0.7em;">Mermaid error: ' + e.message + '</pre>';
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
fixMermaidFonts();
|
|
966
|
+
|
|
967
|
+
// Render KaTeX (base64 encoded to preserve backslashes)
|
|
968
|
+
document.querySelectorAll('.math-block').forEach(el => {
|
|
969
|
+
let tex = '';
|
|
970
|
+
try {
|
|
971
|
+
const b64 = el.getAttribute('data-tex-b64') || '';
|
|
972
|
+
tex = b64 ? atob(b64) : (el.getAttribute('data-tex') || '');
|
|
973
|
+
} catch(e) {
|
|
974
|
+
tex = el.getAttribute('data-tex') || '';
|
|
975
|
+
}
|
|
976
|
+
tex = tex.replace(/\\\\n/g, String.fromCharCode(10));
|
|
977
|
+
try {
|
|
978
|
+
katex.render(tex, el, { displayMode: true, throwOnError: false });
|
|
979
|
+
} catch(e) {
|
|
980
|
+
el.textContent = tex;
|
|
981
|
+
}
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
// Render Charts with vibrant colors
|
|
985
|
+
document.querySelectorAll('.chart-container').forEach(el => {
|
|
986
|
+
const canvas = el.querySelector('canvas');
|
|
987
|
+
const type = el.getAttribute('data-chart-type') || 'bar';
|
|
988
|
+
const jsonStr = el.getAttribute('data-chart-json') || '{}';
|
|
989
|
+
try {
|
|
990
|
+
const data = JSON.parse(jsonStr);
|
|
991
|
+
new Chart(canvas, {
|
|
992
|
+
type: type,
|
|
993
|
+
data: data,
|
|
994
|
+
options: {
|
|
995
|
+
responsive: true,
|
|
996
|
+
maintainAspectRatio: true,
|
|
997
|
+
plugins: {
|
|
998
|
+
legend: {
|
|
999
|
+
labels: {
|
|
1000
|
+
color: '${properties.theme === 'light' || properties.theme === 'minimal' ? '#333' : '#cdd6f4'}',
|
|
1001
|
+
font: { family: 'Inter, sans-serif', size: 13 }
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
},
|
|
1005
|
+
scales: type === 'pie' || type === 'doughnut' || type === 'radar' ? {} : {
|
|
1006
|
+
x: {
|
|
1007
|
+
ticks: { color: '${properties.theme === 'light' || properties.theme === 'minimal' ? '#666' : '#a6adc8'}', font: { family: 'Inter, sans-serif', size: 12 } },
|
|
1008
|
+
grid: { color: 'rgba(255,255,255,0.06)' }
|
|
1009
|
+
},
|
|
1010
|
+
y: {
|
|
1011
|
+
ticks: { color: '${properties.theme === 'light' || properties.theme === 'minimal' ? '#666' : '#a6adc8'}', font: { family: 'Inter, sans-serif', size: 12 } },
|
|
1012
|
+
grid: { color: 'rgba(255,255,255,0.06)' }
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
} catch(e) {
|
|
1018
|
+
canvas.parentElement.innerHTML = '<p style="color:#f38ba8;">Chart error: ' + e.message + '</p>';
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
// Syntax highlight code blocks
|
|
1023
|
+
document.querySelectorAll('pre code').forEach(block => {
|
|
1024
|
+
try { hljs.highlightElement(block); } catch(e) {}
|
|
1025
|
+
});
|
|
1026
|
+
});
|
|
1027
|
+
</script>
|
|
1028
|
+
</body>
|
|
1029
|
+
</html>`;
|
|
1030
|
+
}
|
|
1031
|
+
// ─── Block-to-HTML Renderers (using Tiptap node types) ───────────────
|
|
1032
|
+
function blocksToHTML(blocks) {
|
|
1033
|
+
return blocks.map(blockToHTML).join('\n');
|
|
1034
|
+
}
|
|
1035
|
+
function blockToHTML(node) {
|
|
1036
|
+
switch (node.type) {
|
|
1037
|
+
case 'paragraph':
|
|
1038
|
+
return `<p>${inlineToHTML(node.content)}</p>`;
|
|
1039
|
+
case 'heading': {
|
|
1040
|
+
const level = node.attrs?.level || 2;
|
|
1041
|
+
return `<h${level}>${inlineToHTML(node.content)}</h${level}>`;
|
|
1042
|
+
}
|
|
1043
|
+
// ─── Lists (Tiptap names) ───
|
|
1044
|
+
case 'bulletList': {
|
|
1045
|
+
const items = (node.content || []).map((item) => `<li>${blocksToHTML(item.content || [])}</li>`).join('');
|
|
1046
|
+
return `<ul>${items}</ul>`;
|
|
1047
|
+
}
|
|
1048
|
+
case 'orderedList': {
|
|
1049
|
+
const start = node.attrs?.start || 1;
|
|
1050
|
+
const items = (node.content || []).map((item) => `<li>${blocksToHTML(item.content || [])}</li>`).join('');
|
|
1051
|
+
return `<ol start="${start}">${items}</ol>`;
|
|
1052
|
+
}
|
|
1053
|
+
case 'listItem':
|
|
1054
|
+
return blocksToHTML(node.content || []);
|
|
1055
|
+
// ─── Checklists (Tiptap names) ───
|
|
1056
|
+
case 'taskList': {
|
|
1057
|
+
const items = (node.content || []).map((item) => {
|
|
1058
|
+
const checked = item.attrs?.checked;
|
|
1059
|
+
const icon = checked ? '☑' : '☐';
|
|
1060
|
+
const style = checked ? 'opacity:0.6;text-decoration:line-through;' : '';
|
|
1061
|
+
return `<li><span class="check-icon">${icon}</span><span style="${style}">${blocksToHTML(item.content || [])}</span></li>`;
|
|
1062
|
+
}).join('');
|
|
1063
|
+
return `<ul class="checklist">${items}</ul>`;
|
|
1064
|
+
}
|
|
1065
|
+
case 'taskItem':
|
|
1066
|
+
return blocksToHTML(node.content || []);
|
|
1067
|
+
// ─── Code ───
|
|
1068
|
+
case 'codeBlock': {
|
|
1069
|
+
const lang = node.attrs?.language || '';
|
|
1070
|
+
const text = extractTextWithNewlines(node);
|
|
1071
|
+
return `<pre><code class="language-${escapeHtml(lang)}">${escapeHtml(text)}</code></pre>`;
|
|
1072
|
+
}
|
|
1073
|
+
// ─── Quote ───
|
|
1074
|
+
case 'blockquote':
|
|
1075
|
+
return `<blockquote>${blocksToHTML(node.content || [])}</blockquote>`;
|
|
1076
|
+
// ─── Callout ───
|
|
1077
|
+
case 'callout': {
|
|
1078
|
+
const variant = node.attrs?.variant || 'info';
|
|
1079
|
+
return `<div class="callout callout-${escapeHtml(variant)}">${blocksToHTML(node.content || [])}</div>`;
|
|
1080
|
+
}
|
|
1081
|
+
// ─── Table (Tiptap names) ───
|
|
1082
|
+
case 'table': {
|
|
1083
|
+
const rows = (node.content || []).map((row) => {
|
|
1084
|
+
const cells = (row.content || []).map((cell) => {
|
|
1085
|
+
const tag = cell.type === 'tableHeader' ? 'th' : 'td';
|
|
1086
|
+
const colspan = cell.attrs?.colspan > 1 ? ` colspan="${cell.attrs.colspan}"` : '';
|
|
1087
|
+
const rowspan = cell.attrs?.rowspan > 1 ? ` rowspan="${cell.attrs.rowspan}"` : '';
|
|
1088
|
+
return `<${tag}${colspan}${rowspan}>${blocksToHTML(cell.content || [])}</${tag}>`;
|
|
1089
|
+
}).join('');
|
|
1090
|
+
return `<tr>${cells}</tr>`;
|
|
1091
|
+
}).join('');
|
|
1092
|
+
return `<table>${rows}</table>`;
|
|
1093
|
+
}
|
|
1094
|
+
// ─── Mermaid (attrs.src) ───
|
|
1095
|
+
case 'mermaid': {
|
|
1096
|
+
const mermaidTitle = node.attrs?.title || '';
|
|
1097
|
+
const src = node.attrs?.src || extractText(node);
|
|
1098
|
+
// Don't escapeHtml mermaid — it needs raw text. But we do need to
|
|
1099
|
+
// convert literal \n strings to real newlines for the JS renderer.
|
|
1100
|
+
const mermaidTitleHtml = mermaidTitle ? `<div class="block-title">${escapeHtml(mermaidTitle)}</div>` : '';
|
|
1101
|
+
return `${mermaidTitleHtml}<div class="mermaid">${src}</div>`;
|
|
1102
|
+
}
|
|
1103
|
+
// ─── Chart (attrs.json + attrs.chartType) ───
|
|
1104
|
+
case 'chart': {
|
|
1105
|
+
const chartTitle = node.attrs?.title || '';
|
|
1106
|
+
const chartType = node.attrs?.chartType || node.attrs?.charttype || 'bar';
|
|
1107
|
+
const json = node.attrs?.json || '';
|
|
1108
|
+
const chartTitleHtml = chartTitle ? `<div class="block-title">${escapeHtml(chartTitle)}</div>` : '';
|
|
1109
|
+
return `${chartTitleHtml}<div class="chart-container" data-chart-type="${escapeHtml(chartType)}" data-chart-json="${escapeHtml(json)}"><canvas></canvas></div>`;
|
|
1110
|
+
}
|
|
1111
|
+
// ─── Math (attrs.src, rendered via KaTeX) ───
|
|
1112
|
+
case 'math': {
|
|
1113
|
+
const mathTitle = node.attrs?.title || '';
|
|
1114
|
+
let src = node.attrs?.src || extractText(node);
|
|
1115
|
+
// Normalize double backslashes to single (jot parser preserves literal \\, KaTeX needs \)
|
|
1116
|
+
// Using String.fromCharCode to avoid backslash escaping issues in bundled code
|
|
1117
|
+
const dblBs = String.fromCharCode(92, 92);
|
|
1118
|
+
const sglBs = String.fromCharCode(92);
|
|
1119
|
+
while (src.includes(dblBs)) {
|
|
1120
|
+
src = src.split(dblBs).join(sglBs);
|
|
1121
|
+
}
|
|
1122
|
+
// Base64 encode LaTeX to preserve backslashes through HTML attribute
|
|
1123
|
+
const texB64 = typeof btoa === 'function' ? btoa(unescape(encodeURIComponent(src))) : Buffer.from(src, 'utf-8').toString('base64');
|
|
1124
|
+
const mathTitleHtml = mathTitle ? `<div class="block-title">${escapeHtml(mathTitle)}</div>` : '';
|
|
1125
|
+
return `${mathTitleHtml}<div class="math-block" data-tex-b64="${texB64}"></div>`;
|
|
1126
|
+
}
|
|
1127
|
+
// ─── Image (inline Tiptap image) ───
|
|
1128
|
+
case 'image': {
|
|
1129
|
+
const imgSrc = node.attrs?.src || '';
|
|
1130
|
+
const imgAlt = node.attrs?.alt || '';
|
|
1131
|
+
return `<img src="${escapeHtml(imgSrc)}" alt="${escapeHtml(imgAlt)}" />`;
|
|
1132
|
+
}
|
|
1133
|
+
// ─── ImageBlock (jotx custom image with caption/alignment) ───
|
|
1134
|
+
case 'imageBlock': {
|
|
1135
|
+
const ibSrc = node.attrs?.src || '';
|
|
1136
|
+
const ibAlt = node.attrs?.alt || '';
|
|
1137
|
+
const ibCaption = node.attrs?.caption || '';
|
|
1138
|
+
const ibAlign = node.attrs?.align || 'center';
|
|
1139
|
+
const ibWidth = node.attrs?.width || 800;
|
|
1140
|
+
return `<div class="image-block" style="text-align:${ibAlign};">
|
|
1141
|
+
<img src="${escapeHtml(ibSrc)}" alt="${escapeHtml(ibAlt)}" style="max-width:${Math.min(ibWidth, 900)}px;width:100%;" />
|
|
1142
|
+
${ibCaption ? `<p class="image-caption">${escapeHtml(ibCaption)}</p>` : ''}
|
|
1143
|
+
</div>`;
|
|
1144
|
+
}
|
|
1145
|
+
// ─── FloatImageBlock ───
|
|
1146
|
+
case 'floatImageBlock': {
|
|
1147
|
+
const fiSrc = node.attrs?.src || '';
|
|
1148
|
+
const fiAlt = node.attrs?.alt || '';
|
|
1149
|
+
const fiFloat = node.attrs?.float || 'right';
|
|
1150
|
+
const fiWidth = node.attrs?.width || 300;
|
|
1151
|
+
return `<img src="${escapeHtml(fiSrc)}" alt="${escapeHtml(fiAlt)}" style="float:${fiFloat};max-width:${fiWidth}px;margin:${fiFloat === 'left' ? '0 16px 8px 0' : '0 0 8px 16px'};" />`;
|
|
1152
|
+
}
|
|
1153
|
+
// ─── VideoBlock ───
|
|
1154
|
+
case 'videoBlock': {
|
|
1155
|
+
const vSrc = node.attrs?.src || '';
|
|
1156
|
+
const vType = node.attrs?.type || 'local';
|
|
1157
|
+
const vTitle = node.attrs?.title || '';
|
|
1158
|
+
if (vType === 'youtube') {
|
|
1159
|
+
// Extract YouTube ID
|
|
1160
|
+
const ytMatch = vSrc.match(/(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))([^&?#]+)/);
|
|
1161
|
+
const ytId = ytMatch ? ytMatch[1] : vSrc;
|
|
1162
|
+
const ytUrl = `https://www.youtube.com/watch?v=${escapeHtml(ytId)}`;
|
|
1163
|
+
const thumbUrl = `https://img.youtube.com/vi/${escapeHtml(ytId)}/hqdefault.jpg`;
|
|
1164
|
+
return `<div style="text-align:center;margin:0.5em 0;">
|
|
1165
|
+
<a href="${ytUrl}" target="_blank" rel="noopener" style="display:inline-block;position:relative;cursor:pointer;text-decoration:none;">
|
|
1166
|
+
<img src="${thumbUrl}" alt="${escapeHtml(vTitle || 'YouTube Video')}" style="width:640px;max-width:90%;border-radius:6px;" />
|
|
1167
|
+
<div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:68px;height:48px;background:rgba(0,0,0,0.7);border-radius:12px;display:flex;align-items:center;justify-content:center;">
|
|
1168
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="white"><polygon points="6,4 20,12 6,20"/></svg>
|
|
1169
|
+
</div>
|
|
1170
|
+
</a>
|
|
1171
|
+
${vTitle ? `<p class="image-caption">${escapeHtml(vTitle)} — <a href="${ytUrl}" target="_blank" style="color:#89b4fa;">Open on YouTube ↗</a></p>` : `<p class="image-caption"><a href="${ytUrl}" target="_blank" style="color:#89b4fa;">Open on YouTube ↗</a></p>`}
|
|
1172
|
+
</div>`;
|
|
1173
|
+
}
|
|
1174
|
+
return `<div style="text-align:center;margin:0.5em 0;">
|
|
1175
|
+
<video src="${escapeHtml(vSrc)}" controls style="max-width:640px;border-radius:6px;"></video>
|
|
1176
|
+
${vTitle ? `<p class="image-caption">${escapeHtml(vTitle)}</p>` : ''}
|
|
1177
|
+
</div>`;
|
|
1178
|
+
}
|
|
1179
|
+
// ─── Columns ───
|
|
1180
|
+
case 'columns': {
|
|
1181
|
+
const cols = (node.content || []).map((col) => `<div>${blocksToHTML(col.content || [])}</div>`).join('');
|
|
1182
|
+
return `<div class="slide-columns">${cols}</div>`;
|
|
1183
|
+
}
|
|
1184
|
+
// ─── Column (child of columns) ───
|
|
1185
|
+
case 'column':
|
|
1186
|
+
return blocksToHTML(node.content || []);
|
|
1187
|
+
// ─── GridCard (card grid layout) ───
|
|
1188
|
+
case 'gridcard': {
|
|
1189
|
+
const layout = node.attrs?.layout || '2x2';
|
|
1190
|
+
const [gridCols] = layout.split('x').map(Number);
|
|
1191
|
+
const cards = (node.content || []).map((card) => {
|
|
1192
|
+
const cTitle = card.attrs?.title || '';
|
|
1193
|
+
const cValue = card.attrs?.value || '';
|
|
1194
|
+
const cIcon = card.attrs?.icon || '';
|
|
1195
|
+
const cColor = card.attrs?.color || 'teal';
|
|
1196
|
+
return `<div class="slide-card slide-card-${escapeHtml(cColor)}">
|
|
1197
|
+
${cIcon ? `<div class="card-icon">${escapeHtml(cIcon)}</div>` : ''}
|
|
1198
|
+
<div class="card-title">${escapeHtml(cTitle)}</div>
|
|
1199
|
+
${cValue ? `<div class="card-value">${escapeHtml(cValue)}</div>` : ''}
|
|
1200
|
+
</div>`;
|
|
1201
|
+
}).join('');
|
|
1202
|
+
return `<div class="slide-grid" style="grid-template-columns:repeat(${gridCols},1fr);">${cards}</div>`;
|
|
1203
|
+
}
|
|
1204
|
+
// ─── Card (standalone, child of gridcard) ───
|
|
1205
|
+
case 'card': {
|
|
1206
|
+
const cardTitle = node.attrs?.title || '';
|
|
1207
|
+
const cardValue = node.attrs?.value || '';
|
|
1208
|
+
const cardIcon = node.attrs?.icon || '';
|
|
1209
|
+
const cardColor = node.attrs?.color || 'teal';
|
|
1210
|
+
return `<div class="slide-card slide-card-${escapeHtml(cardColor)}">
|
|
1211
|
+
${cardIcon ? `<div class="card-icon">${escapeHtml(cardIcon)}</div>` : ''}
|
|
1212
|
+
<div class="card-title">${escapeHtml(cardTitle)}</div>
|
|
1213
|
+
${cardValue ? `<div class="card-value">${escapeHtml(cardValue)}</div>` : ''}
|
|
1214
|
+
</div>`;
|
|
1215
|
+
}
|
|
1216
|
+
// ─── Button ───
|
|
1217
|
+
case 'button': {
|
|
1218
|
+
const bLabel = node.attrs?.label || 'Button';
|
|
1219
|
+
const bStyle = node.attrs?.style || 'primary';
|
|
1220
|
+
const bAlign = node.attrs?.align || 'left';
|
|
1221
|
+
return `<div style="text-align:${bAlign};margin:0.3em 0;">
|
|
1222
|
+
<span class="slide-button slide-button-${escapeHtml(bStyle)}">${escapeHtml(bLabel)}</span>
|
|
1223
|
+
</div>`;
|
|
1224
|
+
}
|
|
1225
|
+
// ─── ButtonStrip ───
|
|
1226
|
+
case 'buttonstrip': {
|
|
1227
|
+
const buttons = (node.content || []).map((btn) => {
|
|
1228
|
+
const btnLabel = btn.attrs?.label || 'Button';
|
|
1229
|
+
const btnStyle = btn.attrs?.style || 'primary';
|
|
1230
|
+
return `<span class="slide-button slide-button-${escapeHtml(btnStyle)}">${escapeHtml(btnLabel)}</span>`;
|
|
1231
|
+
}).join('');
|
|
1232
|
+
return `<div class="slide-buttonstrip">${buttons}</div>`;
|
|
1233
|
+
}
|
|
1234
|
+
// ─── LinkBlock ───
|
|
1235
|
+
case 'linkBlock': {
|
|
1236
|
+
const linkHref = node.attrs?.href || '';
|
|
1237
|
+
const linkText = inlineToHTML(node.content) || escapeHtml(linkHref);
|
|
1238
|
+
return `<div class="slide-link"><a href="${escapeHtml(linkHref)}" target="_blank">🔗 ${linkText}</a></div>`;
|
|
1239
|
+
}
|
|
1240
|
+
// ─── Attach ───
|
|
1241
|
+
case 'attach': {
|
|
1242
|
+
const attachPath = node.attrs?.path || '';
|
|
1243
|
+
const attachText = node.attrs?.text || attachPath;
|
|
1244
|
+
return `<div class="slide-link">📎 ${escapeHtml(attachText)}</div>`;
|
|
1245
|
+
}
|
|
1246
|
+
// ─── DateTime ───
|
|
1247
|
+
case 'datetime': {
|
|
1248
|
+
const dtValue = node.attrs?.value || '';
|
|
1249
|
+
const dtMode = node.attrs?.mode || 'datetime';
|
|
1250
|
+
let display = dtValue;
|
|
1251
|
+
try {
|
|
1252
|
+
const d = new Date(dtValue);
|
|
1253
|
+
if (!isNaN(d.getTime())) {
|
|
1254
|
+
if (dtMode === 'date')
|
|
1255
|
+
display = d.toLocaleDateString();
|
|
1256
|
+
else if (dtMode === 'time')
|
|
1257
|
+
display = d.toLocaleTimeString();
|
|
1258
|
+
else
|
|
1259
|
+
display = d.toLocaleString();
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
catch { /* use raw value */ }
|
|
1263
|
+
return `<span class="slide-datetime">📅 ${escapeHtml(display)}</span>`;
|
|
1264
|
+
}
|
|
1265
|
+
// ─── JotxLink (internal link) ───
|
|
1266
|
+
case 'jotxlink': {
|
|
1267
|
+
const jlTarget = node.attrs?.target || '';
|
|
1268
|
+
const jlText = node.attrs?.text || jlTarget;
|
|
1269
|
+
return `<span class="slide-jotxlink">📄 ${escapeHtml(jlText)}</span>`;
|
|
1270
|
+
}
|
|
1271
|
+
// ─── CodeReference ───
|
|
1272
|
+
case 'codeReference': {
|
|
1273
|
+
const crPath = node.attrs?.path || '';
|
|
1274
|
+
const crLang = node.attrs?.language || '';
|
|
1275
|
+
const crText = node.attrs?.text || '';
|
|
1276
|
+
const crLines = node.attrs?.lines || '';
|
|
1277
|
+
return `<div>
|
|
1278
|
+
<div style="font-size:0.7em;opacity:0.6;margin-bottom:4px;">📁 ${escapeHtml(crPath)}${crLines ? ` (L${escapeHtml(crLines)})` : ''}</div>
|
|
1279
|
+
${crText ? `<pre><code class="language-${escapeHtml(crLang)}">${escapeHtml(crText)}</code></pre>` : ''}
|
|
1280
|
+
</div>`;
|
|
1281
|
+
}
|
|
1282
|
+
// ─── LinkCard (URL preview) ───
|
|
1283
|
+
case 'linkcard': {
|
|
1284
|
+
const lcUrl = node.attrs?.url || '';
|
|
1285
|
+
const lcTitle = node.attrs?.title || lcUrl;
|
|
1286
|
+
const lcDesc = node.attrs?.description || '';
|
|
1287
|
+
const lcImage = node.attrs?.image || '';
|
|
1288
|
+
return `<div class="slide-linkcard">
|
|
1289
|
+
${lcImage ? `<img src="${escapeHtml(lcImage)}" style="width:60px;height:60px;object-fit:cover;border-radius:4px;float:left;margin-right:12px;" />` : ''}
|
|
1290
|
+
<div>
|
|
1291
|
+
<strong>${escapeHtml(lcTitle)}</strong>
|
|
1292
|
+
${lcDesc ? `<br><span style="font-size:0.85em;opacity:0.7;">${escapeHtml(lcDesc)}</span>` : ''}
|
|
1293
|
+
<br><a href="${escapeHtml(lcUrl)}" style="font-size:0.75em;" target="_blank">${escapeHtml(lcUrl)}</a>
|
|
1294
|
+
</div>
|
|
1295
|
+
</div>`;
|
|
1296
|
+
}
|
|
1297
|
+
// ─── Properties ───
|
|
1298
|
+
case 'properties': {
|
|
1299
|
+
const items = (node.content || []).map((prop) => {
|
|
1300
|
+
const key = prop.attrs?.key || '';
|
|
1301
|
+
const value = prop.attrs?.value || '';
|
|
1302
|
+
return `<tr><td><strong>${escapeHtml(key)}</strong></td><td>${escapeHtml(value)}</td></tr>`;
|
|
1303
|
+
}).join('');
|
|
1304
|
+
return items ? `<table class="properties-table">${items}</table>` : '';
|
|
1305
|
+
}
|
|
1306
|
+
// ─── Section (container) ───
|
|
1307
|
+
case 'section': {
|
|
1308
|
+
const sTitle = node.attrs?.title || '';
|
|
1309
|
+
const titleHtml = sTitle ? `<h3>${escapeHtml(sTitle)}</h3>` : '';
|
|
1310
|
+
return `${titleHtml}${blocksToHTML(node.content || [])}`;
|
|
1311
|
+
}
|
|
1312
|
+
// ─── Toggle (collapsible — rendered expanded in slides) ───
|
|
1313
|
+
case 'toggle': {
|
|
1314
|
+
const tTitle = node.attrs?.title || 'Toggle';
|
|
1315
|
+
return `<div class="slide-toggle">
|
|
1316
|
+
<div class="toggle-header">▸ ${escapeHtml(tTitle)}</div>
|
|
1317
|
+
<div class="toggle-content">${blocksToHTML(node.content || [])}</div>
|
|
1318
|
+
</div>`;
|
|
1319
|
+
}
|
|
1320
|
+
// ─── Divider within a slide ───
|
|
1321
|
+
case 'divider':
|
|
1322
|
+
case 'horizontalRule':
|
|
1323
|
+
return '<hr style="border:none;border-top:1px solid rgba(255,255,255,0.15);margin:0.5em 0;">';
|
|
1324
|
+
// ─── SlideProperties (skip in rendering) ───
|
|
1325
|
+
case 'slideproperties':
|
|
1326
|
+
return '';
|
|
1327
|
+
default:
|
|
1328
|
+
// Fallback: try to extract content recursively
|
|
1329
|
+
if (node.content)
|
|
1330
|
+
return blocksToHTML(node.content);
|
|
1331
|
+
if (node.text)
|
|
1332
|
+
return `<p>${escapeHtml(node.text)}</p>`;
|
|
1333
|
+
return '';
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
function inlineToHTML(content) {
|
|
1337
|
+
if (!content)
|
|
1338
|
+
return '';
|
|
1339
|
+
return content.map((node) => {
|
|
1340
|
+
if (node.type === 'text') {
|
|
1341
|
+
let text = escapeHtml(node.text || '');
|
|
1342
|
+
const marks = node.marks || [];
|
|
1343
|
+
for (const mark of marks) {
|
|
1344
|
+
switch (mark.type) {
|
|
1345
|
+
case 'bold':
|
|
1346
|
+
text = `<strong>${text}</strong>`;
|
|
1347
|
+
break;
|
|
1348
|
+
case 'italic':
|
|
1349
|
+
text = `<em>${text}</em>`;
|
|
1350
|
+
break;
|
|
1351
|
+
case 'code':
|
|
1352
|
+
text = `<code>${text}</code>`;
|
|
1353
|
+
break;
|
|
1354
|
+
case 'strike':
|
|
1355
|
+
text = `<del>${text}</del>`;
|
|
1356
|
+
break;
|
|
1357
|
+
case 'link':
|
|
1358
|
+
text = `<a href="${escapeHtml(mark.attrs?.href || '')}">${text}</a>`;
|
|
1359
|
+
break;
|
|
1360
|
+
case 'highlight':
|
|
1361
|
+
text = `<mark>${text}</mark>`;
|
|
1362
|
+
break;
|
|
1363
|
+
case 'underline':
|
|
1364
|
+
text = `<u>${text}</u>`;
|
|
1365
|
+
break;
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
return text;
|
|
1369
|
+
}
|
|
1370
|
+
if (node.type === 'hardBreak')
|
|
1371
|
+
return '<br>';
|
|
1372
|
+
return '';
|
|
1373
|
+
}).join('');
|
|
1374
|
+
}
|
|
1375
|
+
function extractText(node) {
|
|
1376
|
+
if (node.text)
|
|
1377
|
+
return node.text;
|
|
1378
|
+
if (node.content)
|
|
1379
|
+
return node.content.map(extractText).join('');
|
|
1380
|
+
return '';
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* Extract text preserving newlines between block-level nodes (for code blocks)
|
|
1384
|
+
* Each text node in a codeBlock is a separate line
|
|
1385
|
+
*/
|
|
1386
|
+
function extractTextWithNewlines(node) {
|
|
1387
|
+
if (node.text)
|
|
1388
|
+
return node.text;
|
|
1389
|
+
if (node.content) {
|
|
1390
|
+
return node.content.map((child) => {
|
|
1391
|
+
if (child.type === 'text')
|
|
1392
|
+
return child.text || '';
|
|
1393
|
+
if (child.type === 'hardBreak')
|
|
1394
|
+
return '\n';
|
|
1395
|
+
return extractTextWithNewlines(child);
|
|
1396
|
+
}).join('');
|
|
1397
|
+
}
|
|
1398
|
+
return '';
|
|
1399
|
+
}
|
|
1400
|
+
function escapeHtml(text) {
|
|
1401
|
+
return text
|
|
1402
|
+
.replace(/&/g, '&')
|
|
1403
|
+
.replace(/</g, '<')
|
|
1404
|
+
.replace(/>/g, '>')
|
|
1405
|
+
.replace(/"/g, '"');
|
|
1406
|
+
}
|
|
1407
|
+
// ─── Theme CSS ───────────────────────────────────────────────────────
|
|
1408
|
+
function getThemeCSS(theme) {
|
|
1409
|
+
switch (theme) {
|
|
1410
|
+
case 'light':
|
|
1411
|
+
return `
|
|
1412
|
+
:root { --accent-color: #0d9488; --code-bg: #f1f5f9; --code-color: #1e293b; --table-border: rgba(0,0,0,0.15); --table-header-bg: rgba(0,0,0,0.05); }
|
|
1413
|
+
.reveal { background: #ffffff; color: #334155; }
|
|
1414
|
+
.reveal h1, .reveal h2, .reveal h3 { color: #0f766e; }
|
|
1415
|
+
.reveal a { color: #0d9488; }
|
|
1416
|
+
.reveal th { background: rgba(0,0,0,0.04); }
|
|
1417
|
+
.reveal th, .reveal td { border-color: rgba(0,0,0,0.1); }
|
|
1418
|
+
.reveal blockquote { background: rgba(0,0,0,0.03); border-color: #0d9488; }
|
|
1419
|
+
.callout-info { border-color: #0d9488; background: rgba(13,148,136,0.08); }
|
|
1420
|
+
`;
|
|
1421
|
+
case 'corporate':
|
|
1422
|
+
return `
|
|
1423
|
+
:root { --accent-color: #e94560; --code-bg: #16213e; --code-color: #e0e0e0; }
|
|
1424
|
+
.reveal { background: #1a1a2e; color: #eee; }
|
|
1425
|
+
.reveal h1, .reveal h2, .reveal h3 { color: #e94560; }
|
|
1426
|
+
.reveal a { color: #89dceb; }
|
|
1427
|
+
`;
|
|
1428
|
+
case 'minimal':
|
|
1429
|
+
return `
|
|
1430
|
+
:root { --accent-color: #6b7280; --code-bg: #f3f4f6; --code-color: #1f2937; --table-border: rgba(0,0,0,0.12); --table-header-bg: rgba(0,0,0,0.04); }
|
|
1431
|
+
.reveal { background: #fafafa; color: #374151; }
|
|
1432
|
+
.reveal h1, .reveal h2, .reveal h3 { color: #111827; font-weight: 400; }
|
|
1433
|
+
.reveal a { color: #4b5563; text-decoration: underline; }
|
|
1434
|
+
.reveal th { background: rgba(0,0,0,0.04); }
|
|
1435
|
+
.reveal th, .reveal td { border-color: rgba(0,0,0,0.08); }
|
|
1436
|
+
.reveal blockquote { background: rgba(0,0,0,0.03); }
|
|
1437
|
+
`;
|
|
1438
|
+
case 'ocean':
|
|
1439
|
+
return `
|
|
1440
|
+
:root { --accent-color: #38bdf8; --code-bg: #0c1929; --code-color: #bae6fd; }
|
|
1441
|
+
.reveal { background: #0f172a; color: #cbd5e1; }
|
|
1442
|
+
.reveal h1, .reveal h2, .reveal h3 { color: #38bdf8; }
|
|
1443
|
+
.reveal a { color: #7dd3fc; }
|
|
1444
|
+
`;
|
|
1445
|
+
case 'sunset':
|
|
1446
|
+
return `
|
|
1447
|
+
:root { --accent-color: #fb923c; --code-bg: #292524; --code-color: #fde68a; }
|
|
1448
|
+
.reveal { background: #1c1917; color: #e7e5e4; }
|
|
1449
|
+
.reveal h1, .reveal h2, .reveal h3 { color: #fb923c; }
|
|
1450
|
+
.reveal a { color: #fdba74; }
|
|
1451
|
+
`;
|
|
1452
|
+
case 'forest':
|
|
1453
|
+
return `
|
|
1454
|
+
:root { --accent-color: #4ade80; --code-bg: #052e16; --code-color: #bbf7d0; }
|
|
1455
|
+
.reveal { background: #022c22; color: #d1fae5; }
|
|
1456
|
+
.reveal h1, .reveal h2, .reveal h3 { color: #4ade80; }
|
|
1457
|
+
.reveal a { color: #86efac; }
|
|
1458
|
+
`;
|
|
1459
|
+
case 'dracula':
|
|
1460
|
+
return `
|
|
1461
|
+
:root { --accent-color: #bd93f9; --code-bg: #282a36; --code-color: #f8f8f2; }
|
|
1462
|
+
.reveal { background: #282a36; color: #f8f8f2; }
|
|
1463
|
+
.reveal h1, .reveal h2, .reveal h3 { color: #bd93f9; }
|
|
1464
|
+
.reveal a { color: #8be9fd; }
|
|
1465
|
+
.slide-header-line, .slide-footer-line { background: linear-gradient(90deg, #bd93f9, #ff79c6, #8be9fd); }
|
|
1466
|
+
`;
|
|
1467
|
+
case 'neon':
|
|
1468
|
+
return `
|
|
1469
|
+
:root { --accent-color: #00f0ff; --code-bg: #0a0a0a; --code-color: #00f0ff; }
|
|
1470
|
+
.reveal { background: #000000; color: #e0e0e0; }
|
|
1471
|
+
.reveal h1, .reveal h2, .reveal h3 { color: #00f0ff; text-shadow: 0 0 20px rgba(0,240,255,0.4); }
|
|
1472
|
+
.reveal a { color: #ff00ff; }
|
|
1473
|
+
.slide-header-line, .slide-footer-line { background: linear-gradient(90deg, #00f0ff, #ff00ff, #39ff14); }
|
|
1474
|
+
`;
|
|
1475
|
+
case 'rose':
|
|
1476
|
+
return `
|
|
1477
|
+
:root { --accent-color: #be185d; --code-bg: #fdf2f8; --code-color: #831843; --table-border: rgba(190,24,93,0.18); --table-header-bg: rgba(190,24,93,0.06); }
|
|
1478
|
+
.reveal { background: #fff1f2; color: #4a044e; }
|
|
1479
|
+
.reveal h1, .reveal h2, .reveal h3 { color: #be185d; }
|
|
1480
|
+
.reveal a { color: #db2777; }
|
|
1481
|
+
.reveal th { background: rgba(190,24,93,0.06); }
|
|
1482
|
+
.reveal th, .reveal td { border-color: rgba(190,24,93,0.15); }
|
|
1483
|
+
.reveal blockquote { background: rgba(190,24,93,0.04); border-color: #be185d; }
|
|
1484
|
+
.slide-header-line, .slide-footer-line { background: linear-gradient(90deg, #ec4899, #f472b6, #fda4af); }
|
|
1485
|
+
`;
|
|
1486
|
+
case 'solarized':
|
|
1487
|
+
return `
|
|
1488
|
+
:root { --accent-color: #268bd2; --code-bg: #fdf6e3; --code-color: #586e75; --table-border: rgba(0,0,0,0.15); --table-header-bg: rgba(0,0,0,0.05); }
|
|
1489
|
+
.reveal { background: #fdf6e3; color: #657b83; }
|
|
1490
|
+
.reveal h1, .reveal h2, .reveal h3 { color: #268bd2; }
|
|
1491
|
+
.reveal a { color: #2aa198; }
|
|
1492
|
+
.reveal th { background: rgba(0,0,0,0.04); }
|
|
1493
|
+
.reveal th, .reveal td { border-color: rgba(0,0,0,0.1); }
|
|
1494
|
+
.reveal blockquote { background: rgba(0,0,0,0.03); border-color: #268bd2; }
|
|
1495
|
+
`;
|
|
1496
|
+
case 'dark':
|
|
1497
|
+
default:
|
|
1498
|
+
return `
|
|
1499
|
+
:root { --accent-color: #94e2d5; --code-bg: #1e293b; --code-color: #e2e8f0; }
|
|
1500
|
+
.reveal { background: #0f172a; color: #e2e8f0; }
|
|
1501
|
+
.reveal h1, .reveal h2, .reveal h3 { color: #94e2d5; }
|
|
1502
|
+
.reveal a { color: #5eead4; }
|
|
1503
|
+
`;
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
//# sourceMappingURL=PresentationConverter.js.map
|