@liveroom-tech/react-immersive 1.3.3 → 1.3.4

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
@@ -200,7 +200,7 @@ function styleInject(css, { insertAt } = {}) {
200
200
  }
201
201
 
202
202
  // src/styles/ModelViewer.css
203
- styleInject('.ri-viewer--license-checking,\n.ri-viewer--license-invalid {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n background-color: #f9f9f9;\n}\n.ri-viewer {\n --ri-font-family:\n Inter,\n "Segoe UI",\n Helvetica,\n Arial,\n sans-serif;\n --ri-viewer-bg: #1c1c1d;\n --ri-viewer-text: #111827;\n --ri-panel-bg: #ffffff;\n --ri-panel-border: #e5e7eb;\n --ri-panel-title: #111827;\n --ri-panel-muted-text: #4b5563;\n --ri-panel-subtle-text: #9ca3af;\n --ri-panel-surface: #f9fafb;\n --ri-panel-surface-hover: #f3f4f6;\n --ri-panel-surface-border: #e5e7eb;\n --ri-panel-button-text: #1f2937;\n --ri-panel-accent: #2563eb;\n --ri-panel-accent-contrast: #ffffff;\n --ri-license-overlay-bg: rgba(0, 0, 0, 0.04);\n --ri-license-overlay-border: rgba(0, 0, 0, 0.08);\n --ri-license-overlay-text: #888;\n --ri-license-overlay-error-bg: rgba(192, 57, 43, 0.06);\n --ri-license-overlay-error-border: rgba(192, 57, 43, 0.2);\n --ri-license-overlay-error-text: #c0392b;\n --ri-sidepanel-font-family:\n Inter,\n "Segoe UI",\n Helvetica,\n Arial,\n sans-serif;\n --ri-sidepanel-bg: var(--ri-databinding-bg, #111318);\n --ri-sidepanel-open-btn-bg: rgba(20, 20, 20, 0.82);\n --ri-sidepanel-open-btn-bg-hover: #27282b7e;\n --ri-sidepanel-border: rgba(255, 255, 255, 0.06);\n --ri-sidepanel-divider: rgba(255, 255, 255, 0.04);\n --ri-sidepanel-border-strong: rgba(255, 255, 255, 0.08);\n --ri-sidepanel-border-active: rgba(255, 255, 255, 0.18);\n --ri-sidepanel-shadow-left: 4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-sidepanel-shadow-right: -4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-sidepanel-text: var(--ri-databinding-text, #f1f5f9);\n --ri-sidepanel-text-emphasis: var(--ri-databinding-text-hover, #cbd5e1);\n --ri-sidepanel-text-muted: var(--ri-databinding-text-muted, #94a3b8);\n --ri-sidepanel-text-subtle: var(--ri-databinding-text-subtle, #64748b);\n --ri-sidepanel-placeholder: var(--ri-databinding-text-faint, #475569);\n --ri-sidepanel-surface: rgba(255, 255, 255, 0.05);\n --ri-sidepanel-surface-hover: rgba(255, 255, 255, 0.1);\n --ri-sidepanel-accent: #3b82f6;\n --ri-sidepanel-accent-soft: rgba(59, 130, 246, 0.12);\n --ri-databinding-bg: #111318;\n --ri-databinding-border: rgba(255, 255, 255, 0.06);\n --ri-databinding-border-strong: rgba(255, 255, 255, 0.08);\n --ri-databinding-border-hover: rgba(255, 255, 255, 0.15);\n --ri-databinding-shadow: 4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-databinding-font-family:\n "Inter",\n "Segoe UI",\n sans-serif;\n --ri-databinding-mono-font-family:\n "JetBrains Mono",\n "Fira Code",\n "Menlo",\n monospace;\n --ri-databinding-text: #f1f5f9;\n --ri-databinding-text-muted: #94a3b8;\n --ri-databinding-text-subtle: #64748b;\n --ri-databinding-text-faint: #475569;\n --ri-databinding-text-hover: #cbd5e1;\n --ri-databinding-surface: rgba(255, 255, 255, 0.03);\n --ri-databinding-surface-hover: rgba(255, 255, 255, 0.06);\n --ri-databinding-accent: #93c5fd;\n --ri-databinding-accent-glow: rgba(147, 197, 253, 0.8);\n --ri-databinding-accent-border: rgba(96, 165, 250, 0.55);\n --ri-databinding-accent-gradient:\n linear-gradient(\n \n 135deg,\n #1e3a8a 0%,\n #1d4ed8 60%,\n #2563eb 100% );\n --ri-databinding-accent-gradient-active:\n linear-gradient(\n \n 135deg,\n #1e3a8a 0%,\n #1e40af 100% );\n display: flex;\n flex-direction: row;\n width: 100%;\n height: 100%;\n min-height: 0;\n overflow: hidden;\n background: var(--ri-viewer-bg);\n position: relative;\n color: var(--ri-viewer-text);\n font-family: var(--ri-font-family);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n text-rendering: optimizeLegibility;\n font-synthesis: none;\n}\n.ri-viewer * {\n box-sizing: border-box;\n}\n.ri-viewerLeft {\n display: flex;\n flex: 1;\n min-height: 0;\n min-width: 0;\n position: relative;\n}\n.ri-viewerRight {\n display: flex;\n flex-shrink: 0;\n min-width: 0;\n}\n.ri-panel {\n width: 280px;\n min-height: 0;\n background: var(--ri-panel-bg);\n border-right: 1px solid var(--ri-panel-border);\n display: flex;\n flex-direction: column;\n padding: 20px;\n overflow-y: auto;\n flex-shrink: 0;\n transition: margin-left 0.3s ease, transform 0.3s ease;\n}\n.ri-panel--open {\n transform: translateX(0);\n margin-left: 0;\n}\n.ri-panel--closed {\n transform: translateX(-100%);\n margin-left: -280px;\n}\n.ri-panelHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 12px;\n}\n.ri-panelTitle {\n margin: 0;\n font-size: 15px;\n font-weight: 700;\n color: var(--ri-panel-title);\n line-height: 1.4;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.ri-closeButton {\n border: 0;\n background: transparent;\n color: var(--ri-panel-subtle-text);\n font-size: 22px;\n line-height: 1;\n padding: 0;\n cursor: pointer;\n flex-shrink: 0;\n transition: color 0.2s ease;\n}\n.ri-closeButton:hover {\n color: var(--ri-panel-muted-text);\n}\n.ri-json {\n margin: 0 0 16px;\n padding: 12px;\n background: var(--ri-panel-surface);\n border: 1px solid var(--ri-panel-surface-border);\n border-radius: 12px;\n font-size: 12px;\n line-height: 1.55;\n color: var(--ri-panel-button-text);\n overflow-x: auto;\n white-space: pre-wrap;\n word-break: break-word;\n}\n.ri-divider {\n border-top: 1px solid var(--ri-panel-border);\n margin-bottom: 12px;\n}\n.ri-sectionLabel {\n margin-bottom: 8px;\n font-size: 11px;\n font-weight: 700;\n color: var(--ri-panel-subtle-text);\n text-transform: uppercase;\n letter-spacing: 0.16em;\n}\n.ri-actionList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.ri-actionButton {\n width: 100%;\n border-radius: 8px;\n border: 1px solid var(--ri-panel-surface-border);\n background: var(--ri-panel-surface);\n color: var(--ri-panel-button-text);\n padding: 10px 12px;\n text-align: left;\n font-size: 14px;\n line-height: 1.4;\n cursor: pointer;\n transition:\n background-color 0.2s ease,\n border-color 0.2s ease,\n color 0.2s ease;\n}\n.ri-actionButton:hover {\n background: var(--ri-panel-surface-hover);\n border-color: var(--ri-panel-border);\n}\n.ri-actionButton--active {\n background: var(--ri-panel-accent);\n border-color: var(--ri-panel-accent);\n color: var(--ri-panel-accent-contrast);\n}\n.ri-actionButton--active:hover {\n background: var(--ri-panel-accent);\n border-color: var(--ri-panel-accent);\n}\n.ri-canvasArea {\n flex: 1 1 auto;\n position: relative;\n min-width: 0;\n}\n.ri-canvasArea--measuring {\n cursor: crosshair;\n}\n.ri-canvasArea canvas {\n opacity: 1;\n transition: opacity 0.18s ease;\n}\n.ri-canvasArea--positioning canvas {\n opacity: 0;\n transition: none;\n}\n.ri-bindingsMismatchBanner {\n position: absolute;\n top: 12px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 12px 7px 10px;\n border-radius: 8px;\n background: rgba(30, 20, 0, 0.88);\n border: 1px solid rgba(251, 191, 36, 0.35);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n color: rgb(251, 191, 36);\n font-size: 12px;\n font-weight: 500;\n line-height: 1.4;\n white-space: nowrap;\n pointer-events: auto;\n max-width: calc(100% - 32px);\n white-space: normal;\n text-align: center;\n}\n.ri-bindingsMismatchBanner__icon {\n flex-shrink: 0;\n}\n.ri-bindingsMismatchBanner__dismiss {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 4px;\n padding: 2px;\n background: none;\n border: none;\n border-radius: 4px;\n color: rgba(251, 191, 36, 0.7);\n cursor: pointer;\n transition: color 0.15s ease, background 0.15s ease;\n}\n.ri-bindingsMismatchBanner__dismiss:hover {\n color: rgb(251, 191, 36);\n background: rgba(251, 191, 36, 0.12);\n}\n.ri-measureTools {\n position: absolute;\n top: 16px;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-measureButton {\n width: 34px;\n height: 34px;\n padding: 0;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n color: rgba(255, 255, 255, 0.75);\n border-radius: 8px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n touch-action: manipulation;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n transition:\n background 0.15s ease,\n color 0.15s ease,\n border-color 0.15s ease;\n}\n.ri-measureButton:hover {\n background: rgba(40, 40, 40, 0.92);\n color: #fff;\n border-color: rgba(255, 255, 255, 0.25);\n}\n.ri-measureButton:active {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-measureButton--active {\n background: #22d3ee;\n border-color: #22d3ee;\n color: #06343c;\n}\n.ri-measureButton--active:hover {\n background: #38dbf0;\n color: #06343c;\n}\n.ri-measureHint {\n padding: 6px 10px;\n border-radius: 8px;\n font-size: 12px;\n line-height: 1;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n white-space: nowrap;\n}\n.ri-explodeTools {\n position: absolute;\n top: 16px;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-explodeSlider {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 34px;\n padding: 0 12px;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 8px;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n}\n.ri-explodeSlider input[type=range] {\n width: 120px;\n accent-color: #22d3ee;\n cursor: pointer;\n}\n.ri-cinematicTools {\n position: absolute;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-measureDot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: #22d3ee;\n border: 2px solid #fff;\n box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.4);\n pointer-events: none;\n}\n.ri-measureLabel {\n padding: 3px 7px;\n border-radius: 6px;\n font-size: 12px;\n font-weight: 600;\n line-height: 1;\n white-space: nowrap;\n color: #06343c;\n background: #22d3ee;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n pointer-events: none;\n user-select: none;\n}\n.ri-xrTools {\n position: absolute;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-xrButton {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n height: 34px;\n padding: 0 10px;\n font-size: 13px;\n font-weight: 500;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.16);\n border-radius: 8px;\n cursor: pointer;\n touch-action: manipulation;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n transition: background 0.15s ease, border-color 0.15s ease;\n}\n.ri-xrButton:hover {\n background: rgba(40, 40, 40, 0.92);\n border-color: rgba(255, 255, 255, 0.3);\n}\n.ri-mobileHandoffBackdrop {\n position: absolute;\n z-index: 30;\n inset: 0;\n display: grid;\n place-items: center;\n padding: 20px;\n background: rgba(2, 6, 23, 0.68);\n backdrop-filter: blur(5px);\n -webkit-backdrop-filter: blur(5px);\n}\n.ri-mobileHandoffDialog {\n position: relative;\n display: flex;\n width: min(100%, 300px);\n flex-direction: column;\n align-items: center;\n padding: 16px;\n text-align: center;\n color: #e2e8f0;\n background: #0f172a;\n border: 1px solid rgba(148, 163, 184, 0.3);\n border-radius: 16px;\n box-shadow: 0 24px 80px rgba(0, 0, 0, 0.5);\n}\n.ri-mobileHandoffDialog > svg {\n color: #67e8f9;\n}\n.ri-mobileHandoffDialog h2 {\n margin: 12px 0 0;\n font-size: 18px;\n line-height: 1.25;\n color: #fff;\n}\n.ri-mobileHandoffDialog p {\n margin: 8px 0 16px;\n font-size: 13px;\n line-height: 1.5;\n color: #cbd5e1;\n}\n.ri-mobileHandoffQr {\n display: grid;\n place-items: center;\n padding: 10px;\n background: #fff;\n border-radius: 10px;\n}\n.ri-mobileHandoffDialog a {\n margin-top: 16px;\n font-size: 13px;\n font-weight: 600;\n color: #a5f3fc;\n text-decoration: underline;\n text-underline-offset: 3px;\n}\n.ri-mobileHandoffClose {\n position: absolute;\n top: 10px;\n right: 10px;\n display: grid;\n width: 30px;\n height: 30px;\n place-items: center;\n padding: 0;\n color: #cbd5e1;\n background: transparent;\n border: 0;\n border-radius: 6px;\n cursor: pointer;\n}\n.ri-mobileHandoffClose:hover {\n color: #fff;\n background: rgba(255, 255, 255, 0.1);\n}\n.ri-arHintOverlay {\n position: absolute;\n top: 18px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n width: min(calc(100% - 32px), 360px);\n pointer-events: none;\n}\n.ri-arHint {\n margin: 0 auto;\n width: fit-content;\n max-width: 100%;\n padding: 6px 12px;\n font-size: 11px;\n font-weight: 500;\n line-height: 1.2;\n text-align: center;\n color: rgba(255, 255, 255, 0.92);\n background: rgba(20, 20, 20, 0.58);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 999px;\n user-select: none;\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n}\n.ri-arOverlay {\n position: absolute;\n bottom: 24px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n width: min(calc(100% - 32px), 420px);\n pointer-events: none;\n}\n.ri-arControls {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n}\n.ri-arStatus,\n.ri-arActionButton {\n max-width: 100%;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.3;\n text-align: center;\n white-space: nowrap;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.16);\n border-radius: 19px;\n user-select: none;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n}\n.ri-arStatus {\n pointer-events: none;\n}\n.ri-arActionButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n cursor: pointer;\n pointer-events: auto;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n transition:\n background 0.15s ease,\n border-color 0.15s ease,\n color 0.15s ease,\n transform 0.15s ease;\n}\n.ri-arActionButton svg {\n flex-shrink: 0;\n}\n.ri-arActionButton:hover {\n background: rgba(40, 40, 40, 0.92);\n border-color: rgba(255, 255, 255, 0.3);\n}\n.ri-arActionButton:active {\n transform: scale(0.98);\n}\n.ri-arActionButton--primary {\n color: #06343c;\n background: #22d3ee;\n border-color: transparent;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n}\n.ri-arActionButton--primary:hover {\n background: #67e8f9;\n border-color: transparent;\n}\n.ri-arActionButton.ri-arActionButton--active,\n.ri-arActionButton[aria-pressed=true],\n.ri-arActionButton.ri-arActionButton--active:hover,\n.ri-arActionButton[aria-pressed=true]:hover,\n.ri-arActionButton.ri-arActionButton--active:active,\n.ri-arActionButton[aria-pressed=true]:active {\n color: #06343c;\n background: #22d3ee;\n border-color: transparent;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n}\n.ri-hiddenInput {\n display: none;\n}\n.ri-popover {\n position: absolute;\n z-index: 50;\n background: #ffffff;\n border: 1px solid #e5e7eb;\n border-radius: 16px;\n box-shadow: 0 24px 48px rgba(15, 23, 42, 0.18);\n color: #111827;\n}\n.ri-popover--inline {\n position: static;\n z-index: auto;\n width: 100%;\n border-radius: 10px;\n box-shadow: none;\n}\n.ri-popover--inline .react-colorful {\n width: 100%;\n}\n.ri-popover--inline {\n background: var(--ri-databinding-surface, rgba(255, 255, 255, 0.03));\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text, #f1f5f9);\n}\n.ri-popover--inline .ri-popoverHeader {\n border-bottom-color: var( --ri-databinding-border-strong, rgba(255, 255, 255, 0.08) );\n}\n.ri-popover--inline .ri-popoverLabel {\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-colorCode {\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-secondaryButton {\n background: var(--ri-databinding-surface-hover, rgba(255, 255, 255, 0.06));\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text, #f1f5f9);\n}\n.ri-popover--inline .ri-secondaryButton:hover {\n background: rgba(255, 255, 255, 0.12);\n}\n.ri-popover--inline .ri-previewFrame {\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n}\n.ri-popover--inline .ri-emptyState {\n border-color: var(--ri-databinding-border-hover, rgba(255, 255, 255, 0.15));\n color: var(--ri-databinding-text-subtle, #64748b);\n}\n.ri-popover--inline .ri-uploadButton {\n background: var(--ri-databinding-accent-gradient, #2563eb);\n color: #ffffff;\n}\n.ri-popover--inline .ri-uploadButton:hover {\n background: var(--ri-databinding-accent-gradient-active, #1d4ed8);\n}\n.ri-popover--inline .ri-removeButton {\n background: transparent;\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-removeButton:hover {\n background: var(--ri-databinding-surface-hover, rgba(255, 255, 255, 0.06));\n}\n.ri-popover--inline .ri-formatHint {\n color: var(--ri-databinding-text-subtle, #64748b);\n}\n.ri-popoverHeader {\n padding: 12px 16px;\n}\n.ri-popoverLabel {\n margin: 0 0 8px;\n font-size: 11px;\n font-weight: 700;\n color: #6b7280;\n text-transform: uppercase;\n letter-spacing: 0.16em;\n}\n.ri-inlineRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n margin-top: 8px;\n}\n.ri-colorCode {\n font-size: 12px;\n color: #9ca3af;\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Monaco,\n Consolas,\n monospace;\n text-transform: uppercase;\n}\n.ri-secondaryButton {\n border: 1px solid #e5e7eb;\n background: #f3f4f6;\n color: #374151;\n border-radius: 8px;\n padding: 6px 10px;\n font-size: 12px;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-secondaryButton:hover {\n background: #e5e7eb;\n}\n.ri-materialBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding-left: 12px;\n padding-right: 12px;\n padding-bottom: 12px;\n}\n.ri-previewFrame {\n position: relative;\n height: 112px;\n border-radius: 12px;\n overflow: hidden;\n border: 1px solid #e5e7eb;\n}\n.ri-previewImage {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n.ri-previewCaption {\n position: absolute;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n padding: 4px 8px;\n color: #ffffff;\n font-size: 10px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ri-emptyState {\n height: 112px;\n border-radius: 12px;\n border: 2px dashed #e5e7eb;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 4px;\n color: #d1d5db;\n text-align: center;\n}\n.ri-uploadButton {\n width: 100%;\n border: 0;\n border-radius: 10px;\n background: #111827;\n color: #ffffff;\n padding: 10px 12px;\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-uploadButton:hover {\n background: #374151;\n}\n.ri-removeButton {\n width: 100%;\n border-radius: 10px;\n border: 1px solid #e5e7eb;\n background: #ffffff;\n color: #6b7280;\n padding: 8px 12px;\n font-size: 12px;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-removeButton:hover {\n background: #f9fafb;\n}\n.ri-formatHint {\n margin: 0;\n text-align: center;\n font-size: 10px;\n color: #d1d5db;\n}\n.ri-buttonContainer {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 8px;\n position: absolute;\n bottom: 12px;\n left: 12px;\n right: 12px;\n z-index: 10;\n}\n.ri-tourControls {\n display: flex;\n gap: 6px;\n align-items: center;\n flex-shrink: 0;\n}\n.ri-rightControls {\n display: flex;\n gap: 6px;\n align-items: center;\n flex-shrink: 0;\n margin-left: auto;\n}\n.ri-rightControls:only-child {\n margin-left: auto;\n}\n.ri-navButtonSingle,\n.ri-tourButton,\n.ri-resetButton,\n.ri-downloadButton {\n height: 34px;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n color: rgba(255, 255, 255, 0.75);\n border-radius: 8px;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n cursor: pointer;\n transition:\n background 0.15s ease,\n color 0.15s ease,\n border-color 0.15s ease,\n transform 0.15s ease;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n}\n.ri-navButtonSingle:hover:not(:disabled),\n.ri-tourButton:hover:not(:disabled),\n.ri-resetButton:hover:not(:disabled) {\n background: rgba(40, 40, 40, 0.92);\n color: #fff;\n border-color: rgba(255, 255, 255, 0.25);\n}\n.ri-navButtonSingle:active:not(:disabled),\n.ri-tourButton:active:not(:disabled),\n.ri-resetButton:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-navButtonSingle:disabled,\n.ri-tourButton:disabled,\n.ri-resetButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.ri-navButtonSingle {\n padding: 0 16px;\n font-size: 13px;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 8px;\n white-space: nowrap;\n}\n.ri-tourButton {\n padding: 0 12px;\n font-size: 13px;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 4px;\n white-space: nowrap;\n}\n.ri-tourButton svg {\n width: 15px;\n height: 15px;\n flex-shrink: 0;\n}\n.ri-tourButton--stop {\n border-color: rgba(255, 68, 68, 0.4);\n color: #ff6b6b;\n}\n.ri-tourButton--stop:hover:not(:disabled) {\n background: rgba(255, 68, 68, 0.15);\n border-color: rgba(255, 68, 68, 0.6);\n color: #ff6b6b;\n}\n.ri-tourButton--stop:active:not(:disabled) {\n background: #ff4444;\n color: #fff;\n border-color: #ff4444;\n}\n.ri-tourButton--next {\n border-color: rgba(56, 219, 240, 0.3);\n color: #38dbf0;\n}\n.ri-tourButton--next:hover:not(:disabled) {\n background: rgba(56, 219, 240, 0.12);\n border-color: rgba(56, 219, 240, 0.5);\n color: #38dbf0;\n}\n.ri-tourButton--next:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-resetButton {\n width: 34px;\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.ri-resetButton svg {\n width: 15px;\n height: 15px;\n pointer-events: none;\n flex-shrink: 0;\n}\n.ri-resetButton:focus-visible {\n outline: 2px solid rgba(255, 255, 255, 0.5);\n outline-offset: 2px;\n}\n.ri-downloadWrapper {\n position: relative;\n display: inline-flex;\n flex-direction: column;\n align-items: stretch;\n}\n.ri-downloadButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 0 16px;\n font-size: 13px;\n font-weight: 500;\n white-space: nowrap;\n}\n.ri-downloadButton:hover:not(:disabled) {\n background: rgba(0, 0, 0, 0.72);\n border-color: rgba(255, 255, 255, 0.3);\n color: #fff;\n}\n.ri-downloadButton:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-downloadButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.ri-downloadButton__spinner {\n display: inline-block;\n width: 13px;\n height: 13px;\n border: 2px solid rgba(255, 255, 255, 0.3);\n border-top-color: #fff;\n border-radius: 50%;\n animation: ri-spin 0.7s linear infinite;\n flex-shrink: 0;\n}\n.ri-downloadMenu {\n position: absolute;\n bottom: calc(100% + 6px);\n right: 0;\n min-width: 168px;\n background: rgba(20, 20, 20, 0.92);\n border: 1px solid rgba(255, 255, 255, 0.14);\n border-radius: 10px;\n overflow: hidden;\n z-index: 100;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n animation: ri-menuReveal 0.12s ease;\n}\n@keyframes ri-menuReveal {\n from {\n opacity: 0;\n transform: translateY(4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n.ri-downloadMenuItem {\n display: flex;\n align-items: center;\n gap: 9px;\n width: 100%;\n padding: 10px 14px;\n font-size: 13px;\n font-weight: 400;\n color: #fff;\n background: transparent;\n border: none;\n cursor: pointer;\n text-align: left;\n transition: background 0.1s ease;\n}\n.ri-downloadMenuItem:hover:not(:disabled) {\n background: rgba(255, 255, 255, 0.1);\n}\n.ri-downloadMenuItem:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.ri-downloadMenuDivider {\n height: 1px;\n background: rgba(255, 255, 255, 0.1);\n margin: 0 10px;\n}\n@keyframes ri-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.ri-annotationMarker {\n position: relative;\n width: 36px;\n height: 36px;\n border-radius: 50%;\n background: rgba(20, 20, 20, 0.82);\n border: 2px solid rgba(255, 255, 255, 0.9);\n color: #fff;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n font-weight: 700;\n cursor: pointer;\n z-index: 20;\n user-select: none;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);\n pointer-events: auto;\n transition: opacity 0.3s ease, transform 0.3s ease;\n}\n.ri-annotationMarker:hover {\n transform: scale(1.08);\n box-shadow: 0 12px 28px rgba(15, 23, 42, 0.18);\n background: rgba(40, 40, 40, 0.92);\n}\n.ri-annotationPopup {\n position: absolute;\n width: min(270px, calc(100vw - 32px));\n background: rgba(20, 20, 20, 0.85);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 16px;\n padding: 16px;\n z-index: 30;\n display: flex;\n flex-direction: column;\n gap: 12px;\n box-shadow: 0 24px 48px rgba(15, 23, 42, 0.18);\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n}\n.ri-annotationInput,\n.ri-annotationTextarea {\n width: 100%;\n border: 1px solid rgba(255, 255, 255, 0.12);\n background: rgba(255, 255, 255, 0.05);\n color: #e5e7eb;\n border-radius: 10px;\n padding: 10px 12px;\n outline: none;\n font-family: inherit;\n transition: border-color 0.2s ease, box-shadow 0.2s ease;\n}\n.ri-annotationInput {\n font-size: 14px;\n font-weight: 500;\n}\n.ri-annotationTextarea {\n font-size: 13px;\n resize: vertical;\n min-height: 110px;\n}\n.ri-annotationInput:focus,\n.ri-annotationTextarea:focus {\n border-color: #38dbf0;\n box-shadow: 0 0 0 3px rgba(56, 219, 240, 0.12);\n}\n.ri-annotationActions {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.ri-annotationOkBtn {\n border: 0;\n border-radius: 8px;\n background: rgba(56, 219, 240, 0.8);\n color: #06343c;\n padding: 8px 18px;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n transition: background-color 0.2s ease, opacity 0.2s ease;\n}\n.ri-annotationOkBtn:hover:not(:disabled) {\n background: #38dbf0;\n}\n.ri-annotationOkBtn:disabled,\n.ri-annotationOkBtn.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.ri-annotationCancelBtn {\n border: 1px solid rgba(255, 255, 255, 0.12);\n background: transparent;\n color: #9ca3af;\n border-radius: 8px;\n padding: 8px 12px;\n font-size: 13px;\n cursor: pointer;\n transition: background-color 0.2s ease, border-color 0.2s ease;\n}\n.ri-annotationCancelBtn:hover {\n background: rgba(255, 255, 255, 0.05);\n border-color: rgba(255, 255, 255, 0.2);\n}\n.ri-annotationIcons {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 12px;\n color: #9ca3af;\n}\n.ri-annotationIcons svg {\n flex-shrink: 0;\n}\n.ri-viewAnnotationPopup {\n position: relative;\n background: rgba(20, 20, 20, 0.85);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 8px;\n padding: 12px 16px;\n min-width: 200px;\n max-width: min(280px, calc(100vw - 48px));\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);\n pointer-events: auto;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n animation: ri-viewAnnotationFadeIn 0.15s ease-out;\n margin-left: 24px;\n margin-top: -20px;\n isolation: isolate;\n z-index: 1000;\n}\n.ri-viewAnnotationHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n}\n.ri-viewAnnotationTitle {\n margin: 0;\n color: #e2e8f0;\n font-size: 14px;\n font-weight: 600;\n}\n.ri-viewAnnotationClose {\n background: transparent;\n border: none;\n color: #b7bdc6;\n cursor: pointer;\n font-size: 18px;\n padding: 0 4px;\n transition: color 0.2s;\n line-height: 1;\n}\n.ri-viewAnnotationClose:hover {\n color: #e2e8f0;\n}\n.ri-viewAnnotationDescription {\n margin: 0;\n color: #cbd5e1;\n font-size: 13px;\n line-height: 1.4;\n word-wrap: break-word;\n}\n@keyframes ri-viewAnnotationFadeIn {\n from {\n opacity: 0;\n transform: translateY(-6px) scale(0.96);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n.ri-annotationNavSingle {\n position: absolute;\n bottom: 120px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n pointer-events: none;\n}\n.ri-annotationNavSingle > * {\n pointer-events: auto;\n}\n.ri-navBadge {\n background: rgba(255, 255, 255, 0.1);\n padding: 2px 10px;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 400;\n color: rgba(255, 255, 255, 0.7);\n}\n.ri-annotationTitleHint {\n background: rgba(0, 0, 0, 0.7);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n color: white;\n padding: 8px 20px;\n border-radius: 8px;\n font-size: 13px;\n border: 1px solid rgba(255, 255, 255, 0.08);\n text-align: center;\n max-width: 350px;\n animation: slideUp 0.3s ease;\n}\n.ri-annotationHintTitle {\n font-weight: 600;\n color: #e5e7eb;\n}\n.ri-annotationHintDesc {\n font-size: 12px;\n opacity: 0.6;\n margin-top: 2px;\n color: #d1d5db;\n}\n@keyframes slideUp {\n from {\n opacity: 0;\n transform: translateY(10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n@media (max-width: 768px) {\n .ri-xrTools {\n gap: 6px;\n }\n .ri-xrButton {\n height: 34px;\n gap: 6px;\n padding: 0 8px;\n font-size: 11px;\n }\n .ri-xrButton svg {\n width: 14px;\n height: 14px;\n }\n .ri-xrButtonPrefix {\n display: none;\n }\n .ri-annotationNavSingle {\n bottom: 100px;\n }\n .ri-navButtonSingle {\n padding: 8px 20px;\n font-size: 13px;\n min-width: 110px;\n }\n .ri-annotationTitleHint {\n padding: 6px 14px;\n font-size: 12px;\n max-width: 250px;\n }\n .ri-viewAnnotationPopup {\n min-width: 160px;\n max-width: 220px;\n padding: 10px 12px;\n }\n .ri-viewAnnotationTitle {\n font-size: 13px;\n }\n .ri-viewAnnotationDescription {\n font-size: 12px;\n }\n .ri-buttonContainer {\n flex-wrap: wrap;\n gap: 6px;\n }\n .ri-tourControls {\n flex-wrap: wrap;\n }\n .ri-rightControls {\n flex-wrap: wrap;\n }\n}\n@media (max-width: 480px) {\n .ri-xrTools {\n gap: 5px;\n }\n .ri-xrButton {\n height: 34px;\n gap: 5px;\n font-size: 10px;\n }\n .ri-xrButton svg {\n width: 13px;\n height: 13px;\n }\n .ri-annotationNavSingle {\n bottom: 80px;\n }\n .ri-navButtonSingle {\n padding: 6px 16px;\n font-size: 12px;\n min-width: 90px;\n gap: 6px;\n }\n .ri-annotationTitleHint {\n padding: 4px 10px;\n font-size: 11px;\n max-width: 200px;\n }\n .ri-navBadge {\n font-size: 10px;\n padding: 1px 6px;\n }\n .ri-tourButton {\n padding: 0 8px;\n font-size: 12px;\n }\n .ri-tourButton span {\n display: none;\n }\n .ri-downloadButton {\n padding: 0 10px;\n font-size: 12px;\n }\n .ri-downloadButton span {\n display: none;\n }\n}\n.ri-canvasArea .bb-loadingOverlay {\n position: absolute;\n inset: 0;\n z-index: 12;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 16px;\n padding: 24px;\n text-align: center;\n border-radius: 14px;\n background: rgba(15, 23, 42, 0.46);\n backdrop-filter: blur(1px);\n}\n.ri-canvasArea .bb-loadingSpinner {\n width: 40px;\n height: 40px;\n border-radius: 999px;\n border: 3px solid rgba(226, 232, 240, 0.24);\n border-top-color: #93c5fd;\n animation: bb-spin 0.8s linear infinite;\n}\n.ri-canvasArea .bb-loadingMessage {\n max-width: 320px;\n color: #f7f8f9;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.5;\n}\n@keyframes bb-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.ri-largeModelBanner {\n position: absolute;\n top: 12px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 12px 7px 10px;\n border-radius: 8px;\n background: rgba(30, 20, 0, 0.88);\n border: 1px solid rgba(251, 191, 36, 0.35);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n color: rgb(251, 191, 36);\n font-size: 12px;\n font-weight: 500;\n line-height: 1.4;\n pointer-events: auto;\n max-width: calc(100% - 32px);\n white-space: normal;\n text-align: center;\n}\n.ri-largeModelBanner--offset {\n top: 64px;\n}\n.ri-largeModelBanner__icon {\n flex-shrink: 0;\n}\n.ri-largeModelBanner__dismiss {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 4px;\n padding: 2px;\n background: none;\n border: none;\n border-radius: 4px;\n color: rgba(251, 191, 36, 0.7);\n cursor: pointer;\n transition: color 0.15s ease, background 0.15s ease;\n}\n.ri-largeModelBanner__dismiss:hover {\n color: rgb(251, 191, 36);\n background: rgba(251, 191, 36, 0.12);\n}\n');
203
+ styleInject('.ri-viewer--license-checking,\n.ri-viewer--license-invalid {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n background-color: #f9f9f9;\n}\n.ri-viewer {\n --ri-font-family:\n Inter,\n "Segoe UI",\n Helvetica,\n Arial,\n sans-serif;\n --ri-viewer-bg: #1c1c1d;\n --ri-viewer-text: #111827;\n --ri-panel-bg: #ffffff;\n --ri-panel-border: #e5e7eb;\n --ri-panel-title: #111827;\n --ri-panel-muted-text: #4b5563;\n --ri-panel-subtle-text: #9ca3af;\n --ri-panel-surface: #f9fafb;\n --ri-panel-surface-hover: #f3f4f6;\n --ri-panel-surface-border: #e5e7eb;\n --ri-panel-button-text: #1f2937;\n --ri-panel-accent: #2563eb;\n --ri-panel-accent-contrast: #ffffff;\n --ri-license-overlay-bg: rgba(0, 0, 0, 0.04);\n --ri-license-overlay-border: rgba(0, 0, 0, 0.08);\n --ri-license-overlay-text: #888;\n --ri-license-overlay-error-bg: rgba(192, 57, 43, 0.06);\n --ri-license-overlay-error-border: rgba(192, 57, 43, 0.2);\n --ri-license-overlay-error-text: #c0392b;\n --ri-sidepanel-font-family:\n Inter,\n "Segoe UI",\n Helvetica,\n Arial,\n sans-serif;\n --ri-sidepanel-bg: var(--ri-databinding-bg, #111318);\n --ri-sidepanel-open-btn-bg: rgba(20, 20, 20, 0.82);\n --ri-sidepanel-open-btn-bg-hover: #27282b7e;\n --ri-sidepanel-border: rgba(255, 255, 255, 0.06);\n --ri-sidepanel-divider: rgba(255, 255, 255, 0.04);\n --ri-sidepanel-border-strong: rgba(255, 255, 255, 0.08);\n --ri-sidepanel-border-active: rgba(255, 255, 255, 0.18);\n --ri-sidepanel-shadow-left: 4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-sidepanel-shadow-right: -4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-sidepanel-text: var(--ri-databinding-text, #f1f5f9);\n --ri-sidepanel-text-emphasis: var(--ri-databinding-text-hover, #cbd5e1);\n --ri-sidepanel-text-muted: var(--ri-databinding-text-muted, #94a3b8);\n --ri-sidepanel-text-subtle: var(--ri-databinding-text-subtle, #64748b);\n --ri-sidepanel-placeholder: var(--ri-databinding-text-faint, #475569);\n --ri-sidepanel-surface: rgba(255, 255, 255, 0.05);\n --ri-sidepanel-surface-hover: rgba(255, 255, 255, 0.1);\n --ri-sidepanel-accent: #3b82f6;\n --ri-sidepanel-accent-soft: rgba(59, 130, 246, 0.12);\n --ri-databinding-bg: #111318;\n --ri-databinding-border: rgba(255, 255, 255, 0.06);\n --ri-databinding-border-strong: rgba(255, 255, 255, 0.08);\n --ri-databinding-border-hover: rgba(255, 255, 255, 0.15);\n --ri-databinding-shadow: 4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-databinding-font-family:\n "Inter",\n "Segoe UI",\n sans-serif;\n --ri-databinding-mono-font-family:\n "JetBrains Mono",\n "Fira Code",\n "Menlo",\n monospace;\n --ri-databinding-text: #f1f5f9;\n --ri-databinding-text-muted: #94a3b8;\n --ri-databinding-text-subtle: #64748b;\n --ri-databinding-text-faint: #475569;\n --ri-databinding-text-hover: #cbd5e1;\n --ri-databinding-surface: rgba(255, 255, 255, 0.03);\n --ri-databinding-surface-hover: rgba(255, 255, 255, 0.06);\n --ri-databinding-accent: #93c5fd;\n --ri-databinding-accent-glow: rgba(147, 197, 253, 0.8);\n --ri-databinding-accent-border: rgba(96, 165, 250, 0.55);\n --ri-databinding-accent-gradient:\n linear-gradient(\n \n 135deg,\n #1e3a8a 0%,\n #1d4ed8 60%,\n #2563eb 100% );\n --ri-databinding-accent-gradient-active:\n linear-gradient(\n \n 135deg,\n #1e3a8a 0%,\n #1e40af 100% );\n display: flex;\n flex-direction: row;\n width: 100%;\n height: 100%;\n min-height: 0;\n overflow: hidden;\n background: var(--ri-viewer-bg);\n position: relative;\n color: var(--ri-viewer-text);\n font-family: var(--ri-font-family);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n text-rendering: optimizeLegibility;\n font-synthesis: none;\n}\n.ri-viewer * {\n box-sizing: border-box;\n}\n.ri-viewerLeft {\n display: flex;\n flex: 1;\n min-height: 0;\n min-width: 0;\n position: relative;\n}\n.ri-viewerRight {\n display: flex;\n flex-shrink: 0;\n min-width: 0;\n}\n.ri-panel {\n width: 280px;\n min-height: 0;\n background: var(--ri-panel-bg);\n border-right: 1px solid var(--ri-panel-border);\n display: flex;\n flex-direction: column;\n padding: 20px;\n overflow-y: auto;\n flex-shrink: 0;\n transition: margin-left 0.3s ease, transform 0.3s ease;\n}\n.ri-panel--open {\n transform: translateX(0);\n margin-left: 0;\n}\n.ri-panel--closed {\n transform: translateX(-100%);\n margin-left: -280px;\n}\n.ri-panelHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 12px;\n}\n.ri-panelTitle {\n margin: 0;\n font-size: 15px;\n font-weight: 700;\n color: var(--ri-panel-title);\n line-height: 1.4;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.ri-closeButton {\n border: 0;\n background: transparent;\n color: var(--ri-panel-subtle-text);\n font-size: 22px;\n line-height: 1;\n padding: 0;\n cursor: pointer;\n flex-shrink: 0;\n transition: color 0.2s ease;\n}\n.ri-closeButton:hover {\n color: var(--ri-panel-muted-text);\n}\n.ri-json {\n margin: 0 0 16px;\n padding: 12px;\n background: var(--ri-panel-surface);\n border: 1px solid var(--ri-panel-surface-border);\n border-radius: 12px;\n font-size: 12px;\n line-height: 1.55;\n color: var(--ri-panel-button-text);\n overflow-x: auto;\n white-space: pre-wrap;\n word-break: break-word;\n}\n.ri-divider {\n border-top: 1px solid var(--ri-panel-border);\n margin-bottom: 12px;\n}\n.ri-sectionLabel {\n margin-bottom: 8px;\n font-size: 11px;\n font-weight: 700;\n color: var(--ri-panel-subtle-text);\n text-transform: uppercase;\n letter-spacing: 0.16em;\n}\n.ri-actionList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.ri-actionButton {\n width: 100%;\n border-radius: 8px;\n border: 1px solid var(--ri-panel-surface-border);\n background: var(--ri-panel-surface);\n color: var(--ri-panel-button-text);\n padding: 10px 12px;\n text-align: left;\n font-size: 14px;\n line-height: 1.4;\n cursor: pointer;\n transition:\n background-color 0.2s ease,\n border-color 0.2s ease,\n color 0.2s ease;\n}\n.ri-actionButton:hover {\n background: var(--ri-panel-surface-hover);\n border-color: var(--ri-panel-border);\n}\n.ri-actionButton--active {\n background: var(--ri-panel-accent);\n border-color: var(--ri-panel-accent);\n color: var(--ri-panel-accent-contrast);\n}\n.ri-actionButton--active:hover {\n background: var(--ri-panel-accent);\n border-color: var(--ri-panel-accent);\n}\n.ri-canvasArea {\n flex: 1 1 auto;\n position: relative;\n min-width: 0;\n}\n.ri-canvasArea--measuring {\n cursor: crosshair;\n}\n.ri-canvasArea canvas {\n opacity: 1;\n transition: opacity 0.18s ease;\n}\n.ri-canvasArea--positioning canvas {\n opacity: 0;\n transition: none;\n}\n.ri-bindingsMismatchBanner {\n position: absolute;\n top: 12px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 12px 7px 10px;\n border-radius: 8px;\n background: rgba(30, 20, 0, 0.88);\n border: 1px solid rgba(251, 191, 36, 0.35);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n color: rgb(251, 191, 36);\n font-size: 12px;\n font-weight: 500;\n line-height: 1.4;\n white-space: nowrap;\n pointer-events: auto;\n max-width: calc(100% - 32px);\n white-space: normal;\n text-align: center;\n}\n.ri-bindingsMismatchBanner__icon {\n flex-shrink: 0;\n}\n.ri-bindingsMismatchBanner__dismiss {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 4px;\n padding: 2px;\n background: none;\n border: none;\n border-radius: 4px;\n color: rgba(251, 191, 36, 0.7);\n cursor: pointer;\n transition: color 0.15s ease, background 0.15s ease;\n}\n.ri-bindingsMismatchBanner__dismiss:hover {\n color: rgb(251, 191, 36);\n background: rgba(251, 191, 36, 0.12);\n}\n.ri-measureTools {\n position: absolute;\n top: 16px;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-measureButton {\n width: 34px;\n height: 34px;\n padding: 0;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n color: rgba(255, 255, 255, 0.75);\n border-radius: 8px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n touch-action: manipulation;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n transition:\n background 0.15s ease,\n color 0.15s ease,\n border-color 0.15s ease;\n}\n.ri-measureButton:hover {\n background: rgba(40, 40, 40, 0.92);\n color: #fff;\n border-color: rgba(255, 255, 255, 0.25);\n}\n.ri-measureButton:active {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-measureButton--active {\n background: #22d3ee;\n border-color: #22d3ee;\n color: #06343c;\n}\n.ri-measureButton--active:hover {\n background: #38dbf0;\n color: #06343c;\n}\n.ri-measureHint {\n padding: 6px 10px;\n border-radius: 8px;\n font-size: 12px;\n line-height: 1;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n white-space: nowrap;\n}\n.ri-explodeTools {\n position: absolute;\n top: 16px;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-explodeSlider {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 34px;\n padding: 0 12px;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 8px;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n}\n.ri-explodeSlider input[type=range] {\n width: 120px;\n accent-color: #22d3ee;\n cursor: pointer;\n}\n.ri-cinematicTools {\n position: absolute;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-measureDot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: #22d3ee;\n border: 2px solid #fff;\n box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.4);\n pointer-events: none;\n}\n.ri-measureLabel {\n padding: 3px 7px;\n border-radius: 6px;\n font-size: 12px;\n font-weight: 600;\n line-height: 1;\n white-space: nowrap;\n color: #06343c;\n background: #22d3ee;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n pointer-events: none;\n user-select: none;\n}\n.ri-xrTools {\n position: absolute;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-xrButton {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n height: 34px;\n padding: 0 10px;\n font-size: 13px;\n font-weight: 500;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.16);\n border-radius: 8px;\n cursor: pointer;\n touch-action: manipulation;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n transition: background 0.15s ease, border-color 0.15s ease;\n}\n.ri-xrButton:hover {\n background: rgba(40, 40, 40, 0.92);\n border-color: rgba(255, 255, 255, 0.3);\n}\n.ri-mobileHandoffBackdrop {\n position: absolute;\n z-index: 30;\n inset: 0;\n display: grid;\n place-items: center;\n padding: 20px;\n background: rgba(2, 6, 23, 0.68);\n backdrop-filter: blur(5px);\n -webkit-backdrop-filter: blur(5px);\n}\n.ri-mobileHandoffDialog {\n position: relative;\n display: flex;\n width: min(100%, 300px);\n flex-direction: column;\n align-items: center;\n padding: 16px;\n text-align: center;\n color: #e2e8f0;\n background: #0f172a;\n border: 1px solid rgba(148, 163, 184, 0.3);\n border-radius: 16px;\n box-shadow: 0 24px 80px rgba(0, 0, 0, 0.5);\n}\n.ri-mobileHandoffDialog > svg {\n color: #67e8f9;\n}\n.ri-mobileHandoffDialog h2 {\n margin: 12px 0 0;\n font-size: 18px;\n line-height: 1.25;\n color: #fff;\n}\n.ri-mobileHandoffDialog p {\n margin: 8px 0 16px;\n font-size: 13px;\n line-height: 1.5;\n color: #cbd5e1;\n}\n.ri-mobileHandoffQr {\n display: grid;\n place-items: center;\n padding: 10px;\n background: #fff;\n border-radius: 10px;\n}\n.ri-mobileHandoffDialog a {\n margin-top: 16px;\n font-size: 13px;\n font-weight: 600;\n color: #a5f3fc;\n text-decoration: underline;\n text-underline-offset: 3px;\n}\n.ri-mobileHandoffClose {\n position: absolute;\n top: 10px;\n right: 10px;\n display: grid;\n width: 30px;\n height: 30px;\n place-items: center;\n padding: 0;\n color: #cbd5e1;\n background: transparent;\n border: 0;\n border-radius: 6px;\n cursor: pointer;\n}\n.ri-mobileHandoffClose:hover {\n color: #fff;\n background: rgba(255, 255, 255, 0.1);\n}\n.ri-arHintOverlay {\n position: absolute;\n top: 18px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n width: min(calc(100% - 32px), 360px);\n pointer-events: none;\n}\n.ri-arHint {\n margin: 0 auto;\n width: fit-content;\n max-width: 100%;\n padding: 6px 12px;\n font-size: 11px;\n font-weight: 500;\n line-height: 1.2;\n text-align: center;\n color: rgba(255, 255, 255, 0.92);\n background: rgba(20, 20, 20, 0.58);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 999px;\n user-select: none;\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n}\n.ri-arOverlay {\n position: absolute;\n bottom: 24px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n width: min(calc(100% - 32px), 420px);\n pointer-events: none;\n}\n.ri-arControls {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n}\n.ri-arStatus,\n.ri-arActionButton {\n max-width: 100%;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.3;\n text-align: center;\n white-space: nowrap;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.16);\n border-radius: 19px;\n user-select: none;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n}\n.ri-arStatus {\n pointer-events: none;\n}\n.ri-arActionButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n cursor: pointer;\n pointer-events: auto;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n transition:\n background 0.15s ease,\n border-color 0.15s ease,\n color 0.15s ease,\n transform 0.15s ease;\n}\n.ri-arActionButton svg {\n flex-shrink: 0;\n}\n.ri-arActionButton:hover {\n background: rgba(40, 40, 40, 0.92);\n border-color: rgba(255, 255, 255, 0.3);\n}\n.ri-arActionButton:active {\n transform: scale(0.98);\n}\n.ri-arActionButton--primary {\n color: #06343c;\n background: #22d3ee;\n border-color: transparent;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n}\n.ri-arActionButton--primary:hover {\n background: #67e8f9;\n border-color: transparent;\n}\n.ri-arActionButton.ri-arActionButton--active,\n.ri-arActionButton[aria-pressed=true],\n.ri-arActionButton.ri-arActionButton--active:hover,\n.ri-arActionButton[aria-pressed=true]:hover,\n.ri-arActionButton.ri-arActionButton--active:active,\n.ri-arActionButton[aria-pressed=true]:active {\n color: #06343c;\n background: #22d3ee;\n border-color: transparent;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n}\n.ri-hiddenInput {\n display: none;\n}\n.ri-popover {\n position: absolute;\n z-index: 50;\n background: #ffffff;\n border: 1px solid #e5e7eb;\n border-radius: 16px;\n box-shadow: 0 24px 48px rgba(15, 23, 42, 0.18);\n color: #111827;\n}\n.ri-popover--inline {\n position: static;\n z-index: auto;\n width: 100%;\n border-radius: 10px;\n box-shadow: none;\n}\n.ri-popover--inline .react-colorful {\n width: 100%;\n}\n.ri-popover--inline {\n background: var(--ri-databinding-surface, rgba(255, 255, 255, 0.03));\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text, #f1f5f9);\n}\n.ri-popover--inline .ri-popoverHeader {\n border-bottom-color: var( --ri-databinding-border-strong, rgba(255, 255, 255, 0.08) );\n}\n.ri-popover--inline .ri-popoverLabel {\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-colorCode {\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-secondaryButton {\n background: var(--ri-databinding-surface-hover, rgba(255, 255, 255, 0.06));\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text, #f1f5f9);\n}\n.ri-popover--inline .ri-secondaryButton:hover {\n background: rgba(255, 255, 255, 0.12);\n}\n.ri-popover--inline .ri-previewFrame {\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n}\n.ri-popover--inline .ri-emptyState {\n border-color: var(--ri-databinding-border-hover, rgba(255, 255, 255, 0.15));\n color: var(--ri-databinding-text-subtle, #64748b);\n}\n.ri-popover--inline .ri-uploadButton {\n background: var(--ri-databinding-accent-gradient, #2563eb);\n color: #ffffff;\n}\n.ri-popover--inline .ri-uploadButton:hover {\n background: var(--ri-databinding-accent-gradient-active, #1d4ed8);\n}\n.ri-popover--inline .ri-removeButton {\n background: transparent;\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-removeButton:hover {\n background: var(--ri-databinding-surface-hover, rgba(255, 255, 255, 0.06));\n}\n.ri-popover--inline .ri-formatHint {\n color: var(--ri-databinding-text-subtle, #64748b);\n}\n.ri-popoverHeader {\n padding: 12px 16px;\n}\n.ri-popoverLabel {\n margin: 0 0 8px;\n font-size: 11px;\n font-weight: 700;\n color: #6b7280;\n text-transform: uppercase;\n letter-spacing: 0.16em;\n}\n.ri-inlineRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n margin-top: 8px;\n}\n.ri-colorCode {\n font-size: 12px;\n color: #9ca3af;\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Monaco,\n Consolas,\n monospace;\n text-transform: uppercase;\n}\n.ri-secondaryButton {\n border: 1px solid #e5e7eb;\n background: #f3f4f6;\n color: #374151;\n border-radius: 8px;\n padding: 6px 10px;\n font-size: 12px;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-secondaryButton:hover {\n background: #e5e7eb;\n}\n.ri-materialBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding-left: 12px;\n padding-right: 12px;\n padding-bottom: 12px;\n}\n.ri-previewFrame {\n position: relative;\n height: 112px;\n border-radius: 12px;\n overflow: hidden;\n border: 1px solid #e5e7eb;\n}\n.ri-previewImage {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n.ri-previewCaption {\n position: absolute;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n padding: 4px 8px;\n color: #ffffff;\n font-size: 10px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ri-emptyState {\n height: 112px;\n border-radius: 12px;\n border: 2px dashed #e5e7eb;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 4px;\n color: #d1d5db;\n text-align: center;\n}\n.ri-uploadButton {\n width: 100%;\n border: 0;\n border-radius: 10px;\n background: #111827;\n color: #ffffff;\n padding: 10px 12px;\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-uploadButton:hover {\n background: #374151;\n}\n.ri-removeButton {\n width: 100%;\n border-radius: 10px;\n border: 1px solid #e5e7eb;\n background: #ffffff;\n color: #6b7280;\n padding: 8px 12px;\n font-size: 12px;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-removeButton:hover {\n background: #f9fafb;\n}\n.ri-formatHint {\n margin: 0;\n text-align: center;\n font-size: 10px;\n color: #d1d5db;\n}\n.ri-buttonContainer {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 8px;\n position: absolute;\n bottom: 12px;\n left: 12px;\n right: 12px;\n z-index: 10;\n}\n.ri-tourControls {\n display: flex;\n gap: 6px;\n align-items: center;\n flex-shrink: 0;\n}\n.ri-rightControls {\n display: flex;\n gap: 6px;\n align-items: center;\n flex-shrink: 0;\n margin-left: auto;\n}\n.ri-rightControls:only-child {\n margin-left: auto;\n}\n.ri-navButtonSingle,\n.ri-tourButton,\n.ri-resetButton,\n.ri-downloadButton {\n height: 34px;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n color: rgba(255, 255, 255, 0.75);\n border-radius: 8px;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n cursor: pointer;\n transition:\n background 0.15s ease,\n color 0.15s ease,\n border-color 0.15s ease,\n transform 0.15s ease;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n}\n.ri-navButtonSingle:hover:not(:disabled),\n.ri-tourButton:hover:not(:disabled),\n.ri-resetButton:hover:not(:disabled) {\n background: rgba(40, 40, 40, 0.92);\n color: #fff;\n border-color: rgba(255, 255, 255, 0.25);\n}\n.ri-navButtonSingle:active:not(:disabled),\n.ri-tourButton:active:not(:disabled),\n.ri-resetButton:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-navButtonSingle:disabled,\n.ri-tourButton:disabled,\n.ri-resetButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.ri-navButtonSingle {\n padding: 0 16px;\n font-size: 13px;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 8px;\n white-space: nowrap;\n}\n.ri-tourButton {\n padding: 0 12px;\n font-size: 13px;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 4px;\n white-space: nowrap;\n}\n.ri-tourButton svg {\n width: 15px;\n height: 15px;\n flex-shrink: 0;\n}\n.ri-tourButton--stop {\n border-color: rgba(255, 68, 68, 0.4);\n color: #ff6b6b;\n}\n.ri-tourButton--stop:hover:not(:disabled) {\n background: rgba(255, 68, 68, 0.15);\n border-color: rgba(255, 68, 68, 0.6);\n color: #ff6b6b;\n}\n.ri-tourButton--stop:active:not(:disabled) {\n background: #ff4444;\n color: #fff;\n border-color: #ff4444;\n}\n.ri-tourButton--next {\n border-color: rgba(56, 219, 240, 0.3);\n color: #38dbf0;\n}\n.ri-tourButton--next:hover:not(:disabled) {\n background: rgba(56, 219, 240, 0.12);\n border-color: rgba(56, 219, 240, 0.5);\n color: #38dbf0;\n}\n.ri-tourButton--next:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-resetButton {\n width: 34px;\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.ri-resetButton svg {\n width: 15px;\n height: 15px;\n pointer-events: none;\n flex-shrink: 0;\n}\n.ri-resetButton:focus-visible {\n outline: 2px solid rgba(255, 255, 255, 0.5);\n outline-offset: 2px;\n}\n.ri-downloadWrapper {\n position: relative;\n display: inline-flex;\n flex-direction: column;\n align-items: stretch;\n}\n.ri-downloadButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 0 16px;\n font-size: 13px;\n font-weight: 500;\n white-space: nowrap;\n}\n.ri-downloadButton:hover:not(:disabled) {\n background: rgba(0, 0, 0, 0.72);\n border-color: rgba(255, 255, 255, 0.3);\n color: #fff;\n}\n.ri-downloadButton:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-downloadButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.ri-downloadButton__spinner {\n display: inline-block;\n width: 13px;\n height: 13px;\n border: 2px solid rgba(255, 255, 255, 0.3);\n border-top-color: #fff;\n border-radius: 50%;\n animation: ri-spin 0.7s linear infinite;\n flex-shrink: 0;\n}\n.ri-downloadMenu {\n position: absolute;\n bottom: calc(100% + 6px);\n right: 0;\n min-width: 168px;\n background: rgba(20, 20, 20, 0.92);\n border: 1px solid rgba(255, 255, 255, 0.14);\n border-radius: 10px;\n overflow: hidden;\n z-index: 100;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n animation: ri-menuReveal 0.12s ease;\n}\n@keyframes ri-menuReveal {\n from {\n opacity: 0;\n transform: translateY(4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n.ri-downloadMenuItem {\n display: flex;\n align-items: center;\n gap: 9px;\n width: 100%;\n padding: 10px 14px;\n font-size: 13px;\n font-weight: 400;\n color: #fff;\n background: transparent;\n border: none;\n cursor: pointer;\n text-align: left;\n transition: background 0.1s ease;\n}\n.ri-downloadMenuItem:hover:not(:disabled) {\n background: rgba(255, 255, 255, 0.1);\n}\n.ri-downloadMenuItem:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.ri-downloadMenuDivider {\n height: 1px;\n background: rgba(255, 255, 255, 0.1);\n margin: 0 10px;\n}\n@keyframes ri-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.ri-annotationMarker {\n position: relative;\n width: 36px;\n height: 36px;\n border-radius: 50%;\n background: rgba(20, 20, 20, 0.82);\n border: 2px solid rgba(255, 255, 255, 0.9);\n color: #fff;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n font-weight: 700;\n cursor: pointer;\n z-index: 20;\n user-select: none;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);\n pointer-events: auto;\n transition: opacity 0.3s ease, transform 0.3s ease;\n}\n.ri-annotationMarker:hover {\n transform: scale(1.08);\n box-shadow: 0 12px 28px rgba(15, 23, 42, 0.18);\n background: rgba(40, 40, 40, 0.92);\n}\n.ri-annotationPopup {\n position: absolute;\n width: min(270px, calc(100vw - 32px));\n background: rgba(20, 20, 20, 0.85);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 16px;\n padding: 16px;\n z-index: 30;\n display: flex;\n flex-direction: column;\n gap: 12px;\n box-shadow: 0 24px 48px rgba(15, 23, 42, 0.18);\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n}\n.ri-annotationInput,\n.ri-annotationTextarea {\n width: 100%;\n border: 1px solid rgba(255, 255, 255, 0.12);\n background: rgba(255, 255, 255, 0.05);\n color: #e5e7eb;\n border-radius: 10px;\n padding: 10px 12px;\n outline: none;\n font-family: inherit;\n transition: border-color 0.2s ease, box-shadow 0.2s ease;\n}\n.ri-annotationInput {\n font-size: 14px;\n font-weight: 500;\n}\n.ri-annotationTextarea {\n font-size: 13px;\n resize: vertical;\n min-height: 110px;\n}\n.ri-annotationInput:focus,\n.ri-annotationTextarea:focus {\n border-color: #38dbf0;\n box-shadow: 0 0 0 3px rgba(56, 219, 240, 0.12);\n}\n.ri-annotationActions {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.ri-annotationOkBtn {\n border: 0;\n border-radius: 8px;\n background: rgba(56, 219, 240, 0.8);\n color: #06343c;\n padding: 8px 18px;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n transition: background-color 0.2s ease, opacity 0.2s ease;\n}\n.ri-annotationOkBtn:hover:not(:disabled) {\n background: #38dbf0;\n}\n.ri-annotationOkBtn:disabled,\n.ri-annotationOkBtn.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.ri-annotationCancelBtn {\n border: 1px solid rgba(255, 255, 255, 0.12);\n background: transparent;\n color: #9ca3af;\n border-radius: 8px;\n padding: 8px 12px;\n font-size: 13px;\n cursor: pointer;\n transition: background-color 0.2s ease, border-color 0.2s ease;\n}\n.ri-annotationCancelBtn:hover {\n background: rgba(255, 255, 255, 0.05);\n border-color: rgba(255, 255, 255, 0.2);\n}\n.ri-annotationIcons {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 12px;\n color: #9ca3af;\n}\n.ri-annotationIcons svg {\n flex-shrink: 0;\n}\n.ri-viewAnnotationPopup {\n position: relative;\n background: rgba(20, 20, 20, 0.85);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 8px;\n padding: 12px 16px;\n min-width: 200px;\n max-width: min(280px, calc(100vw - 48px));\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);\n pointer-events: auto;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n animation: ri-viewAnnotationFadeIn 0.15s ease-out;\n margin-left: 24px;\n margin-top: -20px;\n isolation: isolate;\n z-index: 1000;\n}\n.ri-viewAnnotationHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n}\n.ri-viewAnnotationTitle {\n margin: 0;\n color: #e2e8f0;\n font-size: 14px;\n font-weight: 600;\n}\n.ri-viewAnnotationClose {\n background: transparent;\n border: none;\n color: #b7bdc6;\n cursor: pointer;\n font-size: 18px;\n padding: 0 4px;\n transition: color 0.2s;\n line-height: 1;\n}\n.ri-viewAnnotationClose:hover {\n color: #e2e8f0;\n}\n.ri-viewAnnotationDescription {\n margin: 0;\n color: #cbd5e1;\n font-size: 13px;\n line-height: 1.4;\n word-wrap: break-word;\n}\n@keyframes ri-viewAnnotationFadeIn {\n from {\n opacity: 0;\n transform: translateY(-6px) scale(0.96);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n.ri-annotationNavSingle {\n position: absolute;\n bottom: 120px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n pointer-events: none;\n}\n.ri-annotationNavSingle > * {\n pointer-events: auto;\n}\n.ri-navBadge {\n background: rgba(255, 255, 255, 0.1);\n padding: 2px 10px;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 400;\n color: rgba(255, 255, 255, 0.7);\n}\n.ri-annotationTitleHint {\n background: rgba(0, 0, 0, 0.7);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n color: white;\n padding: 8px 20px;\n border-radius: 8px;\n font-size: 13px;\n border: 1px solid rgba(255, 255, 255, 0.08);\n text-align: center;\n max-width: 350px;\n animation: slideUp 0.3s ease;\n}\n.ri-annotationHintTitle {\n font-weight: 600;\n color: #e5e7eb;\n}\n.ri-annotationHintDesc {\n font-size: 12px;\n opacity: 0.6;\n margin-top: 2px;\n color: #d1d5db;\n}\n@keyframes slideUp {\n from {\n opacity: 0;\n transform: translateY(10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n.ri-canvasArea .bb-loadingOverlay {\n position: absolute;\n inset: 0;\n z-index: 12;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 16px;\n padding: 24px;\n text-align: center;\n border-radius: 14px;\n background: rgba(15, 23, 42, 0.46);\n backdrop-filter: blur(1px);\n}\n.ri-canvasArea .bb-loadingSpinner {\n width: 40px;\n height: 40px;\n border-radius: 999px;\n border: 3px solid rgba(226, 232, 240, 0.24);\n border-top-color: #93c5fd;\n animation: bb-spin 0.8s linear infinite;\n}\n.ri-canvasArea .bb-loadingMessage {\n max-width: 320px;\n color: #f7f8f9;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.5;\n}\n@keyframes bb-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.ri-largeModelBanner {\n position: absolute;\n top: 12px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 12px 7px 10px;\n border-radius: 8px;\n background: rgba(30, 20, 0, 0.88);\n border: 1px solid rgba(251, 191, 36, 0.35);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n color: rgb(251, 191, 36);\n font-size: 12px;\n font-weight: 500;\n line-height: 1.4;\n pointer-events: auto;\n max-width: calc(100% - 32px);\n white-space: normal;\n text-align: center;\n}\n.ri-largeModelBanner--offset {\n top: 64px;\n}\n.ri-largeModelBanner__icon {\n flex-shrink: 0;\n}\n.ri-largeModelBanner__dismiss {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 4px;\n padding: 2px;\n background: none;\n border: none;\n border-radius: 4px;\n color: rgba(251, 191, 36, 0.7);\n cursor: pointer;\n transition: color 0.15s ease, background 0.15s ease;\n}\n.ri-largeModelBanner__dismiss:hover {\n color: rgb(251, 191, 36);\n background: rgba(251, 191, 36, 0.12);\n}\n@media (max-width: 768px) {\n .ri-xrTools {\n gap: 6px;\n }\n .ri-xrButton {\n height: 34px;\n gap: 6px;\n padding: 0 8px;\n font-size: 11px;\n }\n .ri-xrButton svg {\n width: 14px;\n height: 14px;\n }\n .ri-xrButtonPrefix {\n display: none;\n }\n .ri-annotationNavSingle {\n bottom: 100px;\n }\n .ri-navButtonSingle {\n padding: 8px 20px;\n font-size: 13px;\n min-width: 110px;\n }\n .ri-annotationTitleHint {\n padding: 6px 14px;\n font-size: 12px;\n max-width: 250px;\n }\n .ri-viewAnnotationPopup {\n min-width: 160px;\n max-width: 220px;\n padding: 10px 12px;\n }\n .ri-viewAnnotationTitle {\n font-size: 13px;\n }\n .ri-viewAnnotationDescription {\n font-size: 12px;\n }\n .ri-buttonContainer {\n flex-wrap: wrap;\n gap: 6px;\n }\n .ri-tourControls {\n flex-wrap: wrap;\n }\n .ri-rightControls {\n flex-wrap: wrap;\n }\n}\n@media (max-width: 480px) {\n .ri-xrTools {\n gap: 5px;\n }\n .ri-xrButton {\n height: 34px;\n gap: 5px;\n font-size: 10px;\n }\n .ri-xrButton svg {\n width: 13px;\n height: 13px;\n }\n .ri-annotationNavSingle {\n bottom: 80px;\n }\n .ri-navButtonSingle {\n padding: 6px 16px;\n font-size: 12px;\n min-width: 90px;\n gap: 6px;\n }\n .ri-annotationTitleHint {\n padding: 4px 10px;\n font-size: 11px;\n max-width: 200px;\n }\n .ri-navBadge {\n font-size: 10px;\n padding: 1px 6px;\n }\n .ri-tourButton {\n padding: 0 8px;\n font-size: 12px;\n }\n .ri-tourButton span {\n display: none;\n }\n .ri-downloadButton {\n padding: 0 10px;\n font-size: 12px;\n }\n .ri-downloadButton span {\n display: none;\n }\n}\n');
204
204
 
