@vincentt-xr/sdk 2.0.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/GROUNDING.md +537 -0
  2. package/LICENSE +7 -0
  3. package/README.md +85 -0
  4. package/dist/DebuggerUI-CEfdddkZ.cjs +1 -0
  5. package/dist/DebuggerUI-Dgop6Q6o.js +269 -0
  6. package/dist/audio/AudioEngine.d.ts +98 -0
  7. package/dist/audio/index.d.ts +34 -0
  8. package/dist/components/AspectRatioContainer/index.d.ts +10 -0
  9. package/dist/components/BodyTracker/index.d.ts +15 -0
  10. package/dist/components/Debugger/DebuggerProvider.d.ts +22 -0
  11. package/dist/components/Debugger/DebuggerUI.d.ts +13 -0
  12. package/dist/components/Debugger/index.d.ts +38 -0
  13. package/dist/components/Debugger/styles.d.ts +19 -0
  14. package/dist/components/FaceMesh/FaceMesh.d.ts +26 -0
  15. package/dist/components/FaceMesh/canonical/CanonicalFaceMeshRenderer.d.ts +34 -0
  16. package/dist/components/FaceMesh/canonical/parseCanonicalFaceModel.d.ts +16 -0
  17. package/dist/components/FaceMesh/canonical/useCanonicalFaceMesh.d.ts +15 -0
  18. package/dist/components/FaceMesh/deform.d.ts +36 -0
  19. package/dist/components/FaceMesh/index.d.ts +6 -0
  20. package/dist/components/FaceMesh/indices.d.ts +1 -0
  21. package/dist/components/FaceMesh/material/blendModes.d.ts +2 -0
  22. package/dist/components/FaceMesh/material/defaults.d.ts +3 -0
  23. package/dist/components/FaceMesh/material/index.d.ts +5 -0
  24. package/dist/components/FaceMesh/material/options.d.ts +25 -0
  25. package/dist/components/FaceMesh/material/support.d.ts +3 -0
  26. package/dist/components/FaceMesh/material/types.d.ts +23 -0
  27. package/dist/components/FaceMesh/uv.d.ts +1 -0
  28. package/dist/components/FaceRetouch/Background.d.ts +16 -0
  29. package/dist/components/FaceRetouch/index.d.ts +6 -0
  30. package/dist/components/FaceRetouch/landmarkMask.d.ts +23 -0
  31. package/dist/components/FaceRetouch/retouchRegions.d.ts +23 -0
  32. package/dist/components/FaceRetouch/useFaceRetouchMask.d.ts +14 -0
  33. package/dist/components/FaceRetouch/useFaceRetouchRegions.d.ts +12 -0
  34. package/dist/components/FaceTracker/BoundingBoxOverlay.d.ts +20 -0
  35. package/dist/components/FaceTracker/index.d.ts +54 -0
  36. package/dist/components/GestureTracker/index.d.ts +31 -0
  37. package/dist/components/GestureTrigger/index.d.ts +3 -0
  38. package/dist/components/HandTracker/index.d.ts +24 -0
  39. package/dist/components/HeadBinding/index.d.ts +31 -0
  40. package/dist/components/HeadBindingOccluder/index.d.ts +36 -0
  41. package/dist/components/HeadOccluder/index.d.ts +13 -0
  42. package/dist/components/Panel/index.d.ts +71 -0
  43. package/dist/components/PerspectiveCamera/index.d.ts +14 -0
  44. package/dist/components/ScreenSpaceUI/ScreenTransform.d.ts +54 -0
  45. package/dist/components/ScreenSpaceUI/context.d.ts +29 -0
  46. package/dist/components/ScreenSpaceUI/index.d.ts +40 -0
  47. package/dist/components/Segmentation/index.d.ts +44 -0
  48. package/dist/components/SpriteAnimation/index.d.ts +52 -0
  49. package/dist/components/TextLabel/index.d.ts +62 -0
  50. package/dist/components/TrackingAnchor/index.d.ts +74 -0
  51. package/dist/components/VideoBackground/index.d.ts +38 -0
  52. package/dist/components/XRCompositor/index.d.ts +5 -0
  53. package/dist/components/XRDebugView/index.d.ts +6 -0
  54. package/dist/components/XRLayer/index.d.ts +6 -0
  55. package/dist/components/XRMediaSurface/index.d.ts +27 -0
  56. package/dist/components/XRProvider/index.d.ts +13 -0
  57. package/dist/components/XRScene/defaults.d.ts +4 -0
  58. package/dist/components/XRScene/index.d.ts +15 -0
  59. package/dist/components/XRViewport/index.d.ts +101 -0
  60. package/dist/components/index.d.ts +26 -0
  61. package/dist/config/index.d.ts +8 -0
  62. package/dist/constants/index.d.ts +25 -0
  63. package/dist/constants/renderLayers.d.ts +25 -0
  64. package/dist/entries/all.d.ts +15 -0
  65. package/dist/entries/core.d.ts +21 -0
  66. package/dist/entries/face-effects.d.ts +19 -0
  67. package/dist/entries/internal.d.ts +16 -0
  68. package/dist/entries/low-level.d.ts +30 -0
  69. package/dist/entries/tracking.d.ts +15 -0
  70. package/dist/face-effects.cjs +252 -0
  71. package/dist/face-effects.js +3542 -0
  72. package/dist/hasFullUnitUVBounds-BGvcFsQM.js +26 -0
  73. package/dist/hasFullUnitUVBounds-p1Dl0ejX.cjs +1 -0
  74. package/dist/hooks/faceResults.d.ts +39 -0
  75. package/dist/hooks/selectors.d.ts +33 -0
  76. package/dist/hooks/useCalibrationFaceTracker.d.ts +23 -0
  77. package/dist/hooks/useDebugger/index.d.ts +25 -0
  78. package/dist/hooks/useDebugger/types.d.ts +20 -0
  79. package/dist/hooks/useFaceBoundingBox.d.ts +11 -0
  80. package/dist/hooks/useFaceResults.d.ts +31 -0
  81. package/dist/hooks/useGestureEdge.d.ts +53 -0
  82. package/dist/hooks/useMediaRecorder.d.ts +40 -0
  83. package/dist/hooks/useTrackerContext.d.ts +35 -0
  84. package/dist/hooks/useXRModel.d.ts +15 -0
  85. package/dist/hooks/useXRSession/constants.d.ts +4 -0
  86. package/dist/hooks/useXRSession/facade.d.ts +39 -0
  87. package/dist/hooks/useXRSession/fns.d.ts +5 -0
  88. package/dist/hooks/useXRSession/index.d.ts +4 -0
  89. package/dist/hooks/useXRSession/registry.d.ts +52 -0
  90. package/dist/hooks/useXRSession/store.d.ts +40 -0
  91. package/dist/index-B5J4gzn2.cjs +1 -0
  92. package/dist/index-BD-JWMjE.js +1991 -0
  93. package/dist/index-BVBVg96G.cjs +1 -0
  94. package/dist/index-CYMj26Ej.js +1331 -0
  95. package/dist/index-DtwHElmG.js +157 -0
  96. package/dist/index-PtFmD6B8.cjs +1 -0
  97. package/dist/internal-DH5d1Xnq.cjs +1 -0
  98. package/dist/internal-KOKJDxCG.js +519 -0
  99. package/dist/internal.cjs +1 -0
  100. package/dist/internal.js +14 -0
  101. package/dist/low-level.cjs +1 -0
  102. package/dist/low-level.js +941 -0
  103. package/dist/main.cjs +1 -0
  104. package/dist/main.js +612 -0
  105. package/dist/models/Base/index.d.ts +25 -0
  106. package/dist/models/Body/index.d.ts +22 -0
  107. package/dist/models/Face/constants.d.ts +2 -0
  108. package/dist/models/Face/index.d.ts +45 -0
  109. package/dist/models/Face/landmarksData.d.ts +2 -0
  110. package/dist/models/Hand/GestureTracking.d.ts +21 -0
  111. package/dist/models/Hand/HandTracking.d.ts +21 -0
  112. package/dist/models/Segmentation/index.d.ts +75 -0
  113. package/dist/models/index.d.ts +5 -0
  114. package/dist/modules/XRClient/index.d.ts +9 -0
  115. package/dist/modules/index.d.ts +1 -0
  116. package/dist/selectors-BHO6TnoC.js +59 -0
  117. package/dist/selectors-Bub0o3qy.cjs +1 -0
  118. package/dist/services/ApiService.d.ts +12 -0
  119. package/dist/services/MetricsCollector/CameraFPSTracker.d.ts +25 -0
  120. package/dist/services/MetricsCollector/MemoryTracker.d.ts +43 -0
  121. package/dist/services/MetricsCollector/RenderTimeTracker.d.ts +10 -0
  122. package/dist/services/MetricsCollector/ResolutionTracker.d.ts +26 -0
  123. package/dist/services/MetricsCollector/index.d.ts +59 -0
  124. package/dist/services/MetricsCollector/types.d.ts +47 -0
  125. package/dist/services/MetricsCollector/utils.d.ts +65 -0
  126. package/dist/texture/computeContainScale.d.ts +25 -0
  127. package/dist/texture/hasFullUnitUVBounds.d.ts +37 -0
  128. package/dist/texture/types.d.ts +20 -0
  129. package/dist/tracking.cjs +1 -0
  130. package/dist/tracking.js +344 -0
  131. package/dist/types/body.d.ts +3 -0
  132. package/dist/types/client.d.ts +3 -0
  133. package/dist/types/common.d.ts +9 -0
  134. package/dist/types/compositor.d.ts +23 -0
  135. package/dist/types/debugger.d.ts +83 -0
  136. package/dist/types/face.d.ts +67 -0
  137. package/dist/types/gesture-tracking.d.ts +15 -0
  138. package/dist/types/hand-tracking.d.ts +5 -0
  139. package/dist/types/headBinding.d.ts +67 -0
  140. package/dist/types/index.d.ts +12 -0
  141. package/dist/types/logger.d.ts +4 -0
  142. package/dist/types/model.d.ts +46 -0
  143. package/dist/types/nodes.d.ts +35 -0
  144. package/dist/types/segmentation.d.ts +31 -0
  145. package/dist/types/session.d.ts +102 -0
  146. package/dist/types/tracking.d.ts +30 -0
  147. package/dist/types/util-types.d.ts +1 -0
  148. package/dist/useFaceResults-cpsSmKKc.js +81 -0
  149. package/dist/useFaceResults-hVUJYSEY.cjs +1 -0
  150. package/dist/utils/calibrationDebug.d.ts +9 -0
  151. package/dist/utils/dataURL.d.ts +6 -0
  152. package/dist/utils/drawMediaCover.d.ts +18 -0
  153. package/dist/utils/errors.d.ts +3 -0
  154. package/dist/utils/fns.d.ts +48 -0
  155. package/dist/utils/headBinding.d.ts +85 -0
  156. package/dist/utils/integrity.d.ts +43 -0
  157. package/dist/utils/landmarkBounds.d.ts +11 -0
  158. package/dist/utils/logger.d.ts +7 -0
  159. package/package.json +113 -0
