@liveroom-tech/react-immersive 1.3.2 → 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";
@@ -600,6 +600,7 @@ var DEFAULT_SCENE_POST_PROCESSING_CONFIG = {
600
600
  rampEnd: [1, 1],
601
601
  startColor: "#000000",
602
602
  endColor: "#ffffff",
603
+ opacity: 0.5,
603
604
  rampBias: 0.5,
604
605
  rampGain: 0.5,
605
606
  rampMask: false,
@@ -1467,7 +1468,10 @@ function PostProcessingEffects({
1467
1468
  }
1468
1469
  ),
1469
1470
  pp.pixelation.enabled === true && /* @__PURE__ */ jsx4(Pixelation, { granularity: pp.pixelation.granularity }),
1470
- pp.ramp.enabled === true && /* @__PURE__ */ jsx4(
1471
+ pp.ramp.enabled === true && // Ramp's non-mask shader emits an opaque full-screen gradient.
1472
+ // Blend it with the scene by default instead of replacing the
1473
+ // model completely; mask mode already performs its own mix.
1474
+ /* @__PURE__ */ jsx4(
1471
1475
  Ramp,
1472
1476
  {
1473
1477
  rampType: rampTypeFromName(pp.ramp.rampType),
@@ -1475,6 +1479,7 @@ function PostProcessingEffects({
1475
1479
  rampEnd: pp.ramp.rampEnd,
1476
1480
  startColor: rampStartColor,
1477
1481
  endColor: rampEndColor,
1482
+ opacity: pp.ramp.rampMask ? 1 : pp.ramp.opacity ?? 0.5,
1478
1483
  rampBias: pp.ramp.rampBias,
1479
1484
  rampGain: pp.ramp.rampGain,
1480
1485
  rampMask: pp.ramp.rampMask,
@@ -4125,6 +4130,7 @@ import {
4125
4130
  Box3 as Box33,
4126
4131
  CatmullRomCurve3,
4127
4132
  Sphere,
4133
+ Spherical,
4128
4134
  Vector3 as Vector36
4129
4135
  } from "three";
4130
4136
  var ENTER_BLEND_SECONDS = 1.2;
@@ -4133,18 +4139,75 @@ function CinematicCamera({
4133
4139
  sceneRef,
4134
4140
  config,
4135
4141
  playing,
4142
+ resetOnPlay = false,
4136
4143
  onFinished,
4137
- onUserInteract
4144
+ onUserInteract,
4145
+ onStop
4138
4146
  }) {
4139
4147
  const invalidate = useThree9((state) => state.invalidate);
4140
4148
  const waypoints = useMemo11(() => config.waypoints ?? [], [config.waypoints]);
4141
4149
  const hasPath = waypoints.length >= 2;
4142
4150
  const loop = config.loop ?? true;
4143
- 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;
4144
4163
  const signature = useMemo11(
4145
- () => 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(";"),
4146
4173
  [waypoints]
4147
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]);
4148
4211
  const curves = useMemo11(() => {
4149
4212
  if (!hasPath) return null;
4150
4213
  return {
@@ -4162,29 +4225,83 @@ function CinematicCamera({
4162
4225
  )
4163
4226
  };
4164
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]);
4165
4248
  const elapsedRef = useRef8(0);
4166
4249
  const enterElapsedRef = useRef8(0);
4167
4250
  const enterPos = useRef8(new Vector36());
4168
4251
  const enterTarget = useRef8(new Vector36());
4169
4252
  const startedRef = useRef8(false);
4170
4253
  const pausedElapsedRef = useRef8(0);
4254
+ const hasFinishedRef = useRef8(false);
4171
4255
  const orbitRef = useRef8(null);
4172
4256
  const tmpPos = useMemo11(() => new Vector36(), []);
4173
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]);
4174
4272
  useEffect11(() => {
4175
4273
  if (!playing) {
4176
4274
  startedRef.current = false;
4177
- if (elapsedRef.current > 0) {
4275
+ if (elapsedRef.current > 0 && !resetOnPlay) {
4178
4276
  pausedElapsedRef.current = elapsedRef.current;
4277
+ } else if (resetOnPlay) {
4278
+ pausedElapsedRef.current = 0;
4279
+ }
4280
+ if (elapsedRef.current > 0 || hasFinishedRef.current) {
4281
+ onStop?.();
4179
4282
  }
4180
4283
  return;
4181
4284
  }
4285
+ if (elapsedRef.current >= duration && !loop) {
4286
+ elapsedRef.current = 0;
4287
+ pausedElapsedRef.current = 0;
4288
+ hasFinishedRef.current = false;
4289
+ }
4182
4290
  const controls = controlsRef.current;
4183
4291
  if (controls) {
4184
4292
  controls.getPosition(enterPos.current);
4185
4293
  controls.getTarget(enterTarget.current);
4186
4294
  }
4187
- 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
+ }
4188
4305
  enterElapsedRef.current = 0;
4189
4306
  if (!hasPath) {
4190
4307
  const scene = sceneRef.current;
@@ -4198,12 +4315,26 @@ function CinematicCamera({
4198
4315
  }
4199
4316
  startedRef.current = true;
4200
4317
  invalidate();
4201
- }, [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
+ ]);
4202
4330
  useEffect11(() => {
4203
4331
  if (!playing || !onUserInteract) return;
4204
4332
  const controls = controlsRef.current;
4205
4333
  if (!controls) return;
4206
- const handle = () => onUserInteract();
4334
+ const handle = () => {
4335
+ if (hasFinishedRef.current) return;
4336
+ onUserInteract();
4337
+ };
4207
4338
  controls.addEventListener("controlstart", handle);
4208
4339
  return () => controls.removeEventListener("controlstart", handle);
4209
4340
  }, [playing, controlsRef, onUserInteract]);
@@ -4213,12 +4344,33 @@ function CinematicCamera({
4213
4344
  if (!controls) return;
4214
4345
  elapsedRef.current += delta;
4215
4346
  enterElapsedRef.current += delta;
4216
- let u = elapsedRef.current / duration;
4217
- const finished = !loop && u >= 1;
4347
+ let u = getCurveParameter(elapsedRef.current);
4348
+ const finished = !loop && elapsedRef.current >= duration;
4218
4349
  u = loop ? u % 1 : Math.min(u, 1);
4219
4350
  if (curves) {
4220
- curves.position.getPointAt(u, tmpPos);
4221
- 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
+ }
4222
4374
  } else {
4223
4375
  const orbit = orbitRef.current;
4224
4376
  if (!orbit) return;
@@ -4248,6 +4400,8 @@ function CinematicCamera({
4248
4400
  );
4249
4401
  if (finished) {
4250
4402
  startedRef.current = false;
4403
+ hasFinishedRef.current = true;
4404
+ elapsedRef.current = 0;
4251
4405
  pausedElapsedRef.current = 0;
4252
4406
  onFinished?.();
4253
4407
  return;
@@ -5648,9 +5802,11 @@ function ModelViewer({
5648
5802
  return { ...base ?? {}, ...cinematic };
5649
5803
  }, [cinematic, resolvedSceneConfig]);
5650
5804
  const [cinematicPlaying, setCinematicPlaying] = useState10(false);
5805
+ const [cinematicReset, setCinematicReset] = useState10(false);
5651
5806
  const cinematicAutoStartedRef = useRef11(false);
5652
5807
  useEffect15(() => {
5653
5808
  setCinematicPlaying(false);
5809
+ setCinematicReset(false);
5654
5810
  cinematicAutoStartedRef.current = false;
5655
5811
  }, [modelUrl]);
5656
5812
  useEffect15(() => {
@@ -5658,6 +5814,7 @@ function ModelViewer({
5658
5814
  if (modelLoaded && cameraPositioned && !isAutoFraming) {
5659
5815
  cinematicAutoStartedRef.current = true;
5660
5816
  setCinematicPlaying(true);
5817
+ setCinematicReset(false);
5661
5818
  }
5662
5819
  }, [cinematicConfig, modelLoaded, cameraPositioned, isAutoFraming]);
5663
5820
  const xrStore = useMemo13(() => {
@@ -6811,6 +6968,20 @@ function ModelViewer({
6811
6968
  console.error("Reset camera error:", error);
6812
6969
  }
6813
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]);
6814
6985
  const handleCameraChangeWithAnnotations = useCallback4(
6815
6986
  (cameraValue, controls) => {
6816
6987
  handleCameraChangeInternal(cameraValue, controls);
@@ -7127,8 +7298,14 @@ function ModelViewer({
7127
7298
  sceneRef,
7128
7299
  config: cinematicConfig,
7129
7300
  playing: cinematicPlaying,
7130
- onFinished: () => setCinematicPlaying(false),
7131
- onUserInteract: () => setCinematicPlaying(false)
7301
+ resetOnPlay: cinematicReset,
7302
+ onFinished: () => {
7303
+ setCinematicPlaying(false);
7304
+ setCinematicReset(false);
7305
+ },
7306
+ onUserInteract: () => {
7307
+ setCinematicPlaying(false);
7308
+ }
7132
7309
  }
7133
7310
  ),
7134
7311
  showMeasureTools && /* @__PURE__ */ jsx19(
@@ -7356,24 +7533,43 @@ function ModelViewer({
7356
7533
  ]
7357
7534
  }
7358
7535
  ),
7359
- cinematicConfig && /* @__PURE__ */ jsx19(
7536
+ cinematicConfig && /* @__PURE__ */ jsxs15(
7360
7537
  "div",
7361
7538
  {
7362
7539
  className: "ri-cinematicTools",
7363
7540
  style: {
7364
7541
  top: `${topLeftRow((xrToolsVisible ? 1 : 0) + (showMeasureTools ? 1 : 0) + (showExplodeControls ? 1 : 0))}px`
7365
7542
  },
7366
- children: /* @__PURE__ */ jsx19(
7367
- "button",
7368
- {
7369
- type: "button",
7370
- className: `ri-measureButton ${cinematicPlaying ? "ri-measureButton--active" : ""}`,
7371
- onClick: () => setCinematicPlaying((prev) => !prev),
7372
- title: cinematicPlaying ? "Pause cinematic" : "Play cinematic",
7373
- "aria-pressed": cinematicPlaying,
7374
- children: cinematicPlaying ? /* @__PURE__ */ jsx19(Pause, { size: 16 }) : /* @__PURE__ */ jsx19(Play, { size: 16 })
7375
- }
7376
- )
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
+ ]
7377
7573
  }
7378
7574
  ),
7379
7575
  (showResetButton || showDownloadButton || showAnnotationNavigation && sortedAnnotations.length > 0) && /* @__PURE__ */ jsxs15("div", { className: "ri-buttonContainer", children: [
@@ -7613,7 +7809,7 @@ function ModelViewer({
7613
7809
  }
7614
7810
 
7615
7811
  // src/components/BindingBuilder/BindingBuilder.tsx
7616
- 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";
7617
7813
  import {
7618
7814
  Redo2,
7619
7815
  Undo2,
@@ -7660,14 +7856,16 @@ function buildCameraState(camera, controls) {
7660
7856
  position: toTuple(position),
7661
7857
  target: toTuple(target),
7662
7858
  fov: "fov" in camera && typeof camera.fov === "number" ? camera.fov : null,
7663
- 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
7664
7862
  };
7665
7863
  }
7666
7864
  function isSameCameraState(left, right) {
7667
7865
  if (!left) {
7668
7866
  return false;
7669
7867
  }
7670
- 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;
7671
7869
  }
7672
7870
  async function runSmoothCameraTransition2(controls, action, smoothTime = 0.6) {
7673
7871
  const originalSmoothTime = controls.smoothTime;
@@ -7822,15 +8020,27 @@ function useViewerCamera() {
7822
8020
  }
7823
8021
  try {
7824
8022
  markInitialAutoFitOverride(controls);
7825
- await controls.setLookAt(
7826
- state.position[0],
7827
- state.position[1],
7828
- state.position[2],
7829
- state.target[0],
7830
- state.target[1],
7831
- state.target[2],
7832
- enableTransition
7833
- );
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);
7834
8044
  const camera = controls.camera;
7835
8045
  let projectionChanged = false;
7836
8046
  if (state.fov !== null && "fov" in camera && typeof camera.fov === "number") {
@@ -8138,7 +8348,7 @@ function inferMimeType(path) {
8138
8348
  }
8139
8349
 
8140
8350
  // src/components/BindingBuilder/SceneSettings/SceneSettingsTab.tsx
8141
- import { useState as useState19, useEffect as useEffect18 } from "react";
8351
+ import { useState as useState20, useEffect as useEffect18 } from "react";
8142
8352
 
8143
8353
  // src/components/BindingBuilder/SceneSettings/GeneralSceneSettings.tsx
8144
8354
  import { useState as useState14 } from "react";
@@ -8324,9 +8534,10 @@ function SliderRow({
8324
8534
  max,
8325
8535
  step,
8326
8536
  formatValue,
8327
- onChange
8537
+ onChange,
8538
+ disabled = false
8328
8539
  }) {
8329
- return /* @__PURE__ */ jsxs16("label", { className: "se-field", children: [
8540
+ return /* @__PURE__ */ jsxs16("label", { className: `se-field ${disabled ? "se-field--disabled" : ""}`, children: [
8330
8541
  /* @__PURE__ */ jsxs16("span", { className: "se-fieldLabel", children: [
8331
8542
  label,
8332
8543
  /* @__PURE__ */ jsx20("span", { className: "se-fieldValue", children: formatValue ? formatValue(value) : value })
@@ -8340,7 +8551,8 @@ function SliderRow({
8340
8551
  max,
8341
8552
  step,
8342
8553
  value,
8343
- onChange: (e) => onChange(Number(e.target.value))
8554
+ onChange: (e) => onChange(Number(e.target.value)),
8555
+ disabled
8344
8556
  }
8345
8557
  )
8346
8558
  ] });
@@ -10386,7 +10598,7 @@ function RampSection({
10386
10598
  PPSection,
10387
10599
  {
10388
10600
  title: "Ramp",
10389
- tooltip: "Maps shading through a custom color ramp or gradient.",
10601
+ tooltip: "Adds a screen-space color gradient, or masks effects earlier in the stack.",
10390
10602
  open: open.ramp,
10391
10603
  onToggle: () => toggle("ramp"),
10392
10604
  disabled: !ramp.enabled,
@@ -10446,6 +10658,17 @@ function RampSection({
10446
10658
  onChange: (v) => set("ramp", { ...ramp, endColor: v })
10447
10659
  }
10448
10660
  ),
10661
+ !ramp.rampMask && /* @__PURE__ */ jsx25(
10662
+ SliderRow,
10663
+ {
10664
+ label: "Gradient Opacity",
10665
+ value: ramp.opacity ?? 0.5,
10666
+ min: 0,
10667
+ max: 1,
10668
+ step: 0.01,
10669
+ onChange: (v) => set("ramp", { ...ramp, opacity: v })
10670
+ }
10671
+ ),
10449
10672
  /* @__PURE__ */ jsx25(
10450
10673
  SliderRow,
10451
10674
  {
@@ -10471,7 +10694,7 @@ function RampSection({
10471
10694
  /* @__PURE__ */ jsx25(
10472
10695
  ToggleRow,
10473
10696
  {
10474
- label: "Mask",
10697
+ label: "Mask Earlier Effects",
10475
10698
  checked: ramp.rampMask,
10476
10699
  onChange: (v) => set("ramp", { ...ramp, rampMask: v })
10477
10700
  }
@@ -11745,14 +11968,16 @@ function AnnotationsTab({ config, onChange }) {
11745
11968
  }
11746
11969
 
11747
11970
  // src/components/BindingBuilder/SceneSettings/CinematicTab.tsx
11748
- import { ArrowDown, ArrowUp, MapPin as MapPin2, Plus as Plus2, Trash2 as Trash24 } from "lucide-react";
11749
- 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";
11750
11974
  var DEFAULT_CINEMATIC = {
11751
11975
  enabled: false,
11752
11976
  waypoints: [],
11753
11977
  loop: true,
11754
11978
  autoPlay: false
11755
11979
  };
11980
+ var DEFAULT_SEGMENT_DURATION = 3.5;
11756
11981
  function CinematicTab({
11757
11982
  config,
11758
11983
  onChange,
@@ -11761,31 +11986,76 @@ function CinematicTab({
11761
11986
  }) {
11762
11987
  const cinematic = config.cinematic ?? DEFAULT_CINEMATIC;
11763
11988
  const { waypoints } = cinematic;
11764
- 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) => {
11765
12002
  onChange({ ...config, cinematic: { ...cinematic, ...patch } });
11766
- };
11767
- const addWaypoint = () => {
12003
+ }, [config, cinematic, onChange]);
12004
+ const addWaypoint = useCallback7(() => {
11768
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);
11769
12015
  update({
11770
- waypoints: [
11771
- ...waypoints,
11772
- {
11773
- position: [...cameraState.position],
11774
- target: [...cameraState.target]
11775
- }
11776
- ]
12016
+ waypoints: newWaypoints,
12017
+ duration: newDuration
11777
12018
  });
11778
- };
11779
- const deleteWaypoint = (index) => {
11780
- update({ waypoints: waypoints.filter((_, i) => i !== index) });
11781
- };
11782
- 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) => {
11783
12029
  const next = index + delta;
11784
12030
  if (next < 0 || next >= waypoints.length) return;
11785
12031
  const reordered = [...waypoints];
11786
12032
  [reordered[index], reordered[next]] = [reordered[next], reordered[index]];
11787
- update({ waypoints: reordered });
11788
- };
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;
11789
12059
  return /* @__PURE__ */ jsxs27(Section, { title: "Cinematic Camera", children: [
11790
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." }),
11791
12061
  /* @__PURE__ */ jsxs27(
@@ -11802,62 +12072,165 @@ function CinematicTab({
11802
12072
  ]
11803
12073
  }
11804
12074
  ),
11805
- 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: [
11806
- /* @__PURE__ */ jsx31("div", { className: "se-waypointBadge", children: index + 1 }),
11807
- /* @__PURE__ */ jsxs27("div", { className: "se-waypointBody", children: [
11808
- /* @__PURE__ */ jsxs27("div", { className: "se-waypointCoords", children: [
11809
- "pos [",
11810
- waypoint.position.map((n) => n.toFixed(1)).join(", "),
11811
- "]"
11812
- ] }),
11813
- /* @__PURE__ */ jsxs27("div", { className: "se-waypointCoords", children: [
11814
- "look [",
11815
- waypoint.target.map((n) => n.toFixed(1)).join(", "),
11816
- "]"
11817
- ] })
11818
- ] }),
11819
- /* @__PURE__ */ jsxs27("div", { className: "se-waypointActions", children: [
12075
+ /* @__PURE__ */ jsxs27("div", { className: "se-customizeToggle", children: [
12076
+ /* @__PURE__ */ jsxs27("label", { className: "se-toggleLabel", children: [
11820
12077
  /* @__PURE__ */ jsx31(
11821
- "button",
11822
- {
11823
- className: "se-waypointAction",
11824
- onClick: () => onPreviewWaypoint?.(waypoint),
11825
- title: "Fly camera to this waypoint",
11826
- disabled: !onPreviewWaypoint,
11827
- children: /* @__PURE__ */ jsx31(MapPin2, { size: 15 })
11828
- }
11829
- ),
11830
- /* @__PURE__ */ jsx31(
11831
- "button",
11832
- {
11833
- className: "se-waypointAction",
11834
- onClick: () => moveWaypoint(index, -1),
11835
- title: "Move up",
11836
- disabled: index === 0,
11837
- children: /* @__PURE__ */ jsx31(ArrowUp, { size: 15 })
11838
- }
11839
- ),
11840
- /* @__PURE__ */ jsx31(
11841
- "button",
12078
+ "input",
11842
12079
  {
11843
- className: "se-waypointAction",
11844
- onClick: () => moveWaypoint(index, 1),
11845
- title: "Move down",
11846
- disabled: index === waypoints.length - 1,
11847
- children: /* @__PURE__ */ jsx31(ArrowDown, { size: 15 })
12080
+ type: "checkbox",
12081
+ checked: customizeDurations,
12082
+ onChange: (e) => setCustomizeDurations(e.target.checked)
11848
12083
  }
11849
12084
  ),
11850
- /* @__PURE__ */ jsx31(
11851
- "button",
11852
- {
11853
- className: "se-waypointAction se-waypointAction--delete",
11854
- onClick: () => deleteWaypoint(index),
11855
- title: "Delete waypoint",
11856
- children: /* @__PURE__ */ jsx31(Trash24, { size: 15 })
11857
- }
11858
- )
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"
12092
+ ] })
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)" })
11859
12232
  ] })
11860
- ] }, index)) }),
12233
+ ] }),
11861
12234
  /* @__PURE__ */ jsx31(
11862
12235
  ToggleRow,
11863
12236
  {
@@ -11871,7 +12244,11 @@ function CinematicTab({
11871
12244
  {
11872
12245
  label: "Loop",
11873
12246
  checked: cinematic.loop,
11874
- onChange: (loop) => update({ loop })
12247
+ onChange: (loop) => {
12248
+ const newLoop = loop;
12249
+ const newDuration = calculateTotalDuration(waypoints, newLoop);
12250
+ update({ loop: newLoop, duration: newDuration });
12251
+ }
11875
12252
  }
11876
12253
  ),
11877
12254
  /* @__PURE__ */ jsx31(
@@ -11882,11 +12259,11 @@ function CinematicTab({
11882
12259
  onChange: (autoPlay) => update({ autoPlay })
11883
12260
  }
11884
12261
  ),
11885
- /* @__PURE__ */ jsx31(
12262
+ !customizeDurations && hasEnoughWaypoints && /* @__PURE__ */ jsx31(
11886
12263
  SliderRow,
11887
12264
  {
11888
12265
  label: "Duration",
11889
- value: cinematic.duration ?? Math.max(waypoints.length * 3.5, 8),
12266
+ value: cinematic.duration ?? calculateTotalDuration(waypoints, cinematic.loop),
11890
12267
  min: 4,
11891
12268
  max: 120,
11892
12269
  step: 1,
@@ -11898,7 +12275,7 @@ function CinematicTab({
11898
12275
  }
11899
12276
 
11900
12277
  // src/styles/SceneSettingsTab.css
11901
- 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');
11902
12279
 
11903
12280
  // src/components/BindingBuilder/SceneSettings/SceneSettingsTab.tsx
11904
12281
  import {
@@ -11944,7 +12321,7 @@ function SceneSettingsTab({
11944
12321
  animationClipDetails = [],
11945
12322
  animationPlayback = FALLBACK_ANIMATION_PLAYBACK
11946
12323
  }) {
11947
- const [internalActiveSubTab, setInternalActiveSubTab] = useState19("General");
12324
+ const [internalActiveSubTab, setInternalActiveSubTab] = useState20("General");
11948
12325
  const activeSubTab = controlledActiveSubTab ?? internalActiveSubTab;
11949
12326
  const resolvedAnimationPlayback = animationPlayback ?? FALLBACK_ANIMATION_PLAYBACK;
11950
12327
  const { tier } = useBindingBuilderContext();
@@ -12130,7 +12507,7 @@ function EnableToggle2({
12130
12507
  }
12131
12508
 
12132
12509
  // src/components/BindingBuilder/components/MaterialSection.tsx
12133
- import { useState as useState21 } from "react";
12510
+ import { useState as useState22 } from "react";
12134
12511
 
12135
12512
  // src/components/ModelViewer/types/objectBinding.ts
12136
12513
  var MATERIAL_SIDES = ["front", "back", "double"];
@@ -12177,7 +12554,7 @@ function MaterialPanelGroup({
12177
12554
  }
12178
12555
 
12179
12556
  // src/components/BindingBuilder/components/MaterialFields.tsx
12180
- import { useRef as useRef15, useState as useState20 } from "react";
12557
+ import { useRef as useRef15, useState as useState21 } from "react";
12181
12558
  import { HexColorInput as HexColorInput3, HexColorPicker as HexColorPicker4 } from "react-colorful";
12182
12559
  import { FileUp } from "lucide-react";
12183
12560
  import { jsx as jsx35, jsxs as jsxs30 } from "react/jsx-runtime";
@@ -12244,7 +12621,7 @@ function MaterialColorField({
12244
12621
  fallback = "#ffffff",
12245
12622
  onChange
12246
12623
  }) {
12247
- const [open, setOpen] = useState20(false);
12624
+ const [open, setOpen] = useState21(false);
12248
12625
  const color = value || fallback;
12249
12626
  const anchorRef = useRef15(null);
12250
12627
  return /* @__PURE__ */ jsxs30("div", { className: "bb-field", children: [
@@ -12373,7 +12750,7 @@ function MaterialSliderField({
12373
12750
  }
12374
12751
 
12375
12752
  // src/components/BindingBuilder/components/MaterialSection.tsx
12376
- 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";
12377
12754
  function MaterialSection({
12378
12755
  selectedItem,
12379
12756
  selectedMaterial,
@@ -12398,11 +12775,11 @@ function MaterialSection({
12398
12775
  tier: "starter",
12399
12776
  locked: !canUseStarterFeatures
12400
12777
  };
12401
- const [roughnessMode, setRoughnessMode] = useState21("roughness");
12402
- const [anisotropyMode, setAnisotropyMode] = useState21(
12778
+ const [roughnessMode, setRoughnessMode] = useState22("roughness");
12779
+ const [anisotropyMode, setAnisotropyMode] = useState22(
12403
12780
  "factor"
12404
12781
  );
12405
- return /* @__PURE__ */ jsxs31(Fragment10, { children: [
12782
+ return /* @__PURE__ */ jsxs31(Fragment11, { children: [
12406
12783
  /* @__PURE__ */ jsx36(
12407
12784
  MaterialPanelGroup,
12408
12785
  {
@@ -12441,7 +12818,7 @@ function MaterialSection({
12441
12818
  }))
12442
12819
  }
12443
12820
  ),
12444
- pbrWorkflow === "metalness" ? /* @__PURE__ */ jsxs31(Fragment10, { children: [
12821
+ pbrWorkflow === "metalness" ? /* @__PURE__ */ jsxs31(Fragment11, { children: [
12445
12822
  /* @__PURE__ */ jsx36(
12446
12823
  MaterialSliderField,
12447
12824
  {
@@ -12465,7 +12842,7 @@ function MaterialSection({
12465
12842
  onChange: (value) => setSelectedTexturePath(selectedItem, "metalnessMap", value)
12466
12843
  }
12467
12844
  )
12468
- ] }) : /* @__PURE__ */ jsxs31(Fragment10, { children: [
12845
+ ] }) : /* @__PURE__ */ jsxs31(Fragment11, { children: [
12469
12846
  /* @__PURE__ */ jsx36(
12470
12847
  MaterialSliderField,
12471
12848
  {
@@ -13438,7 +13815,7 @@ function MaterialPresetGallery({
13438
13815
  }
13439
13816
 
13440
13817
  // src/components/BindingBuilder/components/ToolbarExportMenu.tsx
13441
- 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";
13442
13819
  import { ChevronDown as ChevronDown5, Download as Download2, FileCode2, FileJson } from "lucide-react";
13443
13820
  import { jsx as jsx38, jsxs as jsxs33 } from "react/jsx-runtime";
13444
13821
  function ToolbarExportMenu({
@@ -13447,7 +13824,7 @@ function ToolbarExportMenu({
13447
13824
  onExportJson,
13448
13825
  onExportTs
13449
13826
  }) {
13450
- const [open, setOpen] = useState22(false);
13827
+ const [open, setOpen] = useState23(false);
13451
13828
  const wrapperRef = useRef16(null);
13452
13829
  useEffect19(() => {
13453
13830
  if (!open) return;
@@ -13781,7 +14158,7 @@ function ActionsPanel({
13781
14158
 
13782
14159
  // src/components/BindingBuilder/components/CameraPositionPanel.tsx
13783
14160
  import { MapPin as MapPin3, Trash2 as Trash25 } from "lucide-react";
13784
- 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";
13785
14162
  function CameraPositionPanel({
13786
14163
  selectedBinding,
13787
14164
  open,
@@ -13800,7 +14177,7 @@ function CameraPositionPanel({
13800
14177
  /* @__PURE__ */ jsx42("span", { className: "bb-cameraLiveBadge", children: "LIVE" }),
13801
14178
  /* @__PURE__ */ jsxs37("div", { className: "se-waypointBody", children: [
13802
14179
  /* @__PURE__ */ jsx42("div", { className: "bb-cameraWaypointLabel", children: "Current view" }),
13803
- cameraState ? /* @__PURE__ */ jsxs37(Fragment11, { children: [
14180
+ cameraState ? /* @__PURE__ */ jsxs37(Fragment12, { children: [
13804
14181
  /* @__PURE__ */ jsxs37("div", { className: "se-waypointCoords", children: [
13805
14182
  "pos ",
13806
14183
  formattedCameraState?.position
@@ -13886,7 +14263,7 @@ function CameraPositionPanel({
13886
14263
  }
13887
14264
 
13888
14265
  // src/components/BindingBuilder/components/SceneNodesPanel.tsx
13889
- import { useMemo as useMemo15, useState as useState23 } from "react";
14266
+ import { useMemo as useMemo15, useState as useState24 } from "react";
13890
14267
 
13891
14268
  // src/components/BindingBuilder/utils/bindingFactory.ts
13892
14269
  function slugify(value) {
@@ -13966,7 +14343,7 @@ function collectNodeNames(scene) {
13966
14343
  }
13967
14344
 
13968
14345
  // src/components/BindingBuilder/components/SceneNodesPanel.tsx
13969
- 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";
13970
14347
  function SceneNodesPanel({
13971
14348
  bindings,
13972
14349
  selectedItem,
@@ -13975,7 +14352,7 @@ function SceneNodesPanel({
13975
14352
  isOpen = true,
13976
14353
  onToggle
13977
14354
  }) {
13978
- const [search, setSearch] = useState23("");
14355
+ const [search, setSearch] = useState24("");
13979
14356
  const nodeKeys = useMemo15(() => Object.keys(bindings), [bindings]);
13980
14357
  const filteredNodeKeys = useMemo15(() => {
13981
14358
  const query = search.trim().toLowerCase();
@@ -14002,7 +14379,7 @@ function SceneNodesPanel({
14002
14379
  children: "\u2190"
14003
14380
  }
14004
14381
  ),
14005
- isOpen && /* @__PURE__ */ jsxs38(Fragment12, { children: [
14382
+ isOpen && /* @__PURE__ */ jsxs38(Fragment13, { children: [
14006
14383
  /* @__PURE__ */ jsxs38("div", { children: [
14007
14384
  /* @__PURE__ */ jsx43("div", { className: "bb-sectionKicker", children: "Scene Nodes" }),
14008
14385
  /* @__PURE__ */ jsxs38("div", { className: "bb-sectionHeading", children: [
@@ -14120,7 +14497,7 @@ function mergeAnimationConfig(sceneConfig, animationDetails) {
14120
14497
  }
14121
14498
 
14122
14499
  // src/components/BindingBuilder/hooks/useUndoableState.ts
14123
- import { useCallback as useCallback7, useReducer, useRef as useRef17 } from "react";
14500
+ import { useCallback as useCallback8, useReducer, useRef as useRef17 } from "react";
14124
14501
  var MAX_HISTORY = 100;
14125
14502
  function historyReducer(state, action) {
14126
14503
  switch (action.type) {
@@ -14165,7 +14542,7 @@ function useUndoableState(initial, coalesceWindowMs = 600) {
14165
14542
  future: []
14166
14543
  });
14167
14544
  const lastSetAt = useRef17(0);
14168
- const setValue = useCallback7(
14545
+ const setValue = useCallback8(
14169
14546
  (updater) => {
14170
14547
  const now = Date.now();
14171
14548
  const coalesce = now - lastSetAt.current < coalesceWindowMs;
@@ -14174,15 +14551,15 @@ function useUndoableState(initial, coalesceWindowMs = 600) {
14174
14551
  },
14175
14552
  [coalesceWindowMs]
14176
14553
  );
14177
- const undo = useCallback7(() => {
14554
+ const undo = useCallback8(() => {
14178
14555
  lastSetAt.current = 0;
14179
14556
  dispatch({ type: "undo" });
14180
14557
  }, []);
14181
- const redo = useCallback7(() => {
14558
+ const redo = useCallback8(() => {
14182
14559
  lastSetAt.current = 0;
14183
14560
  dispatch({ type: "redo" });
14184
14561
  }, []);
14185
- const reset = useCallback7((value) => {
14562
+ const reset = useCallback8((value) => {
14186
14563
  lastSetAt.current = 0;
14187
14564
  dispatch({ type: "reset", value });
14188
14565
  }, []);
@@ -14411,7 +14788,7 @@ function removeMaterialPanelFields(material, panel) {
14411
14788
  }
14412
14789
 
14413
14790
  // src/components/BindingBuilder/BindingBuilder.tsx
14414
- 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";
14415
14792
  var DEMO_MODEL_URL = "/room.glb";
14416
14793
  function isPlainObject(value) {
14417
14794
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -14431,27 +14808,27 @@ function BindingBuilder({ licenseKey }) {
14431
14808
  const modelInputRef = useRef18(null);
14432
14809
  const bindingsInputRef = useRef18(null);
14433
14810
  const sceneConfigInputRef = useRef18(null);
14434
- const [modelUrl, setModelUrl] = useState24(null);
14435
- const [modelName, setModelName] = useState24("");
14436
- const [selectedItem, setSelectedItem] = useState24(null);
14811
+ const [modelUrl, setModelUrl] = useState25(null);
14812
+ const [modelName, setModelName] = useState25("");
14813
+ const [selectedItem, setSelectedItem] = useState25(null);
14437
14814
  const bindingsHistory = useUndoableState({});
14438
14815
  const bindings = bindingsHistory.value;
14439
14816
  const setBindings = bindingsHistory.setValue;
14440
- const [loading, setLoading] = useState24(false);
14441
- const [isExporting, setIsExporting] = useState24(false);
14442
- const [statusMessage, setStatusMessage] = useState24(
14817
+ const [loading, setLoading] = useState25(false);
14818
+ const [isExporting, setIsExporting] = useState25(false);
14819
+ const [statusMessage, setStatusMessage] = useState25(
14443
14820
  "Load the demo model or upload a GLB/GLTF model to generate bindings."
14444
14821
  );
14445
- const [statusTone, setStatusTone] = useState24("default");
14446
- const [statusBannerVisible, setStatusBannerVisible] = useState24(false);
14447
- const [metricsDraft, setMetricsDraft] = useState24("{}");
14448
- const [metadataDraft, setMetadataDraft] = useState24("{}");
14449
- const [metricsError, setMetricsError] = useState24(null);
14450
- const [metadataError, setMetadataError] = useState24(null);
14451
- const [isPreviewDragActive, setIsPreviewDragActive] = useState24(false);
14452
- const [isEditorCollapsed, setIsEditorCollapsed] = useState24(false);
14453
- const [isNodesCollapsed, setIsNodesCollapsed] = useState24(false);
14454
- 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({
14455
14832
  identity: true,
14456
14833
  basics: true,
14457
14834
  material: true,
@@ -14460,7 +14837,7 @@ function BindingBuilder({ licenseKey }) {
14460
14837
  metrics: false,
14461
14838
  metadata: false
14462
14839
  });
14463
- const [openMaterialPanels, setOpenMaterialPanels] = useState24({
14840
+ const [openMaterialPanels, setOpenMaterialPanels] = useState25({
14464
14841
  pbr: true,
14465
14842
  roughness: false,
14466
14843
  emission: false,
@@ -14476,24 +14853,24 @@ function BindingBuilder({ licenseKey }) {
14476
14853
  });
14477
14854
  const toggleObjectSection = (key) => setOpenObjectSections((prev) => ({ ...prev, [key]: !prev[key] }));
14478
14855
  const toggleMaterialPanel = (key) => setOpenMaterialPanels((prev) => ({ ...prev, [key]: !prev[key] }));
14479
- const [activeEditorTab, setActiveEditorTab] = useState24(
14856
+ const [activeEditorTab, setActiveEditorTab] = useState25(
14480
14857
  "object"
14481
14858
  );
14482
- const [activeSubTab, setActiveSubTab] = useState24("General");
14483
- const [pbrWorkflow, setPbrWorkflow] = useState24("metalness");
14484
- const [materialPanelEnabled, setMaterialPanelEnabled] = useState24(createMaterialPanelEnabledState());
14859
+ const [activeSubTab, setActiveSubTab] = useState25("General");
14860
+ const [pbrWorkflow, setPbrWorkflow] = useState25("metalness");
14861
+ const [materialPanelEnabled, setMaterialPanelEnabled] = useState25(createMaterialPanelEnabledState());
14485
14862
  const materialPanelItemRef = useRef18(null);
14486
- const [zoomOnSelected, setZoomOnSelected] = useState24(false);
14487
- const [highlightOnHover, setHighlightOnHover] = useState24(true);
14488
- const [resetDialogOpen, setResetDialogOpen] = useState24(false);
14489
- 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(
14490
14867
  null
14491
14868
  );
14492
14869
  const sceneConfigHistory = useUndoableState(DEFAULT_SCENE_CONFIG);
14493
14870
  const sceneConfig = sceneConfigHistory.value;
14494
14871
  const setSceneConfig = sceneConfigHistory.setValue;
14495
- const [activeAnnotation, setActiveAnnotation] = useState24(null);
14496
- const [panelHoveredBinding, setPanelHoveredBinding] = useState24(null);
14872
+ const [activeAnnotation, setActiveAnnotation] = useState25(null);
14873
+ const [panelHoveredBinding, setPanelHoveredBinding] = useState25(null);
14497
14874
  const license = useLicense(licenseKey);
14498
14875
  const tier = normalizeTier(license.tier);
14499
14876
  const bindingBuilderContextValue = useMemo16(
@@ -14519,14 +14896,16 @@ function BindingBuilder({ licenseKey }) {
14519
14896
  handleCameraChange,
14520
14897
  handleViewerReady
14521
14898
  } = useViewerCamera();
14522
- const handlePreviewWaypoint = useCallback8(
14899
+ const handlePreviewWaypoint = useCallback9(
14523
14900
  (waypoint) => {
14524
14901
  setCameraState(
14525
14902
  {
14526
14903
  position: waypoint.position,
14527
14904
  target: waypoint.target,
14528
14905
  fov: cameraState?.fov ?? null,
14529
- zoom: cameraState?.zoom ?? 1
14906
+ zoom: cameraState?.zoom ?? 1,
14907
+ azimuthAngle: waypoint.azimuthAngle,
14908
+ polarAngle: waypoint.polarAngle
14530
14909
  },
14531
14910
  true
14532
14911
  );
@@ -14541,7 +14920,7 @@ function BindingBuilder({ licenseKey }) {
14541
14920
  position: formatVector3(cameraState.position),
14542
14921
  target: formatVector3(cameraState.target)
14543
14922
  } : null;
14544
- const handleAnnotationsChange = useCallback8(
14923
+ const handleAnnotationsChange = useCallback9(
14545
14924
  (newAnnotations) => {
14546
14925
  setSceneConfig((prev) => {
14547
14926
  const currentIds = prev.annotations.map((a) => a.id).join(",");
@@ -14557,7 +14936,7 @@ function BindingBuilder({ licenseKey }) {
14557
14936
  },
14558
14937
  []
14559
14938
  );
14560
- const handleActiveAnnotationChange = useCallback8(
14939
+ const handleActiveAnnotationChange = useCallback9(
14561
14940
  (annotation) => {
14562
14941
  setActiveAnnotation(
14563
14942
  annotation ? {
@@ -14678,7 +15057,7 @@ function BindingBuilder({ licenseKey }) {
14678
15057
  });
14679
15058
  }
14680
15059
  };
14681
- const applyBindingsFromScene = useCallback8(
15060
+ const applyBindingsFromScene = useCallback9(
14682
15061
  (scene, nextModelName) => {
14683
15062
  const nodeNames = collectNodeNames(scene);
14684
15063
  bindingsHistory.reset(buildBindingsFromNodes(nodeNames));
@@ -14717,7 +15096,7 @@ function BindingBuilder({ licenseKey }) {
14717
15096
  bindingsHistory.reset({});
14718
15097
  setSelectedItem(null);
14719
15098
  };
14720
- const handleViewerModelLoaded = useCallback8(
15099
+ const handleViewerModelLoaded = useCallback9(
14721
15100
  (scene) => {
14722
15101
  const pending = pendingLoadRef.current;
14723
15102
  if (!pending || pending.requestId !== loadRequestRef.current) return;
@@ -14727,7 +15106,7 @@ function BindingBuilder({ licenseKey }) {
14727
15106
  },
14728
15107
  [applyBindingsFromScene]
14729
15108
  );
14730
- const handleViewerLoadError = useCallback8((error) => {
15109
+ const handleViewerLoadError = useCallback9((error) => {
14731
15110
  const pending = pendingLoadRef.current;
14732
15111
  if (!pending || pending.requestId !== loadRequestRef.current) return;
14733
15112
  pendingLoadRef.current = null;
@@ -14738,7 +15117,7 @@ function BindingBuilder({ licenseKey }) {
14738
15117
  setStatusTone("error");
14739
15118
  setLoading(false);
14740
15119
  }, []);
14741
- const handleAnimationsReady = useCallback8(
15120
+ const handleAnimationsReady = useCallback9(
14742
15121
  (controls) => {
14743
15122
  viewerAnimations.handleAnimationsReady(controls);
14744
15123
  const details = controls.clipDetails ?? controls.clips.map((sourceName) => ({ sourceName, duration: 0 }));
@@ -15039,7 +15418,7 @@ function BindingBuilder({ licenseKey }) {
15039
15418
  }));
15040
15419
  setError(null);
15041
15420
  };
15042
- const handleViewerSelect = useCallback8(
15421
+ const handleViewerSelect = useCallback9(
15043
15422
  (binding) => {
15044
15423
  if (!binding) {
15045
15424
  setSelectedItem(null);
@@ -15052,7 +15431,7 @@ function BindingBuilder({ licenseKey }) {
15052
15431
  },
15053
15432
  [bindings]
15054
15433
  );
15055
- const handleSceneNodeSelect = useCallback8(
15434
+ const handleSceneNodeSelect = useCallback9(
15056
15435
  async (nodeKey) => {
15057
15436
  setSelectedItem(nodeKey);
15058
15437
  setPanelHoveredBinding(null);
@@ -15060,7 +15439,7 @@ function BindingBuilder({ licenseKey }) {
15060
15439
  },
15061
15440
  [focusObject]
15062
15441
  );
15063
- const saveCameraPositionForSelectedObject = useCallback8(() => {
15442
+ const saveCameraPositionForSelectedObject = useCallback9(() => {
15064
15443
  if (!selectedItem || !cameraState) {
15065
15444
  return;
15066
15445
  }
@@ -15075,7 +15454,7 @@ function BindingBuilder({ licenseKey }) {
15075
15454
  cameraState: currentCameraState
15076
15455
  }));
15077
15456
  }, [selectedItem, cameraState]);
15078
- const goToSavedCameraPosition = useCallback8(() => {
15457
+ const goToSavedCameraPosition = useCallback9(() => {
15079
15458
  if (!savedCameraPosition?.position || !savedCameraPosition?.target) return;
15080
15459
  setCameraState(
15081
15460
  {
@@ -15087,28 +15466,28 @@ function BindingBuilder({ licenseKey }) {
15087
15466
  true
15088
15467
  );
15089
15468
  }, [savedCameraPosition, setCameraState, cameraState]);
15090
- const clearSavedCameraPosition = useCallback8(() => {
15469
+ const clearSavedCameraPosition = useCallback9(() => {
15091
15470
  if (!selectedItem) return;
15092
15471
  updateBinding(selectedItem, (binding) => ({
15093
15472
  ...binding,
15094
15473
  cameraState: void 0
15095
15474
  }));
15096
15475
  }, [selectedItem]);
15097
- const handleResetView = useCallback8(async () => {
15476
+ const handleResetView = useCallback9(async () => {
15098
15477
  await resetView();
15099
15478
  }, [resetView]);
15100
- const handleFitScene = useCallback8(async () => {
15479
+ const handleFitScene = useCallback9(async () => {
15101
15480
  await fitScene();
15102
15481
  }, [fitScene]);
15103
- const openResetDialog = useCallback8((target) => {
15482
+ const openResetDialog = useCallback9((target) => {
15104
15483
  setResetTarget(target);
15105
15484
  setResetDialogOpen(true);
15106
15485
  }, []);
15107
- const closeResetDialog = useCallback8(() => {
15486
+ const closeResetDialog = useCallback9(() => {
15108
15487
  setResetDialogOpen(false);
15109
15488
  setResetTarget(null);
15110
15489
  }, []);
15111
- const executeReset = useCallback8(() => {
15490
+ const executeReset = useCallback9(() => {
15112
15491
  if (resetTarget === "bindings") {
15113
15492
  if (!nodeKeys.length) {
15114
15493
  closeResetDialog();
@@ -15205,7 +15584,7 @@ function BindingBuilder({ licenseKey }) {
15205
15584
  }
15206
15585
  )
15207
15586
  ] }),
15208
- !isEditorCollapsed && /* @__PURE__ */ jsxs40(Fragment13, { children: [
15587
+ !isEditorCollapsed && /* @__PURE__ */ jsxs40(Fragment14, { children: [
15209
15588
  /* @__PURE__ */ jsxs40("div", { className: "bb-tabRow", children: [
15210
15589
  /* @__PURE__ */ jsx45(
15211
15590
  "button",
@@ -15226,7 +15605,7 @@ function BindingBuilder({ licenseKey }) {
15226
15605
  }
15227
15606
  )
15228
15607
  ] }),
15229
- /* @__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(
15230
15609
  SceneSettingsTab,
15231
15610
  {
15232
15611
  config: sceneConfig,
@@ -15239,14 +15618,16 @@ function BindingBuilder({ licenseKey }) {
15239
15618
  // the whole settings panel for no reason.
15240
15619
  activeSubTab === "Cinematic" && cameraState ? {
15241
15620
  position: cameraState.position,
15242
- target: cameraState.target
15621
+ target: cameraState.target,
15622
+ azimuthAngle: cameraState.azimuthAngle,
15623
+ polarAngle: cameraState.polarAngle
15243
15624
  } : null
15244
15625
  ),
15245
15626
  onPreviewWaypoint: handlePreviewWaypoint,
15246
15627
  animationClipDetails: viewerAnimations.clipDetails,
15247
15628
  animationPlayback: viewerAnimations
15248
15629
  }
15249
- ) : /* @__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: [
15250
15631
  /* @__PURE__ */ jsx45("div", { className: "bb-editorHeader", children: /* @__PURE__ */ jsxs40("div", { children: [
15251
15632
  /* @__PURE__ */ jsx45("div", { className: "bb-sectionKicker", children: "Editing Binding" }),
15252
15633
  /* @__PURE__ */ jsx45("h2", { className: "bb-editorTitle", children: selectedBinding.label || selectedItem }),
@@ -15666,8 +16047,8 @@ function BindingBuilder({ licenseKey }) {
15666
16047
  // src/components/SimpleModelViewer/SimpleModelViewer.tsx
15667
16048
  import {
15668
16049
  useRef as useRef19,
15669
- useState as useState25,
15670
- useCallback as useCallback9,
16050
+ useState as useState26,
16051
+ useCallback as useCallback10,
15671
16052
  useEffect as useEffect21,
15672
16053
  useMemo as useMemo17,
15673
16054
  Component as Component3,
@@ -15690,14 +16071,14 @@ styleInject('.smv-root {\n --smv-panel-font-family:\n Inter,\n "Segoe UI"
15690
16071
  import {
15691
16072
  X as X3,
15692
16073
  Check,
15693
- Settings,
16074
+ Settings as Settings2,
15694
16075
  ChevronDown as ChevronDown6,
15695
16076
  Boxes as Boxes2,
15696
16077
  Trash2 as Trash26,
15697
16078
  Upload as Upload2,
15698
16079
  AlertTriangle as AlertTriangle4
15699
16080
  } from "lucide-react";
15700
- 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";
15701
16082
  var HIGHLIGHT2 = { color: "#93c5fd", intensity: 0.05 };
15702
16083
  var ENV_PRESETS = [
15703
16084
  "apartment",
@@ -15757,7 +16138,7 @@ function InnerModel({
15757
16138
  meshopt: true
15758
16139
  });
15759
16140
  const invalidate = useThree11((state) => state.invalidate);
15760
- const [hoveredKey, setHoveredKey] = useState25(null);
16141
+ const [hoveredKey, setHoveredKey] = useState26(null);
15761
16142
  const scene = useMemo17(
15762
16143
  () => SkeletonUtils2.clone(rawScene),
15763
16144
  [rawScene]
@@ -16059,7 +16440,7 @@ function EnvironmentSection({
16059
16440
  title,
16060
16441
  children
16061
16442
  }) {
16062
- const [collapsed, setCollapsed] = useState25(false);
16443
+ const [collapsed, setCollapsed] = useState26(false);
16063
16444
  return /* @__PURE__ */ jsxs41("div", { className: "env-section", children: [
16064
16445
  /* @__PURE__ */ jsxs41(
16065
16446
  "button",
@@ -16098,7 +16479,7 @@ function EnvironmentPanel({
16098
16479
  {
16099
16480
  className: "side-panel__open-btn side-panel__open-btn--left",
16100
16481
  onClick: () => onToggle(true),
16101
- children: /* @__PURE__ */ jsx46(Settings, { size: 20, strokeWidth: 1.8 })
16482
+ children: /* @__PURE__ */ jsx46(Settings2, { size: 20, strokeWidth: 1.8 })
16102
16483
  }
16103
16484
  ),
16104
16485
  /* @__PURE__ */ jsxs41(
@@ -16224,8 +16605,8 @@ function SceneObjectsPanel2({
16224
16605
  onHover,
16225
16606
  onToggleVisibility
16226
16607
  }) {
16227
- const [search, setSearch] = useState25("");
16228
- const [rowHovered, setRowHovered] = useState25(null);
16608
+ const [search, setSearch] = useState26("");
16609
+ const [rowHovered, setRowHovered] = useState26(null);
16229
16610
  const entries = Object.values(meshKeys).filter(
16230
16611
  (m) => m.label.toLowerCase().includes(search.toLowerCase())
16231
16612
  );
@@ -16354,30 +16735,30 @@ function SimpleModelViewer({
16354
16735
  const fileInputRef = useRef19(null);
16355
16736
  const uploadedModelRef = useRef19(null);
16356
16737
  const dragDepthRef = useRef19(0);
16357
- const [loadedScene, setLoadedScene] = useState25(null);
16358
- const [largeModelTriangles, setLargeModelTriangles] = useState25(
16738
+ const [loadedScene, setLoadedScene] = useState26(null);
16739
+ const [largeModelTriangles, setLargeModelTriangles] = useState26(
16359
16740
  null
16360
16741
  );
16361
- const [largeModelNoticeDismissed, setLargeModelNoticeDismissed] = useState25(false);
16362
- const [meshKeys, setMeshKeys] = useState25({});
16363
- const [hiddenKeys, setHiddenKeys] = useState25(/* @__PURE__ */ new Set());
16364
- const [selectedKey, setSelectedKey] = useState25(null);
16365
- const [panelHoveredKey, setPanelHoveredKey] = useState25(null);
16366
- const [uploadedModelUrl, setUploadedModelUrl] = useState25(null);
16367
- 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(
16368
16749
  null
16369
16750
  );
16370
- const [modelLoadFailed, setModelLoadFailed] = useState25(false);
16371
- const [isResolvingUpload, setIsResolvingUpload] = useState25(false);
16372
- const [initialFitComplete, setInitialFitComplete] = useState25(false);
16373
- const [isDragActive, setIsDragActive] = useState25(false);
16374
- 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(
16375
16756
  () => typeof window === "undefined" ? true : window.innerWidth > 900
16376
16757
  );
16377
- const [isEnvPanelOpen, setIsEnvPanelOpen] = useState25(
16758
+ const [isEnvPanelOpen, setIsEnvPanelOpen] = useState26(
16378
16759
  () => typeof window === "undefined" ? true : window.innerWidth > 900
16379
16760
  );
16380
- const [envSettings, setEnvSettings] = useState25({
16761
+ const [envSettings, setEnvSettings] = useState26({
16381
16762
  backgroundEnabled,
16382
16763
  backgroundColor,
16383
16764
  autoRotate,
@@ -16389,7 +16770,7 @@ function SimpleModelViewer({
16389
16770
  directionalColor
16390
16771
  });
16391
16772
  const activeModelUrl = enableModelUpload ? uploadedModelUrl : modelUrl;
16392
- const resetModelState = useCallback9(() => {
16773
+ const resetModelState = useCallback10(() => {
16393
16774
  setLoadedScene(null);
16394
16775
  setLargeModelTriangles(null);
16395
16776
  setLargeModelNoticeDismissed(false);
@@ -16400,11 +16781,11 @@ function SimpleModelViewer({
16400
16781
  setModelLoadFailed(false);
16401
16782
  setInitialFitComplete(false);
16402
16783
  }, []);
16403
- const revokeUploadedModelUrl = useCallback9(() => {
16784
+ const revokeUploadedModelUrl = useCallback10(() => {
16404
16785
  uploadedModelRef.current?.cleanup();
16405
16786
  uploadedModelRef.current = null;
16406
16787
  }, []);
16407
- const clearUploadedModel = useCallback9(() => {
16788
+ const clearUploadedModel = useCallback10(() => {
16408
16789
  revokeUploadedModelUrl();
16409
16790
  setUploadedModelUrl(null);
16410
16791
  setUploadedModelName(null);
@@ -16433,7 +16814,7 @@ function SimpleModelViewer({
16433
16814
  resetModelState,
16434
16815
  revokeUploadedModelUrl
16435
16816
  ]);
16436
- const handleUploadedModel = useCallback9(
16817
+ const handleUploadedModel = useCallback10(
16437
16818
  async (file) => {
16438
16819
  setIsResolvingUpload(true);
16439
16820
  try {
@@ -16452,7 +16833,7 @@ function SimpleModelViewer({
16452
16833
  },
16453
16834
  [onLoadError, resetModelState, revokeUploadedModelUrl]
16454
16835
  );
16455
- const handleFileSelection = useCallback9(
16836
+ const handleFileSelection = useCallback10(
16456
16837
  (files) => {
16457
16838
  const file = files?.[0];
16458
16839
  if (!file) return;
@@ -16465,7 +16846,7 @@ function SimpleModelViewer({
16465
16846
  revokeUploadedModelUrl();
16466
16847
  };
16467
16848
  }, [revokeUploadedModelUrl]);
16468
- const handleEnvSettingChange = useCallback9(
16849
+ const handleEnvSettingChange = useCallback10(
16469
16850
  (key, value) => {
16470
16851
  setEnvSettings((prev) => ({ ...prev, [key]: value }));
16471
16852
  },
@@ -16477,12 +16858,12 @@ function SimpleModelViewer({
16477
16858
  useEffect21(() => {
16478
16859
  resetModelState();
16479
16860
  }, [activeModelUrl, resetModelState]);
16480
- const handleMeshesReady = useCallback9((meshes) => {
16861
+ const handleMeshesReady = useCallback10((meshes) => {
16481
16862
  setMeshKeys(meshes);
16482
16863
  setHiddenKeys(/* @__PURE__ */ new Set());
16483
16864
  setSelectedKey(null);
16484
16865
  }, []);
16485
- const handleModelLoaded = useCallback9(
16866
+ const handleModelLoaded = useCallback10(
16486
16867
  (scene) => {
16487
16868
  setLoadedScene(scene);
16488
16869
  setModelLoadFailed(false);
@@ -16493,14 +16874,14 @@ function SimpleModelViewer({
16493
16874
  },
16494
16875
  [onModelLoaded]
16495
16876
  );
16496
- const handleLoadError = useCallback9(
16877
+ const handleLoadError = useCallback10(
16497
16878
  (error) => {
16498
16879
  setModelLoadFailed(true);
16499
16880
  onLoadError?.(error);
16500
16881
  },
16501
16882
  [onLoadError]
16502
16883
  );
16503
- const focusMesh = useCallback9(
16884
+ const focusMesh = useCallback10(
16504
16885
  (key) => {
16505
16886
  setSelectedKey(key);
16506
16887
  if (!zoomOnSelected) return;
@@ -16516,14 +16897,14 @@ function SimpleModelViewer({
16516
16897
  },
16517
16898
  [zoomOnSelected]
16518
16899
  );
16519
- const handleToggleVisibility = useCallback9((key) => {
16900
+ const handleToggleVisibility = useCallback10((key) => {
16520
16901
  setHiddenKeys((prev) => {
16521
16902
  const next = new Set(prev);
16522
16903
  next.has(key) ? next.delete(key) : next.add(key);
16523
16904
  return next;
16524
16905
  });
16525
16906
  }, []);
16526
- const handleDragEnter = useCallback9(
16907
+ const handleDragEnter = useCallback10(
16527
16908
  (event) => {
16528
16909
  if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
16529
16910
  return;
@@ -16535,7 +16916,7 @@ function SimpleModelViewer({
16535
16916
  },
16536
16917
  [enableModelUpload]
16537
16918
  );
16538
- const handleDragOver = useCallback9(
16919
+ const handleDragOver = useCallback10(
16539
16920
  (event) => {
16540
16921
  if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
16541
16922
  return;
@@ -16547,7 +16928,7 @@ function SimpleModelViewer({
16547
16928
  },
16548
16929
  [enableModelUpload]
16549
16930
  );
16550
- const handleDragLeave = useCallback9(
16931
+ const handleDragLeave = useCallback10(
16551
16932
  (event) => {
16552
16933
  if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
16553
16934
  return;
@@ -16561,7 +16942,7 @@ function SimpleModelViewer({
16561
16942
  },
16562
16943
  [enableModelUpload]
16563
16944
  );
16564
- const handleDrop = useCallback9(
16945
+ const handleDrop = useCallback10(
16565
16946
  (event) => {
16566
16947
  if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
16567
16948
  return;
@@ -16615,7 +16996,7 @@ function SimpleModelViewer({
16615
16996
  }
16616
16997
  )
16617
16998
  ] }),
16618
- enableModelUpload ? /* @__PURE__ */ jsxs41(Fragment14, { children: [
16999
+ enableModelUpload ? /* @__PURE__ */ jsxs41(Fragment15, { children: [
16619
17000
  /* @__PURE__ */ jsx46(
16620
17001
  "input",
16621
17002
  {
@@ -16629,7 +17010,7 @@ function SimpleModelViewer({
16629
17010
  }
16630
17011
  }
16631
17012
  ),
16632
- 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: [
16633
17014
  /* @__PURE__ */ jsx46("span", { className: "smv-upload-toolbar__name", children: uploadedModelName ?? "Uploaded model" }),
16634
17015
  /* @__PURE__ */ jsxs41(
16635
17016
  "button",
@@ -16790,18 +17171,18 @@ function SimpleModelViewer({
16790
17171
  }
16791
17172
 
16792
17173
  // src/hooks/useViewerSelection.ts
16793
- import { useCallback as useCallback10, useState as useState26 } from "react";
17174
+ import { useCallback as useCallback11, useState as useState27 } from "react";
16794
17175
  function useViewerSelection(initialSelectedObjectBinding = null) {
16795
- const [selectedObjectBinding, setSelectedObjectBinding] = useState26(
17176
+ const [selectedObjectBinding, setSelectedObjectBinding] = useState27(
16796
17177
  initialSelectedObjectBinding
16797
17178
  );
16798
- const handleObjectSelect = useCallback10(
17179
+ const handleObjectSelect = useCallback11(
16799
17180
  (binding) => {
16800
17181
  setSelectedObjectBinding(binding);
16801
17182
  },
16802
17183
  [setSelectedObjectBinding]
16803
17184
  );
16804
- const clearSelection = useCallback10(() => {
17185
+ const clearSelection = useCallback11(() => {
16805
17186
  setSelectedObjectBinding(null);
16806
17187
  }, []);
16807
17188
  return {
@@ -16839,14 +17220,14 @@ function useObjectBindingIds(objectBindings) {
16839
17220
  }
16840
17221
 
16841
17222
  // src/hooks/useObjectVisibility.ts
16842
- import { useCallback as useCallback11, useMemo as useMemo20 } from "react";
17223
+ import { useCallback as useCallback12, useMemo as useMemo20 } from "react";
16843
17224
  function getHiddenObjects(objectBindings) {
16844
17225
  return Object.fromEntries(
16845
17226
  Object.entries(objectBindings).filter(([, binding]) => binding.visible === false).map(([bindingKey]) => [bindingKey, true])
16846
17227
  );
16847
17228
  }
16848
17229
  function useObjectVisibility(objectBindings, onObjectBindingsChange) {
16849
- const updateVisibility = useCallback11(
17230
+ const updateVisibility = useCallback12(
16850
17231
  (objectId, visible) => {
16851
17232
  const bindingKey = resolveBindingKey(objectBindings, objectId);
16852
17233
  if (!bindingKey) return;
@@ -16862,15 +17243,15 @@ function useObjectVisibility(objectBindings, onObjectBindingsChange) {
16862
17243
  },
16863
17244
  [objectBindings, onObjectBindingsChange]
16864
17245
  );
16865
- const hideObject = useCallback11(
17246
+ const hideObject = useCallback12(
16866
17247
  (objectId) => updateVisibility(objectId, false),
16867
17248
  [updateVisibility]
16868
17249
  );
16869
- const showObject = useCallback11(
17250
+ const showObject = useCallback12(
16870
17251
  (objectId) => updateVisibility(objectId, true),
16871
17252
  [updateVisibility]
16872
17253
  );
16873
- const toggleObjectVisibility = useCallback11(
17254
+ const toggleObjectVisibility = useCallback12(
16874
17255
  (objectId) => {
16875
17256
  const bindingKey = resolveBindingKey(objectBindings, objectId);
16876
17257
  if (!bindingKey) return;
@@ -16886,7 +17267,7 @@ function useObjectVisibility(objectBindings, onObjectBindingsChange) {
16886
17267
  },
16887
17268
  [objectBindings, onObjectBindingsChange]
16888
17269
  );
16889
- const clearHiddenObjects = useCallback11(() => {
17270
+ const clearHiddenObjects = useCallback12(() => {
16890
17271
  onObjectBindingsChange(
16891
17272
  Object.fromEntries(
16892
17273
  Object.entries(objectBindings).map(([bindingKey, binding]) => [
@@ -16906,7 +17287,7 @@ function useObjectVisibility(objectBindings, onObjectBindingsChange) {
16906
17287
  ),
16907
17288
  [hiddenObjects, objectBindings]
16908
17289
  );
16909
- const isObjectHidden = useCallback11(
17290
+ const isObjectHidden = useCallback12(
16910
17291
  (objectId) => {
16911
17292
  const bindingKey = resolveBindingKey(objectBindings, objectId);
16912
17293
  return bindingKey ? objectBindings[bindingKey]?.visible === false : false;
@@ -16925,10 +17306,10 @@ function useObjectVisibility(objectBindings, onObjectBindingsChange) {
16925
17306
  }
16926
17307
 
16927
17308
  // src/hooks/useViewerHover.ts
16928
- import { useCallback as useCallback12, useState as useState27 } from "react";
17309
+ import { useCallback as useCallback13, useState as useState28 } from "react";
16929
17310
  function useViewerHover() {
16930
- const [hoveredObjectBinding, setHoveredObjectBinding] = useState27(null);
16931
- const handleHoveredObject = useCallback12(
17311
+ const [hoveredObjectBinding, setHoveredObjectBinding] = useState28(null);
17312
+ const handleHoveredObject = useCallback13(
16932
17313
  (binding) => {
16933
17314
  setHoveredObjectBinding(binding);
16934
17315
  },
@@ -16941,18 +17322,18 @@ function useViewerHover() {
16941
17322
  }
16942
17323
 
16943
17324
  // src/hooks/useViewerModel.ts
16944
- import { useCallback as useCallback13, useState as useState28 } from "react";
17325
+ import { useCallback as useCallback14, useState as useState29 } from "react";
16945
17326
  import { Box3 as Box36, Vector3 as Vector310 } from "three";
16946
17327
  function toTuple2(vector) {
16947
17328
  return [vector.x, vector.y, vector.z];
16948
17329
  }
16949
17330
  function useViewerModel() {
16950
- const [isLoading, setIsLoading] = useState28(true);
16951
- const [isReady, setIsReady] = useState28(false);
16952
- const [error, setError] = useState28(null);
16953
- const [bounds, setBounds] = useState28(null);
16954
- const [objectCount, setObjectCount] = useState28(0);
16955
- 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) => {
16956
17337
  const box = new Box36().setFromObject(scene);
16957
17338
  const min = box.min.clone();
16958
17339
  const max = box.max.clone();
@@ -16977,14 +17358,14 @@ function useViewerModel() {
16977
17358
  setIsLoading(false);
16978
17359
  setIsReady(true);
16979
17360
  }, []);
16980
- const handleLoadError = useCallback13((nextError) => {
17361
+ const handleLoadError = useCallback14((nextError) => {
16981
17362
  setError(nextError);
16982
17363
  setBounds(null);
16983
17364
  setObjectCount(0);
16984
17365
  setIsLoading(false);
16985
17366
  setIsReady(false);
16986
17367
  }, []);
16987
- const resetModelState = useCallback13(() => {
17368
+ const resetModelState = useCallback14(() => {
16988
17369
  setIsLoading(true);
16989
17370
  setIsReady(false);
16990
17371
  setError(null);
@@ -17004,10 +17385,10 @@ function useViewerModel() {
17004
17385
  }
17005
17386
 
17006
17387
  // src/hooks/useViewerActions.ts
17007
- import { useCallback as useCallback14 } from "react";
17388
+ import { useCallback as useCallback15 } from "react";
17008
17389
  function useViewerActions(objectBindings, options = {}) {
17009
17390
  const { onAction, onObjectBindingsChange } = options;
17010
- const getActionsForObject = useCallback14(
17391
+ const getActionsForObject = useCallback15(
17011
17392
  (objectId) => {
17012
17393
  const bindingKey = resolveBindingKey(objectBindings, objectId);
17013
17394
  if (!bindingKey) {
@@ -17017,7 +17398,7 @@ function useViewerActions(objectBindings, options = {}) {
17017
17398
  },
17018
17399
  [objectBindings]
17019
17400
  );
17020
- const runAction = useCallback14(
17401
+ const runAction = useCallback15(
17021
17402
  (objectId, actionId) => {
17022
17403
  const bindingKey = resolveBindingKey(objectBindings, objectId);
17023
17404
  if (!bindingKey) {
@@ -17059,7 +17440,7 @@ function useViewerActions(objectBindings, options = {}) {
17059
17440
  }
17060
17441
 
17061
17442
  // src/hooks/useShareableViewerState.ts
17062
- 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";
17063
17444
  function serializeState(cameraState, objectBindings, selectedObjectBinding) {
17064
17445
  const hidden = Object.values(objectBindings).filter((binding) => binding.visible === false).map((binding) => binding.id);
17065
17446
  return {
@@ -17080,7 +17461,7 @@ function useShareableViewerState({
17080
17461
  autoSync = true
17081
17462
  }) {
17082
17463
  const lastWrittenRawRef = useRef20(null);
17083
- const buildHash = useCallback15(() => {
17464
+ const buildHash = useCallback16(() => {
17084
17465
  const state = serializeState(
17085
17466
  cameraState,
17086
17467
  objectBindings,
@@ -17088,12 +17469,12 @@ function useShareableViewerState({
17088
17469
  );
17089
17470
  return `${paramKey}=${encodeURIComponent(JSON.stringify(state))}`;
17090
17471
  }, [cameraState, objectBindings, selectedObjectBinding, paramKey]);
17091
- const getShareUrl = useCallback15(() => {
17472
+ const getShareUrl = useCallback16(() => {
17092
17473
  const url = new URL(window.location.href);
17093
17474
  url.hash = buildHash();
17094
17475
  return url.toString();
17095
17476
  }, [buildHash]);
17096
- const syncUrl = useCallback15(() => {
17477
+ const syncUrl = useCallback16(() => {
17097
17478
  const url = new URL(window.location.href);
17098
17479
  url.hash = buildHash();
17099
17480
  window.history.replaceState(null, "", url.toString());
@@ -17111,7 +17492,7 @@ function useShareableViewerState({
17111
17492
  objectBindings,
17112
17493
  selectedObjectBinding
17113
17494
  };
17114
- const [restoreAttempted, setRestoreAttempted] = useState29(false);
17495
+ const [restoreAttempted, setRestoreAttempted] = useState30(false);
17115
17496
  const lastSyncedHashRef = useRef20(null);
17116
17497
  useEffect22(() => {
17117
17498
  if (!autoSync || !restoreAttempted) return;
@@ -17137,7 +17518,7 @@ function useShareableViewerState({
17137
17518
  }, [autoSync, restoreAttempted, debounceMs, paramKey]);
17138
17519
  const lastRestoredRawRef = useRef20(null);
17139
17520
  const restoringRawRef = useRef20(null);
17140
- const restoreFromUrl = useCallback15(async () => {
17521
+ const restoreFromUrl = useCallback16(async () => {
17141
17522
  setRestoreAttempted(true);
17142
17523
  const rawHash = window.location.hash.startsWith("#") ? window.location.hash.slice(1) : window.location.hash;
17143
17524
  const raw = new URLSearchParams(rawHash).get(paramKey);