205
205
  // src/components/LicenseOverlay.tsx
206
206
  import { AlertTriangle } from "lucide-react";
@@ -4130,6 +4130,7 @@ import {
4130
4130
  Box3 as Box33,
4131
4131
  CatmullRomCurve3,
4132
4132
  Sphere,
4133
+ Spherical,
4133
4134
  Vector3 as Vector36
4134
4135
  } from "three";
4135
4136
  var ENTER_BLEND_SECONDS = 1.2;
@@ -4138,18 +4139,75 @@ function CinematicCamera({
4138
4139
  sceneRef,
4139
4140
  config,
4140
4141
  playing,
4142
+ resetOnPlay = false,
4141
4143
  onFinished,
4142
- onUserInteract
4144
+ onUserInteract,
4145
+ onStop
4143
4146
  }) {
4144
4147
  const invalidate = useThree9((state) => state.invalidate);
4145
4148
  const waypoints = useMemo11(() => config.waypoints ?? [], [config.waypoints]);
4146
4149
  const hasPath = waypoints.length >= 2;
4147
4150
  const loop = config.loop ?? true;
4148
- const duration = config.duration ?? (hasPath ? Math.max(waypoints.length * 3.5, 8) : 24);
4151
+ const calculatedDuration = useMemo11(() => {
4152
+ if (waypoints.length < 2) return 24;
4153
+ let total = 0;
4154
+ for (let i = 0; i < waypoints.length - 1; i++) {
4155
+ total += waypoints[i].segmentDuration ?? 3.5;
4156
+ }
4157
+ if (loop && waypoints.length > 1) {
4158
+ total += waypoints[waypoints.length - 1].segmentDuration ?? 3.5;
4159
+ }
4160
+ return Math.max(total, 4);
4161
+ }, [waypoints, loop]);
4162
+ const duration = config.duration ?? calculatedDuration;
4149
4163
  const signature = useMemo11(
4150
- () => waypoints.map((w) => [...w.position, ...w.target].join(",")).join(";"),
4164
+ () => waypoints.map(
4165
+ (waypoint) => [
4166
+ ...waypoint.position,
4167
+ ...waypoint.target,
4168
+ waypoint.azimuthAngle ?? "",
4169
+ waypoint.polarAngle ?? "",
4170
+ waypoint.segmentDuration ?? ""
4171
+ ].join(",")
4172
+ ).join(";"),
4151
4173
  [waypoints]
4152
4174
  );
4175
+ const segmentTimes = useMemo11(() => {
4176
+ const times = [];
4177
+ if (waypoints.length < 2) return times;
4178
+ let accumulated = 0;
4179
+ for (let i = 0; i < waypoints.length - 1; i++) {
4180
+ accumulated += waypoints[i].segmentDuration ?? 3.5;
4181
+ times.push(accumulated);
4182
+ }
4183
+ if (loop && waypoints.length > 1) {
4184
+ accumulated += waypoints[waypoints.length - 1].segmentDuration ?? 3.5;
4185
+ times.push(accumulated);
4186
+ }
4187
+ return times;
4188
+ }, [waypoints, loop]);
4189
+ const getCurveParameter = useMemo11(() => {
4190
+ return (elapsed) => {
4191
+ if (waypoints.length < 2 || segmentTimes.length === 0) {
4192
+ return elapsed / duration;
4193
+ }
4194
+ const segmentTotal = segmentTimes[segmentTimes.length - 1];
4195
+ const playbackElapsed = loop ? elapsed % duration : Math.min(elapsed, duration);
4196
+ const normalizedElapsed = duration > 0 ? playbackElapsed / duration * segmentTotal : 0;
4197
+ for (let i = 0; i < segmentTimes.length; i++) {
4198
+ if (normalizedElapsed <= segmentTimes[i]) {
4199
+ const prevTime = i === 0 ? 0 : segmentTimes[i - 1];
4200
+ const segmentDuration = segmentTimes[i] - prevTime;
4201
+ const progress = segmentDuration > 0 ? (normalizedElapsed - prevTime) / segmentDuration : 0;
4202
+ const totalSegments = loop ? waypoints.length : waypoints.length - 1;
4203
+ const baseU = i / totalSegments;
4204
+ const segmentU = 1 / totalSegments;
4205
+ return baseU + progress * segmentU;
4206
+ }
4207
+ }
4208
+ return 1;
4209
+ };
4210
+ }, [waypoints.length, segmentTimes, duration, loop]);
4153
4211
  const curves = useMemo11(() => {
4154
4212
  if (!hasPath) return null;
4155
4213
  return {
@@ -4167,29 +4225,83 @@ function CinematicCamera({
4167
4225
  )
4168
4226
  };
4169
4227
  }, [signature, hasPath, loop]);
4228
+ const orbitPath = useMemo11(() => {
4229
+ if (!hasPath || !waypoints.every(
4230
+ (waypoint) => Number.isFinite(waypoint.azimuthAngle) && Number.isFinite(waypoint.polarAngle)
4231
+ )) {
4232
+ return null;
4233
+ }
4234
+ const points = waypoints.map((waypoint) => ({
4235
+ azimuth: waypoint.azimuthAngle,
4236
+ polar: waypoint.polarAngle,
4237
+ distance: new Vector36(...waypoint.position).distanceTo(
4238
+ new Vector36(...waypoint.target)
4239
+ )
4240
+ }));
4241
+ for (let index = 1; index < points.length; index += 1) {
4242
+ const previous = points[index - 1].azimuth;
4243
+ const current = points[index].azimuth;
4244
+ points[index].azimuth = current + Math.round((previous - current) / (Math.PI * 2)) * Math.PI * 2;
4245
+ }
4246
+ return points;
4247
+ }, [signature, hasPath]);
4170
4248
  const elapsedRef = useRef8(0);
4171
4249
  const enterElapsedRef = useRef8(0);
4172
4250
  const enterPos = useRef8(new Vector36());
4173
4251
  const enterTarget = useRef8(new Vector36());
4174
4252
  const startedRef = useRef8(false);
4175
4253
  const pausedElapsedRef = useRef8(0);
4254
+ const hasFinishedRef = useRef8(false);
4176
4255
  const orbitRef = useRef8(null);
4177
4256
  const tmpPos = useMemo11(() => new Vector36(), []);
4178
4257
  const tmpTarget = useMemo11(() => new Vector36(), []);
4258
+ const tmpSpherical = useMemo11(() => new Spherical(), []);
4259
+ useEffect11(() => {
4260
+ elapsedRef.current = 0;
4261
+ pausedElapsedRef.current = 0;
4262
+ enterElapsedRef.current = 0;
4263
+ hasFinishedRef.current = false;
4264
+ }, [signature, loop, duration]);
4265
+ useEffect11(() => {
4266
+ if (resetOnPlay) {
4267
+ pausedElapsedRef.current = 0;
4268
+ elapsedRef.current = 0;
4269
+ hasFinishedRef.current = false;
4270
+ }
4271
+ }, [resetOnPlay]);
4179
4272
  useEffect11(() => {
4180
4273
  if (!playing) {
4181
4274
  startedRef.current = false;
4182
- if (elapsedRef.current > 0) {
4275
+ if (elapsedRef.current > 0 && !resetOnPlay) {
4183
4276
  pausedElapsedRef.current = elapsedRef.current;
4277
+ } else if (resetOnPlay) {
4278
+ pausedElapsedRef.current = 0;
4279
+ }
4280
+ if (elapsedRef.current > 0 || hasFinishedRef.current) {
4281
+ onStop?.();
4184
4282
  }
4185
4283
  return;
4186
4284
  }
4285
+ if (elapsedRef.current >= duration && !loop) {
4286
+ elapsedRef.current = 0;
4287
+ pausedElapsedRef.current = 0;
4288
+ hasFinishedRef.current = false;
4289
+ }
4187
4290
  const controls = controlsRef.current;
4188
4291
  if (controls) {
4189
4292
  controls.getPosition(enterPos.current);
4190
4293
  controls.getTarget(enterTarget.current);
4191
4294
  }
4192
- elapsedRef.current = pausedElapsedRef.current || 0;
4295
+ if (resetOnPlay || hasFinishedRef.current || pausedElapsedRef.current === 0) {
4296
+ elapsedRef.current = 0;
4297
+ pausedElapsedRef.current = 0;
4298
+ hasFinishedRef.current = false;
4299
+ } else if (pausedElapsedRef.current > 0 && pausedElapsedRef.current < duration) {
4300
+ elapsedRef.current = pausedElapsedRef.current;
4301
+ } else {
4302
+ elapsedRef.current = 0;
4303
+ pausedElapsedRef.current = 0;
4304
+ }
4193
4305
  enterElapsedRef.current = 0;
4194
4306
  if (!hasPath) {
4195
4307
  const scene = sceneRef.current;
@@ -4203,12 +4315,26 @@ function CinematicCamera({
4203
4315
  }
4204
4316
  startedRef.current = true;
4205
4317
  invalidate();
4206
- }, [playing, hasPath, controlsRef, sceneRef, invalidate]);
4318
+ }, [
4319
+ playing,
4320
+ hasPath,
4321
+ controlsRef,
4322
+ sceneRef,
4323
+ invalidate,
4324
+ duration,
4325
+ loop,
4326
+ signature,
4327
+ resetOnPlay,
4328
+ onStop
4329
+ ]);
4207
4330
  useEffect11(() => {
4208
4331
  if (!playing || !onUserInteract) return;
4209
4332
  const controls = controlsRef.current;
4210
4333
  if (!controls) return;
4211
- const handle = () => onUserInteract();
4334
+ const handle = () => {
4335
+ if (hasFinishedRef.current) return;
4336
+ onUserInteract();
4337
+ };
4212
4338
  controls.addEventListener("controlstart", handle);
4213
4339
  return () => controls.removeEventListener("controlstart", handle);
4214
4340
  }, [playing, controlsRef, onUserInteract]);
@@ -4218,12 +4344,33 @@ function CinematicCamera({
4218
4344
  if (!controls) return;
4219
4345
  elapsedRef.current += delta;
4220
4346
  enterElapsedRef.current += delta;
4221
- let u = elapsedRef.current / duration;
4222
- const finished = !loop && u >= 1;
4347
+ let u = getCurveParameter(elapsedRef.current);
4348
+ const finished = !loop && elapsedRef.current >= duration;
4223
4349
  u = loop ? u % 1 : Math.min(u, 1);
4224
4350
  if (curves) {
4225
- curves.position.getPointAt(u, tmpPos);
4226
- curves.target.getPointAt(u, tmpTarget);
4351
+ curves.target.getPoint(u, tmpTarget);
4352
+ if (orbitPath) {
4353
+ const segmentCount = loop ? orbitPath.length : orbitPath.length - 1;
4354
+ const scaled = Math.min(u, 1) * segmentCount;
4355
+ const segment = Math.min(Math.floor(scaled), segmentCount - 1);
4356
+ const next = (segment + 1) % orbitPath.length;
4357
+ const localT = u >= 1 ? 1 : scaled - segment;
4358
+ const eased = localT * localT * (3 - 2 * localT);
4359
+ const start = orbitPath[segment];
4360
+ const end = orbitPath[next];
4361
+ const endAzimuth = end.azimuth + Math.round((start.azimuth - end.azimuth) / (Math.PI * 2)) * Math.PI * 2;
4362
+ const azimuth = start.azimuth + (endAzimuth - start.azimuth) * eased;
4363
+ const polar = start.polar + (end.polar - start.polar) * eased;
4364
+ const distance = start.distance + (end.distance - start.distance) * eased;
4365
+ tmpSpherical.set(
4366
+ Math.max(distance, 1e-6),
4367
+ Math.min(Math.max(polar, 1e-6), Math.PI - 1e-6),
4368
+ azimuth
4369
+ );
4370
+ tmpPos.setFromSpherical(tmpSpherical).add(tmpTarget);
4371
+ } else {
4372
+ curves.position.getPoint(u, tmpPos);
4373
+ }
4227
4374
  } else {
4228
4375
  const orbit = orbitRef.current;
4229
4376
  if (!orbit) return;
@@ -4253,6 +4400,8 @@ function CinematicCamera({
4253
4400
  );
4254
4401
  if (finished) {
4255
4402
  startedRef.current = false;
4403
+ hasFinishedRef.current = true;
4404
+ elapsedRef.current = 0;
4256
4405
  pausedElapsedRef.current = 0;
4257
4406
  onFinished?.();
4258
4407
  return;
@@ -5653,9 +5802,11 @@ function ModelViewer({
5653
5802
  return { ...base ?? {}, ...cinematic };
5654
5803
  }, [cinematic, resolvedSceneConfig]);
5655
5804
  const [cinematicPlaying, setCinematicPlaying] = useState10(false);
5805
+ const [cinematicReset, setCinematicReset] = useState10(false);
5656
5806
  const cinematicAutoStartedRef = useRef11(false);
5657
5807
  useEffect15(() => {
5658
5808
  setCinematicPlaying(false);
5809
+ setCinematicReset(false);
5659
5810
  cinematicAutoStartedRef.current = false;
5660
5811
  }, [modelUrl]);
5661
5812
  useEffect15(() => {
@@ -5663,6 +5814,7 @@ function ModelViewer({
5663
5814
  if (modelLoaded && cameraPositioned && !isAutoFraming) {
5664
5815
  cinematicAutoStartedRef.current = true;
5665
5816
  setCinematicPlaying(true);
5817
+ setCinematicReset(false);
5666
5818
  }
5667
5819
  }, [cinematicConfig, modelLoaded, cameraPositioned, isAutoFraming]);
5668
5820
  const xrStore = useMemo13(() => {
@@ -6816,6 +6968,20 @@ function ModelViewer({
6816
6968
  console.error("Reset camera error:", error);
6817
6969
  }
6818
6970
  }, [onAutoFit, fitScene]);
6971
+ const handleCinematicPlay = useCallback4(() => {
6972
+ setCinematicReset(false);
6973
+ setCinematicPlaying(true);
6974
+ }, []);
6975
+ const handleCinematicPause = useCallback4(() => {
6976
+ setCinematicPlaying(false);
6977
+ }, []);
6978
+ const handleCinematicStop = useCallback4(() => {
6979
+ setCinematicPlaying(false);
6980
+ setCinematicReset(true);
6981
+ setTimeout(() => {
6982
+ handleReset();
6983
+ }, 100);
6984
+ }, [handleReset]);
6819
6985
  const handleCameraChangeWithAnnotations = useCallback4(
6820
6986
  (cameraValue, controls) => {
6821
6987
  handleCameraChangeInternal(cameraValue, controls);
@@ -7132,8 +7298,14 @@ function ModelViewer({
7132
7298
  sceneRef,
7133
7299
  config: cinematicConfig,
7134
7300
  playing: cinematicPlaying,
7135
- onFinished: () => setCinematicPlaying(false),
7136
- onUserInteract: () => setCinematicPlaying(false)
7301
+ resetOnPlay: cinematicReset,
7302
+ onFinished: () => {
7303
+ setCinematicPlaying(false);
7304
+ setCinematicReset(false);
7305
+ },
7306
+ onUserInteract: () => {
7307
+ setCinematicPlaying(false);
7308
+ }
7137
7309
  }
7138
7310
  ),
7139
7311
  showMeasureTools && /* @__PURE__ */ jsx19(
@@ -7361,24 +7533,43 @@ function ModelViewer({
7361
7533
  ]
7362
7534
  }
7363
7535
  ),
7364
- cinematicConfig && /* @__PURE__ */ jsx19(
7536
+ cinematicConfig && /* @__PURE__ */ jsxs15(
7365
7537
  "div",
7366
7538
  {
7367
7539
  className: "ri-cinematicTools",
7368
7540
  style: {
7369
7541
  top: `${topLeftRow((xrToolsVisible ? 1 : 0) + (showMeasureTools ? 1 : 0) + (showExplodeControls ? 1 : 0))}px`
7370
7542
  },
7371
- children: /* @__PURE__ */ jsx19(
7372
- "button",
7373
- {
7374
- type: "button",
7375
- className: `ri-measureButton ${cinematicPlaying ? "ri-measureButton--active" : ""}`,
7376
- onClick: () => setCinematicPlaying((prev) => !prev),
7377
- title: cinematicPlaying ? "Pause cinematic" : "Play cinematic",
7378
- "aria-pressed": cinematicPlaying,
7379
- children: cinematicPlaying ? /* @__PURE__ */ jsx19(Pause, { size: 16 }) : /* @__PURE__ */ jsx19(Play, { size: 16 })
7380
- }
7381
- )
7543
+ children: [
7544
+ /* @__PURE__ */ jsx19(
7545
+ "button",
7546
+ {
7547
+ type: "button",
7548
+ className: `ri-measureButton ${cinematicPlaying ? "ri-measureButton--active" : ""}`,
7549
+ onClick: () => {
7550
+ if (cinematicPlaying) {
7551
+ setCinematicPlaying(false);
7552
+ } else {
7553
+ setCinematicReset(false);
7554
+ setCinematicPlaying(true);
7555
+ }
7556
+ },
7557
+ title: cinematicPlaying ? "Pause cinematic" : "Play cinematic",
7558
+ "aria-pressed": cinematicPlaying,
7559
+ children: cinematicPlaying ? /* @__PURE__ */ jsx19(Pause, { size: 16 }) : /* @__PURE__ */ jsx19(Play, { size: 16 })
7560
+ }
7561
+ ),
7562
+ cinematicPlaying && /* @__PURE__ */ jsx19(
7563
+ "button",
7564
+ {
7565
+ type: "button",
7566
+ className: "ri-measureButton ri-measureButton--stop",
7567
+ onClick: handleCinematicStop,
7568
+ title: "Stop cinematic and reset camera",
7569
+ children: /* @__PURE__ */ jsx19(Square, { size: 16 })
7570
+ }
7571
+ )
7572
+ ]
7382
7573
  }
7383
7574
  ),
7384
7575
  (showResetButton || showDownloadButton || showAnnotationNavigation && sortedAnnotations.length > 0) && /* @__PURE__ */ jsxs15("div", { className: "ri-buttonContainer", children: [
@@ -7618,7 +7809,7 @@ function ModelViewer({
7618
7809
  }
7619
7810
 
7620
7811
  // src/components/BindingBuilder/BindingBuilder.tsx
7621
- import { useCallback as useCallback8, useEffect as useEffect20, useMemo as useMemo16, useRef as useRef18, useState as useState24 } from "react";
7812
+ import { useCallback as useCallback9, useEffect as useEffect20, useMemo as useMemo16, useRef as useRef18, useState as useState25 } from "react";
7622
7813
  import {
7623
7814
  Redo2,
7624
7815
  Undo2,
@@ -7665,14 +7856,16 @@ function buildCameraState(camera, controls) {
7665
7856
  position: toTuple(position),
7666
7857
  target: toTuple(target),
7667
7858
  fov: "fov" in camera && typeof camera.fov === "number" ? camera.fov : null,
7668
- zoom: "zoom" in camera && typeof camera.zoom === "number" ? camera.zoom : 1
7859
+ zoom: "zoom" in camera && typeof camera.zoom === "number" ? camera.zoom : 1,
7860
+ azimuthAngle: controls.azimuthAngle,
7861
+ polarAngle: controls.polarAngle
7669
7862
  };
7670
7863
  }
7671
7864
  function isSameCameraState(left, right) {
7672
7865
  if (!left) {
7673
7866
  return false;
7674
7867
  }
7675
- return left.position[0] === right.position[0] && left.position[1] === right.position[1] && left.position[2] === right.position[2] && left.target[0] === right.target[0] && left.target[1] === right.target[1] && left.target[2] === right.target[2] && left.fov === right.fov && left.zoom === right.zoom;
7868
+ return left.position[0] === right.position[0] && left.position[1] === right.position[1] && left.position[2] === right.position[2] && left.target[0] === right.target[0] && left.target[1] === right.target[1] && left.target[2] === right.target[2] && left.fov === right.fov && left.zoom === right.zoom && left.azimuthAngle === right.azimuthAngle && left.polarAngle === right.polarAngle;
7676
7869
  }
7677
7870
  async function runSmoothCameraTransition2(controls, action, smoothTime = 0.6) {
7678
7871
  const originalSmoothTime = controls.smoothTime;
@@ -7827,15 +8020,27 @@ function useViewerCamera() {
7827
8020
  }
7828
8021
  try {
7829
8022
  markInitialAutoFitOverride(controls);
7830
- await controls.setLookAt(
7831
- state.position[0],
7832
- state.position[1],
7833
- state.position[2],
7834
- state.target[0],
7835
- state.target[1],
7836
- state.target[2],
7837
- enableTransition
7838
- );
8023
+ const transitions = [
8024
+ controls.setLookAt(
8025
+ state.position[0],
8026
+ state.position[1],
8027
+ state.position[2],
8028
+ state.target[0],
8029
+ state.target[1],
8030
+ state.target[2],
8031
+ enableTransition
8032
+ )
8033
+ ];
8034
+ if (state.azimuthAngle != null && state.polarAngle != null) {
8035
+ transitions.push(
8036
+ controls.rotateTo(
8037
+ state.azimuthAngle,
8038
+ state.polarAngle,
8039
+ enableTransition
8040
+ )
8041
+ );
8042
+ }
8043
+ await Promise.all(transitions);
7839
8044
  const camera = controls.camera;
7840
8045
  let projectionChanged = false;
7841
8046
  if (state.fov !== null && "fov" in camera && typeof camera.fov === "number") {
@@ -8143,7 +8348,7 @@ function inferMimeType(path) {
8143
8348
  }
8144
8349
 
8145
8350
  // src/components/BindingBuilder/SceneSettings/SceneSettingsTab.tsx
8146
- import { useState as useState19, useEffect as useEffect18 } from "react";
8351
+ import { useState as useState20, useEffect as useEffect18 } from "react";
8147
8352
 
8148
8353
  // src/components/BindingBuilder/SceneSettings/GeneralSceneSettings.tsx
8149
8354
  import { useState as useState14 } from "react";
@@ -8329,9 +8534,10 @@ function SliderRow({
8329
8534
  max,
8330
8535
  step,
8331
8536
  formatValue,
8332
- onChange
8537
+ onChange,
8538
+ disabled = false
8333
8539
  }) {
8334
- return /* @__PURE__ */ jsxs16("label", { className: "se-field", children: [
8540
+ return /* @__PURE__ */ jsxs16("label", { className: `se-field ${disabled ? "se-field--disabled" : ""}`, children: [
8335
8541
  /* @__PURE__ */ jsxs16("span", { className: "se-fieldLabel", children: [
8336
8542
  label,
8337
8543
  /* @__PURE__ */ jsx20("span", { className: "se-fieldValue", children: formatValue ? formatValue(value) : value })
@@ -8345,7 +8551,8 @@ function SliderRow({
8345
8551
  max,
8346
8552
  step,
8347
8553
  value,
8348
- onChange: (e) => onChange(Number(e.target.value))
8554
+ onChange: (e) => onChange(Number(e.target.value)),
8555
+ disabled
8349
8556
  }
8350
8557
  )
8351
8558
  ] });
@@ -11761,14 +11968,16 @@ function AnnotationsTab({ config, onChange }) {
11761
11968
  }
11762
11969
 
11763
11970
  // src/components/BindingBuilder/SceneSettings/CinematicTab.tsx
11764
- import { ArrowDown, ArrowUp, MapPin as MapPin2, Plus as Plus2, Trash2 as Trash24 } from "lucide-react";
11765
- import { jsx as jsx31, jsxs as jsxs27 } from "react/jsx-runtime";
11971
+ import { useState as useState19, useCallback as useCallback7 } from "react";
11972
+ import { ArrowDown, ArrowUp, MapPin as MapPin2, Plus as Plus2, Trash2 as Trash24, Settings } from "lucide-react";
11973
+ import { Fragment as Fragment10, jsx as jsx31, jsxs as jsxs27 } from "react/jsx-runtime";
11766
11974
  var DEFAULT_CINEMATIC = {
11767
11975
  enabled: false,
11768
11976
  waypoints: [],
11769
11977
  loop: true,
11770
11978
  autoPlay: false
11771
11979
  };
11980
+ var DEFAULT_SEGMENT_DURATION = 3.5;
11772
11981
  function CinematicTab({
11773
11982
  config,
11774
11983
  onChange,
@@ -11777,31 +11986,76 @@ function CinematicTab({
11777
11986
  }) {
11778
11987
  const cinematic = config.cinematic ?? DEFAULT_CINEMATIC;
11779
11988
  const { waypoints } = cinematic;
11780
- const update = (patch) => {
11989
+ const calculateTotalDuration = useCallback7((wps, loop) => {
11990
+ if (wps.length < 2) return 0;
11991
+ let total = 0;
11992
+ for (let i = 0; i < wps.length - 1; i++) {
11993
+ const segDur = wps[i].segmentDuration ?? DEFAULT_SEGMENT_DURATION;
11994
+ total += segDur;
11995
+ }
11996
+ if (loop && wps.length > 1) {
11997
+ total += wps[wps.length - 1].segmentDuration ?? DEFAULT_SEGMENT_DURATION;
11998
+ }
11999
+ return Math.max(total, 0);
12000
+ }, []);
12001
+ const update = useCallback7((patch) => {
11781
12002
  onChange({ ...config, cinematic: { ...cinematic, ...patch } });
11782
- };
11783
- const addWaypoint = () => {
12003
+ }, [config, cinematic, onChange]);
12004
+ const addWaypoint = useCallback7(() => {
11784
12005
  if (!cameraState) return;
12006
+ const newWaypoint = {
12007
+ position: [...cameraState.position],
12008
+ target: [...cameraState.target],
12009
+ ...cameraState.azimuthAngle != null ? { azimuthAngle: cameraState.azimuthAngle } : {},
12010
+ ...cameraState.polarAngle != null ? { polarAngle: cameraState.polarAngle } : {},
12011
+ segmentDuration: DEFAULT_SEGMENT_DURATION
12012
+ };
12013
+ const newWaypoints = [...waypoints, newWaypoint];
12014
+ const newDuration = calculateTotalDuration(newWaypoints, cinematic.loop);
11785
12015
  update({
11786
- waypoints: [
11787
- ...waypoints,
11788
- {
11789
- position: [...cameraState.position],
11790
- target: [...cameraState.target]
11791
- }
11792
- ]
12016
+ waypoints: newWaypoints,
12017
+ duration: newDuration
11793
12018
  });
11794
- };
11795
- const deleteWaypoint = (index) => {
11796
- update({ waypoints: waypoints.filter((_, i) => i !== index) });
11797
- };
11798
- const moveWaypoint = (index, delta) => {
12019
+ }, [cameraState, waypoints, cinematic.loop, calculateTotalDuration, update]);
12020
+ const deleteWaypoint = useCallback7((index) => {
12021
+ const newWaypoints = waypoints.filter((_, i) => i !== index);
12022
+ const newDuration = calculateTotalDuration(newWaypoints, cinematic.loop);
12023
+ update({
12024
+ waypoints: newWaypoints,
12025
+ duration: newDuration
12026
+ });
12027
+ }, [waypoints, cinematic.loop, calculateTotalDuration, update]);
12028
+ const moveWaypoint = useCallback7((index, delta) => {
11799
12029
  const next = index + delta;
11800
12030
  if (next < 0 || next >= waypoints.length) return;
11801
12031
  const reordered = [...waypoints];
11802
12032
  [reordered[index], reordered[next]] = [reordered[next], reordered[index]];
11803
- update({ waypoints: reordered });
11804
- };
12033
+ const newDuration = calculateTotalDuration(reordered, cinematic.loop);
12034
+ update({
12035
+ waypoints: reordered,
12036
+ duration: newDuration
12037
+ });
12038
+ }, [waypoints, cinematic.loop, calculateTotalDuration, update]);
12039
+ const updateWaypoint = useCallback7((index, updates) => {
12040
+ const newWaypoints = waypoints.map((wp, i) => {
12041
+ if (i === index) {
12042
+ return { ...wp, ...updates };
12043
+ }
12044
+ return wp;
12045
+ });
12046
+ const newDuration = calculateTotalDuration(newWaypoints, cinematic.loop);
12047
+ update({
12048
+ waypoints: newWaypoints,
12049
+ duration: newDuration
12050
+ });
12051
+ }, [waypoints, cinematic.loop, calculateTotalDuration, update]);
12052
+ const [customizeDurations, setCustomizeDurations] = useState19(false);
12053
+ const hasSegmentDurations = waypoints.some((w) => w.segmentDuration !== void 0);
12054
+ const getDuration = useCallback7((index) => {
12055
+ if (index >= waypoints.length - 1) return DEFAULT_SEGMENT_DURATION;
12056
+ return waypoints[index]?.segmentDuration ?? DEFAULT_SEGMENT_DURATION;
12057
+ }, [waypoints]);
12058
+ const hasEnoughWaypoints = waypoints.length >= 2;
11805
12059
  return /* @__PURE__ */ jsxs27(Section, { title: "Cinematic Camera", children: [
11806
12060
  /* @__PURE__ */ jsx31("p", { className: "se-cinematicHint", children: "Frame the model in the viewer, then capture it as a waypoint. The camera glides through the waypoints in order \u2014 enable and press play in the viewer to preview." }),
11807
12061
  /* @__PURE__ */ jsxs27(
@@ -11818,62 +12072,165 @@ function CinematicTab({
11818
12072
  ]
11819
12073
  }
11820
12074
  ),
11821
- waypoints.length === 0 ? /* @__PURE__ */ jsx31("div", { className: "bb-emptyAnnotations", children: "No waypoints yet. Position the camera and add your first one." }) : /* @__PURE__ */ jsx31("div", { className: "se-waypointList", children: waypoints.map((waypoint, index) => /* @__PURE__ */ jsxs27("div", { className: "se-waypointCard", children: [
11822
- /* @__PURE__ */ jsx31("div", { className: "se-waypointBadge", children: index + 1 }),
11823
- /* @__PURE__ */ jsxs27("div", { className: "se-waypointBody", children: [
11824
- /* @__PURE__ */ jsxs27("div", { className: "se-waypointCoords", children: [
11825
- "pos [",
11826
- waypoint.position.map((n) => n.toFixed(1)).join(", "),
11827
- "]"
11828
- ] }),
11829
- /* @__PURE__ */ jsxs27("div", { className: "se-waypointCoords", children: [
11830
- "look [",
11831
- waypoint.target.map((n) => n.toFixed(1)).join(", "),
11832
- "]"
11833
- ] })
11834
- ] }),
11835
- /* @__PURE__ */ jsxs27("div", { className: "se-waypointActions", children: [
11836
- /* @__PURE__ */ jsx31(
11837
- "button",
11838
- {
11839
- className: "se-waypointAction",
11840
- onClick: () => onPreviewWaypoint?.(waypoint),
11841
- title: "Fly camera to this waypoint",
11842
- disabled: !onPreviewWaypoint,
11843
- children: /* @__PURE__ */ jsx31(MapPin2, { size: 15 })
11844
- }
11845
- ),
11846
- /* @__PURE__ */ jsx31(
11847
- "button",
11848
- {
11849
- className: "se-waypointAction",
11850
- onClick: () => moveWaypoint(index, -1),
11851
- title: "Move up",
11852
- disabled: index === 0,
11853
- children: /* @__PURE__ */ jsx31(ArrowUp, { size: 15 })
11854
- }
11855
- ),
12075
+ /* @__PURE__ */ jsxs27("div", { className: "se-customizeToggle", children: [
12076
+ /* @__PURE__ */ jsxs27("label", { className: "se-toggleLabel", children: [
11856
12077
  /* @__PURE__ */ jsx31(
11857
- "button",
12078
+ "input",
11858
12079
  {
11859
- className: "se-waypointAction",
11860
- onClick: () => moveWaypoint(index, 1),
11861
- title: "Move down",
11862
- disabled: index === waypoints.length - 1,
11863
- children: /* @__PURE__ */ jsx31(ArrowDown, { size: 15 })
12080
+ type: "checkbox",
12081
+ checked: customizeDurations,
12082
+ onChange: (e) => setCustomizeDurations(e.target.checked)
11864
12083
  }
11865
12084
  ),
11866
- /* @__PURE__ */ jsx31(
11867
- "button",
11868
- {
11869
- className: "se-waypointAction se-waypointAction--delete",
11870
- onClick: () => deleteWaypoint(index),
11871
- title: "Delete waypoint",
11872
- children: /* @__PURE__ */ jsx31(Trash24, { size: 15 })
11873
- }
11874
- )
12085
+ /* @__PURE__ */ jsx31(Settings, { size: 14 }),
12086
+ /* @__PURE__ */ jsx31("span", { children: "Customize durations between waypoints" })
12087
+ ] }),
12088
+ customizeDurations && /* @__PURE__ */ jsxs27("span", { className: "se-toggleHint", children: [
12089
+ "Set duration for each segment between waypoints: 1\u21922, 2\u21923, etc. Default: ",
12090
+ DEFAULT_SEGMENT_DURATION,
12091
+ "s"
11875
12092
  ] })
11876
- ] }, index)) }),
12093
+ ] }),
12094
+ waypoints.length === 0 ? /* @__PURE__ */ jsx31("div", { className: "bb-emptyAnnotations", children: "No waypoints yet. Position the camera and add your first one." }) : /* @__PURE__ */ jsxs27(Fragment10, { children: [
12095
+ /* @__PURE__ */ jsx31("div", { className: "se-waypointList", children: waypoints.map((waypoint, index) => {
12096
+ const dur = getDuration(index);
12097
+ const isLast = index === waypoints.length - 1;
12098
+ const isCustom = waypoint.segmentDuration !== void 0 && waypoint.segmentDuration !== DEFAULT_SEGMENT_DURATION;
12099
+ return /* @__PURE__ */ jsxs27("div", { children: [
12100
+ /* @__PURE__ */ jsxs27("div", { className: "se-waypointCard", children: [
12101
+ /* @__PURE__ */ jsx31("div", { className: "se-waypointBadge", children: index + 1 }),
12102
+ /* @__PURE__ */ jsxs27("div", { className: "se-waypointBody", children: [
12103
+ /* @__PURE__ */ jsxs27("div", { className: "se-waypointCoords", children: [
12104
+ "pos [",
12105
+ waypoint.position.map((n) => n.toFixed(1)).join(", "),
12106
+ "]"
12107
+ ] }),
12108
+ /* @__PURE__ */ jsxs27("div", { className: "se-waypointCoords", children: [
12109
+ "look [",
12110
+ waypoint.target.map((n) => n.toFixed(1)).join(", "),
12111
+ "]"
12112
+ ] }),
12113
+ customizeDurations && !isLast && /* @__PURE__ */ jsxs27("div", { className: `se-customBadge ${isCustom ? "se-customBadge--custom" : "se-customBadge--default"}`, children: [
12114
+ dur.toFixed(1),
12115
+ "s",
12116
+ !isCustom && " (default)"
12117
+ ] })
12118
+ ] }),
12119
+ /* @__PURE__ */ jsxs27("div", { className: "se-waypointActions", children: [
12120
+ /* @__PURE__ */ jsx31(
12121
+ "button",
12122
+ {
12123
+ className: "se-waypointAction",
12124
+ onClick: () => onPreviewWaypoint?.(waypoint),
12125
+ title: "Fly camera to this waypoint",
12126
+ disabled: !onPreviewWaypoint,
12127
+ children: /* @__PURE__ */ jsx31(MapPin2, { size: 15 })
12128
+ }
12129
+ ),
12130
+ /* @__PURE__ */ jsx31(
12131
+ "button",
12132
+ {
12133
+ className: "se-waypointAction",
12134
+ onClick: () => moveWaypoint(index, -1),
12135
+ title: "Move up",
12136
+ disabled: index === 0,
12137
+ children: /* @__PURE__ */ jsx31(ArrowUp, { size: 15 })
12138
+ }
12139
+ ),
12140
+ /* @__PURE__ */ jsx31(
12141
+ "button",
12142
+ {
12143
+ className: "se-waypointAction",
12144
+ onClick: () => moveWaypoint(index, 1),
12145
+ title: "Move down",
12146
+ disabled: isLast,
12147
+ children: /* @__PURE__ */ jsx31(ArrowDown, { size: 15 })
12148
+ }
12149
+ ),
12150
+ /* @__PURE__ */ jsx31(
12151
+ "button",
12152
+ {
12153
+ className: "se-waypointAction se-waypointAction--delete",
12154
+ onClick: () => deleteWaypoint(index),
12155
+ title: "Delete waypoint",
12156
+ children: /* @__PURE__ */ jsx31(Trash24, { size: 15 })
12157
+ }
12158
+ )
12159
+ ] })
12160
+ ] }),
12161
+ customizeDurations && !isLast && /* @__PURE__ */ jsxs27("div", { className: "se-segmentBetween", children: [
12162
+ /* @__PURE__ */ jsxs27("div", { className: "se-segmentLabel", children: [
12163
+ index + 1,
12164
+ " \u2192 ",
12165
+ index + 2,
12166
+ !waypoint.segmentDuration && " (default)"
12167
+ ] }),
12168
+ /* @__PURE__ */ jsxs27("div", { className: "se-segmentControls", children: [
12169
+ /* @__PURE__ */ jsx31("span", { className: "se-durationLabel", children: "Duration:" }),
12170
+ /* @__PURE__ */ jsx31(
12171
+ "input",
12172
+ {
12173
+ type: "range",
12174
+ className: "se-segmentSlider",
12175
+ min: 1,
12176
+ max: 20,
12177
+ step: 0.5,
12178
+ value: dur,
12179
+ onChange: (e) => {
12180
+ const val = parseFloat(e.target.value);
12181
+ updateWaypoint(index, { segmentDuration: val });
12182
+ }
12183
+ }
12184
+ ),
12185
+ /* @__PURE__ */ jsxs27("span", { className: "se-segmentValue", children: [
12186
+ dur.toFixed(1),
12187
+ "s"
12188
+ ] }),
12189
+ /* @__PURE__ */ jsxs27("div", { className: "se-presetButtons", children: [
12190
+ [2, 4, 6, 8].map((preset) => /* @__PURE__ */ jsxs27(
12191
+ "button",
12192
+ {
12193
+ className: "se-presetButton",
12194
+ onClick: () => updateWaypoint(index, { segmentDuration: preset }),
12195
+ children: [
12196
+ preset,
12197
+ "s"
12198
+ ]
12199
+ },
12200
+ `preset-${index}-${preset}`
12201
+ )),
12202
+ /* @__PURE__ */ jsx31(
12203
+ "button",
12204
+ {
12205
+ className: "se-presetButton se-presetButton--default",
12206
+ onClick: () => {
12207
+ const newWaypoints = waypoints.map((wp, i) => {
12208
+ if (i === index) {
12209
+ const { segmentDuration, ...rest } = wp;
12210
+ return rest;
12211
+ }
12212
+ return wp;
12213
+ });
12214
+ const newDuration = calculateTotalDuration(newWaypoints, cinematic.loop);
12215
+ update({
12216
+ waypoints: newWaypoints,
12217
+ duration: newDuration
12218
+ });
12219
+ },
12220
+ children: "Reset"
12221
+ }
12222
+ )
12223
+ ] })
12224
+ ] })
12225
+ ] })
12226
+ ] }, `waypoint-wrapper-${index}`);
12227
+ }) }),
12228
+ /* @__PURE__ */ jsxs27("div", { className: "se-totalDuration", children: [
12229
+ /* @__PURE__ */ jsx31("span", { className: "se-totalLabel", children: "Total duration:" }),
12230
+ /* @__PURE__ */ jsx31("span", { className: "se-totalValue", children: hasEnoughWaypoints ? (cinematic.duration ?? calculateTotalDuration(waypoints, cinematic.loop)).toFixed(1) + "s" : "-" }),
12231
+ !hasEnoughWaypoints && /* @__PURE__ */ jsx31("span", { className: "se-totalHint", children: "(need at least 2 waypoints)" })
12232
+ ] })
12233
+ ] }),
11877
12234
  /* @__PURE__ */ jsx31(
11878
12235
  ToggleRow,
11879
12236
  {
@@ -11887,7 +12244,11 @@ function CinematicTab({
11887
12244
  {
11888
12245
  label: "Loop",
11889
12246
  checked: cinematic.loop,
11890
- onChange: (loop) => update({ loop })
12247
+ onChange: (loop) => {
12248
+ const newLoop = loop;
12249
+ const newDuration = calculateTotalDuration(waypoints, newLoop);
12250
+ update({ loop: newLoop, duration: newDuration });
12251
+ }
11891
12252
  }
11892
12253
  ),
11893
12254
  /* @__PURE__ */ jsx31(
@@ -11898,11 +12259,11 @@ function CinematicTab({
11898
12259
  onChange: (autoPlay) => update({ autoPlay })
11899
12260
  }
11900
12261
  ),
11901
- /* @__PURE__ */ jsx31(
12262
+ !customizeDurations && hasEnoughWaypoints && /* @__PURE__ */ jsx31(
11902
12263
  SliderRow,
11903
12264
  {
11904
12265
  label: "Duration",
11905
- value: cinematic.duration ?? Math.max(waypoints.length * 3.5, 8),
12266
+ value: cinematic.duration ?? calculateTotalDuration(waypoints, cinematic.loop),
11906
12267
  min: 4,
11907
12268
  max: 120,
11908
12269
  step: 1,
@@ -11914,7 +12275,7 @@ function CinematicTab({
11914
12275
  }
11915
12276
 
11916
12277
  // src/styles/SceneSettingsTab.css
11917
- styleInject('.se-tabContent {\n display: flex;\n flex-direction: column;\n}\n.se-tabContent * {\n box-sizing: border-box;\n}\n.se-subTabRow {\n display: flex;\n justify-content: space-between;\n gap: 6px;\n padding-bottom: 16px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.1);\n margin-bottom: 4px;\n}\n.se-subTab {\n padding: 8px 12px;\n display: flex;\n justify-items: center;\n border-radius: 10px;\n border: 1px solid rgba(148, 163, 184, 0.18);\n background: rgba(255, 255, 255, 0.04);\n color: #b3bfd2;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n cursor: pointer;\n transition:\n background-color 0.18s ease,\n border-color 0.18s ease,\n color 0.18s ease;\n}\n.se-subTab:hover {\n background: rgba(255, 255, 255, 0.08);\n border-color: rgba(148, 163, 184, 0.32);\n}\n.se-subTab--active {\n background: rgba(99, 102, 241, 0.18);\n border-color: rgba(99, 102, 241, 0.4);\n color: #f8fafc;\n}\n.se-generalContent {\n display: flex;\n flex-direction: column;\n padding-top: 0;\n}\n.se-placeholder {\n font-size: 13px;\n color: #93a4c0;\n}\n.se-section {\n margin-bottom: 10px;\n border-radius: 7px;\n background: rgba(255, 255, 255, 0.03);\n border: 1px solid rgba(148, 163, 184, 0.12);\n overflow: hidden;\n padding: 0;\n}\n.se-section:last-child {\n margin-bottom: 0;\n}\n.se-sectionTitle {\n margin: 0;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: #c3cbdb;\n padding: 11px 14px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.1);\n}\n.se-sectionBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 14px;\n}\n.se-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n}\n.se-fieldInline {\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n.se-fieldLabel {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n font-size: 12px;\n font-weight: 700;\n color: #d7dee9;\n}\n.se-fieldValue {\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n color: #93a4c0;\n}\n.se-slider {\n width: 100%;\n accent-color: #2aa8d8;\n cursor: pointer;\n}\n.se-checkbox {\n width: 18px;\n height: 18px;\n accent-color: #2aa8d8;\n cursor: pointer;\n}\n.se-select,\n.se-textInput,\n.se-numberInput {\n background: rgba(15, 23, 42, 0.28);\n border: 1px solid rgba(148, 163, 184, 0.2);\n border-radius: 7px;\n color: #f8fafc;\n font-size: 13px;\n padding: 11px 13px;\n outline: none;\n}\n.se-select:focus,\n.se-textInput:focus,\n.se-numberInput:focus {\n outline: none;\n border-color: rgba(42, 168, 216, 0.65);\n box-shadow: 0 0 0 4px rgba(42, 168, 216, 0.12);\n}\n.se-select {\n min-width: 130px;\n text-transform: capitalize;\n}\n.se-textInput {\n width: 100%;\n}\n.se-numberInput {\n width: 100%;\n}\n.se-swatch {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n min-width: 96px;\n height: 30px;\n padding: 0 8px;\n border-radius: 8px;\n border: 1px solid rgba(148, 163, 184, 0.35);\n cursor: pointer;\n box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);\n}\n.se-swatchCode {\n font-size: 11px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n color: #fff;\n mix-blend-mode: difference;\n}\n.se-colorPicker {\n margin-top: 6px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n width: fit-content;\n}\n.se-colorPicker .react-colorful {\n width: 100%;\n height: 140px;\n}\n.se-inlineColorPicker .react-colorful {\n width: 100%;\n height: 240px;\n}\n.se-colorPickerPopover {\n z-index: 1000;\n padding: 10px;\n border-radius: 8px;\n background: #1b2333;\n border: 1px solid rgba(148, 163, 184, 0.25);\n box-shadow: 0 12px 28px rgba(0, 0, 0, 0.4);\n}\n.se-colorPickerPopover .react-colorful {\n width: 200px;\n height: 160px;\n}\n.se-vectorRow {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 8px;\n}\n.se-vectorAxis {\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-size: 11px;\n color: #93a4c0;\n}\n.se-panel {\n margin-bottom: 10px;\n overflow: hidden;\n border: 1px solid rgba(148, 163, 184, 0.12);\n border-radius: 7px;\n background: rgba(255, 255, 255, 0.03);\n}\n.se-panel:last-child {\n margin-bottom: 0;\n}\n.se-panelHeader {\n display: flex;\n width: 100%;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n border: none;\n background: rgba(255, 255, 255, 0.03);\n padding: 11px 14px;\n color: #e5e7eb;\n cursor: pointer;\n text-align: left;\n}\n.se-panelHeader:hover {\n background: rgba(255, 255, 255, 0.06);\n}\n.se-panelHeaderMain {\n display: flex;\n min-width: 0;\n align-items: center;\n gap: 8px;\n}\n.se-panelChevron {\n flex-shrink: 0;\n color: #8b97ab;\n transform: rotate(-90deg);\n transition: transform 0.18s ease;\n}\n.se-panelChevron--open {\n transform: rotate(0deg);\n}\n.se-panelTitle {\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: #c3cbdb;\n}\n.se-panelHelp {\n display: inline-flex;\n flex-shrink: 0;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n margin-left: 2px;\n border-radius: 999px;\n color: #b9c0ca;\n cursor: help;\n transition:\n background 0.18s ease,\n color 0.18s ease,\n transform 0.18s ease;\n}\n.se-panelHelp:hover {\n background: rgba(255, 255, 255, 0.08);\n color: #eef2ff;\n transform: translateY(-0.5px);\n}\n.se-panelTrailing {\n display: flex;\n align-items: center;\n}\n.se-panelBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 14px;\n border-top: 1px solid rgba(148, 163, 184, 0.1);\n background: transparent;\n}\n.se-iconButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 30px;\n height: 30px;\n border: none;\n border-radius: 8px;\n background: rgba(255, 255, 255, 0.08);\n color: #d7dde8;\n cursor: pointer;\n}\n.se-iconButton:hover {\n background: rgba(255, 255, 255, 0.12);\n}\n.se-axisStepperGrid {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 12px;\n}\n.se-axisStepper {\n display: grid;\n grid-template-columns: 40px 1fr 40px;\n align-items: center;\n min-height: 30px;\n border-radius: 10px;\n background: rgba(17, 24, 39, 0.62);\n border: 1px solid rgba(255, 255, 255, 0.05);\n}\n.se-axisStepperButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n border: none;\n background: transparent;\n color: #f3f4f6;\n cursor: pointer;\n}\n.se-axisStepperButton:hover {\n background: rgba(255, 255, 255, 0.04);\n}\n.se-axisStepperLabel {\n text-align: center;\n font-size: 15px;\n color: #f3f4f6;\n}\n.se-segmentedControl {\n display: grid;\n grid-auto-flow: column;\n overflow: hidden;\n border-radius: 10px;\n background: rgba(17, 24, 39, 0.62);\n}\n.se-segmentedOption {\n min-height: 35px;\n border: none;\n background: transparent;\n color: #d1d5db;\n font-size: 14px;\n text-transform: uppercase;\n cursor: pointer;\n}\n.se-segmentedOption--active {\n background: #2aa8d8;\n color: #fff;\n}\n.se-helperCard {\n padding: 32px;\n text-align: center;\n background: rgba(15, 23, 42, 0.44);\n border-radius: 10px;\n border: 1px solid rgba(148, 163, 184, 0.15);\n color: #94a3b8;\n font-size: 14px;\n}\n.se-helperCard strong {\n font-size: 14px;\n}\n.se-helperCard span {\n font-size: 13px;\n line-height: 1.4;\n color: #c7d0df;\n}\n.se-switch {\n position: relative;\n display: inline-flex;\n align-items: center;\n}\n.se-switch input[type=checkbox] {\n position: absolute;\n left: 0;\n top: 0;\n z-index: 10;\n width: 100%;\n height: 100%;\n cursor: pointer;\n opacity: 0;\n}\n.se-switchTrack {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n.se-switchTrack::before {\n content: "";\n width: 36px;\n height: 20px;\n background: #4b5563;\n display: inline-block;\n border-radius: 20px;\n transition: background-color 0.2s ease-in;\n}\n.se-switchThumb {\n position: absolute;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n left: 2px;\n top: 2px;\n z-index: 2;\n background: #fff;\n box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);\n transition: 0.2s ease-in;\n}\n.se-switch input[type=checkbox]:hover + .se-switchTrack .se-switchThumb {\n box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.2), 0 3px 8px 0 rgba(0, 0, 0, 0.1);\n}\n.se-switch input[type=checkbox]:checked + .se-switchTrack::before {\n background: #376fcb;\n}\n.se-switch input[type=checkbox]:checked + .se-switchTrack .se-switchThumb {\n background: #4285f4;\n left: 18px;\n}\n.se-switchLabel {\n font-size: 12px;\n font-weight: 700;\n letter-spacing: 0.06em;\n color: #e5e7eb;\n}\n.se-switch input[type=checkbox]:checked ~ .se-switchTrack .se-switchLabel {\n color: #2aa8d8;\n}\n.se-lightList {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.se-lightRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px solid rgba(148, 163, 184, 0.1);\n background: rgba(255, 255, 255, 0.03);\n cursor: pointer;\n transition: background-color 0.15s ease, border-color 0.15s ease;\n}\n.se-lightRow:hover {\n background: rgba(255, 255, 255, 0.06);\n}\n.se-lightRow--active {\n background: rgba(42, 168, 216, 0.12);\n border-color: rgba(42, 168, 216, 0.35);\n}\n.se-lightRowLabel {\n font-size: 13px;\n color: #d8e0ee;\n}\n.se-lightActions {\n display: flex;\n gap: 8px;\n}\n.se-lightProps {\n display: flex;\n flex-direction: column;\n gap: 14px;\n padding-top: 4px;\n border-top: 1px solid rgba(148, 163, 184, 0.1);\n}\n.se-animationList {\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\n.se-animationCard {\n display: flex;\n flex-direction: column;\n gap: 14px;\n padding: 16px;\n border-radius: 14px;\n border: 1px solid rgba(148, 163, 184, 0.16);\n background: rgba(15, 23, 42, 0.48);\n}\n.se-animationCard--active {\n border-color: rgba(42, 168, 216, 0.45);\n box-shadow: inset 0 0 0 1px rgba(42, 168, 216, 0.16);\n}\n.se-animationHeader {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 12px;\n}\n.se-animationHeading {\n display: flex;\n min-width: 0;\n flex-direction: column;\n gap: 6px;\n}\n.se-animationTitleRow {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n}\n.se-animationStatus,\n.se-animationBadge {\n display: inline-flex;\n align-items: center;\n min-height: 22px;\n padding: 0 8px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n}\n.se-animationStatus {\n background: rgba(42, 168, 216, 0.16);\n color: #9fe8ff;\n}\n.se-animationBadge {\n background: rgba(99, 102, 241, 0.18);\n color: #d9ddff;\n}\n.se-animationSource {\n font-size: 12px;\n color: #93a4c0;\n word-break: break-word;\n}\n.se-animationMeta {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n.se-animationMeta span {\n border-radius: 999px;\n background: rgba(255, 255, 255, 0.06);\n padding: 4px 9px;\n font-size: 12px;\n color: #c6d0e2;\n}\n.se-animationDangerButton,\n.se-animationButton,\n.se-animationAutoplayButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n border: 1px solid rgba(148, 163, 184, 0.2);\n border-radius: 10px;\n background: rgba(255, 255, 255, 0.04);\n color: #e2e8f0;\n cursor: pointer;\n transition:\n background-color 0.18s ease,\n border-color 0.18s ease,\n color 0.18s ease;\n}\n.se-animationButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.se-animationDangerButton {\n width: 36px;\n height: 36px;\n color: #fca5a5;\n}\n.se-animationDangerButton:hover,\n.se-animationButton:hover,\n.se-animationAutoplayButton:hover {\n background: rgba(255, 255, 255, 0.08);\n border-color: rgba(148, 163, 184, 0.34);\n}\n.se-animationButtonRow {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n}\n.se-animationButton {\n min-height: 36px;\n padding: 0 14px;\n font-size: 13px;\n font-weight: 600;\n}\n.se-animationButton--icon {\n flex: 1;\n min-width: 0;\n padding: 0;\n height: 38px;\n}\n.se-animationSeekRow {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.se-animationSeek {\n flex: 1;\n min-width: 0;\n}\n.se-animationSeekTime {\n flex-shrink: 0;\n min-width: 86px;\n text-align: right;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n color: #97a3b6;\n}\n.se-animationGrid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 12px;\n}\n.se-animationSpeedRow {\n display: grid;\n grid-template-columns: minmax(0, 1fr) 88px;\n gap: 10px;\n align-items: center;\n}\n.se-animationSpeedInput {\n text-align: right;\n}\n.se-animationAutoplayButton {\n align-self: flex-start;\n min-height: 36px;\n padding: 0 14px;\n font-size: 12px;\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n}\n.se-animationAutoplayButton--active {\n background: rgba(42, 168, 216, 0.18);\n border-color: rgba(42, 168, 216, 0.42);\n color: #dff8ff;\n}\n.se-removedAnimations {\n overflow: hidden;\n border-radius: 12px;\n border: 1px solid rgba(148, 163, 184, 0.14);\n background: rgba(255, 255, 255, 0.03);\n}\n.se-removedAnimationList {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 14px;\n}\n.se-removedAnimationRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n border-radius: 10px;\n background: rgba(15, 23, 42, 0.42);\n padding: 12px;\n}\n.se-removedAnimationName {\n font-size: 13px;\n font-weight: 600;\n color: #e2e8f0;\n}\n@media (max-width: 900px) {\n .se-animationGrid {\n grid-template-columns: 1fr;\n }\n .se-animationSpeedRow {\n grid-template-columns: 1fr;\n }\n}\n.bb-viewSpotOverlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.6);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n backdrop-filter: blur(4px);\n animation: bb-fadeIn 0.2s ease;\n}\n.bb-viewSpotPopup {\n background: #1e1e20;\n border-radius: 12px;\n max-width: 480px;\n width: 90%;\n max-height: 80vh;\n overflow-y: auto;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);\n border: 1px solid #333335;\n animation: bb-slideUp 0.25s ease;\n}\n.bb-viewSpotHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px;\n border-bottom: 1px solid #2a2a2c;\n}\n.bb-viewSpotTitle {\n font-size: 18px;\n font-weight: 600;\n color: #e8e8ea;\n margin: 0;\n}\n.bb-viewSpotClose {\n background: none;\n border: none;\n color: #888;\n font-size: 24px;\n cursor: pointer;\n padding: 0 4px;\n line-height: 1;\n transition: color 0.2s;\n}\n.bb-viewSpotClose:hover {\n color: #e8e8ea;\n}\n.bb-viewSpotBody {\n padding: 24px;\n}\n.bb-viewSpotDescription {\n color: #c8c8ca;\n font-size: 14px;\n line-height: 1.6;\n margin-bottom: 20px;\n white-space: pre-wrap;\n word-break: break-word;\n}\n.bb-viewSpotPosition {\n background: #141416;\n border-radius: 8px;\n padding: 12px 16px;\n margin-bottom: 24px;\n font-size: 13px;\n color: #a0a0a2;\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n.bb-viewSpotLabel {\n font-weight: 500;\n color: #888;\n}\n.bb-viewSpotCode {\n background: #0a0a0c;\n padding: 4px 10px;\n border-radius: 4px;\n font-family:\n "Monaco",\n "Menlo",\n monospace;\n font-size: 12px;\n color: #8be9fd;\n}\n.bb-viewSpotActions {\n display: flex;\n justify-content: flex-end;\n gap: 10px;\n padding-top: 4px;\n}\n.bb-editAnnotationOverlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.6);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n}\n.bb-editAnnotationPopup {\n background: #0f141e;\n border-radius: 16px;\n padding: 24px;\n min-width: 200px;\n max-width: 400px;\n width: 90%;\n border: 1px solid rgba(148, 163, 184, 0.18);\n}\n.bb-editAnnotationHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 16px;\n padding-bottom: 12px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.12);\n}\n.bb-editAnnotationTitle {\n color: #f8fafc;\n font-size: 18px;\n font-weight: 600;\n margin: 0;\n}\n.bb-editAnnotationClose {\n background: none;\n border: none;\n color: #94a3b8;\n font-size: 24px;\n cursor: pointer;\n padding: 0 4px;\n}\n.bb-editAnnotationClose:hover {\n color: #f8fafc;\n}\n.bb-editAnnotationBody {\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\n.bb-editAnnotationActions {\n display: flex;\n gap: 8px;\n margin-top: 8px;\n}\n.se-cinematicHint {\n margin: 0 0 10px;\n font-size: 12px;\n line-height: 1.5;\n color: rgba(255, 255, 255, 0.55);\n}\n.bb-button.se-cinematicAdd {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n width: 100%;\n margin-bottom: 12px;\n background: #135bc0;\n color: white;\n}\n.bb-button.se-cinematicAdd:hover:not(:disabled) {\n background: #1a68d6;\n}\n.se-waypointList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-bottom: 14px;\n}\n.se-waypointCard {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 10px;\n border: 1px solid rgba(255, 255, 255, 0.1);\n border-radius: 8px;\n background: rgba(255, 255, 255, 0.03);\n}\n.se-waypointBadge {\n flex: none;\n width: 22px;\n height: 22px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n font-weight: 600;\n color: #06343c;\n background: #22d3ee;\n}\n.se-waypointBody {\n flex: 1;\n min-width: 0;\n}\n.se-waypointCoords {\n font-size: 11px;\n line-height: 1.4;\n color: rgba(255, 255, 255, 0.6);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.se-waypointActions {\n flex: none;\n display: flex;\n gap: 2px;\n}\n.se-waypointAction {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n padding: 0;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: rgba(255, 255, 255, 0.6);\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n}\n.se-waypointAction:hover:not(:disabled) {\n background: rgba(255, 255, 255, 0.1);\n color: #fff;\n}\n.se-waypointAction:disabled {\n opacity: 0.35;\n cursor: default;\n}\n.se-waypointAction--delete:hover:not(:disabled) {\n background: rgba(248, 113, 113, 0.16);\n color: #f87171;\n}\n.bb-cameraWaypoint {\n align-items: flex-start;\n margin-bottom: 8px;\n}\n.bb-cameraWaypoint .bb-cameraLiveBadge,\n.bb-cameraWaypoint .bb-cameraSavedBadge {\n flex: none;\n}\n.bb-cameraWaypointLabel {\n font-size: 11px;\n font-weight: 600;\n color: rgba(255, 255, 255, 0.8);\n margin-bottom: 2px;\n}\n.bb-cameraControls--centered {\n justify-content: center;\n margin: 12px 0;\n}\n');
12278
+ styleInject('.se-tabContent {\n display: flex;\n flex-direction: column;\n}\n.se-tabContent * {\n box-sizing: border-box;\n}\n.se-subTabRow {\n display: flex;\n justify-content: space-between;\n gap: 6px;\n padding-bottom: 16px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.1);\n margin-bottom: 4px;\n}\n.se-subTab {\n padding: 8px 12px;\n display: flex;\n justify-items: center;\n border-radius: 10px;\n border: 1px solid rgba(148, 163, 184, 0.18);\n background: rgba(255, 255, 255, 0.04);\n color: #b3bfd2;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n cursor: pointer;\n transition:\n background-color 0.18s ease,\n border-color 0.18s ease,\n color 0.18s ease;\n}\n.se-subTab:hover {\n background: rgba(255, 255, 255, 0.08);\n border-color: rgba(148, 163, 184, 0.32);\n}\n.se-subTab--active {\n background: rgba(99, 102, 241, 0.18);\n border-color: rgba(99, 102, 241, 0.4);\n color: #f8fafc;\n}\n.se-generalContent {\n display: flex;\n flex-direction: column;\n padding-top: 0;\n}\n.se-placeholder {\n font-size: 13px;\n color: #93a4c0;\n}\n.se-section {\n margin-bottom: 10px;\n border-radius: 7px;\n background: rgba(255, 255, 255, 0.03);\n border: 1px solid rgba(148, 163, 184, 0.12);\n overflow: hidden;\n padding: 0;\n}\n.se-section:last-child {\n margin-bottom: 0;\n}\n.se-sectionTitle {\n margin: 0;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: #c3cbdb;\n padding: 11px 14px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.1);\n}\n.se-sectionBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 14px;\n}\n.se-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n}\n.se-fieldInline {\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n.se-fieldLabel {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n font-size: 12px;\n font-weight: 700;\n color: #d7dee9;\n}\n.se-fieldValue {\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n color: #93a4c0;\n}\n.se-slider {\n width: 100%;\n accent-color: #2aa8d8;\n cursor: pointer;\n}\n.se-checkbox {\n width: 18px;\n height: 18px;\n accent-color: #2aa8d8;\n cursor: pointer;\n}\n.se-select,\n.se-textInput,\n.se-numberInput {\n background: rgba(15, 23, 42, 0.28);\n border: 1px solid rgba(148, 163, 184, 0.2);\n border-radius: 7px;\n color: #f8fafc;\n font-size: 13px;\n padding: 11px 13px;\n outline: none;\n}\n.se-select:focus,\n.se-textInput:focus,\n.se-numberInput:focus {\n outline: none;\n border-color: rgba(42, 168, 216, 0.65);\n box-shadow: 0 0 0 4px rgba(42, 168, 216, 0.12);\n}\n.se-select {\n min-width: 130px;\n text-transform: capitalize;\n}\n.se-textInput {\n width: 100%;\n}\n.se-numberInput {\n width: 100%;\n}\n.se-swatch {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n min-width: 96px;\n height: 30px;\n padding: 0 8px;\n border-radius: 8px;\n border: 1px solid rgba(148, 163, 184, 0.35);\n cursor: pointer;\n box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);\n}\n.se-swatchCode {\n font-size: 11px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n color: #fff;\n mix-blend-mode: difference;\n}\n.se-colorPicker {\n margin-top: 6px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n width: fit-content;\n}\n.se-colorPicker .react-colorful {\n width: 100%;\n height: 140px;\n}\n.se-inlineColorPicker .react-colorful {\n width: 100%;\n height: 240px;\n}\n.se-colorPickerPopover {\n z-index: 1000;\n padding: 10px;\n border-radius: 8px;\n background: #1b2333;\n border: 1px solid rgba(148, 163, 184, 0.25);\n box-shadow: 0 12px 28px rgba(0, 0, 0, 0.4);\n}\n.se-colorPickerPopover .react-colorful {\n width: 200px;\n height: 160px;\n}\n.se-vectorRow {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 8px;\n}\n.se-vectorAxis {\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-size: 11px;\n color: #93a4c0;\n}\n.se-panel {\n margin-bottom: 10px;\n overflow: hidden;\n border: 1px solid rgba(148, 163, 184, 0.12);\n border-radius: 7px;\n background: rgba(255, 255, 255, 0.03);\n}\n.se-panel:last-child {\n margin-bottom: 0;\n}\n.se-panelHeader {\n display: flex;\n width: 100%;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n border: none;\n background: rgba(255, 255, 255, 0.03);\n padding: 11px 14px;\n color: #e5e7eb;\n cursor: pointer;\n text-align: left;\n}\n.se-panelHeader:hover {\n background: rgba(255, 255, 255, 0.06);\n}\n.se-panelHeaderMain {\n display: flex;\n min-width: 0;\n align-items: center;\n gap: 8px;\n}\n.se-panelChevron {\n flex-shrink: 0;\n color: #8b97ab;\n transform: rotate(-90deg);\n transition: transform 0.18s ease;\n}\n.se-panelChevron--open {\n transform: rotate(0deg);\n}\n.se-panelTitle {\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: #c3cbdb;\n}\n.se-panelHelp {\n display: inline-flex;\n flex-shrink: 0;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n margin-left: 2px;\n border-radius: 999px;\n color: #b9c0ca;\n cursor: help;\n transition:\n background 0.18s ease,\n color 0.18s ease,\n transform 0.18s ease;\n}\n.se-panelHelp:hover {\n background: rgba(255, 255, 255, 0.08);\n color: #eef2ff;\n transform: translateY(-0.5px);\n}\n.se-panelTrailing {\n display: flex;\n align-items: center;\n}\n.se-panelBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 14px;\n border-top: 1px solid rgba(148, 163, 184, 0.1);\n background: transparent;\n}\n.se-iconButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 30px;\n height: 30px;\n border: none;\n border-radius: 8px;\n background: rgba(255, 255, 255, 0.08);\n color: #d7dde8;\n cursor: pointer;\n}\n.se-iconButton:hover {\n background: rgba(255, 255, 255, 0.12);\n}\n.se-axisStepperGrid {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 12px;\n}\n.se-axisStepper {\n display: grid;\n grid-template-columns: 40px 1fr 40px;\n align-items: center;\n min-height: 30px;\n border-radius: 10px;\n background: rgba(17, 24, 39, 0.62);\n border: 1px solid rgba(255, 255, 255, 0.05);\n}\n.se-axisStepperButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n border: none;\n background: transparent;\n color: #f3f4f6;\n cursor: pointer;\n}\n.se-axisStepperButton:hover {\n background: rgba(255, 255, 255, 0.04);\n}\n.se-axisStepperLabel {\n text-align: center;\n font-size: 15px;\n color: #f3f4f6;\n}\n.se-segmentedControl {\n display: grid;\n grid-auto-flow: column;\n overflow: hidden;\n border-radius: 10px;\n background: rgba(17, 24, 39, 0.62);\n}\n.se-segmentedOption {\n min-height: 35px;\n border: none;\n background: transparent;\n color: #d1d5db;\n font-size: 14px;\n text-transform: uppercase;\n cursor: pointer;\n}\n.se-segmentedOption--active {\n background: #2aa8d8;\n color: #fff;\n}\n.se-helperCard {\n padding: 32px;\n text-align: center;\n background: rgba(15, 23, 42, 0.44);\n border-radius: 10px;\n border: 1px solid rgba(148, 163, 184, 0.15);\n color: #94a3b8;\n font-size: 14px;\n}\n.se-helperCard strong {\n font-size: 14px;\n}\n.se-helperCard span {\n font-size: 13px;\n line-height: 1.4;\n color: #c7d0df;\n}\n.se-switch {\n position: relative;\n display: inline-flex;\n align-items: center;\n}\n.se-switch input[type=checkbox] {\n position: absolute;\n left: 0;\n top: 0;\n z-index: 10;\n width: 100%;\n height: 100%;\n cursor: pointer;\n opacity: 0;\n}\n.se-switchTrack {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n.se-switchTrack::before {\n content: "";\n width: 36px;\n height: 20px;\n background: #4b5563;\n display: inline-block;\n border-radius: 20px;\n transition: background-color 0.2s ease-in;\n}\n.se-switchThumb {\n position: absolute;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n left: 2px;\n top: 2px;\n z-index: 2;\n background: #fff;\n box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);\n transition: 0.2s ease-in;\n}\n.se-switch input[type=checkbox]:hover + .se-switchTrack .se-switchThumb {\n box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.2), 0 3px 8px 0 rgba(0, 0, 0, 0.1);\n}\n.se-switch input[type=checkbox]:checked + .se-switchTrack::before {\n background: #376fcb;\n}\n.se-switch input[type=checkbox]:checked + .se-switchTrack .se-switchThumb {\n background: #4285f4;\n left: 18px;\n}\n.se-switchLabel {\n font-size: 12px;\n font-weight: 700;\n letter-spacing: 0.06em;\n color: #e5e7eb;\n}\n.se-switch input[type=checkbox]:checked ~ .se-switchTrack .se-switchLabel {\n color: #2aa8d8;\n}\n.se-lightList {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.se-lightRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px solid rgba(148, 163, 184, 0.1);\n background: rgba(255, 255, 255, 0.03);\n cursor: pointer;\n transition: background-color 0.15s ease, border-color 0.15s ease;\n}\n.se-lightRow:hover {\n background: rgba(255, 255, 255, 0.06);\n}\n.se-lightRow--active {\n background: rgba(42, 168, 216, 0.12);\n border-color: rgba(42, 168, 216, 0.35);\n}\n.se-lightRowLabel {\n font-size: 13px;\n color: #d8e0ee;\n}\n.se-lightActions {\n display: flex;\n gap: 8px;\n}\n.se-lightProps {\n display: flex;\n flex-direction: column;\n gap: 14px;\n padding-top: 4px;\n border-top: 1px solid rgba(148, 163, 184, 0.1);\n}\n.se-animationList {\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\n.se-animationCard {\n display: flex;\n flex-direction: column;\n gap: 14px;\n padding: 16px;\n border-radius: 14px;\n border: 1px solid rgba(148, 163, 184, 0.16);\n background: rgba(15, 23, 42, 0.48);\n}\n.se-animationCard--active {\n border-color: rgba(42, 168, 216, 0.45);\n box-shadow: inset 0 0 0 1px rgba(42, 168, 216, 0.16);\n}\n.se-animationHeader {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 12px;\n}\n.se-animationHeading {\n display: flex;\n min-width: 0;\n flex-direction: column;\n gap: 6px;\n}\n.se-animationTitleRow {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n}\n.se-animationStatus,\n.se-animationBadge {\n display: inline-flex;\n align-items: center;\n min-height: 22px;\n padding: 0 8px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n}\n.se-animationStatus {\n background: rgba(42, 168, 216, 0.16);\n color: #9fe8ff;\n}\n.se-animationBadge {\n background: rgba(99, 102, 241, 0.18);\n color: #d9ddff;\n}\n.se-animationSource {\n font-size: 12px;\n color: #93a4c0;\n word-break: break-word;\n}\n.se-animationMeta {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n.se-animationMeta span {\n border-radius: 999px;\n background: rgba(255, 255, 255, 0.06);\n padding: 4px 9px;\n font-size: 12px;\n color: #c6d0e2;\n}\n.se-animationDangerButton,\n.se-animationButton,\n.se-animationAutoplayButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n border: 1px solid rgba(148, 163, 184, 0.2);\n border-radius: 10px;\n background: rgba(255, 255, 255, 0.04);\n color: #e2e8f0;\n cursor: pointer;\n transition:\n background-color 0.18s ease,\n border-color 0.18s ease,\n color 0.18s ease;\n}\n.se-animationButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.se-animationDangerButton {\n width: 36px;\n height: 36px;\n color: #fca5a5;\n}\n.se-animationDangerButton:hover,\n.se-animationButton:hover,\n.se-animationAutoplayButton:hover {\n background: rgba(255, 255, 255, 0.08);\n border-color: rgba(148, 163, 184, 0.34);\n}\n.se-animationButtonRow {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n}\n.se-animationButton {\n min-height: 36px;\n padding: 0 14px;\n font-size: 13px;\n font-weight: 600;\n}\n.se-animationButton--icon {\n flex: 1;\n min-width: 0;\n padding: 0;\n height: 38px;\n}\n.se-animationSeekRow {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.se-animationSeek {\n flex: 1;\n min-width: 0;\n}\n.se-animationSeekTime {\n flex-shrink: 0;\n min-width: 86px;\n text-align: right;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n color: #97a3b6;\n}\n.se-animationGrid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 12px;\n}\n.se-animationSpeedRow {\n display: grid;\n grid-template-columns: minmax(0, 1fr) 88px;\n gap: 10px;\n align-items: center;\n}\n.se-animationSpeedInput {\n text-align: right;\n}\n.se-animationAutoplayButton {\n align-self: flex-start;\n min-height: 36px;\n padding: 0 14px;\n font-size: 12px;\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n}\n.se-animationAutoplayButton--active {\n background: rgba(42, 168, 216, 0.18);\n border-color: rgba(42, 168, 216, 0.42);\n color: #dff8ff;\n}\n.se-removedAnimations {\n overflow: hidden;\n border-radius: 12px;\n border: 1px solid rgba(148, 163, 184, 0.14);\n background: rgba(255, 255, 255, 0.03);\n}\n.se-removedAnimationList {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 14px;\n}\n.se-removedAnimationRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n border-radius: 10px;\n background: rgba(15, 23, 42, 0.42);\n padding: 12px;\n}\n.se-removedAnimationName {\n font-size: 13px;\n font-weight: 600;\n color: #e2e8f0;\n}\n@media (max-width: 900px) {\n .se-animationGrid {\n grid-template-columns: 1fr;\n }\n .se-animationSpeedRow {\n grid-template-columns: 1fr;\n }\n}\n.bb-viewSpotOverlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.6);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n backdrop-filter: blur(4px);\n animation: bb-fadeIn 0.2s ease;\n}\n.bb-viewSpotPopup {\n background: #1e1e20;\n border-radius: 12px;\n max-width: 480px;\n width: 90%;\n max-height: 80vh;\n overflow-y: auto;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);\n border: 1px solid #333335;\n animation: bb-slideUp 0.25s ease;\n}\n.bb-viewSpotHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px;\n border-bottom: 1px solid #2a2a2c;\n}\n.bb-viewSpotTitle {\n font-size: 18px;\n font-weight: 600;\n color: #e8e8ea;\n margin: 0;\n}\n.bb-viewSpotClose {\n background: none;\n border: none;\n color: #888;\n font-size: 24px;\n cursor: pointer;\n padding: 0 4px;\n line-height: 1;\n transition: color 0.2s;\n}\n.bb-viewSpotClose:hover {\n color: #e8e8ea;\n}\n.bb-viewSpotBody {\n padding: 24px;\n}\n.bb-viewSpotDescription {\n color: #c8c8ca;\n font-size: 14px;\n line-height: 1.6;\n margin-bottom: 20px;\n white-space: pre-wrap;\n word-break: break-word;\n}\n.bb-viewSpotPosition {\n background: #141416;\n border-radius: 8px;\n padding: 12px 16px;\n margin-bottom: 24px;\n font-size: 13px;\n color: #a0a0a2;\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n.bb-viewSpotLabel {\n font-weight: 500;\n color: #888;\n}\n.bb-viewSpotCode {\n background: #0a0a0c;\n padding: 4px 10px;\n border-radius: 4px;\n font-family:\n "Monaco",\n "Menlo",\n monospace;\n font-size: 12px;\n color: #8be9fd;\n}\n.bb-viewSpotActions {\n display: flex;\n justify-content: flex-end;\n gap: 10px;\n padding-top: 4px;\n}\n.bb-editAnnotationOverlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.6);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n}\n.bb-editAnnotationPopup {\n background: #0f141e;\n border-radius: 16px;\n padding: 24px;\n min-width: 200px;\n max-width: 400px;\n width: 90%;\n border: 1px solid rgba(148, 163, 184, 0.18);\n}\n.bb-editAnnotationHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 16px;\n padding-bottom: 12px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.12);\n}\n.bb-editAnnotationTitle {\n color: #f8fafc;\n font-size: 18px;\n font-weight: 600;\n margin: 0;\n}\n.bb-editAnnotationClose {\n background: none;\n border: none;\n color: #94a3b8;\n font-size: 24px;\n cursor: pointer;\n padding: 0 4px;\n}\n.bb-editAnnotationClose:hover {\n color: #f8fafc;\n}\n.bb-editAnnotationBody {\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\n.bb-editAnnotationActions {\n display: flex;\n gap: 8px;\n margin-top: 8px;\n}\n.se-cinematicHint {\n margin: 0 0 10px;\n font-size: 12px;\n line-height: 1.5;\n color: rgba(255, 255, 255, 0.55);\n}\n.bb-button.se-cinematicAdd {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n width: 100%;\n margin-bottom: 12px;\n background: #135bc0;\n color: white;\n}\n.bb-button.se-cinematicAdd:hover:not(:disabled) {\n background: #1a68d6;\n}\n.se-waypointList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-bottom: 14px;\n}\n.se-waypointCard {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 12px;\n border: 1px solid rgba(255, 255, 255, 0.08);\n border-radius: 8px;\n background: rgba(255, 255, 255, 0.03);\n}\n.se-waypointBadge {\n flex: none;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n font-weight: 600;\n color: #06343c;\n background: #22d3ee;\n}\n.se-waypointBody {\n flex: 1;\n min-width: 0;\n}\n.se-waypointCoords {\n font-size: 11px;\n line-height: 1.4;\n color: rgba(255, 255, 255, 0.5);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.se-waypointActions {\n flex: none;\n display: flex;\n gap: 2px;\n}\n.se-waypointAction {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n padding: 0;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: rgba(255, 255, 255, 0.5);\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n}\n.se-waypointAction:hover:not(:disabled) {\n background: rgba(255, 255, 255, 0.08);\n color: #fff;\n}\n.se-waypointAction:disabled {\n opacity: 0.35;\n cursor: default;\n}\n.se-waypointAction--delete:hover:not(:disabled) {\n background: rgba(248, 113, 113, 0.16);\n color: #f87171;\n}\n.se-customBadge {\n display: inline-block;\n padding: 1px 8px;\n border-radius: 3px;\n background: rgba(34, 211, 238, 0.15);\n color: #22d3ee;\n font-size: 10px;\n font-weight: 600;\n margin-top: 4px;\n}\n.se-customizeToggle {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 8px 12px;\n background: rgba(255, 255, 255, 0.03);\n border-radius: 8px;\n border: 1px solid rgba(148, 163, 184, 0.08);\n margin: 8px 0 12px;\n}\n.se-toggleLabel {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n font-weight: 500;\n color: #f8fafc;\n cursor: pointer;\n user-select: none;\n}\n.se-toggleLabel input[type=checkbox] {\n width: 16px;\n height: 16px;\n accent-color: #22d3ee;\n cursor: pointer;\n}\n.se-toggleLabel svg {\n color: #22d3ee;\n width: 14px;\n height: 14px;\n}\n.se-toggleHint {\n font-size: 11px;\n color: rgba(255, 255, 255, 0.4);\n padding-left: 28px;\n}\n.se-segmentBetween {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px 14px;\n margin: 6px 0 6px 32px;\n background: rgba(255, 255, 255, 0.03);\n border-radius: 6px;\n border-left: 3px solid rgba(34, 211, 238, 0.25);\n}\n.se-segmentLabel {\n font-size: 11px;\n font-weight: 500;\n color: rgba(255, 255, 255, 0.45);\n}\n.se-segmentControls {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n.se-durationLabel {\n font-size: 10px;\n color: rgba(255, 255, 255, 0.35);\n font-weight: 500;\n white-space: nowrap;\n}\n.se-segmentSlider {\n flex: 1;\n min-width: 80px;\n max-width: 150px;\n height: 4px;\n -webkit-appearance: none;\n appearance: none;\n background: rgba(255, 255, 255, 0.1);\n border-radius: 2px;\n outline: none;\n}\n.se-segmentSlider::-webkit-slider-thumb {\n -webkit-appearance: none;\n appearance: none;\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: #22d3ee;\n cursor: pointer;\n border: 2px solid rgba(255, 255, 255, 0.2);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n}\n.se-segmentSlider::-moz-range-thumb {\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: #22d3ee;\n cursor: pointer;\n border: 2px solid rgba(255, 255, 255, 0.2);\n}\n.se-segmentValue {\n font-size: 12px;\n font-weight: 600;\n color: #22d3ee;\n min-width: 36px;\n text-align: center;\n}\n.se-presetButtons {\n display: flex;\n gap: 4px;\n}\n.se-presetButton {\n padding: 2px 8px;\n background: rgba(255, 255, 255, 0.06);\n border: 1px solid rgba(148, 163, 184, 0.15);\n border-radius: 4px;\n font-size: 10px;\n color: rgba(255, 255, 255, 0.6);\n cursor: pointer;\n transition: all 0.2s ease;\n font-weight: 500;\n}\n.se-presetButton:hover {\n background: #22d3ee;\n color: #06343c;\n border-color: #22d3ee;\n}\n.se-totalDuration {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 8px 4px;\n margin: 8px 0 12px;\n border-top: 1px solid rgba(148, 163, 184, 0.1);\n padding-top: 12px;\n}\n.se-totalLabel {\n font-size: 12px;\n color: rgba(255, 255, 255, 0.5);\n font-weight: 500;\n}\n.se-totalValue {\n font-size: 14px;\n font-weight: 600;\n color: #22d3ee;\n}\n.se-totalHint {\n font-size: 11px;\n color: rgba(255, 255, 255, 0.35);\n font-weight: 400;\n margin-left: 8px;\n}\n');
11918
12279
 
11919
12280
  // src/components/BindingBuilder/SceneSettings/SceneSettingsTab.tsx
11920
12281
  import {
@@ -11960,7 +12321,7 @@ function SceneSettingsTab({
11960
12321
  animationClipDetails = [],
11961
12322
  animationPlayback = FALLBACK_ANIMATION_PLAYBACK
11962
12323
  }) {
11963
- const [internalActiveSubTab, setInternalActiveSubTab] = useState19("General");
12324
+ const [internalActiveSubTab, setInternalActiveSubTab] = useState20("General");
11964
12325
  const activeSubTab = controlledActiveSubTab ?? internalActiveSubTab;
11965
12326
  const resolvedAnimationPlayback = animationPlayback ?? FALLBACK_ANIMATION_PLAYBACK;
11966
12327
  const { tier } = useBindingBuilderContext();
@@ -12146,7 +12507,7 @@ function EnableToggle2({
12146
12507
  }
12147
12508
 
12148
12509
  // src/components/BindingBuilder/components/MaterialSection.tsx
12149
- import { useState as useState21 } from "react";
12510
+ import { useState as useState22 } from "react";
12150
12511
 
12151
12512
  // src/components/ModelViewer/types/objectBinding.ts
12152
12513
  var MATERIAL_SIDES = ["front", "back", "double"];
@@ -12193,7 +12554,7 @@ function MaterialPanelGroup({
12193
12554
  }
12194
12555
 
12195
12556
  // src/components/BindingBuilder/components/MaterialFields.tsx
12196
- import { useRef as useRef15, useState as useState20 } from "react";
12557
+ import { useRef as useRef15, useState as useState21 } from "react";
12197
12558
  import { HexColorInput as HexColorInput3, HexColorPicker as HexColorPicker4 } from "react-colorful";
12198
12559
  import { FileUp } from "lucide-react";
12199
12560
  import { jsx as jsx35, jsxs as jsxs30 } from "react/jsx-runtime";
@@ -12260,7 +12621,7 @@ function MaterialColorField({
12260
12621
  fallback = "#ffffff",
12261
12622
  onChange
12262
12623
  }) {
12263
- const [open, setOpen] = useState20(false);
12624
+ const [open, setOpen] = useState21(false);
12264
12625
  const color = value || fallback;
12265
12626
  const anchorRef = useRef15(null);
12266
12627
  return /* @__PURE__ */ jsxs30("div", { className: "bb-field", children: [
@@ -12389,7 +12750,7 @@ function MaterialSliderField({
12389
12750
  }
12390
12751
 
12391
12752
  // src/components/BindingBuilder/components/MaterialSection.tsx
12392
- import { Fragment as Fragment10, jsx as jsx36, jsxs as jsxs31 } from "react/jsx-runtime";
12753
+ import { Fragment as Fragment11, jsx as jsx36, jsxs as jsxs31 } from "react/jsx-runtime";
12393
12754
  function MaterialSection({
12394
12755
  selectedItem,
12395
12756
  selectedMaterial,
@@ -12414,11 +12775,11 @@ function MaterialSection({
12414
12775
  tier: "starter",
12415
12776
  locked: !canUseStarterFeatures
12416
12777
  };
12417
- const [roughnessMode, setRoughnessMode] = useState21("roughness");
12418
- const [anisotropyMode, setAnisotropyMode] = useState21(
12778
+ const [roughnessMode, setRoughnessMode] = useState22("roughness");
12779
+ const [anisotropyMode, setAnisotropyMode] = useState22(
12419
12780
  "factor"
12420
12781
  );
12421
- return /* @__PURE__ */ jsxs31(Fragment10, { children: [
12782
+ return /* @__PURE__ */ jsxs31(Fragment11, { children: [
12422
12783
  /* @__PURE__ */ jsx36(
12423
12784
  MaterialPanelGroup,
12424
12785
  {
@@ -12457,7 +12818,7 @@ function MaterialSection({
12457
12818
  }))
12458
12819
  }
12459
12820
  ),
12460
- pbrWorkflow === "metalness" ? /* @__PURE__ */ jsxs31(Fragment10, { children: [
12821
+ pbrWorkflow === "metalness" ? /* @__PURE__ */ jsxs31(Fragment11, { children: [
12461
12822
  /* @__PURE__ */ jsx36(
12462
12823
  MaterialSliderField,
12463
12824
  {
@@ -12481,7 +12842,7 @@ function MaterialSection({
12481
12842
  onChange: (value) => setSelectedTexturePath(selectedItem, "metalnessMap", value)
12482
12843
  }
12483
12844
  )
12484
- ] }) : /* @__PURE__ */ jsxs31(Fragment10, { children: [
12845
+ ] }) : /* @__PURE__ */ jsxs31(Fragment11, { children: [
12485
12846
  /* @__PURE__ */ jsx36(
12486
12847
  MaterialSliderField,
12487
12848
  {
@@ -13454,7 +13815,7 @@ function MaterialPresetGallery({
13454
13815
  }
13455
13816
 
13456
13817
  // src/components/BindingBuilder/components/ToolbarExportMenu.tsx
13457
- import { useEffect as useEffect19, useRef as useRef16, useState as useState22 } from "react";
13818
+ import { useEffect as useEffect19, useRef as useRef16, useState as useState23 } from "react";
13458
13819
  import { ChevronDown as ChevronDown5, Download as Download2, FileCode2, FileJson } from "lucide-react";
13459
13820
  import { jsx as jsx38, jsxs as jsxs33 } from "react/jsx-runtime";
13460
13821
  function ToolbarExportMenu({
@@ -13463,7 +13824,7 @@ function ToolbarExportMenu({
13463
13824
  onExportJson,
13464
13825
  onExportTs
13465
13826
  }) {
13466
- const [open, setOpen] = useState22(false);
13827
+ const [open, setOpen] = useState23(false);
13467
13828
  const wrapperRef = useRef16(null);
13468
13829
  useEffect19(() => {
13469
13830
  if (!open) return;
@@ -13797,7 +14158,7 @@ function ActionsPanel({
13797
14158
 
13798
14159
  // src/components/BindingBuilder/components/CameraPositionPanel.tsx
13799
14160
  import { MapPin as MapPin3, Trash2 as Trash25 } from "lucide-react";
13800
- import { Fragment as Fragment11, jsx as jsx42, jsxs as jsxs37 } from "react/jsx-runtime";
14161
+ import { Fragment as Fragment12, jsx as jsx42, jsxs as jsxs37 } from "react/jsx-runtime";
13801
14162
  function CameraPositionPanel({
13802
14163
  selectedBinding,
13803
14164
  open,
@@ -13816,7 +14177,7 @@ function CameraPositionPanel({
13816
14177
  /* @__PURE__ */ jsx42("span", { className: "bb-cameraLiveBadge", children: "LIVE" }),
13817
14178
  /* @__PURE__ */ jsxs37("div", { className: "se-waypointBody", children: [
13818
14179
  /* @__PURE__ */ jsx42("div", { className: "bb-cameraWaypointLabel", children: "Current view" }),
13819
- cameraState ? /* @__PURE__ */ jsxs37(Fragment11, { children: [
14180
+ cameraState ? /* @__PURE__ */ jsxs37(Fragment12, { children: [
13820
14181
  /* @__PURE__ */ jsxs37("div", { className: "se-waypointCoords", children: [
13821
14182
  "pos ",
13822
14183
  formattedCameraState?.position
@@ -13902,7 +14263,7 @@ function CameraPositionPanel({
13902
14263
  }
13903
14264
 
13904
14265
  // src/components/BindingBuilder/components/SceneNodesPanel.tsx
13905
- import { useMemo as useMemo15, useState as useState23 } from "react";
14266
+ import { useMemo as useMemo15, useState as useState24 } from "react";
13906
14267
 
13907
14268
  // src/components/BindingBuilder/utils/bindingFactory.ts
13908
14269
  function slugify(value) {
@@ -13982,7 +14343,7 @@ function collectNodeNames(scene) {
13982
14343
  }
13983
14344
 
13984
14345
  // src/components/BindingBuilder/components/SceneNodesPanel.tsx
13985
- import { Fragment as Fragment12, jsx as jsx43, jsxs as jsxs38 } from "react/jsx-runtime";
14346
+ import { Fragment as Fragment13, jsx as jsx43, jsxs as jsxs38 } from "react/jsx-runtime";
13986
14347
  function SceneNodesPanel({
13987
14348
  bindings,
13988
14349
  selectedItem,
@@ -13991,7 +14352,7 @@ function SceneNodesPanel({
13991
14352
  isOpen = true,
13992
14353
  onToggle
13993
14354
  }) {
13994
- const [search, setSearch] = useState23("");
14355
+ const [search, setSearch] = useState24("");
13995
14356
  const nodeKeys = useMemo15(() => Object.keys(bindings), [bindings]);
13996
14357
  const filteredNodeKeys = useMemo15(() => {
13997
14358
  const query = search.trim().toLowerCase();
@@ -14018,7 +14379,7 @@ function SceneNodesPanel({
14018
14379
  children: "\u2190"
14019
14380
  }
14020
14381
  ),
14021
- isOpen && /* @__PURE__ */ jsxs38(Fragment12, { children: [
14382
+ isOpen && /* @__PURE__ */ jsxs38(Fragment13, { children: [
14022
14383
  /* @__PURE__ */ jsxs38("div", { children: [
14023
14384
  /* @__PURE__ */ jsx43("div", { className: "bb-sectionKicker", children: "Scene Nodes" }),
14024
14385
  /* @__PURE__ */ jsxs38("div", { className: "bb-sectionHeading", children: [
@@ -14136,7 +14497,7 @@ function mergeAnimationConfig(sceneConfig, animationDetails) {
14136
14497
  }
14137
14498
 
14138
14499
  // src/components/BindingBuilder/hooks/useUndoableState.ts
14139
- import { useCallback as useCallback7, useReducer, useRef as useRef17 } from "react";
14500
+ import { useCallback as useCallback8, useReducer, useRef as useRef17 } from "react";
14140
14501
  var MAX_HISTORY = 100;
14141
14502
  function historyReducer(state, action) {
14142
14503
  switch (action.type) {
@@ -14181,7 +14542,7 @@ function useUndoableState(initial, coalesceWindowMs = 600) {
14181
14542
  future: []
14182
14543
  });
14183
14544
  const lastSetAt = useRef17(0);
14184
- const setValue = useCallback7(
14545
+ const setValue = useCallback8(
14185
14546
  (updater) => {
14186
14547
  const now = Date.now();
14187
14548
  const coalesce = now - lastSetAt.current < coalesceWindowMs;
@@ -14190,15 +14551,15 @@ function useUndoableState(initial, coalesceWindowMs = 600) {
14190
14551
  },
14191
14552
  [coalesceWindowMs]
14192
14553
  );
14193
- const undo = useCallback7(() => {
14554
+ const undo = useCallback8(() => {
14194
14555
  lastSetAt.current = 0;
14195
14556
  dispatch({ type: "undo" });
14196
14557
  }, []);
14197
- const redo = useCallback7(() => {
14558
+ const redo = useCallback8(() => {
14198
14559
  lastSetAt.current = 0;
14199
14560
  dispatch({ type: "redo" });
14200
14561
  }, []);
14201
- const reset = useCallback7((value) => {
14562
+ const reset = useCallback8((value) => {
14202
14563
  lastSetAt.current = 0;
14203
14564
  dispatch({ type: "reset", value });
14204
14565
  }, []);
@@ -14427,7 +14788,7 @@ function removeMaterialPanelFields(material, panel) {
14427
14788
  }
14428
14789
 
14429
14790
  // src/components/BindingBuilder/BindingBuilder.tsx
14430
- import { Fragment as Fragment13, jsx as jsx45, jsxs as jsxs40 } from "react/jsx-runtime";
14791
+ import { Fragment as Fragment14, jsx as jsx45, jsxs as jsxs40 } from "react/jsx-runtime";
14431
14792
  var DEMO_MODEL_URL = "/room.glb";
14432
14793
  function isPlainObject(value) {
14433
14794
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -14447,27 +14808,27 @@ function BindingBuilder({ licenseKey }) {
14447
14808
  const modelInputRef = useRef18(null);
14448
14809
  const bindingsInputRef = useRef18(null);
14449
14810
  const sceneConfigInputRef = useRef18(null);
14450
- const [modelUrl, setModelUrl] = useState24(null);
14451
- const [modelName, setModelName] = useState24("");
14452
- const [selectedItem, setSelectedItem] = useState24(null);
14811
+ const [modelUrl, setModelUrl] = useState25(null);
14812
+ const [modelName, setModelName] = useState25("");
14813
+ const [selectedItem, setSelectedItem] = useState25(null);
14453
14814
  const bindingsHistory = useUndoableState({});
14454
14815
  const bindings = bindingsHistory.value;
14455
14816
  const setBindings = bindingsHistory.setValue;
14456
- const [loading, setLoading] = useState24(false);
14457
- const [isExporting, setIsExporting] = useState24(false);
14458
- const [statusMessage, setStatusMessage] = useState24(
14817
+ const [loading, setLoading] = useState25(false);
14818
+ const [isExporting, setIsExporting] = useState25(false);
14819
+ const [statusMessage, setStatusMessage] = useState25(
14459
14820
  "Load the demo model or upload a GLB/GLTF model to generate bindings."
14460
14821
  );
14461
- const [statusTone, setStatusTone] = useState24("default");
14462
- const [statusBannerVisible, setStatusBannerVisible] = useState24(false);
14463
- const [metricsDraft, setMetricsDraft] = useState24("{}");
14464
- const [metadataDraft, setMetadataDraft] = useState24("{}");
14465
- const [metricsError, setMetricsError] = useState24(null);
14466
- const [metadataError, setMetadataError] = useState24(null);
14467
- const [isPreviewDragActive, setIsPreviewDragActive] = useState24(false);
14468
- const [isEditorCollapsed, setIsEditorCollapsed] = useState24(false);
14469
- const [isNodesCollapsed, setIsNodesCollapsed] = useState24(false);
14470
- const [openObjectSections, setOpenObjectSections] = useState24({
14822
+ const [statusTone, setStatusTone] = useState25("default");
14823
+ const [statusBannerVisible, setStatusBannerVisible] = useState25(false);
14824
+ const [metricsDraft, setMetricsDraft] = useState25("{}");
14825
+ const [metadataDraft, setMetadataDraft] = useState25("{}");
14826
+ const [metricsError, setMetricsError] = useState25(null);
14827
+ const [metadataError, setMetadataError] = useState25(null);
14828
+ const [isPreviewDragActive, setIsPreviewDragActive] = useState25(false);
14829
+ const [isEditorCollapsed, setIsEditorCollapsed] = useState25(false);
14830
+ const [isNodesCollapsed, setIsNodesCollapsed] = useState25(false);
14831
+ const [openObjectSections, setOpenObjectSections] = useState25({
14471
14832
  identity: true,
14472
14833
  basics: true,
14473
14834
  material: true,
@@ -14476,7 +14837,7 @@ function BindingBuilder({ licenseKey }) {
14476
14837
  metrics: false,
14477
14838
  metadata: false
14478
14839
  });
14479
- const [openMaterialPanels, setOpenMaterialPanels] = useState24({
14840
+ const [openMaterialPanels, setOpenMaterialPanels] = useState25({
14480
14841
  pbr: true,
14481
14842
  roughness: false,
14482
14843
  emission: false,
@@ -14492,24 +14853,24 @@ function BindingBuilder({ licenseKey }) {
14492
14853
  });
14493
14854
  const toggleObjectSection = (key) => setOpenObjectSections((prev) => ({ ...prev, [key]: !prev[key] }));
14494
14855
  const toggleMaterialPanel = (key) => setOpenMaterialPanels((prev) => ({ ...prev, [key]: !prev[key] }));
14495
- const [activeEditorTab, setActiveEditorTab] = useState24(
14856
+ const [activeEditorTab, setActiveEditorTab] = useState25(
14496
14857
  "object"
14497
14858
  );
14498
- const [activeSubTab, setActiveSubTab] = useState24("General");
14499
- const [pbrWorkflow, setPbrWorkflow] = useState24("metalness");
14500
- const [materialPanelEnabled, setMaterialPanelEnabled] = useState24(createMaterialPanelEnabledState());
14859
+ const [activeSubTab, setActiveSubTab] = useState25("General");
14860
+ const [pbrWorkflow, setPbrWorkflow] = useState25("metalness");
14861
+ const [materialPanelEnabled, setMaterialPanelEnabled] = useState25(createMaterialPanelEnabledState());
14501
14862
  const materialPanelItemRef = useRef18(null);
14502
- const [zoomOnSelected, setZoomOnSelected] = useState24(false);
14503
- const [highlightOnHover, setHighlightOnHover] = useState24(true);
14504
- const [resetDialogOpen, setResetDialogOpen] = useState24(false);
14505
- const [resetTarget, setResetTarget] = useState24(
14863
+ const [zoomOnSelected, setZoomOnSelected] = useState25(false);
14864
+ const [highlightOnHover, setHighlightOnHover] = useState25(true);
14865
+ const [resetDialogOpen, setResetDialogOpen] = useState25(false);
14866
+ const [resetTarget, setResetTarget] = useState25(
14506
14867
  null
14507
14868
  );
14508
14869
  const sceneConfigHistory = useUndoableState(DEFAULT_SCENE_CONFIG);
14509
14870
  const sceneConfig = sceneConfigHistory.value;
14510
14871
  const setSceneConfig = sceneConfigHistory.setValue;
14511
- const [activeAnnotation, setActiveAnnotation] = useState24(null);
14512
- const [panelHoveredBinding, setPanelHoveredBinding] = useState24(null);
14872
+ const [activeAnnotation, setActiveAnnotation] = useState25(null);
14873
+ const [panelHoveredBinding, setPanelHoveredBinding] = useState25(null);
14513
14874
  const license = useLicense(licenseKey);
14514
14875
  const tier = normalizeTier(license.tier);
14515
14876
  const bindingBuilderContextValue = useMemo16(
@@ -14535,14 +14896,16 @@ function BindingBuilder({ licenseKey }) {
14535
14896
  handleCameraChange,
14536
14897
  handleViewerReady
14537
14898
  } = useViewerCamera();
14538
- const handlePreviewWaypoint = useCallback8(
14899
+ const handlePreviewWaypoint = useCallback9(
14539
14900
  (waypoint) => {
14540
14901
  setCameraState(
14541
14902
  {
14542
14903
  position: waypoint.position,
14543
14904
  target: waypoint.target,
14544
14905
  fov: cameraState?.fov ?? null,
14545
- zoom: cameraState?.zoom ?? 1
14906
+ zoom: cameraState?.zoom ?? 1,
14907
+ azimuthAngle: waypoint.azimuthAngle,
14908
+ polarAngle: waypoint.polarAngle
14546
14909
  },
14547
14910
  true
14548
14911
  );
@@ -14557,7 +14920,7 @@ function BindingBuilder({ licenseKey }) {
14557
14920
  position: formatVector3(cameraState.position),
14558
14921
  target: formatVector3(cameraState.target)
14559
14922
  } : null;
14560
- const handleAnnotationsChange = useCallback8(
14923
+ const handleAnnotationsChange = useCallback9(
14561
14924
  (newAnnotations) => {
14562
14925
  setSceneConfig((prev) => {
14563
14926
  const currentIds = prev.annotations.map((a) => a.id).join(",");
@@ -14573,7 +14936,7 @@ function BindingBuilder({ licenseKey }) {
14573
14936
  },
14574
14937
  []
14575
14938
  );
14576
- const handleActiveAnnotationChange = useCallback8(
14939
+ const handleActiveAnnotationChange = useCallback9(
14577
14940
  (annotation) => {
14578
14941
  setActiveAnnotation(
14579
14942
  annotation ? {
@@ -14694,7 +15057,7 @@ function BindingBuilder({ licenseKey }) {
14694
15057
  });
14695
15058
  }
14696
15059
  };
14697
- const applyBindingsFromScene = useCallback8(
15060
+ const applyBindingsFromScene = useCallback9(
14698
15061
  (scene, nextModelName) => {
14699
15062
  const nodeNames = collectNodeNames(scene);
14700
15063
  bindingsHistory.reset(buildBindingsFromNodes(nodeNames));
@@ -14733,7 +15096,7 @@ function BindingBuilder({ licenseKey }) {
14733
15096
  bindingsHistory.reset({});
14734
15097
  setSelectedItem(null);
14735
15098
  };
14736
- const handleViewerModelLoaded = useCallback8(
15099
+ const handleViewerModelLoaded = useCallback9(
14737
15100
  (scene) => {
14738
15101
  const pending = pendingLoadRef.current;
14739
15102
  if (!pending || pending.requestId !== loadRequestRef.current) return;
@@ -14743,7 +15106,7 @@ function BindingBuilder({ licenseKey }) {
14743
15106
  },
14744
15107
  [applyBindingsFromScene]
14745
15108
  );
14746
- const handleViewerLoadError = useCallback8((error) => {
15109
+ const handleViewerLoadError = useCallback9((error) => {
14747
15110
  const pending = pendingLoadRef.current;
14748
15111
  if (!pending || pending.requestId !== loadRequestRef.current) return;
14749
15112
  pendingLoadRef.current = null;
@@ -14754,7 +15117,7 @@ function BindingBuilder({ licenseKey }) {
14754
15117
  setStatusTone("error");
14755
15118
  setLoading(false);
14756
15119
  }, []);
14757
- const handleAnimationsReady = useCallback8(
15120
+ const handleAnimationsReady = useCallback9(
14758
15121
  (controls) => {
14759
15122
  viewerAnimations.handleAnimationsReady(controls);
14760
15123
  const details = controls.clipDetails ?? controls.clips.map((sourceName) => ({ sourceName, duration: 0 }));
@@ -15055,7 +15418,7 @@ function BindingBuilder({ licenseKey }) {
15055
15418
  }));
15056
15419
  setError(null);
15057
15420
  };
15058
- const handleViewerSelect = useCallback8(
15421
+ const handleViewerSelect = useCallback9(
15059
15422
  (binding) => {
15060
15423
  if (!binding) {
15061
15424
  setSelectedItem(null);
@@ -15068,7 +15431,7 @@ function BindingBuilder({ licenseKey }) {
15068
15431
  },
15069
15432
  [bindings]
15070
15433
  );
15071
- const handleSceneNodeSelect = useCallback8(
15434
+ const handleSceneNodeSelect = useCallback9(
15072
15435
  async (nodeKey) => {
15073
15436
  setSelectedItem(nodeKey);
15074
15437
  setPanelHoveredBinding(null);
@@ -15076,7 +15439,7 @@ function BindingBuilder({ licenseKey }) {
15076
15439
  },
15077
15440
  [focusObject]
15078
15441
  );
15079
- const saveCameraPositionForSelectedObject = useCallback8(() => {
15442
+ const saveCameraPositionForSelectedObject = useCallback9(() => {
15080
15443
  if (!selectedItem || !cameraState) {
15081
15444
  return;
15082
15445
  }
@@ -15091,7 +15454,7 @@ function BindingBuilder({ licenseKey }) {
15091
15454
  cameraState: currentCameraState
15092
15455
  }));
15093
15456
  }, [selectedItem, cameraState]);
15094
- const goToSavedCameraPosition = useCallback8(() => {
15457
+ const goToSavedCameraPosition = useCallback9(() => {
15095
15458
  if (!savedCameraPosition?.position || !savedCameraPosition?.target) return;
15096
15459
  setCameraState(
15097
15460
  {
@@ -15103,28 +15466,28 @@ function BindingBuilder({ licenseKey }) {
15103
15466
  true
15104
15467
  );
15105
15468
  }, [savedCameraPosition, setCameraState, cameraState]);
15106
- const clearSavedCameraPosition = useCallback8(() => {
15469
+ const clearSavedCameraPosition = useCallback9(() => {
15107
15470
  if (!selectedItem) return;
15108
15471
  updateBinding(selectedItem, (binding) => ({
15109
15472
  ...binding,
15110
15473
  cameraState: void 0
15111
15474
  }));
15112
15475
  }, [selectedItem]);
15113
- const handleResetView = useCallback8(async () => {
15476
+ const handleResetView = useCallback9(async () => {
15114
15477
  await resetView();
15115
15478
  }, [resetView]);
15116
- const handleFitScene = useCallback8(async () => {
15479
+ const handleFitScene = useCallback9(async () => {
15117
15480
  await fitScene();
15118
15481
  }, [fitScene]);
15119
- const openResetDialog = useCallback8((target) => {
15482
+ const openResetDialog = useCallback9((target) => {
15120
15483
  setResetTarget(target);
15121
15484
  setResetDialogOpen(true);
15122
15485
  }, []);
15123
- const closeResetDialog = useCallback8(() => {
15486
+ const closeResetDialog = useCallback9(() => {
15124
15487
  setResetDialogOpen(false);
15125
15488
  setResetTarget(null);
15126
15489
  }, []);
15127
- const executeReset = useCallback8(() => {
15490
+ const executeReset = useCallback9(() => {
15128
15491
  if (resetTarget === "bindings") {
15129
15492
  if (!nodeKeys.length) {
15130
15493
  closeResetDialog();
@@ -15221,7 +15584,7 @@ function BindingBuilder({ licenseKey }) {
15221
15584
  }
15222
15585
  )
15223
15586
  ] }),
15224
- !isEditorCollapsed && /* @__PURE__ */ jsxs40(Fragment13, { children: [
15587
+ !isEditorCollapsed && /* @__PURE__ */ jsxs40(Fragment14, { children: [
15225
15588
  /* @__PURE__ */ jsxs40("div", { className: "bb-tabRow", children: [
15226
15589
  /* @__PURE__ */ jsx45(
15227
15590
  "button",
@@ -15242,7 +15605,7 @@ function BindingBuilder({ licenseKey }) {
15242
15605
  }
15243
15606
  )
15244
15607
  ] }),
15245
- /* @__PURE__ */ jsx45("section", { className: "bb-editor", children: activeEditorTab === "scene" ? /* @__PURE__ */ jsx45(Fragment13, { children: modelUrl ? /* @__PURE__ */ jsx45(
15608
+ /* @__PURE__ */ jsx45("section", { className: "bb-editor", children: activeEditorTab === "scene" ? /* @__PURE__ */ jsx45(Fragment14, { children: modelUrl ? /* @__PURE__ */ jsx45(
15246
15609
  SceneSettingsTab,
15247
15610
  {
15248
15611
  config: sceneConfig,
@@ -15255,14 +15618,16 @@ function BindingBuilder({ licenseKey }) {
15255
15618
  // the whole settings panel for no reason.
15256
15619
  activeSubTab === "Cinematic" && cameraState ? {
15257
15620
  position: cameraState.position,
15258
- target: cameraState.target
15621
+ target: cameraState.target,
15622
+ azimuthAngle: cameraState.azimuthAngle,
15623
+ polarAngle: cameraState.polarAngle
15259
15624
  } : null
15260
15625
  ),
15261
15626
  onPreviewWaypoint: handlePreviewWaypoint,
15262
15627
  animationClipDetails: viewerAnimations.clipDetails,
15263
15628
  animationPlayback: viewerAnimations
15264
15629
  }
15265
- ) : /* @__PURE__ */ jsx45("div", { className: "bb-emptyEditor", children: "Upload a model to edit its scene settings." }) }) : !selectedBinding || !selectedItem ? /* @__PURE__ */ jsx45("div", { className: "bb-emptyEditor", children: "Select a generated node binding to edit its values." }) : /* @__PURE__ */ jsxs40(Fragment13, { children: [
15630
+ ) : /* @__PURE__ */ jsx45("div", { className: "bb-emptyEditor", children: "Upload a model to edit its scene settings." }) }) : !selectedBinding || !selectedItem ? /* @__PURE__ */ jsx45("div", { className: "bb-emptyEditor", children: "Select a generated node binding to edit its values." }) : /* @__PURE__ */ jsxs40(Fragment14, { children: [
15266
15631
  /* @__PURE__ */ jsx45("div", { className: "bb-editorHeader", children: /* @__PURE__ */ jsxs40("div", { children: [
15267
15632
  /* @__PURE__ */ jsx45("div", { className: "bb-sectionKicker", children: "Editing Binding" }),
15268
15633
  /* @__PURE__ */ jsx45("h2", { className: "bb-editorTitle", children: selectedBinding.label || selectedItem }),
@@ -15682,8 +16047,8 @@ function BindingBuilder({ licenseKey }) {
15682
16047
  // src/components/SimpleModelViewer/SimpleModelViewer.tsx
15683
16048
  import {
15684
16049
  useRef as useRef19,
15685
- useState as useState25,
15686
- useCallback as useCallback9,
16050
+ useState as useState26,
16051
+ useCallback as useCallback10,
15687
16052
  useEffect as useEffect21,
15688
16053
  useMemo as useMemo17,
15689
16054
  Component as Component3,
@@ -15706,14 +16071,14 @@ styleInject('.smv-root {\n --smv-panel-font-family:\n Inter,\n "Segoe UI"
15706
16071
  import {
15707
16072
  X as X3,
15708
16073
  Check,
15709
- Settings,
16074
+ Settings as Settings2,
15710
16075
  ChevronDown as ChevronDown6,
15711
16076
  Boxes as Boxes2,
15712
16077
  Trash2 as Trash26,
15713
16078
  Upload as Upload2,
15714
16079
  AlertTriangle as AlertTriangle4
15715
16080
  } from "lucide-react";
15716
- import { Fragment as Fragment14, jsx as jsx46, jsxs as jsxs41 } from "react/jsx-runtime";
16081
+ import { Fragment as Fragment15, jsx as jsx46, jsxs as jsxs41 } from "react/jsx-runtime";
15717
16082
  var HIGHLIGHT2 = { color: "#93c5fd", intensity: 0.05 };
15718
16083
  var ENV_PRESETS = [
15719
16084
  "apartment",
@@ -15773,7 +16138,7 @@ function InnerModel({
15773
16138
  meshopt: true
15774
16139
  });
15775
16140
  const invalidate = useThree11((state) => state.invalidate);
15776
- const [hoveredKey, setHoveredKey] = useState25(null);
16141
+ const [hoveredKey, setHoveredKey] = useState26(null);
15777
16142
  const scene = useMemo17(
15778
16143
  () => SkeletonUtils2.clone(rawScene),
15779
16144
  [rawScene]
@@ -16075,7 +16440,7 @@ function EnvironmentSection({
16075
16440
  title,
16076
16441
  children
16077
16442
  }) {
16078
- const [collapsed, setCollapsed] = useState25(false);
16443
+ const [collapsed, setCollapsed] = useState26(false);
16079
16444
  return /* @__PURE__ */ jsxs41("div", { className: "env-section", children: [
16080
16445
  /* @__PURE__ */ jsxs41(
16081
16446
  "button",
@@ -16114,7 +16479,7 @@ function EnvironmentPanel({
16114
16479
  {
16115
16480
  className: "side-panel__open-btn side-panel__open-btn--left",
16116
16481
  onClick: () => onToggle(true),
16117
- children: /* @__PURE__ */ jsx46(Settings, { size: 20, strokeWidth: 1.8 })
16482
+ children: /* @__PURE__ */ jsx46(Settings2, { size: 20, strokeWidth: 1.8 })
16118
16483
  }
16119
16484
  ),
16120
16485
  /* @__PURE__ */ jsxs41(
@@ -16240,8 +16605,8 @@ function SceneObjectsPanel2({
16240
16605
  onHover,
16241
16606
  onToggleVisibility
16242
16607
  }) {
16243
- const [search, setSearch] = useState25("");
16244
- const [rowHovered, setRowHovered] = useState25(null);
16608
+ const [search, setSearch] = useState26("");
16609
+ const [rowHovered, setRowHovered] = useState26(null);
16245
16610
  const entries = Object.values(meshKeys).filter(
16246
16611
  (m) => m.label.toLowerCase().includes(search.toLowerCase())
16247
16612
  );
@@ -16370,30 +16735,30 @@ function SimpleModelViewer({
16370
16735
  const fileInputRef = useRef19(null);
16371
16736
  const uploadedModelRef = useRef19(null);
16372
16737
  const dragDepthRef = useRef19(0);
16373
- const [loadedScene, setLoadedScene] = useState25(null);
16374
- const [largeModelTriangles, setLargeModelTriangles] = useState25(
16738
+ const [loadedScene, setLoadedScene] = useState26(null);
16739
+ const [largeModelTriangles, setLargeModelTriangles] = useState26(
16375
16740
  null
16376
16741
  );
16377
- const [largeModelNoticeDismissed, setLargeModelNoticeDismissed] = useState25(false);
16378
- const [meshKeys, setMeshKeys] = useState25({});
16379
- const [hiddenKeys, setHiddenKeys] = useState25(/* @__PURE__ */ new Set());
16380
- const [selectedKey, setSelectedKey] = useState25(null);
16381
- const [panelHoveredKey, setPanelHoveredKey] = useState25(null);
16382
- const [uploadedModelUrl, setUploadedModelUrl] = useState25(null);
16383
- const [uploadedModelName, setUploadedModelName] = useState25(
16742
+ const [largeModelNoticeDismissed, setLargeModelNoticeDismissed] = useState26(false);
16743
+ const [meshKeys, setMeshKeys] = useState26({});
16744
+ const [hiddenKeys, setHiddenKeys] = useState26(/* @__PURE__ */ new Set());
16745
+ const [selectedKey, setSelectedKey] = useState26(null);
16746
+ const [panelHoveredKey, setPanelHoveredKey] = useState26(null);
16747
+ const [uploadedModelUrl, setUploadedModelUrl] = useState26(null);
16748
+ const [uploadedModelName, setUploadedModelName] = useState26(
16384
16749
  null
16385
16750
  );
16386
- const [modelLoadFailed, setModelLoadFailed] = useState25(false);
16387
- const [isResolvingUpload, setIsResolvingUpload] = useState25(false);
16388
- const [initialFitComplete, setInitialFitComplete] = useState25(false);
16389
- const [isDragActive, setIsDragActive] = useState25(false);
16390
- const [isPanelOpen, setIsPanelOpen] = useState25(
16751
+ const [modelLoadFailed, setModelLoadFailed] = useState26(false);
16752
+ const [isResolvingUpload, setIsResolvingUpload] = useState26(false);
16753
+ const [initialFitComplete, setInitialFitComplete] = useState26(false);
16754
+ const [isDragActive, setIsDragActive] = useState26(false);
16755
+ const [isPanelOpen, setIsPanelOpen] = useState26(
16391
16756
  () => typeof window === "undefined" ? true : window.innerWidth > 900
16392
16757
  );
16393
- const [isEnvPanelOpen, setIsEnvPanelOpen] = useState25(
16758
+ const [isEnvPanelOpen, setIsEnvPanelOpen] = useState26(
16394
16759
  () => typeof window === "undefined" ? true : window.innerWidth > 900
16395
16760
  );
16396
- const [envSettings, setEnvSettings] = useState25({
16761
+ const [envSettings, setEnvSettings] = useState26({
16397
16762
  backgroundEnabled,
16398
16763
  backgroundColor,
16399
16764
  autoRotate,
@@ -16405,7 +16770,7 @@ function SimpleModelViewer({
16405
16770
  directionalColor
16406
16771
  });
16407
16772
  const activeModelUrl = enableModelUpload ? uploadedModelUrl : modelUrl;
16408
- const resetModelState = useCallback9(() => {
16773
+ const resetModelState = useCallback10(() => {
16409
16774
  setLoadedScene(null);
16410
16775
  setLargeModelTriangles(null);
16411
16776
  setLargeModelNoticeDismissed(false);
@@ -16416,11 +16781,11 @@ function SimpleModelViewer({
16416
16781
  setModelLoadFailed(false);
16417
16782
  setInitialFitComplete(false);
16418
16783
  }, []);
16419
- const revokeUploadedModelUrl = useCallback9(() => {
16784
+ const revokeUploadedModelUrl = useCallback10(() => {
16420
16785
  uploadedModelRef.current?.cleanup();
16421
16786
  uploadedModelRef.current = null;
16422
16787
  }, []);
16423
- const clearUploadedModel = useCallback9(() => {
16788
+ const clearUploadedModel = useCallback10(() => {
16424
16789
  revokeUploadedModelUrl();
16425
16790
  setUploadedModelUrl(null);
16426
16791
  setUploadedModelName(null);
@@ -16449,7 +16814,7 @@ function SimpleModelViewer({
16449
16814
  resetModelState,
16450
16815
  revokeUploadedModelUrl
16451
16816
  ]);
16452
- const handleUploadedModel = useCallback9(
16817
+ const handleUploadedModel = useCallback10(
16453
16818
  async (file) => {
16454
16819
  setIsResolvingUpload(true);
16455
16820
  try {
@@ -16468,7 +16833,7 @@ function SimpleModelViewer({
16468
16833
  },
16469
16834
  [onLoadError, resetModelState, revokeUploadedModelUrl]
16470
16835
  );
16471
- const handleFileSelection = useCallback9(
16836
+ const handleFileSelection = useCallback10(
16472
16837
  (files) => {
16473
16838
  const file = files?.[0];
16474
16839
  if (!file) return;
@@ -16481,7 +16846,7 @@ function SimpleModelViewer({
16481
16846
  revokeUploadedModelUrl();
16482
16847
  };
16483
16848
  }, [revokeUploadedModelUrl]);
16484
- const handleEnvSettingChange = useCallback9(
16849
+ const handleEnvSettingChange = useCallback10(
16485
16850
  (key, value) => {
16486
16851
  setEnvSettings((prev) => ({ ...prev, [key]: value }));
16487
16852
  },
@@ -16493,12 +16858,12 @@ function SimpleModelViewer({
16493
16858
  useEffect21(() => {
16494
16859
  resetModelState();
16495
16860
  }, [activeModelUrl, resetModelState]);
16496
- const handleMeshesReady = useCallback9((meshes) => {
16861
+ const handleMeshesReady = useCallback10((meshes) => {
16497
16862
  setMeshKeys(meshes);
16498
16863
  setHiddenKeys(/* @__PURE__ */ new Set());
16499
16864
  setSelectedKey(null);
16500
16865
  }, []);
16501
- const handleModelLoaded = useCallback9(
16866
+ const handleModelLoaded = useCallback10(
16502
16867
  (scene) => {
16503
16868
  setLoadedScene(scene);
16504
16869
  setModelLoadFailed(false);
@@ -16509,14 +16874,14 @@ function SimpleModelViewer({
16509
16874
  },
16510
16875
  [onModelLoaded]
16511
16876
  );
16512
- const handleLoadError = useCallback9(
16877
+ const handleLoadError = useCallback10(
16513
16878
  (error) => {
16514
16879
  setModelLoadFailed(true);
16515
16880
  onLoadError?.(error);
16516
16881
  },
16517
16882
  [onLoadError]
16518
16883
  );
16519
- const focusMesh = useCallback9(
16884
+ const focusMesh = useCallback10(
16520
16885
  (key) => {
16521
16886
  setSelectedKey(key);
16522
16887
  if (!zoomOnSelected) return;
@@ -16532,14 +16897,14 @@ function SimpleModelViewer({
16532
16897
  },
16533
16898
  [zoomOnSelected]
16534
16899
  );
16535
- const handleToggleVisibility = useCallback9((key) => {
16900
+ const handleToggleVisibility = useCallback10((key) => {
16536
16901
  setHiddenKeys((prev) => {
16537
16902
  const next = new Set(prev);
16538
16903
  next.has(key) ? next.delete(key) : next.add(key);
16539
16904
  return next;
16540
16905
  });
16541
16906
  }, []);
16542
- const handleDragEnter = useCallback9(
16907
+ const handleDragEnter = useCallback10(
16543
16908
  (event) => {
16544
16909
  if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
16545
16910
  return;
@@ -16551,7 +16916,7 @@ function SimpleModelViewer({
16551
16916
  },
16552
16917
  [enableModelUpload]
16553
16918
  );
16554
- const handleDragOver = useCallback9(
16919
+ const handleDragOver = useCallback10(
16555
16920
  (event) => {
16556
16921
  if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
16557
16922
  return;
@@ -16563,7 +16928,7 @@ function SimpleModelViewer({
16563
16928
  },
16564
16929
  [enableModelUpload]
16565
16930
  );
16566
- const handleDragLeave = useCallback9(
16931
+ const handleDragLeave = useCallback10(
16567
16932
  (event) => {
16568
16933
  if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
16569
16934
  return;
@@ -16577,7 +16942,7 @@ function SimpleModelViewer({
16577
16942
  },
16578
16943
  [enableModelUpload]
16579
16944
  );
16580
- const handleDrop = useCallback9(
16945
+ const handleDrop = useCallback10(
16581
16946
  (event) => {
16582
16947
  if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
16583
16948
  return;
@@ -16631,7 +16996,7 @@ function SimpleModelViewer({
16631
16996
  }
16632
16997
  )
16633
16998
  ] }),
16634
- enableModelUpload ? /* @__PURE__ */ jsxs41(Fragment14, { children: [
16999
+ enableModelUpload ? /* @__PURE__ */ jsxs41(Fragment15, { children: [
16635
17000
  /* @__PURE__ */ jsx46(
16636
17001
  "input",
16637
17002
  {
@@ -16645,7 +17010,7 @@ function SimpleModelViewer({
16645
17010
  }
16646
17011
  }
16647
17012
  ),
16648
- activeModelUrl ? /* @__PURE__ */ jsx46("div", { className: "smv-upload-toolbar", children: /* @__PURE__ */ jsxs41(Fragment14, { children: [
17013
+ activeModelUrl ? /* @__PURE__ */ jsx46("div", { className: "smv-upload-toolbar", children: /* @__PURE__ */ jsxs41(Fragment15, { children: [
16649
17014
  /* @__PURE__ */ jsx46("span", { className: "smv-upload-toolbar__name", children: uploadedModelName ?? "Uploaded model" }),
16650
17015
  /* @__PURE__ */ jsxs41(
16651
17016
  "button",
@@ -16806,18 +17171,18 @@ function SimpleModelViewer({
16806
17171
  }
16807
17172
 
16808
17173
  // src/hooks/useViewerSelection.ts
16809
- import { useCallback as useCallback10, useState as useState26 } from "react";
17174
+ import { useCallback as useCallback11, useState as useState27 } from "react";
16810
17175
  function useViewerSelection(initialSelectedObjectBinding = null) {
16811
- const [selectedObjectBinding, setSelectedObjectBinding] = useState26(
17176
+ const [selectedObjectBinding, setSelectedObjectBinding] = useState27(
16812
17177
  initialSelectedObjectBinding
16813
17178
  );
16814
- const handleObjectSelect = useCallback10(
17179
+ const handleObjectSelect = useCallback11(
16815
17180
  (binding) => {
16816
17181
  setSelectedObjectBinding(binding);
16817
17182
  },
16818
17183
  [setSelectedObjectBinding]
16819
17184
  );
16820
- const clearSelection = useCallback10(() => {
17185
+ const clearSelection = useCallback11(() => {
16821
17186
  setSelectedObjectBinding(null);
16822
17187
  }, []);
16823
17188
  return {
@@ -16855,14 +17220,14 @@ function useObjectBindingIds(objectBindings) {
16855
17220
  }
16856
17221
 
16857
17222
  // src/hooks/useObjectVisibility.ts
16858
- import { useCallback as useCallback11, useMemo as useMemo20 } from "react";
17223
+ import { useCallback as useCallback12, useMemo as useMemo20 } from "react";
16859
17224
  function getHiddenObjects(objectBindings) {
16860
17225
  return Object.fromEntries(
16861
17226
  Object.entries(objectBindings).filter(([, binding]) => binding.visible === false).map(([bindingKey]) => [bindingKey, true])
16862
17227
  );
16863
17228
  }
16864
17229
  function useObjectVisibility(objectBindings, onObjectBindingsChange) {
16865
- const updateVisibility = useCallback11(
17230
+ const updateVisibility = useCallback12(
16866
17231
  (objectId, visible) => {
16867
17232
  const bindingKey = resolveBindingKey(objectBindings, objectId);
16868
17233
  if (!bindingKey) return;
@@ -16878,15 +17243,15 @@ function useObjectVisibility(objectBindings, onObjectBindingsChange) {
16878
17243
  },
16879
17244
  [objectBindings, onObjectBindingsChange]
16880
17245
  );
16881
- const hideObject = useCallback11(
17246
+ const hideObject = useCallback12(
16882
17247
  (objectId) => updateVisibility(objectId, false),
16883
17248
  [updateVisibility]
16884
17249
  );
16885
- const showObject = useCallback11(
17250
+ const showObject = useCallback12(
16886
17251
  (objectId) => updateVisibility(objectId, true),
16887
17252
  [updateVisibility]
16888
17253
  );
16889
- const toggleObjectVisibility = useCallback11(
17254
+ const toggleObjectVisibility = useCallback12(
16890
17255
  (objectId) => {
16891
17256
  const bindingKey = resolveBindingKey(objectBindings, objectId);
16892
17257
  if (!bindingKey) return;
@@ -16902,7 +17267,7 @@ function useObjectVisibility(objectBindings, onObjectBindingsChange) {
16902
17267
  },
16903
17268
  [objectBindings, onObjectBindingsChange]
16904
17269
  );
16905
- const clearHiddenObjects = useCallback11(() => {
17270
+ const clearHiddenObjects = useCallback12(() => {
16906
17271
  onObjectBindingsChange(
16907
17272
  Object.fromEntries(
16908
17273
  Object.entries(objectBindings).map(([bindingKey, binding]) => [
@@ -16922,7 +17287,7 @@ function useObjectVisibility(objectBindings, onObjectBindingsChange) {
16922
17287
  ),
16923
17288
  [hiddenObjects, objectBindings]
16924
17289
  );
16925
- const isObjectHidden = useCallback11(
17290
+ const isObjectHidden = useCallback12(
16926
17291
  (objectId) => {
16927
17292
  const bindingKey = resolveBindingKey(objectBindings, objectId);
16928
17293
  return bindingKey ? objectBindings[bindingKey]?.visible === false : false;
@@ -16941,10 +17306,10 @@ function useObjectVisibility(objectBindings, onObjectBindingsChange) {
16941
17306
  }
16942
17307
 
16943
17308
  // src/hooks/useViewerHover.ts
16944
- import { useCallback as useCallback12, useState as useState27 } from "react";
17309
+ import { useCallback as useCallback13, useState as useState28 } from "react";
16945
17310
  function useViewerHover() {
16946
- const [hoveredObjectBinding, setHoveredObjectBinding] = useState27(null);
16947
- const handleHoveredObject = useCallback12(
17311
+ const [hoveredObjectBinding, setHoveredObjectBinding] = useState28(null);
17312
+ const handleHoveredObject = useCallback13(
16948
17313
  (binding) => {
16949
17314
  setHoveredObjectBinding(binding);
16950
17315
  },
@@ -16957,18 +17322,18 @@ function useViewerHover() {
16957
17322
  }
16958
17323
 
16959
17324
  // src/hooks/useViewerModel.ts
16960
- import { useCallback as useCallback13, useState as useState28 } from "react";
17325
+ import { useCallback as useCallback14, useState as useState29 } from "react";
16961
17326
  import { Box3 as Box36, Vector3 as Vector310 } from "three";
16962
17327
  function toTuple2(vector) {
16963
17328
  return [vector.x, vector.y, vector.z];
16964
17329
  }
16965
17330
  function useViewerModel() {
16966
- const [isLoading, setIsLoading] = useState28(true);
16967
- const [isReady, setIsReady] = useState28(false);
16968
- const [error, setError] = useState28(null);
16969
- const [bounds, setBounds] = useState28(null);
16970
- const [objectCount, setObjectCount] = useState28(0);
16971
- const handleModelLoaded = useCallback13((scene) => {
17331
+ const [isLoading, setIsLoading] = useState29(true);
17332
+ const [isReady, setIsReady] = useState29(false);
17333
+ const [error, setError] = useState29(null);
17334
+ const [bounds, setBounds] = useState29(null);
17335
+ const [objectCount, setObjectCount] = useState29(0);
17336
+ const handleModelLoaded = useCallback14((scene) => {
16972
17337
  const box = new Box36().setFromObject(scene);
16973
17338
  const min = box.min.clone();
16974
17339
  const max = box.max.clone();
@@ -16993,14 +17358,14 @@ function useViewerModel() {
16993
17358
  setIsLoading(false);
16994
17359
  setIsReady(true);
16995
17360
  }, []);
16996
- const handleLoadError = useCallback13((nextError) => {
17361
+ const handleLoadError = useCallback14((nextError) => {
16997
17362
  setError(nextError);
16998
17363
  setBounds(null);
16999
17364
  setObjectCount(0);
17000
17365
  setIsLoading(false);
17001
17366
  setIsReady(false);
17002
17367
  }, []);
17003
- const resetModelState = useCallback13(() => {
17368
+ const resetModelState = useCallback14(() => {
17004
17369
  setIsLoading(true);
17005
17370
  setIsReady(false);
17006
17371
  setError(null);
@@ -17020,10 +17385,10 @@ function useViewerModel() {
17020
17385
  }
17021
17386
 
17022
17387
  // src/hooks/useViewerActions.ts
17023
- import { useCallback as useCallback14 } from "react";
17388
+ import { useCallback as useCallback15 } from "react";
17024
17389
  function useViewerActions(objectBindings, options = {}) {
17025
17390
  const { onAction, onObjectBindingsChange } = options;
17026
- const getActionsForObject = useCallback14(
17391
+ const getActionsForObject = useCallback15(
17027
17392
  (objectId) => {
17028
17393
  const bindingKey = resolveBindingKey(objectBindings, objectId);
17029
17394
  if (!bindingKey) {
@@ -17033,7 +17398,7 @@ function useViewerActions(objectBindings, options = {}) {
17033
17398
  },
17034
17399
  [objectBindings]
17035
17400
  );
17036
- const runAction = useCallback14(
17401
+ const runAction = useCallback15(
17037
17402
  (objectId, actionId) => {
17038
17403
  const bindingKey = resolveBindingKey(objectBindings, objectId);
17039
17404
  if (!bindingKey) {
@@ -17075,7 +17440,7 @@ function useViewerActions(objectBindings, options = {}) {
17075
17440
  }
17076
17441
 
17077
17442
  // src/hooks/useShareableViewerState.ts
17078
- import { useCallback as useCallback15, useEffect as useEffect22, useRef as useRef20, useState as useState29 } from "react";
17443
+ import { useCallback as useCallback16, useEffect as useEffect22, useRef as useRef20, useState as useState30 } from "react";
17079
17444
  function serializeState(cameraState, objectBindings, selectedObjectBinding) {
17080
17445
  const hidden = Object.values(objectBindings).filter((binding) => binding.visible === false).map((binding) => binding.id);
17081
17446
  return {
@@ -17096,7 +17461,7 @@ function useShareableViewerState({
17096
17461
  autoSync = true
17097
17462
  }) {
17098
17463
  const lastWrittenRawRef = useRef20(null);
17099
- const buildHash = useCallback15(() => {
17464
+ const buildHash = useCallback16(() => {
17100
17465
  const state = serializeState(
17101
17466
  cameraState,
17102
17467
  objectBindings,
@@ -17104,12 +17469,12 @@ function useShareableViewerState({
17104
17469
  );
17105
17470
  return `${paramKey}=${encodeURIComponent(JSON.stringify(state))}`;
17106
17471
  }, [cameraState, objectBindings, selectedObjectBinding, paramKey]);
17107
- const getShareUrl = useCallback15(() => {
17472
+ const getShareUrl = useCallback16(() => {
17108
17473
  const url = new URL(window.location.href);
17109
17474
  url.hash = buildHash();
17110
17475
  return url.toString();
17111
17476
  }, [buildHash]);
17112
- const syncUrl = useCallback15(() => {
17477
+ const syncUrl = useCallback16(() => {
17113
17478
  const url = new URL(window.location.href);
17114
17479
  url.hash = buildHash();
17115
17480
  window.history.replaceState(null, "", url.toString());
@@ -17127,7 +17492,7 @@ function useShareableViewerState({
17127
17492
  objectBindings,
17128
17493
  selectedObjectBinding
17129
17494
  };
17130
- const [restoreAttempted, setRestoreAttempted] = useState29(false);
17495
+ const [restoreAttempted, setRestoreAttempted] = useState30(false);
17131
17496
  const lastSyncedHashRef = useRef20(null);
17132
17497
  useEffect22(() => {
17133
17498
  if (!autoSync || !restoreAttempted) return;
@@ -17153,7 +17518,7 @@ function useShareableViewerState({
17153
17518
  }, [autoSync, restoreAttempted, debounceMs, paramKey]);
17154
17519
  const lastRestoredRawRef = useRef20(null);
17155
17520
  const restoringRawRef = useRef20(null);
17156
- const restoreFromUrl = useCallback15(async () => {
17521
+ const restoreFromUrl = useCallback16(async () => {
17157
17522
  setRestoreAttempted(true);
17158
17523
  const rawHash = window.location.hash.startsWith("#") ? window.location.hash.slice(1) : window.location.hash;
17159
17524
  const raw = new URLSearchParams(rawHash).get(paramKey);