@pep/term-deck 1.0.14 → 1.0.16
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/bin/term-deck.d.ts +1 -0
- package/dist/bin/term-deck.js +1916 -0
- package/dist/bin/term-deck.js.map +1 -0
- package/dist/index.d.ts +670 -0
- package/dist/index.js +159 -0
- package/dist/index.js.map +1 -0
- package/package.json +16 -13
- package/bin/term-deck.js +0 -14
- package/bin/term-deck.ts +0 -45
- package/src/cli/__tests__/errors.test.ts +0 -201
- package/src/cli/__tests__/help.test.ts +0 -157
- package/src/cli/__tests__/init.test.ts +0 -110
- package/src/cli/commands/export.ts +0 -33
- package/src/cli/commands/init.ts +0 -125
- package/src/cli/commands/present.ts +0 -29
- package/src/cli/errors.ts +0 -77
- package/src/core/__tests__/slide.test.ts +0 -1759
- package/src/core/__tests__/theme.test.ts +0 -1103
- package/src/core/slide.ts +0 -509
- package/src/core/theme.ts +0 -388
- package/src/export/__tests__/recorder.test.ts +0 -566
- package/src/export/recorder.ts +0 -639
- package/src/index.ts +0 -36
- package/src/presenter/__tests__/main.test.ts +0 -244
- package/src/presenter/main.ts +0 -658
- package/src/renderer/__tests__/screen-extended.test.ts +0 -801
- package/src/renderer/__tests__/screen.test.ts +0 -525
- package/src/renderer/screen.ts +0 -671
- package/src/schemas/__tests__/config.test.ts +0 -429
- package/src/schemas/__tests__/slide.test.ts +0 -349
- package/src/schemas/__tests__/theme.test.ts +0 -970
- package/src/schemas/__tests__/validation.test.ts +0 -256
- package/src/schemas/config.ts +0 -58
- package/src/schemas/slide.ts +0 -56
- package/src/schemas/theme.ts +0 -203
- package/src/schemas/validation.ts +0 -64
- package/src/themes/matrix/index.ts +0 -53
|
@@ -0,0 +1,1916 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import 'url';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import matter from 'gray-matter';
|
|
6
|
+
import { mkdir, writeFile, rm, access, readFile, unlink } from 'fs/promises';
|
|
7
|
+
import fg from 'fast-glob';
|
|
8
|
+
import blessed from 'neo-blessed';
|
|
9
|
+
import gradient2 from 'gradient-string';
|
|
10
|
+
import 'yaml';
|
|
11
|
+
import 'deepmerge';
|
|
12
|
+
import { mermaidToAscii as mermaidToAscii$1 } from 'mermaid-ascii';
|
|
13
|
+
import figlet from 'figlet';
|
|
14
|
+
import { Command } from 'commander';
|
|
15
|
+
import { tmpdir } from 'os';
|
|
16
|
+
import { execa } from 'execa';
|
|
17
|
+
|
|
18
|
+
var __defProp = Object.defineProperty;
|
|
19
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
20
|
+
var __esm = (fn, res) => function __init() {
|
|
21
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
22
|
+
};
|
|
23
|
+
var __export = (target, all) => {
|
|
24
|
+
for (var name in all)
|
|
25
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
26
|
+
};
|
|
27
|
+
var init_esm_shims = __esm({
|
|
28
|
+
"node_modules/.pnpm/tsup@8.5.1_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3_yaml@2.8.2/node_modules/tsup/assets/esm_shims.js"() {
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
var HexColorSchema, GradientSchema, ThemeSchema, DEFAULT_THEME;
|
|
32
|
+
var init_theme = __esm({
|
|
33
|
+
"src/schemas/theme.ts"() {
|
|
34
|
+
init_esm_shims();
|
|
35
|
+
HexColorSchema = z.string().regex(/^#[0-9a-fA-F]{6}$/, {
|
|
36
|
+
message: "Color must be a valid hex color (e.g., #ff0066)"
|
|
37
|
+
});
|
|
38
|
+
GradientSchema = z.array(HexColorSchema).min(2, {
|
|
39
|
+
message: "Gradient must have at least 2 colors"
|
|
40
|
+
});
|
|
41
|
+
ThemeSchema = z.object({
|
|
42
|
+
// Theme metadata
|
|
43
|
+
name: z.string().min(1, { message: "Theme name is required" }),
|
|
44
|
+
description: z.string().optional(),
|
|
45
|
+
author: z.string().optional(),
|
|
46
|
+
version: z.string().optional(),
|
|
47
|
+
// Color palette
|
|
48
|
+
colors: z.object({
|
|
49
|
+
primary: HexColorSchema,
|
|
50
|
+
secondary: HexColorSchema.optional(),
|
|
51
|
+
accent: HexColorSchema,
|
|
52
|
+
background: HexColorSchema,
|
|
53
|
+
text: HexColorSchema,
|
|
54
|
+
muted: HexColorSchema,
|
|
55
|
+
success: HexColorSchema.optional(),
|
|
56
|
+
warning: HexColorSchema.optional(),
|
|
57
|
+
error: HexColorSchema.optional()
|
|
58
|
+
}),
|
|
59
|
+
// Named gradients for bigText
|
|
60
|
+
gradients: z.record(z.string(), GradientSchema).refine(
|
|
61
|
+
(g) => Object.keys(g).length >= 1,
|
|
62
|
+
{ message: "At least one gradient must be defined" }
|
|
63
|
+
),
|
|
64
|
+
// Glyph set for matrix rain background
|
|
65
|
+
glyphs: z.string().min(10, {
|
|
66
|
+
message: "Glyph set must have at least 10 characters"
|
|
67
|
+
}),
|
|
68
|
+
// Animation settings
|
|
69
|
+
animations: z.object({
|
|
70
|
+
// Speed multiplier (1.0 = normal, 0.5 = half speed, 2.0 = double speed)
|
|
71
|
+
revealSpeed: z.number().min(0.1).max(5).default(1),
|
|
72
|
+
// Matrix rain density (number of drops)
|
|
73
|
+
matrixDensity: z.number().min(10).max(200).default(50),
|
|
74
|
+
// Glitch effect iterations
|
|
75
|
+
glitchIterations: z.number().min(1).max(20).default(5),
|
|
76
|
+
// Delay between lines during reveal (ms)
|
|
77
|
+
lineDelay: z.number().min(0).max(500).default(30),
|
|
78
|
+
// Matrix rain update interval (ms)
|
|
79
|
+
matrixInterval: z.number().min(20).max(200).default(80)
|
|
80
|
+
}),
|
|
81
|
+
// Window appearance
|
|
82
|
+
window: z.object({
|
|
83
|
+
// Border style
|
|
84
|
+
borderStyle: z.enum(["line", "double", "rounded", "none"]).default("line"),
|
|
85
|
+
// Shadow effect
|
|
86
|
+
shadow: z.boolean().default(true),
|
|
87
|
+
// Padding inside windows
|
|
88
|
+
padding: z.object({
|
|
89
|
+
top: z.number().min(0).max(5).default(1),
|
|
90
|
+
bottom: z.number().min(0).max(5).default(1),
|
|
91
|
+
left: z.number().min(0).max(10).default(2),
|
|
92
|
+
right: z.number().min(0).max(10).default(2)
|
|
93
|
+
}).optional()
|
|
94
|
+
}).optional()
|
|
95
|
+
});
|
|
96
|
+
ThemeSchema.deepPartial();
|
|
97
|
+
DEFAULT_THEME = {
|
|
98
|
+
name: "matrix",
|
|
99
|
+
description: "Default cyberpunk/matrix theme",
|
|
100
|
+
colors: {
|
|
101
|
+
primary: "#00cc66",
|
|
102
|
+
accent: "#ff6600",
|
|
103
|
+
background: "#0a0a0a",
|
|
104
|
+
text: "#ffffff",
|
|
105
|
+
muted: "#666666"
|
|
106
|
+
},
|
|
107
|
+
gradients: {
|
|
108
|
+
fire: ["#ff6600", "#ff3300", "#ff0066"],
|
|
109
|
+
cool: ["#00ccff", "#0066ff", "#6600ff"],
|
|
110
|
+
pink: ["#ff0066", "#ff0099", "#cc00ff"],
|
|
111
|
+
hf: ["#99cc00", "#00cc66", "#00cccc"]
|
|
112
|
+
},
|
|
113
|
+
glyphs: "\uFF71\uFF72\uFF73\uFF74\uFF75\uFF76\uFF77\uFF78\uFF79\uFF7A\uFF7B\uFF7C\uFF7D\uFF7E\uFF7F\uFF80\uFF81\uFF82\uFF83\uFF84\uFF85\uFF86\uFF87\uFF88\uFF89\uFF8A\uFF8B\uFF8C\uFF8D\uFF8E\uFF8F\uFF90\uFF91\uFF92\uFF93\uFF94\uFF95\uFF96\uFF97\uFF98\uFF99\uFF9A\uFF9B\uFF9C\uFF9D0123456789",
|
|
114
|
+
animations: {
|
|
115
|
+
revealSpeed: 1,
|
|
116
|
+
matrixDensity: 50,
|
|
117
|
+
glitchIterations: 5,
|
|
118
|
+
lineDelay: 30,
|
|
119
|
+
matrixInterval: 80
|
|
120
|
+
},
|
|
121
|
+
window: {
|
|
122
|
+
borderStyle: "line",
|
|
123
|
+
shadow: true,
|
|
124
|
+
padding: {
|
|
125
|
+
top: 1,
|
|
126
|
+
bottom: 1,
|
|
127
|
+
left: 2,
|
|
128
|
+
right: 2
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
var SettingsSchema, ExportSettingsSchema, DeckConfigSchema;
|
|
135
|
+
var init_config = __esm({
|
|
136
|
+
"src/schemas/config.ts"() {
|
|
137
|
+
init_esm_shims();
|
|
138
|
+
init_theme();
|
|
139
|
+
SettingsSchema = z.object({
|
|
140
|
+
// Start slide (0-indexed)
|
|
141
|
+
startSlide: z.number().min(0).default(0),
|
|
142
|
+
// Loop back to first slide after last
|
|
143
|
+
loop: z.boolean().default(false),
|
|
144
|
+
// Auto-advance slides (ms, 0 = disabled)
|
|
145
|
+
autoAdvance: z.number().min(0).default(0),
|
|
146
|
+
// Show slide numbers
|
|
147
|
+
showSlideNumbers: z.boolean().default(false),
|
|
148
|
+
// Show progress bar
|
|
149
|
+
showProgress: z.boolean().default(false)
|
|
150
|
+
});
|
|
151
|
+
ExportSettingsSchema = z.object({
|
|
152
|
+
// Output width in characters (min 80, max 400)
|
|
153
|
+
width: z.number().min(80).max(400).default(120),
|
|
154
|
+
// Output height in characters (min 24, max 100)
|
|
155
|
+
height: z.number().min(24).max(100).default(40),
|
|
156
|
+
// Frames per second for video (min 10, max 60)
|
|
157
|
+
fps: z.number().min(10).max(60).default(30)
|
|
158
|
+
});
|
|
159
|
+
DeckConfigSchema = z.object({
|
|
160
|
+
// Presentation metadata
|
|
161
|
+
title: z.string().optional(),
|
|
162
|
+
author: z.string().optional(),
|
|
163
|
+
date: z.string().optional(),
|
|
164
|
+
// Theme (already validated Theme object)
|
|
165
|
+
theme: ThemeSchema,
|
|
166
|
+
// Presentation settings
|
|
167
|
+
settings: SettingsSchema.optional(),
|
|
168
|
+
// Export settings
|
|
169
|
+
export: ExportSettingsSchema.optional()
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// src/schemas/validation.ts
|
|
175
|
+
function formatZodError(error, context) {
|
|
176
|
+
const issues = error.issues.map((issue) => {
|
|
177
|
+
const path2 = issue.path.join(".");
|
|
178
|
+
return ` - ${path2 ? `${path2}: ` : ""}${issue.message}`;
|
|
179
|
+
});
|
|
180
|
+
return `Invalid ${context}:
|
|
181
|
+
${issues.join("\n")}`;
|
|
182
|
+
}
|
|
183
|
+
function safeParse(schema, data, context) {
|
|
184
|
+
const result = schema.safeParse(data);
|
|
185
|
+
if (!result.success) {
|
|
186
|
+
throw new ValidationError(formatZodError(result.error, context));
|
|
187
|
+
}
|
|
188
|
+
return result.data;
|
|
189
|
+
}
|
|
190
|
+
var ValidationError;
|
|
191
|
+
var init_validation = __esm({
|
|
192
|
+
"src/schemas/validation.ts"() {
|
|
193
|
+
init_esm_shims();
|
|
194
|
+
ValidationError = class extends Error {
|
|
195
|
+
constructor(message) {
|
|
196
|
+
super(message);
|
|
197
|
+
this.name = "ValidationError";
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
var SlideFrontmatterSchema, SlideSchema;
|
|
203
|
+
var init_slide = __esm({
|
|
204
|
+
"src/schemas/slide.ts"() {
|
|
205
|
+
init_esm_shims();
|
|
206
|
+
SlideFrontmatterSchema = z.object({
|
|
207
|
+
// Required: window title
|
|
208
|
+
title: z.string().min(1, {
|
|
209
|
+
message: "Slide must have a title"
|
|
210
|
+
}),
|
|
211
|
+
// ASCII art text (figlet) - can be a single line or multiple lines
|
|
212
|
+
bigText: z.union([
|
|
213
|
+
z.string(),
|
|
214
|
+
z.array(z.string())
|
|
215
|
+
]).optional(),
|
|
216
|
+
// Which gradient to use for bigText
|
|
217
|
+
gradient: z.string().optional(),
|
|
218
|
+
// Override theme for this slide
|
|
219
|
+
theme: z.string().optional(),
|
|
220
|
+
// Transition effect
|
|
221
|
+
transition: z.enum([
|
|
222
|
+
"glitch",
|
|
223
|
+
// Default: glitch reveal line by line
|
|
224
|
+
"fade",
|
|
225
|
+
// Fade in
|
|
226
|
+
"instant",
|
|
227
|
+
// No animation
|
|
228
|
+
"typewriter"
|
|
229
|
+
// Character by character
|
|
230
|
+
]).default("glitch"),
|
|
231
|
+
// Custom metadata (ignored by renderer, useful for tooling)
|
|
232
|
+
meta: z.record(z.string(), z.unknown()).optional()
|
|
233
|
+
});
|
|
234
|
+
SlideSchema = z.object({
|
|
235
|
+
// Parsed frontmatter
|
|
236
|
+
frontmatter: SlideFrontmatterSchema,
|
|
237
|
+
// Markdown body content
|
|
238
|
+
body: z.string(),
|
|
239
|
+
// Presenter notes (extracted from <!-- notes --> block)
|
|
240
|
+
notes: z.string().optional(),
|
|
241
|
+
// Source file path
|
|
242
|
+
sourcePath: z.string(),
|
|
243
|
+
// Slide index in deck (0-indexed)
|
|
244
|
+
index: z.number()
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
function extractNotes(content) {
|
|
249
|
+
const notesStart = content.indexOf(NOTES_MARKER);
|
|
250
|
+
if (notesStart === -1) {
|
|
251
|
+
return { body: content };
|
|
252
|
+
}
|
|
253
|
+
const body = content.slice(0, notesStart).trim();
|
|
254
|
+
const notesEnd = content.indexOf(NOTES_END_MARKER, notesStart);
|
|
255
|
+
let notes;
|
|
256
|
+
if (notesEnd !== -1) {
|
|
257
|
+
notes = content.slice(notesStart + NOTES_MARKER.length, notesEnd).trim();
|
|
258
|
+
} else {
|
|
259
|
+
notes = content.slice(notesStart + NOTES_MARKER.length).trim();
|
|
260
|
+
}
|
|
261
|
+
return { body, notes: notes || void 0 };
|
|
262
|
+
}
|
|
263
|
+
async function parseSlide(filePath, index) {
|
|
264
|
+
const content = await readFile(filePath, "utf-8");
|
|
265
|
+
const { data, content: rawBody } = matter(content);
|
|
266
|
+
const { body, notes } = extractNotes(rawBody);
|
|
267
|
+
const frontmatter = safeParse(
|
|
268
|
+
SlideFrontmatterSchema,
|
|
269
|
+
data,
|
|
270
|
+
`frontmatter in ${filePath}`
|
|
271
|
+
);
|
|
272
|
+
const slide = {
|
|
273
|
+
frontmatter,
|
|
274
|
+
body: body.trim(),
|
|
275
|
+
notes: notes?.trim(),
|
|
276
|
+
sourcePath: filePath,
|
|
277
|
+
index
|
|
278
|
+
};
|
|
279
|
+
return safeParse(SlideSchema, slide, `slide ${filePath}`);
|
|
280
|
+
}
|
|
281
|
+
var NOTES_MARKER, NOTES_END_MARKER, SlideParseError;
|
|
282
|
+
var init_slide2 = __esm({
|
|
283
|
+
"src/core/slide.ts"() {
|
|
284
|
+
init_esm_shims();
|
|
285
|
+
init_slide();
|
|
286
|
+
init_validation();
|
|
287
|
+
NOTES_MARKER = "<!-- notes -->";
|
|
288
|
+
NOTES_END_MARKER = "<!-- /notes -->";
|
|
289
|
+
SlideParseError = class extends Error {
|
|
290
|
+
/**
|
|
291
|
+
* @param message - The error message describing what went wrong
|
|
292
|
+
* @param filePath - Path to the slide file that failed to parse
|
|
293
|
+
* @param cause - Optional underlying error that caused this failure
|
|
294
|
+
*/
|
|
295
|
+
constructor(message, filePath, cause) {
|
|
296
|
+
super(message);
|
|
297
|
+
this.filePath = filePath;
|
|
298
|
+
this.cause = cause;
|
|
299
|
+
this.name = "SlideParseError";
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
async function findSlideFiles(dir) {
|
|
305
|
+
const pattern = join(dir, "*.md");
|
|
306
|
+
const foundFiles = await fg(pattern, { onlyFiles: true });
|
|
307
|
+
const files = [];
|
|
308
|
+
for (const filePath of foundFiles) {
|
|
309
|
+
const name = filePath.split("/").pop() || "";
|
|
310
|
+
if (name === "README.md" || name.startsWith("_")) {
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
files.push({
|
|
314
|
+
path: filePath,
|
|
315
|
+
name,
|
|
316
|
+
index: 0
|
|
317
|
+
// Will be set after sorting
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
files.sort((a, b) => a.name.localeCompare(b.name, void 0, { numeric: true }));
|
|
321
|
+
files.forEach((file, i) => {
|
|
322
|
+
file.index = i;
|
|
323
|
+
});
|
|
324
|
+
return files;
|
|
325
|
+
}
|
|
326
|
+
async function loadDeckConfig(slidesDir) {
|
|
327
|
+
const configPath = join(slidesDir, "deck.config.ts");
|
|
328
|
+
try {
|
|
329
|
+
try {
|
|
330
|
+
await access(configPath);
|
|
331
|
+
} catch {
|
|
332
|
+
return {
|
|
333
|
+
theme: DEFAULT_THEME
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
const cacheBuster = `${Date.now()}-${Math.random()}`;
|
|
337
|
+
const configModule = await import(configPath + "?t=" + cacheBuster);
|
|
338
|
+
if (!configModule.default) {
|
|
339
|
+
throw new Error("deck.config.ts must export default config");
|
|
340
|
+
}
|
|
341
|
+
return safeParse(DeckConfigSchema, configModule.default, "deck.config.ts");
|
|
342
|
+
} catch (error) {
|
|
343
|
+
if (error.code === "MODULE_NOT_FOUND") {
|
|
344
|
+
return { theme: DEFAULT_THEME };
|
|
345
|
+
}
|
|
346
|
+
throw error;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
async function loadDeck(slidesDir) {
|
|
350
|
+
const config = await loadDeckConfig(slidesDir);
|
|
351
|
+
const slideFiles = await findSlideFiles(slidesDir);
|
|
352
|
+
const slides = await Promise.all(
|
|
353
|
+
slideFiles.map((file) => parseSlide(file.path, file.index))
|
|
354
|
+
);
|
|
355
|
+
return {
|
|
356
|
+
slides,
|
|
357
|
+
config,
|
|
358
|
+
basePath: slidesDir
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
var DeckLoadError;
|
|
362
|
+
var init_deck_loader = __esm({
|
|
363
|
+
"src/core/deck-loader.ts"() {
|
|
364
|
+
init_esm_shims();
|
|
365
|
+
init_config();
|
|
366
|
+
init_validation();
|
|
367
|
+
init_theme();
|
|
368
|
+
init_slide2();
|
|
369
|
+
DeckLoadError = class extends Error {
|
|
370
|
+
/**
|
|
371
|
+
* @param message - The error message describing what went wrong
|
|
372
|
+
* @param slidesDir - Path to the directory that was being loaded
|
|
373
|
+
* @param cause - Optional underlying error that caused this failure
|
|
374
|
+
*/
|
|
375
|
+
constructor(message, slidesDir, cause) {
|
|
376
|
+
super(message);
|
|
377
|
+
this.slidesDir = slidesDir;
|
|
378
|
+
this.cause = cause;
|
|
379
|
+
this.name = "DeckLoadError";
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
function generateTrail(glyphs, length) {
|
|
385
|
+
return Array.from(
|
|
386
|
+
{ length },
|
|
387
|
+
() => glyphs[Math.floor(Math.random() * glyphs.length)]
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
function renderMatrixRain(screen, state) {
|
|
391
|
+
const { matrixBox, matrixDrops, theme } = state;
|
|
392
|
+
const width = Math.max(20, screen.width || 80);
|
|
393
|
+
const height = Math.max(10, screen.height || 24);
|
|
394
|
+
const grid = Array.from(
|
|
395
|
+
{ length: height },
|
|
396
|
+
() => Array(width).fill(" ")
|
|
397
|
+
);
|
|
398
|
+
for (const drop of matrixDrops) {
|
|
399
|
+
drop.y += drop.speed;
|
|
400
|
+
if (drop.y > height + drop.trail.length) {
|
|
401
|
+
drop.y = -drop.trail.length;
|
|
402
|
+
drop.x = Math.floor(Math.random() * width);
|
|
403
|
+
}
|
|
404
|
+
for (let i = 0; i < drop.trail.length; i++) {
|
|
405
|
+
const y = Math.floor(drop.y) - i;
|
|
406
|
+
if (y >= 0 && y < height && drop.x < width) {
|
|
407
|
+
grid[y][drop.x] = drop.trail[i];
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
let output = "";
|
|
412
|
+
for (let y = 0; y < height; y++) {
|
|
413
|
+
for (let x = 0; x < width; x++) {
|
|
414
|
+
const char = grid[y][x];
|
|
415
|
+
if (char !== " ") {
|
|
416
|
+
const brightness = Math.random() > 0.7 ? "{bold}" : "";
|
|
417
|
+
output += `${brightness}{${theme.colors.primary}-fg}${char}{/}`;
|
|
418
|
+
} else {
|
|
419
|
+
output += " ";
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if (y < height - 1) output += "\n";
|
|
423
|
+
}
|
|
424
|
+
matrixBox.setContent(output);
|
|
425
|
+
}
|
|
426
|
+
function initMatrixRain(screen, state) {
|
|
427
|
+
const { theme } = state;
|
|
428
|
+
const width = screen.width || 80;
|
|
429
|
+
const height = screen.height || 24;
|
|
430
|
+
const density = theme.animations.matrixDensity;
|
|
431
|
+
state.matrixDrops = [];
|
|
432
|
+
for (let i = 0; i < density; i++) {
|
|
433
|
+
state.matrixDrops.push({
|
|
434
|
+
x: Math.floor(Math.random() * width),
|
|
435
|
+
y: Math.floor(Math.random() * height),
|
|
436
|
+
speed: 0.3 + Math.random() * 0.7,
|
|
437
|
+
trail: generateTrail(theme.glyphs, 5 + Math.floor(Math.random() * 10))
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
state.matrixInterval = setInterval(() => {
|
|
441
|
+
renderMatrixRain(screen, state);
|
|
442
|
+
screen.render();
|
|
443
|
+
}, theme.animations.matrixInterval);
|
|
444
|
+
}
|
|
445
|
+
function stopMatrixRain(state) {
|
|
446
|
+
if (state.matrixInterval) {
|
|
447
|
+
clearInterval(state.matrixInterval);
|
|
448
|
+
state.matrixInterval = null;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
function createMatrixBox(screen) {
|
|
452
|
+
const matrixBox = blessed.box({
|
|
453
|
+
top: 0,
|
|
454
|
+
left: 0,
|
|
455
|
+
width: "100%",
|
|
456
|
+
height: "100%",
|
|
457
|
+
tags: true
|
|
458
|
+
});
|
|
459
|
+
screen.append(matrixBox);
|
|
460
|
+
return matrixBox;
|
|
461
|
+
}
|
|
462
|
+
var init_matrix_rain = __esm({
|
|
463
|
+
"src/renderer/effects/matrix-rain.ts"() {
|
|
464
|
+
init_esm_shims();
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
function getWindowColor(index, theme) {
|
|
468
|
+
const colors = [
|
|
469
|
+
theme.colors.primary,
|
|
470
|
+
theme.colors.accent,
|
|
471
|
+
theme.colors.secondary ?? theme.colors.primary,
|
|
472
|
+
"#ff0066",
|
|
473
|
+
// pink
|
|
474
|
+
"#9966ff",
|
|
475
|
+
// purple
|
|
476
|
+
"#ffcc00"
|
|
477
|
+
// yellow
|
|
478
|
+
];
|
|
479
|
+
return colors[index % colors.length];
|
|
480
|
+
}
|
|
481
|
+
function createWindow(screen, windowStack, theme, options) {
|
|
482
|
+
const windowIndex = windowStack.length;
|
|
483
|
+
const color = options.color ?? getWindowColor(windowIndex, theme);
|
|
484
|
+
const screenWidth = screen.width || 120;
|
|
485
|
+
const screenHeight = screen.height || 40;
|
|
486
|
+
const width = options.width ?? Math.floor(screenWidth * 0.75);
|
|
487
|
+
const height = options.height ?? Math.floor(screenHeight * 0.7);
|
|
488
|
+
const maxTop = Math.max(1, screenHeight - height - 2);
|
|
489
|
+
const maxLeft = Math.max(1, screenWidth - width - 2);
|
|
490
|
+
const top = options.top ?? Math.floor(Math.random() * maxTop);
|
|
491
|
+
const left = options.left ?? Math.floor(Math.random() * maxLeft);
|
|
492
|
+
const window = theme.window ?? { borderStyle: "line", shadow: true };
|
|
493
|
+
const padding = window.padding ?? { top: 1, bottom: 1, left: 2, right: 2 };
|
|
494
|
+
const box = blessed.box({
|
|
495
|
+
top,
|
|
496
|
+
left,
|
|
497
|
+
width,
|
|
498
|
+
height,
|
|
499
|
+
border: {
|
|
500
|
+
type: window.borderStyle === "none" ? void 0 : "line"
|
|
501
|
+
},
|
|
502
|
+
label: ` ${options.title} `,
|
|
503
|
+
style: {
|
|
504
|
+
fg: theme.colors.text,
|
|
505
|
+
bg: theme.colors.background,
|
|
506
|
+
border: { fg: color },
|
|
507
|
+
label: { fg: color, bold: true }
|
|
508
|
+
},
|
|
509
|
+
padding,
|
|
510
|
+
tags: true,
|
|
511
|
+
shadow: window.shadow
|
|
512
|
+
});
|
|
513
|
+
screen.append(box);
|
|
514
|
+
windowStack.push(box);
|
|
515
|
+
return box;
|
|
516
|
+
}
|
|
517
|
+
function clearWindows(windowStack) {
|
|
518
|
+
for (const window of windowStack) {
|
|
519
|
+
window.destroy();
|
|
520
|
+
}
|
|
521
|
+
windowStack.length = 0;
|
|
522
|
+
}
|
|
523
|
+
var init_window_manager = __esm({
|
|
524
|
+
"src/renderer/window-manager.ts"() {
|
|
525
|
+
init_esm_shims();
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
// src/core/theme-errors.ts
|
|
530
|
+
var ThemeError;
|
|
531
|
+
var init_theme_errors = __esm({
|
|
532
|
+
"src/core/theme-errors.ts"() {
|
|
533
|
+
init_esm_shims();
|
|
534
|
+
init_validation();
|
|
535
|
+
ThemeError = class extends Error {
|
|
536
|
+
/**
|
|
537
|
+
* @param message - The error message
|
|
538
|
+
* @param themeName - Optional name of the theme that caused the error
|
|
539
|
+
* @param path - Optional path to the theme file or package
|
|
540
|
+
*/
|
|
541
|
+
constructor(message, themeName, path2) {
|
|
542
|
+
super(message);
|
|
543
|
+
this.themeName = themeName;
|
|
544
|
+
this.path = path2;
|
|
545
|
+
this.name = "ThemeError";
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
function resolveColorToken(token, theme) {
|
|
551
|
+
switch (token) {
|
|
552
|
+
case "PRIMARY":
|
|
553
|
+
return theme.colors.primary;
|
|
554
|
+
case "SECONDARY":
|
|
555
|
+
return theme.colors.secondary ?? theme.colors.primary;
|
|
556
|
+
case "ACCENT":
|
|
557
|
+
return theme.colors.accent;
|
|
558
|
+
case "MUTED":
|
|
559
|
+
return theme.colors.muted;
|
|
560
|
+
case "TEXT":
|
|
561
|
+
return theme.colors.text;
|
|
562
|
+
case "BACKGROUND":
|
|
563
|
+
return theme.colors.background;
|
|
564
|
+
}
|
|
565
|
+
return BUILTIN_COLORS[token] ?? theme.colors.text;
|
|
566
|
+
}
|
|
567
|
+
function colorTokensToBlessedTags(content, theme) {
|
|
568
|
+
return content.replace(
|
|
569
|
+
/\{(GREEN|ORANGE|CYAN|PINK|WHITE|GRAY|PRIMARY|SECONDARY|ACCENT|MUTED|TEXT|BACKGROUND|\/)\}/g,
|
|
570
|
+
(_, token) => {
|
|
571
|
+
if (token === "/") {
|
|
572
|
+
return "{/}";
|
|
573
|
+
}
|
|
574
|
+
const color = resolveColorToken(token, theme);
|
|
575
|
+
return `{${color}-fg}`;
|
|
576
|
+
}
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
var BUILTIN_COLORS;
|
|
580
|
+
var init_theme_colors = __esm({
|
|
581
|
+
"src/core/theme-colors.ts"() {
|
|
582
|
+
init_esm_shims();
|
|
583
|
+
BUILTIN_COLORS = {
|
|
584
|
+
GREEN: "#00cc66",
|
|
585
|
+
ORANGE: "#ff6600",
|
|
586
|
+
CYAN: "#00ccff",
|
|
587
|
+
PINK: "#ff0066",
|
|
588
|
+
WHITE: "#ffffff",
|
|
589
|
+
GRAY: "#666666"
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
var init_theme2 = __esm({
|
|
594
|
+
"src/core/theme.ts"() {
|
|
595
|
+
init_esm_shims();
|
|
596
|
+
init_theme();
|
|
597
|
+
init_validation();
|
|
598
|
+
init_theme_errors();
|
|
599
|
+
init_theme_colors();
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
function hasMermaidDiagrams(content) {
|
|
603
|
+
MERMAID_BLOCK_PATTERN.lastIndex = 0;
|
|
604
|
+
return MERMAID_BLOCK_PATTERN.test(content);
|
|
605
|
+
}
|
|
606
|
+
function extractMermaidBlocks(content) {
|
|
607
|
+
const blocks = [];
|
|
608
|
+
let match;
|
|
609
|
+
MERMAID_BLOCK_PATTERN.lastIndex = 0;
|
|
610
|
+
while ((match = MERMAID_BLOCK_PATTERN.exec(content)) !== null) {
|
|
611
|
+
blocks.push(match[1].trim());
|
|
612
|
+
}
|
|
613
|
+
return blocks;
|
|
614
|
+
}
|
|
615
|
+
function formatMermaidError(code, _error) {
|
|
616
|
+
const lines = [
|
|
617
|
+
"\u250C\u2500 Diagram (parse error) \u2500\u2510",
|
|
618
|
+
"\u2502 \u2502"
|
|
619
|
+
];
|
|
620
|
+
const codeLines = code.split("\n").slice(0, 5);
|
|
621
|
+
for (const line of codeLines) {
|
|
622
|
+
const truncated = line.slice(0, 23).padEnd(23);
|
|
623
|
+
lines.push(`\u2502 ${truncated} \u2502`);
|
|
624
|
+
}
|
|
625
|
+
if (code.split("\n").length > 5) {
|
|
626
|
+
lines.push("\u2502 ... \u2502");
|
|
627
|
+
}
|
|
628
|
+
lines.push("\u2502 \u2502");
|
|
629
|
+
lines.push("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
630
|
+
return lines.join("\n");
|
|
631
|
+
}
|
|
632
|
+
function mermaidToAscii(mermaidCode) {
|
|
633
|
+
try {
|
|
634
|
+
return mermaidToAscii$1(mermaidCode);
|
|
635
|
+
} catch (error) {
|
|
636
|
+
return formatMermaidError(mermaidCode);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
function escapeRegex(str) {
|
|
640
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
641
|
+
}
|
|
642
|
+
function processMermaidDiagrams(content) {
|
|
643
|
+
if (!hasMermaidDiagrams(content)) {
|
|
644
|
+
return content;
|
|
645
|
+
}
|
|
646
|
+
let result = content;
|
|
647
|
+
const blocks = extractMermaidBlocks(content);
|
|
648
|
+
for (const block of blocks) {
|
|
649
|
+
const ascii = mermaidToAscii(block);
|
|
650
|
+
result = result.replace(
|
|
651
|
+
new RegExp("```mermaid\\n" + escapeRegex(block) + "\\n?```", "g"),
|
|
652
|
+
ascii
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
return result;
|
|
656
|
+
}
|
|
657
|
+
var MERMAID_BLOCK_PATTERN;
|
|
658
|
+
var init_mermaid = __esm({
|
|
659
|
+
"src/core/utils/mermaid.ts"() {
|
|
660
|
+
init_esm_shims();
|
|
661
|
+
MERMAID_BLOCK_PATTERN = /```mermaid\n([\s\S]*?)```/g;
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
// src/core/content-processor.ts
|
|
666
|
+
async function processSlideContent(body, theme) {
|
|
667
|
+
let processed = processMermaidDiagrams(body);
|
|
668
|
+
processed = colorTokensToBlessedTags(processed, theme);
|
|
669
|
+
return processed;
|
|
670
|
+
}
|
|
671
|
+
function normalizeBigText(bigText) {
|
|
672
|
+
if (!bigText) return [];
|
|
673
|
+
return Array.isArray(bigText) ? bigText : [bigText];
|
|
674
|
+
}
|
|
675
|
+
var init_content_processor = __esm({
|
|
676
|
+
"src/core/content-processor.ts"() {
|
|
677
|
+
init_esm_shims();
|
|
678
|
+
init_theme2();
|
|
679
|
+
init_mermaid();
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
// src/renderer/animations/constants.ts
|
|
684
|
+
var GLITCH_CHARS, PROTECTED_CHARS;
|
|
685
|
+
var init_constants = __esm({
|
|
686
|
+
"src/renderer/animations/constants.ts"() {
|
|
687
|
+
init_esm_shims();
|
|
688
|
+
GLITCH_CHARS = "\u2588\u2593\u2592\u2591\u2580\u2584\u258C\u2590\u25A0\u25A1\u25AA\u25AB\u25CF\u25CB\u25CA\u25D8\u25D9\u2666\u2663\u2660\u2665\u2605\u2606\u2302\u207F\xB2\xB3\xC6\xD8\u221E\u2248\u2260\xB1\xD7\xF7\u03B1\u03B2\u03B3\u03B4\u03B5\u03B6\u03B7\u03B8\u03BB\u03BC\u03C0\u03C3\u03C6\u03C9\u0394\u03A3\u03A9\uFF71\uFF72\uFF73\uFF74\uFF75\uFF76\uFF77\uFF78\uFF79\uFF7A\uFF7B\uFF7C\uFF7D\uFF7E\uFF7F\uFF80\uFF81\uFF82\uFF83\uFF84\uFF85\uFF86\uFF87\uFF88\uFF89\uFF8A\uFF8B\uFF8C\uFF8D\uFF8E\uFF8F\uFF90\uFF91\uFF92\uFF93\uFF94\uFF95\uFF96\uFF97\uFF98\uFF99\uFF9A\uFF9B\uFF9C\uFF9D";
|
|
689
|
+
PROTECTED_CHARS = /* @__PURE__ */ new Set([
|
|
690
|
+
" ",
|
|
691
|
+
" ",
|
|
692
|
+
"\n",
|
|
693
|
+
"{",
|
|
694
|
+
"}",
|
|
695
|
+
"-",
|
|
696
|
+
"/",
|
|
697
|
+
"#",
|
|
698
|
+
"[",
|
|
699
|
+
"]",
|
|
700
|
+
"(",
|
|
701
|
+
")",
|
|
702
|
+
":",
|
|
703
|
+
";",
|
|
704
|
+
",",
|
|
705
|
+
".",
|
|
706
|
+
"!",
|
|
707
|
+
"?",
|
|
708
|
+
"'",
|
|
709
|
+
'"',
|
|
710
|
+
"`",
|
|
711
|
+
"_",
|
|
712
|
+
"|",
|
|
713
|
+
"\\",
|
|
714
|
+
"<",
|
|
715
|
+
">",
|
|
716
|
+
"=",
|
|
717
|
+
"+",
|
|
718
|
+
"*",
|
|
719
|
+
"&",
|
|
720
|
+
"^",
|
|
721
|
+
"%",
|
|
722
|
+
"$",
|
|
723
|
+
"@",
|
|
724
|
+
"~",
|
|
725
|
+
// Box drawing
|
|
726
|
+
"\u250C",
|
|
727
|
+
"\u2510",
|
|
728
|
+
"\u2514",
|
|
729
|
+
"\u2518",
|
|
730
|
+
"\u2502",
|
|
731
|
+
"\u2500",
|
|
732
|
+
"\u251C",
|
|
733
|
+
"\u2524",
|
|
734
|
+
"\u252C",
|
|
735
|
+
"\u2534",
|
|
736
|
+
"\u253C",
|
|
737
|
+
"\u2550",
|
|
738
|
+
"\u2551",
|
|
739
|
+
"\u2554",
|
|
740
|
+
"\u2557",
|
|
741
|
+
"\u255A",
|
|
742
|
+
"\u255D",
|
|
743
|
+
"\u2560",
|
|
744
|
+
"\u2563",
|
|
745
|
+
"\u2566",
|
|
746
|
+
"\u2569",
|
|
747
|
+
"\u256C",
|
|
748
|
+
"\u256D",
|
|
749
|
+
"\u256E",
|
|
750
|
+
"\u256F",
|
|
751
|
+
"\u2570",
|
|
752
|
+
// Arrows
|
|
753
|
+
"\u2192",
|
|
754
|
+
"\u2190",
|
|
755
|
+
"\u2191",
|
|
756
|
+
"\u2193",
|
|
757
|
+
"\u25B6",
|
|
758
|
+
"\u25C0",
|
|
759
|
+
"\u25B2",
|
|
760
|
+
"\u25BC",
|
|
761
|
+
"\u25BA",
|
|
762
|
+
"\u25C4"
|
|
763
|
+
]);
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
// src/renderer/animations/helpers/animation-utils.ts
|
|
768
|
+
function sleep(ms) {
|
|
769
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
770
|
+
}
|
|
771
|
+
function renderContent(box, screen, content) {
|
|
772
|
+
box.setContent(content);
|
|
773
|
+
screen.render();
|
|
774
|
+
}
|
|
775
|
+
var init_animation_utils = __esm({
|
|
776
|
+
"src/renderer/animations/helpers/animation-utils.ts"() {
|
|
777
|
+
init_esm_shims();
|
|
778
|
+
}
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
// src/renderer/animations/transitions/glitch-transition.ts
|
|
782
|
+
async function glitchLine(box, screen, currentLines, newLine, iterations = 5) {
|
|
783
|
+
for (let i = iterations; i >= 0; i--) {
|
|
784
|
+
const scrambleRatio = i / iterations;
|
|
785
|
+
let scrambledLine = "";
|
|
786
|
+
for (const char of newLine) {
|
|
787
|
+
if (PROTECTED_CHARS.has(char)) {
|
|
788
|
+
scrambledLine += char;
|
|
789
|
+
} else if (Math.random() < scrambleRatio) {
|
|
790
|
+
scrambledLine += GLITCH_CHARS[Math.floor(Math.random() * GLITCH_CHARS.length)];
|
|
791
|
+
} else {
|
|
792
|
+
scrambledLine += char;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
renderContent(box, screen, [...currentLines, scrambledLine].join("\n"));
|
|
796
|
+
await sleep(20);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
async function lineByLineReveal(box, screen, content, theme) {
|
|
800
|
+
const lines = content.split("\n");
|
|
801
|
+
const revealedLines = [];
|
|
802
|
+
const lineDelay = theme.animations.lineDelay;
|
|
803
|
+
const glitchIterations = theme.animations.glitchIterations;
|
|
804
|
+
for (const line of lines) {
|
|
805
|
+
await glitchLine(box, screen, revealedLines, line, glitchIterations);
|
|
806
|
+
revealedLines.push(line);
|
|
807
|
+
renderContent(box, screen, revealedLines.join("\n"));
|
|
808
|
+
if (line.trim()) {
|
|
809
|
+
await sleep(lineDelay);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
var init_glitch_transition = __esm({
|
|
814
|
+
"src/renderer/animations/transitions/glitch-transition.ts"() {
|
|
815
|
+
init_esm_shims();
|
|
816
|
+
init_constants();
|
|
817
|
+
init_animation_utils();
|
|
818
|
+
}
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
// src/renderer/animations/transitions/fade-transition.ts
|
|
822
|
+
async function fadeInReveal(box, screen, content, theme) {
|
|
823
|
+
const steps = 10;
|
|
824
|
+
const delay = theme.animations.lineDelay * 2 / steps;
|
|
825
|
+
for (let step = 0; step < steps; step++) {
|
|
826
|
+
const revealRatio = step / steps;
|
|
827
|
+
let revealed = "";
|
|
828
|
+
for (const char of content) {
|
|
829
|
+
if (char === "\n" || PROTECTED_CHARS.has(char) || Math.random() < revealRatio) {
|
|
830
|
+
revealed += char;
|
|
831
|
+
} else {
|
|
832
|
+
revealed += " ";
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
renderContent(box, screen, revealed);
|
|
836
|
+
await sleep(delay);
|
|
837
|
+
}
|
|
838
|
+
renderContent(box, screen, content);
|
|
839
|
+
}
|
|
840
|
+
var init_fade_transition = __esm({
|
|
841
|
+
"src/renderer/animations/transitions/fade-transition.ts"() {
|
|
842
|
+
init_esm_shims();
|
|
843
|
+
init_constants();
|
|
844
|
+
init_animation_utils();
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
// src/renderer/animations/transitions/typewriter-transition.ts
|
|
849
|
+
async function typewriterReveal(box, screen, content, theme) {
|
|
850
|
+
const charDelay = theme.animations.lineDelay / 5;
|
|
851
|
+
let revealed = "";
|
|
852
|
+
for (const char of content) {
|
|
853
|
+
revealed += char;
|
|
854
|
+
renderContent(box, screen, revealed);
|
|
855
|
+
if (char !== " " && char !== "\n") {
|
|
856
|
+
await sleep(charDelay);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
var init_typewriter_transition = __esm({
|
|
861
|
+
"src/renderer/animations/transitions/typewriter-transition.ts"() {
|
|
862
|
+
init_esm_shims();
|
|
863
|
+
init_animation_utils();
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
// src/renderer/animations/transitions/instant-transition.ts
|
|
868
|
+
function instantReveal(box, screen, content) {
|
|
869
|
+
renderContent(box, screen, content);
|
|
870
|
+
}
|
|
871
|
+
var init_instant_transition = __esm({
|
|
872
|
+
"src/renderer/animations/transitions/instant-transition.ts"() {
|
|
873
|
+
init_esm_shims();
|
|
874
|
+
init_animation_utils();
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
// src/renderer/animations/transition-orchestrator.ts
|
|
879
|
+
async function applyTransition(box, screen, content, transition, theme) {
|
|
880
|
+
switch (transition) {
|
|
881
|
+
case "glitch":
|
|
882
|
+
await lineByLineReveal(box, screen, content, theme);
|
|
883
|
+
break;
|
|
884
|
+
case "fade":
|
|
885
|
+
await fadeInReveal(box, screen, content, theme);
|
|
886
|
+
break;
|
|
887
|
+
case "instant":
|
|
888
|
+
instantReveal(box, screen, content);
|
|
889
|
+
break;
|
|
890
|
+
case "typewriter":
|
|
891
|
+
await typewriterReveal(box, screen, content, theme);
|
|
892
|
+
break;
|
|
893
|
+
default:
|
|
894
|
+
instantReveal(box, screen, content);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
var init_transition_orchestrator = __esm({
|
|
898
|
+
"src/renderer/animations/transition-orchestrator.ts"() {
|
|
899
|
+
init_esm_shims();
|
|
900
|
+
init_glitch_transition();
|
|
901
|
+
init_fade_transition();
|
|
902
|
+
init_typewriter_transition();
|
|
903
|
+
init_instant_transition();
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
// src/renderer/animations/transitions.ts
|
|
908
|
+
var init_transitions = __esm({
|
|
909
|
+
"src/renderer/animations/transitions.ts"() {
|
|
910
|
+
init_esm_shims();
|
|
911
|
+
init_transition_orchestrator();
|
|
912
|
+
init_glitch_transition();
|
|
913
|
+
init_fade_transition();
|
|
914
|
+
init_typewriter_transition();
|
|
915
|
+
init_instant_transition();
|
|
916
|
+
}
|
|
917
|
+
});
|
|
918
|
+
async function generateBigText(text, gradientColors, font = "Standard") {
|
|
919
|
+
return new Promise((resolve, reject) => {
|
|
920
|
+
figlet.text(text, { font }, (err, result) => {
|
|
921
|
+
if (err || !result) {
|
|
922
|
+
reject(err ?? new Error("Failed to generate figlet text"));
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
const gradientFn = gradient2(gradientColors);
|
|
926
|
+
resolve(gradientFn(result));
|
|
927
|
+
});
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
async function generateMultiLineBigText(lines, gradientColors, font = "Standard") {
|
|
931
|
+
const results = await Promise.all(
|
|
932
|
+
lines.map((line) => generateBigText(line, gradientColors, font))
|
|
933
|
+
);
|
|
934
|
+
return results.join("\n");
|
|
935
|
+
}
|
|
936
|
+
var init_text_generator = __esm({
|
|
937
|
+
"src/renderer/text-generator.ts"() {
|
|
938
|
+
init_esm_shims();
|
|
939
|
+
}
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
// src/renderer/slide-renderer.ts
|
|
943
|
+
async function renderSlide(screen, windowStack, theme, slide) {
|
|
944
|
+
const { frontmatter, body } = slide;
|
|
945
|
+
const window = createWindow(screen, windowStack, theme, {
|
|
946
|
+
title: frontmatter.title
|
|
947
|
+
});
|
|
948
|
+
let content = "";
|
|
949
|
+
const bigTextLines = normalizeBigText(frontmatter.bigText);
|
|
950
|
+
if (bigTextLines.length > 0) {
|
|
951
|
+
const gradientName = frontmatter.gradient ?? "fire";
|
|
952
|
+
const gradientColors = theme.gradients[gradientName] ?? theme.gradients.fire;
|
|
953
|
+
const bigText = await generateMultiLineBigText(bigTextLines, gradientColors);
|
|
954
|
+
content += bigText + "\n\n";
|
|
955
|
+
}
|
|
956
|
+
const processedBody = await processSlideContent(body, theme);
|
|
957
|
+
content += processedBody;
|
|
958
|
+
const transition = frontmatter.transition ?? "glitch";
|
|
959
|
+
await applyTransition(window, screen, content, transition, theme);
|
|
960
|
+
return window;
|
|
961
|
+
}
|
|
962
|
+
var init_slide_renderer = __esm({
|
|
963
|
+
"src/renderer/slide-renderer.ts"() {
|
|
964
|
+
init_esm_shims();
|
|
965
|
+
init_content_processor();
|
|
966
|
+
init_transitions();
|
|
967
|
+
init_text_generator();
|
|
968
|
+
init_window_manager();
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
function createScreen(title = "term-deck") {
|
|
972
|
+
const screen = blessed.screen({
|
|
973
|
+
smartCSR: true,
|
|
974
|
+
title,
|
|
975
|
+
fullUnicode: true,
|
|
976
|
+
mouse: false,
|
|
977
|
+
altScreen: true
|
|
978
|
+
});
|
|
979
|
+
return screen;
|
|
980
|
+
}
|
|
981
|
+
function createRenderer(theme) {
|
|
982
|
+
const screen = createScreen();
|
|
983
|
+
const matrixBox = createMatrixBox(screen);
|
|
984
|
+
const matrixRain = {
|
|
985
|
+
matrixBox,
|
|
986
|
+
matrixDrops: [],
|
|
987
|
+
matrixInterval: null,
|
|
988
|
+
theme
|
|
989
|
+
};
|
|
990
|
+
const renderer = {
|
|
991
|
+
screen,
|
|
992
|
+
windowStack: [],
|
|
993
|
+
theme,
|
|
994
|
+
matrixRain
|
|
995
|
+
};
|
|
996
|
+
initMatrixRain(screen, matrixRain);
|
|
997
|
+
return renderer;
|
|
998
|
+
}
|
|
999
|
+
function destroyRenderer(renderer) {
|
|
1000
|
+
stopMatrixRain(renderer.matrixRain);
|
|
1001
|
+
clearWindows(renderer.windowStack);
|
|
1002
|
+
renderer.screen.destroy();
|
|
1003
|
+
}
|
|
1004
|
+
function clearWindows2(renderer) {
|
|
1005
|
+
clearWindows(renderer.windowStack);
|
|
1006
|
+
}
|
|
1007
|
+
async function renderSlide2(renderer, slide) {
|
|
1008
|
+
return renderSlide(renderer.screen, renderer.windowStack, renderer.theme, slide);
|
|
1009
|
+
}
|
|
1010
|
+
var init_screen = __esm({
|
|
1011
|
+
"src/renderer/screen.ts"() {
|
|
1012
|
+
init_esm_shims();
|
|
1013
|
+
init_matrix_rain();
|
|
1014
|
+
init_window_manager();
|
|
1015
|
+
init_slide_renderer();
|
|
1016
|
+
init_transitions();
|
|
1017
|
+
init_window_manager();
|
|
1018
|
+
init_text_generator();
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
async function findAvailableTty() {
|
|
1022
|
+
const candidates = [
|
|
1023
|
+
"/dev/ttys001",
|
|
1024
|
+
"/dev/ttys002",
|
|
1025
|
+
"/dev/ttys003",
|
|
1026
|
+
"/dev/pts/1",
|
|
1027
|
+
"/dev/pts/2"
|
|
1028
|
+
];
|
|
1029
|
+
for (const tty of candidates) {
|
|
1030
|
+
try {
|
|
1031
|
+
await access(tty);
|
|
1032
|
+
return tty;
|
|
1033
|
+
} catch {
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
throw new Error(
|
|
1037
|
+
"Could not find available TTY for notes window. Open a second terminal, run `tty`, and pass the path with --notes-tty"
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
async function createNotesWindow(ttyPath) {
|
|
1041
|
+
const blessed5 = (await import('neo-blessed')).default;
|
|
1042
|
+
const { openSync } = await import('fs');
|
|
1043
|
+
const tty = ttyPath ?? await findAvailableTty();
|
|
1044
|
+
const screen = blessed5.screen({
|
|
1045
|
+
smartCSR: true,
|
|
1046
|
+
title: "term-deck notes",
|
|
1047
|
+
fullUnicode: true,
|
|
1048
|
+
input: openSync(tty, "r"),
|
|
1049
|
+
output: openSync(tty, "w")
|
|
1050
|
+
});
|
|
1051
|
+
const contentBox = blessed5.box({
|
|
1052
|
+
top: 0,
|
|
1053
|
+
left: 0,
|
|
1054
|
+
width: "100%",
|
|
1055
|
+
height: "100%",
|
|
1056
|
+
tags: true,
|
|
1057
|
+
padding: 2,
|
|
1058
|
+
style: {
|
|
1059
|
+
fg: "#ffffff",
|
|
1060
|
+
bg: "#1a1a1a"
|
|
1061
|
+
}
|
|
1062
|
+
});
|
|
1063
|
+
screen.append(contentBox);
|
|
1064
|
+
screen.render();
|
|
1065
|
+
return {
|
|
1066
|
+
screen,
|
|
1067
|
+
contentBox,
|
|
1068
|
+
tty
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
function updateNotesWindow(notesWindow, currentSlide, nextSlide2, currentIndex, totalSlides) {
|
|
1072
|
+
const { contentBox, screen } = notesWindow;
|
|
1073
|
+
let content = "";
|
|
1074
|
+
content += `{bold}Slide ${currentIndex + 1} of ${totalSlides}{/bold}
|
|
1075
|
+
`;
|
|
1076
|
+
content += `{gray-fg}${currentSlide.frontmatter.title}{/}
|
|
1077
|
+
`;
|
|
1078
|
+
content += "\n";
|
|
1079
|
+
content += "\u2500".repeat(50) + "\n";
|
|
1080
|
+
content += "\n";
|
|
1081
|
+
if (currentSlide.notes) {
|
|
1082
|
+
content += "{bold}PRESENTER NOTES:{/bold}\n\n";
|
|
1083
|
+
content += currentSlide.notes + "\n";
|
|
1084
|
+
} else {
|
|
1085
|
+
content += "{gray-fg}No notes for this slide{/}\n";
|
|
1086
|
+
}
|
|
1087
|
+
content += "\n";
|
|
1088
|
+
content += "\u2500".repeat(50) + "\n";
|
|
1089
|
+
content += "\n";
|
|
1090
|
+
if (nextSlide2) {
|
|
1091
|
+
content += `{bold}NEXT:{/bold} "${nextSlide2.frontmatter.title}"
|
|
1092
|
+
`;
|
|
1093
|
+
} else {
|
|
1094
|
+
content += "{gray-fg}Last slide{/}\n";
|
|
1095
|
+
}
|
|
1096
|
+
contentBox.setContent(content);
|
|
1097
|
+
screen.render();
|
|
1098
|
+
}
|
|
1099
|
+
function toggleNotesVisibility(notesWindow) {
|
|
1100
|
+
const { contentBox, screen } = notesWindow;
|
|
1101
|
+
contentBox.toggle();
|
|
1102
|
+
screen.render();
|
|
1103
|
+
}
|
|
1104
|
+
function destroyNotesWindow(notesWindow) {
|
|
1105
|
+
notesWindow.screen.destroy();
|
|
1106
|
+
}
|
|
1107
|
+
var init_notes_window = __esm({
|
|
1108
|
+
"src/presenter/notes-window.ts"() {
|
|
1109
|
+
init_esm_shims();
|
|
1110
|
+
}
|
|
1111
|
+
});
|
|
1112
|
+
|
|
1113
|
+
// src/presenter/navigation.ts
|
|
1114
|
+
async function showSlide(presenter, index) {
|
|
1115
|
+
if (presenter.isAnimating) return;
|
|
1116
|
+
if (index < 0 || index >= presenter.deck.slides.length) return;
|
|
1117
|
+
presenter.isAnimating = true;
|
|
1118
|
+
presenter.currentSlide = index;
|
|
1119
|
+
const slide = presenter.deck.slides[index];
|
|
1120
|
+
await renderSlide2(presenter.renderer, slide);
|
|
1121
|
+
presenter.renderer.screen.render();
|
|
1122
|
+
if (presenter.notesWindow) {
|
|
1123
|
+
const nextSlide2 = presenter.deck.slides[index + 1];
|
|
1124
|
+
updateNotesWindow(
|
|
1125
|
+
presenter.notesWindow,
|
|
1126
|
+
slide,
|
|
1127
|
+
nextSlide2,
|
|
1128
|
+
index,
|
|
1129
|
+
presenter.deck.slides.length
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
if (presenter.progressBar) {
|
|
1133
|
+
updateProgress(presenter.progressBar, presenter.currentSlide, presenter.deck.slides.length);
|
|
1134
|
+
}
|
|
1135
|
+
presenter.isAnimating = false;
|
|
1136
|
+
}
|
|
1137
|
+
async function nextSlide(presenter) {
|
|
1138
|
+
const nextIndex = presenter.currentSlide + 1;
|
|
1139
|
+
const { slides } = presenter.deck;
|
|
1140
|
+
const loop = presenter.deck.config.settings?.loop ?? false;
|
|
1141
|
+
if (nextIndex >= slides.length) {
|
|
1142
|
+
if (loop) {
|
|
1143
|
+
await showSlide(presenter, 0);
|
|
1144
|
+
}
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
await showSlide(presenter, nextIndex);
|
|
1148
|
+
}
|
|
1149
|
+
async function prevSlide(presenter) {
|
|
1150
|
+
const prevIndex = presenter.currentSlide - 1;
|
|
1151
|
+
const { slides } = presenter.deck;
|
|
1152
|
+
const loop = presenter.deck.config.settings?.loop ?? false;
|
|
1153
|
+
if (prevIndex < 0) {
|
|
1154
|
+
if (loop) {
|
|
1155
|
+
clearWindows2(presenter.renderer);
|
|
1156
|
+
for (let i = 0; i < slides.length; i++) {
|
|
1157
|
+
await renderSlide2(presenter.renderer, slides[i]);
|
|
1158
|
+
}
|
|
1159
|
+
presenter.currentSlide = slides.length - 1;
|
|
1160
|
+
updateUIComponents(presenter, slides.length - 1);
|
|
1161
|
+
presenter.renderer.screen.render();
|
|
1162
|
+
}
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
clearWindows2(presenter.renderer);
|
|
1166
|
+
for (let i = 0; i <= prevIndex; i++) {
|
|
1167
|
+
await renderSlide2(presenter.renderer, slides[i]);
|
|
1168
|
+
}
|
|
1169
|
+
presenter.currentSlide = prevIndex;
|
|
1170
|
+
updateUIComponents(presenter, prevIndex);
|
|
1171
|
+
presenter.renderer.screen.render();
|
|
1172
|
+
}
|
|
1173
|
+
async function jumpToSlide(presenter, index) {
|
|
1174
|
+
if (index < 0 || index >= presenter.deck.slides.length) return;
|
|
1175
|
+
clearWindows2(presenter.renderer);
|
|
1176
|
+
for (let i = 0; i <= index; i++) {
|
|
1177
|
+
await renderSlide2(presenter.renderer, presenter.deck.slides[i]);
|
|
1178
|
+
}
|
|
1179
|
+
presenter.currentSlide = index;
|
|
1180
|
+
updateUIComponents(presenter, index);
|
|
1181
|
+
presenter.renderer.screen.render();
|
|
1182
|
+
}
|
|
1183
|
+
function updateProgress(progressBar, current, total) {
|
|
1184
|
+
const progress = (current + 1) / total * 100;
|
|
1185
|
+
progressBar.setProgress(progress);
|
|
1186
|
+
}
|
|
1187
|
+
function updateUIComponents(presenter, currentIndex) {
|
|
1188
|
+
const { slides } = presenter.deck;
|
|
1189
|
+
const currentSlide = slides[currentIndex];
|
|
1190
|
+
const nextSlide2 = slides[currentIndex + 1];
|
|
1191
|
+
if (presenter.notesWindow) {
|
|
1192
|
+
updateNotesWindow(
|
|
1193
|
+
presenter.notesWindow,
|
|
1194
|
+
currentSlide,
|
|
1195
|
+
nextSlide2,
|
|
1196
|
+
currentIndex,
|
|
1197
|
+
slides.length
|
|
1198
|
+
);
|
|
1199
|
+
}
|
|
1200
|
+
if (presenter.progressBar) {
|
|
1201
|
+
updateProgress(presenter.progressBar, currentIndex, slides.length);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
var init_navigation = __esm({
|
|
1205
|
+
"src/presenter/navigation.ts"() {
|
|
1206
|
+
init_esm_shims();
|
|
1207
|
+
init_screen();
|
|
1208
|
+
init_notes_window();
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1211
|
+
|
|
1212
|
+
// src/presenter/keyboard-controls.ts
|
|
1213
|
+
function setupControls(presenter) {
|
|
1214
|
+
const { screen } = presenter.renderer;
|
|
1215
|
+
screen.key(["space", "enter", "right", "n"], () => {
|
|
1216
|
+
nextSlide(presenter);
|
|
1217
|
+
});
|
|
1218
|
+
screen.key(["left", "backspace", "p"], () => {
|
|
1219
|
+
prevSlide(presenter);
|
|
1220
|
+
});
|
|
1221
|
+
screen.key(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], (ch) => {
|
|
1222
|
+
const index = parseInt(ch, 10);
|
|
1223
|
+
jumpToSlide(presenter, index);
|
|
1224
|
+
});
|
|
1225
|
+
screen.key(["l"], () => {
|
|
1226
|
+
showSlideList(presenter);
|
|
1227
|
+
});
|
|
1228
|
+
screen.key(["N"], () => {
|
|
1229
|
+
if (presenter.notesWindow) {
|
|
1230
|
+
toggleNotesVisibility(presenter.notesWindow);
|
|
1231
|
+
}
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
function showSlideList(presenter) {
|
|
1235
|
+
const { screen } = presenter.renderer;
|
|
1236
|
+
const { slides } = presenter.deck;
|
|
1237
|
+
const listContent = slides.map((slide, i) => {
|
|
1238
|
+
const marker = i === presenter.currentSlide ? "\u25B6 " : " ";
|
|
1239
|
+
return `${marker}${i}: ${slide.frontmatter.title}`;
|
|
1240
|
+
}).join("\n");
|
|
1241
|
+
const listBox = screen.box({
|
|
1242
|
+
top: "center",
|
|
1243
|
+
left: "center",
|
|
1244
|
+
width: 50,
|
|
1245
|
+
height: Math.min(slides.length + 4, 20),
|
|
1246
|
+
border: { type: "line" },
|
|
1247
|
+
label: " SLIDES (press number or Esc) ",
|
|
1248
|
+
style: {
|
|
1249
|
+
fg: "#ffffff",
|
|
1250
|
+
bg: "#0a0a0a",
|
|
1251
|
+
border: { fg: "#ffcc00" }
|
|
1252
|
+
},
|
|
1253
|
+
padding: 1,
|
|
1254
|
+
tags: true,
|
|
1255
|
+
content: listContent
|
|
1256
|
+
});
|
|
1257
|
+
screen.append(listBox);
|
|
1258
|
+
screen.render();
|
|
1259
|
+
const closeList = () => {
|
|
1260
|
+
listBox.destroy();
|
|
1261
|
+
screen.render();
|
|
1262
|
+
};
|
|
1263
|
+
screen.onceKey(["escape", "l", "q"], closeList);
|
|
1264
|
+
screen.onceKey(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], (ch) => {
|
|
1265
|
+
closeList();
|
|
1266
|
+
jumpToSlide(presenter, parseInt(ch ?? "0", 10));
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
var init_keyboard_controls = __esm({
|
|
1270
|
+
"src/presenter/keyboard-controls.ts"() {
|
|
1271
|
+
init_esm_shims();
|
|
1272
|
+
init_navigation();
|
|
1273
|
+
init_notes_window();
|
|
1274
|
+
}
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
// src/presenter/main.ts
|
|
1278
|
+
var main_exports = {};
|
|
1279
|
+
__export(main_exports, {
|
|
1280
|
+
jumpToSlide: () => jumpToSlide,
|
|
1281
|
+
present: () => present,
|
|
1282
|
+
prevSlide: () => prevSlide
|
|
1283
|
+
});
|
|
1284
|
+
async function present(slidesDir, options = {}) {
|
|
1285
|
+
const deck = await loadDeck(slidesDir);
|
|
1286
|
+
if (deck.slides.length === 0) {
|
|
1287
|
+
throw new Error(`No slides found in ${slidesDir}`);
|
|
1288
|
+
}
|
|
1289
|
+
const renderer = createRenderer(deck.config.theme);
|
|
1290
|
+
const presenter = {
|
|
1291
|
+
deck,
|
|
1292
|
+
renderer,
|
|
1293
|
+
currentSlide: options.startSlide ?? deck.config.settings?.startSlide ?? 0,
|
|
1294
|
+
isAnimating: false,
|
|
1295
|
+
notesWindow: null,
|
|
1296
|
+
autoAdvanceTimer: null,
|
|
1297
|
+
progressBar: null
|
|
1298
|
+
};
|
|
1299
|
+
if (options.showNotes) {
|
|
1300
|
+
presenter.notesWindow = await createNotesWindow(options.notesTty);
|
|
1301
|
+
}
|
|
1302
|
+
if (deck.config.settings?.showProgress) {
|
|
1303
|
+
presenter.progressBar = createProgressBar(presenter);
|
|
1304
|
+
}
|
|
1305
|
+
setupControls(presenter);
|
|
1306
|
+
await showSlide(presenter, presenter.currentSlide);
|
|
1307
|
+
if (presenter.progressBar) {
|
|
1308
|
+
updateProgress(presenter.progressBar, presenter.currentSlide, deck.slides.length);
|
|
1309
|
+
}
|
|
1310
|
+
presenter.autoAdvanceTimer = startAutoAdvance(presenter);
|
|
1311
|
+
await new Promise((resolve) => {
|
|
1312
|
+
renderer.screen.key(["q", "C-c", "escape"], () => {
|
|
1313
|
+
cleanup(presenter);
|
|
1314
|
+
resolve();
|
|
1315
|
+
});
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
function cleanup(presenter) {
|
|
1319
|
+
stopAutoAdvance(presenter.autoAdvanceTimer);
|
|
1320
|
+
if (presenter.notesWindow) {
|
|
1321
|
+
destroyNotesWindow(presenter.notesWindow);
|
|
1322
|
+
}
|
|
1323
|
+
destroyRenderer(presenter.renderer);
|
|
1324
|
+
}
|
|
1325
|
+
function startAutoAdvance(presenter) {
|
|
1326
|
+
const interval = presenter.deck.config.settings?.autoAdvance;
|
|
1327
|
+
if (!interval || interval <= 0) {
|
|
1328
|
+
return null;
|
|
1329
|
+
}
|
|
1330
|
+
return setInterval(() => {
|
|
1331
|
+
if (!presenter.isAnimating) {
|
|
1332
|
+
nextSlide(presenter);
|
|
1333
|
+
}
|
|
1334
|
+
}, interval);
|
|
1335
|
+
}
|
|
1336
|
+
function stopAutoAdvance(timer) {
|
|
1337
|
+
if (timer) {
|
|
1338
|
+
clearInterval(timer);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
function createProgressBar(presenter) {
|
|
1342
|
+
const { screen } = presenter.renderer;
|
|
1343
|
+
const progressBar = blessed.progressbar({
|
|
1344
|
+
bottom: 0,
|
|
1345
|
+
left: 0,
|
|
1346
|
+
width: "100%",
|
|
1347
|
+
height: 1,
|
|
1348
|
+
style: {
|
|
1349
|
+
bg: "#333333",
|
|
1350
|
+
bar: { bg: "#00cc66" }
|
|
1351
|
+
},
|
|
1352
|
+
filled: 0
|
|
1353
|
+
});
|
|
1354
|
+
screen.append(progressBar);
|
|
1355
|
+
return progressBar;
|
|
1356
|
+
}
|
|
1357
|
+
var init_main = __esm({
|
|
1358
|
+
"src/presenter/main.ts"() {
|
|
1359
|
+
init_esm_shims();
|
|
1360
|
+
init_deck_loader();
|
|
1361
|
+
init_screen();
|
|
1362
|
+
init_notes_window();
|
|
1363
|
+
init_keyboard_controls();
|
|
1364
|
+
init_navigation();
|
|
1365
|
+
init_navigation();
|
|
1366
|
+
}
|
|
1367
|
+
});
|
|
1368
|
+
|
|
1369
|
+
// bin/term-deck.ts
|
|
1370
|
+
init_esm_shims();
|
|
1371
|
+
|
|
1372
|
+
// package.json
|
|
1373
|
+
var version = "1.0.16";
|
|
1374
|
+
|
|
1375
|
+
// src/cli/commands/present.ts
|
|
1376
|
+
init_esm_shims();
|
|
1377
|
+
init_main();
|
|
1378
|
+
|
|
1379
|
+
// src/cli/errors.ts
|
|
1380
|
+
init_esm_shims();
|
|
1381
|
+
init_validation();
|
|
1382
|
+
init_slide2();
|
|
1383
|
+
init_deck_loader();
|
|
1384
|
+
init_theme2();
|
|
1385
|
+
function handleError(error) {
|
|
1386
|
+
if (error instanceof ValidationError) {
|
|
1387
|
+
console.error(`
|
|
1388
|
+
${error.message}`);
|
|
1389
|
+
process.exit(1);
|
|
1390
|
+
}
|
|
1391
|
+
if (error instanceof SlideParseError) {
|
|
1392
|
+
console.error(`
|
|
1393
|
+
Slide error in ${error.filePath}:`);
|
|
1394
|
+
console.error(` ${error.message}`);
|
|
1395
|
+
if (error.cause) {
|
|
1396
|
+
const causeMessage = error.cause instanceof Error ? error.cause.message : String(error.cause);
|
|
1397
|
+
console.error(` Caused by: ${causeMessage}`);
|
|
1398
|
+
}
|
|
1399
|
+
process.exit(1);
|
|
1400
|
+
}
|
|
1401
|
+
if (error instanceof DeckLoadError) {
|
|
1402
|
+
console.error(`
|
|
1403
|
+
Failed to load deck from ${error.slidesDir}:`);
|
|
1404
|
+
console.error(` ${error.message}`);
|
|
1405
|
+
process.exit(1);
|
|
1406
|
+
}
|
|
1407
|
+
if (error instanceof ThemeError) {
|
|
1408
|
+
console.error("\nTheme error:");
|
|
1409
|
+
console.error(` ${error.message}`);
|
|
1410
|
+
process.exit(1);
|
|
1411
|
+
}
|
|
1412
|
+
if (error instanceof Error) {
|
|
1413
|
+
if (error.message.includes("ENOENT")) {
|
|
1414
|
+
console.error("\nFile or directory not found.");
|
|
1415
|
+
console.error(` ${error.message}`);
|
|
1416
|
+
process.exit(1);
|
|
1417
|
+
}
|
|
1418
|
+
if (error.message.includes("ffmpeg")) {
|
|
1419
|
+
console.error("\nffmpeg error:");
|
|
1420
|
+
console.error(` ${error.message}`);
|
|
1421
|
+
console.error("\nMake sure ffmpeg is installed:");
|
|
1422
|
+
console.error(" macOS: brew install ffmpeg");
|
|
1423
|
+
console.error(" Ubuntu: sudo apt install ffmpeg");
|
|
1424
|
+
process.exit(1);
|
|
1425
|
+
}
|
|
1426
|
+
console.error(`
|
|
1427
|
+
Error: ${error.message}`);
|
|
1428
|
+
if (process.env.DEBUG) {
|
|
1429
|
+
console.error(error.stack);
|
|
1430
|
+
}
|
|
1431
|
+
process.exit(1);
|
|
1432
|
+
}
|
|
1433
|
+
console.error("\nUnknown error occurred");
|
|
1434
|
+
console.error(error);
|
|
1435
|
+
process.exit(1);
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
// src/cli/commands/present.ts
|
|
1439
|
+
var presentCommand = new Command("present").description("Start a presentation").argument("<dir>", "Slides directory").option("-s, --start <n>", "Start at slide number", "0").option("-n, --notes", "Show presenter notes in separate terminal").option("--notes-tty <path>", "TTY device for notes window (e.g., /dev/ttys001)").option("-l, --loop", "Loop back to first slide after last").action(async (dir, options) => {
|
|
1440
|
+
try {
|
|
1441
|
+
await present(dir, {
|
|
1442
|
+
startSlide: Number.parseInt(options.start, 10),
|
|
1443
|
+
showNotes: options.notes,
|
|
1444
|
+
notesTty: options.notesTty,
|
|
1445
|
+
loop: options.loop
|
|
1446
|
+
});
|
|
1447
|
+
} catch (error) {
|
|
1448
|
+
handleError(error);
|
|
1449
|
+
}
|
|
1450
|
+
});
|
|
1451
|
+
|
|
1452
|
+
// src/cli/commands/export.ts
|
|
1453
|
+
init_esm_shims();
|
|
1454
|
+
|
|
1455
|
+
// src/export/recorder.ts
|
|
1456
|
+
init_esm_shims();
|
|
1457
|
+
|
|
1458
|
+
// src/export/recording-session.ts
|
|
1459
|
+
init_esm_shims();
|
|
1460
|
+
async function createRecordingSession(options) {
|
|
1461
|
+
const tempDir = join(tmpdir(), `term-deck-export-${Date.now()}`);
|
|
1462
|
+
await mkdir(tempDir, { recursive: true });
|
|
1463
|
+
return {
|
|
1464
|
+
tempDir,
|
|
1465
|
+
frameCount: 0,
|
|
1466
|
+
width: options.width ?? 120,
|
|
1467
|
+
height: options.height ?? 40,
|
|
1468
|
+
fps: options.fps ?? 30
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
async function saveFrame(session, png) {
|
|
1472
|
+
const frameNum = session.frameCount.toString().padStart(6, "0");
|
|
1473
|
+
const framePath = join(session.tempDir, `frame_${frameNum}.png`);
|
|
1474
|
+
await writeFile(framePath, png);
|
|
1475
|
+
session.frameCount++;
|
|
1476
|
+
}
|
|
1477
|
+
async function cleanupSession(session) {
|
|
1478
|
+
await rm(session.tempDir, { recursive: true, force: true });
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
// src/export/presentation-exporter.ts
|
|
1482
|
+
init_esm_shims();
|
|
1483
|
+
init_deck_loader();
|
|
1484
|
+
init_screen();
|
|
1485
|
+
|
|
1486
|
+
// src/renderer/types/screen.ts
|
|
1487
|
+
init_esm_shims();
|
|
1488
|
+
function setScreenDimensions(screen, width, height) {
|
|
1489
|
+
screen.width = width;
|
|
1490
|
+
screen.height = height;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
// src/export/utils/virtual-terminal.ts
|
|
1494
|
+
init_esm_shims();
|
|
1495
|
+
var VirtualTerminal = class {
|
|
1496
|
+
constructor(width, height) {
|
|
1497
|
+
this.width = width;
|
|
1498
|
+
this.height = height;
|
|
1499
|
+
this.buffer = Array.from(
|
|
1500
|
+
{ length: height },
|
|
1501
|
+
() => Array(width).fill(" ")
|
|
1502
|
+
);
|
|
1503
|
+
this.colors = Array.from(
|
|
1504
|
+
{ length: height },
|
|
1505
|
+
() => Array(width).fill("#ffffff")
|
|
1506
|
+
);
|
|
1507
|
+
}
|
|
1508
|
+
buffer;
|
|
1509
|
+
colors;
|
|
1510
|
+
/**
|
|
1511
|
+
* Set character at position with optional color
|
|
1512
|
+
*/
|
|
1513
|
+
setChar(x, y, char, color = "#ffffff") {
|
|
1514
|
+
if (x >= 0 && x < this.width && y >= 0 && y < this.height) {
|
|
1515
|
+
this.buffer[y][x] = char;
|
|
1516
|
+
this.colors[y][x] = color;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
/**
|
|
1520
|
+
* Clear the buffer to blank spaces
|
|
1521
|
+
*/
|
|
1522
|
+
clear() {
|
|
1523
|
+
for (let y = 0; y < this.height; y++) {
|
|
1524
|
+
for (let x = 0; x < this.width; x++) {
|
|
1525
|
+
this.buffer[y][x] = " ";
|
|
1526
|
+
this.colors[y][x] = "#ffffff";
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
/**
|
|
1531
|
+
* Get buffer contents as plain text string
|
|
1532
|
+
*/
|
|
1533
|
+
toString() {
|
|
1534
|
+
return this.buffer.map((row) => row.join("")).join("\n");
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Get the raw character buffer
|
|
1538
|
+
*/
|
|
1539
|
+
getBuffer() {
|
|
1540
|
+
return this.buffer;
|
|
1541
|
+
}
|
|
1542
|
+
/**
|
|
1543
|
+
* Get the color buffer
|
|
1544
|
+
*/
|
|
1545
|
+
getColors() {
|
|
1546
|
+
return this.colors;
|
|
1547
|
+
}
|
|
1548
|
+
/**
|
|
1549
|
+
* Convert buffer to PNG image data
|
|
1550
|
+
*/
|
|
1551
|
+
async toPng() {
|
|
1552
|
+
return renderTerminalToPng(this.buffer, this.colors, this.width, this.height);
|
|
1553
|
+
}
|
|
1554
|
+
};
|
|
1555
|
+
var CHAR_WIDTH = 10;
|
|
1556
|
+
var CHAR_HEIGHT = 20;
|
|
1557
|
+
async function renderTerminalToPng(buffer, colors, width, height) {
|
|
1558
|
+
const { createCanvas } = await import('canvas');
|
|
1559
|
+
const canvas = createCanvas(width * CHAR_WIDTH, height * CHAR_HEIGHT);
|
|
1560
|
+
const ctx = canvas.getContext("2d");
|
|
1561
|
+
ctx.fillStyle = "#0a0a0a";
|
|
1562
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
1563
|
+
ctx.font = `${CHAR_HEIGHT - 4}px monospace`;
|
|
1564
|
+
ctx.textBaseline = "top";
|
|
1565
|
+
for (let y = 0; y < height; y++) {
|
|
1566
|
+
for (let x = 0; x < width; x++) {
|
|
1567
|
+
const char = buffer[y][x];
|
|
1568
|
+
const color = colors[y][x];
|
|
1569
|
+
if (char !== " ") {
|
|
1570
|
+
ctx.fillStyle = color;
|
|
1571
|
+
ctx.fillText(char, x * CHAR_WIDTH, y * CHAR_HEIGHT + 2);
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
return canvas.toBuffer("image/png");
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
// src/export/capture/screen-capture.ts
|
|
1579
|
+
init_esm_shims();
|
|
1580
|
+
|
|
1581
|
+
// src/export/utils/color-conversion.ts
|
|
1582
|
+
init_esm_shims();
|
|
1583
|
+
function ansi256ToHex(code) {
|
|
1584
|
+
const standard16 = [
|
|
1585
|
+
"#000000",
|
|
1586
|
+
"#800000",
|
|
1587
|
+
"#008000",
|
|
1588
|
+
"#808000",
|
|
1589
|
+
"#000080",
|
|
1590
|
+
"#800080",
|
|
1591
|
+
"#008080",
|
|
1592
|
+
"#c0c0c0",
|
|
1593
|
+
"#808080",
|
|
1594
|
+
"#ff0000",
|
|
1595
|
+
"#00ff00",
|
|
1596
|
+
"#ffff00",
|
|
1597
|
+
"#0000ff",
|
|
1598
|
+
"#ff00ff",
|
|
1599
|
+
"#00ffff",
|
|
1600
|
+
"#ffffff"
|
|
1601
|
+
];
|
|
1602
|
+
if (code < 16) {
|
|
1603
|
+
return standard16[code];
|
|
1604
|
+
}
|
|
1605
|
+
if (code < 232) {
|
|
1606
|
+
const n = code - 16;
|
|
1607
|
+
const r = Math.floor(n / 36) * 51;
|
|
1608
|
+
const g = Math.floor(n % 36 / 6) * 51;
|
|
1609
|
+
const b = n % 6 * 51;
|
|
1610
|
+
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
|
1611
|
+
}
|
|
1612
|
+
const gray = (code - 232) * 10 + 8;
|
|
1613
|
+
const hex = gray.toString(16).padStart(2, "0");
|
|
1614
|
+
return `#${hex}${hex}${hex}`;
|
|
1615
|
+
}
|
|
1616
|
+
function extractColor(attr) {
|
|
1617
|
+
if (!attr) return null;
|
|
1618
|
+
if (typeof attr === "object" && attr.fg !== void 0) {
|
|
1619
|
+
if (typeof attr.fg === "string" && attr.fg.startsWith("#")) {
|
|
1620
|
+
return attr.fg;
|
|
1621
|
+
}
|
|
1622
|
+
if (typeof attr.fg === "number") {
|
|
1623
|
+
return ansi256ToHex(attr.fg);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
return null;
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
// src/export/capture/screen-capture.ts
|
|
1630
|
+
function captureScreen(screen, vt) {
|
|
1631
|
+
const lines = screen.lines || [];
|
|
1632
|
+
for (let y = 0; y < Math.min(lines.length, vt.height); y++) {
|
|
1633
|
+
const line = lines[y];
|
|
1634
|
+
if (!line) continue;
|
|
1635
|
+
for (let x = 0; x < Math.min(line.length, vt.width); x++) {
|
|
1636
|
+
const cell = line[x];
|
|
1637
|
+
if (!cell) continue;
|
|
1638
|
+
const char = Array.isArray(cell) ? cell[0] : cell;
|
|
1639
|
+
const attr = Array.isArray(cell) ? cell[1] : null;
|
|
1640
|
+
const color = extractColor(attr) || "#ffffff";
|
|
1641
|
+
vt.setChar(x, y, char || " ", color);
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
// src/export/encoding/ffmpeg-encoder.ts
|
|
1647
|
+
init_esm_shims();
|
|
1648
|
+
async function checkFfmpeg() {
|
|
1649
|
+
try {
|
|
1650
|
+
await execa("which", ["ffmpeg"]);
|
|
1651
|
+
} catch {
|
|
1652
|
+
throw new Error(
|
|
1653
|
+
"ffmpeg not found. Install it with:\n macOS: brew install ffmpeg\n Ubuntu: sudo apt install ffmpeg"
|
|
1654
|
+
);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
function detectFormat(output) {
|
|
1658
|
+
if (output.endsWith(".gif")) return "gif";
|
|
1659
|
+
if (output.endsWith(".mp4")) return "mp4";
|
|
1660
|
+
throw new Error(
|
|
1661
|
+
`Unknown output format for ${output}. Use .mp4 or .gif extension.`
|
|
1662
|
+
);
|
|
1663
|
+
}
|
|
1664
|
+
async function encodeVideo(options) {
|
|
1665
|
+
const { inputPattern, output, format, fps, quality } = options;
|
|
1666
|
+
if (format === "mp4") {
|
|
1667
|
+
await encodeMp4(inputPattern, output, fps, quality ?? 80);
|
|
1668
|
+
} else {
|
|
1669
|
+
await encodeGif(inputPattern, output, fps);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
async function encodeMp4(input, output, fps, quality) {
|
|
1673
|
+
const crf = Math.round(51 - quality / 100 * 33);
|
|
1674
|
+
await execa("ffmpeg", [
|
|
1675
|
+
"-y",
|
|
1676
|
+
// Overwrite output file
|
|
1677
|
+
"-framerate",
|
|
1678
|
+
fps.toString(),
|
|
1679
|
+
"-i",
|
|
1680
|
+
input,
|
|
1681
|
+
"-c:v",
|
|
1682
|
+
"libx264",
|
|
1683
|
+
// H.264 codec
|
|
1684
|
+
"-crf",
|
|
1685
|
+
crf.toString(),
|
|
1686
|
+
"-pix_fmt",
|
|
1687
|
+
"yuv420p",
|
|
1688
|
+
// Pixel format for compatibility
|
|
1689
|
+
output
|
|
1690
|
+
]);
|
|
1691
|
+
}
|
|
1692
|
+
async function encodeGif(input, output, fps) {
|
|
1693
|
+
const { tmpdir: tmpdir2 } = await import('os');
|
|
1694
|
+
const { join: join5 } = await import('path');
|
|
1695
|
+
const paletteFile = join5(tmpdir2(), `palette-${Date.now()}.png`);
|
|
1696
|
+
try {
|
|
1697
|
+
await execa("ffmpeg", [
|
|
1698
|
+
"-y",
|
|
1699
|
+
"-framerate",
|
|
1700
|
+
fps.toString(),
|
|
1701
|
+
"-i",
|
|
1702
|
+
input,
|
|
1703
|
+
"-vf",
|
|
1704
|
+
`fps=${fps},scale=-1:-1:flags=lanczos,palettegen=stats_mode=diff`,
|
|
1705
|
+
paletteFile
|
|
1706
|
+
]);
|
|
1707
|
+
await execa("ffmpeg", [
|
|
1708
|
+
"-y",
|
|
1709
|
+
"-framerate",
|
|
1710
|
+
fps.toString(),
|
|
1711
|
+
"-i",
|
|
1712
|
+
input,
|
|
1713
|
+
"-i",
|
|
1714
|
+
paletteFile,
|
|
1715
|
+
"-lavfi",
|
|
1716
|
+
`fps=${fps},scale=-1:-1:flags=lanczos[x];[x][1:v]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle`,
|
|
1717
|
+
output
|
|
1718
|
+
]);
|
|
1719
|
+
} finally {
|
|
1720
|
+
try {
|
|
1721
|
+
await unlink(paletteFile);
|
|
1722
|
+
} catch {
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
// src/export/presentation-exporter.ts
|
|
1728
|
+
async function encodeFramesToVideo(tempDir, output, format, fps, quality) {
|
|
1729
|
+
const inputPattern = join(tempDir, "frame_%06d.png");
|
|
1730
|
+
await encodeVideo({
|
|
1731
|
+
inputPattern,
|
|
1732
|
+
output,
|
|
1733
|
+
format,
|
|
1734
|
+
fps,
|
|
1735
|
+
quality
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1738
|
+
async function exportPresentation(slidesDir, options) {
|
|
1739
|
+
await checkFfmpeg();
|
|
1740
|
+
const format = detectFormat(options.output);
|
|
1741
|
+
const deck = await loadDeck(slidesDir);
|
|
1742
|
+
if (deck.slides.length === 0) {
|
|
1743
|
+
throw new Error(`No slides found in ${slidesDir}`);
|
|
1744
|
+
}
|
|
1745
|
+
const session = await createRecordingSession(options);
|
|
1746
|
+
const vt = new VirtualTerminal(session.width, session.height);
|
|
1747
|
+
const renderer = createRenderer(deck.config.theme);
|
|
1748
|
+
setScreenDimensions(renderer.screen, session.width, session.height);
|
|
1749
|
+
const slideTime = options.slideTime ?? 3;
|
|
1750
|
+
const framesPerSlide = session.fps * slideTime;
|
|
1751
|
+
console.log(`Exporting ${deck.slides.length} slides...`);
|
|
1752
|
+
try {
|
|
1753
|
+
for (let i = 0; i < deck.slides.length; i++) {
|
|
1754
|
+
const slide = deck.slides[i];
|
|
1755
|
+
console.log(` Slide ${i + 1}/${deck.slides.length}: ${slide.frontmatter.title}`);
|
|
1756
|
+
await renderSlide2(renderer, slide);
|
|
1757
|
+
for (let f = 0; f < framesPerSlide; f++) {
|
|
1758
|
+
renderer.screen.render();
|
|
1759
|
+
captureScreen(renderer.screen, vt);
|
|
1760
|
+
const png = await vt.toPng();
|
|
1761
|
+
await saveFrame(session, png);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
console.log("Encoding video...");
|
|
1765
|
+
await encodeFramesToVideo(
|
|
1766
|
+
session.tempDir,
|
|
1767
|
+
options.output,
|
|
1768
|
+
format,
|
|
1769
|
+
session.fps,
|
|
1770
|
+
options.quality
|
|
1771
|
+
);
|
|
1772
|
+
console.log(`Exported to ${options.output}`);
|
|
1773
|
+
} finally {
|
|
1774
|
+
destroyRenderer(renderer);
|
|
1775
|
+
await cleanupSession(session);
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
// src/export/ansi-recorder.ts
|
|
1780
|
+
init_esm_shims();
|
|
1781
|
+
init_deck_loader();
|
|
1782
|
+
init_screen();
|
|
1783
|
+
|
|
1784
|
+
// src/cli/commands/export.ts
|
|
1785
|
+
var exportCommand = new Command("export").description("Export presentation to GIF or MP4").argument("<dir>", "Slides directory").requiredOption("-o, --output <file>", "Output file (.mp4 or .gif)").option("-w, --width <n>", "Terminal width in characters", "120").option("-h, --height <n>", "Terminal height in characters", "40").option("--fps <n>", "Frames per second", "30").option("-t, --slide-time <n>", "Seconds per slide", "3").option("-q, --quality <n>", "Quality 1-100 (video only)", "80").action(async (dir, options) => {
|
|
1786
|
+
try {
|
|
1787
|
+
await exportPresentation(dir, {
|
|
1788
|
+
output: options.output,
|
|
1789
|
+
width: Number.parseInt(options.width, 10),
|
|
1790
|
+
height: Number.parseInt(options.height, 10),
|
|
1791
|
+
fps: Number.parseInt(options.fps, 10),
|
|
1792
|
+
slideTime: Number.parseFloat(options.slideTime),
|
|
1793
|
+
quality: Number.parseInt(options.quality, 10)
|
|
1794
|
+
});
|
|
1795
|
+
} catch (error) {
|
|
1796
|
+
handleError(error);
|
|
1797
|
+
}
|
|
1798
|
+
});
|
|
1799
|
+
|
|
1800
|
+
// src/cli/commands/init.ts
|
|
1801
|
+
init_esm_shims();
|
|
1802
|
+
var initCommand = new Command("init").description("Create a new presentation deck").argument("<name>", "Deck name (will create directory)").option("-t, --theme <name>", "Theme to use", "matrix").action(async (name, options) => {
|
|
1803
|
+
try {
|
|
1804
|
+
await initDeck(name, options.theme);
|
|
1805
|
+
console.log(`Created deck: ${name}/`);
|
|
1806
|
+
console.log("\nNext steps:");
|
|
1807
|
+
console.log(` cd ${name}/slides`);
|
|
1808
|
+
console.log(" term-deck present .");
|
|
1809
|
+
} catch (error) {
|
|
1810
|
+
handleError(error);
|
|
1811
|
+
}
|
|
1812
|
+
});
|
|
1813
|
+
async function initDeck(name, theme) {
|
|
1814
|
+
const deckDir = join(process.cwd(), name);
|
|
1815
|
+
const slidesDir = join(deckDir, "slides");
|
|
1816
|
+
await mkdir(slidesDir, { recursive: true });
|
|
1817
|
+
await writeFile(join(slidesDir, ".gitkeep"), "");
|
|
1818
|
+
const configContent = `import { defineConfig } from 'term-deck'
|
|
1819
|
+
import matrix from '@term-deck/theme-matrix'
|
|
1820
|
+
|
|
1821
|
+
export default defineConfig({
|
|
1822
|
+
title: '${name}',
|
|
1823
|
+
theme: matrix,
|
|
1824
|
+
})
|
|
1825
|
+
`;
|
|
1826
|
+
await writeFile(join(slidesDir, "deck.config.ts"), configContent);
|
|
1827
|
+
const slide1 = `---
|
|
1828
|
+
title: ${name.toUpperCase()}
|
|
1829
|
+
bigText: ${name.toUpperCase()}
|
|
1830
|
+
gradient: fire
|
|
1831
|
+
---
|
|
1832
|
+
|
|
1833
|
+
{GREEN}Welcome to your presentation{/}
|
|
1834
|
+
|
|
1835
|
+
Press {CYAN}Space{/} or {CYAN}\u2192{/} to advance
|
|
1836
|
+
`;
|
|
1837
|
+
const slide2 = `---
|
|
1838
|
+
title: SLIDE TWO
|
|
1839
|
+
bigText: HELLO
|
|
1840
|
+
gradient: cool
|
|
1841
|
+
---
|
|
1842
|
+
|
|
1843
|
+
{WHITE}This is the second slide{/}
|
|
1844
|
+
|
|
1845
|
+
- Point one
|
|
1846
|
+
- Point two
|
|
1847
|
+
- Point three
|
|
1848
|
+
|
|
1849
|
+
<!-- notes -->
|
|
1850
|
+
Remember to explain each point clearly.
|
|
1851
|
+
`;
|
|
1852
|
+
const slide3 = `---
|
|
1853
|
+
title: THE END
|
|
1854
|
+
bigText: FIN
|
|
1855
|
+
gradient: pink
|
|
1856
|
+
---
|
|
1857
|
+
|
|
1858
|
+
{ORANGE}Thank you!{/}
|
|
1859
|
+
|
|
1860
|
+
Press {CYAN}q{/} to exit
|
|
1861
|
+
`;
|
|
1862
|
+
await writeFile(join(slidesDir, "01-intro.md"), slide1);
|
|
1863
|
+
await writeFile(join(slidesDir, "02-content.md"), slide2);
|
|
1864
|
+
await writeFile(join(slidesDir, "03-end.md"), slide3);
|
|
1865
|
+
const readme = `# ${name}
|
|
1866
|
+
|
|
1867
|
+
A term-deck presentation.
|
|
1868
|
+
|
|
1869
|
+
## Usage
|
|
1870
|
+
|
|
1871
|
+
\`\`\`bash
|
|
1872
|
+
cd slides
|
|
1873
|
+
term-deck present .
|
|
1874
|
+
\`\`\`
|
|
1875
|
+
|
|
1876
|
+
## Export
|
|
1877
|
+
|
|
1878
|
+
\`\`\`bash
|
|
1879
|
+
term-deck export slides/ -o ${name}.mp4
|
|
1880
|
+
term-deck export slides/ -o ${name}.gif
|
|
1881
|
+
\`\`\`
|
|
1882
|
+
|
|
1883
|
+
## Hotkeys
|
|
1884
|
+
|
|
1885
|
+
| Key | Action |
|
|
1886
|
+
|-----|--------|
|
|
1887
|
+
| Space / \u2192 | Next slide |
|
|
1888
|
+
| \u2190 | Previous slide |
|
|
1889
|
+
| 0-9 | Jump to slide |
|
|
1890
|
+
| l | Show slide list |
|
|
1891
|
+
| q | Quit |
|
|
1892
|
+
`;
|
|
1893
|
+
await writeFile(join(deckDir, "README.md"), readme);
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
// bin/term-deck.ts
|
|
1897
|
+
var program = new Command();
|
|
1898
|
+
program.name("term-deck").description("Terminal presentation tool with a cyberpunk aesthetic").version(version);
|
|
1899
|
+
program.addCommand(presentCommand);
|
|
1900
|
+
program.addCommand(exportCommand);
|
|
1901
|
+
program.addCommand(initCommand);
|
|
1902
|
+
program.argument("[dir]", "Slides directory to present").action(async (dir) => {
|
|
1903
|
+
if (dir) {
|
|
1904
|
+
try {
|
|
1905
|
+
const { present: present2 } = await Promise.resolve().then(() => (init_main(), main_exports));
|
|
1906
|
+
await present2(dir, {});
|
|
1907
|
+
} catch (error) {
|
|
1908
|
+
handleError(error);
|
|
1909
|
+
}
|
|
1910
|
+
} else {
|
|
1911
|
+
program.help();
|
|
1912
|
+
}
|
|
1913
|
+
});
|
|
1914
|
+
program.parse();
|
|
1915
|
+
//# sourceMappingURL=term-deck.js.map
|
|
1916
|
+
//# sourceMappingURL=term-deck.js.map
|