@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.
- package/README.md +175 -0
- package/dist/core/ExperienceController.d.ts +39 -0
- package/dist/core/ExperienceController.d.ts.map +1 -1
- package/dist/core/index.js +2 -2
- package/dist/core/index.js.map +3 -3
- package/dist/index.js +39 -39
- package/dist/index.js.map +3 -3
- package/examples/README.md +84 -0
- package/examples/p5js/index.html +39 -0
- package/examples/p5js/main.ts +136 -0
- package/examples/react/index.html +22 -0
- package/examples/react/main.tsx +310 -0
- package/examples/react-landing/README.md +64 -0
- package/examples/react-landing/index.html +14 -0
- package/examples/react-landing/package.json +23 -0
- package/examples/react-landing/public/boids-flocking-project.js +12 -0
- package/examples/react-landing/src/App.css +379 -0
- package/examples/react-landing/src/App.tsx +483 -0
- package/examples/react-landing/src/main.tsx +9 -0
- package/examples/react-landing/src/types.d.ts +24 -0
- package/examples/react-landing/tsconfig.json +20 -0
- package/examples/react-landing/vite.config.ts +9 -0
- package/examples/recording/index.html +113 -0
- package/examples/recording/main.ts +256 -0
- package/examples/threejs/index.html +40 -0
- package/examples/threejs/main.ts +196 -0
- package/examples/vanilla-canvas/index.html +77 -0
- package/examples/vanilla-canvas/main.ts +162 -0
- package/package.json +5 -1
|
@@ -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
|
+
}
|