@vincentt-xr/sdk 2.0.0-alpha.0 → 2.0.0-alpha.1

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 (106) hide show
  1. package/GROUNDING.md +232 -14
  2. package/dist/{DebuggerUI-CEfdddkZ.cjs → DebuggerUI-CatYc-RG.cjs} +1 -1
  3. package/dist/{DebuggerUI-Dgop6Q6o.js → DebuggerUI-gLMKiVwJ.js} +1 -1
  4. package/dist/MediaSourceSwitcher-C-xYg0nQ.js +317 -0
  5. package/dist/MediaSourceSwitcher-Cq3g4IKw.cjs +31 -0
  6. package/dist/components/FaceMesh/canonical/CanonicalFaceMeshRenderer.d.ts +6 -1
  7. package/dist/components/FaceMesh/material/defaults.d.ts +4 -0
  8. package/dist/components/FaceMesh/material/types.d.ts +2 -0
  9. package/dist/components/FaceRetouch/Background.d.ts +6 -1
  10. package/dist/components/FaceRetouch/defaults.d.ts +13 -0
  11. package/dist/components/FaceRetouch/index.d.ts +3 -1
  12. package/dist/components/FaceRetouch/landmarkMask.d.ts +4 -1
  13. package/dist/components/FaceRetouch/useFaceRetouchMask.d.ts +2 -1
  14. package/dist/components/FaceWarp3D/FaceWarp3DHead.d.ts +15 -0
  15. package/dist/components/FaceWarp3D/defaults.d.ts +37 -0
  16. package/dist/components/FaceWarp3D/index.d.ts +3 -0
  17. package/dist/components/FaceWarp3D/warp.d.ts +13 -0
  18. package/dist/components/HandJoints2D/index.d.ts +6 -0
  19. package/dist/components/HandTracker/index.d.ts +2 -2
  20. package/dist/components/HandTracking2D/HandVisual.d.ts +12 -0
  21. package/dist/components/HandTracking2D/index.d.ts +5 -0
  22. package/dist/components/HandTracking2DController/index.d.ts +5 -0
  23. package/dist/components/HandTracking3D/index.d.ts +6 -0
  24. package/dist/components/ScreenImage/index.d.ts +87 -0
  25. package/dist/components/ScreenText/index.d.ts +20 -0
  26. package/dist/components/TextLabel/index.d.ts +99 -0
  27. package/dist/components/XRScene/index.d.ts +14 -3
  28. package/dist/components/XRViewport/index.d.ts +17 -0
  29. package/dist/components/index.d.ts +7 -0
  30. package/dist/debug-ui/default-setting/index.d.ts +37 -0
  31. package/dist/debug-ui/default-setting.cjs +21 -0
  32. package/dist/debug-ui/default-setting.js +777 -0
  33. package/dist/debug-ui/face-warp-3d/index.d.ts +6 -0
  34. package/dist/debug-ui/hand-joints-2d/index.d.ts +6 -0
  35. package/dist/debug-ui/hand-tracking-2d/index.d.ts +6 -0
  36. package/dist/debug-ui/hand-tracking-2d-controller/index.d.ts +6 -0
  37. package/dist/debug-ui/hand-tracking-3d/index.d.ts +6 -0
  38. package/dist/debug-ui/index.d.ts +23 -0
  39. package/dist/debug-ui/layer-panel/index.d.ts +17 -0
  40. package/dist/debug-ui/media-source/MediaSourceSwitcher.d.ts +10 -0
  41. package/dist/debug-ui/media-source/index.d.ts +4 -0
  42. package/dist/debug-ui/media-source/media-preset.d.ts +9 -0
  43. package/dist/debug-ui/media-source.cjs +1 -0
  44. package/dist/debug-ui/media-source.js +7 -0
  45. package/dist/debug-ui/screen-image-settings/index.d.ts +7 -0
  46. package/dist/debug-ui/screen-text-settings/index.d.ts +122 -0
  47. package/dist/debug-ui/settings-panel/index.d.ts +38 -0
  48. package/dist/debug-ui/shared/MaterialSection.d.ts +13 -0
  49. package/dist/debug-ui/shared/ScreenTransformSection.d.ts +29 -0
  50. package/dist/debug-ui.cjs +20 -0
  51. package/dist/debug-ui.js +6063 -0
  52. package/dist/defaults-BLpcN3pG.cjs +1 -0
  53. package/dist/defaults-LtY4M7dg.js +55 -0
  54. package/dist/entries/core.d.ts +5 -1
  55. package/dist/entries/face-effects.d.ts +2 -0
  56. package/dist/entries/low-level.d.ts +3 -0
  57. package/dist/face-effects.cjs +113 -23
  58. package/dist/face-effects.js +1566 -1399
  59. package/dist/handJoints2d.d.ts +49 -0
  60. package/dist/handTracking2d.d.ts +33 -0
  61. package/dist/handTracking2dController.d.ts +65 -0
  62. package/dist/handTracking3d.d.ts +52 -0
  63. package/dist/{index-BD-JWMjE.js → index-BGNbrc4C.js} +113 -137
  64. package/dist/index-BXyObVHF.cjs +1 -0
  65. package/dist/index-CY3bUAcW.js +12 -0
  66. package/dist/index-C_Duh13c.cjs +1 -0
  67. package/dist/index-D8fpPQkC.cjs +1 -0
  68. package/dist/index-DxshgBu0.cjs +1 -0
  69. package/dist/index-O-2crZ3M.js +1102 -0
  70. package/dist/{index-CYMj26Ej.js → index-safCs_af.js} +426 -416
  71. package/dist/{internal-DH5d1Xnq.cjs → internal-BjyhBP8i.cjs} +1 -1
  72. package/dist/{internal-KOKJDxCG.js → internal-Cae1ps15.js} +3 -3
  73. package/dist/internal.cjs +1 -1
  74. package/dist/internal.js +2 -2
  75. package/dist/layerStack.d.ts +29 -0
  76. package/dist/low-level.cjs +1 -1
  77. package/dist/low-level.js +74 -890
  78. package/dist/main.cjs +73 -1
  79. package/dist/main.js +1656 -407
  80. package/dist/models/Hand/HandTracking.d.ts +2 -0
  81. package/dist/screen2d/index.d.ts +4 -0
  82. package/dist/screen2d/material-runtime.d.ts +32 -0
  83. package/dist/screen2d/texture.d.ts +10 -0
  84. package/dist/screen2d/transform.d.ts +25 -0
  85. package/dist/screen2d/types.d.ts +90 -0
  86. package/dist/screenImagePresets.d.ts +13 -0
  87. package/dist/screenImageSettings-6NRg4cx9.js +39 -0
  88. package/dist/screenImageSettings-y_B4_767.cjs +1 -0
  89. package/dist/screenImageSettings.d.ts +50 -0
  90. package/dist/screenTextPresets.d.ts +6 -0
  91. package/dist/screenTextSettings.d.ts +131 -0
  92. package/dist/{selectors-Bub0o3qy.cjs → selectors-BLl2JUWW.cjs} +1 -1
  93. package/dist/{selectors-BHO6TnoC.js → selectors-CXd_x-6t.js} +6 -6
  94. package/dist/tracking.cjs +1 -1
  95. package/dist/tracking.js +193 -168
  96. package/dist/types/hand-tracking.d.ts +8 -0
  97. package/dist/types/nodes.d.ts +2 -0
  98. package/dist/useXRModel-3DcUa28n.cjs +1 -0
  99. package/dist/useXRModel-BMJE1hrk.js +28 -0
  100. package/dist/x-Co8jPhP0.js +127 -0
  101. package/dist/x-KC8E4Gaj.cjs +41 -0
  102. package/package.json +19 -1
  103. package/dist/index-B5J4gzn2.cjs +0 -1
  104. package/dist/index-BVBVg96G.cjs +0 -1
  105. package/dist/index-DtwHElmG.js +0 -157
  106. package/dist/index-PtFmD6B8.cjs +0 -1
