{"modules":[{"name":"three","title":"Three.js","kind":"lib","status":"available","summary":"3D rendering, the workhorse for game/art prototypes. (r185 splits into module+core files, both platform-served; r170 stays available.)","spec":"three@0.185.1","version":"0.185.1","versions":["0.185.1","0.170.0"],"license":"MIT","source":"npm:three","sizeKB":404,"requires":[],"permissions":[],"flags":{"wasm":false,"streamed":false},"compatibility":null,"install":{"manifest":"{\n  \"modules\": [\n    \"three@0.185.1\"\n  ]\n}","importFrom":"three"},"example":null,"api":[],"notes":[],"upstreamDocs":"https://threejs.org/docs/","labs":[]},{"name":"pixi","title":"PixiJS","kind":"lib","status":"available","summary":"Fast 2D WebGL renderer.","spec":"pixi@8.6.0","version":"8.6.0","versions":["8.6.0"],"license":"MIT","source":"npm:pixi.js (dist/pixi.min.mjs — the package's own lib/index.mjs imports bare earcut/eventemitter3, unservable as-is)","sizeKB":180,"requires":[],"permissions":[],"flags":{"wasm":false,"streamed":false},"compatibility":null,"install":{"manifest":"{\n  \"modules\": [\n    \"pixi@8.6.0\"\n  ]\n}","importFrom":"pixi"},"example":null,"api":[],"notes":[],"upstreamDocs":"https://pixijs.com/8.x/guides","labs":[]},{"name":"phaser","title":"Phaser","kind":"lib","status":"available","summary":"Batteries-included 2D game framework (scenes, tilemaps, arcade physics). (Phaser 4; the 3.87 API stays available as phaser@3.87.0.)","spec":"phaser@4.2.1","version":"4.2.1","versions":["4.2.1","3.87.0"],"license":"MIT","source":"npm:phaser","sizeKB":1430,"requires":[],"permissions":[],"flags":{"wasm":false,"streamed":false},"compatibility":null,"install":{"manifest":"{\n  \"modules\": [\n    \"phaser@4.2.1\"\n  ]\n}","importFrom":"phaser"},"example":null,"api":[],"notes":[],"upstreamDocs":"https://docs.phaser.io/","labs":[]},{"name":"p5","title":"p5.js","kind":"lib","status":"available","summary":"Creative coding (p5 2.x). For byte-light sketches see prefab-sketch.","spec":"p5@2.0.2","version":"2.0.2","versions":["2.0.2"],"license":"LGPL-2.1","source":"npm:p5 (esbuild-bundled — no single-file browser ESM shipped; see tools/bundle-vendor.mjs)","sizeKB":309,"requires":[],"permissions":[],"flags":{"wasm":false,"streamed":false},"compatibility":null,"install":{"manifest":"{\n  \"modules\": [\n    \"p5@2.0.2\"\n  ]\n}","importFrom":"p5"},"example":null,"api":[],"notes":[],"upstreamDocs":"https://p5js.org/reference/","labs":[]},{"name":"rapier3d","title":"Rapier 3D physics","kind":"lib","status":"available","summary":"3D physics (WASM). Pairs with three. (0.19 fetches its .wasm beside the module instead of embedding it; 0.14 stays available.)","spec":"rapier3d@0.19.3","version":"0.19.3","versions":["0.19.3","0.14.0"],"license":"Apache-2.0","source":"npm:@dimforge/rapier3d-compat","sizeKB":1386,"requires":[],"permissions":[],"flags":{"wasm":true,"streamed":false},"compatibility":null,"install":{"manifest":"{\n  \"modules\": [\n    \"rapier3d@0.19.3\"\n  ]\n}","importFrom":"rapier3d"},"example":null,"api":[],"notes":[],"upstreamDocs":"https://rapier.rs/docs/","labs":[]},{"name":"matter","title":"Matter.js physics","kind":"lib","status":"available","summary":"Lightweight 2D physics (no WASM).","spec":"matter@0.20.0","version":"0.20.0","versions":["0.20.0"],"license":"MIT","source":"npm:matter-js (esbuild-bundled — package ships UMD/CJS only, no ESM build; see tools/bundle-vendor.mjs)","sizeKB":26,"requires":[],"permissions":[],"flags":{"wasm":false,"streamed":false},"compatibility":null,"install":{"manifest":"{\n  \"modules\": [\n    \"matter@0.20.0\"\n  ]\n}","importFrom":"matter"},"example":null,"api":[],"notes":[],"upstreamDocs":"https://brm.io/matter-js/docs/","labs":[]},{"name":"tone","title":"Tone.js","kind":"lib","status":"available","summary":"Web Audio synthesis & scheduling.","spec":"tone@15.0.4","version":"15.0.4","versions":["15.0.4"],"license":"MIT","source":"npm:tone (esbuild-bundled — package ships UMD only under 'unpkg'; its 'module' entry is real ESM but has un-inlined tslib/standardized-audio-context deps, so it still needs bundling; see tools/bundle-vendor.mjs)","sizeKB":81,"requires":[],"permissions":[],"flags":{"wasm":false,"streamed":false},"compatibility":null,"install":{"manifest":"{\n  \"modules\": [\n    \"tone@15.0.4\"\n  ]\n}","importFrom":"tone"},"example":null,"api":[],"notes":[],"upstreamDocs":"https://tonejs.github.io/docs/","labs":[]},{"name":"lil-gui","title":"lil-gui","kind":"lib","status":"available","summary":"Tiny tweak-panel for exposing prototype parameters.","spec":"lil-gui@0.20.0","version":"0.20.0","versions":["0.20.0"],"license":"MIT","source":"npm:lil-gui","sizeKB":14,"requires":[],"permissions":[],"flags":{"wasm":false,"streamed":false},"compatibility":null,"install":{"manifest":"{\n  \"modules\": [\n    \"lil-gui@0.20.0\"\n  ]\n}","importFrom":"lil-gui"},"example":null,"api":[],"notes":[],"upstreamDocs":"https://lil-gui.georgealways.com/","labs":[]},{"name":"motion","title":"Motion","kind":"lib","status":"available","summary":"Animation library (vanilla Motion One API).","spec":"motion@12.42.2","version":"12.42.2","versions":["12.42.2","11.15.0"],"license":"MIT","source":"npm:motion (esbuild-bundled — its ESM re-exports from sibling packages framer-motion/motion-dom/motion-utils via relative paths that escape the served package root; see tools/bundle-vendor.mjs). GSAP excluded: proprietary license forbids self-CDN redistribution.","sizeKB":46,"requires":[],"permissions":[],"flags":{"wasm":false,"streamed":false},"compatibility":null,"install":{"manifest":"{\n  \"modules\": [\n    \"motion@12.42.2\"\n  ]\n}","importFrom":"motion"},"example":null,"api":[],"notes":[],"upstreamDocs":"https://motion.dev/docs","labs":[]},{"name":"chart","title":"Chart.js","kind":"lib","status":"available","summary":"Charts for dashboard/tool prototypes.","spec":"chart@4.4.7","version":"4.4.7","versions":["4.4.7"],"license":"MIT","source":"npm:chart.js","sizeKB":69,"requires":[],"permissions":[],"flags":{"wasm":false,"streamed":false},"compatibility":null,"install":{"manifest":"{\n  \"modules\": [\n    \"chart@4.4.7\"\n  ]\n}","importFrom":"chart"},"example":null,"api":[],"notes":[],"upstreamDocs":"https://www.chartjs.org/docs/latest/","labs":[]},{"name":"d3","title":"D3","kind":"lib","status":"available","summary":"Data-driven documents for bespoke viz.","spec":"d3@7.9.0","version":"7.9.0","versions":["7.9.0"],"license":"ISC","source":"npm:d3 (esbuild-bundled — package ships either a UMD build or an unbundled source split across ~29 separate d3-* packages, no single-file browser ESM; see tools/bundle-vendor.mjs)","sizeKB":93,"requires":[],"permissions":[],"flags":{"wasm":false,"streamed":false},"compatibility":null,"install":{"manifest":"{\n  \"modules\": [\n    \"d3@7.9.0\"\n  ]\n}","importFrom":"d3"},"example":null,"api":[],"notes":[],"upstreamDocs":"https://d3js.org/","labs":[]},{"name":"maplibre-gl","title":"MapLibre GL JS","kind":"lib","status":"available","summary":"Interactive vector maps (WebGL). The base for prefab-map; import it directly for custom map styling/layers beyond what the prefab exposes.","spec":"maplibre-gl@6.0.0","version":"6.0.0","versions":["6.0.0"],"license":"BSD-3-Clause","source":"npm:maplibre-gl","sizeKB":138,"requires":[],"permissions":[],"flags":{"wasm":false,"streamed":false},"compatibility":null,"install":{"manifest":"{\n  \"modules\": [\n    \"maplibre-gl@6.0.0\"\n  ]\n}","importFrom":"maplibre-gl"},"example":null,"api":[],"notes":[],"upstreamDocs":"https://maplibre.org/maplibre-gl-js/docs/","labs":[]},{"name":"simplex-noise","title":"Simplex noise","kind":"lib","status":"available","summary":"Procedural noise.","spec":"simplex-noise@4.0.3","version":"4.0.3","versions":["4.0.3"],"license":"MIT","source":"npm:simplex-noise","sizeKB":3,"requires":[],"permissions":[],"flags":{"wasm":false,"streamed":false},"compatibility":null,"install":{"manifest":"{\n  \"modules\": [\n    \"simplex-noise@4.0.3\"\n  ]\n}","importFrom":"simplex-noise"},"example":null,"api":[],"notes":[],"upstreamDocs":"https://github.com/jwagner/simplex-noise.js","labs":[]},{"name":"onnxruntime-web","title":"ONNX Runtime Web","kind":"lib","status":"available","summary":"ONNX inference runtime (WASM + WebGPU), the base for model packs.","spec":"onnxruntime-web@1.27.0","version":"1.27.0","versions":["1.27.0","1.20.1"],"license":"MIT","source":"npm:onnxruntime-web","sizeKB":3470,"requires":[],"permissions":[],"flags":{"wasm":true,"streamed":false},"compatibility":null,"install":{"manifest":"{\n  \"modules\": [\n    \"onnxruntime-web@1.27.0\"\n  ]\n}","importFrom":"onnxruntime-web"},"example":null,"api":[],"notes":[],"upstreamDocs":"https://onnxruntime.ai/docs/get-started/with-javascript/web.html","labs":[]},{"name":"transformers","title":"Transformers.js","kind":"lib","status":"planned","summary":"Pipelines over ONNX: detection, segmentation, depth, ASR, embeddings.","spec":"transformers@3.2.0","version":"3.2.0","versions":["3.2.0"],"license":"Apache-2.0","source":"npm:@huggingface/transformers","sizeKB":800,"requires":[],"permissions":[],"flags":{"wasm":true,"streamed":false},"compatibility":null,"install":{"manifest":null,"importFrom":"transformers"},"example":null,"api":[],"notes":[],"upstreamDocs":"https://huggingface.co/docs/transformers.js","labs":[]},{"name":"mediapipe-vision","title":"MediaPipe Vision","kind":"lib","status":"available","summary":"Hands / pose / face landmarks, realtime body-driven interaction.","spec":"mediapipe-vision@0.10.18","version":"0.10.18","versions":["0.10.18"],"license":"Apache-2.0","source":"npm:@mediapipe/tasks-vision","sizeKB":9800,"requires":[],"permissions":["camera"],"flags":{"wasm":true,"streamed":false},"compatibility":null,"install":{"manifest":"{\n  \"modules\": [\n    \"mediapipe-vision@0.10.18\"\n  ],\n  \"permissions\": [\n    \"camera\"\n  ]\n}","importFrom":"mediapipe-vision"},"example":null,"api":[],"notes":[],"upstreamDocs":"https://ai.google.dev/edge/mediapipe/solutions/vision/hand_landmarker/web_js","labs":[]},{"name":"model-rfdetr-nano","title":"RF-DETR nano object detection","kind":"model","status":"planned","summary":"RF-DETR nano object detection weights, real-time DETR in the browser. (YOLOv8 excluded: AGPL.)","spec":"model-rfdetr-nano@1.0.0","version":"1.0.0","versions":["1.0.0"],"license":"Apache-2.0","source":"hf:roboflow/rf-detr (ONNX export)","sizeKB":30000,"requires":["onnxruntime-web"],"permissions":[],"flags":{"wasm":false,"streamed":false},"compatibility":null,"install":{"manifest":null,"importFrom":null},"example":null,"api":[],"notes":[],"upstreamDocs":null,"labs":[]},{"name":"model-whisper-tiny","title":"Whisper tiny speech-to-text","kind":"model","status":"planned","summary":"Local speech-to-text, voice-controlled prototypes with zero API cost.","spec":"model-whisper-tiny@1.0.0","version":"1.0.0","versions":["1.0.0"],"license":"MIT","source":"hf:onnx-community/whisper-tiny","sizeKB":40000,"requires":["transformers"],"permissions":["microphone"],"flags":{"wasm":false,"streamed":false},"compatibility":null,"install":{"manifest":null,"importFrom":null},"example":null,"api":[],"notes":[],"upstreamDocs":null,"labs":[]},{"name":"model-depth-anything-small","title":"Depth Anything small","kind":"model","status":"planned","summary":"Monocular depth from the webcam, parallax/AR-ish effects.","spec":"model-depth-anything-small@1.0.0","version":"1.0.0","versions":["1.0.0"],"license":"Apache-2.0","source":"hf:onnx-community/depth-anything-v2-small","sizeKB":50000,"requires":["transformers"],"permissions":[],"flags":{"wasm":false,"streamed":false},"compatibility":null,"install":{"manifest":null,"importFrom":null},"example":null,"api":[],"notes":[],"upstreamDocs":null,"labs":[]},{"name":"prefab-audio","title":"Audio","kind":"prefab","status":"available","summary":"Game/app audio over raw Web Audio: load samples from your bundle, play with volume/rate/loop, zero-asset beeps. Replaces maintenance-mode audio shims.","spec":"prefab-audio@0.1.1","version":"0.1.1","versions":["0.1.1","0.1.0"],"license":"MIT","source":"prototir","sizeKB":2,"requires":[],"permissions":[],"flags":{"wasm":false,"streamed":false},"compatibility":"Desktop and mobile browsers.","install":{"manifest":"{\n  \"modules\": [\n    \"prefab-audio@0.1.1\"\n  ]\n}","importFrom":"prefab-audio"},"example":"import { createAudio } from 'prefab-audio';\n\nconst audio = createAudio();\nawait audio.load('coin', 'assets/coin.ogg'); // a file in your bundle\naudio.play('coin', { volume: 0.8 });\naudio.beep(880, 0.08); // no file needed","api":[{"heading":"createAudio({ volume = 1 })","entries":[{"signature":"await audio.load(name, url)","description":"Fetches and decodes a sample from your bundle and keeps it under `name`. Throws if the file is missing."},{"signature":"audio.play(name, { volume = 1, rate = 1, loop = false })","description":"Plays a loaded sample. Returns a voice { stop(), source, gain }, or null when the name is unknown."},{"signature":"audio.beep(freq = 880, duration = 0.08, { volume = 0.5, type = 'sine' })","description":"A synthesized blip for UI feedback, with no audio file. Returns the same voice shape."},{"signature":"audio.volume","description":"Master volume, readable and settable."},{"signature":"audio.stopAll()","description":"Stops every playing voice."},{"signature":"audio.destroy()","description":"Stops everything, removes its listeners and closes the AudioContext."},{"signature":"audio.ctx, audio.master","description":"The AudioContext and master GainNode, for your own routing."}]}],"notes":["Browsers start audio suspended; the context resumes on the first pointer or key press, so the first sound should follow a player action."],"upstreamDocs":null,"labs":[]},{"name":"prefab-audio-features","title":"Audio features","kind":"prefab","status":"available","summary":"Audio-reactive features from a native AnalyserNode: rms, energy, spectral centroid + raw bins. Mic (with consent) or any AudioNode/MediaStream.","spec":"prefab-audio-features@0.1.1","version":"0.1.1","versions":["0.1.1","0.1.0"],"license":"MIT","source":"prototir","sizeKB":2,"requires":[],"permissions":["microphone"],"flags":{"wasm":false,"streamed":false},"compatibility":"Desktop and mobile browsers. The microphone needs the \"microphone\" permission in prototir.json; the player is asked first.","install":{"manifest":"{\n  \"modules\": [\n    \"prefab-audio-features@0.1.1\"\n  ],\n  \"permissions\": [\n    \"microphone\"\n  ]\n}","importFrom":"prefab-audio-features"},"example":"import { createAudioFeatures } from 'prefab-audio-features';\n\nconst af = await createAudioFeatures({\n  onFeatures({ rms, energy, centroid }) {\n    blob.scale = 1 + rms * 4;\n  }\n});","api":[{"heading":"createAudioFeatures(options)","entries":[{"signature":"source: 'microphone' | MediaStream | AudioNode","description":"What to listen to. Defaults to the microphone. Pass an AudioNode (for example from prefab-audio) to react to your own soundtrack."},{"signature":"context","description":"An AudioContext to use; must match an AudioNode source's context."},{"signature":"fftSize = 1024, smoothing = 0.8","description":"AnalyserNode settings."},{"signature":"onFeatures({ rms, energy, centroid, freq })","description":"Called every animation frame. rms is loudness, energy is overall level 0..1, centroid is brightness 0..1, freq is the raw frequency bins."}]},{"heading":"The returned object","entries":[{"signature":"features","description":"The latest values, updated every frame."},{"signature":"analyser, ctx, stream","description":"The underlying Web Audio objects."},{"signature":"destroy()","description":"Stops the loop. Stops the microphone and closes the context only if it created them; yours stay alive."}]}],"notes":[],"upstreamDocs":null,"labs":[]},{"name":"prefab-sketch","title":"Sketch","kind":"prefab","status":"available","summary":"The byte-light creative-coding loop: full-window 2D canvas, dpr scaling, resize, draw(ctx, {t, dt, mouse}), p5 ergonomics at ~1KB.","spec":"prefab-sketch@0.1.1","version":"0.1.1","versions":["0.1.1","0.1.0"],"license":"MIT","source":"prototir","sizeKB":1,"requires":[],"permissions":[],"flags":{"wasm":false,"streamed":false},"compatibility":"Desktop and mobile browsers.","install":{"manifest":"{\n  \"modules\": [\n    \"prefab-sketch@0.1.1\"\n  ]\n}","importFrom":"prefab-sketch"},"example":"import { createSketch } from 'prefab-sketch';\n\ncreateSketch({\n  draw(ctx, { width, height, mouse }) {\n    ctx.fillStyle = '#0a0a0a';\n    ctx.fillRect(0, 0, width, height);\n    ctx.strokeStyle = '#ededed';\n    ctx.strokeRect(mouse.x - 20, mouse.y - 20, 40, 40);\n  }\n});","api":[{"heading":"createSketch(options)","entries":[{"signature":"draw(ctx, state)","description":"Required. Called every frame with the 2D context and state { t, dt, frame, width, height, mouse: { x, y, down } }. dt is capped at 0.05 s."},{"signature":"setup(ctx, state)","description":"Called once before the first frame."},{"signature":"canvas, mount = document.body","description":"Draw into your own canvas, or let it add a full-window one to `mount`."},{"signature":"dpr = min(devicePixelRatio, 2), clear = false","description":"Pixel density, and whether to clear the canvas before each frame."}]},{"heading":"The returned sketch","entries":[{"signature":"canvas, ctx, state","description":"The canvas, its context and the live state."},{"signature":"destroy()","description":"Stops the loop, removes listeners, and removes the canvas if it created it."}]}],"notes":["The byte-light path for canvas art: the p5 setup/draw ergonomics without shipping the library. p5 is still in the catalog for Processing-style sketches."],"upstreamDocs":null,"labs":[]},{"name":"prefab-hand-controls","title":"Hand controls","kind":"prefab","status":"available","summary":"Hand landmarks → pointer/pinch events; camera + MediaPipe HandLandmarker + its model weights bundled in, no separate model pack needed. Reported pointer position is smoothed (configurable) to cut per-frame detector jitter without adding lag to pinch detection.","spec":"prefab-hand-controls@0.1.2","version":"0.1.2","versions":["0.1.2","0.1.1","0.1.0"],"license":"MIT","source":"prototir","sizeKB":7640,"requires":["mediapipe-vision"],"permissions":["camera"],"flags":{"wasm":false,"streamed":false},"compatibility":"Desktop and mobile browsers with a camera. Needs \"permissions\": [\"camera\"] in prototir.json; the player is asked first.","install":{"manifest":"{\n  \"modules\": [\n    \"prefab-hand-controls@0.1.2\"\n  ],\n  \"permissions\": [\n    \"camera\"\n  ]\n}","importFrom":"prefab-hand-controls"},"example":"import { createHandControls } from 'prefab-hand-controls';\n\nconst hand = await createHandControls({\n  onMove({ x, y }) {\n    cursor.style.left = `${x * 100}%`;\n    cursor.style.top = `${y * 100}%`;\n  },\n  onPinch({ down }) {\n    down ? grab() : release();\n  }\n});","api":[{"heading":"createHandControls(options)","entries":[{"signature":"onMove({ x, y })","description":"Index fingertip position, 0..1 of the view, smoothed."},{"signature":"onPinch({ x, y, down })","description":"Thumb-to-index pinch, fired only when it starts or ends."},{"signature":"pinchThreshold = 0.07, numHands = 1, mirror = true","description":"Pinch distance, hands to track, and whether to mirror like a selfie view."},{"signature":"smoothing = 0.5","description":"0 is raw, closer to 1 is steadier but lags. Only the reported position is smoothed; pinch detection uses raw landmarks."},{"signature":"delegate = 'CPU', showPreview = true, video, mount","description":"'GPU' is faster where WebGL is available. showPreview draws the camera image; video passes getUserMedia constraints."}]},{"heading":"The returned object","entries":[{"signature":"video, stream, landmarker","description":"The camera element, stream and MediaPipe landmarker."},{"signature":"destroy()","description":"Stops the camera and the model."}]}],"notes":["The hand model ships with the module, so detection makes no external network calls."],"upstreamDocs":null,"labs":[]},{"name":"prefab-voice-input","title":"Voice input","kind":"prefab","status":"planned","summary":"Push-to-talk → local transcript events.","spec":"prefab-voice-input@0.1.0","version":"0.1.0","versions":["0.1.0"],"license":"MIT","source":"prototir","sizeKB":null,"requires":["model-whisper-tiny"],"permissions":["microphone"],"flags":{"wasm":false,"streamed":false},"compatibility":null,"install":{"manifest":null,"importFrom":"prefab-voice-input"},"example":null,"api":[],"notes":[],"upstreamDocs":null,"labs":[]},{"name":"prefab-i18n","title":"Localization","kind":"prefab","status":"available","summary":"Localized strings and voice-over from your own bundle: Intl plurals/number/date, per-locale lazy loading, audio fallback chain.","spec":"prefab-i18n@0.1.1","version":"0.1.1","versions":["0.1.1","0.1.0"],"license":"MIT","source":"prototir","sizeKB":3,"requires":[],"permissions":[],"flags":{"wasm":false,"streamed":false},"compatibility":"Desktop and mobile browsers.","install":{"manifest":"{\n  \"modules\": [\n    \"prefab-i18n@0.1.1\"\n  ]\n}","importFrom":"prefab-i18n"},"example":"import { createI18n } from 'prefab-i18n';\n\nconst i18n = await createI18n({\n  locales: ['en', 'it'],\n  fallback: 'en',\n  loadStrings: (locale) => fetch(`locales/${locale}.json`).then((r) => r.json())\n});\n\ntitle.textContent = i18n.t('play');\ncoins.textContent = i18n.t('coins', { count: 3 });","api":[{"heading":"createI18n(options)","entries":[{"signature":"locales = ['en'], fallback = locales[0]","description":"The languages you publish, and the one used when a key is missing."},{"signature":"locale, preferred","description":"Force a starting locale, or pass the player's preference list; otherwise the browser's languages are used."},{"signature":"strings, loadStrings(locale)","description":"Inline tables, or a loader called for one locale at a time. Only the active locale (and the fallback) is ever fetched."},{"signature":"audio: { base, map, locales? }","description":"Voice-over from your bundle at base/locale/file. `locales` narrows it to the languages you recorded."},{"signature":"onChange(locale)","description":"Called after setLocale switches language."}]},{"heading":"The returned i18n","entries":[{"signature":"i18n.t(key, vars)","description":"Translates a key, filling {placeholders}. With vars.count, picks the plural form ({ one, other, ... }) via Intl.PluralRules. A missing key returns the key itself."},{"signature":"i18n.number(value, options), i18n.date(value, options)","description":"Locale-aware formatting through Intl."},{"signature":"await i18n.setLocale(locale)","description":"Switches language, loading its strings on demand."},{"signature":"i18n.locale, i18n.locales, i18n.fallback","description":"The current state."},{"signature":"await i18n.playAudio(key, { volume, loop }), i18n.stopAudio()","description":"Plays a localized clip, falling back to the fallback locale. Returns null when the browser blocks autoplay."},{"signature":"await i18n.preloadAudio(keys?)","description":"Preloads the active locale's clips."},{"signature":"i18n.destroy()","description":"Stops audio and clears caches."}]},{"heading":"Also exported","entries":[{"signature":"negotiateLocale(preferred, available, fallback)","description":"Picks the best available locale for a preference list."}]}],"notes":["Built for the sandbox: a prototype cannot reach translation services, so everything resolves from your own bundle."],"upstreamDocs":null,"labs":[]},{"name":"prefab-input","title":"Input","kind":"prefab","status":"available","summary":"One input API for desktop and mobile: keyboard, or fully customizable virtual controls, any number of buttons/joysticks/d-pads with position, size, shape and short/long-press semantics; same axes/pressed()/onPress() reads either way.","spec":"prefab-input@0.2.0","version":"0.2.0","versions":["0.2.0","0.1.0"],"license":"MIT","source":"prototir","sizeKB":null,"requires":[],"permissions":[],"flags":{"wasm":false,"streamed":false},"compatibility":"Desktop (keyboard) and mobile (touch). On touch devices it draws on-screen controls; multi-touch works, so two sticks can be used at once.","install":{"manifest":"{\n  \"modules\": [\n    \"prefab-input@0.2.0\"\n  ]\n}","importFrom":"prefab-input"},"example":"import { createInput } from 'prefab-input';\n\nconst input = createInput({\n  actions: [{ id: 'jump', key: 'Space', label: 'A' }]\n});\n\nfunction frame() {\n  const { x, y } = input.axes; // -1..1, y = forward\n  if (input.pressed('jump')) player.jump();\n  requestAnimationFrame(frame);\n}\nframe();","api":[{"heading":"createInput(options)","entries":[{"signature":"actions: [{ id, key, label?, position?, size?, shape?, longPressMs? }]","description":"Named actions. `key` is a KeyboardEvent.code such as 'Space' or 'KeyE'. On touch each action becomes a button; `position` is { left | right | top | bottom } in px or CSS lengths, `shape` is 'round' or 'square'."},{"signature":"joystick: true | false | { position?, size?, keys? }","description":"The primary 'move' stick, on by default. WASD and the arrow keys drive it on desktop."},{"signature":"joysticks: [{ id, position?, size?, keys?: { up, down, left, right } }]","description":"Extra sticks, read with axis(id). `keys` gives each one a desktop mapping."},{"signature":"dpad: false | true | { id?, position?, size?, keys? }","description":"A digital 4-way pad writing -1, 0 or 1 into an axis ('move' by default)."},{"signature":"virtual: 'auto' | true | false","description":"Whether to draw on-screen controls. 'auto' shows them on touch devices."},{"signature":"joystickSize: 120, longPressMs: 450, mount: document.body","description":"Default stick size in px, default long-press threshold in ms (actions can override it), and where the overlay is added."}]},{"heading":"The returned input","entries":[{"signature":"input.axes","description":"The 'move' axis: { x, y }, each -1..1."},{"signature":"input.axis(id)","description":"Any stick or d-pad axis by id."},{"signature":"input.pressed(id)","description":"Whether an action is held right now."},{"signature":"input.onPress(id, cb) / onRelease / onShortPress / onLongPress","description":"Edge events, identical for keys and on-screen buttons. A long press fires when the threshold is reached, while still held. Each returns an off() function."},{"signature":"input.virtual","description":"Whether on-screen controls are showing."},{"signature":"input.destroy()","description":"Removes the overlay and every listener."}]}],"notes":["0.2.0 only adds to the 0.1.0 API; 0.1.0 stays servable for prototypes that pinned it."],"upstreamDocs":null,"labs":[]},{"name":"prefab-shader-canvas","title":"Shader canvas","kind":"prefab","status":"available","summary":"Full-screen fragment shader boilerplate: uniforms for time/mouse/resolution.","spec":"prefab-shader-canvas@0.1.1","version":"0.1.1","versions":["0.1.1","0.1.0"],"license":"MIT","source":"prototir","sizeKB":null,"requires":[],"permissions":[],"flags":{"wasm":false,"streamed":false},"compatibility":"Desktop and mobile browsers with WebGL.","install":{"manifest":"{\n  \"modules\": [\n    \"prefab-shader-canvas@0.1.1\"\n  ]\n}","importFrom":"prefab-shader-canvas"},"example":"import { createShaderCanvas } from 'prefab-shader-canvas';\n\ncreateShaderCanvas({\n  fragment: `\n    precision mediump float;\n    varying vec2 v_uv;\n    uniform float u_time;\n    void main() {\n      gl_FragColor = vec4(v_uv, 0.5 + 0.5 * sin(u_time), 1.0);\n    }`\n});","api":[{"heading":"createShaderCanvas({ fragment, canvas, onFrame })","entries":[{"signature":"fragment","description":"GLSL fragment shader. Uniforms: float u_time (seconds), vec2 u_resolution (px), vec2 u_mouse (px, top-left origin); varying vec2 v_uv in 0..1. Compile errors are thrown with the GLSL log."},{"signature":"canvas","description":"Your own canvas; otherwise a full-window one is added to the page."},{"signature":"onFrame(time)","description":"Called after each frame is drawn."},{"signature":"returns { canvas, destroy() }","description":"destroy() stops the loop and frees the GL program and buffers."}]}],"notes":["Throws when WebGL is unavailable, so a fallback can be shown."],"upstreamDocs":null,"labs":[]},{"name":"prefab-map","title":"Map","kind":"prefab","status":"available","summary":"Embeddable vector map: markers, popups, GeoJSON overlays, light/dark/grayscale themes, click/fly-to/pan-and-zoom. Real OpenStreetMap-derived roads, water, and towns worldwide, up to zoom 10 anywhere on Earth - no external tile host or API key, no CSP change, mandatory OSM/Protomaps attribution. The basemap is served on demand over ranged CDN fetches, like a shared image or model asset, not downloaded in full by every prototype that uses it.","spec":"prefab-map@0.1.6","version":"0.1.6","versions":["0.1.6","0.1.5","0.1.4","0.1.3","0.1.2","0.1.1","0.1.0"],"license":"MIT","source":"prototir","sizeKB":3639992,"requires":["maplibre-gl"],"permissions":[],"flags":{"wasm":false,"streamed":true},"compatibility":"Desktop and mobile browsers with WebGL. Worldwide coverage up to zoom 10; tiles stream from the Prototir CDN as the player explores.","install":{"manifest":"{\n  \"modules\": [\n    \"prefab-map@0.1.6\"\n  ]\n}","importFrom":"prefab-map"},"example":"import { createMap } from 'prefab-map';\n\nconst map = await createMap({\n  theme: 'dark',\n  markers: [{ lngLat: [-0.1278, 51.5074], popup: 'London' }]\n});","api":[{"heading":"createMap(options)","entries":[{"signature":"container, mount = document.body","description":"Your own element, or a new one added to `mount`."},{"signature":"center = [10, 25], zoom = 1.6, maxZoom = 10","description":"Starting view as [longitude, latitude]. Zoom is capped at the basemap detail, 10."},{"signature":"theme: 'light' | 'dark' | 'grayscale'","description":"Basemap style."},{"signature":"markers: [{ lngLat, color?, popup?, id? }]","description":"Markers added when the map loads. popup is HTML."},{"signature":"geojson: [{ id?, data, type = 'fill', paint?, filter? }]","description":"GeoJSON overlays drawn as MapLibre layers."},{"signature":"onLoad(map), onClick(event), onMoveEnd({ center, zoom })","description":"Map events."}]},{"heading":"The returned map","entries":[{"signature":"addMarker(marker) / removeMarker(id)","description":"Returns the marker id."},{"signature":"addGeoJSON(overlay)","description":"Adds or updates an overlay by id."},{"signature":"setTheme(theme)","description":"Switches style and keeps your overlays."},{"signature":"map","description":"The MapLibre map, for anything else."},{"signature":"destroy()","description":"Removes the map."}]}],"notes":["Place and road names are not drawn yet. OpenStreetMap and Protomaps attribution is always shown."],"upstreamDocs":null,"labs":[]},{"name":"capability-ai","title":"Managed AI","kind":"capability","status":"available","summary":"Provider-neutral managed AI generation through Prototir.ai. The sandbox has connect-src 'none'; requests hop through the shell to a metered, moderated gateway. Prototir owns credentials and routing, while the prototype owner's paid plan and AI credits fund usage; signed-in visitors never spend their own allowance.","spec":"capability-ai","version":"sdk","versions":[],"license":"-","source":"Prototir.ai via Player Shell postMessage → platform proxy","sizeKB":null,"requires":[],"permissions":[],"flags":{"wasm":false,"streamed":false},"compatibility":"Inside the Prototir player, with AI enabled for the prototype.","install":{"manifest":"{\n  \"ai\": { \"mode\": \"managed\" }\n}","importFrom":null},"example":"const quest = await Prototir.ai.generate({\n  prompt: 'Give the player a short quest hook.',\n  maxTokens: 80\n});","api":[{"heading":"Prototir.ai","entries":[{"signature":"generate({ prompt, maxTokens?, timeoutMs? })","description":"Generates text through Prototir. Rejects with { code, message }; timeoutMs defaults to 30 seconds."}]}],"notes":["Enable it with \"ai\": { \"mode\": \"managed\" } in prototir.json. No API keys belong in a prototype: routing, moderation and allowances are handled by Prototir."],"upstreamDocs":null,"labs":[]},{"name":"capability-storage","title":"Storage","kind":"capability","status":"available","summary":"Per-prototype key-value persistence. Opaque-origin iframes have no reliable localStorage; the shell stores device-local values per prototype and isolates signed-in players on shared browsers.","spec":"capability-storage","version":"sdk","versions":[],"license":"-","source":"Prototir.storage via Player Shell","sizeKB":null,"requires":[],"permissions":[],"flags":{"wasm":false,"streamed":false},"compatibility":"Inside the Prototir player. Outside it, get returns null.","install":{"manifest":null,"importFrom":null},"example":"await Prototir.storage.set('difficulty', 'hard');\nconst difficulty = await Prototir.storage.get('difficulty');","api":[{"heading":"Prototir.storage","entries":[{"signature":"get(key)","description":"The stored string, or null."},{"signature":"set(key, value)","description":"Stores a string for this prototype and the signed-in player."},{"signature":"remove(key)","description":"Removes a value."}]}],"notes":["Keys up to 128 characters, values up to 64 KiB of UTF-8. Store JSON as a string for structured data.","Prototypes run in an isolated frame with no reliable localStorage, so this goes through the player."],"upstreamDocs":null,"labs":[]},{"name":"capability-permissions","title":"Camera and microphone","kind":"capability","status":"available","summary":"Camera/microphone: declared in prototir.json, granted via iframe allow= after an explicit shell-level user consent prompt.","spec":"capability-permissions","version":"sdk","versions":[],"license":"-","source":"manifest permissions + shell consent UI","sizeKB":null,"requires":[],"permissions":[],"flags":{"wasm":false,"streamed":false},"compatibility":"Inside the Prototir player, after the player agrees.","install":{"manifest":"{\n  \"permissions\": [\"camera\", \"microphone\"]\n}","importFrom":null},"example":"{\n  \"permissions\": [\"camera\"]\n}","api":[],"notes":["Declare \"camera\" and/or \"microphone\" in prototir.json. The player is asked before the prototype gets access; then use getUserMedia as usual, or a prefab that does (hand controls, audio features)."],"upstreamDocs":null,"labs":[]}]}