selkie 1.1.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
@@ -20,6 +20,7 @@ selkie -t svg # Paper.js vector/SVG project
20
20
  selkie -t three # Three.js 3D project
21
21
  selkie -t glsl # WebGL shader project
22
22
  selkie -t ml5-hand # ML5.js hand tracking project
23
+ selkie -t pressure # p5.js pressure drawing (iPad + Apple Pencil)
23
24
  ```
24
25
 
25
26
  You'll be prompted for a folder name, then the project will be created with all dependencies installed.
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, svgTemplate, threeTemplate, glslTemplate, ml5HandTrackingTemplate, buildScriptContent } from './templates/templates.js';
10
- import { packageJsonContent, svgPackageJsonContent, threePackageJsonContent, glslPackageJsonContent, ml5HandTrackingPackageJsonContent } 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
 
@@ -73,8 +76,13 @@ async function createProject() {
73
76
  sketchContent = ml5HandTrackingTemplate.sketchContent;
74
77
  randomContent = ml5HandTrackingTemplate.randomContent;
75
78
  selectedPackageJson = ml5HandTrackingPackageJsonContent;
79
+ } else if (templateType === 'pressure') {
80
+ htmlContent = pressureTemplate.htmlContent;
81
+ sketchContent = pressureTemplate.sketchContent;
82
+ randomContent = pressureTemplate.randomContent;
83
+ selectedPackageJson = pressurePackageJsonContent;
76
84
  } else {
77
- throw new Error('Unknown template type. Available templates: default, svg, three, glsl, ml5-hand');
85
+ throw new Error('Unknown template type. Available templates: default, svg, three, glsl, ml5-hand, pressure');
78
86
  }
79
87
 
80
88
  // Create index.html
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "selkie",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
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",
@@ -40,8 +40,5 @@
40
40
  "p5": "^1.9.4",
41
41
  "paper": "^0.12.18",
42
42
  "three": "^0.170.0"
43
- },
44
- "devDependencies": {
45
- "live-server": "^1.2.2"
46
43
  }
47
44
  }
@@ -20,6 +20,27 @@ export const packageJsonContent = `{
20
20
  "license": "ISC"
21
21
  }`;
22
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
+
23
44
  export const svgPackageJsonContent = `{
24
45
  "name": "project-name",
25
46
  "version": "1.0.0",
@@ -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
+ `;
@@ -0,0 +1,81 @@
1
+ export const template = {
2
+ htmlContent:
3
+ `<!DOCTYPE html>
4
+ <html>
5
+ <head>
6
+ <title>p5.js Sketch</title>
7
+ <script src="scripts/p5.js"></script>
8
+ <script src="scripts/sketch.js"></script>
9
+ <script src="scripts/random.js"></script>
10
+ </head>
11
+ <body>
12
+ </body>
13
+ </html>`,
14
+ sketchContent: `function setup() {
15
+ createCanvas(400, 400);
16
+ }
17
+
18
+ function draw() {
19
+ background(220);
20
+ }`,
21
+ randomContent: `
22
+ // RANDOM Class ////////////////////////////////////////////////////////////
23
+ class Random {
24
+ constructor () {
25
+ this.useA = false;
26
+ let sfc32 = function (uint128Hex) {
27
+ let a = parseInt (uint128Hex.substr (0, 8), 16);
28
+ let b = parseInt (uint128Hex.substr (8, 8), 16);
29
+ let c = parseInt (uint128Hex.substr (16, 8), 16);
30
+ let d = parseInt (uint128Hex.substr (24, 8), 16);
31
+ return function () {
32
+ a |= 0;
33
+ b |= 0;
34
+ c |= 0;
35
+ d |= 0;
36
+ let t = (((a + b) | 0) + d) | 0;
37
+ d = (d + 1) | 0;
38
+ a = b ^ (b >>> 9);
39
+ b = (c + (c << 3)) | 0;
40
+ c = (c << 21) | (c >>> 11);
41
+ c = (c + t) | 0;
42
+ return (t >>> 0) / 4294967296;
43
+ };
44
+ };
45
+ // seed prngA with first half of tokenData.hash
46
+ this.prngA = new sfc32 (tokenData.hash.substr (2, 32));
47
+ // seed prngB with second half of tokenData.hash
48
+ this.prngB = new sfc32 (tokenData.hash.substr (34, 32));
49
+ for (let i = 0; i < 1e6; i += 2) {
50
+ this.prngA ();
51
+ this.prngB ();
52
+ }
53
+ }
54
+ // random number between 0 (inclusive) and 1 (exclusive)
55
+ random_dec () {
56
+ this.useA = !this.useA;
57
+ return this.useA ? this.prngA () : this.prngB ();
58
+ }
59
+ // random number between a (inclusive) and b (exclusive)
60
+ random_num (a, b) {
61
+ return a + (b - a) * this.random_dec ();
62
+ }
63
+ // random integer between a (inclusive) and b (inclusive)
64
+ // requires a < b for proper probability distribution
65
+ random_int (a, b) {
66
+ return Math.floor (this.random_num (a, b + 1));
67
+ }
68
+
69
+ // random boolean with p as percent liklihood of true
70
+ random_bool (p) {
71
+ return this.random_dec () < p;
72
+ }
73
+ // random value in an array of items
74
+ random_choice (list) {
75
+ return list[this.random_int (0, list.length - 1)];
76
+ }
77
+ }
78
+
79
+ `,
80
+ colorContent: `let a = ['CDCDCD', 'FF0000', 'FFA500', 'FFFF00', '008000', '0000FF', '4B0082', 'EE82EE'];`
81
+ }
@@ -0,0 +1,251 @@
1
+ export const glslTemplate = {
2
+ htmlContent: `<!DOCTYPE html>
3
+ <html>
4
+ <head>
5
+ <title>GLSL Shader Sketch</title>
6
+ <style>
7
+ body {
8
+ margin: 0;
9
+ padding: 0;
10
+ overflow: hidden;
11
+ background: #000;
12
+ display: flex;
13
+ justify-content: center;
14
+ align-items: center;
15
+ min-height: 100vh;
16
+ }
17
+ canvas {
18
+ display: block;
19
+ max-width: 100%;
20
+ max-height: 100%;
21
+ }
22
+ </style>
23
+ </head>
24
+ <body>
25
+ <canvas id="canvas"></canvas>
26
+ <script src="scripts/random.js"></script>
27
+ <script src="scripts/sketch.js"></script>
28
+ </body>
29
+ </html>`,
30
+ sketchContent: `// WebGL setup
31
+ const canvas = document.getElementById('canvas');
32
+ const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
33
+
34
+ if (!gl) {
35
+ alert('WebGL not supported');
36
+ }
37
+
38
+ // Set canvas size
39
+ canvas.width = window.innerWidth;
40
+ canvas.height = window.innerHeight;
41
+ gl.viewport(0, 0, canvas.width, canvas.height);
42
+
43
+ // Vertex shader source
44
+ const vertexShaderSource = \`
45
+ attribute vec2 a_position;
46
+ varying vec2 v_uv;
47
+
48
+ void main() {
49
+ gl_Position = vec4(a_position, 0.0, 1.0);
50
+ v_uv = a_position * 0.5 + 0.5;
51
+ }
52
+ \`;
53
+
54
+ // Fragment shader source
55
+ const fragmentShaderSource = \`
56
+ precision mediump float;
57
+ uniform float u_time;
58
+ uniform vec2 u_resolution;
59
+ varying vec2 v_uv;
60
+
61
+ void main() {
62
+ vec2 uv = v_uv;
63
+
64
+ // Example: Animated gradient
65
+ vec3 color = vec3(
66
+ sin(uv.x * 3.14 + u_time) * 0.5 + 0.5,
67
+ sin(uv.y * 3.14 + u_time * 0.7) * 0.5 + 0.5,
68
+ sin((uv.x + uv.y) * 2.0 + u_time * 1.3) * 0.5 + 0.5
69
+ );
70
+
71
+ gl_FragColor = vec4(color, 1.0);
72
+ }
73
+ \`;
74
+
75
+ // Compile shader
76
+ function compileShader(source, type) {
77
+ const shader = gl.createShader(type);
78
+ gl.shaderSource(shader, source);
79
+ gl.compileShader(shader);
80
+
81
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
82
+ console.error('Shader compilation error:', gl.getShaderInfoLog(shader));
83
+ gl.deleteShader(shader);
84
+ return null;
85
+ }
86
+
87
+ return shader;
88
+ }
89
+
90
+ // Create shader program
91
+ function createProgram(vertexSource, fragmentSource) {
92
+ const vertexShader = compileShader(vertexSource, gl.VERTEX_SHADER);
93
+ const fragmentShader = compileShader(fragmentSource, gl.FRAGMENT_SHADER);
94
+
95
+ if (!vertexShader || !fragmentShader) {
96
+ return null;
97
+ }
98
+
99
+ const program = gl.createProgram();
100
+ gl.attachShader(program, vertexShader);
101
+ gl.attachShader(program, fragmentShader);
102
+ gl.linkProgram(program);
103
+
104
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
105
+ console.error('Program linking error:', gl.getProgramInfoLog(program));
106
+ gl.deleteProgram(program);
107
+ return null;
108
+ }
109
+
110
+ return program;
111
+ }
112
+
113
+ // Create full-screen quad
114
+ function createQuad() {
115
+ const positions = new Float32Array([
116
+ -1, -1,
117
+ 1, -1,
118
+ -1, 1,
119
+ -1, 1,
120
+ 1, -1,
121
+ 1, 1,
122
+ ]);
123
+
124
+ const buffer = gl.createBuffer();
125
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
126
+ gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
127
+ return buffer;
128
+ }
129
+
130
+ // Initialize
131
+ const program = createProgram(vertexShaderSource, fragmentShaderSource);
132
+ const quadBuffer = createQuad();
133
+
134
+ if (!program) {
135
+ alert('Failed to create shader program');
136
+ }
137
+
138
+ // Get attribute and uniform locations
139
+ const positionLocation = gl.getAttribLocation(program, 'a_position');
140
+ const timeLocation = gl.getUniformLocation(program, 'u_time');
141
+ const resolutionLocation = gl.getUniformLocation(program, 'u_resolution');
142
+
143
+ // Animation loop
144
+ let startTime = Date.now();
145
+
146
+ function animate() {
147
+ requestAnimationFrame(animate);
148
+
149
+ const currentTime = (Date.now() - startTime) / 1000.0;
150
+
151
+ // Clear
152
+ gl.clearColor(0, 0, 0, 1);
153
+ gl.clear(gl.COLOR_BUFFER_BIT);
154
+
155
+ // Use program
156
+ gl.useProgram(program);
157
+
158
+ // Set uniforms
159
+ gl.uniform1f(timeLocation, currentTime);
160
+ gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
161
+
162
+ // Set up quad
163
+ gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
164
+ gl.enableVertexAttribArray(positionLocation);
165
+ gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
166
+
167
+ // Draw
168
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
169
+ }
170
+
171
+ // Handle window resize
172
+ window.addEventListener('resize', () => {
173
+ canvas.width = window.innerWidth;
174
+ canvas.height = window.innerHeight;
175
+ gl.viewport(0, 0, canvas.width, canvas.height);
176
+ });
177
+
178
+ // Start animation
179
+ animate();
180
+
181
+ // Export PNG function - call this from console: exportPNG()
182
+ window.exportPNG = function(filename) {
183
+ filename = filename || 'sketch.png';
184
+ const link = document.createElement('a');
185
+ link.href = canvas.toDataURL('image/png');
186
+ link.download = filename;
187
+ link.click();
188
+ console.log('PNG exported as ' + filename);
189
+ };
190
+
191
+ // Keyboard shortcut: Press 's' to save PNG
192
+ document.addEventListener('keydown', function(e) {
193
+ if (e.key === 's' && !e.ctrlKey && !e.metaKey) {
194
+ window.exportPNG();
195
+ }
196
+ });`,
197
+ randomContent: `// RANDOM Class ////////////////////////////////////////////////////////////
198
+ class Random {
199
+ constructor(seed) {
200
+ this.seed = seed || Math.random().toString(16).substr(2, 32).padEnd(32, '0') +
201
+ Math.random().toString(16).substr(2, 32).padEnd(32, '0');
202
+ this.useA = false;
203
+ let sfc32 = function(uint128Hex) {
204
+ let a = parseInt(uint128Hex.substr(0, 8), 16);
205
+ let b = parseInt(uint128Hex.substr(8, 8), 16);
206
+ let c = parseInt(uint128Hex.substr(16, 8), 16);
207
+ let d = parseInt(uint128Hex.substr(24, 8), 16);
208
+ return function() {
209
+ a |= 0; b |= 0; c |= 0; d |= 0;
210
+ let t = (((a + b) | 0) + d) | 0;
211
+ d = (d + 1) | 0;
212
+ a = b ^ (b >>> 9);
213
+ b = (c + (c << 3)) | 0;
214
+ c = (c << 21) | (c >>> 11);
215
+ c = (c + t) | 0;
216
+ return (t >>> 0) / 4294967296;
217
+ };
218
+ };
219
+ this.prngA = new sfc32(this.seed.substr(0, 32));
220
+ this.prngB = new sfc32(this.seed.substr(32, 32));
221
+ for (let i = 0; i < 1e6; i += 2) {
222
+ this.prngA();
223
+ this.prngB();
224
+ }
225
+ }
226
+
227
+ random_dec() {
228
+ this.useA = !this.useA;
229
+ return this.useA ? this.prngA() : this.prngB();
230
+ }
231
+
232
+ random_num(a, b) {
233
+ return a + (b - a) * this.random_dec();
234
+ }
235
+
236
+ random_int(a, b) {
237
+ return Math.floor(this.random_num(a, b + 1));
238
+ }
239
+
240
+ random_bool(p) {
241
+ return this.random_dec() < p;
242
+ }
243
+
244
+ random_choice(list) {
245
+ return list[this.random_int(0, list.length - 1)];
246
+ }
247
+ }
248
+
249
+ // Global random instance
250
+ var R = new Random();`
251
+ }