@liveroom-tech/react-immersive 1.3.3 → 1.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -242,7 +242,7 @@ function styleInject(css, { insertAt } = {}) {
242
242
  }
243
243
 
244
244
  // src/styles/ModelViewer.css
245
- 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');
245
+ 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');
246
246
 
247
247
  // src/components/LicenseOverlay.tsx
248
248
  var import_lucide_react = require("lucide-react");
@@ -4099,18 +4099,75 @@ function CinematicCamera({
4099
4099
  sceneRef,
4100
4100
  config,
4101
4101
  playing,
4102
+ resetOnPlay = false,
4102
4103
  onFinished,
4103
- onUserInteract
4104
+ onUserInteract,
4105
+ onStop
4104
4106
  }) {
4105
4107
  const invalidate = (0, import_fiber10.useThree)((state) => state.invalidate);
4106
4108
  const waypoints = (0, import_react15.useMemo)(() => config.waypoints ?? [], [config.waypoints]);
4107
4109
  const hasPath = waypoints.length >= 2;
4108
4110
  const loop = config.loop ?? true;
4109
- const duration = config.duration ?? (hasPath ? Math.max(waypoints.length * 3.5, 8) : 24);
4111
+ const calculatedDuration = (0, import_react15.useMemo)(() => {
4112
+ if (waypoints.length < 2) return 24;
4113
+ let total = 0;
4114
+ for (let i = 0; i < waypoints.length - 1; i++) {
4115
+ total += waypoints[i].segmentDuration ?? 3.5;
4116
+ }
4117
+ if (loop && waypoints.length > 1) {
4118
+ total += waypoints[waypoints.length - 1].segmentDuration ?? 3.5;
4119
+ }
4120
+ return Math.max(total, 4);
4121
+ }, [waypoints, loop]);
4122
+ const duration = config.duration ?? calculatedDuration;
4110
4123
  const signature = (0, import_react15.useMemo)(
4111
- () => waypoints.map((w) => [...w.position, ...w.target].join(",")).join(";"),
4124
+ () => waypoints.map(
4125
+ (waypoint) => [
4126
+ ...waypoint.position,
4127
+ ...waypoint.target,
4128
+ waypoint.azimuthAngle ?? "",
4129
+ waypoint.polarAngle ?? "",
4130
+ waypoint.segmentDuration ?? ""
4131
+ ].join(",")
4132
+ ).join(";"),
4112
4133
  [waypoints]
4113
4134
  );
4135
+ const segmentTimes = (0, import_react15.useMemo)(() => {
4136
+ const times = [];
4137
+ if (waypoints.length < 2) return times;
4138
+ let accumulated = 0;
4139
+ for (let i = 0; i < waypoints.length - 1; i++) {
4140
+ accumulated += waypoints[i].segmentDuration ?? 3.5;
4141
+ times.push(accumulated);
4142
+ }
4143
+ if (loop && waypoints.length > 1) {
4144
+ accumulated += waypoints[waypoints.length - 1].segmentDuration ?? 3.5;
4145
+ times.push(accumulated);
4146
+ }
4147
+ return times;
4148
+ }, [waypoints, loop]);
4149
+ const getCurveParameter = (0, import_react15.useMemo)(() => {
4150
+ return (elapsed) => {
4151
+ if (waypoints.length < 2 || segmentTimes.length === 0) {
4152
+ return elapsed / duration;
4153
+ }
4154
+ const segmentTotal = segmentTimes[segmentTimes.length - 1];
4155
+ const playbackElapsed = loop ? elapsed % duration : Math.min(elapsed, duration);
4156
+ const normalizedElapsed = duration > 0 ? playbackElapsed / duration * segmentTotal : 0;
4157
+ for (let i = 0; i < segmentTimes.length; i++) {
4158
+ if (normalizedElapsed <= segmentTimes[i]) {
4159
+ const prevTime = i === 0 ? 0 : segmentTimes[i - 1];
4160
+ const segmentDuration = segmentTimes[i] - prevTime;
4161
+ const progress = segmentDuration > 0 ? (normalizedElapsed - prevTime) / segmentDuration : 0;
4162
+ const totalSegments = loop ? waypoints.length : waypoints.length - 1;
4163
+ const baseU = i / totalSegments;
4164
+ const segmentU = 1 / totalSegments;
4165
+ return baseU + progress * segmentU;
4166
+ }
4167
+ }
4168
+ return 1;
4169
+ };
4170
+ }, [waypoints.length, segmentTimes, duration, loop]);
4114
4171
  const curves = (0, import_react15.useMemo)(() => {
4115
4172
  if (!hasPath) return null;
4116
4173
  return {
@@ -4128,29 +4185,83 @@ function CinematicCamera({
4128
4185
  )
4129
4186
  };
4130
4187
  }, [signature, hasPath, loop]);
4188
+ const orbitPath = (0, import_react15.useMemo)(() => {
4189
+ if (!hasPath || !waypoints.every(
4190
+ (waypoint) => Number.isFinite(waypoint.azimuthAngle) && Number.isFinite(waypoint.polarAngle)
4191
+ )) {
4192
+ return null;
4193
+ }
4194
+ const points = waypoints.map((waypoint) => ({
4195
+ azimuth: waypoint.azimuthAngle,
4196
+ polar: waypoint.polarAngle,
4197
+ distance: new import_three18.Vector3(...waypoint.position).distanceTo(
4198
+ new import_three18.Vector3(...waypoint.target)
4199
+ )
4200
+ }));
4201
+ for (let index = 1; index < points.length; index += 1) {
4202
+ const previous = points[index - 1].azimuth;
4203
+ const current = points[index].azimuth;
4204
+ points[index].azimuth = current + Math.round((previous - current) / (Math.PI * 2)) * Math.PI * 2;
4205
+ }
4206
+ return points;
4207
+ }, [signature, hasPath]);
4131
4208
  const elapsedRef = (0, import_react15.useRef)(0);
4132
4209
  const enterElapsedRef = (0, import_react15.useRef)(0);
4133
4210
  const enterPos = (0, import_react15.useRef)(new import_three18.Vector3());
4134
4211
  const enterTarget = (0, import_react15.useRef)(new import_three18.Vector3());
4135
4212
  const startedRef = (0, import_react15.useRef)(false);
4136
4213
  const pausedElapsedRef = (0, import_react15.useRef)(0);
4214
+ const hasFinishedRef = (0, import_react15.useRef)(false);
4137
4215
  const orbitRef = (0, import_react15.useRef)(null);
4138
4216
  const tmpPos = (0, import_react15.useMemo)(() => new import_three18.Vector3(), []);
4139
4217
  const tmpTarget = (0, import_react15.useMemo)(() => new import_three18.Vector3(), []);
4218
+ const tmpSpherical = (0, import_react15.useMemo)(() => new import_three18.Spherical(), []);
4219
+ (0, import_react15.useEffect)(() => {
4220
+ elapsedRef.current = 0;
4221
+ pausedElapsedRef.current = 0;
4222
+ enterElapsedRef.current = 0;
4223
+ hasFinishedRef.current = false;
4224
+ }, [signature, loop, duration]);
4225
+ (0, import_react15.useEffect)(() => {
4226
+ if (resetOnPlay) {
4227
+ pausedElapsedRef.current = 0;
4228
+ elapsedRef.current = 0;
4229
+ hasFinishedRef.current = false;
4230
+ }
4231
+ }, [resetOnPlay]);
4140
4232
  (0, import_react15.useEffect)(() => {
4141
4233
  if (!playing) {
4142
4234
  startedRef.current = false;
4143
- if (elapsedRef.current > 0) {
4235
+ if (elapsedRef.current > 0 && !resetOnPlay) {
4144
4236
  pausedElapsedRef.current = elapsedRef.current;
4237
+ } else if (resetOnPlay) {
4238
+ pausedElapsedRef.current = 0;
4239
+ }
4240
+ if (elapsedRef.current > 0 || hasFinishedRef.current) {
4241
+ onStop?.();
4145
4242
  }
4146
4243
  return;
4147
4244
  }
4245
+ if (elapsedRef.current >= duration && !loop) {
4246
+ elapsedRef.current = 0;
4247
+ pausedElapsedRef.current = 0;
4248
+ hasFinishedRef.current = false;
4249
+ }
4148
4250
  const controls = controlsRef.current;
4149
4251
  if (controls) {
4150
4252
  controls.getPosition(enterPos.current);
4151
4253
  controls.getTarget(enterTarget.current);
4152
4254
  }
4153
- elapsedRef.current = pausedElapsedRef.current || 0;
4255
+ if (resetOnPlay || hasFinishedRef.current || pausedElapsedRef.current === 0) {
4256
+ elapsedRef.current = 0;
4257
+ pausedElapsedRef.current = 0;
4258
+ hasFinishedRef.current = false;
4259
+ } else if (pausedElapsedRef.current > 0 && pausedElapsedRef.current < duration) {
4260
+ elapsedRef.current = pausedElapsedRef.current;
4261
+ } else {
4262
+ elapsedRef.current = 0;
4263
+ pausedElapsedRef.current = 0;
4264
+ }
4154
4265
  enterElapsedRef.current = 0;
4155
4266
  if (!hasPath) {
4156
4267
  const scene = sceneRef.current;
@@ -4164,12 +4275,26 @@ function CinematicCamera({
4164
4275
  }
4165
4276
  startedRef.current = true;
4166
4277
  invalidate();
4167
- }, [playing, hasPath, controlsRef, sceneRef, invalidate]);
4278
+ }, [
4279
+ playing,
4280
+ hasPath,
4281
+ controlsRef,
4282
+ sceneRef,
4283
+ invalidate,
4284
+ duration,
4285
+ loop,
4286
+ signature,
4287
+ resetOnPlay,
4288
+ onStop
4289
+ ]);
4168
4290
  (0, import_react15.useEffect)(() => {
4169
4291
  if (!playing || !onUserInteract) return;
4170
4292
  const controls = controlsRef.current;
4171
4293
  if (!controls) return;
4172
- const handle = () => onUserInteract();
4294
+ const handle = () => {
4295
+ if (hasFinishedRef.current) return;
4296
+ onUserInteract();
4297
+ };
4173
4298
  controls.addEventListener("controlstart", handle);
4174
4299
  return () => controls.removeEventListener("controlstart", handle);
4175
4300
  }, [playing, controlsRef, onUserInteract]);
@@ -4179,12 +4304,33 @@ function CinematicCamera({
4179
4304
  if (!controls) return;
4180
4305
  elapsedRef.current += delta;
4181
4306
  enterElapsedRef.current += delta;
4182
- let u = elapsedRef.current / duration;
4183
- const finished = !loop && u >= 1;
4307
+ let u = getCurveParameter(elapsedRef.current);
4308
+ const finished = !loop && elapsedRef.current >= duration;
4184
4309
  u = loop ? u % 1 : Math.min(u, 1);
4185
4310
  if (curves) {
4186
- curves.position.getPointAt(u, tmpPos);
4187
- curves.target.getPointAt(u, tmpTarget);
4311
+ curves.target.getPoint(u, tmpTarget);
4312
+ if (orbitPath) {
4313
+ const segmentCount = loop ? orbitPath.length : orbitPath.length - 1;
4314
+ const scaled = Math.min(u, 1) * segmentCount;
4315
+ const segment = Math.min(Math.floor(scaled), segmentCount - 1);
4316
+ const next = (segment + 1) % orbitPath.length;
4317
+ const localT = u >= 1 ? 1 : scaled - segment;
4318
+ const eased = localT * localT * (3 - 2 * localT);
4319
+ const start = orbitPath[segment];
4320
+ const end = orbitPath[next];
4321
+ const endAzimuth = end.azimuth + Math.round((start.azimuth - end.azimuth) / (Math.PI * 2)) * Math.PI * 2;
4322
+ const azimuth = start.azimuth + (endAzimuth - start.azimuth) * eased;
4323
+ const polar = start.polar + (end.polar - start.polar) * eased;
4324
+ const distance = start.distance + (end.distance - start.distance) * eased;
4325
+ tmpSpherical.set(
4326
+ Math.max(distance, 1e-6),
4327
+ Math.min(Math.max(polar, 1e-6), Math.PI - 1e-6),
4328
+ azimuth
4329
+ );
4330
+ tmpPos.setFromSpherical(tmpSpherical).add(tmpTarget);
4331
+ } else {
4332
+ curves.position.getPoint(u, tmpPos);
4333
+ }
4188
4334
  } else {
4189
4335
  const orbit = orbitRef.current;
4190
4336
  if (!orbit) return;
@@ -4214,6 +4360,8 @@ function CinematicCamera({
4214
4360
  );
4215
4361
  if (finished) {
4216
4362
  startedRef.current = false;
4363
+ hasFinishedRef.current = true;
4364
+ elapsedRef.current = 0;
4217
4365
  pausedElapsedRef.current = 0;
4218
4366
  onFinished?.();
4219
4367
  return;
@@ -5594,9 +5742,11 @@ function ModelViewer({
5594
5742
  return { ...base ?? {}, ...cinematic };
5595
5743
  }, [cinematic, resolvedSceneConfig]);
5596
5744
  const [cinematicPlaying, setCinematicPlaying] = (0, import_react22.useState)(false);
5745
+ const [cinematicReset, setCinematicReset] = (0, import_react22.useState)(false);
5597
5746
  const cinematicAutoStartedRef = (0, import_react22.useRef)(false);
5598
5747
  (0, import_react22.useEffect)(() => {
5599
5748
  setCinematicPlaying(false);
5749
+ setCinematicReset(false);
5600
5750
  cinematicAutoStartedRef.current = false;
5601
5751
  }, [modelUrl]);
5602
5752
  (0, import_react22.useEffect)(() => {
@@ -5604,6 +5754,7 @@ function ModelViewer({
5604
5754
  if (modelLoaded && cameraPositioned && !isAutoFraming) {
5605
5755
  cinematicAutoStartedRef.current = true;
5606
5756
  setCinematicPlaying(true);
5757
+ setCinematicReset(false);
5607
5758
  }
5608
5759
  }, [cinematicConfig, modelLoaded, cameraPositioned, isAutoFraming]);
5609
5760
  const xrStore = (0, import_react22.useMemo)(() => {
@@ -6757,6 +6908,20 @@ function ModelViewer({
6757
6908
  console.error("Reset camera error:", error);
6758
6909
  }
6759
6910
  }, [onAutoFit, fitScene]);
6911
+ const handleCinematicPlay = (0, import_react22.useCallback)(() => {
6912
+ setCinematicReset(false);
6913
+ setCinematicPlaying(true);
6914
+ }, []);
6915
+ const handleCinematicPause = (0, import_react22.useCallback)(() => {
6916
+ setCinematicPlaying(false);
6917
+ }, []);
6918
+ const handleCinematicStop = (0, import_react22.useCallback)(() => {
6919
+ setCinematicPlaying(false);
6920
+ setCinematicReset(true);
6921
+ setTimeout(() => {
6922
+ handleReset();
6923
+ }, 100);
6924
+ }, [handleReset]);
6760
6925
  const handleCameraChangeWithAnnotations = (0, import_react22.useCallback)(
6761
6926
  (cameraValue, controls) => {
6762
6927
  handleCameraChangeInternal(cameraValue, controls);
@@ -7073,8 +7238,14 @@ function ModelViewer({
7073
7238
  sceneRef,
7074
7239
  config: cinematicConfig,
7075
7240
  playing: cinematicPlaying,
7076
- onFinished: () => setCinematicPlaying(false),
7077
- onUserInteract: () => setCinematicPlaying(false)
7241
+ resetOnPlay: cinematicReset,
7242
+ onFinished: () => {
7243
+ setCinematicPlaying(false);
7244
+ setCinematicReset(false);
7245
+ },
7246
+ onUserInteract: () => {
7247
+ setCinematicPlaying(false);
7248
+ }
7078
7249
  }
7079
7250
  ),
7080
7251
  showMeasureTools && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
@@ -7302,24 +7473,43 @@ function ModelViewer({
7302
7473
  ]
7303
7474
  }
7304
7475
  ),
7305
- cinematicConfig && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
7476
+ cinematicConfig && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
7306
7477
  "div",
7307
7478
  {
7308
7479
  className: "ri-cinematicTools",
7309
7480
  style: {
7310
7481
  top: `${topLeftRow((xrToolsVisible ? 1 : 0) + (showMeasureTools ? 1 : 0) + (showExplodeControls ? 1 : 0))}px`
7311
7482
  },
7312
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
7313
- "button",
7314
- {
7315
- type: "button",
7316
- className: `ri-measureButton ${cinematicPlaying ? "ri-measureButton--active" : ""}`,
7317
- onClick: () => setCinematicPlaying((prev) => !prev),
7318
- title: cinematicPlaying ? "Pause cinematic" : "Play cinematic",
7319
- "aria-pressed": cinematicPlaying,
7320
- children: cinematicPlaying ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react3.Pause, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react3.Play, { size: 16 })
7321
- }
7322
- )
7483
+ children: [
7484
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
7485
+ "button",
7486
+ {
7487
+ type: "button",
7488
+ className: `ri-measureButton ${cinematicPlaying ? "ri-measureButton--active" : ""}`,
7489
+ onClick: () => {
7490
+ if (cinematicPlaying) {
7491
+ setCinematicPlaying(false);
7492
+ } else {
7493
+ setCinematicReset(false);
7494
+ setCinematicPlaying(true);
7495
+ }
7496
+ },
7497
+ title: cinematicPlaying ? "Pause cinematic" : "Play cinematic",
7498
+ "aria-pressed": cinematicPlaying,
7499
+ children: cinematicPlaying ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react3.Pause, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react3.Play, { size: 16 })
7500
+ }
7501
+ ),
7502
+ cinematicPlaying && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
7503
+ "button",
7504
+ {
7505
+ type: "button",
7506
+ className: "ri-measureButton ri-measureButton--stop",
7507
+ onClick: handleCinematicStop,
7508
+ title: "Stop cinematic and reset camera",
7509
+ children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react3.Square, { size: 16 })
7510
+ }
7511
+ )
7512
+ ]
7323
7513
  }
7324
7514
  ),
7325
7515
  (showResetButton || showDownloadButton || showAnnotationNavigation && sortedAnnotations.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "ri-buttonContainer", children: [
@@ -7559,7 +7749,7 @@ function ModelViewer({
7559
7749
  }
7560
7750
 
7561
7751
  // src/components/BindingBuilder/BindingBuilder.tsx
7562
- var import_react38 = require("react");
7752
+ var import_react39 = require("react");
7563
7753
  var import_lucide_react16 = require("lucide-react");
7564
7754
 
7565
7755
  // src/styles/BindingBuilder.css
@@ -7599,14 +7789,16 @@ function buildCameraState(camera, controls) {
7599
7789
  position: toTuple(position),
7600
7790
  target: toTuple(target),
7601
7791
  fov: "fov" in camera && typeof camera.fov === "number" ? camera.fov : null,
7602
- zoom: "zoom" in camera && typeof camera.zoom === "number" ? camera.zoom : 1
7792
+ zoom: "zoom" in camera && typeof camera.zoom === "number" ? camera.zoom : 1,
7793
+ azimuthAngle: controls.azimuthAngle,
7794
+ polarAngle: controls.polarAngle
7603
7795
  };
7604
7796
  }
7605
7797
  function isSameCameraState(left, right) {
7606
7798
  if (!left) {
7607
7799
  return false;
7608
7800
  }
7609
- 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;
7801
+ 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;
7610
7802
  }
7611
7803
  async function runSmoothCameraTransition2(controls, action, smoothTime = 0.6) {
7612
7804
  const originalSmoothTime = controls.smoothTime;
@@ -7761,15 +7953,27 @@ function useViewerCamera() {
7761
7953
  }
7762
7954
  try {
7763
7955
  markInitialAutoFitOverride(controls);
7764
- await controls.setLookAt(
7765
- state.position[0],
7766
- state.position[1],
7767
- state.position[2],
7768
- state.target[0],
7769
- state.target[1],
7770
- state.target[2],
7771
- enableTransition
7772
- );
7956
+ const transitions = [
7957
+ controls.setLookAt(
7958
+ state.position[0],
7959
+ state.position[1],
7960
+ state.position[2],
7961
+ state.target[0],
7962
+ state.target[1],
7963
+ state.target[2],
7964
+ enableTransition
7965
+ )
7966
+ ];
7967
+ if (state.azimuthAngle != null && state.polarAngle != null) {
7968
+ transitions.push(
7969
+ controls.rotateTo(
7970
+ state.azimuthAngle,
7971
+ state.polarAngle,
7972
+ enableTransition
7973
+ )
7974
+ );
7975
+ }
7976
+ await Promise.all(transitions);
7773
7977
  const camera = controls.camera;
7774
7978
  let projectionChanged = false;
7775
7979
  if (state.fov !== null && "fov" in camera && typeof camera.fov === "number") {
@@ -8077,7 +8281,7 @@ function inferMimeType(path) {
8077
8281
  }
8078
8282
 
8079
8283
  // src/components/BindingBuilder/SceneSettings/SceneSettingsTab.tsx
8080
- var import_react32 = require("react");
8284
+ var import_react33 = require("react");
8081
8285
 
8082
8286
  // src/components/BindingBuilder/SceneSettings/GeneralSceneSettings.tsx
8083
8287
  var import_react27 = require("react");
@@ -8258,9 +8462,10 @@ function SliderRow({
8258
8462
  max,
8259
8463
  step,
8260
8464
  formatValue,
8261
- onChange
8465
+ onChange,
8466
+ disabled = false
8262
8467
  }) {
8263
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("label", { className: "se-field", children: [
8468
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("label", { className: `se-field ${disabled ? "se-field--disabled" : ""}`, children: [
8264
8469
  /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "se-fieldLabel", children: [
8265
8470
  label,
8266
8471
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "se-fieldValue", children: formatValue ? formatValue(value) : value })
@@ -8274,7 +8479,8 @@ function SliderRow({
8274
8479
  max,
8275
8480
  step,
8276
8481
  value,
8277
- onChange: (e) => onChange(Number(e.target.value))
8482
+ onChange: (e) => onChange(Number(e.target.value)),
8483
+ disabled
8278
8484
  }
8279
8485
  )
8280
8486
  ] });
@@ -11690,6 +11896,7 @@ function AnnotationsTab({ config, onChange }) {
11690
11896
  }
11691
11897
 
11692
11898
  // src/components/BindingBuilder/SceneSettings/CinematicTab.tsx
11899
+ var import_react32 = require("react");
11693
11900
  var import_lucide_react10 = require("lucide-react");
11694
11901
  var import_jsx_runtime31 = require("react/jsx-runtime");
11695
11902
  var DEFAULT_CINEMATIC = {
@@ -11698,6 +11905,7 @@ var DEFAULT_CINEMATIC = {
11698
11905
  loop: true,
11699
11906
  autoPlay: false
11700
11907
  };
11908
+ var DEFAULT_SEGMENT_DURATION = 3.5;
11701
11909
  function CinematicTab({
11702
11910
  config,
11703
11911
  onChange,
@@ -11706,31 +11914,76 @@ function CinematicTab({
11706
11914
  }) {
11707
11915
  const cinematic = config.cinematic ?? DEFAULT_CINEMATIC;
11708
11916
  const { waypoints } = cinematic;
11709
- const update = (patch) => {
11917
+ const calculateTotalDuration = (0, import_react32.useCallback)((wps, loop) => {
11918
+ if (wps.length < 2) return 0;
11919
+ let total = 0;
11920
+ for (let i = 0; i < wps.length - 1; i++) {
11921
+ const segDur = wps[i].segmentDuration ?? DEFAULT_SEGMENT_DURATION;
11922
+ total += segDur;
11923
+ }
11924
+ if (loop && wps.length > 1) {
11925
+ total += wps[wps.length - 1].segmentDuration ?? DEFAULT_SEGMENT_DURATION;
11926
+ }
11927
+ return Math.max(total, 0);
11928
+ }, []);
11929
+ const update = (0, import_react32.useCallback)((patch) => {
11710
11930
  onChange({ ...config, cinematic: { ...cinematic, ...patch } });
11711
- };
11712
- const addWaypoint = () => {
11931
+ }, [config, cinematic, onChange]);
11932
+ const addWaypoint = (0, import_react32.useCallback)(() => {
11713
11933
  if (!cameraState) return;
11934
+ const newWaypoint = {
11935
+ position: [...cameraState.position],
11936
+ target: [...cameraState.target],
11937
+ ...cameraState.azimuthAngle != null ? { azimuthAngle: cameraState.azimuthAngle } : {},
11938
+ ...cameraState.polarAngle != null ? { polarAngle: cameraState.polarAngle } : {},
11939
+ segmentDuration: DEFAULT_SEGMENT_DURATION
11940
+ };
11941
+ const newWaypoints = [...waypoints, newWaypoint];
11942
+ const newDuration = calculateTotalDuration(newWaypoints, cinematic.loop);
11714
11943
  update({
11715
- waypoints: [
11716
- ...waypoints,
11717
- {
11718
- position: [...cameraState.position],
11719
- target: [...cameraState.target]
11720
- }
11721
- ]
11944
+ waypoints: newWaypoints,
11945
+ duration: newDuration
11722
11946
  });
11723
- };
11724
- const deleteWaypoint = (index) => {
11725
- update({ waypoints: waypoints.filter((_, i) => i !== index) });
11726
- };
11727
- const moveWaypoint = (index, delta) => {
11947
+ }, [cameraState, waypoints, cinematic.loop, calculateTotalDuration, update]);
11948
+ const deleteWaypoint = (0, import_react32.useCallback)((index) => {
11949
+ const newWaypoints = waypoints.filter((_, i) => i !== index);
11950
+ const newDuration = calculateTotalDuration(newWaypoints, cinematic.loop);
11951
+ update({
11952
+ waypoints: newWaypoints,
11953
+ duration: newDuration
11954
+ });
11955
+ }, [waypoints, cinematic.loop, calculateTotalDuration, update]);
11956
+ const moveWaypoint = (0, import_react32.useCallback)((index, delta) => {
11728
11957
  const next = index + delta;
11729
11958
  if (next < 0 || next >= waypoints.length) return;
11730
11959
  const reordered = [...waypoints];
11731
11960
  [reordered[index], reordered[next]] = [reordered[next], reordered[index]];
11732
- update({ waypoints: reordered });
11733
- };
11961
+ const newDuration = calculateTotalDuration(reordered, cinematic.loop);
11962
+ update({
11963
+ waypoints: reordered,
11964
+ duration: newDuration
11965
+ });
11966
+ }, [waypoints, cinematic.loop, calculateTotalDuration, update]);
11967
+ const updateWaypoint = (0, import_react32.useCallback)((index, updates) => {
11968
+ const newWaypoints = waypoints.map((wp, i) => {
11969
+ if (i === index) {
11970
+ return { ...wp, ...updates };
11971
+ }
11972
+ return wp;
11973
+ });
11974
+ const newDuration = calculateTotalDuration(newWaypoints, cinematic.loop);
11975
+ update({
11976
+ waypoints: newWaypoints,
11977
+ duration: newDuration
11978
+ });
11979
+ }, [waypoints, cinematic.loop, calculateTotalDuration, update]);
11980
+ const [customizeDurations, setCustomizeDurations] = (0, import_react32.useState)(false);
11981
+ const hasSegmentDurations = waypoints.some((w) => w.segmentDuration !== void 0);
11982
+ const getDuration = (0, import_react32.useCallback)((index) => {
11983
+ if (index >= waypoints.length - 1) return DEFAULT_SEGMENT_DURATION;
11984
+ return waypoints[index]?.segmentDuration ?? DEFAULT_SEGMENT_DURATION;
11985
+ }, [waypoints]);
11986
+ const hasEnoughWaypoints = waypoints.length >= 2;
11734
11987
  return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(Section, { title: "Cinematic Camera", children: [
11735
11988
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("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." }),
11736
11989
  /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
@@ -11747,62 +12000,165 @@ function CinematicTab({
11747
12000
  ]
11748
12001
  }
11749
12002
  ),
11750
- waypoints.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "bb-emptyAnnotations", children: "No waypoints yet. Position the camera and add your first one." }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "se-waypointList", children: waypoints.map((waypoint, index) => /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-waypointCard", children: [
11751
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "se-waypointBadge", children: index + 1 }),
11752
- /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-waypointBody", children: [
11753
- /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-waypointCoords", children: [
11754
- "pos [",
11755
- waypoint.position.map((n) => n.toFixed(1)).join(", "),
11756
- "]"
11757
- ] }),
11758
- /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-waypointCoords", children: [
11759
- "look [",
11760
- waypoint.target.map((n) => n.toFixed(1)).join(", "),
11761
- "]"
11762
- ] })
11763
- ] }),
11764
- /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-waypointActions", children: [
12003
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-customizeToggle", children: [
12004
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("label", { className: "se-toggleLabel", children: [
11765
12005
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
11766
- "button",
11767
- {
11768
- className: "se-waypointAction",
11769
- onClick: () => onPreviewWaypoint?.(waypoint),
11770
- title: "Fly camera to this waypoint",
11771
- disabled: !onPreviewWaypoint,
11772
- children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.MapPin, { size: 15 })
11773
- }
11774
- ),
11775
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
11776
- "button",
11777
- {
11778
- className: "se-waypointAction",
11779
- onClick: () => moveWaypoint(index, -1),
11780
- title: "Move up",
11781
- disabled: index === 0,
11782
- children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.ArrowUp, { size: 15 })
11783
- }
11784
- ),
11785
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
11786
- "button",
12006
+ "input",
11787
12007
  {
11788
- className: "se-waypointAction",
11789
- onClick: () => moveWaypoint(index, 1),
11790
- title: "Move down",
11791
- disabled: index === waypoints.length - 1,
11792
- children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.ArrowDown, { size: 15 })
12008
+ type: "checkbox",
12009
+ checked: customizeDurations,
12010
+ onChange: (e) => setCustomizeDurations(e.target.checked)
11793
12011
  }
11794
12012
  ),
11795
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
11796
- "button",
11797
- {
11798
- className: "se-waypointAction se-waypointAction--delete",
11799
- onClick: () => deleteWaypoint(index),
11800
- title: "Delete waypoint",
11801
- children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.Trash2, { size: 15 })
11802
- }
11803
- )
12013
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.Settings, { size: 14 }),
12014
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { children: "Customize durations between waypoints" })
12015
+ ] }),
12016
+ customizeDurations && /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("span", { className: "se-toggleHint", children: [
12017
+ "Set duration for each segment between waypoints: 1\u21922, 2\u21923, etc. Default: ",
12018
+ DEFAULT_SEGMENT_DURATION,
12019
+ "s"
11804
12020
  ] })
