selkie 1.0.0 → 1.2.0

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/README.md CHANGED
@@ -1,19 +1,65 @@
1
- # Selkie
1
+ # Selkie
2
2
 
3
3
  In Celtic mythology, selkies are beings that can transform from seals into humans by shedding their seal skins. They often feature in stories of love and longing, where a selkie's human form is hidden away or discovered by a human.
4
4
 
5
- This library is heavily influenced as a template starter for generative projects. For now it only works with p5. I'll be extending it to also include other templates.
5
+ A CLI tool for scaffolding generative art projects. Quickly create projects with p5.js, Paper.js, Three.js, WebGL/GLSL shaders, or ML5.js hand tracking.
6
6
 
7
- This is a project to help better understand the process of streamlining my sketches in p5. I've been able to quickly iternate using Processing for years now. But I want to really get a grasp on creating a system that will generate the work that I will be able to use.
7
+ ## Installation
8
8
 
9
9
  ```bash
10
10
  npm install selkie
11
11
  ```
12
12
 
13
+ ## Usage
14
+
15
+ Create a new project with one of the available templates:
13
16
 
14
- In order to create a new project, use the following after it has been installed.
15
17
  ```bash
16
- selkie -t default
18
+ selkie -t default # p5.js canvas project
19
+ selkie -t svg # Paper.js vector/SVG project
20
+ selkie -t three # Three.js 3D project
21
+ selkie -t glsl # WebGL shader project
22
+ selkie -t ml5-hand # ML5.js hand tracking project
23
+ selkie -t pressure # p5.js pressure drawing (iPad + Apple Pencil)
17
24
  ```
18
25
 
26
+ You'll be prompted for a folder name, then the project will be created with all dependencies installed.
27
+
28
+ ## Working on Your Sketch
29
+
30
+ Each generated project includes a dev server:
31
+
32
+ ```bash
33
+ cd your-project
34
+ npm start # Opens in browser with live reload
35
+ ```
36
+
37
+ Edit `scripts/sketch.js` to create your generative art. Press 's' to export PNG/SVG (depending on template).
38
+
39
+ ## Building a Single-File Bundle
40
+
41
+ When you're ready to share your sketch, build it into a single HTML file that works offline on any device:
42
+
43
+ ```bash
44
+ npm run build
45
+ ```
46
+
47
+ This creates `dist/bundle.html` - a minified, self-contained file you can:
48
+ - Open directly in any browser (no server needed)
49
+ - Share via email, USB, or any file transfer
50
+ - Upload to any static hosting
51
+
52
+ **Note:** The ML5 hand tracking template still requires internet for AI models.
53
+
54
+ ## Templates
55
+
56
+ | Template | Library | Output | Notes |
57
+ |----------|---------|--------|-------|
58
+ | `default` | p5.js | Canvas/PNG | Classic creative coding |
59
+ | `svg` | Paper.js | SVG vectors | Perfect for plotters |
60
+ | `three` | Three.js | WebGL 3D | 3D graphics |
61
+ | `glsl` | Native WebGL | Shaders | Smallest bundle (~5KB) |
62
+ | `ml5-hand` | ML5.js | Hand tracking | Requires webcam + internet |
63
+
64
+ All templates include a seeded random number generator for deterministic/reproducible outputs.
19
65
 
package/index.js CHANGED
@@ -6,17 +6,20 @@ import path from 'path';
6
6
  import { Command } from 'commander';
7
7
  import { fileURLToPath } from 'url';
8
8
  import { exec, execSync } from 'child_process';
9
- import { template as defaultTemplate } from './templates.js';
10
- import { packageJsonContent } from './package_details.js';
9
+ import { template as defaultTemplate, svgTemplate, threeTemplate, glslTemplate, ml5HandTrackingTemplate, pressureTemplate, buildScriptContent } from './templates/templates.js';
10
+ import { packageJsonContent, svgPackageJsonContent, threePackageJsonContent, glslPackageJsonContent, ml5HandTrackingPackageJsonContent, pressurePackageJsonContent } from './package_details.js';
11
11
 
