{
  "name": "ob.Pal control catalogue",
  "about": "ob.Pal makes any phone the controller for what’s on a screen, with no app. This is everything such a phone can drive, and how: utilities, the controllers built from them, the routes motion takes, profiles, control systems and bridges, and the embed that puts ob.Pal on any page.",
  "spec": "https://github.com/Axialon/obpal/blob/main/spec/CATALOGUE.md",
  "protocol": "https://github.com/Axialon/obpal/blob/main/spec/PROTOCOL.md",
  "profileSchema": "https://obpal.blackboxes.net/profile.schema.json",
  "addProfile": "Check it with checkProfile() in @obpal/core (or the builder at https://obpal.blackboxes.net/catalogue/#build), then add catalogue/profiles/<id>.json to https://github.com/Axialon/obpal in a pull request, or open an issue with the JSON.",
  "utilities": [
    {
      "id": "music.hit",
      "name": "Drum hit",
      "what": "A velocity pad or acceleration peak plays a percussion voice",
      "status": "Shipped"
    },
    {
      "id": "music.note",
      "name": "Tone note",
      "what": "Scale-locked notes with sustain, tilt bend and orientation expression",
      "status": "Shipped"
    },
    {
      "id": "pad",
      "name": "Gamepad",
      "what": "Sticks, D-pad, face buttons, bumpers and analog triggers",
      "status": "Shipped"
    },
    {
      "id": "motion.aim",
      "name": "Aim",
      "what": "Turning the phone turns a view, by the gyro’s rate",
      "status": "Shipped"
    },
    {
      "id": "motion.steer",
      "name": "Steer",
      "what": "Tilting the phone holds a stick over",
      "status": "Shipped"
    },
    {
      "id": "motion.point",
      "name": "Point",
      "what": "Wii-style: the cursor is where the phone points",
      "status": "Shipped"
    },
    {
      "id": "motion.track",
      "name": "3D",
      "what": "Where the phone is in space, from its own sensors, Wii-style",
      "status": "Shipped"
    },
    {
      "id": "motion.hold",
      "name": "1:1",
      "what": "What you hold turns exactly as the phone does",
      "status": "Shipped"
    },
    {
      "id": "motion.tilt",
      "name": "Tilt",
      "what": "A racing-style tilt stick",
      "status": "Shipped"
    },
    {
      "id": "touch.trackpad",
      "name": "Trackpad",
      "what": "Drag, two-finger pan, pinch and twist",
      "status": "Shipped"
    }
  ],
  "controllers": [
    {
      "id": "face.gamepad",
      "name": "Gamepad",
      "category": "Controller",
      "for": "Sticks, D-pad, face buttons and triggers, with gyro aim, tilt steering and pointing",
      "utilities": [
        "pad",
        "motion.aim",
        "motion.steer",
        "motion.point"
      ],
      "modes": [
        "gamepad"
      ],
      "controls": [
        "a",
        "b",
        "x",
        "y",
        "lb",
        "rb",
        "lt",
        "rt",
        "view",
        "menu",
        "ls",
        "rs",
        "up",
        "down",
        "left",
        "right",
        "guide"
      ]
    },
    {
      "id": "face.wheel",
      "name": "Steering wheel",
      "category": "Controller",
      "for": "Tilt to steer, the triggers as pedals: the gamepad with the Driving profile",
      "utilities": [
        "pad",
        "motion.steer"
      ],
      "modes": [
        "gamepad"
      ],
      "controls": [
        "a",
        "b",
        "x",
        "y",
        "lb",
        "rb",
        "lt",
        "rt",
        "view",
        "menu",
        "ls",
        "rs",
        "up",
        "down",
        "left",
        "right",
        "guide"
      ]
    },
    {
      "id": "face.wii",
      "name": "Wii remote",
      "category": "Pointer",
      "for": "Point at the screen: A selects, hold B to grab, − and + zoom",
      "utilities": [
        "motion.point"
      ],
      "modes": [
        "point"
      ],
      "controls": [
        "a",
        "b",
        "minus",
        "home",
        "plus"
      ]
    },
    {
      "id": "face.mouse",
      "name": "Air mouse",
      "category": "Pointer",
      "for": "Point at the screen: Left and Right click, and a wheel scrolls",
      "utilities": [
        "motion.point"
      ],
      "modes": [
        "point"
      ],
      "controls": [
        "left",
        "right",
        "middle",
        "wheel",
        "minus",
        "plus",
        "home"
      ]
    },
    {
      "id": "face.trackpad",
      "name": "Trackpad",
      "category": "Touch",
      "for": "Drag, pan, pinch and twist; with the gyro on, turn things 1:1 or tilt them",
      "utilities": [
        "touch.trackpad",
        "motion.hold",
        "motion.tilt"
      ],
      "modes": [
        "tilt",
        "hold"
      ],
      "controls": [
        "grab",
        "level"
      ]
    },
    {
      "id": "face.hand",
      "name": "3D hand",
      "category": "3D",
      "for": "Hold the pad and move the phone: what you hold follows your hand",
      "utilities": [
        "motion.track"
      ],
      "modes": [
        "track"
      ],
      "controls": [
        "hold",
        "recentre"
      ]
    },
    {
      "id": "face.keyboard",
      "name": "Keyboard",
      "category": "Keys",
      "for": "The phone’s own keyboard types on the screen, with Esc, Tab, the arrows and Enter",
      "utilities": [],
      "modes": [],
      "controls": [
        "key-Escape",
        "key-Tab",
        "key-ArrowLeft",
        "key-ArrowUp",
        "key-ArrowDown",
        "key-ArrowRight",
        "key-Backspace",
        "key-Enter"
      ]
    },
    {
      "id": "face.drums",
      "name": "Drums",
      "category": "Music",
      "for": "Velocity pads and held strike gestures",
      "utilities": [
        "music.hit",
        "motion.tilt"
      ],
      "modes": [
        "pad"
      ],
      "controls": [
        "kick",
        "snare",
        "hat"
      ]
    },
    {
      "id": "face.keys",
      "name": "Tone keys",
      "category": "Music",
      "for": "Scale-locked notes, tilt bend, sustain and an air instrument",
      "utilities": [
        "music.note",
        "motion.tilt"
      ],
      "modes": [
        "pad"
      ],
      "controls": [
        "note1",
        "note2",
        "note3",
        "note4",
        "note5",
        "note6",
        "note7",
        "note8",
        "sustain",
        "octaveup",
        "octavedown"
      ]
    }
  ],
  "controllerId": "^[a-z]{2,12}\\.[a-z0-9-]{1,32}$",
  "routes": {
    "motion.aim": [
      "stick.right",
      "stick.left",
      "mouse"
    ],
    "motion.steer": [
      "stick.wheel",
      "stick.fly",
      "stick.left",
      "stick.right"
    ],
    "motion.point": [
      "pointer"
    ]
  },
  "limits": {
    "gain": [
      0.25,
      4
    ],
    "curve": [
      0.5,
      3
    ],
    "deadzone": [
      0,
      0.5
    ],
    "id": "^[a-z][a-z0-9-]{1,31}$",
    "name": 40,
    "for": 120,
    "buttons": 32
  },
  "buttons": {
    "inputId": "^(key:(#\\d{1,3}|[A-Za-z0-9]{1,32})|media:[a-z]{2,24}|pad:(raw:)?(b\\d{1,2}|a\\d{1,2}[+-])|back)$",
    "target": "^(none|tray:\\S{1,40}|app:(gyro|recentre|next|prev|keyboard)|[a-z][a-z0-9]{0,15}|key-[A-Za-z]{1,16})$",
    "inputs": [
      {
        "source": "media",
        "ids": [
          "media:playpause",
          "media:nexttrack",
          "media:previoustrack"
        ]
      },
      {
        "source": "keys",
        "ids": [
          "key:Enter",
          "key:Space",
          "key:Escape",
          "key:Backspace",
          "key:ArrowLeft",
          "key:ArrowRight",
          "key:ArrowUp",
          "key:ArrowDown",
          "key:PageUp",
          "key:PageDown",
          "key:Home",
          "key:End",
          "key:F5",
          "key:Period",
          "key:KeyB",
          "key:MediaPlayPause",
          "key:MediaTrackNext",
          "key:MediaTrackPrevious"
        ]
      },
      {
        "source": "pad",
        "ids": [
          "pad:b0",
          "pad:b1",
          "pad:b2",
          "pad:b3",
          "pad:b4",
          "pad:b5",
          "pad:b6",
          "pad:b7",
          "pad:b8",
          "pad:b9",
          "pad:b10",
          "pad:b11",
          "pad:b12",
          "pad:b13",
          "pad:b14",
          "pad:b15",
          "pad:b16",
          "pad:a0-",
          "pad:a0+",
          "pad:a1-",
          "pad:a1+",
          "pad:a2-",
          "pad:a2+",
          "pad:a3-",
          "pad:a3+"
        ]
      },
      {
        "source": "back",
        "ids": [
          "back"
        ]
      },
      {
        "source": "volume",
        "ids": [
          "key:AudioVolumeUp",
          "key:AudioVolumeDown"
        ]
      }
    ],
    "keys": [
      "key-Escape",
      "key-Tab",
      "key-ArrowLeft",
      "key-ArrowUp",
      "key-ArrowDown",
      "key-ArrowRight",
      "key-Backspace",
      "key-Enter"
    ],
    "app": [
      "gyro",
      "recentre",
      "next",
      "prev",
      "keyboard"
    ],
    "defaults": {
      "face.drums": {
        "key:AudioVolumeUp": "kick",
        "key:Enter": "kick",
        "key:NumpadEnter": "kick",
        "key:Space": "kick",
        "key:MediaPlayPause": "kick",
        "media:playpause": "kick",
        "pad:b0": "kick",
        "key:AudioVolumeDown": "snare",
        "key:Escape": "snare",
        "key:Backspace": "snare",
        "pad:b1": "snare",
        "key:ArrowRight": "hat",
        "key:PageDown": "hat",
        "key:MediaTrackNext": "hat",
        "media:nexttrack": "hat",
        "pad:b15": "hat"
      },
      "face.keys": {
        "key:AudioVolumeUp": "note1",
        "key:Enter": "note1",
        "key:NumpadEnter": "note1",
        "key:Space": "note1",
        "key:MediaPlayPause": "note1",
        "media:playpause": "note1",
        "pad:b0": "note1",
        "key:AudioVolumeDown": "sustain",
        "key:Escape": "sustain",
        "key:Backspace": "sustain",
        "pad:b1": "sustain",
        "key:ArrowRight": "octaveup",
        "key:PageDown": "octaveup",
        "key:MediaTrackNext": "octaveup",
        "media:nexttrack": "octaveup",
        "pad:b15": "octaveup",
        "key:ArrowLeft": "octavedown",
        "key:PageUp": "octavedown",
        "key:MediaTrackPrevious": "octavedown",
        "media:previoustrack": "octavedown",
        "pad:b14": "octavedown"
      },
      "face.gamepad": {
        "key:AudioVolumeUp": "a",
        "key:Enter": "a",
        "key:NumpadEnter": "a",
        "key:Space": "a",
        "key:MediaPlayPause": "a",
        "media:playpause": "a",
        "pad:b0": "a",
        "key:AudioVolumeDown": "b",
        "key:Escape": "b",
        "key:Backspace": "b",
        "pad:b1": "b",
        "key:ArrowRight": "right",
        "key:PageDown": "right",
        "key:MediaTrackNext": "right",
        "media:nexttrack": "right",
        "pad:b15": "right",
        "key:ArrowLeft": "left",
        "key:PageUp": "left",
        "key:MediaTrackPrevious": "left",
        "media:previoustrack": "left",
        "pad:b14": "left",
        "key:ArrowUp": "up",
        "key:ArrowDown": "down",
        "pad:b2": "x",
        "pad:b3": "y",
        "pad:b4": "lb",
        "pad:b5": "rb",
        "pad:b6": "lt",
        "pad:b7": "rt",
        "pad:b8": "view",
        "pad:b9": "menu",
        "pad:b10": "ls",
        "pad:b11": "rs",
        "pad:b12": "up",
        "pad:b13": "down",
        "pad:b16": "guide"
      },
      "face.wheel": {
        "key:AudioVolumeUp": "rt",
        "key:Enter": "a",
        "key:NumpadEnter": "a",
        "key:Space": "a",
        "key:MediaPlayPause": "a",
        "media:playpause": "a",
        "pad:b0": "a",
        "key:AudioVolumeDown": "lt",
        "key:Escape": "b",
        "key:Backspace": "b",
        "pad:b1": "b",
        "key:ArrowRight": "right",
        "key:PageDown": "right",
        "key:MediaTrackNext": "right",
        "media:nexttrack": "right",
        "pad:b15": "right",
        "key:ArrowLeft": "left",
        "key:PageUp": "left",
        "key:MediaTrackPrevious": "left",
        "media:previoustrack": "left",
        "pad:b14": "left",
        "key:ArrowUp": "up",
        "key:ArrowDown": "down",
        "pad:b2": "x",
        "pad:b3": "y",
        "pad:b4": "lb",
        "pad:b5": "rb",
        "pad:b6": "lt",
        "pad:b7": "rt",
        "pad:b8": "view",
        "pad:b9": "menu",
        "pad:b10": "ls",
        "pad:b11": "rs",
        "pad:b12": "up",
        "pad:b13": "down",
        "pad:b16": "guide"
      },
      "face.wii": {
        "key:AudioVolumeUp": "a",
        "key:Enter": "a",
        "key:NumpadEnter": "a",
        "key:Space": "a",
        "key:MediaPlayPause": "a",
        "media:playpause": "a",
        "pad:b0": "a",
        "key:AudioVolumeDown": "b",
        "key:Escape": "b",
        "key:Backspace": "b",
        "pad:b1": "b",
        "key:ArrowRight": "plus",
        "key:PageDown": "plus",
        "key:MediaTrackNext": "plus",
        "media:nexttrack": "plus",
        "pad:b15": "plus",
        "key:ArrowLeft": "minus",
        "key:PageUp": "minus",
        "key:MediaTrackPrevious": "minus",
        "media:previoustrack": "minus",
        "pad:b14": "minus"
      },
      "face.mouse": {
        "key:AudioVolumeUp": "left",
        "key:Enter": "left",
        "key:NumpadEnter": "left",
        "key:Space": "left",
        "key:MediaPlayPause": "left",
        "media:playpause": "left",
        "pad:b0": "left",
        "key:AudioVolumeDown": "wheel",
        "key:Escape": "wheel",
        "key:Backspace": "wheel",
        "pad:b1": "wheel",
        "key:ArrowRight": "plus",
        "key:PageDown": "plus",
        "key:MediaTrackNext": "plus",
        "media:nexttrack": "plus",
        "pad:b15": "plus",
        "key:ArrowLeft": "minus",
        "key:PageUp": "minus",
        "key:MediaTrackPrevious": "minus",
        "media:previoustrack": "minus",
        "pad:b14": "minus"
      },
      "face.trackpad": {
        "key:AudioVolumeUp": "grab",
        "key:Enter": "grab",
        "key:NumpadEnter": "grab",
        "key:Space": "grab",
        "key:MediaPlayPause": "grab",
        "media:playpause": "grab",
        "pad:b0": "grab",
        "key:AudioVolumeDown": "level",
        "key:Escape": "level",
        "key:Backspace": "level",
        "pad:b1": "level"
      },
      "face.hand": {
        "key:AudioVolumeUp": "hold",
        "key:Enter": "hold",
        "key:NumpadEnter": "hold",
        "key:Space": "hold",
        "key:MediaPlayPause": "hold",
        "pad:b0": "hold",
        "key:AudioVolumeDown": "recentre",
        "key:Escape": "recentre",
        "key:Backspace": "recentre",
        "pad:b1": "recentre"
      },
      "face.keyboard": {}
    },
    "smart": {
      "clicker": {
        "face.wii": {
          "key:PageDown": "key-ArrowRight",
          "key:ArrowRight": "key-ArrowRight",
          "key:ArrowDown": "key-ArrowRight",
          "key:PageUp": "key-ArrowLeft",
          "key:ArrowLeft": "key-ArrowLeft",
          "key:ArrowUp": "key-ArrowLeft",
          "key:Escape": "key-Escape",
          "key:F5": "a",
          "key:Period": "b",
          "key:KeyB": "b"
        },
        "face.mouse": {
          "key:PageDown": "key-ArrowRight",
          "key:ArrowRight": "key-ArrowRight",
          "key:ArrowDown": "key-ArrowRight",
          "key:PageUp": "key-ArrowLeft",
          "key:ArrowLeft": "key-ArrowLeft",
          "key:ArrowUp": "key-ArrowLeft",
          "key:Escape": "key-Escape",
          "key:F5": "left",
          "key:Period": "wheel",
          "key:KeyB": "wheel"
        },
        "face.trackpad": {
          "key:PageDown": "key-ArrowRight",
          "key:ArrowRight": "key-ArrowRight",
          "key:ArrowDown": "key-ArrowRight",
          "key:PageUp": "key-ArrowLeft",
          "key:ArrowLeft": "key-ArrowLeft",
          "key:ArrowUp": "key-ArrowLeft",
          "key:Escape": "key-Escape",
          "key:F5": "grab",
          "key:Period": "level",
          "key:KeyB": "level"
        },
        "face.hand": {
          "key:PageDown": "key-ArrowRight",
          "key:ArrowRight": "key-ArrowRight",
          "key:ArrowDown": "key-ArrowRight",
          "key:PageUp": "key-ArrowLeft",
          "key:ArrowLeft": "key-ArrowLeft",
          "key:ArrowUp": "key-ArrowLeft",
          "key:Escape": "key-Escape",
          "key:Period": "recentre",
          "key:KeyB": "recentre"
        },
        "face.gamepad": {
          "key:F5": "a",
          "key:Period": "b",
          "key:KeyB": "b"
        },
        "face.wheel": {
          "key:F5": "a",
          "key:Period": "b",
          "key:KeyB": "b"
        }
      },
      "pad": {
        "face.wii": {
          "pad:b4": "minus",
          "pad:b5": "plus",
          "pad:b2": "home",
          "pad:b9": "app:next",
          "pad:b8": "app:prev"
        },
        "face.mouse": {
          "pad:b4": "minus",
          "pad:b5": "plus",
          "pad:b2": "middle",
          "pad:b3": "right",
          "pad:b9": "app:next",
          "pad:b8": "app:prev"
        },
        "face.trackpad": {
          "pad:b2": "app:recentre",
          "pad:b9": "app:next",
          "pad:b8": "app:prev"
        },
        "face.hand": {
          "pad:b2": "recentre",
          "pad:b9": "app:next",
          "pad:b8": "app:prev"
        }
      },
      "selfie": {},
      "headset": {},
      "keyboard": {}
    }
  },
  "profiles": [
    {
      "id": "default",
      "name": "Default",
      "for": "Most gamepad games",
      "on": [],
      "aim": {
        "route": "stick.right",
        "gain": 1,
        "curve": 1,
        "deadzone": 0.2,
        "invertY": false,
        "edgeTurn": false
      },
      "steer": {
        "route": "stick.wheel",
        "gain": 1,
        "curve": 1,
        "deadzone": 0.2,
        "invertY": false,
        "edgeTurn": false
      },
      "point": {
        "route": "pointer",
        "gain": 1,
        "curve": 1,
        "deadzone": 0.2,
        "invertY": false,
        "edgeTurn": false
      }
    },
    {
      "id": "flight",
      "name": "Flight",
      "for": "Flight and space games: tilt the phone like a yoke",
      "on": [
        "motion.steer"
      ],
      "aim": {
        "route": "stick.right",
        "gain": 1,
        "curve": 1,
        "deadzone": 0.2,
        "invertY": false,
        "edgeTurn": false
      },
      "steer": {
        "route": "stick.fly",
        "gain": 1,
        "curve": 1,
        "deadzone": 0.2,
        "invertY": false,
        "edgeTurn": false
      },
      "point": {
        "route": "pointer",
        "gain": 1,
        "curve": 1,
        "deadzone": 0.2,
        "invertY": false,
        "edgeTurn": false
      }
    },
    {
      "id": "driving",
      "name": "Driving",
      "for": "Racing: tilt to steer, triggers for throttle and brake",
      "on": [
        "motion.steer"
      ],
      "aim": {
        "route": "stick.right",
        "gain": 1,
        "curve": 1,
        "deadzone": 0.2,
        "invertY": false,
        "edgeTurn": false
      },
      "steer": {
        "route": "stick.wheel",
        "gain": 1,
        "curve": 1,
        "deadzone": 0.2,
        "invertY": false,
        "edgeTurn": false
      },
      "point": {
        "route": "pointer",
        "gain": 1,
        "curve": 1,
        "deadzone": 0.2,
        "invertY": false,
        "edgeTurn": false
      }
    },
    {
      "id": "shooter",
      "name": "Shooter",
      "for": "First-person shooters: gyro mouse under pointer lock",
      "on": [],
      "aim": {
        "route": "mouse",
        "gain": 1,
        "curve": 1,
        "deadzone": 0.2,
        "invertY": false,
        "edgeTurn": false
      },
      "steer": {
        "route": "stick.wheel",
        "gain": 1,
        "curve": 1,
        "deadzone": 0.2,
        "invertY": false,
        "edgeTurn": false
      },
      "point": {
        "route": "pointer",
        "gain": 1,
        "curve": 1,
        "deadzone": 0.2,
        "invertY": false,
        "edgeTurn": true
      }
    },
    {
      "id": "pointer",
      "name": "Pointer",
      "for": "Menus, point-and-click and Wii-style games",
      "on": [
        "motion.point"
      ],
      "aim": {
        "route": "stick.right",
        "gain": 1,
        "curve": 1,
        "deadzone": 0.2,
        "invertY": false,
        "edgeTurn": false
      },
      "steer": {
        "route": "stick.wheel",
        "gain": 1,
        "curve": 1,
        "deadzone": 0.2,
        "invertY": false,
        "edgeTurn": false
      },
      "point": {
        "route": "pointer",
        "gain": 1,
        "curve": 1,
        "deadzone": 0.2,
        "invertY": false,
        "edgeTurn": false
      }
    }
  ],
  "community": [
    {
      "id": "crane",
      "name": "Crane",
      "for": "Lifting with a steady yoke: slow tilt, fine aim",
      "on": [],
      "aim": {
        "route": "stick.right",
        "gain": 0.6,
        "curve": 1.6,
        "deadzone": 0.15,
        "invertY": false,
        "edgeTurn": false
      },
      "steer": {
        "route": "stick.fly",
        "gain": 0.8,
        "curve": 1.4,
        "deadzone": 0.2,
        "invertY": false,
        "edgeTurn": false
      },
      "point": {
        "route": "pointer",
        "gain": 1,
        "curve": 1,
        "deadzone": 0.2,
        "invertY": false,
        "edgeTurn": false
      }
    }
  ],
  "systems": [
    {
      "id": "system.scene3d",
      "name": "Shared 3D scenes",
      "what": "Each object and part in the Viewer, one person each",
      "status": "Shipped",
      "link": "/view/"
    },
    {
      "id": "system.gamepad-slots",
      "name": "Gamepad slots",
      "what": "Players 1 to 4 in a browser game, one phone each",
      "status": "Public sim",
      "link": "/sim/arena/"
    },
    {
      "id": "system.desktop",
      "name": "Your computer",
      "what": "Keys and the mouse for the program in front (ob.Pal Desktop)",
      "status": "Shipped",
      "link": "/link/"
    },
    {
      "id": "system.robot-arm",
      "name": "Robot arms",
      "what": "Whole arms or single joints, simulated or real (Feetech, serial, ROS 2)",
      "status": "Public sim",
      "link": "/sim/arm/"
    }
  ],
  "bridges": [
    {
      "id": "bridge.gamepad",
      "name": "Game controllers",
      "what": "Any Gamepad API controller, through a phone or a PC",
      "status": "Planned"
    },
    {
      "id": "bridge.joycon",
      "name": "Joy-Con",
      "what": "Joy-Con and Switch Pro, with motion, over WebHID",
      "status": "Planned"
    },
    {
      "id": "bridge.wiimote",
      "name": "Wii Remote",
      "what": "Wii Remote and Nunchuk, with IR pointing, over WebHID",
      "status": "Planned"
    },
    {
      "id": "bridge.xr",
      "name": "VR controllers",
      "what": "Headset controllers and hands (WebXR), one person per hand",
      "status": "Planned"
    }
  ],
  "embed": {
    "script": "https://obpal.blackboxes.net/embed.js",
    "element": "obpal-remote",
    "demo": "https://obpal.blackboxes.net/embed/",
    "snippet": "<script type=\"module\" src=\"https://obpal.blackboxes.net/embed.js\"></script>\n<obpal-remote app=\"My scene\" seats=\"4\" modes=\"face.trackpad face.wii\"></obpal-remote>",
    "npm": "@obpal/host (defineObpalRemote from @obpal/host/element); not published yet",
    "attributes": {
      "app": "The name the phone shows (\"Controlling …\"); the page title by default",
      "modes": "What the phone offers, in order: mode names (point, hold, tilt, pad, gamepad, track) and catalogue controller ids (face.wii, face.gamepad, …; see controllers). The first opens on the phone",
      "seats": "How many phones at once: 1 (a new one takes over, the default) to 8 (a shared scene: each takes over a node of its own)",
      "profile": "A catalogue profile to suggest (flight, driving, …)",
      "corner": "Where the pairing chip sits: bottom-right (default), bottom-left, top-right, top-left, or inline where the element is",
      "accent": "A CSS colour for the chip",
      "open": "Start with the code showing (reflects the chip; opening starts the remote)",
      "label": "The chip’s words (default \"Scan to control\")",
      "scheme": "The chip’s colours: auto (default), light or dark",
      "code": "false hides the short code beside the QR code",
      "test-link": "Adds \"Open on this device\" for trying it without a phone",
      "service": "The room service (default: where embed.js came from)"
    },
    "events": {
      "obpal-status": "{ status }: idle, starting, ready, connecting, connected, offline, unsupported (no WebRTC, or not https) or error",
      "obpal-connect": "{ name, caps }: a phone connected to an empty scene (with one seat, every phone that takes over)",
      "obpal-disconnect": "The last phone left",
      "obpal-join": "{ participant }: someone joined ({ id, name, color, lead, caps, controller, profile })",
      "obpal-leave": "{ participant }: someone left; what they held is free",
      "obpal-button": "{ id, ev, participant }: tray buttons, trackpad taps (pad), the Wii face (wii-a, wii-b, wii-plus, wii-minus), the key row (key-Enter, …)",
      "obpal-mode": "{ mode, controller, profile, participant }: a phone switched controller",
      "obpal-claim": "{ node, participant }: a phone asks for a node (null lets go); cancel it to decide yourself",
      "obpal-text": "{ s, del, participant }: typing from the phone’s keyboard",
      "obpal-value": "{ id, v, participant }: a tray toggle or picker",
      "obpal-toss": "{ v, participant }: a flick upward (layout.toss)",
      "obpal-recenter": "{ participant }",
      "obpal-pad": "{ connected, participant }: a phone entered or left the gamepad"
    },
    "api": {
      "frame(now, who?)": "This frame’s input (qRel, tilt, pad1, pad2, zoom, twist, aim, pose, clutch, touching): the lead’s, or one participant’s. Once per rendered frame each",
      "participants": "Everyone controlling the scene, the lead first",
      "setScene({ nodes, held? })": "What phones may take over ({ id, name, kind, group?, parent? }); held (node → participant) sets who holds what",
      "holder(node) / holding(who) / held": "Who holds a node, what someone holds, the whole table",
      "layout": "Layout fields beside the attributes: tray buttons, keys, toss, point, wheel, utilities",
      "ready": "Resolves with the Remote (awaiting it starts it now), or null where this browser can’t host a phone",
      "remote": "The Remote (the host SDK), once started",
      "window.obpal.remote(opts)": "A Remote without the element: Remote.create through this script"
    },
    "csp": "script-src https://obpal.blackboxes.net; connect-src https://obpal.blackboxes.net wss://obpal.blackboxes.net. No inline scripts or styles are needed"
  }
}