11805
- ] }, index)) }),
12021
+ ] }),
12022
+ waypoints.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "bb-emptyAnnotations", children: "No waypoints yet. Position the camera and add your first one." }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_jsx_runtime31.Fragment, { children: [
12023
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "se-waypointList", children: waypoints.map((waypoint, index) => {
12024
+ const dur = getDuration(index);
12025
+ const isLast = index === waypoints.length - 1;
12026
+ const isCustom = waypoint.segmentDuration !== void 0 && waypoint.segmentDuration !== DEFAULT_SEGMENT_DURATION;
12027
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { children: [
12028
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-waypointCard", children: [
12029
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "se-waypointBadge", children: index + 1 }),
12030
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-waypointBody", children: [
12031
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-waypointCoords", children: [
12032
+ "pos [",
12033
+ waypoint.position.map((n) => n.toFixed(1)).join(", "),
12034
+ "]"
12035
+ ] }),
12036
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-waypointCoords", children: [
12037
+ "look [",
12038
+ waypoint.target.map((n) => n.toFixed(1)).join(", "),
12039
+ "]"
12040
+ ] }),
12041
+ customizeDurations && !isLast && /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: `se-customBadge ${isCustom ? "se-customBadge--custom" : "se-customBadge--default"}`, children: [
12042
+ dur.toFixed(1),
12043
+ "s",
12044
+ !isCustom && " (default)"
12045
+ ] })
12046
+ ] }),
12047
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-waypointActions", children: [
12048
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
12049
+ "button",
12050
+ {
12051
+ className: "se-waypointAction",
12052
+ onClick: () => onPreviewWaypoint?.(waypoint),
12053
+ title: "Fly camera to this waypoint",
12054
+ disabled: !onPreviewWaypoint,
12055
+ children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.MapPin, { size: 15 })
12056
+ }
12057
+ ),
12058
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
12059
+ "button",
12060
+ {
12061
+ className: "se-waypointAction",
12062
+ onClick: () => moveWaypoint(index, -1),
12063
+ title: "Move up",
12064
+ disabled: index === 0,
12065
+ children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.ArrowUp, { size: 15 })
12066
+ }
12067
+ ),
12068
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
12069
+ "button",
12070
+ {
12071
+ className: "se-waypointAction",
12072
+ onClick: () => moveWaypoint(index, 1),
12073
+ title: "Move down",
12074
+ disabled: isLast,
12075
+ children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.ArrowDown, { size: 15 })
12076
+ }
12077
+ ),
12078
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
12079
+ "button",
12080
+ {
12081
+ className: "se-waypointAction se-waypointAction--delete",
12082
+ onClick: () => deleteWaypoint(index),
12083
+ title: "Delete waypoint",
12084
+ children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.Trash2, { size: 15 })
12085
+ }
12086
+ )
12087
+ ] })
12088
+ ] }),
12089
+ customizeDurations && !isLast && /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-segmentBetween", children: [
12090
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-segmentLabel", children: [
12091
+ index + 1,
12092
+ " \u2192 ",
12093
+ index + 2,
12094
+ !waypoint.segmentDuration && " (default)"
12095
+ ] }),
12096
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-segmentControls", children: [
12097
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "se-durationLabel", children: "Duration:" }),
12098
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
12099
+ "input",
12100
+ {
12101
+ type: "range",
12102
+ className: "se-segmentSlider",
12103
+ min: 1,
12104
+ max: 20,
12105
+ step: 0.5,
12106
+ value: dur,
12107
+ onChange: (e) => {
12108
+ const val = parseFloat(e.target.value);
12109
+ updateWaypoint(index, { segmentDuration: val });
12110
+ }
12111
+ }
12112
+ ),
12113
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("span", { className: "se-segmentValue", children: [
12114
+ dur.toFixed(1),
12115
+ "s"
12116
+ ] }),
12117
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-presetButtons", children: [
12118
+ [2, 4, 6, 8].map((preset) => /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
12119
+ "button",
12120
+ {
12121
+ className: "se-presetButton",
12122
+ onClick: () => updateWaypoint(index, { segmentDuration: preset }),
12123
+ children: [
12124
+ preset,
12125
+ "s"
12126
+ ]
12127
+ },
12128
+ `preset-${index}-${preset}`
12129
+ )),
12130
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
12131
+ "button",
12132
+ {
12133
+ className: "se-presetButton se-presetButton--default",
12134
+ onClick: () => {
12135
+ const newWaypoints = waypoints.map((wp, i) => {
12136
+ if (i === index) {
12137
+ const { segmentDuration, ...rest } = wp;
12138
+ return rest;
12139
+ }
12140
+ return wp;
12141
+ });
12142
+ const newDuration = calculateTotalDuration(newWaypoints, cinematic.loop);
12143
+ update({
12144
+ waypoints: newWaypoints,
12145
+ duration: newDuration
12146
+ });
12147
+ },
12148
+ children: "Reset"
12149
+ }
12150
+ )
12151
+ ] })
12152
+ ] })
12153
+ ] })
12154
+ ] }, `waypoint-wrapper-${index}`);
12155
+ }) }),
12156
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-totalDuration", children: [
12157
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "se-totalLabel", children: "Total duration:" }),
12158
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "se-totalValue", children: hasEnoughWaypoints ? (cinematic.duration ?? calculateTotalDuration(waypoints, cinematic.loop)).toFixed(1) + "s" : "-" }),
12159
+ !hasEnoughWaypoints && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "se-totalHint", children: "(need at least 2 waypoints)" })
12160
+ ] })
12161
+ ] }),
11806
12162
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
11807
12163
  ToggleRow,
