@liveroom-tech/react-immersive 1.3.2 → 1.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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");
@@ -637,6 +637,7 @@ var DEFAULT_SCENE_POST_PROCESSING_CONFIG = {
637
637
  rampEnd: [1, 1],
638
638
  startColor: "#000000",
639
639
  endColor: "#ffffff",
640
+ opacity: 0.5,
640
641
  rampBias: 0.5,
641
642
  rampGain: 0.5,
642
643
  rampMask: false,
@@ -1474,7 +1475,10 @@ function PostProcessingEffects({
1474
1475
  }
1475
1476
  ),
1476
1477
  pp.pixelation.enabled === true && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_postprocessing5.Pixelation, { granularity: pp.pixelation.granularity }),
1477
- pp.ramp.enabled === true && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1478
+ pp.ramp.enabled === true && // Ramp's non-mask shader emits an opaque full-screen gradient.
1479
+ // Blend it with the scene by default instead of replacing the
1480
+ // model completely; mask mode already performs its own mix.
1481
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1478
1482
  import_postprocessing5.Ramp,
1479
1483
  {
1480
1484
  rampType: rampTypeFromName(pp.ramp.rampType),
@@ -1482,6 +1486,7 @@ function PostProcessingEffects({
1482
1486
  rampEnd: pp.ramp.rampEnd,
1483
1487
  startColor: rampStartColor,
1484
1488
  endColor: rampEndColor,
1489
+ opacity: pp.ramp.rampMask ? 1 : pp.ramp.opacity ?? 0.5,
1485
1490
  rampBias: pp.ramp.rampBias,
1486
1491
  rampGain: pp.ramp.rampGain,
1487
1492
  rampMask: pp.ramp.rampMask,
@@ -4094,18 +4099,75 @@ function CinematicCamera({
4094
4099
  sceneRef,
4095
4100
  config,
4096
4101
  playing,
4102
+ resetOnPlay = false,
4097
4103
  onFinished,
4098
- onUserInteract
4104
+ onUserInteract,
4105
+ onStop
4099
4106
  }) {
4100
4107
  const invalidate = (0, import_fiber10.useThree)((state) => state.invalidate);
4101
4108
  const waypoints = (0, import_react15.useMemo)(() => config.waypoints ?? [], [config.waypoints]);
4102
4109
  const hasPath = waypoints.length >= 2;
4103
4110
  const loop = config.loop ?? true;
4104
- 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;
4105
4123
  const signature = (0, import_react15.useMemo)(
4106
- () => 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(";"),
4107
4133
  [waypoints]
4108
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]);
4109
4171
  const curves = (0, import_react15.useMemo)(() => {
4110
4172
  if (!hasPath) return null;
4111
4173
  return {
@@ -4123,29 +4185,83 @@ function CinematicCamera({
4123
4185
  )
4124
4186
  };
4125
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]);
4126
4208
  const elapsedRef = (0, import_react15.useRef)(0);
4127
4209
  const enterElapsedRef = (0, import_react15.useRef)(0);
4128
4210
  const enterPos = (0, import_react15.useRef)(new import_three18.Vector3());
4129
4211
  const enterTarget = (0, import_react15.useRef)(new import_three18.Vector3());
4130
4212
  const startedRef = (0, import_react15.useRef)(false);
4131
4213
  const pausedElapsedRef = (0, import_react15.useRef)(0);
4214
+ const hasFinishedRef = (0, import_react15.useRef)(false);
4132
4215
  const orbitRef = (0, import_react15.useRef)(null);
4133
4216
  const tmpPos = (0, import_react15.useMemo)(() => new import_three18.Vector3(), []);
4134
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]);
4135
4232
  (0, import_react15.useEffect)(() => {
4136
4233
  if (!playing) {
4137
4234
  startedRef.current = false;
4138
- if (elapsedRef.current > 0) {
4235
+ if (elapsedRef.current > 0 && !resetOnPlay) {
4139
4236
  pausedElapsedRef.current = elapsedRef.current;
4237
+ } else if (resetOnPlay) {
4238
+ pausedElapsedRef.current = 0;
4239
+ }
4240
+ if (elapsedRef.current > 0 || hasFinishedRef.current) {
4241
+ onStop?.();
4140
4242
  }
4141
4243
  return;
4142
4244
  }
4245
+ if (elapsedRef.current >= duration && !loop) {
4246
+ elapsedRef.current = 0;
4247
+ pausedElapsedRef.current = 0;
4248
+ hasFinishedRef.current = false;
4249
+ }
4143
4250
  const controls = controlsRef.current;
4144
4251
  if (controls) {
4145
4252
  controls.getPosition(enterPos.current);
4146
4253
  controls.getTarget(enterTarget.current);
4147
4254
  }
4148
- 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
+ }
4149
4265
  enterElapsedRef.current = 0;
4150
4266
  if (!hasPath) {
4151
4267
  const scene = sceneRef.current;
@@ -4159,12 +4275,26 @@ function CinematicCamera({
4159
4275
  }
4160
4276
  startedRef.current = true;
4161
4277
  invalidate();
4162
- }, [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
+ ]);
4163
4290
  (0, import_react15.useEffect)(() => {
4164
4291
  if (!playing || !onUserInteract) return;
4165
4292
  const controls = controlsRef.current;
4166
4293
  if (!controls) return;
4167
- const handle = () => onUserInteract();
4294
+ const handle = () => {
4295
+ if (hasFinishedRef.current) return;
4296
+ onUserInteract();
4297
+ };
4168
4298
  controls.addEventListener("controlstart", handle);
4169
4299
  return () => controls.removeEventListener("controlstart", handle);
4170
4300
  }, [playing, controlsRef, onUserInteract]);
@@ -4174,12 +4304,33 @@ function CinematicCamera({
4174
4304
  if (!controls) return;
4175
4305
  elapsedRef.current += delta;
4176
4306
  enterElapsedRef.current += delta;
4177
- let u = elapsedRef.current / duration;
4178
- const finished = !loop && u >= 1;
4307
+ let u = getCurveParameter(elapsedRef.current);
4308
+ const finished = !loop && elapsedRef.current >= duration;
4179
4309
  u = loop ? u % 1 : Math.min(u, 1);
4180
4310
  if (curves) {
4181
- curves.position.getPointAt(u, tmpPos);
4182
- 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
+ }
4183
4334
  } else {
4184
4335
  const orbit = orbitRef.current;
4185
4336
  if (!orbit) return;
@@ -4209,6 +4360,8 @@ function CinematicCamera({
4209
4360
  );
4210
4361
  if (finished) {
4211
4362
  startedRef.current = false;
4363
+ hasFinishedRef.current = true;
4364
+ elapsedRef.current = 0;
4212
4365
  pausedElapsedRef.current = 0;
4213
4366
  onFinished?.();
4214
4367
  return;
@@ -5589,9 +5742,11 @@ function ModelViewer({
5589
5742
  return { ...base ?? {}, ...cinematic };
5590
5743
  }, [cinematic, resolvedSceneConfig]);
5591
5744
  const [cinematicPlaying, setCinematicPlaying] = (0, import_react22.useState)(false);
5745
+ const [cinematicReset, setCinematicReset] = (0, import_react22.useState)(false);
5592
5746
  const cinematicAutoStartedRef = (0, import_react22.useRef)(false);
5593
5747
  (0, import_react22.useEffect)(() => {
5594
5748
  setCinematicPlaying(false);
5749
+ setCinematicReset(false);
5595
5750
  cinematicAutoStartedRef.current = false;
5596
5751
  }, [modelUrl]);
5597
5752
  (0, import_react22.useEffect)(() => {
@@ -5599,6 +5754,7 @@ function ModelViewer({
5599
5754
  if (modelLoaded && cameraPositioned && !isAutoFraming) {
5600
5755
  cinematicAutoStartedRef.current = true;
5601
5756
  setCinematicPlaying(true);
5757
+ setCinematicReset(false);
5602
5758
  }
5603
5759
  }, [cinematicConfig, modelLoaded, cameraPositioned, isAutoFraming]);
5604
5760
  const xrStore = (0, import_react22.useMemo)(() => {
@@ -6752,6 +6908,20 @@ function ModelViewer({
6752
6908
  console.error("Reset camera error:", error);
6753
6909
  }
6754
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]);
6755
6925
  const handleCameraChangeWithAnnotations = (0, import_react22.useCallback)(
6756
6926
  (cameraValue, controls) => {
6757
6927
  handleCameraChangeInternal(cameraValue, controls);
@@ -7068,8 +7238,14 @@ function ModelViewer({
7068
7238
  sceneRef,
7069
7239
  config: cinematicConfig,
7070
7240
  playing: cinematicPlaying,
7071
- onFinished: () => setCinematicPlaying(false),
7072
- onUserInteract: () => setCinematicPlaying(false)
7241
+ resetOnPlay: cinematicReset,
7242
+ onFinished: () => {
7243
+ setCinematicPlaying(false);
7244
+ setCinematicReset(false);
7245
+ },
7246
+ onUserInteract: () => {
7247
+ setCinematicPlaying(false);
7248
+ }
7073
7249
  }
7074
7250
  ),
7075
7251
  showMeasureTools && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
@@ -7297,24 +7473,43 @@ function ModelViewer({
7297
7473
  ]
7298
7474
  }
7299
7475
  ),
7300
- cinematicConfig && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
7476
+ cinematicConfig && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
7301
7477
  "div",
7302
7478
  {
7303
7479
  className: "ri-cinematicTools",
7304
7480
  style: {
7305
7481
  top: `${topLeftRow((xrToolsVisible ? 1 : 0) + (showMeasureTools ? 1 : 0) + (showExplodeControls ? 1 : 0))}px`
7306
7482
  },
7307
- children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
7308
- "button",
7309
- {
7310
- type: "button",
7311
- className: `ri-measureButton ${cinematicPlaying ? "ri-measureButton--active" : ""}`,
7312
- onClick: () => setCinematicPlaying((prev) => !prev),
7313
- title: cinematicPlaying ? "Pause cinematic" : "Play cinematic",
7314
- "aria-pressed": cinematicPlaying,
7315
- 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 })
7316
- }
7317
- )
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
+ ]
7318
7513
  }
7319
7514
  ),
7320
7515
  (showResetButton || showDownloadButton || showAnnotationNavigation && sortedAnnotations.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "ri-buttonContainer", children: [
@@ -7554,7 +7749,7 @@ function ModelViewer({
7554
7749
  }
7555
7750
 
7556
7751
  // src/components/BindingBuilder/BindingBuilder.tsx
7557
- var import_react38 = require("react");
7752
+ var import_react39 = require("react");
7558
7753
  var import_lucide_react16 = require("lucide-react");
7559
7754
 
7560
7755
  // src/styles/BindingBuilder.css
@@ -7594,14 +7789,16 @@ function buildCameraState(camera, controls) {
7594
7789
  position: toTuple(position),
7595
7790
  target: toTuple(target),
7596
7791
  fov: "fov" in camera && typeof camera.fov === "number" ? camera.fov : null,
7597
- 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
7598
7795
  };
7599
7796
  }
7600
7797
  function isSameCameraState(left, right) {
7601
7798
  if (!left) {
7602
7799
  return false;
7603
7800
  }
7604
- 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;
7605
7802
  }
7606
7803
  async function runSmoothCameraTransition2(controls, action, smoothTime = 0.6) {
7607
7804
  const originalSmoothTime = controls.smoothTime;
@@ -7756,15 +7953,27 @@ function useViewerCamera() {
7756
7953
  }
7757
7954
  try {
7758
7955
  markInitialAutoFitOverride(controls);
7759
- await controls.setLookAt(
7760
- state.position[0],
7761
- state.position[1],
7762
- state.position[2],
7763
- state.target[0],
7764
- state.target[1],
7765
- state.target[2],
7766
- enableTransition
7767
- );
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);
7768
7977
  const camera = controls.camera;
7769
7978
  let projectionChanged = false;
7770
7979
  if (state.fov !== null && "fov" in camera && typeof camera.fov === "number") {
@@ -8072,7 +8281,7 @@ function inferMimeType(path) {
8072
8281
  }
8073
8282
 
8074
8283
  // src/components/BindingBuilder/SceneSettings/SceneSettingsTab.tsx
8075
- var import_react32 = require("react");
8284
+ var import_react33 = require("react");
8076
8285
 
8077
8286
  // src/components/BindingBuilder/SceneSettings/GeneralSceneSettings.tsx
8078
8287
  var import_react27 = require("react");
@@ -8253,9 +8462,10 @@ function SliderRow({
8253
8462
  max,
8254
8463
  step,
8255
8464
  formatValue,
8256
- onChange
8465
+ onChange,
8466
+ disabled = false
8257
8467
  }) {
8258
- 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: [
8259
8469
  /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "se-fieldLabel", children: [
8260
8470
  label,
8261
8471
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "se-fieldValue", children: formatValue ? formatValue(value) : value })
@@ -8269,7 +8479,8 @@ function SliderRow({
8269
8479
  max,
8270
8480
  step,
8271
8481
  value,
8272
- onChange: (e) => onChange(Number(e.target.value))
8482
+ onChange: (e) => onChange(Number(e.target.value)),
8483
+ disabled
8273
8484
  }
8274
8485
  )
8275
8486
  ] });
@@ -10315,7 +10526,7 @@ function RampSection({
10315
10526
  PPSection,
10316
10527
  {
10317
10528
  title: "Ramp",
10318
- tooltip: "Maps shading through a custom color ramp or gradient.",
10529
+ tooltip: "Adds a screen-space color gradient, or masks effects earlier in the stack.",
10319
10530
  open: open.ramp,
10320
10531
  onToggle: () => toggle("ramp"),
10321
10532
  disabled: !ramp.enabled,
@@ -10375,6 +10586,17 @@ function RampSection({
10375
10586
  onChange: (v) => set("ramp", { ...ramp, endColor: v })
10376
10587
  }
10377
10588
  ),
10589
+ !ramp.rampMask && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
10590
+ SliderRow,
10591
+ {
10592
+ label: "Gradient Opacity",
10593
+ value: ramp.opacity ?? 0.5,
10594
+ min: 0,
10595
+ max: 1,
10596
+ step: 0.01,
10597
+ onChange: (v) => set("ramp", { ...ramp, opacity: v })
10598
+ }
10599
+ ),
10378
10600
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
10379
10601
  SliderRow,
10380
10602
  {
@@ -10400,7 +10622,7 @@ function RampSection({
10400
10622
  /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
10401
10623
  ToggleRow,
10402
10624
  {
10403
- label: "Mask",
10625
+ label: "Mask Earlier Effects",
10404
10626
  checked: ramp.rampMask,
10405
10627
  onChange: (v) => set("ramp", { ...ramp, rampMask: v })
10406
10628
  }
@@ -11674,6 +11896,7 @@ function AnnotationsTab({ config, onChange }) {
11674
11896
  }
11675
11897
 
11676
11898
  // src/components/BindingBuilder/SceneSettings/CinematicTab.tsx
11899
+ var import_react32 = require("react");
11677
11900
  var import_lucide_react10 = require("lucide-react");
11678
11901
  var import_jsx_runtime31 = require("react/jsx-runtime");
11679
11902
  var DEFAULT_CINEMATIC = {
@@ -11682,6 +11905,7 @@ var DEFAULT_CINEMATIC = {
11682
11905
  loop: true,
11683
11906
  autoPlay: false
11684
11907
  };
11908
+ var DEFAULT_SEGMENT_DURATION = 3.5;
11685
11909
  function CinematicTab({
11686
11910
  config,
11687
11911
  onChange,
@@ -11690,31 +11914,76 @@ function CinematicTab({
11690
11914
  }) {
11691
11915
  const cinematic = config.cinematic ?? DEFAULT_CINEMATIC;
11692
11916
  const { waypoints } = cinematic;
11693
- 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) => {
11694
11930
  onChange({ ...config, cinematic: { ...cinematic, ...patch } });
11695
- };
11696
- const addWaypoint = () => {
11931
+ }, [config, cinematic, onChange]);
11932
+ const addWaypoint = (0, import_react32.useCallback)(() => {
11697
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);
11698
11943
  update({
11699
- waypoints: [
11700
- ...waypoints,
11701
- {
11702
- position: [...cameraState.position],
11703
- target: [...cameraState.target]
11704
- }
11705
- ]
11944
+ waypoints: newWaypoints,
11945
+ duration: newDuration
11706
11946
  });
11707
- };
11708
- const deleteWaypoint = (index) => {
11709
- update({ waypoints: waypoints.filter((_, i) => i !== index) });
11710
- };
11711
- 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) => {
11712
11957
  const next = index + delta;
11713
11958
  if (next < 0 || next >= waypoints.length) return;
11714
11959
  const reordered = [...waypoints];
11715
11960
  [reordered[index], reordered[next]] = [reordered[next], reordered[index]];
11716
- update({ waypoints: reordered });
11717
- };
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;
11718
11987
  return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(Section, { title: "Cinematic Camera", children: [
11719
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." }),
11720
11989
  /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
@@ -11731,62 +12000,165 @@ function CinematicTab({
11731
12000
  ]
11732
12001
  }
11733
12002
  ),
11734
- 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: [
11735
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "se-waypointBadge", children: index + 1 }),
11736
- /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-waypointBody", children: [
11737
- /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-waypointCoords", children: [
11738
- "pos [",
11739
- waypoint.position.map((n) => n.toFixed(1)).join(", "),
11740
- "]"
11741
- ] }),
11742
- /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-waypointCoords", children: [
11743
- "look [",
11744
- waypoint.target.map((n) => n.toFixed(1)).join(", "),
11745
- "]"
11746
- ] })
11747
- ] }),
11748
- /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-waypointActions", children: [
11749
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
11750
- "button",
11751
- {
11752
- className: "se-waypointAction",
11753
- onClick: () => onPreviewWaypoint?.(waypoint),
11754
- title: "Fly camera to this waypoint",
11755
- disabled: !onPreviewWaypoint,
11756
- children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.MapPin, { size: 15 })
11757
- }
11758
- ),
11759
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
11760
- "button",
11761
- {
11762
- className: "se-waypointAction",
11763
- onClick: () => moveWaypoint(index, -1),
11764
- title: "Move up",
11765
- disabled: index === 0,
11766
- children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.ArrowUp, { size: 15 })
11767
- }
11768
- ),
12003
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-customizeToggle", children: [
12004
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("label", { className: "se-toggleLabel", children: [
11769
12005
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
11770
- "button",
12006
+ "input",
11771
12007
  {
11772
- className: "se-waypointAction",
11773
- onClick: () => moveWaypoint(index, 1),
11774
- title: "Move down",
11775
- disabled: index === waypoints.length - 1,
11776
- 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)
11777
12011
  }
11778
12012
  ),
11779
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
11780
- "button",
11781
- {
11782
- className: "se-waypointAction se-waypointAction--delete",
11783
- onClick: () => deleteWaypoint(index),
11784
- title: "Delete waypoint",
11785
- children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.Trash2, { size: 15 })
11786
- }
11787
- )
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"
11788
12020
  ] })
