{
  "name": "Device orientation",
  "id": "demisel.space.orientation",
  "module": {
    "kind": "javascript",
    "id": "demisel.space.orientation",
    "name": "Device orientation",
    "inputs": [],
    "outputs": [
      {
        "id": "qx",
        "label": "qx",
        "type": "number"
      },
      {
        "id": "qy",
        "label": "qy",
        "type": "number"
      },
      {
        "id": "qz",
        "label": "qz",
        "type": "number"
      },
      {
        "id": "qw",
        "label": "qw",
        "type": "number"
      }
    ],
    "update": "(_inputs, state) => ({\n    qx: state.qx ?? 0,\n    qy: state.qy ?? 0,\n    qz: state.qz ?? 0,\n    qw: state.qw ?? 1,\n  })",
    "setup": "(_inputs, state) => {\n    state.q = { x: 0, y: 0, z: 0, w: 1 };\n    state.lastTime = null;\n\n    state.onMotion = (event) => {\n      const r = event.rotationRate;\n      if (!r) return;\n\n      const now = performance.now();\n      if (state.lastTime === null) { state.lastTime = now; return; }\n      const dt = Math.min((now - state.lastTime) / 1000, 0.05);\n      state.lastTime = now;\n\n      const wx = (r.alpha  ?? 0) * Math.PI / 180;\n      const wy = (r.beta ?? 0) * Math.PI / 180;\n      const wz = (r.gamma ?? 0) * Math.PI / 180;\n\n      const { x, y, z, w } = state.q;\n      const h = 0.5 * dt;\n      const nx = x + h * ( w*wx + y*wz - z*wy);\n      const ny = y + h * ( w*wy - x*wz + z*wx);\n      const nz = z + h * ( w*wz + x*wy - y*wx);\n      const nw = w + h * (-x*wx - y*wy - z*wz);\n      const len = Math.sqrt(nx*nx + ny*ny + nz*nz + nw*nw);\n      state.q = { x: nx/len, y: ny/len, z: nz/len, w: nw/len };\n      state.qx = state.q.x;\n      state.qy = state.q.y;\n      state.qz = state.q.z;\n      state.qw = state.q.w;\n    };\n\n    const needsPermission =\n      typeof DeviceMotionEvent !== 'undefined' &&\n      typeof DeviceMotionEvent.requestPermission === 'function';\n\n    if (!needsPermission) {\n      window.addEventListener('devicemotion', state.onMotion);\n      return;\n    }\n\n    const overlay = document.createElement('button');\n    overlay.textContent = 'Tap to enable tilt';\n    overlay.style.cssText = `\n      position: fixed; inset: 0; width: 100%; height: 100%;\n      background: transparent; border: none; cursor: pointer;\n      font-size: 1.2rem; color: white; z-index: 9999;\n    `;\n    document.body.appendChild(overlay);\n    state.overlay = overlay;\n\n    overlay.addEventListener('click', () => {\n      DeviceMotionEvent.requestPermission()\n        .then((p) => { if (p === 'granted') window.addEventListener('devicemotion', state.onMotion); })\n        .catch(() => {})\n        .finally(() => overlay.remove());\n    });\n  }",
    "teardown": "(state) => {\n    window.removeEventListener('devicemotion', state.onMotion);\n    state.overlay?.remove();\n  }"
  }
}
