diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..efd81c5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+node_modules/
+dist/
+dist-demo/
+*.tsbuildinfo
+.DS_Store
diff --git a/packages/twin-renderer/.gitignore b/packages/twin-renderer/.gitignore
new file mode 100644
index 0000000..4c61bf8
--- /dev/null
+++ b/packages/twin-renderer/.gitignore
@@ -0,0 +1,4 @@
+node_modules
+dist
+dist-demo
+*.tsbuildinfo
diff --git a/packages/twin-renderer/DESIGN.md b/packages/twin-renderer/DESIGN.md
new file mode 100644
index 0000000..665f924
--- /dev/null
+++ b/packages/twin-renderer/DESIGN.md
@@ -0,0 +1,6 @@
+# DESIGN
+
+- **Kinfolk test:** every frame must fit a Kinfolk spread — warm paper tones, restraint, no clutter. A sterile gray-studio render is a regression.
+- **Soft natural light:** Poly Haven CC0 HDRI key + warm directional fill, never a hard three-point rig.
+- **DOF on hero preset:** the `hero` camera carries shallow depth-of-field + subtle bloom; `pdp`/`lifestyle` stay crisp.
+- **No debug overlays in prod:** no axes/grid/stats helpers in the exported library — only the product, its shadow, and the post stack.
diff --git a/packages/twin-renderer/README.md b/packages/twin-renderer/README.md
new file mode 100644
index 0000000..558631e
--- /dev/null
+++ b/packages/twin-renderer/README.md
@@ -0,0 +1,125 @@
+# @shoploop/twin-renderer
+
+Browser-side Three.js renderer for `product_twin.usda` files emitted by
+[`@shoploop/connector-shopify`](../../connectors/shopify). It is the web-facing
+counterpart to the open-imagine GPU render path — **same source-of-truth USD,
+two consumers**: an interactive browser preview here, an offline EXR render there.
+
+- Minimal USDA → scene-graph parser (no heavy USD libs, no WASM).
+- `react-three-fiber` renderer with an editorial light/post rig (HDRI, DOF, bloom, ground shadow).
+- Variant sets from the twin become runtime UI controls.
+- External GLB/USDZ mesh refs load in-browser; placeholder twins render a tasteful procedural stand-in.
+
+## Install
+
+```sh
+pnpm add @shoploop/twin-renderer three @react-three/fiber @react-three/drei @react-three/postprocessing
+```
+
+`react`, `react-dom`, and the Three stack are peer/externalized — the host app owns them.
+
+## Quick start
+
+```tsx
+import { ProductTwin, VariantSelector } from "@shoploop/twin-renderer";
+
+export function Preview() {
+ return (
+
+
+ console.log(selection, commerce["commerce:price"])
+ }
+ >
+
+
+
+
+
+ );
+}
+```
+
+`` fetches and parses the `.usda`, builds the scene, and provides a
+context that the built-in `` reads. Drop the selector anywhere
+inside `` as an HTML overlay, or use it standalone with explicit
+`stage`/`value`/`onChange` props.
+
+### Props (`ProductTwin`)
+
+| prop | type | notes |
+|---|---|---|
+| `productTwinUrl` | `string` | URL/path to a `.usda` from `shoploop-shopify ingest` |
+| `stage` | `ProductTwinStage?` | pre-parsed stage; skips the fetch |
+| `initialVariants` | `Record?` | initial selection, e.g. `{ size: "_100g" }` |
+| `onVariantChange` | `(c: VariantChange) => void` | fires on every selection change |
+| `environment` | `string?` | HDRI URL or a drei preset name (`apartment`, `sunset`, …) |
+| `cameraPreset` | `"hero" \| "pdp" \| "lifestyle"` | `hero` adds shallow DOF |
+| `placeholder` | `Partial?` | `{ kind: "tea-tin" \| "box", color, radius, height }` |
+
+## Pointing at a real twin
+
+```sh
+# in connectors/shopify
+pnpm shoploop-shopify ingest --product > varitea_product_twin.usda
+```
+
+Serve the `.usda` as a static asset and pass its URL to `productTwinUrl`. When
+the connector attaches a Shopify Model3d (`prepend references = @…glb@`), the
+renderer fetches and loads it via `GLTFLoader`/`USDZLoader`. When the twin only
+carries the placeholder `def Mesh`, a procedural rounded-cylinder tea tin renders
+instead — no asset required.
+
+## Embedding
+
+### Next.js (App Router)
+
+The renderer is client-only (WebGL + `fetch`). Wrap it in a client component and
+lazy-load to keep it out of the server bundle:
+
+```tsx
+"use client";
+import dynamic from "next/dynamic";
+const ProductTwin = dynamic(
+ () => import("@shoploop/twin-renderer").then((m) => m.ProductTwin),
+ { ssr: false },
+);
+```
+
+### Remix
+
+Render inside a `` boundary (e.g. `remix-utils`) so the canvas only
+mounts in the browser; pass the twin URL from your loader data.
+
+There is **no server-side GLB rendering** — all geometry work is browser-side Three.js.
+
+## Performance notes
+
+- Target **60 fps on a 2021 MBP** for the placeholder twin at the `hero` preset.
+- Mobile budget: **≥30 fps** mid-tier phones. Bloom + DOF are the heaviest passes —
+ drop to `pdp`/`lifestyle` (DOF off) or set `bloom={0}` on low-end devices.
+- `dpr` is capped at `[1, 2]`; shadow map is 2048² (halve for mobile if needed).
+- Geometry is built once per stage; variant switches only restate commerce data,
+ so the size selector is allocation-free after first load.
+- Keep external GLBs **< 2 MB** / **< 50k tris** for a snappy first paint.
+
+## Scripts
+
+```sh
+pnpm --filter @shoploop/twin-renderer dev # Vite dev server on :5173 (demo)
+pnpm --filter @shoploop/twin-renderer build # typecheck + ESM lib (+ .d.ts) + demo site
+pnpm --filter @shoploop/twin-renderer test # vitest (parser + scene builder)
+```
+
+## Scope of the USDA parser
+
+Supports exactly the subset the Shopify connector emits: layer metadata +
+`customLayerData`, `def`/`over`/`class` prims, `Xform`/`Mesh`/`Material`/`Scope`,
+prim metadata (`kind`, `variantSets`, `variants`, `references`, `apiSchemas`),
+typed attributes (`string`/`int`/`float`/`bool`/`token`/`asset` + `[]` arrays +
+numeric tuples), relationships, `variantSet` blocks, and the `commerce:*` /
+`brand:*` namespaces. It is **not** a general OpenUSD implementation.
+
+See [DESIGN.md](./DESIGN.md) for the aesthetic contract.
diff --git a/packages/twin-renderer/demo/index.html b/packages/twin-renderer/demo/index.html
new file mode 100644
index 0000000..1814e7f
--- /dev/null
+++ b/packages/twin-renderer/demo/index.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+ Varitea · Product Twin
+
+
+
+
+
+
+
diff --git a/packages/twin-renderer/demo/main.tsx b/packages/twin-renderer/demo/main.tsx
new file mode 100644
index 0000000..81c1fa4
--- /dev/null
+++ b/packages/twin-renderer/demo/main.tsx
@@ -0,0 +1,75 @@
+import { StrictMode, useState } from "react";
+import { createRoot } from "react-dom/client";
+import { ProductTwin, type VariantChange, VariantSelector } from "../src/index.js";
+
+const USDA_URL = "/varitea_product_twin.usda";
+
+function priceLabel(commerce: VariantChange["commerce"]): string {
+ const price = commerce["commerce:price"];
+ const value = commerce["commerce:option_value"];
+ const sku = commerce["commerce:sku"];
+ const parts = [
+ value ? String(value) : null,
+ price ? `$${String(price)}` : null,
+ sku ? String(sku) : null,
+ ].filter(Boolean);
+ return parts.join(" · ");
+}
+
+function App() {
+ const [change, setChange] = useState(null);
+
+ return (
+
+
+
+
+ Drink Varitea
+
+
Loose-Leaf — Original
+
+
{change ? priceLabel(change.commerce) : "—"}
+
+
+
+
+
+ Small-batch loose-leaf tea blend. Stone-fruit and chamomile notes, cold-brew friendly.
+ Steep 3–5 min at 90°C. Geometry is a procedural placeholder until a GLB/USDZ ref lands in the
+ twin — same source-of-truth USD the open-imagine GPU path consumes.
+
+
+ );
+}
+
+createRoot(document.getElementById("root")!).render(
+
+
+ ,
+);
diff --git a/packages/twin-renderer/package.json b/packages/twin-renderer/package.json
new file mode 100644
index 0000000..26a26b3
--- /dev/null
+++ b/packages/twin-renderer/package.json
@@ -0,0 +1,58 @@
+{
+ "name": "@shoploop/twin-renderer",
+ "version": "0.1.0",
+ "description": "Browser-side Three.js renderer for product_twin.usda emitted by @shoploop/connector-shopify. Web counterpart to the open-imagine GPU render path.",
+ "license": "UNLICENSED",
+ "private": true,
+ "type": "module",
+ "main": "./dist/index.js",
+ "module": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.js"
+ }
+ },
+ "files": [
+ "dist"
+ ],
+ "sideEffects": false,
+ "scripts": {
+ "dev": "vite --mode demo --port 5173",
+ "build": "tsc -p tsconfig.build.json --noEmit && vite build && vite build --mode demo",
+ "build:lib": "vite build",
+ "build:demo": "vite build --mode demo",
+ "preview": "vite preview --mode demo --port 5173",
+ "typecheck": "tsc -p tsconfig.json --noEmit",
+ "lint": "tsc -p tsconfig.json --noEmit",
+ "test": "vitest run",
+ "test:watch": "vitest"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "dependencies": {
+ "@react-three/drei": "^9.114.0",
+ "@react-three/fiber": "^8.17.10",
+ "@react-three/postprocessing": "^2.16.3",
+ "three": "^0.169.0"
+ },
+ "devDependencies": {
+ "@types/react": "^18.3.0",
+ "@types/react-dom": "^18.3.0",
+ "@types/three": "^0.169.0",
+ "@vitejs/plugin-react": "^4.3.0",
+ "jsdom": "^24.1.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "typescript": "^5.6.0",
+ "vite": "^5.4.0",
+ "vite-plugin-dts": "^4.2.0",
+ "vitest": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+}
diff --git a/packages/twin-renderer/public/varitea_product_twin.usda b/packages/twin-renderer/public/varitea_product_twin.usda
new file mode 100644
index 0000000..d5c95b8
--- /dev/null
+++ b/packages/twin-renderer/public/varitea_product_twin.usda
@@ -0,0 +1,82 @@
+#usda 1.0
+(
+ defaultPrim = "Product_varitea_loose_leaf_original"
+ metersPerUnit = 1
+ upAxis = "Y"
+ customLayerData = {
+ string "commerce:shop" = "drinkvaritea.myshopify.com"
+ string "commerce:shopify_id" = "gid://shopify/Product/7700112233445"
+ string "commerce:generator" = "@shoploop/connector-shopify"
+ string "commerce:generated_at" = "2026-06-20T12:00:00.000Z"
+ }
+)
+
+def Xform "Product_varitea_loose_leaf_original" (
+ kind = "component"
+ variantSets = "size"
+ variants = {
+ string size = "_50g"
+ }
+)
+{
+ custom string commerce:title = "Varitea Loose-Leaf — Original"
+ custom string commerce:handle = "varitea-loose-leaf-original"
+ custom string commerce:vendor = "Drink Varitea"
+ custom string commerce:product_type = "Tea"
+ custom string commerce:status = "ACTIVE"
+ custom string commerce:description = "Small-batch loose-leaf tea blend. Stone-fruit and chamomile notes, cold-brew friendly. Steep 3–5 min at 90°C."
+ custom string commerce:price = "14.00"
+ custom string commerce:sku = "VAR-LL-50"
+ custom int commerce:inventory = 120
+ custom string[] brand:tags = ["loose-leaf", "tea", "wellness", "cold-brew"]
+ custom string brand:rules_ref = "/etc/shoploop/brand_rules.json"
+
+ def Scope "Looks"
+ {
+ def Material "PreviewMat"
+ {
+ custom asset commerce:preview_image = @https://cdn.shopify.com/s/files/1/varitea/loose-leaf-original.jpg@
+ custom string commerce:preview_alt = "Varitea loose-leaf original in a kraft pouch on linen"
+ token outputs:surface
+ }
+ }
+
+ def Mesh "Placeholder" (
+ prepend apiSchemas = ["MaterialBindingAPI"]
+ )
+ {
+ float3[] extent = [(-0.5, -0.5, -0.5), (0.5, 0.5, 0.5)]
+ int[] faceVertexCounts = [4, 4, 4, 4, 4, 4]
+ int[] faceVertexIndices = [0, 1, 3, 2, 2, 3, 5, 4, 4, 5, 7, 6, 6, 7, 1, 0, 1, 7, 5, 3, 6, 0, 2, 4]
+ point3f[] points = [(-0.5, -0.5, 0.5), (0.5, -0.5, 0.5), (-0.5, 0.5, 0.5), (0.5, 0.5, 0.5), (-0.5, 0.5, -0.5), (0.5, 0.5, -0.5), (-0.5, -0.5, -0.5), (0.5, -0.5, -0.5)]
+ uniform token subdivisionScheme = "none"
+ rel material:binding =
+ custom bool commerce:placeholder = true
+ }
+
+ variantSet "size" = {
+ "_50g" {
+ custom string commerce:option_value = "50g"
+ custom string commerce:sku = "VAR-LL-50"
+ custom string commerce:price = "14.00"
+ custom int commerce:inventory = 120
+ custom string commerce:variant_id = "gid://shopify/ProductVariant/41001"
+ }
+
+ "_100g" {
+ custom string commerce:option_value = "100g"
+ custom string commerce:sku = "VAR-LL-100"
+ custom string commerce:price = "24.00"
+ custom int commerce:inventory = 80
+ custom string commerce:variant_id = "gid://shopify/ProductVariant/41002"
+ }
+
+ "_250g" {
+ custom string commerce:option_value = "250g"
+ custom string commerce:sku = "VAR-LL-250"
+ custom string commerce:price = "52.00"
+ custom int commerce:inventory = 35
+ custom string commerce:variant_id = "gid://shopify/ProductVariant/41003"
+ }
+ }
+}
diff --git a/packages/twin-renderer/src/components/ProductTwin.tsx b/packages/twin-renderer/src/components/ProductTwin.tsx
new file mode 100644
index 0000000..2b079d2
--- /dev/null
+++ b/packages/twin-renderer/src/components/ProductTwin.tsx
@@ -0,0 +1,144 @@
+import { OrbitControls } from "@react-three/drei";
+import { Canvas } from "@react-three/fiber";
+import { type ReactNode, useEffect, useMemo, useRef, useState } from "react";
+import type * as THREE from "three";
+import {
+ type AttrValue,
+ getVariantSets,
+ parseUsda,
+ type ProductTwinStage,
+ resolveVariantData,
+} from "../parser/usda.js";
+import { buildScene, type PlaceholderConfig } from "../scene/build.js";
+import { createAssetLoader } from "../scene/loaders.js";
+import { CAMERA_PRESETS, type CameraPreset, Stage } from "./Stage.js";
+import { TwinContext } from "./context.js";
+
+export interface VariantChange {
+ selection: Record;
+ commerce: Record;
+}
+
+export interface ProductTwinProps {
+ /** URL/path to a `.usda` emitted by `shoploop-shopify ingest`. */
+ productTwinUrl: string;
+ /** Pre-parsed stage instead of a URL (skips the fetch). */
+ stage?: ProductTwinStage;
+ initialVariants?: Record;
+ onVariantChange?: (change: VariantChange) => void;
+ /** HDRI url or drei preset name. */
+ environment?: string;
+ cameraPreset?: CameraPreset;
+ placeholder?: Partial;
+ /** HTML overlay (e.g. ) rendered above the canvas. */
+ children?: ReactNode;
+ className?: string;
+ style?: React.CSSProperties;
+}
+
+function defaultsFor(stage: ProductTwinStage, initial?: Record): Record {
+ const sel: Record = {};
+ for (const set of getVariantSets(stage)) sel[set.name] = initial?.[set.name] ?? set.default;
+ return sel;
+}
+
+export function ProductTwin({
+ productTwinUrl,
+ stage: stageProp,
+ initialVariants,
+ onVariantChange,
+ environment,
+ cameraPreset = "hero",
+ placeholder,
+ children,
+ className,
+ style,
+}: ProductTwinProps) {
+ const [stage, setStage] = useState(stageProp ?? null);
+ const [selection, setSelection] = useState>({});
+
+ // Fetch + parse the USDA (unless a stage was passed directly).
+ useEffect(() => {
+ if (stageProp) {
+ setStage(stageProp);
+ return;
+ }
+ let cancelled = false;
+ fetch(productTwinUrl)
+ .then((r) => r.text())
+ .then((text) => {
+ if (!cancelled) setStage(parseUsda(text));
+ })
+ .catch(() => {
+ if (!cancelled) setStage(null);
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, [productTwinUrl, stageProp]);
+
+ // Seed the selection once the stage is known.
+ useEffect(() => {
+ if (stage) setSelection(defaultsFor(stage, initialVariants));
+ // initialVariants is intentionally read once at stage load.
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [stage]);
+
+ const loader = useMemo(() => createAssetLoader(), []);
+ const groupRef = useRef(null);
+
+ // Geometry is built per-stage; variant changes only restate commerce data
+ // (our connector's variant sets are size options over one mesh).
+ const built = useMemo(() => {
+ if (!stage) return null;
+ return buildScene(stage, { selection, loadAsset: loader, placeholder });
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [stage, loader]);
+
+ useEffect(() => {
+ groupRef.current = built?.group ?? null;
+ }, [built]);
+
+ // Restate userData + emit on every selection change.
+ useEffect(() => {
+ if (!stage || !built) return;
+ const commerce = resolveVariantData(stage, selection);
+ built.group.userData = { ...built.group.userData, variant: selection, commerce };
+ onVariantChange?.({ selection, commerce });
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [selection, built, stage]);
+
+ const setVariant = (setName: string, value: string) =>
+ setSelection((prev) => ({ ...prev, [setName]: value }));
+
+ const cam = CAMERA_PRESETS[cameraPreset];
+
+ return (
+
+
+
+
+ {built ? : null}
+
+
+
+ {children}
+
+
+ );
+}
diff --git a/packages/twin-renderer/src/components/Stage.tsx b/packages/twin-renderer/src/components/Stage.tsx
new file mode 100644
index 0000000..1d6a1cb
--- /dev/null
+++ b/packages/twin-renderer/src/components/Stage.tsx
@@ -0,0 +1,98 @@
+import { ContactShadows, Environment } from "@react-three/drei";
+import { Bloom, DepthOfField, EffectComposer, Vignette } from "@react-three/postprocessing";
+import type { ReactNode } from "react";
+
+export type CameraPreset = "hero" | "pdp" | "lifestyle";
+
+export interface CameraConfig {
+ position: [number, number, number];
+ fov: number;
+ target: [number, number, number];
+ /** Depth-of-field focus distance; only applied when `dof` is true. */
+ focusDistance: number;
+ dof: boolean;
+}
+
+// Soft, editorial framings. Hero gets shallow DOF; pdp/lifestyle stay crisp.
+export const CAMERA_PRESETS: Record = {
+ hero: { position: [0.9, 0.75, 2.0], fov: 32, target: [0, 0.45, 0], focusDistance: 0.012, dof: true },
+ pdp: { position: [0, 0.6, 2.4], fov: 28, target: [0, 0.5, 0], focusDistance: 0.02, dof: false },
+ lifestyle: { position: [1.8, 1.0, 2.6], fov: 40, target: [0, 0.4, 0], focusDistance: 0.02, dof: false },
+};
+
+// Poly Haven CC0, soft natural light. Runtime-fetched to keep the package small;
+// falls back to a built-in drei preset if the CDN is unreachable.
+export const DEFAULT_HDRI =
+ "https://dl.polyhaven.org/file/ph-assets/HDRIs/hdr/1k/studio_small_09_1k.hdr";
+
+export interface StageProps {
+ preset?: CameraPreset;
+ /** HDRI url, a drei preset name, or a checked-in path. */
+ environment?: string;
+ /** Subtle bloom intensity. 0 disables. */
+ bloom?: number;
+ children: ReactNode;
+}
+
+const DREI_PRESETS = new Set([
+ "apartment",
+ "city",
+ "dawn",
+ "forest",
+ "lobby",
+ "night",
+ "park",
+ "studio",
+ "sunset",
+ "warehouse",
+]);
+
+export function Stage({ preset = "hero", environment = DEFAULT_HDRI, bloom = 0.35, children }: StageProps) {
+ const cam = CAMERA_PRESETS[preset];
+ const usePreset = DREI_PRESETS.has(environment);
+
+ return (
+ <>
+ {usePreset ? (
+
+ ) : (
+
+ )}
+
+ {/* Soft key + warm fill. No sterile gray-studio three-point rig. */}
+
+
+
+
+ {children}
+
+
+
+
+ {cam.dof ? (
+
+ ) : (
+ <>>
+ )}
+ {bloom > 0 ? : <>>}
+
+
+ >
+ );
+}
diff --git a/packages/twin-renderer/src/components/VariantSelector.tsx b/packages/twin-renderer/src/components/VariantSelector.tsx
new file mode 100644
index 0000000..2d85a44
--- /dev/null
+++ b/packages/twin-renderer/src/components/VariantSelector.tsx
@@ -0,0 +1,63 @@
+import { getVariantSets, type ProductTwinStage } from "../parser/usda.js";
+import { useTwinContext } from "./context.js";
+
+export interface VariantSelectorProps {
+ /** Optional explicit stage; falls back to the enclosing context. */
+ stage?: ProductTwinStage | null;
+ value?: Record;
+ onChange?: (setName: string, value: string) => void;
+ className?: string;
+}
+
+// Reads variant sets straight from the parsed stage and emits selection changes.
+// Usable standalone (pass `stage`/`value`/`onChange`) or inside .
+export function VariantSelector({ stage, value, onChange, className }: VariantSelectorProps) {
+ const ctx = useTwinContext();
+ const activeStage = stage ?? ctx?.stage ?? null;
+ const selection = value ?? ctx?.selection ?? {};
+ const emit = onChange ?? ctx?.setVariant;
+
+ if (!activeStage) return null;
+ const sets = getVariantSets(activeStage);
+ if (sets.length === 0) return null;
+
+ return (
+
+ {sets.map((set) => (
+
+
+ {set.name}
+
+
+ {set.options.map((opt) => {
+ const selected = (selection[set.name] ?? set.default) === opt;
+ return (
+ emit?.(set.name, opt)}
+ style={{
+ padding: "8px 14px",
+ borderRadius: 999,
+ border: `1px solid ${selected ? "#2b2b2b" : "#d8d2c8"}`,
+ background: selected ? "#2b2b2b" : "transparent",
+ color: selected ? "#f7f3ec" : "#2b2b2b",
+ fontSize: 14,
+ cursor: "pointer",
+ transition: "all 120ms ease",
+ }}
+ >
+ {opt.replace(/^_/, "")}
+
+ );
+ })}
+
+
+ ))}
+
+ );
+}
diff --git a/packages/twin-renderer/src/components/context.ts b/packages/twin-renderer/src/components/context.ts
new file mode 100644
index 0000000..557d725
--- /dev/null
+++ b/packages/twin-renderer/src/components/context.ts
@@ -0,0 +1,14 @@
+import { createContext, useContext } from "react";
+import type { ProductTwinStage } from "../parser/usda.js";
+
+export interface TwinContextValue {
+ stage: ProductTwinStage | null;
+ selection: Record;
+ setVariant: (setName: string, value: string) => void;
+}
+
+export const TwinContext = createContext(null);
+
+export function useTwinContext(): TwinContextValue | null {
+ return useContext(TwinContext);
+}
diff --git a/packages/twin-renderer/src/index.ts b/packages/twin-renderer/src/index.ts
new file mode 100644
index 0000000..22ba236
--- /dev/null
+++ b/packages/twin-renderer/src/index.ts
@@ -0,0 +1,46 @@
+// Public API — keep this surface tiny and stable; it ships into the Varitea
+// landing page and any host React app.
+
+export { ProductTwin } from "./components/ProductTwin.js";
+export type { ProductTwinProps, VariantChange } from "./components/ProductTwin.js";
+
+export { VariantSelector } from "./components/VariantSelector.js";
+export type { VariantSelectorProps } from "./components/VariantSelector.js";
+
+export { Stage, CAMERA_PRESETS, DEFAULT_HDRI } from "./components/Stage.js";
+export type { StageProps, CameraPreset, CameraConfig } from "./components/Stage.js";
+
+export { TwinContext, useTwinContext } from "./components/context.js";
+export type { TwinContextValue } from "./components/context.js";
+
+export {
+ parseUsda,
+ getVariantSets,
+ resolveVariantData,
+ getLayerCommerce,
+} from "./parser/usda.js";
+export type {
+ ProductTwinStage,
+ Prim,
+ PrimMetadata,
+ Attribute,
+ Relationship,
+ VariantSet,
+ VariantOption,
+ VariantSetView,
+ LayerMetadata,
+ AttrValue,
+ AssetRef,
+ PathRef,
+} from "./parser/usda.js";
+
+export { buildScene, DEFAULT_PLACEHOLDER } from "./scene/build.js";
+export type {
+ BuildOptions,
+ BuiltScene,
+ PlaceholderConfig,
+ PlaceholderKind,
+ AssetLoader,
+} from "./scene/build.js";
+
+export { createAssetLoader } from "./scene/loaders.js";
diff --git a/packages/twin-renderer/src/parser/usda.ts b/packages/twin-renderer/src/parser/usda.ts
new file mode 100644
index 0000000..9388f16
--- /dev/null
+++ b/packages/twin-renderer/src/parser/usda.ts
@@ -0,0 +1,556 @@
+// Minimal USDA parser for the subset emitted by @shoploop/connector-shopify.
+// Scope is deliberately narrow — this is NOT a general OpenUSD implementation.
+// Supported: layer metadata + customLayerData, `def`/`over`/`class` prims with
+// metadata parens (kind, variantSets, variants, references, apiSchemas),
+// typed attributes (string/int/float/bool/token/asset + [] arrays + numeric
+// tuples), relationships, and `variantSet` blocks. Anything outside that surface
+// is skipped rather than throwing, so connector changes degrade gracefully.
+
+export type AssetRef = { kind: "asset"; path: string };
+export type PathRef = { kind: "path"; path: string };
+
+export type AttrValue =
+ | string
+ | number
+ | boolean
+ | AssetRef
+ | PathRef
+ | AttrValue[]
+ | number[]
+ | number[][]
+ | { [key: string]: AttrValue };
+
+export interface Attribute {
+ name: string;
+ type: string;
+ custom: boolean;
+ uniform: boolean;
+ value?: AttrValue;
+}
+
+export interface Relationship {
+ name: string;
+ target: string | string[];
+}
+
+export interface VariantOption {
+ name: string;
+ attributes: Attribute[];
+ children: Prim[];
+}
+
+export interface VariantSet {
+ name: string;
+ variants: VariantOption[];
+}
+
+export interface PrimMetadata {
+ kind?: string;
+ /** Declared variant set names, e.g. ["size"]. */
+ variantSets: string[];
+ /** Default/selected variant per set, e.g. { size: "_50g" }. */
+ variants: Record;
+ /** External asset references (GLB/USDZ) from `references = @file@`. */
+ references: AssetRef[];
+ apiSchemas: string[];
+ raw: Record;
+}
+
+export interface Prim {
+ specifier: "def" | "over" | "class";
+ typeName?: string;
+ name: string;
+ path: string;
+ metadata: PrimMetadata;
+ attributes: Attribute[];
+ relationships: Relationship[];
+ children: Prim[];
+ variantSets: VariantSet[];
+}
+
+export interface LayerMetadata {
+ defaultPrim?: string;
+ metersPerUnit?: number;
+ upAxis?: string;
+ customLayerData: Record;
+}
+
+export interface ProductTwinStage {
+ version: string;
+ metadata: LayerMetadata;
+ prims: Prim[];
+ /** Convenience: the prim named by defaultPrim, else the first prim. */
+ root?: Prim;
+}
+
+const WORD_RE = /[A-Za-z0-9_:.\-/]/;
+
+class Scanner {
+ private i = 0;
+ constructor(private readonly src: string) {}
+
+ get pos(): number {
+ return this.i;
+ }
+
+ eof(): boolean {
+ return this.i >= this.src.length;
+ }
+
+ /** Skip whitespace, commas, and `#` line comments. */
+ skip(): void {
+ while (this.i < this.src.length) {
+ const c = this.src[this.i];
+ if (c === "#") {
+ while (this.i < this.src.length && this.src[this.i] !== "\n") this.i++;
+ } else if (c === " " || c === "\t" || c === "\r" || c === "\n" || c === ",") {
+ this.i++;
+ } else {
+ break;
+ }
+ }
+ }
+
+ peekChar(): string {
+ this.skip();
+ return this.src[this.i] ?? "";
+ }
+
+ match(ch: string): boolean {
+ this.skip();
+ if (this.src[this.i] === ch) {
+ this.i++;
+ return true;
+ }
+ return false;
+ }
+
+ expect(ch: string): void {
+ if (!this.match(ch)) {
+ throw new Error(`USDA parse error: expected '${ch}' at offset ${this.i}`);
+ }
+ }
+
+ /** A bare word: identifier, namespaced name, or type (incl. trailing []). */
+ word(): string {
+ this.skip();
+ const start = this.i;
+ while (this.i < this.src.length && WORD_RE.test(this.src[this.i])) this.i++;
+ let w = this.src.slice(start, this.i);
+ // type tokens may carry an array suffix: string[] / float3[] / point3f[]
+ if (this.src[this.i] === "[" && this.src[this.i + 1] === "]") {
+ w += "[]";
+ this.i += 2;
+ }
+ return w;
+ }
+
+ /** Read a double-quoted string with USDA escapes. Assumes current char is `"`. */
+ string(): string {
+ this.skip();
+ if (this.src[this.i] !== '"') throw new Error(`expected string at ${this.i}`);
+ this.i++;
+ let out = "";
+ while (this.i < this.src.length) {
+ const c = this.src[this.i++];
+ if (c === "\\") {
+ const n = this.src[this.i++];
+ out += n === "n" ? "\n" : n === "t" ? "\t" : n;
+ } else if (c === '"') {
+ break;
+ } else {
+ out += c;
+ }
+ }
+ return out;
+ }
+
+ /** Read an `@asset@` ref. `@@` is an empty asset. Assumes current char is `@`. */
+ asset(): string {
+ this.skip();
+ this.i++; // opening @
+ const start = this.i;
+ while (this.i < this.src.length && this.src[this.i] !== "@") this.i++;
+ const v = this.src.slice(start, this.i);
+ this.i++; // closing @
+ return v;
+ }
+
+ /** Read a `` reference. Assumes current char is `<`. */
+ path(): string {
+ this.skip();
+ this.i++; // <
+ const start = this.i;
+ while (this.i < this.src.length && this.src[this.i] !== ">") this.i++;
+ const v = this.src.slice(start, this.i);
+ this.i++; // >
+ return v;
+ }
+}
+
+function isNumberWord(w: string): boolean {
+ return /^[-+]?(\d+\.?\d*|\.\d+)([eE][-+]?\d+)?$/.test(w);
+}
+
+// Read a single USDA value starting at the scanner head.
+function readValue(s: Scanner): AttrValue {
+ const c = s.peekChar();
+ if (c === '"') return s.string();
+ if (c === "@") return { kind: "asset", path: s.asset() };
+ if (c === "<") return { kind: "path", path: s.path() };
+ if (c === "[") return readArray(s);
+ if (c === "(") return readTuple(s);
+ if (c === "{") return readDict(s);
+
+ const w = s.word();
+ if (w === "true") return true;
+ if (w === "false") return false;
+ if (isNumberWord(w)) return Number(w);
+ return w; // bare token, e.g. an enum-like value
+}
+
+function readArray(s: Scanner): AttrValue[] {
+ s.expect("[");
+ const items: AttrValue[] = [];
+ while (s.peekChar() !== "]" && !s.eof()) {
+ items.push(readValue(s));
+ }
+ s.expect("]");
+ return items;
+}
+
+// Numeric tuple like (-0.5, 0.5, 0.5) -> number[].
+function readTuple(s: Scanner): number[] {
+ s.expect("(");
+ const nums: number[] = [];
+ while (s.peekChar() !== ")" && !s.eof()) {
+ const v = readValue(s);
+ if (typeof v === "number") nums.push(v);
+ }
+ s.expect(")");
+ return nums;
+}
+
+function readDict(s: Scanner): Record {
+ s.expect("{");
+ const obj: Record = {};
+ while (s.peekChar() !== "}" && !s.eof()) {
+ // entries: "key" = value OR key = value
+ s.word(); // type token (ignored)
+ const key = s.peekChar() === '"' ? s.string() : s.word();
+ s.expect("=");
+ obj[key] = readValue(s);
+ }
+ s.expect("}");
+ return obj;
+}
+
+const SPECIFIERS = new Set(["def", "over", "class"]);
+const QUALIFIERS = new Set(["custom", "uniform", "prepend", "append", "varying", "config"]);
+
+// Parse the body of a `{ ... }` prim/variant block into attrs/rels/children/variantSets.
+interface Body {
+ attributes: Attribute[];
+ relationships: Relationship[];
+ children: Prim[];
+ variantSets: VariantSet[];
+}
+
+function parseBody(s: Scanner, parentPath: string): Body {
+ s.expect("{");
+ const body: Body = { attributes: [], relationships: [], children: [], variantSets: [] };
+
+ while (!s.eof()) {
+ const c = s.peekChar();
+ if (c === "}") break;
+
+ const w = s.word();
+
+ if (SPECIFIERS.has(w)) {
+ body.children.push(parsePrim(s, w as Prim["specifier"], parentPath));
+ continue;
+ }
+ if (w === "variantSet") {
+ body.variantSets.push(parseVariantSet(s, parentPath));
+ continue;
+ }
+ if (w === "rel") {
+ body.relationships.push(parseRelationship(s));
+ continue;
+ }
+ // Otherwise it's an attribute line, possibly with qualifiers.
+ const attr = parseAttribute(s, w);
+ if (attr) body.attributes.push(attr);
+ }
+
+ s.expect("}");
+ return body;
+}
+
+function pathString(v: AttrValue): string {
+ if (typeof v === "object" && v !== null && "kind" in v) {
+ const o = v as AssetRef | PathRef;
+ if (o.kind === "path" || o.kind === "asset") return o.path;
+ }
+ return String(v);
+}
+
+function parseRelationship(s: Scanner): Relationship {
+ const name = s.word();
+ let target: string | string[] = "";
+ if (s.match("=")) {
+ const v = readValue(s);
+ target = Array.isArray(v) ? v.map(pathString) : pathString(v);
+ }
+ return { name, target };
+}
+
+// `firstWord` is the token already consumed (a qualifier or the type name).
+function parseAttribute(s: Scanner, firstWord: string): Attribute | null {
+ let custom = false;
+ let uniform = false;
+ let type = firstWord;
+
+ while (QUALIFIERS.has(type)) {
+ if (type === "custom") custom = true;
+ if (type === "uniform") uniform = true;
+ type = s.word();
+ }
+
+ if (!type) return null;
+ const name = s.word();
+ if (!name) return null;
+
+ let value: AttrValue | undefined;
+ if (s.match("=")) {
+ value = readValue(s);
+ }
+ return { name, type, custom, uniform, value };
+}
+
+function parseVariantSet(s: Scanner, parentPath: string): VariantSet {
+ const name = s.string();
+ s.expect("=");
+ s.expect("{");
+ const variants: VariantOption[] = [];
+ while (s.peekChar() !== "}" && !s.eof()) {
+ const vName = s.string();
+ const vPath = `${parentPath}{${name}=${vName}}`;
+ const body = parseBody(s, vPath);
+ variants.push({ name: vName, attributes: body.attributes, children: body.children });
+ }
+ s.expect("}");
+ return { name, variants };
+}
+
+function parsePrim(s: Scanner, specifier: Prim["specifier"], parentPath: string): Prim {
+ // After specifier: optional typeName, then quoted name.
+ let typeName: string | undefined;
+ let name: string;
+ if (s.peekChar() === '"') {
+ name = s.string();
+ } else {
+ typeName = s.word();
+ name = s.string();
+ }
+
+ const path = `${parentPath}/${name}`;
+ const metadata: PrimMetadata = {
+ variantSets: [],
+ variants: {},
+ references: [],
+ apiSchemas: [],
+ raw: {},
+ };
+
+ // Optional metadata parens.
+ if (s.peekChar() === "(") {
+ parsePrimMetadata(s, metadata);
+ }
+
+ const body: Body = s.peekChar() === "{"
+ ? parseBody(s, path)
+ : { attributes: [], relationships: [], children: [], variantSets: [] };
+
+ return {
+ specifier,
+ typeName,
+ name,
+ path,
+ metadata,
+ attributes: body.attributes,
+ relationships: body.relationships,
+ children: body.children,
+ variantSets: body.variantSets,
+ };
+}
+
+function parsePrimMetadata(s: Scanner, meta: PrimMetadata): void {
+ s.expect("(");
+ while (s.peekChar() !== ")" && !s.eof()) {
+ // Each line: [prepend|append] key = value (string-literal metadata also possible)
+ if (s.peekChar() === '"') {
+ // documentation string or similar; consume and ignore
+ s.string();
+ continue;
+ }
+ let key = s.word();
+ if (key === "prepend" || key === "append") key = s.word();
+ if (!key) break;
+ if (!s.match("=")) continue;
+ const value = readValue(s);
+ meta.raw[key] = value;
+
+ switch (key) {
+ case "kind":
+ if (typeof value === "string") meta.kind = value;
+ break;
+ case "variantSets":
+ meta.variantSets = toStringArray(value);
+ break;
+ case "variants":
+ if (isPlainObject(value)) {
+ for (const [k, v] of Object.entries(value)) meta.variants[k] = String(v);
+ }
+ break;
+ case "references":
+ meta.references = collectAssets(value);
+ break;
+ case "apiSchemas":
+ meta.apiSchemas = toStringArray(value);
+ break;
+ default:
+ break;
+ }
+ }
+ s.expect(")");
+}
+
+function isPlainObject(v: AttrValue): v is Record {
+ return typeof v === "object" && v !== null && !Array.isArray(v) && !("kind" in v);
+}
+
+function toStringArray(v: AttrValue): string[] {
+ if (typeof v === "string") return [v];
+ if (Array.isArray(v)) return v.map(String);
+ return [];
+}
+
+function collectAssets(v: AttrValue): AssetRef[] {
+ if (Array.isArray(v)) return v.flatMap(collectAssets);
+ if (typeof v === "object" && v !== null && "kind" in v && v.kind === "asset") {
+ const path = (v as AssetRef).path;
+ return path ? [{ kind: "asset", path }] : [];
+ }
+ return [];
+}
+
+function parseLayerMetadata(s: Scanner): LayerMetadata {
+ const meta: LayerMetadata = { customLayerData: {} };
+ if (s.peekChar() !== "(") return meta;
+ s.expect("(");
+ while (s.peekChar() !== ")" && !s.eof()) {
+ const key = s.word();
+ if (!key) break;
+ if (!s.match("=")) continue;
+ const value = readValue(s);
+ switch (key) {
+ case "defaultPrim":
+ if (typeof value === "string") meta.defaultPrim = value;
+ break;
+ case "metersPerUnit":
+ if (typeof value === "number") meta.metersPerUnit = value;
+ break;
+ case "upAxis":
+ if (typeof value === "string") meta.upAxis = value;
+ break;
+ case "customLayerData":
+ if (isPlainObject(value)) meta.customLayerData = value;
+ break;
+ default:
+ break;
+ }
+ }
+ s.expect(")");
+ return meta;
+}
+
+export function parseUsda(src: string): ProductTwinStage {
+ const s = new Scanner(src);
+
+ // Magic line `#usda 1.0` is consumed as a comment by skip(); recover the version.
+ const versionMatch = src.match(/#usda\s+([\d.]+)/);
+ const version = versionMatch ? versionMatch[1] : "1.0";
+
+ const metadata = parseLayerMetadata(s);
+
+ const prims: Prim[] = [];
+ while (!s.eof()) {
+ const w = s.word();
+ if (!w) break;
+ if (SPECIFIERS.has(w)) {
+ prims.push(parsePrim(s, w as Prim["specifier"], ""));
+ } else if (s.eof()) {
+ break;
+ }
+ }
+
+ const root =
+ (metadata.defaultPrim && prims.find((p) => p.name === metadata.defaultPrim)) || prims[0];
+
+ return { version, metadata, prims, root };
+}
+
+// ---- Stage helpers (used by the scene builder and components) ----------------
+
+export interface VariantSetView {
+ name: string;
+ options: string[];
+ /** Default selection from prim metadata, falls back to first option. */
+ default: string;
+}
+
+/** Flatten the declared + authored variant sets on the root prim into a UI view. */
+export function getVariantSets(stage: ProductTwinStage): VariantSetView[] {
+ const root = stage.root;
+ if (!root) return [];
+ return root.variantSets.map((vs) => {
+ const options = vs.variants.map((v) => v.name);
+ return {
+ name: vs.name,
+ options,
+ default: root.metadata.variants[vs.name] ?? options[0] ?? "",
+ };
+ });
+}
+
+/** Merge the commerce attrs that a given variant selection authors over the root. */
+export function resolveVariantData(
+ stage: ProductTwinStage,
+ selection: Record,
+): Record {
+ const root = stage.root;
+ const data: Record = {};
+ if (!root) return data;
+ for (const attr of root.attributes) {
+ if (attr.value !== undefined) data[attr.name] = attr.value;
+ }
+ for (const vs of root.variantSets) {
+ const chosen = selection[vs.name] ?? root.metadata.variants[vs.name];
+ const opt = vs.variants.find((v) => v.name === chosen);
+ if (!opt) continue;
+ for (const attr of opt.attributes) {
+ if (attr.value !== undefined) data[attr.name] = attr.value;
+ }
+ }
+ return data;
+}
+
+/** Read the layer-level commerce:* metadata (shop, generator, timestamps). */
+export function getLayerCommerce(stage: ProductTwinStage): Record {
+ const out: Record = {};
+ for (const [k, v] of Object.entries(stage.metadata.customLayerData)) {
+ if (k.startsWith("commerce:")) out[k] = String(v);
+ }
+ return out;
+}
diff --git a/packages/twin-renderer/src/scene/build.ts b/packages/twin-renderer/src/scene/build.ts
new file mode 100644
index 0000000..08d1e83
--- /dev/null
+++ b/packages/twin-renderer/src/scene/build.ts
@@ -0,0 +1,262 @@
+import * as THREE from "three";
+import {
+ type AttrValue,
+ type Prim,
+ type ProductTwinStage,
+ resolveVariantData,
+} from "../parser/usda.js";
+
+export type PlaceholderKind = "tea-tin" | "box";
+
+export interface PlaceholderConfig {
+ kind: PlaceholderKind;
+ /** Base color of the placeholder body (kraft/warm by default). */
+ color: string;
+ radius: number;
+ height: number;
+}
+
+export const DEFAULT_PLACEHOLDER: PlaceholderConfig = {
+ kind: "tea-tin",
+ color: "#c9a27a",
+ radius: 0.32,
+ height: 0.9,
+};
+
+/** Loads an external GLB/USDZ ref into an Object3D. Injected so the builder stays
+ * synchronous + network-free for tests; ProductTwin wires real three loaders. */
+export type AssetLoader = (url: string, format: "glb" | "usdz") => Promise;
+
+export interface BuildOptions {
+ selection?: Record;
+ placeholder?: Partial;
+ loadAsset?: AssetLoader;
+ /** Called once any async external assets have been attached. */
+ onAssetLoaded?: (object: THREE.Object3D) => void;
+}
+
+export interface BuiltScene {
+ group: THREE.Group;
+ /** Resolved commerce/brand attrs for the active variant selection. */
+ variantData: Record;
+ /** True when a procedural placeholder stands in for real geometry. */
+ isPlaceholder: boolean;
+ /** External asset URLs discovered in the stage (may still be loading). */
+ externalRefs: { url: string; format: "glb" | "usdz" }[];
+}
+
+function assetFormat(url: string): "glb" | "usdz" | null {
+ const clean = url.split("?")[0].toLowerCase();
+ if (clean.endsWith(".glb") || clean.endsWith(".gltf")) return "glb";
+ if (clean.endsWith(".usdz")) return "usdz";
+ return null;
+}
+
+// A tasteful stand-in: rounded cylinder reading as a tea tin. No debug look.
+function buildTeaTin(cfg: PlaceholderConfig): THREE.Group {
+ const g = new THREE.Group();
+ g.name = "PlaceholderTeaTin";
+
+ const bodyMat = new THREE.MeshStandardMaterial({
+ color: new THREE.Color(cfg.color),
+ roughness: 0.55,
+ metalness: 0.15,
+ });
+ const lidMat = new THREE.MeshStandardMaterial({
+ color: new THREE.Color(cfg.color).multiplyScalar(0.82),
+ roughness: 0.4,
+ metalness: 0.25,
+ });
+
+ const r = cfg.radius;
+ const h = cfg.height;
+ const seg = 64;
+
+ const body = new THREE.Mesh(new THREE.CylinderGeometry(r, r, h * 0.82, seg, 1, false), bodyMat);
+ body.position.y = h * 0.41;
+ body.castShadow = true;
+ body.receiveShadow = true;
+ g.add(body);
+
+ // Rounded shoulder + lid for the tin silhouette.
+ const shoulder = new THREE.Mesh(new THREE.TorusGeometry(r * 0.96, r * 0.12, 16, seg), lidMat);
+ shoulder.rotation.x = Math.PI / 2;
+ shoulder.position.y = h * 0.82;
+ shoulder.castShadow = true;
+ g.add(shoulder);
+
+ const lid = new THREE.Mesh(new THREE.CylinderGeometry(r * 0.92, r * 0.96, h * 0.16, seg), lidMat);
+ lid.position.y = h * 0.9;
+ lid.castShadow = true;
+ g.add(lid);
+
+ return g;
+}
+
+function buildBox(cfg: PlaceholderConfig): THREE.Group {
+ const g = new THREE.Group();
+ g.name = "PlaceholderBox";
+ const mat = new THREE.MeshStandardMaterial({
+ color: new THREE.Color(cfg.color),
+ roughness: 0.6,
+ metalness: 0.1,
+ });
+ const s = cfg.radius * 1.6;
+ const mesh = new THREE.Mesh(new THREE.BoxGeometry(s, cfg.height, s), mat);
+ mesh.position.y = cfg.height / 2;
+ mesh.castShadow = true;
+ mesh.receiveShadow = true;
+ g.add(mesh);
+ return g;
+}
+
+function buildPlaceholder(cfg: PlaceholderConfig): THREE.Group {
+ return cfg.kind === "box" ? buildBox(cfg) : buildTeaTin(cfg);
+}
+
+// Real authored mesh geometry (the connector's placeholder cube has explicit
+// points/faceVertexIndices). We honor it when present so nothing is invented.
+function buildAuthoredMesh(prim: Prim): THREE.Mesh | null {
+ const points = prim.attributes.find((a) => a.name === "points")?.value;
+ const counts = prim.attributes.find((a) => a.name === "faceVertexCounts")?.value;
+ const indices = prim.attributes.find((a) => a.name === "faceVertexIndices")?.value;
+ if (!Array.isArray(points) || !Array.isArray(indices) || !Array.isArray(counts)) return null;
+
+ const flatPoints: number[] = [];
+ for (const p of points as number[][]) {
+ if (Array.isArray(p)) flatPoints.push(p[0], p[1], p[2]);
+ }
+ if (flatPoints.length === 0) return null;
+
+ // Triangulate polygons (fan) using faceVertexCounts + faceVertexIndices.
+ const tri: number[] = [];
+ let cursor = 0;
+ for (const count of counts as number[]) {
+ const face = (indices as number[]).slice(cursor, cursor + count);
+ cursor += count;
+ for (let k = 1; k < count - 1; k++) {
+ tri.push(face[0], face[k], face[k + 1]);
+ }
+ }
+
+ const geo = new THREE.BufferGeometry();
+ geo.setAttribute("position", new THREE.Float32BufferAttribute(flatPoints, 3));
+ geo.setIndex(tri);
+ geo.computeVertexNormals();
+
+ const mesh = new THREE.Mesh(
+ geo,
+ new THREE.MeshStandardMaterial({ color: new THREE.Color("#c9a27a"), roughness: 0.6 }),
+ );
+ mesh.name = prim.name;
+ mesh.castShadow = true;
+ mesh.receiveShadow = true;
+ return mesh;
+}
+
+function isPlaceholderMesh(prim: Prim): boolean {
+ const flag = prim.attributes.find((a) => a.name === "commerce:placeholder")?.value;
+ return flag === true;
+}
+
+// Walk a prim subtree and attach geometry / external-ref placeholders.
+function buildPrim(
+ prim: Prim,
+ cfg: PlaceholderConfig,
+ opts: BuildOptions,
+ out: { externalRefs: BuiltScene["externalRefs"]; usedPlaceholder: boolean },
+): THREE.Object3D | null {
+ // External GLB/USDZ reference on an Xform (connector's "Geometry" prim).
+ const ref = prim.metadata.references[0];
+ if (ref) {
+ const fmt = assetFormat(ref.path);
+ if (fmt) {
+ out.externalRefs.push({ url: ref.path, format: fmt });
+ const holder = new THREE.Group();
+ holder.name = prim.name;
+ if (opts.loadAsset) {
+ opts
+ .loadAsset(ref.path, fmt)
+ .then((obj) => {
+ holder.add(obj);
+ opts.onAssetLoaded?.(obj);
+ })
+ .catch(() => {
+ // Network/loader failure degrades to a procedural placeholder.
+ const ph = buildPlaceholder(cfg);
+ holder.add(ph);
+ opts.onAssetLoaded?.(ph);
+ });
+ }
+ return holder;
+ }
+ }
+
+ if (prim.typeName === "Mesh") {
+ if (isPlaceholderMesh(prim)) {
+ out.usedPlaceholder = true;
+ return buildPlaceholder(cfg);
+ }
+ const authored = buildAuthoredMesh(prim);
+ if (authored) return authored;
+ out.usedPlaceholder = true;
+ return buildPlaceholder(cfg);
+ }
+
+ // Scope/Material/other: recurse into children only (no renderable geometry here).
+ if (prim.typeName === "Xform" || prim.typeName === "Scope" || !prim.typeName) {
+ const group = new THREE.Group();
+ group.name = prim.name;
+ let added = false;
+ for (const child of prim.children) {
+ const obj = buildPrim(child, cfg, opts, out);
+ if (obj) {
+ group.add(obj);
+ added = true;
+ }
+ }
+ return added ? group : null;
+ }
+
+ return null;
+}
+
+export function buildScene(stage: ProductTwinStage, opts: BuildOptions = {}): BuiltScene {
+ const cfg: PlaceholderConfig = { ...DEFAULT_PLACEHOLDER, ...opts.placeholder };
+ const selection = opts.selection ?? {};
+ const root = stage.root;
+
+ const group = new THREE.Group();
+ group.name = root?.name ?? "ProductTwin";
+
+ const out = { externalRefs: [] as BuiltScene["externalRefs"], usedPlaceholder: false };
+
+ if (root) {
+ let rendered = false;
+ for (const child of root.children) {
+ const obj = buildPrim(child, cfg, opts, out);
+ if (obj) {
+ group.add(obj);
+ rendered = true;
+ }
+ }
+ // Root with no renderable children still gets a placeholder so the stage is never empty.
+ if (!rendered) {
+ group.add(buildPlaceholder(cfg));
+ out.usedPlaceholder = true;
+ }
+ } else {
+ group.add(buildPlaceholder(cfg));
+ out.usedPlaceholder = true;
+ }
+
+ const variantData = resolveVariantData(stage, selection);
+ group.userData = { ...group.userData, variant: selection, commerce: variantData };
+
+ return {
+ group,
+ variantData,
+ isPlaceholder: out.usedPlaceholder,
+ externalRefs: out.externalRefs,
+ };
+}
diff --git a/packages/twin-renderer/src/scene/loaders.ts b/packages/twin-renderer/src/scene/loaders.ts
new file mode 100644
index 0000000..e2206ec
--- /dev/null
+++ b/packages/twin-renderer/src/scene/loaders.ts
@@ -0,0 +1,30 @@
+import * as THREE from "three";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
+import { USDZLoader } from "three/examples/jsm/loaders/USDZLoader.js";
+import type { AssetLoader } from "./build.js";
+
+// Browser-side loaders for external mesh refs. Kept out of build.ts so the
+// builder remains synchronous and network-free under jsdom tests.
+export function createAssetLoader(): AssetLoader {
+ const gltf = new GLTFLoader();
+ const usdz = new USDZLoader();
+
+ return (url, format) =>
+ new Promise((resolve, reject) => {
+ if (format === "glb") {
+ gltf.load(
+ url,
+ (g) => resolve(g.scene),
+ undefined,
+ (err) => reject(err),
+ );
+ } else {
+ usdz.load(
+ url,
+ (obj) => resolve(obj),
+ undefined,
+ (err) => reject(err),
+ );
+ }
+ });
+}
diff --git a/packages/twin-renderer/test/build.test.ts b/packages/twin-renderer/test/build.test.ts
new file mode 100644
index 0000000..1f3ab1e
--- /dev/null
+++ b/packages/twin-renderer/test/build.test.ts
@@ -0,0 +1,90 @@
+import { readFileSync } from "node:fs";
+import { join } from "node:path";
+import * as THREE from "three";
+import { describe, expect, it, vi } from "vitest";
+import { parseUsda } from "../src/parser/usda.js";
+import { buildScene } from "../src/scene/build.js";
+
+const FIXTURE = join(process.cwd(), "test/fixtures/varitea_product_twin.usda");
+const stage = parseUsda(readFileSync(FIXTURE, "utf8"));
+
+function meshCount(root: THREE.Object3D): number {
+ let n = 0;
+ root.traverse((o) => {
+ if ((o as THREE.Mesh).isMesh) n++;
+ });
+ return n;
+}
+
+describe("buildScene — Varitea fixture", () => {
+ it("produces a THREE.Group with a procedural placeholder for the default variant", () => {
+ const built = buildScene(stage, { selection: { size: "_50g" } });
+ expect(built.group).toBeInstanceOf(THREE.Group);
+ expect(built.isPlaceholder).toBe(true);
+ expect(meshCount(built.group)).toBeGreaterThan(0);
+ expect(built.externalRefs).toHaveLength(0);
+ });
+
+ it("names the root group after the product prim", () => {
+ const built = buildScene(stage);
+ expect(built.group.name).toBe("Product_varitea_loose_leaf_original");
+ });
+
+ it("attaches resolved commerce data to userData per selection", () => {
+ const fifty = buildScene(stage, { selection: { size: "_50g" } });
+ expect(fifty.variantData["commerce:price"]).toBe("14.00");
+ expect(fifty.group.userData.commerce["commerce:price"]).toBe("14.00");
+ expect(fifty.group.userData.variant).toEqual({ size: "_50g" });
+
+ const big = buildScene(stage, { selection: { size: "_250g" } });
+ expect(big.variantData["commerce:price"]).toBe("52.00");
+ expect(big.variantData["commerce:sku"]).toBe("VAR-LL-250");
+ });
+
+ it("uses the tea-tin placeholder by default and a box when configured", () => {
+ const tin = buildScene(stage, { placeholder: { kind: "tea-tin" } });
+ let foundTin = false;
+ tin.group.traverse((o) => {
+ if (o.name === "PlaceholderTeaTin") foundTin = true;
+ });
+ expect(foundTin).toBe(true);
+
+ const box = buildScene(stage, { placeholder: { kind: "box" } });
+ let foundBox = false;
+ box.group.traverse((o) => {
+ if (o.name === "PlaceholderBox") foundBox = true;
+ });
+ expect(foundBox).toBe(true);
+ });
+
+ it("discovers external GLB refs and invokes the injected loader", async () => {
+ const src = `#usda 1.0
+def Xform "Product" (
+)
+{
+ def Xform "Geometry" (
+ prepend references = @https://cdn.example.com/tin.glb@
+ )
+ {
+ custom string commerce:mesh_source = "https://cdn.example.com/tin.glb"
+ }
+}
+`;
+ const refStage = parseUsda(src);
+ const loaded = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1));
+ loaded.name = "LoadedGLB";
+ const loadAsset = vi.fn().mockResolvedValue(loaded);
+
+ const built = buildScene(refStage, { loadAsset });
+ expect(built.externalRefs).toEqual([{ url: "https://cdn.example.com/tin.glb", format: "glb" }]);
+ expect(loadAsset).toHaveBeenCalledWith("https://cdn.example.com/tin.glb", "glb");
+
+ await Promise.resolve();
+ await Promise.resolve();
+ let found = false;
+ built.group.traverse((o) => {
+ if (o.name === "LoadedGLB") found = true;
+ });
+ expect(found).toBe(true);
+ });
+});
diff --git a/packages/twin-renderer/test/fixtures/varitea_product_twin.usda b/packages/twin-renderer/test/fixtures/varitea_product_twin.usda
new file mode 100644
index 0000000..d5c95b8
--- /dev/null
+++ b/packages/twin-renderer/test/fixtures/varitea_product_twin.usda
@@ -0,0 +1,82 @@
+#usda 1.0
+(
+ defaultPrim = "Product_varitea_loose_leaf_original"
+ metersPerUnit = 1
+ upAxis = "Y"
+ customLayerData = {
+ string "commerce:shop" = "drinkvaritea.myshopify.com"
+ string "commerce:shopify_id" = "gid://shopify/Product/7700112233445"
+ string "commerce:generator" = "@shoploop/connector-shopify"
+ string "commerce:generated_at" = "2026-06-20T12:00:00.000Z"
+ }
+)
+
+def Xform "Product_varitea_loose_leaf_original" (
+ kind = "component"
+ variantSets = "size"
+ variants = {
+ string size = "_50g"
+ }
+)
+{
+ custom string commerce:title = "Varitea Loose-Leaf — Original"
+ custom string commerce:handle = "varitea-loose-leaf-original"
+ custom string commerce:vendor = "Drink Varitea"
+ custom string commerce:product_type = "Tea"
+ custom string commerce:status = "ACTIVE"
+ custom string commerce:description = "Small-batch loose-leaf tea blend. Stone-fruit and chamomile notes, cold-brew friendly. Steep 3–5 min at 90°C."
+ custom string commerce:price = "14.00"
+ custom string commerce:sku = "VAR-LL-50"
+ custom int commerce:inventory = 120
+ custom string[] brand:tags = ["loose-leaf", "tea", "wellness", "cold-brew"]
+ custom string brand:rules_ref = "/etc/shoploop/brand_rules.json"
+
+ def Scope "Looks"
+ {
+ def Material "PreviewMat"
+ {
+ custom asset commerce:preview_image = @https://cdn.shopify.com/s/files/1/varitea/loose-leaf-original.jpg@
+ custom string commerce:preview_alt = "Varitea loose-leaf original in a kraft pouch on linen"
+ token outputs:surface
+ }
+ }
+
+ def Mesh "Placeholder" (
+ prepend apiSchemas = ["MaterialBindingAPI"]
+ )
+ {
+ float3[] extent = [(-0.5, -0.5, -0.5), (0.5, 0.5, 0.5)]
+ int[] faceVertexCounts = [4, 4, 4, 4, 4, 4]
+ int[] faceVertexIndices = [0, 1, 3, 2, 2, 3, 5, 4, 4, 5, 7, 6, 6, 7, 1, 0, 1, 7, 5, 3, 6, 0, 2, 4]
+ point3f[] points = [(-0.5, -0.5, 0.5), (0.5, -0.5, 0.5), (-0.5, 0.5, 0.5), (0.5, 0.5, 0.5), (-0.5, 0.5, -0.5), (0.5, 0.5, -0.5), (-0.5, -0.5, -0.5), (0.5, -0.5, -0.5)]
+ uniform token subdivisionScheme = "none"
+ rel material:binding =
+ custom bool commerce:placeholder = true
+ }
+
+ variantSet "size" = {
+ "_50g" {
+ custom string commerce:option_value = "50g"
+ custom string commerce:sku = "VAR-LL-50"
+ custom string commerce:price = "14.00"
+ custom int commerce:inventory = 120
+ custom string commerce:variant_id = "gid://shopify/ProductVariant/41001"
+ }
+
+ "_100g" {
+ custom string commerce:option_value = "100g"
+ custom string commerce:sku = "VAR-LL-100"
+ custom string commerce:price = "24.00"
+ custom int commerce:inventory = 80
+ custom string commerce:variant_id = "gid://shopify/ProductVariant/41002"
+ }
+
+ "_250g" {
+ custom string commerce:option_value = "250g"
+ custom string commerce:sku = "VAR-LL-250"
+ custom string commerce:price = "52.00"
+ custom int commerce:inventory = 35
+ custom string commerce:variant_id = "gid://shopify/ProductVariant/41003"
+ }
+ }
+}
diff --git a/packages/twin-renderer/test/parser.test.ts b/packages/twin-renderer/test/parser.test.ts
new file mode 100644
index 0000000..5e32af2
--- /dev/null
+++ b/packages/twin-renderer/test/parser.test.ts
@@ -0,0 +1,126 @@
+import { readFileSync } from "node:fs";
+import { join } from "node:path";
+import { describe, expect, it } from "vitest";
+import {
+ getLayerCommerce,
+ getVariantSets,
+ parseUsda,
+ resolveVariantData,
+} from "../src/parser/usda.js";
+
+const FIXTURE = join(process.cwd(), "test/fixtures/varitea_product_twin.usda");
+const usda = readFileSync(FIXTURE, "utf8");
+
+describe("parseUsda — Varitea fixture", () => {
+ const stage = parseUsda(usda);
+
+ it("captures layer metadata + commerce:* customLayerData", () => {
+ expect(stage.version).toBe("1.0");
+ expect(stage.metadata.defaultPrim).toBe("Product_varitea_loose_leaf_original");
+ expect(stage.metadata.upAxis).toBe("Y");
+ expect(stage.metadata.metersPerUnit).toBe(1);
+
+ const layer = getLayerCommerce(stage);
+ expect(layer["commerce:shop"]).toBe("drinkvaritea.myshopify.com");
+ expect(layer["commerce:generator"]).toBe("@shoploop/connector-shopify");
+ expect(layer["commerce:shopify_id"]).toBe("gid://shopify/Product/7700112233445");
+ });
+
+ it("resolves the root Xform prim with kind + variant metadata", () => {
+ const root = stage.root!;
+ expect(root.specifier).toBe("def");
+ expect(root.typeName).toBe("Xform");
+ expect(root.name).toBe("Product_varitea_loose_leaf_original");
+ expect(root.metadata.kind).toBe("component");
+ expect(root.metadata.variantSets).toEqual(["size"]);
+ expect(root.metadata.variants).toEqual({ size: "_50g" });
+ });
+
+ it("reads root-level commerce + brand attributes with correct types", () => {
+ const attrs = Object.fromEntries(stage.root!.attributes.map((a) => [a.name, a.value]));
+ expect(attrs["commerce:title"]).toBe("Varitea Loose-Leaf — Original");
+ expect(attrs["commerce:price"]).toBe("14.00");
+ expect(attrs["commerce:sku"]).toBe("VAR-LL-50");
+ expect(attrs["commerce:inventory"]).toBe(120);
+ expect(attrs["brand:tags"]).toEqual(["loose-leaf", "tea", "wellness", "cold-brew"]);
+ expect(attrs["brand:rules_ref"]).toBe("/etc/shoploop/brand_rules.json");
+ });
+
+ it("parses the Looks scope, Material, and asset preview ref", () => {
+ const looks = stage.root!.children.find((c) => c.name === "Looks");
+ expect(looks?.typeName).toBe("Scope");
+ const mat = looks?.children.find((c) => c.name === "PreviewMat");
+ expect(mat?.typeName).toBe("Material");
+ const preview = mat?.attributes.find((a) => a.name === "commerce:preview_image")?.value;
+ expect(preview).toEqual({
+ kind: "asset",
+ path: "https://cdn.shopify.com/s/files/1/varitea/loose-leaf-original.jpg",
+ });
+ });
+
+ it("parses the placeholder Mesh with geometry + material binding rel", () => {
+ const mesh = stage.root!.children.find((c) => c.name === "Placeholder");
+ expect(mesh?.typeName).toBe("Mesh");
+ expect(mesh?.metadata.apiSchemas).toEqual(["MaterialBindingAPI"]);
+ expect(mesh?.attributes.find((a) => a.name === "commerce:placeholder")?.value).toBe(true);
+ const points = mesh?.attributes.find((a) => a.name === "points")?.value as number[][];
+ expect(points).toHaveLength(8);
+ expect(points[0]).toEqual([-0.5, -0.5, 0.5]);
+ const binding = mesh?.relationships.find((r) => r.name === "material:binding");
+ expect(binding?.target).toBe("/Product_varitea_loose_leaf_original/Looks/PreviewMat");
+ });
+
+ it("exposes the size variant set with three options", () => {
+ const sets = getVariantSets(stage);
+ expect(sets).toHaveLength(1);
+ expect(sets[0].name).toBe("size");
+ expect(sets[0].options).toEqual(["_50g", "_100g", "_250g"]);
+ expect(sets[0].default).toBe("_50g");
+ });
+
+ it("merges variant-authored commerce data per selection", () => {
+ expect(resolveVariantData(stage, { size: "_50g" })["commerce:price"]).toBe("14.00");
+ expect(resolveVariantData(stage, { size: "_100g" })["commerce:price"]).toBe("24.00");
+ const big = resolveVariantData(stage, { size: "_250g" });
+ expect(big["commerce:price"]).toBe("52.00");
+ expect(big["commerce:sku"]).toBe("VAR-LL-250");
+ expect(big["commerce:inventory"]).toBe(35);
+ expect(big["commerce:variant_id"]).toBe("gid://shopify/ProductVariant/41003");
+ });
+});
+
+describe("parseUsda — external reference + over subset", () => {
+ it("captures external GLB references on an Xform", () => {
+ const src = `#usda 1.0
+def Xform "Geometry" (
+ prepend references = @https://cdn.example.com/tin.glb@
+)
+{
+ custom string commerce:mesh_source = "https://cdn.example.com/tin.glb"
+ custom token commerce:mesh_format = "glb"
+}
+`;
+ const stage = parseUsda(src);
+ expect(stage.root!.metadata.references).toEqual([
+ { kind: "asset", path: "https://cdn.example.com/tin.glb" },
+ ]);
+ });
+
+ it("parses `over` specifier and multi-variantSet arrays", () => {
+ const src = `#usda 1.0
+over "Product" (
+ variantSets = ["size", "color"]
+ variants = {
+ string size = "m"
+ string color = "sage"
+ }
+)
+{
+}
+`;
+ const stage = parseUsda(src);
+ expect(stage.root!.specifier).toBe("over");
+ expect(stage.root!.metadata.variantSets).toEqual(["size", "color"]);
+ expect(stage.root!.metadata.variants).toEqual({ size: "m", color: "sage" });
+ });
+});
diff --git a/packages/twin-renderer/tsconfig.build.json b/packages/twin-renderer/tsconfig.build.json
new file mode 100644
index 0000000..2dd3057
--- /dev/null
+++ b/packages/twin-renderer/tsconfig.build.json
@@ -0,0 +1,7 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "types": ["node"]
+ },
+ "include": ["src"]
+}
diff --git a/packages/twin-renderer/tsconfig.json b/packages/twin-renderer/tsconfig.json
new file mode 100644
index 0000000..8dc5f85
--- /dev/null
+++ b/packages/twin-renderer/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "target": "ES2021",
+ "lib": ["ES2021", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "jsx": "react-jsx",
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "verbatimModuleSyntax": false,
+ "types": ["vitest/globals", "node"]
+ },
+ "include": ["src", "demo", "test", "vite.config.ts", "vitest.config.ts"]
+}
diff --git a/packages/twin-renderer/vite.config.ts b/packages/twin-renderer/vite.config.ts
new file mode 100644
index 0000000..6a0d933
--- /dev/null
+++ b/packages/twin-renderer/vite.config.ts
@@ -0,0 +1,47 @@
+import { resolve } from "node:path";
+import react from "@vitejs/plugin-react";
+import { defineConfig } from "vite";
+import dts from "vite-plugin-dts";
+
+// Two build targets share one config:
+// default mode -> ESM library (src/index.ts), externalized peers, .d.ts emitted
+// --mode demo -> standalone static site from demo/index.html
+export default defineConfig(({ mode }) => {
+ if (mode === "demo") {
+ return {
+ plugins: [react()],
+ root: resolve(__dirname, "demo"),
+ publicDir: resolve(__dirname, "public"),
+ build: {
+ outDir: resolve(__dirname, "dist-demo"),
+ emptyOutDir: true,
+ },
+ };
+ }
+
+ return {
+ plugins: [
+ react(),
+ dts({ include: ["src"], rollupTypes: true, tsconfigPath: "./tsconfig.build.json" }),
+ ],
+ build: {
+ lib: {
+ entry: resolve(__dirname, "src/index.ts"),
+ formats: ["es"],
+ fileName: () => "index.js",
+ },
+ rollupOptions: {
+ // Keep the bundle tiny and embeddable: the host app owns React + Three.
+ external: [
+ "react",
+ "react-dom",
+ "react/jsx-runtime",
+ "three",
+ "@react-three/fiber",
+ "@react-three/drei",
+ "@react-three/postprocessing",
+ ],
+ },
+ },
+ };
+});
diff --git a/packages/twin-renderer/vitest.config.ts b/packages/twin-renderer/vitest.config.ts
new file mode 100644
index 0000000..f82cfeb
--- /dev/null
+++ b/packages/twin-renderer/vitest.config.ts
@@ -0,0 +1,9 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ globals: true,
+ environment: "jsdom",
+ include: ["test/**/*.test.ts", "test/**/*.test.tsx"],
+ },
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
new file mode 100644
index 0000000..5feca8e
--- /dev/null
+++ b/pnpm-lock.yaml
@@ -0,0 +1,3186 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ packages/twin-renderer:
+ dependencies:
+ '@react-three/drei':
+ specifier: ^9.114.0
+ version: 9.122.0(@react-three/fiber@8.18.0(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.169.0))(@types/react@18.3.31)(@types/three@0.169.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.169.0)(use-sync-external-store@1.6.0(react@18.3.1))
+ '@react-three/fiber':
+ specifier: ^8.17.10
+ version: 8.18.0(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.169.0)
+ '@react-three/postprocessing':
+ specifier: ^2.16.3
+ version: 2.19.1(@react-three/fiber@8.18.0(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.169.0))(@types/three@0.169.0)(react@18.3.1)(three@0.169.0)
+ three:
+ specifier: ^0.169.0
+ version: 0.169.0
+ devDependencies:
+ '@types/react':
+ specifier: ^18.3.0
+ version: 18.3.31
+ '@types/react-dom':
+ specifier: ^18.3.0
+ version: 18.3.7(@types/react@18.3.31)
+ '@types/three':
+ specifier: ^0.169.0
+ version: 0.169.0
+ '@vitejs/plugin-react':
+ specifier: ^4.3.0
+ version: 4.7.0(vite@5.4.21)
+ jsdom:
+ specifier: ^24.1.0
+ version: 24.1.3
+ react:
+ specifier: ^18.3.1
+ version: 18.3.1
+ react-dom:
+ specifier: ^18.3.1
+ version: 18.3.1(react@18.3.1)
+ typescript:
+ specifier: ^5.6.0
+ version: 5.9.3
+ vite:
+ specifier: ^5.4.0
+ version: 5.4.21
+ vite-plugin-dts:
+ specifier: ^4.2.0
+ version: 4.5.4(rollup@4.62.2)(typescript@5.9.3)(vite@5.4.21)
+ vitest:
+ specifier: ^2.1.0
+ version: 2.1.9(jsdom@24.1.3)
+
+packages:
+
+ '@asamuzakjp/css-color@3.2.0':
+ resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==}
+
+ '@babel/code-frame@7.29.7':
+ resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/compat-data@7.29.7':
+ resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/core@7.29.7':
+ resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/generator@7.29.7':
+ resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-compilation-targets@7.29.7':
+ resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-globals@7.29.7':
+ resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-imports@7.29.7':
+ resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-transforms@7.29.7':
+ resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-plugin-utils@7.29.7':
+ resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-string-parser@7.29.7':
+ resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-identifier@7.29.7':
+ resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-option@7.29.7':
+ resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helpers@7.29.7':
+ resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/parser@7.29.7':
+ resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ '@babel/plugin-transform-react-jsx-self@7.29.7':
+ resolution: {integrity: sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-jsx-source@7.29.7':
+ resolution: {integrity: sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/runtime@7.29.7':
+ resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/template@7.29.7':
+ resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/traverse@7.29.7':
+ resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/types@7.29.7':
+ resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==}
+ engines: {node: '>=6.9.0'}
+
+ '@csstools/color-helpers@5.1.0':
+ resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
+ engines: {node: '>=18'}
+
+ '@csstools/css-calc@2.1.4':
+ resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^3.0.5
+ '@csstools/css-tokenizer': ^3.0.4
+
+ '@csstools/css-color-parser@3.1.0':
+ resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^3.0.5
+ '@csstools/css-tokenizer': ^3.0.4
+
+ '@csstools/css-parser-algorithms@3.0.5':
+ resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-tokenizer': ^3.0.4
+
+ '@csstools/css-tokenizer@3.0.4':
+ resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
+ engines: {node: '>=18'}
+
+ '@esbuild/aix-ppc64@0.21.5':
+ resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.21.5':
+ resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.21.5':
+ resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.21.5':
+ resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.21.5':
+ resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.21.5':
+ resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.21.5':
+ resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.21.5':
+ resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.21.5':
+ resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.21.5':
+ resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.21.5':
+ resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.21.5':
+ resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
+ engines: {node: '>=12'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.21.5':
+ resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
+ engines: {node: '>=12'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.21.5':
+ resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.21.5':
+ resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
+ engines: {node: '>=12'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.21.5':
+ resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
+ engines: {node: '>=12'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.21.5':
+ resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-x64@0.21.5':
+ resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-x64@0.21.5':
+ resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/sunos-x64@0.21.5':
+ resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.21.5':
+ resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.21.5':
+ resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.21.5':
+ resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [win32]
+
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+ '@jridgewell/remapping@2.3.5':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+ '@mediapipe/tasks-vision@0.10.17':
+ resolution: {integrity: sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==}
+
+ '@microsoft/api-extractor-model@7.33.8':
+ resolution: {integrity: sha512-aIcoQggPyer3B6Ze3usz0YWC/oBwUHfRH5ETUsr+oT2BRA6SfTJl7IKPcPZkX4UR+PohowzW4uMxsvjrn8vm+w==}
+
+ '@microsoft/api-extractor@7.58.9':
+ resolution: {integrity: sha512-S2UF4yza5GoxCmf7hJQNxJNZN9ltOVuOQv8Dy+Z21aol5ERoBNMdWcQHm4MJMPPItW4H/4rZD906iaf4mUojJA==}
+ hasBin: true
+
+ '@microsoft/tsdoc-config@0.18.1':
+ resolution: {integrity: sha512-9brPoVdfN9k9g0dcWkFeA7IH9bbcttzDJlXvkf8b2OBzd5MueR1V2wkKBL0abn0otvmkHJC6aapBOTJDDeMCZg==}
+
+ '@microsoft/tsdoc@0.16.0':
+ resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==}
+
+ '@monogrid/gainmap-js@3.4.0':
+ resolution: {integrity: sha512-2Z0FATFHaoYJ8b+Y4y4Hgfn3FRFwuU5zRrk+9dFWp4uGAdHGqVEdP7HP+gLA3X469KXHmfupJaUbKo1b/aDKIg==}
+ peerDependencies:
+ three: '>= 0.159.0'
+
+ '@react-spring/animated@9.7.5':
+ resolution: {integrity: sha512-Tqrwz7pIlsSDITzxoLS3n/v/YCUHQdOIKtOJf4yL6kYVSDTSmVK1LI1Q3M/uu2Sx4X3pIWF3xLUhlsA6SPNTNg==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+
+ '@react-spring/core@9.7.5':
+ resolution: {integrity: sha512-rmEqcxRcu7dWh7MnCcMXLvrf6/SDlSokLaLTxiPlAYi11nN3B5oiCUAblO72o+9z/87j2uzxa2Inm8UbLjXA+w==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+
+ '@react-spring/rafz@9.7.5':
+ resolution: {integrity: sha512-5ZenDQMC48wjUzPAm1EtwQ5Ot3bLIAwwqP2w2owG5KoNdNHpEJV263nGhCeKKmuA3vG2zLLOdu3or6kuDjA6Aw==}
+
+ '@react-spring/shared@9.7.5':
+ resolution: {integrity: sha512-wdtoJrhUeeyD/PP/zo+np2s1Z820Ohr/BbuVYv+3dVLW7WctoiN7std8rISoYoHpUXtbkpesSKuPIw/6U1w1Pw==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+
+ '@react-spring/three@9.7.5':
+ resolution: {integrity: sha512-RxIsCoQfUqOS3POmhVHa1wdWS0wyHAUway73uRLp3GAL5U2iYVNdnzQsep6M2NZ994BlW8TcKuMtQHUqOsy6WA==}
+ peerDependencies:
+ '@react-three/fiber': '>=6.0'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ three: '>=0.126'
+
+ '@react-spring/types@9.7.5':
+ resolution: {integrity: sha512-HVj7LrZ4ReHWBimBvu2SKND3cDVUPWKLqRTmWe/fNY6o1owGOX0cAHbdPDTMelgBlVbrTKrre6lFkhqGZErK/g==}
+
+ '@react-three/drei@9.122.0':
+ resolution: {integrity: sha512-SEO/F/rBCTjlLez7WAlpys+iGe9hty4rNgjZvgkQeXFSiwqD4Hbk/wNHMAbdd8vprO2Aj81mihv4dF5bC7D0CA==}
+ peerDependencies:
+ '@react-three/fiber': ^8
+ react: ^18
+ react-dom: ^18
+ three: '>=0.137'
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
+
+ '@react-three/fiber@8.18.0':
+ resolution: {integrity: sha512-FYZZqD0UUHUswKz3LQl2Z7H24AhD14XGTsIRw3SJaXUxyfVMi+1yiZGmqTcPt/CkPpdU7rrxqcyQ1zJE5DjvIQ==}
+ peerDependencies:
+ expo: '>=43.0'
+ expo-asset: '>=8.4'
+ expo-file-system: '>=11.0'
+ expo-gl: '>=11.0'
+ react: '>=18 <19'
+ react-dom: '>=18 <19'
+ react-native: '>=0.64'
+ three: '>=0.133'
+ peerDependenciesMeta:
+ expo:
+ optional: true
+ expo-asset:
+ optional: true
+ expo-file-system:
+ optional: true
+ expo-gl:
+ optional: true
+ react-dom:
+ optional: true
+ react-native:
+ optional: true
+
+ '@react-three/postprocessing@2.19.1':
+ resolution: {integrity: sha512-7P25LOSToH/I6b3UipNK17IIFlX4FDUmWcaomfwu82+CzhXTOz8Fcc1ZXEZ7vFA/5Fr/2peNlXgXZJvoa+aCdA==}
+ peerDependencies:
+ '@react-three/fiber': ^8.0
+ react: ^18.0
+ three: '>= 0.138.0'
+
+ '@rolldown/pluginutils@1.0.0-beta.27':
+ resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
+
+ '@rollup/pluginutils@5.4.0':
+ resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
+ peerDependenciesMeta:
+ rollup:
+ optional: true
+
+ '@rollup/rollup-android-arm-eabi@4.62.2':
+ resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/rollup-android-arm64@4.62.2':
+ resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/rollup-darwin-arm64@4.62.2':
+ resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-x64@4.62.2':
+ resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-freebsd-arm64@4.62.2':
+ resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-x64@4.62.2':
+ resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.62.2':
+ resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm-musleabihf@4.62.2':
+ resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-gnu@4.62.2':
+ resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-musl@4.62.2':
+ resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loong64-gnu@4.62.2':
+ resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loong64-musl@4.62.2':
+ resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-ppc64-gnu@4.62.2':
+ resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-ppc64-musl@4.62.2':
+ resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-gnu@4.62.2':
+ resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-musl@4.62.2':
+ resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-s390x-gnu@4.62.2':
+ resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-gnu@4.62.2':
+ resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-musl@4.62.2':
+ resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-openbsd-x64@4.62.2':
+ resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@rollup/rollup-openharmony-arm64@4.62.2':
+ resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rollup/rollup-win32-arm64-msvc@4.62.2':
+ resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-ia32-msvc@4.62.2':
+ resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-gnu@4.62.2':
+ resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.62.2':
+ resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rushstack/node-core-library@5.23.1':
+ resolution: {integrity: sha512-wlKmIKIYCKuCASbITvOxLZXepPbwXvrv7S6ig6XNWFchSyhL/E2txmVXspHY49Wu2dzf7nI27a2k/yV5BA3EiA==}
+ peerDependencies:
+ '@types/node': '*'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@rushstack/problem-matcher@0.2.1':
+ resolution: {integrity: sha512-gulfhBs6n+I5b7DvjKRfhMGyUejtSgOHTclF/eONr8hcgF1APEDjhxIsfdUYYMzC3rvLwGluqLjbwCFZ8nxrog==}
+ peerDependencies:
+ '@types/node': '*'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@rushstack/rig-package@0.7.3':
+ resolution: {integrity: sha512-aAA518n6wxxjCfnTAOjQnm7ngNE0FVHxHAw2pxKlIhxrMn0XQjGcXKF0oKWpjBgJOmsaJpVob/v+zr3zxgPWuA==}
+
+ '@rushstack/terminal@0.24.0':
+ resolution: {integrity: sha512-8ZQS4MMaGsv27EXCBiH7WMPkRZrffeDoIevs6z9TM5dzqiY6+Hn4evfK/G+gvgBTjfvfkHIZPQQmalmI2sM4TQ==}
+ peerDependencies:
+ '@types/node': '*'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@rushstack/ts-command-line@5.3.10':
+ resolution: {integrity: sha512-fwI076HYknC0IrMXdY6UmjDv+PH7NHhNJX3/pY2UblSE5XrXgndXZPiOe/6ZtuFpn6DvVDVNhtkIzQ+Qu/MhVQ==}
+
+ '@tweenjs/tween.js@23.1.3':
+ resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==}
+
+ '@types/argparse@1.0.38':
+ resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==}
+
+ '@types/babel__core@7.20.5':
+ resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+
+ '@types/babel__generator@7.27.0':
+ resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
+
+ '@types/babel__template@7.4.4':
+ resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+
+ '@types/babel__traverse@7.28.0':
+ resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
+
+ '@types/draco3d@1.4.10':
+ resolution: {integrity: sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==}
+
+ '@types/estree@1.0.9':
+ resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
+
+ '@types/offscreencanvas@2019.7.3':
+ resolution: {integrity: sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==}
+
+ '@types/prop-types@15.7.15':
+ resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
+
+ '@types/react-dom@18.3.7':
+ resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==}
+ peerDependencies:
+ '@types/react': ^18.0.0
+
+ '@types/react-reconciler@0.26.7':
+ resolution: {integrity: sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==}
+
+ '@types/react-reconciler@0.28.9':
+ resolution: {integrity: sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==}
+ peerDependencies:
+ '@types/react': '*'
+
+ '@types/react@18.3.31':
+ resolution: {integrity: sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==}
+
+ '@types/stats.js@0.17.4':
+ resolution: {integrity: sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==}
+
+ '@types/three@0.169.0':
+ resolution: {integrity: sha512-oan7qCgJBt03wIaK+4xPWclYRPG9wzcg7Z2f5T8xYTNEF95kh0t0lklxLLYBDo7gQiGLYzE6iF4ta7nXF2bcsw==}
+
+ '@types/webxr@0.5.24':
+ resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==}
+
+ '@use-gesture/core@10.3.1':
+ resolution: {integrity: sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==}
+
+ '@use-gesture/react@10.3.1':
+ resolution: {integrity: sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==}
+ peerDependencies:
+ react: '>= 16.8.0'
+
+ '@vitejs/plugin-react@4.7.0':
+ resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
+
+ '@vitest/expect@2.1.9':
+ resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==}
+
+ '@vitest/mocker@2.1.9':
+ resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==}
+ peerDependencies:
+ msw: ^2.4.9
+ vite: ^5.0.0
+ peerDependenciesMeta:
+ msw:
+ optional: true
+ vite:
+ optional: true
+
+ '@vitest/pretty-format@2.1.9':
+ resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==}
+
+ '@vitest/runner@2.1.9':
+ resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==}
+
+ '@vitest/snapshot@2.1.9':
+ resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==}
+
+ '@vitest/spy@2.1.9':
+ resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==}
+
+ '@vitest/utils@2.1.9':
+ resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==}
+
+ '@volar/language-core@2.4.28':
+ resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==}
+
+ '@volar/source-map@2.4.28':
+ resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==}
+
+ '@volar/typescript@2.4.28':
+ resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==}
+
+ '@vue/compiler-core@3.5.38':
+ resolution: {integrity: sha512-s99aGxWYig9ErHbct27KXEGhrBYlRI6c4MwAgXErOAbX9xiW37/uMa+XUDO69zLz83dng8UUZ70CTOJrLrYrEQ==}
+
+ '@vue/compiler-dom@3.5.38':
+ resolution: {integrity: sha512-JTqp25l8aFfJYF7/KmsXZjAxJz7T+SjmTJLoXVjHtc2BrSgSiW2n9Aem/cWq1OPe68A8JL06B3eVdhlP0H4TVw==}
+
+ '@vue/compiler-vue2@2.7.16':
+ resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==}
+
+ '@vue/language-core@2.2.0':
+ resolution: {integrity: sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==}
+ peerDependencies:
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ '@vue/shared@3.5.38':
+ resolution: {integrity: sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug==}
+
+ '@webgpu/types@0.1.71':
+ resolution: {integrity: sha512-mMy8/ODcKhab808co15eW+yN+HgXoQxRQHTiBV9Mrvl1r0ufnid7YOcI+gi4eUWSWl9ezD6TW2KXccrL8HCh2A==}
+
+ acorn@8.17.0:
+ resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ agent-base@7.1.4:
+ resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
+ engines: {node: '>= 14'}
+
+ ajv-draft-04@1.0.0:
+ resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==}
+ peerDependencies:
+ ajv: ^8.5.0
+ peerDependenciesMeta:
+ ajv:
+ optional: true
+
+ ajv-formats@3.0.1:
+ resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
+ peerDependencies:
+ ajv: ^8.0.0
+ peerDependenciesMeta:
+ ajv:
+ optional: true
+
+ ajv@8.18.0:
+ resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==}
+
+ alien-signals@0.4.14:
+ resolution: {integrity: sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==}
+
+ argparse@1.0.10:
+ resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
+
+ assertion-error@2.0.1:
+ resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
+ engines: {node: '>=12'}
+
+ asynckit@0.4.0:
+ resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+
+ balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ balanced-match@4.0.4:
+ resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
+ engines: {node: 18 || 20 || >=22}
+
+ base64-js@1.5.1:
+ resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
+
+ baseline-browser-mapping@2.10.38:
+ resolution: {integrity: sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ bidi-js@1.0.3:
+ resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
+
+ brace-expansion@2.1.1:
+ resolution: {integrity: sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==}
+
+ brace-expansion@5.0.6:
+ resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==}
+ engines: {node: 18 || 20 || >=22}
+
+ browserslist@4.28.4:
+ resolution: {integrity: sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
+ buffer@6.0.3:
+ resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
+
+ cac@6.7.14:
+ resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
+ engines: {node: '>=8'}
+
+ call-bind-apply-helpers@1.0.2:
+ resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
+ engines: {node: '>= 0.4'}
+
+ camera-controls@2.10.1:
+ resolution: {integrity: sha512-KnaKdcvkBJ1Irbrzl8XD6WtZltkRjp869Jx8c0ujs9K+9WD+1D7ryBsCiVqJYUqt6i/HR5FxT7RLASieUD+Q5w==}
+ peerDependencies:
+ three: '>=0.126.1'
+
+ caniuse-lite@1.0.30001799:
+ resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==}
+
+ chai@5.3.3:
+ resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
+ engines: {node: '>=18'}
+
+ check-error@2.1.3:
+ resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==}
+ engines: {node: '>= 16'}
+
+ combined-stream@1.0.8:
+ resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+ engines: {node: '>= 0.8'}
+
+ compare-versions@6.1.1:
+ resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==}
+
+ confbox@0.1.8:
+ resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
+
+ confbox@0.2.4:
+ resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==}
+
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+ cross-env@7.0.3:
+ resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
+ engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'}
+ hasBin: true
+
+ cross-spawn@7.0.6:
+ resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+ engines: {node: '>= 8'}
+
+ cssstyle@4.6.0:
+ resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==}
+ engines: {node: '>=18'}
+
+ csstype@3.2.3:
+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
+ data-urls@5.0.0:
+ resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
+ engines: {node: '>=18'}
+
+ de-indent@1.0.2:
+ resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
+
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ decimal.js@10.6.0:
+ resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
+
+ deep-eql@5.0.2:
+ resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
+ engines: {node: '>=6'}
+
+ delayed-stream@1.0.0:
+ resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+ engines: {node: '>=0.4.0'}
+
+ detect-gpu@5.0.70:
+ resolution: {integrity: sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==}
+
+ diff@8.0.4:
+ resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==}
+ engines: {node: '>=0.3.1'}
+
+ draco3d@1.5.7:
+ resolution: {integrity: sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==}
+
+ dunder-proto@1.0.1:
+ resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
+ engines: {node: '>= 0.4'}
+
+ electron-to-chromium@1.5.378:
+ resolution: {integrity: sha512-VinvOAuuPmdD1guEgGv5f2Qp7/vlfqOrUOMYNnOD4wj3pit8kRsQHzfIf6teyUGWo15Tg5+bOJaRunvyltpVWQ==}
+
+ entities@6.0.1:
+ resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
+ engines: {node: '>=0.12'}
+
+ entities@7.0.1:
+ resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
+ engines: {node: '>=0.12'}
+
+ es-define-property@1.0.1:
+ resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
+ engines: {node: '>= 0.4'}
+
+ es-errors@1.3.0:
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ engines: {node: '>= 0.4'}
+
+ es-module-lexer@1.7.0:
+ resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
+
+ es-object-atoms@1.1.2:
+ resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==}
+ engines: {node: '>= 0.4'}
+
+ es-set-tostringtag@2.1.0:
+ resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
+ engines: {node: '>= 0.4'}
+
+ esbuild@0.21.5:
+ resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
+ engines: {node: '>=12'}
+ hasBin: true
+
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
+ estree-walker@2.0.2:
+ resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
+
+ estree-walker@3.0.3:
+ resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
+
+ expect-type@1.3.0:
+ resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
+ engines: {node: '>=12.0.0'}
+
+ exsolve@1.1.0:
+ resolution: {integrity: sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw==}
+
+ fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+ fast-uri@3.1.2:
+ resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==}
+
+ fflate@0.6.10:
+ resolution: {integrity: sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==}
+
+ fflate@0.8.3:
+ resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==}
+
+ form-data@4.0.6:
+ resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==}
+ engines: {node: '>= 6'}
+
+ fs-extra@11.3.5:
+ resolution: {integrity: sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==}
+ engines: {node: '>=14.14'}
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+ gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+
+ get-intrinsic@1.3.0:
+ resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
+ engines: {node: '>= 0.4'}
+
+ get-proto@1.0.1:
+ resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
+ engines: {node: '>= 0.4'}
+
+ glsl-noise@0.0.0:
+ resolution: {integrity: sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==}
+
+ gopd@1.2.0:
+ resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
+ engines: {node: '>= 0.4'}
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ has-flag@4.0.0:
+ resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+ engines: {node: '>=8'}
+
+ has-symbols@1.1.0:
+ resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
+ engines: {node: '>= 0.4'}
+
+ has-tostringtag@1.0.2:
+ resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
+ engines: {node: '>= 0.4'}
+
+ hasown@2.0.4:
+ resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
+ engines: {node: '>= 0.4'}
+
+ he@1.2.0:
+ resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
+ hasBin: true
+
+ hls.js@1.6.16:
+ resolution: {integrity: sha512-VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA==}
+
+ html-encoding-sniffer@4.0.0:
+ resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
+ engines: {node: '>=18'}
+
+ http-proxy-agent@7.0.2:
+ resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
+ engines: {node: '>= 14'}
+
+ https-proxy-agent@7.0.6:
+ resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
+ engines: {node: '>= 14'}
+
+ iconv-lite@0.6.3:
+ resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
+ engines: {node: '>=0.10.0'}
+
+ ieee754@1.2.1:
+ resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
+
+ immediate@3.0.6:
+ resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
+
+ import-lazy@4.0.0:
+ resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==}
+ engines: {node: '>=8'}
+
+ is-core-module@2.16.2:
+ resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==}
+ engines: {node: '>= 0.4'}
+
+ is-potential-custom-element-name@1.0.1:
+ resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
+
+ is-promise@2.2.2:
+ resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==}
+
+ isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ its-fine@1.2.5:
+ resolution: {integrity: sha512-fXtDA0X0t0eBYAGLVM5YsgJGsJ5jEmqZEPrGbzdf5awjv0xE7nqv3TVnvtUF060Tkes15DbDAKW/I48vsb6SyA==}
+ peerDependencies:
+ react: '>=18.0'
+
+ jju@1.4.0:
+ resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==}
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ jsdom@24.1.3:
+ resolution: {integrity: sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ canvas: ^2.11.2
+ peerDependenciesMeta:
+ canvas:
+ optional: true
+
+ jsesc@3.1.0:
+ resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ json-schema-traverse@1.0.0:
+ resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+
+ json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ jsonfile@6.2.1:
+ resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==}
+
+ kolorist@1.8.0:
+ resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
+
+ lie@3.3.0:
+ resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==}
+
+ local-pkg@1.2.1:
+ resolution: {integrity: sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==}
+ engines: {node: '>=14'}
+
+ loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+
+ loupe@3.2.1:
+ resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
+
+ lru-cache@10.4.3:
+ resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+
+ lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+ maath@0.10.8:
+ resolution: {integrity: sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==}
+ peerDependencies:
+ '@types/three': '>=0.134.0'
+ three: '>=0.134.0'
+
+ maath@0.6.0:
+ resolution: {integrity: sha512-dSb2xQuP7vDnaYqfoKzlApeRcR2xtN8/f7WV/TMAkBC8552TwTLtOO0JTcSygkYMjNDPoo6V01jTw/aPi4JrMw==}
+ peerDependencies:
+ '@types/three': '>=0.144.0'
+ three: '>=0.144.0'
+
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
+ math-intrinsics@1.1.0:
+ resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
+ engines: {node: '>= 0.4'}
+
+ meshline@3.3.1:
+ resolution: {integrity: sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==}
+ peerDependencies:
+ three: '>=0.137'
+
+ meshoptimizer@0.18.1:
+ resolution: {integrity: sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==}
+
+ mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+
+ minimatch@10.2.3:
+ resolution: {integrity: sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==}
+ engines: {node: 18 || 20 || >=22}
+
+ minimatch@9.0.9:
+ resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ mlly@1.8.2:
+ resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ muggle-string@0.4.1:
+ resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
+
+ n8ao@1.10.2:
+ resolution: {integrity: sha512-gh4i0xFP8DiRaNoX75kPiG3kGl7PmX9SW/OIn3Sv/YZmHrA8faLdSU1MM6M4gKtfKpxDnZagodu8FKob5URCVw==}
+ peerDependencies:
+ postprocessing: '>=6.30.0'
+ three: '>=0.137'
+
+ nanoid@3.3.15:
+ resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ node-releases@2.0.49:
+ resolution: {integrity: sha512-f06bl1D+8ZDkn2oOQQKAh5/otFWqVnM1Q5oerA8Pex7UfT66Tx4IPHIqVVFKqFT3FUtaDstdgkM7yT7JWhqxfw==}
+ engines: {node: '>=18'}
+
+ nwsapi@2.2.24:
+ resolution: {integrity: sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==}
+
+ object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+
+ parse5@7.3.0:
+ resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
+
+ path-browserify@1.0.1:
+ resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
+
+ path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+
+ path-parse@1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+ pathe@1.1.2:
+ resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
+
+ pathe@2.0.3:
+ resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+
+ pathval@2.0.1:
+ resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
+ engines: {node: '>= 14.16'}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ picomatch@4.0.4:
+ resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
+ engines: {node: '>=12'}
+
+ pkg-types@1.3.1:
+ resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
+
+ pkg-types@2.3.1:
+ resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==}
+
+ postcss@8.5.15:
+ resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ postprocessing@6.39.1:
+ resolution: {integrity: sha512-R2dG2zy+BAx3USl5EHw+PvnrlbT5PKnZVp3se0HCR0pWH8WQdh742yNG4YWOsq6c0bFpffk0Gd2RqPeoP/wKng==}
+ peerDependencies:
+ three: '>= 0.168.0 < 0.185.0'
+
+ potpack@1.0.2:
+ resolution: {integrity: sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==}
+
+ promise-worker-transferable@1.0.4:
+ resolution: {integrity: sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==}
+
+ prop-types@15.8.1:
+ resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+
+ psl@1.15.0:
+ resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==}
+
+ punycode@2.3.1:
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
+
+ quansync@0.2.11:
+ resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
+
+ querystringify@2.2.0:
+ resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
+
+ react-composer@5.0.3:
+ resolution: {integrity: sha512-1uWd07EME6XZvMfapwZmc7NgCZqDemcvicRi3wMJzXsQLvZ3L7fTHVyPy1bZdnWXM4iPjYuNE+uJ41MLKeTtnA==}
+ peerDependencies:
+ react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
+
+ react-dom@18.3.1:
+ resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
+ peerDependencies:
+ react: ^18.3.1
+
+ react-is@16.13.1:
+ resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+
+ react-reconciler@0.27.0:
+ resolution: {integrity: sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA==}
+ engines: {node: '>=0.10.0'}
+ peerDependencies:
+ react: ^18.0.0
+
+ react-refresh@0.17.0:
+ resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
+ engines: {node: '>=0.10.0'}
+
+ react-use-measure@2.1.7:
+ resolution: {integrity: sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==}
+ peerDependencies:
+ react: '>=16.13'
+ react-dom: '>=16.13'
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
+
+ react@18.3.1:
+ resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
+ engines: {node: '>=0.10.0'}
+
+ require-from-string@2.0.2:
+ resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
+ engines: {node: '>=0.10.0'}
+
+ requires-port@1.0.0:
+ resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
+
+ resolve@1.22.12:
+ resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==}
+ engines: {node: '>= 0.4'}
+ hasBin: true
+
+ rollup@4.62.2:
+ resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
+ rrweb-cssom@0.7.1:
+ resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==}
+
+ rrweb-cssom@0.8.0:
+ resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==}
+
+ safer-buffer@2.1.2:
+ resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+
+ saxes@6.0.0:
+ resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
+ engines: {node: '>=v12.22.7'}
+
+ scheduler@0.21.0:
+ resolution: {integrity: sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==}
+
+ scheduler@0.23.2:
+ resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
+
+ semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+
+ semver@7.7.4:
+ resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+
+ shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+
+ siginfo@2.0.0:
+ resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ source-map@0.6.1:
+ resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
+ engines: {node: '>=0.10.0'}
+
+ sprintf-js@1.0.3:
+ resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
+
+ stackback@0.0.2:
+ resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
+
+ stats-gl@2.4.2:
+ resolution: {integrity: sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==}
+ peerDependencies:
+ '@types/three': '*'
+ three: '*'
+
+ stats.js@0.17.0:
+ resolution: {integrity: sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==}
+
+ std-env@3.10.0:
+ resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
+
+ string-argv@0.3.2:
+ resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
+ engines: {node: '>=0.6.19'}
+
+ supports-color@8.1.1:
+ resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
+ engines: {node: '>=10'}
+
+ supports-preserve-symlinks-flag@1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+
+ suspend-react@0.1.3:
+ resolution: {integrity: sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==}
+ peerDependencies:
+ react: '>=17.0'
+
+ symbol-tree@3.2.4:
+ resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
+
+ three-mesh-bvh@0.7.8:
+ resolution: {integrity: sha512-BGEZTOIC14U0XIRw3tO4jY7IjP7n7v24nv9JXS1CyeVRWOCkcOMhRnmENUjuV39gktAw4Ofhr0OvIAiTspQrrw==}
+ deprecated: Deprecated due to three.js version incompatibility. Please use v0.8.0, instead.
+ peerDependencies:
+ three: '>= 0.151.0'
+
+ three-stdlib@2.36.1:
+ resolution: {integrity: sha512-XyGQrFmNQ5O/IoKm556ftwKsBg11TIb301MB5dWNicziQBEs2g3gtOYIf7pFiLa0zI2gUwhtCjv9fmjnxKZ1Cg==}
+ peerDependencies:
+ three: '>=0.128.0'
+
+ three@0.169.0:
+ resolution: {integrity: sha512-Ed906MA3dR4TS5riErd4QBsRGPcx+HBDX2O5yYE5GqJeFQTPU+M56Va/f/Oph9X7uZo3W3o4l2ZhBZ6f6qUv0w==}
+
+ tinybench@2.9.0:
+ resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
+
+ tinyexec@0.3.2:
+ resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
+
+ tinypool@1.1.1:
+ resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+
+ tinyrainbow@1.2.0:
+ resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==}
+ engines: {node: '>=14.0.0'}
+
+ tinyspy@3.0.2:
+ resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
+ engines: {node: '>=14.0.0'}
+
+ tough-cookie@4.1.4:
+ resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==}
+ engines: {node: '>=6'}
+
+ tr46@5.1.1:
+ resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==}
+ engines: {node: '>=18'}
+
+ troika-three-text@0.52.4:
+ resolution: {integrity: sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==}
+ peerDependencies:
+ three: '>=0.125.0'
+
+ troika-three-utils@0.52.4:
+ resolution: {integrity: sha512-NORAStSVa/BDiG52Mfudk4j1FG4jC4ILutB3foPnfGbOeIs9+G5vZLa0pnmnaftZUGm4UwSoqEpWdqvC7zms3A==}
+ peerDependencies:
+ three: '>=0.125.0'
+
+ troika-worker-utils@0.52.0:
+ resolution: {integrity: sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==}
+
+ tunnel-rat@0.1.2:
+ resolution: {integrity: sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==}
+
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ ufo@1.6.4:
+ resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==}
+
+ universalify@0.2.0:
+ resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
+ engines: {node: '>= 4.0.0'}
+
+ universalify@2.0.1:
+ resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
+ engines: {node: '>= 10.0.0'}
+
+ update-browserslist-db@1.2.3:
+ resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
+ url-parse@1.5.10:
+ resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
+
+ use-sync-external-store@1.6.0:
+ resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ utility-types@3.11.0:
+ resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==}
+ engines: {node: '>= 4'}
+
+ vite-node@2.1.9:
+ resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ hasBin: true
+
+ vite-plugin-dts@4.5.4:
+ resolution: {integrity: sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg==}
+ peerDependencies:
+ typescript: '*'
+ vite: '*'
+ peerDependenciesMeta:
+ vite:
+ optional: true
+
+ vite@5.4.21:
+ resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^18.0.0 || >=20.0.0
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ sass-embedded: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.4.0
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+
+ vitest@2.1.9:
+ resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ hasBin: true
+ peerDependencies:
+ '@edge-runtime/vm': '*'
+ '@types/node': ^18.0.0 || >=20.0.0
+ '@vitest/browser': 2.1.9
+ '@vitest/ui': 2.1.9
+ happy-dom: '*'
+ jsdom: '*'
+ peerDependenciesMeta:
+ '@edge-runtime/vm':
+ optional: true
+ '@types/node':
+ optional: true
+ '@vitest/browser':
+ optional: true
+ '@vitest/ui':
+ optional: true
+ happy-dom:
+ optional: true
+ jsdom:
+ optional: true
+
+ vscode-uri@3.1.0:
+ resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
+
+ w3c-xmlserializer@5.0.0:
+ resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
+ engines: {node: '>=18'}
+
+ webgl-constants@1.1.1:
+ resolution: {integrity: sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==}
+
+ webgl-sdf-generator@1.1.1:
+ resolution: {integrity: sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==}
+
+ webidl-conversions@7.0.0:
+ resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
+ engines: {node: '>=12'}
+
+ whatwg-encoding@3.1.1:
+ resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
+ engines: {node: '>=18'}
+ deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
+
+ whatwg-mimetype@4.0.0:
+ resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
+ engines: {node: '>=18'}
+
+ whatwg-url@14.2.0:
+ resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
+ engines: {node: '>=18'}
+
+ which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+
+ why-is-node-running@2.3.0:
+ resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
+ engines: {node: '>=8'}
+ hasBin: true
+
+ ws@8.21.0:
+ resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
+ xml-name-validator@5.0.0:
+ resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
+ engines: {node: '>=18'}
+
+ xmlchars@2.2.0:
+ resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
+
+ yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+ zustand@3.7.2:
+ resolution: {integrity: sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==}
+ engines: {node: '>=12.7.0'}
+ peerDependencies:
+ react: '>=16.8'
+ peerDependenciesMeta:
+ react:
+ optional: true
+
+ zustand@4.5.7:
+ resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==}
+ engines: {node: '>=12.7.0'}
+ peerDependencies:
+ '@types/react': '>=16.8'
+ immer: '>=9.0.6'
+ react: '>=16.8'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+
+ zustand@5.0.14:
+ resolution: {integrity: sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==}
+ engines: {node: '>=12.20.0'}
+ peerDependencies:
+ '@types/react': '>=18.0.0'
+ immer: '>=9.0.6'
+ react: '>=18.0.0'
+ use-sync-external-store: '>=1.2.0'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+ use-sync-external-store:
+ optional: true
+
+snapshots:
+
+ '@asamuzakjp/css-color@3.2.0':
+ dependencies:
+ '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ lru-cache: 10.4.3
+
+ '@babel/code-frame@7.29.7':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.29.7
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/compat-data@7.29.7': {}
+
+ '@babel/core@7.29.7':
+ dependencies:
+ '@babel/code-frame': 7.29.7
+ '@babel/generator': 7.29.7
+ '@babel/helper-compilation-targets': 7.29.7
+ '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7)
+ '@babel/helpers': 7.29.7
+ '@babel/parser': 7.29.7
+ '@babel/template': 7.29.7
+ '@babel/traverse': 7.29.7
+ '@babel/types': 7.29.7
+ '@jridgewell/remapping': 2.3.5
+ convert-source-map: 2.0.0
+ debug: 4.4.3
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/generator@7.29.7':
+ dependencies:
+ '@babel/parser': 7.29.7
+ '@babel/types': 7.29.7
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+ jsesc: 3.1.0
+
+ '@babel/helper-compilation-targets@7.29.7':
+ dependencies:
+ '@babel/compat-data': 7.29.7
+ '@babel/helper-validator-option': 7.29.7
+ browserslist: 4.28.4
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ '@babel/helper-globals@7.29.7': {}
+
+ '@babel/helper-module-imports@7.29.7':
+ dependencies:
+ '@babel/traverse': 7.29.7
+ '@babel/types': 7.29.7
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)':
+ dependencies:
+ '@babel/core': 7.29.7
+ '@babel/helper-module-imports': 7.29.7
+ '@babel/helper-validator-identifier': 7.29.7
+ '@babel/traverse': 7.29.7
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-plugin-utils@7.29.7': {}
+
+ '@babel/helper-string-parser@7.29.7': {}
+
+ '@babel/helper-validator-identifier@7.29.7': {}
+
+ '@babel/helper-validator-option@7.29.7': {}
+
+ '@babel/helpers@7.29.7':
+ dependencies:
+ '@babel/template': 7.29.7
+ '@babel/types': 7.29.7
+
+ '@babel/parser@7.29.7':
+ dependencies:
+ '@babel/types': 7.29.7
+
+ '@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.7)':
+ dependencies:
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.29.7
+
+ '@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.7)':
+ dependencies:
+ '@babel/core': 7.29.7
+ '@babel/helper-plugin-utils': 7.29.7
+
+ '@babel/runtime@7.29.7': {}
+
+ '@babel/template@7.29.7':
+ dependencies:
+ '@babel/code-frame': 7.29.7
+ '@babel/parser': 7.29.7
+ '@babel/types': 7.29.7
+
+ '@babel/traverse@7.29.7':
+ dependencies:
+ '@babel/code-frame': 7.29.7
+ '@babel/generator': 7.29.7
+ '@babel/helper-globals': 7.29.7
+ '@babel/parser': 7.29.7
+ '@babel/template': 7.29.7
+ '@babel/types': 7.29.7
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/types@7.29.7':
+ dependencies:
+ '@babel/helper-string-parser': 7.29.7
+ '@babel/helper-validator-identifier': 7.29.7
+
+ '@csstools/color-helpers@5.1.0': {}
+
+ '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
+ dependencies:
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
+ dependencies:
+ '@csstools/color-helpers': 5.1.0
+ '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)':
+ dependencies:
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/css-tokenizer@3.0.4': {}
+
+ '@esbuild/aix-ppc64@0.21.5':
+ optional: true
+
+ '@esbuild/android-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/android-arm@0.21.5':
+ optional: true
+
+ '@esbuild/android-x64@0.21.5':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/darwin-x64@0.21.5':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-arm@0.21.5':
+ optional: true
+
+ '@esbuild/linux-ia32@0.21.5':
+ optional: true
+
+ '@esbuild/linux-loong64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.21.5':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.21.5':
+ optional: true
+
+ '@esbuild/linux-s390x@0.21.5':
+ optional: true
+
+ '@esbuild/linux-x64@0.21.5':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.21.5':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.21.5':
+ optional: true
+
+ '@esbuild/sunos-x64@0.21.5':
+ optional: true
+
+ '@esbuild/win32-arm64@0.21.5':
+ optional: true
+
+ '@esbuild/win32-ia32@0.21.5':
+ optional: true
+
+ '@esbuild/win32-x64@0.21.5':
+ optional: true
+
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@mediapipe/tasks-vision@0.10.17': {}
+
+ '@microsoft/api-extractor-model@7.33.8':
+ dependencies:
+ '@microsoft/tsdoc': 0.16.0
+ '@microsoft/tsdoc-config': 0.18.1
+ '@rushstack/node-core-library': 5.23.1
+ transitivePeerDependencies:
+ - '@types/node'
+
+ '@microsoft/api-extractor@7.58.9':
+ dependencies:
+ '@microsoft/api-extractor-model': 7.33.8
+ '@microsoft/tsdoc': 0.16.0
+ '@microsoft/tsdoc-config': 0.18.1
+ '@rushstack/node-core-library': 5.23.1
+ '@rushstack/rig-package': 0.7.3
+ '@rushstack/terminal': 0.24.0
+ '@rushstack/ts-command-line': 5.3.10
+ diff: 8.0.4
+ minimatch: 10.2.3
+ resolve: 1.22.12
+ semver: 7.7.4
+ source-map: 0.6.1
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - '@types/node'
+
+ '@microsoft/tsdoc-config@0.18.1':
+ dependencies:
+ '@microsoft/tsdoc': 0.16.0
+ ajv: 8.18.0
+ jju: 1.4.0
+ resolve: 1.22.12
+
+ '@microsoft/tsdoc@0.16.0': {}
+
+ '@monogrid/gainmap-js@3.4.0(three@0.169.0)':
+ dependencies:
+ promise-worker-transferable: 1.0.4
+ three: 0.169.0
+
+ '@react-spring/animated@9.7.5(react@18.3.1)':
+ dependencies:
+ '@react-spring/shared': 9.7.5(react@18.3.1)
+ '@react-spring/types': 9.7.5
+ react: 18.3.1
+
+ '@react-spring/core@9.7.5(react@18.3.1)':
+ dependencies:
+ '@react-spring/animated': 9.7.5(react@18.3.1)
+ '@react-spring/shared': 9.7.5(react@18.3.1)
+ '@react-spring/types': 9.7.5
+ react: 18.3.1
+
+ '@react-spring/rafz@9.7.5': {}
+
+ '@react-spring/shared@9.7.5(react@18.3.1)':
+ dependencies:
+ '@react-spring/rafz': 9.7.5
+ '@react-spring/types': 9.7.5
+ react: 18.3.1
+
+ '@react-spring/three@9.7.5(@react-three/fiber@8.18.0(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.169.0))(react@18.3.1)(three@0.169.0)':
+ dependencies:
+ '@react-spring/animated': 9.7.5(react@18.3.1)
+ '@react-spring/core': 9.7.5(react@18.3.1)
+ '@react-spring/shared': 9.7.5(react@18.3.1)
+ '@react-spring/types': 9.7.5
+ '@react-three/fiber': 8.18.0(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.169.0)
+ react: 18.3.1
+ three: 0.169.0
+
+ '@react-spring/types@9.7.5': {}
+
+ '@react-three/drei@9.122.0(@react-three/fiber@8.18.0(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.169.0))(@types/react@18.3.31)(@types/three@0.169.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.169.0)(use-sync-external-store@1.6.0(react@18.3.1))':
+ dependencies:
+ '@babel/runtime': 7.29.7
+ '@mediapipe/tasks-vision': 0.10.17
+ '@monogrid/gainmap-js': 3.4.0(three@0.169.0)
+ '@react-spring/three': 9.7.5(@react-three/fiber@8.18.0(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.169.0))(react@18.3.1)(three@0.169.0)
+ '@react-three/fiber': 8.18.0(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.169.0)
+ '@use-gesture/react': 10.3.1(react@18.3.1)
+ camera-controls: 2.10.1(three@0.169.0)
+ cross-env: 7.0.3
+ detect-gpu: 5.0.70
+ glsl-noise: 0.0.0
+ hls.js: 1.6.16
+ maath: 0.10.8(@types/three@0.169.0)(three@0.169.0)
+ meshline: 3.3.1(three@0.169.0)
+ react: 18.3.1
+ react-composer: 5.0.3(react@18.3.1)
+ stats-gl: 2.4.2(@types/three@0.169.0)(three@0.169.0)
+ stats.js: 0.17.0
+ suspend-react: 0.1.3(react@18.3.1)
+ three: 0.169.0
+ three-mesh-bvh: 0.7.8(three@0.169.0)
+ three-stdlib: 2.36.1(three@0.169.0)
+ troika-three-text: 0.52.4(three@0.169.0)
+ tunnel-rat: 0.1.2(@types/react@18.3.31)(react@18.3.1)
+ utility-types: 3.11.0
+ zustand: 5.0.14(@types/react@18.3.31)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1))
+ optionalDependencies:
+ react-dom: 18.3.1(react@18.3.1)
+ transitivePeerDependencies:
+ - '@types/react'
+ - '@types/three'
+ - immer
+ - use-sync-external-store
+
+ '@react-three/fiber@8.18.0(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.169.0)':
+ dependencies:
+ '@babel/runtime': 7.29.7
+ '@types/react-reconciler': 0.26.7
+ '@types/webxr': 0.5.24
+ base64-js: 1.5.1
+ buffer: 6.0.3
+ its-fine: 1.2.5(@types/react@18.3.31)(react@18.3.1)
+ react: 18.3.1
+ react-reconciler: 0.27.0(react@18.3.1)
+ react-use-measure: 2.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ scheduler: 0.21.0
+ suspend-react: 0.1.3(react@18.3.1)
+ three: 0.169.0
+ zustand: 3.7.2(react@18.3.1)
+ optionalDependencies:
+ react-dom: 18.3.1(react@18.3.1)
+ transitivePeerDependencies:
+ - '@types/react'
+
+ '@react-three/postprocessing@2.19.1(@react-three/fiber@8.18.0(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.169.0))(@types/three@0.169.0)(react@18.3.1)(three@0.169.0)':
+ dependencies:
+ '@react-three/fiber': 8.18.0(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.169.0)
+ buffer: 6.0.3
+ maath: 0.6.0(@types/three@0.169.0)(three@0.169.0)
+ n8ao: 1.10.2(postprocessing@6.39.1(three@0.169.0))(three@0.169.0)
+ postprocessing: 6.39.1(three@0.169.0)
+ react: 18.3.1
+ three: 0.169.0
+ three-stdlib: 2.36.1(three@0.169.0)
+ transitivePeerDependencies:
+ - '@types/three'
+
+ '@rolldown/pluginutils@1.0.0-beta.27': {}
+
+ '@rollup/pluginutils@5.4.0(rollup@4.62.2)':
+ dependencies:
+ '@types/estree': 1.0.9
+ estree-walker: 2.0.2
+ picomatch: 4.0.4
+ optionalDependencies:
+ rollup: 4.62.2
+
+ '@rollup/rollup-android-arm-eabi@4.62.2':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.62.2':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.62.2':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.62.2':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.62.2':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-gnu@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-loong64-musl@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-gnu@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-musl@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-gnu@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-musl@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-s390x-gnu@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-x64-gnu@4.62.2':
+ optional: true
+
+ '@rollup/rollup-linux-x64-musl@4.62.2':
+ optional: true
+
+ '@rollup/rollup-openbsd-x64@4.62.2':
+ optional: true
+
+ '@rollup/rollup-openharmony-arm64@4.62.2':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.62.2':
+ optional: true
+
+ '@rollup/rollup-win32-ia32-msvc@4.62.2':
+ optional: true
+
+ '@rollup/rollup-win32-x64-gnu@4.62.2':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.62.2':
+ optional: true
+
+ '@rushstack/node-core-library@5.23.1':
+ dependencies:
+ ajv: 8.18.0
+ ajv-draft-04: 1.0.0(ajv@8.18.0)
+ ajv-formats: 3.0.1(ajv@8.18.0)
+ fs-extra: 11.3.5
+ import-lazy: 4.0.0
+ jju: 1.4.0
+ resolve: 1.22.12
+ semver: 7.7.4
+
+ '@rushstack/problem-matcher@0.2.1': {}
+
+ '@rushstack/rig-package@0.7.3':
+ dependencies:
+ jju: 1.4.0
+ resolve: 1.22.12
+
+ '@rushstack/terminal@0.24.0':
+ dependencies:
+ '@rushstack/node-core-library': 5.23.1
+ '@rushstack/problem-matcher': 0.2.1
+ supports-color: 8.1.1
+
+ '@rushstack/ts-command-line@5.3.10':
+ dependencies:
+ '@rushstack/terminal': 0.24.0
+ '@types/argparse': 1.0.38
+ argparse: 1.0.10
+ string-argv: 0.3.2
+ transitivePeerDependencies:
+ - '@types/node'
+
+ '@tweenjs/tween.js@23.1.3': {}
+
+ '@types/argparse@1.0.38': {}
+
+ '@types/babel__core@7.20.5':
+ dependencies:
+ '@babel/parser': 7.29.7
+ '@babel/types': 7.29.7
+ '@types/babel__generator': 7.27.0
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.28.0
+
+ '@types/babel__generator@7.27.0':
+ dependencies:
+ '@babel/types': 7.29.7
+
+ '@types/babel__template@7.4.4':
+ dependencies:
+ '@babel/parser': 7.29.7
+ '@babel/types': 7.29.7
+
+ '@types/babel__traverse@7.28.0':
+ dependencies:
+ '@babel/types': 7.29.7
+
+ '@types/draco3d@1.4.10': {}
+
+ '@types/estree@1.0.9': {}
+
+ '@types/offscreencanvas@2019.7.3': {}
+
+ '@types/prop-types@15.7.15': {}
+
+ '@types/react-dom@18.3.7(@types/react@18.3.31)':
+ dependencies:
+ '@types/react': 18.3.31
+
+ '@types/react-reconciler@0.26.7':
+ dependencies:
+ '@types/react': 18.3.31
+
+ '@types/react-reconciler@0.28.9(@types/react@18.3.31)':
+ dependencies:
+ '@types/react': 18.3.31
+
+ '@types/react@18.3.31':
+ dependencies:
+ '@types/prop-types': 15.7.15
+ csstype: 3.2.3
+
+ '@types/stats.js@0.17.4': {}
+
+ '@types/three@0.169.0':
+ dependencies:
+ '@tweenjs/tween.js': 23.1.3
+ '@types/stats.js': 0.17.4
+ '@types/webxr': 0.5.24
+ '@webgpu/types': 0.1.71
+ fflate: 0.8.3
+ meshoptimizer: 0.18.1
+
+ '@types/webxr@0.5.24': {}
+
+ '@use-gesture/core@10.3.1': {}
+
+ '@use-gesture/react@10.3.1(react@18.3.1)':
+ dependencies:
+ '@use-gesture/core': 10.3.1
+ react: 18.3.1
+
+ '@vitejs/plugin-react@4.7.0(vite@5.4.21)':
+ dependencies:
+ '@babel/core': 7.29.7
+ '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7)
+ '@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7)
+ '@rolldown/pluginutils': 1.0.0-beta.27
+ '@types/babel__core': 7.20.5
+ react-refresh: 0.17.0
+ vite: 5.4.21
+ transitivePeerDependencies:
+ - supports-color
+
+ '@vitest/expect@2.1.9':
+ dependencies:
+ '@vitest/spy': 2.1.9
+ '@vitest/utils': 2.1.9
+ chai: 5.3.3
+ tinyrainbow: 1.2.0
+
+ '@vitest/mocker@2.1.9(vite@5.4.21)':
+ dependencies:
+ '@vitest/spy': 2.1.9
+ estree-walker: 3.0.3
+ magic-string: 0.30.21
+ optionalDependencies:
+ vite: 5.4.21
+
+ '@vitest/pretty-format@2.1.9':
+ dependencies:
+ tinyrainbow: 1.2.0
+
+ '@vitest/runner@2.1.9':
+ dependencies:
+ '@vitest/utils': 2.1.9
+ pathe: 1.1.2
+
+ '@vitest/snapshot@2.1.9':
+ dependencies:
+ '@vitest/pretty-format': 2.1.9
+ magic-string: 0.30.21
+ pathe: 1.1.2
+
+ '@vitest/spy@2.1.9':
+ dependencies:
+ tinyspy: 3.0.2
+
+ '@vitest/utils@2.1.9':
+ dependencies:
+ '@vitest/pretty-format': 2.1.9
+ loupe: 3.2.1
+ tinyrainbow: 1.2.0
+
+ '@volar/language-core@2.4.28':
+ dependencies:
+ '@volar/source-map': 2.4.28
+
+ '@volar/source-map@2.4.28': {}
+
+ '@volar/typescript@2.4.28':
+ dependencies:
+ '@volar/language-core': 2.4.28
+ path-browserify: 1.0.1
+ vscode-uri: 3.1.0
+
+ '@vue/compiler-core@3.5.38':
+ dependencies:
+ '@babel/parser': 7.29.7
+ '@vue/shared': 3.5.38
+ entities: 7.0.1
+ estree-walker: 2.0.2
+ source-map-js: 1.2.1
+
+ '@vue/compiler-dom@3.5.38':
+ dependencies:
+ '@vue/compiler-core': 3.5.38
+ '@vue/shared': 3.5.38
+
+ '@vue/compiler-vue2@2.7.16':
+ dependencies:
+ de-indent: 1.0.2
+ he: 1.2.0
+
+ '@vue/language-core@2.2.0(typescript@5.9.3)':
+ dependencies:
+ '@volar/language-core': 2.4.28
+ '@vue/compiler-dom': 3.5.38
+ '@vue/compiler-vue2': 2.7.16
+ '@vue/shared': 3.5.38
+ alien-signals: 0.4.14
+ minimatch: 9.0.9
+ muggle-string: 0.4.1
+ path-browserify: 1.0.1
+ optionalDependencies:
+ typescript: 5.9.3
+
+ '@vue/shared@3.5.38': {}
+
+ '@webgpu/types@0.1.71': {}
+
+ acorn@8.17.0: {}
+
+ agent-base@7.1.4: {}
+
+ ajv-draft-04@1.0.0(ajv@8.18.0):
+ optionalDependencies:
+ ajv: 8.18.0
+
+ ajv-formats@3.0.1(ajv@8.18.0):
+ optionalDependencies:
+ ajv: 8.18.0
+
+ ajv@8.18.0:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-uri: 3.1.2
+ json-schema-traverse: 1.0.0
+ require-from-string: 2.0.2
+
+ alien-signals@0.4.14: {}
+
+ argparse@1.0.10:
+ dependencies:
+ sprintf-js: 1.0.3
+
+ assertion-error@2.0.1: {}
+
+ asynckit@0.4.0: {}
+
+ balanced-match@1.0.2: {}
+
+ balanced-match@4.0.4: {}
+
+ base64-js@1.5.1: {}
+
+ baseline-browser-mapping@2.10.38: {}
+
+ bidi-js@1.0.3:
+ dependencies:
+ require-from-string: 2.0.2
+
+ brace-expansion@2.1.1:
+ dependencies:
+ balanced-match: 1.0.2
+
+ brace-expansion@5.0.6:
+ dependencies:
+ balanced-match: 4.0.4
+
+ browserslist@4.28.4:
+ dependencies:
+ baseline-browser-mapping: 2.10.38
+ caniuse-lite: 1.0.30001799
+ electron-to-chromium: 1.5.378
+ node-releases: 2.0.49
+ update-browserslist-db: 1.2.3(browserslist@4.28.4)
+
+ buffer@6.0.3:
+ dependencies:
+ base64-js: 1.5.1
+ ieee754: 1.2.1
+
+ cac@6.7.14: {}
+
+ call-bind-apply-helpers@1.0.2:
+ dependencies:
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+
+ camera-controls@2.10.1(three@0.169.0):
+ dependencies:
+ three: 0.169.0
+
+ caniuse-lite@1.0.30001799: {}
+
+ chai@5.3.3:
+ dependencies:
+ assertion-error: 2.0.1
+ check-error: 2.1.3
+ deep-eql: 5.0.2
+ loupe: 3.2.1
+ pathval: 2.0.1
+
+ check-error@2.1.3: {}
+
+ combined-stream@1.0.8:
+ dependencies:
+ delayed-stream: 1.0.0
+
+ compare-versions@6.1.1: {}
+
+ confbox@0.1.8: {}
+
+ confbox@0.2.4: {}
+
+ convert-source-map@2.0.0: {}
+
+ cross-env@7.0.3:
+ dependencies:
+ cross-spawn: 7.0.6
+
+ cross-spawn@7.0.6:
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+
+ cssstyle@4.6.0:
+ dependencies:
+ '@asamuzakjp/css-color': 3.2.0
+ rrweb-cssom: 0.8.0
+
+ csstype@3.2.3: {}
+
+ data-urls@5.0.0:
+ dependencies:
+ whatwg-mimetype: 4.0.0
+ whatwg-url: 14.2.0
+
+ de-indent@1.0.2: {}
+
+ debug@4.4.3:
+ dependencies:
+ ms: 2.1.3
+
+ decimal.js@10.6.0: {}
+
+ deep-eql@5.0.2: {}
+
+ delayed-stream@1.0.0: {}
+
+ detect-gpu@5.0.70:
+ dependencies:
+ webgl-constants: 1.1.1
+
+ diff@8.0.4: {}
+
+ draco3d@1.5.7: {}
+
+ dunder-proto@1.0.1:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
+ electron-to-chromium@1.5.378: {}
+
+ entities@6.0.1: {}
+
+ entities@7.0.1: {}
+
+ es-define-property@1.0.1: {}
+
+ es-errors@1.3.0: {}
+
+ es-module-lexer@1.7.0: {}
+
+ es-object-atoms@1.1.2:
+ dependencies:
+ es-errors: 1.3.0
+
+ es-set-tostringtag@2.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ has-tostringtag: 1.0.2
+ hasown: 2.0.4
+
+ esbuild@0.21.5:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.21.5
+ '@esbuild/android-arm': 0.21.5
+ '@esbuild/android-arm64': 0.21.5
+ '@esbuild/android-x64': 0.21.5
+ '@esbuild/darwin-arm64': 0.21.5
+ '@esbuild/darwin-x64': 0.21.5
+ '@esbuild/freebsd-arm64': 0.21.5
+ '@esbuild/freebsd-x64': 0.21.5
+ '@esbuild/linux-arm': 0.21.5
+ '@esbuild/linux-arm64': 0.21.5
+ '@esbuild/linux-ia32': 0.21.5
+ '@esbuild/linux-loong64': 0.21.5
+ '@esbuild/linux-mips64el': 0.21.5
+ '@esbuild/linux-ppc64': 0.21.5
+ '@esbuild/linux-riscv64': 0.21.5
+ '@esbuild/linux-s390x': 0.21.5
+ '@esbuild/linux-x64': 0.21.5
+ '@esbuild/netbsd-x64': 0.21.5
+ '@esbuild/openbsd-x64': 0.21.5
+ '@esbuild/sunos-x64': 0.21.5
+ '@esbuild/win32-arm64': 0.21.5
+ '@esbuild/win32-ia32': 0.21.5
+ '@esbuild/win32-x64': 0.21.5
+
+ escalade@3.2.0: {}
+
+ estree-walker@2.0.2: {}
+
+ estree-walker@3.0.3:
+ dependencies:
+ '@types/estree': 1.0.9
+
+ expect-type@1.3.0: {}
+
+ exsolve@1.1.0: {}
+
+ fast-deep-equal@3.1.3: {}
+
+ fast-uri@3.1.2: {}
+
+ fflate@0.6.10: {}
+
+ fflate@0.8.3: {}
+
+ form-data@4.0.6:
+ dependencies:
+ asynckit: 0.4.0
+ combined-stream: 1.0.8
+ es-set-tostringtag: 2.1.0
+ hasown: 2.0.4
+ mime-types: 2.1.35
+
+ fs-extra@11.3.5:
+ dependencies:
+ graceful-fs: 4.2.11
+ jsonfile: 6.2.1
+ universalify: 2.0.1
+
+ fsevents@2.3.3:
+ optional: true
+
+ function-bind@1.1.2: {}
+
+ gensync@1.0.0-beta.2: {}
+
+ get-intrinsic@1.3.0:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.2
+ function-bind: 1.1.2
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ has-symbols: 1.1.0
+ hasown: 2.0.4
+ math-intrinsics: 1.1.0
+
+ get-proto@1.0.1:
+ dependencies:
+ dunder-proto: 1.0.1
+ es-object-atoms: 1.1.2
+
+ glsl-noise@0.0.0: {}
+
+ gopd@1.2.0: {}
+
+ graceful-fs@4.2.11: {}
+
+ has-flag@4.0.0: {}
+
+ has-symbols@1.1.0: {}
+
+ has-tostringtag@1.0.2:
+ dependencies:
+ has-symbols: 1.1.0
+
+ hasown@2.0.4:
+ dependencies:
+ function-bind: 1.1.2
+
+ he@1.2.0: {}
+
+ hls.js@1.6.16: {}
+
+ html-encoding-sniffer@4.0.0:
+ dependencies:
+ whatwg-encoding: 3.1.1
+
+ http-proxy-agent@7.0.2:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ https-proxy-agent@7.0.6:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ iconv-lite@0.6.3:
+ dependencies:
+ safer-buffer: 2.1.2
+
+ ieee754@1.2.1: {}
+
+ immediate@3.0.6: {}
+
+ import-lazy@4.0.0: {}
+
+ is-core-module@2.16.2:
+ dependencies:
+ hasown: 2.0.4
+
+ is-potential-custom-element-name@1.0.1: {}
+
+ is-promise@2.2.2: {}
+
+ isexe@2.0.0: {}
+
+ its-fine@1.2.5(@types/react@18.3.31)(react@18.3.1):
+ dependencies:
+ '@types/react-reconciler': 0.28.9(@types/react@18.3.31)
+ react: 18.3.1
+ transitivePeerDependencies:
+ - '@types/react'
+
+ jju@1.4.0: {}
+
+ js-tokens@4.0.0: {}
+
+ jsdom@24.1.3:
+ dependencies:
+ cssstyle: 4.6.0
+ data-urls: 5.0.0
+ decimal.js: 10.6.0
+ form-data: 4.0.6
+ html-encoding-sniffer: 4.0.0
+ http-proxy-agent: 7.0.2
+ https-proxy-agent: 7.0.6
+ is-potential-custom-element-name: 1.0.1
+ nwsapi: 2.2.24
+ parse5: 7.3.0
+ rrweb-cssom: 0.7.1
+ saxes: 6.0.0
+ symbol-tree: 3.2.4
+ tough-cookie: 4.1.4
+ w3c-xmlserializer: 5.0.0
+ webidl-conversions: 7.0.0
+ whatwg-encoding: 3.1.1
+ whatwg-mimetype: 4.0.0
+ whatwg-url: 14.2.0
+ ws: 8.21.0
+ xml-name-validator: 5.0.0
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ jsesc@3.1.0: {}
+
+ json-schema-traverse@1.0.0: {}
+
+ json5@2.2.3: {}
+
+ jsonfile@6.2.1:
+ dependencies:
+ universalify: 2.0.1
+ optionalDependencies:
+ graceful-fs: 4.2.11
+
+ kolorist@1.8.0: {}
+
+ lie@3.3.0:
+ dependencies:
+ immediate: 3.0.6
+
+ local-pkg@1.2.1:
+ dependencies:
+ mlly: 1.8.2
+ pkg-types: 2.3.1
+ quansync: 0.2.11
+
+ loose-envify@1.4.0:
+ dependencies:
+ js-tokens: 4.0.0
+
+ loupe@3.2.1: {}
+
+ lru-cache@10.4.3: {}
+
+ lru-cache@5.1.1:
+ dependencies:
+ yallist: 3.1.1
+
+ maath@0.10.8(@types/three@0.169.0)(three@0.169.0):
+ dependencies:
+ '@types/three': 0.169.0
+ three: 0.169.0
+
+ maath@0.6.0(@types/three@0.169.0)(three@0.169.0):
+ dependencies:
+ '@types/three': 0.169.0
+ three: 0.169.0
+
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ math-intrinsics@1.1.0: {}
+
+ meshline@3.3.1(three@0.169.0):
+ dependencies:
+ three: 0.169.0
+
+ meshoptimizer@0.18.1: {}
+
+ mime-db@1.52.0: {}
+
+ mime-types@2.1.35:
+ dependencies:
+ mime-db: 1.52.0
+
+ minimatch@10.2.3:
+ dependencies:
+ brace-expansion: 5.0.6
+
+ minimatch@9.0.9:
+ dependencies:
+ brace-expansion: 2.1.1
+
+ mlly@1.8.2:
+ dependencies:
+ acorn: 8.17.0
+ pathe: 2.0.3
+ pkg-types: 1.3.1
+ ufo: 1.6.4
+
+ ms@2.1.3: {}
+
+ muggle-string@0.4.1: {}
+
+ n8ao@1.10.2(postprocessing@6.39.1(three@0.169.0))(three@0.169.0):
+ dependencies:
+ postprocessing: 6.39.1(three@0.169.0)
+ three: 0.169.0
+
+ nanoid@3.3.15: {}
+
+ node-releases@2.0.49: {}
+
+ nwsapi@2.2.24: {}
+
+ object-assign@4.1.1: {}
+
+ parse5@7.3.0:
+ dependencies:
+ entities: 6.0.1
+
+ path-browserify@1.0.1: {}
+
+ path-key@3.1.1: {}
+
+ path-parse@1.0.7: {}
+
+ pathe@1.1.2: {}
+
+ pathe@2.0.3: {}
+
+ pathval@2.0.1: {}
+
+ picocolors@1.1.1: {}
+
+ picomatch@4.0.4: {}
+
+ pkg-types@1.3.1:
+ dependencies:
+ confbox: 0.1.8
+ mlly: 1.8.2
+ pathe: 2.0.3
+
+ pkg-types@2.3.1:
+ dependencies:
+ confbox: 0.2.4
+ exsolve: 1.1.0
+ pathe: 2.0.3
+
+ postcss@8.5.15:
+ dependencies:
+ nanoid: 3.3.15
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ postprocessing@6.39.1(three@0.169.0):
+ dependencies:
+ three: 0.169.0
+
+ potpack@1.0.2: {}
+
+ promise-worker-transferable@1.0.4:
+ dependencies:
+ is-promise: 2.2.2
+ lie: 3.3.0
+
+ prop-types@15.8.1:
+ dependencies:
+ loose-envify: 1.4.0
+ object-assign: 4.1.1
+ react-is: 16.13.1
+
+ psl@1.15.0:
+ dependencies:
+ punycode: 2.3.1
+
+ punycode@2.3.1: {}
+
+ quansync@0.2.11: {}
+
+ querystringify@2.2.0: {}
+
+ react-composer@5.0.3(react@18.3.1):
+ dependencies:
+ prop-types: 15.8.1
+ react: 18.3.1
+
+ react-dom@18.3.1(react@18.3.1):
+ dependencies:
+ loose-envify: 1.4.0
+ react: 18.3.1
+ scheduler: 0.23.2
+
+ react-is@16.13.1: {}
+
+ react-reconciler@0.27.0(react@18.3.1):
+ dependencies:
+ loose-envify: 1.4.0
+ react: 18.3.1
+ scheduler: 0.21.0
+
+ react-refresh@0.17.0: {}
+
+ react-use-measure@2.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+ optionalDependencies:
+ react-dom: 18.3.1(react@18.3.1)
+
+ react@18.3.1:
+ dependencies:
+ loose-envify: 1.4.0
+
+ require-from-string@2.0.2: {}
+
+ requires-port@1.0.0: {}
+
+ resolve@1.22.12:
+ dependencies:
+ es-errors: 1.3.0
+ is-core-module: 2.16.2
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ rollup@4.62.2:
+ dependencies:
+ '@types/estree': 1.0.9
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.62.2
+ '@rollup/rollup-android-arm64': 4.62.2
+ '@rollup/rollup-darwin-arm64': 4.62.2
+ '@rollup/rollup-darwin-x64': 4.62.2
+ '@rollup/rollup-freebsd-arm64': 4.62.2
+ '@rollup/rollup-freebsd-x64': 4.62.2
+ '@rollup/rollup-linux-arm-gnueabihf': 4.62.2
+ '@rollup/rollup-linux-arm-musleabihf': 4.62.2
+ '@rollup/rollup-linux-arm64-gnu': 4.62.2
+ '@rollup/rollup-linux-arm64-musl': 4.62.2
+ '@rollup/rollup-linux-loong64-gnu': 4.62.2
+ '@rollup/rollup-linux-loong64-musl': 4.62.2
+ '@rollup/rollup-linux-ppc64-gnu': 4.62.2
+ '@rollup/rollup-linux-ppc64-musl': 4.62.2
+ '@rollup/rollup-linux-riscv64-gnu': 4.62.2
+ '@rollup/rollup-linux-riscv64-musl': 4.62.2
+ '@rollup/rollup-linux-s390x-gnu': 4.62.2
+ '@rollup/rollup-linux-x64-gnu': 4.62.2
+ '@rollup/rollup-linux-x64-musl': 4.62.2
+ '@rollup/rollup-openbsd-x64': 4.62.2
+ '@rollup/rollup-openharmony-arm64': 4.62.2
+ '@rollup/rollup-win32-arm64-msvc': 4.62.2
+ '@rollup/rollup-win32-ia32-msvc': 4.62.2
+ '@rollup/rollup-win32-x64-gnu': 4.62.2
+ '@rollup/rollup-win32-x64-msvc': 4.62.2
+ fsevents: 2.3.3
+
+ rrweb-cssom@0.7.1: {}
+
+ rrweb-cssom@0.8.0: {}
+
+ safer-buffer@2.1.2: {}
+
+ saxes@6.0.0:
+ dependencies:
+ xmlchars: 2.2.0
+
+ scheduler@0.21.0:
+ dependencies:
+ loose-envify: 1.4.0
+
+ scheduler@0.23.2:
+ dependencies:
+ loose-envify: 1.4.0
+
+ semver@6.3.1: {}
+
+ semver@7.7.4: {}
+
+ shebang-command@2.0.0:
+ dependencies:
+ shebang-regex: 3.0.0
+
+ shebang-regex@3.0.0: {}
+
+ siginfo@2.0.0: {}
+
+ source-map-js@1.2.1: {}
+
+ source-map@0.6.1: {}
+
+ sprintf-js@1.0.3: {}
+
+ stackback@0.0.2: {}
+
+ stats-gl@2.4.2(@types/three@0.169.0)(three@0.169.0):
+ dependencies:
+ '@types/three': 0.169.0
+ three: 0.169.0
+
+ stats.js@0.17.0: {}
+
+ std-env@3.10.0: {}
+
+ string-argv@0.3.2: {}
+
+ supports-color@8.1.1:
+ dependencies:
+ has-flag: 4.0.0
+
+ supports-preserve-symlinks-flag@1.0.0: {}
+
+ suspend-react@0.1.3(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+
+ symbol-tree@3.2.4: {}
+
+ three-mesh-bvh@0.7.8(three@0.169.0):
+ dependencies:
+ three: 0.169.0
+
+ three-stdlib@2.36.1(three@0.169.0):
+ dependencies:
+ '@types/draco3d': 1.4.10
+ '@types/offscreencanvas': 2019.7.3
+ '@types/webxr': 0.5.24
+ draco3d: 1.5.7
+ fflate: 0.6.10
+ potpack: 1.0.2
+ three: 0.169.0
+
+ three@0.169.0: {}
+
+ tinybench@2.9.0: {}
+
+ tinyexec@0.3.2: {}
+
+ tinypool@1.1.1: {}
+
+ tinyrainbow@1.2.0: {}
+
+ tinyspy@3.0.2: {}
+
+ tough-cookie@4.1.4:
+ dependencies:
+ psl: 1.15.0
+ punycode: 2.3.1
+ universalify: 0.2.0
+ url-parse: 1.5.10
+
+ tr46@5.1.1:
+ dependencies:
+ punycode: 2.3.1
+
+ troika-three-text@0.52.4(three@0.169.0):
+ dependencies:
+ bidi-js: 1.0.3
+ three: 0.169.0
+ troika-three-utils: 0.52.4(three@0.169.0)
+ troika-worker-utils: 0.52.0
+ webgl-sdf-generator: 1.1.1
+
+ troika-three-utils@0.52.4(three@0.169.0):
+ dependencies:
+ three: 0.169.0
+
+ troika-worker-utils@0.52.0: {}
+
+ tunnel-rat@0.1.2(@types/react@18.3.31)(react@18.3.1):
+ dependencies:
+ zustand: 4.5.7(@types/react@18.3.31)(react@18.3.1)
+ transitivePeerDependencies:
+ - '@types/react'
+ - immer
+ - react
+
+ typescript@5.9.3: {}
+
+ ufo@1.6.4: {}
+
+ universalify@0.2.0: {}
+
+ universalify@2.0.1: {}
+
+ update-browserslist-db@1.2.3(browserslist@4.28.4):
+ dependencies:
+ browserslist: 4.28.4
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
+ url-parse@1.5.10:
+ dependencies:
+ querystringify: 2.2.0
+ requires-port: 1.0.0
+
+ use-sync-external-store@1.6.0(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+
+ utility-types@3.11.0: {}
+
+ vite-node@2.1.9:
+ dependencies:
+ cac: 6.7.14
+ debug: 4.4.3
+ es-module-lexer: 1.7.0
+ pathe: 1.1.2
+ vite: 5.4.21
+ transitivePeerDependencies:
+ - '@types/node'
+ - less
+ - lightningcss
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+
+ vite-plugin-dts@4.5.4(rollup@4.62.2)(typescript@5.9.3)(vite@5.4.21):
+ dependencies:
+ '@microsoft/api-extractor': 7.58.9
+ '@rollup/pluginutils': 5.4.0(rollup@4.62.2)
+ '@volar/typescript': 2.4.28
+ '@vue/language-core': 2.2.0(typescript@5.9.3)
+ compare-versions: 6.1.1
+ debug: 4.4.3
+ kolorist: 1.8.0
+ local-pkg: 1.2.1
+ magic-string: 0.30.21
+ typescript: 5.9.3
+ optionalDependencies:
+ vite: 5.4.21
+ transitivePeerDependencies:
+ - '@types/node'
+ - rollup
+ - supports-color
+
+ vite@5.4.21:
+ dependencies:
+ esbuild: 0.21.5
+ postcss: 8.5.15
+ rollup: 4.62.2
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ vitest@2.1.9(jsdom@24.1.3):
+ dependencies:
+ '@vitest/expect': 2.1.9
+ '@vitest/mocker': 2.1.9(vite@5.4.21)
+ '@vitest/pretty-format': 2.1.9
+ '@vitest/runner': 2.1.9
+ '@vitest/snapshot': 2.1.9
+ '@vitest/spy': 2.1.9
+ '@vitest/utils': 2.1.9
+ chai: 5.3.3
+ debug: 4.4.3
+ expect-type: 1.3.0
+ magic-string: 0.30.21
+ pathe: 1.1.2
+ std-env: 3.10.0
+ tinybench: 2.9.0
+ tinyexec: 0.3.2
+ tinypool: 1.1.1
+ tinyrainbow: 1.2.0
+ vite: 5.4.21
+ vite-node: 2.1.9
+ why-is-node-running: 2.3.0
+ optionalDependencies:
+ jsdom: 24.1.3
+ transitivePeerDependencies:
+ - less
+ - lightningcss
+ - msw
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+
+ vscode-uri@3.1.0: {}
+
+ w3c-xmlserializer@5.0.0:
+ dependencies:
+ xml-name-validator: 5.0.0
+
+ webgl-constants@1.1.1: {}
+
+ webgl-sdf-generator@1.1.1: {}
+
+ webidl-conversions@7.0.0: {}
+
+ whatwg-encoding@3.1.1:
+ dependencies:
+ iconv-lite: 0.6.3
+
+ whatwg-mimetype@4.0.0: {}
+
+ whatwg-url@14.2.0:
+ dependencies:
+ tr46: 5.1.1
+ webidl-conversions: 7.0.0
+
+ which@2.0.2:
+ dependencies:
+ isexe: 2.0.0
+
+ why-is-node-running@2.3.0:
+ dependencies:
+ siginfo: 2.0.0
+ stackback: 0.0.2
+
+ ws@8.21.0: {}
+
+ xml-name-validator@5.0.0: {}
+
+ xmlchars@2.2.0: {}
+
+ yallist@3.1.1: {}
+
+ zustand@3.7.2(react@18.3.1):
+ optionalDependencies:
+ react: 18.3.1
+
+ zustand@4.5.7(@types/react@18.3.31)(react@18.3.1):
+ dependencies:
+ use-sync-external-store: 1.6.0(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.31
+ react: 18.3.1
+
+ zustand@5.0.14(@types/react@18.3.31)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)):
+ optionalDependencies:
+ '@types/react': 18.3.31
+ react: 18.3.1
+ use-sync-external-store: 1.6.0(react@18.3.1)
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
new file mode 100644
index 0000000..15add63
--- /dev/null
+++ b/pnpm-workspace.yaml
@@ -0,0 +1,3 @@
+packages:
+ - "connectors/*"
+ - "packages/*"