@ericchen1990/pano-view 2.0.0 → 2.2.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.cjs CHANGED
@@ -769,7 +769,7 @@ function useHotspotAccessibilityLayer() {
769
769
  "button",
770
770
  {
771
771
  "aria-label": entry.label,
772
- className: "sr-only",
772
+ className: "pano-sr-only",
773
773
  onBlur: () => entry.onFocusChange(false),
774
774
  onClick: (event) => entry.activate(event.nativeEvent),
775
775
  onFocus: () => entry.onFocusChange(true),
@@ -1478,9 +1478,6 @@ function DefaultContextMenuPanel({
1478
1478
  fontSize: resolved.fontSize,
1479
1479
  minWidth: resolved.minWidth,
1480
1480
  padding: resolved.padding,
1481
- pointerEvents: "auto",
1482
- position: "relative",
1483
- userSelect: "none",
1484
1481
  ...style
1485
1482
  };
1486
1483
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
@@ -1488,7 +1485,7 @@ function DefaultContextMenuPanel({
1488
1485
  {
1489
1486
  ref: menuRef,
1490
1487
  "aria-label": "Panorama context menu",
1491
- className: cn("relative select-none", className),
1488
+ className: cn("pano-context-menu", className),
1492
1489
  onKeyDown: handleKeyDown,
1493
1490
  role: "menu",
1494
1491
  style: panelStyle,
@@ -1498,7 +1495,7 @@ function DefaultContextMenuPanel({
1498
1495
  "div",
1499
1496
  {
1500
1497
  "aria-hidden": true,
1501
- className: "pointer-events-none absolute inset-0 z-0",
1498
+ className: "pano-context-menu-backdrop",
1502
1499
  style: {
1503
1500
  background: resolved.background,
1504
1501
  borderRadius: resolved.borderRadius,
@@ -1506,7 +1503,7 @@ function DefaultContextMenuPanel({
1506
1503
  }
1507
1504
  }
1508
1505
  ),
1509
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "relative z-[1]", children: visibleItems.map((item, index) => {
1506
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "pano-context-menu-items", children: visibleItems.map((item, index) => {
1510
1507
  if (!isActionItem(item)) {
1511
1508
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1512
1509
  "div",
@@ -1544,7 +1541,7 @@ function DefaultContextMenuPanel({
1544
1541
  {
1545
1542
  "aria-disabled": disabled || void 0,
1546
1543
  className: cn(
1547
- "flex w-full items-center gap-2 rounded text-left",
1544
+ "pano-context-menu-item",
1548
1545
  itemClassName,
1549
1546
  item.className
1550
1547
  ),
@@ -1579,7 +1576,7 @@ function DefaultContextMenuPanel({
1579
1576
  "span",
1580
1577
  {
1581
1578
  "aria-hidden": true,
1582
- className: "inline-flex shrink-0 items-center justify-center",
1579
+ className: "pano-context-menu-item-icon",
1583
1580
  style: {
1584
1581
  height: iconSize,
1585
1582
  width: iconSize
@@ -1630,7 +1627,7 @@ function PositionedMenuShell({
1630
1627
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1631
1628
  "div",
1632
1629
  {
1633
- className: "absolute z-20",
1630
+ className: "pano-context-menu-shell",
1634
1631
  ref: shellRef,
1635
1632
  style: {
1636
1633
  left: offset.left,
@@ -2747,7 +2744,7 @@ function usePanoChromeOverlay() {
2747
2744
  const overlay = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2748
2745
  "div",
2749
2746
  {
2750
- className: "pointer-events-none absolute inset-0 z-[15]",
2747
+ className: "pano-chrome-overlay",
2751
2748
  "data-pano-chrome-overlay": "",
2752
2749
  ref: setOverlayNode
2753
2750
  }
@@ -2839,14 +2836,7 @@ function PanoVideoCaptionsOverlay({
2839
2836
  return null;
2840
2837
  }
2841
2838
  const wrapperStyle = {
2842
- bottom: snapshot.captionAppearance.bottom,
2843
- display: "flex",
2844
- justifyContent: "center",
2845
- left: 0,
2846
- pointerEvents: "none",
2847
- position: "absolute",
2848
- right: 0,
2849
- zIndex: 1
2839
+ bottom: snapshot.captionAppearance.bottom
2850
2840
  };
2851
2841
  const textStyle = {
2852
2842
  background: snapshot.captionAppearance.background,
@@ -2865,7 +2855,7 @@ function PanoVideoCaptionsOverlay({
2865
2855
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2866
2856
  "div",
2867
2857
  {
2868
- className: "pointer-events-none absolute inset-x-0 z-1 flex justify-center",
2858
+ className: "pano-video-captions",
2869
2859
  "data-pano-video-captions": "",
2870
2860
  style: wrapperStyle,
2871
2861
  children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: textStyle, children: snapshot.captionText })
@@ -3100,7 +3090,7 @@ function ChromeButton({
3100
3090
  "button",
3101
3091
  {
3102
3092
  "aria-label": label,
3103
- className: "inline-flex min-h-7 min-w-7 items-center justify-center rounded",
3093
+ className: "pano-video-chrome-button",
3104
3094
  disabled,
3105
3095
  id,
3106
3096
  onClick,
@@ -3108,18 +3098,7 @@ function ChromeButton({
3108
3098
  event.stopPropagation();
3109
3099
  },
3110
3100
  style: {
3111
- alignItems: "center",
3112
- background: "transparent",
3113
- border: "none",
3114
- borderRadius: 4,
3115
- color: appearance.color,
3116
- cursor: disabled ? "default" : "pointer",
3117
- display: "inline-flex",
3118
- height: 28,
3119
- justifyContent: "center",
3120
- minWidth: 28,
3121
- opacity: disabled ? 0.45 : 1,
3122
- padding: 4
3101
+ color: appearance.color
3123
3102
  },
3124
3103
  title: label,
3125
3104
  type: "button",
@@ -3164,7 +3143,7 @@ function ChromeMenuSurface({
3164
3143
  "div",
3165
3144
  {
3166
3145
  "aria-labelledby": labelledBy,
3167
- className: "absolute right-0 flex min-w-28 flex-col p-1",
3146
+ className: "pano-video-chrome-menu",
3168
3147
  id,
3169
3148
  role,
3170
3149
  style: {
@@ -3188,7 +3167,7 @@ function ChromeMenuRadioItem({
3188
3167
  "button",
3189
3168
  {
3190
3169
  "aria-checked": active,
3191
- className: "rounded px-2.5 py-1.5 text-left whitespace-nowrap",
3170
+ className: "pano-video-chrome-menu-item",
3192
3171
  onClick,
3193
3172
  role: "menuitemradio",
3194
3173
  style: {
@@ -3212,7 +3191,7 @@ function ChromeMenuActionItem({
3212
3191
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3213
3192
  "button",
3214
3193
  {
3215
- className: "flex items-center justify-between gap-3 rounded px-2.5 py-1.5 text-left whitespace-nowrap",
3194
+ className: "pano-video-chrome-menu-action",
3216
3195
  onClick,
3217
3196
  role: "menuitem",
3218
3197
  style: {
@@ -3233,17 +3212,10 @@ function ChromeMenuActionItem({
3233
3212
  function ChromeMenuSectionLabel({
3234
3213
  children
3235
3214
  }) {
3236
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3237
- "div",
3238
- {
3239
- className: "px-2.5 pt-2 pb-1 uppercase opacity-70",
3240
- style: { fontSize: 10, letterSpacing: "0.08em" },
3241
- children
3242
- }
3243
- );
3215
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "pano-video-chrome-menu-label", children });
3244
3216
  }
3245
3217
  function ChromeMenuDivider() {
3246
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "my-1 h-px bg-white/10" });
3218
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "pano-video-chrome-menu-divider" });
3247
3219
  }
3248
3220
  function CaptionsMenu({
3249
3221
  appearance,
@@ -3264,7 +3236,7 @@ function CaptionsMenu({
3264
3236
  labelledBy,
3265
3237
  role: "menu",
3266
3238
  children: panel === "tracks" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
3267
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex max-h-48 flex-col overflow-y-auto", children: [
3239
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "pano-video-chrome-menu-list", children: [
3268
3240
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3269
3241
  ChromeMenuRadioItem,
3270
3242
  {
@@ -3372,7 +3344,7 @@ function VerticalVolumeSlider({
3372
3344
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3373
3345
  "div",
3374
3346
  {
3375
- className: "absolute left-1/2 flex min-w-11 -translate-x-1/2 flex-col items-center rounded-[14px]",
3347
+ className: "pano-video-volume-popover",
3376
3348
  style: {
3377
3349
  background: "rgba(12, 12, 12, 0.92)",
3378
3350
  bottom: `calc(100% + ${CHROME_POPOVER_GAP}px)`,
@@ -3387,7 +3359,7 @@ function VerticalVolumeSlider({
3387
3359
  "span",
3388
3360
  {
3389
3361
  "aria-hidden": true,
3390
- className: "mb-3.5 font-bold leading-none [font-variant-numeric:tabular-nums]",
3362
+ className: "pano-video-volume-percent",
3391
3363
  style: {
3392
3364
  color: appearance.color,
3393
3365
  fontSize: VOLUME_POP_FONT_SIZE
@@ -3426,7 +3398,7 @@ function VerticalVolumeSlider({
3426
3398
  },
3427
3399
  ref: trackRef,
3428
3400
  role: "slider",
3429
- className: "relative cursor-pointer rounded-[999px]",
3401
+ className: "pano-video-volume-track",
3430
3402
  style: {
3431
3403
  background: "rgba(255, 255, 255, 0.14)",
3432
3404
  borderRadius: 999,
@@ -3523,7 +3495,7 @@ function VolumeControl({
3523
3495
  "div",
3524
3496
  {
3525
3497
  "data-pano-video-volume": "",
3526
- className: "relative",
3498
+ className: "pano-video-volume",
3527
3499
  onBlur: (event) => {
3528
3500
  if (event.currentTarget.contains(event.relatedTarget)) {
3529
3501
  return;
@@ -3854,7 +3826,7 @@ function PanoVideoControlsHud({
3854
3826
  transition: "opacity 160ms ease",
3855
3827
  zIndex: 2
3856
3828
  };
3857
- const rateButton = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "relative", "data-pano-video-menu": "rate", children: [
3829
+ const rateButton = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "pano-video-menu", "data-pano-video-menu": "rate", children: [
3858
3830
  !compact && menu === "rate" ? menuNode : null,
3859
3831
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3860
3832
  ChromeButton,
@@ -3864,11 +3836,11 @@ function PanoVideoControlsHud({
3864
3836
  onClick: () => {
3865
3837
  toggleMenu("rate");
3866
3838
  },
3867
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "px-0.5 text-[11px]", id: `${barId}-rate-btn`, children: snapshot.playbackRate === 1 ? "1x" : `${snapshot.playbackRate}x` })
3839
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "pano-video-chrome-label", id: `${barId}-rate-btn`, children: snapshot.playbackRate === 1 ? "1x" : `${snapshot.playbackRate}x` })
3868
3840
  }
3869
3841
  )
3870
3842
  ] });
3871
- const variantButton = canSwitchVariant ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "relative", "data-pano-video-menu": "variant", children: [
3843
+ const variantButton = canSwitchVariant ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "pano-video-menu", "data-pano-video-menu": "variant", children: [
3872
3844
  !compact && menu === "variant" ? menuNode : null,
3873
3845
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3874
3846
  ChromeButton,
@@ -3878,11 +3850,11 @@ function PanoVideoControlsHud({
3878
3850
  onClick: () => {
3879
3851
  toggleMenu("variant");
3880
3852
  },
3881
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "px-0.5 text-[11px]", id: `${barId}-variant-btn`, children: snapshot.variants.find((variant) => variant.id === snapshot.variantId)?.label ?? "Quality" })
3853
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "pano-video-chrome-label", id: `${barId}-variant-btn`, children: snapshot.variants.find((variant) => variant.id === snapshot.variantId)?.label ?? "Quality" })
3882
3854
  }
3883
3855
  )
3884
3856
  ] }) : null;
3885
- const captionsButton = showCaptionsMenu ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "relative", "data-pano-video-menu": "captions", children: [
3857
+ const captionsButton = showCaptionsMenu ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "pano-video-menu", "data-pano-video-menu": "captions", children: [
3886
3858
  !compact && menu === "captions" ? menuNode : null,
3887
3859
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3888
3860
  ChromeButton,
@@ -3899,7 +3871,7 @@ function PanoVideoControlsHud({
3899
3871
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3900
3872
  "div",
3901
3873
  {
3902
- className: "absolute right-2.5 bottom-2.5 left-2.5 z-2 flex items-center gap-2 transition-opacity",
3874
+ className: "pano-video-controls",
3903
3875
  "data-pano-video-controls": "",
3904
3876
  style: barStyle,
3905
3877
  children: [
@@ -3914,7 +3886,7 @@ function PanoVideoControlsHud({
3914
3886
  children: snapshot.playing ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, { path: "M5 3.5h2.2v9H5zM8.8 3.5H11v9H8.8z" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Icon, { path: "M5 3.2v9.6L13 8z" })
3915
3887
  }
3916
3888
  ),
3917
- hideTime ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "min-w-8.5 opacity-[0.85] [font-variant-numeric:tabular-nums]", children: formatMediaTime(snapshot.currentTime) }),
3889
+ hideTime ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "pano-video-time", children: formatMediaTime(snapshot.currentTime) }),
3918
3890
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3919
3891
  "input",
3920
3892
  {
@@ -3937,7 +3909,7 @@ function PanoVideoControlsHud({
3937
3909
  value: duration > 0 ? snapshot.currentTime : 0
3938
3910
  }
3939
3911
  ),
3940
- hideTime ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "min-w-8.5 opacity-[0.85]", children: formatMediaTime(duration) }),
3912
+ hideTime ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "pano-video-time", children: formatMediaTime(duration) }),
3941
3913
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3942
3914
  VolumeControl,
3943
3915
  {
@@ -3954,7 +3926,7 @@ function PanoVideoControlsHud({
3954
3926
  volumeAdjustable: snapshot.volumeAdjustable
3955
3927
  }
3956
3928
  ),
3957
- compact ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "relative", "data-pano-video-menu": "more", children: [
3929
+ compact ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "pano-video-menu", "data-pano-video-menu": "more", children: [
3958
3930
  menu !== null ? menuNode : null,
3959
3931
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3960
3932
  ChromeButton,
@@ -4191,7 +4163,7 @@ function ChromeButton2({
4191
4163
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4192
4164
  "button",
4193
4165
  {
4194
- className: "pointer-events-auto border border-white/20 px-3 py-2 font-semibold shadow-lg backdrop-blur-sm",
4166
+ className: "pano-webvr-button",
4195
4167
  onClick,
4196
4168
  style: {
4197
4169
  background: appearance.background,
@@ -4229,19 +4201,19 @@ function SetupPanel({
4229
4201
  "div",
4230
4202
  {
4231
4203
  "aria-label": "Mobile VR setup",
4232
- className: "pointer-events-auto absolute top-1/2 left-1/2 w-[min(22rem,calc(100%-2rem))] -translate-x-1/2 -translate-y-1/2 border border-white/20 p-5 text-center shadow-2xl backdrop-blur-md",
4204
+ className: "pano-webvr-setup",
4233
4205
  role: "dialog",
4234
4206
  style: panelStyle,
4235
4207
  children: [
4236
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h2", { className: "m-0 mb-5 text-2xl font-extrabold", children: appearance.setupTitle ?? "MobileVR Setup" }),
4237
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "mb-5 block font-semibold", children: [
4208
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h2", { className: "pano-webvr-setup-title", children: appearance.setupTitle ?? "MobileVR Setup" }),
4209
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "pano-webvr-setup-field", children: [
4238
4210
  "Screen size (inch)",
4239
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "mt-2 flex items-center justify-center gap-3", children: [
4211
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "pano-webvr-setup-row", children: [
4240
4212
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4241
4213
  "button",
4242
4214
  {
4243
4215
  "aria-label": "Decrease screen size",
4244
- className: "text-2xl",
4216
+ className: "pano-webvr-setup-stepper",
4245
4217
  onClick: () => setScreensize(
4246
4218
  settings.screensize === "auto" ? 5 : Math.max(3, settings.screensize - 0.1)
4247
4219
  ),
@@ -4249,12 +4221,12 @@ function SetupPanel({
4249
4221
  children: "\u2039"
4250
4222
  }
4251
4223
  ),
4252
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("strong", { className: "min-w-16 text-xl", children: settings.screensize === "auto" ? "Auto" : settings.screensize.toFixed(1) }),
4224
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("strong", { className: "pano-webvr-setup-value", children: settings.screensize === "auto" ? "Auto" : settings.screensize.toFixed(1) }),
4253
4225
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4254
4226
  "button",
4255
4227
  {
4256
4228
  "aria-label": "Increase screen size",
4257
- className: "text-2xl",
4229
+ className: "pano-webvr-setup-stepper",
4258
4230
  onClick: () => setScreensize(
4259
4231
  settings.screensize === "auto" ? 5 : Math.min(20, settings.screensize + 0.1)
4260
4232
  ),
@@ -4264,25 +4236,25 @@ function SetupPanel({
4264
4236
  )
4265
4237
  ] })
4266
4238
  ] }),
4267
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("fieldset", { className: "m-0 mb-5 border-0 p-0", children: [
4268
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("legend", { className: "mb-2 font-semibold", children: "VR headset preset" }),
4269
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "mt-2 flex items-center justify-center gap-3", children: [
4239
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("fieldset", { className: "pano-webvr-setup-fieldset", children: [
4240
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("legend", { className: "pano-webvr-setup-legend", children: "VR headset preset" }),
4241
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "pano-webvr-setup-row", children: [
4270
4242
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4271
4243
  "button",
4272
4244
  {
4273
4245
  "aria-label": "Previous headset preset",
4274
- className: "text-2xl",
4246
+ className: "pano-webvr-setup-stepper",
4275
4247
  onClick: () => cycleProfile(-1),
4276
4248
  type: "button",
4277
4249
  children: "\u2039"
4278
4250
  }
4279
4251
  ),
4280
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("strong", { className: "min-w-32 text-xl", children: WEBVR_PROFILES[settings.profileId].label }),
4252
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("strong", { className: "pano-webvr-setup-value-wide", children: WEBVR_PROFILES[settings.profileId].label }),
4281
4253
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4282
4254
  "button",
4283
4255
  {
4284
4256
  "aria-label": "Next headset preset",
4285
- className: "text-2xl",
4257
+ className: "pano-webvr-setup-stepper",
4286
4258
  onClick: () => cycleProfile(1),
4287
4259
  type: "button",
4288
4260
  children: "\u203A"
@@ -4314,7 +4286,7 @@ function WebVRChromeBridge({ host }) {
4314
4286
  }
4315
4287
  const appearance = resolveAppearance(chrome);
4316
4288
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
4317
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "pointer-events-none absolute inset-x-0 top-4 flex justify-center", children: mode ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex gap-2", children: [
4289
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "pano-webvr-toolbar", children: mode ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "pano-webvr-toolbar-actions", children: [
4318
4290
  mode !== "webxr" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4319
4291
  ChromeButton2,
4320
4292
  {
@@ -4342,7 +4314,7 @@ function WebVRChromeBridge({ host }) {
4342
4314
  mode === "fake" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
4343
4315
  "p",
4344
4316
  {
4345
- className: "pointer-events-none absolute inset-x-0 bottom-5 m-0 text-center text-xs font-semibold text-white drop-shadow",
4317
+ className: "pano-webvr-hint",
4346
4318
  style: { color: appearance.color },
4347
4319
  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."
4348
4320
  }
@@ -4688,7 +4660,7 @@ var PanoViewer = (0, import_react22.forwardRef)(
4688
4660
  "div",
4689
4661
  {
4690
4662
  ...divProps,
4691
- className: cn("relative overflow-hidden", divProps.className),
4663
+ className: cn("pano-viewer", divProps.className),
4692
4664
  "data-pano-viewer": "",
4693
4665
  ref: rootRef,
4694
4666
  "aria-label": ariaLabel,
@@ -4787,12 +4759,9 @@ var PanoViewer = (0, import_react22.forwardRef)(
4787
4759
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
4788
4760
  "div",
4789
4761
  {
4790
- className: "pointer-events-none absolute inset-0 z-15",
4762
+ className: "pano-chrome-overlay",
4791
4763
  "data-pano-chrome-overlay": "",
4792
4764
  ref: setOverlayNode,
4793
- style: {
4794
- inset: 0
4795
- },
4796
4765
  children: [
4797
4766
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
4798
4767
  PanoVideoChromeBridge,
@@ -6364,6 +6333,27 @@ var import_fiber15 = require("@react-three/fiber");
6364
6333
  var import_react31 = require("react");
6365
6334
  var import_three9 = require("three");
6366
6335
 
6336
+ // src/asset-url.ts
6337
+ function isAbsoluteAssetUrl(url) {
6338
+ return url.startsWith("/") || url.startsWith("//") || /^[a-z][a-z0-9+.-]*:/i.test(url);
6339
+ }
6340
+ function resolveRelativeAssetUrl(baseUrl, relativePath) {
6341
+ const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
6342
+ const normalizedPath = relativePath.replace(/^\/+/, "");
6343
+ return normalizedBaseUrl ? `${normalizedBaseUrl}/${normalizedPath}` : normalizedPath;
6344
+ }
6345
+ function resolveAssetUrl(baseUrl, path) {
6346
+ return isAbsoluteAssetUrl(path) ? path : resolveRelativeAssetUrl(baseUrl, path);
6347
+ }
6348
+ function resolveUrlAgainstFile(fileUrl, path) {
6349
+ if (isAbsoluteAssetUrl(path)) {
6350
+ return path;
6351
+ }
6352
+ const slash = fileUrl.lastIndexOf("/");
6353
+ const directory = slash === -1 ? "" : fileUrl.slice(0, slash);
6354
+ return resolveRelativeAssetUrl(directory, path);
6355
+ }
6356
+
6367
6357
  // src/pano-filter/pano-basic-material.tsx
6368
6358
  var import_react30 = require("react");
6369
6359
 
@@ -6580,25 +6570,22 @@ function PanoBasicMaterial({
6580
6570
 
6581
6571
  // src/sphere.tsx
6582
6572
  var import_jsx_runtime15 = require("react/jsx-runtime");
6583
- function Sphere({
6584
- src,
6585
- yawOffset = 0,
6586
- onLoad,
6587
- onError,
6588
- visible = true
6589
- }) {
6590
- const gl = (0, import_fiber15.useThree)((state) => state.gl);
6573
+ function useEquirectTexture(url, anisotropy, generateMipmaps, onLoad, onError) {
6591
6574
  const [texture, setTexture] = (0, import_react31.useState)(null);
6592
6575
  const onLoadRef = (0, import_react31.useRef)(onLoad);
6593
6576
  const onErrorRef = (0, import_react31.useRef)(onError);
6594
6577
  onLoadRef.current = onLoad;
6595
6578
  onErrorRef.current = onError;
6596
6579
  (0, import_react31.useEffect)(() => {
6580
+ if (!url) {
6581
+ setTexture(null);
6582
+ return;
6583
+ }
6597
6584
  let active = true;
6598
6585
  const loader = new import_three9.TextureLoader();
6599
6586
  setTexture(null);
6600
6587
  loader.load(
6601
- src,
6588
+ url,
6602
6589
  (loadedTexture) => {
6603
6590
  if (!active) {
6604
6591
  loadedTexture.dispose();
@@ -6608,10 +6595,10 @@ function Sphere({
6608
6595
  loadedTexture.wrapS = import_three9.RepeatWrapping;
6609
6596
  loadedTexture.repeat.x = -1;
6610
6597
  loadedTexture.offset.x = 1;
6611
- loadedTexture.anisotropy = gl.capabilities.getMaxAnisotropy();
6598
+ loadedTexture.anisotropy = anisotropy;
6612
6599
  loadedTexture.magFilter = import_three9.LinearFilter;
6613
- loadedTexture.minFilter = import_three9.LinearMipmapLinearFilter;
6614
- loadedTexture.generateMipmaps = true;
6600
+ loadedTexture.minFilter = generateMipmaps ? import_three9.LinearMipmapLinearFilter : import_three9.LinearFilter;
6601
+ loadedTexture.generateMipmaps = generateMipmaps;
6615
6602
  loadedTexture.needsUpdate = true;
6616
6603
  setTexture(loadedTexture);
6617
6604
  onLoadRef.current?.(loadedTexture);
@@ -6619,6 +6606,7 @@ function Sphere({
6619
6606
  void 0,
6620
6607
  (error) => {
6621
6608
  if (active) {
6609
+ setTexture(null);
6622
6610
  onErrorRef.current?.(error);
6623
6611
  }
6624
6612
  }
@@ -6626,14 +6614,58 @@ function Sphere({
6626
6614
  return () => {
6627
6615
  active = false;
6628
6616
  };
6629
- }, [gl, src]);
6617
+ }, [anisotropy, generateMipmaps, url]);
6630
6618
  (0, import_react31.useEffect)(
6631
6619
  () => () => {
6632
6620
  texture?.dispose();
6633
6621
  },
6634
6622
  [texture]
6635
6623
  );
6636
- if (!texture) {
6624
+ return texture;
6625
+ }
6626
+ function Sphere({
6627
+ src,
6628
+ previewUrl,
6629
+ yawOffset = 0,
6630
+ onReady,
6631
+ onLoad,
6632
+ onError,
6633
+ onPreviewError,
6634
+ visible = true
6635
+ }) {
6636
+ const gl = (0, import_fiber15.useThree)((state) => state.gl);
6637
+ const anisotropy = gl.capabilities.getMaxAnisotropy();
6638
+ const resolvedPreviewUrl = (0, import_react31.useMemo)(
6639
+ () => resolveUrlAgainstFile(src, previewUrl),
6640
+ [previewUrl, src]
6641
+ );
6642
+ const texture = useEquirectTexture(
6643
+ src,
6644
+ anisotropy,
6645
+ true,
6646
+ onLoad,
6647
+ onError
6648
+ );
6649
+ const previewTexture = useEquirectTexture(
6650
+ texture ? null : resolvedPreviewUrl,
6651
+ anisotropy,
6652
+ false,
6653
+ void 0,
6654
+ onPreviewError
6655
+ );
6656
+ const displayTexture = texture ?? previewTexture;
6657
+ const readyRef = (0, import_react31.useRef)(false);
6658
+ (0, import_react31.useEffect)(() => {
6659
+ readyRef.current = false;
6660
+ }, [resolvedPreviewUrl, src]);
6661
+ (0, import_react31.useEffect)(() => {
6662
+ if (!displayTexture || readyRef.current) {
6663
+ return;
6664
+ }
6665
+ readyRef.current = true;
6666
+ onReady?.();
6667
+ }, [displayTexture, onReady]);
6668
+ if (!displayTexture) {
6637
6669
  return null;
6638
6670
  }
6639
6671
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
@@ -6646,7 +6678,7 @@ function Sphere({
6646
6678
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
6647
6679
  PanoBasicMaterial,
6648
6680
  {
6649
- map: texture,
6681
+ map: displayTexture,
6650
6682
  side: import_three9.BackSide,
6651
6683
  toneMapped: false
6652
6684
  }
@@ -8436,13 +8468,12 @@ function parseMultires(multires) {
8436
8468
  );
8437
8469
  return { tileSize, levels };
8438
8470
  }
8471
+ var DEFAULT_TILE_URL_TEMPLATE = "tiles/%s/l%l/%v/l%l_%s_%h_%v.webp";
8439
8472
  function buildDefaultTileUrlTemplate() {
8440
- return "tiles/%s/l%l/%v/l%l_%s_%h_%v.webp";
8473
+ return DEFAULT_TILE_URL_TEMPLATE;
8441
8474
  }
8442
8475
  function resolveRelativeTileUrl(baseUrl, relativePath) {
8443
- const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
8444
- const normalizedPath = relativePath.replace(/^\/+/, "");
8445
- return normalizedBaseUrl ? `${normalizedBaseUrl}/${normalizedPath}` : normalizedPath;
8476
+ return resolveRelativeAssetUrl(baseUrl, relativePath);
8446
8477
  }
8447
8478
  function resolveTemplateUrl(template, address) {
8448
8479
  return template.replace(/%s/g, address.face).replace(/%l/g, String(address.level)).replace(/%(0*)([hxucvywr])/g, (_match, zeroes, placeholder) => {
@@ -8808,7 +8839,7 @@ function Tile({
8808
8839
  [levels, tileSize]
8809
8840
  );
8810
8841
  const anisotropy = gl.capabilities.getMaxAnisotropy();
8811
- const effectivePreviewUrl = previewUrl === void 0 ? `${baseUrl.replace(/\/$/, "")}/previews/cube-vertical.webp` : previewUrl;
8842
+ const effectivePreviewUrl = resolveAssetUrl(baseUrl, previewUrl);
8812
8843
  const previewTexture = usePreviewTexture(
8813
8844
  effectivePreviewUrl,
8814
8845
  anisotropy,
@@ -9006,9 +9037,10 @@ function SceneSource({
9006
9037
  Sphere,
9007
9038
  {
9008
9039
  src: scene.src,
9040
+ previewUrl: scene.previewUrl,
9009
9041
  yawOffset: scene.yawOffset,
9010
9042
  visible,
9011
- onLoad: onReady,
9043
+ onReady,
9012
9044
  onError
9013
9045
  }
9014
9046
  );
@@ -9499,7 +9531,7 @@ function HotspotTooltip({
9499
9531
  return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(PanoHtml, { pointerEvents: "none", style: WRAPPER_STYLE, zIndexRange: [20, 10], children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
9500
9532
  "div",
9501
9533
  {
9502
- className: "pointer-events-none flex gap-2 whitespace-nowrap",
9534
+ className: "pano-hotspot-tooltip",
9503
9535
  role: "tooltip",
9504
9536
  style: bubbleStyle,
9505
9537
  children: [
@@ -9507,12 +9539,12 @@ function HotspotTooltip({
9507
9539
  "img",
9508
9540
  {
9509
9541
  alt: content.imageAlt ?? content.text ?? "",
9510
- className: "block h-auto max-h-[120px] max-w-[220px] rounded object-contain",
9542
+ className: "pano-hotspot-tooltip-image",
9511
9543
  onError: () => setImageFailed(true),
9512
9544
  src: content.image
9513
9545
  }
9514
9546
  ) : null,
9515
- content.text ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "m-0 overflow-visible whitespace-nowrap leading-[1.4]", children: content.text }) : null
9547
+ content.text ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "pano-hotspot-tooltip-text", children: content.text }) : null
9516
9548
  ]
9517
9549
  }
9518
9550
  ) });
@@ -12057,7 +12089,7 @@ function IframeHotspot({
12057
12089
  "iframe",
12058
12090
  {
12059
12091
  allow,
12060
- className: "block h-full w-full border-0",
12092
+ className: "pano-iframe-hotspot",
12061
12093
  loading,
12062
12094
  onError: (event) => onError?.(event.nativeEvent),
12063
12095
  onLoad,