11808
12164
  {
@@ -11816,7 +12172,11 @@ function CinematicTab({
11816
12172
  {
11817
12173
  label: "Loop",
11818
12174
  checked: cinematic.loop,
11819
- onChange: (loop) => update({ loop })
12175
+ onChange: (loop) => {
12176
+ const newLoop = loop;
12177
+ const newDuration = calculateTotalDuration(waypoints, newLoop);
12178
+ update({ loop: newLoop, duration: newDuration });
12179
+ }
11820
12180
  }
11821
12181
  ),
11822
12182
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
@@ -11827,11 +12187,11 @@ function CinematicTab({
11827
12187
  onChange: (autoPlay) => update({ autoPlay })
11828
12188
  }
11829
12189
  ),
11830
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
12190
+ !customizeDurations && hasEnoughWaypoints && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
11831
12191
  SliderRow,
11832
12192
  {
11833
12193
  label: "Duration",
11834
- value: cinematic.duration ?? Math.max(waypoints.length * 3.5, 8),
12194
+ value: cinematic.duration ?? calculateTotalDuration(waypoints, cinematic.loop),
11835
12195
  min: 4,
11836
12196
  max: 120,
11837
12197
  step: 1,
@@ -11843,7 +12203,7 @@ function CinematicTab({
11843
12203
  }
11844
12204
 
11845
12205
  // src/styles/SceneSettingsTab.css
11846
- 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');
12206
+ 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');
11847
12207
 
11848
12208
  // src/components/BindingBuilder/SceneSettings/SceneSettingsTab.tsx
11849
12209
  var import_lucide_react11 = require("lucide-react");
@@ -11882,13 +12242,13 @@ function SceneSettingsTab({
11882
12242
  animationClipDetails = [],
11883
12243
  animationPlayback = FALLBACK_ANIMATION_PLAYBACK
11884
12244
  }) {
11885
- const [internalActiveSubTab, setInternalActiveSubTab] = (0, import_react32.useState)("General");
12245
+ const [internalActiveSubTab, setInternalActiveSubTab] = (0, import_react33.useState)("General");
11886
12246
  const activeSubTab = controlledActiveSubTab ?? internalActiveSubTab;
11887
12247
  const resolvedAnimationPlayback = animationPlayback ?? FALLBACK_ANIMATION_PLAYBACK;
11888
12248
  const { tier } = useBindingBuilderContext();
11889
12249
  const canUseStarterFeatures = hasTier(tier, "starter");
11890
12250
  const canUseGrowthFeatures = hasTier(tier, "growth");
11891
- (0, import_react32.useEffect)(() => {
12251
+ (0, import_react33.useEffect)(() => {
11892
12252
  if (controlledActiveSubTab !== void 0) {
11893
12253
  setInternalActiveSubTab(controlledActiveSubTab);
11894
12254
  }
@@ -12068,7 +12428,7 @@ function EnableToggle2({
12068
12428
  }
12069
12429
 
12070
12430
  // src/components/BindingBuilder/components/MaterialSection.tsx
12071
- var import_react34 = require("react");
12431
+ var import_react35 = require("react");
12072
12432
 
12073
12433
  // src/components/ModelViewer/types/objectBinding.ts
12074
12434
  var MATERIAL_SIDES = ["front", "back", "double"];
@@ -12115,7 +12475,7 @@ function MaterialPanelGroup({
12115
12475
  }
12116
12476
 
12117
12477
  // src/components/BindingBuilder/components/MaterialFields.tsx
12118
- var import_react33 = require("react");
12478
+ var import_react34 = require("react");
12119
12479
  var import_react_colorful4 = require("react-colorful");
12120
12480
  var import_lucide_react13 = require("lucide-react");
12121
12481
  var import_jsx_runtime35 = require("react/jsx-runtime");
@@ -12125,7 +12485,7 @@ function MaterialTexturePathField({
12125
12485
  placeholder,
12126
12486
  onChange
12127
12487
  }) {
12128
- const inputRef = (0, import_react33.useRef)(null);
12488
+ const inputRef = (0, import_react34.useRef)(null);
12129
12489
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "bb-field bb-field--full", children: [
12130
12490
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("label", { className: "bb-label", children: label }),
12131
12491
  /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "bb-fileField", children: [
@@ -12182,9 +12542,9 @@ function MaterialColorField({
12182
12542
  fallback = "#ffffff",
12183
12543
  onChange
12184
12544
  }) {
12185
- const [open, setOpen] = (0, import_react33.useState)(false);
12545
+ const [open, setOpen] = (0, import_react34.useState)(false);
12186
12546
  const color = value || fallback;
12187
- const anchorRef = (0, import_react33.useRef)(null);
12547
+ const anchorRef = (0, import_react34.useRef)(null);
12188
12548
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "bb-field", children: [
12189
12549
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "bb-label", children: label }),
12190
12550
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "se-fieldInline", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { ref: anchorRef, style: { display: "inline-flex" }, children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
@@ -12336,8 +12696,8 @@ function MaterialSection({
12336
12696
  tier: "starter",
12337
12697
  locked: !canUseStarterFeatures
12338
12698
  };
12339
- const [roughnessMode, setRoughnessMode] = (0, import_react34.useState)("roughness");
12340
- const [anisotropyMode, setAnisotropyMode] = (0, import_react34.useState)(
12699
+ const [roughnessMode, setRoughnessMode] = (0, import_react35.useState)("roughness");
12700
+ const [anisotropyMode, setAnisotropyMode] = (0, import_react35.useState)(
12341
12701
  "factor"
12342
12702
  );
12343
12703
  return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
@@ -13376,7 +13736,7 @@ function MaterialPresetGallery({
13376
13736
  }
13377
13737
 
13378
13738
  // src/components/BindingBuilder/components/ToolbarExportMenu.tsx
13379
- var import_react35 = require("react");
13739
+ var import_react36 = require("react");
13380
13740
  var import_lucide_react14 = require("lucide-react");
13381
13741
  var import_jsx_runtime38 = require("react/jsx-runtime");
13382
13742
  function ToolbarExportMenu({
@@ -13385,9 +13745,9 @@ function ToolbarExportMenu({
13385
13745
  onExportJson,
13386
13746
  onExportTs
13387
13747
  }) {
13388
- const [open, setOpen] = (0, import_react35.useState)(false);
13389
- const wrapperRef = (0, import_react35.useRef)(null);
13390
- (0, import_react35.useEffect)(() => {
13748
+ const [open, setOpen] = (0, import_react36.useState)(false);
13749
+ const wrapperRef = (0, import_react36.useRef)(null);
13750
+ (0, import_react36.useEffect)(() => {
13391
13751
  if (!open) return;
13392
13752
  const close = (event) => {
13393
13753
  if (!wrapperRef.current?.contains(event.target)) {
@@ -13824,7 +14184,7 @@ function CameraPositionPanel({
13824
14184
  }
13825
14185
 
13826
14186
  // src/components/BindingBuilder/components/SceneNodesPanel.tsx
13827
- var import_react36 = require("react");
14187
+ var import_react37 = require("react");
13828
14188
 
13829
14189
  // src/components/BindingBuilder/utils/bindingFactory.ts
13830
14190
  function slugify(value) {
@@ -13913,9 +14273,9 @@ function SceneNodesPanel({
13913
14273
  isOpen = true,
13914
14274
  onToggle
13915
14275
  }) {
13916
- const [search, setSearch] = (0, import_react36.useState)("");
13917
- const nodeKeys = (0, import_react36.useMemo)(() => Object.keys(bindings), [bindings]);
13918
- const filteredNodeKeys = (0, import_react36.useMemo)(() => {
14276
+ const [search, setSearch] = (0, import_react37.useState)("");
14277
+ const nodeKeys = (0, import_react37.useMemo)(() => Object.keys(bindings), [bindings]);
14278
+ const filteredNodeKeys = (0, import_react37.useMemo)(() => {
13919
14279
  const query = search.trim().toLowerCase();
13920
14280
  if (!query) return nodeKeys;
13921
14281
  return nodeKeys.filter((nodeKey) => {
@@ -14058,7 +14418,7 @@ function mergeAnimationConfig(sceneConfig, animationDetails) {
14058
14418
  }
14059
14419
 
14060
14420
  // src/components/BindingBuilder/hooks/useUndoableState.ts
14061
- var import_react37 = require("react");
14421
+ var import_react38 = require("react");
14062
14422
  var MAX_HISTORY = 100;
14063
14423
  function historyReducer(state, action) {
14064
14424
  switch (action.type) {
@@ -14097,13 +14457,13 @@ function historyReducer(state, action) {
14097
14457
  }
14098
14458
  }
14099
14459
  function useUndoableState(initial, coalesceWindowMs = 600) {
14100
- const [state, dispatch] = (0, import_react37.useReducer)(historyReducer, {
14460
+ const [state, dispatch] = (0, import_react38.useReducer)(historyReducer, {
14101
14461
  past: [],
14102
14462
  present: initial,
14103
14463
  future: []
14104
14464
  });
14105
- const lastSetAt = (0, import_react37.useRef)(0);
14106
- const setValue = (0, import_react37.useCallback)(
14465
+ const lastSetAt = (0, import_react38.useRef)(0);
14466
+ const setValue = (0, import_react38.useCallback)(
14107
14467
  (updater) => {
14108
14468
  const now = Date.now();
14109
14469
  const coalesce = now - lastSetAt.current < coalesceWindowMs;
@@ -14112,15 +14472,15 @@ function useUndoableState(initial, coalesceWindowMs = 600) {
14112
14472
  },
14113
14473
  [coalesceWindowMs]
14114
14474
  );
14115
- const undo = (0, import_react37.useCallback)(() => {
14475
+ const undo = (0, import_react38.useCallback)(() => {
14116
14476
  lastSetAt.current = 0;
14117
14477
  dispatch({ type: "undo" });
14118
14478
  }, []);
14119
- const redo = (0, import_react37.useCallback)(() => {
14479
+ const redo = (0, import_react38.useCallback)(() => {
14120
14480
  lastSetAt.current = 0;
14121
14481
  dispatch({ type: "redo" });
14122
14482
  }, []);
14123
- const reset = (0, import_react37.useCallback)((value) => {
14483
+ const reset = (0, import_react38.useCallback)((value) => {
14124
14484
  lastSetAt.current = 0;
14125
14485
  dispatch({ type: "reset", value });
14126
14486
  }, []);
@@ -14366,30 +14726,30 @@ function mergeSceneConfigSection(current, incoming) {
14366
14726
  return next;
14367
14727
  }
14368
14728
  function BindingBuilder({ licenseKey }) {
14369
- const modelInputRef = (0, import_react38.useRef)(null);
14370
- const bindingsInputRef = (0, import_react38.useRef)(null);
14371
- const sceneConfigInputRef = (0, import_react38.useRef)(null);
14372
- const [modelUrl, setModelUrl] = (0, import_react38.useState)(null);
14373
- const [modelName, setModelName] = (0, import_react38.useState)("");
14374
- const [selectedItem, setSelectedItem] = (0, import_react38.useState)(null);
14729
+ const modelInputRef = (0, import_react39.useRef)(null);
14730
+ const bindingsInputRef = (0, import_react39.useRef)(null);
14731
+ const sceneConfigInputRef = (0, import_react39.useRef)(null);
14732
+ const [modelUrl, setModelUrl] = (0, import_react39.useState)(null);
14733
+ const [modelName, setModelName] = (0, import_react39.useState)("");
14734
+ const [selectedItem, setSelectedItem] = (0, import_react39.useState)(null);
14375
14735
  const bindingsHistory = useUndoableState({});
14376
14736
  const bindings = bindingsHistory.value;
14377
14737
  const setBindings = bindingsHistory.setValue;
14378
- const [loading, setLoading] = (0, import_react38.useState)(false);
14379
- const [isExporting, setIsExporting] = (0, import_react38.useState)(false);
14380
- const [statusMessage, setStatusMessage] = (0, import_react38.useState)(
14738
+ const [loading, setLoading] = (0, import_react39.useState)(false);
14739
+ const [isExporting, setIsExporting] = (0, import_react39.useState)(false);
14740
+ const [statusMessage, setStatusMessage] = (0, import_react39.useState)(
14381
14741
  "Load the demo model or upload a GLB/GLTF model to generate bindings."
14382
14742
  );
14383
- const [statusTone, setStatusTone] = (0, import_react38.useState)("default");
14384
- const [statusBannerVisible, setStatusBannerVisible] = (0, import_react38.useState)(false);
14385
- const [metricsDraft, setMetricsDraft] = (0, import_react38.useState)("{}");
14386
- const [metadataDraft, setMetadataDraft] = (0, import_react38.useState)("{}");
14387
- const [metricsError, setMetricsError] = (0, import_react38.useState)(null);
14388
- const [metadataError, setMetadataError] = (0, import_react38.useState)(null);
14389
- const [isPreviewDragActive, setIsPreviewDragActive] = (0, import_react38.useState)(false);
14390
- const [isEditorCollapsed, setIsEditorCollapsed] = (0, import_react38.useState)(false);
14391
- const [isNodesCollapsed, setIsNodesCollapsed] = (0, import_react38.useState)(false);
14392
- const [openObjectSections, setOpenObjectSections] = (0, import_react38.useState)({
14743
+ const [statusTone, setStatusTone] = (0, import_react39.useState)("default");
14744
+ const [statusBannerVisible, setStatusBannerVisible] = (0, import_react39.useState)(false);
14745
+ const [metricsDraft, setMetricsDraft] = (0, import_react39.useState)("{}");
14746
+ const [metadataDraft, setMetadataDraft] = (0, import_react39.useState)("{}");
14747
+ const [metricsError, setMetricsError] = (0, import_react39.useState)(null);
14748
+ const [metadataError, setMetadataError] = (0, import_react39.useState)(null);
14749
+ const [isPreviewDragActive, setIsPreviewDragActive] = (0, import_react39.useState)(false);
14750
+ const [isEditorCollapsed, setIsEditorCollapsed] = (0, import_react39.useState)(false);
14751
+ const [isNodesCollapsed, setIsNodesCollapsed] = (0, import_react39.useState)(false);
14752
+ const [openObjectSections, setOpenObjectSections] = (0, import_react39.useState)({
14393
14753
  identity: true,
14394
14754
  basics: true,
14395
14755
  material: true,
@@ -14398,7 +14758,7 @@ function BindingBuilder({ licenseKey }) {
14398
14758
  metrics: false,
14399
14759
  metadata: false
14400
14760
  });
14401
- const [openMaterialPanels, setOpenMaterialPanels] = (0, import_react38.useState)({
14761
+ const [openMaterialPanels, setOpenMaterialPanels] = (0, import_react39.useState)({
14402
14762
  pbr: true,
14403
14763
  roughness: false,
14404
14764
  emission: false,
@@ -14414,27 +14774,27 @@ function BindingBuilder({ licenseKey }) {
14414
14774
  });
14415
14775
  const toggleObjectSection = (key) => setOpenObjectSections((prev) => ({ ...prev, [key]: !prev[key] }));
14416
14776
  const toggleMaterialPanel = (key) => setOpenMaterialPanels((prev) => ({ ...prev, [key]: !prev[key] }));
14417
- const [activeEditorTab, setActiveEditorTab] = (0, import_react38.useState)(
14777
+ const [activeEditorTab, setActiveEditorTab] = (0, import_react39.useState)(
14418
14778
  "object"
14419
14779
  );
14420
- const [activeSubTab, setActiveSubTab] = (0, import_react38.useState)("General");
14421
- const [pbrWorkflow, setPbrWorkflow] = (0, import_react38.useState)("metalness");
14422
- const [materialPanelEnabled, setMaterialPanelEnabled] = (0, import_react38.useState)(createMaterialPanelEnabledState());
14423
- const materialPanelItemRef = (0, import_react38.useRef)(null);
14424
- const [zoomOnSelected, setZoomOnSelected] = (0, import_react38.useState)(false);
14425
- const [highlightOnHover, setHighlightOnHover] = (0, import_react38.useState)(true);
14426
- const [resetDialogOpen, setResetDialogOpen] = (0, import_react38.useState)(false);
14427
- const [resetTarget, setResetTarget] = (0, import_react38.useState)(
14780
+ const [activeSubTab, setActiveSubTab] = (0, import_react39.useState)("General");
14781
+ const [pbrWorkflow, setPbrWorkflow] = (0, import_react39.useState)("metalness");
14782
+ const [materialPanelEnabled, setMaterialPanelEnabled] = (0, import_react39.useState)(createMaterialPanelEnabledState());
14783
+ const materialPanelItemRef = (0, import_react39.useRef)(null);
14784
+ const [zoomOnSelected, setZoomOnSelected] = (0, import_react39.useState)(false);
14785
+ const [highlightOnHover, setHighlightOnHover] = (0, import_react39.useState)(true);
14786
+ const [resetDialogOpen, setResetDialogOpen] = (0, import_react39.useState)(false);
14787
+ const [resetTarget, setResetTarget] = (0, import_react39.useState)(
14428
14788
  null
14429
14789
  );
14430
14790
  const sceneConfigHistory = useUndoableState(DEFAULT_SCENE_CONFIG);
14431
14791
  const sceneConfig = sceneConfigHistory.value;
14432
14792
  const setSceneConfig = sceneConfigHistory.setValue;
14433
- const [activeAnnotation, setActiveAnnotation] = (0, import_react38.useState)(null);
14434
- const [panelHoveredBinding, setPanelHoveredBinding] = (0, import_react38.useState)(null);
14793
+ const [activeAnnotation, setActiveAnnotation] = (0, import_react39.useState)(null);
14794
+ const [panelHoveredBinding, setPanelHoveredBinding] = (0, import_react39.useState)(null);
14435
14795
  const license = useLicense(licenseKey);
14436
14796
  const tier = normalizeTier(license.tier);
14437
- const bindingBuilderContextValue = (0, import_react38.useMemo)(
14797
+ const bindingBuilderContextValue = (0, import_react39.useMemo)(
14438
14798
  () => ({
14439
14799
  isInsideEditor: true,
14440
14800
  activeTab: activeEditorTab,
@@ -14443,11 +14803,11 @@ function BindingBuilder({ licenseKey }) {
14443
14803
  }),
14444
14804
  [activeEditorTab, activeSubTab, tier]
14445
14805
  );
14446
- const uploadedModelRef = (0, import_react38.useRef)(null);
14447
- const previewDragDepthRef = (0, import_react38.useRef)(0);
14448
- const loadRequestRef = (0, import_react38.useRef)(0);
14449
- const pendingLoadRef = (0, import_react38.useRef)(null);
14450
- const loadedSceneRef = (0, import_react38.useRef)(null);
14806
+ const uploadedModelRef = (0, import_react39.useRef)(null);
14807
+ const previewDragDepthRef = (0, import_react39.useRef)(0);
14808
+ const loadRequestRef = (0, import_react39.useRef)(0);
14809
+ const pendingLoadRef = (0, import_react39.useRef)(null);
14810
+ const loadedSceneRef = (0, import_react39.useRef)(null);
14451
14811
  const {
14452
14812
  cameraState,
14453
14813
  resetView,
@@ -14457,14 +14817,16 @@ function BindingBuilder({ licenseKey }) {
14457
14817
  handleCameraChange,
14458
14818
  handleViewerReady
14459
14819
  } = useViewerCamera();
14460
- const handlePreviewWaypoint = (0, import_react38.useCallback)(
14820
+ const handlePreviewWaypoint = (0, import_react39.useCallback)(
14461
14821
  (waypoint) => {
14462
14822
  setCameraState(
14463
14823
  {
14464
14824
  position: waypoint.position,
14465
14825
  target: waypoint.target,
14466
14826
  fov: cameraState?.fov ?? null,
14467
- zoom: cameraState?.zoom ?? 1
14827
+ zoom: cameraState?.zoom ?? 1,
14828
+ azimuthAngle: waypoint.azimuthAngle,
14829
+ polarAngle: waypoint.polarAngle
14468
14830
  },
14469
14831
  true
14470
14832
  );
@@ -14479,7 +14841,7 @@ function BindingBuilder({ licenseKey }) {
14479
14841
  position: formatVector3(cameraState.position),
14480
14842
  target: formatVector3(cameraState.target)
14481
14843
  } : null;
14482
- const handleAnnotationsChange = (0, import_react38.useCallback)(
14844
+ const handleAnnotationsChange = (0, import_react39.useCallback)(
14483
14845
  (newAnnotations) => {
14484
14846
  setSceneConfig((prev) => {
14485
14847
  const currentIds = prev.annotations.map((a) => a.id).join(",");
@@ -14495,7 +14857,7 @@ function BindingBuilder({ licenseKey }) {
14495
14857
  },
14496
14858
  []
14497
14859
  );
14498
- const handleActiveAnnotationChange = (0, import_react38.useCallback)(
14860
+ const handleActiveAnnotationChange = (0, import_react39.useCallback)(
14499
14861
  (annotation) => {
14500
14862
  setActiveAnnotation(
14501
14863
  annotation ? {
@@ -14505,7 +14867,7 @@ function BindingBuilder({ licenseKey }) {
14505
14867
  },
14506
14868
  []
14507
14869
  );
14508
- (0, import_react38.useEffect)(() => {
14870
+ (0, import_react39.useEffect)(() => {
14509
14871
  if (!selectedBinding) {
14510
14872
  setMetricsDraft("{}");
14511
14873
  setMetadataDraft("{}");
@@ -14518,7 +14880,7 @@ function BindingBuilder({ licenseKey }) {
14518
14880
  setMetricsError(null);
14519
14881
  setMetadataError(null);
14520
14882
  }, [selectedBinding]);
14521
- (0, import_react38.useEffect)(() => {
14883
+ (0, import_react39.useEffect)(() => {
14522
14884
  const derived = createMaterialPanelEnabledState(selectedMaterial);
14523
14885
  if (materialPanelItemRef.current !== selectedItem) {
14524
14886
  materialPanelItemRef.current = selectedItem;
@@ -14537,12 +14899,12 @@ function BindingBuilder({ licenseKey }) {
14537
14899
  return changed ? next : prev;
14538
14900
  });
14539
14901
  }, [selectedItem, selectedMaterial]);
14540
- (0, import_react38.useEffect)(() => {
14902
+ (0, import_react39.useEffect)(() => {
14541
14903
  return () => {
14542
14904
  uploadedModelRef.current?.cleanup();
14543
14905
  };
14544
14906
  }, []);
14545
- (0, import_react38.useEffect)(() => {
14907
+ (0, import_react39.useEffect)(() => {
14546
14908
  const handleKeyDown = (event) => {
14547
14909
  if (event.key.toLowerCase() !== "z" || !(event.metaKey || event.ctrlKey)) {
14548
14910
  return;
@@ -14563,7 +14925,7 @@ function BindingBuilder({ licenseKey }) {
14563
14925
  window.addEventListener("keydown", handleKeyDown);
14564
14926
  return () => window.removeEventListener("keydown", handleKeyDown);
14565
14927
  }, [activeEditorTab, bindingsHistory, sceneConfigHistory]);
14566
- (0, import_react38.useEffect)(() => {
14928
+ (0, import_react39.useEffect)(() => {
14567
14929
  if (loading || statusTone === "default" || statusTone === "success") return;
14568
14930
  setStatusBannerVisible(true);
14569
14931
  const timeout = window.setTimeout(
@@ -14572,8 +14934,8 @@ function BindingBuilder({ licenseKey }) {
14572
14934
  );
14573
14935
  return () => window.clearTimeout(timeout);
14574
14936
  }, [statusMessage, statusTone, loading]);
14575
- const nodeKeys = (0, import_react38.useMemo)(() => Object.keys(bindings), [bindings]);
14576
- const idError = (0, import_react38.useMemo)(() => {
14937
+ const nodeKeys = (0, import_react39.useMemo)(() => Object.keys(bindings), [bindings]);
14938
+ const idError = (0, import_react39.useMemo)(() => {
14577
14939
  if (!selectedItem || !selectedBinding) return null;
14578
14940
  const trimmed = selectedBinding.id.trim();
14579
14941
  if (!trimmed) return "ID is required.";
@@ -14616,7 +14978,7 @@ function BindingBuilder({ licenseKey }) {
14616
14978
  });
14617
14979
  }
14618
14980
  };
14619
- const applyBindingsFromScene = (0, import_react38.useCallback)(
14981
+ const applyBindingsFromScene = (0, import_react39.useCallback)(
14620
14982
  (scene, nextModelName) => {
14621
14983
  const nodeNames = collectNodeNames(scene);
14622
14984
  bindingsHistory.reset(buildBindingsFromNodes(nodeNames));
@@ -14655,7 +15017,7 @@ function BindingBuilder({ licenseKey }) {
14655
15017
  bindingsHistory.reset({});
14656
15018
  setSelectedItem(null);
14657
15019
  };
14658
- const handleViewerModelLoaded = (0, import_react38.useCallback)(
15020
+ const handleViewerModelLoaded = (0, import_react39.useCallback)(
14659
15021
  (scene) => {
14660
15022
  const pending = pendingLoadRef.current;
14661
15023
  if (!pending || pending.requestId !== loadRequestRef.current) return;
@@ -14665,7 +15027,7 @@ function BindingBuilder({ licenseKey }) {
14665
15027
  },
14666
15028
  [applyBindingsFromScene]
14667
15029
  );
14668
- const handleViewerLoadError = (0, import_react38.useCallback)((error) => {
15030
+ const handleViewerLoadError = (0, import_react39.useCallback)((error) => {
14669
15031
  const pending = pendingLoadRef.current;
14670
15032
  if (!pending || pending.requestId !== loadRequestRef.current) return;
14671
15033
  pendingLoadRef.current = null;
@@ -14676,7 +15038,7 @@ function BindingBuilder({ licenseKey }) {
14676
15038
  setStatusTone("error");
14677
15039
  setLoading(false);
14678
15040
  }, []);
14679
- const handleAnimationsReady = (0, import_react38.useCallback)(
15041
+ const handleAnimationsReady = (0, import_react39.useCallback)(
14680
15042
  (controls) => {
14681
15043
  viewerAnimations.handleAnimationsReady(controls);
14682
15044
  const details = controls.clipDetails ?? controls.clips.map((sourceName) => ({ sourceName, duration: 0 }));
@@ -14977,7 +15339,7 @@ function BindingBuilder({ licenseKey }) {
14977
15339
  }));
14978
15340
  setError(null);
14979
15341
  };
14980
- const handleViewerSelect = (0, import_react38.useCallback)(
15342
+ const handleViewerSelect = (0, import_react39.useCallback)(
14981
15343
  (binding) => {
14982
15344
  if (!binding) {
14983
15345
  setSelectedItem(null);
@@ -14990,7 +15352,7 @@ function BindingBuilder({ licenseKey }) {
14990
15352
  },
14991
15353
  [bindings]
14992
15354
  );
14993
- const handleSceneNodeSelect = (0, import_react38.useCallback)(
15355
+ const handleSceneNodeSelect = (0, import_react39.useCallback)(
14994
15356
  async (nodeKey) => {
14995
15357
  setSelectedItem(nodeKey);
14996
15358
  setPanelHoveredBinding(null);
@@ -14998,7 +15360,7 @@ function BindingBuilder({ licenseKey }) {
14998
15360
  },
14999
15361
  [focusObject]
15000
15362
  );
15001
- const saveCameraPositionForSelectedObject = (0, import_react38.useCallback)(() => {
15363
+ const saveCameraPositionForSelectedObject = (0, import_react39.useCallback)(() => {
15002
15364
  if (!selectedItem || !cameraState) {
15003
15365
  return;
15004
15366
  }
@@ -15013,7 +15375,7 @@ function BindingBuilder({ licenseKey }) {
15013
15375
  cameraState: currentCameraState
15014
15376
  }));
15015
15377
  }, [selectedItem, cameraState]);
15016
- const goToSavedCameraPosition = (0, import_react38.useCallback)(() => {
15378
+ const goToSavedCameraPosition = (0, import_react39.useCallback)(() => {
15017
15379
  if (!savedCameraPosition?.position || !savedCameraPosition?.target) return;
15018
15380
  setCameraState(
15019
15381
  {
@@ -15025,28 +15387,28 @@ function BindingBuilder({ licenseKey }) {
15025
15387
  true
15026
15388
  );
15027
15389
  }, [savedCameraPosition, setCameraState, cameraState]);
15028
- const clearSavedCameraPosition = (0, import_react38.useCallback)(() => {
15390
+ const clearSavedCameraPosition = (0, import_react39.useCallback)(() => {
15029
15391
  if (!selectedItem) return;
15030
15392
  updateBinding(selectedItem, (binding) => ({
15031
15393
  ...binding,
15032
15394
  cameraState: void 0
15033
15395
  }));
15034
15396
  }, [selectedItem]);
15035
- const handleResetView = (0, import_react38.useCallback)(async () => {
15397
+ const handleResetView = (0, import_react39.useCallback)(async () => {
15036
15398
  await resetView();
15037
15399
  }, [resetView]);
15038
- const handleFitScene = (0, import_react38.useCallback)(async () => {
15400
+ const handleFitScene = (0, import_react39.useCallback)(async () => {
15039
15401
  await fitScene();
15040
15402
  }, [fitScene]);
15041
- const openResetDialog = (0, import_react38.useCallback)((target) => {
15403
+ const openResetDialog = (0, import_react39.useCallback)((target) => {
15042
15404
  setResetTarget(target);
15043
15405
  setResetDialogOpen(true);
15044
15406
  }, []);
15045
- const closeResetDialog = (0, import_react38.useCallback)(() => {
15407
+ const closeResetDialog = (0, import_react39.useCallback)(() => {
15046
15408
  setResetDialogOpen(false);
15047
15409
  setResetTarget(null);
15048
15410
  }, []);
15049
- const executeReset = (0, import_react38.useCallback)(() => {
15411
+ const executeReset = (0, import_react39.useCallback)(() => {
15050
15412
  if (resetTarget === "bindings") {
15051
15413
  if (!nodeKeys.length) {
15052
15414
  closeResetDialog();
@@ -15177,7 +15539,9 @@ function BindingBuilder({ licenseKey }) {
15177
15539
  // the whole settings panel for no reason.
15178
15540
  activeSubTab === "Cinematic" && cameraState ? {
15179
15541
  position: cameraState.position,
15180
- target: cameraState.target
15542
+ target: cameraState.target,
15543
+ azimuthAngle: cameraState.azimuthAngle,
15544
+ polarAngle: cameraState.polarAngle
15181
15545
  } : null
15182
15546
  ),
15183
15547
  onPreviewWaypoint: handlePreviewWaypoint,
@@ -15602,7 +15966,7 @@ function BindingBuilder({ licenseKey }) {
15602
15966
  }
15603
15967
 
15604
15968
  // src/components/SimpleModelViewer/SimpleModelViewer.tsx
15605
- var import_react39 = require("react");
15969
+ var import_react40 = require("react");
15606
15970
  var import_drei6 = require("@react-three/drei");
15607
15971
  var import_fiber12 = require("@react-three/fiber");
15608
15972
  var import_fiber13 = require("@react-three/fiber");
@@ -15642,7 +16006,7 @@ function applyClippingPlanes(scene, controls) {
15642
16006
  camera.updateProjectionMatrix();
15643
16007
  controls.maxDistance = size * 10;
15644
16008
  }
15645
- var ModelErrorBoundary2 = class extends import_react39.Component {
16009
+ var ModelErrorBoundary2 = class extends import_react40.Component {
15646
16010
  constructor() {
15647
16011
  super(...arguments);
15648
16012
  this.state = { hasError: false };
@@ -15674,12 +16038,12 @@ function InnerModel({
15674
16038
  meshopt: true
15675
16039
  });
15676
16040
  const invalidate = (0, import_fiber12.useThree)((state) => state.invalidate);
15677
- const [hoveredKey, setHoveredKey] = (0, import_react39.useState)(null);
15678
- const scene = (0, import_react39.useMemo)(
16041
+ const [hoveredKey, setHoveredKey] = (0, import_react40.useState)(null);
16042
+ const scene = (0, import_react40.useMemo)(
15679
16043
  () => import_three_stdlib4.SkeletonUtils.clone(rawScene),
15680
16044
  [rawScene]
15681
16045
  );
15682
- const meshMap = (0, import_react39.useMemo)(() => {
16046
+ const meshMap = (0, import_react40.useMemo)(() => {
15683
16047
  const map = {};
15684
16048
  scene.traverse((obj) => {
15685
16049
  if (!isMeshNode2(obj)) return;
@@ -15688,7 +16052,7 @@ function InnerModel({
15688
16052
  });
15689
16053
  return map;
15690
16054
  }, [scene]);
15691
- const perKeyMaterials = (0, import_react39.useMemo)(() => {
16055
+ const perKeyMaterials = (0, import_react40.useMemo)(() => {
15692
16056
  const map = {};
15693
16057
  for (const [key, { ref }] of Object.entries(meshMap)) {
15694
16058
  const original = Array.isArray(ref.material) ? ref.material[0] : ref.material;
@@ -15704,14 +16068,14 @@ function InnerModel({
15704
16068
  }
15705
16069
  return map;
15706
16070
  }, [meshMap]);
15707
- (0, import_react39.useEffect)(
16071
+ (0, import_react40.useEffect)(
15708
16072
  () => () => {
15709
16073
  Object.values(perKeyMaterials).forEach((m) => m.dispose());
15710
16074
  },
15711
16075
  [perKeyMaterials]
15712
16076
  );
15713
- const notifiedUrl = (0, import_react39.useRef)(null);
15714
- (0, import_react39.useEffect)(() => {
16077
+ const notifiedUrl = (0, import_react40.useRef)(null);
16078
+ (0, import_react40.useEffect)(() => {
15715
16079
  if (notifiedUrl.current === url) return;
15716
16080
  notifiedUrl.current = url;
15717
16081
  scene.updateWorldMatrix(true, true);
@@ -15719,7 +16083,7 @@ function InnerModel({
15719
16083
  onMeshesReady(meshMap);
15720
16084
  onModelLoaded(scene);
15721
16085
  }, [url, scene, meshMap, onMeshesReady, onModelLoaded]);
15722
- (0, import_react39.useEffect)(() => {
16086
+ (0, import_react40.useEffect)(() => {
15723
16087
  const controller = new AbortController();
15724
16088
  void buildSceneBVH(scene, controller.signal);
15725
16089
  return () => {
@@ -15727,26 +16091,26 @@ function InnerModel({
15727
16091
  disposeSceneBVH(scene);
15728
16092
  };
15729
16093
  }, [scene]);
15730
- const pointerRaycastEnabled = (0, import_react39.useMemo)(
16094
+ const pointerRaycastEnabled = (0, import_react40.useMemo)(
15731
16095
  () => !exceedsBvhBudget(scene),
15732
16096
  [scene]
15733
16097
  );
15734
- (0, import_react39.useEffect)(() => {
16098
+ (0, import_react40.useEffect)(() => {
15735
16099
  for (const [key, { ref }] of Object.entries(meshMap)) {
15736
16100
  nodeRefsOut.current[key] = ref;
15737
16101
  }
15738
16102
  }, [meshMap, nodeRefsOut]);
15739
- (0, import_react39.useEffect)(() => {
16103
+ (0, import_react40.useEffect)(() => {
15740
16104
  if (!highlightOnHover && hoveredKey !== null) {
15741
16105
  setHoveredKey(null);
15742
16106
  }
15743
16107
  }, [highlightOnHover, hoveredKey]);
15744
- (0, import_react39.useEffect)(() => {
16108
+ (0, import_react40.useEffect)(() => {
15745
16109
  return () => {
15746
16110
  document.body.style.cursor = "default";
15747
16111
  };
15748
16112
  }, []);
15749
- (0, import_react39.useEffect)(() => {
16113
+ (0, import_react40.useEffect)(() => {
15750
16114
  for (const [key, { ref }] of Object.entries(meshMap)) {
15751
16115
  ref.visible = !hiddenKeys.has(key);
15752
16116
  const mat = perKeyMaterials[key];
@@ -15816,11 +16180,11 @@ function CameraFitter({
15816
16180
  refitOnResize,
15817
16181
  onInitialFitComplete
15818
16182
  }) {
15819
- const fitted = (0, import_react39.useRef)(false);
16183
+ const fitted = (0, import_react40.useRef)(false);
15820
16184
  const invalidate = (0, import_fiber12.useThree)((state) => state.invalidate);
15821
16185
  const size = (0, import_fiber12.useThree)((state) => state.size);
15822
- const resizeTimerRef = (0, import_react39.useRef)(null);
15823
- (0, import_react39.useEffect)(() => {
16186
+ const resizeTimerRef = (0, import_react40.useRef)(null);
16187
+ (0, import_react40.useEffect)(() => {
15824
16188
  fitted.current = false;
15825
16189
  invalidate();
15826
16190
  }, [invalidate, scene]);
@@ -15840,7 +16204,7 @@ function CameraFitter({
15840
16204
  }).catch(() => {
15841
16205
  }).finally(onInitialFitComplete);
15842
16206
  });
15843
- (0, import_react39.useEffect)(() => {
16207
+ (0, import_react40.useEffect)(() => {
15844
16208
  if (!refitOnResize || !fitted.current || !scene || !controlsRef.current) {
15845
16209
  return;
15846
16210
  }
@@ -15868,7 +16232,7 @@ function CameraFitter({
15868
16232
  function ExposureController({ exposure }) {
15869
16233
  const gl = (0, import_fiber12.useThree)((state) => state.gl);
15870
16234
  const invalidate = (0, import_fiber12.useThree)((state) => state.invalidate);
15871
- (0, import_react39.useEffect)(() => {
16235
+ (0, import_react40.useEffect)(() => {
15872
16236
  gl.toneMappingExposure = exposure;
15873
16237
  invalidate();
15874
16238
  }, [gl, exposure, invalidate]);
@@ -15976,7 +16340,7 @@ function EnvironmentSection({
15976
16340
  title,
15977
16341
  children
15978
16342
  }) {
15979
- const [collapsed, setCollapsed] = (0, import_react39.useState)(false);
16343
+ const [collapsed, setCollapsed] = (0, import_react40.useState)(false);
15980
16344
  return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "env-section", children: [
15981
16345
  /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
15982
16346
  "button",
@@ -16141,8 +16505,8 @@ function SceneObjectsPanel2({
16141
16505
  onHover,
16142
16506
  onToggleVisibility
16143
16507
  }) {
16144
- const [search, setSearch] = (0, import_react39.useState)("");
16145
- const [rowHovered, setRowHovered] = (0, import_react39.useState)(null);
16508
+ const [search, setSearch] = (0, import_react40.useState)("");
16509
+ const [rowHovered, setRowHovered] = (0, import_react40.useState)(null);
16146
16510
  const entries = Object.values(meshKeys).filter(
16147
16511
  (m) => m.label.toLowerCase().includes(search.toLowerCase())
16148
16512
  );
@@ -16266,35 +16630,35 @@ function SimpleModelViewer({
16266
16630
  onModelLoaded,
16267
16631
  onLoadError
16268
16632
  }) {
16269
- const cameraControlsRef = (0, import_react39.useRef)(null);
16270
- const nodeRefsOut = (0, import_react39.useRef)({});
16271
- const fileInputRef = (0, import_react39.useRef)(null);
16272
- const uploadedModelRef = (0, import_react39.useRef)(null);
16273
- const dragDepthRef = (0, import_react39.useRef)(0);
16274
- const [loadedScene, setLoadedScene] = (0, import_react39.useState)(null);
16275
- const [largeModelTriangles, setLargeModelTriangles] = (0, import_react39.useState)(
16633
+ const cameraControlsRef = (0, import_react40.useRef)(null);
16634
+ const nodeRefsOut = (0, import_react40.useRef)({});
16635
+ const fileInputRef = (0, import_react40.useRef)(null);
16636
+ const uploadedModelRef = (0, import_react40.useRef)(null);
16637
+ const dragDepthRef = (0, import_react40.useRef)(0);
16638
+ const [loadedScene, setLoadedScene] = (0, import_react40.useState)(null);
16639
+ const [largeModelTriangles, setLargeModelTriangles] = (0, import_react40.useState)(
16276
16640
  null
16277
16641
  );
16278
- const [largeModelNoticeDismissed, setLargeModelNoticeDismissed] = (0, import_react39.useState)(false);
16279
- const [meshKeys, setMeshKeys] = (0, import_react39.useState)({});
16280
- const [hiddenKeys, setHiddenKeys] = (0, import_react39.useState)(/* @__PURE__ */ new Set());
16281
- const [selectedKey, setSelectedKey] = (0, import_react39.useState)(null);
16282
- const [panelHoveredKey, setPanelHoveredKey] = (0, import_react39.useState)(null);
16283
- const [uploadedModelUrl, setUploadedModelUrl] = (0, import_react39.useState)(null);
16284
- const [uploadedModelName, setUploadedModelName] = (0, import_react39.useState)(
16642
+ const [largeModelNoticeDismissed, setLargeModelNoticeDismissed] = (0, import_react40.useState)(false);
16643
+ const [meshKeys, setMeshKeys] = (0, import_react40.useState)({});
16644
+ const [hiddenKeys, setHiddenKeys] = (0, import_react40.useState)(/* @__PURE__ */ new Set());
16645
+ const [selectedKey, setSelectedKey] = (0, import_react40.useState)(null);
16646
+ const [panelHoveredKey, setPanelHoveredKey] = (0, import_react40.useState)(null);
16647
+ const [uploadedModelUrl, setUploadedModelUrl] = (0, import_react40.useState)(null);
16648
+ const [uploadedModelName, setUploadedModelName] = (0, import_react40.useState)(
16285
16649
  null
16286
16650
  );
16287
- const [modelLoadFailed, setModelLoadFailed] = (0, import_react39.useState)(false);
16288
- const [isResolvingUpload, setIsResolvingUpload] = (0, import_react39.useState)(false);
16289
- const [initialFitComplete, setInitialFitComplete] = (0, import_react39.useState)(false);
16290
- const [isDragActive, setIsDragActive] = (0, import_react39.useState)(false);
16291
- const [isPanelOpen, setIsPanelOpen] = (0, import_react39.useState)(
16651
+ const [modelLoadFailed, setModelLoadFailed] = (0, import_react40.useState)(false);
16652
+ const [isResolvingUpload, setIsResolvingUpload] = (0, import_react40.useState)(false);
16653
+ const [initialFitComplete, setInitialFitComplete] = (0, import_react40.useState)(false);
16654
+ const [isDragActive, setIsDragActive] = (0, import_react40.useState)(false);
16655
+ const [isPanelOpen, setIsPanelOpen] = (0, import_react40.useState)(
16292
16656
  () => typeof window === "undefined" ? true : window.innerWidth > 900
16293
16657
  );
16294
- const [isEnvPanelOpen, setIsEnvPanelOpen] = (0, import_react39.useState)(
16658
+ const [isEnvPanelOpen, setIsEnvPanelOpen] = (0, import_react40.useState)(
16295
16659
  () => typeof window === "undefined" ? true : window.innerWidth > 900
16296
16660
  );
16297
- const [envSettings, setEnvSettings] = (0, import_react39.useState)({
16661
+ const [envSettings, setEnvSettings] = (0, import_react40.useState)({
16298
16662
  backgroundEnabled,
16299
16663
  backgroundColor,
16300
16664
  autoRotate,
@@ -16306,7 +16670,7 @@ function SimpleModelViewer({
16306
16670
  directionalColor
16307
16671
  });
16308
16672
  const activeModelUrl = enableModelUpload ? uploadedModelUrl : modelUrl;
16309
- const resetModelState = (0, import_react39.useCallback)(() => {
16673
+ const resetModelState = (0, import_react40.useCallback)(() => {
16310
16674
  setLoadedScene(null);
16311
16675
  setLargeModelTriangles(null);
16312
16676
  setLargeModelNoticeDismissed(false);
@@ -16317,11 +16681,11 @@ function SimpleModelViewer({
16317
16681
  setModelLoadFailed(false);
16318
16682
  setInitialFitComplete(false);
16319
16683
  }, []);
16320
- const revokeUploadedModelUrl = (0, import_react39.useCallback)(() => {
16684
+ const revokeUploadedModelUrl = (0, import_react40.useCallback)(() => {
16321
16685
  uploadedModelRef.current?.cleanup();
16322
16686
  uploadedModelRef.current = null;
16323
16687
  }, []);
16324
- const clearUploadedModel = (0, import_react39.useCallback)(() => {
16688
+ const clearUploadedModel = (0, import_react40.useCallback)(() => {
16325
16689
  revokeUploadedModelUrl();
16326
16690
  setUploadedModelUrl(null);
16327
16691
  setUploadedModelName(null);
@@ -16350,7 +16714,7 @@ function SimpleModelViewer({
16350
16714
  resetModelState,
16351
16715
  revokeUploadedModelUrl
16352
16716
  ]);
16353
- const handleUploadedModel = (0, import_react39.useCallback)(
16717
+ const handleUploadedModel = (0, import_react40.useCallback)(
16354
16718
  async (file) => {
16355
16719
  setIsResolvingUpload(true);
16356
16720
  try {
@@ -16369,7 +16733,7 @@ function SimpleModelViewer({
16369
16733
  },
16370
16734
  [onLoadError, resetModelState, revokeUploadedModelUrl]
16371
16735
  );
16372
- const handleFileSelection = (0, import_react39.useCallback)(
16736
+ const handleFileSelection = (0, import_react40.useCallback)(
16373
16737
  (files) => {
16374
16738
  const file = files?.[0];
16375
16739
  if (!file) return;
@@ -16377,29 +16741,29 @@ function SimpleModelViewer({
16377
16741
  },
16378
16742
  [handleUploadedModel]
16379
16743
  );
16380
- (0, import_react39.useEffect)(() => {
16744
+ (0, import_react40.useEffect)(() => {
16381
16745
  return () => {
16382
16746
  revokeUploadedModelUrl();
16383
16747
  };
16384
16748
  }, [revokeUploadedModelUrl]);
16385
- const handleEnvSettingChange = (0, import_react39.useCallback)(
16749
+ const handleEnvSettingChange = (0, import_react40.useCallback)(
16386
16750
  (key, value) => {
16387
16751
  setEnvSettings((prev) => ({ ...prev, [key]: value }));
16388
16752
  },
16389
16753
  []
16390
16754
  );
16391
- (0, import_react39.useEffect)(() => {
16755
+ (0, import_react40.useEffect)(() => {
16392
16756
  setEnvSettings((prev) => ({ ...prev, backgroundColor }));
16393
16757
  }, [backgroundColor]);
16394
- (0, import_react39.useEffect)(() => {
16758
+ (0, import_react40.useEffect)(() => {
16395
16759
  resetModelState();
16396
16760
  }, [activeModelUrl, resetModelState]);
16397
- const handleMeshesReady = (0, import_react39.useCallback)((meshes) => {
16761
+ const handleMeshesReady = (0, import_react40.useCallback)((meshes) => {
16398
16762
  setMeshKeys(meshes);
16399
16763
  setHiddenKeys(/* @__PURE__ */ new Set());
16400
16764
  setSelectedKey(null);
16401
16765
  }, []);
16402
- const handleModelLoaded = (0, import_react39.useCallback)(
16766
+ const handleModelLoaded = (0, import_react40.useCallback)(
16403
16767
  (scene) => {
16404
16768
  setLoadedScene(scene);
16405
16769
  setModelLoadFailed(false);
@@ -16410,14 +16774,14 @@ function SimpleModelViewer({
16410
16774
  },
16411
16775
  [onModelLoaded]
16412
16776
  );
16413
- const handleLoadError = (0, import_react39.useCallback)(
16777
+ const handleLoadError = (0, import_react40.useCallback)(
16414
16778
  (error) => {
16415
16779
  setModelLoadFailed(true);
16416
16780
  onLoadError?.(error);
16417
16781
  },
16418
16782
  [onLoadError]
16419
16783
  );
16420
- const focusMesh = (0, import_react39.useCallback)(
16784
+ const focusMesh = (0, import_react40.useCallback)(
16421
16785
  (key) => {
16422
16786
  setSelectedKey(key);
16423
16787
  if (!zoomOnSelected) return;
@@ -16433,14 +16797,14 @@ function SimpleModelViewer({
16433
16797
  },
16434
16798
  [zoomOnSelected]
16435
16799
  );
16436
- const handleToggleVisibility = (0, import_react39.useCallback)((key) => {
16800
+ const handleToggleVisibility = (0, import_react40.useCallback)((key) => {
16437
16801
  setHiddenKeys((prev) => {
16438
16802
  const next = new Set(prev);
16439
16803
  next.has(key) ? next.delete(key) : next.add(key);
16440
16804
  return next;
16441
16805
  });
16442
16806
  }, []);
16443
- const handleDragEnter = (0, import_react39.useCallback)(
16807
+ const handleDragEnter = (0, import_react40.useCallback)(
16444
16808
  (event) => {
16445
16809
  if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
16446
16810
  return;
@@ -16452,7 +16816,7 @@ function SimpleModelViewer({
16452
16816
  },
16453
16817
  [enableModelUpload]
16454
16818
  );
16455
- const handleDragOver = (0, import_react39.useCallback)(
16819
+ const handleDragOver = (0, import_react40.useCallback)(
16456
16820
  (event) => {
16457
16821
  if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
16458
16822
  return;
@@ -16464,7 +16828,7 @@ function SimpleModelViewer({
16464
16828
  },
16465
16829
  [enableModelUpload]
16466
16830
  );
16467
- const handleDragLeave = (0, import_react39.useCallback)(
16831
+ const handleDragLeave = (0, import_react40.useCallback)(
16468
16832
  (event) => {
16469
16833
  if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
16470
16834
  return;
@@ -16478,7 +16842,7 @@ function SimpleModelViewer({
16478
16842
  },
16479
16843
  [enableModelUpload]
16480
16844
  );
16481
- const handleDrop = (0, import_react39.useCallback)(
16845
+ const handleDrop = (0, import_react40.useCallback)(
16482
16846
  (event) => {
16483
16847
  if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
16484
16848
  return;
@@ -16571,7 +16935,7 @@ function SimpleModelViewer({
16571
16935
  raycaster: { firstHitOnly: true },
16572
16936
  style: { background: envSettings.backgroundColor },
16573
16937
  camera: { position: [0, 0, 5], fov: 45, near: 0.1, far: 1e3 },
16574
- children: /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(import_react39.Suspense, { fallback: null, children: [
16938
+ children: /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(import_react40.Suspense, { fallback: null, children: [
16575
16939
  /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
16576
16940
  "ambientLight",
16577
16941
  {
@@ -16589,7 +16953,7 @@ function SimpleModelViewer({
16589
16953
  }
16590
16954
  ),
16591
16955
  /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("directionalLight", { position: [-5, 5, 5], intensity: 0.5 }),
16592
- /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_react39.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
16956
+ /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_react40.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
16593
16957
  import_drei6.Environment,
16594
16958
  {
16595
16959
  preset: envSettings.envPreset,
@@ -16608,7 +16972,7 @@ function SimpleModelViewer({
16608
16972
  ModelErrorBoundary2,
16609
16973
  {
16610
16974
  onLoadError: handleLoadError,
16611
- children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_react39.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
16975
+ children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_react40.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
16612
16976
  InnerModel,
16613
16977
  {
16614
16978
  url: activeModelUrl,
@@ -16707,18 +17071,18 @@ function SimpleModelViewer({
16707
17071
  }
16708
17072
 
16709
17073
  // src/hooks/useViewerSelection.ts
16710
- var import_react40 = require("react");
17074
+ var import_react41 = require("react");
16711
17075
  function useViewerSelection(initialSelectedObjectBinding = null) {
16712
- const [selectedObjectBinding, setSelectedObjectBinding] = (0, import_react40.useState)(
17076
+ const [selectedObjectBinding, setSelectedObjectBinding] = (0, import_react41.useState)(
16713
17077
  initialSelectedObjectBinding
16714
17078
  );
16715
- const handleObjectSelect = (0, import_react40.useCallback)(
17079
+ const handleObjectSelect = (0, import_react41.useCallback)(
16716
17080
  (binding) => {
16717
17081
  setSelectedObjectBinding(binding);
16718
17082
  },
16719
17083
  [setSelectedObjectBinding]
16720
17084
  );
16721
- const clearSelection = (0, import_react40.useCallback)(() => {
17085
+ const clearSelection = (0, import_react41.useCallback)(() => {
16722
17086
  setSelectedObjectBinding(null);
16723
17087
  }, []);
16724
17088
  return {
@@ -16730,9 +17094,9 @@ function useViewerSelection(initialSelectedObjectBinding = null) {
16730
17094
  }
16731
17095
 
16732
17096
  // src/hooks/useObjectBinding.ts
16733
- var import_react41 = require("react");
17097
+ var import_react42 = require("react");
16734
17098
  function useObjectBinding(objectBindings, objectId) {
16735
- return (0, import_react41.useMemo)(() => {
17099
+ return (0, import_react42.useMemo)(() => {
16736
17100
  if (!objectId) {
16737
17101
  return null;
16738
17102
  }
@@ -16747,23 +17111,23 @@ function useObjectBinding(objectBindings, objectId) {
16747
17111
  }
16748
17112
 
16749
17113
  // src/hooks/useObjectBindingIds.ts
16750
- var import_react42 = require("react");
17114
+ var import_react43 = require("react");
16751
17115
  function useObjectBindingIds(objectBindings) {
16752
- return (0, import_react42.useMemo)(
17116
+ return (0, import_react43.useMemo)(
16753
17117
  () => Object.values(objectBindings).map((binding) => binding.modelObjectId),
16754
17118
  [objectBindings]
16755
17119
  );
16756
17120
  }
16757
17121
 
16758
17122
  // src/hooks/useObjectVisibility.ts
16759
- var import_react43 = require("react");
17123
+ var import_react44 = require("react");
16760
17124
  function getHiddenObjects(objectBindings) {
16761
17125
  return Object.fromEntries(
16762
17126
  Object.entries(objectBindings).filter(([, binding]) => binding.visible === false).map(([bindingKey]) => [bindingKey, true])
16763
17127
  );
16764
17128
  }
16765
17129
  function useObjectVisibility(objectBindings, onObjectBindingsChange) {
16766
- const updateVisibility = (0, import_react43.useCallback)(
17130
+ const updateVisibility = (0, import_react44.useCallback)(
16767
17131
  (objectId, visible) => {
16768
17132
  const bindingKey = resolveBindingKey(objectBindings, objectId);
16769
17133
  if (!bindingKey) return;
@@ -16779,15 +17143,15 @@ function useObjectVisibility(objectBindings, onObjectBindingsChange) {
16779
17143
  },
16780
17144
  [objectBindings, onObjectBindingsChange]
16781
17145
  );
16782
- const hideObject = (0, import_react43.useCallback)(
17146
+ const hideObject = (0, import_react44.useCallback)(
16783
17147
  (objectId) => updateVisibility(objectId, false),
16784
17148
  [updateVisibility]
16785
17149
  );
16786
- const showObject = (0, import_react43.useCallback)(
17150
+ const showObject = (0, import_react44.useCallback)(
16787
17151
  (objectId) => updateVisibility(objectId, true),
16788
17152
  [updateVisibility]
16789
17153
  );
16790
- const toggleObjectVisibility = (0, import_react43.useCallback)(
17154
+ const toggleObjectVisibility = (0, import_react44.useCallback)(
16791
17155
  (objectId) => {
16792
17156
  const bindingKey = resolveBindingKey(objectBindings, objectId);
16793
17157
  if (!bindingKey) return;
@@ -16803,7 +17167,7 @@ function useObjectVisibility(objectBindings, onObjectBindingsChange) {
16803
17167
  },
16804
17168
  [objectBindings, onObjectBindingsChange]
16805
17169
  );
16806
- const clearHiddenObjects = (0, import_react43.useCallback)(() => {
17170
+ const clearHiddenObjects = (0, import_react44.useCallback)(() => {
16807
17171
  onObjectBindingsChange(
16808
17172
  Object.fromEntries(
16809
17173
  Object.entries(objectBindings).map(([bindingKey, binding]) => [
@@ -16813,17 +17177,17 @@ function useObjectVisibility(objectBindings, onObjectBindingsChange) {
16813
17177
  )
16814
17178
  );
16815
17179
  }, [objectBindings, onObjectBindingsChange]);
16816
- const hiddenObjects = (0, import_react43.useMemo)(
17180
+ const hiddenObjects = (0, import_react44.useMemo)(
16817
17181
  () => getHiddenObjects(objectBindings),
16818
17182
  [objectBindings]
16819
17183
  );
16820
- const hiddenObjectIds = (0, import_react43.useMemo)(
17184
+ const hiddenObjectIds = (0, import_react44.useMemo)(
16821
17185
  () => Object.entries(hiddenObjects).map(
16822
17186
  ([bindingKey]) => objectBindings[bindingKey]?.id ?? bindingKey
16823
17187
  ),
16824
17188
  [hiddenObjects, objectBindings]
16825
17189
  );
16826
- const isObjectHidden = (0, import_react43.useCallback)(
17190
+ const isObjectHidden = (0, import_react44.useCallback)(
16827
17191
  (objectId) => {
16828
17192
  const bindingKey = resolveBindingKey(objectBindings, objectId);
16829
17193
  return bindingKey ? objectBindings[bindingKey]?.visible === false : false;
@@ -16842,10 +17206,10 @@ function useObjectVisibility(objectBindings, onObjectBindingsChange) {
16842
17206
  }
16843
17207
 
16844
17208
  // src/hooks/useViewerHover.ts
16845
- var import_react44 = require("react");
17209
+ var import_react45 = require("react");
16846
17210
  function useViewerHover() {
16847
- const [hoveredObjectBinding, setHoveredObjectBinding] = (0, import_react44.useState)(null);
16848
- const handleHoveredObject = (0, import_react44.useCallback)(
17211
+ const [hoveredObjectBinding, setHoveredObjectBinding] = (0, import_react45.useState)(null);
17212
+ const handleHoveredObject = (0, import_react45.useCallback)(
16849
17213
  (binding) => {
16850
17214
  setHoveredObjectBinding(binding);
16851
17215
  },
@@ -16858,18 +17222,18 @@ function useViewerHover() {
16858
17222
  }
16859
17223
 
16860
17224
  // src/hooks/useViewerModel.ts
16861
- var import_react45 = require("react");
17225
+ var import_react46 = require("react");
16862
17226
  var import_three22 = require("three");
16863
17227
  function toTuple2(vector) {
16864
17228
  return [vector.x, vector.y, vector.z];
16865
17229
  }
16866
17230
  function useViewerModel() {
16867
- const [isLoading, setIsLoading] = (0, import_react45.useState)(true);
16868
- const [isReady, setIsReady] = (0, import_react45.useState)(false);
16869
- const [error, setError] = (0, import_react45.useState)(null);
16870
- const [bounds, setBounds] = (0, import_react45.useState)(null);
16871
- const [objectCount, setObjectCount] = (0, import_react45.useState)(0);
16872
- const handleModelLoaded = (0, import_react45.useCallback)((scene) => {
17231
+ const [isLoading, setIsLoading] = (0, import_react46.useState)(true);
17232
+ const [isReady, setIsReady] = (0, import_react46.useState)(false);
17233
+ const [error, setError] = (0, import_react46.useState)(null);
17234
+ const [bounds, setBounds] = (0, import_react46.useState)(null);
17235
+ const [objectCount, setObjectCount] = (0, import_react46.useState)(0);
17236
+ const handleModelLoaded = (0, import_react46.useCallback)((scene) => {
16873
17237
  const box = new import_three22.Box3().setFromObject(scene);
16874
17238
  const min = box.min.clone();
16875
17239
  const max = box.max.clone();
@@ -16894,14 +17258,14 @@ function useViewerModel() {
16894
17258
  setIsLoading(false);
16895
17259
  setIsReady(true);
16896
17260
  }, []);
16897
- const handleLoadError = (0, import_react45.useCallback)((nextError) => {
17261
+ const handleLoadError = (0, import_react46.useCallback)((nextError) => {
16898
17262
  setError(nextError);
16899
17263
  setBounds(null);
16900
17264
  setObjectCount(0);
16901
17265
  setIsLoading(false);
16902
17266
  setIsReady(false);
16903
17267
  }, []);
16904
- const resetModelState = (0, import_react45.useCallback)(() => {
17268
+ const resetModelState = (0, import_react46.useCallback)(() => {
16905
17269
  setIsLoading(true);
16906
17270
  setIsReady(false);
16907
17271
  setError(null);
@@ -16921,10 +17285,10 @@ function useViewerModel() {
16921
17285
  }
16922
17286
 
16923
17287
  // src/hooks/useViewerActions.ts
16924
- var import_react46 = require("react");
17288
+ var import_react47 = require("react");
16925
17289
  function useViewerActions(objectBindings, options = {}) {
16926
17290
  const { onAction, onObjectBindingsChange } = options;
16927
- const getActionsForObject = (0, import_react46.useCallback)(
17291
+ const getActionsForObject = (0, import_react47.useCallback)(
16928
17292
  (objectId) => {
16929
17293
  const bindingKey = resolveBindingKey(objectBindings, objectId);
16930
17294
  if (!bindingKey) {
@@ -16934,7 +17298,7 @@ function useViewerActions(objectBindings, options = {}) {
16934
17298
  },
16935
17299
  [objectBindings]
16936
17300
  );
16937
- const runAction = (0, import_react46.useCallback)(
17301
+ const runAction = (0, import_react47.useCallback)(
16938
17302
  (objectId, actionId) => {
16939
17303
  const bindingKey = resolveBindingKey(objectBindings, objectId);
16940
17304
  if (!bindingKey) {
@@ -16976,7 +17340,7 @@ function useViewerActions(objectBindings, options = {}) {
16976
17340
  }
16977
17341
 
16978
17342
  // src/hooks/useShareableViewerState.ts
16979
- var import_react47 = require("react");
17343
+ var import_react48 = require("react");
16980
17344
  function serializeState(cameraState, objectBindings, selectedObjectBinding) {
16981
17345
  const hidden = Object.values(objectBindings).filter((binding) => binding.visible === false).map((binding) => binding.id);
16982
17346
  return {
@@ -16996,8 +17360,8 @@ function useShareableViewerState({
16996
17360
  debounceMs = 400,
16997
17361
  autoSync = true
16998
17362
  }) {
16999
- const lastWrittenRawRef = (0, import_react47.useRef)(null);
17000
- const buildHash = (0, import_react47.useCallback)(() => {
17363
+ const lastWrittenRawRef = (0, import_react48.useRef)(null);
17364
+ const buildHash = (0, import_react48.useCallback)(() => {
17001
17365
  const state = serializeState(
17002
17366
  cameraState,
17003
17367
  objectBindings,
@@ -17005,12 +17369,12 @@ function useShareableViewerState({
17005
17369
  );
17006
17370
  return `${paramKey}=${encodeURIComponent(JSON.stringify(state))}`;
17007
17371
  }, [cameraState, objectBindings, selectedObjectBinding, paramKey]);
17008
- const getShareUrl = (0, import_react47.useCallback)(() => {
17372
+ const getShareUrl = (0, import_react48.useCallback)(() => {
17009
17373
  const url = new URL(window.location.href);
17010
17374
  url.hash = buildHash();
17011
17375
  return url.toString();
17012
17376
  }, [buildHash]);
17013
- const syncUrl = (0, import_react47.useCallback)(() => {
17377
+ const syncUrl = (0, import_react48.useCallback)(() => {
17014
17378
  const url = new URL(window.location.href);
17015
17379
  url.hash = buildHash();
17016
17380
  window.history.replaceState(null, "", url.toString());
@@ -17018,7 +17382,7 @@ function useShareableViewerState({
17018
17382
  paramKey
17019
17383
  );
17020
17384
  }, [buildHash, paramKey]);
17021
- const latestStateRef = (0, import_react47.useRef)({
17385
+ const latestStateRef = (0, import_react48.useRef)({
17022
17386
  cameraState,
17023
17387
  objectBindings,
17024
17388
  selectedObjectBinding
@@ -17028,9 +17392,9 @@ function useShareableViewerState({
17028
17392
  objectBindings,
17029
17393
  selectedObjectBinding
17030
17394
  };
17031
- const [restoreAttempted, setRestoreAttempted] = (0, import_react47.useState)(false);
17032
- const lastSyncedHashRef = (0, import_react47.useRef)(null);
17033
- (0, import_react47.useEffect)(() => {
17395
+ const [restoreAttempted, setRestoreAttempted] = (0, import_react48.useState)(false);
17396
+ const lastSyncedHashRef = (0, import_react48.useRef)(null);
17397
+ (0, import_react48.useEffect)(() => {
17034
17398
  if (!autoSync || !restoreAttempted) return;
17035
17399
  const tick = () => {
17036
17400
  const current = latestStateRef.current;
@@ -17052,9 +17416,9 @@ function useShareableViewerState({
17052
17416
  const id = window.setInterval(tick, debounceMs);
17053
17417
  return () => window.clearInterval(id);
17054
17418
  }, [autoSync, restoreAttempted, debounceMs, paramKey]);
17055
- const lastRestoredRawRef = (0, import_react47.useRef)(null);
17056
- const restoringRawRef = (0, import_react47.useRef)(null);
17057
- const restoreFromUrl = (0, import_react47.useCallback)(async () => {
17419
+ const lastRestoredRawRef = (0, import_react48.useRef)(null);
17420
+ const restoringRawRef = (0, import_react48.useRef)(null);
17421
+ const restoreFromUrl = (0, import_react48.useCallback)(async () => {
17058
17422
  setRestoreAttempted(true);
17059
17423
  const rawHash = window.location.hash.startsWith("#") ? window.location.hash.slice(1) : window.location.hash;
17060
17424
  const raw = new URLSearchParams(rawHash).get(paramKey);
@@ -17107,7 +17471,7 @@ function useShareableViewerState({
17107
17471
  setCameraState,
17108
17472
  setSelectedObjectBinding
17109
17473
  ]);
17110
- (0, import_react47.useEffect)(() => {
17474
+ (0, import_react48.useEffect)(() => {
17111
17475
  const handleHashChange = () => {
17112
17476
  void restoreFromUrl();
17113
17477
  };