# Unicorn Studio Runtime Control Guide ## What This Guide Is For This document is written for humans and LLM agents integrating Unicorn Studio scenes into websites and apps. Use it when you want to embed, optimize, or customize a published Unicorn Studio scene from application code. It explains the public runtime control surface: embedding, performance parameters, variables, direct layer control, textures, mouse-driven scene behavior, and safe integration patterns. It intentionally focuses on in-build runtime integration. It omits setup and authoring details that do not affect the published runtime contract. ## Agent Decision Rules A Unicorn Studio scene is a published WebGL composition made of layers and effects. The published scene data decides what is customizable. Runtime code should use the safest public API available for the thing it wants to change. Follow this order: 1. Use `scene.setPreset()` when the scene exposes an authored preset that matches the desired variable state. 2. Use `scene.setVariable()` when the scene exposes an authored variable. 3. Inspect `scene.getVariableManifest()` before guessing variable names or types. 4. Inspect `scene.getLayers()` before using direct layer controls. 5. Use `scene.setProp()` only when there is no authored variable for the layer property. 6. Use `scene.setTexture()` only for texture sampler replacement. 7. Use embed/runtime performance parameters such as `scale`, `dpi`, `fps`, `lazyLoad`, and `production` before inventing custom loading or render throttling. 8. Recreate the scene only for structural changes that the runtime API cannot express. Do not treat direct layer mutation as equal to variables. Variables are authored integration points; direct controls are fallback tools for un-authored scenes or internal tooling. ## Finding Ready-Made Examples Before building a custom visual from scratch, check the embeddable examples catalog: - JSON catalog: https://www.unicorn.studio/llm-example-projects.json - Plain text catalog: https://www.unicorn.studio/llm-example-projects.txt - Docs page: https://www.unicorn.studio/docs/example-projects/ Each example includes a published project ID, suggested use cases, tags, variable definitions, defaults, and drop-in HTML. Prefer these authored variables over direct layer mutation when adapting an example for a user. Runtime precedence for a property is: ```text authored base value -> breakpoint value -> variable/runtime override -> active event animation ``` In plain language: ```text Breakpoints choose the default. Variables let host code control that default. Events animate on top of that default while they are active. ``` ## Loading A Scene ### Include The Script Add the UMD script to the `
` of your page: ```html ``` Or import it into your component: ```js import * as UnicornStudio from './path/to/unicornStudio.umd.js'; ``` ### Inline Initialization Any element with `data-us-project` or `data-us-project-src` will be initialized by calling `UnicornStudio.init()`. Use `data-us-project` for a published Unicorn Studio embed ID: ```html ``` If you are hosting your own exported JSON file, use `data-us-project-src` instead of `data-us-project`: ```html ``` Do not use both `data-us-project` and `data-us-project-src` on the same element. If you host your own exported JSON, update that file when you make changes to your scene in Unicorn Studio. ### Dynamic Initialization You can add a scene dynamically during or after page load. ```js const scene = await UnicornStudio.addScene({ element: document.querySelector('#unicorn'), // elementId: 'unicorn' is also supported. projectId: 'YOUR_PROJECT_EMBED_ID', initialPreset: 'Dark Theme', fps: 60, scale: 1, dpi: 1.5, lazyLoad: true, fixed: false, altText: 'Welcome to Unicorn Studio', ariaLabel: 'This is a canvas scene', production: false, interactivity: { mouse: { disableMobile: false, disabled: false } }, breakpoints: [ { name: 'Desktop', max: Infinity, min: 992 }, { name: 'Tablet', max: 991, min: 576 }, { name: 'Mobile', max: 575, min: 0 } ] }); ``` Use either `projectId` or `filePath`, not both. `projectId` loads a published Unicorn Studio embed. `filePath` loads a self-hosted exported JSON file. Published scene defaults can be overridden by optional params passed to `addScene()`. The object returned by `addScene()` is the public scene instance used throughout this guide. To remove one scene, call `scene.destroy()`. To destroy every active scene, use: ```js UnicornStudio.destroy(); ``` If you are using Unicorn Studio in a SPA with dynamic routing, destroy scenes on unmount. ### Embed Parameters Inline embeds use `data-us-[param]` attributes. Dynamic scenes pass the same values as `addScene()` options. - `data-us-project`: published scene project ID. - `data-us-project-src`: path to a hosted project JSON file. - `data-us-scale`: canvas rendering scale, usually `0.25` to `1`. - `data-us-dpi`: scene resolution, usually `1` to `1.5`. - `data-us-fps`: target render loop FPS. - `data-us-lazyload`: defers resource creation until the scene enters the viewport. - `data-us-production`: serves scene data from the production CDN and improves caching. - `data-us-disablemobile`: disables mobile mouse or touch movement. - `data-us-disablemouse`: disables mouse interaction. - `data-us-fixed`: makes the scene behave like a fixed element. - `data-us-alttext`: SEO text placed inside the canvas. - `data-us-arialabel`: accessibility label applied to the canvas. - `data-us-vars`: JSON object of initial variable values. - `data-us-preset`: authored preset ID or name to apply on load. - `data-us-controls`: lazy-loads the Unicorn Studio runtime controls panel for published variables. - `data-us-controls-src`: optional override for the controls bundle URL. Use only for local development or debugging. The container element must have defined width and height. Scenes use the container dimensions when they initialize. ### Runtime Controls Panel Use `data-us-controls` to show a built-in Unicorn Studio controls panel for any variables published with the scene. The controls bundle is loaded lazily only when controls are enabled. ```html ``` For debugging an existing embed page, append `?controls=1` or `?controls=true` to the page URL. This enables the same controls panel without changing the embed markup. ```txt https://example.com/page-with-embed?controls=1 ``` The panel uses authored variable definitions and calls `scene.setVariable()` under the hood. If published presets exist, it shows a preset dropdown and applies them with `scene.setPreset()`. It is meant for manual tweaking, demos, debugging, or lightweight user-facing controls. Build custom UI with `getVariableManifest()`, `getPresets()`, `setPreset()`, and `setVariable()` when you need full control over layout or behavior. ### Scene Lifecycle Helpers ```js scene.resize(); scene.paused = true; scene.paused = false; scene.destroy(); ``` Window and orientation resize are handled by the SDK. Use `scene.resize()` only after app-driven layout changes that do not trigger a window resize. Use `scene.paused` to pause or resume rendering without destroying the scene. ### Virtual Scroll In SDK `v2.1.12+`, host apps can supply a virtual scroll value for scene visibility and scroll-based animations instead of letting Unicorn Studio read native `window.scrollY`. Use this when the page uses a smooth scroll library such as Lenis that moves content with transforms, because native scroll position may not match the visual page position. ```js UnicornStudio.setScroll(scrollY); ``` To return to normal browser scroll tracking: ```js UnicornStudio.useNativeScroll(); ``` Lenis example: ```js const lenis = new Lenis(); lenis.on('scroll', ({ scroll }) => { UnicornStudio.setScroll(scroll); }); function raf(time) { lenis.raf(time); requestAnimationFrame(raf); } requestAnimationFrame(raf); ``` If a virtual scroller is destroyed or disabled, call `UnicornStudio.useNativeScroll()` so Unicorn Studio resumes using `window.scrollY || window.pageYOffset`. ## Inspecting Capabilities Before changing a scene, inspect what it exposes. Prefer authored variables when they exist; use layer inspection only when you need a fallback path. ### getVariableManifest() Use `getVariableManifest()` to build custom controls, admin panels, debugging tools, or CMS integrations. ```js const manifest = scene.getVariableManifest(); ``` Each item includes: - `id` - `name` - `type` - `defaultValue` - `currentValue` - `description` - `validation` - `bindingCount` - `bindings` Each binding includes: - `layerId` - `kind` - `target` - `enabled` Binding kinds: - `prop`: normal layer or effect property - `source`: source URL such as image/video `src` - `texture`: named shader sampler such as `uTexture` ### getLayers() Use `getLayers()` only when there is no suitable variable and you need to identify a layer for direct control. ```js const layers = scene.getLayers(); console.table(layers); ``` Layer descriptors contain: - `id` - `publicId` - `name` - `type` Published layer `id` values are stable canonical IDs. `publicId` is the optional human-readable alias derived from layer names or layer types, for example `beam`, `beam1`, `heroImage`, or `gradient`. Direct layer controls accept either value, but prefer `id` for durable integrations. Use `scene.getLayer(idOrName)` when you need the runtime layer object for methods such as `hide()` and `show()`. ## Preferred Control Path: Variables Variables are the preferred integration API. They are authored in Unicorn Studio and published with the scene. A variable definition describes a public value, and bindings connect that value to one or more layer targets. Use variables for: - Brand colors - Theme colors - CMS image or video URLs - Numeric effect intensity - Motion speed - Boolean toggles - Text-like string controls - Reusable values shared across multiple layers Avoid direct layer mutation when a variable exists for the same value. ### Setting Initial Variable Values Use `initialVariables` when creating a scene in JavaScript: ```js const scene = await UnicornStudio.addScene({ projectId: 'PROJECT_ID', element: document.querySelector('#unicorn'), initialVariables: { brandColor: '#7c3aed', intensity: 0.65, heroImage: 'https://example.com/hero.jpg' } }); ``` Use `data-us-vars` for declarative HTML embeds: ```html ``` `initialVariables` and `data-us-vars` are applied after the scene loads its authored defaults. ### Updating Variables After Load ```js scene.setVariable('brandColor', '#ff4fd8'); scene.setVariable('intensity', 0.9); scene.setVariables({ brandColor: '#4f46e5', intensity: 0.4 }); ``` `setVariable()` and `setVariables()` return the scene instance for chaining. If a variable name is unknown or a value fails validation, Unicorn Studio logs a console warning and leaves the current value unchanged. ### Reading Variables ```js const brandColor = scene.getVariable('brandColor'); const allValues = scene.getVariables(); const definition = scene.getVariableDefinition('brandColor'); const definitions = scene.getVariableDefinitions(); ``` ### Using Variable Presets Presets are authored groups of variable values published with the scene. They are the best API when you want to switch between known design states such as themes, variants, or content sets. ```js const presets = scene.getPresets(); const preset = scene.getPreset('Dark Theme'); // accepts preset ID or name scene.setPreset(preset.id); ``` Use `initialPreset` when creating a scene in JavaScript: ```js const scene = await UnicornStudio.addScene({ projectId: 'PROJECT_ID', element: document.querySelector('#unicorn'), initialPreset: 'Dark Theme' }); ``` Use `data-us-preset` for declarative HTML embeds: ```html ``` For quick debugging, append `?preset=Dark%20Theme` to the page URL. Presets are applied after authored defaults and before `initialVariables` or `data-us-vars`, so explicit variable values can override a preset on load. ### Listening For Variable Changes ```js const unsubscribe = scene.onVariableChange((name, value, values) => { console.log('Variable changed:', name, value, values); }); unsubscribe(); ``` The callback receives: - `name`: changed variable name - `value`: value passed to `setVariable()` - `values`: current variable values after the change ### Variable Types And Values Common variable types: - `number`: JavaScript number - `boolean`: JavaScript boolean - `string`: JavaScript string - `color`: hex string such as `#7c3aed` - `vec2`: object with `{ type: 'Vec2', x, y }` - `vec3`: object with `{ type: 'Vec3', x, y, z }` - `texture`: URL string or texture-like object, depending on the authored binding Color variables should usually be set as hex strings: ```js scene.setVariable('brandColor', '#0ea5e9'); ``` Vector variables should include the vector type: ```js scene.setVariable('position', { type: 'Vec2', x: 0.5, y: 0.35 }); scene.setVariable('direction', { type: 'Vec3', x: 0.2, y: 0.8, z: 0.1 }); ``` ### Build Controls From The Manifest Example control builder: ```js function createControls(scene, container) { scene.getVariableManifest().forEach(variable => { const input = document.createElement('input'); input.name = variable.name; if (variable.type === 'color') { input.type = 'color'; input.value = variable.currentValue || variable.defaultValue || '#ffffff'; } else if (variable.type === 'number') { input.type = 'range'; input.min = variable.validation?.min ?? 0; input.max = variable.validation?.max ?? 1; input.step = variable.validation?.step ?? 0.01; input.value = variable.currentValue ?? variable.defaultValue ?? 0; } else if (variable.type === 'boolean') { input.type = 'checkbox'; input.checked = Boolean(variable.currentValue ?? variable.defaultValue); } else { input.type = 'text'; input.value = variable.currentValue ?? variable.defaultValue ?? ''; } input.addEventListener('input', () => { const value = input.type === 'checkbox' ? input.checked : input.value; scene.setVariable(variable.name, value); }); container.append(input); }); } ``` ## Fallback Control Path: Direct Layer Control Use direct controls when a scene was not authored with variables or when building an internal tool. Inspect `scene.getLayers()` first, then target a known layer ID or name. ### Set A Layer Property ```js scene.setProp('beam', 'opacity', 0.5); scene.setProp('beam', 'radius', 0.7); scene.setProp('headline', 'left', 0.5); ``` `setProp()` applies a runtime override. It does not rewrite the published scene JSON and it does not persist after the scene is destroyed. Prefer variables when controlling values that should be stable integration points. If `setProp()` targets a property that was baked out of the published shader, Unicorn Studio logs a create-variable link when the scene has edit metadata. Follow that link to open the editor with the variable form prefilled; the editor still requires a user to confirm creation before anything changes. ### Show Or Hide A Layer ```js const layer = scene.getLayer('mobileOnlyImage'); layer?.hide(); layer?.show(); ``` Use `hide()` and `show()` for runtime visibility toggles in SDK v2.1.6 and newer, including host-controlled responsive behavior such as hiding a layer for a mobile breakpoint. These methods update the live scene only; they do not rewrite the published scene JSON and they do not persist after the scene is destroyed. ### Set A Texture ```js scene.setTexture('distortionLayer', 'uTexture', 'https://example.com/displacement.png'); ``` Use `setTexture()` when replacing a named shader sampler. If the scene exposes a texture variable, prefer `setVariable()` instead. ## Mouse And Pointer Behavior Unicorn Studio has built-in mouse tracking for effects that use mouse interaction. The runtime updates an internal `uMousePos` uniform for those effects. This is how authored controls such as track mouse, mouse momentum, mouse spring, hover events, and mousemove events work. Important details: - Mouse values are normalized for each affected item. - `trackAxes` can limit tracking to `x`, `y`, or `xy`. - `mouseMomentum` smooths pointer movement. - `mouseSpring` creates spring-like pointer motion. - Hover and mousemove state effects use this built-in tracking internally. Use `scene.getMouse()` to read the latest scene-level pointer position: ```js const mouse = scene.getMouse(); // { x: 0.5, y: 0.5, inBounds: false, moved: false } ``` `x` is normalized from left to right. `y` is normalized from bottom to top, matching `uMousePos` and Vec2 variable coordinates. Before the SDK observes pointer movement for the scene, `getMouse()` returns the center position with `moved: false`. `inBounds` tells you whether the last observed pointer position is inside the scene element. When `inBounds` is false, `x` and `y` may fall outside the `0` to `1` range. `getMouse()` is scene-level. It does not include per-item `trackAxes`, `mouseMomentum`, or `mouseSpring`, so a specific layer's final internal `uMousePos` can differ from the scene-level value. If host application code needs to coordinate pointer events with other app UI, listen to pointer events in your app and use `scene.setVariable()` or `scene.setProp()` to pass derived values into the scene. Example host-controlled pointer variable: ```js const el = document.querySelector('[data-us-project]'); el.addEventListener('pointermove', event => { const rect = el.getBoundingClientRect(); scene.setVariable('cursor', { type: 'Vec2', x: (event.clientX - rect.left) / rect.width, y: 1 - (event.clientY - rect.top) / rect.height }); }); ``` Use authored mouse tracking when the interaction is visual and contained inside the scene. Use `scene.getMouse()` when host code needs the current scene-level pointer position. Use host pointer events when the interaction must coordinate with other application UI. ## Breakpoints And Events Precedence Variables are base/runtime values. Events are temporary presentation. The mental model is: ```text Breakpoints choose the default. Variables let host code control that default. Events animate over that value while active. ``` Practical implications: - If a property is responsive and variable-bound, the variable wins globally. - If a hover, scroll, or mousemove event targets a variable-bound property, the event animates from the variable-driven base value. - Appear events store explicit start/end values, so they may not reflect later variable changes in the same way. - If you need different variable values per breakpoint, set variables from host code when your layout changes. ## Performance And Production Unicorn Studio scenes render as WebGL compositions. Runtime integration code cannot rewrite the scene's internal shader graph, but it can control loading, resolution, frame rate, visibility, and scene lifecycle. ### Runtime Performance Parameters Use these before building custom performance logic: - `scale`: lowers canvas rendering scale. `0.25` to `1` is the useful range. - `dpi`: controls scene resolution. Start around `1` to `1.5`; higher values look sharper but cost more. For very small scenes/microinterations using a dpi or 3-4 might be needed. - `fps`: sets the render loop target. `30` or `60` is usually enough. - `lazyLoad`: delays initialization until viewport entry. - `production`: serves scene data from the production CDN and improves caching. For inline embeds, use `data-us-scale`, `data-us-dpi`, `data-us-fps`, `data-us-lazyload`, and `data-us-production`. ### Built-In Runtime Behavior Do not add custom wrappers for behavior the SDK already provides: - The SDK owns its render loop. It creates Curtains with `autoRender: false`, then renders through its own scheduler. - Dynamic scenes render only while `scene.isInView` and `scene.initialized` are true. - Offscreen dynamic scenes are marked not in view and do not keep rendering frames. - Scenes without dynamic layers do not stay in the continuous animation loop after their initial render work. - `fps` is enforced by the SDK render loop; host code should not add a second frame limiter. - `scene.paused = true` pauses frame rendering for that scene without destroying it. - `lazyLoad` delays plane/resource initialization until the scene is near the viewport. It is not needed for above-the-fold scenes. - Page visibility changes cancel and restart the SDK animation loop, so do not add a separate document-hidden render controller. - Window resize and orientation changes are debounced and call the scene refresh/resize path automatically. - When a scene comes back into view, the SDK refreshes its bounds and resizes it before rendering. - Mouse and touch listeners are global, passive, and only update scenes that are in view. - `UnicornStudio.destroy()` tears down all scenes and unbinds SDK events. Use `scene.resize()` manually only after app-driven layout changes that may not trigger a window resize, such as opening a panel, changing a container's CSS size, or animating layout around the canvas. ### Runtime Cost Model Large scenes, fullscreen canvases, high `dpi`, high `scale`, high `fps`, and many simultaneous scenes increase GPU work. If a scene is slow in an app, first reduce runtime cost before changing application architecture. Good runtime mitigations: - Enable `lazyLoad` for scenes below the fold. - Use `production` for live embeds. - Lower `scale` [0.25-1] before lowering layout size. - Lower `dpi` when the scene does not need retina sharpness. - Lower `fps` for ambient or background scenes. - Prefer smaller scene containers over fullscreen canvases when the design allows it. - Destroy scenes on route unmount instead of hiding them indefinitely. - Trust the SDK's in-view render gating before adding custom IntersectionObserver pause/resume code. ### Multiple Scenes Multiple scenes increase initial load time and GPU memory usage. Unicorn Studio manages multiple scenes, pauses rendering when scenes are outside the viewport, and throttles expensive work where possible, but WebGL has a hard context limit. Avoid more than 10 scenes on one page; browsers commonly cap WebGL contexts around 16. If runtime knobs are already conservative and performance is still poor, the published scene itself likely needs to be simplified or republished with different scene-level optimizations. Do not try to mutate internal WebGL programs from host code. ### Caching And Updates Use production mode for live embeds when possible: ```html ``` Production updates may take 1-2 minutes to propagate through the CDN. To bypass cached project data during development or QA, use an update query parameter: ```html ``` ### Performance Checklist - Enable `lazyLoad` for scenes below the fold. - Use `production` for live embeds. - Start with conservative `scale`, `dpi`, and `fps` values, then raise quality only as needed. - Destroy scenes when their route or component unmounts. - Avoid many fullscreen scenes on the same page. - Do not duplicate the SDK's visibility, resize, mouse, or render-loop management. - Test on real devices and browsers before shipping. ## Recipes ### Theme A Scene From A Design System ```js scene.setVariables({ brandColor: tokens.colors.brand[500], accentColor: tokens.colors.accent[400], glowAmount: prefersReducedMotion ? 0.15 : 0.65 }); ``` ### Drive A Scene From CMS Data ```js scene.setVariables({ headlineImage: entry.heroImage.url, paletteColor: entry.themeColor, intensity: Number(entry.effectIntensity ?? 0.5) }); ``` ### Animate A Variable With GSAP ```js const proxy = { amount: scene.getVariable('amount') ?? 0 }; gsap.to(proxy, { amount: 1, duration: 1.2, ease: 'power2.out', onUpdate: () => scene.setVariable('amount', proxy.amount) }); ``` ### Sync A Variable To Scroll Progress ```js window.addEventListener('scroll', () => { const max = document.documentElement.scrollHeight - window.innerHeight; const progress = max > 0 ? window.scrollY / max : 0; scene.setVariable('scrollProgress', progress); }); ``` ### Toggle A Visual Mode ```js scene.setVariable('isActive', true); scene.setVariable('isActive', false); ``` ### Swap An Image Or Video ```js scene.setVariable('heroImage', 'https://example.com/hero.webp'); scene.setVariable('introVideo', 'https://example.com/intro.mp4'); ``` ### React Integration ```js const sceneRef = useRef(null); useEffect(() => { let scene; let mounted = true; UnicornStudio.addScene({ projectId, element: ref.current, initialVariables: { brandColor, intensity } }).then(nextScene => { if (!mounted) { nextScene.destroy?.(); return; } scene = nextScene; sceneRef.current = nextScene; }); return () => { mounted = false; sceneRef.current = null; scene?.destroy?.(); }; }, [projectId]); useEffect(() => { sceneRef.current?.setVariable('brandColor', brandColor); }, [brandColor]); ``` ### Plain JavaScript Theme Switcher ```js const scene = await UnicornStudio.addScene({ projectId, element }); document.querySelector('[data-theme-toggle]').addEventListener('click', event => { const theme = event.currentTarget.dataset.theme; scene.setVariables( theme === 'dark' ? { brandColor: '#8b5cf6', backgroundMix: 0.2 } : { brandColor: '#2563eb', backgroundMix: 0.8 } ); }); ``` ## Debugging Start with variables: ```js console.table(scene.getVariableManifest()); console.log(scene.getVariables()); ``` Then inspect layers: ```js console.table(scene.getLayers()); ``` If `setVariable()` does nothing: - Confirm the variable name exists in `getVariableDefinitions()`. - Confirm the variable has at least one binding in `getVariableManifest()`. - Confirm the value type matches the variable type. - Confirm the published scene includes the variable and its binding. - If the console warning includes a create-variable link, open it and ask the user to confirm the prefilled variable instead of guessing uniforms. If `setProp()` does nothing: - Confirm the layer ID exists in `getLayers()`. - Confirm the property is a live runtime uniform or runtime-supported layer property. - Prefer a published variable if you need reliable public control. - If the console warning includes a create-variable link, use it to add a real authored variable for that layer property. ## Anti-Patterns - Do not fetch published project JSON manually just to change a value. - Do not mutate internal Curtains.js planes or uniforms as an integration strategy. - Do not depend on internal `scene.mouse` data; use `scene.getMouse()` for public scene-level pointer coordinates. - Do not recreate responsiveness in host code when authored Unicorn breakpoints already solve it. - Do not rebuild or reinitialize a scene for simple value changes. - Do not guess layer IDs when `getLayers()` can tell you the published IDs. - Do not use `setProp()` where an authored variable exists for the same value. - Do not ship many fullscreen scenes on one page without lazy loading and device testing. - Do not default to maximum `scale`, `dpi`, or `fps` unless the scene actually needs it. - Do not wrap every scene in a custom render loop, resize observer, visibility controller, or mouse tracker before checking the SDK behavior. ## Public API Reference The `UnicornStudio` object supports: ```js UnicornStudio.init(); UnicornStudio.addScene(options); UnicornStudio.destroy(); ``` Common `addScene()` options: - `element` or `elementId` - `projectId` or `filePath` - `initialVariables` - `fps` - `scale` - `dpi` - `lazyLoad` - `fixed` - `altText` - `ariaLabel` - `production` - `interactivity.mouse.disableMobile` - `interactivity.mouse.disabled` - `breakpoints` The scene object supports: ```js scene.resize(); scene.destroy(); scene.paused = true; scene.paused = false; scene.getMouse(); scene.setVariable(name, value); scene.setVariables(values); scene.getVariable(name); scene.getVariables(); scene.getVariableDefinition(name); scene.getVariableDefinitions(); scene.getVariableManifest(); scene.onVariableChange(callback); scene.setProp(layerIdOrName, prop, value); scene.setTexture(layerIdOrName, samplerName, value); scene.getLayers(); scene.getLayer(layerIdOrName); ``` Layer objects returned by `scene.getLayer()` support: ```js layer.hide(); layer.show(); // Model layers only: modelLayer.onLoad(callback); ``` Return values: - `UnicornStudio.init()` returns a promise that resolves with initialized scenes. - `UnicornStudio.addScene()` returns a promise that resolves with one scene instance. - `UnicornStudio.destroy()` destroys all active scenes. - `scene.resize()` resizes the scene after its container dimensions change. - `scene.destroy()` removes one scene. - `scene.getMouse()` returns `{ x, y, inBounds, moved }` for the latest scene-level pointer position. - `setVariable()`, `setVariables()`, `setProp()`, and `setTexture()` return the scene instance for chaining. - `getVariable()` returns the current value for a variable name. - `getVariables()` returns an object keyed by variable name. - `getVariableDefinition()` returns a variable definition or `null`. - `getVariableDefinitions()` returns all variable definitions. - `getVariableManifest()` returns variable definitions with current values and binding metadata. - `getLayers()` returns lightweight layer descriptors with `id`, `publicId`, `name`, and `type`. - `getLayer()` returns the runtime layer object for a layer ID or name, or `null`. - `layer.hide()` and `layer.show()` return the layer instance for chaining. - `onVariableChange()` returns an unsubscribe function. - `modelLayer.onLoad(callback)` registers one model load callback on a model layer. It runs when the model finishes loading, or immediately if the model is already loaded, and receives `{ layer, object, modelUrl }` where `object` is the loaded Three.js object/group. Calling it again replaces the previous model load callback.