@hypertools/sdk 0.1.0 → 0.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
@@ -1,9 +1,9 @@
1
- # @hypertool/sdk
1
+ # @hypertools/sdk
2
2
 
3
3
  Vanilla-first SDK for embedding interactive creative coding experiences. Works with p5.js, Three.js, WebGL, Canvas, and any other rendering library.
4
4
 
5
- [![npm version](https://img.shields.io/npm/v/@hypertool/sdk.svg)](https://www.npmjs.com/package/@hypertool/sdk)
6
- [![npm downloads](https://img.shields.io/npm/dm/@hypertool/sdk.svg)](https://www.npmjs.com/package/@hypertool/sdk)
5
+ [![npm version](https://img.shields.io/npm/v/@hypertools/sdk.svg)](https://www.npmjs.com/package/@hypertools/sdk)
6
+ [![npm downloads](https://img.shields.io/npm/dm/@hypertools/sdk.svg)](https://www.npmjs.com/package/@hypertools/sdk)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
8
 
9
9
  ## Features
@@ -20,16 +20,16 @@ Vanilla-first SDK for embedding interactive creative coding experiences. Works w
20
20
 
21
21
  ```bash
22
22
  # npm
23
- npm install @hypertool/sdk
23
+ npm install @hypertools/sdk
24
24
 
25
25
  # yarn
26
- yarn add @hypertool/sdk
26
+ yarn add @hypertools/sdk
27
27
 
28
28
  # pnpm
29
- pnpm add @hypertool/sdk
29
+ pnpm add @hypertools/sdk
30
30
 
31
31
  # bun
32
- bun add @hypertool/sdk
32
+ bun add @hypertools/sdk
33
33
  ```
34
34
 
35
35
  ## Quick Start
@@ -37,7 +37,7 @@ bun add @hypertool/sdk
37
37
  ### Basic Usage
38
38
 
39
39
  ```typescript
40
- import { Experience } from '@hypertool/sdk';
40
+ import { Experience } from '@hypertools/sdk';
41
41
 
42
42
  const experience = new Experience({
43
43
  mount: document.getElementById('canvas-container'),
@@ -92,7 +92,7 @@ experience.destroy();
92
92
  ### With p5.js
93
93
 
94
94
  ```typescript
95
- import { Experience } from '@hypertool/sdk';
95
+ import { Experience } from '@hypertools/sdk';
96
96
  import p5 from 'p5';
97
97
 
98
98
  new Experience({
@@ -135,7 +135,7 @@ new Experience({
135
135
  ### With Three.js
136
136
 
137
137
  ```typescript
138
- import { Experience } from '@hypertool/sdk';
138
+ import { Experience } from '@hypertools/sdk';
139
139
  import * as THREE from 'three';
140
140
 
141
141
  new Experience({
@@ -190,11 +190,11 @@ The SDK is modular - import only what you need:
190
190
 
191
191
  | Entry | Purpose |
192
192
  |-------|---------|
193
- | `@hypertool/sdk` | Core Experience class + utilities |
194
- | `@hypertool/sdk/controls` | Tweakpane-based control panel (requires `tweakpane` peer dep) |
195
- | `@hypertool/sdk/recording` | Video/image capture & timeline |
196
- | `@hypertool/sdk/react` | React hooks & components (requires `react` peer dep) |
197
- | `@hypertool/sdk/capture` | Low-level capture utilities |
193
+ | `@hypertools/sdk` | Core Experience class + utilities |
194
+ | `@hypertools/sdk/controls` | Tweakpane-based control panel (requires `tweakpane` peer dep) |
195
+ | `@hypertools/sdk/recording` | Video/image capture & timeline |
196
+ | `@hypertools/sdk/react` | React hooks & components (requires `react` peer dep) |
197
+ | `@hypertools/sdk/capture` | Low-level capture utilities |
198
198
 
199
199
  ## API Reference
200
200
 
@@ -286,7 +286,7 @@ experience.mount; // HTMLElement
286
286
  ### Recording Module
287
287
 
288
288
  ```typescript
289
- import { VideoRecorder, ImageCapture, Timeline } from '@hypertool/sdk/recording';
289
+ import { VideoRecorder, ImageCapture, Timeline } from '@hypertools/sdk/recording';
290
290
 
291
291
  // Video recording
292
292
  const recorder = VideoRecorder.start(canvas, {
@@ -325,7 +325,7 @@ timeline.seek(2500); // Jump to 2.5s
325
325
  ### React Integration
326
326
 
327
327
  ```tsx
328
- import { useExperience, ExperienceView } from '@hypertool/sdk/react';
328
+ import { useExperience, ExperienceView } from '@hypertools/sdk/react';
329
329
 
330
330
  function MyVisualization() {
331
331
  const {
@@ -383,7 +383,7 @@ function MyVisualization() {
383
383
  Add a visual control panel with Tweakpane:
384
384
 
385
385
  ```typescript
386
- import { HypertoolControls } from '@hypertool/sdk/controls';
386
+ import { HypertoolControls } from '@hypertools/sdk/controls';
387
387
 
388
388
  const controls = new HypertoolControls({
389
389
  container: document.getElementById('controls'),
@@ -431,7 +431,7 @@ import type {
431
431
  ParamDefinitions,
432
432
  ParamValues,
433
433
  ExperienceEvent,
434
- } from '@hypertool/sdk';
434
+ } from '@hypertools/sdk';
435
435
  ```
436
436
 
437
437
  ## Contributing
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * Usage:
11
11
  * ```ts
12
- * import { CaptureManager } from '@hypertool/sdk/capture';
12
+ * import { CaptureManager } from '@hypertools/sdk/capture';
13
13
  *
14
14
  * // Auto-detect render target
15
15
  * const capture = new CaptureManager();
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @deprecated Use @hypertool/sdk/export instead
2
+ * @deprecated Use @hypertools/sdk/export instead
3
3
  * This module is kept for backward compatibility only.
4
4
  */
5
5
  export * from '../export';
@@ -488,7 +488,7 @@ function __setupDraggable__(container, pane) {
488
488
  }
489
489
  `}function u(R){let{tagName:U,currentParams:q={},showControls:x=!1,controlsTitle:B,background:M="transparent"}=R,K=C(q),A=B||U.split("-").map((Y)=>Y.charAt(0).toUpperCase()+Y.slice(1)).join(" "),z=x?h(A):"",O=x?y():"",L=x?'<div class="controls-container"></div>':"";return`
490
490
  // Export-friendly HyperTool Runtime
491
- // Generated by @hypertool/sdk
491
+ // Generated by @hypertools/sdk
492
492
  // Controls: ${x?"enabled":"disabled"}
493
493
 
494
494
  // Baked-in current params from export
@@ -797,4 +797,4 @@ declare global {
797
797
  }
798
798
  `}export{w as wrapSetupCode,G as generateWebComponent,H as generateTypeDefinitions,c as generatePreviewHtml,N as generateExportTypes,u as generateExportRuntime,g as generateExportReadme,d as generateExportHtml,T as generateBundle,P as extractParamDefs};
799
799
 
800
- //# debugId=8BED4B1E577CB55764756E2164756E21
800
+ //# debugId=70609385B33828B264756E2164756E21
@@ -5,9 +5,9 @@
5
5
  "/**\n * Bundle generation using esbuild\n */\n\nexport interface BundleOptions {\n /** Entry file path or inline code */\n entry: string | { code: string; filename?: string };\n\n /** Additional files for bundling */\n files?: Record<string, string>;\n\n /** Output format (default: 'iife') */\n format?: 'iife' | 'esm' | 'cjs';\n\n /** Minify output (default: true) */\n minify?: boolean;\n\n /** Generate source maps */\n sourcemap?: boolean;\n\n /** Global name for IIFE format */\n globalName?: string;\n\n /** External packages to exclude from bundle */\n external?: string[];\n\n /** Target environment (default: 'browser') */\n target?: 'browser' | 'node';\n\n /** Define replacements */\n define?: Record<string, string>;\n}\n\nexport interface BundleResult {\n /** Bundled code */\n code: string;\n\n /** Source map (if requested) */\n map?: string;\n\n /** Build warnings */\n warnings: string[];\n\n /** Output size in bytes */\n size: number;\n}\n\n/**\n * Bundle code using esbuild\n *\n * This function is designed to run in Node.js/Bun server environment\n * (e.g., backend export service)\n */\nexport async function generateBundle(options: BundleOptions): Promise<BundleResult> {\n // Dynamic import esbuild (only available server-side)\n let esbuild: typeof import('esbuild');\n try {\n esbuild = await import('esbuild');\n } catch {\n throw new Error(\n 'esbuild is not available. This function is meant to run server-side.'\n );\n }\n\n const { mkdtemp, writeFile, readFile, rm, mkdir } = await import('fs/promises');\n const { tmpdir } = await import('os');\n const { join, dirname } = await import('path');\n\n const tempDir = await mkdtemp(join(tmpdir(), 'hypertool-bundle-'));\n\n try {\n // Write files to temp directory\n if (options.files) {\n for (const [filePath, content] of Object.entries(options.files)) {\n const fullPath = join(tempDir, filePath.replace(/^\\//, ''));\n const dir = dirname(fullPath);\n await mkdir(dir, { recursive: true });\n await writeFile(fullPath, content);\n }\n }\n\n // Determine entry\n let entryPath: string;\n if (typeof options.entry === 'string') {\n entryPath = join(tempDir, options.entry.replace(/^\\//, ''));\n } else {\n const filename = options.entry.filename ?? 'entry.ts';\n entryPath = join(tempDir, filename);\n await writeFile(entryPath, options.entry.code);\n }\n\n // Bundle with esbuild\n const result = await esbuild.build({\n entryPoints: [entryPath],\n bundle: true,\n outfile: join(tempDir, 'out.js'),\n platform: options.target === 'node' ? 'node' : 'browser',\n format: options.format ?? 'iife',\n minify: options.minify ?? true,\n sourcemap: options.sourcemap ? 'external' : false,\n globalName: options.globalName,\n external: options.external,\n define: options.define,\n write: true,\n metafile: true,\n });\n\n // Read output\n const code = await readFile(join(tempDir, 'out.js'), 'utf-8');\n let map: string | undefined;\n\n if (options.sourcemap) {\n try {\n map = await readFile(join(tempDir, 'out.js.map'), 'utf-8');\n } catch {\n // Source map might not exist\n }\n }\n\n return {\n code,\n map,\n warnings: result.warnings.map((w: { text: string }) => w.text),\n size: Buffer.byteLength(code, 'utf-8'),\n };\n } finally {\n // Cleanup\n await rm(tempDir, { recursive: true, force: true });\n }\n}\n\n/**\n * Extract param definitions from code using regex\n */\nexport function extractParamDefs(\n code: string\n): Record<string, { type: string; value: unknown }> | null {\n // Match export const paramDefs = { ... }\n const match = code.match(\n /export\\s+(?:const|let|var)\\s+(?:paramDefs|controlDefinitions)\\s*=\\s*(\\{[\\s\\S]*?\\});?/\n );\n\n if (!match) return null;\n\n try {\n // This is a simplified extraction - in production you'd use AST parsing\n // For now, we'll return null and let the backend handle it\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Wrap user code to expose setup function globally\n */\nexport function wrapSetupCode(code: string): string {\n return `\n${code}\n\n// Expose setup function globally for web component\nif (typeof setup === 'function') {\n window.__hypertool_setup__ = setup;\n}\nif (typeof paramDefs !== 'undefined') {\n window.__hypertool_paramDefs__ = paramDefs;\n}\n`;\n}\n",
6
6
  "/**\n * Web component generator for exports\n */\n\nimport type { ParamDefinitions } from '../../core/ParamStore';\n\nexport interface WebComponentOptions {\n /** Component tag name (e.g., 'my-experience') */\n tagName: string;\n\n /** Class name (e.g., 'MyExperience') */\n className: string;\n\n /** Bundled experience code */\n bundledCode: string;\n\n /** Parameter definitions */\n paramDefs: ParamDefinitions;\n\n /** Current parameter values */\n currentParams?: Record<string, unknown>;\n\n /** Include Tweakpane controls */\n showControls?: boolean;\n\n /** Background color */\n background?: string;\n}\n\n/**\n * Escape string for safe embedding in JavaScript\n */\nfunction escapeForJS(str: string): string {\n return str\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/`/g, '\\\\`')\n .replace(/\\$/g, '\\\\$')\n .replace(/<\\/script>/gi, '<\\\\/script>');\n}\n\n/**\n * Generate a self-contained web component\n */\nexport function generateWebComponent(options: WebComponentOptions): string {\n const {\n tagName,\n className,\n bundledCode,\n paramDefs,\n currentParams = {},\n showControls = false,\n background = 'transparent',\n } = options;\n\n // Escape JSON for embedding\n const paramsJson = JSON.stringify(currentParams)\n .replace(/</g, '\\\\u003c')\n .replace(/>/g, '\\\\u003e');\n\n const defsJson = JSON.stringify(paramDefs)\n .replace(/</g, '\\\\u003c')\n .replace(/>/g, '\\\\u003e');\n\n const controlsScript = showControls\n ? `\n // Load Tweakpane for controls\n async function loadTweakpane() {\n if (window.Tweakpane) return window.Tweakpane;\n return new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.src = 'https://cdn.jsdelivr.net/npm/tweakpane@4.0.5/dist/tweakpane.min.js';\n script.onload = () => resolve(window.Tweakpane);\n script.onerror = reject;\n document.head.appendChild(script);\n });\n }\n\n async function createControls(params, defs, container) {\n try {\n const Tweakpane = await loadTweakpane();\n const pane = new Tweakpane.Pane({\n title: '${className} Controls',\n container,\n });\n\n for (const [key, def] of Object.entries(defs)) {\n const config = { label: def.label || key };\n if (def.type === 'number') {\n if (def.min !== undefined) config.min = def.min;\n if (def.max !== undefined) config.max = def.max;\n if (def.step !== undefined) config.step = def.step;\n }\n if (def.type === 'select' && def.options) {\n config.options = def.options.reduce((acc, opt) => {\n const val = typeof opt === 'object' ? opt.value : opt;\n const label = typeof opt === 'object' ? opt.label : opt;\n acc[label] = val;\n return acc;\n }, {});\n }\n try {\n pane.addBinding(params, key, config);\n } catch (e) {\n console.warn('Failed to add control:', key, e);\n }\n }\n\n return pane;\n } catch (e) {\n console.warn('Failed to load Tweakpane:', e);\n return null;\n }\n }\n `\n : '';\n\n return `\n/**\n * ${className} Web Component\n * Generated by HyperTool SDK\n */\n\n(function() {\n 'use strict';\n\n const __PARAMS__ = ${paramsJson};\n const __PARAM_DEFS__ = ${defsJson};\n const __SHOW_CONTROLS__ = ${showControls};\n const __BACKGROUND__ = '${background}';\n\n ${controlsScript}\n\n class ${className}Element extends HTMLElement {\n #mount = null;\n #params = null;\n #cleanup = null;\n #resizeObserver = null;\n #pane = null;\n #isReady = false;\n\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n }\n\n static get observedAttributes() {\n return ['autoplay', 'background', ...Object.keys(__PARAM_DEFS__)];\n }\n\n connectedCallback() {\n this.#render();\n this.#initialize();\n }\n\n disconnectedCallback() {\n this.#destroy();\n }\n\n attributeChangedCallback(name, oldValue, newValue) {\n if (oldValue === newValue) return;\n\n if (name === 'background') {\n if (this.#mount) {\n this.#mount.style.background = newValue || __BACKGROUND__;\n }\n return;\n }\n\n if (name in __PARAM_DEFS__ && this.#params) {\n this.#params[name] = this.#parseAttribute(newValue, __PARAM_DEFS__[name].type);\n }\n }\n\n #render() {\n this.shadowRoot.innerHTML = \\`\n <style>\n :host {\n display: block;\n width: 100%;\n height: 100%;\n position: relative;\n }\n .mount {\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n background: \\${__BACKGROUND__};\n }\n .controls-container {\n position: absolute;\n top: 8px;\n right: 8px;\n z-index: 1000;\n }\n </style>\n <div class=\"mount\"></div>\n \\${__SHOW_CONTROLS__ ? '<div class=\"controls-container\"></div>' : ''}\n \\`;\n\n this.#mount = this.shadowRoot.querySelector('.mount');\n }\n\n async #initialize() {\n // Initialize params from defaults + attributes\n this.#params = { ...__PARAMS__ };\n\n for (const [key, def] of Object.entries(__PARAM_DEFS__)) {\n const attrValue = this.getAttribute(key);\n if (attrValue !== null) {\n this.#params[key] = this.#parseAttribute(attrValue, def.type);\n }\n }\n\n // Create reactive proxy\n const self = this;\n this.#params = new Proxy(this.#params, {\n set(target, prop, value) {\n const previous = target[prop];\n target[prop] = value;\n if (previous !== value) {\n self.dispatchEvent(new CustomEvent('paramchange', {\n detail: { key: prop, value, previousValue: previous },\n bubbles: true,\n }));\n }\n return true;\n }\n });\n\n // Setup resize observer\n this.#resizeObserver = new ResizeObserver(() => {\n this.dispatchEvent(new CustomEvent('resize', {\n detail: {\n width: this.#mount.clientWidth,\n height: this.#mount.clientHeight\n },\n bubbles: true,\n }));\n });\n this.#resizeObserver.observe(this.#mount);\n\n // Setup controls if enabled\n if (__SHOW_CONTROLS__) {\n const controlsContainer = this.shadowRoot.querySelector('.controls-container');\n if (controlsContainer && typeof createControls === 'function') {\n this.#pane = await createControls(this.#params, __PARAM_DEFS__, controlsContainer);\n }\n }\n\n // Run experience setup\n try {\n const context = {\n mount: this.#mount,\n params: this.#params,\n exports: {\n captureImage: (format) => this.captureImage(format),\n setFilename: () => {},\n useDefaultCanvasCapture: () => {},\n },\n environment: {\n window,\n document,\n onResize: (cb) => {\n const handler = () => cb(this.#mount.clientWidth, this.#mount.clientHeight);\n handler();\n const ro = new ResizeObserver(handler);\n ro.observe(this.#mount);\n return () => ro.disconnect();\n },\n },\n registerObject: () => {},\n };\n\n // Execute bundled code\n ${escapeForJS(bundledCode)}\n\n // Call setup if defined\n if (typeof window.__hypertool_setup__ === 'function') {\n this.#cleanup = await window.__hypertool_setup__(context);\n } else if (typeof setup === 'function') {\n this.#cleanup = await setup(context);\n }\n\n this.#isReady = true;\n this.dispatchEvent(new CustomEvent('ready', { bubbles: true }));\n\n // Autoplay by default\n if (this.getAttribute('autoplay') !== 'false') {\n this.play();\n }\n } catch (error) {\n console.error('[${className}] Setup error:', error);\n this.dispatchEvent(new CustomEvent('error', {\n detail: { error },\n bubbles: true,\n }));\n }\n }\n\n #destroy() {\n if (this.#cleanup && typeof this.#cleanup === 'function') {\n try {\n this.#cleanup();\n } catch (e) {\n console.error('[${className}] Cleanup error:', e);\n }\n }\n this.#resizeObserver?.disconnect();\n this.#pane?.dispose();\n this.#cleanup = null;\n this.#params = null;\n this.#isReady = false;\n }\n\n #parseAttribute(value, type) {\n switch (type) {\n case 'number':\n return parseFloat(value);\n case 'boolean':\n return value === 'true' || value === '';\n case 'color':\n return value;\n default:\n return value;\n }\n }\n\n // Public API\n get isReady() { return this.#isReady; }\n\n setParam(key, value) {\n if (this.#params && key in __PARAM_DEFS__) {\n this.#params[key] = value;\n this.#pane?.refresh();\n }\n }\n\n setParams(params) {\n if (!this.#params) return;\n for (const [key, value] of Object.entries(params)) {\n if (key in __PARAM_DEFS__) {\n this.#params[key] = value;\n }\n }\n this.#pane?.refresh();\n }\n\n getParams() {\n return this.#params ? { ...this.#params } : {};\n }\n\n getParamDefs() {\n return __PARAM_DEFS__;\n }\n\n async captureImage(format = 'png') {\n const canvas = this.#mount?.querySelector('canvas');\n if (!canvas) return null;\n\n return new Promise((resolve) => {\n canvas.toBlob(resolve, \\`image/\\${format}\\`, format === 'jpeg' ? 0.92 : undefined);\n });\n }\n\n play() {\n this.dispatchEvent(new CustomEvent('play', { bubbles: true }));\n }\n\n pause() {\n this.dispatchEvent(new CustomEvent('pause', { bubbles: true }));\n }\n }\n\n // Register custom element\n if (!customElements.get('${tagName}')) {\n customElements.define('${tagName}', ${className}Element);\n }\n})();\n`;\n}\n\n/**\n * Generate TypeScript definitions for the web component\n */\nexport function generateTypeDefinitions(options: WebComponentOptions): string {\n const { className, tagName, paramDefs } = options;\n\n const paramTypes = Object.entries(paramDefs)\n .map(([key, def]) => {\n let type: string;\n switch (def.type) {\n case 'number':\n type = 'number';\n break;\n case 'boolean':\n type = 'boolean';\n break;\n case 'color':\n case 'string':\n type = 'string';\n break;\n case 'select':\n if (def.options) {\n type = def.options\n .map((opt) => `'${typeof opt === 'object' ? opt.value : opt}'`)\n .join(' | ');\n } else {\n type = 'string';\n }\n break;\n default:\n type = 'unknown';\n }\n return ` ${key}?: ${type};`;\n })\n .join('\\n');\n\n return `/**\n * TypeScript definitions for ${className}\n * Generated by HyperTool SDK\n */\n\ndeclare namespace JSX {\n interface IntrinsicElements {\n '${tagName}': ${className}Attributes;\n }\n}\n\ninterface ${className}Attributes {\n autoplay?: boolean | 'true' | 'false';\n background?: string;\n${paramTypes}\n}\n\ninterface ${className}Element extends HTMLElement {\n readonly isReady: boolean;\n setParam(key: string, value: unknown): void;\n setParams(params: Record<string, unknown>): void;\n getParams(): Record<string, unknown>;\n getParamDefs(): Record<string, { type: string; value: unknown; [key: string]: unknown }>;\n captureImage(format?: 'png' | 'jpeg' | 'webp'): Promise<Blob | null>;\n play(): void;\n pause(): void;\n}\n\ninterface ${className}Events {\n ready: CustomEvent<void>;\n error: CustomEvent<{ error: Error }>;\n paramchange: CustomEvent<{ key: string; value: unknown; previousValue: unknown }>;\n resize: CustomEvent<{ width: number; height: number }>;\n play: CustomEvent<void>;\n pause: CustomEvent<void>;\n}\n`;\n}\n",
7
7
  "/**\n * Design tokens inherited from the parent Studio application\n * These CSS variables are defined in src/app/globals.css\n */\nexport const studioTheme = {\n // CSS variables that should be available in the iframe\n cssVariables: {\n '--bg': '#0a0e14',\n '--bg-elevated': '#0f1419',\n '--muted': '#1a2332',\n '--text': '#e6edf3',\n '--text-secondary': '#8b949e',\n '--accent': '#58d5ff',\n '--accent-2': '#42a5cc',\n '--border': '#21262d',\n '--border-hover': '#30363d',\n '--success': '#3fb950',\n '--error': '#f85149',\n\n // Hypertool-specific variables\n '--ht-text-color-main-200': '#00ffd4',\n '--ht-text-color-main-300': '#6ff3dd',\n '--ht-text-color-main-500': '#4b8e85',\n '--ht-text-color-grey-200': '#8c8d8f',\n\n '--ht-bg-color-200': '#8c8d8f',\n '--ht-bg-color-200-active': '#7a7b7d',\n '--ht-bg-color-200-focus': '#7e7f81',\n '--ht-bg-color-200-hover': '#828384',\n\n '--ht-bg-color-300': '#5e6068',\n '--ht-bg-color-300-active': '#4c4e56',\n '--ht-bg-color-300-focus': '#55575f',\n '--ht-bg-color-300-hover': '#595b63',\n\n '--ht-bg-color-400': '#cccccc',\n '--ht-bg-color-400-active': '#b3b3b3',\n '--ht-bg-color-400-focus': '#c0c0c0',\n '--ht-bg-color-400-hover': '#c6c6c6',\n\n '--ht-bg-color-500': '#1C1D20',\n '--ht-bg-color-500-active': '#0a0b0e',\n '--ht-bg-color-500-focus': '#131417',\n '--ht-bg-color-500-hover': '#16171a',\n\n '--ht-bg-color-600': '#37383D',\n '--ht-bg-color-600-active': '#25262b',\n '--ht-bg-color-600-focus': '#2e2f34',\n '--ht-bg-color-600-hover': '#323338',\n\n '--ht-bg-color-700': '#0f1419',\n '--ht-bg-color-700-active': '#16171c',\n '--ht-bg-color-700-focus': '#1f2025',\n '--ht-bg-color-700-hover': '#232429',\n\n '--ht-border-radius': '8px',\n\n '--ht-base-font-family-base': '\"HydrogenType400\", ui-sans-serif, system-ui, sans-serif',\n '--ht-base-font-family-display': '\"Departure Mono\", Roboto Mono, Source Code Pro, Menlo, Courier, monospace',\n '--ht-base-font-family-sans': '\"Routed Gothic\", ui-sans-serif, system-ui, sans-serif',\n '--ht-base-font-family-mono': '\"Routed Gothic Narrow\", ui-monospace, SFMono-Regular, SF Mono, Consolas, Liberation Mono, Menlo, monospace',\n },\n\n // Tweakpane-specific theme variables\n tweakpane: {\n '--tp-base-background-color': 'var(--ht-bg-color-700)',\n '--tp-base-shadow-color': 'hsla(0, 0%, 0%, 0.2)',\n\n '--tp-container-background-color': 'var(--ht-bg-color-600)',\n '--tp-container-background-color-active': 'var(--ht-bg-color-600-active)',\n '--tp-container-background-color-focus': 'var(--ht-bg-color-600-focus)',\n '--tp-container-background-color-hover': 'var(--ht-bg-color-600-hover)',\n '--tp-container-foreground-color': 'var(--ht-text-color-main-300)',\n\n '--tp-button-background-color': 'var(--ht-bg-color-400)',\n '--tp-button-background-color-active': 'color-mix(in srgb, var(--ht-bg-color-400-active) 80%, white)',\n '--tp-button-background-color-focus': 'color-mix(in srgb, var(--ht-bg-color-400-focus) 85%, white)',\n '--tp-button-background-color-hover': 'color-mix(in srgb, var(--ht-bg-color-400-hover) 90%, white)',\n '--tp-button-foreground-color': 'var(--ht-text-color-main-300)',\n\n '--tp-groove-foreground-color': 'var(--ht-bg-color-300)',\n\n '--tp-input-background-color': 'var(--ht-bg-color-500)',\n '--tp-input-background-color-active': 'var(--ht-bg-color-500-active)',\n '--tp-input-background-color-focus': 'var(--ht-bg-color-500-focus)',\n '--tp-input-background-color-hover': 'var(--ht-bg-color-500-hover)',\n '--tp-input-foreground-color': 'var(--ht-text-color-main-300)',\n\n '--tp-label-foreground-color': 'var(--ht-text-color-main-300)',\n\n '--tp-monitor-background-color': 'var(--ht-bg-color-500)',\n '--tp-monitor-foreground-color': 'var(--ht-text-color-main-300)',\n }\n};\n\n/**\n * Inject CSS variables into the document\n */\nexport function injectThemeVariables() {\n const style = document.createElement('style');\n style.id = 'hypertool-theme';\n\n const cssVars = Object.entries({\n ...studioTheme.cssVariables,\n ...studioTheme.tweakpane\n })\n .map(([key, value]) => ` ${key}: ${value};`)\n .join('\\n');\n\n style.textContent = `\n body {\n margin: 0;\n padding: 0;\n }\n\n:root {\n${cssVars}\n}\n\n/* Controls panel positioning and styling */\n.controls-container {\n position: fixed;\n top: 8px;\n right: 8px;\n z-index: 1000;\n max-height: calc(100vh - 16px);\n overflow-y: auto;\n}\n\n/* Ensure Tweakpane pane has background */\n.tp-dfwv {\n background-color: var(--tp-base-background-color, #0f1419) !important;\n}\n`;\n\n document.head.appendChild(style);\n}",
8
- "/**\n * Export Runtime Generator\n *\n * Generates the __hypertool__ runtime replacement for standalone exports.\n * This provides the same API as the studio runtime but creates a web component\n * instead of connecting to studio controls.\n *\n * Used by both export and preview endpoints.\n */\n\nexport interface ExportRuntimeOptions {\n /** Component tag name (kebab-case, e.g., 'my-experience') */\n tagName: string;\n\n /** Current parameter values to bake into the export */\n currentParams?: Record<string, unknown>;\n\n /** Whether to include Tweakpane controls panel */\n showControls?: boolean;\n\n /** Controls panel title (defaults to tagName) */\n controlsTitle?: string;\n\n /** Background color for the component */\n background?: string;\n}\n\n/**\n * Safely serialize params for injection into JavaScript code\n * Escapes characters that could break script context\n */\nfunction safeJsonStringify(obj: unknown): string {\n const json = JSON.stringify(obj);\n return json\n .replace(/</g, '\\\\u003c') // Prevent </script> injection\n .replace(/>/g, '\\\\u003e') // Prevent tag injection\n .replace(/\\u2028/g, '\\\\u2028') // Line separator\n .replace(/\\u2029/g, '\\\\u2029'); // Paragraph separator\n}\n\n// Import theme from controls module - single source of truth\nimport { studioTheme } from '../controls/theme';\n\n/**\n * Generate theme CSS string from studioTheme object\n * Converts the theme variables to CSS that works in Shadow DOM\n */\nfunction generateThemeCSS(): string {\n const cssVars = Object.entries({\n ...studioTheme.cssVariables,\n ...studioTheme.tweakpane\n })\n .map(([key, value]) => ` ${key}: ${value};`)\n .join('\\n');\n\n return `\n/* Hypertool Theme - generated from studioTheme */\n:host {\n${cssVars}\n}\n\n/* Ensure Tweakpane uses our theme */\n.tp-dfwv {\n background-color: var(--tp-base-background-color) !important;\n border-radius: 8px;\n}\n`;\n}\n\n/**\n * Generate controls-related code (Tweakpane loaded from esm.sh CDN)\n */\nfunction generateControlsCode(controlsTitle: string): string {\n const themeCSS = generateThemeCSS();\n\n return `\n// Tweakpane loaded dynamically from esm.sh CDN at runtime\nlet __Pane__: any = null;\n\nasync function __loadTweakpane__(): Promise<void> {\n if (__Pane__) return;\n try {\n const module = await import('https://esm.sh/tweakpane@4');\n __Pane__ = module.Pane;\n } catch (e) {\n console.warn('[Controls] Failed to load Tweakpane from CDN:', e);\n }\n}\n\nconst __THEME_CSS__ = \\`${themeCSS}\\`;\n\nasync function __createControls__(params: Record<string, unknown>, defs: Record<string, any>, container: HTMLElement, shadowRoot: ShadowRoot) {\n await __loadTweakpane__();\n if (!__Pane__) return null;\n\n try {\n const pane = new __Pane__({\n title: '${controlsTitle}',\n container,\n });\n\n for (const [key, def] of Object.entries(defs)) {\n if (!def || def.type === 'folder') continue;\n\n const config: Record<string, unknown> = { label: def.label || key };\n\n if (def.type === 'number') {\n if (def.min !== undefined) config.min = def.min;\n if (def.max !== undefined) config.max = def.max;\n if (def.step !== undefined) config.step = def.step;\n }\n\n if (def.type === 'select' && def.options) {\n config.options = def.options.reduce((acc: Record<string, unknown>, opt: unknown) => {\n const val = typeof opt === 'object' && opt !== null ? (opt as any).value : opt;\n const label = typeof opt === 'object' && opt !== null ? (opt as any).label : String(opt);\n acc[label] = val;\n return acc;\n }, {});\n }\n\n try {\n pane.addBinding(params, key, config);\n } catch (e) {\n console.warn('[Controls] Failed to add binding for:', key, e);\n }\n }\n\n // Tweakpane injects CSS into document.head - copy it to shadow root for isolation\n setTimeout(() => {\n const tpStyles = document.querySelectorAll('style');\n tpStyles.forEach(style => {\n if (style.textContent?.includes('.tp-') || style.textContent?.includes('tp-dfwv')) {\n const clone = style.cloneNode(true) as HTMLStyleElement;\n shadowRoot.appendChild(clone);\n }\n });\n }, 0);\n\n return pane;\n } catch (e) {\n console.warn('[Controls] Failed to create controls:', e);\n return null;\n }\n}\n`;\n}\n\n/**\n * Generate draggable controls functionality\n */\nfunction generateDraggableCode(): string {\n return `\n// Make controls panel draggable\nfunction __setupDraggable__(container, pane) {\n if (!pane || !pane.element) return;\n\n const titleEl = pane.element.querySelector('.tp-rotv_t');\n if (!titleEl) return;\n\n let isDragging = false;\n let startX = 0;\n let startY = 0;\n let initialRight = 8;\n let initialTop = 8;\n\n titleEl.style.cursor = 'grab';\n\n titleEl.addEventListener('mousedown', (e) => {\n if (e.button !== 0) return;\n isDragging = true;\n startX = e.clientX;\n startY = e.clientY;\n\n const style = getComputedStyle(container);\n initialRight = parseInt(style.right) || 8;\n initialTop = parseInt(style.top) || 8;\n\n titleEl.style.cursor = 'grabbing';\n e.preventDefault();\n });\n\n document.addEventListener('mousemove', (e) => {\n if (!isDragging) return;\n\n const deltaX = e.clientX - startX;\n const deltaY = e.clientY - startY;\n\n container.style.right = Math.max(0, initialRight - deltaX) + 'px';\n container.style.top = Math.max(0, initialTop + deltaY) + 'px';\n });\n\n document.addEventListener('mouseup', () => {\n if (isDragging) {\n isDragging = false;\n titleEl.style.cursor = 'grab';\n }\n });\n}\n`;\n}\n\n/**\n * Generate the export-friendly __hypertool__ runtime\n *\n * This provides the same API as the studio runtime but creates a web component\n * instead of connecting to studio controls.\n */\nexport function generateExportRuntime(options: ExportRuntimeOptions): string {\n const {\n tagName,\n currentParams = {},\n showControls = false,\n controlsTitle,\n background = 'transparent',\n } = options;\n\n const currentParamsJson = safeJsonStringify(currentParams);\n const title = controlsTitle || tagName.split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join(' ');\n\n const controlsCode = showControls ? generateControlsCode(title) : '';\n const draggableCode = showControls ? generateDraggableCode() : '';\n const controlsContainerHtml = showControls\n ? `<div class=\"controls-container\"></div>`\n : '';\n const controlsContainerStyle = showControls\n ? `\n .controls-container {\n position: absolute;\n top: 8px;\n right: 8px;\n z-index: 1000;\n max-height: calc(100% - 16px);\n overflow-y: auto;\n }\n /* Tweakpane theme overrides */\n .controls-container .tp-dfwv {\n background-color: rgba(15, 20, 25, 0.95) !important;\n }\n `\n : '';\n\n return `\n// Export-friendly HyperTool Runtime\n// Generated by @hypertool/sdk\n// Controls: ${showControls ? 'enabled' : 'disabled'}\n\n// Baked-in current params from export\nconst __BAKED_PARAMS__ = ${currentParamsJson};\nconst __SHOW_CONTROLS__ = ${showControls};\nconst __BACKGROUND__ = '${background}';\n\n${controlsCode}\n${draggableCode}\n\nexport type ControlDefinitions = Record<string, {\n type: string;\n value: unknown;\n label?: string;\n min?: number;\n max?: number;\n step?: number;\n options?: unknown[];\n}>;\n\nexport interface SandboxContext {\n mount: HTMLElement;\n params: Record<string, unknown>;\n exports: {\n setFilename: (name: string) => void;\n useDefaultCanvasCapture: (use: boolean) => void;\n captureImage: () => Promise<Blob>;\n };\n environment: {\n onResize: (callback: () => void) => () => void;\n window: Window;\n document: Document;\n };\n controls: null;\n}\n\ninterface SandboxConfig {\n controls?: {\n definitions?: ControlDefinitions;\n options?: { title?: string };\n };\n exportWidget?: {\n filename?: string;\n useCanvasCapture?: boolean;\n enabled?: boolean;\n };\n setup: (context: SandboxContext) => (() => void) | void;\n}\n\n// Store for the experience definition\nlet __experienceDef__: SandboxConfig | null = null;\n\n/**\n * createSandbox - Export version\n * Instead of connecting to studio, registers a web component\n */\nexport async function createSandbox(config: SandboxConfig): Promise<void> {\n __experienceDef__ = config;\n\n // Create the web component\n class HypertoolElement extends HTMLElement {\n private _mount: HTMLElement | null = null;\n private _controlsContainer: HTMLElement | null = null;\n private _cleanup: (() => void) | void = undefined;\n private _params: Record<string, unknown> = {};\n private _pane: any = null;\n\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n }\n\n connectedCallback() {\n if (!this.shadowRoot || !__experienceDef__) return;\n\n // Setup styles (includes theme CSS when controls enabled)\n const style = document.createElement('style');\n style.textContent = \\`\n \\${__SHOW_CONTROLS__ && typeof __THEME_CSS__ !== 'undefined' ? __THEME_CSS__ : ''}\n :host { display: block; width: 100%; height: 100%; }\n .mount {\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n background: \\${__BACKGROUND__};\n }\n ${controlsContainerStyle}\n \\`;\n this.shadowRoot.appendChild(style);\n\n // Create mount\n this._mount = document.createElement('div');\n this._mount.className = 'mount';\n this.shadowRoot.appendChild(this._mount);\n\n // Create controls container if enabled\n if (__SHOW_CONTROLS__) {\n this._controlsContainer = document.createElement('div');\n this._controlsContainer.className = 'controls-container';\n this.shadowRoot.appendChild(this._controlsContainer);\n }\n\n // Initialize params: baked params > HTML attributes > definition defaults\n const defs = __experienceDef__.controls?.definitions || {};\n for (const [key, def] of Object.entries(defs)) {\n // Priority: HTML attribute > baked param > definition default\n if (this.getAttribute(key) !== null) {\n this._params[key] = this._parseAttr(key, this.getAttribute(key)!, def);\n } else if (key in __BAKED_PARAMS__) {\n this._params[key] = __BAKED_PARAMS__[key];\n } else {\n this._params[key] = def.value;\n }\n }\n\n // Make params reactive\n const self = this;\n this._params = new Proxy(this._params, {\n set: (target, prop, value) => {\n const previous = target[prop as string];\n target[prop as string] = value;\n if (previous !== value) {\n self.dispatchEvent(new CustomEvent('paramchange', {\n detail: { key: prop, value, previousValue: previous },\n bubbles: true,\n }));\n }\n return true;\n }\n });\n\n // Create context\n const context: SandboxContext = {\n mount: this._mount,\n params: this._params,\n exports: {\n setFilename: () => {},\n useDefaultCanvasCapture: () => {},\n captureImage: async () => {\n const canvas = this._mount?.querySelector('canvas');\n if (!canvas) throw new Error('No canvas found');\n return new Promise((res, rej) => {\n canvas.toBlob(b => b ? res(b) : rej(new Error('Failed to capture')));\n });\n },\n },\n environment: {\n onResize: (callback) => {\n const ro = new ResizeObserver(() => callback());\n ro.observe(this._mount!);\n return () => ro.disconnect();\n },\n window,\n document,\n },\n controls: null,\n };\n\n // Run setup\n this._cleanup = __experienceDef__.setup(context);\n\n // Setup controls if enabled (async - loads Tweakpane from CDN)\n if (__SHOW_CONTROLS__ && this._controlsContainer && typeof __createControls__ === 'function') {\n __createControls__(this._params, defs, this._controlsContainer, this.shadowRoot!).then((pane: any) => {\n this._pane = pane;\n if (this._pane && typeof __setupDraggable__ === 'function') {\n __setupDraggable__(this._controlsContainer, this._pane);\n }\n });\n }\n\n // Dispatch ready event\n this.dispatchEvent(new CustomEvent('ready', { bubbles: true }));\n }\n\n disconnectedCallback() {\n if (this._cleanup) this._cleanup();\n if (this._pane?.dispose) this._pane.dispose();\n }\n\n private _parseAttr(key: string, value: string, def: { type: string }): unknown {\n switch (def.type) {\n case 'number': return parseFloat(value);\n case 'boolean': return value === 'true' || value === '';\n default: return value;\n }\n }\n\n // Public API\n setParam(key: string, value: unknown) {\n this._params[key] = value;\n this._pane?.refresh();\n }\n setParams(params: Record<string, unknown>) {\n for (const [k, v] of Object.entries(params)) this._params[k] = v;\n this._pane?.refresh();\n }\n getParams() { return { ...this._params }; }\n getParamDefs() { return __experienceDef__?.controls?.definitions || {}; }\n }\n\n // Register the component\n if (!customElements.get('${tagName}')) {\n customElements.define('${tagName}', HypertoolElement);\n }\n}\n`;\n}\n\n/**\n * Generate HTML with external script reference\n */\nexport function generateExportHtml(tagName: string): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${tagName}</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html, body { width: 100%; height: 100%; overflow: hidden; background: #0a0a0a; }\n ${tagName} { display: block; width: 100%; height: 100%; }\n </style>\n</head>\n<body>\n <${tagName}></${tagName}>\n <script src=\"./${tagName}.js\"></script>\n</body>\n</html>`;\n}\n\n/**\n * Generate HTML with inline script (for preview)\n * Includes permissive CSP for external library loading\n */\nexport function generatePreviewHtml(tagName: string, code: string): string {\n const cdnHosts = [\n 'https://cdnjs.cloudflare.com',\n 'https://cdn.jsdelivr.net',\n 'https://unpkg.com',\n 'https://esm.sh',\n 'https://cdn.skypack.dev',\n ].join(' ');\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'self' blob: data: ${cdnHosts}; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: data: ${cdnHosts}; style-src 'self' 'unsafe-inline' ${cdnHosts}; img-src 'self' blob: data: *; media-src 'self' blob: data: *; connect-src *; font-src 'self' data: ${cdnHosts};\">\n <title>${tagName}</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html, body { width: 100%; height: 100%; overflow: hidden; background: #0a0a0a; }\n ${tagName} { display: block; width: 100%; height: 100%; }\n </style>\n</head>\n<body>\n <${tagName}></${tagName}>\n <script>\n${code}\n </script>\n</body>\n</html>`;\n}\n\n/**\n * Generate README for the exported component\n */\nexport function generateExportReadme(tagName: string, className: string): string {\n return `# ${className}\n\n## Usage\n\n\\`\\`\\`html\n<script src=\"${tagName}.js\"></script>\n<${tagName}></${tagName}>\n\\`\\`\\`\n\n## API\n\n\\`\\`\\`javascript\nconst el = document.querySelector('${tagName}');\n\n// Set parameters\nel.setParam('paramName', value);\nel.setParams({ param1: value1, param2: value2 });\n\n// Get current parameters\nconst params = el.getParams();\n\n// Get parameter definitions\nconst defs = el.getParamDefs();\n\\`\\`\\`\n\n## Events\n\n\\`\\`\\`javascript\nel.addEventListener('ready', () => console.log('Ready!'));\nel.addEventListener('paramchange', (e) => {\n console.log('Param changed:', e.detail.key, e.detail.value);\n});\n\\`\\`\\`\n\n## Files\n\n- \\`${tagName}.js\\` - The bundled component\n- \\`index.html\\` - Example page (loads external JS)\n- \\`preview.html\\` - Standalone preview (inline JS)\n`;\n}\n\n/**\n * Generate TypeScript type definitions\n */\nexport function generateExportTypes(tagName: string, className: string): string {\n return `export interface ${className}Element extends HTMLElement {\n setParam(key: string, value: unknown): void;\n setParams(params: Record<string, unknown>): void;\n getParams(): Record<string, unknown>;\n getParamDefs(): Record<string, { type: string; value: unknown; [key: string]: unknown }>;\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n '${tagName}': ${className}Element;\n }\n}\n`;\n}\n"
8
+ "/**\n * Export Runtime Generator\n *\n * Generates the __hypertool__ runtime replacement for standalone exports.\n * This provides the same API as the studio runtime but creates a web component\n * instead of connecting to studio controls.\n *\n * Used by both export and preview endpoints.\n */\n\nexport interface ExportRuntimeOptions {\n /** Component tag name (kebab-case, e.g., 'my-experience') */\n tagName: string;\n\n /** Current parameter values to bake into the export */\n currentParams?: Record<string, unknown>;\n\n /** Whether to include Tweakpane controls panel */\n showControls?: boolean;\n\n /** Controls panel title (defaults to tagName) */\n controlsTitle?: string;\n\n /** Background color for the component */\n background?: string;\n}\n\n/**\n * Safely serialize params for injection into JavaScript code\n * Escapes characters that could break script context\n */\nfunction safeJsonStringify(obj: unknown): string {\n const json = JSON.stringify(obj);\n return json\n .replace(/</g, '\\\\u003c') // Prevent </script> injection\n .replace(/>/g, '\\\\u003e') // Prevent tag injection\n .replace(/\\u2028/g, '\\\\u2028') // Line separator\n .replace(/\\u2029/g, '\\\\u2029'); // Paragraph separator\n}\n\n// Import theme from controls module - single source of truth\nimport { studioTheme } from '../controls/theme';\n\n/**\n * Generate theme CSS string from studioTheme object\n * Converts the theme variables to CSS that works in Shadow DOM\n */\nfunction generateThemeCSS(): string {\n const cssVars = Object.entries({\n ...studioTheme.cssVariables,\n ...studioTheme.tweakpane\n })\n .map(([key, value]) => ` ${key}: ${value};`)\n .join('\\n');\n\n return `\n/* Hypertool Theme - generated from studioTheme */\n:host {\n${cssVars}\n}\n\n/* Ensure Tweakpane uses our theme */\n.tp-dfwv {\n background-color: var(--tp-base-background-color) !important;\n border-radius: 8px;\n}\n`;\n}\n\n/**\n * Generate controls-related code (Tweakpane loaded from esm.sh CDN)\n */\nfunction generateControlsCode(controlsTitle: string): string {\n const themeCSS = generateThemeCSS();\n\n return `\n// Tweakpane loaded dynamically from esm.sh CDN at runtime\nlet __Pane__: any = null;\n\nasync function __loadTweakpane__(): Promise<void> {\n if (__Pane__) return;\n try {\n const module = await import('https://esm.sh/tweakpane@4');\n __Pane__ = module.Pane;\n } catch (e) {\n console.warn('[Controls] Failed to load Tweakpane from CDN:', e);\n }\n}\n\nconst __THEME_CSS__ = \\`${themeCSS}\\`;\n\nasync function __createControls__(params: Record<string, unknown>, defs: Record<string, any>, container: HTMLElement, shadowRoot: ShadowRoot) {\n await __loadTweakpane__();\n if (!__Pane__) return null;\n\n try {\n const pane = new __Pane__({\n title: '${controlsTitle}',\n container,\n });\n\n for (const [key, def] of Object.entries(defs)) {\n if (!def || def.type === 'folder') continue;\n\n const config: Record<string, unknown> = { label: def.label || key };\n\n if (def.type === 'number') {\n if (def.min !== undefined) config.min = def.min;\n if (def.max !== undefined) config.max = def.max;\n if (def.step !== undefined) config.step = def.step;\n }\n\n if (def.type === 'select' && def.options) {\n config.options = def.options.reduce((acc: Record<string, unknown>, opt: unknown) => {\n const val = typeof opt === 'object' && opt !== null ? (opt as any).value : opt;\n const label = typeof opt === 'object' && opt !== null ? (opt as any).label : String(opt);\n acc[label] = val;\n return acc;\n }, {});\n }\n\n try {\n pane.addBinding(params, key, config);\n } catch (e) {\n console.warn('[Controls] Failed to add binding for:', key, e);\n }\n }\n\n // Tweakpane injects CSS into document.head - copy it to shadow root for isolation\n setTimeout(() => {\n const tpStyles = document.querySelectorAll('style');\n tpStyles.forEach(style => {\n if (style.textContent?.includes('.tp-') || style.textContent?.includes('tp-dfwv')) {\n const clone = style.cloneNode(true) as HTMLStyleElement;\n shadowRoot.appendChild(clone);\n }\n });\n }, 0);\n\n return pane;\n } catch (e) {\n console.warn('[Controls] Failed to create controls:', e);\n return null;\n }\n}\n`;\n}\n\n/**\n * Generate draggable controls functionality\n */\nfunction generateDraggableCode(): string {\n return `\n// Make controls panel draggable\nfunction __setupDraggable__(container, pane) {\n if (!pane || !pane.element) return;\n\n const titleEl = pane.element.querySelector('.tp-rotv_t');\n if (!titleEl) return;\n\n let isDragging = false;\n let startX = 0;\n let startY = 0;\n let initialRight = 8;\n let initialTop = 8;\n\n titleEl.style.cursor = 'grab';\n\n titleEl.addEventListener('mousedown', (e) => {\n if (e.button !== 0) return;\n isDragging = true;\n startX = e.clientX;\n startY = e.clientY;\n\n const style = getComputedStyle(container);\n initialRight = parseInt(style.right) || 8;\n initialTop = parseInt(style.top) || 8;\n\n titleEl.style.cursor = 'grabbing';\n e.preventDefault();\n });\n\n document.addEventListener('mousemove', (e) => {\n if (!isDragging) return;\n\n const deltaX = e.clientX - startX;\n const deltaY = e.clientY - startY;\n\n container.style.right = Math.max(0, initialRight - deltaX) + 'px';\n container.style.top = Math.max(0, initialTop + deltaY) + 'px';\n });\n\n document.addEventListener('mouseup', () => {\n if (isDragging) {\n isDragging = false;\n titleEl.style.cursor = 'grab';\n }\n });\n}\n`;\n}\n\n/**\n * Generate the export-friendly __hypertool__ runtime\n *\n * This provides the same API as the studio runtime but creates a web component\n * instead of connecting to studio controls.\n */\nexport function generateExportRuntime(options: ExportRuntimeOptions): string {\n const {\n tagName,\n currentParams = {},\n showControls = false,\n controlsTitle,\n background = 'transparent',\n } = options;\n\n const currentParamsJson = safeJsonStringify(currentParams);\n const title = controlsTitle || tagName.split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join(' ');\n\n const controlsCode = showControls ? generateControlsCode(title) : '';\n const draggableCode = showControls ? generateDraggableCode() : '';\n const controlsContainerHtml = showControls\n ? `<div class=\"controls-container\"></div>`\n : '';\n const controlsContainerStyle = showControls\n ? `\n .controls-container {\n position: absolute;\n top: 8px;\n right: 8px;\n z-index: 1000;\n max-height: calc(100% - 16px);\n overflow-y: auto;\n }\n /* Tweakpane theme overrides */\n .controls-container .tp-dfwv {\n background-color: rgba(15, 20, 25, 0.95) !important;\n }\n `\n : '';\n\n return `\n// Export-friendly HyperTool Runtime\n// Generated by @hypertools/sdk\n// Controls: ${showControls ? 'enabled' : 'disabled'}\n\n// Baked-in current params from export\nconst __BAKED_PARAMS__ = ${currentParamsJson};\nconst __SHOW_CONTROLS__ = ${showControls};\nconst __BACKGROUND__ = '${background}';\n\n${controlsCode}\n${draggableCode}\n\nexport type ControlDefinitions = Record<string, {\n type: string;\n value: unknown;\n label?: string;\n min?: number;\n max?: number;\n step?: number;\n options?: unknown[];\n}>;\n\nexport interface SandboxContext {\n mount: HTMLElement;\n params: Record<string, unknown>;\n exports: {\n setFilename: (name: string) => void;\n useDefaultCanvasCapture: (use: boolean) => void;\n captureImage: () => Promise<Blob>;\n };\n environment: {\n onResize: (callback: () => void) => () => void;\n window: Window;\n document: Document;\n };\n controls: null;\n}\n\ninterface SandboxConfig {\n controls?: {\n definitions?: ControlDefinitions;\n options?: { title?: string };\n };\n exportWidget?: {\n filename?: string;\n useCanvasCapture?: boolean;\n enabled?: boolean;\n };\n setup: (context: SandboxContext) => (() => void) | void;\n}\n\n// Store for the experience definition\nlet __experienceDef__: SandboxConfig | null = null;\n\n/**\n * createSandbox - Export version\n * Instead of connecting to studio, registers a web component\n */\nexport async function createSandbox(config: SandboxConfig): Promise<void> {\n __experienceDef__ = config;\n\n // Create the web component\n class HypertoolElement extends HTMLElement {\n private _mount: HTMLElement | null = null;\n private _controlsContainer: HTMLElement | null = null;\n private _cleanup: (() => void) | void = undefined;\n private _params: Record<string, unknown> = {};\n private _pane: any = null;\n\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n }\n\n connectedCallback() {\n if (!this.shadowRoot || !__experienceDef__) return;\n\n // Setup styles (includes theme CSS when controls enabled)\n const style = document.createElement('style');\n style.textContent = \\`\n \\${__SHOW_CONTROLS__ && typeof __THEME_CSS__ !== 'undefined' ? __THEME_CSS__ : ''}\n :host { display: block; width: 100%; height: 100%; }\n .mount {\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n background: \\${__BACKGROUND__};\n }\n ${controlsContainerStyle}\n \\`;\n this.shadowRoot.appendChild(style);\n\n // Create mount\n this._mount = document.createElement('div');\n this._mount.className = 'mount';\n this.shadowRoot.appendChild(this._mount);\n\n // Create controls container if enabled\n if (__SHOW_CONTROLS__) {\n this._controlsContainer = document.createElement('div');\n this._controlsContainer.className = 'controls-container';\n this.shadowRoot.appendChild(this._controlsContainer);\n }\n\n // Initialize params: baked params > HTML attributes > definition defaults\n const defs = __experienceDef__.controls?.definitions || {};\n for (const [key, def] of Object.entries(defs)) {\n // Priority: HTML attribute > baked param > definition default\n if (this.getAttribute(key) !== null) {\n this._params[key] = this._parseAttr(key, this.getAttribute(key)!, def);\n } else if (key in __BAKED_PARAMS__) {\n this._params[key] = __BAKED_PARAMS__[key];\n } else {\n this._params[key] = def.value;\n }\n }\n\n // Make params reactive\n const self = this;\n this._params = new Proxy(this._params, {\n set: (target, prop, value) => {\n const previous = target[prop as string];\n target[prop as string] = value;\n if (previous !== value) {\n self.dispatchEvent(new CustomEvent('paramchange', {\n detail: { key: prop, value, previousValue: previous },\n bubbles: true,\n }));\n }\n return true;\n }\n });\n\n // Create context\n const context: SandboxContext = {\n mount: this._mount,\n params: this._params,\n exports: {\n setFilename: () => {},\n useDefaultCanvasCapture: () => {},\n captureImage: async () => {\n const canvas = this._mount?.querySelector('canvas');\n if (!canvas) throw new Error('No canvas found');\n return new Promise((res, rej) => {\n canvas.toBlob(b => b ? res(b) : rej(new Error('Failed to capture')));\n });\n },\n },\n environment: {\n onResize: (callback) => {\n const ro = new ResizeObserver(() => callback());\n ro.observe(this._mount!);\n return () => ro.disconnect();\n },\n window,\n document,\n },\n controls: null,\n };\n\n // Run setup\n this._cleanup = __experienceDef__.setup(context);\n\n // Setup controls if enabled (async - loads Tweakpane from CDN)\n if (__SHOW_CONTROLS__ && this._controlsContainer && typeof __createControls__ === 'function') {\n __createControls__(this._params, defs, this._controlsContainer, this.shadowRoot!).then((pane: any) => {\n this._pane = pane;\n if (this._pane && typeof __setupDraggable__ === 'function') {\n __setupDraggable__(this._controlsContainer, this._pane);\n }\n });\n }\n\n // Dispatch ready event\n this.dispatchEvent(new CustomEvent('ready', { bubbles: true }));\n }\n\n disconnectedCallback() {\n if (this._cleanup) this._cleanup();\n if (this._pane?.dispose) this._pane.dispose();\n }\n\n private _parseAttr(key: string, value: string, def: { type: string }): unknown {\n switch (def.type) {\n case 'number': return parseFloat(value);\n case 'boolean': return value === 'true' || value === '';\n default: return value;\n }\n }\n\n // Public API\n setParam(key: string, value: unknown) {\n this._params[key] = value;\n this._pane?.refresh();\n }\n setParams(params: Record<string, unknown>) {\n for (const [k, v] of Object.entries(params)) this._params[k] = v;\n this._pane?.refresh();\n }\n getParams() { return { ...this._params }; }\n getParamDefs() { return __experienceDef__?.controls?.definitions || {}; }\n }\n\n // Register the component\n if (!customElements.get('${tagName}')) {\n customElements.define('${tagName}', HypertoolElement);\n }\n}\n`;\n}\n\n/**\n * Generate HTML with external script reference\n */\nexport function generateExportHtml(tagName: string): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${tagName}</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html, body { width: 100%; height: 100%; overflow: hidden; background: #0a0a0a; }\n ${tagName} { display: block; width: 100%; height: 100%; }\n </style>\n</head>\n<body>\n <${tagName}></${tagName}>\n <script src=\"./${tagName}.js\"></script>\n</body>\n</html>`;\n}\n\n/**\n * Generate HTML with inline script (for preview)\n * Includes permissive CSP for external library loading\n */\nexport function generatePreviewHtml(tagName: string, code: string): string {\n const cdnHosts = [\n 'https://cdnjs.cloudflare.com',\n 'https://cdn.jsdelivr.net',\n 'https://unpkg.com',\n 'https://esm.sh',\n 'https://cdn.skypack.dev',\n ].join(' ');\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'self' blob: data: ${cdnHosts}; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: data: ${cdnHosts}; style-src 'self' 'unsafe-inline' ${cdnHosts}; img-src 'self' blob: data: *; media-src 'self' blob: data: *; connect-src *; font-src 'self' data: ${cdnHosts};\">\n <title>${tagName}</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html, body { width: 100%; height: 100%; overflow: hidden; background: #0a0a0a; }\n ${tagName} { display: block; width: 100%; height: 100%; }\n </style>\n</head>\n<body>\n <${tagName}></${tagName}>\n <script>\n${code}\n </script>\n</body>\n</html>`;\n}\n\n/**\n * Generate README for the exported component\n */\nexport function generateExportReadme(tagName: string, className: string): string {\n return `# ${className}\n\n## Usage\n\n\\`\\`\\`html\n<script src=\"${tagName}.js\"></script>\n<${tagName}></${tagName}>\n\\`\\`\\`\n\n## API\n\n\\`\\`\\`javascript\nconst el = document.querySelector('${tagName}');\n\n// Set parameters\nel.setParam('paramName', value);\nel.setParams({ param1: value1, param2: value2 });\n\n// Get current parameters\nconst params = el.getParams();\n\n// Get parameter definitions\nconst defs = el.getParamDefs();\n\\`\\`\\`\n\n## Events\n\n\\`\\`\\`javascript\nel.addEventListener('ready', () => console.log('Ready!'));\nel.addEventListener('paramchange', (e) => {\n console.log('Param changed:', e.detail.key, e.detail.value);\n});\n\\`\\`\\`\n\n## Files\n\n- \\`${tagName}.js\\` - The bundled component\n- \\`index.html\\` - Example page (loads external JS)\n- \\`preview.html\\` - Standalone preview (inline JS)\n`;\n}\n\n/**\n * Generate TypeScript type definitions\n */\nexport function generateExportTypes(tagName: string, className: string): string {\n return `export interface ${className}Element extends HTMLElement {\n setParam(key: string, value: unknown): void;\n setParams(params: Record<string, unknown>): void;\n getParams(): Record<string, unknown>;\n getParamDefs(): Record<string, { type: string; value: unknown; [key: string]: unknown }>;\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n '${tagName}': ${className}Element;\n }\n}\n`;\n}\n"
9
9
  ],
10
10
  "mappings": "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",
11
- "debugId": "8BED4B1E577CB55764756E2164756E21",
11
+ "debugId": "70609385B33828B264756E2164756E21",
12
12
  "names": []
13
13
  }
@@ -488,7 +488,7 @@ function __setupDraggable__(container, pane) {
488
488
  }
489
489
  `}function u(q){let{tagName:z,currentParams:A={},showControls:B=!1,controlsTitle:M,background:R="transparent"}=q,O=b(A),L=M||z.split("-").map((Z)=>Z.charAt(0).toUpperCase()+Z.slice(1)).join(" "),K=B?h(L):"",U=B?y():"",Q=B?'<div class="controls-container"></div>':"";return`
490
490
  // Export-friendly HyperTool Runtime
491
- // Generated by @hypertool/sdk
491
+ // Generated by @hypertools/sdk
492
492
  // Controls: ${B?"enabled":"disabled"}
493
493
 
494
494
  // Baked-in current params from export
@@ -797,4 +797,4 @@ declare global {
797
797
  }
798
798
  `}export{w as wrapSetupCode,x as generateWebComponent,H as generateTypeDefinitions,g as generatePreviewHtml,N as generateExportTypes,u as generateExportRuntime,c as generateExportReadme,d as generateExportHtml,P as generateBundle,k as extractParamDefs};
799
799
 
800
- //# debugId=B1C37420ECCD274264756E2164756E21
800
+ //# debugId=BBAF843355D811CE64756E2164756E21
@@ -5,9 +5,9 @@
5
5
  "/**\n * Bundle generation using esbuild\n */\n\nexport interface BundleOptions {\n /** Entry file path or inline code */\n entry: string | { code: string; filename?: string };\n\n /** Additional files for bundling */\n files?: Record<string, string>;\n\n /** Output format (default: 'iife') */\n format?: 'iife' | 'esm' | 'cjs';\n\n /** Minify output (default: true) */\n minify?: boolean;\n\n /** Generate source maps */\n sourcemap?: boolean;\n\n /** Global name for IIFE format */\n globalName?: string;\n\n /** External packages to exclude from bundle */\n external?: string[];\n\n /** Target environment (default: 'browser') */\n target?: 'browser' | 'node';\n\n /** Define replacements */\n define?: Record<string, string>;\n}\n\nexport interface BundleResult {\n /** Bundled code */\n code: string;\n\n /** Source map (if requested) */\n map?: string;\n\n /** Build warnings */\n warnings: string[];\n\n /** Output size in bytes */\n size: number;\n}\n\n/**\n * Bundle code using esbuild\n *\n * This function is designed to run in Node.js/Bun server environment\n * (e.g., backend export service)\n */\nexport async function generateBundle(options: BundleOptions): Promise<BundleResult> {\n // Dynamic import esbuild (only available server-side)\n let esbuild: typeof import('esbuild');\n try {\n esbuild = await import('esbuild');\n } catch {\n throw new Error(\n 'esbuild is not available. This function is meant to run server-side.'\n );\n }\n\n const { mkdtemp, writeFile, readFile, rm, mkdir } = await import('fs/promises');\n const { tmpdir } = await import('os');\n const { join, dirname } = await import('path');\n\n const tempDir = await mkdtemp(join(tmpdir(), 'hypertool-bundle-'));\n\n try {\n // Write files to temp directory\n if (options.files) {\n for (const [filePath, content] of Object.entries(options.files)) {\n const fullPath = join(tempDir, filePath.replace(/^\\//, ''));\n const dir = dirname(fullPath);\n await mkdir(dir, { recursive: true });\n await writeFile(fullPath, content);\n }\n }\n\n // Determine entry\n let entryPath: string;\n if (typeof options.entry === 'string') {\n entryPath = join(tempDir, options.entry.replace(/^\\//, ''));\n } else {\n const filename = options.entry.filename ?? 'entry.ts';\n entryPath = join(tempDir, filename);\n await writeFile(entryPath, options.entry.code);\n }\n\n // Bundle with esbuild\n const result = await esbuild.build({\n entryPoints: [entryPath],\n bundle: true,\n outfile: join(tempDir, 'out.js'),\n platform: options.target === 'node' ? 'node' : 'browser',\n format: options.format ?? 'iife',\n minify: options.minify ?? true,\n sourcemap: options.sourcemap ? 'external' : false,\n globalName: options.globalName,\n external: options.external,\n define: options.define,\n write: true,\n metafile: true,\n });\n\n // Read output\n const code = await readFile(join(tempDir, 'out.js'), 'utf-8');\n let map: string | undefined;\n\n if (options.sourcemap) {\n try {\n map = await readFile(join(tempDir, 'out.js.map'), 'utf-8');\n } catch {\n // Source map might not exist\n }\n }\n\n return {\n code,\n map,\n warnings: result.warnings.map((w: { text: string }) => w.text),\n size: Buffer.byteLength(code, 'utf-8'),\n };\n } finally {\n // Cleanup\n await rm(tempDir, { recursive: true, force: true });\n }\n}\n\n/**\n * Extract param definitions from code using regex\n */\nexport function extractParamDefs(\n code: string\n): Record<string, { type: string; value: unknown }> | null {\n // Match export const paramDefs = { ... }\n const match = code.match(\n /export\\s+(?:const|let|var)\\s+(?:paramDefs|controlDefinitions)\\s*=\\s*(\\{[\\s\\S]*?\\});?/\n );\n\n if (!match) return null;\n\n try {\n // This is a simplified extraction - in production you'd use AST parsing\n // For now, we'll return null and let the backend handle it\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Wrap user code to expose setup function globally\n */\nexport function wrapSetupCode(code: string): string {\n return `\n${code}\n\n// Expose setup function globally for web component\nif (typeof setup === 'function') {\n window.__hypertool_setup__ = setup;\n}\nif (typeof paramDefs !== 'undefined') {\n window.__hypertool_paramDefs__ = paramDefs;\n}\n`;\n}\n",
6
6
  "/**\n * Web component generator for exports\n */\n\nimport type { ParamDefinitions } from '../../core/ParamStore';\n\nexport interface WebComponentOptions {\n /** Component tag name (e.g., 'my-experience') */\n tagName: string;\n\n /** Class name (e.g., 'MyExperience') */\n className: string;\n\n /** Bundled experience code */\n bundledCode: string;\n\n /** Parameter definitions */\n paramDefs: ParamDefinitions;\n\n /** Current parameter values */\n currentParams?: Record<string, unknown>;\n\n /** Include Tweakpane controls */\n showControls?: boolean;\n\n /** Background color */\n background?: string;\n}\n\n/**\n * Escape string for safe embedding in JavaScript\n */\nfunction escapeForJS(str: string): string {\n return str\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/`/g, '\\\\`')\n .replace(/\\$/g, '\\\\$')\n .replace(/<\\/script>/gi, '<\\\\/script>');\n}\n\n/**\n * Generate a self-contained web component\n */\nexport function generateWebComponent(options: WebComponentOptions): string {\n const {\n tagName,\n className,\n bundledCode,\n paramDefs,\n currentParams = {},\n showControls = false,\n background = 'transparent',\n } = options;\n\n // Escape JSON for embedding\n const paramsJson = JSON.stringify(currentParams)\n .replace(/</g, '\\\\u003c')\n .replace(/>/g, '\\\\u003e');\n\n const defsJson = JSON.stringify(paramDefs)\n .replace(/</g, '\\\\u003c')\n .replace(/>/g, '\\\\u003e');\n\n const controlsScript = showControls\n ? `\n // Load Tweakpane for controls\n async function loadTweakpane() {\n if (window.Tweakpane) return window.Tweakpane;\n return new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.src = 'https://cdn.jsdelivr.net/npm/tweakpane@4.0.5/dist/tweakpane.min.js';\n script.onload = () => resolve(window.Tweakpane);\n script.onerror = reject;\n document.head.appendChild(script);\n });\n }\n\n async function createControls(params, defs, container) {\n try {\n const Tweakpane = await loadTweakpane();\n const pane = new Tweakpane.Pane({\n title: '${className} Controls',\n container,\n });\n\n for (const [key, def] of Object.entries(defs)) {\n const config = { label: def.label || key };\n if (def.type === 'number') {\n if (def.min !== undefined) config.min = def.min;\n if (def.max !== undefined) config.max = def.max;\n if (def.step !== undefined) config.step = def.step;\n }\n if (def.type === 'select' && def.options) {\n config.options = def.options.reduce((acc, opt) => {\n const val = typeof opt === 'object' ? opt.value : opt;\n const label = typeof opt === 'object' ? opt.label : opt;\n acc[label] = val;\n return acc;\n }, {});\n }\n try {\n pane.addBinding(params, key, config);\n } catch (e) {\n console.warn('Failed to add control:', key, e);\n }\n }\n\n return pane;\n } catch (e) {\n console.warn('Failed to load Tweakpane:', e);\n return null;\n }\n }\n `\n : '';\n\n return `\n/**\n * ${className} Web Component\n * Generated by HyperTool SDK\n */\n\n(function() {\n 'use strict';\n\n const __PARAMS__ = ${paramsJson};\n const __PARAM_DEFS__ = ${defsJson};\n const __SHOW_CONTROLS__ = ${showControls};\n const __BACKGROUND__ = '${background}';\n\n ${controlsScript}\n\n class ${className}Element extends HTMLElement {\n #mount = null;\n #params = null;\n #cleanup = null;\n #resizeObserver = null;\n #pane = null;\n #isReady = false;\n\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n }\n\n static get observedAttributes() {\n return ['autoplay', 'background', ...Object.keys(__PARAM_DEFS__)];\n }\n\n connectedCallback() {\n this.#render();\n this.#initialize();\n }\n\n disconnectedCallback() {\n this.#destroy();\n }\n\n attributeChangedCallback(name, oldValue, newValue) {\n if (oldValue === newValue) return;\n\n if (name === 'background') {\n if (this.#mount) {\n this.#mount.style.background = newValue || __BACKGROUND__;\n }\n return;\n }\n\n if (name in __PARAM_DEFS__ && this.#params) {\n this.#params[name] = this.#parseAttribute(newValue, __PARAM_DEFS__[name].type);\n }\n }\n\n #render() {\n this.shadowRoot.innerHTML = \\`\n <style>\n :host {\n display: block;\n width: 100%;\n height: 100%;\n position: relative;\n }\n .mount {\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n background: \\${__BACKGROUND__};\n }\n .controls-container {\n position: absolute;\n top: 8px;\n right: 8px;\n z-index: 1000;\n }\n </style>\n <div class=\"mount\"></div>\n \\${__SHOW_CONTROLS__ ? '<div class=\"controls-container\"></div>' : ''}\n \\`;\n\n this.#mount = this.shadowRoot.querySelector('.mount');\n }\n\n async #initialize() {\n // Initialize params from defaults + attributes\n this.#params = { ...__PARAMS__ };\n\n for (const [key, def] of Object.entries(__PARAM_DEFS__)) {\n const attrValue = this.getAttribute(key);\n if (attrValue !== null) {\n this.#params[key] = this.#parseAttribute(attrValue, def.type);\n }\n }\n\n // Create reactive proxy\n const self = this;\n this.#params = new Proxy(this.#params, {\n set(target, prop, value) {\n const previous = target[prop];\n target[prop] = value;\n if (previous !== value) {\n self.dispatchEvent(new CustomEvent('paramchange', {\n detail: { key: prop, value, previousValue: previous },\n bubbles: true,\n }));\n }\n return true;\n }\n });\n\n // Setup resize observer\n this.#resizeObserver = new ResizeObserver(() => {\n this.dispatchEvent(new CustomEvent('resize', {\n detail: {\n width: this.#mount.clientWidth,\n height: this.#mount.clientHeight\n },\n bubbles: true,\n }));\n });\n this.#resizeObserver.observe(this.#mount);\n\n // Setup controls if enabled\n if (__SHOW_CONTROLS__) {\n const controlsContainer = this.shadowRoot.querySelector('.controls-container');\n if (controlsContainer && typeof createControls === 'function') {\n this.#pane = await createControls(this.#params, __PARAM_DEFS__, controlsContainer);\n }\n }\n\n // Run experience setup\n try {\n const context = {\n mount: this.#mount,\n params: this.#params,\n exports: {\n captureImage: (format) => this.captureImage(format),\n setFilename: () => {},\n useDefaultCanvasCapture: () => {},\n },\n environment: {\n window,\n document,\n onResize: (cb) => {\n const handler = () => cb(this.#mount.clientWidth, this.#mount.clientHeight);\n handler();\n const ro = new ResizeObserver(handler);\n ro.observe(this.#mount);\n return () => ro.disconnect();\n },\n },\n registerObject: () => {},\n };\n\n // Execute bundled code\n ${escapeForJS(bundledCode)}\n\n // Call setup if defined\n if (typeof window.__hypertool_setup__ === 'function') {\n this.#cleanup = await window.__hypertool_setup__(context);\n } else if (typeof setup === 'function') {\n this.#cleanup = await setup(context);\n }\n\n this.#isReady = true;\n this.dispatchEvent(new CustomEvent('ready', { bubbles: true }));\n\n // Autoplay by default\n if (this.getAttribute('autoplay') !== 'false') {\n this.play();\n }\n } catch (error) {\n console.error('[${className}] Setup error:', error);\n this.dispatchEvent(new CustomEvent('error', {\n detail: { error },\n bubbles: true,\n }));\n }\n }\n\n #destroy() {\n if (this.#cleanup && typeof this.#cleanup === 'function') {\n try {\n this.#cleanup();\n } catch (e) {\n console.error('[${className}] Cleanup error:', e);\n }\n }\n this.#resizeObserver?.disconnect();\n this.#pane?.dispose();\n this.#cleanup = null;\n this.#params = null;\n this.#isReady = false;\n }\n\n #parseAttribute(value, type) {\n switch (type) {\n case 'number':\n return parseFloat(value);\n case 'boolean':\n return value === 'true' || value === '';\n case 'color':\n return value;\n default:\n return value;\n }\n }\n\n // Public API\n get isReady() { return this.#isReady; }\n\n setParam(key, value) {\n if (this.#params && key in __PARAM_DEFS__) {\n this.#params[key] = value;\n this.#pane?.refresh();\n }\n }\n\n setParams(params) {\n if (!this.#params) return;\n for (const [key, value] of Object.entries(params)) {\n if (key in __PARAM_DEFS__) {\n this.#params[key] = value;\n }\n }\n this.#pane?.refresh();\n }\n\n getParams() {\n return this.#params ? { ...this.#params } : {};\n }\n\n getParamDefs() {\n return __PARAM_DEFS__;\n }\n\n async captureImage(format = 'png') {\n const canvas = this.#mount?.querySelector('canvas');\n if (!canvas) return null;\n\n return new Promise((resolve) => {\n canvas.toBlob(resolve, \\`image/\\${format}\\`, format === 'jpeg' ? 0.92 : undefined);\n });\n }\n\n play() {\n this.dispatchEvent(new CustomEvent('play', { bubbles: true }));\n }\n\n pause() {\n this.dispatchEvent(new CustomEvent('pause', { bubbles: true }));\n }\n }\n\n // Register custom element\n if (!customElements.get('${tagName}')) {\n customElements.define('${tagName}', ${className}Element);\n }\n})();\n`;\n}\n\n/**\n * Generate TypeScript definitions for the web component\n */\nexport function generateTypeDefinitions(options: WebComponentOptions): string {\n const { className, tagName, paramDefs } = options;\n\n const paramTypes = Object.entries(paramDefs)\n .map(([key, def]) => {\n let type: string;\n switch (def.type) {\n case 'number':\n type = 'number';\n break;\n case 'boolean':\n type = 'boolean';\n break;\n case 'color':\n case 'string':\n type = 'string';\n break;\n case 'select':\n if (def.options) {\n type = def.options\n .map((opt) => `'${typeof opt === 'object' ? opt.value : opt}'`)\n .join(' | ');\n } else {\n type = 'string';\n }\n break;\n default:\n type = 'unknown';\n }\n return ` ${key}?: ${type};`;\n })\n .join('\\n');\n\n return `/**\n * TypeScript definitions for ${className}\n * Generated by HyperTool SDK\n */\n\ndeclare namespace JSX {\n interface IntrinsicElements {\n '${tagName}': ${className}Attributes;\n }\n}\n\ninterface ${className}Attributes {\n autoplay?: boolean | 'true' | 'false';\n background?: string;\n${paramTypes}\n}\n\ninterface ${className}Element extends HTMLElement {\n readonly isReady: boolean;\n setParam(key: string, value: unknown): void;\n setParams(params: Record<string, unknown>): void;\n getParams(): Record<string, unknown>;\n getParamDefs(): Record<string, { type: string; value: unknown; [key: string]: unknown }>;\n captureImage(format?: 'png' | 'jpeg' | 'webp'): Promise<Blob | null>;\n play(): void;\n pause(): void;\n}\n\ninterface ${className}Events {\n ready: CustomEvent<void>;\n error: CustomEvent<{ error: Error }>;\n paramchange: CustomEvent<{ key: string; value: unknown; previousValue: unknown }>;\n resize: CustomEvent<{ width: number; height: number }>;\n play: CustomEvent<void>;\n pause: CustomEvent<void>;\n}\n`;\n}\n",
7
7
  "/**\n * Design tokens inherited from the parent Studio application\n * These CSS variables are defined in src/app/globals.css\n */\nexport const studioTheme = {\n // CSS variables that should be available in the iframe\n cssVariables: {\n '--bg': '#0a0e14',\n '--bg-elevated': '#0f1419',\n '--muted': '#1a2332',\n '--text': '#e6edf3',\n '--text-secondary': '#8b949e',\n '--accent': '#58d5ff',\n '--accent-2': '#42a5cc',\n '--border': '#21262d',\n '--border-hover': '#30363d',\n '--success': '#3fb950',\n '--error': '#f85149',\n\n // Hypertool-specific variables\n '--ht-text-color-main-200': '#00ffd4',\n '--ht-text-color-main-300': '#6ff3dd',\n '--ht-text-color-main-500': '#4b8e85',\n '--ht-text-color-grey-200': '#8c8d8f',\n\n '--ht-bg-color-200': '#8c8d8f',\n '--ht-bg-color-200-active': '#7a7b7d',\n '--ht-bg-color-200-focus': '#7e7f81',\n '--ht-bg-color-200-hover': '#828384',\n\n '--ht-bg-color-300': '#5e6068',\n '--ht-bg-color-300-active': '#4c4e56',\n '--ht-bg-color-300-focus': '#55575f',\n '--ht-bg-color-300-hover': '#595b63',\n\n '--ht-bg-color-400': '#cccccc',\n '--ht-bg-color-400-active': '#b3b3b3',\n '--ht-bg-color-400-focus': '#c0c0c0',\n '--ht-bg-color-400-hover': '#c6c6c6',\n\n '--ht-bg-color-500': '#1C1D20',\n '--ht-bg-color-500-active': '#0a0b0e',\n '--ht-bg-color-500-focus': '#131417',\n '--ht-bg-color-500-hover': '#16171a',\n\n '--ht-bg-color-600': '#37383D',\n '--ht-bg-color-600-active': '#25262b',\n '--ht-bg-color-600-focus': '#2e2f34',\n '--ht-bg-color-600-hover': '#323338',\n\n '--ht-bg-color-700': '#0f1419',\n '--ht-bg-color-700-active': '#16171c',\n '--ht-bg-color-700-focus': '#1f2025',\n '--ht-bg-color-700-hover': '#232429',\n\n '--ht-border-radius': '8px',\n\n '--ht-base-font-family-base': '\"HydrogenType400\", ui-sans-serif, system-ui, sans-serif',\n '--ht-base-font-family-display': '\"Departure Mono\", Roboto Mono, Source Code Pro, Menlo, Courier, monospace',\n '--ht-base-font-family-sans': '\"Routed Gothic\", ui-sans-serif, system-ui, sans-serif',\n '--ht-base-font-family-mono': '\"Routed Gothic Narrow\", ui-monospace, SFMono-Regular, SF Mono, Consolas, Liberation Mono, Menlo, monospace',\n },\n\n // Tweakpane-specific theme variables\n tweakpane: {\n '--tp-base-background-color': 'var(--ht-bg-color-700)',\n '--tp-base-shadow-color': 'hsla(0, 0%, 0%, 0.2)',\n\n '--tp-container-background-color': 'var(--ht-bg-color-600)',\n '--tp-container-background-color-active': 'var(--ht-bg-color-600-active)',\n '--tp-container-background-color-focus': 'var(--ht-bg-color-600-focus)',\n '--tp-container-background-color-hover': 'var(--ht-bg-color-600-hover)',\n '--tp-container-foreground-color': 'var(--ht-text-color-main-300)',\n\n '--tp-button-background-color': 'var(--ht-bg-color-400)',\n '--tp-button-background-color-active': 'color-mix(in srgb, var(--ht-bg-color-400-active) 80%, white)',\n '--tp-button-background-color-focus': 'color-mix(in srgb, var(--ht-bg-color-400-focus) 85%, white)',\n '--tp-button-background-color-hover': 'color-mix(in srgb, var(--ht-bg-color-400-hover) 90%, white)',\n '--tp-button-foreground-color': 'var(--ht-text-color-main-300)',\n\n '--tp-groove-foreground-color': 'var(--ht-bg-color-300)',\n\n '--tp-input-background-color': 'var(--ht-bg-color-500)',\n '--tp-input-background-color-active': 'var(--ht-bg-color-500-active)',\n '--tp-input-background-color-focus': 'var(--ht-bg-color-500-focus)',\n '--tp-input-background-color-hover': 'var(--ht-bg-color-500-hover)',\n '--tp-input-foreground-color': 'var(--ht-text-color-main-300)',\n\n '--tp-label-foreground-color': 'var(--ht-text-color-main-300)',\n\n '--tp-monitor-background-color': 'var(--ht-bg-color-500)',\n '--tp-monitor-foreground-color': 'var(--ht-text-color-main-300)',\n }\n};\n\n/**\n * Inject CSS variables into the document\n */\nexport function injectThemeVariables() {\n const style = document.createElement('style');\n style.id = 'hypertool-theme';\n\n const cssVars = Object.entries({\n ...studioTheme.cssVariables,\n ...studioTheme.tweakpane\n })\n .map(([key, value]) => ` ${key}: ${value};`)\n .join('\\n');\n\n style.textContent = `\n body {\n margin: 0;\n padding: 0;\n }\n\n:root {\n${cssVars}\n}\n\n/* Controls panel positioning and styling */\n.controls-container {\n position: fixed;\n top: 8px;\n right: 8px;\n z-index: 1000;\n max-height: calc(100vh - 16px);\n overflow-y: auto;\n}\n\n/* Ensure Tweakpane pane has background */\n.tp-dfwv {\n background-color: var(--tp-base-background-color, #0f1419) !important;\n}\n`;\n\n document.head.appendChild(style);\n}",
8
- "/**\n * Export Runtime Generator\n *\n * Generates the __hypertool__ runtime replacement for standalone exports.\n * This provides the same API as the studio runtime but creates a web component\n * instead of connecting to studio controls.\n *\n * Used by both export and preview endpoints.\n */\n\nexport interface ExportRuntimeOptions {\n /** Component tag name (kebab-case, e.g., 'my-experience') */\n tagName: string;\n\n /** Current parameter values to bake into the export */\n currentParams?: Record<string, unknown>;\n\n /** Whether to include Tweakpane controls panel */\n showControls?: boolean;\n\n /** Controls panel title (defaults to tagName) */\n controlsTitle?: string;\n\n /** Background color for the component */\n background?: string;\n}\n\n/**\n * Safely serialize params for injection into JavaScript code\n * Escapes characters that could break script context\n */\nfunction safeJsonStringify(obj: unknown): string {\n const json = JSON.stringify(obj);\n return json\n .replace(/</g, '\\\\u003c') // Prevent </script> injection\n .replace(/>/g, '\\\\u003e') // Prevent tag injection\n .replace(/\\u2028/g, '\\\\u2028') // Line separator\n .replace(/\\u2029/g, '\\\\u2029'); // Paragraph separator\n}\n\n// Import theme from controls module - single source of truth\nimport { studioTheme } from '../controls/theme';\n\n/**\n * Generate theme CSS string from studioTheme object\n * Converts the theme variables to CSS that works in Shadow DOM\n */\nfunction generateThemeCSS(): string {\n const cssVars = Object.entries({\n ...studioTheme.cssVariables,\n ...studioTheme.tweakpane\n })\n .map(([key, value]) => ` ${key}: ${value};`)\n .join('\\n');\n\n return `\n/* Hypertool Theme - generated from studioTheme */\n:host {\n${cssVars}\n}\n\n/* Ensure Tweakpane uses our theme */\n.tp-dfwv {\n background-color: var(--tp-base-background-color) !important;\n border-radius: 8px;\n}\n`;\n}\n\n/**\n * Generate controls-related code (Tweakpane loaded from esm.sh CDN)\n */\nfunction generateControlsCode(controlsTitle: string): string {\n const themeCSS = generateThemeCSS();\n\n return `\n// Tweakpane loaded dynamically from esm.sh CDN at runtime\nlet __Pane__: any = null;\n\nasync function __loadTweakpane__(): Promise<void> {\n if (__Pane__) return;\n try {\n const module = await import('https://esm.sh/tweakpane@4');\n __Pane__ = module.Pane;\n } catch (e) {\n console.warn('[Controls] Failed to load Tweakpane from CDN:', e);\n }\n}\n\nconst __THEME_CSS__ = \\`${themeCSS}\\`;\n\nasync function __createControls__(params: Record<string, unknown>, defs: Record<string, any>, container: HTMLElement, shadowRoot: ShadowRoot) {\n await __loadTweakpane__();\n if (!__Pane__) return null;\n\n try {\n const pane = new __Pane__({\n title: '${controlsTitle}',\n container,\n });\n\n for (const [key, def] of Object.entries(defs)) {\n if (!def || def.type === 'folder') continue;\n\n const config: Record<string, unknown> = { label: def.label || key };\n\n if (def.type === 'number') {\n if (def.min !== undefined) config.min = def.min;\n if (def.max !== undefined) config.max = def.max;\n if (def.step !== undefined) config.step = def.step;\n }\n\n if (def.type === 'select' && def.options) {\n config.options = def.options.reduce((acc: Record<string, unknown>, opt: unknown) => {\n const val = typeof opt === 'object' && opt !== null ? (opt as any).value : opt;\n const label = typeof opt === 'object' && opt !== null ? (opt as any).label : String(opt);\n acc[label] = val;\n return acc;\n }, {});\n }\n\n try {\n pane.addBinding(params, key, config);\n } catch (e) {\n console.warn('[Controls] Failed to add binding for:', key, e);\n }\n }\n\n // Tweakpane injects CSS into document.head - copy it to shadow root for isolation\n setTimeout(() => {\n const tpStyles = document.querySelectorAll('style');\n tpStyles.forEach(style => {\n if (style.textContent?.includes('.tp-') || style.textContent?.includes('tp-dfwv')) {\n const clone = style.cloneNode(true) as HTMLStyleElement;\n shadowRoot.appendChild(clone);\n }\n });\n }, 0);\n\n return pane;\n } catch (e) {\n console.warn('[Controls] Failed to create controls:', e);\n return null;\n }\n}\n`;\n}\n\n/**\n * Generate draggable controls functionality\n */\nfunction generateDraggableCode(): string {\n return `\n// Make controls panel draggable\nfunction __setupDraggable__(container, pane) {\n if (!pane || !pane.element) return;\n\n const titleEl = pane.element.querySelector('.tp-rotv_t');\n if (!titleEl) return;\n\n let isDragging = false;\n let startX = 0;\n let startY = 0;\n let initialRight = 8;\n let initialTop = 8;\n\n titleEl.style.cursor = 'grab';\n\n titleEl.addEventListener('mousedown', (e) => {\n if (e.button !== 0) return;\n isDragging = true;\n startX = e.clientX;\n startY = e.clientY;\n\n const style = getComputedStyle(container);\n initialRight = parseInt(style.right) || 8;\n initialTop = parseInt(style.top) || 8;\n\n titleEl.style.cursor = 'grabbing';\n e.preventDefault();\n });\n\n document.addEventListener('mousemove', (e) => {\n if (!isDragging) return;\n\n const deltaX = e.clientX - startX;\n const deltaY = e.clientY - startY;\n\n container.style.right = Math.max(0, initialRight - deltaX) + 'px';\n container.style.top = Math.max(0, initialTop + deltaY) + 'px';\n });\n\n document.addEventListener('mouseup', () => {\n if (isDragging) {\n isDragging = false;\n titleEl.style.cursor = 'grab';\n }\n });\n}\n`;\n}\n\n/**\n * Generate the export-friendly __hypertool__ runtime\n *\n * This provides the same API as the studio runtime but creates a web component\n * instead of connecting to studio controls.\n */\nexport function generateExportRuntime(options: ExportRuntimeOptions): string {\n const {\n tagName,\n currentParams = {},\n showControls = false,\n controlsTitle,\n background = 'transparent',\n } = options;\n\n const currentParamsJson = safeJsonStringify(currentParams);\n const title = controlsTitle || tagName.split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join(' ');\n\n const controlsCode = showControls ? generateControlsCode(title) : '';\n const draggableCode = showControls ? generateDraggableCode() : '';\n const controlsContainerHtml = showControls\n ? `<div class=\"controls-container\"></div>`\n : '';\n const controlsContainerStyle = showControls\n ? `\n .controls-container {\n position: absolute;\n top: 8px;\n right: 8px;\n z-index: 1000;\n max-height: calc(100% - 16px);\n overflow-y: auto;\n }\n /* Tweakpane theme overrides */\n .controls-container .tp-dfwv {\n background-color: rgba(15, 20, 25, 0.95) !important;\n }\n `\n : '';\n\n return `\n// Export-friendly HyperTool Runtime\n// Generated by @hypertool/sdk\n// Controls: ${showControls ? 'enabled' : 'disabled'}\n\n// Baked-in current params from export\nconst __BAKED_PARAMS__ = ${currentParamsJson};\nconst __SHOW_CONTROLS__ = ${showControls};\nconst __BACKGROUND__ = '${background}';\n\n${controlsCode}\n${draggableCode}\n\nexport type ControlDefinitions = Record<string, {\n type: string;\n value: unknown;\n label?: string;\n min?: number;\n max?: number;\n step?: number;\n options?: unknown[];\n}>;\n\nexport interface SandboxContext {\n mount: HTMLElement;\n params: Record<string, unknown>;\n exports: {\n setFilename: (name: string) => void;\n useDefaultCanvasCapture: (use: boolean) => void;\n captureImage: () => Promise<Blob>;\n };\n environment: {\n onResize: (callback: () => void) => () => void;\n window: Window;\n document: Document;\n };\n controls: null;\n}\n\ninterface SandboxConfig {\n controls?: {\n definitions?: ControlDefinitions;\n options?: { title?: string };\n };\n exportWidget?: {\n filename?: string;\n useCanvasCapture?: boolean;\n enabled?: boolean;\n };\n setup: (context: SandboxContext) => (() => void) | void;\n}\n\n// Store for the experience definition\nlet __experienceDef__: SandboxConfig | null = null;\n\n/**\n * createSandbox - Export version\n * Instead of connecting to studio, registers a web component\n */\nexport async function createSandbox(config: SandboxConfig): Promise<void> {\n __experienceDef__ = config;\n\n // Create the web component\n class HypertoolElement extends HTMLElement {\n private _mount: HTMLElement | null = null;\n private _controlsContainer: HTMLElement | null = null;\n private _cleanup: (() => void) | void = undefined;\n private _params: Record<string, unknown> = {};\n private _pane: any = null;\n\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n }\n\n connectedCallback() {\n if (!this.shadowRoot || !__experienceDef__) return;\n\n // Setup styles (includes theme CSS when controls enabled)\n const style = document.createElement('style');\n style.textContent = \\`\n \\${__SHOW_CONTROLS__ && typeof __THEME_CSS__ !== 'undefined' ? __THEME_CSS__ : ''}\n :host { display: block; width: 100%; height: 100%; }\n .mount {\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n background: \\${__BACKGROUND__};\n }\n ${controlsContainerStyle}\n \\`;\n this.shadowRoot.appendChild(style);\n\n // Create mount\n this._mount = document.createElement('div');\n this._mount.className = 'mount';\n this.shadowRoot.appendChild(this._mount);\n\n // Create controls container if enabled\n if (__SHOW_CONTROLS__) {\n this._controlsContainer = document.createElement('div');\n this._controlsContainer.className = 'controls-container';\n this.shadowRoot.appendChild(this._controlsContainer);\n }\n\n // Initialize params: baked params > HTML attributes > definition defaults\n const defs = __experienceDef__.controls?.definitions || {};\n for (const [key, def] of Object.entries(defs)) {\n // Priority: HTML attribute > baked param > definition default\n if (this.getAttribute(key) !== null) {\n this._params[key] = this._parseAttr(key, this.getAttribute(key)!, def);\n } else if (key in __BAKED_PARAMS__) {\n this._params[key] = __BAKED_PARAMS__[key];\n } else {\n this._params[key] = def.value;\n }\n }\n\n // Make params reactive\n const self = this;\n this._params = new Proxy(this._params, {\n set: (target, prop, value) => {\n const previous = target[prop as string];\n target[prop as string] = value;\n if (previous !== value) {\n self.dispatchEvent(new CustomEvent('paramchange', {\n detail: { key: prop, value, previousValue: previous },\n bubbles: true,\n }));\n }\n return true;\n }\n });\n\n // Create context\n const context: SandboxContext = {\n mount: this._mount,\n params: this._params,\n exports: {\n setFilename: () => {},\n useDefaultCanvasCapture: () => {},\n captureImage: async () => {\n const canvas = this._mount?.querySelector('canvas');\n if (!canvas) throw new Error('No canvas found');\n return new Promise((res, rej) => {\n canvas.toBlob(b => b ? res(b) : rej(new Error('Failed to capture')));\n });\n },\n },\n environment: {\n onResize: (callback) => {\n const ro = new ResizeObserver(() => callback());\n ro.observe(this._mount!);\n return () => ro.disconnect();\n },\n window,\n document,\n },\n controls: null,\n };\n\n // Run setup\n this._cleanup = __experienceDef__.setup(context);\n\n // Setup controls if enabled (async - loads Tweakpane from CDN)\n if (__SHOW_CONTROLS__ && this._controlsContainer && typeof __createControls__ === 'function') {\n __createControls__(this._params, defs, this._controlsContainer, this.shadowRoot!).then((pane: any) => {\n this._pane = pane;\n if (this._pane && typeof __setupDraggable__ === 'function') {\n __setupDraggable__(this._controlsContainer, this._pane);\n }\n });\n }\n\n // Dispatch ready event\n this.dispatchEvent(new CustomEvent('ready', { bubbles: true }));\n }\n\n disconnectedCallback() {\n if (this._cleanup) this._cleanup();\n if (this._pane?.dispose) this._pane.dispose();\n }\n\n private _parseAttr(key: string, value: string, def: { type: string }): unknown {\n switch (def.type) {\n case 'number': return parseFloat(value);\n case 'boolean': return value === 'true' || value === '';\n default: return value;\n }\n }\n\n // Public API\n setParam(key: string, value: unknown) {\n this._params[key] = value;\n this._pane?.refresh();\n }\n setParams(params: Record<string, unknown>) {\n for (const [k, v] of Object.entries(params)) this._params[k] = v;\n this._pane?.refresh();\n }\n getParams() { return { ...this._params }; }\n getParamDefs() { return __experienceDef__?.controls?.definitions || {}; }\n }\n\n // Register the component\n if (!customElements.get('${tagName}')) {\n customElements.define('${tagName}', HypertoolElement);\n }\n}\n`;\n}\n\n/**\n * Generate HTML with external script reference\n */\nexport function generateExportHtml(tagName: string): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${tagName}</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html, body { width: 100%; height: 100%; overflow: hidden; background: #0a0a0a; }\n ${tagName} { display: block; width: 100%; height: 100%; }\n </style>\n</head>\n<body>\n <${tagName}></${tagName}>\n <script src=\"./${tagName}.js\"></script>\n</body>\n</html>`;\n}\n\n/**\n * Generate HTML with inline script (for preview)\n * Includes permissive CSP for external library loading\n */\nexport function generatePreviewHtml(tagName: string, code: string): string {\n const cdnHosts = [\n 'https://cdnjs.cloudflare.com',\n 'https://cdn.jsdelivr.net',\n 'https://unpkg.com',\n 'https://esm.sh',\n 'https://cdn.skypack.dev',\n ].join(' ');\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'self' blob: data: ${cdnHosts}; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: data: ${cdnHosts}; style-src 'self' 'unsafe-inline' ${cdnHosts}; img-src 'self' blob: data: *; media-src 'self' blob: data: *; connect-src *; font-src 'self' data: ${cdnHosts};\">\n <title>${tagName}</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html, body { width: 100%; height: 100%; overflow: hidden; background: #0a0a0a; }\n ${tagName} { display: block; width: 100%; height: 100%; }\n </style>\n</head>\n<body>\n <${tagName}></${tagName}>\n <script>\n${code}\n </script>\n</body>\n</html>`;\n}\n\n/**\n * Generate README for the exported component\n */\nexport function generateExportReadme(tagName: string, className: string): string {\n return `# ${className}\n\n## Usage\n\n\\`\\`\\`html\n<script src=\"${tagName}.js\"></script>\n<${tagName}></${tagName}>\n\\`\\`\\`\n\n## API\n\n\\`\\`\\`javascript\nconst el = document.querySelector('${tagName}');\n\n// Set parameters\nel.setParam('paramName', value);\nel.setParams({ param1: value1, param2: value2 });\n\n// Get current parameters\nconst params = el.getParams();\n\n// Get parameter definitions\nconst defs = el.getParamDefs();\n\\`\\`\\`\n\n## Events\n\n\\`\\`\\`javascript\nel.addEventListener('ready', () => console.log('Ready!'));\nel.addEventListener('paramchange', (e) => {\n console.log('Param changed:', e.detail.key, e.detail.value);\n});\n\\`\\`\\`\n\n## Files\n\n- \\`${tagName}.js\\` - The bundled component\n- \\`index.html\\` - Example page (loads external JS)\n- \\`preview.html\\` - Standalone preview (inline JS)\n`;\n}\n\n/**\n * Generate TypeScript type definitions\n */\nexport function generateExportTypes(tagName: string, className: string): string {\n return `export interface ${className}Element extends HTMLElement {\n setParam(key: string, value: unknown): void;\n setParams(params: Record<string, unknown>): void;\n getParams(): Record<string, unknown>;\n getParamDefs(): Record<string, { type: string; value: unknown; [key: string]: unknown }>;\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n '${tagName}': ${className}Element;\n }\n}\n`;\n}\n"
8
+ "/**\n * Export Runtime Generator\n *\n * Generates the __hypertool__ runtime replacement for standalone exports.\n * This provides the same API as the studio runtime but creates a web component\n * instead of connecting to studio controls.\n *\n * Used by both export and preview endpoints.\n */\n\nexport interface ExportRuntimeOptions {\n /** Component tag name (kebab-case, e.g., 'my-experience') */\n tagName: string;\n\n /** Current parameter values to bake into the export */\n currentParams?: Record<string, unknown>;\n\n /** Whether to include Tweakpane controls panel */\n showControls?: boolean;\n\n /** Controls panel title (defaults to tagName) */\n controlsTitle?: string;\n\n /** Background color for the component */\n background?: string;\n}\n\n/**\n * Safely serialize params for injection into JavaScript code\n * Escapes characters that could break script context\n */\nfunction safeJsonStringify(obj: unknown): string {\n const json = JSON.stringify(obj);\n return json\n .replace(/</g, '\\\\u003c') // Prevent </script> injection\n .replace(/>/g, '\\\\u003e') // Prevent tag injection\n .replace(/\\u2028/g, '\\\\u2028') // Line separator\n .replace(/\\u2029/g, '\\\\u2029'); // Paragraph separator\n}\n\n// Import theme from controls module - single source of truth\nimport { studioTheme } from '../controls/theme';\n\n/**\n * Generate theme CSS string from studioTheme object\n * Converts the theme variables to CSS that works in Shadow DOM\n */\nfunction generateThemeCSS(): string {\n const cssVars = Object.entries({\n ...studioTheme.cssVariables,\n ...studioTheme.tweakpane\n })\n .map(([key, value]) => ` ${key}: ${value};`)\n .join('\\n');\n\n return `\n/* Hypertool Theme - generated from studioTheme */\n:host {\n${cssVars}\n}\n\n/* Ensure Tweakpane uses our theme */\n.tp-dfwv {\n background-color: var(--tp-base-background-color) !important;\n border-radius: 8px;\n}\n`;\n}\n\n/**\n * Generate controls-related code (Tweakpane loaded from esm.sh CDN)\n */\nfunction generateControlsCode(controlsTitle: string): string {\n const themeCSS = generateThemeCSS();\n\n return `\n// Tweakpane loaded dynamically from esm.sh CDN at runtime\nlet __Pane__: any = null;\n\nasync function __loadTweakpane__(): Promise<void> {\n if (__Pane__) return;\n try {\n const module = await import('https://esm.sh/tweakpane@4');\n __Pane__ = module.Pane;\n } catch (e) {\n console.warn('[Controls] Failed to load Tweakpane from CDN:', e);\n }\n}\n\nconst __THEME_CSS__ = \\`${themeCSS}\\`;\n\nasync function __createControls__(params: Record<string, unknown>, defs: Record<string, any>, container: HTMLElement, shadowRoot: ShadowRoot) {\n await __loadTweakpane__();\n if (!__Pane__) return null;\n\n try {\n const pane = new __Pane__({\n title: '${controlsTitle}',\n container,\n });\n\n for (const [key, def] of Object.entries(defs)) {\n if (!def || def.type === 'folder') continue;\n\n const config: Record<string, unknown> = { label: def.label || key };\n\n if (def.type === 'number') {\n if (def.min !== undefined) config.min = def.min;\n if (def.max !== undefined) config.max = def.max;\n if (def.step !== undefined) config.step = def.step;\n }\n\n if (def.type === 'select' && def.options) {\n config.options = def.options.reduce((acc: Record<string, unknown>, opt: unknown) => {\n const val = typeof opt === 'object' && opt !== null ? (opt as any).value : opt;\n const label = typeof opt === 'object' && opt !== null ? (opt as any).label : String(opt);\n acc[label] = val;\n return acc;\n }, {});\n }\n\n try {\n pane.addBinding(params, key, config);\n } catch (e) {\n console.warn('[Controls] Failed to add binding for:', key, e);\n }\n }\n\n // Tweakpane injects CSS into document.head - copy it to shadow root for isolation\n setTimeout(() => {\n const tpStyles = document.querySelectorAll('style');\n tpStyles.forEach(style => {\n if (style.textContent?.includes('.tp-') || style.textContent?.includes('tp-dfwv')) {\n const clone = style.cloneNode(true) as HTMLStyleElement;\n shadowRoot.appendChild(clone);\n }\n });\n }, 0);\n\n return pane;\n } catch (e) {\n console.warn('[Controls] Failed to create controls:', e);\n return null;\n }\n}\n`;\n}\n\n/**\n * Generate draggable controls functionality\n */\nfunction generateDraggableCode(): string {\n return `\n// Make controls panel draggable\nfunction __setupDraggable__(container, pane) {\n if (!pane || !pane.element) return;\n\n const titleEl = pane.element.querySelector('.tp-rotv_t');\n if (!titleEl) return;\n\n let isDragging = false;\n let startX = 0;\n let startY = 0;\n let initialRight = 8;\n let initialTop = 8;\n\n titleEl.style.cursor = 'grab';\n\n titleEl.addEventListener('mousedown', (e) => {\n if (e.button !== 0) return;\n isDragging = true;\n startX = e.clientX;\n startY = e.clientY;\n\n const style = getComputedStyle(container);\n initialRight = parseInt(style.right) || 8;\n initialTop = parseInt(style.top) || 8;\n\n titleEl.style.cursor = 'grabbing';\n e.preventDefault();\n });\n\n document.addEventListener('mousemove', (e) => {\n if (!isDragging) return;\n\n const deltaX = e.clientX - startX;\n const deltaY = e.clientY - startY;\n\n container.style.right = Math.max(0, initialRight - deltaX) + 'px';\n container.style.top = Math.max(0, initialTop + deltaY) + 'px';\n });\n\n document.addEventListener('mouseup', () => {\n if (isDragging) {\n isDragging = false;\n titleEl.style.cursor = 'grab';\n }\n });\n}\n`;\n}\n\n/**\n * Generate the export-friendly __hypertool__ runtime\n *\n * This provides the same API as the studio runtime but creates a web component\n * instead of connecting to studio controls.\n */\nexport function generateExportRuntime(options: ExportRuntimeOptions): string {\n const {\n tagName,\n currentParams = {},\n showControls = false,\n controlsTitle,\n background = 'transparent',\n } = options;\n\n const currentParamsJson = safeJsonStringify(currentParams);\n const title = controlsTitle || tagName.split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join(' ');\n\n const controlsCode = showControls ? generateControlsCode(title) : '';\n const draggableCode = showControls ? generateDraggableCode() : '';\n const controlsContainerHtml = showControls\n ? `<div class=\"controls-container\"></div>`\n : '';\n const controlsContainerStyle = showControls\n ? `\n .controls-container {\n position: absolute;\n top: 8px;\n right: 8px;\n z-index: 1000;\n max-height: calc(100% - 16px);\n overflow-y: auto;\n }\n /* Tweakpane theme overrides */\n .controls-container .tp-dfwv {\n background-color: rgba(15, 20, 25, 0.95) !important;\n }\n `\n : '';\n\n return `\n// Export-friendly HyperTool Runtime\n// Generated by @hypertools/sdk\n// Controls: ${showControls ? 'enabled' : 'disabled'}\n\n// Baked-in current params from export\nconst __BAKED_PARAMS__ = ${currentParamsJson};\nconst __SHOW_CONTROLS__ = ${showControls};\nconst __BACKGROUND__ = '${background}';\n\n${controlsCode}\n${draggableCode}\n\nexport type ControlDefinitions = Record<string, {\n type: string;\n value: unknown;\n label?: string;\n min?: number;\n max?: number;\n step?: number;\n options?: unknown[];\n}>;\n\nexport interface SandboxContext {\n mount: HTMLElement;\n params: Record<string, unknown>;\n exports: {\n setFilename: (name: string) => void;\n useDefaultCanvasCapture: (use: boolean) => void;\n captureImage: () => Promise<Blob>;\n };\n environment: {\n onResize: (callback: () => void) => () => void;\n window: Window;\n document: Document;\n };\n controls: null;\n}\n\ninterface SandboxConfig {\n controls?: {\n definitions?: ControlDefinitions;\n options?: { title?: string };\n };\n exportWidget?: {\n filename?: string;\n useCanvasCapture?: boolean;\n enabled?: boolean;\n };\n setup: (context: SandboxContext) => (() => void) | void;\n}\n\n// Store for the experience definition\nlet __experienceDef__: SandboxConfig | null = null;\n\n/**\n * createSandbox - Export version\n * Instead of connecting to studio, registers a web component\n */\nexport async function createSandbox(config: SandboxConfig): Promise<void> {\n __experienceDef__ = config;\n\n // Create the web component\n class HypertoolElement extends HTMLElement {\n private _mount: HTMLElement | null = null;\n private _controlsContainer: HTMLElement | null = null;\n private _cleanup: (() => void) | void = undefined;\n private _params: Record<string, unknown> = {};\n private _pane: any = null;\n\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n }\n\n connectedCallback() {\n if (!this.shadowRoot || !__experienceDef__) return;\n\n // Setup styles (includes theme CSS when controls enabled)\n const style = document.createElement('style');\n style.textContent = \\`\n \\${__SHOW_CONTROLS__ && typeof __THEME_CSS__ !== 'undefined' ? __THEME_CSS__ : ''}\n :host { display: block; width: 100%; height: 100%; }\n .mount {\n width: 100%;\n height: 100%;\n position: relative;\n overflow: hidden;\n background: \\${__BACKGROUND__};\n }\n ${controlsContainerStyle}\n \\`;\n this.shadowRoot.appendChild(style);\n\n // Create mount\n this._mount = document.createElement('div');\n this._mount.className = 'mount';\n this.shadowRoot.appendChild(this._mount);\n\n // Create controls container if enabled\n if (__SHOW_CONTROLS__) {\n this._controlsContainer = document.createElement('div');\n this._controlsContainer.className = 'controls-container';\n this.shadowRoot.appendChild(this._controlsContainer);\n }\n\n // Initialize params: baked params > HTML attributes > definition defaults\n const defs = __experienceDef__.controls?.definitions || {};\n for (const [key, def] of Object.entries(defs)) {\n // Priority: HTML attribute > baked param > definition default\n if (this.getAttribute(key) !== null) {\n this._params[key] = this._parseAttr(key, this.getAttribute(key)!, def);\n } else if (key in __BAKED_PARAMS__) {\n this._params[key] = __BAKED_PARAMS__[key];\n } else {\n this._params[key] = def.value;\n }\n }\n\n // Make params reactive\n const self = this;\n this._params = new Proxy(this._params, {\n set: (target, prop, value) => {\n const previous = target[prop as string];\n target[prop as string] = value;\n if (previous !== value) {\n self.dispatchEvent(new CustomEvent('paramchange', {\n detail: { key: prop, value, previousValue: previous },\n bubbles: true,\n }));\n }\n return true;\n }\n });\n\n // Create context\n const context: SandboxContext = {\n mount: this._mount,\n params: this._params,\n exports: {\n setFilename: () => {},\n useDefaultCanvasCapture: () => {},\n captureImage: async () => {\n const canvas = this._mount?.querySelector('canvas');\n if (!canvas) throw new Error('No canvas found');\n return new Promise((res, rej) => {\n canvas.toBlob(b => b ? res(b) : rej(new Error('Failed to capture')));\n });\n },\n },\n environment: {\n onResize: (callback) => {\n const ro = new ResizeObserver(() => callback());\n ro.observe(this._mount!);\n return () => ro.disconnect();\n },\n window,\n document,\n },\n controls: null,\n };\n\n // Run setup\n this._cleanup = __experienceDef__.setup(context);\n\n // Setup controls if enabled (async - loads Tweakpane from CDN)\n if (__SHOW_CONTROLS__ && this._controlsContainer && typeof __createControls__ === 'function') {\n __createControls__(this._params, defs, this._controlsContainer, this.shadowRoot!).then((pane: any) => {\n this._pane = pane;\n if (this._pane && typeof __setupDraggable__ === 'function') {\n __setupDraggable__(this._controlsContainer, this._pane);\n }\n });\n }\n\n // Dispatch ready event\n this.dispatchEvent(new CustomEvent('ready', { bubbles: true }));\n }\n\n disconnectedCallback() {\n if (this._cleanup) this._cleanup();\n if (this._pane?.dispose) this._pane.dispose();\n }\n\n private _parseAttr(key: string, value: string, def: { type: string }): unknown {\n switch (def.type) {\n case 'number': return parseFloat(value);\n case 'boolean': return value === 'true' || value === '';\n default: return value;\n }\n }\n\n // Public API\n setParam(key: string, value: unknown) {\n this._params[key] = value;\n this._pane?.refresh();\n }\n setParams(params: Record<string, unknown>) {\n for (const [k, v] of Object.entries(params)) this._params[k] = v;\n this._pane?.refresh();\n }\n getParams() { return { ...this._params }; }\n getParamDefs() { return __experienceDef__?.controls?.definitions || {}; }\n }\n\n // Register the component\n if (!customElements.get('${tagName}')) {\n customElements.define('${tagName}', HypertoolElement);\n }\n}\n`;\n}\n\n/**\n * Generate HTML with external script reference\n */\nexport function generateExportHtml(tagName: string): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${tagName}</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html, body { width: 100%; height: 100%; overflow: hidden; background: #0a0a0a; }\n ${tagName} { display: block; width: 100%; height: 100%; }\n </style>\n</head>\n<body>\n <${tagName}></${tagName}>\n <script src=\"./${tagName}.js\"></script>\n</body>\n</html>`;\n}\n\n/**\n * Generate HTML with inline script (for preview)\n * Includes permissive CSP for external library loading\n */\nexport function generatePreviewHtml(tagName: string, code: string): string {\n const cdnHosts = [\n 'https://cdnjs.cloudflare.com',\n 'https://cdn.jsdelivr.net',\n 'https://unpkg.com',\n 'https://esm.sh',\n 'https://cdn.skypack.dev',\n ].join(' ');\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'self' blob: data: ${cdnHosts}; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: data: ${cdnHosts}; style-src 'self' 'unsafe-inline' ${cdnHosts}; img-src 'self' blob: data: *; media-src 'self' blob: data: *; connect-src *; font-src 'self' data: ${cdnHosts};\">\n <title>${tagName}</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html, body { width: 100%; height: 100%; overflow: hidden; background: #0a0a0a; }\n ${tagName} { display: block; width: 100%; height: 100%; }\n </style>\n</head>\n<body>\n <${tagName}></${tagName}>\n <script>\n${code}\n </script>\n</body>\n</html>`;\n}\n\n/**\n * Generate README for the exported component\n */\nexport function generateExportReadme(tagName: string, className: string): string {\n return `# ${className}\n\n## Usage\n\n\\`\\`\\`html\n<script src=\"${tagName}.js\"></script>\n<${tagName}></${tagName}>\n\\`\\`\\`\n\n## API\n\n\\`\\`\\`javascript\nconst el = document.querySelector('${tagName}');\n\n// Set parameters\nel.setParam('paramName', value);\nel.setParams({ param1: value1, param2: value2 });\n\n// Get current parameters\nconst params = el.getParams();\n\n// Get parameter definitions\nconst defs = el.getParamDefs();\n\\`\\`\\`\n\n## Events\n\n\\`\\`\\`javascript\nel.addEventListener('ready', () => console.log('Ready!'));\nel.addEventListener('paramchange', (e) => {\n console.log('Param changed:', e.detail.key, e.detail.value);\n});\n\\`\\`\\`\n\n## Files\n\n- \\`${tagName}.js\\` - The bundled component\n- \\`index.html\\` - Example page (loads external JS)\n- \\`preview.html\\` - Standalone preview (inline JS)\n`;\n}\n\n/**\n * Generate TypeScript type definitions\n */\nexport function generateExportTypes(tagName: string, className: string): string {\n return `export interface ${className}Element extends HTMLElement {\n setParam(key: string, value: unknown): void;\n setParams(params: Record<string, unknown>): void;\n getParams(): Record<string, unknown>;\n getParamDefs(): Record<string, { type: string; value: unknown; [key: string]: unknown }>;\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n '${tagName}': ${className}Element;\n }\n}\n`;\n}\n"
9
9
  ],
10
10
  "mappings": "4XAqDA,eAAsB,CAAc,CAAC,EAA+C,CAElF,IAAI,EACJ,GAAI,CACF,EAAU,KAAa,mBACvB,KAAM,CACN,MAAU,MACR,sEACF,EAGF,IAAQ,UAAS,YAAW,WAAU,KAAI,SAAU,KAAa,wBACzD,UAAW,KAAa,eACxB,OAAM,WAAY,KAAa,gBAEjC,EAAU,MAAM,EAAQ,EAAK,EAAO,EAAG,mBAAmB,CAAC,EAEjE,GAAI,CAEF,GAAI,EAAQ,MACV,QAAY,EAAU,KAAY,OAAO,QAAQ,EAAQ,KAAK,EAAG,CAC/D,IAAM,EAAW,EAAK,EAAS,EAAS,QAAQ,MAAO,EAAE,CAAC,EACpD,EAAM,EAAQ,CAAQ,EAC5B,MAAM,EAAM,EAAK,CAAE,UAAW,EAAK,CAAC,EACpC,MAAM,EAAU,EAAU,CAAO,EAKrC,IAAI,EACJ,GAAI,OAAO,EAAQ,QAAU,SAC3B,EAAY,EAAK,EAAS,EAAQ,MAAM,QAAQ,MAAO,EAAE,CAAC,EACrD,KACL,IAAM,EAAW,EAAQ,MAAM,UAAY,WAC3C,EAAY,EAAK,EAAS,CAAQ,EAClC,MAAM,EAAU,EAAW,EAAQ,MAAM,IAAI,EAI/C,IAAM,EAAS,MAAM,EAAQ,MAAM,CACjC,YAAa,CAAC,CAAS,EACvB,OAAQ,GACR,QAAS,EAAK,EAAS,QAAQ,EAC/B,SAAU,EAAQ,SAAW,OAAS,OAAS,UAC/C,OAAQ,EAAQ,QAAU,OAC1B,OAAQ,EAAQ,QAAU,GAC1B,UAAW,EAAQ,UAAY,WAAa,GAC5C,WAAY,EAAQ,WACpB,SAAU,EAAQ,SAClB,OAAQ,EAAQ,OAChB,MAAO,GACP,SAAU,EACZ,CAAC,EAGK,EAAO,MAAM,EAAS,EAAK,EAAS,QAAQ,EAAG,OAAO,EACxD,EAEJ,GAAI,EAAQ,UACV,GAAI,CACF,EAAM,MAAM,EAAS,EAAK,EAAS,YAAY,EAAG,OAAO,EACzD,KAAM,EAKV,MAAO,CACL,OACA,MACA,SAAU,EAAO,SAAS,IAAI,CAAC,IAAwB,EAAE,IAAI,EAC7D,KAAM,OAAO,WAAW,EAAM,OAAO,CACvC,SACA,CAEA,MAAM,EAAG,EAAS,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,GAO/C,SAAS,CAAgB,CAC9B,EACyD,CAMzD,GAAI,CAJU,EAAK,MACjB,sFACF,EAEY,OAAO,KAEnB,GAAI,CAGF,OAAO,KACP,KAAM,CACN,OAAO,MAOJ,SAAS,CAAa,CAAC,EAAsB,CAClD,MAAO;AAAA,EACP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EC9HF,SAAS,CAAW,CAAC,EAAqB,CACxC,OAAO,EACJ,QAAQ,MAAO,MAAM,EACrB,QAAQ,KAAM,KAAK,EACnB,QAAQ,MAAO,KAAK,EACpB,QAAQ,eAAgB,aAAa,EAMnC,SAAS,CAAoB,CAAC,EAAsC,CACzE,IACE,UACA,YACA,cACA,YACA,gBAAgB,CAAC,EACjB,eAAe,GACf,aAAa,eACX,EAGE,EAAa,KAAK,UAAU,CAAa,EAC5C,QAAQ,KAAM,SAAS,EACvB,QAAQ,KAAM,SAAS,EAEpB,EAAW,KAAK,UAAU,CAAS,EACtC,QAAQ,KAAM,SAAS,EACvB,QAAQ,KAAM,SAAS,EAEpB,EAAiB,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAiBc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAiCd,GAEJ,MAAO;AAAA;AAAA,KAEJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAOkB;AAAA,2BACI;AAAA,8BACG;AAAA,4BACF;AAAA;AAAA,IAExB;AAAA;AAAA,UAEM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UA+IA,EAAY,CAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAiBP;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4BAaE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6BAsEC;AAAA,6BACA,OAAa;AAAA;AAAA;AAAA,EASnC,SAAS,CAAuB,CAAC,EAAsC,CAC5E,IAAQ,YAAW,UAAS,aAAc,EAEpC,EAAa,OAAO,QAAQ,CAAS,EACxC,IAAI,EAAE,EAAK,KAAS,CACnB,IAAI,EACJ,OAAQ,EAAI,UACL,SACH,EAAO,SACP,UACG,UACH,EAAO,UACP,UACG,YACA,SACH,EAAO,SACP,UACG,SACH,GAAI,EAAI,QACN,EAAO,EAAI,QACR,IAAI,CAAC,IAAQ,IAAI,OAAO,IAAQ,SAAW,EAAI,MAAQ,IAAM,EAC7D,KAAK,KAAK,EAEb,OAAO,SAET,cAEA,EAAO,UAEX,MAAO,KAAK,OAAS,KACtB,EACA,KAAK;AAAA,CAAI,EAEZ,MAAO;AAAA,gCACuB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAMzB,OAAa;AAAA;AAAA;AAAA;AAAA,YAIR;AAAA;AAAA;AAAA,EAGV;AAAA;AAAA;AAAA,YAGU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAWA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EC1bL,IAAM,EAAc,CAEzB,aAAc,CACZ,OAAQ,UACR,gBAAiB,UACjB,UAAW,UACX,SAAU,UACV,mBAAoB,UACpB,WAAY,UACZ,aAAc,UACd,WAAY,UACZ,iBAAkB,UAClB,YAAa,UACb,UAAW,UAGX,2BAA4B,UAC5B,2BAA4B,UAC5B,2BAA4B,UAC5B,2BAA4B,UAE5B,oBAAqB,UACrB,2BAA4B,UAC5B,0BAA2B,UAC3B,0BAA2B,UAE3B,oBAAqB,UACrB,2BAA4B,UAC5B,0BAA2B,UAC3B,0BAA2B,UAE3B,oBAAqB,UACrB,2BAA4B,UAC5B,0BAA2B,UAC3B,0BAA2B,UAE3B,oBAAqB,UACrB,2BAA4B,UAC5B,0BAA2B,UAC3B,0BAA2B,UAE3B,oBAAqB,UACrB,2BAA4B,UAC5B,0BAA2B,UAC3B,0BAA2B,UAE3B,oBAAqB,UACrB,2BAA4B,UAC5B,0BAA2B,UAC3B,0BAA2B,UAE3B,qBAAsB,MAEtB,6BAA8B,0DAC9B,gCAAiC,4EACjC,6BAA8B,wDAC9B,6BAA8B,4GAChC,EAGA,UAAW,CACT,6BAA8B,yBAC9B,yBAA0B,uBAE1B,kCAAmC,yBACnC,yCAA0C,gCAC1C,wCAAyC,+BACzC,wCAAyC,+BACzC,kCAAmC,gCAEnC,+BAAgC,yBAChC,sCAAuC,+DACvC,qCAAsC,8DACtC,qCAAsC,8DACtC,+BAAgC,gCAEhC,+BAAgC,yBAEhC,8BAA+B,yBAC/B,qCAAsC,gCACtC,oCAAqC,+BACrC,oCAAqC,+BACrC,8BAA+B,gCAE/B,8BAA+B,gCAE/B,gCAAiC,yBACjC,gCAAiC,+BACnC,CACF,EC9DA,SAAS,CAAiB,CAAC,EAAsB,CAE/C,OADa,KAAK,UAAU,CAAG,EAE5B,QAAQ,KAAM,SAAS,EACvB,QAAQ,KAAM,SAAS,EACvB,QAAQ,UAAW,SAAS,EAC5B,QAAQ,UAAW,SAAS,EAUjC,SAAS,CAAgB,EAAW,CAQlC,MAAO;AAAA;AAAA;AAAA,EAPS,OAAO,QAAQ,IAC1B,EAAY,gBACZ,EAAY,SACjB,CAAC,EACE,IAAI,EAAE,EAAK,KAAW,KAAK,MAAQ,IAAQ,EAC3C,KAAK;AAAA,CAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBd,SAAS,CAAoB,CAAC,EAA+B,CAG3D,MAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAFU,EAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAwBpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsDhB,SAAS,CAAqB,EAAW,CACvC,MAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwDF,SAAS,CAAqB,CAAC,EAAuC,CAC3E,IACE,UACA,gBAAgB,CAAC,EACjB,eAAe,GACf,gBACA,aAAa,eACX,EAEE,EAAoB,EAAkB,CAAa,EACnD,EAAQ,GAAiB,EAAQ,MAAM,GAAG,EAAE,IAAI,KAAK,EAAE,OAAO,CAAC,EAAE,YAAY,EAAI,EAAE,MAAM,CAAC,CAAC,EAAE,KAAK,GAAG,EAErG,EAAe,EAAe,EAAqB,CAAK,EAAI,GAC5D,EAAgB,EAAe,EAAsB,EAAI,GACzD,EAAwB,EAC1B,yCACA,GAkBJ,MAAO;AAAA;AAAA;AAAA,eAGM,EAAe,UAAY;AAAA;AAAA;AAAA,2BAGf;AAAA,4BACC;AAAA,0BACF;AAAA;AAAA,EAExB;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UA5B+B,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAcA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6BAgNuB;AAAA,6BACA;AAAA;AAAA;AAAA,EAStB,SAAS,CAAkB,CAAC,EAAyB,CAC1D,MAAO;AAAA;AAAA;AAAA;AAAA;AAAA,WAKE;AAAA;AAAA;AAAA;AAAA,MAIL;AAAA;AAAA;AAAA;AAAA,KAID,OAAa;AAAA,mBACC;AAAA;AAAA,SASZ,SAAS,CAAmB,CAAC,EAAiB,EAAsB,CACzE,IAAM,EAAW,CACf,+BACA,2BACA,oBACA,iBACA,yBACF,EAAE,KAAK,GAAG,EAEV,MAAO;AAAA;AAAA;AAAA;AAAA;AAAA,uFAK8E,kEAAyE,uCAA8C,yGAAgH;AAAA,WACnT;AAAA;AAAA;AAAA;AAAA,MAIL;AAAA;AAAA;AAAA;AAAA,KAID,OAAa;AAAA;AAAA,EAEhB;AAAA;AAAA;AAAA,SASK,SAAS,CAAoB,CAAC,EAAiB,EAA2B,CAC/E,MAAO,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,eAKC;AAAA,GACZ,OAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qCAMqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAwB/B;AAAA;AAAA;AAAA,EASC,SAAS,CAAmB,CAAC,EAAiB,EAA2B,CAC9E,MAAO,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAStB,OAAa;AAAA;AAAA;",
11
- "debugId": "B1C37420ECCD274264756E2164756E21",
11
+ "debugId": "BBAF843355D811CE64756E2164756E21",
12
12
  "names": []
13
13
  }
package/dist/index.d.ts CHANGED
@@ -1,15 +1,15 @@
1
1
  /**
2
- * @hypertool/sdk
2
+ * @hypertools/sdk
3
3
  *
4
4
  * HyperTool SDK - Vanilla-first SDK for embedding interactive experiences
5
5
  *
6
6
  * Main entry exports the Experience class (external API)
7
7
  * Internal frame/controls are available via subpath imports:
8
- * - @hypertool/sdk/frame
9
- * - @hypertool/sdk/controls
10
- * - @hypertool/sdk/recording
11
- * - @hypertool/sdk/react
12
- * - @hypertool/sdk/codegen
8
+ * - @hypertools/sdk/frame
9
+ * - @hypertools/sdk/controls
10
+ * - @hypertools/sdk/recording
11
+ * - @hypertools/sdk/react
12
+ * - @hypertools/sdk/codegen
13
13
  */
14
14
  export { Experience } from "./core/Experience";
15
15
  export type { ExperienceConfig, ExperienceContext, ExportsApi, EnvironmentApi, } from "./core/Experience";