11789
- ] }, 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
+ ] }),
11790
12162
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
11791
12163
  ToggleRow,
11792
12164
  {
@@ -11800,7 +12172,11 @@ function CinematicTab({
11800
12172
  {
11801
12173
  label: "Loop",
11802
12174
  checked: cinematic.loop,
11803
- onChange: (loop) => update({ loop })
12175
+ onChange: (loop) => {
12176
+ const newLoop = loop;
12177
+ const newDuration = calculateTotalDuration(waypoints, newLoop);
12178
+ update({ loop: newLoop, duration: newDuration });
12179
+ }
11804
12180
  }
11805
12181
  ),
11806
12182
  /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
@@ -11811,11 +12187,11 @@ function CinematicTab({
11811
12187
  onChange: (autoPlay) => update({ autoPlay })
11812
12188
  }
11813
12189
  ),
11814
- /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
12190
+ !customizeDurations && hasEnoughWaypoints && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
11815
12191
  SliderRow,
11816
12192
  {
11817
12193
  label: "Duration",
11818
- value: cinematic.duration ?? Math.max(waypoints.length * 3.5, 8),
12194
+ value: cinematic.duration ?? calculateTotalDuration(waypoints, cinematic.loop),
11819
12195
  min: 4,
11820
12196
  max: 120,
11821
12197
  step: 1,
@@ -11827,7 +12203,7 @@ function CinematicTab({
11827
12203
  }
11828
12204
 
11829
12205
  // src/styles/SceneSettingsTab.css
11830
- 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');
11831
12207
 
11832
12208
  // src/components/BindingBuilder/SceneSettings/SceneSettingsTab.tsx
11833
12209
  var import_lucide_react11 = require("lucide-react");
@@ -11866,13 +12242,13 @@ function SceneSettingsTab({
11866
12242
  animationClipDetails = [],
11867
12243
  animationPlayback = FALLBACK_ANIMATION_PLAYBACK
11868
12244
  }) {
11869
- const [internalActiveSubTab, setInternalActiveSubTab] = (0, import_react32.useState)("General");
12245
+ const [internalActiveSubTab, setInternalActiveSubTab] = (0, import_react33.useState)("General");
11870
12246
  const activeSubTab = controlledActiveSubTab ?? internalActiveSubTab;
11871
12247
  const resolvedAnimationPlayback = animationPlayback ?? FALLBACK_ANIMATION_PLAYBACK;
11872
12248
  const { tier } = useBindingBuilderContext();
11873
12249
  const canUseStarterFeatures = hasTier(tier, "starter");
11874
12250
  const canUseGrowthFeatures = hasTier(tier, "growth");
11875
- (0, import_react32.useEffect)(() => {
12251
+ (0, import_react33.useEffect)(() => {
11876
12252
  if (controlledActiveSubTab !== void 0) {
11877
12253
  setInternalActiveSubTab(controlledActiveSubTab);
11878
12254
  }
@@ -12052,7 +12428,7 @@ function EnableToggle2({
12052
12428
  }
12053
12429
 
12054
12430
  // src/components/BindingBuilder/components/MaterialSection.tsx
12055
- var import_react34 = require("react");
12431
+ var import_react35 = require("react");
12056
12432
 
12057
12433
  // src/components/ModelViewer/types/objectBinding.ts
12058
12434
  var MATERIAL_SIDES = ["front", "back", "double"];
@@ -12099,7 +12475,7 @@ function MaterialPanelGroup({
12099
12475
  }
12100
12476
 
12101
12477
  // src/components/BindingBuilder/components/MaterialFields.tsx
12102
- var import_react33 = require("react");
12478
+ var import_react34 = require("react");
12103
12479
  var import_react_colorful4 = require("react-colorful");
12104
12480
  var import_lucide_react13 = require("lucide-react");
12105
12481
  var import_jsx_runtime35 = require("react/jsx-runtime");
@@ -12109,7 +12485,7 @@ function MaterialTexturePathField({
12109
12485
  placeholder,
12110
12486
  onChange
12111
12487
  }) {
12112
- const inputRef = (0, import_react33.useRef)(null);
12488
+ const inputRef = (0, import_react34.useRef)(null);
12113
12489
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "bb-field bb-field--full", children: [
12114
12490
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("label", { className: "bb-label", children: label }),
12115
12491
  /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "bb-fileField", children: [
@@ -12166,9 +12542,9 @@ function MaterialColorField({
12166
12542
  fallback = "#ffffff",
12167
12543
  onChange
12168
12544
  }) {
12169
- const [open, setOpen] = (0, import_react33.useState)(false);
12545
+ const [open, setOpen] = (0, import_react34.useState)(false);
12170
12546
  const color = value || fallback;
12171
- const anchorRef = (0, import_react33.useRef)(null);
12547
+ const anchorRef = (0, import_react34.useRef)(null);
12172
12548
  return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "bb-field", children: [
12173
12549
  /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "bb-label", children: label }),
12174
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)(
@@ -12320,8 +12696,8 @@ function MaterialSection({
12320
12696
  tier: "starter",
12321
12697
  locked: !canUseStarterFeatures
12322
12698
  };
12323
- const [roughnessMode, setRoughnessMode] = (0, import_react34.useState)("roughness");
12324
- const [anisotropyMode, setAnisotropyMode] = (0, import_react34.useState)(
12699
+ const [roughnessMode, setRoughnessMode] = (0, import_react35.useState)("roughness");
12700
+ const [anisotropyMode, setAnisotropyMode] = (0, import_react35.useState)(
12325
12701
  "factor"
12326
12702
  );
12327
12703
  return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
@@ -13360,7 +13736,7 @@ function MaterialPresetGallery({
13360
13736
  }
13361
13737
 
13362
13738
  // src/components/BindingBuilder/components/ToolbarExportMenu.tsx
13363
- var import_react35 = require("react");
13739
+ var import_react36 = require("react");
13364
13740
  var import_lucide_react14 = require("lucide-react");
13365
13741
  var import_jsx_runtime38 = require("react/jsx-runtime");
13366
13742
  function ToolbarExportMenu({
@@ -13369,9 +13745,9 @@ function ToolbarExportMenu({
13369
13745
  onExportJson,
13370
13746
  onExportTs
13371
13747
  }) {
13372
- const [open, setOpen] = (0, import_react35.useState)(false);
13373
- const wrapperRef = (0, import_react35.useRef)(null);
13374
- (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)(() => {
13375
13751
  if (!open) return;
13376
13752
  const close = (event) => {
13377
13753
  if (!wrapperRef.current?.contains(event.target)) {
@@ -13808,7 +14184,7 @@ function CameraPositionPanel({
13808
14184
  }
13809
14185
 
13810
14186
  // src/components/BindingBuilder/components/SceneNodesPanel.tsx
13811
- var import_react36 = require("react");
14187
+ var import_react37 = require("react");
13812
14188
 
13813
14189
  // src/components/BindingBuilder/utils/bindingFactory.ts
13814
14190
  function slugify(value) {
@@ -13897,9 +14273,9 @@ function SceneNodesPanel({
13897
14273
  isOpen = true,
13898
14274
  onToggle
13899
14275
  }) {
13900
- const [search, setSearch] = (0, import_react36.useState)("");
13901
- const nodeKeys = (0, import_react36.useMemo)(() => Object.keys(bindings), [bindings]);
13902
- 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)(() => {
13903
14279
  const query = search.trim().toLowerCase();
13904
14280
  if (!query) return nodeKeys;
13905
14281
  return nodeKeys.filter((nodeKey) => {
@@ -14042,7 +14418,7 @@ function mergeAnimationConfig(sceneConfig, animationDetails) {
14042
14418
  }
14043
14419
 
14044
14420
  // src/components/BindingBuilder/hooks/useUndoableState.ts
14045
- var import_react37 = require("react");
14421
+ var import_react38 = require("react");
14046
14422
  var MAX_HISTORY = 100;
14047
14423
  function historyReducer(state, action) {
14048
14424
  switch (action.type) {
@@ -14081,13 +14457,13 @@ function historyReducer(state, action) {
14081
14457
  }
14082
14458
  }
14083
14459
  function useUndoableState(initial, coalesceWindowMs = 600) {
14084
- const [state, dispatch] = (0, import_react37.useReducer)(historyReducer, {
14460
+ const [state, dispatch] = (0, import_react38.useReducer)(historyReducer, {
14085
14461
  past: [],
14086
14462
  present: initial,
14087
14463
  future: []
14088
14464
  });
14089
- const lastSetAt = (0, import_react37.useRef)(0);
14090
- const setValue = (0, import_react37.useCallback)(
14465
+ const lastSetAt = (0, import_react38.useRef)(0);
14466
+ const setValue = (0, import_react38.useCallback)(
14091
14467
  (updater) => {
14092
14468
  const now = Date.now();
14093
14469
  const coalesce = now - lastSetAt.current < coalesceWindowMs;
@@ -14096,15 +14472,15 @@ function useUndoableState(initial, coalesceWindowMs = 600) {
14096
14472
  },
14097
14473
  [coalesceWindowMs]
14098
14474
  );
14099
- const undo = (0, import_react37.useCallback)(() => {
14475
+ const undo = (0, import_react38.useCallback)(() => {
14100
14476
  lastSetAt.current = 0;
14101
14477
  dispatch({ type: "undo" });
14102
14478
  }, []);
14103
- const redo = (0, import_react37.useCallback)(() => {
14479
+ const redo = (0, import_react38.useCallback)(() => {
14104
14480
  lastSetAt.current = 0;
14105
14481
  dispatch({ type: "redo" });
14106
14482
  }, []);
14107
- const reset = (0, import_react37.useCallback)((value) => {
14483
+ const reset = (0, import_react38.useCallback)((value) => {
14108
14484
  lastSetAt.current = 0;
14109
14485
  dispatch({ type: "reset", value });
14110
14486
  }, []);
@@ -14350,30 +14726,30 @@ function mergeSceneConfigSection(current, incoming) {
14350
14726
  return next;
14351
14727
  }
14352
14728
  function BindingBuilder({ licenseKey }) {
14353
- const modelInputRef = (0, import_react38.useRef)(null);
14354
- const bindingsInputRef = (0, import_react38.useRef)(null);
14355
- const sceneConfigInputRef = (0, import_react38.useRef)(null);
14356
- const [modelUrl, setModelUrl] = (0, import_react38.useState)(null);
14357
- const [modelName, setModelName] = (0, import_react38.useState)("");
14358
- 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);
14359
14735
  const bindingsHistory = useUndoableState({});
14360
14736
  const bindings = bindingsHistory.value;
14361
14737
  const setBindings = bindingsHistory.setValue;
14362
- const [loading, setLoading] = (0, import_react38.useState)(false);
14363
- const [isExporting, setIsExporting] = (0, import_react38.useState)(false);
14364
- 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)(
14365
14741
  "Load the demo model or upload a GLB/GLTF model to generate bindings."
14366
14742
  );
14367
- const [statusTone, setStatusTone] = (0, import_react38.useState)("default");
14368
- const [statusBannerVisible, setStatusBannerVisible] = (0, import_react38.useState)(false);
14369
- const [metricsDraft, setMetricsDraft] = (0, import_react38.useState)("{}");
14370
- const [metadataDraft, setMetadataDraft] = (0, import_react38.useState)("{}");
14371
- const [metricsError, setMetricsError] = (0, import_react38.useState)(null);
14372
- const [metadataError, setMetadataError] = (0, import_react38.useState)(null);
14373
- const [isPreviewDragActive, setIsPreviewDragActive] = (0, import_react38.useState)(false);
14374
- const [isEditorCollapsed, setIsEditorCollapsed] = (0, import_react38.useState)(false);
14375
- const [isNodesCollapsed, setIsNodesCollapsed] = (0, import_react38.useState)(false);
14376
- 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)({
14377
14753
  identity: true,
14378
14754
  basics: true,
14379
14755
  material: true,
@@ -14382,7 +14758,7 @@ function BindingBuilder({ licenseKey }) {
14382
14758
  metrics: false,
14383
14759
  metadata: false
14384
14760
  });
14385
- const [openMaterialPanels, setOpenMaterialPanels] = (0, import_react38.useState)({
14761
+ const [openMaterialPanels, setOpenMaterialPanels] = (0, import_react39.useState)({
14386
14762
  pbr: true,
14387
14763
  roughness: false,
14388
14764
  emission: false,
@@ -14398,27 +14774,27 @@ function BindingBuilder({ licenseKey }) {
14398
14774
  });
14399
14775
  const toggleObjectSection = (key) => setOpenObjectSections((prev) => ({ ...prev, [key]: !prev[key] }));
14400
14776
  const toggleMaterialPanel = (key) => setOpenMaterialPanels((prev) => ({ ...prev, [key]: !prev[key] }));
14401
- const [activeEditorTab, setActiveEditorTab] = (0, import_react38.useState)(
14777
+ const [activeEditorTab, setActiveEditorTab] = (0, import_react39.useState)(
14402
14778
  "object"
14403
14779
  );
14404
- const [activeSubTab, setActiveSubTab] = (0, import_react38.useState)("General");
14405
- const [pbrWorkflow, setPbrWorkflow] = (0, import_react38.useState)("metalness");
14406
- const [materialPanelEnabled, setMaterialPanelEnabled] = (0, import_react38.useState)(createMaterialPanelEnabledState());
14407
- const materialPanelItemRef = (0, import_react38.useRef)(null);
14408
- const [zoomOnSelected, setZoomOnSelected] = (0, import_react38.useState)(false);
14409
- const [highlightOnHover, setHighlightOnHover] = (0, import_react38.useState)(true);
14410
- const [resetDialogOpen, setResetDialogOpen] = (0, import_react38.useState)(false);
14411
- 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)(
14412
14788
  null
14413
14789
  );
14414
14790
  const sceneConfigHistory = useUndoableState(DEFAULT_SCENE_CONFIG);
14415
14791
  const sceneConfig = sceneConfigHistory.value;
14416
14792
  const setSceneConfig = sceneConfigHistory.setValue;
14417
- const [activeAnnotation, setActiveAnnotation] = (0, import_react38.useState)(null);
14418
- 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);
14419
14795
  const license = useLicense(licenseKey);
14420
14796
  const tier = normalizeTier(license.tier);
14421
- const bindingBuilderContextValue = (0, import_react38.useMemo)(
14797
+ const bindingBuilderContextValue = (0, import_react39.useMemo)(
14422
14798
  () => ({
14423
14799
  isInsideEditor: true,
14424
14800
  activeTab: activeEditorTab,
@@ -14427,11 +14803,11 @@ function BindingBuilder({ licenseKey }) {
14427
14803
  }),
14428
14804
  [activeEditorTab, activeSubTab, tier]
14429
14805
  );
14430
- const uploadedModelRef = (0, import_react38.useRef)(null);
14431
- const previewDragDepthRef = (0, import_react38.useRef)(0);
14432
- const loadRequestRef = (0, import_react38.useRef)(0);
14433
- const pendingLoadRef = (0, import_react38.useRef)(null);
14434
- 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);
14435
14811
  const {
14436
14812
  cameraState,
14437
14813
  resetView,
@@ -14441,14 +14817,16 @@ function BindingBuilder({ licenseKey }) {
14441
14817
  handleCameraChange,
14442
14818
  handleViewerReady
14443
14819
  } = useViewerCamera();
14444
- const handlePreviewWaypoint = (0, import_react38.useCallback)(
14820
+ const handlePreviewWaypoint = (0, import_react39.useCallback)(
14445
14821
  (waypoint) => {
14446
14822
  setCameraState(
14447
14823
  {
14448
14824
  position: waypoint.position,
14449
14825
  target: waypoint.target,
14450
14826
  fov: cameraState?.fov ?? null,
14451
- zoom: cameraState?.zoom ?? 1
14827
+ zoom: cameraState?.zoom ?? 1,
14828
+ azimuthAngle: waypoint.azimuthAngle,
14829
+ polarAngle: waypoint.polarAngle
14452
14830
  },
14453
14831
  true
14454
14832
  );
@@ -14463,7 +14841,7 @@ function BindingBuilder({ licenseKey }) {
14463
14841
  position: formatVector3(cameraState.position),
14464
14842
  target: formatVector3(cameraState.target)
14465
14843
  } : null;
14466
- const handleAnnotationsChange = (0, import_react38.useCallback)(
14844
+ const handleAnnotationsChange = (0, import_react39.useCallback)(
14467
14845
  (newAnnotations) => {
14468
14846
  setSceneConfig((prev) => {
14469
14847
  const currentIds = prev.annotations.map((a) => a.id).join(",");
@@ -14479,7 +14857,7 @@ function BindingBuilder({ licenseKey }) {
14479
14857
  },
14480
14858
  []
14481
14859
  );
14482
- const handleActiveAnnotationChange = (0, import_react38.useCallback)(
14860
+ const handleActiveAnnotationChange = (0, import_react39.useCallback)(
14483
14861
  (annotation) => {
14484
14862
  setActiveAnnotation(
14485
14863
  annotation ? {
@@ -14489,7 +14867,7 @@ function BindingBuilder({ licenseKey }) {
14489
14867
  },
14490
14868
  []
14491
14869
  );
14492
- (0, import_react38.useEffect)(() => {
14870
+ (0, import_react39.useEffect)(() => {
14493
14871
  if (!selectedBinding) {
14494
14872
  setMetricsDraft("{}");
14495
14873
  setMetadataDraft("{}");
@@ -14502,7 +14880,7 @@ function BindingBuilder({ licenseKey }) {
14502
14880
  setMetricsError(null);
14503
14881
  setMetadataError(null);
14504
14882
  }, [selectedBinding]);
14505
- (0, import_react38.useEffect)(() => {
14883
+ (0, import_react39.useEffect)(() => {
14506
14884
  const derived = createMaterialPanelEnabledState(selectedMaterial);
14507
14885
  if (materialPanelItemRef.current !== selectedItem) {
14508
14886
  materialPanelItemRef.current = selectedItem;
@@ -14521,12 +14899,12 @@ function BindingBuilder({ licenseKey }) {
14521
14899
  return changed ? next : prev;
14522
14900
  });
14523
14901
  }, [selectedItem, selectedMaterial]);
14524
- (0, import_react38.useEffect)(() => {
14902
+ (0, import_react39.useEffect)(() => {
14525
14903
  return () => {
14526
14904
  uploadedModelRef.current?.cleanup();
14527
14905
  };
14528
14906
  }, []);
14529
- (0, import_react38.useEffect)(() => {
14907
+ (0, import_react39.useEffect)(() => {
14530
14908
  const handleKeyDown = (event) => {
14531
14909
  if (event.key.toLowerCase() !== "z" || !(event.metaKey || event.ctrlKey)) {
14532
14910
  return;
@@ -14547,7 +14925,7 @@ function BindingBuilder({ licenseKey }) {
14547
14925
  window.addEventListener("keydown", handleKeyDown);
14548
14926
  return () => window.removeEventListener("keydown", handleKeyDown);
14549
14927
  }, [activeEditorTab, bindingsHistory, sceneConfigHistory]);
14550
- (0, import_react38.useEffect)(() => {
14928
+ (0, import_react39.useEffect)(() => {
14551
14929
  if (loading || statusTone === "default" || statusTone === "success") return;
14552
14930
  setStatusBannerVisible(true);
14553
14931
  const timeout = window.setTimeout(
@@ -14556,8 +14934,8 @@ function BindingBuilder({ licenseKey }) {
14556
14934
  );
14557
14935
  return () => window.clearTimeout(timeout);
14558
14936
  }, [statusMessage, statusTone, loading]);
14559
- const nodeKeys = (0, import_react38.useMemo)(() => Object.keys(bindings), [bindings]);
14560
- const idError = (0, import_react38.useMemo)(() => {
14937
+ const nodeKeys = (0, import_react39.useMemo)(() => Object.keys(bindings), [bindings]);
14938
+ const idError = (0, import_react39.useMemo)(() => {
14561
14939
  if (!selectedItem || !selectedBinding) return null;
14562
14940
  const trimmed = selectedBinding.id.trim();
14563
14941
  if (!trimmed) return "ID is required.";
@@ -14600,7 +14978,7 @@ function BindingBuilder({ licenseKey }) {
14600
14978
  });
14601
14979
  }
14602
14980
  };
14603
- const applyBindingsFromScene = (0, import_react38.useCallback)(
14981
+ const applyBindingsFromScene = (0, import_react39.useCallback)(
14604
14982
  (scene, nextModelName) => {
14605
14983
  const nodeNames = collectNodeNames(scene);
14606
14984
  bindingsHistory.reset(buildBindingsFromNodes(nodeNames));
@@ -14639,7 +15017,7 @@ function BindingBuilder({ licenseKey }) {
14639
15017
  bindingsHistory.reset({});
14640
15018
  setSelectedItem(null);
14641
15019
  };
14642
- const handleViewerModelLoaded = (0, import_react38.useCallback)(
15020
+ const handleViewerModelLoaded = (0, import_react39.useCallback)(
14643
15021
  (scene) => {
14644
15022
  const pending = pendingLoadRef.current;
14645
15023
  if (!pending || pending.requestId !== loadRequestRef.current) return;
@@ -14649,7 +15027,7 @@ function BindingBuilder({ licenseKey }) {
14649
15027
  },
14650
15028
  [applyBindingsFromScene]
14651
15029
  );
14652
- const handleViewerLoadError = (0, import_react38.useCallback)((error) => {
15030
+ const handleViewerLoadError = (0, import_react39.useCallback)((error) => {
14653
15031
  const pending = pendingLoadRef.current;
14654
15032
  if (!pending || pending.requestId !== loadRequestRef.current) return;
14655
15033
  pendingLoadRef.current = null;
@@ -14660,7 +15038,7 @@ function BindingBuilder({ licenseKey }) {
14660
15038
  setStatusTone("error");
14661
15039
  setLoading(false);
14662
15040
  }, []);
14663
- const handleAnimationsReady = (0, import_react38.useCallback)(
15041
+ const handleAnimationsReady = (0, import_react39.useCallback)(
14664
15042
  (controls) => {
14665
15043
  viewerAnimations.handleAnimationsReady(controls);
14666
15044
  const details = controls.clipDetails ?? controls.clips.map((sourceName) => ({ sourceName, duration: 0 }));
@@ -14961,7 +15339,7 @@ function BindingBuilder({ licenseKey }) {
14961
15339
  }));
14962
15340
  setError(null);
14963
15341
  };
14964
- const handleViewerSelect = (0, import_react38.useCallback)(
15342
+ const handleViewerSelect = (0, import_react39.useCallback)(
14965
15343
  (binding) => {
14966
15344
  if (!binding) {
14967
15345
  setSelectedItem(null);
@@ -14974,7 +15352,7 @@ function BindingBuilder({ licenseKey }) {
14974
15352
  },
14975
15353
  [bindings]
14976
15354
  );
14977
- const handleSceneNodeSelect = (0, import_react38.useCallback)(
15355
+ const handleSceneNodeSelect = (0, import_react39.useCallback)(
14978
15356
  async (nodeKey) => {
14979
15357
  setSelectedItem(nodeKey);
14980
15358
  setPanelHoveredBinding(null);
@@ -14982,7 +15360,7 @@ function BindingBuilder({ licenseKey }) {
14982
15360
  },
14983
15361
  [focusObject]
14984
15362
  );
14985
- const saveCameraPositionForSelectedObject = (0, import_react38.useCallback)(() => {
15363
+ const saveCameraPositionForSelectedObject = (0, import_react39.useCallback)(() => {
14986
15364
  if (!selectedItem || !cameraState) {
14987
15365
  return;
14988
15366
  }
@@ -14997,7 +15375,7 @@ function BindingBuilder({ licenseKey }) {
14997
15375
  cameraState: currentCameraState
14998
15376
  }));
14999
15377
  }, [selectedItem, cameraState]);
15000
- const goToSavedCameraPosition = (0, import_react38.useCallback)(() => {
15378
+ const goToSavedCameraPosition = (0, import_react39.useCallback)(() => {
15001
15379
  if (!savedCameraPosition?.position || !savedCameraPosition?.target) return;
15002
15380
  setCameraState(
15003
15381
  {
@@ -15009,28 +15387,28 @@ function BindingBuilder({ licenseKey }) {
15009
15387
  true
15010
15388
  );
15011
15389
  }, [savedCameraPosition, setCameraState, cameraState]);
15012
- const clearSavedCameraPosition = (0, import_react38.useCallback)(() => {
15390
+ const clearSavedCameraPosition = (0, import_react39.useCallback)(() => {
15013
15391
  if (!selectedItem) return;
15014
15392
  updateBinding(selectedItem, (binding) => ({
15015
15393
  ...binding,
15016
15394
  cameraState: void 0
15017
15395
  }));
15018
15396
  }, [selectedItem]);
15019
- const handleResetView = (0, import_react38.useCallback)(async () => {
15397
+ const handleResetView = (0, import_react39.useCallback)(async () => {
15020
15398
  await resetView();
15021
15399
  }, [resetView]);
15022
- const handleFitScene = (0, import_react38.useCallback)(async () => {
15400
+ const handleFitScene = (0, import_react39.useCallback)(async () => {
15023
15401
  await fitScene();
15024
15402
  }, [fitScene]);
15025
- const openResetDialog = (0, import_react38.useCallback)((target) => {
15403
+ const openResetDialog = (0, import_react39.useCallback)((target) => {
15026
15404
  setResetTarget(target);
15027
15405
  setResetDialogOpen(true);
15028
15406
  }, []);
15029
- const closeResetDialog = (0, import_react38.useCallback)(() => {
15407
+ const closeResetDialog = (0, import_react39.useCallback)(() => {
15030
15408
  setResetDialogOpen(false);
15031
15409
  setResetTarget(null);
15032
15410
  }, []);
15033
- const executeReset = (0, import_react38.useCallback)(() => {
15411
+ const executeReset = (0, import_react39.useCallback)(() => {
15034
15412
  if (resetTarget === "bindings") {
15035
15413
  if (!nodeKeys.length) {
15036
15414
  closeResetDialog();
@@ -15161,7 +15539,9 @@ function BindingBuilder({ licenseKey }) {
15161
15539
  // the whole settings panel for no reason.
15162
15540
  activeSubTab === "Cinematic" && cameraState ? {
15163
15541
  position: cameraState.position,
15164
- target: cameraState.target
15542
+ target: cameraState.target,
15543
+ azimuthAngle: cameraState.azimuthAngle,
15544
+ polarAngle: cameraState.polarAngle
15165
15545
  } : null
15166
15546
  ),
15167
15547
  onPreviewWaypoint: handlePreviewWaypoint,
@@ -15586,7 +15966,7 @@ function BindingBuilder({ licenseKey }) {
15586
15966
  }
15587
15967
 
15588
15968
  // src/components/SimpleModelViewer/SimpleModelViewer.tsx
15589
- var import_react39 = require("react");
15969
+ var import_react40 = require("react");
15590
15970
  var import_drei6 = require("@react-three/drei");
15591
15971
  var import_fiber12 = require("@react-three/fiber");
15592
15972
  var import_fiber13 = require("@react-three/fiber");
@@ -15626,7 +16006,7 @@ function applyClippingPlanes(scene, controls) {
15626
16006
  camera.updateProjectionMatrix();
15627
16007
  controls.maxDistance = size * 10;
15628
16008
  }
15629
- var ModelErrorBoundary2 = class extends import_react39.Component {
16009
+ var ModelErrorBoundary2 = class extends import_react40.Component {
15630
16010
  constructor() {
15631
16011
  super(...arguments);
15632
16012
  this.state = { hasError: false };
@@ -15658,12 +16038,12 @@ function InnerModel({
15658
16038
  meshopt: true
15659
16039
  });
15660
16040
  const invalidate = (0, import_fiber12.useThree)((state) => state.invalidate);
15661
- const [hoveredKey, setHoveredKey] = (0, import_react39.useState)(null);
15662
- const scene = (0, import_react39.useMemo)(
16041
+ const [hoveredKey, setHoveredKey] = (0, import_react40.useState)(null);
16042
+ const scene = (0, import_react40.useMemo)(
15663
16043
  () => import_three_stdlib4.SkeletonUtils.clone(rawScene),
15664
16044
  [rawScene]
15665
16045
  );
15666
- const meshMap = (0, import_react39.useMemo)(() => {
16046
+ const meshMap = (0, import_react40.useMemo)(() => {
15667
16047
  const map = {};
15668
16048
  scene.traverse((obj) => {
15669
16049
  if (!isMeshNode2(obj)) return;
@@ -15672,7 +16052,7 @@ function InnerModel({
15672
16052
  });
15673
16053
  return map;
15674
16054
  }, [scene]);
15675
- const perKeyMaterials = (0, import_react39.useMemo)(() => {
16055
+ const perKeyMaterials = (0, import_react40.useMemo)(() => {
15676
16056
  const map = {};
15677
16057
  for (const [key, { ref }] of Object.entries(meshMap)) {
15678
16058
  const original = Array.isArray(ref.material) ? ref.material[0] : ref.material;
@@ -15688,14 +16068,14 @@ function InnerModel({
15688
16068
  }
15689
16069
  return map;
15690
16070
  }, [meshMap]);
15691
- (0, import_react39.useEffect)(
16071
+ (0, import_react40.useEffect)(
15692
16072
  () => () => {
15693
16073
  Object.values(perKeyMaterials).forEach((m) => m.dispose());
15694
16074
  },
15695
16075
  [perKeyMaterials]
15696
16076
  );
15697
- const notifiedUrl = (0, import_react39.useRef)(null);
15698
- (0, import_react39.useEffect)(() => {
16077
+ const notifiedUrl = (0, import_react40.useRef)(null);
16078
+ (0, import_react40.useEffect)(() => {
15699
16079
  if (notifiedUrl.current === url) return;
15700
16080
  notifiedUrl.current = url;
15701
16081
  scene.updateWorldMatrix(true, true);
@@ -15703,7 +16083,7 @@ function InnerModel({
15703
16083
  onMeshesReady(meshMap);
15704
16084
  onModelLoaded(scene);
15705
16085
  }, [url, scene, meshMap, onMeshesReady, onModelLoaded]);
15706
- (0, import_react39.useEffect)(() => {
16086
+ (0, import_react40.useEffect)(() => {
15707
16087
  const controller = new AbortController();
15708
16088
  void buildSceneBVH(scene, controller.signal);
15709
16089
  return () => {
@@ -15711,26 +16091,26 @@ function InnerModel({
15711
16091
  disposeSceneBVH(scene);
15712
16092
  };
15713
16093
  }, [scene]);
15714
- const pointerRaycastEnabled = (0, import_react39.useMemo)(
16094
+ const pointerRaycastEnabled = (0, import_react40.useMemo)(
15715
16095
  () => !exceedsBvhBudget(scene),
15716
16096
  [scene]
15717
16097
  );
15718
- (0, import_react39.useEffect)(() => {
16098
+ (0, import_react40.useEffect)(() => {
15719
16099
  for (const [key, { ref }] of Object.entries(meshMap)) {
15720
16100
  nodeRefsOut.current[key] = ref;
15721
16101
  }
15722
16102
  }, [meshMap, nodeRefsOut]);
15723
- (0, import_react39.useEffect)(() => {
16103
+ (0, import_react40.useEffect)(() => {
15724
16104
  if (!highlightOnHover && hoveredKey !== null) {
15725
16105
  setHoveredKey(null);
15726
16106
  }
15727
16107
  }, [highlightOnHover, hoveredKey]);
15728
- (0, import_react39.useEffect)(() => {
16108
+ (0, import_react40.useEffect)(() => {
15729
16109
  return () => {
15730
16110
  document.body.style.cursor = "default";
15731
16111
  };
15732
16112
  }, []);
15733
- (0, import_react39.useEffect)(() => {
16113
+ (0, import_react40.useEffect)(() => {
15734
16114
  for (const [key, { ref }] of Object.entries(meshMap)) {
15735
16115
  ref.visible = !hiddenKeys.has(key);
15736
16116
  const mat = perKeyMaterials[key];
@@ -15800,11 +16180,11 @@ function CameraFitter({
15800
16180
  refitOnResize,
15801
16181
  onInitialFitComplete
15802
16182
  }) {
15803
- const fitted = (0, import_react39.useRef)(false);
16183
+ const fitted = (0, import_react40.useRef)(false);
15804
16184
  const invalidate = (0, import_fiber12.useThree)((state) => state.invalidate);
15805
16185
  const size = (0, import_fiber12.useThree)((state) => state.size);
15806
- const resizeTimerRef = (0, import_react39.useRef)(null);
15807
- (0, import_react39.useEffect)(() => {
16186
+ const resizeTimerRef = (0, import_react40.useRef)(null);
16187
+ (0, import_react40.useEffect)(() => {
15808
16188
  fitted.current = false;
15809
16189
  invalidate();
15810
16190
  }, [invalidate, scene]);
@@ -15824,7 +16204,7 @@ function CameraFitter({
15824
16204
  }).catch(() => {
15825
16205
  }).finally(onInitialFitComplete);
15826
16206
  });
15827
- (0, import_react39.useEffect)(() => {
16207
+ (0, import_react40.useEffect)(() => {
15828
16208
  if (!refitOnResize || !fitted.current || !scene || !controlsRef.current) {
15829
16209
  return;
15830
16210
  }
@@ -15852,7 +16232,7 @@ function CameraFitter({
15852
16232
  function ExposureController({ exposure }) {
15853
16233
  const gl = (0, import_fiber12.useThree)((state) => state.gl);
15854
16234
  const invalidate = (0, import_fiber12.useThree)((state) => state.invalidate);
15855
- (0, import_react39.useEffect)(() => {
16235
+ (0, import_react40.useEffect)(() => {
15856
16236
  gl.toneMappingExposure = exposure;
15857
16237
  invalidate();
15858
16238
  }, [gl, exposure, invalidate]);
@@ -15960,7 +16340,7 @@ function EnvironmentSection({
15960
16340
  title,
15961
16341
  children
15962
16342
  }) {
15963
- const [collapsed, setCollapsed] = (0, import_react39.useState)(false);
16343
+ const [collapsed, setCollapsed] = (0, import_react40.useState)(false);
15964
16344
  return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "env-section", children: [
15965
16345
  /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
15966
16346
  "button",
@@ -16125,8 +16505,8 @@ function SceneObjectsPanel2({
16125
16505
  onHover,
16126
16506
  onToggleVisibility
16127
16507
  }) {
16128
- const [search, setSearch] = (0, import_react39.useState)("");
16129
- 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);
16130
16510
  const entries = Object.values(meshKeys).filter(
16131
16511
  (m) => m.label.toLowerCase().includes(search.toLowerCase())
16132
16512
  );
@@ -16250,35 +16630,35 @@ function SimpleModelViewer({
16250
16630
  onModelLoaded,
16251
16631
  onLoadError
16252
16632
  }) {
16253
- const cameraControlsRef = (0, import_react39.useRef)(null);
16254
- const nodeRefsOut = (0, import_react39.useRef)({});
16255
- const fileInputRef = (0, import_react39.useRef)(null);
16256
- const uploadedModelRef = (0, import_react39.useRef)(null);
16257
- const dragDepthRef = (0, import_react39.useRef)(0);
16258
- const [loadedScene, setLoadedScene] = (0, import_react39.useState)(null);
16259
- 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)(
16260
16640
  null
16261
16641
  );
16262
- const [largeModelNoticeDismissed, setLargeModelNoticeDismissed] = (0, import_react39.useState)(false);
16263
- const [meshKeys, setMeshKeys] = (0, import_react39.useState)({});
16264
- const [hiddenKeys, setHiddenKeys] = (0, import_react39.useState)(/* @__PURE__ */ new Set());
16265
- const [selectedKey, setSelectedKey] = (0, import_react39.useState)(null);
16266
- const [panelHoveredKey, setPanelHoveredKey] = (0, import_react39.useState)(null);
16267
- const [uploadedModelUrl, setUploadedModelUrl] = (0, import_react39.useState)(null);
16268
- 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)(
16269
16649
  null
16270
16650
  );
16271
- const [modelLoadFailed, setModelLoadFailed] = (0, import_react39.useState)(false);
16272
- const [isResolvingUpload, setIsResolvingUpload] = (0, import_react39.useState)(false);
16273
- const [initialFitComplete, setInitialFitComplete] = (0, import_react39.useState)(false);
16274
- const [isDragActive, setIsDragActive] = (0, import_react39.useState)(false);
16275
- 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)(
16276
16656
  () => typeof window === "undefined" ? true : window.innerWidth > 900
16277
16657
  );
16278
- const [isEnvPanelOpen, setIsEnvPanelOpen] = (0, import_react39.useState)(
16658
+ const [isEnvPanelOpen, setIsEnvPanelOpen] = (0, import_react40.useState)(
16279
16659
  () => typeof window === "undefined" ? true : window.innerWidth > 900
16280
16660
  );
16281
- const [envSettings, setEnvSettings] = (0, import_react39.useState)({
16661
+ const [envSettings, setEnvSettings] = (0, import_react40.useState)({
16282
16662
  backgroundEnabled,
16283
16663
  backgroundColor,
16284
16664
  autoRotate,
@@ -16290,7 +16670,7 @@ function SimpleModelViewer({
16290
16670
  directionalColor
16291
16671
  });
16292
16672
  const activeModelUrl = enableModelUpload ? uploadedModelUrl : modelUrl;
16293
- const resetModelState = (0, import_react39.useCallback)(() => {
16673
+ const resetModelState = (0, import_react40.useCallback)(() => {
16294
16674
  setLoadedScene(null);
16295
16675
  setLargeModelTriangles(null);
16296
16676
  setLargeModelNoticeDismissed(false);
@@ -16301,11 +16681,11 @@ function SimpleModelViewer({
16301
16681
  setModelLoadFailed(false);
16302
16682
  setInitialFitComplete(false);
16303
16683
  }, []);
16304
- const revokeUploadedModelUrl = (0, import_react39.useCallback)(() => {
16684
+ const revokeUploadedModelUrl = (0, import_react40.useCallback)(() => {
16305
16685
  uploadedModelRef.current?.cleanup();
16306
16686
  uploadedModelRef.current = null;
16307
16687
  }, []);
16308
- const clearUploadedModel = (0, import_react39.useCallback)(() => {
16688
+ const clearUploadedModel = (0, import_react40.useCallback)(() => {
16309
16689
  revokeUploadedModelUrl();
16310
16690
  setUploadedModelUrl(null);
16311
16691
  setUploadedModelName(null);
@@ -16334,7 +16714,7 @@ function SimpleModelViewer({
16334
16714
  resetModelState,
16335
16715
  revokeUploadedModelUrl
16336
16716
  ]);
16337
- const handleUploadedModel = (0, import_react39.useCallback)(
16717
+ const handleUploadedModel = (0, import_react40.useCallback)(
16338
16718
  async (file) => {
16339
16719
  setIsResolvingUpload(true);
16340
16720
  try {
@@ -16353,7 +16733,7 @@ function SimpleModelViewer({
16353
16733
  },
16354
16734
  [onLoadError, resetModelState, revokeUploadedModelUrl]
16355
16735
  );
16356
- const handleFileSelection = (0, import_react39.useCallback)(
16736
+ const handleFileSelection = (0, import_react40.useCallback)(
16357
16737
  (files) => {
16358
16738
  const file = files?.[0];
16359
16739
  if (!file) return;
@@ -16361,29 +16741,29 @@ function SimpleModelViewer({
16361
16741
  },
16362
16742
  [handleUploadedModel]
16363
16743
  );
16364
- (0, import_react39.useEffect)(() => {
16744
+ (0, import_react40.useEffect)(() => {
16365
16745
  return () => {
16366
16746
  revokeUploadedModelUrl();
16367
16747
  };
16368
16748
  }, [revokeUploadedModelUrl]);
16369
- const handleEnvSettingChange = (0, import_react39.useCallback)(
16749
+ const handleEnvSettingChange = (0, import_react40.useCallback)(
16370
16750
  (key, value) => {
16371
16751
  setEnvSettings((prev) => ({ ...prev, [key]: value }));
16372
16752
  },
16373
16753
  []
16374
16754
  );
16375
- (0, import_react39.useEffect)(() => {
16755
+ (0, import_react40.useEffect)(() => {
16376
16756
  setEnvSettings((prev) => ({ ...prev, backgroundColor }));
16377
16757
  }, [backgroundColor]);
16378
- (0, import_react39.useEffect)(() => {
16758
+ (0, import_react40.useEffect)(() => {
16379
16759
  resetModelState();
16380
16760
  }, [activeModelUrl, resetModelState]);
16381
- const handleMeshesReady = (0, import_react39.useCallback)((meshes) => {
16761
+ const handleMeshesReady = (0, import_react40.useCallback)((meshes) => {
16382
16762
  setMeshKeys(meshes);
16383
16763
  setHiddenKeys(/* @__PURE__ */ new Set());
16384
16764
  setSelectedKey(null);
16385
16765
  }, []);
16386
- const handleModelLoaded = (0, import_react39.useCallback)(
16766
+ const handleModelLoaded = (0, import_react40.useCallback)(
16387
16767
  (scene) => {
16388
16768
  setLoadedScene(scene);
16389
16769
  setModelLoadFailed(false);
@@ -16394,14 +16774,14 @@ function SimpleModelViewer({
16394
16774
  },
16395
16775
  [onModelLoaded]
16396
16776
  );
16397
- const handleLoadError = (0, import_react39.useCallback)(
16777
+ const handleLoadError = (0, import_react40.useCallback)(
16398
16778
  (error) => {
16399
16779
  setModelLoadFailed(true);
16400
16780
  onLoadError?.(error);
16401
16781
  },
16402
16782
  [onLoadError]
16403
16783
  );
16404
- const focusMesh = (0, import_react39.useCallback)(
16784
+ const focusMesh = (0, import_react40.useCallback)(
16405
16785
  (key) => {
16406
16786
  setSelectedKey(key);
16407
16787
  if (!zoomOnSelected) return;
@@ -16417,14 +16797,14 @@ function SimpleModelViewer({
16417
16797
  },
16418
16798
  [zoomOnSelected]
16419
16799
  );
16420
- const handleToggleVisibility = (0, import_react39.useCallback)((key) => {
16800
+ const handleToggleVisibility = (0, import_react40.useCallback)((key) => {
16421
16801
  setHiddenKeys((prev) => {
16422
16802
  const next = new Set(prev);
16423
16803
  next.has(key) ? next.delete(key) : next.add(key);
16424
16804
  return next;
16425
16805
  });
16426
16806
  }, []);
16427
- const handleDragEnter = (0, import_react39.useCallback)(
16807
+ const handleDragEnter = (0, import_react40.useCallback)(
16428
16808
  (event) => {
16429
16809
  if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
16430
16810
  return;
@@ -16436,7 +16816,7 @@ function SimpleModelViewer({
16436
16816
  },
16437
16817
  [enableModelUpload]
16438
16818
  );
16439
- const handleDragOver = (0, import_react39.useCallback)(
16819
+ const handleDragOver = (0, import_react40.useCallback)(
16440
16820
  (event) => {
16441
16821
  if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
16442
16822
  return;
@@ -16448,7 +16828,7 @@ function SimpleModelViewer({
16448
16828
  },
16449
16829
  [enableModelUpload]
16450
16830
  );
16451
- const handleDragLeave = (0, import_react39.useCallback)(
16831
+ const handleDragLeave = (0, import_react40.useCallback)(
16452
16832
  (event) => {
16453
16833
  if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
16454
16834
  return;
@@ -16462,7 +16842,7 @@ function SimpleModelViewer({
16462
16842
  },
16463
16843
  [enableModelUpload]
16464
16844
  );
16465
- const handleDrop = (0, import_react39.useCallback)(
16845
+ const handleDrop = (0, import_react40.useCallback)(
16466
16846
  (event) => {
16467
16847
  if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
16468
16848
  return;
@@ -16555,7 +16935,7 @@ function SimpleModelViewer({
16555
16935
  raycaster: { firstHitOnly: true },
16556
16936
  style: { background: envSettings.backgroundColor },
16557
16937
  camera: { position: [0, 0, 5], fov: 45, near: 0.1, far: 1e3 },
16558
- 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: [
16559
16939
  /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
16560
16940
  "ambientLight",
16561
16941
  {
@@ -16573,7 +16953,7 @@ function SimpleModelViewer({
16573
16953
  }
16574
16954
  ),
16575
16955
  /* @__PURE__ */ (0, import_jsx_runtime46.jsx)("directionalLight", { position: [-5, 5, 5], intensity: 0.5 }),
16576
- /* @__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)(
16577
16957
  import_drei6.Environment,
16578
16958
  {
16579
16959
  preset: envSettings.envPreset,
@@ -16592,7 +16972,7 @@ function SimpleModelViewer({
16592
16972
  ModelErrorBoundary2,
16593
16973
  {
16594
16974
  onLoadError: handleLoadError,
16595
- 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)(
16596
16976
  InnerModel,
16597
16977
  {
16598
16978
  url: activeModelUrl,
@@ -16691,18 +17071,18 @@ function SimpleModelViewer({
16691
17071
  }
16692
17072
 
16693
17073
  // src/hooks/useViewerSelection.ts
16694
- var import_react40 = require("react");
17074
+ var import_react41 = require("react");
16695
17075
  function useViewerSelection(initialSelectedObjectBinding = null) {
16696
- const [selectedObjectBinding, setSelectedObjectBinding] = (0, import_react40.useState)(
17076
+ const [selectedObjectBinding, setSelectedObjectBinding] = (0, import_react41.useState)(
16697
17077
  initialSelectedObjectBinding
16698
17078
  );
16699
- const handleObjectSelect = (0, import_react40.useCallback)(
17079
+ const handleObjectSelect = (0, import_react41.useCallback)(
16700
17080
  (binding) => {
16701
17081
  setSelectedObjectBinding(binding);
16702
17082
  },
16703
17083
  [setSelectedObjectBinding]
16704
17084
  );
16705
- const clearSelection = (0, import_react40.useCallback)(() => {
17085
+ const clearSelection = (0, import_react41.useCallback)(() => {
16706
17086
  setSelectedObjectBinding(null);
16707
17087
  }, []);
16708
17088
  return {
@@ -16714,9 +17094,9 @@ function useViewerSelection(initialSelectedObjectBinding = null) {
16714
17094
  }
16715
17095
 
16716
17096
  // src/hooks/useObjectBinding.ts
16717
- var import_react41 = require("react");
17097
+ var import_react42 = require("react");
16718
17098
  function useObjectBinding(objectBindings, objectId) {
16719
- return (0, import_react41.useMemo)(() => {
17099
+ return (0, import_react42.useMemo)(() => {
16720
17100
  if (!objectId) {
16721
17101
  return null;
16722
17102
  }
@@ -16731,23 +17111,23 @@ function useObjectBinding(objectBindings, objectId) {
16731
17111
  }
16732
17112
 
16733
17113
  // src/hooks/useObjectBindingIds.ts
16734
- var import_react42 = require("react");
17114
+ var import_react43 = require("react");
16735
17115
  function useObjectBindingIds(objectBindings) {
16736
- return (0, import_react42.useMemo)(
17116
+ return (0, import_react43.useMemo)(
16737
17117
  () => Object.values(objectBindings).map((binding) => binding.modelObjectId),
16738
17118
  [objectBindings]
16739
17119
  );
16740
17120
  }
16741
17121
 
16742
17122
  // src/hooks/useObjectVisibility.ts
16743
- var import_react43 = require("react");
17123
+ var import_react44 = require("react");
16744
17124
  function getHiddenObjects(objectBindings) {
16745
17125
  return Object.fromEntries(
16746
17126
  Object.entries(objectBindings).filter(([, binding]) => binding.visible === false).map(([bindingKey]) => [bindingKey, true])
16747
17127
  );
16748
17128
  }
16749
17129
  function useObjectVisibility(objectBindings, onObjectBindingsChange) {
16750
- const updateVisibility = (0, import_react43.useCallback)(
17130
+ const updateVisibility = (0, import_react44.useCallback)(
16751
17131
  (objectId, visible) => {
16752
17132
  const bindingKey = resolveBindingKey(objectBindings, objectId);
16753
17133
  if (!bindingKey) return;
@@ -16763,15 +17143,15 @@ function useObjectVisibility(objectBindings, onObjectBindingsChange) {
16763
17143
  },
16764
17144
  [objectBindings, onObjectBindingsChange]
16765
17145
  );
16766
- const hideObject = (0, import_react43.useCallback)(
17146
+ const hideObject = (0, import_react44.useCallback)(
16767
17147
  (objectId) => updateVisibility(objectId, false),
16768
17148
  [updateVisibility]
16769
17149
  );
16770
- const showObject = (0, import_react43.useCallback)(
17150
+ const showObject = (0, import_react44.useCallback)(
16771
17151
  (objectId) => updateVisibility(objectId, true),
16772
17152
  [updateVisibility]
16773
17153
  );
16774
- const toggleObjectVisibility = (0, import_react43.useCallback)(
17154
+ const toggleObjectVisibility = (0, import_react44.useCallback)(
16775
17155
  (objectId) => {
16776
17156
  const bindingKey = resolveBindingKey(objectBindings, objectId);
16777
17157
  if (!bindingKey) return;
@@ -16787,7 +17167,7 @@ function useObjectVisibility(objectBindings, onObjectBindingsChange) {
16787
17167
  },
16788
17168
  [objectBindings, onObjectBindingsChange]
16789
17169
  );
16790
- const clearHiddenObjects = (0, import_react43.useCallback)(() => {
17170
+ const clearHiddenObjects = (0, import_react44.useCallback)(() => {
16791
17171
  onObjectBindingsChange(
16792
17172
  Object.fromEntries(
16793
17173
  Object.entries(objectBindings).map(([bindingKey, binding]) => [
@@ -16797,17 +17177,17 @@ function useObjectVisibility(objectBindings, onObjectBindingsChange) {
16797
17177
  )
16798
17178
  );
16799
17179
  }, [objectBindings, onObjectBindingsChange]);
16800
- const hiddenObjects = (0, import_react43.useMemo)(
17180
+ const hiddenObjects = (0, import_react44.useMemo)(
16801
17181
  () => getHiddenObjects(objectBindings),
16802
17182
  [objectBindings]
16803
17183
  );
16804
- const hiddenObjectIds = (0, import_react43.useMemo)(
17184
+ const hiddenObjectIds = (0, import_react44.useMemo)(
16805
17185
  () => Object.entries(hiddenObjects).map(
16806
17186
  ([bindingKey]) => objectBindings[bindingKey]?.id ?? bindingKey
16807
17187
  ),
16808
17188
  [hiddenObjects, objectBindings]
16809
17189
  );
16810
- const isObjectHidden = (0, import_react43.useCallback)(
17190
+ const isObjectHidden = (0, import_react44.useCallback)(
16811
17191
  (objectId) => {
16812
17192
  const bindingKey = resolveBindingKey(objectBindings, objectId);
16813
17193
  return bindingKey ? objectBindings[bindingKey]?.visible === false : false;
@@ -16826,10 +17206,10 @@ function useObjectVisibility(objectBindings, onObjectBindingsChange) {
16826
17206
  }
16827
17207
 
16828
17208
  // src/hooks/useViewerHover.ts
16829
- var import_react44 = require("react");
17209
+ var import_react45 = require("react");
16830
17210
  function useViewerHover() {
16831
- const [hoveredObjectBinding, setHoveredObjectBinding] = (0, import_react44.useState)(null);
16832
- const handleHoveredObject = (0, import_react44.useCallback)(
17211
+ const [hoveredObjectBinding, setHoveredObjectBinding] = (0, import_react45.useState)(null);
17212
+ const handleHoveredObject = (0, import_react45.useCallback)(
16833
17213
  (binding) => {
16834
17214
  setHoveredObjectBinding(binding);
16835
17215
  },
@@ -16842,18 +17222,18 @@ function useViewerHover() {
16842
17222
  }
16843
17223
 
16844
17224
  // src/hooks/useViewerModel.ts
16845
- var import_react45 = require("react");
17225
+ var import_react46 = require("react");
16846
17226
  var import_three22 = require("three");
16847
17227
  function toTuple2(vector) {
16848
17228
  return [vector.x, vector.y, vector.z];
16849
17229
  }
16850
17230
  function useViewerModel() {
16851
- const [isLoading, setIsLoading] = (0, import_react45.useState)(true);
16852
- const [isReady, setIsReady] = (0, import_react45.useState)(false);
16853
- const [error, setError] = (0, import_react45.useState)(null);
16854
- const [bounds, setBounds] = (0, import_react45.useState)(null);
16855
- const [objectCount, setObjectCount] = (0, import_react45.useState)(0);
16856
- 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) => {
16857
17237
  const box = new import_three22.Box3().setFromObject(scene);
16858
17238
  const min = box.min.clone();
16859
17239
  const max = box.max.clone();
@@ -16878,14 +17258,14 @@ function useViewerModel() {
16878
17258
  setIsLoading(false);
16879
17259
  setIsReady(true);
16880
17260
  }, []);
16881
- const handleLoadError = (0, import_react45.useCallback)((nextError) => {
17261
+ const handleLoadError = (0, import_react46.useCallback)((nextError) => {
16882
17262
  setError(nextError);
16883
17263
  setBounds(null);
16884
17264
  setObjectCount(0);
16885
17265
  setIsLoading(false);
16886
17266
  setIsReady(false);
16887
17267
  }, []);
16888
- const resetModelState = (0, import_react45.useCallback)(() => {
17268
+ const resetModelState = (0, import_react46.useCallback)(() => {
16889
17269
  setIsLoading(true);
16890
17270
  setIsReady(false);
16891
17271
  setError(null);
@@ -16905,10 +17285,10 @@ function useViewerModel() {
16905
17285
  }
16906
17286
 
16907
17287
  // src/hooks/useViewerActions.ts
16908
- var import_react46 = require("react");
17288
+ var import_react47 = require("react");
16909
17289
  function useViewerActions(objectBindings, options = {}) {
16910
17290
  const { onAction, onObjectBindingsChange } = options;
16911
- const getActionsForObject = (0, import_react46.useCallback)(
17291
+ const getActionsForObject = (0, import_react47.useCallback)(
16912
17292
  (objectId) => {
16913
17293
  const bindingKey = resolveBindingKey(objectBindings, objectId);
16914
17294
  if (!bindingKey) {
@@ -16918,7 +17298,7 @@ function useViewerActions(objectBindings, options = {}) {
16918
17298
  },
16919
17299
  [objectBindings]
16920
17300
  );
16921
- const runAction = (0, import_react46.useCallback)(
17301
+ const runAction = (0, import_react47.useCallback)(
16922
17302
  (objectId, actionId) => {
16923
17303
  const bindingKey = resolveBindingKey(objectBindings, objectId);
16924
17304
  if (!bindingKey) {
@@ -16960,7 +17340,7 @@ function useViewerActions(objectBindings, options = {}) {
16960
17340
  }
16961
17341
 
16962
17342
  // src/hooks/useShareableViewerState.ts
16963
- var import_react47 = require("react");
17343
+ var import_react48 = require("react");
16964
17344
  function serializeState(cameraState, objectBindings, selectedObjectBinding) {
16965
17345
  const hidden = Object.values(objectBindings).filter((binding) => binding.visible === false).map((binding) => binding.id);
16966
17346
  return {
@@ -16980,8 +17360,8 @@ function useShareableViewerState({
16980
17360
  debounceMs = 400,
16981
17361
  autoSync = true
16982
17362
  }) {
16983
- const lastWrittenRawRef = (0, import_react47.useRef)(null);
16984
- const buildHash = (0, import_react47.useCallback)(() => {
17363
+ const lastWrittenRawRef = (0, import_react48.useRef)(null);
17364
+ const buildHash = (0, import_react48.useCallback)(() => {
16985
17365
  const state = serializeState(
16986
17366
  cameraState,
16987
17367
  objectBindings,
@@ -16989,12 +17369,12 @@ function useShareableViewerState({
16989
17369
  );
16990
17370
  return `${paramKey}=${encodeURIComponent(JSON.stringify(state))}`;
16991
17371
  }, [cameraState, objectBindings, selectedObjectBinding, paramKey]);
16992
- const getShareUrl = (0, import_react47.useCallback)(() => {
17372
+ const getShareUrl = (0, import_react48.useCallback)(() => {
16993
17373
  const url = new URL(window.location.href);
16994
17374
  url.hash = buildHash();
16995
17375
  return url.toString();
16996
17376
  }, [buildHash]);
16997
- const syncUrl = (0, import_react47.useCallback)(() => {
17377
+ const syncUrl = (0, import_react48.useCallback)(() => {
16998
17378
  const url = new URL(window.location.href);
16999
17379
  url.hash = buildHash();
17000
17380
  window.history.replaceState(null, "", url.toString());
@@ -17002,7 +17382,7 @@ function useShareableViewerState({
17002
17382
  paramKey
17003
17383
  );
17004
17384
  }, [buildHash, paramKey]);
17005
- const latestStateRef = (0, import_react47.useRef)({
17385
+ const latestStateRef = (0, import_react48.useRef)({
17006
17386
  cameraState,
17007
17387
  objectBindings,
17008
17388
  selectedObjectBinding
@@ -17012,9 +17392,9 @@ function useShareableViewerState({
17012
17392
  objectBindings,
17013
17393
  selectedObjectBinding
17014
17394
  };
17015
- const [restoreAttempted, setRestoreAttempted] = (0, import_react47.useState)(false);
17016
- const lastSyncedHashRef = (0, import_react47.useRef)(null);
17017
- (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)(() => {
17018
17398
  if (!autoSync || !restoreAttempted) return;
17019
17399
  const tick = () => {
17020
17400
  const current = latestStateRef.current;
@@ -17036,9 +17416,9 @@ function useShareableViewerState({
17036
17416
  const id = window.setInterval(tick, debounceMs);
17037
17417
  return () => window.clearInterval(id);
17038
17418
  }, [autoSync, restoreAttempted, debounceMs, paramKey]);
17039
- const lastRestoredRawRef = (0, import_react47.useRef)(null);
17040
- const restoringRawRef = (0, import_react47.useRef)(null);
17041
- 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 () => {
17042
17422
  setRestoreAttempted(true);
17043
17423
  const rawHash = window.location.hash.startsWith("#") ? window.location.hash.slice(1) : window.location.hash;
17044
17424
  const raw = new URLSearchParams(rawHash).get(paramKey);
@@ -17091,7 +17471,7 @@ function useShareableViewerState({
17091
17471
  setCameraState,
17092
17472
  setSelectedObjectBinding
17093
17473
  ]);
17094
- (0, import_react47.useEffect)(() => {
17474
+ (0, import_react48.useEffect)(() => {
17095
17475
  const handleHashChange = () => {
17096
17476
  void restoreFromUrl();
17097
17477
  };