@hypertools/sdk 0.3.2 → 0.4.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.
@@ -0,0 +1,84 @@
1
+ # @hypertools/sdk Examples
2
+
3
+ Working examples demonstrating different SDK features and integrations.
4
+
5
+ ## Examples
6
+
7
+ | Example | Description |
8
+ |---------|-------------|
9
+ | [vanilla-canvas](./vanilla-canvas/) | Pure Canvas API with particle system |
10
+ | [p5js](./p5js/) | p5.js generative art integration |
11
+ | [threejs](./threejs/) | Three.js 3D scene with particles |
12
+ | [react](./react/) | React hooks integration |
13
+ | [recording](./recording/) | Video capture & timeline animation |
14
+
15
+ ## Running Examples
16
+
17
+ These examples use TypeScript and require a bundler. You can run them with:
18
+
19
+ ### Using Vite (recommended)
20
+
21
+ ```bash
22
+ # Install vite
23
+ npm install -g vite
24
+
25
+ # Run an example
26
+ cd examples/vanilla-canvas
27
+ vite
28
+ ```
29
+
30
+ ### Using esbuild + serve
31
+
32
+ ```bash
33
+ # Install dependencies
34
+ npm install -g esbuild serve
35
+
36
+ # Build and serve
37
+ cd examples/vanilla-canvas
38
+ esbuild main.ts --bundle --outfile=main.js --format=esm
39
+ serve .
40
+ ```
41
+
42
+ ### Using Bun
43
+
44
+ ```bash
45
+ cd examples/vanilla-canvas
46
+ bun run --bun vite
47
+ ```
48
+
49
+ ## Example Structure
50
+
51
+ Each example contains:
52
+
53
+ - `index.html` - HTML entry point
54
+ - `main.ts` or `main.tsx` - TypeScript source code
55
+
56
+ ## Dependencies
57
+
58
+ Some examples require peer dependencies:
59
+
60
+ - **p5js**: Requires p5.js (loaded via CDN in example)
61
+ - **threejs**: Requires `three` package
62
+ - **react**: Requires `react` and `react-dom`
63
+
64
+ ## Creating Your Own
65
+
66
+ Use these examples as templates for your own experiences:
67
+
68
+ ```typescript
69
+ import { Experience } from '@hypertools/sdk';
70
+
71
+ new Experience({
72
+ mount: document.getElementById('container')!,
73
+ paramDefs: {
74
+ // Define your parameters
75
+ },
76
+ setup(context) {
77
+ // Your creative code here
78
+
79
+ return () => {
80
+ // Cleanup
81
+ };
82
+ },
83
+ });
84
+ ```
@@ -0,0 +1,39 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>HyperTool SDK - p5.js Example</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body {
10
+ font-family: system-ui, sans-serif;
11
+ background: #0f0f1a;
12
+ color: #fff;
13
+ min-height: 100vh;
14
+ display: flex;
15
+ flex-direction: column;
16
+ align-items: center;
17
+ padding: 2rem;
18
+ }
19
+ h1 { margin-bottom: 1rem; }
20
+ #sketch-container {
21
+ border-radius: 8px;
22
+ overflow: hidden;
23
+ }
24
+ .info {
25
+ margin-top: 1rem;
26
+ font-size: 0.9rem;
27
+ color: #888;
28
+ }
29
+ </style>
30
+ </head>
31
+ <body>
32
+ <h1>p5.js Generative Art</h1>
33
+ <div id="sketch-container"></div>
34
+ <p class="info">Move your mouse to influence the pattern. Parameters are reactive!</p>
35
+
36
+ <script src="https://cdn.jsdelivr.net/npm/p5@1.9.0/lib/p5.min.js"></script>
37
+ <script type="module" src="./main.ts"></script>
38
+ </body>
39
+ </html>
@@ -0,0 +1,136 @@
1
+ /**
2
+ * p5.js Example
3
+ *
4
+ * Demonstrates @hypertools/sdk integration with p5.js.
5
+ * Creates a generative art piece with reactive parameters.
6
+ */
7
+
8
+ import { Experience } from '@hypertools/sdk';
9
+ import type p5 from 'p5';
10
+
11
+ // Declare p5 as global (loaded via CDN)
12
+ declare const p5: typeof import('p5');
13
+
14
+ new Experience({
15
+ mount: document.getElementById('sketch-container')!,
16
+ paramDefs: {
17
+ backgroundColor: {
18
+ type: 'color',
19
+ value: '#0f0f1a',
20
+ label: 'Background',
21
+ },
22
+ primaryColor: {
23
+ type: 'color',
24
+ value: '#ff6b6b',
25
+ label: 'Primary Color',
26
+ },
27
+ secondaryColor: {
28
+ type: 'color',
29
+ value: '#4ecdc4',
30
+ label: 'Secondary Color',
31
+ },
32
+ shapeCount: {
33
+ type: 'number',
34
+ value: 50,
35
+ min: 10,
36
+ max: 200,
37
+ step: 10,
38
+ label: 'Shape Count',
39
+ },
40
+ noiseScale: {
41
+ type: 'number',
42
+ value: 0.01,
43
+ min: 0.001,
44
+ max: 0.05,
45
+ step: 0.001,
46
+ label: 'Noise Scale',
47
+ },
48
+ rotationSpeed: {
49
+ type: 'number',
50
+ value: 0.5,
51
+ min: 0,
52
+ max: 2,
53
+ step: 0.1,
54
+ label: 'Rotation Speed',
55
+ },
56
+ animate: {
57
+ type: 'boolean',
58
+ value: true,
59
+ label: 'Animate',
60
+ },
61
+ },
62
+ setup(context) {
63
+ let sketch: p5;
64
+ let time = 0;
65
+
66
+ new p5((p: p5) => {
67
+ sketch = p;
68
+
69
+ p.setup = () => {
70
+ p.createCanvas(800, 600);
71
+ p.colorMode(p.HSB, 360, 100, 100, 100);
72
+ p.noStroke();
73
+ };
74
+
75
+ p.draw = () => {
76
+ if (!context.params.animate) return;
77
+
78
+ // Background with slight transparency for trails
79
+ p.background(context.params.backgroundColor as string);
80
+
81
+ const count = context.params.shapeCount as number;
82
+ const noiseScale = context.params.noiseScale as number;
83
+ const rotSpeed = context.params.rotationSpeed as number;
84
+
85
+ p.push();
86
+ p.translate(p.width / 2, p.height / 2);
87
+
88
+ // Mouse influence
89
+ const mx = p.map(p.mouseX, 0, p.width, -1, 1);
90
+ const my = p.map(p.mouseY, 0, p.height, -1, 1);
91
+
92
+ for (let i = 0; i < count; i++) {
93
+ const angle = p.map(i, 0, count, 0, p.TWO_PI);
94
+ const noiseVal = p.noise(
95
+ p.cos(angle) * noiseScale * 100 + time,
96
+ p.sin(angle) * noiseScale * 100 + time,
97
+ time * 0.5
98
+ );
99
+
100
+ const radius = 100 + noiseVal * 150 + mx * 50;
101
+ const x = p.cos(angle + time * rotSpeed) * radius;
102
+ const y = p.sin(angle + time * rotSpeed) * radius;
103
+
104
+ // Alternate colors
105
+ const color = i % 2 === 0
106
+ ? context.params.primaryColor
107
+ : context.params.secondaryColor;
108
+
109
+ p.fill(color as string);
110
+
111
+ const size = 10 + noiseVal * 30 + my * 10;
112
+ p.ellipse(x, y, size, size);
113
+
114
+ // Inner ring
115
+ const innerRadius = radius * 0.6;
116
+ const ix = p.cos(angle - time * rotSpeed * 0.5) * innerRadius;
117
+ const iy = p.sin(angle - time * rotSpeed * 0.5) * innerRadius;
118
+
119
+ p.fill(color as string);
120
+ p.ellipse(ix, iy, size * 0.5, size * 0.5);
121
+ }
122
+
123
+ p.pop();
124
+
125
+ time += 0.01;
126
+ };
127
+ }, context.mount);
128
+
129
+ // Register the p5 instance for external access
130
+ context.registerObject('p5Instance', sketch!, { type: 'p5' });
131
+
132
+ return () => {
133
+ sketch?.remove();
134
+ };
135
+ },
136
+ });
@@ -0,0 +1,22 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>HyperTool SDK - React Example</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body {
10
+ font-family: system-ui, sans-serif;
11
+ background: #1a1a2e;
12
+ color: #fff;
13
+ min-height: 100vh;
14
+ }
15
+ #root { padding: 2rem; }
16
+ </style>
17
+ </head>
18
+ <body>
19
+ <div id="root"></div>
20
+ <script type="module" src="./main.tsx"></script>
21
+ </body>
22
+ </html>
@@ -0,0 +1,310 @@
1
+ /**
2
+ * React Example
3
+ *
4
+ * Demonstrates @hypertools/sdk React integration with useExperience hook.
5
+ * Creates a visualization with React-controlled parameters.
6
+ */
7
+
8
+ import React, { useState } from 'react';
9
+ import { createRoot } from 'react-dom/client';
10
+ import { useExperience, ExperienceView } from '@hypertools/sdk/react';
11
+
12
+ // Styles
13
+ const styles = {
14
+ container: {
15
+ maxWidth: '900px',
16
+ margin: '0 auto',
17
+ },
18
+ title: {
19
+ marginBottom: '1rem',
20
+ textAlign: 'center' as const,
21
+ },
22
+ experienceContainer: {
23
+ borderRadius: '8px',
24
+ overflow: 'hidden',
25
+ marginBottom: '1.5rem',
26
+ },
27
+ controls: {
28
+ display: 'grid',
29
+ gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
30
+ gap: '1rem',
31
+ padding: '1rem',
32
+ background: '#2a2a4a',
33
+ borderRadius: '8px',
34
+ },
35
+ controlGroup: {
36
+ display: 'flex',
37
+ flexDirection: 'column' as const,
38
+ gap: '0.5rem',
39
+ },
40
+ label: {
41
+ fontSize: '0.9rem',
42
+ color: '#aaa',
43
+ },
44
+ input: {
45
+ padding: '0.5rem',
46
+ borderRadius: '4px',
47
+ border: '1px solid #4a4a6a',
48
+ background: '#1a1a2e',
49
+ color: '#fff',
50
+ },
51
+ button: {
52
+ padding: '0.75rem 1.5rem',
53
+ borderRadius: '4px',
54
+ border: 'none',
55
+ background: '#4a4a6a',
56
+ color: '#fff',
57
+ cursor: 'pointer',
58
+ fontSize: '1rem',
59
+ },
60
+ buttonPrimary: {
61
+ background: '#00ff88',
62
+ color: '#000',
63
+ },
64
+ status: {
65
+ marginTop: '1rem',
66
+ padding: '1rem',
67
+ background: '#2a2a4a',
68
+ borderRadius: '8px',
69
+ fontFamily: 'monospace',
70
+ fontSize: '0.85rem',
71
+ },
72
+ };
73
+
74
+ function WaveVisualization() {
75
+ const {
76
+ experience,
77
+ isReady,
78
+ isPlaying,
79
+ params,
80
+ setParam,
81
+ play,
82
+ pause,
83
+ toggle,
84
+ } = useExperience({
85
+ paramDefs: {
86
+ waveColor: {
87
+ type: 'color',
88
+ value: '#00ff88',
89
+ label: 'Wave Color',
90
+ },
91
+ backgroundColor: {
92
+ type: 'color',
93
+ value: '#1a1a2e',
94
+ label: 'Background',
95
+ },
96
+ amplitude: {
97
+ type: 'number',
98
+ value: 50,
99
+ min: 10,
100
+ max: 150,
101
+ label: 'Amplitude',
102
+ },
103
+ frequency: {
104
+ type: 'number',
105
+ value: 0.02,
106
+ min: 0.005,
107
+ max: 0.05,
108
+ step: 0.005,
109
+ label: 'Frequency',
110
+ },
111
+ waveCount: {
112
+ type: 'number',
113
+ value: 5,
114
+ min: 1,
115
+ max: 10,
116
+ label: 'Wave Count',
117
+ },
118
+ speed: {
119
+ type: 'number',
120
+ value: 0.05,
121
+ min: 0.01,
122
+ max: 0.2,
123
+ step: 0.01,
124
+ label: 'Speed',
125
+ },
126
+ },
127
+ setup(context) {
128
+ const canvas = document.createElement('canvas');
129
+ const ctx = canvas.getContext('2d')!;
130
+ canvas.width = 800;
131
+ canvas.height = 400;
132
+ context.mount.appendChild(canvas);
133
+
134
+ let time = 0;
135
+
136
+ context.experience.on('frame', () => {
137
+ const {
138
+ waveColor,
139
+ backgroundColor,
140
+ amplitude,
141
+ frequency,
142
+ waveCount,
143
+ speed,
144
+ } = context.params as Record<string, unknown>;
145
+
146
+ // Clear
147
+ ctx.fillStyle = backgroundColor as string;
148
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
149
+
150
+ // Draw waves
151
+ for (let w = 0; w < (waveCount as number); w++) {
152
+ const offset = w * 0.5;
153
+ const alpha = 1 - w / (waveCount as number) * 0.5;
154
+
155
+ ctx.beginPath();
156
+ ctx.strokeStyle = (waveColor as string) + Math.floor(alpha * 255).toString(16).padStart(2, '0');
157
+ ctx.lineWidth = 3;
158
+
159
+ for (let x = 0; x < canvas.width; x++) {
160
+ const y =
161
+ canvas.height / 2 +
162
+ Math.sin((x * (frequency as number) + time + offset) * Math.PI * 2) *
163
+ (amplitude as number) *
164
+ (1 - w / (waveCount as number) * 0.3);
165
+
166
+ if (x === 0) {
167
+ ctx.moveTo(x, y);
168
+ } else {
169
+ ctx.lineTo(x, y);
170
+ }
171
+ }
172
+
173
+ ctx.stroke();
174
+ }
175
+
176
+ time += speed as number;
177
+ });
178
+
179
+ return () => canvas.remove();
180
+ },
181
+ });
182
+
183
+ const [captureStatus, setCaptureStatus] = useState<string>('');
184
+
185
+ const handleCapture = async () => {
186
+ if (!experience) return;
187
+ setCaptureStatus('Capturing...');
188
+ const blob = await experience.captureImage('png');
189
+ if (blob) {
190
+ const url = URL.createObjectURL(blob);
191
+ const a = document.createElement('a');
192
+ a.href = url;
193
+ a.download = 'wave-capture.png';
194
+ a.click();
195
+ URL.revokeObjectURL(url);
196
+ setCaptureStatus('Captured!');
197
+ } else {
198
+ setCaptureStatus('Capture failed');
199
+ }
200
+ setTimeout(() => setCaptureStatus(''), 2000);
201
+ };
202
+
203
+ return (
204
+ <div style={styles.container}>
205
+ <h1 style={styles.title}>React Integration Example</h1>
206
+
207
+ <div style={styles.experienceContainer}>
208
+ <ExperienceView
209
+ experience={experience}
210
+ style={{ width: '100%', height: '400px' }}
211
+ />
212
+ </div>
213
+
214
+ <div style={styles.controls}>
215
+ <div style={styles.controlGroup}>
216
+ <label style={styles.label}>Wave Color</label>
217
+ <input
218
+ type="color"
219
+ value={params.waveColor as string}
220
+ onChange={(e) => setParam('waveColor', e.target.value)}
221
+ style={styles.input}
222
+ />
223
+ </div>
224
+
225
+ <div style={styles.controlGroup}>
226
+ <label style={styles.label}>Background</label>
227
+ <input
228
+ type="color"
229
+ value={params.backgroundColor as string}
230
+ onChange={(e) => setParam('backgroundColor', e.target.value)}
231
+ style={styles.input}
232
+ />
233
+ </div>
234
+
235
+ <div style={styles.controlGroup}>
236
+ <label style={styles.label}>Amplitude: {params.amplitude}</label>
237
+ <input
238
+ type="range"
239
+ min={10}
240
+ max={150}
241
+ value={params.amplitude as number}
242
+ onChange={(e) => setParam('amplitude', Number(e.target.value))}
243
+ style={styles.input}
244
+ />
245
+ </div>
246
+
247
+ <div style={styles.controlGroup}>
248
+ <label style={styles.label}>Frequency: {(params.frequency as number)?.toFixed(3)}</label>
249
+ <input
250
+ type="range"
251
+ min={0.005}
252
+ max={0.05}
253
+ step={0.005}
254
+ value={params.frequency as number}
255
+ onChange={(e) => setParam('frequency', Number(e.target.value))}
256
+ style={styles.input}
257
+ />
258
+ </div>
259
+
260
+ <div style={styles.controlGroup}>
261
+ <label style={styles.label}>Wave Count: {params.waveCount}</label>
262
+ <input
263
+ type="range"
264
+ min={1}
265
+ max={10}
266
+ value={params.waveCount as number}
267
+ onChange={(e) => setParam('waveCount', Number(e.target.value))}
268
+ style={styles.input}
269
+ />
270
+ </div>
271
+
272
+ <div style={styles.controlGroup}>
273
+ <label style={styles.label}>Speed: {(params.speed as number)?.toFixed(2)}</label>
274
+ <input
275
+ type="range"
276
+ min={0.01}
277
+ max={0.2}
278
+ step={0.01}
279
+ value={params.speed as number}
280
+ onChange={(e) => setParam('speed', Number(e.target.value))}
281
+ style={styles.input}
282
+ />
283
+ </div>
284
+ </div>
285
+
286
+ <div style={{ display: 'flex', gap: '1rem', marginTop: '1rem' }}>
287
+ <button
288
+ style={{ ...styles.button, ...(isPlaying ? {} : styles.buttonPrimary) }}
289
+ onClick={toggle}
290
+ >
291
+ {isPlaying ? 'Pause' : 'Play'}
292
+ </button>
293
+
294
+ <button style={styles.button} onClick={handleCapture}>
295
+ {captureStatus || 'Capture PNG'}
296
+ </button>
297
+ </div>
298
+
299
+ <div style={styles.status}>
300
+ <div>Ready: {isReady ? 'Yes' : 'No'}</div>
301
+ <div>Playing: {isPlaying ? 'Yes' : 'No'}</div>
302
+ <div>Frame: {experience?.currentFrame ?? 0}</div>
303
+ </div>
304
+ </div>
305
+ );
306
+ }
307
+
308
+ // Mount React app
309
+ const root = createRoot(document.getElementById('root')!);
310
+ root.render(<WaveVisualization />);
@@ -0,0 +1,64 @@
1
+ # React Landing Page Example
2
+
3
+ This example demonstrates how to use an exported HyperTools experience as an interactive background in a React application, and how to build custom features on top of it using the `@hypertools/sdk`.
4
+
5
+ ## What This Example Shows
6
+
7
+ ### Core SDK Usage
8
+ - Using `ExperienceController` to connect to an exported web component
9
+ - Setting parameters programmatically with `setParam()` and `setParams()`
10
+ - Dispatching synthetic events with `dispatchToCanvas()` for triggering interactions
11
+ - Listening to parameter changes with the event system
12
+
13
+ ### Custom Features Built on Top
14
+ This example showcases what developers can build beyond what the HyperTools platform provides:
15
+
16
+ 1. **Preset System** - Pre-configured settings with visual selection indicator
17
+ 2. **Click-to-Form Mode** - Click anywhere to trigger formation at that position
18
+ 3. **Auto-pilot Mode** - Automatically cycles through presets
19
+ 4. **Idle Screensaver** - Starts auto-pilot after 10 seconds of inactivity
20
+ 5. **Time-Based Theme** - Applies text based on time of day
21
+ 6. **Share Configuration URL** - Generate shareable URLs with encoded settings
22
+ 7. **Keyboard Shortcuts** - Full keyboard control for power users
23
+
24
+ ## Getting Started
25
+
26
+ 1. Install dependencies:
27
+ ```bash
28
+ bun install
29
+ ```
30
+
31
+ 2. Place your exported HyperTools experience in the `public/` folder (e.g., `boids-flocking-project.js`)
32
+
33
+ 3. Update `index.html` to load your experience:
34
+ ```html
35
+ <script src="/your-experience-name.js"></script>
36
+ ```
37
+
38
+ 4. Update `src/App.tsx` to use your experience's tag name:
39
+ ```tsx
40
+ <your-experience-name ref={experienceRef} />
41
+ ```
42
+
43
+ 5. Run the dev server:
44
+ ```bash
45
+ bun run dev
46
+ ```
47
+
48
+ ## Keyboard Shortcuts
49
+
50
+ | Key | Action |
51
+ |-----|--------|
52
+ | `Space` | Trigger formation |
53
+ | `↑` / `↓` | Adjust bird count |
54
+ | `R` | Toggle rainbow mode |
55
+ | `S` | Share config URL |
56
+ | `A` | Toggle auto-pilot |
57
+ | `1-4` | Apply presets |
58
+ | `?` | Show help |
59
+
60
+ ## Key Files
61
+
62
+ - `src/App.tsx` - Main React component with SDK integration and custom features
63
+ - `src/App.css` - Styling for the control panel and overlays
64
+ - `public/` - Place your exported experience here
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Landing Page with Boids Background</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <!-- Load the exported experience web component -->
11
+ <script src="/boids-flocking-project.js"></script>
12
+ <script type="module" src="/src/main.tsx"></script>
13
+ </body>
14
+ </html>
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "react-landing-example",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc && vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "@hypertools/sdk": "latest",
13
+ "react": "^18.3.1",
14
+ "react-dom": "^18.3.1"
15
+ },
16
+ "devDependencies": {
17
+ "@types/react": "^18.3.18",
18
+ "@types/react-dom": "^18.3.5",
19
+ "@vitejs/plugin-react": "^4.3.4",
20
+ "typescript": "~5.6.2",
21
+ "vite": "^5.4.11"
22
+ }
23
+ }