package/GROUNDING.md CHANGED
@@ -54,7 +54,9 @@ Coordinates are normalized: -1 to +1, with +y up. The center of the screen is `(
54
54
  <mesh name="frame"><planeGeometry args={[1, 1]} /><meshBasicMaterial map={frameTex} transparent /></mesh>
55
55
  </ScreenTransform>
56
56
 
57
- // Bottom-center prompt
57
+ // Bottom-center prompt.
58
+ // Prefer <ScreenText> for ordinary screen copy; use this low-level pairing only
59
+ // when you need custom anchors.
58
60
  <ScreenTransform anchors={{ left: -0.4, right: 0.4, top: -0.5, bottom: -0.9 }}>
59
61
  <TextLabel name="prompt" text="Show your hand" fontSize={220} />
60
62
  </ScreenTransform>
@@ -84,6 +86,88 @@ const [sx, sy] = computeContainScale(rectAspect, 1200 / 800); // content w/h rat
84
86
  </mesh>;
85
87
  ```
86
88
 
89
+ ### `<ScreenImage>` — Effect House-style screen-space image object
90
+
91
+ Use this for screen-space image planes such as logos, stickers, frames, badges,
92
+ and photo placeholders. `ScreenImage` owns its `ScreenTransform`; for normal
93
+ authoring prefer `position`, `size`, and `rotation`. `anchors` still exist as
94
+ the lower-level layout escape hatch. If no source is provided, it renders the
95
+ SDK-owned square default image.
96
+
97
+ ```tsx
98
+ import { ScreenImage, ScreenSpaceUI } from "@vincentt-xr/sdk";
99
+
100
+ <ScreenSpaceUI>
101
+ <ScreenImage
102
+ name="defaultSquareImage"
103
+ anchors={{ left: -0.35, right: 0.35, top: 0.35, bottom: -0.35 }}
104
+ fit="contain"
105
+ />
106
+ </ScreenSpaceUI>
107
+
108
+ <ScreenSpaceUI>
109
+ <ScreenImage
110
+ name="logo"
111
+ source={{ kind: "image", src: "https://cdn.../logo.png" }}
112
+ coordinateSpace="canvas"
113
+ canvasSize={{ width: 720, height: 1280 }}
114
+ position={[-180, 464]}
115
+ size={[216, 160]}
116
+ stretchMode="fit"
117
+ opacity={0.95}
118
+ />
119
+ </ScreenSpaceUI>
120
+ ```
121
+
122
+ Props:
123
+
124
+ - `source`: `{ kind: "default" }`, `{ kind: "image", src }`, or `{ kind: "texture", texture }`.
125
+ - `src`: shorthand for `{ kind: "image", src }`.
126
+ - `fit`: `"stretch" | "contain" | "cover"`; default `"contain"`.
127
+ - `coordinateSpace`: `"normalized" | "canvas"`; default `"normalized"`.
128
+ - `canvasSize`: design canvas size for `coordinateSpace="canvas"`; default
129
+ `{ width: 720, height: 1280 }`.
130
+ - `position`, `size`, `rotation`: author-facing screen-object transform.
131
+ In normalized mode, `position` is the rect center in normalized screen space
132
+ and `size` is normalized rect width/height. In canvas mode, `position` and
133
+ `size` are design-canvas pixels with center origin and +y up, matching the
134
+ Effect House 720x1280 Screen Transform convention.
135
+ - `anchors`, `offsets`, `pivot`: forwarded to the owned `ScreenTransform`.
136
+ - `opacity`, `color`, `flipX`, `flipY`, `renderOrder`, `transparent`, `alphaTest`, `overlay`, `visible`.
137
+
138
+ Effect House transfer rule: Screen Transform `Size` is the screen-space
139
+ bounding rectangle, not the image's natural pixel size. The image then fits
140
+ inside that rectangle according to `stretchMode`. Fit/fill/stretch changes the
141
+ texture content inside the rectangle; it must not change the object's rigid
142
+ transform. Rotation rotates the whole rectangle around `pivot`.
143
+
144
+ Settings:
145
+
146
+ - `DEFAULT_SCREEN_IMAGE_SETTINGS.transformation`: coordinate space, canvas size,
147
+ position, size, rotation, pivot, render order, overlay/depth behavior, and visibility.
148
+ - `DEFAULT_SCREEN_IMAGE_SETTINGS.image`: source kind, src, fit, opacity, color,
149
+ flips, transparency, and alpha test.
150
+ - `sourceKind` includes future media categories (`video`, `sequence`, `gif`),
151
+ but the current component implementation supports `default`, static `image`,
152
+ and already-loaded Three `texture`.
153
+
154
+ Use `<ScreenImage>` instead of hand-authoring `<ScreenTransform>` + `<mesh>`
155
+ when the object is conceptually a 2D screen image. Use the lower-level mesh path
156
+ only when you need custom geometry or material behavior.
157
+
158
+ ### Shared debug authoring surface for `ScreenImage`
159
+
160
+ The SDK ships a dev-only inspector at `lib/debug-ui/screen-image-settings` for
161
+ authoring `ScreenImage` settings in creator tooling and the v2 template
162
+ preview. It exposes the image renderer, transform, material, and fit controls
163
+ as one shared panel. End-user shipping surfaces should not include the debug
164
+ chrome.
165
+
166
+ The panel's transform copy button copies a JSON payload for the selected object
167
+ including `name`, `kind: "screenImage"`, `transform`, `image`, `material`, and
168
+ `screenSpaceUI`. The `transform` includes `coordinateSpace` and `canvasSize`.
169
+ Use that payload as the handoff from human layout tuning back to code.
170
+
87
171
  ### `<SpriteAnimation sheet={{ url, cols, rows, frames, fps }}>` — Animated transparent overlay
