diff --git a/generate_index.py b/generate_index.py
index e2eefc1..086561a 100755
--- a/generate_index.py
+++ b/generate_index.py
@@ -89,7 +89,8 @@ def generate_demo_data():
'mapbox': [],
'claudeDevTools': [],
'uiSingle': [],
- 'uiModular': []
+ 'uiModular': [],
+ 'infiniteVariants': []
}
# Scan Three.js demos
@@ -220,6 +221,47 @@ def generate_demo_data():
'techniques': ['Separation of Concerns', 'Modular Architecture']
})
+ # Scan Infinite Variants test outputs
+ if os.path.exists('infinite_variants'):
+ variant_dirs = sorted(Path('infinite_variants').glob('infinite_variant_*/test_output'))
+ for variant_dir in variant_dirs:
+ # Extract variant number
+ variant_match = re.search(r'infinite_variant_(\d+)', str(variant_dir))
+ variant_num = variant_match.group(1) if variant_match else '?'
+
+ # Variant names for better titles
+ variant_names = {
+ '1': 'Pattern Synthesis',
+ '2': 'Utility Commands',
+ '3': 'Pluggable Templates',
+ '4': 'Quality Evaluation',
+ '5': 'Config-Driven',
+ '6': 'State Management',
+ '7': 'Meta Self-Improvement'
+ }
+ variant_name = variant_names.get(variant_num, f'Variant {variant_num}')
+
+ # Scan HTML and JS files in this variant's test_output
+ variant_files = sorted(list(variant_dir.glob('*.html')) + list(variant_dir.glob('*.js')))
+ for filepath in variant_files:
+ # Extract title based on file type
+ if filepath.suffix == '.html':
+ title = extract_title_from_html(str(filepath)) or filepath.name
+ description = extract_description_from_html(str(filepath))
+ else:
+ # For .js files, use filename as title
+ title = filepath.stem.replace('_', ' ').title()
+ description = f"Meta-aware JavaScript code pattern"
+
+ demos['infiniteVariants'].append({
+ 'number': len(demos['infiniteVariants']) + 1,
+ 'title': title,
+ 'description': f"[Variant {variant_num}: {variant_name}] {description}",
+ 'path': str(filepath),
+ 'type': variant_name,
+ 'techniques': ['Infinite Loop Variant', variant_name]
+ })
+
return demos
def generate_index_html(demos):
@@ -317,6 +359,7 @@ def main():
print(f" • Claude DevTools: {len(demos['claudeDevTools'])}")
print(f" • UI Single File: {len(demos['uiSingle'])}")
print(f" • UI Modular: {len(demos['uiModular'])}")
+ print(f" • Infinite Variants: {len(demos['infiniteVariants'])}")
print(f" • Total: {sum(len(demos[cat]) for cat in demos)}")
print("\n✨ Generating index.html...")
diff --git a/generate_screenshots.js b/generate_screenshots.js
new file mode 100755
index 0000000..9a3ea40
--- /dev/null
+++ b/generate_screenshots.js
@@ -0,0 +1,214 @@
+#!/usr/bin/env node
+
+/**
+ * Screenshot Generator for Infinite Agents Dashboard
+ *
+ * Automatically captures screenshots of all demos for preview thumbnails.
+ * Uses Playwright to render each demo and save a screenshot.
+ *
+ * Installation:
+ * npm install -D playwright
+ * npx playwright install chromium
+ *
+ * Usage:
+ * node generate_screenshots.js
+ * node generate_screenshots.js --category threejs
+ * node generate_screenshots.js --single threejs_viz/threejs_viz_1.html
+ */
+
+const { chromium } = require('playwright');
+const fs = require('fs');
+const path = require('path');
+
+// Demo categories and their file patterns
+const DEMO_CATEGORIES = {
+ threejs: {
+ pattern: 'threejs_viz/threejs_viz_*.html',
+ delay: 3000, // Extra time for WebGL to render
+ },
+ sdg: {
+ pattern: 'sdg_viz/sdg_viz_*.html',
+ delay: 2000,
+ },
+ d3: {
+ pattern: 'd3_test/d3_viz_*.html',
+ delay: 1500,
+ },
+ mapbox: {
+ pattern: 'mapbox_test/mapbox_globe_*/index.html',
+ delay: 3000, // Mapbox needs time to load tiles
+ },
+ claudeDevTools: {
+ pattern: 'claude_code_devtools/claude_devtool_*.html',
+ delay: 1000,
+ },
+ uiSingle: {
+ pattern: 'src/ui_hybrid_*.html',
+ delay: 800,
+ },
+ uiInfinite: {
+ pattern: 'src_infinite/ui_hybrid_*.html',
+ delay: 800,
+ },
+ uiModular: {
+ pattern: 'src_group/ui_hybrid_*/index.html',
+ delay: 800,
+ },
+ infiniteVariants: {
+ pattern: 'infinite_variants/infinite_variant_*/test_output/*.html',
+ delay: 1500, // Mixed content, some may have animations
+ },
+};
+
+// Parse command line arguments
+const args = process.argv.slice(2);
+const categoryFilter = args.find(arg => arg.startsWith('--category='))?.split('=')[1];
+const singleFile = args.find(arg => arg.startsWith('--single='))?.split('=')[1];
+const PORT = args.find(arg => arg.startsWith('--port='))?.split('=')[1] || 8889;
+
+// Create screenshots directory
+const SCREENSHOTS_DIR = path.join(__dirname, 'screenshots');
+if (!fs.existsSync(SCREENSHOTS_DIR)) {
+ fs.mkdirSync(SCREENSHOTS_DIR, { recursive: true });
+ console.log(`✅ Created screenshots directory: ${SCREENSHOTS_DIR}`);
+}
+
+// Scan directory for demo files
+function scanDirectory(pattern) {
+ const glob = require('glob');
+ return glob.sync(pattern);
+}
+
+// Get all demo files
+function getAllDemos() {
+ const demos = [];
+
+ for (const [category, config] of Object.entries(DEMO_CATEGORIES)) {
+ if (categoryFilter && category !== categoryFilter) continue;
+
+ const files = scanDirectory(config.pattern);
+ files.forEach(file => {
+ demos.push({
+ path: file,
+ category,
+ delay: config.delay,
+ });
+ });
+ }
+
+ return demos;
+}
+
+// Capture screenshot for a single demo
+async function captureScreenshot(browser, demo, index, total) {
+ const page = await browser.newPage();
+
+ try {
+ // Set viewport size (desktop resolution)
+ await page.setViewportSize({ width: 1920, height: 1080 });
+
+ const url = `http://localhost:${PORT}/${demo.path}`;
+ console.log(`\n📸 [${index + 1}/${total}] ${demo.path}`);
+
+ // Navigate to demo
+ await page.goto(url, {
+ waitUntil: 'networkidle',
+ timeout: 30000,
+ });
+
+ // Wait for demo to render
+ await page.waitForTimeout(demo.delay);
+
+ // Generate screenshot filename
+ const screenshotFilename = demo.path.replace(/\//g, '_').replace('.html', '.png');
+ const screenshotPath = path.join(SCREENSHOTS_DIR, screenshotFilename);
+
+ // Capture screenshot
+ await page.screenshot({
+ path: screenshotPath,
+ fullPage: false, // Just viewport, not entire scrollable page
+ });
+
+ console.log(` ✅ Saved: ${screenshotFilename}`);
+
+ } catch (error) {
+ console.error(` ❌ Failed: ${error.message}`);
+ } finally {
+ await page.close();
+ }
+}
+
+// Main execution
+async function main() {
+ console.log('🚀 Infinite Agents Screenshot Generator\n');
+
+ // Get demo files
+ let demos;
+ if (singleFile) {
+ demos = [{
+ path: singleFile,
+ category: 'custom',
+ delay: 2000,
+ }];
+ } else {
+ demos = getAllDemos();
+ }
+
+ if (demos.length === 0) {
+ console.error('❌ No demo files found!');
+ console.log('\nMake sure:');
+ console.log(' 1. You are in the project root directory');
+ console.log(' 2. Demo files exist in their respective directories');
+ console.log(' 3. Category filter is correct (if using --category)');
+ process.exit(1);
+ }
+
+ console.log(`📊 Found ${demos.length} demos to screenshot\n`);
+
+ // Check if server is running
+ console.log(`🔍 Checking if server is running on http://localhost:${PORT}...`);
+ try {
+ const http = require('http');
+ await new Promise((resolve, reject) => {
+ const req = http.get(`http://localhost:${PORT}`, (res) => {
+ resolve();
+ });
+ req.on('error', reject);
+ req.setTimeout(3000, () => reject(new Error('Timeout')));
+ });
+ console.log(' ✅ Server is running\n');
+ } catch (error) {
+ console.error(' ❌ Server is not running!');
+ console.log(`\n🔧 Start the server first:`);
+ console.log(` python3 -m http.server ${PORT}\n`);
+ process.exit(1);
+ }
+
+ // Launch browser
+ console.log('🌐 Launching browser...');
+ const browser = await chromium.launch({
+ headless: true,
+ });
+ console.log(' ✅ Browser ready\n');
+
+ // Capture screenshots
+ console.log('📸 Capturing screenshots...');
+ console.log('━'.repeat(50));
+
+ for (let i = 0; i < demos.length; i++) {
+ await captureScreenshot(browser, demos[i], i, demos.length);
+ }
+
+ // Cleanup
+ await browser.close();
+
+ console.log('\n' + '━'.repeat(50));
+ console.log(`\n✨ Done! Captured ${demos.length} screenshots`);
+ console.log(`📁 Screenshots saved to: ${SCREENSHOTS_DIR}\n`);
+}
+
+// Run
+main().catch(error => {
+ console.error('\n❌ Fatal error:', error);
+ process.exit(1);
+});
diff --git a/index.html b/index.html
index 5708dfe..e97a054 100644
--- a/index.html
+++ b/index.html
@@ -2,6 +2,9 @@
+
+
+
@@ -242,6 +245,81 @@
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.2);
}
+ /* Screenshot Preview in Card */
+ .demo-screenshot {
+ width: 100%;
+ height: 200px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ overflow: hidden;
+ cursor: pointer;
+ }
+
+ .demo-screenshot img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ transition: transform 0.3s ease;
+ }
+
+ .demo-card:hover .demo-screenshot img {
+ transform: scale(1.05);
+ }
+
+ .demo-screenshot-placeholder {
+ color: white;
+ font-size: 4rem;
+ opacity: 0.5;
+ text-align: center;
+ padding: 20px;
+ }
+
+ .demo-screenshot-placeholder-text {
+ font-size: 0.9rem;
+ opacity: 0.7;
+ margin-top: 10px;
+ font-weight: 500;
+ }
+
+ .demo-screenshot-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: background 0.3s ease;
+ pointer-events: none;
+ }
+
+ .demo-card:hover .demo-screenshot-overlay {
+ background: rgba(0, 0, 0, 0.3);
+ }
+
+ .demo-screenshot-overlay span {
+ background: white;
+ color: var(--primary);
+ padding: 10px 20px;
+ border-radius: 25px;
+ font-weight: 600;
+ font-size: 0.9rem;
+ opacity: 0;
+ transform: translateY(10px);
+ transition: all 0.3s ease;
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
+ }
+
+ .demo-card:hover .demo-screenshot-overlay span {
+ opacity: 1;
+ transform: translateY(0);
+ }
+
.demo-card::before {
content: '';
position: absolute;
@@ -358,6 +436,7 @@
.hidden {
display: none !important;
}
+
@@ -375,15 +454,15 @@
@@ -406,6 +485,7 @@
+
@@ -417,7 +497,7 @@
Three.js 3D Visualizations
Progressive WebGL/WebGPU visualizations with foundation → expert learning path
- 5 demos
+ 10 demos
@@ -500,6 +580,19 @@
+
+
+