diff --git a/packages/templates/build-gallery.js b/packages/templates/build-gallery.js
new file mode 100644
index 00000000..7d64226b
--- /dev/null
+++ b/packages/templates/build-gallery.js
@@ -0,0 +1,338 @@
+#!/usr/bin/env node
+
+// Static site generator for the soroban templates gallery
+// Creates a complete HTML page with embedded SVGs
+
+const fs = require('fs');
+const path = require('path');
+
+const examples = [
+ {
+ id: 'basic-5',
+ title: 'Basic Number 5',
+ description: 'Simple representation of 5 with monochrome diamonds',
+ number: 5,
+ config: {
+ bead_shape: 'diamond',
+ color_scheme: 'monochrome',
+ base_size: 1.5
+ }
+ },
+ {
+ id: 'colorful-123',
+ title: 'Colorful 123',
+ description: 'Number 123 with place-value colors and diamond beads',
+ number: 123,
+ config: {
+ bead_shape: 'diamond',
+ color_scheme: 'place-value',
+ base_size: 1.2
+ }
+ },
+ {
+ id: 'circles-42',
+ title: 'Circle Beads - 42',
+ description: 'Number 42 with circular beads and heaven-earth colors',
+ number: 42,
+ config: {
+ bead_shape: 'circle',
+ color_scheme: 'heaven-earth',
+ base_size: 1.8
+ }
+ },
+ {
+ id: 'large-7',
+ title: 'Large Scale - 7',
+ description: 'Single digit with maximum scale for detail work',
+ number: 7,
+ config: {
+ bead_shape: 'diamond',
+ color_scheme: 'place-value',
+ base_size: 2.5
+ }
+ },
+ {
+ id: 'compact-999',
+ title: 'Compact 999',
+ description: 'Large number with hidden inactive beads for clean look',
+ number: 999,
+ config: {
+ bead_shape: 'square',
+ color_scheme: 'alternating',
+ hide_inactive: true,
+ base_size: 1.0
+ }
+ },
+ {
+ id: 'educational-1234',
+ title: 'Educational 1234',
+ description: 'Four-digit number showing empty columns for learning',
+ number: 1234,
+ config: {
+ bead_shape: 'circle',
+ color_scheme: 'place-value',
+ show_empty: true,
+ base_size: 1.3
+ }
+ }
+];
+
+function buildStaticGallery() {
+ console.log('šļø Building static soroban gallery...');
+
+ let svgCount = 0;
+ let missingCount = 0;
+
+ // Generate the example cards HTML
+ const exampleCards = examples.map(example => {
+ const svgPath = `gallery/${example.id}.svg`;
+ let svgContent = '';
+
+ if (fs.existsSync(svgPath)) {
+ svgContent = fs.readFileSync(svgPath, 'utf8');
+ svgCount++;
+ console.log(`ā
Embedded ${example.id}.svg`);
+ } else {
+ svgContent = `
+
+
ā ļø
+
SVG not generated
+
+ Run npm run gallery first
+
+
+ `;
+ missingCount++;
+ console.log(`ā ļø Missing ${svgPath}`);
+ }
+
+ const configText = Object.entries(example.config)
+ .map(([key, value]) => `${key}: ${value}`)
+ .join(' ');
+
+ return `
+
+ `;
+ }).join('\n');
+
+ // Create the complete HTML
+ const html = `
+
+
+
+
+ š§® Soroban Templates Gallery
+
+
+
+
+
+
+
+
+ ${svgCount} examples rendered${missingCount > 0 ? `, ${missingCount} missing` : ''}
+ ⢠Generated on ${new Date().toLocaleDateString()} at ${new Date().toLocaleTimeString()}
+
+
+
+
+ ${exampleCards}
+
+
+
+
+
+`;
+
+ // Write the static gallery
+ fs.writeFileSync('gallery-static.html', html);
+
+ console.log('\nš Build Summary:');
+ console.log(` ā
SVGs embedded: ${svgCount}`);
+ if (missingCount > 0) {
+ console.log(` ā ļø SVGs missing: ${missingCount}`);
+ }
+ console.log(` š Output: gallery-static.html`);
+
+ console.log('\nš Static gallery built successfully!');
+ console.log(' š Open gallery-static.html in your browser');
+
+ return true;
+}
+
+// Run the static site generator
+if (require.main === module) {
+ buildStaticGallery();
+}
\ No newline at end of file
diff --git a/packages/templates/gallery-static.html b/packages/templates/gallery-static.html
new file mode 100644
index 00000000..c0a27c95
--- /dev/null
+++ b/packages/templates/gallery-static.html
@@ -0,0 +1,1023 @@
+
+
+
+
+
+ š§® Soroban Templates Gallery
+
+
+
+
+
+
+
+
+ 6 examples rendered
+ ⢠Generated on 9/16/2025 at 10:15:32 AM
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/templates/gallery.html b/packages/templates/gallery.html
index 64093c90..490938d9 100644
--- a/packages/templates/gallery.html
+++ b/packages/templates/gallery.html
@@ -414,31 +414,30 @@
}
async function generateTypstSvg(example) {
- // Load the actual generated SVG file from gallery/
+ // For local file access, we'll show a clickable link to the SVG
+ // This avoids CORS issues with fetch() on local files
const svgPath = `gallery/${example.id}.svg`;
- try {
- const response = await fetch(svgPath);
- if (!response.ok) {
- throw new Error(`Failed to load ${svgPath}: ${response.status}`);
- }
- const svgContent = await response.text();
- return svgContent;
- } catch (error) {
- // If we can't load the SVG file, show an error message
- return `
-
-
ā ļø
-
SVG file not found
-
- Run npm run gallery to generate SVGs
-
-
- Looking for: ${svgPath}
+ return `
+
+
+
š§®
+
Soroban for ${example.number}
+
+ ${example.config.bead_shape} beads, ${example.config.color_scheme} colors
+
+ š Open SVG File
+
- `;
- }
+
+ File: ${svgPath}
+
+
+ `;
}
diff --git a/packages/templates/package.json b/packages/templates/package.json
index 291f44e1..4c6584b3 100644
--- a/packages/templates/package.json
+++ b/packages/templates/package.json
@@ -30,8 +30,9 @@
"examples:node": "node examples/node-example.js",
"examples:python": "python3 examples/python-example.py",
"gallery": "node generate-gallery.js",
- "gallery:open": "npm run gallery && open gallery.html",
- "gallery:clean": "rm -rf gallery/"
+ "gallery:build": "node build-gallery.js",
+ "gallery:static": "npm run gallery && npm run gallery:build && open gallery-static.html",
+ "gallery:clean": "rm -rf gallery/ gallery-static.html gallery-embedded.html"
},
"keywords": [
"typst",