88
172
 
89
173
  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.
@@ -115,6 +199,39 @@ import { SpriteAnimation, ScreenSpaceUI } from "@vincentt-xr/sdk";
115
199
 
116
200
  Mount the tracker component; it registers the underlying ML model automatically. No `registerXRPipeline` call needed.
117
201
 
202
+ ### 3D face warp — FBX head binding and face proportion deformation
203
+
204
+ Use `faceWarp3D` on `<CanonicalFaceMeshRenderer>` to deform the live canonical
205
+ face mesh before projection. Defaults are no-op; pass `FACE_WARP_3D_PRESET` for
206
+ a visible starter look, or provide partial settings.
207
+
208
+ ```tsx
209
+ import { CanonicalFaceMeshRenderer, FACE_WARP_3D_PRESET } from "@vincentt-xr/sdk/face-effects";
210
+
211
+ <CanonicalFaceMeshRenderer
212
+ materialType="shader"
213
+ shaderPreset="originalFace"
214
+ textureMap="none"
215
+ alphaMap="canonical"
216
+ alphaOpacity={1}
217
+ baseColor="#ffffff"
218
+ opacity={1}
219
+ side="front"
220
+ faceAlphaMaskUrl="https://cdn.vincentt.studio/assets/sdk/face/v1/Canonical_Face_Color_Mask.png"
221
+ faceWarp3D={FACE_WARP_3D_PRESET}
222
+ />;
223
+ ```
224
+
225
+ Settings (`FaceWarp3DSettings`) include `faceWidth`, `faceHeight`,
226
+ `foreheadHeight`, `cheekWidth`, `eyeSize`, `eyeDistance`, `noseSize`,
227
+ `noseLength`, `mouthWidth`, `mouthHeight`, `jawWidth`, `chinHeight`, plus
228
+ `enabled` and `strength`. Setting values are normalized around zero; typical UI
229
+ sliders map `-100..100` to `-1..1`.
230
+
231
+ For local tuning, import `FaceWarp3DSettingsPanel` from
232
+ `@vincentt-xr/sdk/debug-ui`. It is a draggable debug panel and should not
233
+ be included in the final production scene.
234
+
118
235
  ### `<FaceTracker>` — Face tracking (468 landmarks)
