@ericchen1990/pano-view 3.9.0 → 4.0.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.
package/dist/index.mjs CHANGED
@@ -1284,8 +1284,8 @@ var require_howler = __commonJS({
1284
1284
  break;
1285
1285
  }
1286
1286
  }
1287
- if (cache && remCache) {
1288
- delete cache[self._src];
1287
+ if (cache2 && remCache) {
1288
+ delete cache2[self._src];
1289
1289
  }
1290
1290
  Howler4.noAudio = false;
1291
1291
  self._state = "unloaded";
@@ -1540,7 +1540,7 @@ var require_howler = __commonJS({
1540
1540
  _refreshBuffer: function(sound) {
1541
1541
  var self = this;
1542
1542
  sound._node.bufferSource = Howler4.ctx.createBufferSource();
1543
- sound._node.bufferSource.buffer = cache[self._src];
1543
+ sound._node.bufferSource.buffer = cache2[self._src];
1544
1544
  if (sound._panner) {
1545
1545
  sound._node.bufferSource.connect(sound._panner);
1546
1546
  } else {
@@ -1702,11 +1702,11 @@ var require_howler = __commonJS({
1702
1702
  self._node.removeEventListener("ended", self._endFn, false);
1703
1703
  }
1704
1704
  };
1705
- var cache = {};
1705
+ var cache2 = {};
1706
1706
  var loadBuffer = function(self) {
1707
1707
  var url = self._src;
1708
- if (cache[url]) {
1709
- self._duration = cache[url].duration;
1708
+ if (cache2[url]) {
1709
+ self._duration = cache2[url].duration;
1710
1710
  loadSound(self);
1711
1711
  return;
1712
1712
  }
@@ -1740,7 +1740,7 @@ var require_howler = __commonJS({
1740
1740
  self._html5 = true;
1741
1741
  self._webAudio = false;
1742
1742
  self._sounds = [];
1743
- delete cache[url];
1743
+ delete cache2[url];
1744
1744
  self.load();
1745
1745
  }
1746
1746
  };
@@ -1760,7 +1760,7 @@ var require_howler = __commonJS({
1760
1760
  };
1761
1761
  var success = function(buffer) {
1762
1762
  if (buffer && self._sounds.length > 0) {
1763
- cache[self._src] = buffer;
1763
+ cache2[self._src] = buffer;
1764
1764
  loadSound(self, buffer);
1765
1765
  } else {
1766
1766
  error();
@@ -2308,12 +2308,12 @@ import { Canvas } from "@react-three/fiber";
2308
2308
  import { createXRStore, XR } from "@react-three/xr";
2309
2309
  import {
2310
2310
  forwardRef as forwardRef2,
2311
- useCallback as useCallback6,
2311
+ useCallback as useCallback7,
2312
2312
  useContext as useContext16,
2313
2313
  useEffect as useEffect14,
2314
2314
  useImperativeHandle as useImperativeHandle2,
2315
2315
  useMemo as useMemo8,
2316
- useRef as useRef13,
2316
+ useRef as useRef14,
2317
2317
  useState as useState7,
2318
2318
  useSyncExternalStore as useSyncExternalStore8
2319
2319
  } from "react";
@@ -3875,6 +3875,18 @@ import { useSyncExternalStore } from "react";
3875
3875
  // src/webvr/host.ts
3876
3876
  import { createContext as createContext5 } from "react";
3877
3877
  var WebVRRuntimeContext = createContext5(null);
3878
+ var EMPTY_WEBVR_STATE = Object.freeze({
3879
+ available: false,
3880
+ enabled: false,
3881
+ mode: null
3882
+ });
3883
+ function createWebVRState(available, mode) {
3884
+ return Object.freeze({
3885
+ available,
3886
+ enabled: mode !== null,
3887
+ mode
3888
+ });
3889
+ }
3878
3890
  function createWebVRHost() {
3879
3891
  return {
3880
3892
  controller: null,
@@ -3884,18 +3896,28 @@ function createWebVRHost() {
3884
3896
  chrome: true,
3885
3897
  mode: null,
3886
3898
  setupOpen: false,
3887
- settings: { screensize: "auto", profileId: "cardboard-v1" }
3899
+ settings: { screensize: "auto", profileId: "cardboard-v1" },
3900
+ detectedScreensize: null
3888
3901
  },
3902
+ state: EMPTY_WEBVR_STATE,
3889
3903
  listeners: /* @__PURE__ */ new Set()
3890
3904
  };
3891
3905
  }
3892
3906
  function updateWebVRHost(host, snapshot) {
3893
- host.snapshot = { ...host.snapshot, ...snapshot };
3907
+ const previous = host.snapshot;
3908
+ const next = { ...previous, ...snapshot };
3909
+ host.snapshot = next;
3894
3910
  host.revision += 1;
3911
+ if (previous.available !== next.available || previous.mode !== next.mode) {
3912
+ host.state = createWebVRState(next.available, next.mode);
3913
+ }
3895
3914
  for (const listener of host.listeners) {
3896
3915
  listener();
3897
3916
  }
3898
3917
  }
3918
+ function getWebVRState(host) {
3919
+ return host.state;
3920
+ }
3899
3921
  function subscribeWebVRHost(host, listener) {
3900
3922
  host.listeners.add(listener);
3901
3923
  return () => {
@@ -6151,6 +6173,52 @@ function createPanoModalHost() {
6151
6173
  };
6152
6174
  }
6153
6175
 
6176
+ // src/overlay/partition.ts
6177
+ import {
6178
+ Children,
6179
+ Fragment,
6180
+ isValidElement
6181
+ } from "react";
6182
+ var PANO_OVERLAY_TYPE = /* @__PURE__ */ Symbol.for("pano-view.PanoOverlay");
6183
+ function isPanoOverlayType(type) {
6184
+ return typeof type === "function" && type[PANO_OVERLAY_TYPE] === true;
6185
+ }
6186
+ function isPanoOverlayElement(node) {
6187
+ return isValidElement(node) && isPanoOverlayType(node.type);
6188
+ }
6189
+ function isFragmentElement(node) {
6190
+ return isValidElement(node) && node.type === Fragment;
6191
+ }
6192
+ function flattenViewerChildren(children) {
6193
+ const flat = [];
6194
+ for (const item of Children.toArray(children)) {
6195
+ if (isFragmentElement(item)) {
6196
+ flat.push(...flattenViewerChildren(item.props.children));
6197
+ } else {
6198
+ flat.push(item);
6199
+ }
6200
+ }
6201
+ return flat;
6202
+ }
6203
+ function partitionPanoOverlay(children) {
6204
+ const items = flattenViewerChildren(children);
6205
+ const overlayElements = [];
6206
+ const sceneItems = [];
6207
+ let found = false;
6208
+ for (const item of items) {
6209
+ if (isPanoOverlayElement(item)) {
6210
+ found = true;
6211
+ overlayElements.push(item);
6212
+ } else {
6213
+ sceneItems.push(item);
6214
+ }
6215
+ }
6216
+ if (!found) {
6217
+ return { sceneChildren: children, overlayElements: [] };
6218
+ }
6219
+ return { sceneChildren: sceneItems, overlayElements };
6220
+ }
6221
+
6154
6222
  // src/overlay/pano-overlay.tsx
6155
6223
  import {
6156
6224
  createContext as createContext12,
@@ -6170,17 +6238,33 @@ function getOverlayVisibleRect(rect, viewport) {
6170
6238
  }
6171
6239
 
6172
6240
  // src/overlay/pano-overlay.tsx
6173
- import { jsx as jsx5 } from "react/jsx-runtime";
6241
+ import { Fragment as Fragment2, jsx as jsx5 } from "react/jsx-runtime";
6174
6242
  var PanoOverlayContext = createContext12(null);
6175
6243
  var PanoModalHostContext = createContext12(null);
6244
+ var PanoOverlayOutletContext = createContext12(false);
6245
+ function PanoOverlay({ children }) {
6246
+ const inOutlet = useContext11(PanoOverlayOutletContext);
6247
+ if (!inOutlet) {
6248
+ throw new Error(
6249
+ "PanoOverlay must be a direct child of PanoViewer (or of a Fragment under PanoViewer), not nested inside scene components."
6250
+ );
6251
+ }
6252
+ return /* @__PURE__ */ jsx5(Fragment2, { children });
6253
+ }
6254
+ PanoOverlay[PANO_OVERLAY_TYPE] = true;
6255
+ var OVERLAY_HOOK_SCOPE = "PanoOverlay or PanoViewer.renderOverlay";
6176
6256
  function usePanoOverlay() {
6177
6257
  const context = useContext11(PanoOverlayContext);
6178
- if (!context) throw new Error("usePanoOverlay must be used inside PanoViewer.renderOverlay.");
6258
+ if (!context) {
6259
+ throw new Error(`usePanoOverlay must be used inside ${OVERLAY_HOOK_SCOPE}.`);
6260
+ }
6179
6261
  return context;
6180
6262
  }
6181
6263
  function usePanoModal(open) {
6182
6264
  const host = useContext11(PanoModalHostContext);
6183
- if (!host) throw new Error("usePanoModal must be used inside PanoViewer.renderOverlay.");
6265
+ if (!host) {
6266
+ throw new Error(`usePanoModal must be used inside ${OVERLAY_HOOK_SCOPE}.`);
6267
+ }
6184
6268
  useLayoutEffect3(() => open ? host.acquire() : void 0, [host, open]);
6185
6269
  }
6186
6270
  function PanoOverlayPortalProvider({
@@ -6931,7 +7015,7 @@ function cueText(cue) {
6931
7015
  }
6932
7016
 
6933
7017
  // src/video/pano-video-controls.tsx
6934
- import { Fragment, jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
7018
+ import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
6935
7019
  var DEFAULT_PANO_VIDEO_CONTROLS_APPEARANCE = {
6936
7020
  background: "rgba(16, 16, 16, 0.88)",
6937
7021
  color: "#f4f4f4",
@@ -7165,7 +7249,7 @@ function CaptionsMenu({
7165
7249
  id,
7166
7250
  labelledBy,
7167
7251
  role: "menu",
7168
- children: panel === "tracks" ? /* @__PURE__ */ jsxs4(Fragment, { children: [
7252
+ children: panel === "tracks" ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
7169
7253
  /* @__PURE__ */ jsxs4("div", { className: "pano-video-chrome-menu-list", children: [
7170
7254
  /* @__PURE__ */ jsx9(
7171
7255
  ChromeMenuRadioItem,
@@ -7204,7 +7288,7 @@ function CaptionsMenu({
7204
7288
  secondary: /* @__PURE__ */ jsx9("span", { "aria-hidden": true, children: "\u203A" })
7205
7289
  }
7206
7290
  )
7207
- ] }) : /* @__PURE__ */ jsxs4(Fragment, { children: [
7291
+ ] }) : /* @__PURE__ */ jsxs4(Fragment3, { children: [
7208
7292
  /* @__PURE__ */ jsx9(
7209
7293
  ChromeMenuActionItem,
7210
7294
  {
@@ -7870,7 +7954,7 @@ function PanoVideoControlsHud({
7870
7954
  children: /* @__PURE__ */ jsx9(Icon, { path: "M3 8a1 1 0 1 0 2 0 1 1 0 1 0-2 0M7 8a1 1 0 1 0 2 0 1 1 0 1 0-2 0M11 8a1 1 0 1 0 2 0 1 1 0 1 0-2 0" })
7871
7955
  }
7872
7956
  )
7873
- ] }) : /* @__PURE__ */ jsxs4(Fragment, { children: [
7957
+ ] }) : /* @__PURE__ */ jsxs4(Fragment3, { children: [
7874
7958
  rateButton,
7875
7959
  variantButton,
7876
7960
  captionsButton
@@ -7900,7 +7984,7 @@ function PanoVideoControls({ appearance }) {
7900
7984
  }
7901
7985
 
7902
7986
  // src/video/pano-video-chrome-bridge.tsx
7903
- import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
7987
+ import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
7904
7988
  function PanoVideoChromeBridge({
7905
7989
  fullscreen,
7906
7990
  host,
@@ -7915,7 +7999,7 @@ function PanoVideoChromeBridge({
7915
7999
  return null;
7916
8000
  }
7917
8001
  const showDefaultControls = host.controller !== null && host.controls !== false && host.controlClaims === 0;
7918
- return /* @__PURE__ */ jsxs5(Fragment2, { children: [
8002
+ return /* @__PURE__ */ jsxs5(Fragment4, { children: [
7919
8003
  showDefaultControls ? /* @__PURE__ */ jsx10(
7920
8004
  PanoVideoControlsHud,
7921
8005
  {
@@ -7930,7 +8014,13 @@ function PanoVideoChromeBridge({
7930
8014
  }
7931
8015
 
7932
8016
  // src/webvr/chrome.tsx
7933
- import { useEffect as useEffect13, useState as useState6, useSyncExternalStore as useSyncExternalStore7 } from "react";
8017
+ import {
8018
+ useCallback as useCallback6,
8019
+ useEffect as useEffect13,
8020
+ useRef as useRef12,
8021
+ useState as useState6,
8022
+ useSyncExternalStore as useSyncExternalStore7
8023
+ } from "react";
7934
8024
 
7935
8025
  // src/webvr/appearance.ts
7936
8026
  var DEFAULT_WEBVR_CHROME_APPEARANCE = {
@@ -7996,6 +8086,7 @@ var WEBVR_PROFILES = {
7996
8086
  fov: 80,
7997
8087
  ipdMm: 60,
7998
8088
  screenToLensMm: 42,
8089
+ trayDistanceMm: 0,
7999
8090
  k1: 0,
8000
8091
  k2: 0
8001
8092
  },
@@ -8005,6 +8096,7 @@ var WEBVR_PROFILES = {
8005
8096
  fov: 80,
8006
8097
  ipdMm: 60,
8007
8098
  screenToLensMm: 42,
8099
+ trayDistanceMm: 35,
8008
8100
  k1: 0.441,
8009
8101
  k2: 0.156
8010
8102
  },
@@ -8014,6 +8106,7 @@ var WEBVR_PROFILES = {
8014
8106
  fov: 90,
8015
8107
  ipdMm: 64,
8016
8108
  screenToLensMm: 39,
8109
+ trayDistanceMm: 0,
8017
8110
  k1: 0.336,
8018
8111
  k2: 0.553
8019
8112
  },
@@ -8023,6 +8116,7 @@ var WEBVR_PROFILES = {
8023
8116
  fov: 96,
8024
8117
  ipdMm: 63.5,
8025
8118
  screenToLensMm: 50,
8119
+ trayDistanceMm: 0,
8026
8120
  k1: 0.215,
8027
8121
  k2: 0.215
8028
8122
  },
@@ -8032,6 +8126,7 @@ var WEBVR_PROFILES = {
8032
8126
  fov: 90,
8033
8127
  ipdMm: 61.5,
8034
8128
  screenToLensMm: 44,
8129
+ trayDistanceMm: 0,
8035
8130
  k1: 0.385,
8036
8131
  k2: 0.593
8037
8132
  }
@@ -8082,12 +8177,28 @@ function saveWebVRSettings(settings) {
8082
8177
  } catch {
8083
8178
  }
8084
8179
  }
8180
+ function clearWebVRSettings() {
8181
+ if (typeof localStorage === "undefined") {
8182
+ return;
8183
+ }
8184
+ try {
8185
+ localStorage.removeItem(STORAGE_KEY);
8186
+ } catch {
8187
+ }
8188
+ }
8189
+ function resolveInitialWebVRSettings(screensize, profile) {
8190
+ return {
8191
+ screensize: screensize ?? DEFAULT_WEBVR_SETTINGS.screensize,
8192
+ profileId: typeof profile === "string" ? profile : DEFAULT_WEBVR_SETTINGS.profileId
8193
+ };
8194
+ }
8085
8195
  function clamp2(value, min, max) {
8086
8196
  return Math.max(min, Math.min(max, Number.isFinite(value) ? value : min));
8087
8197
  }
8088
8198
 
8089
8199
  // src/webvr/chrome.tsx
8090
- import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
8200
+ import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
8201
+ var SESSION_CHROME_IDLE_MS = 3e3;
8091
8202
  function resolveAppearance(value) {
8092
8203
  return {
8093
8204
  ...DEFAULT_WEBVR_CHROME_APPEARANCE,
@@ -8115,8 +8226,15 @@ function ChromeButton2({
8115
8226
  }
8116
8227
  );
8117
8228
  }
8229
+ function formatScreensizeLabel(settings, detectedScreensize) {
8230
+ if (settings.screensize === "auto") {
8231
+ return detectedScreensize === null ? "Auto" : `Auto \xB7 ${detectedScreensize.toFixed(1)}`;
8232
+ }
8233
+ return settings.screensize.toFixed(1);
8234
+ }
8118
8235
  function SetupPanel({
8119
8236
  appearance,
8237
+ detectedScreensize,
8120
8238
  host,
8121
8239
  settings
8122
8240
  }) {
@@ -8160,7 +8278,7 @@ function SetupPanel({
8160
8278
  children: "\u2039"
8161
8279
  }
8162
8280
  ),
8163
- /* @__PURE__ */ jsx11("strong", { className: "pano-webvr-setup-value", children: settings.screensize === "auto" ? "Auto" : settings.screensize.toFixed(1) }),
8281
+ /* @__PURE__ */ jsx11("strong", { className: "pano-webvr-setup-value", children: formatScreensizeLabel(settings, detectedScreensize) }),
8164
8282
  /* @__PURE__ */ jsx11(
8165
8283
  "button",
8166
8284
  {
@@ -8201,42 +8319,61 @@ function SetupPanel({
8201
8319
  )
8202
8320
  ] })
8203
8321
  ] }),
8322
+ /* @__PURE__ */ jsxs6("div", { className: "pano-webvr-setup-row", children: [
8323
+ /* @__PURE__ */ jsx11(
8324
+ ChromeButton2,
8325
+ {
8326
+ appearance,
8327
+ onClick: () => controller?.resetSettings(),
8328
+ children: "Reset"
8329
+ }
8330
+ ),
8331
+ /* @__PURE__ */ jsx11(
8332
+ ChromeButton2,
8333
+ {
8334
+ appearance,
8335
+ onClick: () => controller?.closeSetup(),
8336
+ children: "Close"
8337
+ }
8338
+ )
8339
+ ] })
8340
+ ]
8341
+ }
8342
+ );
8343
+ }
8344
+ function SessionActions({
8345
+ appearance,
8346
+ host,
8347
+ mode,
8348
+ visible
8349
+ }) {
8350
+ return /* @__PURE__ */ jsxs6(
8351
+ "div",
8352
+ {
8353
+ "aria-hidden": !visible,
8354
+ className: visible ? "pano-webvr-toolbar-actions" : "pano-webvr-toolbar-actions pano-webvr-toolbar-actions-hidden",
8355
+ inert: !visible ? true : void 0,
8356
+ children: [
8357
+ mode !== "webxr" ? /* @__PURE__ */ jsx11(
8358
+ ChromeButton2,
8359
+ {
8360
+ appearance,
8361
+ onClick: () => host.controller?.openSetup(),
8362
+ children: "VR Setup"
8363
+ }
8364
+ ) : null,
8204
8365
  /* @__PURE__ */ jsx11(
8205
8366
  ChromeButton2,
8206
8367
  {
8207
8368
  appearance,
8208
- onClick: () => controller?.closeSetup(),
8209
- children: "Close"
8369
+ onClick: () => void host.controller?.exitVR(),
8370
+ children: "Exit VR"
8210
8371
  }
8211
8372
  )
8212
8373
  ]
8213
8374
  }
8214
8375
  );
8215
8376
  }
8216
- function SessionActions({
8217
- appearance,
8218
- host,
8219
- mode
8220
- }) {
8221
- return /* @__PURE__ */ jsxs6("div", { className: "pano-webvr-toolbar-actions", children: [
8222
- mode !== "webxr" ? /* @__PURE__ */ jsx11(
8223
- ChromeButton2,
8224
- {
8225
- appearance,
8226
- onClick: () => host.controller?.openSetup(),
8227
- children: "VR Setup"
8228
- }
8229
- ) : null,
8230
- /* @__PURE__ */ jsx11(
8231
- ChromeButton2,
8232
- {
8233
- appearance,
8234
- onClick: () => void host.controller?.exitVR(),
8235
- children: "Exit VR"
8236
- }
8237
- )
8238
- ] });
8239
- }
8240
8377
  function RotatePhoneIcon2() {
8241
8378
  return /* @__PURE__ */ jsxs6(
8242
8379
  "svg",
@@ -8267,6 +8404,51 @@ function PortraitPrompt() {
8267
8404
  /* @__PURE__ */ jsx11("p", { className: "pano-webvr-portrait-text", children: "Rotate your phone to landscape" })
8268
8405
  ] });
8269
8406
  }
8407
+ function useSessionChromeVisibility(active, keepVisible, viewer) {
8408
+ const [visible, setVisible] = useState6(true);
8409
+ const idleTimerRef = useRef12(0);
8410
+ const bump = useCallback6(() => {
8411
+ setVisible(true);
8412
+ window.clearTimeout(idleTimerRef.current);
8413
+ if (!active || keepVisible) {
8414
+ return;
8415
+ }
8416
+ idleTimerRef.current = window.setTimeout(() => {
8417
+ setVisible(false);
8418
+ }, SESSION_CHROME_IDLE_MS);
8419
+ }, [active, keepVisible]);
8420
+ useEffect13(() => {
8421
+ if (!active) {
8422
+ window.clearTimeout(idleTimerRef.current);
8423
+ setVisible(true);
8424
+ return;
8425
+ }
8426
+ bump();
8427
+ }, [active, bump, keepVisible]);
8428
+ useEffect13(() => {
8429
+ if (!active || !viewer) {
8430
+ return;
8431
+ }
8432
+ const reveal = () => {
8433
+ bump();
8434
+ };
8435
+ viewer.addEventListener("pointerdown", reveal);
8436
+ viewer.addEventListener("wheel", reveal, { passive: true });
8437
+ viewer.addEventListener("keydown", reveal);
8438
+ return () => {
8439
+ viewer.removeEventListener("pointerdown", reveal);
8440
+ viewer.removeEventListener("wheel", reveal);
8441
+ viewer.removeEventListener("keydown", reveal);
8442
+ };
8443
+ }, [active, bump, viewer]);
8444
+ useEffect13(
8445
+ () => () => {
8446
+ window.clearTimeout(idleTimerRef.current);
8447
+ },
8448
+ []
8449
+ );
8450
+ return keepVisible || visible;
8451
+ }
8270
8452
  function WebVRChromeBridge({ host }) {
8271
8453
  useSyncExternalStore7(
8272
8454
  (listener) => subscribeWebVRHost(host, listener),
@@ -8274,11 +8456,29 @@ function WebVRChromeBridge({ host }) {
8274
8456
  () => host.revision
8275
8457
  );
8276
8458
  const portrait = usePortraitOrientation();
8277
- const { available, chrome, mode, settings, setupOpen } = host.snapshot;
8459
+ const {
8460
+ chrome,
8461
+ detectedScreensize,
8462
+ mode,
8463
+ settings,
8464
+ setupOpen
8465
+ } = host.snapshot;
8278
8466
  const [mounted, setMounted] = useState6(false);
8467
+ const [viewer, setViewer] = useState6(null);
8468
+ const toolbarRef = useRef12(null);
8279
8469
  useEffect13(() => {
8280
8470
  setMounted(true);
8281
8471
  }, []);
8472
+ useEffect13(() => {
8473
+ const node = toolbarRef.current?.closest("[data-pano-viewer]") ?? null;
8474
+ setViewer(node);
8475
+ }, [mode]);
8476
+ const sessionActive = mode !== null;
8477
+ const chromeVisible = useSessionChromeVisibility(
8478
+ sessionActive,
8479
+ setupOpen,
8480
+ viewer
8481
+ );
8282
8482
  if (chrome === false || !host.controller) {
8283
8483
  return null;
8284
8484
  }
@@ -8287,17 +8487,29 @@ function WebVRChromeBridge({ host }) {
8287
8487
  if (showPortraitPrompt) {
8288
8488
  return /* @__PURE__ */ jsx11(PortraitPrompt, {});
8289
8489
  }
8290
- return /* @__PURE__ */ jsxs6(Fragment3, { children: [
8291
- /* @__PURE__ */ jsx11("div", { className: "pano-webvr-toolbar", children: mode ? /* @__PURE__ */ jsx11(SessionActions, { appearance, host, mode }) : /* @__PURE__ */ jsx11(
8292
- ChromeButton2,
8490
+ if (!mode) {
8491
+ return null;
8492
+ }
8493
+ return /* @__PURE__ */ jsxs6(Fragment5, { children: [
8494
+ /* @__PURE__ */ jsx11("div", { className: "pano-webvr-toolbar", ref: toolbarRef, children: /* @__PURE__ */ jsx11(
8495
+ SessionActions,
8293
8496
  {
8294
8497
  appearance,
8295
- onClick: () => void host.controller?.enterVR(),
8296
- children: available ? "Enter VR" : "VR unavailable"
8498
+ host,
8499
+ mode,
8500
+ visible: chromeVisible
8297
8501
  }
8298
8502
  ) }),
8299
- mode === "fake" ? /* @__PURE__ */ jsx11("p", { className: "pano-webvr-hint", style: { color: appearance.color }, children: "Simulated VR Mode \xB7 Click the view to lock the mouse, then move to look around. Use a headset or phone for real VR." }) : null,
8300
- setupOpen ? /* @__PURE__ */ jsx11(SetupPanel, { appearance, host, settings }) : null
8503
+ mode === "fake" && chromeVisible ? /* @__PURE__ */ jsx11("p", { className: "pano-webvr-hint", style: { color: appearance.color }, children: "Simulated VR Mode \xB7 Click the view to lock the mouse, then move to look around. Use a headset or phone for real VR." }) : null,
8504
+ setupOpen ? /* @__PURE__ */ jsx11(
8505
+ SetupPanel,
8506
+ {
8507
+ appearance,
8508
+ detectedScreensize,
8509
+ host,
8510
+ settings
8511
+ }
8512
+ ) : null
8301
8513
  ] });
8302
8514
  }
8303
8515
 
@@ -8330,12 +8542,12 @@ function projectStereoHtmlPosition(el, camera, size, eye) {
8330
8542
  // src/webvr/xr-origin.tsx
8331
8543
  import { useFrame as useFrame7, useThree as useThree8 } from "@react-three/fiber";
8332
8544
  import { useXR as useXR2, XROrigin } from "@react-three/xr";
8333
- import { useRef as useRef12 } from "react";
8545
+ import { useRef as useRef13 } from "react";
8334
8546
  import { Vector3 as Vector36 } from "three";
8335
8547
  import { jsx as jsx12 } from "react/jsx-runtime";
8336
8548
  function PanoramaXROrigin() {
8337
- const originRef = useRef12(null);
8338
- const localHeadPositionRef = useRef12(new Vector36());
8549
+ const originRef = useRef13(null);
8550
+ const localHeadPositionRef = useRef13(new Vector36());
8339
8551
  const session2 = useXR2((state) => state.session);
8340
8552
  const gl = useThree8((state) => state.gl);
8341
8553
  useFrame7(() => {
@@ -8450,11 +8662,11 @@ var PanoViewer = forwardRef2(
8450
8662
  "aria-label": ariaLabel = "Panorama viewer",
8451
8663
  ...divProps
8452
8664
  }, ref) {
8453
- const rootRef = useRef13(null);
8454
- const controlsRef = useRef13(null);
8665
+ const rootRef = useRef14(null);
8666
+ const controlsRef = useRef14(null);
8455
8667
  const modalHost = useMemo8(() => createPanoModalHost(), []);
8456
8668
  const modalOpen = useSyncExternalStore8(modalHost.subscribe, modalHost.getSnapshot, () => false);
8457
- const setControlsRuntime = useCallback6((runtime) => {
8669
+ const setControlsRuntime = useCallback7((runtime) => {
8458
8670
  controlsRef.current = runtime;
8459
8671
  modalHost.connect(runtime);
8460
8672
  }, [modalHost]);
@@ -8514,12 +8726,12 @@ var PanoViewer = forwardRef2(
8514
8726
  }),
8515
8727
  [bgmHost, videoHost]
8516
8728
  );
8517
- const runMediaActivation = useCallback6(() => {
8729
+ const runMediaActivation = useCallback7(() => {
8518
8730
  const resume = mediaActivationControls.resumeAudio();
8519
8731
  mediaActivationOptions?.onActivate?.(mediaActivationControls);
8520
8732
  return resume;
8521
8733
  }, [mediaActivationControls, mediaActivationOptions]);
8522
- const activateMedia = useCallback6(() => {
8734
+ const activateMedia = useCallback7(() => {
8523
8735
  setMediaActivated(true);
8524
8736
  return runMediaActivation();
8525
8737
  }, [runMediaActivation]);
@@ -8601,7 +8813,7 @@ var PanoViewer = forwardRef2(
8601
8813
  ]);
8602
8814
  const limitsKey = JSON.stringify(viewLimits);
8603
8815
  const parsedLimits = useMemo8(() => parseViewLimits(viewLimits), [limitsKey]);
8604
- const fallbackViewRef = useRef13(DEFAULT_VIEW);
8816
+ const fallbackViewRef = useRef14(DEFAULT_VIEW);
8605
8817
  const normalizedMinFov = Math.max(1, Math.min(minFov, maxFov - 1));
8606
8818
  const normalizedMaxFov = Math.min(
8607
8819
  179,
@@ -8664,7 +8876,7 @@ var PanoViewer = forwardRef2(
8664
8876
  }),
8665
8877
  [controlOptions.keyboard]
8666
8878
  );
8667
- const enterFullscreen = useCallback6(async () => {
8879
+ const enterFullscreen = useCallback7(async () => {
8668
8880
  if (typeof document === "undefined") {
8669
8881
  return;
8670
8882
  }
@@ -8683,7 +8895,7 @@ var PanoViewer = forwardRef2(
8683
8895
  acquireIphoneLandscapeFullscreen(root, "viewer");
8684
8896
  }
8685
8897
  }, []);
8686
- const exitFullscreen = useCallback6(async () => {
8898
+ const exitFullscreen = useCallback7(async () => {
8687
8899
  if (typeof document === "undefined") {
8688
8900
  return;
8689
8901
  }
@@ -8695,7 +8907,7 @@ var PanoViewer = forwardRef2(
8695
8907
  await exitElementFullscreen();
8696
8908
  }
8697
8909
  }, []);
8698
- const toggleFullscreen = useCallback6(async () => {
8910
+ const toggleFullscreen = useCallback7(async () => {
8699
8911
  if (typeof document === "undefined") {
8700
8912
  return;
8701
8913
  }
@@ -8711,6 +8923,10 @@ var PanoViewer = forwardRef2(
8711
8923
  }, [enterFullscreen, exitFullscreen]);
8712
8924
  const [isViewerFullscreen, setIsViewerFullscreen] = useState7(false);
8713
8925
  const { context: overlayContext, setPortalContainer } = usePanoOverlayHost(isViewerFullscreen);
8926
+ const { sceneChildren, overlayElements } = useMemo8(
8927
+ () => partitionPanoOverlay(children),
8928
+ [children]
8929
+ );
8714
8930
  const iphoneFullscreenAllowsScroll = useSyncExternalStore8(
8715
8931
  subscribeIphoneLandscapeFullscreen,
8716
8932
  () => getIphoneLandscapeFullscreenSnapshot().allowsDocumentScroll,
@@ -8794,7 +9010,7 @@ var PanoViewer = forwardRef2(
8794
9010
  useEffect14(() => {
8795
9011
  setLegacyAutoRotate(legacyAutoRotateOptions.autoRotate ?? false);
8796
9012
  }, [legacyAutoRotateOptions.autoRotate]);
8797
- const isFullscreenRef = useRef13(false);
9013
+ const isFullscreenRef = useRef14(false);
8798
9014
  isFullscreenRef.current = isViewerFullscreen;
8799
9015
  useImperativeHandle2(
8800
9016
  ref,
@@ -8834,6 +9050,8 @@ var PanoViewer = forwardRef2(
8834
9050
  isVRAvailable: () => webVRHost.snapshot.available,
8835
9051
  isVREnabled: () => webVRHost.snapshot.mode !== null,
8836
9052
  getVRMode: () => webVRHost.snapshot.mode,
9053
+ getVRState: () => webVRHost.state,
9054
+ subscribeVR: (onStoreChange) => subscribeWebVRHost(webVRHost, onStoreChange),
8837
9055
  requestVRPermission: () => webVRHost.controller?.requestPermission() ?? Promise.resolve(false),
8838
9056
  // -- Video --
8839
9057
  getVideo: () => videoHost.controller,
@@ -8966,7 +9184,7 @@ var PanoViewer = forwardRef2(
8966
9184
  /* @__PURE__ */ jsx13(PanoramaEventSurface, {}),
8967
9185
  contextMenuChannel.mount ? /* @__PURE__ */ jsx13(PanoContextMenu, { ...contextMenuChannel.props }) : null,
8968
9186
  /* @__PURE__ */ jsxs7(ControlClaimsContext.Provider, { value: controlClaims, children: [
8969
- children,
9187
+ sceneChildren,
8970
9188
  /* @__PURE__ */ jsx13(
8971
9189
  DefaultControlChannels,
8972
9190
  {
@@ -9011,15 +9229,18 @@ var PanoViewer = forwardRef2(
9011
9229
  contextMenuOverlay,
9012
9230
  hotspotAccessibilityControls
9013
9231
  ] }),
9014
- /* @__PURE__ */ jsx13(PanoEventBusContext.Provider, { value: eventBus, children: /* @__PURE__ */ jsx13(PanoModalHostContext.Provider, { value: modalHost, children: /* @__PURE__ */ jsx13(PanoOverlayContext.Provider, { value: overlayContext, children: /* @__PURE__ */ jsx13(
9232
+ /* @__PURE__ */ jsx13(PanoEventBusContext.Provider, { value: eventBus, children: /* @__PURE__ */ jsx13(PanoModalHostContext.Provider, { value: modalHost, children: /* @__PURE__ */ jsx13(PanoOverlayContext.Provider, { value: overlayContext, children: /* @__PURE__ */ jsx13(PanoOverlayOutletContext.Provider, { value: true, children: /* @__PURE__ */ jsxs7(
9015
9233
  "div",
9016
9234
  {
9017
9235
  "data-pano-overlay": "",
9018
9236
  ref: setPortalContainer,
9019
9237
  style: { position: "absolute", inset: 0, zIndex: 30, pointerEvents: "none" },
9020
- children: renderOverlay?.(overlayContext)
9238
+ children: [
9239
+ renderOverlay?.(overlayContext),
9240
+ overlayElements
9241
+ ]
9021
9242
  }
9022
- ) }) }) })
9243
+ ) }) }) }) })
9023
9244
  ]
9024
9245
  }
9025
9246
  );
@@ -9111,107 +9332,440 @@ function hotspotAction(hotspot, trigger) {
9111
9332
  return hotspot.events?.[trigger] ?? (trigger === "click" ? hotspot.action : void 0);
9112
9333
  }
9113
9334
 
9114
- // src/project-scenes.tsx
9115
- import {
9116
- useContext as useContext22,
9117
- useMemo as useMemo22,
9118
- useState as useState21
9119
- } from "react";
9120
-
9121
- // src/scenes.tsx
9122
- import { useFrame as useFrame13, useThree as useThree16 } from "@react-three/fiber";
9123
- import {
9124
- Fragment as Fragment4,
9125
- useCallback as useCallback8,
9126
- useContext as useContext19,
9127
- useEffect as useEffect23,
9128
- useMemo as useMemo17,
9129
- useRef as useRef22,
9130
- useState as useState12
9131
- } from "react";
9132
- import {
9133
- LinearSRGBColorSpace,
9134
- LinearFilter as LinearFilter4,
9135
- WebGLRenderTarget
9136
- } from "three";
9137
-
9138
- // src/scene-detail.tsx
9139
- import { createContext as createContext16, useContext as useContext17 } from "react";
9140
- var SceneDetailContext = createContext16(null);
9141
- function useSceneDetailReporter() {
9142
- return useContext17(SceneDetailContext);
9143
- }
9144
- function viewTilesAreDrawable(textures) {
9145
- return textures.length > 0 && textures.every((texture) => texture != null);
9335
+ // src/overlay/align.ts
9336
+ var PANO_OVERLAY_ALIGNS = [
9337
+ "top",
9338
+ "bottom",
9339
+ "left",
9340
+ "right",
9341
+ "top-left",
9342
+ "top-right",
9343
+ "bottom-left",
9344
+ "bottom-right"
9345
+ ];
9346
+ var ALIGN_LIST = PANO_OVERLAY_ALIGNS.join(", ");
9347
+ var SAFE = {
9348
+ top: "var(--pano-safe-top)",
9349
+ right: "var(--pano-safe-right)",
9350
+ bottom: "var(--pano-safe-bottom)",
9351
+ left: "var(--pano-safe-left)"
9352
+ };
9353
+ function cssLength(value) {
9354
+ return typeof value === "number" ? `${value}px` : value;
9146
9355
  }
9147
- function publishSceneDetail(reporter, lastToken, ready) {
9148
- if (!reporter) {
9149
- return;
9356
+ function isOverlayCorner(align) {
9357
+ switch (align) {
9358
+ case "top-left":
9359
+ case "top-right":
9360
+ case "bottom-left":
9361
+ case "bottom-right":
9362
+ return true;
9363
+ case "top":
9364
+ case "bottom":
9365
+ case "left":
9366
+ case "right":
9367
+ return false;
9368
+ default: {
9369
+ const exhaustive = align;
9370
+ return exhaustive;
9371
+ }
9150
9372
  }
9151
- const token = `${reporter.generation}:${ready ? 1 : 0}`;
9152
- if (lastToken.current === token) {
9153
- return;
9373
+ }
9374
+ function resolveOverlayAxis(align, axis) {
9375
+ switch (align) {
9376
+ case "top":
9377
+ case "bottom":
9378
+ return "horizontal";
9379
+ case "left":
9380
+ case "right":
9381
+ return "vertical";
9382
+ case "top-left":
9383
+ case "top-right":
9384
+ case "bottom-left":
9385
+ case "bottom-right":
9386
+ return axis ?? "horizontal";
9387
+ default: {
9388
+ const exhaustive = align;
9389
+ return exhaustive;
9390
+ }
9154
9391
  }
9155
- lastToken.current = token;
9156
- reporter.report(ready);
9157
9392
  }
9158
-
9159
- // src/scene-transitions/presets.ts
9160
- var TRANSITION_DEFAULTS = {
9161
- none: { duration: 0, krpanoBlend: "NOBLEND" },
9162
- crossfade: { duration: 1, krpanoBlend: "BLEND(1.0, easeInCubic)" },
9163
- zoom: { duration: 2, krpanoBlend: "ZOOMBLEND(2.0, 2.0, easeInOutSine)" },
9164
- blackout: { duration: 2, krpanoBlend: "COLORBLEND(2.0, 0x000000, easeOutSine)" },
9165
- whiteFlash: { duration: 1, krpanoBlend: "LIGHTBLEND(1.0, 0xFFFFFF, 2.0, linear)" },
9166
- slideRightToLeft: { duration: 1, krpanoBlend: "SLIDEBLEND(1.0, 0.0, 0.2, linear)" },
9167
- slideTopToBottom: { duration: 1, krpanoBlend: "SLIDEBLEND(1.0, 90.0, 0.01, linear)" },
9168
- slideDiagonal: { duration: 1, krpanoBlend: "SLIDEBLEND(1.0, 135.0, 0.4, linear)" },
9169
- circleOpen: { duration: 1, krpanoBlend: "OPENBLEND(1.0, 0.0, 0.2, 0.0, linear)" },
9170
- verticalOpen: { duration: 0.7, krpanoBlend: "OPENBLEND(0.7, 1.0, 0.1, 0.0, linear)" },
9171
- horizontalOpen: { duration: 1, krpanoBlend: "OPENBLEND(1.0, -1.0, 0.3, 0.0, linear)" },
9172
- ellipticZoomOpen: { duration: 1, krpanoBlend: "OPENBLEND(1.0, -0.5, 0.3, 0.8, linear)" },
9173
- pixelate: { duration: 1, krpanoBlend: "pano-view cinematic" },
9174
- gridWipe: { duration: 1.2, krpanoBlend: "pano-view cinematic" },
9175
- gridWipeUp: { duration: 1.2, krpanoBlend: "pano-view cinematic" },
9176
- gridWipeRight: { duration: 1.2, krpanoBlend: "pano-view cinematic" },
9177
- gridWipeDiagonal: { duration: 1.2, krpanoBlend: "pano-view cinematic" },
9178
- gridWipeCenter: { duration: 1.2, krpanoBlend: "pano-view cinematic" },
9179
- gridWipeChecker: { duration: 1.2, krpanoBlend: "pano-view cinematic" },
9180
- dissolve: { duration: 1, krpanoBlend: "pano-view cinematic" },
9181
- shatter: { duration: 1.2, krpanoBlend: "pano-view cinematic" },
9182
- particles: { duration: 1.4, krpanoBlend: "pano-view cinematic" },
9183
- glitch: { duration: 0.8, krpanoBlend: "pano-view cinematic" },
9184
- swirl: { duration: 1.2, krpanoBlend: "pano-view cinematic" },
9185
- clockWipe: { duration: 1, krpanoBlend: "pano-view cinematic" },
9186
- ripple: { duration: 1.2, krpanoBlend: "pano-view cinematic" },
9187
- zoomBlur: { duration: 1, krpanoBlend: "pano-view cinematic" },
9188
- hexDissolve: { duration: 1.1, krpanoBlend: "pano-view cinematic" },
9189
- filmBurn: { duration: 1.3, krpanoBlend: "pano-view cinematic" }
9190
- };
9191
- function resolveTransition(transition) {
9192
- const preset = typeof transition === "string" ? transition : transition?.preset ?? "crossfade";
9193
- return {
9194
- preset,
9195
- duration: Math.max(
9196
- 0,
9197
- transition && typeof transition === "object" && Number.isFinite(transition.duration) ? transition.duration : TRANSITION_DEFAULTS[preset].duration
9198
- )
9199
- };
9393
+ function overlayCollapseEdge(align, axis) {
9394
+ switch (align) {
9395
+ case "top":
9396
+ return "top";
9397
+ case "bottom":
9398
+ return "bottom";
9399
+ case "left":
9400
+ return "left";
9401
+ case "right":
9402
+ return "right";
9403
+ case "top-left":
9404
+ return resolveOverlayAxis(align, axis) === "horizontal" ? "top" : "left";
9405
+ case "top-right":
9406
+ return resolveOverlayAxis(align, axis) === "horizontal" ? "top" : "right";
9407
+ case "bottom-left":
9408
+ return resolveOverlayAxis(align, axis) === "horizontal" ? "bottom" : "left";
9409
+ case "bottom-right":
9410
+ return resolveOverlayAxis(align, axis) === "horizontal" ? "bottom" : "right";
9411
+ default: {
9412
+ const exhaustive = align;
9413
+ return exhaustive;
9414
+ }
9415
+ }
9200
9416
  }
9201
- function isSnapshotTransition(preset) {
9202
- return preset !== "none" && preset !== "particles";
9417
+ function overlayChevron(edge, collapsed) {
9418
+ switch (edge) {
9419
+ case "top":
9420
+ return collapsed ? "down" : "up";
9421
+ case "bottom":
9422
+ return collapsed ? "up" : "down";
9423
+ case "left":
9424
+ return collapsed ? "right" : "left";
9425
+ case "right":
9426
+ return collapsed ? "left" : "right";
9427
+ default: {
9428
+ const exhaustive = edge;
9429
+ return exhaustive;
9430
+ }
9431
+ }
9203
9432
  }
9204
-
9205
- // src/scene-transitions/snapshot-hold.tsx
9206
- import { useFrame as useFrame8, useThree as useThree9 } from "@react-three/fiber";
9207
- import { useEffect as useEffect15, useMemo as useMemo9, useRef as useRef14 } from "react";
9208
- import { Vector3 as Vector38 } from "three";
9209
-
9210
- // src/scene-transitions/overlay-utils.ts
9211
- import { PlaneGeometry } from "three";
9212
- var OVERLAY_RENDER_ORDER = 1e4;
9213
- var FULLSCREEN_PLANE = new PlaneGeometry(1, 1);
9214
- function isPerspectiveCamera(camera) {
9433
+ function overlayContentAlign(align, axis) {
9434
+ const resolved = resolveOverlayAxis(align, axis);
9435
+ if (resolved === "horizontal") {
9436
+ switch (align) {
9437
+ case "top-left":
9438
+ case "bottom-left":
9439
+ return "start";
9440
+ case "top-right":
9441
+ case "bottom-right":
9442
+ return "end";
9443
+ case "top":
9444
+ case "bottom":
9445
+ case "left":
9446
+ case "right":
9447
+ return "center";
9448
+ default: {
9449
+ const exhaustive = align;
9450
+ return exhaustive;
9451
+ }
9452
+ }
9453
+ }
9454
+ switch (align) {
9455
+ case "top-left":
9456
+ case "top-right":
9457
+ return "start";
9458
+ case "bottom-left":
9459
+ case "bottom-right":
9460
+ return "end";
9461
+ case "top":
9462
+ case "bottom":
9463
+ case "left":
9464
+ case "right":
9465
+ return "center";
9466
+ default: {
9467
+ const exhaustive = align;
9468
+ return exhaustive;
9469
+ }
9470
+ }
9471
+ }
9472
+ function resolveOverlayInset(inset, fallback) {
9473
+ const fallbackLength = cssLength(fallback);
9474
+ if (inset === void 0) {
9475
+ return {
9476
+ top: fallbackLength,
9477
+ right: fallbackLength,
9478
+ bottom: fallbackLength,
9479
+ left: fallbackLength
9480
+ };
9481
+ }
9482
+ if (typeof inset === "number" || typeof inset === "string") {
9483
+ const value = cssLength(inset);
9484
+ return { top: value, right: value, bottom: value, left: value };
9485
+ }
9486
+ return {
9487
+ top: cssLength(inset.top ?? fallback),
9488
+ right: cssLength(inset.right ?? fallback),
9489
+ bottom: cssLength(inset.bottom ?? fallback),
9490
+ left: cssLength(inset.left ?? fallback)
9491
+ };
9492
+ }
9493
+ function overlayDockInset(edge, inset) {
9494
+ switch (edge) {
9495
+ case "top":
9496
+ return inset.top;
9497
+ case "bottom":
9498
+ return inset.bottom;
9499
+ case "left":
9500
+ return inset.left;
9501
+ case "right":
9502
+ return inset.right;
9503
+ default: {
9504
+ const exhaustive = edge;
9505
+ return exhaustive;
9506
+ }
9507
+ }
9508
+ }
9509
+ function resolveOverlayAnchor(anchor, defaults) {
9510
+ const align = anchor?.align ?? defaults.align;
9511
+ const axis = resolveOverlayAxis(align, anchor?.axis);
9512
+ const inset = resolveOverlayInset(anchor?.inset, defaults.inset);
9513
+ const collapseEdge = overlayCollapseEdge(align, anchor?.axis);
9514
+ return {
9515
+ align,
9516
+ axis,
9517
+ inset,
9518
+ collapseEdge,
9519
+ contentAlign: overlayContentAlign(align, anchor?.axis),
9520
+ dockInset: overlayDockInset(collapseEdge, inset)
9521
+ };
9522
+ }
9523
+ function withSafe(inset, safeVariable) {
9524
+ return `calc(${inset} + ${safeVariable})`;
9525
+ }
9526
+ function maxBox(inset) {
9527
+ return {
9528
+ maxWidth: `calc(100% - ${inset.left} - ${inset.right} - ${SAFE.left} - ${SAFE.right})`,
9529
+ maxHeight: `calc(100% - ${inset.top} - ${inset.bottom} - ${SAFE.top} - ${SAFE.bottom})`
9530
+ };
9531
+ }
9532
+ function horizontalSpan(inset, width) {
9533
+ const left = withSafe(inset.left, SAFE.left);
9534
+ const right = withSafe(inset.right, SAFE.right);
9535
+ if (width === void 0) {
9536
+ return { left, right, width: void 0, marginInline: void 0 };
9537
+ }
9538
+ return { left, right, width, marginInline: "auto" };
9539
+ }
9540
+ function verticalSpan(inset, height) {
9541
+ const top = withSafe(inset.top, SAFE.top);
9542
+ const bottom = withSafe(inset.bottom, SAFE.bottom);
9543
+ if (height === void 0) {
9544
+ return { top, bottom, height: void 0, marginBlock: void 0 };
9545
+ }
9546
+ return { top, bottom, height, marginBlock: "auto" };
9547
+ }
9548
+ function overlayBoxStyle(align, inset, width, height) {
9549
+ const widthValue = width === void 0 ? void 0 : cssLength(width);
9550
+ const heightValue = height === void 0 ? void 0 : cssLength(height);
9551
+ const limits = maxBox(inset);
9552
+ switch (align) {
9553
+ case "top":
9554
+ return {
9555
+ ...horizontalSpan(inset, widthValue),
9556
+ top: withSafe(inset.top, SAFE.top),
9557
+ bottom: "auto",
9558
+ height: heightValue,
9559
+ ...limits
9560
+ };
9561
+ case "bottom":
9562
+ return {
9563
+ ...horizontalSpan(inset, widthValue),
9564
+ top: "auto",
9565
+ bottom: withSafe(inset.bottom, SAFE.bottom),
9566
+ height: heightValue,
9567
+ ...limits
9568
+ };
9569
+ case "left":
9570
+ return {
9571
+ ...verticalSpan(inset, heightValue),
9572
+ left: withSafe(inset.left, SAFE.left),
9573
+ right: "auto",
9574
+ width: widthValue,
9575
+ ...limits
9576
+ };
9577
+ case "right":
9578
+ return {
9579
+ ...verticalSpan(inset, heightValue),
9580
+ left: "auto",
9581
+ right: withSafe(inset.right, SAFE.right),
9582
+ width: widthValue,
9583
+ ...limits
9584
+ };
9585
+ case "top-left":
9586
+ return {
9587
+ top: withSafe(inset.top, SAFE.top),
9588
+ left: withSafe(inset.left, SAFE.left),
9589
+ right: "auto",
9590
+ bottom: "auto",
9591
+ width: widthValue,
9592
+ height: heightValue,
9593
+ ...limits
9594
+ };
9595
+ case "top-right":
9596
+ return {
9597
+ top: withSafe(inset.top, SAFE.top),
9598
+ right: withSafe(inset.right, SAFE.right),
9599
+ left: "auto",
9600
+ bottom: "auto",
9601
+ width: widthValue,
9602
+ height: heightValue,
9603
+ ...limits
9604
+ };
9605
+ case "bottom-left":
9606
+ return {
9607
+ bottom: withSafe(inset.bottom, SAFE.bottom),
9608
+ left: withSafe(inset.left, SAFE.left),
9609
+ top: "auto",
9610
+ right: "auto",
9611
+ width: widthValue,
9612
+ height: heightValue,
9613
+ ...limits
9614
+ };
9615
+ case "bottom-right":
9616
+ return {
9617
+ bottom: withSafe(inset.bottom, SAFE.bottom),
9618
+ right: withSafe(inset.right, SAFE.right),
9619
+ top: "auto",
9620
+ left: "auto",
9621
+ width: widthValue,
9622
+ height: heightValue,
9623
+ ...limits
9624
+ };
9625
+ default: {
9626
+ const exhaustive = align;
9627
+ return exhaustive;
9628
+ }
9629
+ }
9630
+ }
9631
+ function isRecord2(value) {
9632
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9633
+ }
9634
+ function parseInsetLength(value, field) {
9635
+ if (typeof value === "number" && Number.isFinite(value)) return value;
9636
+ if (typeof value === "string" && value.trim()) return value;
9637
+ throw new Error(`${field}: expected a finite number or a nonempty string`);
9638
+ }
9639
+ function parseInset(value, field) {
9640
+ if (typeof value === "number" || typeof value === "string") {
9641
+ return parseInsetLength(value, field);
9642
+ }
9643
+ if (!isRecord2(value)) throw new Error(`${field}: expected a number, string, or object`);
9644
+ const sides = ["top", "right", "bottom", "left"];
9645
+ const inset = {};
9646
+ for (const side of sides) {
9647
+ if (value[side] === void 0) continue;
9648
+ inset[side] = parseInsetLength(value[side], `${field}.${side}`);
9649
+ }
9650
+ return inset;
9651
+ }
9652
+ function parseOverlayAnchor(value, field) {
9653
+ if (!isRecord2(value)) throw new Error(`${field}: expected an object`);
9654
+ const align = value.align === void 0 ? void 0 : typeof value.align === "string" && PANO_OVERLAY_ALIGNS.includes(value.align) ? value.align : (() => {
9655
+ throw new Error(`${field}.align: expected ${ALIGN_LIST}`);
9656
+ })();
9657
+ const axis = value.axis === void 0 ? void 0 : value.axis === "horizontal" || value.axis === "vertical" ? value.axis : (() => {
9658
+ throw new Error(`${field}.axis: expected "horizontal" or "vertical"`);
9659
+ })();
9660
+ const inset = value.inset === void 0 ? void 0 : parseInset(value.inset, `${field}.inset`);
9661
+ return {
9662
+ ...align === void 0 ? {} : { align },
9663
+ ...axis === void 0 ? {} : { axis },
9664
+ ...inset === void 0 ? {} : { inset }
9665
+ };
9666
+ }
9667
+
9668
+ // src/project-scenes.tsx
9669
+ import {
9670
+ useContext as useContext22,
9671
+ useMemo as useMemo22,
9672
+ useState as useState21
9673
+ } from "react";
9674
+
9675
+ // src/scenes.tsx
9676
+ import { useFrame as useFrame13, useThree as useThree16 } from "@react-three/fiber";
9677
+ import {
9678
+ Fragment as Fragment6,
9679
+ useCallback as useCallback9,
9680
+ useContext as useContext19,
9681
+ useEffect as useEffect23,
9682
+ useMemo as useMemo17,
9683
+ useRef as useRef23,
9684
+ useState as useState12
9685
+ } from "react";
9686
+ import {
9687
+ LinearSRGBColorSpace,
9688
+ LinearFilter as LinearFilter3,
9689
+ WebGLRenderTarget
9690
+ } from "three";
9691
+
9692
+ // src/scene-detail.tsx
9693
+ import { createContext as createContext16, useContext as useContext17 } from "react";
9694
+ var SceneDetailContext = createContext16(null);
9695
+ function useSceneDetailReporter() {
9696
+ return useContext17(SceneDetailContext);
9697
+ }
9698
+ function viewTilesAreDrawable(textures) {
9699
+ return textures.length > 0 && textures.every((texture) => texture != null);
9700
+ }
9701
+ function publishSceneDetail(reporter, lastToken, ready) {
9702
+ if (!reporter) {
9703
+ return;
9704
+ }
9705
+ const token = `${reporter.generation}:${ready ? 1 : 0}`;
9706
+ if (lastToken.current === token) {
9707
+ return;
9708
+ }
9709
+ lastToken.current = token;
9710
+ reporter.report(ready);
9711
+ }
9712
+
9713
+ // src/scene-transitions/presets.ts
9714
+ var TRANSITION_DEFAULTS = {
9715
+ none: { duration: 0, krpanoBlend: "NOBLEND" },
9716
+ crossfade: { duration: 1, krpanoBlend: "BLEND(1.0, easeInCubic)" },
9717
+ zoom: { duration: 2, krpanoBlend: "ZOOMBLEND(2.0, 2.0, easeInOutSine)" },
9718
+ blackout: { duration: 2, krpanoBlend: "COLORBLEND(2.0, 0x000000, easeOutSine)" },
9719
+ whiteFlash: { duration: 1, krpanoBlend: "LIGHTBLEND(1.0, 0xFFFFFF, 2.0, linear)" },
9720
+ slideRightToLeft: { duration: 1, krpanoBlend: "SLIDEBLEND(1.0, 0.0, 0.2, linear)" },
9721
+ slideTopToBottom: { duration: 1, krpanoBlend: "SLIDEBLEND(1.0, 90.0, 0.01, linear)" },
9722
+ slideDiagonal: { duration: 1, krpanoBlend: "SLIDEBLEND(1.0, 135.0, 0.4, linear)" },
9723
+ circleOpen: { duration: 1, krpanoBlend: "OPENBLEND(1.0, 0.0, 0.2, 0.0, linear)" },
9724
+ verticalOpen: { duration: 0.7, krpanoBlend: "OPENBLEND(0.7, 1.0, 0.1, 0.0, linear)" },
9725
+ horizontalOpen: { duration: 1, krpanoBlend: "OPENBLEND(1.0, -1.0, 0.3, 0.0, linear)" },
9726
+ ellipticZoomOpen: { duration: 1, krpanoBlend: "OPENBLEND(1.0, -0.5, 0.3, 0.8, linear)" },
9727
+ pixelate: { duration: 1, krpanoBlend: "pano-view cinematic" },
9728
+ gridWipe: { duration: 1.2, krpanoBlend: "pano-view cinematic" },
9729
+ gridWipeUp: { duration: 1.2, krpanoBlend: "pano-view cinematic" },
9730
+ gridWipeRight: { duration: 1.2, krpanoBlend: "pano-view cinematic" },
9731
+ gridWipeDiagonal: { duration: 1.2, krpanoBlend: "pano-view cinematic" },
9732
+ gridWipeCenter: { duration: 1.2, krpanoBlend: "pano-view cinematic" },
9733
+ gridWipeChecker: { duration: 1.2, krpanoBlend: "pano-view cinematic" },
9734
+ dissolve: { duration: 1, krpanoBlend: "pano-view cinematic" },
9735
+ shatter: { duration: 1.2, krpanoBlend: "pano-view cinematic" },
9736
+ particles: { duration: 1.4, krpanoBlend: "pano-view cinematic" },
9737
+ glitch: { duration: 0.8, krpanoBlend: "pano-view cinematic" },
9738
+ swirl: { duration: 1.2, krpanoBlend: "pano-view cinematic" },
9739
+ clockWipe: { duration: 1, krpanoBlend: "pano-view cinematic" },
9740
+ ripple: { duration: 1.2, krpanoBlend: "pano-view cinematic" },
9741
+ zoomBlur: { duration: 1, krpanoBlend: "pano-view cinematic" },
9742
+ hexDissolve: { duration: 1.1, krpanoBlend: "pano-view cinematic" },
9743
+ filmBurn: { duration: 1.3, krpanoBlend: "pano-view cinematic" }
9744
+ };
9745
+ function resolveTransition(transition) {
9746
+ const preset = typeof transition === "string" ? transition : transition?.preset ?? "crossfade";
9747
+ return {
9748
+ preset,
9749
+ duration: Math.max(
9750
+ 0,
9751
+ transition && typeof transition === "object" && Number.isFinite(transition.duration) ? transition.duration : TRANSITION_DEFAULTS[preset].duration
9752
+ )
9753
+ };
9754
+ }
9755
+ function isSnapshotTransition(preset) {
9756
+ return preset !== "none" && preset !== "particles";
9757
+ }
9758
+
9759
+ // src/scene-transitions/snapshot-hold.tsx
9760
+ import { useFrame as useFrame8, useThree as useThree9 } from "@react-three/fiber";
9761
+ import { useEffect as useEffect15, useMemo as useMemo9, useRef as useRef15 } from "react";
9762
+ import { Vector3 as Vector38 } from "three";
9763
+
9764
+ // src/scene-transitions/overlay-utils.ts
9765
+ import { PlaneGeometry } from "three";
9766
+ var OVERLAY_RENDER_ORDER = 1e4;
9767
+ var FULLSCREEN_PLANE = new PlaneGeometry(1, 1);
9768
+ function isPerspectiveCamera(camera) {
9215
9769
  return camera.isPerspectiveCamera === true;
9216
9770
  }
9217
9771
  function placeFullscreenOverlay(mesh, camera, direction) {
@@ -9540,8 +10094,8 @@ function createSnapshotMaterial(preset, texture) {
9540
10094
  import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
9541
10095
  function SnapshotHold({ snapshot }) {
9542
10096
  const { camera, gl } = useThree9();
9543
- const meshRef = useRef14(null);
9544
- const directionRef = useRef14(new Vector38());
10097
+ const meshRef = useRef15(null);
10098
+ const directionRef = useRef15(new Vector38());
9545
10099
  const material = useMemo9(
9546
10100
  () => createSnapshotMaterial("crossfade", snapshot.texture),
9547
10101
  [snapshot.texture]
@@ -9624,7 +10178,7 @@ function phaseAfterSnapshot(preset, hold) {
9624
10178
 
9625
10179
  // src/scene-transitions/particle-overlay.tsx
9626
10180
  import { useFrame as useFrame9, useThree as useThree10 } from "@react-three/fiber";
9627
- import { useEffect as useEffect16, useMemo as useMemo10, useRef as useRef15 } from "react";
10181
+ import { useEffect as useEffect16, useMemo as useMemo10, useRef as useRef16 } from "react";
9628
10182
  import {
9629
10183
  DoubleSide as DoubleSide2,
9630
10184
  InstancedBufferAttribute,
@@ -9711,10 +10265,10 @@ function ParticleOverlay({
9711
10265
  onFinish
9712
10266
  }) {
9713
10267
  const { camera } = useThree10();
9714
- const meshRef = useRef15(null);
9715
- const startRef = useRef15(null);
9716
- const finishedRef = useRef15(false);
9717
- const directionRef = useRef15(new Vector39());
10268
+ const meshRef = useRef16(null);
10269
+ const startRef = useRef16(null);
10270
+ const finishedRef = useRef16(false);
10271
+ const directionRef = useRef16(new Vector39());
9718
10272
  const geometry = useMemo10(() => createParticleGeometry(), []);
9719
10273
  const material = useMemo10(
9720
10274
  () => new ShaderMaterial2({
@@ -9770,7 +10324,7 @@ function ParticleOverlay({
9770
10324
 
9771
10325
  // src/scene-transitions/snapshot-overlay.tsx
9772
10326
  import { useFrame as useFrame10, useThree as useThree11 } from "@react-three/fiber";
9773
- import { useEffect as useEffect17, useMemo as useMemo11, useRef as useRef16 } from "react";
10327
+ import { useEffect as useEffect17, useMemo as useMemo11, useRef as useRef17 } from "react";
9774
10328
  import { Vector3 as Vector310 } from "three";
9775
10329
  import { jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
9776
10330
  function SnapshotOverlay({
@@ -9781,10 +10335,10 @@ function SnapshotOverlay({
9781
10335
  onFinish
9782
10336
  }) {
9783
10337
  const { camera, gl } = useThree11();
9784
- const meshRef = useRef16(null);
9785
- const startRef = useRef16(null);
9786
- const finishedRef = useRef16(false);
9787
- const directionRef = useRef16(new Vector310());
10338
+ const meshRef = useRef17(null);
10339
+ const startRef = useRef17(null);
10340
+ const finishedRef = useRef17(false);
10341
+ const directionRef = useRef17(new Vector310());
9788
10342
  const material = useMemo11(() => {
9789
10343
  if (!isSnapshotTransition(transition.preset)) {
9790
10344
  return null;
@@ -9874,7 +10428,7 @@ function TransitionOverlay(props) {
9874
10428
 
9875
10429
  // src/sphere.tsx
9876
10430
  import { useThree as useThree12 } from "@react-three/fiber";
9877
- import { useEffect as useEffect18, useMemo as useMemo13, useRef as useRef18, useState as useState8 } from "react";
10431
+ import { useEffect as useEffect18, useMemo as useMemo13, useRef as useRef19, useState as useState8 } from "react";
9878
10432
  import {
9879
10433
  BackSide as BackSide2,
9880
10434
  LinearFilter,
@@ -9886,7 +10440,7 @@ import {
9886
10440
  } from "three";
9887
10441
 
9888
10442
  // src/pano-filter/pano-basic-material.tsx
9889
- import { useLayoutEffect as useLayoutEffect4, useMemo as useMemo12, useRef as useRef17 } from "react";
10443
+ import { useLayoutEffect as useLayoutEffect4, useMemo as useMemo12, useRef as useRef18 } from "react";
9890
10444
  import {
9891
10445
  Matrix4,
9892
10446
  Vector4
@@ -10086,9 +10640,9 @@ function PanoBasicMaterial({
10086
10640
  const preset = snapshot.enabled && target === "panorama" ? snapshot.preset : "none";
10087
10641
  const matrix = snapshot.enabled && (target === "panorama" || snapshot.colorMatrixScope === "all") ? snapshot.colorMatrix : null;
10088
10642
  const hasMatrix = matrix !== null;
10089
- const currentRef = useRef17({ matrix, intensity: snapshot.intensity });
10643
+ const currentRef = useRef18({ matrix, intensity: snapshot.intensity });
10090
10644
  currentRef.current = { matrix, intensity: snapshot.intensity };
10091
- const shaderRef = useRef17(null);
10645
+ const shaderRef = useRef18(null);
10092
10646
  const onBeforeCompile = useMemo12(() => {
10093
10647
  const compile = (shader) => {
10094
10648
  shaderRef.current = shader;
@@ -10136,8 +10690,8 @@ function PanoBasicMaterial({
10136
10690
  import { jsx as jsx19, jsxs as jsxs10 } from "react/jsx-runtime";
10137
10691
  function useEquirectTexture(url, anisotropy, generateMipmaps, onLoad, onError) {
10138
10692
  const [texture, setTexture] = useState8(null);
10139
- const onLoadRef = useRef18(onLoad);
10140
- const onErrorRef = useRef18(onError);
10693
+ const onLoadRef = useRef19(onLoad);
10694
+ const onErrorRef = useRef19(onError);
10141
10695
  onLoadRef.current = onLoad;
10142
10696
  onErrorRef.current = onError;
10143
10697
  useEffect18(() => {
@@ -10218,7 +10772,7 @@ function Sphere({
10218
10772
  onPreviewError
10219
10773
  );
10220
10774
  const displayTexture = texture ?? previewTexture;
10221
- const readyRef = useRef18(false);
10775
+ const readyRef = useRef19(false);
10222
10776
  const detailReporter = useSceneDetailReporter();
10223
10777
  useEffect18(() => {
10224
10778
  readyRef.current = false;
@@ -10264,13 +10818,14 @@ function Sphere({
10264
10818
 
10265
10819
  // src/sphere-tile/sphere-tile.tsx
10266
10820
  import { useFrame as useFrame11, useThree as useThree14 } from "@react-three/fiber";
10267
- import { memo, useCallback as useCallback7, useEffect as useEffect21, useMemo as useMemo15, useRef as useRef20, useState as useState10 } from "react";
10821
+ import { memo, useCallback as useCallback8, useEffect as useEffect21, useMemo as useMemo15, useRef as useRef21, useState as useState10 } from "react";
10268
10822
  import { BackSide as BackSide3, Frustum, Matrix4 as Matrix42, PerspectiveCamera as PerspectiveCamera4 } from "three";
10269
10823
 
10270
10824
  // src/tile/texture-manager.ts
10271
10825
  import {
10272
10826
  ClampToEdgeWrapping,
10273
10827
  LinearFilter as LinearFilter2,
10828
+ LinearMipmapLinearFilter as LinearMipmapLinearFilter2,
10274
10829
  SRGBColorSpace as SRGBColorSpace2,
10275
10830
  TextureLoader as TextureLoader2
10276
10831
  } from "three";
@@ -10279,6 +10834,20 @@ var EMPTY_TEXTURE_SNAPSHOT = {
10279
10834
  texture: null,
10280
10835
  error: null
10281
10836
  };
10837
+ var MIPMAP_MEMORY_FACTOR = 4 / 3;
10838
+ function mipmappedTextureBytes(width, height) {
10839
+ return Math.ceil(width * height * 4 * MIPMAP_MEMORY_FACTOR);
10840
+ }
10841
+ function configureMipmappedTexture(texture, anisotropy) {
10842
+ texture.colorSpace = SRGBColorSpace2;
10843
+ texture.wrapS = ClampToEdgeWrapping;
10844
+ texture.wrapT = ClampToEdgeWrapping;
10845
+ texture.magFilter = LinearFilter2;
10846
+ texture.minFilter = LinearMipmapLinearFilter2;
10847
+ texture.generateMipmaps = true;
10848
+ texture.anisotropy = Math.max(1, anisotropy);
10849
+ texture.needsUpdate = true;
10850
+ }
10282
10851
  var TileTextureManager = class {
10283
10852
  constructor(options) {
10284
10853
  this.loader = new TextureLoader2();
@@ -10430,7 +10999,10 @@ var TileTextureManager = class {
10430
10999
  entry.texture = texture;
10431
11000
  entry.status = "loaded";
10432
11001
  entry.error = null;
10433
- entry.bytes = entry.address.width * entry.address.height * 4;
11002
+ entry.bytes = mipmappedTextureBytes(
11003
+ entry.address.width,
11004
+ entry.address.height
11005
+ );
10434
11006
  entry.lastUsed = performance.now();
10435
11007
  this.totalBytes += entry.bytes;
10436
11008
  this.loaded += 1;
@@ -10453,14 +11025,7 @@ var TileTextureManager = class {
10453
11025
  }
10454
11026
  }
10455
11027
  configureTexture(texture) {
10456
- texture.colorSpace = SRGBColorSpace2;
10457
- texture.wrapS = ClampToEdgeWrapping;
10458
- texture.wrapT = ClampToEdgeWrapping;
10459
- texture.magFilter = LinearFilter2;
10460
- texture.minFilter = LinearFilter2;
10461
- texture.generateMipmaps = false;
10462
- texture.anisotropy = Math.min(4, this.options.anisotropy);
10463
- texture.needsUpdate = true;
11028
+ configureMipmappedTexture(texture, this.options.anisotropy);
10464
11029
  }
10465
11030
  evict() {
10466
11031
  if (this.totalBytes <= this.options.maxBytes) {
@@ -10534,16 +11099,11 @@ function useSharedTileTextureManager() {
10534
11099
  }
10535
11100
 
10536
11101
  // src/tile/texture-hooks.ts
10537
- import { useEffect as useEffect20, useRef as useRef19, useState as useState9 } from "react";
10538
- import {
10539
- ClampToEdgeWrapping as ClampToEdgeWrapping2,
10540
- LinearFilter as LinearFilter3,
10541
- SRGBColorSpace as SRGBColorSpace3,
10542
- TextureLoader as TextureLoader3
10543
- } from "three";
11102
+ import { useEffect as useEffect20, useRef as useRef20, useState as useState9 } from "react";
11103
+ import { TextureLoader as TextureLoader3 } from "three";
10544
11104
  function usePreviewTexture(url, anisotropy, onError) {
10545
11105
  const [texture, setTexture] = useState9(null);
10546
- const onErrorRef = useRef19(onError);
11106
+ const onErrorRef = useRef20(onError);
10547
11107
  onErrorRef.current = onError;
10548
11108
  useEffect20(() => {
10549
11109
  if (!url) {
@@ -10560,14 +11120,7 @@ function usePreviewTexture(url, anisotropy, onError) {
10560
11120
  loadedTexture.dispose();
10561
11121
  return;
10562
11122
  }
10563
- loadedTexture.colorSpace = SRGBColorSpace3;
10564
- loadedTexture.wrapS = ClampToEdgeWrapping2;
10565
- loadedTexture.wrapT = ClampToEdgeWrapping2;
10566
- loadedTexture.magFilter = LinearFilter3;
10567
- loadedTexture.minFilter = LinearFilter3;
10568
- loadedTexture.generateMipmaps = false;
10569
- loadedTexture.anisotropy = Math.min(4, anisotropy);
10570
- loadedTexture.needsUpdate = true;
11123
+ configureMipmappedTexture(loadedTexture, anisotropy);
10571
11124
  setTexture(loadedTexture);
10572
11125
  },
10573
11126
  void 0,
@@ -10739,8 +11292,17 @@ function sphereTileGeometry(address, yawOffset2, radius = DEFAULT_PANORAMA_RADIU
10739
11292
  geometry.computeBoundingSphere();
10740
11293
  return geometry;
10741
11294
  }
10742
- function preferredSphereLevel(levels, height, dpr, verticalFov) {
10743
- const needed = height * dpr * 180 / Math.max(1, verticalFov);
11295
+ function tanHalfFov(verticalFov) {
11296
+ const fov = Math.min(179, Math.max(1, verticalFov));
11297
+ return Math.tan(MathUtils6.degToRad(fov) * 0.5);
11298
+ }
11299
+ function preferredSphereLevel(levels, height, dpr, verticalFov, width = height) {
11300
+ const pixelHeight = Math.max(1, height) * dpr;
11301
+ const pixelWidth = Math.max(1, width) * dpr;
11302
+ const tanHalfV = tanHalfFov(verticalFov);
11303
+ const tanHalfH = pixelWidth / pixelHeight * tanHalfV;
11304
+ const edgeScale = 1 + Math.max(tanHalfV, tanHalfH) ** 2;
11305
+ const needed = pixelHeight * 0.5 / tanHalfV * Math.PI * edgeScale;
10744
11306
  const index = levels.findIndex((level) => level.height >= needed);
10745
11307
  return index < 0 ? levels.length - 1 : index;
10746
11308
  }
@@ -10841,7 +11403,7 @@ function SphereTileSource({
10841
11403
  anisotropy,
10842
11404
  onPreviewError
10843
11405
  );
10844
- const callbacks = useRef20({
11406
+ const callbacks = useRef21({
10845
11407
  onReady,
10846
11408
  onLoadProgress,
10847
11409
  onTileError,
@@ -10874,8 +11436,8 @@ function SphereTileSource({
10874
11436
  );
10875
11437
  const manager = sharedManager ?? ownedManager;
10876
11438
  const budget = manager.memoryBudgetBytes;
10877
- const readyRef = useRef20(false);
10878
- const ready = useCallback7(() => {
11439
+ const readyRef = useRef21(false);
11440
+ const ready = useCallback8(() => {
10879
11441
  if (!readyRef.current) {
10880
11442
  readyRef.current = true;
10881
11443
  callbacks.current.onReady?.();
@@ -10916,10 +11478,10 @@ function SphereTileSource({
10916
11478
  );
10917
11479
  const [layers, setLayers] = useState10([]);
10918
11480
  const detailReporter = useSceneDetailReporter();
10919
- const detailReporterRef = useRef20(detailReporter);
11481
+ const detailReporterRef = useRef21(detailReporter);
10920
11482
  detailReporterRef.current = detailReporter;
10921
- const detailTokenRef = useRef20("");
10922
- const targetKeysRef = useRef20([]);
11483
+ const detailTokenRef = useRef21("");
11484
+ const targetKeysRef = useRef21([]);
10923
11485
  const scratch = useMemo15(
10924
11486
  () => ({
10925
11487
  camera: new PerspectiveCamera4(),
@@ -10968,7 +11530,13 @@ function SphereTileSource({
10968
11530
  scratch.camera.matrixWorldInverse
10969
11531
  );
10970
11532
  scratch.frustum.setFromProjectionMatrix(scratch.matrix);
10971
- let level = loadMode === "preview" ? -1 : loadMode === "base" ? 0 : preferredSphereLevel(config.levels, size.height, dpr, camera.fov);
11533
+ let level = loadMode === "preview" ? -1 : loadMode === "base" ? 0 : preferredSphereLevel(
11534
+ config.levels,
11535
+ size.height,
11536
+ dpr,
11537
+ camera.fov,
11538
+ size.width
11539
+ );
10972
11540
  const next = [];
10973
11541
  let residentBytes = 0;
10974
11542
  for (let index = 0; index <= level; index++) {
@@ -10976,7 +11544,7 @@ function SphereTileSource({
10976
11544
  ({ bounds }) => scratch.frustum.intersectsBox(bounds)
10977
11545
  ).map(({ address }) => address);
10978
11546
  const bytes = addresses.reduce(
10979
- (total, address) => total + address.width * address.height * 4,
11547
+ (total, address) => total + mipmappedTextureBytes(address.width, address.height),
10980
11548
  0
10981
11549
  );
10982
11550
  if (residentBytes + bytes > budget) {
@@ -11042,7 +11610,7 @@ function SphereTile({ multires, ...props }) {
11042
11610
 
11043
11611
  // src/tile/tile.tsx
11044
11612
  import { useFrame as useFrame12, useThree as useThree15 } from "@react-three/fiber";
11045
- import { memo as memo2, useEffect as useEffect22, useMemo as useMemo16, useRef as useRef21, useState as useState11 } from "react";
11613
+ import { memo as memo2, useEffect as useEffect22, useMemo as useMemo16, useRef as useRef22, useState as useState11 } from "react";
11046
11614
  import {
11047
11615
  Frustum as Frustum2,
11048
11616
  Matrix4 as Matrix43,
@@ -11055,6 +11623,7 @@ import {
11055
11623
  import {
11056
11624
  Box3 as Box32,
11057
11625
  Euler as Euler4,
11626
+ MathUtils as MathUtils7,
11058
11627
  Quaternion as Quaternion2,
11059
11628
  Vector3 as Vector312
11060
11629
  } from "three";
@@ -11149,8 +11718,19 @@ function makeTileAddress(face, level, faceSize, tileSize, col, row) {
11149
11718
  function tileKey(address) {
11150
11719
  return `${address.level}/${address.face}/${address.row}/${address.col}`;
11151
11720
  }
11152
- function getPreferredLevel(levels, viewportHeight, pixelRatio, verticalFov, currentLevel) {
11153
- const requiredFaceSize = viewportHeight * pixelRatio * (90 / Math.max(verticalFov, 1));
11721
+ function tanHalfFov2(verticalFov) {
11722
+ const fov = Math.min(179, Math.max(1, verticalFov));
11723
+ return Math.tan(MathUtils7.degToRad(fov) * 0.5);
11724
+ }
11725
+ function getPreferredLevel(levels, viewportHeight, pixelRatio, verticalFov, currentLevel, viewportWidth = viewportHeight) {
11726
+ const height = Math.max(1, viewportHeight) * pixelRatio;
11727
+ const width = Math.max(1, viewportWidth) * pixelRatio;
11728
+ const tanHalfV = tanHalfFov2(verticalFov);
11729
+ const requiredFaceSize = Math.max(
11730
+ height / tanHalfV,
11731
+ height * tanHalfV,
11732
+ width * width * tanHalfV / height
11733
+ );
11154
11734
  let preferredLevel = levels.length;
11155
11735
  for (let index = 0; index < levels.length; index += 1) {
11156
11736
  if (levels[index] >= requiredFaceSize) {
@@ -11481,9 +12061,9 @@ function Tile({
11481
12061
  onPreviewError
11482
12062
  );
11483
12063
  const sharedManager = useSharedTileTextureManager();
11484
- const progressRef = useRef21(onLoadProgress);
11485
- const errorRef = useRef21(onTileError);
11486
- const levelChangeRef = useRef21(onLevelChange);
12064
+ const progressRef = useRef22(onLoadProgress);
12065
+ const errorRef = useRef22(onTileError);
12066
+ const levelChangeRef = useRef22(onLevelChange);
11487
12067
  progressRef.current = onLoadProgress;
11488
12068
  errorRef.current = onTileError;
11489
12069
  levelChangeRef.current = onLevelChange;
@@ -11518,7 +12098,7 @@ function Tile({
11518
12098
  queueMicrotask(() => manager.releaseUnused());
11519
12099
  };
11520
12100
  }, [manager, ownedManager]);
11521
- const readyRef = useRef21(false);
12101
+ const readyRef = useRef22(false);
11522
12102
  useEffect22(() => {
11523
12103
  readyRef.current = false;
11524
12104
  }, [effectivePreviewUrl]);
@@ -11538,21 +12118,21 @@ function Tile({
11538
12118
  [baseUrl, resolveTileUrl, template]
11539
12119
  );
11540
12120
  const [layers, setLayers] = useState11([]);
11541
- const layerTokenRef = useRef21("");
11542
- const currentLevelRef = useRef21(1);
11543
- const lastUpdateAtRef = useRef21(-Infinity);
11544
- const visibilityDirtyRef = useRef21(true);
11545
- const lastVisibilityQuaternionRef = useRef21(camera.quaternion.clone());
11546
- const lastVisibilityFovRef = useRef21(Number.NaN);
11547
- const lastViewportRef = useRef21({ width: 0, height: 0, dpr: 0 });
11548
- const frustumRef = useRef21(new Frustum2());
11549
- const projectionMatrixRef = useRef21(new Matrix43());
11550
- const visibilityCameraRef = useRef21(new PerspectiveCamera5());
11551
- const trackedLoadModeRef = useRef21(loadMode);
11552
- const targetKeysRef = useRef21([]);
11553
- const detailTokenRef = useRef21("");
12121
+ const layerTokenRef = useRef22("");
12122
+ const currentLevelRef = useRef22(1);
12123
+ const lastUpdateAtRef = useRef22(-Infinity);
12124
+ const visibilityDirtyRef = useRef22(true);
12125
+ const lastVisibilityQuaternionRef = useRef22(camera.quaternion.clone());
12126
+ const lastVisibilityFovRef = useRef22(Number.NaN);
12127
+ const lastViewportRef = useRef22({ width: 0, height: 0, dpr: 0 });
12128
+ const frustumRef = useRef22(new Frustum2());
12129
+ const projectionMatrixRef = useRef22(new Matrix43());
12130
+ const visibilityCameraRef = useRef22(new PerspectiveCamera5());
12131
+ const trackedLoadModeRef = useRef22(loadMode);
12132
+ const targetKeysRef = useRef22([]);
12133
+ const detailTokenRef = useRef22("");
11554
12134
  const detailReporter = useSceneDetailReporter();
11555
- const detailReporterRef = useRef21(detailReporter);
12135
+ const detailReporterRef = useRef22(detailReporter);
11556
12136
  detailReporterRef.current = detailReporter;
11557
12137
  useEffect22(() => {
11558
12138
  currentLevelRef.current = 1;
@@ -11610,7 +12190,8 @@ function Tile({
11610
12190
  size.height,
11611
12191
  pixelRatio,
11612
12192
  camera.fov,
11613
- currentLevelRef.current
12193
+ currentLevelRef.current,
12194
+ size.width
11614
12195
  );
11615
12196
  if (targetLevel !== currentLevelRef.current) {
11616
12197
  currentLevelRef.current = targetLevel;
@@ -11736,8 +12317,8 @@ function SnapshotCapture({
11736
12317
  onCaptured
11737
12318
  }) {
11738
12319
  const { camera, gl, scene, size } = useThree16();
11739
- const capturedRequestRef = useRef22(0);
11740
- const renderTargetRef = useRef22(null);
12320
+ const capturedRequestRef = useRef23(0);
12321
+ const renderTargetRef = useRef23(null);
11741
12322
  useEffect23(
11742
12323
  () => () => {
11743
12324
  renderTargetRef.current?.dispose();
@@ -11761,8 +12342,8 @@ function SnapshotCapture({
11761
12342
  });
11762
12343
  nextTarget.texture.colorSpace = LinearSRGBColorSpace;
11763
12344
  nextTarget.texture.generateMipmaps = false;
11764
- nextTarget.texture.minFilter = LinearFilter4;
11765
- nextTarget.texture.magFilter = LinearFilter4;
12345
+ nextTarget.texture.minFilter = LinearFilter3;
12346
+ nextTarget.texture.magFilter = LinearFilter3;
11766
12347
  const previousTarget = gl.getRenderTarget();
11767
12348
  gl.setRenderTarget(nextTarget);
11768
12349
  gl.clear();
@@ -11819,20 +12400,20 @@ function ScenesController({
11819
12400
  const [transitionRunId, setTransitionRunId] = useState12(0);
11820
12401
  const [snapshot, setSnapshot] = useState12(null);
11821
12402
  const [recoveryKey, setRecoveryKey] = useState12(0);
11822
- const requestedSceneIdRef = useRef22(activeSceneId);
11823
- const captureReasonRef = useRef22("start");
11824
- const interactionReleaseRef = useRef22(null);
11825
- const slotsRef = useRef22(slots);
11826
- const phaseRef = useRef22(phase);
11827
- const liveSlotRef = useRef22(liveSlot);
11828
- const transitionFromRef = useRef22(initialScene);
11829
- const readySceneRef = useRef22(null);
11830
- const waitRef = useRef22(null);
11831
- const waitTimerRef = useRef22(null);
11832
- const detailGenerationRef = useRef22(0);
12403
+ const requestedSceneIdRef = useRef23(activeSceneId);
12404
+ const captureReasonRef = useRef23("start");
12405
+ const interactionReleaseRef = useRef23(null);
12406
+ const slotsRef = useRef23(slots);
12407
+ const phaseRef = useRef23(phase);
12408
+ const liveSlotRef = useRef23(liveSlot);
12409
+ const transitionFromRef = useRef23(initialScene);
12410
+ const readySceneRef = useRef23(null);
12411
+ const waitRef = useRef23(null);
12412
+ const waitTimerRef = useRef23(null);
12413
+ const detailGenerationRef = useRef23(0);
11833
12414
  const [detailGeneration, setDetailGeneration] = useState12(0);
11834
- const mountedRef = useRef22(true);
11835
- const waitForDetailMsRef = useRef22(0);
12415
+ const mountedRef = useRef23(true);
12416
+ const waitForDetailMsRef = useRef23(0);
11836
12417
  waitForDetailMsRef.current = normalizeTransitionWaitMs(transitionWaitForDetailMs);
11837
12418
  slotsRef.current = slots;
11838
12419
  phaseRef.current = phase;
@@ -11847,14 +12428,14 @@ function ScenesController({
11847
12428
  useEffect23(() => () => {
11848
12429
  controlsRef?.current?.setSceneViewLimits(void 0);
11849
12430
  }, [controlsRef]);
11850
- const releaseInteraction = useCallback8(() => {
12431
+ const releaseInteraction = useCallback9(() => {
11851
12432
  interactionReleaseRef.current?.();
11852
12433
  interactionReleaseRef.current = null;
11853
12434
  }, []);
11854
- const reportError = useCallback8((sceneId, error) => {
12435
+ const reportError = useCallback9((sceneId, error) => {
11855
12436
  onTransitionError?.({ sceneId, error });
11856
12437
  }, [onTransitionError]);
11857
- const clearDetailWait = useCallback8((bumpGeneration) => {
12438
+ const clearDetailWait = useCallback9((bumpGeneration) => {
11858
12439
  if (waitTimerRef.current !== null) {
11859
12440
  clearTimeout(waitTimerRef.current);
11860
12441
  waitTimerRef.current = null;
@@ -11882,7 +12463,7 @@ function ScenesController({
11882
12463
  clearDetailWait(false);
11883
12464
  };
11884
12465
  }, [clearDetailWait]);
11885
- const startPreload = useCallback8((scene) => {
12466
+ const startPreload = useCallback9((scene) => {
11886
12467
  clearDetailWait(true);
11887
12468
  const nextSlot = 1 - liveSlotRef.current;
11888
12469
  requestedSceneIdRef.current = scene.id;
@@ -11920,13 +12501,13 @@ function ScenesController({
11920
12501
  }
11921
12502
  startPreload(requested);
11922
12503
  }, [activeSceneId, clearDetailWait, pendingSlot, reportError, sceneMap, startPreload]);
11923
- const notifySceneReady = useCallback8((scene) => {
12504
+ const notifySceneReady = useCallback9((scene) => {
11924
12505
  if (!scene || readySceneRef.current === scene) return;
11925
12506
  readySceneRef.current = scene;
11926
12507
  controlsRef?.current?.setSceneViewLimits(sceneMap.get(scene.id)?.viewLimits);
11927
12508
  onSceneReady?.(scene.id);
11928
12509
  }, [controlsRef, onSceneReady, sceneMap]);
11929
- const handleReady = useCallback8((slot) => {
12510
+ const handleReady = useCallback9((slot) => {
11930
12511
  if (slot === liveSlotRef.current && pendingSlot === null && phaseRef.current === "idle") {
11931
12512
  notifySceneReady(slotsRef.current[slot]);
11932
12513
  return;
@@ -11962,7 +12543,7 @@ function ScenesController({
11962
12543
  setPhase("capturing");
11963
12544
  setCaptureRequestId((current) => current + 1);
11964
12545
  }, [controlsRef, notifySceneReady, onTransitionEnd, pendingSlot, transition.preset]);
11965
- const finishTransition = useCallback8(() => {
12546
+ const finishTransition = useCallback9(() => {
11966
12547
  const nextSlot = pendingSlot;
11967
12548
  const previousScene = transitionFromRef.current;
11968
12549
  const nextScene = nextSlot === null ? null : slotsRef.current[nextSlot];
@@ -11987,11 +12568,11 @@ function ScenesController({
11987
12568
  });
11988
12569
  }
11989
12570
  }, [onTransitionEnd, pendingSlot, releaseInteraction, snapshot, transition.preset]);
11990
- const finishTransitionRef = useRef22(finishTransition);
12571
+ const finishTransitionRef = useRef23(finishTransition);
11991
12572
  finishTransitionRef.current = finishTransition;
11992
- const transitionRef = useRef22(transition);
12573
+ const transitionRef = useRef23(transition);
11993
12574
  transitionRef.current = transition;
11994
- const consumeDetailSignal = useCallback8((signal) => {
12575
+ const consumeDetailSignal = useCallback9((signal) => {
11995
12576
  const decision = acceptDetailWait(waitRef.current, signal);
11996
12577
  waitRef.current = decision.wait;
11997
12578
  if (!decision.start) {
@@ -12011,7 +12592,7 @@ function ScenesController({
12011
12592
  setPhase("transitioning");
12012
12593
  setTransitionRunId((current) => current + 1);
12013
12594
  }, []);
12014
- const handleCaptured = useCallback8((nextSnapshot) => {
12595
+ const handleCaptured = useCallback9((nextSnapshot) => {
12015
12596
  if (captureReasonRef.current === "interrupt") {
12016
12597
  setSnapshot((current) => {
12017
12598
  current?.dispose();
@@ -12109,7 +12690,7 @@ function ScenesController({
12109
12690
  startPreload,
12110
12691
  transition.preset
12111
12692
  ]);
12112
- const snapshotRef = useRef22(snapshot);
12693
+ const snapshotRef = useRef23(snapshot);
12113
12694
  snapshotRef.current = snapshot;
12114
12695
  useEffect23(() => () => {
12115
12696
  releaseInteraction();
@@ -12162,7 +12743,7 @@ ${slots[1]?.id ?? ""}`;
12162
12743
  }
12163
12744
  }));
12164
12745
  }, [consumeDetailSignal, detailGeneration, slotSceneKey]);
12165
- return /* @__PURE__ */ jsxs13(Fragment4, { children: [
12746
+ return /* @__PURE__ */ jsxs13(Fragment6, { children: [
12166
12747
  /* @__PURE__ */ jsx23(
12167
12748
  SnapshotCapture,
12168
12749
  {
@@ -12225,7 +12806,7 @@ function Scenes({
12225
12806
  () => controlledId ?? defaultActiveSceneId ?? scenes[0]?.id ?? ""
12226
12807
  );
12227
12808
  const resolvedId = isControlled ? controlledId : internalId;
12228
- const requestSceneChange = useCallback8(
12809
+ const requestSceneChange = useCallback9(
12229
12810
  (id) => {
12230
12811
  if (isControlled) {
12231
12812
  onActiveSceneIdChange?.(id);
@@ -12235,12 +12816,12 @@ function Scenes({
12235
12816
  },
12236
12817
  [isControlled, onActiveSceneIdChange]
12237
12818
  );
12238
- const scenesRef = useRef22(scenes);
12819
+ const scenesRef = useRef23(scenes);
12239
12820
  scenesRef.current = scenes;
12240
- const resolvedIdRef = useRef22(resolvedId);
12821
+ const resolvedIdRef = useRef23(resolvedId);
12241
12822
  resolvedIdRef.current = resolvedId;
12242
12823
  const host = useContext19(ScenesHostContext);
12243
- const phaseRef = useRef22("idle");
12824
+ const phaseRef = useRef23("idle");
12244
12825
  useEffect23(() => {
12245
12826
  if (!host) {
12246
12827
  return;
@@ -12311,12 +12892,12 @@ function Scenes({
12311
12892
 
12312
12893
  // src/hotspot/image-hotspot.tsx
12313
12894
  import { useThree as useThree20 } from "@react-three/fiber";
12314
- import { useEffect as useEffect26, useRef as useRef26, useState as useState15 } from "react";
12895
+ import { useEffect as useEffect26, useRef as useRef27, useState as useState15 } from "react";
12315
12896
  import {
12316
- ClampToEdgeWrapping as ClampToEdgeWrapping3,
12317
- LinearFilter as LinearFilter5,
12318
- LinearMipmapLinearFilter as LinearMipmapLinearFilter2,
12319
- SRGBColorSpace as SRGBColorSpace4,
12897
+ ClampToEdgeWrapping as ClampToEdgeWrapping2,
12898
+ LinearFilter as LinearFilter4,
12899
+ LinearMipmapLinearFilter as LinearMipmapLinearFilter3,
12900
+ SRGBColorSpace as SRGBColorSpace3,
12320
12901
  TextureLoader as TextureLoader4
12321
12902
  } from "three";
12322
12903
 
@@ -12327,13 +12908,13 @@ import {
12327
12908
  useEffect as useEffect25,
12328
12909
  useLayoutEffect as useLayoutEffect7,
12329
12910
  useMemo as useMemo19,
12330
- useRef as useRef25,
12911
+ useRef as useRef26,
12331
12912
  useState as useState14
12332
12913
  } from "react";
12333
12914
  import {
12334
12915
  Box3 as Box34,
12335
12916
  EdgesGeometry,
12336
- MathUtils as MathUtils9,
12917
+ MathUtils as MathUtils10,
12337
12918
  Matrix4 as Matrix44,
12338
12919
  PerspectiveCamera as PerspectiveCamera8,
12339
12920
  Plane,
@@ -12441,12 +13022,12 @@ import { useFrame as useFrame14, useThree as useThree17 } from "@react-three/fib
12441
13022
  import {
12442
13023
  useContext as useContext20,
12443
13024
  useLayoutEffect as useLayoutEffect5,
12444
- useRef as useRef23,
13025
+ useRef as useRef24,
12445
13026
  useSyncExternalStore as useSyncExternalStore9
12446
13027
  } from "react";
12447
13028
  import { createRoot } from "react-dom/client";
12448
13029
  import { Vector3 as Vector314 } from "three";
12449
- import { Fragment as Fragment5, jsx as jsx24, jsxs as jsxs14 } from "react/jsx-runtime";
13030
+ import { Fragment as Fragment7, jsx as jsx24, jsxs as jsxs14 } from "react/jsx-runtime";
12450
13031
  function useFallbackStereoView() {
12451
13032
  const runtime = useContext20(WebVRRuntimeContext);
12452
13033
  const host = runtime?.host ?? null;
@@ -12466,9 +13047,9 @@ function ProjectedHtml({
12466
13047
  eye
12467
13048
  }) {
12468
13049
  const { camera, gl, events, size } = useThree17();
12469
- const group = useRef23(null);
12470
- const overlay = useRef23(null);
12471
- const scratch = useRef23(new Vector314());
13050
+ const group = useRef24(null);
13051
+ const overlay = useRef24(null);
13052
+ const scratch = useRef24(new Vector314());
12472
13053
  const target = events.connected || gl.domElement.parentElement;
12473
13054
  const updatePosition = () => {
12474
13055
  const anchor = group.current;
@@ -12543,7 +13124,7 @@ function PanoHtml(props) {
12543
13124
  if (mode === "mobilevr" && portrait) {
12544
13125
  return null;
12545
13126
  }
12546
- return stereoView ? /* @__PURE__ */ jsxs14(Fragment5, { children: [
13127
+ return stereoView ? /* @__PURE__ */ jsxs14(Fragment7, { children: [
12547
13128
  /* @__PURE__ */ jsx24(ProjectedHtml, { ...props, stereoView, eye: "left" }),
12548
13129
  /* @__PURE__ */ jsx24(ProjectedHtml, { ...props, stereoView, eye: "right" })
12549
13130
  ] }) : /* @__PURE__ */ jsx24(ProjectedHtml, { ...props });
@@ -12680,7 +13261,7 @@ function HotspotTooltip({
12680
13261
 
12681
13262
  // src/hotspot/transform.ts
12682
13263
  import {
12683
- MathUtils as MathUtils7,
13264
+ MathUtils as MathUtils8,
12684
13265
  PerspectiveCamera as PerspectiveCamera6,
12685
13266
  Vector3 as Vector315
12686
13267
  } from "three";
@@ -12692,7 +13273,7 @@ function applyHotspotTransform(group, camera, orientation2, surfaceQuaternion3,
12692
13273
  } else {
12693
13274
  group.quaternion.copy(surfaceQuaternion3);
12694
13275
  }
12695
- group.rotateOnAxis(LOCAL_FORWARD, MathUtils7.degToRad(-rotation));
13276
+ group.rotateOnAxis(LOCAL_FORWARD, MathUtils8.degToRad(-rotation));
12696
13277
  let scaleFactor = scaleMode === "fixed" && camera instanceof PerspectiveCamera6 ? fixedScaleFactor(camera.fov, referenceFov) : 1;
12697
13278
  if (scaleMode === "fixed" && orientation2 === "billboard" && useAngularScale && camera instanceof PerspectiveCamera6) {
12698
13279
  group.updateWorldMatrix(true, false);
@@ -12709,15 +13290,15 @@ function applyHotspotTransform(group, camera, orientation2, surfaceQuaternion3,
12709
13290
  group.updateMatrixWorld();
12710
13291
  }
12711
13292
  function fixedScaleFactor(cameraFov, referenceFov) {
12712
- const safeReferenceFov = MathUtils7.clamp(referenceFov, 1, 179);
12713
- return Math.tan(MathUtils7.degToRad(cameraFov) / 2) / Math.tan(MathUtils7.degToRad(safeReferenceFov) / 2);
13293
+ const safeReferenceFov = MathUtils8.clamp(referenceFov, 1, 179);
13294
+ return Math.tan(MathUtils8.degToRad(cameraFov) / 2) / Math.tan(MathUtils8.degToRad(safeReferenceFov) / 2);
12714
13295
  }
12715
13296
  function selectionFrameScale(worldScaleX, worldScaleY, cameraSpaceDepth, fovDegrees, viewportHeight, outsetPixels) {
12716
13297
  const pixels = Number.isFinite(outsetPixels) && outsetPixels > 0 ? outsetPixels : 0;
12717
13298
  const depth = Number.isFinite(cameraSpaceDepth) ? Math.max(cameraSpaceDepth, 1e-4) : 1e-4;
12718
13299
  const height = Number.isFinite(viewportHeight) ? Math.max(viewportHeight, 1) : 1;
12719
- const fov = Number.isFinite(fovDegrees) ? MathUtils7.clamp(fovDegrees, 1, 179) : 75;
12720
- const pixelsPerWorldUnit = height / (2 * Math.tan(MathUtils7.degToRad(fov) * 0.5) * depth);
13300
+ const fov = Number.isFinite(fovDegrees) ? MathUtils8.clamp(fovDegrees, 1, 179) : 75;
13301
+ const pixelsPerWorldUnit = height / (2 * Math.tan(MathUtils8.degToRad(fov) * 0.5) * depth);
12721
13302
  const axis = (worldScale) => {
12722
13303
  const pixelsPerLocalUnit = pixelsPerWorldUnit * Math.abs(worldScale);
12723
13304
  if (pixelsPerLocalUnit < 1e-4) {
@@ -12731,9 +13312,9 @@ function selectionFrameScale(worldScaleX, worldScaleY, cameraSpaceDepth, fovDegr
12731
13312
  // src/hotspot/hotspot-stroke-line.tsx
12732
13313
  import { Line } from "@react-three/drei";
12733
13314
  import { useFrame as useFrame15, useThree as useThree18 } from "@react-three/fiber";
12734
- import { useLayoutEffect as useLayoutEffect6, useRef as useRef24 } from "react";
13315
+ import { useLayoutEffect as useLayoutEffect6, useRef as useRef25 } from "react";
12735
13316
  import {
12736
- MathUtils as MathUtils8,
13317
+ MathUtils as MathUtils9,
12737
13318
  PerspectiveCamera as PerspectiveCamera7,
12738
13319
  Vector3 as Vector316
12739
13320
  } from "three";
@@ -12747,7 +13328,7 @@ function applyScreenSpaceDashScale(object, camera, viewportHeight) {
12747
13328
  object.getWorldPosition(LINE_WORLD_POSITION);
12748
13329
  const depth = Math.max(camera.position.distanceTo(LINE_WORLD_POSITION), 1);
12749
13330
  const fov = camera instanceof PerspectiveCamera7 ? camera.fov : 75;
12750
- material.dashScale = viewportHeight / (2 * Math.tan(MathUtils8.degToRad(fov) * 0.5) * depth);
13331
+ material.dashScale = viewportHeight / (2 * Math.tan(MathUtils9.degToRad(fov) * 0.5) * depth);
12751
13332
  }
12752
13333
  function resolveDash(dashSize, gapSize) {
12753
13334
  if (dashSize === void 0 || !Number.isFinite(dashSize) || dashSize <= 0) {
@@ -12768,7 +13349,7 @@ function HotspotStrokeLine({
12768
13349
  dashSize,
12769
13350
  gapSize
12770
13351
  }) {
12771
- const lineRef = useRef24(null);
13352
+ const lineRef = useRef25(null);
12772
13353
  const { camera, size } = useThree18();
12773
13354
  const dash = resolveDash(dashSize, gapSize);
12774
13355
  useLayoutEffect6(() => {
@@ -12844,8 +13425,8 @@ var HOTSPOT_MODE_RENDERING = {
12844
13425
  billboard: { orientation: "billboard", placement: "floating" }
12845
13426
  };
12846
13427
  function angularSizeToWorldSize(size, radius) {
12847
- const safeSize = Number.isFinite(size) ? MathUtils9.clamp(Math.abs(size), MIN_ANGULAR_SIZE, MAX_ANGULAR_SIZE) : MIN_ANGULAR_SIZE;
12848
- return 2 * radius * Math.tan(MathUtils9.degToRad(safeSize) / 2);
13428
+ const safeSize = Number.isFinite(size) ? MathUtils10.clamp(Math.abs(size), MIN_ANGULAR_SIZE, MAX_ANGULAR_SIZE) : MIN_ANGULAR_SIZE;
13429
+ return 2 * radius * Math.tan(MathUtils10.degToRad(safeSize) / 2);
12849
13430
  }
12850
13431
  function resolveScale(scale) {
12851
13432
  return Number.isFinite(scale) && scale > 0 ? scale : 1;
@@ -12858,31 +13439,31 @@ function resolveDistance(placement, distance, width, height) {
12858
13439
  if (!Number.isFinite(distance)) {
12859
13440
  return DEFAULT_FLOATING_DISTANCE;
12860
13441
  }
12861
- return MathUtils9.clamp(Math.abs(distance), 0.1, maximumDistance);
13442
+ return MathUtils10.clamp(Math.abs(distance), 0.1, maximumDistance);
12862
13443
  }
12863
13444
  function maximumHotspotDistance(width, height) {
12864
- const safeWidth = MathUtils9.clamp(
13445
+ const safeWidth = MathUtils10.clamp(
12865
13446
  Math.abs(Number.isFinite(width) ? width : MIN_ANGULAR_SIZE),
12866
13447
  MIN_ANGULAR_SIZE,
12867
13448
  MAX_ANGULAR_SIZE
12868
13449
  );
12869
- const safeHeight = MathUtils9.clamp(
13450
+ const safeHeight = MathUtils10.clamp(
12870
13451
  Math.abs(Number.isFinite(height) ? height : MIN_ANGULAR_SIZE),
12871
13452
  MIN_ANGULAR_SIZE,
12872
13453
  MAX_ANGULAR_SIZE
12873
13454
  );
12874
13455
  const cornerFactor = Math.hypot(
12875
13456
  1,
12876
- Math.tan(MathUtils9.degToRad(safeWidth) / 2),
12877
- Math.tan(MathUtils9.degToRad(safeHeight) / 2)
13457
+ Math.tan(MathUtils10.degToRad(safeWidth) / 2),
13458
+ Math.tan(MathUtils10.degToRad(safeHeight) / 2)
12878
13459
  );
12879
13460
  return (PANORAMA_RADIUS - PANORAMA_SURFACE_INSET) / cornerFactor;
12880
13461
  }
12881
13462
  function createSurfaceQuaternion(position) {
12882
13463
  const outward = panoPositionToVector3(position).normalize();
12883
13464
  const inward = outward.clone().negate();
12884
- const pitch = MathUtils9.degToRad(position.pitch);
12885
- const yaw = MathUtils9.degToRad(position.yaw);
13465
+ const pitch = MathUtils10.degToRad(position.pitch);
13466
+ const yaw = MathUtils10.degToRad(position.yaw);
12886
13467
  const localUp = new Vector317(
12887
13468
  -Math.sin(pitch) * Math.sin(yaw),
12888
13469
  Math.cos(pitch),
@@ -12903,7 +13484,7 @@ function makeInteractionEvent(id, position, event) {
12903
13484
  function angularDistance(a, b) {
12904
13485
  const aVector = panoPositionToVector3(a);
12905
13486
  const bVector = panoPositionToVector3(b);
12906
- return MathUtils9.radToDeg(aVector.angleTo(bVector));
13487
+ return MathUtils10.radToDeg(aVector.angleTo(bVector));
12907
13488
  }
12908
13489
  var DRAG_POINTER = new Vector24();
12909
13490
  var DRAG_RAYCASTER = new Raycaster2();
@@ -13186,13 +13767,13 @@ function HotspotAnchor({
13186
13767
  const eventBus = useContext21(PanoEventBusContext);
13187
13768
  const cursorApi = usePanoCursor();
13188
13769
  const { camera, gl, size } = useThree19();
13189
- const groupRef = useRef25(null);
13190
- const selectionFrameRef = useRef25(null);
13191
- const tooltipAnchorRef = useRef25(null);
13192
- const dragStateRef = useRef25(null);
13193
- const updateDragRef = useRef25(() => void 0);
13194
- const endDragRef = useRef25(() => void 0);
13195
- const suppressNextClickRef = useRef25(false);
13770
+ const groupRef = useRef26(null);
13771
+ const selectionFrameRef = useRef26(null);
13772
+ const tooltipAnchorRef = useRef26(null);
13773
+ const dragStateRef = useRef26(null);
13774
+ const updateDragRef = useRef26(() => void 0);
13775
+ const endDragRef = useRef26(() => void 0);
13776
+ const suppressNextClickRef = useRef26(false);
13196
13777
  const [hovered, setHovered] = useState14(false);
13197
13778
  const [dragging, setDragging] = useState14(false);
13198
13779
  const [pinned, setPinned] = useState14(false);
@@ -13234,7 +13815,7 @@ function HotspotAnchor({
13234
13815
  planeGeometry.dispose();
13235
13816
  return geometry;
13236
13817
  }, []);
13237
- const eventHovered = useRef25(false);
13818
+ const eventHovered = useRef26(false);
13238
13819
  useEffect25(() => {
13239
13820
  if (!visible || !interactive || pointerEvents === "none") eventHovered.current = false;
13240
13821
  }, [visible, interactive, pointerEvents]);
@@ -13752,8 +14333,8 @@ import { jsx as jsx29 } from "react/jsx-runtime";
13752
14333
  function useHotspotImageTexture(src, onLoad, onError) {
13753
14334
  const gl = useThree20((state) => state.gl);
13754
14335
  const [texture, setTexture] = useState15(null);
13755
- const onLoadRef = useRef26(onLoad);
13756
- const onErrorRef = useRef26(onError);
14336
+ const onLoadRef = useRef27(onLoad);
14337
+ const onErrorRef = useRef27(onError);
13757
14338
  onLoadRef.current = onLoad;
13758
14339
  onErrorRef.current = onError;
13759
14340
  useEffect26(() => {
@@ -13772,11 +14353,11 @@ function useHotspotImageTexture(src, onLoad, onError) {
13772
14353
  loadedTexture.dispose();
13773
14354
  return;
13774
14355
  }
13775
- loadedTexture.colorSpace = SRGBColorSpace4;
13776
- loadedTexture.wrapS = ClampToEdgeWrapping3;
13777
- loadedTexture.wrapT = ClampToEdgeWrapping3;
13778
- loadedTexture.magFilter = LinearFilter5;
13779
- loadedTexture.minFilter = LinearMipmapLinearFilter2;
14356
+ loadedTexture.colorSpace = SRGBColorSpace3;
14357
+ loadedTexture.wrapS = ClampToEdgeWrapping2;
14358
+ loadedTexture.wrapT = ClampToEdgeWrapping2;
14359
+ loadedTexture.magFilter = LinearFilter4;
14360
+ loadedTexture.minFilter = LinearMipmapLinearFilter3;
13780
14361
  loadedTexture.generateMipmaps = true;
13781
14362
  loadedTexture.anisotropy = gl.capabilities.getMaxAnisotropy();
13782
14363
  loadedTexture.needsUpdate = true;
@@ -13816,13 +14397,13 @@ function ImageHotspot({
13816
14397
  }
13817
14398
 
13818
14399
  // src/hotspot/graphic-hotspot.tsx
13819
- import { useEffect as useEffect27, useRef as useRef27, useState as useState16 } from "react";
14400
+ import { useEffect as useEffect27, useRef as useRef28, useState as useState16 } from "react";
13820
14401
  import {
13821
14402
  CanvasTexture,
13822
- ClampToEdgeWrapping as ClampToEdgeWrapping4,
13823
- LinearFilter as LinearFilter6,
13824
- LinearMipmapLinearFilter as LinearMipmapLinearFilter3,
13825
- SRGBColorSpace as SRGBColorSpace5
14403
+ ClampToEdgeWrapping as ClampToEdgeWrapping3,
14404
+ LinearFilter as LinearFilter5,
14405
+ LinearMipmapLinearFilter as LinearMipmapLinearFilter4,
14406
+ SRGBColorSpace as SRGBColorSpace4
13826
14407
  } from "three";
13827
14408
  import { jsx as jsx30 } from "react/jsx-runtime";
13828
14409
  var GRAPHIC_TEXTURE_SIZE = 512;
@@ -14003,19 +14584,19 @@ function createGraphicTexture(graphic, aspectWidth, aspectHeight) {
14003
14584
  }
14004
14585
  drawGraphic(context, graphic, aspectWidth, aspectHeight);
14005
14586
  const texture = new CanvasTexture(canvas);
14006
- texture.colorSpace = SRGBColorSpace5;
14007
- texture.wrapS = ClampToEdgeWrapping4;
14008
- texture.wrapT = ClampToEdgeWrapping4;
14009
- texture.magFilter = LinearFilter6;
14010
- texture.minFilter = LinearMipmapLinearFilter3;
14587
+ texture.colorSpace = SRGBColorSpace4;
14588
+ texture.wrapS = ClampToEdgeWrapping3;
14589
+ texture.wrapT = ClampToEdgeWrapping3;
14590
+ texture.magFilter = LinearFilter5;
14591
+ texture.minFilter = LinearMipmapLinearFilter4;
14011
14592
  texture.generateMipmaps = true;
14012
14593
  texture.needsUpdate = true;
14013
14594
  return texture;
14014
14595
  }
14015
14596
  function useGraphicTexture(graphic, aspectWidth, aspectHeight, onLoad, onError) {
14016
14597
  const [texture, setTexture] = useState16(null);
14017
- const onLoadRef = useRef27(onLoad);
14018
- const onErrorRef = useRef27(onError);
14598
+ const onLoadRef = useRef28(onLoad);
14599
+ const onErrorRef = useRef28(onError);
14019
14600
  onLoadRef.current = onLoad;
14020
14601
  onErrorRef.current = onError;
14021
14602
  useEffect27(() => {
@@ -14057,12 +14638,12 @@ function GraphicHotspot(props) {
14057
14638
 
14058
14639
  // src/hotspot/sequence-hotspot.tsx
14059
14640
  import { useFrame as useFrame17, useThree as useThree21 } from "@react-three/fiber";
14060
- import { useEffect as useEffect28, useRef as useRef28, useState as useState17 } from "react";
14641
+ import { useEffect as useEffect28, useRef as useRef29, useState as useState17 } from "react";
14061
14642
  import {
14062
- ClampToEdgeWrapping as ClampToEdgeWrapping5,
14063
- LinearFilter as LinearFilter7,
14064
- LinearMipmapLinearFilter as LinearMipmapLinearFilter4,
14065
- SRGBColorSpace as SRGBColorSpace6,
14643
+ ClampToEdgeWrapping as ClampToEdgeWrapping4,
14644
+ LinearFilter as LinearFilter6,
14645
+ LinearMipmapLinearFilter as LinearMipmapLinearFilter5,
14646
+ SRGBColorSpace as SRGBColorSpace5,
14066
14647
  TextureLoader as TextureLoader5
14067
14648
  } from "three";
14068
14649
  import { jsx as jsx31 } from "react/jsx-runtime";
@@ -14081,8 +14662,8 @@ function useSpriteTexture({
14081
14662
  }) {
14082
14663
  const gl = useThree21((state) => state.gl);
14083
14664
  const [texture, setTexture] = useState17(null);
14084
- const onErrorRef = useRef28(onError);
14085
- const onLoadProgressRef = useRef28(onLoadProgress);
14665
+ const onErrorRef = useRef29(onError);
14666
+ const onLoadProgressRef = useRef29(onLoadProgress);
14086
14667
  onErrorRef.current = onError;
14087
14668
  onLoadProgressRef.current = onLoadProgress;
14088
14669
  useEffect28(() => {
@@ -14102,11 +14683,11 @@ function useSpriteTexture({
14102
14683
  loadedTexture.dispose();
14103
14684
  return;
14104
14685
  }
14105
- loadedTexture.colorSpace = SRGBColorSpace6;
14106
- loadedTexture.wrapS = ClampToEdgeWrapping5;
14107
- loadedTexture.wrapT = ClampToEdgeWrapping5;
14108
- loadedTexture.magFilter = LinearFilter7;
14109
- loadedTexture.minFilter = LinearMipmapLinearFilter4;
14686
+ loadedTexture.colorSpace = SRGBColorSpace5;
14687
+ loadedTexture.wrapS = ClampToEdgeWrapping4;
14688
+ loadedTexture.wrapT = ClampToEdgeWrapping4;
14689
+ loadedTexture.magFilter = LinearFilter6;
14690
+ loadedTexture.minFilter = LinearMipmapLinearFilter5;
14110
14691
  loadedTexture.generateMipmaps = true;
14111
14692
  loadedTexture.anisotropy = gl.capabilities.getMaxAnisotropy();
14112
14693
  loadedTexture.needsUpdate = true;
@@ -14156,13 +14737,13 @@ function SequenceHotspot({
14156
14737
  const frameCount = resolveFrameCount(requestedFrameCount);
14157
14738
  const safeFps = resolveFps(fps);
14158
14739
  const [frameIndex, setFrameIndex] = useState17(0);
14159
- const elapsedRef = useRef28(0);
14160
- const frameIndexRef = useRef28(0);
14161
- const endedRef = useRef28(false);
14162
- const lastPlaybackStateRef = useRef28(null);
14163
- const onFrameChangeRef = useRef28(onFrameChange);
14164
- const onPlaybackStateChangeRef = useRef28(onPlaybackStateChange);
14165
- const onEndedRef = useRef28(onEnded);
14740
+ const elapsedRef = useRef29(0);
14741
+ const frameIndexRef = useRef29(0);
14742
+ const endedRef = useRef29(false);
14743
+ const lastPlaybackStateRef = useRef29(null);
14744
+ const onFrameChangeRef = useRef29(onFrameChange);
14745
+ const onPlaybackStateChangeRef = useRef29(onPlaybackStateChange);
14746
+ const onEndedRef = useRef29(onEnded);
14166
14747
  onFrameChangeRef.current = onFrameChange;
14167
14748
  onPlaybackStateChangeRef.current = onPlaybackStateChange;
14168
14749
  onEndedRef.current = onEnded;
@@ -14246,12 +14827,12 @@ function SequenceHotspot({
14246
14827
 
14247
14828
  // src/hotspot/video-hotspot.tsx
14248
14829
  import { useThree as useThree22 } from "@react-three/fiber";
14249
- import { useEffect as useEffect29, useLayoutEffect as useLayoutEffect8, useRef as useRef29, useState as useState18 } from "react";
14830
+ import { useEffect as useEffect29, useLayoutEffect as useLayoutEffect8, useRef as useRef30, useState as useState18 } from "react";
14250
14831
  import {
14251
- ClampToEdgeWrapping as ClampToEdgeWrapping6,
14252
- LinearFilter as LinearFilter8,
14253
- LinearMipmapLinearFilter as LinearMipmapLinearFilter5,
14254
- SRGBColorSpace as SRGBColorSpace7,
14832
+ ClampToEdgeWrapping as ClampToEdgeWrapping5,
14833
+ LinearFilter as LinearFilter7,
14834
+ LinearMipmapLinearFilter as LinearMipmapLinearFilter6,
14835
+ SRGBColorSpace as SRGBColorSpace6,
14255
14836
  TextureLoader as TextureLoader6,
14256
14837
  VideoTexture
14257
14838
  } from "three";
@@ -14317,7 +14898,7 @@ function clampVolume2(volume) {
14317
14898
  function usePosterTexture(id, poster, onError) {
14318
14899
  const gl = useThree22((state) => state.gl);
14319
14900
  const [texture, setTexture] = useState18(null);
14320
- const onErrorRef = useRef29(onError);
14901
+ const onErrorRef = useRef30(onError);
14321
14902
  onErrorRef.current = onError;
14322
14903
  useEffect29(() => {
14323
14904
  let active = true;
@@ -14335,11 +14916,11 @@ function usePosterTexture(id, poster, onError) {
14335
14916
  loadedTexture.dispose();
14336
14917
  return;
14337
14918
  }
14338
- loadedTexture.colorSpace = SRGBColorSpace7;
14339
- loadedTexture.wrapS = ClampToEdgeWrapping6;
14340
- loadedTexture.wrapT = ClampToEdgeWrapping6;
14341
- loadedTexture.magFilter = LinearFilter8;
14342
- loadedTexture.minFilter = LinearMipmapLinearFilter5;
14919
+ loadedTexture.colorSpace = SRGBColorSpace6;
14920
+ loadedTexture.wrapS = ClampToEdgeWrapping5;
14921
+ loadedTexture.wrapT = ClampToEdgeWrapping5;
14922
+ loadedTexture.magFilter = LinearFilter7;
14923
+ loadedTexture.minFilter = LinearMipmapLinearFilter6;
14343
14924
  loadedTexture.generateMipmaps = true;
14344
14925
  loadedTexture.anisotropy = gl.capabilities.getMaxAnisotropy();
14345
14926
  loadedTexture.needsUpdate = true;
@@ -14376,9 +14957,9 @@ function useVideoResource({
14376
14957
  }) {
14377
14958
  const gl = useThree22((state) => state.gl);
14378
14959
  const [resource, setResource] = useState18(null);
14379
- const onEndedRef = useRef29(onEnded);
14380
- const onErrorRef = useRef29(onError);
14381
- const onPlaybackStateChangeRef = useRef29(onPlaybackStateChange);
14960
+ const onEndedRef = useRef30(onEnded);
14961
+ const onErrorRef = useRef30(onError);
14962
+ const onPlaybackStateChangeRef = useRef30(onPlaybackStateChange);
14382
14963
  onEndedRef.current = onEnded;
14383
14964
  onErrorRef.current = onError;
14384
14965
  onPlaybackStateChangeRef.current = onPlaybackStateChange;
@@ -14395,11 +14976,11 @@ function useVideoResource({
14395
14976
  applyInlineVideoPlayback(video, playsInline ?? true);
14396
14977
  video.poster = poster ?? "";
14397
14978
  const texture = new VideoTexture(video);
14398
- texture.colorSpace = SRGBColorSpace7;
14399
- texture.wrapS = ClampToEdgeWrapping6;
14400
- texture.wrapT = ClampToEdgeWrapping6;
14401
- texture.magFilter = LinearFilter8;
14402
- texture.minFilter = LinearFilter8;
14979
+ texture.colorSpace = SRGBColorSpace6;
14980
+ texture.wrapS = ClampToEdgeWrapping5;
14981
+ texture.wrapT = ClampToEdgeWrapping5;
14982
+ texture.magFilter = LinearFilter7;
14983
+ texture.minFilter = LinearFilter7;
14403
14984
  texture.generateMipmaps = false;
14404
14985
  const notifyState = (state) => {
14405
14986
  if (active) {
@@ -14495,8 +15076,8 @@ function VideoHotspot({
14495
15076
  src
14496
15077
  });
14497
15078
  const posterTexture = usePosterTexture(id, poster, onError);
14498
- const onPlaybackErrorRef = useRef29(onPlaybackError);
14499
- const onPlaybackStateChangeRef = useRef29(onPlaybackStateChange);
15079
+ const onPlaybackErrorRef = useRef30(onPlaybackError);
15080
+ const onPlaybackStateChangeRef = useRef30(onPlaybackStateChange);
14500
15081
  onPlaybackErrorRef.current = onPlaybackError;
14501
15082
  onPlaybackStateChangeRef.current = onPlaybackStateChange;
14502
15083
  useLayoutEffect8(() => {
@@ -14537,18 +15118,18 @@ function VideoHotspot({
14537
15118
 
14538
15119
  // src/hotspot/audio-hotspot.tsx
14539
15120
  import { useFrame as useFrame18, useThree as useThree23 } from "@react-three/fiber";
14540
- import { useEffect as useEffect30, useRef as useRef30, useState as useState19 } from "react";
15121
+ import { useEffect as useEffect30, useRef as useRef31, useState as useState19 } from "react";
14541
15122
  import {
14542
15123
  CanvasTexture as CanvasTexture2,
14543
- ClampToEdgeWrapping as ClampToEdgeWrapping7,
14544
- LinearFilter as LinearFilter9,
14545
- LinearMipmapLinearFilter as LinearMipmapLinearFilter6,
14546
- SRGBColorSpace as SRGBColorSpace8,
15124
+ ClampToEdgeWrapping as ClampToEdgeWrapping6,
15125
+ LinearFilter as LinearFilter8,
15126
+ LinearMipmapLinearFilter as LinearMipmapLinearFilter7,
15127
+ SRGBColorSpace as SRGBColorSpace7,
14547
15128
  TextureLoader as TextureLoader7
14548
15129
  } from "three";
14549
15130
 
14550
15131
  // src/hotspot/audio-spatial.ts
14551
- import { MathUtils as MathUtils10, Vector3 as Vector318 } from "three";
15132
+ import { MathUtils as MathUtils11, Vector3 as Vector318 } from "three";
14552
15133
  var LOOK_DIR = new Vector318();
14553
15134
  var SOURCE_DIR = new Vector318();
14554
15135
  var RIGHT_DIR = new Vector318();
@@ -14576,11 +15157,11 @@ function lookRangeGain(angleDegrees, range) {
14576
15157
  function audioSpatialFromView(camera, position, range) {
14577
15158
  camera.getWorldDirection(LOOK_DIR);
14578
15159
  SOURCE_DIR.copy(panoPositionToVector3(position)).normalize();
14579
- const angleDegrees = MathUtils10.radToDeg(
14580
- Math.acos(MathUtils10.clamp(LOOK_DIR.dot(SOURCE_DIR), -1, 1))
15160
+ const angleDegrees = MathUtils11.radToDeg(
15161
+ Math.acos(MathUtils11.clamp(LOOK_DIR.dot(SOURCE_DIR), -1, 1))
14581
15162
  );
14582
15163
  RIGHT_DIR.set(1, 0, 0).applyQuaternion(camera.quaternion);
14583
- const pan = MathUtils10.clamp(
15164
+ const pan = MathUtils11.clamp(
14584
15165
  Math.atan2(SOURCE_DIR.dot(RIGHT_DIR), SOURCE_DIR.dot(LOOK_DIR)) / (Math.PI / 2),
14585
15166
  -1,
14586
15167
  1
@@ -14646,11 +15227,11 @@ function createSpeakerTexture(state) {
14646
15227
  }
14647
15228
  }
14648
15229
  const texture = new CanvasTexture2(canvas);
14649
- texture.colorSpace = SRGBColorSpace8;
14650
- texture.wrapS = ClampToEdgeWrapping7;
14651
- texture.wrapT = ClampToEdgeWrapping7;
14652
- texture.magFilter = LinearFilter9;
14653
- texture.minFilter = LinearMipmapLinearFilter6;
15230
+ texture.colorSpace = SRGBColorSpace7;
15231
+ texture.wrapS = ClampToEdgeWrapping6;
15232
+ texture.wrapT = ClampToEdgeWrapping6;
15233
+ texture.magFilter = LinearFilter8;
15234
+ texture.minFilter = LinearMipmapLinearFilter7;
14654
15235
  texture.generateMipmaps = true;
14655
15236
  texture.needsUpdate = true;
14656
15237
  return texture;
@@ -14675,7 +15256,7 @@ function useSpeakerTextures(enabled) {
14675
15256
  function useIconTexture(icon, enabled, onError, id) {
14676
15257
  const gl = useThree23((state) => state.gl);
14677
15258
  const [texture, setTexture] = useState19(null);
14678
- const onErrorRef = useRef30(onError);
15259
+ const onErrorRef = useRef31(onError);
14679
15260
  onErrorRef.current = onError;
14680
15261
  useEffect30(() => {
14681
15262
  let active = true;
@@ -14693,11 +15274,11 @@ function useIconTexture(icon, enabled, onError, id) {
14693
15274
  loadedTexture.dispose();
14694
15275
  return;
14695
15276
  }
14696
- loadedTexture.colorSpace = SRGBColorSpace8;
14697
- loadedTexture.wrapS = ClampToEdgeWrapping7;
14698
- loadedTexture.wrapT = ClampToEdgeWrapping7;
14699
- loadedTexture.magFilter = LinearFilter9;
14700
- loadedTexture.minFilter = LinearMipmapLinearFilter6;
15277
+ loadedTexture.colorSpace = SRGBColorSpace7;
15278
+ loadedTexture.wrapS = ClampToEdgeWrapping6;
15279
+ loadedTexture.wrapT = ClampToEdgeWrapping6;
15280
+ loadedTexture.magFilter = LinearFilter8;
15281
+ loadedTexture.minFilter = LinearMipmapLinearFilter7;
14701
15282
  loadedTexture.generateMipmaps = true;
14702
15283
  loadedTexture.anisotropy = gl.capabilities.getMaxAnisotropy();
14703
15284
  loadedTexture.needsUpdate = true;
@@ -14735,12 +15316,12 @@ function useAudioHowl({
14735
15316
  format
14736
15317
  }) {
14737
15318
  const [howl, setHowl] = useState19(null);
14738
- const playingRef = useRef30(playing);
14739
- const loopRef = useRef30(loop ?? false);
14740
- const onEndedRef = useRef30(onEnded);
14741
- const onErrorRef = useRef30(onError);
14742
- const onPlaybackErrorRef = useRef30(onPlaybackError);
14743
- const onPlaybackStateChangeRef = useRef30(onPlaybackStateChange);
15319
+ const playingRef = useRef31(playing);
15320
+ const loopRef = useRef31(loop ?? false);
15321
+ const onEndedRef = useRef31(onEnded);
15322
+ const onErrorRef = useRef31(onError);
15323
+ const onPlaybackErrorRef = useRef31(onPlaybackError);
15324
+ const onPlaybackStateChangeRef = useRef31(onPlaybackStateChange);
14744
15325
  playingRef.current = playing;
14745
15326
  loopRef.current = loop ?? false;
14746
15327
  onEndedRef.current = onEnded;
@@ -14833,11 +15414,11 @@ function AudioHotspot({
14833
15414
  const stoppedIconTexture = useIconTexture(icon, marker, onError, id);
14834
15415
  const playingIconTexture = useIconTexture(playingIcon, marker, onError, id);
14835
15416
  const markerTexture = playing ? playingIconTexture ?? stoppedIconTexture ?? speakerTextures.playing : stoppedIconTexture ?? speakerTextures.stopped;
14836
- const playingRef = useRef30(playing);
15417
+ const playingRef = useRef31(playing);
14837
15418
  playingRef.current = playing;
14838
- const mutedRef = useRef30(muted);
14839
- const volumeRef = useRef30(volume);
14840
- const rangeRef = useRef30(range);
15419
+ const mutedRef = useRef31(muted);
15420
+ const volumeRef = useRef31(volume);
15421
+ const rangeRef = useRef31(range);
14841
15422
  mutedRef.current = muted;
14842
15423
  volumeRef.current = volume;
14843
15424
  rangeRef.current = range;
@@ -14886,15 +15467,15 @@ function AudioHotspot({
14886
15467
  }
14887
15468
 
14888
15469
  // src/hotspot/text-hotspot.tsx
14889
- import { useEffect as useEffect31, useRef as useRef31, useState as useState20 } from "react";
15470
+ import { useEffect as useEffect31, useRef as useRef32, useState as useState20 } from "react";
14890
15471
  import { useFrame as useFrame19, useThree as useThree24 } from "@react-three/fiber";
14891
15472
  import {
14892
15473
  CanvasTexture as CanvasTexture3,
14893
- ClampToEdgeWrapping as ClampToEdgeWrapping8,
14894
- LinearFilter as LinearFilter10,
14895
- MathUtils as MathUtils11,
15474
+ ClampToEdgeWrapping as ClampToEdgeWrapping7,
15475
+ LinearFilter as LinearFilter9,
15476
+ MathUtils as MathUtils12,
14896
15477
  PerspectiveCamera as PerspectiveCamera9,
14897
- SRGBColorSpace as SRGBColorSpace9
15478
+ SRGBColorSpace as SRGBColorSpace8
14898
15479
  } from "three";
14899
15480
  import { jsx as jsx34 } from "react/jsx-runtime";
14900
15481
  var TEXT_TEXTURE_LONG_SIDE = 1024;
@@ -14967,7 +15548,7 @@ function projectedDevicePixels(angleDeg, fovDeg, viewportHeight) {
14967
15548
  }
14968
15549
  const fov = Math.min(179, Math.max(1, resolvePositive(fovDeg, DEFAULT_REFERENCE_FOV2)));
14969
15550
  const angle = Math.min(179, Math.max(0.01, Math.abs(angleDeg)));
14970
- return Math.tan(MathUtils11.degToRad(angle) / 2) / Math.tan(MathUtils11.degToRad(fov) / 2) * viewportHeight;
15551
+ return Math.tan(MathUtils12.degToRad(angle) / 2) / Math.tan(MathUtils12.degToRad(fov) / 2) * viewportHeight;
14971
15552
  }
14972
15553
  function snapRasterLongSide(screenLong, layoutLong) {
14973
15554
  if (!(screenLong > 0)) {
@@ -15157,11 +15738,11 @@ function createTextTexture(text, width, height, style, rasterLong) {
15157
15738
  );
15158
15739
  drawTextHotspot(context, layout.canvasWidth, layout.canvasHeight, text, style);
15159
15740
  const texture = new CanvasTexture3(canvas);
15160
- texture.colorSpace = SRGBColorSpace9;
15161
- texture.wrapS = ClampToEdgeWrapping8;
15162
- texture.wrapT = ClampToEdgeWrapping8;
15163
- texture.magFilter = LinearFilter10;
15164
- texture.minFilter = LinearFilter10;
15741
+ texture.colorSpace = SRGBColorSpace8;
15742
+ texture.wrapS = ClampToEdgeWrapping7;
15743
+ texture.wrapT = ClampToEdgeWrapping7;
15744
+ texture.magFilter = LinearFilter9;
15745
+ texture.minFilter = LinearFilter9;
15165
15746
  texture.generateMipmaps = false;
15166
15747
  texture.needsUpdate = true;
15167
15748
  return texture;
@@ -15206,8 +15787,8 @@ function useRasterLongSide(width, height, scale, scaleMode, referenceFov) {
15206
15787
  function useTextTexture(text, width, height, style, scale, scaleMode, referenceFov, onLoad, onError) {
15207
15788
  const rasterLong = useRasterLongSide(width, height, scale, scaleMode, referenceFov);
15208
15789
  const [texture, setTexture] = useState20(null);
15209
- const onLoadRef = useRef31(onLoad);
15210
- const onErrorRef = useRef31(onError);
15790
+ const onLoadRef = useRef32(onLoad);
15791
+ const onErrorRef = useRef32(onError);
15211
15792
  onLoadRef.current = onLoad;
15212
15793
  onErrorRef.current = onError;
15213
15794
  useEffect31(() => {
@@ -15436,7 +16017,7 @@ function IframeHotspot({
15436
16017
  }
15437
16018
 
15438
16019
  // src/hotspot/polygon-hotspot.tsx
15439
- import { useEffect as useEffect32, useMemo as useMemo20, useRef as useRef32 } from "react";
16020
+ import { useEffect as useEffect32, useMemo as useMemo20, useRef as useRef33 } from "react";
15440
16021
  import {
15441
16022
  BufferGeometry as BufferGeometry2,
15442
16023
  DoubleSide as DoubleSide5,
@@ -15715,7 +16296,7 @@ function PolygonHotspot({
15715
16296
  [center, valid, vertices]
15716
16297
  );
15717
16298
  const resolvedStrokeOpacity = Math.max(0, Math.min(strokeOpacity, 1));
15718
- const startVerticesRef = useRef32(null);
16299
+ const startVerticesRef = useRef33(null);
15719
16300
  const issuesKey = issues.map((issue) => issue.code).join(",");
15720
16301
  useEffect32(() => {
15721
16302
  if (!valid) onInvalid?.(issues);
@@ -15800,9 +16381,9 @@ function PolygonHotspot({
15800
16381
  }
15801
16382
 
15802
16383
  // src/hotspot/polyline-hotspot.tsx
15803
- import { useEffect as useEffect33, useMemo as useMemo21, useRef as useRef33 } from "react";
16384
+ import { useEffect as useEffect33, useMemo as useMemo21, useRef as useRef34 } from "react";
15804
16385
  import {
15805
- MathUtils as MathUtils12,
16386
+ MathUtils as MathUtils13,
15806
16387
  Matrix4 as Matrix46,
15807
16388
  Quaternion as Quaternion6,
15808
16389
  Vector3 as Vector320
@@ -15839,8 +16420,8 @@ function polylineCenter(vertices) {
15839
16420
  function surfaceQuaternion2(position) {
15840
16421
  const outward = panoPositionToVector3(position).normalize();
15841
16422
  const inward = outward.clone().negate();
15842
- const pitch = MathUtils12.degToRad(position.pitch);
15843
- const yaw = MathUtils12.degToRad(position.yaw);
16423
+ const pitch = MathUtils13.degToRad(position.pitch);
16424
+ const yaw = MathUtils13.degToRad(position.yaw);
15844
16425
  const localUp = new Vector320(
15845
16426
  -Math.sin(pitch) * Math.sin(yaw),
15846
16427
  Math.cos(pitch),
@@ -15869,7 +16450,7 @@ function subdivisionSteps(vertices) {
15869
16450
  }
15870
16451
  return Math.min(
15871
16452
  8,
15872
- Math.max(1, Math.ceil(MathUtils12.radToDeg(maxAngle) / MAX_SEGMENT_DEGREES))
16453
+ Math.max(1, Math.ceil(MathUtils13.radToDeg(maxAngle) / MAX_SEGMENT_DEGREES))
15873
16454
  );
15874
16455
  }
15875
16456
  function makeLinePositions(vertices, center) {
@@ -15932,7 +16513,7 @@ function PolylineHotspot({
15932
16513
  [center, valid, vertices]
15933
16514
  );
15934
16515
  const opacity = Math.max(0, Math.min(strokeOpacity, 1));
15935
- const startVerticesRef = useRef33(null);
16516
+ const startVerticesRef = useRef34(null);
15936
16517
  useEffect33(() => {
15937
16518
  if (!valid) onInvalid?.(issues);
15938
16519
  }, [issues, onInvalid, valid]);
@@ -16249,7 +16830,7 @@ function ProjectScenes({
16249
16830
  }
16250
16831
 
16251
16832
  // src/background-audio.tsx
16252
- import { useContext as useContext23, useEffect as useEffect34, useMemo as useMemo23, useRef as useRef34, useState as useState22 } from "react";
16833
+ import { useContext as useContext23, useEffect as useEffect34, useMemo as useMemo23, useRef as useRef35, useState as useState22 } from "react";
16253
16834
  var DEFAULT_FADE_MS = 400;
16254
16835
  function resolveFadeMs(value) {
16255
16836
  if (!Number.isFinite(value)) {
@@ -16301,21 +16882,21 @@ function BackgroundAudio({
16301
16882
  const urls = resolveBackgroundAudioUrls(src, sources, sceneId);
16302
16883
  usePanoMediaActivationIntent(playing && urls.length > 0);
16303
16884
  const trackKey = urls.join("\0");
16304
- const currentRef = useRef34(null);
16305
- const outgoingRef = useRef34([]);
16306
- const fadingInRef = useRef34(false);
16307
- const isFirstTrackRef = useRef34(true);
16308
- const playingRef = useRef34(playing);
16309
- const loopRef = useRef34(loop);
16310
- const mutedRef = useRef34(muted);
16311
- const volumeRef = useRef34(volume);
16312
- const mutedPropRef = useRef34(muted);
16313
- const volumePropRef = useRef34(volume);
16314
- const fadeMsRef = useRef34(resolveFadeMs(fadeMs));
16315
- const onPlaybackStateChangeRef = useRef34(onPlaybackStateChange);
16316
- const onPlaybackErrorRef = useRef34(onPlaybackError);
16317
- const onEndedRef = useRef34(onEnded);
16318
- const onErrorRef = useRef34(onError);
16885
+ const currentRef = useRef35(null);
16886
+ const outgoingRef = useRef35([]);
16887
+ const fadingInRef = useRef35(false);
16888
+ const isFirstTrackRef = useRef35(true);
16889
+ const playingRef = useRef35(playing);
16890
+ const loopRef = useRef35(loop);
16891
+ const mutedRef = useRef35(muted);
16892
+ const volumeRef = useRef35(volume);
16893
+ const mutedPropRef = useRef35(muted);
16894
+ const volumePropRef = useRef35(volume);
16895
+ const fadeMsRef = useRef35(resolveFadeMs(fadeMs));
16896
+ const onPlaybackStateChangeRef = useRef35(onPlaybackStateChange);
16897
+ const onPlaybackErrorRef = useRef35(onPlaybackError);
16898
+ const onEndedRef = useRef35(onEnded);
16899
+ const onErrorRef = useRef35(onError);
16319
16900
  playingRef.current = playing;
16320
16901
  loopRef.current = loop;
16321
16902
  fadeMsRef.current = resolveFadeMs(fadeMs);
@@ -16331,7 +16912,7 @@ function BackgroundAudio({
16331
16912
  onPlaybackErrorRef.current = onPlaybackError;
16332
16913
  onEndedRef.current = onEnded;
16333
16914
  onErrorRef.current = onError;
16334
- const snapshotRef = useRef34({
16915
+ const snapshotRef = useRef35({
16335
16916
  ready: false,
16336
16917
  playing: false,
16337
16918
  blocked: false,
@@ -16339,7 +16920,7 @@ function BackgroundAudio({
16339
16920
  muted,
16340
16921
  volume
16341
16922
  });
16342
- const snapshotListenersRef = useRef34(/* @__PURE__ */ new Set());
16923
+ const snapshotListenersRef = useRef35(/* @__PURE__ */ new Set());
16343
16924
  const notifySnapshot = (patch) => {
16344
16925
  const prev = snapshotRef.current;
16345
16926
  snapshotRef.current = { ...prev, ...patch };
@@ -16347,7 +16928,7 @@ function BackgroundAudio({
16347
16928
  listener();
16348
16929
  }
16349
16930
  };
16350
- const isControlledRef = useRef34(isControlled);
16931
+ const isControlledRef = useRef35(isControlled);
16351
16932
  isControlledRef.current = isControlled;
16352
16933
  const controller = useMemo23(
16353
16934
  () => ({
@@ -16635,11 +17216,11 @@ function PanoFilter({
16635
17216
  import { useFrame as useFrame20, useThree as useThree25 } from "@react-three/fiber";
16636
17217
  import {
16637
17218
  forwardRef as forwardRef3,
16638
- useCallback as useCallback9,
17219
+ useCallback as useCallback10,
16639
17220
  useContext as useContext25,
16640
17221
  useEffect as useEffect36,
16641
17222
  useImperativeHandle as useImperativeHandle3,
16642
- useRef as useRef35,
17223
+ useRef as useRef36,
16643
17224
  useState as useState23
16644
17225
  } from "react";
16645
17226
  import { Quaternion as Quaternion7 } from "three";
@@ -16679,30 +17260,30 @@ var Gyro = forwardRef3(function Gyro2({
16679
17260
  const { gl } = useThree25();
16680
17261
  const [internallyDisabled, setInternallyDisabled] = useState23(false);
16681
17262
  const [available, setAvailableState] = useState23(false);
16682
- const availableRef = useRef35(false);
16683
- const availabilityReportedRef = useRef35(null);
16684
- const activeRef = useRef35(false);
16685
- const permissionRef = useRef35(deviceOrientationPermissionState);
16686
- const absoluteEventSourceRef = useRef35(null);
16687
- const rawQuaternionRef = useRef35(new Quaternion7());
16688
- const hasRawQuaternionRef = useRef35(false);
16689
- const calibrationRef = useRef35(new Quaternion7());
16690
- const hasCalibrationRef = useRef35(false);
16691
- const calibratedQuaternionRef = useRef35(new Quaternion7());
16692
- const absoluteOffsetRef = useRef35({ yaw: 0, pitch: 0 });
16693
- const screenAngleRef = useRef35(getScreenOrientationAngle());
16694
- const startPoseRef = useRef35(null);
16695
- const enabledAtRef = useRef35(0);
16696
- const filteredPoseRef = useRef35(null);
16697
- const lastAppliedPoseRef = useRef35(null);
16698
- const lastSamplePoseRef = useRef35(null);
16699
- const lastSampleAtRef = useRef35(null);
16700
- const previousYawRef = useRef35(void 0);
16701
- const absoluteRef = useRef35(absolute);
16702
- const northRef = useRef35(north);
17263
+ const availableRef = useRef36(false);
17264
+ const availabilityReportedRef = useRef36(null);
17265
+ const activeRef = useRef36(false);
17266
+ const permissionRef = useRef36(deviceOrientationPermissionState);
17267
+ const absoluteEventSourceRef = useRef36(null);
17268
+ const rawQuaternionRef = useRef36(new Quaternion7());
17269
+ const hasRawQuaternionRef = useRef36(false);
17270
+ const calibrationRef = useRef36(new Quaternion7());
17271
+ const hasCalibrationRef = useRef36(false);
17272
+ const calibratedQuaternionRef = useRef36(new Quaternion7());
17273
+ const absoluteOffsetRef = useRef36({ yaw: 0, pitch: 0 });
17274
+ const screenAngleRef = useRef36(getScreenOrientationAngle());
17275
+ const startPoseRef = useRef36(null);
17276
+ const enabledAtRef = useRef36(0);
17277
+ const filteredPoseRef = useRef36(null);
17278
+ const lastAppliedPoseRef = useRef36(null);
17279
+ const lastSamplePoseRef = useRef36(null);
17280
+ const lastSampleAtRef = useRef36(null);
17281
+ const previousYawRef = useRef36(void 0);
17282
+ const absoluteRef = useRef36(absolute);
17283
+ const northRef = useRef36(north);
16703
17284
  absoluteRef.current = absolute;
16704
17285
  northRef.current = north;
16705
- const callbacksRef = useRef35({
17286
+ const callbacksRef = useRef36({
16706
17287
  onAvailable,
16707
17288
  onUnavailable,
16708
17289
  onEnable,
@@ -16719,7 +17300,7 @@ var Gyro = forwardRef3(function Gyro2({
16719
17300
  if (!controlsRef) {
16720
17301
  throw new Error("<Gyro> must be rendered inside <PanoViewer>.");
16721
17302
  }
16722
- const emit = useCallback9(
17303
+ const emit = useCallback10(
16723
17304
  (type) => {
16724
17305
  eventBus?.emit(type, void 0);
16725
17306
  switch (type) {
@@ -16746,7 +17327,7 @@ var Gyro = forwardRef3(function Gyro2({
16746
17327
  },
16747
17328
  [eventBus]
16748
17329
  );
16749
- const setAvailable = useCallback9(
17330
+ const setAvailable = useCallback10(
16750
17331
  (nextAvailable) => {
16751
17332
  availableRef.current = nextAvailable;
16752
17333
  setAvailableState(nextAvailable);
@@ -16758,7 +17339,7 @@ var Gyro = forwardRef3(function Gyro2({
16758
17339
  },
16759
17340
  [emit]
16760
17341
  );
16761
- const requestPermission = useCallback9(async () => {
17342
+ const requestPermission = useCallback10(async () => {
16762
17343
  if (!availableRef.current) {
16763
17344
  return false;
16764
17345
  }
@@ -16791,7 +17372,7 @@ var Gyro = forwardRef3(function Gyro2({
16791
17372
  emit("gyrodenied");
16792
17373
  return false;
16793
17374
  }, [emit, setAvailable]);
16794
- const deactivate = useCallback9(() => {
17375
+ const deactivate = useCallback10(() => {
16795
17376
  if (activeRef.current) {
16796
17377
  activeRef.current = false;
16797
17378
  controlsRef.current?.setGyroActive(false);
@@ -16806,7 +17387,7 @@ var Gyro = forwardRef3(function Gyro2({
16806
17387
  lastSampleAtRef.current = null;
16807
17388
  previousYawRef.current = void 0;
16808
17389
  }, [controlsRef, emit]);
16809
- const anchorToView = useCallback9(
17390
+ const anchorToView = useCallback10(
16810
17391
  (yaw, pitch) => {
16811
17392
  if (!hasRawQuaternionRef.current) {
16812
17393
  return;
@@ -16836,7 +17417,7 @@ var Gyro = forwardRef3(function Gyro2({
16836
17417
  },
16837
17418
  []
16838
17419
  );
16839
- const resetSensor = useCallback9(
17420
+ const resetSensor = useCallback10(
16840
17421
  (yaw, pitch) => {
16841
17422
  if (!hasRawQuaternionRef.current) {
16842
17423
  return;
@@ -17118,12 +17699,12 @@ var Gyro = forwardRef3(function Gyro2({
17118
17699
  import { useThree as useThree28 } from "@react-three/fiber";
17119
17700
  import {
17120
17701
  forwardRef as forwardRef4,
17121
- useCallback as useCallback10,
17702
+ useCallback as useCallback11,
17122
17703
  useContext as useContext27,
17123
17704
  useEffect as useEffect40,
17124
17705
  useImperativeHandle as useImperativeHandle4,
17125
17706
  useMemo as useMemo26,
17126
- useRef as useRef39,
17707
+ useRef as useRef40,
17127
17708
  useState as useState24
17128
17709
  } from "react";
17129
17710
 
@@ -17132,18 +17713,196 @@ function isMobileVRDevice() {
17132
17713
  if (typeof navigator === "undefined") {
17133
17714
  return false;
17134
17715
  }
17135
- return /Android|iPad|iPhone|iPod|Mobile/i.test(navigator.userAgent) || navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1;
17716
+ return /Android|iPad|iPhone|iPod|Mobile/i.test(navigator.userAgent) || navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1;
17717
+ }
17718
+ async function detectWebVRCapabilities() {
17719
+ const mobile = isMobileVRDevice();
17720
+ const sensor = typeof window !== "undefined" && window.isSecureContext && typeof DeviceOrientationEvent !== "undefined";
17721
+ let webxr = false;
17722
+ try {
17723
+ webxr = typeof navigator !== "undefined" && navigator.xr !== void 0 && await navigator.xr.isSessionSupported("immersive-vr");
17724
+ } catch {
17725
+ webxr = false;
17726
+ }
17727
+ return { webxr, mobile, sensor };
17728
+ }
17729
+
17730
+ // src/webvr/dpdb.ts
17731
+ var DEFAULT_WEBVR_DATABASE_URL = "https://storage.googleapis.com/cardboard-dpdb/dpdb.json";
17732
+ var SCREENSIZE_MIN = 3;
17733
+ var SCREENSIZE_MAX = 20;
17734
+ var cache = /* @__PURE__ */ new Map();
17735
+ function normalizeDatabaseUrl(value) {
17736
+ if (value === null || value === "") {
17737
+ return null;
17738
+ }
17739
+ if (value === void 0) {
17740
+ return DEFAULT_WEBVR_DATABASE_URL;
17741
+ }
17742
+ const trimmed = value.trim();
17743
+ return trimmed.length > 0 ? trimmed : null;
17744
+ }
17745
+ function physicalResolution(screenWidth, screenHeight, devicePixelRatio) {
17746
+ const ratio = Number.isFinite(devicePixelRatio) && devicePixelRatio > 0 ? devicePixelRatio : 1;
17747
+ return [
17748
+ Math.round(Math.max(0, screenWidth) * ratio),
17749
+ Math.round(Math.max(0, screenHeight) * ratio)
17750
+ ];
17751
+ }
17752
+ function resolutionsMatch(rule, physical) {
17753
+ const [rw, rh] = rule;
17754
+ const [pw, ph] = physical;
17755
+ return rw === pw && rh === ph || rw === ph && rh === pw;
17756
+ }
17757
+ function ruleMatches(rule, input, physical) {
17758
+ if (typeof rule.ua === "string") {
17759
+ return input.userAgent.includes(rule.ua);
17760
+ }
17761
+ if (Array.isArray(rule.res) && rule.res.length === 2 && typeof rule.res[0] === "number" && typeof rule.res[1] === "number") {
17762
+ return resolutionsMatch(
17763
+ [rule.res[0], rule.res[1]],
17764
+ physical
17765
+ );
17766
+ }
17767
+ return false;
17768
+ }
17769
+ function deviceMatches(device, input) {
17770
+ const rules = device.rules;
17771
+ if (!Array.isArray(rules) || rules.length === 0) {
17772
+ return false;
17773
+ }
17774
+ const physical = physicalResolution(
17775
+ input.screenWidth,
17776
+ input.screenHeight,
17777
+ input.devicePixelRatio
17778
+ );
17779
+ return rules.some(
17780
+ (rule) => rule !== null && typeof rule === "object" && ruleMatches(rule, input, physical)
17781
+ );
17782
+ }
17783
+ function diagonalInchesFromDpi(dpi, physicalWidth, physicalHeight) {
17784
+ const shortPx = Math.min(physicalWidth, physicalHeight);
17785
+ const longPx = Math.max(physicalWidth, physicalHeight);
17786
+ if (!(shortPx > 0 && longPx > 0)) {
17787
+ return null;
17788
+ }
17789
+ let dpiX;
17790
+ let dpiY;
17791
+ if (typeof dpi === "number") {
17792
+ if (!(Number.isFinite(dpi) && dpi > 0)) {
17793
+ return null;
17794
+ }
17795
+ dpiX = dpi;
17796
+ dpiY = dpi;
17797
+ } else if (Array.isArray(dpi) && dpi.length >= 1) {
17798
+ const first = dpi[0];
17799
+ const second = dpi.length >= 2 ? dpi[1] : first;
17800
+ if (typeof first !== "number" || typeof second !== "number" || !(Number.isFinite(first) && first > 0) || !(Number.isFinite(second) && second > 0)) {
17801
+ return null;
17802
+ }
17803
+ dpiX = first;
17804
+ dpiY = second;
17805
+ } else {
17806
+ return null;
17807
+ }
17808
+ const widthIn = shortPx / dpiX;
17809
+ const heightIn = longPx / dpiY;
17810
+ const diagonal = Math.sqrt(widthIn * widthIn + heightIn * heightIn);
17811
+ if (!Number.isFinite(diagonal) || diagonal < SCREENSIZE_MIN || diagonal > SCREENSIZE_MAX) {
17812
+ return null;
17813
+ }
17814
+ return Math.round(diagonal * 10) / 10;
17815
+ }
17816
+ function matchDpdbDevice(document2, input) {
17817
+ const devices = document2.devices;
17818
+ if (!Array.isArray(devices)) {
17819
+ return null;
17820
+ }
17821
+ const physical = physicalResolution(
17822
+ input.screenWidth,
17823
+ input.screenHeight,
17824
+ input.devicePixelRatio
17825
+ );
17826
+ for (const device of devices) {
17827
+ if (!deviceMatches(device, input)) {
17828
+ continue;
17829
+ }
17830
+ if (device.dpi === void 0) {
17831
+ continue;
17832
+ }
17833
+ const screensizeInches = diagonalInchesFromDpi(
17834
+ device.dpi,
17835
+ physical[0],
17836
+ physical[1]
17837
+ );
17838
+ if (screensizeInches === null) {
17839
+ continue;
17840
+ }
17841
+ return { device, screensizeInches };
17842
+ }
17843
+ return null;
17844
+ }
17845
+ function readDpdbMatchInput(globalObject = globalThis) {
17846
+ const navigatorObject = globalObject.navigator;
17847
+ const screenObject = globalObject.screen;
17848
+ if (!navigatorObject || !screenObject) {
17849
+ return null;
17850
+ }
17851
+ const width = screenObject.width;
17852
+ const height = screenObject.height;
17853
+ if (typeof width !== "number" || typeof height !== "number" || !(width > 0 && height > 0)) {
17854
+ return null;
17855
+ }
17856
+ const dpr = typeof globalObject.devicePixelRatio === "number" ? globalObject.devicePixelRatio : 1;
17857
+ return {
17858
+ userAgent: navigatorObject.userAgent ?? "",
17859
+ screenWidth: width,
17860
+ screenHeight: height,
17861
+ devicePixelRatio: dpr
17862
+ };
17136
17863
  }
17137
- async function detectWebVRCapabilities() {
17138
- const mobile = isMobileVRDevice();
17139
- const sensor = typeof window !== "undefined" && window.isSecureContext && typeof DeviceOrientationEvent !== "undefined";
17140
- let webxr = false;
17864
+ async function fetchDpdbDocument(url) {
17141
17865
  try {
17142
- webxr = typeof navigator !== "undefined" && navigator.xr !== void 0 && await navigator.xr.isSessionSupported("immersive-vr");
17866
+ const response = await fetch(url, {
17867
+ credentials: "omit",
17868
+ mode: "cors"
17869
+ });
17870
+ if (!response.ok) {
17871
+ return null;
17872
+ }
17873
+ const data = await response.json();
17874
+ if (data === null || typeof data !== "object" || !("devices" in data) || !Array.isArray(data.devices)) {
17875
+ return null;
17876
+ }
17877
+ return data;
17143
17878
  } catch {
17144
- webxr = false;
17879
+ return null;
17145
17880
  }
17146
- return { webxr, mobile, sensor };
17881
+ }
17882
+ function loadDpdbDocument(url) {
17883
+ const existing = cache.get(url);
17884
+ if (existing) {
17885
+ return existing;
17886
+ }
17887
+ const pending = fetchDpdbDocument(url).then((document2) => {
17888
+ if (!document2) {
17889
+ cache.delete(url);
17890
+ }
17891
+ return document2;
17892
+ });
17893
+ cache.set(url, pending);
17894
+ return pending;
17895
+ }
17896
+ async function resolveScreensizeFromDpdb(databaseUrl, input = readDpdbMatchInput()) {
17897
+ const url = normalizeDatabaseUrl(databaseUrl);
17898
+ if (!url || !input) {
17899
+ return null;
17900
+ }
17901
+ const document2 = await loadDpdbDocument(url);
17902
+ if (!document2) {
17903
+ return null;
17904
+ }
17905
+ return matchDpdbDevice(document2, input)?.screensizeInches ?? null;
17147
17906
  }
17148
17907
 
17149
17908
  // src/webvr/presentation.ts
@@ -17303,7 +18062,7 @@ function subscribeWebVRPresentationExit(element, onExit) {
17303
18062
 
17304
18063
  // src/webvr/reticle.tsx
17305
18064
  import { useFrame as useFrame21, useThree as useThree26 } from "@react-three/fiber";
17306
- import { useRef as useRef36 } from "react";
18065
+ import { useRef as useRef37 } from "react";
17307
18066
  import {
17308
18067
  DoubleSide as DoubleSide6,
17309
18068
  Raycaster as Raycaster4,
@@ -17396,13 +18155,13 @@ function WebVRReticle({
17396
18155
  scale = RETICLE_SCALE,
17397
18156
  visible
17398
18157
  }) {
17399
- const groupRef = useRef36(null);
17400
- const ringMeshRef = useRef36(null);
17401
- const directionRef = useRef36(new Vector321());
17402
- const raycasterRef = useRef36(new Raycaster4());
17403
- const hoveredRef = useRef36(null);
17404
- const dwellRef = useRef36(0);
17405
- const clickedRef = useRef36(false);
18158
+ const groupRef = useRef37(null);
18159
+ const ringMeshRef = useRef37(null);
18160
+ const directionRef = useRef37(new Vector321());
18161
+ const raycasterRef = useRef37(new Raycaster4());
18162
+ const hoveredRef = useRef37(null);
18163
+ const dwellRef = useRef37(0);
18164
+ const clickedRef = useRef37(false);
17406
18165
  const { camera, gl, scene } = useThree26();
17407
18166
  useFrame21((_, deltaSeconds) => {
17408
18167
  const group = groupRef.current;
@@ -17565,7 +18324,7 @@ function WebVRReticle({
17565
18324
 
17566
18325
  // src/webvr/stereo.tsx
17567
18326
  import { useFrame as useFrame22, useThree as useThree27 } from "@react-three/fiber";
17568
- import { useContext as useContext26, useEffect as useEffect37, useMemo as useMemo25, useRef as useRef37 } from "react";
18327
+ import { useContext as useContext26, useEffect as useEffect37, useMemo as useMemo25, useRef as useRef38 } from "react";
17569
18328
  import {
17570
18329
  Matrix4 as Matrix47,
17571
18330
  Mesh as Mesh6,
@@ -17580,6 +18339,40 @@ import {
17580
18339
  WebGLRenderTarget as WebGLRenderTarget2
17581
18340
  } from "three";
17582
18341
 
18342
+ // src/webvr/lens-center.ts
18343
+ function physicalScreenFromDiagonal(screensizeInches, displayAspect) {
18344
+ if (!(Number.isFinite(screensizeInches) && screensizeInches > 0 && Number.isFinite(displayAspect) && displayAspect > 0)) {
18345
+ return null;
18346
+ }
18347
+ const diagonalMm = screensizeInches * 25.4;
18348
+ const heightMm = diagonalMm / Math.sqrt(displayAspect * displayAspect + 1);
18349
+ const widthMm = heightMm * displayAspect;
18350
+ if (!(heightMm > 0 && widthMm > 0)) {
18351
+ return null;
18352
+ }
18353
+ return { widthMm, heightMm };
18354
+ }
18355
+ function computeStereoLensCenters(profile, screensize, displayAspect) {
18356
+ const zero = { left: { x: 0, y: 0 }, right: { x: 0, y: 0 } };
18357
+ if (screensize === "auto") {
18358
+ return zero;
18359
+ }
18360
+ const screen = physicalScreenFromDiagonal(screensize, displayAspect);
18361
+ if (!screen) {
18362
+ return zero;
18363
+ }
18364
+ const halfEyeWidthMm = screen.widthMm / 4;
18365
+ const ipdMm = Number.isFinite(profile.ipdMm) ? profile.ipdMm : 60;
18366
+ const x = halfEyeWidthMm > 0 ? (halfEyeWidthMm - ipdMm / 2) / halfEyeWidthMm : 0;
18367
+ const trayDistanceMm = typeof profile.trayDistanceMm === "number" && Number.isFinite(profile.trayDistanceMm) && profile.trayDistanceMm > 0 ? profile.trayDistanceMm : 0;
18368
+ const y = trayDistanceMm > 0 && screen.heightMm > 0 ? 2 * trayDistanceMm / screen.heightMm - 1 : 0;
18369
+ const clampUnit = (value) => Math.max(-1, Math.min(1, Number.isFinite(value) ? value : 0));
18370
+ return {
18371
+ left: { x: clampUnit(x), y: clampUnit(y) },
18372
+ right: { x: clampUnit(-x), y: clampUnit(y) }
18373
+ };
18374
+ }
18375
+
17583
18376
  // src/webvr/stereo-disparity.ts
17584
18377
  var WEBVR_PANORAMA_VIRTUAL_DISTANCE_M = 2;
17585
18378
  function panoramaStereoHalfAngle(ipdMm, virtualDistanceM = WEBVR_PANORAMA_VIRTUAL_DISTANCE_M) {
@@ -17595,19 +18388,32 @@ function panoramaStereoToeInYaw(eye, halfAngleRad) {
17595
18388
  }
17596
18389
 
17597
18390
  // src/webvr/stereo-layout.ts
17598
- function computeStereoLayout(logicalWidth, logicalHeight, pixelRatio) {
18391
+ var DEFAULT_WEBVR_OVERSAMPLING = 1;
18392
+ var MIN_WEBVR_OVERSAMPLING = 0.5;
18393
+ var MAX_WEBVR_OVERSAMPLING = 2;
18394
+ function clampWebVROversampling(value) {
18395
+ if (typeof value !== "number" || !Number.isFinite(value)) {
18396
+ return DEFAULT_WEBVR_OVERSAMPLING;
18397
+ }
18398
+ return Math.max(
18399
+ MIN_WEBVR_OVERSAMPLING,
18400
+ Math.min(MAX_WEBVR_OVERSAMPLING, value)
18401
+ );
18402
+ }
18403
+ function computeStereoLayout(logicalWidth, logicalHeight, pixelRatio, oversampling = DEFAULT_WEBVR_OVERSAMPLING) {
17599
18404
  const fullWidth = Math.max(2, Math.floor(logicalWidth));
17600
18405
  const fullHeight = Math.max(1, Math.floor(logicalHeight));
17601
18406
  const leftWidth = Math.max(1, Math.floor(fullWidth / 2));
17602
18407
  const rightWidth = Math.max(1, fullWidth - leftWidth);
17603
18408
  const ratio = Number.isFinite(pixelRatio) && pixelRatio > 0 ? pixelRatio : 1;
18409
+ const scale = clampWebVROversampling(oversampling);
17604
18410
  return {
17605
18411
  fullWidth,
17606
18412
  fullHeight,
17607
18413
  left: { width: leftWidth, height: fullHeight, offsetX: 0 },
17608
18414
  right: { width: rightWidth, height: fullHeight, offsetX: leftWidth },
17609
- renderTargetWidth: Math.max(1, Math.floor(leftWidth * ratio)),
17610
- renderTargetHeight: Math.max(1, Math.floor(fullHeight * ratio)),
18415
+ renderTargetWidth: Math.max(1, Math.floor(leftWidth * ratio * scale)),
18416
+ renderTargetHeight: Math.max(1, Math.floor(fullHeight * ratio * scale)),
17611
18417
  stereoAspect: leftWidth / fullWidth
17612
18418
  };
17613
18419
  }
@@ -17624,12 +18430,13 @@ void main() {
17624
18430
  var fragmentShader = `
17625
18431
  uniform sampler2D sourceTexture;
17626
18432
  uniform vec2 distortion;
18433
+ uniform vec2 lensCenter;
17627
18434
  varying vec2 vUv;
17628
18435
  void main() {
17629
- vec2 centered = vUv * 2.0 - 1.0;
18436
+ vec2 centered = vUv * 2.0 - 1.0 - lensCenter;
17630
18437
  float radius2 = dot(centered, centered);
17631
18438
  float scale = 1.0 + distortion.x * radius2 + distortion.y * radius2 * radius2;
17632
- vec2 sourceUv = centered * scale * 0.5 + 0.5;
18439
+ vec2 sourceUv = (centered * scale + lensCenter) * 0.5 + 0.5;
17633
18440
  if (sourceUv.x < 0.0 || sourceUv.x > 1.0 || sourceUv.y < 0.0 || sourceUv.y > 1.0) {
17634
18441
  gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
17635
18442
  } else {
@@ -17649,6 +18456,7 @@ function createDistortionResources() {
17649
18456
  fragmentShader,
17650
18457
  uniforms: {
17651
18458
  distortion: { value: new Vector28() },
18459
+ lensCenter: { value: new Vector28() },
17652
18460
  sourceTexture: { value: target.texture }
17653
18461
  },
17654
18462
  vertexShader
@@ -17665,12 +18473,27 @@ function createDistortionResources() {
17665
18473
  target
17666
18474
  };
17667
18475
  }
17668
- function syncStereoCameras(stereo, camera, profile, screensize, size, gl) {
18476
+ function applyEyeViewOffset(eyeCamera, eye, center) {
18477
+ const fullWidth = eye.width;
18478
+ const fullHeight = eye.height;
18479
+ const offsetX = ((center.x + 1) / 2 - 0.5) * fullWidth;
18480
+ const offsetY = (0.5 - (center.y + 1) / 2) * fullHeight;
18481
+ eyeCamera.setViewOffset(
18482
+ fullWidth,
18483
+ fullHeight,
18484
+ offsetX,
18485
+ offsetY,
18486
+ fullWidth,
18487
+ fullHeight
18488
+ );
18489
+ }
18490
+ function syncStereoCameras(stereo, camera, profile, screensize, oversampling, size, gl) {
17669
18491
  gl.getSize(size);
17670
18492
  const layout = computeStereoLayout(
17671
18493
  size.x,
17672
18494
  size.y,
17673
- gl.getPixelRatio()
18495
+ gl.getPixelRatio(),
18496
+ oversampling
17674
18497
  );
17675
18498
  const screenToLensMm = profile.screenToLensMm ?? 42;
17676
18499
  const displayAspect = layout.fullWidth / layout.fullHeight;
@@ -17689,34 +18512,40 @@ function syncStereoCameras(stereo, camera, profile, screensize, size, gl) {
17689
18512
  TOE_IN.makeRotationY(panoramaStereoToeInYaw("right", halfAngle));
17690
18513
  stereo.cameraR.matrix.multiply(TOE_IN);
17691
18514
  stereo.cameraR.matrixWorldNeedsUpdate = true;
18515
+ const centers = computeStereoLensCenters(profile, screensize, displayAspect);
18516
+ applyEyeViewOffset(stereo.cameraL, layout.left, centers.left);
18517
+ applyEyeViewOffset(stereo.cameraR, layout.right, centers.right);
17692
18518
  stereo.cameraL.updateMatrixWorld(true);
17693
18519
  stereo.cameraR.updateMatrixWorld(true);
17694
- return layout;
18520
+ return { layout, centers };
17695
18521
  }
17696
18522
  function WebVRStereoRenderer({
17697
18523
  mode,
18524
+ oversampling = 1,
17698
18525
  profile,
17699
18526
  screensize
17700
18527
  }) {
17701
18528
  const { camera, gl } = useThree27();
17702
18529
  const runtime = useContext26(WebVRRuntimeContext);
17703
- const fallbackStereoRef = useRef37(null);
18530
+ const fallbackStereoRef = useRef38(null);
17704
18531
  if (!fallbackStereoRef.current) {
17705
18532
  fallbackStereoRef.current = new StereoCamera2();
17706
18533
  }
17707
18534
  const stereo = runtime?.stereoView.stereo ?? fallbackStereoRef.current;
17708
- const stereoRef = useRef37(stereo);
18535
+ const stereoRef = useRef38(stereo);
17709
18536
  stereoRef.current = stereo;
17710
18537
  const resources = useMemo25(createDistortionResources, []);
17711
- const sizeRef = useRef37(new Vector28());
17712
- const viewportRef = useRef37(new Vector42());
17713
- const scissorRef = useRef37(new Vector42());
17714
- const originalFovRef = useRef37(null);
17715
- const stereoActiveRef = useRef37(false);
17716
- const profileRef = useRef37(profile);
17717
- const screensizeRef = useRef37(screensize);
18538
+ const sizeRef = useRef38(new Vector28());
18539
+ const viewportRef = useRef38(new Vector42());
18540
+ const scissorRef = useRef38(new Vector42());
18541
+ const originalFovRef = useRef38(null);
18542
+ const stereoActiveRef = useRef38(false);
18543
+ const profileRef = useRef38(profile);
18544
+ const screensizeRef = useRef38(screensize);
18545
+ const oversamplingRef = useRef38(clampWebVROversampling(oversampling));
17718
18546
  profileRef.current = profile;
17719
18547
  screensizeRef.current = screensize;
18548
+ oversamplingRef.current = clampWebVROversampling(oversampling);
17720
18549
  useEffect37(
17721
18550
  () => () => {
17722
18551
  resources.geometry.dispose();
@@ -17737,11 +18566,12 @@ function WebVRStereoRenderer({
17737
18566
  if (originalFovRef.current === null) {
17738
18567
  originalFovRef.current = cameraToRender.fov;
17739
18568
  }
17740
- const layout = syncStereoCameras(
18569
+ const { layout, centers } = syncStereoCameras(
17741
18570
  stereoRef.current,
17742
18571
  cameraToRender,
17743
18572
  currentProfile,
17744
18573
  currentScreensize,
18574
+ oversamplingRef.current,
17745
18575
  sizeRef.current,
17746
18576
  gl
17747
18577
  );
@@ -17753,7 +18583,7 @@ function WebVRStereoRenderer({
17753
18583
  gl.getScissor(scissorRef.current);
17754
18584
  gl.autoClear = true;
17755
18585
  gl.setScissorTest(true);
17756
- const renderEye = (eyeCamera, eye) => {
18586
+ const renderEye = (eyeCamera, eye, center) => {
17757
18587
  if (hasDistortion) {
17758
18588
  if (resources.target.width !== layout.renderTargetWidth || resources.target.height !== layout.renderTargetHeight) {
17759
18589
  resources.target.setSize(
@@ -17770,6 +18600,7 @@ function WebVRStereoRenderer({
17770
18600
  currentProfile.k1,
17771
18601
  currentProfile.k2
17772
18602
  );
18603
+ resources.material.uniforms.lensCenter?.value.set(center.x, center.y);
17773
18604
  originalRender(resources.scene, resources.camera);
17774
18605
  return;
17775
18606
  }
@@ -17778,8 +18609,8 @@ function WebVRStereoRenderer({
17778
18609
  gl.setScissor(eye.offsetX, 0, eye.width, eye.height);
17779
18610
  originalRender(sceneToRender, eyeCamera);
17780
18611
  };
17781
- renderEye(stereoRef.current.cameraL, layout.left);
17782
- renderEye(stereoRef.current.cameraR, layout.right);
18612
+ renderEye(stereoRef.current.cameraL, layout.left, centers.left);
18613
+ renderEye(stereoRef.current.cameraR, layout.right, centers.right);
17783
18614
  gl.setRenderTarget(previousTarget);
17784
18615
  gl.setViewport(viewportRef.current);
17785
18616
  gl.setScissor(scissorRef.current);
@@ -17810,6 +18641,7 @@ function WebVRStereoRenderer({
17810
18641
  camera,
17811
18642
  profileRef.current,
17812
18643
  screensizeRef.current,
18644
+ oversamplingRef.current,
17813
18645
  sizeRef.current,
17814
18646
  gl
17815
18647
  );
@@ -17825,7 +18657,7 @@ function WebVRStereoRenderer({
17825
18657
  }
17826
18658
 
17827
18659
  // src/webvr/pointerlock.ts
17828
- import { useEffect as useEffect38, useRef as useRef38 } from "react";
18660
+ import { useEffect as useEffect38, useRef as useRef39 } from "react";
17829
18661
  var POINTER_LOCK_CURSOR_ID = "webvr:pointerlock";
17830
18662
  async function requestWebVRPointerLock(element) {
17831
18663
  if (typeof document === "undefined") {
@@ -17858,8 +18690,8 @@ function exitWebVRPointerLock(element) {
17858
18690
  }
17859
18691
  function useWebVRPointerLock(enabled, element) {
17860
18692
  const cursor = usePanoCursor();
17861
- const cursorRef = useRef38(cursor);
17862
- const enabledRef = useRef38(enabled);
18693
+ const cursorRef = useRef39(cursor);
18694
+ const enabledRef = useRef39(enabled);
17863
18695
  cursorRef.current = cursor;
17864
18696
  enabledRef.current = enabled;
17865
18697
  useEffect38(() => {
@@ -18039,8 +18871,44 @@ function useWebVRWakeLock(enabled) {
18039
18871
  }, [enabled]);
18040
18872
  }
18041
18873
 
18874
+ // src/webvr/xr-quality.ts
18875
+ function clampWebVRFoveation(value) {
18876
+ if (typeof value !== "number" || !Number.isFinite(value)) {
18877
+ return void 0;
18878
+ }
18879
+ return Math.max(0, Math.min(1, value));
18880
+ }
18881
+ function resolveWebVRFrameRateOption(frameRate) {
18882
+ if (frameRate === void 0) {
18883
+ return void 0;
18884
+ }
18885
+ if (frameRate === "low" || frameRate === "mid" || frameRate === "high") {
18886
+ return frameRate;
18887
+ }
18888
+ if (typeof frameRate !== "number" || !Number.isFinite(frameRate)) {
18889
+ return void 0;
18890
+ }
18891
+ const target = frameRate;
18892
+ return (rates) => {
18893
+ if (rates.length === 0) {
18894
+ return false;
18895
+ }
18896
+ let best = rates[0];
18897
+ let bestDelta = Math.abs(best - target);
18898
+ for (let i = 1; i < rates.length; i += 1) {
18899
+ const candidate = rates[i];
18900
+ const delta = Math.abs(candidate - target);
18901
+ if (delta < bestDelta) {
18902
+ best = candidate;
18903
+ bestDelta = delta;
18904
+ }
18905
+ }
18906
+ return best;
18907
+ };
18908
+ }
18909
+
18042
18910
  // src/webvr/webvr.tsx
18043
- import { Fragment as Fragment6, jsx as jsx40, jsxs as jsxs21 } from "react/jsx-runtime";
18911
+ import { Fragment as Fragment8, jsx as jsx40, jsxs as jsxs21 } from "react/jsx-runtime";
18044
18912
  function initializeSettings(screensize, profile) {
18045
18913
  const stored = loadWebVRSettings();
18046
18914
  return {
@@ -18057,7 +18925,15 @@ var WebVR = forwardRef4(function WebVR2({
18057
18925
  desktopSupport = false,
18058
18926
  fakeSupport = true,
18059
18927
  screensize,
18928
+ databaseUrl,
18060
18929
  profile,
18930
+ absolute = false,
18931
+ north = 0,
18932
+ friction = 0,
18933
+ touchSupport = false,
18934
+ oversampling = 1,
18935
+ foveation,
18936
+ frameRate,
18061
18937
  cursor = true,
18062
18938
  cursorDwellMs = DEFAULT_WEBVR_CURSOR_DWELL_MS,
18063
18939
  wakelock = true,
@@ -18073,20 +18949,36 @@ var WebVR = forwardRef4(function WebVR2({
18073
18949
  const controlsRef = useContext27(PanoramaViewContext);
18074
18950
  const eventBus = useContext27(PanoEventBusContext);
18075
18951
  const gl = useThree28((state) => state.gl);
18076
- const gyroRef = useRef39(null);
18077
- const capabilitiesRef = useRef39({
18952
+ const gyroRef = useRef40(null);
18953
+ const capabilitiesRef = useRef40({
18078
18954
  mobile: false,
18079
18955
  sensor: false,
18080
18956
  webxr: false
18081
18957
  });
18082
- const availableRef = useRef39(false);
18083
- const modeRef = useRef39(null);
18084
- const webXRSessionRef = useRef39(null);
18958
+ const availableRef = useRef40(false);
18959
+ const modeRef = useRef40(null);
18960
+ const webXRSessionRef = useRef40(null);
18961
+ const dpdbRequestIdRef = useRef40(0);
18962
+ const databaseUrlRef = useRef40(databaseUrl);
18963
+ databaseUrlRef.current = databaseUrl;
18964
+ const oversamplingRef = useRef40(clampWebVROversampling(oversampling));
18965
+ oversamplingRef.current = clampWebVROversampling(oversampling);
18966
+ const foveationRef = useRef40(clampWebVRFoveation(foveation));
18967
+ foveationRef.current = clampWebVRFoveation(foveation);
18968
+ const frameRateRef = useRef40(frameRate);
18969
+ frameRateRef.current = frameRate;
18970
+ const screensizePropRef = useRef40(screensize);
18971
+ screensizePropRef.current = screensize;
18972
+ const profilePropRef = useRef40(profile);
18973
+ profilePropRef.current = profile;
18085
18974
  const [mode, setMode] = useState24(null);
18086
18975
  const [settings, setSettings] = useState24(
18087
18976
  () => initializeSettings(screensize, profile)
18088
18977
  );
18089
- const callbacksRef = useRef39({
18978
+ const [detectedScreensize, setDetectedScreensize] = useState24(
18979
+ null
18980
+ );
18981
+ const callbacksRef = useRef40({
18090
18982
  onAvailable,
18091
18983
  onDenied,
18092
18984
  onEnterVR,
@@ -18110,18 +19002,44 @@ var WebVR = forwardRef4(function WebVR2({
18110
19002
  () => resolveWebVRProfile(profile, settings.profileId),
18111
19003
  [profile, settings.profileId]
18112
19004
  );
18113
- const reportDenied = useCallback10(
19005
+ const effectiveScreensize = settings.screensize === "auto" ? detectedScreensize ?? "auto" : settings.screensize;
19006
+ const resolvedOversampling = clampWebVROversampling(oversampling);
19007
+ const reportDenied = useCallback11(
18114
19008
  (error) => {
18115
19009
  eventBus.emit("vrdenied", { error });
18116
19010
  callbacksRef.current.onDenied?.(error);
18117
19011
  },
18118
19012
  [eventBus]
18119
19013
  );
18120
- const reportUnknownDevice = useCallback10(() => {
19014
+ const reportUnknownDevice = useCallback11(() => {
18121
19015
  eventBus.emit("vrunknowndevice", void 0);
18122
19016
  callbacksRef.current.onUnknownDevice?.();
18123
19017
  }, [eventBus]);
18124
- const activate = useCallback10(
19018
+ const clearDetectedScreensize = useCallback11(() => {
19019
+ dpdbRequestIdRef.current += 1;
19020
+ setDetectedScreensize(null);
19021
+ updateWebVRHost(host, { detectedScreensize: null });
19022
+ }, [host]);
19023
+ const resolveAutoScreensize = useCallback11(() => {
19024
+ const requestId = ++dpdbRequestIdRef.current;
19025
+ const url = normalizeDatabaseUrl(databaseUrlRef.current);
19026
+ if (!url) {
19027
+ reportUnknownDevice();
19028
+ return;
19029
+ }
19030
+ void resolveScreensizeFromDpdb(url).then((inches) => {
19031
+ if (requestId !== dpdbRequestIdRef.current) {
19032
+ return;
19033
+ }
19034
+ if (inches === null) {
19035
+ reportUnknownDevice();
19036
+ return;
19037
+ }
19038
+ setDetectedScreensize(inches);
19039
+ updateWebVRHost(host, { detectedScreensize: inches });
19040
+ });
19041
+ }, [host, reportUnknownDevice]);
19042
+ const activate = useCallback11(
18125
19043
  (nextMode) => {
18126
19044
  if (modeRef.current === nextMode) {
18127
19045
  return;
@@ -18136,7 +19054,7 @@ var WebVR = forwardRef4(function WebVR2({
18136
19054
  },
18137
19055
  [controlsRef, eventBus]
18138
19056
  );
18139
- const deactivate = useCallback10(
19057
+ const deactivate = useCallback11(
18140
19058
  (expectedMode) => {
18141
19059
  const previousMode = modeRef.current;
18142
19060
  if (!previousMode || expectedMode && previousMode !== expectedMode) {
@@ -18144,6 +19062,7 @@ var WebVR = forwardRef4(function WebVR2({
18144
19062
  }
18145
19063
  modeRef.current = null;
18146
19064
  setMode(null);
19065
+ clearDetectedScreensize();
18147
19066
  if (previousMode === "fake") {
18148
19067
  controlsRef.current?.setGyroActive(false);
18149
19068
  }
@@ -18151,24 +19070,42 @@ var WebVR = forwardRef4(function WebVR2({
18151
19070
  eventBus.emit("vrexit", { mode: previousMode });
18152
19071
  callbacksRef.current.onExitVR?.(previousMode);
18153
19072
  },
18154
- [controlsRef, eventBus, gl.domElement]
19073
+ [clearDetectedScreensize, controlsRef, eventBus, gl.domElement]
18155
19074
  );
18156
- const requestPermission = useCallback10(async () => {
19075
+ const requestPermission = useCallback11(async () => {
18157
19076
  return await gyroRef.current?.requestPermission() ?? false;
18158
19077
  }, []);
18159
- const enterVR = useCallback10(async () => {
19078
+ const applyWebXRQuality = useCallback11(() => {
19079
+ const scale = oversamplingRef.current;
19080
+ if (typeof gl.xr.setFramebufferScaleFactor === "function") {
19081
+ gl.xr.setFramebufferScaleFactor(scale);
19082
+ }
19083
+ const nextFoveation = foveationRef.current;
19084
+ if (nextFoveation !== void 0 && typeof gl.xr.setFoveation === "function") {
19085
+ gl.xr.setFoveation(nextFoveation);
19086
+ }
19087
+ const frameRateOption = resolveWebVRFrameRateOption(frameRateRef.current);
19088
+ if (frameRateOption !== void 0) {
19089
+ xrStore.setFrameRate(frameRateOption);
19090
+ }
19091
+ }, [gl.xr, xrStore]);
19092
+ const enterVR = useCallback11(async () => {
18160
19093
  if (modeRef.current) {
18161
19094
  return true;
18162
19095
  }
18163
19096
  const capabilities = capabilitiesRef.current;
18164
19097
  if (capabilities.webxr) {
18165
19098
  try {
19099
+ if (typeof gl.xr.setFramebufferScaleFactor === "function") {
19100
+ gl.xr.setFramebufferScaleFactor(oversamplingRef.current);
19101
+ }
18166
19102
  await xrStore.enterVR();
18167
19103
  const session2 = xrStore.getState().session;
18168
19104
  if (!session2) {
18169
19105
  throw new Error("The WebXR session did not start.");
18170
19106
  }
18171
19107
  webXRSessionRef.current = session2;
19108
+ applyWebXRQuality();
18172
19109
  session2.addEventListener(
18173
19110
  "end",
18174
19111
  () => {
@@ -18198,10 +19135,10 @@ var WebVR = forwardRef4(function WebVR2({
18198
19135
  reportDenied();
18199
19136
  return false;
18200
19137
  }
19138
+ activate("mobilevr");
18201
19139
  if (settings.screensize === "auto") {
18202
- reportUnknownDevice();
19140
+ resolveAutoScreensize();
18203
19141
  }
18204
- activate("mobilevr");
18205
19142
  return true;
18206
19143
  }
18207
19144
  if (fakeSupport) {
@@ -18220,21 +19157,23 @@ var WebVR = forwardRef4(function WebVR2({
18220
19157
  return false;
18221
19158
  }, [
18222
19159
  activate,
19160
+ applyWebXRQuality,
18223
19161
  deactivate,
18224
19162
  desktopSupport,
18225
19163
  eventBus,
18226
19164
  fakeSupport,
18227
19165
  gl.domElement,
19166
+ gl.xr,
18228
19167
  mobileVr,
18229
19168
  mousePointerLock,
18230
19169
  reportDenied,
18231
- reportUnknownDevice,
18232
19170
  requestPermission,
19171
+ resolveAutoScreensize,
18233
19172
  settings.screensize,
18234
19173
  wakelock,
18235
19174
  xrStore
18236
19175
  ]);
18237
- const exitVR = useCallback10(async () => {
19176
+ const exitVR = useCallback11(async () => {
18238
19177
  if (modeRef.current === "webxr") {
18239
19178
  const session2 = webXRSessionRef.current ?? xrStore.getState().session ?? null;
18240
19179
  if (session2) {
@@ -18247,21 +19186,52 @@ var WebVR = forwardRef4(function WebVR2({
18247
19186
  deactivate();
18248
19187
  leaveWebVRPresentation();
18249
19188
  }, [deactivate, xrStore]);
18250
- const toggleVR = useCallback10(async () => {
19189
+ const toggleVR = useCallback11(async () => {
18251
19190
  if (modeRef.current) {
18252
19191
  await exitVR();
18253
19192
  return false;
18254
19193
  }
18255
19194
  return enterVR();
18256
19195
  }, [enterVR, exitVR]);
18257
- const updateSettings = useCallback10((next) => {
18258
- const normalized = {
18259
- profileId: next.profileId,
18260
- screensize: next.screensize === "auto" ? "auto" : Math.round(Math.max(3, Math.min(20, next.screensize)) * 10) / 10
18261
- };
18262
- setSettings(normalized);
18263
- saveWebVRSettings(normalized);
18264
- }, []);
19196
+ const updateSettings = useCallback11(
19197
+ (next) => {
19198
+ const normalized = {
19199
+ profileId: next.profileId,
19200
+ screensize: next.screensize === "auto" ? "auto" : Math.round(Math.max(3, Math.min(20, next.screensize)) * 10) / 10
19201
+ };
19202
+ setSettings(normalized);
19203
+ saveWebVRSettings(normalized);
19204
+ if (normalized.screensize === "auto") {
19205
+ if (modeRef.current === "mobilevr") {
19206
+ setDetectedScreensize(null);
19207
+ updateWebVRHost(host, { detectedScreensize: null });
19208
+ resolveAutoScreensize();
19209
+ }
19210
+ } else {
19211
+ clearDetectedScreensize();
19212
+ }
19213
+ },
19214
+ [clearDetectedScreensize, host, resolveAutoScreensize]
19215
+ );
19216
+ const resetSettings = useCallback11(() => {
19217
+ clearWebVRSettings();
19218
+ const next = resolveInitialWebVRSettings(
19219
+ screensizePropRef.current,
19220
+ profilePropRef.current
19221
+ );
19222
+ setSettings(next);
19223
+ if (next.screensize === "auto") {
19224
+ if (modeRef.current === "mobilevr") {
19225
+ setDetectedScreensize(null);
19226
+ updateWebVRHost(host, { detectedScreensize: null });
19227
+ resolveAutoScreensize();
19228
+ } else {
19229
+ clearDetectedScreensize();
19230
+ }
19231
+ } else {
19232
+ clearDetectedScreensize();
19233
+ }
19234
+ }, [clearDetectedScreensize, host, resolveAutoScreensize]);
18265
19235
  const controller = useMemo26(
18266
19236
  () => ({
18267
19237
  closeSetup: () => updateWebVRHost(host, { setupOpen: false }),
@@ -18269,10 +19239,19 @@ var WebVR = forwardRef4(function WebVR2({
18269
19239
  exitVR,
18270
19240
  openSetup: () => updateWebVRHost(host, { setupOpen: true }),
18271
19241
  requestPermission,
19242
+ resetSettings,
18272
19243
  toggleVR,
18273
19244
  updateSettings
18274
19245
  }),
18275
- [enterVR, exitVR, host, requestPermission, toggleVR, updateSettings]
19246
+ [
19247
+ enterVR,
19248
+ exitVR,
19249
+ host,
19250
+ requestPermission,
19251
+ resetSettings,
19252
+ toggleVR,
19253
+ updateSettings
19254
+ ]
18276
19255
  );
18277
19256
  useImperativeHandle4(
18278
19257
  ref,
@@ -18280,12 +19259,15 @@ var WebVR = forwardRef4(function WebVR2({
18280
19259
  enterVR,
18281
19260
  exitVR,
18282
19261
  getMode: () => modeRef.current,
19262
+ getState: () => getWebVRState(host),
18283
19263
  isAvailable: () => availableRef.current,
18284
19264
  isEnabled: () => modeRef.current !== null,
18285
19265
  requestPermission,
19266
+ resetSettings,
19267
+ subscribe: (onStoreChange) => subscribeWebVRHost(host, onStoreChange),
18286
19268
  toggleVR
18287
19269
  }),
18288
- [enterVR, exitVR, requestPermission, toggleVR]
19270
+ [enterVR, exitVR, host, requestPermission, resetSettings, toggleVR]
18289
19271
  );
18290
19272
  useEffect40(() => {
18291
19273
  let active = true;
@@ -18314,13 +19296,24 @@ var WebVR = forwardRef4(function WebVR2({
18314
19296
  return () => {
18315
19297
  if (host.controller === controller) {
18316
19298
  host.controller = null;
18317
- updateWebVRHost(host, { mode: null, setupOpen: false });
19299
+ dpdbRequestIdRef.current += 1;
19300
+ setDetectedScreensize(null);
19301
+ updateWebVRHost(host, {
19302
+ detectedScreensize: null,
19303
+ mode: null,
19304
+ setupOpen: false
19305
+ });
18318
19306
  }
18319
19307
  };
18320
19308
  }, [controller, host]);
18321
19309
  useEffect40(() => {
18322
- updateWebVRHost(host, { chrome, mode, settings });
18323
- }, [chrome, host, mode, settings]);
19310
+ updateWebVRHost(host, {
19311
+ chrome,
19312
+ detectedScreensize,
19313
+ mode,
19314
+ settings
19315
+ });
19316
+ }, [chrome, detectedScreensize, host, mode, settings]);
18324
19317
  useEffect40(() => {
18325
19318
  if (mode !== "fake") {
18326
19319
  return;
@@ -18369,6 +19362,23 @@ var WebVR = forwardRef4(function WebVR2({
18369
19362
  void exitVR();
18370
19363
  });
18371
19364
  }, [exitVR, gl.domElement, mode]);
19365
+ useEffect40(() => {
19366
+ if (mode !== "webxr") {
19367
+ return;
19368
+ }
19369
+ const session2 = webXRSessionRef.current ?? xrStore.getState().session ?? null;
19370
+ if (!session2) {
19371
+ return;
19372
+ }
19373
+ const nextFoveation = foveationRef.current;
19374
+ if (nextFoveation !== void 0 && typeof gl.xr.setFoveation === "function") {
19375
+ gl.xr.setFoveation(nextFoveation);
19376
+ }
19377
+ const frameRateOption = resolveWebVRFrameRateOption(frameRateRef.current);
19378
+ if (frameRateOption !== void 0) {
19379
+ xrStore.setFrameRate(frameRateOption);
19380
+ }
19381
+ }, [foveation, frameRate, gl.xr, mode, xrStore]);
18372
19382
  useWebVRPointerLock(
18373
19383
  mousePointerLock && mode === "fake",
18374
19384
  gl.domElement
@@ -18376,27 +19386,31 @@ var WebVR = forwardRef4(function WebVR2({
18376
19386
  useWebVRWakeLock(
18377
19387
  wakelock && (mode === "mobilevr" || mode === "fake")
18378
19388
  );
18379
- return /* @__PURE__ */ jsxs21(Fragment6, { children: [
19389
+ return /* @__PURE__ */ jsxs21(Fragment8, { children: [
18380
19390
  /* @__PURE__ */ jsx40(
18381
19391
  Gyro,
18382
19392
  {
18383
19393
  ref: gyroRef,
19394
+ absolute,
18384
19395
  desktopSupport,
18385
19396
  enabled: mode === "mobilevr",
19397
+ friction,
19398
+ north,
18386
19399
  onDenied: () => {
18387
19400
  reportDenied();
18388
19401
  deactivate("mobilevr");
18389
19402
  },
18390
19403
  softstart: 0,
18391
- touchMode: "off"
19404
+ touchMode: touchSupport ? "horizontaloffset" : "off"
18392
19405
  }
18393
19406
  ),
18394
19407
  /* @__PURE__ */ jsx40(
18395
19408
  WebVRStereoRenderer,
18396
19409
  {
18397
19410
  mode,
19411
+ oversampling: resolvedOversampling,
18398
19412
  profile: resolvedProfile,
18399
- screensize: settings.screensize
19413
+ screensize: effectiveScreensize
18400
19414
  }
18401
19415
  ),
18402
19416
  /* @__PURE__ */ jsx40(
@@ -18412,12 +19426,12 @@ var WebVR = forwardRef4(function WebVR2({
18412
19426
 
18413
19427
  // src/pano-scene-menu.tsx
18414
19428
  import {
18415
- useCallback as useCallback11,
19429
+ useCallback as useCallback12,
18416
19430
  useEffect as useEffect41,
18417
19431
  useId as useId3,
18418
19432
  useLayoutEffect as useLayoutEffect9,
18419
19433
  useMemo as useMemo27,
18420
- useRef as useRef40,
19434
+ useRef as useRef41,
18421
19435
  useState as useState25
18422
19436
  } from "react";
18423
19437
 
@@ -18551,6 +19565,13 @@ function pushSample(samples, sample, maxCount = 8) {
18551
19565
  return next;
18552
19566
  }
18553
19567
 
19568
+ // src/overlay/toggle.ts
19569
+ function resolveOverlayToggleIcon(icon, collapsed) {
19570
+ if (icon === void 0) return void 0;
19571
+ if (typeof icon === "function") return icon({ collapsed });
19572
+ return icon;
19573
+ }
19574
+
18554
19575
  // src/pano-scene-menu.tsx
18555
19576
  import { jsx as jsx41, jsxs as jsxs22 } from "react/jsx-runtime";
18556
19577
  var SCENE_MENU_DRAG_THRESHOLD_PX = 6;
@@ -18571,37 +19592,15 @@ var DEFAULT_PANO_SCENE_MENU_APPEARANCE = {
18571
19592
  controlBackground: "rgba(255, 255, 255, 0.12)"
18572
19593
  };
18573
19594
  var DEFAULT_INSET = 12;
18574
- function cssLength(value) {
18575
- return typeof value === "number" ? `${value}px` : value;
18576
- }
18577
19595
  function resolveAppearance2(appearance) {
18578
19596
  return {
18579
19597
  ...DEFAULT_PANO_SCENE_MENU_APPEARANCE,
18580
19598
  ...appearance
18581
19599
  };
18582
19600
  }
18583
- function resolveInset(inset) {
18584
- if (inset === void 0) {
18585
- const value = cssLength(DEFAULT_INSET);
18586
- return { top: value, right: value, bottom: value, left: value };
18587
- }
18588
- if (typeof inset === "number" || typeof inset === "string") {
18589
- const value = cssLength(inset);
18590
- return { top: value, right: value, bottom: value, left: value };
18591
- }
18592
- return {
18593
- top: cssLength(inset.top ?? DEFAULT_INSET),
18594
- right: cssLength(inset.right ?? DEFAULT_INSET),
18595
- bottom: cssLength(inset.bottom ?? DEFAULT_INSET),
18596
- left: cssLength(inset.left ?? DEFAULT_INSET)
18597
- };
18598
- }
18599
19601
  function itemLabel(item) {
18600
19602
  return item.title?.trim() || item.id;
18601
19603
  }
18602
- function isHorizontal(placement) {
18603
- return placement === "top" || placement === "bottom";
18604
- }
18605
19604
  var CHEVRON_PATHS = {
18606
19605
  // Keep glyphs optically centered in the 16×16 viewBox.
18607
19606
  up: "M3 11l5-6 5 6",
@@ -18615,40 +19614,6 @@ function arrowChevron(direction, orientation2) {
18615
19614
  }
18616
19615
  return orientation2 === "horizontal" ? "right" : "down";
18617
19616
  }
18618
- function toggleChevron(placement, expanded) {
18619
- switch (placement) {
18620
- case "bottom":
18621
- return expanded ? "down" : "up";
18622
- case "top":
18623
- return expanded ? "up" : "down";
18624
- case "left":
18625
- return expanded ? "left" : "right";
18626
- case "right":
18627
- return expanded ? "right" : "left";
18628
- default: {
18629
- const exhaustive = placement;
18630
- void exhaustive;
18631
- return "down";
18632
- }
18633
- }
18634
- }
18635
- function dockInset(placement, inset) {
18636
- switch (placement) {
18637
- case "top":
18638
- return inset.top;
18639
- case "bottom":
18640
- return inset.bottom;
18641
- case "left":
18642
- return inset.left;
18643
- case "right":
18644
- return inset.right;
18645
- default: {
18646
- const exhaustive = placement;
18647
- void exhaustive;
18648
- return inset.bottom;
18649
- }
18650
- }
18651
- }
18652
19617
  function Chevron({ arrow }) {
18653
19618
  return /* @__PURE__ */ jsx41(
18654
19619
  "svg",
@@ -18671,6 +19636,7 @@ function ControlButton({
18671
19636
  disabled,
18672
19637
  label,
18673
19638
  onClick,
19639
+ style,
18674
19640
  children
18675
19641
  }) {
18676
19642
  return /* @__PURE__ */ jsx41(
@@ -18683,108 +19649,18 @@ function ControlButton({
18683
19649
  onPointerDown: (event) => {
18684
19650
  event.stopPropagation();
18685
19651
  },
19652
+ style,
18686
19653
  title: label,
18687
19654
  type: "button",
18688
19655
  children
18689
19656
  }
18690
19657
  );
18691
19658
  }
18692
- function horizontalAlignStyle(align, inset, widthValue) {
18693
- const left = `calc(${inset.left} + var(--pano-safe-left))`;
18694
- const right = `calc(${inset.right} + var(--pano-safe-right))`;
18695
- if (!widthValue) {
18696
- return { left, right, width: void 0, marginInline: void 0 };
18697
- }
18698
- switch (align) {
18699
- case "start":
18700
- return { left, right: "auto", width: widthValue, marginInline: void 0 };
18701
- case "end":
18702
- return { left: "auto", right, width: widthValue, marginInline: void 0 };
18703
- case "center":
18704
- return { left, right, width: widthValue, marginInline: "auto" };
18705
- default: {
18706
- const exhaustive = align;
18707
- void exhaustive;
18708
- return { left, right, width: widthValue, marginInline: "auto" };
18709
- }
18710
- }
18711
- }
18712
- function verticalAlignStyle(align, inset, heightValue) {
18713
- const top = `calc(${inset.top} + var(--pano-safe-top))`;
18714
- const bottom = `calc(${inset.bottom} + var(--pano-safe-bottom))`;
18715
- if (!heightValue) {
18716
- return { top, bottom, height: void 0, marginBlock: void 0 };
18717
- }
18718
- switch (align) {
18719
- case "start":
18720
- return { top, bottom: "auto", height: heightValue, marginBlock: void 0 };
18721
- case "end":
18722
- return { top: "auto", bottom, height: heightValue, marginBlock: void 0 };
18723
- case "center":
18724
- return { top, bottom, height: heightValue, marginBlock: "auto" };
18725
- default: {
18726
- const exhaustive = align;
18727
- void exhaustive;
18728
- return { top, bottom, height: heightValue, marginBlock: "auto" };
18729
- }
18730
- }
18731
- }
18732
- function placementBoxStyle(placement, inset, width, height, align) {
18733
- const widthValue = width === void 0 ? void 0 : cssLength(width);
18734
- const heightValue = height === void 0 ? void 0 : cssLength(height);
18735
- const maxWidth = `calc(100% - ${inset.left} - ${inset.right} - var(--pano-safe-left) - var(--pano-safe-right))`;
18736
- const maxHeight = `calc(100% - ${inset.top} - ${inset.bottom} - var(--pano-safe-top) - var(--pano-safe-bottom))`;
18737
- switch (placement) {
18738
- case "top":
18739
- return {
18740
- ...horizontalAlignStyle(align, inset, widthValue),
18741
- top: `calc(${inset.top} + var(--pano-safe-top))`,
18742
- bottom: "auto",
18743
- maxWidth,
18744
- height: heightValue,
18745
- maxHeight
18746
- };
18747
- case "bottom":
18748
- return {
18749
- ...horizontalAlignStyle(align, inset, widthValue),
18750
- top: "auto",
18751
- bottom: `calc(${inset.bottom} + var(--pano-safe-bottom))`,
18752
- maxWidth,
18753
- height: heightValue,
18754
- maxHeight
18755
- };
18756
- case "left":
18757
- return {
18758
- ...verticalAlignStyle(align, inset, heightValue),
18759
- right: "auto",
18760
- left: `calc(${inset.left} + var(--pano-safe-left))`,
18761
- width: widthValue,
18762
- maxWidth,
18763
- maxHeight
18764
- };
18765
- case "right":
18766
- return {
18767
- ...verticalAlignStyle(align, inset, heightValue),
18768
- right: `calc(${inset.right} + var(--pano-safe-right))`,
18769
- left: "auto",
18770
- width: widthValue,
18771
- maxWidth,
18772
- maxHeight
18773
- };
18774
- default: {
18775
- const exhaustive = placement;
18776
- void exhaustive;
18777
- return {};
18778
- }
18779
- }
18780
- }
18781
19659
  function PanoSceneMenu({
18782
19660
  items,
18783
19661
  activeId = null,
18784
19662
  onSelect,
18785
- placement = "bottom",
18786
- align = "center",
18787
- inset,
19663
+ anchor,
18788
19664
  width,
18789
19665
  height,
18790
19666
  collapsible = false,
@@ -18801,15 +19677,19 @@ function PanoSceneMenu({
18801
19677
  previousLabel = "Previous scene",
18802
19678
  nextLabel = "Next scene",
18803
19679
  expandLabel = "Show scenes",
18804
- collapseLabel = "Hide scenes"
19680
+ collapseLabel = "Hide scenes",
19681
+ toggle
18805
19682
  }) {
18806
19683
  const listId = useId3();
18807
- const listRef = useRef40(null);
18808
- const trackRef = useRef40(null);
18809
- const itemRefs = useRef40(/* @__PURE__ */ new Map());
19684
+ const listRef = useRef41(null);
19685
+ const trackRef = useRef41(null);
19686
+ const itemRefs = useRef41(/* @__PURE__ */ new Map());
18810
19687
  const resolved = resolveAppearance2(appearance);
18811
- const resolvedInset = resolveInset(inset);
18812
- const horizontal = isHorizontal(placement);
19688
+ const resolvedAnchor = resolveOverlayAnchor(anchor, {
19689
+ align: "bottom",
19690
+ inset: DEFAULT_INSET
19691
+ });
19692
+ const horizontal = resolvedAnchor.axis === "horizontal";
18813
19693
  const customDrag = dragMotionProp !== false;
18814
19694
  const dragMotion = resolveDragMotion(
18815
19695
  dragMotionProp === false ? false : dragMotionProp
@@ -18820,9 +19700,9 @@ function PanoSceneMenu({
18820
19700
  const [offset, setOffset] = useState25(0);
18821
19701
  const [viewportSize, setViewportSize] = useState25(0);
18822
19702
  const [contentSize, setContentSize] = useState25(0);
18823
- const offsetRef = useRef40(0);
18824
- const dragRef = useRef40(null);
18825
- const animRef = useRef40(null);
19703
+ const offsetRef = useRef41(0);
19704
+ const dragRef = useRef41(null);
19705
+ const animRef = useRef41(null);
18826
19706
  const maxOffset = Math.max(0, contentSize - viewportSize);
18827
19707
  offsetRef.current = offset;
18828
19708
  const activeIndex = useMemo27(() => {
@@ -18831,18 +19711,18 @@ function PanoSceneMenu({
18831
19711
  }
18832
19712
  return items.findIndex((item) => item.id === activeId);
18833
19713
  }, [activeId, items]);
18834
- const cancelScrollAnimation = useCallback11(() => {
19714
+ const cancelScrollAnimation = useCallback12(() => {
18835
19715
  const anim = animRef.current;
18836
19716
  if (anim?.raf != null) {
18837
19717
  cancelAnimationFrame(anim.raf);
18838
19718
  }
18839
19719
  animRef.current = null;
18840
19720
  }, []);
18841
- const clampOffset = useCallback11(
19721
+ const clampOffset = useCallback12(
18842
19722
  (value) => Math.min(maxOffset, Math.max(0, value)),
18843
19723
  [maxOffset]
18844
19724
  );
18845
- const startScrollAnimation = useCallback11(
19725
+ const startScrollAnimation = useCallback12(
18846
19726
  (velocity) => {
18847
19727
  cancelScrollAnimation();
18848
19728
  const current = offsetRef.current;
@@ -18982,15 +19862,15 @@ function PanoSceneMenu({
18982
19862
  "--pano-scene-menu-label-align": resolved.labelAlign,
18983
19863
  "--pano-scene-menu-font-size": cssLength(resolved.fontSize),
18984
19864
  "--pano-scene-menu-control-color": resolved.controlColor,
18985
- "--pano-scene-menu-control-background": resolved.controlBackground,
18986
- "--pano-scene-menu-dock-inset": dockInset(placement, resolvedInset)
19865
+ "--pano-scene-menu-control-background": resolved.controlBackground
18987
19866
  };
18988
19867
  return {
18989
19868
  ...vars,
18990
- ...placementBoxStyle(placement, resolvedInset, width, height, align),
19869
+ ["--pano-overlay-dock-inset"]: resolvedAnchor.dockInset,
19870
+ ...overlayBoxStyle(resolvedAnchor.align, resolvedAnchor.inset, width, height),
18991
19871
  ...style
18992
19872
  };
18993
- }, [align, height, placement, resolved, resolvedInset, style, width]);
19873
+ }, [height, resolved, resolvedAnchor, style, width]);
18994
19874
  const setExpanded = (next) => {
18995
19875
  if (!collapsible) {
18996
19876
  return;
@@ -19109,7 +19989,7 @@ function PanoSceneMenu({
19109
19989
  setOffset((previous) => clampOffset(previous + event.deltaX));
19110
19990
  };
19111
19991
  const axisPoint = (event) => horizontal ? event.clientX : event.clientY;
19112
- const endSuppressClickRef = useRef40(false);
19992
+ const endSuppressClickRef = useRef41(false);
19113
19993
  const beginListDrag = (event, captureTarget) => {
19114
19994
  if (!customDrag || event.button !== 0) return;
19115
19995
  cancelScrollAnimation();
@@ -19196,13 +20076,13 @@ function PanoSceneMenu({
19196
20076
  "div",
19197
20077
  {
19198
20078
  "aria-label": label,
19199
- className: cn("pano-scene-menu", className),
19200
- "data-align": align,
20079
+ className: cn("pano-overlay-anchor", "pano-scene-menu", className),
20080
+ "data-align": resolvedAnchor.contentAlign,
20081
+ "data-collapse-edge": resolvedAnchor.collapseEdge,
20082
+ "data-collapsed": collapsible && !expanded ? "true" : "false",
19201
20083
  "data-collapsible": collapsible ? "true" : void 0,
19202
20084
  "data-drag-motion": customDrag ? "custom" : "native",
19203
- "data-expanded": expanded ? "true" : "false",
19204
20085
  "data-orientation": orientation2,
19205
- "data-placement": placement,
19206
20086
  role: "group",
19207
20087
  style: rootStyle,
19208
20088
  children: [
@@ -19307,10 +20187,19 @@ function PanoSceneMenu({
19307
20187
  collapsible ? /* @__PURE__ */ jsx41(
19308
20188
  ControlButton,
19309
20189
  {
19310
- className: "pano-scene-menu-toggle",
20190
+ className: cn(
20191
+ "pano-scene-menu-toggle pano-overlay-anchor-toggle",
20192
+ toggle?.className
20193
+ ),
19311
20194
  label: expanded ? collapseLabel : expandLabel,
19312
20195
  onClick: () => setExpanded(!expanded),
19313
- children: /* @__PURE__ */ jsx41(Chevron, { arrow: toggleChevron(placement, expanded) })
20196
+ style: toggle?.style,
20197
+ children: toggle?.icon === void 0 ? /* @__PURE__ */ jsx41(
20198
+ Chevron,
20199
+ {
20200
+ arrow: overlayChevron(resolvedAnchor.collapseEdge, !expanded)
20201
+ }
20202
+ ) : resolveOverlayToggleIcon(toggle.icon, !expanded)
19314
20203
  }
19315
20204
  ) : null
19316
20205
  ]
@@ -19319,7 +20208,7 @@ function PanoSceneMenu({
19319
20208
  }
19320
20209
 
19321
20210
  // src/use-pano-viewer.ts
19322
- import { useMemo as useMemo28, useRef as useRef41 } from "react";
20211
+ import { useMemo as useMemo28, useRef as useRef42 } from "react";
19323
20212
  function createPanoViewerHandle(ref) {
19324
20213
  return {
19325
20214
  getView: () => ref.current?.getView() ?? { yaw: 0, pitch: 0, fov: 75 },
@@ -19350,6 +20239,36 @@ function createPanoViewerHandle(ref) {
19350
20239
  isVRAvailable: () => ref.current?.isVRAvailable() ?? false,
19351
20240
  isVREnabled: () => ref.current?.isVREnabled() ?? false,
19352
20241
  getVRMode: () => ref.current?.getVRMode() ?? null,
20242
+ getVRState: () => ref.current?.getVRState() ?? EMPTY_WEBVR_STATE,
20243
+ subscribeVR: (onStoreChange) => {
20244
+ let disposed = false;
20245
+ let unsubscribe;
20246
+ const timers = [];
20247
+ const attach = () => {
20248
+ if (disposed || unsubscribe) {
20249
+ return;
20250
+ }
20251
+ const handle = ref.current;
20252
+ if (!handle) {
20253
+ return;
20254
+ }
20255
+ unsubscribe = handle.subscribeVR(onStoreChange);
20256
+ onStoreChange();
20257
+ };
20258
+ attach();
20259
+ if (typeof window !== "undefined") {
20260
+ for (const delay of [0, 16, 50, 100, 250]) {
20261
+ timers.push(window.setTimeout(attach, delay));
20262
+ }
20263
+ }
20264
+ return () => {
20265
+ disposed = true;
20266
+ for (const id of timers) {
20267
+ window.clearTimeout(id);
20268
+ }
20269
+ unsubscribe?.();
20270
+ };
20271
+ },
19353
20272
  requestVRPermission: () => ref.current?.requestVRPermission() ?? Promise.resolve(false),
19354
20273
  getVideo: () => ref.current?.getVideo() ?? null,
19355
20274
  subscribeVideo: (onStoreChange) => ref.current?.subscribeVideo(onStoreChange) ?? (() => {
@@ -19401,7 +20320,7 @@ function createPanoViewerHandle(ref) {
19401
20320
  };
19402
20321
  }
19403
20322
  function usePanoViewer() {
19404
- const ref = useRef41(null);
20323
+ const ref = useRef42(null);
19405
20324
  const handle = useMemo28(() => createPanoViewerHandle(ref), []);
19406
20325
  return useMemo28(() => ({ ref, ...handle }), [handle]);
19407
20326
  }
@@ -19409,25 +20328,25 @@ function usePanoViewer() {
19409
20328
  // src/video/pano-video.tsx
19410
20329
  import { useThree as useThree29 } from "@react-three/fiber";
19411
20330
  import {
19412
- useCallback as useCallback12,
20331
+ useCallback as useCallback13,
19413
20332
  useContext as useContext28,
19414
20333
  useEffect as useEffect42,
19415
20334
  useLayoutEffect as useLayoutEffect10,
19416
20335
  useMemo as useMemo29,
19417
- useRef as useRef42,
20336
+ useRef as useRef43,
19418
20337
  useState as useState26
19419
20338
  } from "react";
19420
20339
  import {
19421
20340
  BackSide as BackSide4,
19422
- LinearFilter as LinearFilter11,
19423
- LinearMipmapLinearFilter as LinearMipmapLinearFilter7,
19424
- MathUtils as MathUtils13,
20341
+ LinearFilter as LinearFilter10,
20342
+ LinearMipmapLinearFilter as LinearMipmapLinearFilter8,
20343
+ MathUtils as MathUtils14,
19425
20344
  RepeatWrapping as RepeatWrapping2,
19426
- SRGBColorSpace as SRGBColorSpace10,
20345
+ SRGBColorSpace as SRGBColorSpace9,
19427
20346
  TextureLoader as TextureLoader8,
19428
20347
  VideoTexture as VideoTexture2
19429
20348
  } from "three";
19430
- import { Fragment as Fragment7, jsx as jsx42, jsxs as jsxs23 } from "react/jsx-runtime";
20349
+ import { Fragment as Fragment9, jsx as jsx42, jsxs as jsxs23 } from "react/jsx-runtime";
19431
20350
  var EMPTY_TRACKS = [];
19432
20351
  var EMPTY_RATES = DEFAULT_PANO_VIDEO_PLAYBACK_RATES;
19433
20352
  function createEmptySnapshot() {
@@ -19503,7 +20422,7 @@ function findTextTrack(video, tracks, trackId) {
19503
20422
  return list.length > 0 ? list[0] : null;
19504
20423
  }
19505
20424
  function applyEquirectangularLayout(texture, anisotropy) {
19506
- texture.colorSpace = SRGBColorSpace10;
20425
+ texture.colorSpace = SRGBColorSpace9;
19507
20426
  texture.wrapS = RepeatWrapping2;
19508
20427
  texture.repeat.x = -1;
19509
20428
  texture.offset.x = 1;
@@ -19513,7 +20432,7 @@ function applyEquirectangularLayout(texture, anisotropy) {
19513
20432
  function usePosterTexture2(poster, onError) {
19514
20433
  const gl = useThree29((state) => state.gl);
19515
20434
  const [texture, setTexture] = useState26(null);
19516
- const onErrorRef = useRef42(onError);
20435
+ const onErrorRef = useRef43(onError);
19517
20436
  onErrorRef.current = onError;
19518
20437
  useEffect42(() => {
19519
20438
  let active = true;
@@ -19536,8 +20455,8 @@ function usePosterTexture2(poster, onError) {
19536
20455
  gl.capabilities.getMaxAnisotropy()
19537
20456
  );
19538
20457
  loadedTexture.wrapT = RepeatWrapping2;
19539
- loadedTexture.magFilter = LinearFilter11;
19540
- loadedTexture.minFilter = LinearMipmapLinearFilter7;
20458
+ loadedTexture.magFilter = LinearFilter10;
20459
+ loadedTexture.minFilter = LinearMipmapLinearFilter8;
19541
20460
  loadedTexture.generateMipmaps = true;
19542
20461
  setTexture(loadedTexture);
19543
20462
  },
@@ -19631,17 +20550,17 @@ function PanoVideo({
19631
20550
  () => captionsEnabled ? defaultPanoVideoTrackId(tracks) : null
19632
20551
  );
19633
20552
  const [ready, setReady] = useState26(false);
19634
- const autoPlayRef = useRef42(autoPlay);
20553
+ const autoPlayRef = useRef43(autoPlay);
19635
20554
  autoPlayRef.current = autoPlay;
19636
- const blockedRef = useRef42(false);
19637
- const captionAppearanceOverrideRef = useRef42(
20555
+ const blockedRef = useRef43(false);
20556
+ const captionAppearanceOverrideRef = useRef43(
19638
20557
  {}
19639
20558
  );
19640
- const mediaCapabilitiesRef = useRef42({
20559
+ const mediaCapabilitiesRef = useRef43({
19641
20560
  volumeAdjustable: true,
19642
20561
  playbackRates: [...DEFAULT_PANO_VIDEO_PLAYBACK_RATES]
19643
20562
  });
19644
- const storeRef = useRef42(null);
20563
+ const storeRef = useRef43(null);
19645
20564
  if (!storeRef.current) {
19646
20565
  storeRef.current = {
19647
20566
  snapshot: {
@@ -19651,7 +20570,7 @@ function PanoVideo({
19651
20570
  listeners: /* @__PURE__ */ new Set()
19652
20571
  };
19653
20572
  }
19654
- const actionsRef = useRef42({
20573
+ const actionsRef = useRef43({
19655
20574
  play: async () => {
19656
20575
  },
19657
20576
  pause: () => {
@@ -19671,12 +20590,12 @@ function PanoVideo({
19671
20590
  setCaptionAppearance: (_appearance) => {
19672
20591
  }
19673
20592
  });
19674
- const onLoadRef = useRef42(onLoad);
19675
- const onErrorRef = useRef42(onError);
19676
- const onPlaybackStateChangeRef = useRef42(onPlaybackStateChange);
19677
- const onEndedRef = useRef42(onEnded);
19678
- const onVariantChangeRef = useRef42(onVariantChange);
19679
- const onTrackChangeRef = useRef42(onTrackChange);
20593
+ const onLoadRef = useRef43(onLoad);
20594
+ const onErrorRef = useRef43(onError);
20595
+ const onPlaybackStateChangeRef = useRef43(onPlaybackStateChange);
20596
+ const onEndedRef = useRef43(onEnded);
20597
+ const onVariantChangeRef = useRef43(onVariantChange);
20598
+ const onTrackChangeRef = useRef43(onTrackChange);
19680
20599
  onLoadRef.current = onLoad;
19681
20600
  onErrorRef.current = onError;
19682
20601
  onPlaybackStateChangeRef.current = onPlaybackStateChange;
@@ -19693,8 +20612,8 @@ function PanoVideo({
19693
20612
  video.preload = "metadata";
19694
20613
  const texture = new VideoTexture2(video);
19695
20614
  applyEquirectangularLayout(texture, 1);
19696
- texture.magFilter = LinearFilter11;
19697
- texture.minFilter = LinearFilter11;
20615
+ texture.magFilter = LinearFilter10;
20616
+ texture.minFilter = LinearFilter10;
19698
20617
  texture.generateMipmaps = false;
19699
20618
  return { video, texture };
19700
20619
  }, []);
@@ -19735,7 +20654,7 @@ function PanoVideo({
19735
20654
  activeVariant?.poster ?? poster,
19736
20655
  onError
19737
20656
  );
19738
- const emitSnapshot = useCallback12(
20657
+ const emitSnapshot = useCallback13(
19739
20658
  (patch) => {
19740
20659
  const store = storeRef.current;
19741
20660
  const video = resource?.video;
@@ -20126,11 +21045,11 @@ function PanoVideo({
20126
21045
  if (variants.length === 0) {
20127
21046
  return null;
20128
21047
  }
20129
- return /* @__PURE__ */ jsxs23(Fragment7, { children: [
21048
+ return /* @__PURE__ */ jsxs23(Fragment9, { children: [
20130
21049
  activeTexture ? /* @__PURE__ */ jsxs23(
20131
21050
  "mesh",
20132
21051
  {
20133
- rotation: [0, MathUtils13.degToRad(90 + yawOffset2), 0],
21052
+ rotation: [0, MathUtils14.degToRad(90 + yawOffset2), 0],
20134
21053
  visible,
20135
21054
  children: [
20136
21055
  /* @__PURE__ */ jsx42("sphereGeometry", { args: [DEFAULT_PANORAMA_RADIUS, 128, 64] }),
@@ -20151,12 +21070,12 @@ function PanoVideo({
20151
21070
 
20152
21071
  // src/map/pano-map.tsx
20153
21072
  import {
20154
- useCallback as useCallback13,
21073
+ useCallback as useCallback14,
20155
21074
  useEffect as useEffect44,
20156
21075
  useId as useId4,
20157
21076
  useLayoutEffect as useLayoutEffect11,
20158
21077
  useMemo as useMemo30,
20159
- useRef as useRef44,
21078
+ useRef as useRef45,
20160
21079
  useState as useState28
20161
21080
  } from "react";
20162
21081
 
@@ -20218,12 +21137,12 @@ function normalizeDegrees(value) {
20218
21137
  }
20219
21138
 
20220
21139
  // src/map/use-map-view.ts
20221
- import { useContext as useContext29, useEffect as useEffect43, useRef as useRef43, useState as useState27 } from "react";
21140
+ import { useContext as useContext29, useEffect as useEffect43, useRef as useRef44, useState as useState27 } from "react";
20222
21141
  var DEFAULT_VIEW2 = { yaw: 0, pitch: 0, fov: 75 };
20223
21142
  function useMapView(controlled) {
20224
21143
  const eventBus = useContext29(PanoEventBusContext);
20225
21144
  const [view, setView] = useState27(controlled ?? DEFAULT_VIEW2);
20226
- const controlledRef = useRef43(controlled);
21145
+ const controlledRef = useRef44(controlled);
20227
21146
  controlledRef.current = controlled;
20228
21147
  useEffect43(() => {
20229
21148
  if (controlled !== void 0) {
@@ -20240,7 +21159,7 @@ function useMapView(controlled) {
20240
21159
  }
20241
21160
 
20242
21161
  // src/map/pano-map.tsx
20243
- import { Fragment as Fragment8, jsx as jsx43, jsxs as jsxs24 } from "react/jsx-runtime";
21162
+ import { Fragment as Fragment10, jsx as jsx43, jsxs as jsxs24 } from "react/jsx-runtime";
20244
21163
  var DEFAULT_ICON_SIZE2 = 28;
20245
21164
  var DEFAULT_SECTOR_COLOR = "#ffffff";
20246
21165
  var DEFAULT_SECTOR_OPACITY = 0.55;
@@ -20377,20 +21296,20 @@ function PanoMap({
20377
21296
  }) {
20378
21297
  const reactId = useId4().replace(/:/g, "");
20379
21298
  const view = useMapView(controlledView);
20380
- const rootRef = useRef44(null);
20381
- const containerRef = useRef44(null);
20382
- const sectorNodeRef = useRef44(null);
20383
- const yawRef = useRef44(view.yaw);
21299
+ const rootRef = useRef45(null);
21300
+ const containerRef = useRef45(null);
21301
+ const sectorNodeRef = useRef45(null);
21302
+ const yawRef = useRef45(view.yaw);
20384
21303
  const [size, setSize] = useState28({ width: 0, height: 0 });
20385
21304
  const [imageSize2, setImageSize] = useState28({ width: 0, height: 0 });
20386
21305
  const [scale, setScale] = useState28(1);
20387
21306
  const [pan, setPan] = useState28({ x: 0, y: 0 });
20388
21307
  const [uncontrolledCollapsed, setUncontrolledCollapsed] = useState28(false);
20389
- const dragRef = useRef44(null);
20390
- const panAnimRef = useRef44(null);
20391
- const panRef = useRef44(pan);
20392
- const scaleRef = useRef44(scale);
20393
- const initialViewKeyRef = useRef44(null);
21308
+ const dragRef = useRef45(null);
21309
+ const panAnimRef = useRef45(null);
21310
+ const panRef = useRef45(pan);
21311
+ const scaleRef = useRef45(scale);
21312
+ const initialViewKeyRef = useRef45(null);
20394
21313
  const spots = map.spots;
20395
21314
  const iconSize = map.iconSize ?? DEFAULT_ICON_SIZE2;
20396
21315
  const interaction = map.interaction ?? "fixed";
@@ -20450,7 +21369,7 @@ function PanoMap({
20450
21369
  };
20451
21370
  panRef.current = pan;
20452
21371
  scaleRef.current = scale;
20453
- const panBoundsFor = useCallback13(
21372
+ const panBoundsFor = useCallback14(
20454
21373
  (nextScale) => {
20455
21374
  const width = fit.width * nextScale;
20456
21375
  const height = fit.height * nextScale;
@@ -20466,7 +21385,7 @@ function PanoMap({
20466
21385
  },
20467
21386
  [fit, size]
20468
21387
  );
20469
- const clampPan = useCallback13(
21388
+ const clampPan = useCallback14(
20470
21389
  (nextScale, nextPan) => {
20471
21390
  const bounds = panBoundsFor(nextScale);
20472
21391
  return {
@@ -20476,14 +21395,14 @@ function PanoMap({
20476
21395
  },
20477
21396
  [panBoundsFor]
20478
21397
  );
20479
- const cancelPanAnimation = useCallback13(() => {
21398
+ const cancelPanAnimation = useCallback14(() => {
20480
21399
  const anim = panAnimRef.current;
20481
21400
  if (anim?.raf != null) {
20482
21401
  cancelAnimationFrame(anim.raf);
20483
21402
  }
20484
21403
  panAnimRef.current = null;
20485
21404
  }, []);
20486
- const startPanAnimation = useCallback13(
21405
+ const startPanAnimation = useCallback14(
20487
21406
  (velocity) => {
20488
21407
  cancelPanAnimation();
20489
21408
  const current = panRef.current;
@@ -20589,7 +21508,7 @@ function PanoMap({
20589
21508
  size.height,
20590
21509
  size.width
20591
21510
  ]);
20592
- const applyZoom = useCallback13(
21511
+ const applyZoom = useCallback14(
20593
21512
  (nextScale, origin) => {
20594
21513
  if (!gestureZoom) return;
20595
21514
  cancelPanAnimation();
@@ -20801,7 +21720,7 @@ function PanoMap({
20801
21720
  children: collapsed ? "\u25A3" : "\u25A2"
20802
21721
  }
20803
21722
  ) : null,
20804
- zoomControls && !collapsed ? /* @__PURE__ */ jsxs24(Fragment8, { children: [
21723
+ zoomControls && !collapsed ? /* @__PURE__ */ jsxs24(Fragment10, { children: [
20805
21724
  /* @__PURE__ */ jsx43(
20806
21725
  "button",
20807
21726
  {
@@ -21043,17 +21962,27 @@ import {
21043
21962
  useEffect as useEffect45,
21044
21963
  useId as useId5,
21045
21964
  useMemo as useMemo31,
21046
- useRef as useRef45,
21965
+ useRef as useRef46,
21047
21966
  useState as useState29
21048
21967
  } from "react";
21049
- import { Fragment as Fragment9, jsx as jsx44, jsxs as jsxs25 } from "react/jsx-runtime";
21968
+ import { Fragment as Fragment11, jsx as jsx44, jsxs as jsxs25 } from "react/jsx-runtime";
21050
21969
  var DEFAULT_DIALOG_WIDTH = 720;
21051
21970
  var DEFAULT_DIALOG_HEIGHT = 520;
21052
- function dockedBox(corner, offsetX, offsetY, width, height) {
21053
- const horizontal = corner === "top-right" || corner === "bottom-right" ? { left: "auto", right: offsetX } : { left: offsetX, right: "auto" };
21054
- const vertical = corner === "bottom-left" || corner === "bottom-right" ? { top: "auto", bottom: offsetY } : { top: offsetY, bottom: "auto" };
21055
- return { ...horizontal, ...vertical, width, height };
21971
+ var MAP_ANCHOR_DEFAULTS = { align: "bottom-left", inset: 16 };
21972
+ function mergeAnchor(base, override) {
21973
+ if (base === void 0 && override === void 0) return void 0;
21974
+ return {
21975
+ ...base,
21976
+ ...override,
21977
+ inset: override?.inset !== void 0 ? override.inset : base?.inset
21978
+ };
21056
21979
  }
21980
+ var CHEVRON_PATHS2 = {
21981
+ up: "M3 11l5-6 5 6",
21982
+ down: "M3 5l5 6 5-6",
21983
+ left: "M11 3 5 8l6 5",
21984
+ right: "M5 3l6 5-6 5"
21985
+ };
21057
21986
  function PanoMapDialogShell({
21058
21987
  open,
21059
21988
  onOpenChange,
@@ -21066,8 +21995,8 @@ function PanoMapDialogShell({
21066
21995
  usePanoModal(open);
21067
21996
  const { size } = usePanoOverlay();
21068
21997
  const titleId = useId5();
21069
- const panelRef = useRef45(null);
21070
- const previouslyFocused = useRef45(null);
21998
+ const panelRef = useRef46(null);
21999
+ const previouslyFocused = useRef46(null);
21071
22000
  useEffect45(() => {
21072
22001
  if (!open) return;
21073
22002
  const active = panelRef.current?.ownerDocument.activeElement;
@@ -21096,7 +22025,7 @@ function PanoMapDialogShell({
21096
22025
  const height = Math.min(preferredHeight, Math.max(0, size.height - 32));
21097
22026
  const left = Math.max(0, (size.width - width) / 2);
21098
22027
  const top = Math.max(0, (size.height - height) / 2);
21099
- return /* @__PURE__ */ jsxs25(Fragment9, { children: [
22028
+ return /* @__PURE__ */ jsxs25(Fragment11, { children: [
21100
22029
  /* @__PURE__ */ jsx44(
21101
22030
  "div",
21102
22031
  {
@@ -21165,11 +22094,9 @@ function PanoMapOverlay({
21165
22094
  placementSceneId,
21166
22095
  presentationOverride,
21167
22096
  presentation: presentationModeProp,
21168
- corner: cornerProp,
22097
+ anchor: anchorProp,
21169
22098
  width: widthProp,
21170
22099
  height: heightProp,
21171
- offsetX: offsetXProp,
21172
- offsetY: offsetYProp,
21173
22100
  dialogWidth = DEFAULT_DIALOG_WIDTH,
21174
22101
  dialogHeight = DEFAULT_DIALOG_HEIGHT,
21175
22102
  interaction,
@@ -21183,23 +22110,22 @@ function PanoMapOverlay({
21183
22110
  title = "Map",
21184
22111
  className,
21185
22112
  style,
22113
+ toggle,
21186
22114
  editorSurface = false
21187
22115
  }) {
21188
22116
  const { size } = usePanoOverlay();
21189
22117
  const [open, setOpen] = useState29(false);
21190
22118
  const [collapsed, setCollapsed] = useState29(false);
21191
22119
  const basePresentation = presentationOverride ?? map.presentation ?? {
21192
- mode: "docked",
21193
- corner: "bottom-left",
22120
+ mode: "panel",
21194
22121
  width: 220,
21195
- height: 160,
21196
- offsetX: 16,
21197
- offsetY: 16
22122
+ height: 160
21198
22123
  };
21199
22124
  const presentationMode = presentationModeProp ?? basePresentation.mode;
21200
- const corner = cornerProp ?? basePresentation.corner ?? "bottom-left";
21201
- const offsetX = offsetXProp ?? basePresentation.offsetX ?? 16;
21202
- const offsetY = offsetYProp ?? basePresentation.offsetY ?? 16;
22125
+ const resolvedAnchor = resolveOverlayAnchor(
22126
+ mergeAnchor(basePresentation.anchor, anchorProp),
22127
+ MAP_ANCHOR_DEFAULTS
22128
+ );
21203
22129
  const width = widthProp ?? basePresentation.width ?? (presentationMode === "dialog" ? 48 : 220);
21204
22130
  const height = heightProp ?? basePresentation.height ?? (presentationMode === "dialog" ? 48 : 160);
21205
22131
  const resolvedMap = useMemo31(() => {
@@ -21231,15 +22157,15 @@ function PanoMapOverlay({
21231
22157
  }
21232
22158
  };
21233
22159
  const editorBox = useMemo31(() => {
21234
- const panelWidth2 = widthProp !== void 0 && Number.isFinite(widthProp) && widthProp > 0 ? widthProp : Math.min(420, Math.max(240, size.width * 0.45));
21235
- const panelHeight2 = heightProp !== void 0 && Number.isFinite(heightProp) && heightProp > 0 ? heightProp : Math.min(320, Math.max(180, size.height * 0.45));
22160
+ const panelWidth = widthProp !== void 0 && Number.isFinite(widthProp) && widthProp > 0 ? widthProp : Math.min(420, Math.max(240, size.width * 0.45));
22161
+ const panelHeight = heightProp !== void 0 && Number.isFinite(heightProp) && heightProp > 0 ? heightProp : Math.min(320, Math.max(180, size.height * 0.45));
21236
22162
  return {
21237
22163
  left: 16,
21238
22164
  right: "auto",
21239
22165
  top: "auto",
21240
22166
  bottom: 16,
21241
- width: panelWidth2,
21242
- height: panelHeight2
22167
+ width: panelWidth,
22168
+ height: panelHeight
21243
22169
  };
21244
22170
  }, [heightProp, size.height, size.width, widthProp]);
21245
22171
  if (editorSurface) {
@@ -21259,7 +22185,7 @@ function PanoMapOverlay({
21259
22185
  );
21260
22186
  }
21261
22187
  if (presentationMode === "dialog") {
21262
- const launcherBox = dockedBox(corner, offsetX, offsetY, width, height);
22188
+ const launcherBox = overlayBoxStyle(resolvedAnchor.align, resolvedAnchor.inset, width, height);
21263
22189
  return /* @__PURE__ */ jsxs25("div", { className: ["pano-map-overlay", className].filter(Boolean).join(" "), "data-pano-map-overlay": "", children: [
21264
22190
  /* @__PURE__ */ jsx44(
21265
22191
  "button",
@@ -21291,29 +22217,63 @@ function PanoMapOverlay({
21291
22217
  )
21292
22218
  ] });
21293
22219
  }
21294
- const panelWidth = collapsed ? Math.max(44, Math.min(width, 56)) : width;
21295
- const panelHeight = collapsed ? Math.max(44, Math.min(height, 56)) : height;
21296
- const box = dockedBox(corner, offsetX, offsetY, panelWidth, panelHeight);
21297
- return /* @__PURE__ */ jsx44(
22220
+ const collapseEnabled = showCollapseControl ?? map.controls?.collapse === true;
22221
+ const box = overlayBoxStyle(resolvedAnchor.align, resolvedAnchor.inset, width, height);
22222
+ const arrow = overlayChevron(resolvedAnchor.collapseEdge, collapsed);
22223
+ return /* @__PURE__ */ jsxs25(
21298
22224
  "div",
21299
22225
  {
21300
- className: ["pano-map-overlay", className].filter(Boolean).join(" "),
22226
+ className: ["pano-overlay-anchor", "pano-map-overlay", className].filter(Boolean).join(" "),
21301
22227
  style: {
21302
22228
  position: "absolute",
21303
- pointerEvents: "auto",
22229
+ pointerEvents: collapseEnabled && collapsed ? "none" : "auto",
22230
+ ["--pano-overlay-dock-inset"]: resolvedAnchor.dockInset,
22231
+ ["--pano-overlay-anchor-radius"]: "8px",
21304
22232
  ...box,
21305
22233
  ...style
21306
22234
  },
21307
22235
  "data-pano-map-overlay": "",
21308
- "data-presentation": "docked",
21309
- children: /* @__PURE__ */ jsx44(
21310
- PanoMap,
21311
- {
21312
- ...mapProps,
21313
- collapsed,
21314
- onCollapsedChange: setCollapsed
21315
- }
21316
- )
22236
+ "data-presentation": "panel",
22237
+ "data-collapse-edge": resolvedAnchor.collapseEdge,
22238
+ "data-collapsed": collapseEnabled && collapsed ? "true" : "false",
22239
+ children: [
22240
+ /* @__PURE__ */ jsx44(
22241
+ "div",
22242
+ {
22243
+ className: "pano-map-overlay-surface",
22244
+ "aria-hidden": collapseEnabled && collapsed ? true : void 0,
22245
+ ...collapseEnabled && collapsed ? { inert: true } : null,
22246
+ style: { height: "100%", width: "100%" },
22247
+ children: /* @__PURE__ */ jsx44(PanoMap, { ...mapProps, showCollapseControl: false })
22248
+ }
22249
+ ),
22250
+ collapseEnabled ? /* @__PURE__ */ jsx44(
22251
+ "button",
22252
+ {
22253
+ type: "button",
22254
+ className: ["pano-map-control", "pano-overlay-anchor-toggle", toggle?.className].filter(Boolean).join(" "),
22255
+ "aria-label": collapsed ? "Expand map" : "Collapse map",
22256
+ "aria-pressed": !collapsed,
22257
+ style: toggle?.style,
22258
+ onClick: () => setCollapsed((value) => !value),
22259
+ children: toggle?.icon === void 0 ? /* @__PURE__ */ jsx44(
22260
+ "svg",
22261
+ {
22262
+ "aria-hidden": true,
22263
+ fill: "none",
22264
+ height: "16",
22265
+ stroke: "currentColor",
22266
+ strokeLinecap: "round",
22267
+ strokeLinejoin: "round",
22268
+ strokeWidth: "1.75",
22269
+ viewBox: "0 0 16 16",
22270
+ width: "16",
22271
+ children: /* @__PURE__ */ jsx44("path", { d: CHEVRON_PATHS2[arrow] })
22272
+ }
22273
+ ) : resolveOverlayToggleIcon(toggle.icon, collapsed)
22274
+ }
22275
+ ) : null
22276
+ ]
21317
22277
  }
21318
22278
  );
21319
22279
  }
@@ -21330,6 +22290,7 @@ export {
21330
22290
  DEFAULT_PANO_VIDEO_PLAYBACK_RATES,
21331
22291
  DEFAULT_WEBVR_CHROME_APPEARANCE,
21332
22292
  DEFAULT_WEBVR_CURSOR_DWELL_MS,
22293
+ EMPTY_WEBVR_STATE,
21333
22294
  GraphicHotspot,
21334
22295
  Gyro,
21335
22296
  IframeHotspot,
@@ -21339,11 +22300,13 @@ export {
21339
22300
  MIN_PANO_CONTEXT_MENU_BACKGROUND_OPACITY,
21340
22301
  MouseControls,
21341
22302
  PANO_FILTER_PRESETS,
22303
+ PANO_OVERLAY_ALIGNS,
21342
22304
  PanoContextMenu,
21343
22305
  PanoEvents,
21344
22306
  PanoFilter,
21345
22307
  PanoMap,
21346
22308
  PanoMapOverlay,
22309
+ PanoOverlay,
21347
22310
  PanoOverlayPortalProvider,
21348
22311
  PanoSceneMenu,
21349
22312
  PanoVideo,
@@ -21373,9 +22336,13 @@ export {
21373
22336
  headingFromVector,
21374
22337
  horizontalFov,
21375
22338
  imagePointToContainer,
22339
+ isOverlayCorner,
21376
22340
  normalizePanoPosition,
21377
22341
  normalizePanoYaw,
21378
22342
  panoPositionToVector3,
22343
+ parseOverlayAnchor,
22344
+ resolveOverlayAnchor,
22345
+ resolveOverlayToggleIcon,
21379
22346
  resolvePanoContextMenuEntries,
21380
22347
  sectorRotation,
21381
22348
  unwrapPolygonVertices,