From 062bdc6d0286077eafb2e6c0a894a3d5326c1554 Mon Sep 17 00:00:00 2001 From: ninan-versioning <269335075+ninan-versioning@users.noreply.github.com> Date: Thu, 25 Jun 2026 03:40:14 +0000 Subject: [PATCH] feat(twin-renderer): Three.js USD product twin renderer + Varitea demo Browser-side renderer consuming product_twin.usda from @shoploop/connector-shopify. Minimal USDA->scene-graph parser, r3f scene builder with procedural placeholder + external GLB/USDZ refs, ProductTwin/VariantSelector/Stage components, and a Varitea demo. Co-Authored-By: Claude Opus 4.7 --- .gitignore | 5 + packages/twin-renderer/.gitignore | 4 + packages/twin-renderer/DESIGN.md | 6 + packages/twin-renderer/README.md | 125 + packages/twin-renderer/demo/index.html | 31 + packages/twin-renderer/demo/main.tsx | 75 + packages/twin-renderer/package.json | 58 + .../public/varitea_product_twin.usda | 82 + .../src/components/ProductTwin.tsx | 144 + .../twin-renderer/src/components/Stage.tsx | 98 + .../src/components/VariantSelector.tsx | 63 + .../twin-renderer/src/components/context.ts | 14 + packages/twin-renderer/src/index.ts | 46 + packages/twin-renderer/src/parser/usda.ts | 556 +++ packages/twin-renderer/src/scene/build.ts | 262 ++ packages/twin-renderer/src/scene/loaders.ts | 30 + packages/twin-renderer/test/build.test.ts | 90 + .../test/fixtures/varitea_product_twin.usda | 82 + packages/twin-renderer/test/parser.test.ts | 126 + packages/twin-renderer/tsconfig.build.json | 7 + packages/twin-renderer/tsconfig.json | 21 + packages/twin-renderer/vite.config.ts | 47 + packages/twin-renderer/vitest.config.ts | 9 + pnpm-lock.yaml | 3186 +++++++++++++++++ pnpm-workspace.yaml | 3 + 25 files changed, 5170 insertions(+) create mode 100644 .gitignore create mode 100644 packages/twin-renderer/.gitignore create mode 100644 packages/twin-renderer/DESIGN.md create mode 100644 packages/twin-renderer/README.md create mode 100644 packages/twin-renderer/demo/index.html create mode 100644 packages/twin-renderer/demo/main.tsx create mode 100644 packages/twin-renderer/package.json create mode 100644 packages/twin-renderer/public/varitea_product_twin.usda create mode 100644 packages/twin-renderer/src/components/ProductTwin.tsx create mode 100644 packages/twin-renderer/src/components/Stage.tsx create mode 100644 packages/twin-renderer/src/components/VariantSelector.tsx create mode 100644 packages/twin-renderer/src/components/context.ts create mode 100644 packages/twin-renderer/src/index.ts create mode 100644 packages/twin-renderer/src/parser/usda.ts create mode 100644 packages/twin-renderer/src/scene/build.ts create mode 100644 packages/twin-renderer/src/scene/loaders.ts create mode 100644 packages/twin-renderer/test/build.test.ts create mode 100644 packages/twin-renderer/test/fixtures/varitea_product_twin.usda create mode 100644 packages/twin-renderer/test/parser.test.ts create mode 100644 packages/twin-renderer/tsconfig.build.json create mode 100644 packages/twin-renderer/tsconfig.json create mode 100644 packages/twin-renderer/vite.config.ts create mode 100644 packages/twin-renderer/vitest.config.ts create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml 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 ( + + ); + })} +
+
+ ))} +
+ ); +} 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/*"