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