{"_id":"@dugyu/luna-stage","_rev":"6-f4919b712569388289fdc1f44f262bc0","name":"@dugyu/luna-stage","dist-tags":{"oidc-bootstrap":"0.0.0-oidc-bootstrap.0","latest":"0.2.2"},"versions":{"0.0.0-oidc-bootstrap.0":{"name":"@dugyu/luna-stage","version":"0.0.0-oidc-bootstrap.0","author":{"name":"The Lynx Authors"},"license":"Apache-2.0","_id":"@dugyu/luna-stage@0.0.0-oidc-bootstrap.0","maintainers":[{"name":"sevensever","email":"dugy.guangyu@gmail.com"}],"dist":{"shasum":"5622cb2edbe830f33ed7cffe457d7e0083915ec4","tarball":"https://registry.npmjs.org/@dugyu/luna-stage/-/luna-stage-0.0.0-oidc-bootstrap.0.tgz","fileCount":3,"integrity":"sha512-ziviTjKP+Ft4KNPBeos9YGfCx/OdLKniGzRhcGv9B5hSEpICueqCDlYK9CbgQ1QrbHkOOyzRWh0r4Zu5CKw9nQ==","signatures":[{"sig":"MEQCIAhdzb1bw2Ty/gfGhoIDy3v8K3NSkxrxMNkAN4nvTtPlAiAiG/qCo7zxzVa4JdMibsFuXxIAy84FwMw0Tm/ha1nHHA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":11985},"gitHead":"a0f0ec95d47f53d93d6704898ae24eb8396b9693","_npmUser":{"name":"sevensever","email":"dugy.guangyu@gmail.com"},"_npmVersion":"11.11.0","description":"Stage system and showcase components for the L.U.N.A project (OIDC bootstrap placeholder)","directories":{},"_nodeVersion":"24.14.1","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/luna-stage_0.0.0-oidc-bootstrap.0_1778072420827_0.6381783438473314","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"name":"@dugyu/luna-stage","version":"0.1.0","keywords":["luna","stage","preview","react","lynx"],"author":{"name":"The Lynx Authors"},"license":"Apache-2.0","_id":"@dugyu/luna-stage@0.1.0","maintainers":[{"name":"sevensever","email":"dugy.guangyu@gmail.com"}],"homepage":"https://github.com/Dugyu/lunarium/tree/main/packages/luna-stage#readme","bugs":{"url":"https://github.com/Dugyu/lunarium/issues"},"dist":{"shasum":"60cf6f38a51db40eb7cdc29b3dd3da0146ca58d5","tarball":"https://registry.npmjs.org/@dugyu/luna-stage/-/luna-stage-0.1.0.tgz","fileCount":141,"integrity":"sha512-Fw+fAkSZHmVEGXHXr3BMZQ3daCZBF5pCND5022oVvQIYZicic2gCJtigh+Vzc8dpJcxYLSqauy6g3yTftHyaTg==","signatures":[{"sig":"MEQCIAlMRy7yVez7HYL98qngukb+Xwk87IRC0mpCy6HzkbKeAiA7aYxem/LHKuLBr9mBOEznFnrbFzall1PX6UqfcHRbHA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@dugyu%2fluna-stage@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":212709},"main":"./dist/index.js","type":"module","_from":"file:dugyu-luna-stage-0.1.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./lynx":{"types":"./dist/lynx/index.d.ts","import":"./dist/lynx/index.js"},"./motion":{"types":"./dist/motion/index.d.ts","import":"./dist/motion/index.js"}},"scripts":{"test":"vitest run","build":"rslib build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:cd0943da-9c42-4a89-b16c-80be4fe9ad91"}},"_resolved":"/tmp/bd6a9e4bd1e4f2103e9e416d921d891c/dugyu-luna-stage-0.1.0.tgz","_integrity":"sha512-Fw+fAkSZHmVEGXHXr3BMZQ3daCZBF5pCND5022oVvQIYZicic2gCJtigh+Vzc8dpJcxYLSqauy6g3yTftHyaTg==","repository":{"url":"git+https://github.com/Dugyu/lunarium.git","type":"git","directory":"packages/luna-stage"},"_npmVersion":"11.11.0","description":"Stage system and showcase components for the L.U.N.A project","directories":{},"sideEffects":false,"_nodeVersion":"24.14.1","dependencies":{"@dugyu/luna-core":"0.4.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"29.0.2","react":"18.3.1","motion":"12.38.0","vitest":"4.1.5","react-dom":"18.3.1","typescript":"5.9.2","@rslib/core":"0.21.1","@types/react":"18.3.24","@types/react-dom":"18.3.7","@lynx-js/web-core":"0.20.3","@rsbuild/plugin-react":"1.4.6","@testing-library/react":"16.3.2","@testing-library/jest-dom":"6.9.1"},"peerDependencies":{"react":">=18.0.0","motion":">=12.23.16","react-dom":">=18.0.0","@lynx-js/web-core":">=0.20.0"},"peerDependenciesMeta":{"motion":{"optional":true},"@lynx-js/web-core":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/luna-stage_0.1.0_1778078613645_0.0865078986586254","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@dugyu/luna-stage","version":"0.1.1","keywords":["luna","stage","preview","react","lynx"],"author":{"name":"The Lynx Authors"},"license":"Apache-2.0","_id":"@dugyu/luna-stage@0.1.1","maintainers":[{"name":"sevensever","email":"dugy.guangyu@gmail.com"}],"homepage":"https://github.com/Dugyu/lunarium/tree/main/packages/luna-stage#readme","bugs":{"url":"https://github.com/Dugyu/lunarium/issues"},"dist":{"shasum":"983710156e961e383a6ff71f57e3e173b1d479a1","tarball":"https://registry.npmjs.org/@dugyu/luna-stage/-/luna-stage-0.1.1.tgz","fileCount":141,"integrity":"sha512-qnGEF3FI6TioiPx/NhNaNowHYF6gLvo4zXl94LFiUpFrsF9rultrxozm8uNpdE5OIi+T3OYN6qHPxJhIcFnY4A==","signatures":[{"sig":"MEUCIAnhKc0ajR5VFS1tW1kPFBBUSGwHB4S7+ZO2Hq5Cg9M+AiEAyYlyg3xS7d6WnOg0romH3Pm7vedHLur+Z5axKFRe6/E=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@dugyu%2fluna-stage@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":212831},"main":"./dist/index.js","type":"module","_from":"file:dugyu-luna-stage-0.1.1.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./lynx":{"types":"./dist/lynx/index.d.ts","import":"./dist/lynx/index.js"},"./motion":{"types":"./dist/motion/index.d.ts","import":"./dist/motion/index.js"}},"scripts":{"test":"vitest run","build":"rslib build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:cd0943da-9c42-4a89-b16c-80be4fe9ad91"}},"_resolved":"/tmp/b76ddbe8435f92e8784fbe5f77ef0eea/dugyu-luna-stage-0.1.1.tgz","_integrity":"sha512-qnGEF3FI6TioiPx/NhNaNowHYF6gLvo4zXl94LFiUpFrsF9rultrxozm8uNpdE5OIi+T3OYN6qHPxJhIcFnY4A==","repository":{"url":"git+https://github.com/Dugyu/lunarium.git","type":"git","directory":"packages/luna-stage"},"_npmVersion":"11.11.0","description":"Stage system and showcase components for the L.U.N.A project","directories":{},"sideEffects":false,"_nodeVersion":"24.14.1","dependencies":{"@dugyu/luna-core":"0.4.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"29.0.2","react":"18.3.1","motion":"12.38.0","vitest":"4.1.5","react-dom":"18.3.1","typescript":"5.9.2","@rslib/core":"0.21.1","@types/react":"18.3.24","@types/react-dom":"18.3.7","@lynx-js/web-core":"0.20.3","@rsbuild/plugin-react":"1.4.6","@testing-library/react":"16.3.2","@testing-library/jest-dom":"6.9.1"},"peerDependencies":{"react":">=18.0.0","motion":">=12.23.16","@lynx-js/web-core":">=0.20.0"},"peerDependenciesMeta":{"motion":{"optional":true},"@lynx-js/web-core":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/luna-stage_0.1.1_1778149097405_0.2741267616072549","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@dugyu/luna-stage","version":"0.2.0","keywords":["luna","stage","preview","react","lynx"],"author":{"name":"The Lynx Authors"},"license":"Apache-2.0","_id":"@dugyu/luna-stage@0.2.0","maintainers":[{"name":"sevensever","email":"dugy.guangyu@gmail.com"}],"homepage":"https://github.com/Dugyu/lunarium/tree/main/packages/luna-stage#readme","bugs":{"url":"https://github.com/Dugyu/lunarium/issues"},"dist":{"shasum":"73b88582d9a41cfd084baa5d6ff20e6b91a459d6","tarball":"https://registry.npmjs.org/@dugyu/luna-stage/-/luna-stage-0.2.0.tgz","fileCount":145,"integrity":"sha512-8mV2U7W9u8pvfoI7qg79C/Gi34w+m2MAgKHaL2T+Wq0a+/1inL49DZ6/p4oxOAKDhByBN7ZHD5VyL2flJG5nuw==","signatures":[{"sig":"MEYCIQDhpfA0HnJ7QaC5j4Cls6qk/ajgO5t7MmICg20j3f80KwIhAIiIejYMa4axWGrKui0eNEbkOKTs/HmDiSgwvWMYGpq9","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@dugyu%2fluna-stage@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":226524},"main":"./dist/index.js","type":"module","_from":"file:dugyu-luna-stage-0.2.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./lynx":{"types":"./dist/lynx/index.d.ts","import":"./dist/lynx/index.js"},"./motion":{"types":"./dist/motion/index.d.ts","import":"./dist/motion/index.js"}},"scripts":{"test":"vitest run","build":"rslib build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:cd0943da-9c42-4a89-b16c-80be4fe9ad91"}},"_resolved":"/tmp/7ce7be301bafbf1a0a732b5205964fb1/dugyu-luna-stage-0.2.0.tgz","_integrity":"sha512-8mV2U7W9u8pvfoI7qg79C/Gi34w+m2MAgKHaL2T+Wq0a+/1inL49DZ6/p4oxOAKDhByBN7ZHD5VyL2flJG5nuw==","repository":{"url":"git+https://github.com/Dugyu/lunarium.git","type":"git","directory":"packages/luna-stage"},"_npmVersion":"11.12.1","description":"Stage system and showcase components for the L.U.N.A project","directories":{},"sideEffects":false,"_nodeVersion":"24.15.0","dependencies":{"@dugyu/luna-core":"0.4.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"29.0.2","react":"18.3.1","motion":"12.38.0","vitest":"4.1.5","react-dom":"18.3.1","typescript":"5.9.2","@rslib/core":"0.21.1","@types/react":"18.3.24","@types/react-dom":"18.3.7","@lynx-js/web-core":"0.20.3","@rsbuild/plugin-react":"1.4.6","@testing-library/react":"16.3.2","@testing-library/jest-dom":"6.9.1"},"peerDependencies":{"react":">=18.0.0","motion":">=12.23.16","@lynx-js/web-core":">=0.20.0"},"peerDependenciesMeta":{"motion":{"optional":true},"@lynx-js/web-core":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/luna-stage_0.2.0_1778868070084_0.5051961011845774","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@dugyu/luna-stage","version":"0.2.1","keywords":["luna","stage","preview","react","lynx"],"author":{"name":"The Lynx Authors"},"license":"Apache-2.0","_id":"@dugyu/luna-stage@0.2.1","maintainers":[{"name":"sevensever","email":"dugy.guangyu@gmail.com"}],"homepage":"https://github.com/Dugyu/lunarium/tree/main/packages/luna-stage#readme","bugs":{"url":"https://github.com/Dugyu/lunarium/issues"},"dist":{"shasum":"9ab85412a073c07fa3e544485fb1affefaaed9b3","tarball":"https://registry.npmjs.org/@dugyu/luna-stage/-/luna-stage-0.2.1.tgz","fileCount":145,"integrity":"sha512-jby48lAQauneiDqTAkI6a9W/qTtb1/3HvJgMsHxbcl8Az2dAbomeVRx87TAmhpkQrcr4c2HOarBnj1gwFQT6cA==","signatures":[{"sig":"MEYCIQDSzv/CCs2KRD9vrIm69wyER599+h7I4OBr+Aguef3ziAIhALKIYqLIKA5JxzDK7XErc8cykERTCQp3+9inK30WdtBe","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@dugyu%2fluna-stage@0.2.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":226052},"main":"./dist/index.js","type":"module","_from":"file:dugyu-luna-stage-0.2.1.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./lynx":{"types":"./dist/lynx/index.d.ts","import":"./dist/lynx/index.js"},"./motion":{"types":"./dist/motion/index.d.ts","import":"./dist/motion/index.js"}},"scripts":{"test":"vitest run","build":"rslib build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:cd0943da-9c42-4a89-b16c-80be4fe9ad91"}},"_resolved":"/tmp/00c73d9e5f24420cd05e4640f61d4aa1/dugyu-luna-stage-0.2.1.tgz","_integrity":"sha512-jby48lAQauneiDqTAkI6a9W/qTtb1/3HvJgMsHxbcl8Az2dAbomeVRx87TAmhpkQrcr4c2HOarBnj1gwFQT6cA==","repository":{"url":"git+https://github.com/Dugyu/lunarium.git","type":"git","directory":"packages/luna-stage"},"_npmVersion":"11.12.1","description":"Stage system and showcase components for the L.U.N.A project","directories":{},"sideEffects":false,"_nodeVersion":"24.15.0","dependencies":{"@dugyu/luna-core":"0.4.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"29.0.2","react":"18.3.1","motion":"12.38.0","vitest":"4.1.5","react-dom":"18.3.1","typescript":"5.9.2","@rslib/core":"0.21.1","@types/react":"18.3.24","@types/react-dom":"18.3.7","@lynx-js/web-core":"0.20.3","@rsbuild/plugin-react":"1.4.6","@testing-library/react":"16.3.2","@testing-library/jest-dom":"6.9.1"},"peerDependencies":{"react":">=18.0.0","motion":">=12.23.16","@lynx-js/web-core":">=0.20.0"},"peerDependenciesMeta":{"motion":{"optional":true},"@lynx-js/web-core":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/luna-stage_0.2.1_1778923109575_0.7565429820887912","host":"s3://npm-registry-packages-npm-production"}},"0.2.2":{"name":"@dugyu/luna-stage","version":"0.2.2","description":"Stage system and showcase components for the L.U.N.A project","keywords":["luna","stage","preview","react","lynx"],"homepage":"https://github.com/Dugyu/lunarium/tree/main/packages/luna-stage#readme","repository":{"type":"git","url":"git+https://github.com/Dugyu/lunarium.git","directory":"packages/luna-stage"},"license":"Apache-2.0","author":{"name":"The Lynx Authors"},"sideEffects":false,"type":"module","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./motion":{"types":"./dist/motion/index.d.ts","import":"./dist/motion/index.js"},"./lynx":{"types":"./dist/lynx/index.d.ts","import":"./dist/lynx/index.js"}},"main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","dependencies":{"@dugyu/luna-core":"0.5.0"},"devDependencies":{"@lynx-js/web-core":"0.20.3","@rsbuild/plugin-react":"1.4.6","@rslib/core":"0.21.1","@testing-library/jest-dom":"6.9.1","@testing-library/react":"16.3.2","@types/react":"18.3.24","@types/react-dom":"18.3.7","jsdom":"29.0.2","motion":"12.38.0","react":"18.3.1","react-dom":"18.3.1","typescript":"5.9.2","vitest":"4.1.5"},"peerDependencies":{"@lynx-js/web-core":">=0.20.0","motion":">=12.23.16","react":">=18.0.0"},"peerDependenciesMeta":{"@lynx-js/web-core":{"optional":true},"motion":{"optional":true}},"publishConfig":{"access":"public"},"scripts":{"build":"rslib build","test":"vitest run"},"_id":"@dugyu/luna-stage@0.2.2","bugs":{"url":"https://github.com/Dugyu/lunarium/issues"},"_integrity":"sha512-WiNWCzUzYI+m60Q9ESRbHaBItYdmziPde8P1QGGTu9I4B8CNatyKwGDVozIVVE9vO+AttITT9x6RsE6dbZF3yQ==","_resolved":"/tmp/cb0aa8d511feed34587bcce4ec4558ea/dugyu-luna-stage-0.2.2.tgz","_from":"file:dugyu-luna-stage-0.2.2.tgz","_nodeVersion":"24.15.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-WiNWCzUzYI+m60Q9ESRbHaBItYdmziPde8P1QGGTu9I4B8CNatyKwGDVozIVVE9vO+AttITT9x6RsE6dbZF3yQ==","shasum":"766fd09147d09dfbdeddb6eedda53be41ca3789f","tarball":"https://registry.npmjs.org/@dugyu/luna-stage/-/luna-stage-0.2.2.tgz","fileCount":145,"unpackedSize":226136,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@dugyu%2fluna-stage@0.2.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIDGN3t0DDii2se1TvbOsH1r2++lgnoOtmp1be8nx4EuUAiEAptNzjTrikEBPz9mkkN/6KHQ7cq5wYzEglcri83DEgZo="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:cd0943da-9c42-4a89-b16c-80be4fe9ad91"}},"directories":{},"maintainers":[{"name":"sevensever","email":"dugy.guangyu@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/luna-stage_0.2.2_1779032389359_0.9396808376171271"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-06T13:00:20.722Z","modified":"2026-05-17T15:39:49.756Z","0.0.0-oidc-bootstrap.0":"2026-05-06T13:00:20.987Z","0.1.0":"2026-05-06T14:43:33.787Z","0.1.1":"2026-05-07T10:18:17.548Z","0.2.0":"2026-05-15T18:01:10.276Z","0.2.1":"2026-05-16T09:18:29.751Z","0.2.2":"2026-05-17T15:39:49.505Z"},"bugs":{"url":"https://github.com/Dugyu/lunarium/issues"},"author":{"name":"The Lynx Authors"},"license":"Apache-2.0","homepage":"https://github.com/Dugyu/lunarium/tree/main/packages/luna-stage#readme","keywords":["luna","stage","preview","react","lynx"],"repository":{"type":"git","url":"git+https://github.com/Dugyu/lunarium.git","directory":"packages/luna-stage"},"description":"Stage system and showcase components for the L.U.N.A project","maintainers":[{"name":"sevensever","email":"dugy.guangyu@gmail.com"}],"readme":"# luna-stage\n\n> Stage system and showcase components for the L.U.N.A project.\n> Atomic components for rendering Lynx bundles into a device shell / stage in the browser.\n\nUse `luna-stage` when you need a single-frame preview (`Stage` + `LunaLynxStage`) or motion-enhanced presentation (`MotionStage`, etc.). For multi-stage orchestration and Studio-level layout, see [`@dugyu/luna-studio`](../luna-studio).\n\n## Contents\n\n- [Installation](#installation)\n- [Entry Points](#entry-points)\n- [Concepts](#concepts)\n- [Quick Start](#quick-start)\n- [API](#api)\n- [Usage Examples](#usage-examples)\n- [SSR / SSG Notes](#ssr--ssg-notes)\n- [Transform Utilities](#transform-utilities)\n\n## Installation\n\n```sh\npnpm i @dugyu/luna-stage\n```\n\n**Peer dependencies** — install in your application:\n\n```sh\npnpm i react\n```\n\nFor Lynx rendering (`LynxStage` / `LunaLynxStage`), install the Lynx Web runtime:\n\n```sh\npnpm i @lynx-js/web-core\n```\n\nFor motion capabilities, install `motion`:\n\n```sh\npnpm i motion\n```\n\n## Entry Points\n\n| Import path                | Requires `motion` | Requires `@lynx-js/web-core` | Use when                                                  |\n| -------------------------- | ----------------- | ---------------------------- | --------------------------------------------------------- |\n| `@dugyu/luna-stage`        | No                | No                           | Static device frame + shared hooks                        |\n| `@dugyu/luna-stage/lynx`   | No                | Yes                          | Lynx bundle rendering (`LynxStage` / `LunaLynxStage`)     |\n| `@dugyu/luna-stage/motion` | Yes               | No                           | Animated transitions + perspective camera (`MotionStage`) |\n\n## Concepts\n\n### Device Frame Model\n\n- `baseWidth` / `baseHeight` define the **design baseline** (default `375×812`), not the viewport size.\n- The frame renders at its base dimensions and uses CSS `transform: scale()` to fit into the viewport.\n- Two visual layers: **outline** (decorative border, simulates device thickness) and **frame** (content area with clip-path).\n- `Stage` only: an absolutely-positioned 0×0 anchor sits inside the viewport at the `placeX/placeY` location; the outline and frame are transformed around this anchor.\n\n### Fit System\n\n- `contain` — scales down to fit fully inside the viewport (no cropping).\n- `cover` — scales up to fully cover the viewport (may crop).\n- `alignX` / `alignY` — alignment of the frame within the viewport. In `cover` mode also determines which part of the frame is preserved when cropped. Physical directions: `left | center | right` / `top | center | bottom`.\n- `placeX` / `placeY` _(Stage only)_ — placement of the anchor within the viewport, decoupled from `alignX/Y`. Defaults to match `alignX/Y` for object-position-like semantics. Override only when you want the frame's internal alignment to differ from its viewport position (rare).\n- `fitProgress` _(motion only)_ — continuous `0→1` interpolation between `contain` and `cover`, driven by a spring.\n\n### Camera Model _(MotionStage only)_\n\nThe transform pipeline operates in two independent spaces:\n\n**Screen space**\n\n- `fit` / `fitProgress` — viewport-driven passive scaling.\n- `zoom` — user-controlled active scaling, applied on top of fit.\n- `panX` / `panY` — screen-space translation in pixels, applied after all scaling.\n\n**World space**\n\n- `world` — 3D position of the frame `{ x, y, z }`. Follows the CSS/OpenGL convention: `+Z` points toward the viewer.\n- `focalLength` — perspective intensity. When `> 0`, enables perspective projection. When `0` or omitted, orthographic.\n- `world.x/y` are projected into screen space before being composited with `panX/panY`. The spring animates the combined screen-space value.\n\nFinal scale = `lerpFitScale(contain, cover, fitSpring) × zoom × depthScale`\n\nFinal translation = `world.xy × depthScale + panXY`\n\n## Quick Start\n\n### Single-frame preview with `LunaLynxStage`\n\n```tsx\nimport { Stage, StageContainer } from '@dugyu/luna-stage';\nimport { LunaLynxStage } from '@dugyu/luna-stage/lynx';\n\nexport default function Preview() {\n  return (\n    <StageContainer className='w-full h-screen'>\n      <Stage>\n        <LunaLynxStage\n          entry='my-component'\n          bundleRoot='/bundles/'\n          lunaTheme='lunaris-dark'\n        />\n      </Stage>\n    </StageContainer>\n  );\n}\n```\n\nDefault bundle URL convention: `${bundleRoot}${entry}.web.bundle`.\n\nOverride it with `resolveBundleSrc` when your host uses a different naming rule.\n\n`bundleRoot` is normalized internally to always end with a trailing slash before URL resolution. This means callers may pass either `'/bundles'` or `'/bundles/'`, and `resolveBundleSrc` will always receive the normalized value.\n\n`lunaTheme` injects LUNA design token globals into the Lynx runtime.\n\n### Without LUNA theming\n\nUse `LynxStage` directly when you don't need LUNA global props forwarded:\n\n```tsx\nimport { Stage, StageContainer } from '@dugyu/luna-stage';\nimport { LynxStage } from '@dugyu/luna-stage/lynx';\n\n<StageContainer className='w-full h-screen'>\n  <Stage>\n    <LynxStage entry='my-component' bundleRoot='/bundles/' />\n  </Stage>\n</StageContainer>;\n```\n\n### Standalone `LynxStage`\n\n`LynxStage` can be used independently of the `Stage` system if you just want to render a Lynx view in a DOM container:\n\n```tsx\nimport { LynxStage } from '@dugyu/luna-stage/lynx';\n\nexport default function StandaloneView() {\n  return (\n    <div className='w-full h-screen'>\n      <LynxStage entry='my-component' bundleRoot='/bundles/' />\n    </div>\n  );\n}\n```\n\n## API\n\n### `Stage`\n\nStatic device frame. Reads viewport size from `StageContainer` context if present, otherwise falls back to `baseWidth/baseHeight`.\n\n`Stage` supports two ways to determine its viewport size:\n\n**Auto-fit to parent (recommended)** — wrap in `StageContainer` so the viewport auto-measures and the frame fits to it:\n\n```tsx\n<StageContainer className='w-full h-full'>\n  <Stage>\n    <YourContent />\n  </Stage>\n</StageContainer>;\n```\n\n**Explicit viewport size** — pass `width` / `height` directly to `Stage`. Useful for fixed-size previews, server-rendered layouts, or anywhere you'd rather not add a measuring wrapper:\n\n```tsx\n<Stage width={390} height={844}>\n  <YourContent />\n</Stage>;\n```\n\n> **Note:** Wrapping `Stage` in a sized `<div>` without `StageContainer` won't make the frame fit the div — `Stage` has no way to read the div's size and will fall back to `baseWidth × baseHeight`. Use `StageContainer` or pass `width`/`height` directly.\n\n#### Props\n\n| Prop         | Type                            | Default     | Description                                                                                                             |\n| ------------ | ------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------- |\n| `baseWidth`  | `number`                        | `375`       | Design baseline width in pixels.                                                                                        |\n| `baseHeight` | `number`                        | `812`       | Design baseline height in pixels.                                                                                       |\n| `width`      | `number`                        | —           | Explicit viewport size. Drives both rendered `<div>` size and fit/scale. Falls back to `StageContainer` → `baseWidth`.  |\n| `height`     | `number`                        | —           | Explicit viewport size. Drives both rendered `<div>` size and fit/scale. Falls back to `StageContainer` → `baseHeight`. |\n| `fit`        | `'contain' \\| 'cover'`          | `'contain'` | Fit mode.                                                                                                               |\n| `alignX`     | `'left' \\| 'center' \\| 'right'` | `'center'`  | Horizontal alignment of frame within viewport. In `cover` mode also determines crop anchor.                             |\n| `alignY`     | `'top' \\| 'center' \\| 'bottom'` | `'center'`  | Vertical alignment of frame within viewport. In `cover` mode also determines crop anchor.                               |\n| `placeX`     | `'left' \\| 'center' \\| 'right'` | `alignX`    | Horizontal placement of the internal anchor within the viewport. Override to decouple placement from alignment.         |\n| `placeY`     | `'top' \\| 'center' \\| 'bottom'` | `alignY`    | Vertical placement of the internal anchor within the viewport. Override to decouple placement from alignment.           |\n| `zoom`       | `number`                        | `1`         | Additional zoom factor on top of fit.                                                                                   |\n| `panX`       | `number`                        | `0`         | Screen-space translation along X (px).                                                                                  |\n| `panY`       | `number`                        | `0`         | Screen-space translation along Y (px).                                                                                  |\n| `className`  | `string`                        | —           | Applied to the outline layer.                                                                                           |\n| `style`      | `CSSProperties`                 | —           | Applied to the outline layer.                                                                                           |\n| `onClick`    | `MouseEventHandler`             | —           | —                                                                                                                       |\n\n### `StageContainer`\n\nMeasures its own DOM size via `ResizeObserver` and provides width/height to child `Stage` components via context.\n\nAdd padding, border, or layout styling to `StageContainer` freely — the measured content box is what `Stage` fits to, so padding works as expected visual margin around the frame.\n\nAvoid setting `transform`, `filter`, `perspective`, `backdrop-filter`, or `contain: paint/layout/strict` on `StageContainer` — these establish a containing block that traps `position: fixed` descendants inside the stage.\n\n#### Props\n\n| Prop             | Type     | Default | Description                    |\n| ---------------- | -------- | ------- | ------------------------------ |\n| `fallbackWidth`  | `number` | `0`     | Used before first measurement. |\n| `fallbackHeight` | `number` | `0`     | Used before first measurement. |\n\nInherits all `<div>` HTML attributes (`className`, `style`, etc.).\n\n### `LynxStage`\n\nCore component for rendering Lynx bundles into a DOM container.\n\n| Prop                  | Type                      | Default | Description                                                                                                                  |\n| --------------------- | ------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------- |\n| `entry`               | `string`                  | —       | Entry name for the Lynx bundle.                                                                                              |\n| `bundleRoot`          | `string`                  | `'/'`   | Resource root used with `entry` to locate the bundle. Normalized to always end with `/` before URL resolution.               |\n| `resolveBundleSrc`    | `function`                | —       | Optional hook for overriding how the final bundle URL is built. Receives the normalized `bundleRoot` (always ends with `/`). |\n| `globalProps`         | `Record<string, unknown>` | —       | Global props to inject into the Lynx runtime.                                                                                |\n| `groupId`             | `number`                  | `7`     | Shared Lynx worker group ID.                                                                                                 |\n| `onNativeModulesCall` | `function`                | —       | Callback for native module calls from Lynx runtime.                                                                          |\n| `onReady`             | `function`                | —       | Callback when the view has rendered successfully.                                                                            |\n| `onError`             | `function`                | —       | Callback when an error occurs during loading or rendering.                                                                   |\n\n### `LunaLynxStage`\n\nExtends `LynxStage` with LUNA theme properties.\n\n| Prop               | Type                      | Default        | Description                                   |\n| ------------------ | ------------------------- | -------------- | --------------------------------------------- |\n| `lunaTheme`        | `LunaThemeKey`            | `'luna-light'` | LUNA theme key (e.g. `'luna-light'`).         |\n| `extraGlobalProps` | `Record<string, unknown>` | —              | Additional global props.                      |\n| _(others)_         |                           |                | All `LynxStage` props (except `globalProps`). |\n\n### `MotionStage`\n\nAnimated device frame. Extends `Stage`'s base props with spring-driven transitions and a perspective camera model.\n\n`MotionStage` is recommended inside `MotionStageContainer` for responsive sizing and layout animations. When rendered standalone it falls back to its baseline size, and you can override sizing explicitly via the `width` / `height` `MotionValue<number>` props.\n\n#### Props\n\n| Prop                 | Type                            | Default             | Description                                                                      |\n| -------------------- | ------------------------------- | ------------------- | -------------------------------------------------------------------------------- |\n| `baseWidth`          | `number`                        | `375`               | Design baseline width in pixels.                                                 |\n| `baseHeight`         | `number`                        | `812`               | Design baseline height in pixels.                                                |\n| `width`              | `MotionValue<number>`           | —                   | Override the viewport width (from context). For advanced cases.                  |\n| `height`             | `MotionValue<number>`           | —                   | Override the viewport height (from context). For advanced cases.                 |\n| `fit`                | `'contain' \\| 'cover'`          | `'contain'`         | Fit mode. Ignored when `fitProgress` is set.                                     |\n| `fitProgress`        | `number`                        | —                   | `0–1` blend between contain and cover, driven by a spring.                       |\n| `zoom`               | `number`                        | `1`                 | Additional zoom factor.                                                          |\n| `panX`               | `number`                        | `0`                 | Screen-space translation along X (px).                                           |\n| `panY`               | `number`                        | `0`                 | Screen-space translation along Y (px).                                           |\n| `world`              | `{ x?, y?, z? }`                | `{ x:0, y:0, z:0 }` | World-space position. `+Z` toward viewer.                                        |\n| `focalLength`        | `number`                        | —                   | Perspective focal length (px). Omit for orthographic.                            |\n| `alignX`             | `'left' \\| 'center' \\| 'right'` | `'center'`          | Horizontal alignment of frame within viewport.                                   |\n| `alignY`             | `'top' \\| 'center' \\| 'bottom'` | `'center'`          | Vertical alignment of frame within viewport.                                     |\n| `fitTransition`      | `SpringOptions`                 | —                   | Spring for `fit` / `fitProgress`.                                                |\n| `zoomTransition`     | `SpringOptions`                 | —                   | Spring for `zoom`.                                                               |\n| `panTransition`      | `SpringOptions`                 | —                   | Spring for combined screen-space translation (`panX/Y` + projected `world.x/y`). |\n| `maskColor`          | `string`                        | `'transparent'`     | Overlay mask color.                                                              |\n| `maskOpacity`        | `number`                        | `0`                 | Overlay mask opacity (animated).                                                 |\n| `contentInteractive` | `boolean`                       | `false`             | Whether the frame's children receive pointer events.                             |\n| `className`          | `string`                        | —                   | Applied to the outline layer.                                                    |\n| `style`              | `MotionStyle`                   | —                   | Applied to the outline layer.                                                    |\n\n### `MotionStageContainer`\n\nMotion-aware equivalent of `StageContainer`. Provides `MotionValue<number>` width/height to child `MotionStage` components via context. Handles layout animations and extracts the parent scale from `transformTemplate` to keep the visual size accurate during transitions.\n\nMust be used as a child of `<AnimatePresence>` for mount/unmount animations to work.\n\n#### Props\n\n| Prop                        | Type                 | Default | Description                                                             |\n| --------------------------- | -------------------- | ------- | ----------------------------------------------------------------------- |\n| `layoutId`                  | `string`             | —       | Unique ID for layout animation matching across mount/unmount. Required. |\n| `onLayoutMeasure`           | `(box: Box) => void` | —       | Forwarded from `motion`. Called when layout box is measured.            |\n| `onLayoutAnimationStart`    | `() => void`         | —       | Forwarded from `motion`. Called when layout animation starts.           |\n| `onLayoutAnimationComplete` | `() => void`         | —       | Forwarded from `motion`. Called when layout animation completes.        |\n\nInherits all `motion.div` props (except `transformTemplate`, which is used internally).\n\n### `MotionPresentation`\n\nA zero-size flex-centered anchor that wraps `AnimatePresence propagate` for descendant mount/unmount animations.\n\n**Use when:** items are dynamically added/removed and need mount/unmount animations.\n\n**Skip when:** components are always mounted, or you only need state transition animations.\n\nInherits all `motion.div` props (`variants`, `initial`, `animate`, `exit`, `transition`, etc.).\n\n## Usage Examples\n\n### Motion: no mount/unmount\n\n```tsx\nimport { MotionStage, MotionStageContainer } from '@dugyu/luna-stage/motion';\n\n<MotionStageContainer className='w-full h-full' layoutId='stage-1'>\n  <MotionStage fitProgress={0} zoom={1.2}>\n    <YourContent />\n  </MotionStage>\n</MotionStageContainer>;\n```\n\n### Motion: with mount/unmount animations\n\n```tsx\nimport { AnimatePresence } from 'motion/react';\nimport {\n  MotionStage,\n  MotionStageContainer,\n  MotionPresentation,\n} from '@dugyu/luna-stage/motion';\n\nconst variants = {\n  initial: { opacity: 0, x: -300 },\n  animate: { opacity: 1, x: 0 },\n  exit: { opacity: 0, x: 300 },\n};\n\n<AnimatePresence mode='popLayout'>\n  {items.map(item => (\n    <MotionStageContainer key={item.id} layoutId={item.id}>\n      <MotionPresentation\n        variants={variants}\n        initial='initial'\n        animate='animate'\n        exit='exit'\n      >\n        <MotionStage world={item.world} focalLength={500}>\n          <YourContent />\n        </MotionStage>\n      </MotionPresentation>\n    </MotionStageContainer>\n  ))}\n</AnimatePresence>;\n```\n\n### Perspective carousel\n\nArrange frames along a circular arc. `+Z` is toward the viewer, so objects at `z < 0` appear further away:\n\n```tsx\nconst theta = (index - mid) * (Math.PI / 9); // 20° apart\nconst world = {\n  x: Math.sin(theta) * radius,\n  z: -Math.cos(theta) * radius, // negative = further from viewer\n};\n\n<MotionStage world={world} focalLength={500}>\n  <YourContent />\n</MotionStage>;\n```\n\n### Orthographic compare layout\n\nOmit `focalLength` (or set to `0`) — all frames render at equal scale regardless of `world.z`:\n\n```tsx\n<MotionStage world={{ x: offset, y: 0, z: 0 }}>\n  <YourContent />\n</MotionStage>;\n```\n\n## Component Table\n\n| Component              | Entry point | Description                                          |\n| ---------------------- | ----------- | ---------------------------------------------------- |\n| `Stage`                | `.`         | Static device frame                                  |\n| `StageContainer`       | `.`         | Auto-measures container, provides size via context   |\n| `LynxStage`            | `./lynx`    | Core Lynx bundle renderer (no LUNA globals)          |\n| `LunaLynxStage`        | `./lynx`    | `LynxStage` + LUNA theme prop injection              |\n| `MotionStage`          | `./motion`  | Animated device frame with perspective camera        |\n| `MotionStageContainer` | `./motion`  | Layout-animated wrapper; provides `MotionValue` size |\n| `MotionPresentation`   | `./motion`  | Mount/unmount transition orchestrator                |\n\n## Hooks\n\n| Export                      | Entry point | Description                                        |\n| --------------------------- | ----------- | -------------------------------------------------- |\n| `useEventCallback`          | `.`         | Stable callback that always calls the latest `fn`  |\n| `useIsClient`               | `.`         | SSR-safe client-side mount detection               |\n| `useContainerResize`        | `.`         | Observes container dimensions via `ResizeObserver` |\n| `useMergedRefs`             | `.`         | Merges multiple refs onto a single element         |\n| `useIsomorphicLayoutEffect` | `.`         | SSR-safe `useLayoutEffect`                         |\n| `useLynxStage`              | `./lynx`    | Core hook for managing Lynx runtime and lifecycle  |\n\n## SSR / SSG Notes\n\n`LynxStage` and `LunaLynxStage` are **client-only**. They render `null` on the server and mount only after hydration.\n\n## Bundle & Asset Conventions\n\n- By default, bundle files follow the naming pattern `*.web.bundle`.\n- `bundleRoot` is normalized to a trailing slash by the default resolver.\n- Default resolved URL: `${bundleRoot}${entry}.web.bundle`.\n- Use `resolveBundleSrc` when your host needs a different file naming rule or full URL composition.\n\n## Transform Utilities\n\nThe functions in `src/utils/transform.ts` are zero-dependency pure functions, safe to copy into other projects.\n\n| Function                                 | Description                                           |\n| ---------------------------------------- | ----------------------------------------------------- |\n| `computeScaleRange(input)`               | Returns `{ contain, cover }` scale factors            |\n| `lerpFitScale(range, t)`                 | Interpolates between contain and cover                |\n| `computeFrameOffset(input)`              | Returns `{ offsetX, offsetY }` for CSS transform      |\n| `computeDepthScale(focalLength, worldZ)` | Perspective depth scale `f / (f - z)`                 |\n| `computeScreenTranslation(input)`        | Projects world XY + screen pan into final translation |\n\nTo integrate fit/scale into a `lynx-view` container: copy `computeScaleRange`, `lerpFitScale`, and `computeFrameOffset`. Fix `lynx-view` at `baseWidth × baseHeight`, apply the computed transform via CSS, and pass design-baseline pixel dimensions to `browserConfig` rather than the container's measured size.\n","readmeFilename":"README.md"}