119
236
 
120
237
  Detects faces and provides face transform to children via context. Supports `faceIndex` (default 0) and `scaleWithFace`.
@@ -154,6 +271,10 @@ import { HandTracker } from "@vincentt-xr/sdk/tracking";
154
271
  <HandTracker />;
155
272
  ```
156
273
 
274
+ Supports multiple detected hands: `hand` selects `"left"` or `"right"` when that
275
+ hand is available; `handIndex` falls back to positional selection. Existing
276
+ one-hand scenes keep working via the default first-hand selection.
277
+
157
278
  ### `<GestureTracker>` — Named gestures
158
279
 
159
280
  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):
@@ -373,29 +494,126 @@ const tex = useTexture("https://cdn.../badge.webp");
373
494
 
374
495
  ---
375
496
 
497
+ ## `<ScreenText>` — Standard screen-space text
498
+
499
+ Import from `@vincentt-xr/sdk`. Use this when you want the standard
500
+ screen-text lane instead of wiring `ScreenTransform` + `TextLabel` yourself.
501
+ For future Vincentt scenes, prefer `ScreenText` over raw `TextLabel` for end-user
502
+ screen copy such as prompts, captions, banners, badges, and titles.
503
+
504
+ `ScreenText` composes:
505
+
506
+ - full-width screen-space lane presets
507
+ - `verticalAlign: "top" | "center" | "bottom"`
508
+ - `TextLabel` rendering props such as `textAlign`, overflow, background, stroke, and shadow
509
+
510
+ ```tsx
511
+ import { ScreenSpaceUI, ScreenText } from "@vincentt-xr/sdk";
512
+
513
+ <ScreenSpaceUI>
514
+ <ScreenText
515
+ name="headline"
516
+ text="Head-Tilt Trivia"
517
+ coordinateSpace="canvas"
518
+ canvasSize={{ width: 720, height: 1280 }}
519
+ position={[0, 500]}
520
+ size={[620, 140]}
521
+ textAlign="center"
522
+ fontSize={270}
523
+ bgFit="text"
524
+ bgColor="transparent"
525
+ />
526
+ </ScreenSpaceUI>;
527
+ ```
528
+
529
+ `ScreenText` supports the same screen coordinate contract as `ScreenImage`:
530
+ `coordinateSpace="normalized"` uses normalized screen units, while
531
+ `coordinateSpace="canvas"` uses Effect House-style design-canvas pixels
532
+ (`720x1280` by default, center origin, +y up). Its `size` is the text bounding
533
+ rectangle. The text renderer then aligns, shrinks, clips, or backgrounds the
534
+ text inside that rectangle.
535
+
536
+ Use `ScreenText` when:
537
+
538
+ - you want top / center / bottom screen placement
539
+ - you want wide-lane text alignment without hand-authoring anchors
540
+ - you want the default end-user screen text behavior
541
+
542
+ Use `TextLabel` when:
543
+
544
+ - you need raw text rendering outside the screen-text presets
545
+ - you want to place text manually in world space or inside a custom transform
546
+ - you are building a lower-level composite where another component owns layout
547
+
548
+ ### Shared debug authoring surface for `ScreenText`
549
+
550
+ The SDK also ships a dev-only inspector at `lib/debug-ui/screen-text-settings`
551
+ for `ScreenText` authoring. It groups the text renderer, transform, style, and
552
+ background controls into separate sections so creators can inspect the lane
553
+ without shipping the debug chrome in production.
554
+
555
+ The panel's transform copy button copies a JSON payload for the selected object
556
+ including `name`, `kind: "screenText"`, `transform`, `content`, `style`,
557
+ `layout`, and `screenSpaceUI`. The `transform` includes `coordinateSpace` and
558
+ `canvasSize`. Use that payload as the handoff from human layout tuning back to
559
+ code.
560
+
561
+ ### Shared debug layer panel
562
+
563
+ The SDK ships a reusable `LayerPanel` in `lib/debug-ui/layer-panel` for
564
+ Photoshop-style stack management. It is intended for dev / creator tooling
565
+ only and is currently used by the template preview to manage `ScreenImage` and
566
+ `ScreenText` layers. Production shipping surfaces should not include the panel.
567
+
568
+ ---
569
+
376
570
  ## `<TextLabel>` — Canvas-rendered text panel
377
571
 
378
572
  Import from `@vincentt-xr/sdk`. Billboarded in 3D space.
379
573
 
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. |
574
+ | prop | type | default | notes |
575
+ | --------------- | ------------------------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------- |
576
+ | `text` | `string` | **required** | The text to render. |
577
+ | `lines` | `string[]` | — | Optional precomputed lines. Skips automatic word wrapping. |
578
+ | `color` | `string` | `#ffffff` | Text color. |
579
+ | `strokeLayers` | `{ color, width, opacity? }[]` | — | Optional multi-stroke stack, drawn outer-to-inner. |
580
+ | `strokeColor` | `string` | — | Optional text stroke color. Pair with `strokeWidth`. |
581
+ | `strokeWidth` | `number` | `0` | Text stroke thickness in canvas px. |
582
+ | `shadowLayers` | `{ color, blur, offsetX?, offsetY?, opacity? }[]` | — | Optional multi-shadow/glow stack, drawn back-to-front. |
583
+ | `shadowColor` | `string` | — | Optional text shadow / glow color. |
584
+ | `shadowBlur` | `number` | `0` | Shadow blur radius in canvas px. |
585
+ | `shadowOffsetX` | `number` | `0` | Shadow X offset in canvas px. |
586
+ | `shadowOffsetY` | `number` | `0` | Shadow Y offset in canvas px. |
587
+ | `fontSize` | `number` | `48` | Canvas px at the default 512px canvas — NOT world units. Chips/badges typically need 200-320. |
588
+ | `textAlign` | `"left" \| "center" \| "right"` | `"center"` | Horizontal text alignment inside the available lane. |
589
+ | `overflow` | `"clip" \| "clamp" \| "ellipsis" \| "shrink"` | — | Overflow behavior. `clamp` keeps full visible lines, `clip` allows canvas-edge clipping. |
590
+ | `resizeToFit` | `boolean` | — | Keep shrinking until the text fits or `minFontSize` is reached. |
591
+ | `minFontSize` | `number` | `12` | Minimum shrink target in canvas px. |
592
+ | `bgColor` | `string` | `rgba(20,40,110,0.88)` | Panel background; use `"transparent"` for text-only. |
593
+ | `bgFit` | `"full" \| "text"` | `"full"` | Background fit mode. Use `"text"` for pill/capsule backgrounds that hug the text block. |
594
+ | `borderColor` | `string` | — | Border color; omit for no border. |
595
+ | `borderRadius` | `number` | `24` | Corner radius; 0 for square. |
596
+ | `aspect` | `number` | `2.5` | Panel shape (width ÷ height). >1 = wide/banner, <1 = tall. |
597
+ | `scale` | `[x,y,z] \| number` | `1` | World size. Combine with `aspect` to make wide banners. |
598
+ | `position` | `[x,y,z]` | `[0,0,0]` | World position. +Y up, -Y down. |
599
+ | `name` | `string` | — | Set on every element you create. |
392
600
 
