+ );
+};
+
+export default ThemeBuilderView;
diff --git a/apps/webviews/src/main.tsx b/apps/webviews/src/main.tsx
index 59584083..f6c79227 100644
--- a/apps/webviews/src/main.tsx
+++ b/apps/webviews/src/main.tsx
@@ -12,6 +12,7 @@ const WEBVIEW_MAP: Record>> =
'preview': lazy(() => import('./components/webviews/PreviewView')),
'presenter': lazy(() => import('./components/webviews/PresenterView')),
'overview': lazy(() => import('./components/webviews/DemoScriptView')),
+ 'themeBuilder': lazy(() => import('./components/webviews/ThemeBuilderView')),
};
const root = document.getElementById('root');
diff --git a/packages/common/src/constants/Command.ts b/packages/common/src/constants/Command.ts
index f70c454a..5353a80b 100644
--- a/packages/common/src/constants/Command.ts
+++ b/packages/common/src/constants/Command.ts
@@ -3,6 +3,7 @@ export const EXTENSION_NAME = 'demo-time';
export const COMMAND = {
// Pro features
authenticate: `${EXTENSION_NAME}.authenticate`,
+ openThemeBuilder: `${EXTENSION_NAME}.openThemeBuilder`,
// Documentation
documentation: `${EXTENSION_NAME}.docs`,
// Demo file actions
diff --git a/packages/common/src/constants/WebViewMessages.ts b/packages/common/src/constants/WebViewMessages.ts
index a1080515..c5d22078 100644
--- a/packages/common/src/constants/WebViewMessages.ts
+++ b/packages/common/src/constants/WebViewMessages.ts
@@ -200,6 +200,15 @@ export const WebViewMessages = {
presenter: {
checkNextDemo: 'checkPresenterNextDemo',
},
+ themeBuilder: {
+ getExistingThemes: 'getThemeBuilderExistingThemes',
+ getExistingLayouts: 'getThemeBuilderExistingLayouts',
+ loadTheme: 'loadThemeBuilderTheme',
+ loadLayout: 'loadThemeBuilderLayout',
+ saveTheme: 'saveThemeBuilderTheme',
+ saveLayout: 'saveThemeBuilderLayout',
+ getPreviewHtml: 'getThemeBuilderPreviewHtml',
+ },
},
toWebview: {
/**
From 6e956c123532701acc517f0f54c1d378516f023b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 23 Dec 2025 18:16:40 +0000
Subject: [PATCH 03/12] Add theme builder menu item and documentation
Co-authored-by: estruyf <2900833+estruyf@users.noreply.github.com>
---
apps/vscode-extension/package.json | 4 +
.../content/docs/features/theme-builder.mdx | 140 ++++++++++++++++++
2 files changed, 144 insertions(+)
create mode 100644 docs/src/content/docs/features/theme-builder.mdx
diff --git a/apps/vscode-extension/package.json b/apps/vscode-extension/package.json
index ec820a88..f788d969 100644
--- a/apps/vscode-extension/package.json
+++ b/apps/vscode-extension/package.json
@@ -690,6 +690,10 @@
"command": "demo-time.showSettings",
"when": "view == demo-time-scenes"
},
+ {
+ "command": "demo-time.openThemeBuilder",
+ "when": "view == demo-time-scenes"
+ },
{
"command": "demo-time.openSupportTheProject",
"when": "view == demo-time-scenes"
diff --git a/docs/src/content/docs/features/theme-builder.mdx b/docs/src/content/docs/features/theme-builder.mdx
new file mode 100644
index 00000000..3b901e9f
--- /dev/null
+++ b/docs/src/content/docs/features/theme-builder.mdx
@@ -0,0 +1,140 @@
+---
+title: Theme Builder (Pro)
+description: Create and customize themes and layouts for your presentation slides
+---
+
+The **Theme Builder** is a Pro feature that allows you to create and customize themes and slide layouts visually, making it easier to style your presentation slides without manually editing CSS and Handlebars files.
+
+## Accessing the Theme Builder
+
+The Theme Builder is available to GitHub Sponsors. To unlock this feature:
+
+1. Run the command **"Demo Time: Authenticate with GitHub (Unlock Pro Features)"** from the command palette
+2. Sign in with your GitHub account
+3. If you're a sponsor, Pro features will be unlocked automatically
+
+Once authenticated, you can access the Theme Builder:
+
+- From the command palette: **"Demo Time: Open Theme Builder (Pro)"**
+- From the Demo Time Scenes view menu
+
+## Theme Editor
+
+The Theme Editor allows you to create custom CSS themes for your slides.
+
+### Creating a New Theme
+
+1. Open the Theme Builder
+2. Click on the **"Theme Editor"** tab
+3. Click the **"New"** button
+4. Enter your theme name
+5. Edit the CSS in the editor
+6. Preview your changes in real-time
+7. Click **"Save Theme"** to save to `.demo/slides/`
+
+### Editing an Existing Theme
+
+1. Select a theme from the dropdown
+2. The theme CSS will load in the editor
+3. Make your changes
+4. Save the updated theme
+
+### Theme Structure
+
+Themes are CSS files saved in the `.demo/slides/` folder. You can use VS Code CSS variables for consistent theming:
+
+```css
+/* Example theme */
+body {
+ background: var(--vscode-editor-background);
+ color: var(--vscode-editor-foreground);
+}
+
+.slide {
+ background: var(--vscode-editor-background) !important;
+ color: var(--vscode-editor-foreground) !important;
+}
+
+.intro h1 {
+ font-size: 3em;
+ font-weight: bold;
+ background: #ffd43b !important;
+ color: #000 !important;
+}
+```
+
+## Layout Editor
+
+The Layout Editor allows you to create custom Handlebars templates for slide layouts.
+
+### Creating a New Layout
+
+1. Open the Theme Builder
+2. Click on the **"Layout Editor"** tab
+3. Click the **"New"** button
+4. Enter your layout name
+5. Edit the Handlebars template
+6. Preview your changes
+7. Click **"Save Layout"** to save to `.demo/layouts/`
+
+### Editing an Existing Layout
+
+1. Select a layout from the dropdown
+2. The layout template will load in the editor
+3. Make your changes
+4. Save the updated layout
+
+### Layout Structure
+
+Layouts are Handlebars (`.hbs`) files saved in the `.demo/layouts/` folder. Use Handlebars syntax to create dynamic layouts:
+
+```handlebars
+
+
+
+
{{metadata.title}}
+ {{#if metadata.subtitle}}
+
{{metadata.subtitle}}
+ {{/if}}
+ {{{content}}}
+
+```
+
+### Available Variables
+
+- `{{{content}}}` - The main slide content (triple braces for unescaped HTML)
+- `{{metadata.*}}` - Access slide metadata properties
+ - `{{metadata.title}}` - Slide title
+ - `{{metadata.subtitle}}` - Slide subtitle
+ - Custom metadata properties from your slides
+
+## Live Preview
+
+Both the Theme Editor and Layout Editor include a live preview panel that updates as you type. This makes it easy to see how your changes will look without switching between files.
+
+## Tips
+
+- **Use VS Code CSS Variables**: For themes that adapt to the user's color scheme, use variables like `var(--vscode-editor-background)`
+- **Inspect Elements**: The preview iframe allows you to inspect elements for easier debugging
+- **Start with Examples**: Load an existing theme or layout as a starting point
+- **Test Across Slides**: After saving, test your theme/layout on multiple slide types
+
+## File Locations
+
+- **Themes**: Saved in `.demo/slides/` as `.css` files
+- **Layouts**: Saved in `.demo/layouts/` as `.hbs` files
+
+## Learn More
+
+- [Slides Documentation](https://demotime.show/slides/)
+- [Example Themes](https://github.com/estruyf/presentation-testing-smarter-ai-playwright/tree/mc2mc/.demo/theme)
+- [Example Layouts](https://github.com/estruyf/presentation-testing-smarter-ai-playwright/tree/main/.demo/layout)
From c5552ea5723b1d94f24bcf99943ee82a9a7f7536 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 23 Dec 2025 18:19:11 +0000
Subject: [PATCH 04/12] Add security sanitization for theme builder preview
Co-authored-by: estruyf <2900833+estruyf@users.noreply.github.com>
---
.../src/panels/ThemeBuilderPanel.ts | 17 +++++++++++++++--
.../components/webviews/ThemeBuilderView.tsx | 2 +-
2 files changed, 16 insertions(+), 3 deletions(-)
diff --git a/apps/vscode-extension/src/panels/ThemeBuilderPanel.ts b/apps/vscode-extension/src/panels/ThemeBuilderPanel.ts
index 89f6ae62..b16c0e57 100644
--- a/apps/vscode-extension/src/panels/ThemeBuilderPanel.ts
+++ b/apps/vscode-extension/src/panels/ThemeBuilderPanel.ts
@@ -276,6 +276,8 @@ export class ThemeBuilderPanel extends BaseWebview {
/**
* Get preview HTML for a slide with custom theme/layout
+ * Note: This generates HTML for preview in a sandboxed iframe.
+ * The content comes from the authenticated user's own files.
*/
private static async getPreviewHtml(
command: string,
@@ -283,6 +285,16 @@ export class ThemeBuilderPanel extends BaseWebview {
payload: { css?: string; html?: string },
) {
try {
+ // Sanitize CSS: Remove potentially dangerous patterns
+ const sanitizedCss = (payload.css || '')
+ .replace(/@import\s+/gi, '/* @import blocked */')
+ .replace(/javascript:/gi, '/* javascript: blocked */');
+
+ // Sanitize HTML: Remove script tags and event handlers
+ const sanitizedHtml = (payload.html || '
Preview
Add your HTML content to see the preview
')
+ .replace(/ with any attributes and any whitespace in closing tag
+ let prevHtml = '';
+ while (prevHtml !== sanitizedHtml) {
+ prevHtml = sanitizedHtml;
+ sanitizedHtml = sanitizedHtml.replace(/