12
12
  // Convert import.meta.url to a file path
13
13
  const __filename = fileURLToPath(import.meta.url);
14
14
  const __dirname = path.dirname(__filename);
15
15
 
16
+ // Read version from package.json so --version stays in sync with releases
17
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
18
+
16
19
  // Initialize commander
17
20
  const program = new Command();
18
21
  program
19
- .version('1.0.0')
22
+ .version(pkg.version)
20
23
  .option('-t, --template <type>', 'specify the template type')
21
24
  .parse(process.argv);
22
25
 
@@ -45,15 +48,41 @@ async function createProject() {
45
48
  await fs.ensureDir(scriptsPath);
46
49
 
47
50
  // Choose the template
48
- let htmlContent, sketchContent, randomContent;
49
-
51
+ let htmlContent, sketchContent, randomContent, selectedPackageJson;
52
+ let templateType = options.template || 'default';
50
53
 
51
- if (options.template === 'default' || !options.template) {
54
+ if (templateType === 'default') {
52
55
  htmlContent = defaultTemplate.htmlContent;
53
56
  sketchContent = defaultTemplate.sketchContent;
54
57
  randomContent = defaultTemplate.randomContent;
58
+ selectedPackageJson = packageJsonContent;
59
+ } else if (templateType === 'svg') {
60
+ htmlContent = svgTemplate.htmlContent;
61
+ sketchContent = svgTemplate.sketchContent;
62
+ randomContent = svgTemplate.randomContent;
63
+ selectedPackageJson = svgPackageJsonContent;
64
+ } else if (templateType === 'three') {
65
+ htmlContent = threeTemplate.htmlContent;
66
+ sketchContent = threeTemplate.sketchContent;
67
+ randomContent = threeTemplate.randomContent;
68
+ selectedPackageJson = threePackageJsonContent;
69
+ } else if (templateType === 'glsl') {
70
+ htmlContent = glslTemplate.htmlContent;
71
+ sketchContent = glslTemplate.sketchContent;
72
+ randomContent = glslTemplate.randomContent;
73
+ selectedPackageJson = glslPackageJsonContent;
74
+ } else if (templateType === 'ml5-hand') {
75
+ htmlContent = ml5HandTrackingTemplate.htmlContent;
76
+ sketchContent = ml5HandTrackingTemplate.sketchContent;
77
+ randomContent = ml5HandTrackingTemplate.randomContent;
78
+ selectedPackageJson = ml5HandTrackingPackageJsonContent;
79
+ } else if (templateType === 'pressure') {
80
+ htmlContent = pressureTemplate.htmlContent;
81
+ sketchContent = pressureTemplate.sketchContent;
82
+ randomContent = pressureTemplate.randomContent;
83
+ selectedPackageJson = pressurePackageJsonContent;
55
84
  } else {
56
- throw new Error('Unknown template type');
85
+ throw new Error('Unknown template type. Available templates: default, svg, three, glsl, ml5-hand, pressure');
57
86
  }
58
87
 
59
88
  // Create index.html
@@ -61,33 +90,61 @@ async function createProject() {
61
90
 
62
91
  // Create package.json
63
92
  const packageJsonPath = path.join(folderPath, 'package.json');
64
- const packageJson = packageJsonContent.replace(/"project-name"/, `"${folderName}"`);
93
+ const packageJson = selectedPackageJson.replace(/"project-name"/, `"${folderName}"`);
65
94
  await fs.writeFile(packageJsonPath, packageJson.trim());
66
95
 
67
- // Install dependencies
68
- console.log('Installing dependencies...');
69
- execSync('npm install -s', { cwd: folderPath, stdio: 'inherit' });
70
-
71
- // Check if p5.min.js exists and copy it to the scripts folder
72
- const p5Path = path.join(__dirname, 'node_modules', 'p5', 'lib', 'p5.min.js');
73
- if (await fs.pathExists(p5Path)) {
74
- await fs.copyFile(p5Path, path.join(scriptsPath, 'p5.js'));
75
- console.log('p5.js copied successfully.');
96
+ // Install dependencies
97
+ console.log('Installing dependencies...');
98
+ execSync('npm install -s', { cwd: folderPath, stdio: 'inherit' });
99
+
100
+ // Copy the appropriate library to the scripts folder
101
+ if (templateType === 'svg') {
102
+ // Copy paper.js for SVG template
103
+ const paperPath = path.join(__dirname, 'node_modules', 'paper', 'dist', 'paper-full.js');
104
+ if (await fs.pathExists(paperPath)) {
105
+ await fs.copyFile(paperPath, path.join(scriptsPath, 'paper-full.js'));
106
+ console.log('paper.js copied successfully.');
107
+ } else {
108
+ throw new Error(`paper-full.js not found at ${paperPath}`);
109
+ }
110
+ } else if (templateType === 'three') {
111
+ // Copy three.js module for Three.js template
112
+ const threePath = path.join(__dirname, 'node_modules', 'three', 'build', 'three.module.js');
113
+ if (await fs.pathExists(threePath)) {
114
+ await fs.copyFile(threePath, path.join(scriptsPath, 'three.module.js'));
115
+ console.log('three.js copied successfully.');
116
+ } else {
117
+ throw new Error(`three.module.js not found at ${threePath}`);
118
+ }
119
+ } else if (templateType === 'glsl') {
120
+ // GLSL template uses native WebGL, no external library needed
121
+ console.log('GLSL template - using native WebGL (no library needed).');
122
+ } else if (templateType === 'ml5-hand') {
123
+ // ML5 template uses CDN, no local library needed
124
+ console.log('ML5 hand tracking template - using ml5.js from CDN.');
76
125
  } else {
77
- throw new Error(`p5.min.js not found at ${p5Path}`);
126
+ // Copy p5.js for default template
127
+ const p5Path = path.join(__dirname, 'node_modules', 'p5', 'lib', 'p5.min.js');
128
+ if (await fs.pathExists(p5Path)) {
129
+ await fs.copyFile(p5Path, path.join(scriptsPath, 'p5.js'));
130
+ console.log('p5.js copied successfully.');
131
+ } else {
132
+ throw new Error(`p5.min.js not found at ${p5Path}`);
133
+ }
78
134
  }
79
135
 
80
136
  // Create sketch.js
81
137
  await fs.writeFile(path.join(scriptsPath, 'sketch.js'), sketchContent.trim());
82
-
83
138
 
84
- // Create random.js
85
- await fs.writeFile(path.join(scriptsPath, 'random.js'), randomContent.trim());
86
-
139
+ // Create random.js
140
+ await fs.writeFile(path.join(scriptsPath, 'random.js'), randomContent.trim());
87
141
 
88
142
  // Create index.html
89
143
  await fs.writeFile(path.join(folderPath, 'index.html'), htmlContent.trim());
90
144
 
145
+ // Create build.js for single-file bundling
146
+ await fs.writeFile(path.join(folderPath, 'build.js'), buildScriptContent.trim());
147
+ console.log('build.js created - run "npm run build" to create a single-file bundle.');
91
148
 
92
149
  console.log(`Project ${folderName} created successfully.`);
93
150
 
package/package.json CHANGED
@@ -1,25 +1,44 @@
1
1
  {
2
2
  "name": "selkie",
3
- "version": "1.0.0",
4
- "description": "Simple generative template creation",
3
+ "version": "1.2.0",
4
+ "description": "CLI for scaffolding generative art projects with p5.js, Paper.js, Three.js, GLSL, and ML5.js",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "selkie": "./index.js"
9
9
  },
10
+ "files": [
11
+ "index.js",
12
+ "templates/",
13
+ "package_details.js",
14
+ "README.md"
15
+ ],
10
16
  "scripts": {
11
17
  "test": "echo \"Error: no test specified\" && exit 1"
12
18
  },
13
- "keywords": [],
19
+ "keywords": [
20
+ "generative-art",
21
+ "creative-coding",
22
+ "p5js",
23
+ "paperjs",
24
+ "threejs",
25
+ "glsl",
26
+ "webgl",
27
+ "scaffold",
28
+ "cli"
29
+ ],
14
30
  "author": "generatecoll",
15
31
  "license": "ISC",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/generatecoll/selkie"
35
+ },
16
36
  "dependencies": {
17
37
  "commander": "^12.1.0",
18
38
  "fs-extra": "^11.2.0",
19
39
  "inquirer": "^9.2.22",
20
- "p5": "^1.9.4"
21
- },
22
- "devDependencies": {
23
- "live-server": "^1.2.2"
40
+ "p5": "^1.9.4",
41
+ "paper": "^0.12.18",
42
+ "three": "^0.170.0"
24
43
  }
25
44
  }
@@ -5,16 +5,116 @@ export const packageJsonContent = `{
5
5
  "description": "",
6
6
  "main": "index.js",
7
7
  "scripts": {
8
- "start": "live-server"
8
+ "start": "live-server",
9
+ "build": "node build.js"
9
10
  },
10
11
  "dependencies": {
11
- "p5": "^1.4.0"
12
+ "p5": "^1.4.0"
12
13
  },
13
14
  "devDependencies": {
14
- "live-server": "^1.2.1"
15
+ "live-server": "^1.2.1",
16
+ "terser": "^5.31.0"
15
17
  },
16
18
  "keywords": [],
17
19
  "author": "",
18
20
  "license": "ISC"
19
- }
20
- `;
21
+ }`;
22
+
23
+ export const pressurePackageJsonContent = `{
24
+ "name": "project-name",
25
+ "version": "1.0.0",
26
+ "description": "Pressure-sensitive p5.js drawing (iPad + Apple Pencil)",
27
+ "main": "index.js",
28
+ "scripts": {
29
+ "start": "live-server",
30
+ "build": "node build.js"
31
+ },
32
+ "dependencies": {
33
+ "p5": "^1.4.0"
34
+ },
35
+ "devDependencies": {
36
+ "live-server": "^1.2.1",
37
+ "terser": "^5.31.0"
38
+ },
39
+ "keywords": ["generative", "p5.js", "apple-pencil", "pressure", "drawing"],
40
+ "author": "",
41
+ "license": "ISC"
42
+ }`;
43
+
44
+ export const svgPackageJsonContent = `{
45
+ "name": "project-name",
46
+ "version": "1.0.0",
47
+ "description": "Paper.js SVG generative art project",
48
+ "main": "index.js",
49
+ "scripts": {
50
+ "start": "live-server",
51
+ "build": "node build.js"
52
+ },
53
+ "dependencies": {
54
+ "paper": "^0.12.18"
55
+ },
56
+ "devDependencies": {
57
+ "live-server": "^1.2.1",
58
+ "terser": "^5.31.0"
59
+ },
60
+ "keywords": ["generative", "svg", "paper.js"],
61
+ "author": "",
62
+ "license": "ISC"
63
+ }`;
64
+
65
+ export const threePackageJsonContent = `{
66
+ "name": "project-name",
67
+ "version": "1.0.0",
68
+ "description": "Three.js 3D generative art project",
69
+ "main": "index.js",
70
+ "scripts": {
71
+ "start": "live-server",
72
+ "build": "node build.js"
73
+ },
74
+ "dependencies": {
75
+ "three": "^0.170.0"
76
+ },
77
+ "devDependencies": {
78
+ "live-server": "^1.2.1",
79
+ "terser": "^5.31.0"
80
+ },
81
+ "keywords": ["generative", "3d", "three.js", "webgl"],
82
+ "author": "",
83
+ "license": "ISC"
84
+ }`;
85
+
86
+ export const glslPackageJsonContent = `{
87
+ "name": "project-name",
88
+ "version": "1.0.0",
89
+ "description": "GLSL WebGL shader project",
90
+ "main": "index.js",
91
+ "scripts": {
92
+ "start": "live-server",
93
+ "build": "node build.js"
94
+ },
95
+ "devDependencies": {
96
+ "live-server": "^1.2.1",
97
+ "terser": "^5.31.0"
98
+ },
99
+ "keywords": ["generative", "glsl", "webgl", "shader"],
100
+ "author": "",
101
+ "license": "ISC"
102
+ }`;
103
+
104
+ export const ml5HandTrackingPackageJsonContent = `{
105
+ "name": "project-name",
106
+ "version": "1.0.0",
107
+ "description": "ML5.js hand tracking project",
108
+ "main": "index.js",
109
+ "scripts": {
110
+ "start": "live-server",
111
+ "build": "node build.js"
112
+ },
113
+ "devDependencies": {
114
+ "live-server": "^1.2.1",
115
+ "terser": "^5.31.0"
116
+ },
117
+ "keywords": ["generative", "ml5", "hand-tracking", "computer-vision"],
118
+ "author": "",
119
+ "license": "ISC"
120
+ }`;
@@ -0,0 +1,268 @@
1
+ // Build script template - shared across all project types
2
+ export const buildScriptContent = `#!/usr/bin/env node
3
+ // build.js - Bundle project into single HTML file
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { minify } = require('terser');
7
+
8
+ async function build() {
9
+ console.log('Building single-file bundle...');
10
+
11
+ const templateType = detectTemplateType();
12
+ console.log('Detected template type:', templateType);
13
+
14
+ // Read source files
15
+ const html = fs.readFileSync('index.html', 'utf8');
16
+ const sketch = fs.readFileSync('scripts/sketch.js', 'utf8');
17
+ const random = fs.readFileSync('scripts/random.js', 'utf8');
18
+
19
+ // Read library based on template type
20
+ let library = '';
21
+ let libraryFile = '';
22
+
23
+ switch (templateType) {
24
+ case 'default':
25
+ libraryFile = 'scripts/p5.js';
26
+ if (fs.existsSync(libraryFile)) {
27
+ library = fs.readFileSync(libraryFile, 'utf8');
28
+ }
29
+ break;
30
+ case 'svg':
31
+ libraryFile = 'scripts/paper-full.js';
32
+ if (fs.existsSync(libraryFile)) {
33
+ library = fs.readFileSync(libraryFile, 'utf8');
34
+ }
35
+ break;
36
+ case 'three':
37
+ libraryFile = 'scripts/three.module.js';
38
+ if (fs.existsSync(libraryFile)) {
39
+ library = fs.readFileSync(libraryFile, 'utf8');
40
+ }
41
+ break;
42
+ case 'glsl':
43
+ // No library needed - uses native WebGL
44
+ break;
45
+ case 'ml5-hand':
46
+ console.warn('\\n⚠️ Warning: ML5 template requires internet for AI models.');
47
+ console.warn(' The bundled file will keep the CDN reference to ml5.js.\\n');
48
+ break;
49
+ }
50
+
51
+ // Minify JavaScript
52
+ let minifiedLibrary = '';
53
+ if (library) {
54
+ try {
55
+ const result = await minify(library, {
56
+ compress: true,
57
+ mangle: true,
58
+ format: { comments: false }
59
+ });
60
+ minifiedLibrary = result.code;
61
+ } catch (e) {
62
+ console.warn('Could not minify library, using original:', e.message);
63
+ minifiedLibrary = library;
64
+ }
65
+ }
66
+
67
+ let minifiedSketch = sketch;
68
+ let minifiedRandom = random;
69
+
70
+ try {
71
+ // For Three.js, transform ES6 imports before minifying
72
+ let sketchToMinify = sketch;
73
+ if (templateType === 'three') {
74
+ sketchToMinify = transformThreeJsSketch(sketch);
75
+ }
76
+
77
+ const sketchResult = await minify(sketchToMinify, {
78
+ compress: true,
79
+ mangle: true,
80
+ format: { comments: false }
81
+ });
82
+ minifiedSketch = sketchResult.code;
83
+ } catch (e) {
84
+ console.warn('Could not minify sketch.js, using original:', e.message);
85
+ if (templateType === 'three') {
86
+ minifiedSketch = transformThreeJsSketch(sketch);
87
+ }
88
+ }
89
+
90
+ try {
91
+ const randomResult = await minify(random, {
92
+ compress: true,
93
+ mangle: true,
94
+ format: { comments: false }
95
+ });
96
+ minifiedRandom = randomResult.code;
97
+ } catch (e) {
98
+ console.warn('Could not minify random.js, using original:', e.message);
99
+ }
100
+
101
+ // Build the single HTML file
102
+ const bundledHtml = buildHtml(html, {
103
+ library: minifiedLibrary,
104
+ sketch: minifiedSketch,
105
+ random: minifiedRandom,
106
+ templateType
107
+ });
108
+
109
+ // Write output
110
+ fs.mkdirSync('dist', { recursive: true });
111
+ fs.writeFileSync('dist/bundle.html', bundledHtml);
112
+
113
+ const originalSize = Buffer.byteLength(html, 'utf8') +
114
+ Buffer.byteLength(sketch, 'utf8') +
115
+ Buffer.byteLength(random, 'utf8') +
116
+ Buffer.byteLength(library, 'utf8');
117
+ const bundleSize = fs.statSync('dist/bundle.html').size;
118
+
119
+ console.log('\\n✓ Bundle created: dist/bundle.html');
120
+ console.log(' Original size: ' + (originalSize / 1024).toFixed(1) + ' KB');
121
+ console.log(' Bundle size: ' + (bundleSize / 1024).toFixed(1) + ' KB');
122
+ console.log('\\nOpen dist/bundle.html in any browser - no server needed!');
123
+ }
124
+
125
+ function detectTemplateType() {
126
+ const html = fs.readFileSync('index.html', 'utf8');
127
+ if (html.includes('three.module.js') || html.includes('importmap')) return 'three';
128
+ if (html.includes('paper-full.js')) return 'svg';
129
+ if (html.includes('ml5')) return 'ml5-hand';
130
+ if (html.includes('p5.js')) return 'default';
131
+ // GLSL uses WebGL directly
132
+ if (html.includes('webgl') || html.includes('WebGL') || html.includes('gl.createShader')) return 'glsl';
133
+ // Check sketch.js for WebGL patterns
134
+ const sketch = fs.readFileSync('scripts/sketch.js', 'utf8');
135
+ if (sketch.includes('getContext(\\'webgl\\')') || sketch.includes('gl.createShader')) return 'glsl';
136
+ return 'default';
137
+ }
138
+
139
+ function transformThreeJsSketch(sketch) {
140
+ // Remove ES6 import statement
141
+ let transformed = sketch.replace(/import\\s+\\*\\s+as\\s+THREE\\s+from\\s+['\"]three['\"];?\\n?/g, '');
142
+ transformed = transformed.replace(/import\\s+\\{[^}]+\\}\\s+from\\s+['\"]three['\"];?\\n?/g, '');
143
+ return transformed;
144
+ }
145
+
146
+ function buildHtml(originalHtml, options) {
147
+ const { library, sketch, random, templateType } = options;
148
+
149
+ // Extract the style content
150
+ const styleMatch = originalHtml.match(/<style>([\\s\\S]*?)<\\/style>/);
151
+ const styles = styleMatch ? styleMatch[1] : '';
152
+
153
+ // Extract title
154
+ const titleMatch = originalHtml.match(/<title>([^<]*)<\\/title>/);
155
+ const title = titleMatch ? titleMatch[1] : 'Generative Sketch';
156
+
157
+ // Build based on template type
158
+ if (templateType === 'three') {
159
+ // Three.js needs special handling - wrap library as IIFE
160
+ const wrappedLibrary = wrapThreeJsAsGlobal(library);
161
+ return \`<!DOCTYPE html>
162
+ <html>
163
+ <head>
164
+ <meta charset="utf-8">
165
+ <title>\${title}</title>
166
+ <style>\${styles}</style>
167
+ </head>
168
+ <body>
169
+ <script>\${wrappedLibrary}</script>
170
+ <script>\${random}</script>
171
+ <script>\${sketch}</script>
172
+ </body>
173
+ </html>\`;
174
+ }
175
+
176
+ if (templateType === 'ml5-hand') {
177
+ // Keep CDN reference for ML5
178
+ return \`<!DOCTYPE html>
179
+ <html>
180
+ <head>
181
+ <meta charset="utf-8">
182
+ <title>\${title}</title>
183
+ <style>\${styles}</style>
184
+ </head>
185
+ <body>
186
+ <div id="container">
187
+ <video id="video" autoplay playsinline></video>
188
+ <canvas id="canvas"></canvas>
189
+ <div id="status">Initializing...</div>
190
+ </div>
191
+ <script src="https://unpkg.com/ml5@1/dist/ml5.min.js"></script>
192
+ <script>\${random}</script>
193
+ <script>\${sketch}</script>
194
+ </body>
195
+ </html>\`;
196
+ }
197
+
198
+ if (templateType === 'svg') {
199
+ // Paper.js template
200
+ return \`<!DOCTYPE html>
201
+ <html>
202
+ <head>
203
+ <meta charset="utf-8">
204
+ <title>\${title}</title>
205
+ <style>\${styles}</style>
206
+ </head>
207
+ <body>
208
+ <canvas id="canvas" resize></canvas>
209
+ <script>\${library}</script>
210
+ <script>\${random}</script>
211
+ <script>\${sketch}</script>
212
+ </body>
213
+ </html>\`;
214
+ }
215
+
216
+ if (templateType === 'glsl') {
217
+ // GLSL template - no library
218
+ return \`<!DOCTYPE html>
219
+ <html>
220
+ <head>
221
+ <meta charset="utf-8">
222
+ <title>\${title}</title>
223
+ <style>\${styles}</style>
224
+ </head>
225
+ <body>
226
+ <canvas id="canvas"></canvas>
227
+ <script>\${random}</script>
228
+ <script>\${sketch}</script>
229
+ </body>
230
+ </html>\`;
231
+ }
232
+
233
+ // Default p5.js template
234
+ return \`<!DOCTYPE html>
235
+ <html>
236
+ <head>
237
+ <meta charset="utf-8">
238
+ <title>\${title}</title>
239
+ <style>\${styles}</style>
240
+ </head>
241
+ <body>
242
+ <script>\${library}</script>
243
+ <script>\${random}</script>
244
+ <script>\${sketch}</script>
245
+ </body>
246
+ </html>\`;
247
+ }
248
+
249
+ function wrapThreeJsAsGlobal(threeCode) {
250
+ // Wrap Three.js module code to expose THREE as global
251
+ // The three.module.js exports everything, we capture it
252
+ return \`(function() {
253
+ var exports = {};
254
+ var module = { exports: exports };
255
+ (function(exports, module) {
256
+ \${threeCode}
257
+ })(exports, module);
258
+ window.THREE = exports.THREE || exports;
259
+ // Also expose common exports directly
260
+ if (exports.Scene) window.THREE = exports;
261
+ })();\`;
262
+ }
263
+
264
+ build().catch(err => {
265
+ console.error('Build failed:', err);
266
+ process.exit(1);
267
+ });
268
+ `;
@@ -76,5 +76,6 @@ class Random {
76
76
  }
77
77
  }
78
78
 
79
- `
80
- }
79
+ `,
80
+ colorContent: `let a = ['CDCDCD', 'FF0000', 'FFA500', 'FFFF00', '008000', '0000FF', '4B0082', 'EE82EE'];`
81
+ }