@hypertools/sdk 0.3.3 → 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,256 @@
1
+ /**
2
+ * Recording Example
3
+ *
4
+ * Demonstrates @hypertools/sdk recording capabilities:
5
+ * - Video recording with MediaRecorder
6
+ * - Image capture
7
+ * - Timeline-based keyframe animation
8
+ */
9
+
10
+ import { Experience } from '@hypertools/sdk';
11
+ import { VideoRecorder, ImageCapture, Timeline } from '@hypertools/sdk/recording';
12
+
13
+ // UI elements
14
+ const statusText = document.getElementById('status-text')!;
15
+ const frameCount = document.getElementById('frame-count')!;
16
+ const capturePngBtn = document.getElementById('capture-png') as HTMLButtonElement;
17
+ const startRecordingBtn = document.getElementById('start-recording') as HTMLButtonElement;
18
+ const stopRecordingBtn = document.getElementById('stop-recording') as HTMLButtonElement;
19
+ const playTimelineBtn = document.getElementById('play-timeline') as HTMLButtonElement;
20
+ const pauseTimelineBtn = document.getElementById('pause-timeline') as HTMLButtonElement;
21
+ const resetTimelineBtn = document.getElementById('reset-timeline') as HTMLButtonElement;
22
+ const seekSlider = document.getElementById('seek-timeline') as HTMLInputElement;
23
+
24
+ // Recording state
25
+ let recorder: ReturnType<typeof VideoRecorder.start> | null = null;
26
+ let canvas: HTMLCanvasElement | null = null;
27
+
28
+ // Create experience
29
+ const experience = new Experience({
30
+ mount: document.getElementById('canvas-container')!,
31
+ paramDefs: {
32
+ hue: { type: 'number', value: 0, min: 0, max: 360 },
33
+ saturation: { type: 'number', value: 80, min: 0, max: 100 },
34
+ scale: { type: 'number', value: 1, min: 0.5, max: 2 },
35
+ rotation: { type: 'number', value: 0, min: 0, max: 360 },
36
+ opacity: { type: 'number', value: 1, min: 0, max: 1 },
37
+ },
38
+ setup(context) {
39
+ canvas = document.createElement('canvas');
40
+ const ctx = canvas.getContext('2d')!;
41
+ canvas.width = 800;
42
+ canvas.height = 600;
43
+ context.mount.appendChild(canvas);
44
+
45
+ let time = 0;
46
+
47
+ function draw() {
48
+ const { hue, saturation, scale, rotation, opacity } = context.params as Record<string, number>;
49
+
50
+ // Clear
51
+ ctx.fillStyle = '#000';
52
+ ctx.fillRect(0, 0, canvas!.width, canvas!.height);
53
+
54
+ // Apply global opacity
55
+ ctx.globalAlpha = opacity;
56
+
57
+ // Center and transform
58
+ ctx.save();
59
+ ctx.translate(canvas!.width / 2, canvas!.height / 2);
60
+ ctx.rotate((rotation * Math.PI) / 180);
61
+ ctx.scale(scale, scale);
62
+
63
+ // Draw animated shapes
64
+ const color = `hsl(${hue}, ${saturation}%, 60%)`;
65
+
66
+ // Central shape
67
+ ctx.fillStyle = color;
68
+ ctx.beginPath();
69
+ const size = 100 + Math.sin(time * 2) * 30;
70
+ ctx.arc(0, 0, size, 0, Math.PI * 2);
71
+ ctx.fill();
72
+
73
+ // Orbiting shapes
74
+ const orbitCount = 6;
75
+ for (let i = 0; i < orbitCount; i++) {
76
+ const angle = (i / orbitCount) * Math.PI * 2 + time;
77
+ const orbitRadius = 180;
78
+ const x = Math.cos(angle) * orbitRadius;
79
+ const y = Math.sin(angle) * orbitRadius;
80
+
81
+ ctx.fillStyle = `hsl(${(hue + i * 30) % 360}, ${saturation}%, 50%)`;
82
+ ctx.beginPath();
83
+ ctx.arc(x, y, 30 + Math.sin(time * 3 + i) * 10, 0, Math.PI * 2);
84
+ ctx.fill();
85
+ }
86
+
87
+ ctx.restore();
88
+ ctx.globalAlpha = 1;
89
+
90
+ time += 0.02;
91
+ }
92
+
93
+ context.experience.on('frame', () => {
94
+ draw();
95
+ frameCount.textContent = `Frame: ${context.experience.currentFrame}`;
96
+ });
97
+
98
+ draw();
99
+
100
+ return () => canvas?.remove();
101
+ },
102
+ });
103
+
104
+ // Timeline setup
105
+ const timeline = new Timeline({
106
+ duration: 5000,
107
+ loop: true,
108
+ });
109
+
110
+ // Add keyframes for a complex animation sequence
111
+ timeline
112
+ // Start: warm colors, normal scale
113
+ .addKeyframe(0, {
114
+ hue: 0,
115
+ saturation: 80,
116
+ scale: 1,
117
+ rotation: 0,
118
+ opacity: 1,
119
+ })
120
+ // 1s: shift to cyan, scale up
121
+ .addKeyframe(1000, {
122
+ hue: 180,
123
+ saturation: 90,
124
+ scale: 1.3,
125
+ rotation: 45,
126
+ opacity: 1,
127
+ }, 'easeInOut')
128
+ // 2s: purple, rotate more
129
+ .addKeyframe(2000, {
130
+ hue: 280,
131
+ saturation: 70,
132
+ scale: 1,
133
+ rotation: 180,
134
+ opacity: 0.8,
135
+ }, 'easeInOut')
136
+ // 3s: green, scale down
137
+ .addKeyframe(3000, {
138
+ hue: 120,
139
+ saturation: 85,
140
+ scale: 0.7,
141
+ rotation: 270,
142
+ opacity: 1,
143
+ }, 'easeOutBack')
144
+ // 4s: back to red-ish
145
+ .addKeyframe(4000, {
146
+ hue: 30,
147
+ saturation: 90,
148
+ scale: 1.1,
149
+ rotation: 350,
150
+ opacity: 0.9,
151
+ }, 'easeInOut')
152
+ // 5s: return to start
153
+ .addKeyframe(5000, {
154
+ hue: 0,
155
+ saturation: 80,
156
+ scale: 1,
157
+ rotation: 360,
158
+ opacity: 1,
159
+ }, 'easeInOut');
160
+
161
+ // Timeline events
162
+ timeline.onUpdate((params) => {
163
+ experience.setParams(params);
164
+ });
165
+
166
+ timeline.onComplete(() => {
167
+ statusText.textContent = 'Timeline complete (looping)';
168
+ });
169
+
170
+ // Capture PNG
171
+ capturePngBtn.addEventListener('click', async () => {
172
+ if (!canvas) return;
173
+
174
+ statusText.textContent = 'Capturing...';
175
+ const blob = await ImageCapture.capture(canvas, 'png');
176
+
177
+ if (blob) {
178
+ const url = URL.createObjectURL(blob);
179
+ const a = document.createElement('a');
180
+ a.href = url;
181
+ a.download = `capture-${Date.now()}.png`;
182
+ a.click();
183
+ URL.revokeObjectURL(url);
184
+ statusText.textContent = 'PNG captured!';
185
+ } else {
186
+ statusText.textContent = 'Capture failed';
187
+ }
188
+ });
189
+
190
+ // Video recording
191
+ startRecordingBtn.addEventListener('click', () => {
192
+ if (!canvas) return;
193
+
194
+ recorder = VideoRecorder.start(canvas, {
195
+ frameRate: 60,
196
+ videoBitsPerSecond: 5000000,
197
+ });
198
+
199
+ startRecordingBtn.disabled = true;
200
+ startRecordingBtn.classList.add('recording');
201
+ stopRecordingBtn.disabled = false;
202
+ statusText.textContent = 'Recording...';
203
+ });
204
+
205
+ stopRecordingBtn.addEventListener('click', async () => {
206
+ if (!recorder) return;
207
+
208
+ statusText.textContent = 'Processing video...';
209
+ const blob = await recorder.stop();
210
+
211
+ if (blob) {
212
+ const url = URL.createObjectURL(blob);
213
+ const a = document.createElement('a');
214
+ a.href = url;
215
+ a.download = `recording-${Date.now()}.webm`;
216
+ a.click();
217
+ URL.revokeObjectURL(url);
218
+ statusText.textContent = 'Video saved!';
219
+ } else {
220
+ statusText.textContent = 'Recording failed';
221
+ }
222
+
223
+ recorder = null;
224
+ startRecordingBtn.disabled = false;
225
+ startRecordingBtn.classList.remove('recording');
226
+ stopRecordingBtn.disabled = true;
227
+ });
228
+
229
+ // Timeline controls
230
+ playTimelineBtn.addEventListener('click', () => {
231
+ timeline.play();
232
+ statusText.textContent = 'Timeline playing...';
233
+ });
234
+
235
+ pauseTimelineBtn.addEventListener('click', () => {
236
+ timeline.pause();
237
+ statusText.textContent = 'Timeline paused';
238
+ });
239
+
240
+ resetTimelineBtn.addEventListener('click', () => {
241
+ timeline.seek(0);
242
+ timeline.pause();
243
+ seekSlider.value = '0';
244
+ statusText.textContent = 'Timeline reset';
245
+ });
246
+
247
+ seekSlider.addEventListener('input', () => {
248
+ timeline.seek(Number(seekSlider.value));
249
+ });
250
+
251
+ // Update seek slider during playback
252
+ setInterval(() => {
253
+ if (timeline.isPlaying) {
254
+ seekSlider.value = String(timeline.currentTime);
255
+ }
256
+ }, 50);
@@ -0,0 +1,40 @@
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 - Three.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: #000;
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
+ #scene-container {
21
+ width: 800px;
22
+ height: 600px;
23
+ border-radius: 8px;
24
+ overflow: hidden;
25
+ }
26
+ .info {
27
+ margin-top: 1rem;
28
+ font-size: 0.9rem;
29
+ color: #888;
30
+ }
31
+ </style>
32
+ </head>
33
+ <body>
34
+ <h1>Three.js Animated Scene</h1>
35
+ <div id="scene-container"></div>
36
+ <p class="info">Reactive 3D scene with configurable parameters</p>
37
+
38
+ <script type="module" src="./main.ts"></script>
39
+ </body>
40
+ </html>
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Three.js Example
3
+ *
4
+ * Demonstrates @hypertools/sdk integration with Three.js.
5
+ * Creates an animated 3D scene with reactive parameters.
6
+ */
7
+
8
+ import { Experience } from '@hypertools/sdk';
9
+ import * as THREE from 'three';
10
+
11
+ new Experience({
12
+ mount: document.getElementById('scene-container')!,
13
+ paramDefs: {
14
+ cubeColor: {
15
+ type: 'color',
16
+ value: '#00ff88',
17
+ label: 'Cube Color',
18
+ },
19
+ torusColor: {
20
+ type: 'color',
21
+ value: '#ff6b6b',
22
+ label: 'Torus Color',
23
+ },
24
+ rotationSpeed: {
25
+ type: 'number',
26
+ value: 0.01,
27
+ min: 0,
28
+ max: 0.1,
29
+ step: 0.005,
30
+ label: 'Rotation Speed',
31
+ },
32
+ wireframe: {
33
+ type: 'boolean',
34
+ value: false,
35
+ label: 'Wireframe',
36
+ },
37
+ particleCount: {
38
+ type: 'number',
39
+ value: 1000,
40
+ min: 100,
41
+ max: 5000,
42
+ step: 100,
43
+ label: 'Particle Count',
44
+ },
45
+ cameraDistance: {
46
+ type: 'number',
47
+ value: 5,
48
+ min: 2,
49
+ max: 10,
50
+ step: 0.5,
51
+ label: 'Camera Distance',
52
+ },
53
+ },
54
+ setup(context) {
55
+ // Scene setup
56
+ const scene = new THREE.Scene();
57
+ const camera = new THREE.PerspectiveCamera(75, 800 / 600, 0.1, 1000);
58
+ const renderer = new THREE.WebGLRenderer({ antialias: true });
59
+
60
+ renderer.setSize(800, 600);
61
+ renderer.setPixelRatio(window.devicePixelRatio);
62
+ context.mount.appendChild(renderer.domElement);
63
+
64
+ // Lighting
65
+ const ambientLight = new THREE.AmbientLight(0x404040);
66
+ scene.add(ambientLight);
67
+
68
+ const pointLight = new THREE.PointLight(0xffffff, 1, 100);
69
+ pointLight.position.set(5, 5, 5);
70
+ scene.add(pointLight);
71
+
72
+ // Central cube
73
+ const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
74
+ const cubeMaterial = new THREE.MeshStandardMaterial({
75
+ color: context.params.cubeColor as string,
76
+ wireframe: context.params.wireframe as boolean,
77
+ });
78
+ const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
79
+ scene.add(cube);
80
+
81
+ // Orbiting torus
82
+ const torusGeometry = new THREE.TorusGeometry(0.5, 0.2, 16, 100);
83
+ const torusMaterial = new THREE.MeshStandardMaterial({
84
+ color: context.params.torusColor as string,
85
+ wireframe: context.params.wireframe as boolean,
86
+ });
87
+ const torus = new THREE.Mesh(torusGeometry, torusMaterial);
88
+ scene.add(torus);
89
+
90
+ // Particle system
91
+ let particles: THREE.Points;
92
+ let particleGeometry: THREE.BufferGeometry;
93
+
94
+ function createParticles(count: number) {
95
+ if (particles) {
96
+ scene.remove(particles);
97
+ particleGeometry.dispose();
98
+ }
99
+
100
+ particleGeometry = new THREE.BufferGeometry();
101
+ const positions = new Float32Array(count * 3);
102
+
103
+ for (let i = 0; i < count * 3; i += 3) {
104
+ positions[i] = (Math.random() - 0.5) * 10;
105
+ positions[i + 1] = (Math.random() - 0.5) * 10;
106
+ positions[i + 2] = (Math.random() - 0.5) * 10;
107
+ }
108
+
109
+ particleGeometry.setAttribute(
110
+ 'position',
111
+ new THREE.BufferAttribute(positions, 3)
112
+ );
113
+
114
+ const particleMaterial = new THREE.PointsMaterial({
115
+ color: 0xffffff,
116
+ size: 0.02,
117
+ transparent: true,
118
+ opacity: 0.8,
119
+ });
120
+
121
+ particles = new THREE.Points(particleGeometry, particleMaterial);
122
+ scene.add(particles);
123
+ }
124
+
125
+ createParticles(context.params.particleCount as number);
126
+
127
+ // Camera position
128
+ camera.position.z = context.params.cameraDistance as number;
129
+
130
+ // Register objects for external access
131
+ context.registerObject('cube', cube, { type: 'mesh', name: 'Central Cube' });
132
+ context.registerObject('torus', torus, { type: 'mesh', name: 'Orbiting Torus' });
133
+ context.registerObject('scene', scene, { type: 'scene' });
134
+ context.registerObject('camera', camera, { type: 'camera' });
135
+
136
+ // Animation
137
+ let time = 0;
138
+ let lastParticleCount = context.params.particleCount as number;
139
+
140
+ context.experience.on('frame', () => {
141
+ const speed = context.params.rotationSpeed as number;
142
+
143
+ // Update cube
144
+ cube.rotation.x += speed;
145
+ cube.rotation.y += speed * 0.7;
146
+ cubeMaterial.color.set(context.params.cubeColor as string);
147
+ cubeMaterial.wireframe = context.params.wireframe as boolean;
148
+
149
+ // Update torus orbit
150
+ time += speed;
151
+ torus.position.x = Math.cos(time) * 2;
152
+ torus.position.z = Math.sin(time) * 2;
153
+ torus.rotation.x += speed * 1.5;
154
+ torus.rotation.y += speed;
155
+ torusMaterial.color.set(context.params.torusColor as string);
156
+ torusMaterial.wireframe = context.params.wireframe as boolean;
157
+
158
+ // Update particles
159
+ const particleCount = context.params.particleCount as number;
160
+ if (particleCount !== lastParticleCount) {
161
+ createParticles(particleCount);
162
+ lastParticleCount = particleCount;
163
+ }
164
+
165
+ if (particles) {
166
+ particles.rotation.y += speed * 0.1;
167
+ }
168
+
169
+ // Update camera
170
+ camera.position.z = context.params.cameraDistance as number;
171
+
172
+ // Render
173
+ renderer.render(scene, camera);
174
+ });
175
+
176
+ // Initial render
177
+ renderer.render(scene, camera);
178
+
179
+ // Handle resize
180
+ context.environment.onResize((width, height) => {
181
+ camera.aspect = width / height;
182
+ camera.updateProjectionMatrix();
183
+ renderer.setSize(width, height);
184
+ });
185
+
186
+ // Cleanup
187
+ return () => {
188
+ renderer.dispose();
189
+ cubeGeometry.dispose();
190
+ cubeMaterial.dispose();
191
+ torusGeometry.dispose();
192
+ torusMaterial.dispose();
193
+ particleGeometry?.dispose();
194
+ };
195
+ },
196
+ });
@@ -0,0 +1,77 @@
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 - Vanilla Canvas 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
+ display: flex;
15
+ flex-direction: column;
16
+ align-items: center;
17
+ padding: 2rem;
18
+ }
19
+ h1 { margin-bottom: 1rem; }
20
+ #canvas-container {
21
+ width: 800px;
22
+ height: 600px;
23
+ border-radius: 8px;
24
+ overflow: hidden;
25
+ }
26
+ .controls {
27
+ margin-top: 1rem;
28
+ display: flex;
29
+ gap: 1rem;
30
+ align-items: center;
31
+ }
32
+ button {
33
+ padding: 0.5rem 1rem;
34
+ border: none;
35
+ border-radius: 4px;
36
+ background: #4a4a6a;
37
+ color: #fff;
38
+ cursor: pointer;
39
+ }
40
+ button:hover { background: #5a5a7a; }
41
+ input[type="color"] {
42
+ width: 50px;
43
+ height: 32px;
44
+ border: none;
45
+ cursor: pointer;
46
+ }
47
+ input[type="range"] { width: 150px; }
48
+ label { font-size: 0.9rem; }
49
+ </style>
50
+ </head>
51
+ <body>
52
+ <h1>Vanilla Canvas Example</h1>
53
+ <div id="canvas-container"></div>
54
+
55
+ <div class="controls">
56
+ <button id="toggle">Pause</button>
57
+ <button id="capture">Capture PNG</button>
58
+
59
+ <label>
60
+ Color:
61
+ <input type="color" id="color" value="#00ff88">
62
+ </label>
63
+
64
+ <label>
65
+ Speed:
66
+ <input type="range" id="speed" min="1" max="20" value="5">
67
+ </label>
68
+
69
+ <label>
70
+ <input type="checkbox" id="showTrails" checked>
71
+ Show Trails
72
+ </label>
73
+ </div>
74
+
75
+ <script type="module" src="./main.ts"></script>
76
+ </body>
77
+ </html>