{"$schema":"https:\/\/ui.particle.academy\/schema\/registry-item.json","name":"scene","type":"registry:ui","title":"Scene types","description":"Engine-agnostic JSON shape (nodes, edges, widget specs).","package":"fancy-3d","dependencies":[],"registryDependencies":[],"files":[{"path":"components\/fancy\/scene\/scene.ts","content":"\/**\r\n * Engine-agnostic scene description.\r\n *\r\n * A scene is plain JSON-serializable data. Adapters consume it to render the\r\n * scene into a concrete engine \u2014 DOM, BabylonJS, three.js, native canvas, etc.\r\n * The same `Scene` should produce a recognizably similar result on every\r\n * supported engine.\r\n *\/\r\n\r\nexport type WidgetSpec =\r\n  | KpiSpec\r\n  | ChartSpec\r\n  | KanbanSpec\r\n  | TableSpec\r\n  | ProfileSpec\r\n  | CalloutSpec\r\n  | FormSpec\r\n  | ActionSpec\r\n  | TimelineSpec\r\n  | CodeSpec\r\n  | ImageSpec\r\n  | TextSpec\r\n  | DemoPageSpec\r\n  | ScreenSpec;\r\n\r\nexport interface KpiSpec {\r\n  kind: \"kpi\";\r\n  label: string;\r\n  value: string;\r\n  delta?: string;\r\n  trend?: \"up\" | \"down\" | \"flat\";\r\n}\r\n\r\nexport interface ChartSpec {\r\n  kind: \"chart\";\r\n  title: string;\r\n  variant: \"line\" | \"bar\" | \"area\";\r\n  series: number[];\r\n  color?: string;\r\n}\r\n\r\nexport interface KanbanSpec {\r\n  kind: \"kanban\";\r\n  columns: { title: string; cards: string[] }[];\r\n}\r\n\r\nexport interface TableSpec {\r\n  kind: \"table\";\r\n  title: string;\r\n  columns: string[];\r\n  rows: (string | number)[][];\r\n}\r\n\r\nexport interface ProfileSpec {\r\n  kind: \"profile\";\r\n  name: string;\r\n  role: string;\r\n  initials: string;\r\n  status?: \"online\" | \"away\" | \"offline\";\r\n}\r\n\r\nexport interface CalloutSpec {\r\n  kind: \"callout\";\r\n  tone: \"info\" | \"success\" | \"warning\" | \"danger\";\r\n  title: string;\r\n  body: string;\r\n}\r\n\r\nexport interface FormSpec {\r\n  kind: \"form\";\r\n  title: string;\r\n  fields: { id: string; label: string; type: \"text\" | \"switch\" | \"number\" }[];\r\n}\r\n\r\nexport interface ActionSpec {\r\n  kind: \"action\";\r\n  title: string;\r\n  buttons: { label: string; variant?: \"primary\" | \"secondary\" | \"ghost\" }[];\r\n}\r\n\r\nexport interface TimelineSpec {\r\n  kind: \"timeline\";\r\n  title: string;\r\n  events: { at: string; label: string }[];\r\n}\r\n\r\nexport interface CodeSpec {\r\n  kind: \"code\";\r\n  title: string;\r\n  language: string;\r\n  code: string;\r\n}\r\n\r\nexport interface ImageSpec {\r\n  kind: \"image\";\r\n  src: string;\r\n  alt: string;\r\n  caption?: string;\r\n}\r\n\r\nexport interface TextSpec {\r\n  kind: \"text\";\r\n  heading: string;\r\n  body: string;\r\n}\r\n\r\nexport interface DemoPageSpec {\r\n  kind: \"demoPage\";\r\n  name: string;\r\n  description: string;\r\n  path: string;\r\n  accent: string;\r\n  category: string;\r\n}\r\n\r\n\/**\r\n * A 3D-native widget \u2014 a screen-shaped panel with a bezel frame, status LED,\r\n * and optional 2D content drawn into the screen surface. Designed to feel\r\n * like a hardware display in a 3D scene.\r\n *\/\r\nexport interface ScreenSpec {\r\n  kind: \"screen\";\r\n  \/** Screen contents \u2014 either a static label or a paint callback that draws\r\n   *  onto a 2D context sized to the screen's inner area. *\/\r\n  content:\r\n    | { type: \"label\"; title: string; subtitle?: string }\r\n    | { type: \"image\"; src: string; alt?: string }\r\n    | { type: \"paint\"; paint: (ctx: CanvasRenderingContext2D, w: number, h: number) => void };\r\n  \/** Bezel color (CSS hex). Defaults to a dark frame. *\/\r\n  bezel?: string;\r\n  \/** Bezel thickness in pixels, scaled with the screen's pixel size. *\/\r\n  bezelThickness?: number;\r\n  \/** Power state \u2014 controls the LED + screen brightness. *\/\r\n  on?: boolean;\r\n  \/** Background fill color when the content does not fill the screen. *\/\r\n  background?: string;\r\n  \/** Brightness multiplier, 0..1. Defaults to 1 when on, 0.05 when off. *\/\r\n  brightness?: number;\r\n}\r\n\r\nexport interface SceneNode {\r\n  id: string;\r\n  position: { x: number; y: number; z?: number };\r\n  size?: { w: number; h: number };\r\n  widget: WidgetSpec;\r\n}\r\n\r\nexport interface SceneEdge {\r\n  id: string;\r\n  from: string;\r\n  to: string;\r\n  label?: string;\r\n  curve?: \"bezier\" | \"step\" | \"straight\";\r\n  animated?: boolean;\r\n}\r\n\r\nexport interface Scene {\r\n  nodes: SceneNode[];\r\n  edges: SceneEdge[];\r\n}\r\n\r\n\/**\r\n * A WidgetAdapter renders one WidgetSpec for one engine. Each engine ships\r\n * its own adapter; the scene data does not change.\r\n *\r\n * The DOM adapter returns a React element. A BabylonJS adapter returns a\r\n * `Mesh`. A three.js adapter would return an `Object3D`.\r\n *\/\r\nexport interface WidgetAdapter<T> {\r\n  render(spec: WidgetSpec, ctx: AdapterContext): T;\r\n}\r\n\r\nexport interface AdapterContext {\r\n  \/** Notify the host when the widget requests selection. *\/\r\n  onSelect?: (id: string) => void;\r\n  \/** The id of the node this widget belongs to. *\/\r\n  nodeId: string;\r\n  \/** Whether the node is currently selected \u2014 adapters may render an outline. *\/\r\n  selected: boolean;\r\n}\r\n\r\nlet _seq = 1000;\r\nexport function nextId(prefix: string): string {\r\n  _seq += 1;\r\n  return `${prefix}-${_seq}`;\r\n}\r\n\r\n\/**\r\n * Convenience: walk a Scene and call the adapter on every node.\r\n *\r\n * Engine-agnostic \u2014 every adapter (DOM, Babylon, three) can share it. The\r\n * host is responsible for placing the returned render results inside its\r\n * scene graph.\r\n *\/\r\nexport function renderScene<T>(\r\n  scene: Scene,\r\n  adapter: WidgetAdapter<T>,\r\n  ctxFor: (node: SceneNode) => AdapterContext\r\n): { node: SceneNode; rendered: T }[] {\r\n  return scene.nodes.map((node) => ({ node, rendered: adapter.render(node.widget, ctxFor(node)) }));\r\n}\r\n","type":"registry:ui","target":"components\/fancy\/scene\/scene.ts"}]}