{
  "schema": "leumas.docs.page/1",
  "id": "pkg:@leumas/perspective-web",
  "slug": "products/perspective-web",
  "kind": "capabilities",
  "bucket": "product",
  "title": "Leumas Perspective — perspective.leumas.tech",
  "name": "Leumas Perspective",
  "eyebrow": "perspective.leumas.tech",
  "chip": null,
  "summary": "Leumas Perspective at perspective.leumas.tech — a 3D display with nothing but a webcam: head-tracked perspective for the web. Ten kits, an image room, the @leumas/headlock SDK and a single-file...",
  "keywords": [
    "head tracking",
    "head-coupled perspective",
    "webcam 3d display",
    "off-axis projection",
    "mediapipe facemesh",
    "image to 3d room"
  ],
  "audience": "both",
  "funnel": {
    "product": null,
    "cta": null
  },
  "body": "\n# Leumas Perspective — perspective.leumas.tech\n\nA 3D display with nothing but a webcam. MediaPipe FaceMesh finds the visitor's face in their own\nbrowser, the page shifts its projection as they move, and a flat screen reads as a window. Ten kits\n(seven canvas scenes, an off-axis three.js cube, an image room, a HUD parallax), the `@leumas/headlock`\nSDK, and a single-file download. Free to run; a membership keeps rooms and their embed links.\n\nBlueprint: `products/TODOS/perspective.leumas.tech.md`. Engine: `shared/packages/headlock`. API:\n`products/leumas-api/src/{routes,controllers}/perspective.js` at `/api/perspective`.\n\n## The two rules\n\n| Rule | Where it is kept |\n|---|---|\n| **The camera never starts on its own, and denial latches.** | `HeadlockStage` is the only component that starts it; the stream comes from `@leumas/devices`' `requestMedia`, so a blocked camera is never re-asked; `useMediaConsent()` reports the state without asking. |\n| **No frame ever leaves the tab.** | There is no upload path in the package or the site; `check:perspective-public` greps both for one on every build. The only bytes a member sends are a room's picture, once, on Save. |\n\n## What is here\n\n- **Engine** — `createHeadLock` (through the latch), seven built-in scenes, `createImageScene`,\n  `createRoomScene`, `createCustomScene`, `runScene`, `KITS`, `createOffAxisCube` (takes `THREE`, so the\n  package stays three-free), `HeadlockStage`, `useHeadLock`, `useMediaConsent`.\n- **MediaPipe self-hosted** at `/perspective-mediapipe/*`, copied from the installed packages at build.\n- **Pages** — `/` vitrine landing whose case IS a stage · `/kits` and `/kits/:id` · `/room` ·\n  `/sdk` · `/download` (the generated `perspective-starter.html`) · `/pricing` · `/faq` · `/legal`.\n  Not indexed: `/embed/:id`, `/portal`.\n- **Money** — Model A, no plan row: `perspective.run` on the floor; `perspective.save` and the\n  `perspective.scenes` ceiling (5 / 50 / ∞) for members. An embed answers while the owner's\n  membership holds and 404s when it lapses.\n\n## Not yet\n\n`@leumas/headlock` is not on the public npm registry; the SDK page says so. Kept rooms are private.\nThe embed catalogue rows are added once the live URLs exist.\n\n## Build and verify\n\n```bash\npnpm --filter @leumas/perspective-web build    # vendor mediapipe → starter → og → vite → prerender → crawler files → check:perspective-public\npnpm smoke:perspective-site                     # the built site through the Imperium worker + the API seam\npnpm --filter @leumas/headlock test\n```\n\nDev: `pnpm dev:perspective` (port 5084; leave `VITE_API_BASE` unset).\n",
  "source": {
    "path": "products/leumas-perspective/README.md",
    "blobSha": "",
    "commit": "",
    "committedAt": "",
    "provenance": "no-git",
    "bytes": 2811,
    "hash": "f53ce8429c00e3c0cf736a46f02a7de3d0b047fd"
  },
  "urls": {
    "html": "/p/products/perspective-web",
    "json": "/docs/products/perspective-web.json",
    "md": "/docs/products/perspective-web.md"
  },
  "links": {
    "composes": [
      "pkg:@leumas/theme",
      "pkg:@leumas/ui"
    ],
    "usedBy": [],
    "product": [],
    "howTo": [],
    "skills": []
  },
  "exports": null
}