package/GROUNDING.md ADDED
@@ -0,0 +1,537 @@
1
+ # Vincentt XR SDK — Grounding Reference
2
+
3
+ The complete, authoritative reference for the `@vincentt-xr/sdk` package:
4
+ every component, hook, and prop you may use, plus the R3F conventions for
5
+ composing them. Use the components and props documented here; do not invent props
6
+ or spelunk `node_modules` for the API.
7
+
8
+ ## Import doors — one per task
9
+
10
+ The API is split into task-domain entry points. Import each symbol from the door
11
+ this reference shows for it; the imports here are literal.
12
+
13
+ - `@vincentt-xr/sdk` — **core**: providers, scene, screen-space layout, `TextLabel`, `Panel`, `SpriteAnimation`, `VideoBackground`, `AspectRatioContainer`, capture/share (`useMediaRecorder`, `dataURLtoFile`), audio.
14
+ - `@vincentt-xr/sdk/tracking` — **trackers**: face/hand/gesture/body/segmentation, `TrackingAnchor`, `GestureTrigger`, the bare `FaceMesh`, `useFaceResults`.
15
+ - `@vincentt-xr/sdk/face-effects` — **face deep**: FaceMesh material config, retouch, canonical mesh, head-binding.
16
+ - `@vincentt-xr/sdk/low-level` — **escape hatch**: raw model-node reads, selector hooks, custom-tracker plumbing. Reach here only when core + tracking can't express it.
17
+
18
+ Trackers (`<FaceTracker>`, `<GestureTracker>`, `<HandTracker>`, `<BodyTracker>`)
19
+ **self-register** when mounted — no explicit `registerXRPipeline` call. Per-frame
20
+ logic is R3F `useFrame`; per-mount setup is `useEffect`.
21
+
22
+ > This file ships inside the SDK tarball. In a v2-template project it is combined
23
+ > with the template's own grounding (template-local helpers + project notes +
24
+ > patterns) into the project's `GROUNDING.md`. Edit the SDK API here, in the repo
25
+ > that owns the API.
26
+
27
+ ---
28
+
29
+ ## Screen-space layout (the primary tool)
30
+
31
+ Most Vincentt projects use screen-space UI: decorative frames, prompts, badges, controls — overlaid on the live camera feed. This is more common than world-space landmark attachment.
32
+
33
+ ### `<ScreenSpaceUI>` — Container for screen-space content
34
+
35
+ Wraps all screen-anchored children. Must be the parent of any `<ScreenTransform>`.
36
+
37
+ ```tsx
38
+ import { ScreenSpaceUI, ScreenTransform } from "@vincentt-xr/sdk";
39
+
40
+ <ScreenSpaceUI>
41
+ <ScreenTransform anchors={{ left: -1, right: 1, top: 1, bottom: -1 }}>
42
+ {/* full-screen content */}
43
+ </ScreenTransform>
44
+ </ScreenSpaceUI>;
45
+ ```
46
+
47
+ ### `<ScreenTransform anchors={{ left, right, top, bottom }}>` — Place an element within a normalized screen rect
48
+
49
+ Coordinates are normalized: -1 to +1, with +y up. The center of the screen is `(0, 0)`. The anchors rect controls BOTH size and placement of the child — it stretches the child (plane, TextLabel, sprite) to fill the rect. Don't add `scale`/`position`/`planeGeometry args` to size or move a screen-anchored child; set the anchors.
50
+
51
+ ```tsx
52
+ // Full-screen frame
53
+ <ScreenTransform anchors={{ left: -1, right: 1, top: 1, bottom: -1 }}>
54
+ <mesh name="frame"><planeGeometry args={[1, 1]} /><meshBasicMaterial map={frameTex} transparent /></mesh>
55
+ </ScreenTransform>
56
+
57
+ // Bottom-center prompt
58
+ <ScreenTransform anchors={{ left: -0.4, right: 0.4, top: -0.5, bottom: -0.9 }}>
59
+ <TextLabel name="prompt" text="Show your hand" fontSize={220} />
60
+ </ScreenTransform>
61
+
62
+ // Full-width strip pinned FLUSH to the top edge (e.g. a colored banner plane)
63
+ <ScreenTransform anchors={{ left: -1, right: 1, top: 1, bottom: 0.6 }}>
64
+ <mesh name="topBar"><planeGeometry args={[1, 1]} /><meshBasicMaterial color="#2255ff" /></mesh>
65
+ </ScreenTransform>
66
+ ```
67
+
68
+ **Converting a screen-region annotation (% coords) to anchors.** Annotation marks come as top-left percentages (x: 0%=left→100%=right, y: 0%=top→100%=bottom). Anchors are normalized -1..+1 with +y UP, so the y sign flips:
69
+ `left = x%/50 - 1`, `right = x%/50 - 1`, `top = 1 - y%/50`, `bottom = 1 - y%/50`. A mark at x 0–100%, y 0–28% → `anchors={{ left: -1, right: 1, top: 1, bottom: 0.44 }}`. **Snap a near-edge mark to that edge** (within ~10%): top→`top: 1`, bottom→`bottom: -1`, left→`left: -1`, right→`right: 1`. A strip drawn near the bottom (y 78–90%) → `anchors={{ left: -1, right: 1, top: -0.6, bottom: -1 }}` (flush at -1, not the literal -0.8).
70
+
71
+ ### `useScreenRectAspect()` + `computeContainScale(rectAspect, contentAspect)` — Aspect-correct fitting
72
+
73
+ Use together when you need a texture or mesh to fit inside its anchor box without stretching.
74
+
75
+ ```tsx
76
+ import { useScreenRectAspect, computeContainScale } from "@vincentt-xr/sdk";
77
+
78
+ const rectAspect = useScreenRectAspect();
79
+ const [sx, sy] = computeContainScale(rectAspect, 1200 / 800); // content w/h ratio
80
+
81
+ <mesh name="badge" scale={[sx * 0.5, sy * 0.5, 1]}>
82
+ <planeGeometry args={[1, 1]} />
83
+ <meshBasicMaterial map={tex} transparent />
84
+ </mesh>;
85
+ ```
86
+
87
+ ### `<SpriteAnimation sheet={{ url, cols, rows, frames, fps }}>` — Animated transparent overlay
88
+
89
+ The primitive for an animated transparent overlay (a frame border, a gesture-instruction graphic, an animated sticker). One transparent WebP atlas of `cols`×`rows` cells; the first `frames` play left-to-right, top-to-bottom at `fps`. It is a single static GPU texture with a per-frame UV offset — no video decode — so it works in every native and in-app browser and costs ~zero heat.
90
+
91
+ **Use this, not transparent video.** No single video codec plays everywhere with alpha (HEVC-alpha is iOS-Safari-only; VP8/VP9-alpha WebM renders blank as a WebGL texture on iOS Safari), and two video-texture decodes on top of face tracking overheat the phone. Export the atlas as a real 8-bit-alpha WebP (with `cwebp`; ffmpeg's `libwebp` drops alpha). Pair opaque H.264 video only for full-frame backgrounds.
92
+
93
+ Defaults to full-screen; pass `anchors` to place it and `contentAspect` (frame width/height) to contain-fit without stretching. Must live inside a `<ScreenSpaceUI>`.
94
+
95
+ ```tsx
96
+ import { SpriteAnimation, ScreenSpaceUI } from "@vincentt-xr/sdk";
97
+
98
+ <ScreenSpaceUI>
99
+ {/* full-screen animated frame border */}
100
+ <SpriteAnimation sheet={{ url: frameUrl, cols: 8, rows: 6, frames: 48, fps: 8 }} name="frame" />
101
+
102
+ {/* a placed, contain-fit instruction clip */}
103
+ <SpriteAnimation
104
+ sheet={{ url: startUrl, cols: 6, rows: 4, frames: 24, fps: 12 }}
105
+ anchors={{ left: -0.6, right: 0.6, top: 0.4, bottom: -0.4 }}
106
+ contentAspect={1}
107
+ name="startHint"
108
+ />
109
+ </ScreenSpaceUI>;
110
+ ```
111
+
112
+ ---
113
+
114
+ ## Trackers (self-registering)
115
+
116
+ Mount the tracker component; it registers the underlying ML model automatically. No `registerXRPipeline` call needed.
117
+
118
+ ### `<FaceTracker>` — Face tracking (468 landmarks)
119
+
120
+ Detects faces and provides face transform to children via context. Supports `faceIndex` (default 0) and `scaleWithFace`.
121
+
122
+ ```tsx
123
+ import { FaceTracker, TrackingAnchor } from "@vincentt-xr/sdk/tracking";
124
+
125
+ <FaceTracker>
126
+ <TrackingAnchor target="face.forehead">
127
+ <mesh name="crown" position={[0, 0.1, 0]}>
128
+ <planeGeometry args={[0.3, 0.15]} />
129
+ <meshBasicMaterial map={crownTex} transparent />
130
+ </mesh>
131
+ </TrackingAnchor>
132
+ </FaceTracker>;
133
+ ```
134
+
135
+ ### `<FaceTracker.Mesh />` / `<FaceMesh>` — Deforming 468-point face mesh
136
+
137
+ For masks, makeup, and full-face decoration that deforms with skin.
138
+
139
+ ```tsx
140
+ import { FaceTracker, FaceMesh } from "@vincentt-xr/sdk/tracking";
141
+
142
+ <FaceTracker>
143
+ <FaceMesh appearance={{ map: maskTex }} />
144
+ </FaceTracker>;
145
+ ```
146
+
147
+ ### `<HandTracker>` — Hand skeleton (21 joints)
148
+
149
+ The hand-skeleton tracker. Use it for 21-joint hand landmarks — anchoring to `hand.indexTip`, `hand.wrist`, etc. It also exposes a crude open-vs-closed grip on `node.gesture` (only `closed_fist` / `open_palm`), but that is a side effect of the skeleton, not a gesture recognizer. **For any named gesture, use `<GestureTracker>`** (next section) — it is the gesture door; `<HandTracker>` is the skeleton door.
150
+
151
+ ```tsx
152
+ import { HandTracker } from "@vincentt-xr/sdk/tracking";
153
+
154
+ <HandTracker />;
155
+ ```
156
+
157
+ ### `<GestureTracker>` — Named gestures
158
+
159
+ The gesture door — the component that recognizes named gestures. Its `node.gesture` is one of these exact strings (copy verbatim — these are the only valid values):
160
+
161
+ `closed_fist`, `open_palm`, `pointing_up`, `thumb_down`, `thumb_up`, `victory`, `i_love_you`, `none`.
162
+
163
+ Catches people out:
164
+
165
+ - It is `thumb_up` / `thumb_down` — singular, no trailing "s" (not `thumbs_up`).
166
+ - The peace sign is `victory`.
167
+ - There is no `open_hand`; the open-hand gesture is `open_palm`.
168
+
169
+ ```tsx
170
+ import { GestureTracker } from "@vincentt-xr/sdk/tracking";
171
+
172
+ <GestureTracker />;
173
+ ```
174
+
175
+ ### `<BodyTracker>` — Pose tracking (33 landmarks)
176
+
177
+ Head, shoulders, elbows, wrists, hips, knees, ankles.
178
+
179
+ ```tsx
180
+ import { BodyTracker, TrackingAnchor } from "@vincentt-xr/sdk/tracking";
181
+
182
+ <BodyTracker>
183
+ <TrackingAnchor target="body.leftShoulder">
184
+ <mesh name="badge-left">
185
+ <sphereGeometry args={[0.05]} />
186
+ <meshBasicMaterial color="red" />
187
+ </mesh>
188
+ </TrackingAnchor>
189
+ </BodyTracker>;
190
+ ```
191
+
192
+ ### `<Segmentation type="portrait">` — Background segmentation (declarative)
193
+
194
+ Registers the portrait segmentation pipeline. The resulting mask feeds into `<VideoBackground segmentationMask={...} />` for background replacement. Mount it alongside a ref or state that holds the mask texture.
195
+
196
+ ---
197
+
198
+ ## Tracker performance & lifecycle
199
+
200
+ ML tracker models are the heaviest thing in an XR app — each one runs a MediaPipe pipeline on the camera feed. Two rules keep apps from overheating the device and from a scene-transition gesture bug.
201
+
202
+ ### Mount shared trackers ONCE, above any scene/phase switch
203
+
204
+ A tracker is a refcounted model: mounting acquires it, unmounting disposes it. If you mount `<GestureTracker />` (or `<FaceTracker>`) **inside** each scene of a multi-scene flow (home → game → photo …), every transition tears the model down and rebuilds it. That (a) churns the GPU and (b) hits a dispose/re-acquire race where the gesture stops being reported after the transition (it works if you deep-link straight to the scene, but not when arriving from another scene).
205
+
206
+ Fix: mount trackers that more than one scene needs **once**, above the phase switch, so the model stays alive for the whole session:
207
+
208
+ ```tsx
209
+ // Scene.tsx
210
+ <>
211
+ <GestureTracker /> {/* persistent — every phase reads its gesture */}
212
+ {renderCurrentPhase()}
213
+ </>
214
+ ```
215
+
216
+ Scope a tracker to a single scene only when just that scene uses it (e.g. a `<FaceTracker>` catcher that exists only during gameplay).
217
+
218
+ ### `active` — pause an off-scene tracker without unmounting it
219
+
220
+ The "mount once" rule keeps a model alive across scenes, but a persistent tracker then runs its MediaPipe pipeline **every frame for the whole session** — even in scenes that don't use it. Each running model does a GPU→CPU readback per inference, and that readback is often the single largest cost in the app; two persistent trackers (e.g. gesture on the menus, face in gameplay) stack two readbacks the whole time when only one is ever needed at once.
221
+
222
+ Every tracker takes an `active` prop (default `true`). Set `active={false}` and the model **pauses inference** — it stays mounted and initialized (no unmount, so no dispose/re-acquire race), it just stops doing per-frame work. Flip it back to `true` and it resumes on the next frame with no re-init. Gate each tracker to the phases that use it:
223
+
224
+ ```tsx
225
+ // Scene.tsx — both trackers persistent (no race), but each only runs when needed.
226
+ <>
227
+ <GestureTracker active={phase !== "game"} /> {/* menus/photo, not gameplay */}
228
+ <FaceTracker active={phase === "game"}>{/* the catcher */}…</FaceTracker>
229
+ {renderCurrentPhase()}
230
+ </>
231
+ ```
232
+
233
+ When several consumers share one model, it runs while **any** of them is active. `active` gates whether the model runs; `perf.targetFps` (below) caps how often it runs while active — use both. `<Segmentation>` also takes `active` (distinct from its `visible` prop, which only gates mask exposure while inference keeps running).
234
+
235
+ ### `perf` — cap inference rate + camera resolution
236
+
237
+ Set `perf` on `<XRProvider>` to trade tracking fidelity for lower load and heat. This is the single biggest lever if the device runs hot.
238
+
239
+ ```tsx
240
+ import { XRProvider } from "@vincentt-xr/sdk";
241
+
242
+ <XRProvider perf={{ targetFps: 24, camRes: 720 }}>
243
+ <App />
244
+ </XRProvider>;
245
+ ```
246
+
247
+ - `targetFps` — caps every tracker model's inference rate (fps). The model's RAF loop still runs, but the expensive detect call is skipped between intervals. Omit → Face/Hand run uncapped, Gesture stays at its 15fps default, Segmentation at 24fps.
248
+ - `camRes` — target width (px) of the image the models **analyze**. This is NOT the visible camera background the user sees — lowering it cuts ML work with no visible quality loss to the feed.
249
+ - `cameraZoom` — **framing** control (not perf): how tightly the webcam is cover-cropped into the viewport. `1` = full native field of view (most of the room visible); default `1.2` fills the frame like a native selfie / Instagram front camera. Applies to the visible feed, the captured photo, and what the trackers see (all read the same crop). Raise for a closer crop, set `1` for the widest shot.
250
+
251
+ Per-tracker override beats the session default:
252
+
253
+ ```tsx
254
+ <GestureTracker targetFps={30} /> {/* crisp gestures … */}
255
+ <FaceTracker targetFps={20}>…</FaceTracker> {/* … cheaper face */}
256
+ ```
257
+
258
+ The base template ships `perf={{ targetFps: 24, camRes: 720 }}` by default — good for both dev laptops and end-user phones. Don't lower it further without a reason; don't remove it to "get full quality" unless you know the target device can take the heat.
259
+
260
+ ---
261
+
262
+ ## Reading tracker state
263
+
264
+ ### `<TrackingAnchor target="...">` — Declarative landmark following
265
+
266
+ Wraps children; the child's world position is updated to the tracked landmark each frame. Use for all decoration that simply follows a face/hand/body point.
267
+
268
+ Valid targets:
269
+
270
+ - **Face:** `face.forehead`, `face.noseTip`, `face.chin`, `face.leftEyeOuter`, `face.rightEyeOuter`, `face.leftCheek`, `face.rightCheek`, `face.mouthCenter`
271
+ - **Hand:** `hand.wrist`, `hand.thumbTip`, `hand.indexTip`, `hand.middleTip`, `hand.ringTip`, `hand.pinkyTip`, `hand.indexBase`, `hand.middleBase`, `hand.ringBase`, `hand.pinkyBase`
272
+ - **Body:** `body.head`, `body.neck`, `body.leftShoulder`, `body.rightShoulder`, `body.leftElbow`, `body.rightElbow`, `body.leftWrist`, `body.rightWrist`, `body.leftHip`, `body.rightHip`, `body.leftKnee`, `body.rightKnee`, `body.leftAnkle`, `body.rightAnkle`
273
+
274
+ ```tsx
275
+ <FaceTracker>
276
+ <TrackingAnchor target="face.noseTip">
277
+ <mesh name="noseDot" scale={[0.04, 0.04, 0.04]}>
278
+ <sphereGeometry />
279
+ <meshBasicMaterial color="red" />
280
+ </mesh>
281
+ </TrackingAnchor>
282
+ </FaceTracker>
283
+ ```
284
+
285
+ **`smoothing` — smooth motion from throttled inference.** Inference is throttled (the `perf` API caps it at 24fps by default) while rendering runs at 60-120fps, so a raw anchor holds then jumps on each new sample — visible stepping under head/hand movement. A plain lerp only hides it at rest; under motion it still steps. Pass `smoothing` (`~15` is a good default) and the anchor runs a **One Euro filter** — the standard adaptive low-pass for interactive tracking: it smooths hard when the landmark moves slowly (kills jitter) and loosens as it speeds up (kills lag), and never extrapolates so it can't overshoot or wobble. Higher `smoothing` is snappier (less lag, slightly more jitter); lower is smoother. It snaps (resets the filter) on first placement and on re-acquire, so it never slides in from a stale position. `0` (default) keeps the raw snap. Rotation is not smoothed. Prefer this over a hand-rolled `useFrame` lerp — a hand-rolled lerp reintroduces the stepping.
286
+
287
+ ```tsx
288
+ <FaceTracker>
289
+ <TrackingAnchor target="face.noseTip" smoothing={15}>
290
+ <mesh name="sticker">{/* follows the nose smoothly, even while moving */}</mesh>
291
+ </TrackingAnchor>
292
+ </FaceTracker>
293
+ ```
294
+
295
+ ### `useXRModelNode(XRModel.GESTURE_TRACKER)` — Read gesture state (the default read path)
296
+
297
+ The gen-3 selector — subscribe to the live gesture node from `useFrame` when you need the gesture name or landmark coordinates for a state machine or fine-grained control. This is the blessed read layer; prefer it over raw node reads.
298
+
299
+ ```tsx
300
+ import { XRModel } from "@vincentt-xr/sdk";
301
+ import { useXRModelNode } from "@vincentt-xr/sdk/low-level";
302
+ import type { XRModelNodeGestureTracking } from "@vincentt-xr/sdk/low-level";
303
+ import { useFrame } from "@react-three/fiber";
304
+
305
+ const gesture = useXRModelNode<XRModelNodeGestureTracking>(XRModel.GESTURE_TRACKER);
306
+
307
+ useFrame(() => {
308
+ if (gesture?.gesture === "victory") {
309
+ // handle peace sign
310
+ }
311
+ });
312
+ ```
313
+
314
+ > **Escape hatch (rarely needed):** `useXRContext()` + `session.getModelNode(...)` from
315
+ > `@vincentt-xr/sdk/low-level` reads the raw node imperatively. Prefer the selector
316
+ > above; reach for the raw read only when you need the session handle for something the
317
+ > selector can't express.
318
+
319
+ ### `<GestureTrigger gestures={["victory"]} onTrigger={(g) => ...}>` — Declarative one-shot gesture handler
320
+
321
+ Fires once per gesture recognition event. The simplest way to act on a gesture — prefer it over a manual `useFrame` poll. Two modes:
322
+
323
+ - **default (`mode="arm"`)** — fires the first time the gesture appears _after_ the user has _not_ been showing it for `debounceMs` (default 800). Use for "raise a NEW gesture to advance," where a stale frame from the prior scene must not fire.
324
+ - **`mode="hold"`** — fires as soon as the gesture is held for `holdMs` (default 200), with **no arm gate**. Use for an **instruction-driven scene** ("show ✌️ to start") where the UI tells the user to raise the gesture and the hand is already up on mount — the arm gate needs a not-showing stretch first, so in `arm` mode it would never fire.
325
+
326
+ ```tsx
327
+ import { GestureTracker, GestureTrigger } from "@vincentt-xr/sdk/tracking";
328
+
329
+ <GestureTracker />
330
+
331
+ // "show ✌️ to start" — hand is up on mount, so use hold mode
332
+ <GestureTrigger
333
+ mode="hold"
334
+ gestures={["victory"]}
335
+ onTrigger={() => startGame()}
336
+ />
337
+
338
+ // "make a NEW gesture to advance" — default arm mode
339
+ <GestureTrigger
340
+ gestures={["thumb_up"]}
341
+ onTrigger={() => next()}
342
+ />
343
+ ```
344
+
345
+ ---
346
+
347
+ ## Camera background
348
+
349
+ ### `<VideoBackground customBackground="#6366f1" segmentationMask={maskTex} renderOrder={-999} />`
350
+
351
+ In a v2-template project this is already mounted in `src/App.tsx` — do NOT add a second one there. Mentioned here because `customBackground` is how you do background replacement: pair it with `<Segmentation type="portrait" />` and pass the segmentation mask texture as `segmentationMask`.
352
+
353
+ ---
354
+
355
+ ## Mesh primitives and textures
356
+
357
+ Use R3F intrinsic `<mesh>`, `<group>`, `<planeGeometry>`, `<meshBasicMaterial>`, etc. Load image textures with drei's `useTexture`.
358
+
359
+ **Always name your meshes** (`name="myLabel"`). Names enable runtime lookup AND visual-feedback editing by the editor.
360
+
361
+ **Materials accept `map`, not `alphaMap`, for image asset slots.**
362
+
363
+ ```tsx
364
+ import { useTexture } from "@react-three/drei";
365
+
366
+ const tex = useTexture("https://cdn.../badge.webp");
367
+
368
+ <mesh name="badge" position={[0, 0.5, 0]}>
369
+ <planeGeometry args={[1, 1]} />
370
+ <meshBasicMaterial map={tex} transparent />
371
+ </mesh>;
372
+ ```
373
+
374
+ ---
375
+
376
+ ## `<TextLabel>` — Canvas-rendered text panel
377
+
378
+ Import from `@vincentt-xr/sdk`. Billboarded in 3D space.
379
+
380
+ | prop | type | default | notes |
381
+ | -------------- | ------------------- | ---------------------- | --------------------------------------------------------------------------------------------- |
382
+ | `text` | `string` | **required** | The text to render. |
383
+ | `color` | `string` | `#ffffff` | Text color. |
384
+ | `fontSize` | `number` | `48` | Canvas px at the default 512px canvas — NOT world units. Chips/badges typically need 200-320. |
385
+ | `bgColor` | `string` | `rgba(20,40,110,0.88)` | Panel background; use `"transparent"` for text-only. |
386
+ | `borderColor` | `string` | — | Border color; omit for no border. |
387
+ | `borderRadius` | `number` | `24` | Corner radius; 0 for square. |
388
+ | `aspect` | `number` | `2.5` | Panel shape (width ÷ height). >1 = wide/banner, <1 = tall. |
389
+ | `scale` | `[x,y,z] \| number` | `1` | World size. Combine with `aspect` to make wide banners. |
390
+ | `position` | `[x,y,z]` | `[0,0,0]` | World position. +Y up, -Y down. |
391
+ | `name` | `string` | — | Set on every element you create. |
392
+
393
+ **Gotchas:**
394
+
395
+ - `fontSize` is canvas px, not world units. Chips/badges need 200-320, not 16-48.
396
+ - `fontSize` does not change panel shape — use `aspect` (shape) and `scale` (size).
397
+ - `TextLabel` ignores the `visible` prop — wrap in `<group visible={false}>` to start hidden.
398
+ - **Inside a `<ScreenTransform>`, the parent stretches the label to fill the anchor rect — `aspect` must equal the rect's aspect or the banner distorts and leaves dead space around the text.** The rect's aspect is `(right − left) / (top − bottom)`. So for a full-width top banner `anchors={{ left: -1, right: 1, top: 1, bottom: 0.76 }}` → aspect `= 2 / 0.24 ≈ 8.3`. Set `borderRadius={0}` for a true edge-to-edge banner. (The rect controls size + placement — don't add `scale`/`position` to fill it; set the anchors instead.)
399
+
400
+ ```tsx
401
+ // Full-width banner pinned to the top, INSIDE a ScreenTransform.
402
+ // aspect = rect width (2) / rect height (0.24) ≈ 8.3 — matches the rect so it fills cleanly.
403
+ <ScreenTransform anchors={{ left: -1, right: 1, top: 1, bottom: 0.76 }}>
404
+ <TextLabel name="topBanner" text="Head-Tilt Trivia" aspect={8.3} borderRadius={0} fontSize={270} />
405
+ </ScreenTransform>
406
+
407
+ // Full-width banner via position+scale, NOT inside a ScreenTransform.
408
+ // Here aspect should match scale.x / scale.y (4 / 0.8 = 5).
409
+ <TextLabel name="topBanner" text="Welcome" position={[0, 1.4, 0]} scale={[4, 0.8, 1]} aspect={5} fontSize={180} />
410
+ ```
411
+
412
+ ---
413
+
414
+ ## `<Panel>` — 3D billboarded panel for grouping UI content
415
+
416
+ ```tsx
417
+ import { Panel, TextLabel } from "@vincentt-xr/sdk";
418
+
419
+ <Panel>
420
+ <TextLabel name="title" text="Score: 10" fontSize={240} />
421
+ </Panel>;
422
+ ```
423
+
424
+ ---
425
+
426
+ ## App shell
427
+
428
+ ### `<AspectRatioContainer>` — Letterbox the app into a 9:16 portrait frame
429
+
430
+ Wrap the app root to keep the AR composition framed the same across phones and
431
+ desktop: on wide viewports it pillarboxes to the 9:16 ratio; on portrait it fills
432
+ the width.
433
+
434
+ ```tsx
435
+ import { AspectRatioContainer } from "@vincentt-xr/sdk";
436
+
437
+ <AspectRatioContainer>
438
+ <XRProvider>{/* ... */}</XRProvider>
439
+ </AspectRatioContainer>;
440
+ ```
441
+
442
+ ---
443
+
444
+ ## Capture & share
445
+
446
+ Grab a photo of the live render or record the canvas, then save or share it.
447
+
448
+ ### `session.captureFrame()` + `dataURLtoFile()` — Single-shot photo
449
+
450
+ `captureFrame()` forces a fresh render and returns a `data:` URL of the current
451
+ frame. `dataURLtoFile(dataUrl, filename)` turns it into a `File` for upload or the
452
+ Web Share API.
453
+
454
+ ```tsx
455
+ import { dataURLtoFile } from "@vincentt-xr/sdk";
456
+ import { useXRContext } from "@vincentt-xr/sdk/low-level";
457
+
458
+ const { session } = useXRContext();
459
+ const dataUrl = session.captureFrame();
460
+ const file = dataURLtoFile(dataUrl, "photo.png");
461
+ ```
462
+
463
+ ### `useMediaRecorder()` — Record the canvas to a video Blob
464
+
465
+ Records a `MediaStream` (from the canvas's `captureStream()`) to a `Blob`. Picks
466
+ the first supported codec (mp4 → webm). Pass `audio: true` to mix in the mic, or
467
+ `audio: <MediaStream>` for your own audio track.
468
+
469
+ ```tsx
470
+ import { useMediaRecorder } from "@vincentt-xr/sdk";
471
+
472
+ const rec = useMediaRecorder();
473
+ const canvas = document.querySelector("#vincentt-xr-canvas canvas");
474
+ await rec.start(canvas.captureStream(30));
475
+ // ...later
476
+ rec.stop(); // -> rec.recordedBlob is the video Blob
477
+ ```
478
+
479
+ ---
480
+
481
+ ## Audio
482
+
483
+ A silent-switch-safe sound engine for AR filters. Audio on iOS has three traps
484
+ every app otherwise re-solves; this bundles the fix. Use it — do not hand-roll
485
+ `new Audio()` or Web Audio.
486
+
487
+ 1. **Gesture-started audio is blocked.** Sound kicked off from a camera-detected
488
+ gesture (a peace sign to start) is not a user-input event, so iOS blocks it.
489
+ Run **muted by default** and let the tap that unmutes be the gesture that
490
+ unlocks playback (IG/Snap-style speaker toggle).
491
+ 2. **Web Audio is silenced by the hardware silent switch.** The engine uses
492
+ `<audio>` elements (the switch-immune "playback" category), not Web Audio.
493
+ 3. **The rewind seek janks a frame.** Pooled SFX rewind on their `ended` event,
494
+ off the hot path — so firing an effect never does a synchronous seek.
495
+
496
+ Two paths, deliberately separate: **background music** is one looping element
497
+ (plays/pauses on mute, never pooled); **interaction SFX** are short one-shots
498
+ through a per-sound voice pool (a concurrency cap bounds decode load) with a
499
+ per-URL coalesce window (the same sound twice in a frame plays once; two
500
+ different sounds at once both play).
501
+
502
+ The API is free functions over one default engine. Preload during the loading
503
+ screen (`registerSound` / `prefetchMusic` return promises to await) so the
504
+ unmute tap is instant.
505
+
506
+ ```tsx
507
+ import {
508
+ registerSound,
509
+ prefetchMusic,
510
+ startMusic,
511
+ playOneShot,
512
+ toggleMuted,
513
+ useMuted,
514
+ } from "@vincentt-xr/sdk";
515
+
516
+ // During the loading gate: buffer every sound so unmute is instant.
517
+ await Promise.all([
518
+ prefetchMusic(musicUrl), // one looping track
519
+ registerSound(catchUrl), // an SFX voice pool
520
+ registerSound(hitUrl),
521
+ ]);
522
+ startMusic(musicUrl); // requested now; actually plays on unmute
523
+
524
+ // Fire an effect from anywhere — including a non-React r3f collision callback.
525
+ // No-op while muted; coalesced per URL; no seek on the hot path.
526
+ playOneShot(catchUrl);
527
+
528
+ // The speaker toggle. The first unmute is the gesture that unlocks iOS audio.
529
+ function SoundToggle() {
530
+ const muted = useMuted();
531
+ return <button onClick={toggleMuted}>{muted ? "🔇" : "🔊"}</button>;
532
+ }
533
+ ```
534
+
535
+ Audio assets: m4a (AAC). A rare app needing a second, independent mixer can
536
+ `new AudioEngine()` and drive it directly; the free functions always target the
537
+ one default engine.
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) Vincentt. All rights reserved.
2
+
3
+ This software and its source code are proprietary and confidential. The
4
+ published package is provided for use with the Vincentt platform only. No
5
+ license is granted to copy, modify, redistribute, reverse-engineer, or create
6
+ derivative works, or to use this software to build a competing product, except
7
+ under a separate written agreement with Vincentt.
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # RTX AR Tracking SDK
2
+
3
+ This SDK is intended for implementing AI-powered tracking for WebAR projects by rangoon.tech.
4
+
5
+ ## Installation
6
+ First, install the library by running the command
7
+
8
+ ```
9
+ npm install @rangoon/track
10
+ ```
11
+
12
+ If you're using yarn,
13
+
14
+ ```
15
+ yarn add @rangoon/track
16
+ ```
17
+
18
+ ## How to use
19
+ The usage of `@rangoon/track` usually involves two steps.
20
+
21
+ ### 1. Start a video stream
22
+ Initialize the SDK by attaching the `@rangoon/track`'s `Webcam` component to your React Project.
23
+ The `Webcam` component will start a video stream which will be used for tracking.
24
+
25
+ ```ts
26
+ import { Webcam } from "@rangoon/track"
27
+ import { useRef } from "react"
28
+
29
+ function App() {
30
+
31
+ const videoRef = useRef<HTMLVideoElement>(null)
32
+
33
+ return (
34
+ <div>
35
+ <Webcam videoReference={videoRef}/>
36
+ </div>
37
+ )
38
+ }
39
+
40
+ export default App
41
+
42
+ ```
43
+
44
+ ### 2. Initiate a tracking model
45
+
46
+ Currently, `@rangoon/track` supports three types of tracking models.
47
+ * Hand Tracking (Landmarks)
48
+ * Hand Gesture Tracking (Landmarks + Gestures)
49
+ * Face Tracking
50
+
51
+ By calling the Hook associated with the respective model, you can obtain the tracking data.
52
+
53
+ Below is an example of obtaining hand-tracking data with `useHandTracking` Hook.
54
+ ```ts
55
+ import { Webcam, useHandTracking } from "@rangoon/track"
56
+ import { useEffect, useRef } from "react"
57
+
58
+ function App() {
59
+
60
+ const videoRef = useRef<HTMLVideoElement>(null)
61
+
62
+ //Initiate the model by passing the webcam reference
63
+ const handData = useHandTracking({
64
+ videoReference: videoRef
65
+ })
66
+
67
+ //Watch the data changes with useEffect
68
+ useEffect(() => {
69
+ console.log(handData)
70
+ }, [handData])
71
+
72
+ return (
73
+ <div>
74
+ <Webcam videoReference={videoRef}/>
75
+ </div>
76
+ )
77
+ }
78
+
79
+ export default App
80
+ ```
81
+
82
+
83
+ ## Feedback
84
+
85
+ If you have any feedback, please reach out to us at waing@rangoon.tech
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),x=require("react"),C=require("react-dom"),s=require("./internal-DH5d1Xnq.cjs"),P={position:"fixed",top:"10px",left:"10px",zIndex:9999,fontFamily:"'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace",fontSize:"12px",color:"#fff",pointerEvents:"auto",userSelect:"none"},h={background:"rgba(0, 0, 0, 0.85)",borderRadius:"6px",padding:"12px 16px",boxShadow:"0 4px 12px rgba(0, 0, 0, 0.5)",backdropFilter:"blur(10px)",border:"1px solid rgba(255, 255, 255, 0.1)",minWidth:"240px"},k={...h,padding:"8px 12px",minWidth:"auto"},F={display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:"12px",paddingBottom:"8px",borderBottom:"1px solid rgba(255, 255, 255, 0.2)"},M={fontSize:"13px",fontWeight:"bold",color:"#00ff9f",letterSpacing:"0.5px",textTransform:"uppercase"},z={display:"flex",gap:"6px"},f={background:"rgba(255, 255, 255, 0.1)",border:"1px solid rgba(255, 255, 255, 0.2)",borderRadius:"4px",color:"#fff",padding:"4px 8px",fontSize:"11px",cursor:"pointer",transition:"all 0.2s"},R={display:"grid",gridTemplateColumns:"1fr 1fr",gap:"10px"},i={display:"flex",flexDirection:"column",gap:"4px"},o={fontSize:"10px",color:"rgba(255, 255, 255, 0.6)",textTransform:"uppercase",letterSpacing:"0.5px"},d={fontSize:"16px",fontWeight:"bold",color:"#fff"},u={...d,color:"#00ff9f"},g={...d,color:"#ffaa00"},m={...d,color:"#ff4444"},l={fontSize:"11px",color:"rgba(255, 255, 255, 0.5)",marginLeft:"4px"},N={width:"100%",height:"4px",background:"rgba(255, 255, 255, 0.2)",borderRadius:"2px",overflow:"hidden",marginTop:"4px"},w=t=>({width:`${t}%`,height:"100%",background:t>90?"#ff4444":t>70?"#ffaa00":"#00ff9f",transition:"width 0.3s ease, background 0.3s ease"}),D={fontSize:"9px",color:"rgba(255, 255, 255, 0.4)",marginTop:"8px",textAlign:"center",fontStyle:"italic"};function a(t,n){return n==="fps"?t>=30?u:t>=20?g:m:t<=40?u:t<=60?g:m}function B({metrics:t,collapsed:n,onCollapse:y,onClose:b,position:c="top-left",keyboardShortcut:j="ctrl+shift+d"}){const[p,v]=x.useState(null);if(x.useEffect(()=>{const r=document.createElement("div");return r.id="xr-debugger-overlay",document.body.appendChild(r),v(r),()=>{document.body.removeChild(r)}},[]),!p)return null;const S={...P,...c==="top-right"&&{left:"auto",right:"10px"},...c==="bottom-left"&&{top:"auto",bottom:"10px"},...c==="bottom-right"&&{top:"auto",bottom:"10px",left:"auto",right:"10px"}};return C.createPortal(e.jsx("div",{style:S,children:e.jsxs("div",{style:n?k:h,children:[e.jsxs("div",{style:F,children:[e.jsx("div",{style:M,children:n?"AR Debugger":"AR Performance"}),e.jsxs("div",{style:z,children:[e.jsx("button",{style:f,onClick:()=>y(!n),onMouseEnter:r=>{r.currentTarget.style.background="rgba(255, 255, 255, 0.2)"},onMouseLeave:r=>{r.currentTarget.style.background="rgba(255, 255, 255, 0.1)"},children:n?"+":"−"}),e.jsx("button",{style:f,onClick:b,onMouseEnter:r=>{r.currentTarget.style.background="rgba(255, 255, 255, 0.2)"},onMouseLeave:r=>{r.currentTarget.style.background="rgba(255, 255, 255, 0.1)"},children:"✕"})]})]}),!n&&e.jsxs(e.Fragment,{children:[e.jsxs("div",{style:R,children:[e.jsxs("div",{style:i,children:[e.jsx("div",{style:o,children:"Camera FPS"}),e.jsxs("div",{children:[e.jsx("span",{style:a(t.cameraFPS,"fps"),children:s.formatNumber(t.cameraFPS,0)}),e.jsx("span",{style:l,children:"fps"})]})]}),e.jsxs("div",{style:i,children:[e.jsx("div",{style:o,children:"Resolution"}),e.jsx("span",{style:d,children:t.resolution})]}),e.jsxs("div",{style:i,children:[e.jsx("div",{style:o,children:"Detection"}),e.jsxs("div",{children:[e.jsx("span",{style:a(t.detectionTime,"time"),children:s.formatNumber(t.detectionTime,1)}),e.jsx("span",{style:l,children:"ms"})]})]}),e.jsxs("div",{style:i,children:[e.jsx("div",{style:o,children:"Rendering"}),e.jsxs("div",{children:[e.jsx("span",{style:a(t.renderTime,"time"),children:s.formatNumber(t.renderTime,1)}),e.jsx("span",{style:l,children:"ms"})]})]}),e.jsxs("div",{style:i,children:[e.jsx("div",{style:o,children:"Effective FPS"}),e.jsxs("div",{children:[e.jsx("span",{style:a(t.effectiveFPS,"fps"),children:s.formatNumber(t.effectiveFPS,0)}),e.jsx("span",{style:l,children:"fps"})]})]}),e.jsxs("div",{style:i,children:[e.jsx("div",{style:o,children:"Memory"}),t.memory?e.jsxs(e.Fragment,{children:[e.jsxs("div",{children:[e.jsx("span",{style:d,children:s.formatNumber(t.memory.used,0)}),e.jsxs("span",{style:l,children:["/ ",s.formatNumber(t.memory.total,0)," MB"]})]}),e.jsx("div",{style:N,children:e.jsx("div",{style:w(t.memory.percentage)})})]}):e.jsx("span",{style:d,children:"N/A"})]})]}),t.modelPerformance.length>0&&e.jsx(e.Fragment,{children:e.jsxs("div",{style:{borderTop:"1px solid rgba(255, 255, 255, 0.15)",marginTop:"12px",paddingTop:"12px"},children:[e.jsx("div",{style:o,children:"Model Performance"}),e.jsx("div",{style:{marginTop:"8px",display:"flex",flexDirection:"column",gap:"6px"},children:t.modelPerformance.map((r,T)=>e.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",fontSize:"11px"},children:[e.jsx("span",{style:{color:"rgba(255, 255, 255, 0.7)"},children:r.name}),e.jsxs("div",{children:[e.jsx("span",{style:a(r.processingTime,"time"),children:s.formatNumber(r.processingTime,1)}),e.jsx("span",{style:l,children:"ms"})]})]},T))})]})}),e.jsxs("div",{style:D,children:["Press ",j.toUpperCase()," to toggle"]})]})]})}),p)}exports.DebuggerUI=B;