diff --git a/public/themes-manifest.json b/public/themes-manifest.json
index 4d94958..ce5c747 100644
--- a/public/themes-manifest.json
+++ b/public/themes-manifest.json
@@ -11,5 +11,5 @@
"hasMasterSlide": true
}
},
- "generated": "2025-08-20T18:06:52.256Z"
+ "generated": "2025-08-20T18:50:35.588Z"
}
\ No newline at end of file
diff --git a/public/themes/default/layouts/title-slide.html b/public/themes/default/layouts/title-slide.html
index 753aa3f..4d124bc 100644
--- a/public/themes/default/layouts/title-slide.html
+++ b/public/themes/default/layouts/title-slide.html
@@ -2,7 +2,8 @@
{{title}}
-
- {{subtitle}}
-
+
+ {{diagram}}
+
+ Static Content
\ No newline at end of file
diff --git a/public/themes/default/style.css b/public/themes/default/style.css
index 16b0d5a..8e19167 100644
--- a/public/themes/default/style.css
+++ b/public/themes/default/style.css
@@ -126,6 +126,8 @@
.layout-title-slide .slot[data-slot="title"] {
font-size: clamp(2rem, 5vw, 4rem);
margin-bottom: 2rem;
+ width: 80%;
+
color: var(--theme-primary);
}
@@ -189,4 +191,4 @@
.slot.empty::before {
display: none;
}
-}
\ No newline at end of file
+}/* Test change Wed Aug 20 13:55:27 CDT 2025 */
diff --git a/src/components/themes/LayoutDetailPage.tsx b/src/components/themes/LayoutDetailPage.tsx
index 344f399..fda4790 100644
--- a/src/components/themes/LayoutDetailPage.tsx
+++ b/src/components/themes/LayoutDetailPage.tsx
@@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react';
import { useParams, Link } from 'react-router-dom';
import type { Theme, SlideLayout } from '../../types/theme';
import { getTheme } from '../../themes';
-import { LayoutPreview } from './LayoutPreview';
import './LayoutDetailPage.css';
export const LayoutDetailPage: React.FC = () => {
@@ -11,7 +10,7 @@ export const LayoutDetailPage: React.FC = () => {
const [layout, setLayout] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
- const [viewMode, setViewMode] = useState<'preview' | 'template' | 'slots'>('preview');
+ const [viewMode, setViewMode] = useState<'template' | 'slots'>('slots');
useEffect(() => {
const loadThemeAndLayout = async () => {
@@ -109,10 +108,10 @@ export const LayoutDetailPage: React.FC = () => {
-
- {viewMode === 'preview' && (
-
- )}
-
{viewMode === 'template' && (
HTML Template
diff --git a/src/components/themes/ThemeBrowser.tsx b/src/components/themes/ThemeBrowser.tsx
index 9c68b56..2df7e76 100644
--- a/src/components/themes/ThemeBrowser.tsx
+++ b/src/components/themes/ThemeBrowser.tsx
@@ -28,34 +28,6 @@ export const ThemeBrowser: React.FC = () => {
loadThemes();
}, []);
- // Listen for theme changes via HMR
- useEffect(() => {
- if (import.meta.hot) {
- const handleThemeChange = (data: { filename: string; eventType: string; timestamp: number }) => {
- console.log('Theme changed, reloading themes:', data);
-
- // Clear theme cache and reload
- setLoading(true);
- setTimeout(async () => {
- try {
- const discoveredThemes = await getThemes(true); // Force cache bust
- setThemes(discoveredThemes);
- setError(null);
- } catch (err) {
- setError(err instanceof Error ? err.message : 'Failed to reload themes');
- } finally {
- setLoading(false);
- }
- }, 100); // Small delay to ensure file is fully written
- };
-
- import.meta.hot.on('theme-changed', handleThemeChange);
-
- return () => {
- import.meta.hot?.off('theme-changed', handleThemeChange);
- };
- }
- }, []);
const handleThemeClick = (theme: Theme) => {
navigate(`/themes/${theme.id}`);
diff --git a/src/components/themes/ThemeDetailPage.css b/src/components/themes/ThemeDetailPage.css
index 474395d..4395951 100644
--- a/src/components/themes/ThemeDetailPage.css
+++ b/src/components/themes/ThemeDetailPage.css
@@ -130,8 +130,8 @@
.layout-card {
border: 1px solid #e5e7eb;
border-radius: 0.75rem;
- overflow: hidden;
background: white;
+ padding: 1.5rem;
transition: all 0.2s ease;
}
@@ -141,23 +141,76 @@
}
.layout-header {
- padding: 1rem;
- border-bottom: 1px solid #e5e7eb;
- background: #f9fafb;
display: flex;
- justify-content: space-between;
- align-items: center;
- flex-wrap: wrap;
- gap: 0.5rem;
+ flex-direction: column;
+ gap: 1rem;
}
.layout-name {
margin: 0;
- font-size: 1rem;
+ font-size: 1.125rem;
font-weight: 600;
color: #1f2937;
}
+.layout-description {
+ margin: 0;
+ font-size: 0.875rem;
+ color: #6b7280;
+ line-height: 1.4;
+}
+
+.layout-info {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ flex-wrap: wrap;
+}
+
+.slot-types {
+ display: flex;
+ gap: 0.25rem;
+ flex-wrap: wrap;
+}
+
+.slot-type-badge {
+ font-size: 0.625rem;
+ font-weight: 500;
+ padding: 0.125rem 0.375rem;
+ border-radius: 0.25rem;
+ text-transform: capitalize;
+}
+
+.slot-type-title {
+ background-color: #fef3c7;
+ color: #92400e;
+}
+
+.slot-type-subtitle {
+ background-color: #e0e7ff;
+ color: #3730a3;
+}
+
+.slot-type-text {
+ background-color: #d1fae5;
+ color: #047857;
+}
+
+.slot-type-image {
+ background-color: #fce7f3;
+ color: #be185d;
+}
+
+.slot-type-video {
+ background-color: #ddd6fe;
+ color: #6b21a8;
+}
+
+.slot-type-list {
+ background-color: #fed7d7;
+ color: #c53030;
+}
+
.layout-slots {
font-size: 0.75rem;
color: #6b7280;
@@ -197,9 +250,6 @@
background-color: #bfdbfe;
}
-.layout-preview-container {
- height: 200px;
-}
/* Variables Section */
.theme-variables {
diff --git a/src/components/themes/ThemeDetailPage.tsx b/src/components/themes/ThemeDetailPage.tsx
index 3c3d30f..861e06e 100644
--- a/src/components/themes/ThemeDetailPage.tsx
+++ b/src/components/themes/ThemeDetailPage.tsx
@@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react';
import { useParams, Link } from 'react-router-dom';
import type { Theme } from '../../types/theme';
import { getTheme } from '../../themes';
-import { LayoutPreview } from './LayoutPreview';
import './ThemeDetailPage.css';
export const ThemeDetailPage: React.FC = () => {
@@ -113,7 +112,17 @@ export const ThemeDetailPage: React.FC = () => {
{layout.name}
-
{layout.slots.length} slots
+
{layout.description}
+
+
{layout.slots.length} slots
+
+ {Array.from(new Set(layout.slots.map(slot => slot.type))).map(type => (
+
+ {type}
+
+ ))}
+
+
{
-
-
-
))}
diff --git a/src/plugins/themeWatcher.ts b/src/plugins/themeWatcher.ts
index 4396d4e..b6f0ee2 100644
--- a/src/plugins/themeWatcher.ts
+++ b/src/plugins/themeWatcher.ts
@@ -21,23 +21,23 @@ export function themeWatcherPlugin(): Plugin {
if (filename && (filename.endsWith('.css') || filename.endsWith('.html'))) {
console.log(`Theme file changed: ${filename}`);
- // Invalidate theme cache and send HMR update
+ // For theme files, we need a full reload since they affect:
+ // - Dynamically loaded CSS files
+ // - Theme manifest caching
+ // - Layout template caching
+ // - CSS variables that might be applied globally
+
+ // Send full reload command to browser
+ server.ws.send({
+ type: 'full-reload'
+ });
+
+ // Also invalidate theme cache for good measure
const moduleId = '/src/utils/themeLoader.ts';
const module = server.moduleGraph.getModuleById(moduleId);
if (module) {
server.reloadModule(module);
}
-
- // Send custom HMR event to notify components
- server.ws.send({
- type: 'custom',
- event: 'theme-changed',
- data: {
- filename,
- eventType,
- timestamp: Date.now()
- }
- });
}
}
);
diff --git a/src/utils/templateRenderer.ts b/src/utils/templateRenderer.ts
index 370c7c8..1bacf54 100644
--- a/src/utils/templateRenderer.ts
+++ b/src/utils/templateRenderer.ts
@@ -1,5 +1,28 @@
import type { SlideLayout, SlotConfig } from '../types/theme';
+/**
+ * Creates a simple SVG pattern for image slots
+ */
+const createImageSVG = (slotName: string, width: number = 400, height: number = 300): string => {
+ const encodedSVG = encodeURIComponent(`
+
+ `);
+ return `data:image/svg+xml,${encodedSVG}`;
+};
+
/**
* Generates sample data for a slot based on its configuration
*/
@@ -11,51 +34,37 @@ export const generateSampleDataForSlot = (slot: SlotConfig): string => {
return defaultContent;
}
+ // Clean up slot name for display
+ const slotDisplayName = id.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
+
// Generate sample data based on slot type
switch (type) {
- case 'title':
- if (id.includes('presentation') || id.includes('main')) {
- return 'Sample Presentation Title';
- }
- return 'Slide Title Example';
-
- case 'subtitle':
- if (id.includes('presentation') || id.includes('main')) {
- return 'A compelling subtitle for your presentation';
- }
- return 'Slide subtitle or description';
-
- case 'text':
- if (id.includes('content') || id.includes('body')) {
- return `This is sample content for the ${id} slot. It demonstrates how text will appear in this layout with multiple sentences to show text flow and formatting.`;
- }
- return `Sample ${id} text content`;
-
case 'image':
- return 'https://via.placeholder.com/400x300/e2e8f0/64748b?text=Sample+Image';
+ return createImageSVG(slotDisplayName);
+
+ case 'title':
+ case 'subtitle':
+ case 'text':
+ case 'heading':
+ return slotDisplayName;
case 'video':
- return 'https://www.example.com/sample-video.mp4';
+ return createImageSVG(`${slotDisplayName} (Video)`, 640, 360);
case 'audio':
- return 'https://www.example.com/sample-audio.mp3';
+ return `[${slotDisplayName} Audio]`;
case 'list':
- return '• First item example\n• Second item example\n• Third item example';
+ return `• ${slotDisplayName} Item 1\n• ${slotDisplayName} Item 2\n• ${slotDisplayName} Item 3`;
case 'code':
- return `function example() {\n console.log("Sample code");\n return true;\n}`;
+ return `// ${slotDisplayName}\nfunction ${id.replace(/-/g, '')}() {\n return "${slotDisplayName}";\n}`;
case 'table':
- return 'Header 1 | Header 2\n--- | ---\nData 1 | Data 2\nData 3 | Data 4';
+ return `${slotDisplayName} Col 1 | ${slotDisplayName} Col 2\n--- | ---\nRow 1 Data | Row 1 Data\nRow 2 Data | Row 2 Data`;
default:
- // Use placeholder as fallback, or generate from slot ID
- if (placeholder && placeholder !== `Enter ${id}`) {
- return placeholder.replace(/^(Enter|Add|Click to add)\s*/i, 'Sample ');
- }
-
- return `Sample ${id.replace(/-/g, ' ')} content`;
+ return slotDisplayName;
}
};