393
601
  **Gotchas:**
394
602
 
395
603
  - `fontSize` is canvas px, not world units. Chips/badges need 200-320, not 16-48.
396
604
  - `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.
605
+ - `textAlign` affects both glyph placement and `bgFit="text"` background placement.
606
+ - `TextLabel` respects the `visible` prop.
607
+ - For text-first 2D treatments, prefer `strokeColor` + `strokeWidth` and `shadowColor` + `shadowBlur` before stacking multiple `TextLabel`s. Native text effects keep the glyph edges aligned and resize cleanly.
608
+ - Use `strokeLayers` / `shadowLayers` when you need richer looks like sticker text, double-outline, or offset-drop-shadow. The single `stroke*` / `shadow*` props are shorthand for one layer and can coexist with the arrays.
609
+ - `TextLabel` now owns real overflow behavior. If you need exact authored line breaks, pass `text` with `\n` or provide `lines` directly; those lines will be rendered as the source layout instead of being re-wrapped from one flattened string.
610
+ - Overflow semantics:
611
+ - `clip`: draw all resolved lines and let the canvas bounds crop them, including partial last lines.
612
+ - `clamp`: keep only full visible lines within the line budget.
613
+ - `ellipsis`: same as clamp, but ellipsize the last visible line.
614
+ - `shrink`: reduce font size until the content fits or `minFontSize` is reached, then clamp if it still overflows.
398
615
  - **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.)
616
+ - If you want the SDK to own that anchor math, use `<ScreenText>` instead of pairing `<ScreenTransform>` with `<TextLabel>` manually.
399
617
 
400
618
  ```tsx
401
619
  // Full-width banner pinned to the top, INSIDE a ScreenTransform.
