@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.
package/README.md CHANGED
@@ -402,10 +402,185 @@ controls.refresh();
402
402
  controls.dispose();
403
403
  ```
404
404
 
405
+ ## Using Exported HyperTools Experiences
406
+
407
+ When you export a project from [HyperTools](https://hypertools.dev), you get a standalone web component that can be embedded anywhere. Use `ExperienceController` from this SDK to control it programmatically.
408
+
409
+ ### Setup
410
+
411
+ 1. **Get your exported experience** - Export from HyperTools to get a JS file (e.g., `my-experience.js`)
412
+
413
+ 2. **Load the experience** in your HTML:
414
+ ```html
415
+ <script src="./my-experience.js"></script>
416
+ <my-experience></my-experience>
417
+ ```
418
+
419
+ 3. **Install the SDK** to control it:
420
+ ```bash
421
+ npm install @hypertools/sdk
422
+ ```
423
+
424
+ ### Basic Control
425
+
426
+ ```typescript
427
+ import { ExperienceController } from '@hypertools/sdk';
428
+
429
+ // Get reference to the web component
430
+ const element = document.querySelector('my-experience');
431
+
432
+ // Connect the controller
433
+ const controller = new ExperienceController({
434
+ element,
435
+ initialParams: {
436
+ speed: 5,
437
+ color: '#ff0000',
438
+ },
439
+ });
440
+
441
+ // Control parameters
442
+ controller.setParam('speed', 10);
443
+ controller.setParams({ speed: 8, color: '#00ff00' });
444
+
445
+ // Get current values
446
+ const params = controller.getParams();
447
+ const paramDefs = controller.getParamDefs();
448
+
449
+ // Reset to defaults
450
+ controller.resetParams();
451
+
452
+ // Listen to changes
453
+ controller.on('paramChange', (event) => {
454
+ console.log(`${event.key} changed to ${event.value}`);
455
+ });
456
+
457
+ // Cleanup when done
458
+ controller.destroy();
459
+ ```
460
+
461
+ ### Dispatching Events
462
+
463
+ Trigger interactions programmatically by dispatching synthetic events:
464
+
465
+ ```typescript
466
+ // Simulate a click
467
+ controller.dispatchToCanvas('click', { clientX: 400, clientY: 300 });
468
+
469
+ // Simulate a long press (e.g., for formation triggers)
470
+ controller.dispatchToCanvas('mousedown', { clientX: 400, clientY: 300 });
471
+ setTimeout(() => {
472
+ controller.dispatchToCanvas('mouseup', { clientX: 400, clientY: 300 });
473
+ }, 600);
474
+
475
+ // Keyboard events
476
+ controller.dispatchToCanvas('keydown', { key: 'Space' });
477
+
478
+ // Custom events
479
+ controller.dispatchCustomEvent('myEvent', { data: 'hello' });
480
+ ```
481
+
482
+ ### React Integration
483
+
484
+ ```tsx
485
+ import { useEffect, useRef, useState } from 'react';
486
+ import { ExperienceController } from '@hypertools/sdk';
487
+ import type { ExportedExperienceElement } from '@hypertools/sdk';
488
+
489
+ function App() {
490
+ const experienceRef = useRef<ExportedExperienceElement>(null);
491
+ const controllerRef = useRef<ExperienceController | null>(null);
492
+ const [isReady, setIsReady] = useState(false);
493
+
494
+ useEffect(() => {
495
+ const element = experienceRef.current;
496
+ if (!element) return;
497
+
498
+ const onReady = () => {
499
+ controllerRef.current = new ExperienceController({ element });
500
+ setIsReady(true);
501
+ };
502
+
503
+ element.addEventListener('ready', onReady, { once: true });
504
+
505
+ return () => {
506
+ element.removeEventListener('ready', onReady);
507
+ controllerRef.current?.destroy();
508
+ };
509
+ }, []);
510
+
511
+ return (
512
+ <div>
513
+ {/* @ts-expect-error - Custom element */}
514
+ <my-experience ref={experienceRef} />
515
+
516
+ {isReady && (
517
+ <button onClick={() => controllerRef.current?.setParam('speed', 10)}>
518
+ Speed Up
519
+ </button>
520
+ )}
521
+ </div>
522
+ );
523
+ }
524
+ ```
525
+
526
+ ### ExperienceController API
527
+
528
+ ```typescript
529
+ // Constructor options
530
+ interface ExperienceControllerConfig {
531
+ element: ExportedExperienceElement; // The web component
532
+ initialParams?: Record<string, unknown>; // Override initial values
533
+ autoConnect?: boolean; // Connect immediately (default: true)
534
+ }
535
+
536
+ // Methods
537
+ controller.connect(); // Connect to element
538
+ controller.disconnect(); // Disconnect from element
539
+ controller.destroy(); // Full cleanup
540
+
541
+ controller.setParam(key, value);
542
+ controller.setParams(params);
543
+ controller.getParams();
544
+ controller.getParamDefs();
545
+ controller.resetParams();
546
+
547
+ controller.on(event, handler); // Subscribe
548
+ controller.once(event, handler); // Subscribe once
549
+ controller.off(event, handler); // Unsubscribe
550
+
551
+ controller.dispatchToCanvas(eventType, eventInit); // Dispatch to canvas
552
+ controller.dispatchToElement(eventType, eventInit); // Dispatch to element
553
+ controller.dispatchCustomEvent(type, detail); // Custom event
554
+
555
+ controller.getCanvas(); // Get canvas element
556
+ controller.getMount(); // Get mount element
557
+
558
+ // Properties
559
+ controller.element; // The web component
560
+ controller.isConnected; // Connection status
561
+ controller.isDestroyed; // Destroyed status
562
+
563
+ // Static factories
564
+ ExperienceController.fromSelector('my-experience');
565
+ await ExperienceController.whenDefined('my-experience');
566
+ ```
567
+
568
+ ### Building Custom Features
569
+
570
+ See [`examples/react-landing/`](./examples/react-landing/) for a complete example showing how to build custom features on top of exported experiences:
571
+
572
+ - **Preset System** - Pre-configured settings with visual selection
573
+ - **Click-to-Form Mode** - Click anywhere to trigger interactions at that position
574
+ - **Auto-pilot Mode** - Automatically cycle through presets
575
+ - **Idle Screensaver** - Start animations after inactivity
576
+ - **Share Configuration** - Generate shareable URLs with encoded settings
577
+ - **Keyboard Shortcuts** - Custom keyboard controls
578
+
405
579
  ## Examples
406
580
 
407
581
  See the `/examples` directory for complete working examples:
408
582
 
583
+ - `examples/react-landing/` - **React app with exported experience as background + custom features**
409
584
  - `examples/vanilla-canvas/` - Pure Canvas API
410
585
  - `examples/p5js/` - p5.js sketch
411
586
  - `examples/threejs/` - Three.js scene
@@ -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>