@@ -1 +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;
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-BjyhBP8i.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"}),B={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 D({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:B,children:["Press ",j.toUpperCase()," to toggle"]})]})]})}),p)}exports.DebuggerUI=D;
@@ -1,7 +1,7 @@
1
1
  import { jsx as e, jsxs as r, Fragment as f } from "react/jsx-runtime";
2
2
  import { useState as P, useEffect as M } from "react";
3
3
  import { createPortal as F } from "react-dom";
4
- import { f as i } from "./internal-KOKJDxCG.js";
4
+ import { f as i } from "./internal-Cae1ps15.js";
5
5
  const z = {
6
6
  position: "fixed",
7
7
  top: "10px",
@@ -0,0 +1,317 @@
1
+ import { l as z } from "./index-CY3bUAcW.js";
2
+ import { jsx as o, jsxs as l } from "react/jsx-runtime";
3
+ import { createPortal as L } from "react-dom";
4
+ import { useState as p, useRef as R, useMemo as W, useEffect as b } from "react";
5
+ import { c as i, X as $ } from "./x-Co8jPhP0.js";
6
+ const w = (t, r) => new URL(`/assets/sdk/debug-ui/v1/${t}/${r}`, z.assetStorageEndpoint).href, v = (t) => t.replace(/\.[^.]+$/, "").replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim().replace(/\b\w/g, (r) => r.toUpperCase()), E = [
7
+ "person_fullbody.mp4",
8
+ "person_looking.mp4",
9
+ "person_shaking_head.mp4",
10
+ "person_thumbUp.mp4",
11
+ "two_people_smiling.mp4"
12
+ ], D = [
13
+ "person_fist_sign.jpg",
14
+ "person_fullbody.jpg",
15
+ "person_peace_sign.jpg",
16
+ "person_portrait.jpg",
17
+ "person_side_portrait.jpg",
18
+ "three_people_smiling.jpg",
19
+ "two_people_smiling.jpg"
20
+ ], N = (t, r) => ({
21
+ id: `sdk-video-${r + 1}`,
22
+ kind: "video",
23
+ label: v(t),
24
+ url: w("videos", t)
25
+ }), P = (t, r) => ({
26
+ id: `sdk-image-${r + 1}`,
27
+ kind: "image",
28
+ label: v(t),
29
+ url: w("images", t)
30
+ }), V = E.map(N), U = D.map(P), he = [
31
+ { id: "webcam", kind: "webcam", label: "Webcam" },
32
+ ...V,
33
+ ...U
34
+ ];
35
+ /**
36
+ * @license lucide-react v1.17.0 - ISC
37
+ *
38
+ * This source code is licensed under the ISC license.
39
+ * See the LICENSE file in the root directory of this source tree.
40
+ */
41
+ const F = [
42
+ [
43
+ "path",
44
+ {
45
+ d: "M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z",
46
+ key: "18u6gg"
47
+ }
48
+ ],
49
+ ["circle", { cx: "12", cy: "13", r: "3", key: "1vg3eu" }]
50
+ ], B = i("camera", F);
51
+ /**
52
+ * @license lucide-react v1.17.0 - ISC
53
+ *
54
+ * This source code is licensed under the ISC license.
55
+ * See the LICENSE file in the root directory of this source tree.
56
+ */
57
+ const K = [["path", { d: "m6 9 6 6 6-6", key: "qrunsl" }]], O = i("chevron-down", K);
58
+ /**
59
+ * @license lucide-react v1.17.0 - ISC
60
+ *
61
+ * This source code is licensed under the ISC license.
62
+ * See the LICENSE file in the root directory of this source tree.
63
+ */
64
+ const X = [
65
+ ["circle", { cx: "9", cy: "12", r: "1", key: "1vctgf" }],
66
+ ["circle", { cx: "9", cy: "5", r: "1", key: "hp0tcf" }],
67
+ ["circle", { cx: "9", cy: "19", r: "1", key: "fkjjf6" }],
68
+ ["circle", { cx: "15", cy: "12", r: "1", key: "1tmaij" }],
69
+ ["circle", { cx: "15", cy: "5", r: "1", key: "19l28e" }],
70
+ ["circle", { cx: "15", cy: "19", r: "1", key: "f4zoj3" }]
71
+ ], A = i("grip-vertical", X);
72
+ /**
73
+ * @license lucide-react v1.17.0 - ISC
74
+ *
75
+ * This source code is licensed under the ISC license.
76
+ * See the LICENSE file in the root directory of this source tree.
77
+ */
78
+ const G = [
79
+ ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2", key: "1m3agn" }],
80
+ ["circle", { cx: "9", cy: "9", r: "2", key: "af1f0g" }],
81
+ ["path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21", key: "1xmnt7" }]
82
+ ], H = i("image", G);
83
+ /**
84
+ * @license lucide-react v1.17.0 - ISC
85
+ *
86
+ * This source code is licensed under the ISC license.
87
+ * See the LICENSE file in the root directory of this source tree.
88
+ */
89
+ const T = [
90
+ ["path", { d: "M4 5h16", key: "1tepv9" }],
91
+ ["path", { d: "M4 12h16", key: "1lakjw" }],
92
+ ["path", { d: "M4 19h16", key: "1djgab" }]
93
+ ], Y = i("menu", T);
94
+ /**
95
+ * @license lucide-react v1.17.0 - ISC
96
+ *
97
+ * This source code is licensed under the ISC license.
98
+ * See the LICENSE file in the root directory of this source tree.
99
+ */
100
+ const q = [
101
+ [
102
+ "path",
103
+ {
104
+ d: "m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.752-.432L16 10.5",
105
+ key: "ftymec"
106
+ }
107
+ ],
108
+ ["rect", { x: "2", y: "6", width: "14", height: "12", rx: "2", key: "158x01" }]
109
+ ], J = i("video", q), Q = (t) => t === "webcam" ? "📷 " : t === "image" ? "🖼 " : "▶ ", Z = {
110
+ position: "fixed",
111
+ zIndex: 2e4,
112
+ pointerEvents: "none"
113
+ }, _ = {
114
+ pointerEvents: "auto",
115
+ border: "1px solid rgba(255, 255, 255, 0.12)",
116
+ background: "rgba(8, 15, 28, 0.58)",
117
+ boxShadow: "0 12px 32px rgba(0, 0, 0, 0.28)",
118
+ backdropFilter: "blur(20px) saturate(1.15)",
119
+ WebkitBackdropFilter: "blur(20px) saturate(1.15)",
120
+ color: "#f8fafc",
121
+ display: "flex"
122
+ }, ee = {
123
+ ..._,
124
+ width: 34,
125
+ height: 34,
126
+ borderRadius: 17,
127
+ alignItems: "center",
128
+ justifyContent: "center",
129
+ cursor: "pointer",
130
+ transition: "transform 150ms ease"
131
+ }, te = {
132
+ ..._,
133
+ flexDirection: "column",
134
+ borderRadius: 12,
135
+ overflow: "hidden",
136
+ padding: 4,
137
+ gap: 4
138
+ }, oe = {
139
+ display: "flex",
140
+ alignItems: "center",
141
+ gap: 3
142
+ }, ne = {
143
+ width: 1,
144
+ height: 12,
145
+ background: "rgba(255, 255, 255, 0.12)",
146
+ margin: "0 3px"
147
+ }, re = {
148
+ display: "flex",
149
+ width: 16,
150
+ height: 24,
151
+ alignItems: "center",
152
+ justifyContent: "center",
153
+ cursor: "grab",
154
+ color: "rgba(148, 163, 184, 0.8)",
155
+ flex: "0 0 auto"
156
+ }, x = (t = !1) => ({
157
+ display: "flex",
158
+ width: 24,
159
+ height: 24,
160
+ alignItems: "center",
161
+ justifyContent: "center",
162
+ borderRadius: 8,
163
+ border: `1px solid ${t ? "rgba(255, 255, 255, 0.12)" : "transparent"}`,
164
+ background: t ? "rgba(255, 255, 255, 0.08)" : "transparent",
165
+ color: t ? "#f8fafc" : "rgba(226, 232, 240, 0.72)",
166
+ cursor: "pointer",
167
+ flex: "0 0 auto",
168
+ transition: "background 120ms ease, color 120ms ease"
169
+ }), ie = {
170
+ position: "relative",
171
+ display: "flex",
172
+ alignItems: "center"
173
+ }, se = {
174
+ width: "100%",
175
+ minWidth: 140,
176
+ height: 24,
177
+ borderRadius: 8,
178
+ border: "1px solid rgba(255, 255, 255, 0.10)",
179
+ background: "rgba(15, 23, 42, 0.65)",
180
+ color: "#f8fafc",
181
+ fontSize: 11,
182
+ outline: "none",
183
+ paddingLeft: 7,
184
+ paddingRight: 20,
185
+ appearance: "none",
186
+ cursor: "pointer"
187
+ }, ae = {
188
+ pointerEvents: "none",
189
+ position: "absolute",
190
+ right: 5,
191
+ top: 0,
192
+ bottom: 0,
193
+ display: "flex",
194
+ alignItems: "center",
195
+ color: "rgba(148, 163, 184, 0.9)"
196
+ }, ce = {
197
+ height: 24,
198
+ display: "flex",
199
+ alignItems: "center",
200
+ padding: "0 8px",
201
+ borderRadius: 8,
202
+ border: "1px solid rgba(255, 255, 255, 0.08)",
203
+ background: "rgba(255, 255, 255, 0.04)",
204
+ color: "rgba(226, 232, 240, 0.72)",
205
+ fontSize: 11
206
+ }, k = { width: 14, height: 14 }, me = ({
207
+ value: t,
208
+ onChange: r,
209
+ sources: s,
210
+ style: S
211
+ }) => {
212
+ const [M, g] = p(!1), [u, j] = p({ x: 16, y: 16 }), [a, h] = p(t.kind), c = R(null), m = W(
213
+ () => ({
214
+ webcam: s.filter((e) => e.kind === "webcam"),
215
+ image: s.filter((e) => e.kind === "image"),
216
+ video: s.filter((e) => e.kind === "video")
217
+ }),
218
+ [s]
219
+ ), y = m[a];
220
+ b(() => {
221
+ h(t.kind);
222
+ }, [t.kind]);
223
+ const C = (e) => {
224
+ h(e);
225
+ const n = m[e][0];
226
+ n && r(n);
227
+ };
228
+ b(() => {
229
+ const e = (d) => {
230
+ c.current && j({
231
+ x: Math.max(8, d.clientX - c.current.x),
232
+ y: Math.max(8, d.clientY - c.current.y)
233
+ });
234
+ }, n = () => {
235
+ c.current = null, document.body.classList.remove("select-none");
236
+ };
237
+ return window.addEventListener("pointermove", e), window.addEventListener("pointerup", n), () => {
238
+ window.removeEventListener("pointermove", e), window.removeEventListener("pointerup", n);
239
+ };
240
+ }, []);
241
+ const f = (e) => {
242
+ const n = e.currentTarget.getBoundingClientRect();
243
+ c.current = {
244
+ x: e.clientX - n.left,
245
+ y: e.clientY - n.top
246
+ }, document.body.classList.add("select-none"), e.currentTarget.setPointerCapture(e.pointerId);
247
+ }, I = () => y.length === 0 ? /* @__PURE__ */ o("div", { style: ce, children: a === "webcam" ? "No camera source" : a === "image" ? "No image preview" : "No video preview" }) : /* @__PURE__ */ l("div", { style: ie, children: [
248
+ /* @__PURE__ */ o(
249
+ "select",
250
+ {
251
+ value: t.id,
252
+ onChange: (e) => {
253
+ const n = s.find((d) => d.id === e.target.value);
254
+ n && r(n);
255
+ },
256
+ style: se,
257
+ children: y.map((e) => /* @__PURE__ */ l("option", { value: e.id, children: [
258
+ Q(a),
259
+ e.label
260
+ ] }, e.id))
261
+ }
262
+ ),
263
+ /* @__PURE__ */ o("div", { style: ae, children: /* @__PURE__ */ o("span", { style: { width: 10, height: 10 }, children: /* @__PURE__ */ o(O, { size: 10, strokeWidth: 1.8 }) }) })
264
+ ] });
265
+ return typeof document > "u" ? null : L(
266
+ /* @__PURE__ */ o("div", { style: { ...Z, left: u.x, top: u.y, ...S }, children: M ? (
267
+ // --- Expanded State ---
268
+ /* @__PURE__ */ l("div", { style: te, children: [
269
+ /* @__PURE__ */ l("div", { style: oe, children: [
270
+ /* @__PURE__ */ o("div", { onPointerDown: f, style: re, title: "Drag to move", children: /* @__PURE__ */ o(A, { size: 11, strokeWidth: 2 }) }),
271
+ /* @__PURE__ */ o(
272
+ "button",
273
+ {
274
+ type: "button",
275
+ onClick: () => g(!1),
276
+ style: x(!1),
277
+ title: "Close Menu",
278
+ children: /* @__PURE__ */ o("span", { style: k, children: /* @__PURE__ */ o($, { size: 14, strokeWidth: 1.8 }) })
279
+ }
280
+ ),
281
+ /* @__PURE__ */ o("div", { style: ne }),
282
+ ["webcam", "image", "video"].map((e) => /* @__PURE__ */ o(
283
+ "button",
284
+ {
285
+ type: "button",
286
+ onClick: () => C(e),
287
+ style: x(a === e),
288
+ title: e.charAt(0).toUpperCase() + e.slice(1),
289
+ children: /* @__PURE__ */ o("span", { style: k, children: e === "webcam" ? /* @__PURE__ */ o(B, { size: 14, strokeWidth: 1.8 }) : e === "image" ? /* @__PURE__ */ o(H, { size: 14, strokeWidth: 1.8 }) : /* @__PURE__ */ o(J, { size: 14, strokeWidth: 1.8 }) })
290
+ },
291
+ e
292
+ ))
293
+ ] }),
294
+ I()
295
+ ] })
296
+ ) : (
297
+ // --- Collapsed State (Minimal FAB) ---
298
+ /* @__PURE__ */ o(
299
+ "div",
300
+ {
301
+ style: ee,
302
+ onClick: () => g(!0),
303
+ onPointerDown: f,
304
+ title: "Open Media Menu",
305
+ children: /* @__PURE__ */ o("span", { style: { width: 16, height: 16 }, children: /* @__PURE__ */ o(Y, { size: 14, strokeWidth: 1.8 }) })
306
+ }
307
+ )
308
+ ) }),
309
+ document.body
310
+ );
311
+ };
312
+ export {
313
+ me as M,
314
+ V as a,
315
+ he as d,
316
+ U as s
317
+ };