@liveroom-tech/react-immersive 1.3.3 → 1.3.5
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/README.md +27 -1
- package/dist/index.d.mts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +1023 -366
- package/dist/index.mjs +975 -316
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -200,7 +200,7 @@ function styleInject(css, { insertAt } = {}) {
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
// src/styles/ModelViewer.css
|
|
203
|
-
styleInject('.ri-viewer--license-checking,\n.ri-viewer--license-invalid {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n background-color: #f9f9f9;\n}\n.ri-viewer {\n --ri-font-family:\n Inter,\n "Segoe UI",\n Helvetica,\n Arial,\n sans-serif;\n --ri-viewer-bg: #1c1c1d;\n --ri-viewer-text: #111827;\n --ri-panel-bg: #ffffff;\n --ri-panel-border: #e5e7eb;\n --ri-panel-title: #111827;\n --ri-panel-muted-text: #4b5563;\n --ri-panel-subtle-text: #9ca3af;\n --ri-panel-surface: #f9fafb;\n --ri-panel-surface-hover: #f3f4f6;\n --ri-panel-surface-border: #e5e7eb;\n --ri-panel-button-text: #1f2937;\n --ri-panel-accent: #2563eb;\n --ri-panel-accent-contrast: #ffffff;\n --ri-license-overlay-bg: rgba(0, 0, 0, 0.04);\n --ri-license-overlay-border: rgba(0, 0, 0, 0.08);\n --ri-license-overlay-text: #888;\n --ri-license-overlay-error-bg: rgba(192, 57, 43, 0.06);\n --ri-license-overlay-error-border: rgba(192, 57, 43, 0.2);\n --ri-license-overlay-error-text: #c0392b;\n --ri-sidepanel-font-family:\n Inter,\n "Segoe UI",\n Helvetica,\n Arial,\n sans-serif;\n --ri-sidepanel-bg: var(--ri-databinding-bg, #111318);\n --ri-sidepanel-open-btn-bg: rgba(20, 20, 20, 0.82);\n --ri-sidepanel-open-btn-bg-hover: #27282b7e;\n --ri-sidepanel-border: rgba(255, 255, 255, 0.06);\n --ri-sidepanel-divider: rgba(255, 255, 255, 0.04);\n --ri-sidepanel-border-strong: rgba(255, 255, 255, 0.08);\n --ri-sidepanel-border-active: rgba(255, 255, 255, 0.18);\n --ri-sidepanel-shadow-left: 4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-sidepanel-shadow-right: -4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-sidepanel-text: var(--ri-databinding-text, #f1f5f9);\n --ri-sidepanel-text-emphasis: var(--ri-databinding-text-hover, #cbd5e1);\n --ri-sidepanel-text-muted: var(--ri-databinding-text-muted, #94a3b8);\n --ri-sidepanel-text-subtle: var(--ri-databinding-text-subtle, #64748b);\n --ri-sidepanel-placeholder: var(--ri-databinding-text-faint, #475569);\n --ri-sidepanel-surface: rgba(255, 255, 255, 0.05);\n --ri-sidepanel-surface-hover: rgba(255, 255, 255, 0.1);\n --ri-sidepanel-accent: #3b82f6;\n --ri-sidepanel-accent-soft: rgba(59, 130, 246, 0.12);\n --ri-databinding-bg: #111318;\n --ri-databinding-border: rgba(255, 255, 255, 0.06);\n --ri-databinding-border-strong: rgba(255, 255, 255, 0.08);\n --ri-databinding-border-hover: rgba(255, 255, 255, 0.15);\n --ri-databinding-shadow: 4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-databinding-font-family:\n "Inter",\n "Segoe UI",\n sans-serif;\n --ri-databinding-mono-font-family:\n "JetBrains Mono",\n "Fira Code",\n "Menlo",\n monospace;\n --ri-databinding-text: #f1f5f9;\n --ri-databinding-text-muted: #94a3b8;\n --ri-databinding-text-subtle: #64748b;\n --ri-databinding-text-faint: #475569;\n --ri-databinding-text-hover: #cbd5e1;\n --ri-databinding-surface: rgba(255, 255, 255, 0.03);\n --ri-databinding-surface-hover: rgba(255, 255, 255, 0.06);\n --ri-databinding-accent: #93c5fd;\n --ri-databinding-accent-glow: rgba(147, 197, 253, 0.8);\n --ri-databinding-accent-border: rgba(96, 165, 250, 0.55);\n --ri-databinding-accent-gradient:\n linear-gradient(\n \n 135deg,\n #1e3a8a 0%,\n #1d4ed8 60%,\n #2563eb 100% );\n --ri-databinding-accent-gradient-active:\n linear-gradient(\n \n 135deg,\n #1e3a8a 0%,\n #1e40af 100% );\n display: flex;\n flex-direction: row;\n width: 100%;\n height: 100%;\n min-height: 0;\n overflow: hidden;\n background: var(--ri-viewer-bg);\n position: relative;\n color: var(--ri-viewer-text);\n font-family: var(--ri-font-family);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n text-rendering: optimizeLegibility;\n font-synthesis: none;\n}\n.ri-viewer * {\n box-sizing: border-box;\n}\n.ri-viewerLeft {\n display: flex;\n flex: 1;\n min-height: 0;\n min-width: 0;\n position: relative;\n}\n.ri-viewerRight {\n display: flex;\n flex-shrink: 0;\n min-width: 0;\n}\n.ri-panel {\n width: 280px;\n min-height: 0;\n background: var(--ri-panel-bg);\n border-right: 1px solid var(--ri-panel-border);\n display: flex;\n flex-direction: column;\n padding: 20px;\n overflow-y: auto;\n flex-shrink: 0;\n transition: margin-left 0.3s ease, transform 0.3s ease;\n}\n.ri-panel--open {\n transform: translateX(0);\n margin-left: 0;\n}\n.ri-panel--closed {\n transform: translateX(-100%);\n margin-left: -280px;\n}\n.ri-panelHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 12px;\n}\n.ri-panelTitle {\n margin: 0;\n font-size: 15px;\n font-weight: 700;\n color: var(--ri-panel-title);\n line-height: 1.4;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.ri-closeButton {\n border: 0;\n background: transparent;\n color: var(--ri-panel-subtle-text);\n font-size: 22px;\n line-height: 1;\n padding: 0;\n cursor: pointer;\n flex-shrink: 0;\n transition: color 0.2s ease;\n}\n.ri-closeButton:hover {\n color: var(--ri-panel-muted-text);\n}\n.ri-json {\n margin: 0 0 16px;\n padding: 12px;\n background: var(--ri-panel-surface);\n border: 1px solid var(--ri-panel-surface-border);\n border-radius: 12px;\n font-size: 12px;\n line-height: 1.55;\n color: var(--ri-panel-button-text);\n overflow-x: auto;\n white-space: pre-wrap;\n word-break: break-word;\n}\n.ri-divider {\n border-top: 1px solid var(--ri-panel-border);\n margin-bottom: 12px;\n}\n.ri-sectionLabel {\n margin-bottom: 8px;\n font-size: 11px;\n font-weight: 700;\n color: var(--ri-panel-subtle-text);\n text-transform: uppercase;\n letter-spacing: 0.16em;\n}\n.ri-actionList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.ri-actionButton {\n width: 100%;\n border-radius: 8px;\n border: 1px solid var(--ri-panel-surface-border);\n background: var(--ri-panel-surface);\n color: var(--ri-panel-button-text);\n padding: 10px 12px;\n text-align: left;\n font-size: 14px;\n line-height: 1.4;\n cursor: pointer;\n transition:\n background-color 0.2s ease,\n border-color 0.2s ease,\n color 0.2s ease;\n}\n.ri-actionButton:hover {\n background: var(--ri-panel-surface-hover);\n border-color: var(--ri-panel-border);\n}\n.ri-actionButton--active {\n background: var(--ri-panel-accent);\n border-color: var(--ri-panel-accent);\n color: var(--ri-panel-accent-contrast);\n}\n.ri-actionButton--active:hover {\n background: var(--ri-panel-accent);\n border-color: var(--ri-panel-accent);\n}\n.ri-canvasArea {\n flex: 1 1 auto;\n position: relative;\n min-width: 0;\n}\n.ri-canvasArea--measuring {\n cursor: crosshair;\n}\n.ri-canvasArea canvas {\n opacity: 1;\n transition: opacity 0.18s ease;\n}\n.ri-canvasArea--positioning canvas {\n opacity: 0;\n transition: none;\n}\n.ri-bindingsMismatchBanner {\n position: absolute;\n top: 12px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 12px 7px 10px;\n border-radius: 8px;\n background: rgba(30, 20, 0, 0.88);\n border: 1px solid rgba(251, 191, 36, 0.35);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n color: rgb(251, 191, 36);\n font-size: 12px;\n font-weight: 500;\n line-height: 1.4;\n white-space: nowrap;\n pointer-events: auto;\n max-width: calc(100% - 32px);\n white-space: normal;\n text-align: center;\n}\n.ri-bindingsMismatchBanner__icon {\n flex-shrink: 0;\n}\n.ri-bindingsMismatchBanner__dismiss {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 4px;\n padding: 2px;\n background: none;\n border: none;\n border-radius: 4px;\n color: rgba(251, 191, 36, 0.7);\n cursor: pointer;\n transition: color 0.15s ease, background 0.15s ease;\n}\n.ri-bindingsMismatchBanner__dismiss:hover {\n color: rgb(251, 191, 36);\n background: rgba(251, 191, 36, 0.12);\n}\n.ri-measureTools {\n position: absolute;\n top: 16px;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-measureButton {\n width: 34px;\n height: 34px;\n padding: 0;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n color: rgba(255, 255, 255, 0.75);\n border-radius: 8px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n touch-action: manipulation;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n transition:\n background 0.15s ease,\n color 0.15s ease,\n border-color 0.15s ease;\n}\n.ri-measureButton:hover {\n background: rgba(40, 40, 40, 0.92);\n color: #fff;\n border-color: rgba(255, 255, 255, 0.25);\n}\n.ri-measureButton:active {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-measureButton--active {\n background: #22d3ee;\n border-color: #22d3ee;\n color: #06343c;\n}\n.ri-measureButton--active:hover {\n background: #38dbf0;\n color: #06343c;\n}\n.ri-measureHint {\n padding: 6px 10px;\n border-radius: 8px;\n font-size: 12px;\n line-height: 1;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n white-space: nowrap;\n}\n.ri-explodeTools {\n position: absolute;\n top: 16px;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-explodeSlider {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 34px;\n padding: 0 12px;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 8px;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n}\n.ri-explodeSlider input[type=range] {\n width: 120px;\n accent-color: #22d3ee;\n cursor: pointer;\n}\n.ri-cinematicTools {\n position: absolute;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-measureDot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: #22d3ee;\n border: 2px solid #fff;\n box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.4);\n pointer-events: none;\n}\n.ri-measureLabel {\n padding: 3px 7px;\n border-radius: 6px;\n font-size: 12px;\n font-weight: 600;\n line-height: 1;\n white-space: nowrap;\n color: #06343c;\n background: #22d3ee;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n pointer-events: none;\n user-select: none;\n}\n.ri-xrTools {\n position: absolute;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-xrButton {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n height: 34px;\n padding: 0 10px;\n font-size: 13px;\n font-weight: 500;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.16);\n border-radius: 8px;\n cursor: pointer;\n touch-action: manipulation;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n transition: background 0.15s ease, border-color 0.15s ease;\n}\n.ri-xrButton:hover {\n background: rgba(40, 40, 40, 0.92);\n border-color: rgba(255, 255, 255, 0.3);\n}\n.ri-mobileHandoffBackdrop {\n position: absolute;\n z-index: 30;\n inset: 0;\n display: grid;\n place-items: center;\n padding: 20px;\n background: rgba(2, 6, 23, 0.68);\n backdrop-filter: blur(5px);\n -webkit-backdrop-filter: blur(5px);\n}\n.ri-mobileHandoffDialog {\n position: relative;\n display: flex;\n width: min(100%, 300px);\n flex-direction: column;\n align-items: center;\n padding: 16px;\n text-align: center;\n color: #e2e8f0;\n background: #0f172a;\n border: 1px solid rgba(148, 163, 184, 0.3);\n border-radius: 16px;\n box-shadow: 0 24px 80px rgba(0, 0, 0, 0.5);\n}\n.ri-mobileHandoffDialog > svg {\n color: #67e8f9;\n}\n.ri-mobileHandoffDialog h2 {\n margin: 12px 0 0;\n font-size: 18px;\n line-height: 1.25;\n color: #fff;\n}\n.ri-mobileHandoffDialog p {\n margin: 8px 0 16px;\n font-size: 13px;\n line-height: 1.5;\n color: #cbd5e1;\n}\n.ri-mobileHandoffQr {\n display: grid;\n place-items: center;\n padding: 10px;\n background: #fff;\n border-radius: 10px;\n}\n.ri-mobileHandoffDialog a {\n margin-top: 16px;\n font-size: 13px;\n font-weight: 600;\n color: #a5f3fc;\n text-decoration: underline;\n text-underline-offset: 3px;\n}\n.ri-mobileHandoffClose {\n position: absolute;\n top: 10px;\n right: 10px;\n display: grid;\n width: 30px;\n height: 30px;\n place-items: center;\n padding: 0;\n color: #cbd5e1;\n background: transparent;\n border: 0;\n border-radius: 6px;\n cursor: pointer;\n}\n.ri-mobileHandoffClose:hover {\n color: #fff;\n background: rgba(255, 255, 255, 0.1);\n}\n.ri-arHintOverlay {\n position: absolute;\n top: 18px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n width: min(calc(100% - 32px), 360px);\n pointer-events: none;\n}\n.ri-arHint {\n margin: 0 auto;\n width: fit-content;\n max-width: 100%;\n padding: 6px 12px;\n font-size: 11px;\n font-weight: 500;\n line-height: 1.2;\n text-align: center;\n color: rgba(255, 255, 255, 0.92);\n background: rgba(20, 20, 20, 0.58);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 999px;\n user-select: none;\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n}\n.ri-arOverlay {\n position: absolute;\n bottom: 24px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n width: min(calc(100% - 32px), 420px);\n pointer-events: none;\n}\n.ri-arControls {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n}\n.ri-arStatus,\n.ri-arActionButton {\n max-width: 100%;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.3;\n text-align: center;\n white-space: nowrap;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.16);\n border-radius: 19px;\n user-select: none;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n}\n.ri-arStatus {\n pointer-events: none;\n}\n.ri-arActionButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n cursor: pointer;\n pointer-events: auto;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n transition:\n background 0.15s ease,\n border-color 0.15s ease,\n color 0.15s ease,\n transform 0.15s ease;\n}\n.ri-arActionButton svg {\n flex-shrink: 0;\n}\n.ri-arActionButton:hover {\n background: rgba(40, 40, 40, 0.92);\n border-color: rgba(255, 255, 255, 0.3);\n}\n.ri-arActionButton:active {\n transform: scale(0.98);\n}\n.ri-arActionButton--primary {\n color: #06343c;\n background: #22d3ee;\n border-color: transparent;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n}\n.ri-arActionButton--primary:hover {\n background: #67e8f9;\n border-color: transparent;\n}\n.ri-arActionButton.ri-arActionButton--active,\n.ri-arActionButton[aria-pressed=true],\n.ri-arActionButton.ri-arActionButton--active:hover,\n.ri-arActionButton[aria-pressed=true]:hover,\n.ri-arActionButton.ri-arActionButton--active:active,\n.ri-arActionButton[aria-pressed=true]:active {\n color: #06343c;\n background: #22d3ee;\n border-color: transparent;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n}\n.ri-hiddenInput {\n display: none;\n}\n.ri-popover {\n position: absolute;\n z-index: 50;\n background: #ffffff;\n border: 1px solid #e5e7eb;\n border-radius: 16px;\n box-shadow: 0 24px 48px rgba(15, 23, 42, 0.18);\n color: #111827;\n}\n.ri-popover--inline {\n position: static;\n z-index: auto;\n width: 100%;\n border-radius: 10px;\n box-shadow: none;\n}\n.ri-popover--inline .react-colorful {\n width: 100%;\n}\n.ri-popover--inline {\n background: var(--ri-databinding-surface, rgba(255, 255, 255, 0.03));\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text, #f1f5f9);\n}\n.ri-popover--inline .ri-popoverHeader {\n border-bottom-color: var( --ri-databinding-border-strong, rgba(255, 255, 255, 0.08) );\n}\n.ri-popover--inline .ri-popoverLabel {\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-colorCode {\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-secondaryButton {\n background: var(--ri-databinding-surface-hover, rgba(255, 255, 255, 0.06));\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text, #f1f5f9);\n}\n.ri-popover--inline .ri-secondaryButton:hover {\n background: rgba(255, 255, 255, 0.12);\n}\n.ri-popover--inline .ri-previewFrame {\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n}\n.ri-popover--inline .ri-emptyState {\n border-color: var(--ri-databinding-border-hover, rgba(255, 255, 255, 0.15));\n color: var(--ri-databinding-text-subtle, #64748b);\n}\n.ri-popover--inline .ri-uploadButton {\n background: var(--ri-databinding-accent-gradient, #2563eb);\n color: #ffffff;\n}\n.ri-popover--inline .ri-uploadButton:hover {\n background: var(--ri-databinding-accent-gradient-active, #1d4ed8);\n}\n.ri-popover--inline .ri-removeButton {\n background: transparent;\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-removeButton:hover {\n background: var(--ri-databinding-surface-hover, rgba(255, 255, 255, 0.06));\n}\n.ri-popover--inline .ri-formatHint {\n color: var(--ri-databinding-text-subtle, #64748b);\n}\n.ri-popoverHeader {\n padding: 12px 16px;\n}\n.ri-popoverLabel {\n margin: 0 0 8px;\n font-size: 11px;\n font-weight: 700;\n color: #6b7280;\n text-transform: uppercase;\n letter-spacing: 0.16em;\n}\n.ri-inlineRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n margin-top: 8px;\n}\n.ri-colorCode {\n font-size: 12px;\n color: #9ca3af;\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Monaco,\n Consolas,\n monospace;\n text-transform: uppercase;\n}\n.ri-secondaryButton {\n border: 1px solid #e5e7eb;\n background: #f3f4f6;\n color: #374151;\n border-radius: 8px;\n padding: 6px 10px;\n font-size: 12px;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-secondaryButton:hover {\n background: #e5e7eb;\n}\n.ri-materialBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding-left: 12px;\n padding-right: 12px;\n padding-bottom: 12px;\n}\n.ri-previewFrame {\n position: relative;\n height: 112px;\n border-radius: 12px;\n overflow: hidden;\n border: 1px solid #e5e7eb;\n}\n.ri-previewImage {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n.ri-previewCaption {\n position: absolute;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n padding: 4px 8px;\n color: #ffffff;\n font-size: 10px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ri-emptyState {\n height: 112px;\n border-radius: 12px;\n border: 2px dashed #e5e7eb;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 4px;\n color: #d1d5db;\n text-align: center;\n}\n.ri-uploadButton {\n width: 100%;\n border: 0;\n border-radius: 10px;\n background: #111827;\n color: #ffffff;\n padding: 10px 12px;\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-uploadButton:hover {\n background: #374151;\n}\n.ri-removeButton {\n width: 100%;\n border-radius: 10px;\n border: 1px solid #e5e7eb;\n background: #ffffff;\n color: #6b7280;\n padding: 8px 12px;\n font-size: 12px;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-removeButton:hover {\n background: #f9fafb;\n}\n.ri-formatHint {\n margin: 0;\n text-align: center;\n font-size: 10px;\n color: #d1d5db;\n}\n.ri-buttonContainer {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 8px;\n position: absolute;\n bottom: 12px;\n left: 12px;\n right: 12px;\n z-index: 10;\n}\n.ri-tourControls {\n display: flex;\n gap: 6px;\n align-items: center;\n flex-shrink: 0;\n}\n.ri-rightControls {\n display: flex;\n gap: 6px;\n align-items: center;\n flex-shrink: 0;\n margin-left: auto;\n}\n.ri-rightControls:only-child {\n margin-left: auto;\n}\n.ri-navButtonSingle,\n.ri-tourButton,\n.ri-resetButton,\n.ri-downloadButton {\n height: 34px;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n color: rgba(255, 255, 255, 0.75);\n border-radius: 8px;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n cursor: pointer;\n transition:\n background 0.15s ease,\n color 0.15s ease,\n border-color 0.15s ease,\n transform 0.15s ease;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n}\n.ri-navButtonSingle:hover:not(:disabled),\n.ri-tourButton:hover:not(:disabled),\n.ri-resetButton:hover:not(:disabled) {\n background: rgba(40, 40, 40, 0.92);\n color: #fff;\n border-color: rgba(255, 255, 255, 0.25);\n}\n.ri-navButtonSingle:active:not(:disabled),\n.ri-tourButton:active:not(:disabled),\n.ri-resetButton:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-navButtonSingle:disabled,\n.ri-tourButton:disabled,\n.ri-resetButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.ri-navButtonSingle {\n padding: 0 16px;\n font-size: 13px;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 8px;\n white-space: nowrap;\n}\n.ri-tourButton {\n padding: 0 12px;\n font-size: 13px;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 4px;\n white-space: nowrap;\n}\n.ri-tourButton svg {\n width: 15px;\n height: 15px;\n flex-shrink: 0;\n}\n.ri-tourButton--stop {\n border-color: rgba(255, 68, 68, 0.4);\n color: #ff6b6b;\n}\n.ri-tourButton--stop:hover:not(:disabled) {\n background: rgba(255, 68, 68, 0.15);\n border-color: rgba(255, 68, 68, 0.6);\n color: #ff6b6b;\n}\n.ri-tourButton--stop:active:not(:disabled) {\n background: #ff4444;\n color: #fff;\n border-color: #ff4444;\n}\n.ri-tourButton--next {\n border-color: rgba(56, 219, 240, 0.3);\n color: #38dbf0;\n}\n.ri-tourButton--next:hover:not(:disabled) {\n background: rgba(56, 219, 240, 0.12);\n border-color: rgba(56, 219, 240, 0.5);\n color: #38dbf0;\n}\n.ri-tourButton--next:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-resetButton {\n width: 34px;\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.ri-resetButton svg {\n width: 15px;\n height: 15px;\n pointer-events: none;\n flex-shrink: 0;\n}\n.ri-resetButton:focus-visible {\n outline: 2px solid rgba(255, 255, 255, 0.5);\n outline-offset: 2px;\n}\n.ri-downloadWrapper {\n position: relative;\n display: inline-flex;\n flex-direction: column;\n align-items: stretch;\n}\n.ri-downloadButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 0 16px;\n font-size: 13px;\n font-weight: 500;\n white-space: nowrap;\n}\n.ri-downloadButton:hover:not(:disabled) {\n background: rgba(0, 0, 0, 0.72);\n border-color: rgba(255, 255, 255, 0.3);\n color: #fff;\n}\n.ri-downloadButton:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-downloadButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.ri-downloadButton__spinner {\n display: inline-block;\n width: 13px;\n height: 13px;\n border: 2px solid rgba(255, 255, 255, 0.3);\n border-top-color: #fff;\n border-radius: 50%;\n animation: ri-spin 0.7s linear infinite;\n flex-shrink: 0;\n}\n.ri-downloadMenu {\n position: absolute;\n bottom: calc(100% + 6px);\n right: 0;\n min-width: 168px;\n background: rgba(20, 20, 20, 0.92);\n border: 1px solid rgba(255, 255, 255, 0.14);\n border-radius: 10px;\n overflow: hidden;\n z-index: 100;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n animation: ri-menuReveal 0.12s ease;\n}\n@keyframes ri-menuReveal {\n from {\n opacity: 0;\n transform: translateY(4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n.ri-downloadMenuItem {\n display: flex;\n align-items: center;\n gap: 9px;\n width: 100%;\n padding: 10px 14px;\n font-size: 13px;\n font-weight: 400;\n color: #fff;\n background: transparent;\n border: none;\n cursor: pointer;\n text-align: left;\n transition: background 0.1s ease;\n}\n.ri-downloadMenuItem:hover:not(:disabled) {\n background: rgba(255, 255, 255, 0.1);\n}\n.ri-downloadMenuItem:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.ri-downloadMenuDivider {\n height: 1px;\n background: rgba(255, 255, 255, 0.1);\n margin: 0 10px;\n}\n@keyframes ri-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.ri-annotationMarker {\n position: relative;\n width: 36px;\n height: 36px;\n border-radius: 50%;\n background: rgba(20, 20, 20, 0.82);\n border: 2px solid rgba(255, 255, 255, 0.9);\n color: #fff;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n font-weight: 700;\n cursor: pointer;\n z-index: 20;\n user-select: none;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);\n pointer-events: auto;\n transition: opacity 0.3s ease, transform 0.3s ease;\n}\n.ri-annotationMarker:hover {\n transform: scale(1.08);\n box-shadow: 0 12px 28px rgba(15, 23, 42, 0.18);\n background: rgba(40, 40, 40, 0.92);\n}\n.ri-annotationPopup {\n position: absolute;\n width: min(270px, calc(100vw - 32px));\n background: rgba(20, 20, 20, 0.85);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 16px;\n padding: 16px;\n z-index: 30;\n display: flex;\n flex-direction: column;\n gap: 12px;\n box-shadow: 0 24px 48px rgba(15, 23, 42, 0.18);\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n}\n.ri-annotationInput,\n.ri-annotationTextarea {\n width: 100%;\n border: 1px solid rgba(255, 255, 255, 0.12);\n background: rgba(255, 255, 255, 0.05);\n color: #e5e7eb;\n border-radius: 10px;\n padding: 10px 12px;\n outline: none;\n font-family: inherit;\n transition: border-color 0.2s ease, box-shadow 0.2s ease;\n}\n.ri-annotationInput {\n font-size: 14px;\n font-weight: 500;\n}\n.ri-annotationTextarea {\n font-size: 13px;\n resize: vertical;\n min-height: 110px;\n}\n.ri-annotationInput:focus,\n.ri-annotationTextarea:focus {\n border-color: #38dbf0;\n box-shadow: 0 0 0 3px rgba(56, 219, 240, 0.12);\n}\n.ri-annotationActions {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.ri-annotationOkBtn {\n border: 0;\n border-radius: 8px;\n background: rgba(56, 219, 240, 0.8);\n color: #06343c;\n padding: 8px 18px;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n transition: background-color 0.2s ease, opacity 0.2s ease;\n}\n.ri-annotationOkBtn:hover:not(:disabled) {\n background: #38dbf0;\n}\n.ri-annotationOkBtn:disabled,\n.ri-annotationOkBtn.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.ri-annotationCancelBtn {\n border: 1px solid rgba(255, 255, 255, 0.12);\n background: transparent;\n color: #9ca3af;\n border-radius: 8px;\n padding: 8px 12px;\n font-size: 13px;\n cursor: pointer;\n transition: background-color 0.2s ease, border-color 0.2s ease;\n}\n.ri-annotationCancelBtn:hover {\n background: rgba(255, 255, 255, 0.05);\n border-color: rgba(255, 255, 255, 0.2);\n}\n.ri-annotationIcons {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 12px;\n color: #9ca3af;\n}\n.ri-annotationIcons svg {\n flex-shrink: 0;\n}\n.ri-viewAnnotationPopup {\n position: relative;\n background: rgba(20, 20, 20, 0.85);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 8px;\n padding: 12px 16px;\n min-width: 200px;\n max-width: min(280px, calc(100vw - 48px));\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);\n pointer-events: auto;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n animation: ri-viewAnnotationFadeIn 0.15s ease-out;\n margin-left: 24px;\n margin-top: -20px;\n isolation: isolate;\n z-index: 1000;\n}\n.ri-viewAnnotationHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n}\n.ri-viewAnnotationTitle {\n margin: 0;\n color: #e2e8f0;\n font-size: 14px;\n font-weight: 600;\n}\n.ri-viewAnnotationClose {\n background: transparent;\n border: none;\n color: #b7bdc6;\n cursor: pointer;\n font-size: 18px;\n padding: 0 4px;\n transition: color 0.2s;\n line-height: 1;\n}\n.ri-viewAnnotationClose:hover {\n color: #e2e8f0;\n}\n.ri-viewAnnotationDescription {\n margin: 0;\n color: #cbd5e1;\n font-size: 13px;\n line-height: 1.4;\n word-wrap: break-word;\n}\n@keyframes ri-viewAnnotationFadeIn {\n from {\n opacity: 0;\n transform: translateY(-6px) scale(0.96);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n.ri-annotationNavSingle {\n position: absolute;\n bottom: 120px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n pointer-events: none;\n}\n.ri-annotationNavSingle > * {\n pointer-events: auto;\n}\n.ri-navBadge {\n background: rgba(255, 255, 255, 0.1);\n padding: 2px 10px;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 400;\n color: rgba(255, 255, 255, 0.7);\n}\n.ri-annotationTitleHint {\n background: rgba(0, 0, 0, 0.7);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n color: white;\n padding: 8px 20px;\n border-radius: 8px;\n font-size: 13px;\n border: 1px solid rgba(255, 255, 255, 0.08);\n text-align: center;\n max-width: 350px;\n animation: slideUp 0.3s ease;\n}\n.ri-annotationHintTitle {\n font-weight: 600;\n color: #e5e7eb;\n}\n.ri-annotationHintDesc {\n font-size: 12px;\n opacity: 0.6;\n margin-top: 2px;\n color: #d1d5db;\n}\n@keyframes slideUp {\n from {\n opacity: 0;\n transform: translateY(10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n@media (max-width: 768px) {\n .ri-xrTools {\n gap: 6px;\n }\n .ri-xrButton {\n height: 34px;\n gap: 6px;\n padding: 0 8px;\n font-size: 11px;\n }\n .ri-xrButton svg {\n width: 14px;\n height: 14px;\n }\n .ri-xrButtonPrefix {\n display: none;\n }\n .ri-annotationNavSingle {\n bottom: 100px;\n }\n .ri-navButtonSingle {\n padding: 8px 20px;\n font-size: 13px;\n min-width: 110px;\n }\n .ri-annotationTitleHint {\n padding: 6px 14px;\n font-size: 12px;\n max-width: 250px;\n }\n .ri-viewAnnotationPopup {\n min-width: 160px;\n max-width: 220px;\n padding: 10px 12px;\n }\n .ri-viewAnnotationTitle {\n font-size: 13px;\n }\n .ri-viewAnnotationDescription {\n font-size: 12px;\n }\n .ri-buttonContainer {\n flex-wrap: wrap;\n gap: 6px;\n }\n .ri-tourControls {\n flex-wrap: wrap;\n }\n .ri-rightControls {\n flex-wrap: wrap;\n }\n}\n@media (max-width: 480px) {\n .ri-xrTools {\n gap: 5px;\n }\n .ri-xrButton {\n height: 34px;\n gap: 5px;\n font-size: 10px;\n }\n .ri-xrButton svg {\n width: 13px;\n height: 13px;\n }\n .ri-annotationNavSingle {\n bottom: 80px;\n }\n .ri-navButtonSingle {\n padding: 6px 16px;\n font-size: 12px;\n min-width: 90px;\n gap: 6px;\n }\n .ri-annotationTitleHint {\n padding: 4px 10px;\n font-size: 11px;\n max-width: 200px;\n }\n .ri-navBadge {\n font-size: 10px;\n padding: 1px 6px;\n }\n .ri-tourButton {\n padding: 0 8px;\n font-size: 12px;\n }\n .ri-tourButton span {\n display: none;\n }\n .ri-downloadButton {\n padding: 0 10px;\n font-size: 12px;\n }\n .ri-downloadButton span {\n display: none;\n }\n}\n.ri-canvasArea .bb-loadingOverlay {\n position: absolute;\n inset: 0;\n z-index: 12;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 16px;\n padding: 24px;\n text-align: center;\n border-radius: 14px;\n background: rgba(15, 23, 42, 0.46);\n backdrop-filter: blur(1px);\n}\n.ri-canvasArea .bb-loadingSpinner {\n width: 40px;\n height: 40px;\n border-radius: 999px;\n border: 3px solid rgba(226, 232, 240, 0.24);\n border-top-color: #93c5fd;\n animation: bb-spin 0.8s linear infinite;\n}\n.ri-canvasArea .bb-loadingMessage {\n max-width: 320px;\n color: #f7f8f9;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.5;\n}\n@keyframes bb-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.ri-largeModelBanner {\n position: absolute;\n top: 12px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 12px 7px 10px;\n border-radius: 8px;\n background: rgba(30, 20, 0, 0.88);\n border: 1px solid rgba(251, 191, 36, 0.35);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n color: rgb(251, 191, 36);\n font-size: 12px;\n font-weight: 500;\n line-height: 1.4;\n pointer-events: auto;\n max-width: calc(100% - 32px);\n white-space: normal;\n text-align: center;\n}\n.ri-largeModelBanner--offset {\n top: 64px;\n}\n.ri-largeModelBanner__icon {\n flex-shrink: 0;\n}\n.ri-largeModelBanner__dismiss {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 4px;\n padding: 2px;\n background: none;\n border: none;\n border-radius: 4px;\n color: rgba(251, 191, 36, 0.7);\n cursor: pointer;\n transition: color 0.15s ease, background 0.15s ease;\n}\n.ri-largeModelBanner__dismiss:hover {\n color: rgb(251, 191, 36);\n background: rgba(251, 191, 36, 0.12);\n}\n');
|
|
203
|
+
styleInject('.ri-viewer--license-checking,\n.ri-viewer--license-invalid {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n background-color: #f9f9f9;\n}\n.ri-viewer {\n --ri-font-family:\n Inter,\n "Segoe UI",\n Helvetica,\n Arial,\n sans-serif;\n --ri-viewer-bg: #1c1c1d;\n --ri-viewer-text: #111827;\n --ri-panel-bg: #ffffff;\n --ri-panel-border: #e5e7eb;\n --ri-panel-title: #111827;\n --ri-panel-muted-text: #4b5563;\n --ri-panel-subtle-text: #9ca3af;\n --ri-panel-surface: #f9fafb;\n --ri-panel-surface-hover: #f3f4f6;\n --ri-panel-surface-border: #e5e7eb;\n --ri-panel-button-text: #1f2937;\n --ri-panel-accent: #2563eb;\n --ri-panel-accent-contrast: #ffffff;\n --ri-license-overlay-bg: rgba(0, 0, 0, 0.04);\n --ri-license-overlay-border: rgba(0, 0, 0, 0.08);\n --ri-license-overlay-text: #888;\n --ri-license-overlay-error-bg: rgba(192, 57, 43, 0.06);\n --ri-license-overlay-error-border: rgba(192, 57, 43, 0.2);\n --ri-license-overlay-error-text: #c0392b;\n --ri-sidepanel-font-family:\n Inter,\n "Segoe UI",\n Helvetica,\n Arial,\n sans-serif;\n --ri-sidepanel-bg: var(--ri-databinding-bg, #111318);\n --ri-sidepanel-open-btn-bg: rgba(20, 20, 20, 0.82);\n --ri-sidepanel-open-btn-bg-hover: #27282b7e;\n --ri-sidepanel-border: rgba(255, 255, 255, 0.06);\n --ri-sidepanel-divider: rgba(255, 255, 255, 0.04);\n --ri-sidepanel-border-strong: rgba(255, 255, 255, 0.08);\n --ri-sidepanel-border-active: rgba(255, 255, 255, 0.18);\n --ri-sidepanel-shadow-left: 4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-sidepanel-shadow-right: -4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-sidepanel-text: var(--ri-databinding-text, #f1f5f9);\n --ri-sidepanel-text-emphasis: var(--ri-databinding-text-hover, #cbd5e1);\n --ri-sidepanel-text-muted: var(--ri-databinding-text-muted, #94a3b8);\n --ri-sidepanel-text-subtle: var(--ri-databinding-text-subtle, #64748b);\n --ri-sidepanel-placeholder: var(--ri-databinding-text-faint, #475569);\n --ri-sidepanel-surface: rgba(255, 255, 255, 0.05);\n --ri-sidepanel-surface-hover: rgba(255, 255, 255, 0.1);\n --ri-sidepanel-accent: #3b82f6;\n --ri-sidepanel-accent-soft: rgba(59, 130, 246, 0.12);\n --ri-databinding-bg: #111318;\n --ri-databinding-border: rgba(255, 255, 255, 0.06);\n --ri-databinding-border-strong: rgba(255, 255, 255, 0.08);\n --ri-databinding-border-hover: rgba(255, 255, 255, 0.15);\n --ri-databinding-shadow: 4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-databinding-font-family:\n "Inter",\n "Segoe UI",\n sans-serif;\n --ri-databinding-mono-font-family:\n "JetBrains Mono",\n "Fira Code",\n "Menlo",\n monospace;\n --ri-databinding-text: #f1f5f9;\n --ri-databinding-text-muted: #94a3b8;\n --ri-databinding-text-subtle: #64748b;\n --ri-databinding-text-faint: #475569;\n --ri-databinding-text-hover: #cbd5e1;\n --ri-databinding-surface: rgba(255, 255, 255, 0.03);\n --ri-databinding-surface-hover: rgba(255, 255, 255, 0.06);\n --ri-databinding-accent: #93c5fd;\n --ri-databinding-accent-glow: rgba(147, 197, 253, 0.8);\n --ri-databinding-accent-border: rgba(96, 165, 250, 0.55);\n --ri-databinding-accent-gradient:\n linear-gradient(\n \n 135deg,\n #1e3a8a 0%,\n #1d4ed8 60%,\n #2563eb 100% );\n --ri-databinding-accent-gradient-active:\n linear-gradient(\n \n 135deg,\n #1e3a8a 0%,\n #1e40af 100% );\n display: flex;\n flex-direction: row;\n width: 100%;\n height: 100%;\n min-height: 0;\n overflow: hidden;\n background: var(--ri-viewer-bg);\n position: relative;\n color: var(--ri-viewer-text);\n font-family: var(--ri-font-family);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n text-rendering: optimizeLegibility;\n font-synthesis: none;\n}\n.ri-viewer * {\n box-sizing: border-box;\n}\n.ri-viewerLeft {\n display: flex;\n flex: 1;\n min-height: 0;\n min-width: 0;\n position: relative;\n}\n.ri-viewerRight {\n display: flex;\n flex-shrink: 0;\n min-width: 0;\n}\n.ri-panel {\n width: 280px;\n min-height: 0;\n background: var(--ri-panel-bg);\n border-right: 1px solid var(--ri-panel-border);\n display: flex;\n flex-direction: column;\n padding: 20px;\n overflow-y: auto;\n flex-shrink: 0;\n transition: margin-left 0.3s ease, transform 0.3s ease;\n}\n.ri-panel--open {\n transform: translateX(0);\n margin-left: 0;\n}\n.ri-panel--closed {\n transform: translateX(-100%);\n margin-left: -280px;\n}\n.ri-panelHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 12px;\n}\n.ri-panelTitle {\n margin: 0;\n font-size: 15px;\n font-weight: 700;\n color: var(--ri-panel-title);\n line-height: 1.4;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.ri-closeButton {\n border: 0;\n background: transparent;\n color: var(--ri-panel-subtle-text);\n font-size: 22px;\n line-height: 1;\n padding: 0;\n cursor: pointer;\n flex-shrink: 0;\n transition: color 0.2s ease;\n}\n.ri-closeButton:hover {\n color: var(--ri-panel-muted-text);\n}\n.ri-json {\n margin: 0 0 16px;\n padding: 12px;\n background: var(--ri-panel-surface);\n border: 1px solid var(--ri-panel-surface-border);\n border-radius: 12px;\n font-size: 12px;\n line-height: 1.55;\n color: var(--ri-panel-button-text);\n overflow-x: auto;\n white-space: pre-wrap;\n word-break: break-word;\n}\n.ri-divider {\n border-top: 1px solid var(--ri-panel-border);\n margin-bottom: 12px;\n}\n.ri-sectionLabel {\n margin-bottom: 8px;\n font-size: 11px;\n font-weight: 700;\n color: var(--ri-panel-subtle-text);\n text-transform: uppercase;\n letter-spacing: 0.16em;\n}\n.ri-actionList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.ri-actionButton {\n width: 100%;\n border-radius: 8px;\n border: 1px solid var(--ri-panel-surface-border);\n background: var(--ri-panel-surface);\n color: var(--ri-panel-button-text);\n padding: 10px 12px;\n text-align: left;\n font-size: 14px;\n line-height: 1.4;\n cursor: pointer;\n transition:\n background-color 0.2s ease,\n border-color 0.2s ease,\n color 0.2s ease;\n}\n.ri-actionButton:hover {\n background: var(--ri-panel-surface-hover);\n border-color: var(--ri-panel-border);\n}\n.ri-actionButton--active {\n background: var(--ri-panel-accent);\n border-color: var(--ri-panel-accent);\n color: var(--ri-panel-accent-contrast);\n}\n.ri-actionButton--active:hover {\n background: var(--ri-panel-accent);\n border-color: var(--ri-panel-accent);\n}\n.ri-canvasArea {\n flex: 1 1 auto;\n position: relative;\n min-width: 0;\n}\n.ri-canvasArea--measuring {\n cursor: crosshair;\n}\n.ri-canvasArea canvas {\n opacity: 1;\n transition: opacity 0.18s ease;\n}\n.ri-canvasArea--positioning canvas {\n opacity: 0;\n transition: none;\n}\n.ri-bindingsMismatchBanner {\n position: absolute;\n top: 12px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 12px 7px 10px;\n border-radius: 8px;\n background: rgba(30, 20, 0, 0.88);\n border: 1px solid rgba(251, 191, 36, 0.35);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n color: rgb(251, 191, 36);\n font-size: 12px;\n font-weight: 500;\n line-height: 1.4;\n white-space: nowrap;\n pointer-events: auto;\n max-width: calc(100% - 32px);\n white-space: normal;\n text-align: center;\n}\n.ri-bindingsMismatchBanner__icon {\n flex-shrink: 0;\n}\n.ri-bindingsMismatchBanner__dismiss {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 4px;\n padding: 2px;\n background: none;\n border: none;\n border-radius: 4px;\n color: rgba(251, 191, 36, 0.7);\n cursor: pointer;\n transition: color 0.15s ease, background 0.15s ease;\n}\n.ri-bindingsMismatchBanner__dismiss:hover {\n color: rgb(251, 191, 36);\n background: rgba(251, 191, 36, 0.12);\n}\n.ri-measureTools {\n position: absolute;\n top: 16px;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-measureButton {\n width: 34px;\n height: 34px;\n padding: 0;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n color: rgba(255, 255, 255, 0.75);\n border-radius: 8px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n touch-action: manipulation;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n transition:\n background 0.15s ease,\n color 0.15s ease,\n border-color 0.15s ease;\n}\n.ri-measureButton:hover {\n background: rgba(40, 40, 40, 0.92);\n color: #fff;\n border-color: rgba(255, 255, 255, 0.25);\n}\n.ri-measureButton:active {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-measureButton--active {\n background: #22d3ee;\n border-color: #22d3ee;\n color: #06343c;\n}\n.ri-measureButton--active:hover {\n background: #38dbf0;\n color: #06343c;\n}\n.ri-measureHint {\n padding: 6px 10px;\n border-radius: 8px;\n font-size: 12px;\n line-height: 1;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n white-space: nowrap;\n}\n.ri-explodeTools {\n position: absolute;\n top: 16px;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-explodeSlider {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 34px;\n padding: 0 12px;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 8px;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n}\n.ri-explodeSlider input[type=range] {\n width: 120px;\n accent-color: #22d3ee;\n cursor: pointer;\n}\n.ri-cinematicTools {\n position: absolute;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-measureDot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: #22d3ee;\n border: 2px solid #fff;\n box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.4);\n pointer-events: none;\n}\n.ri-measureLabel {\n padding: 3px 7px;\n border-radius: 6px;\n font-size: 12px;\n font-weight: 600;\n line-height: 1;\n white-space: nowrap;\n color: #06343c;\n background: #22d3ee;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n pointer-events: none;\n user-select: none;\n}\n.ri-xrTools {\n position: absolute;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-xrButton {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n height: 34px;\n padding: 0 10px;\n font-size: 13px;\n font-weight: 500;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.16);\n border-radius: 8px;\n cursor: pointer;\n touch-action: manipulation;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n transition: background 0.15s ease, border-color 0.15s ease;\n}\n.ri-xrButton:hover {\n background: rgba(40, 40, 40, 0.92);\n border-color: rgba(255, 255, 255, 0.3);\n}\n.ri-mobileHandoffBackdrop {\n position: absolute;\n z-index: 30;\n inset: 0;\n display: grid;\n place-items: center;\n padding: 20px;\n background: rgba(2, 6, 23, 0.68);\n backdrop-filter: blur(5px);\n -webkit-backdrop-filter: blur(5px);\n}\n.ri-mobileHandoffDialog {\n position: relative;\n display: flex;\n width: min(100%, 300px);\n flex-direction: column;\n align-items: center;\n padding: 16px;\n text-align: center;\n color: #e2e8f0;\n background: #0f172a;\n border: 1px solid rgba(148, 163, 184, 0.3);\n border-radius: 16px;\n box-shadow: 0 24px 80px rgba(0, 0, 0, 0.5);\n}\n.ri-mobileHandoffDialog > svg {\n color: #67e8f9;\n}\n.ri-mobileHandoffDialog h2 {\n margin: 12px 0 0;\n font-size: 18px;\n line-height: 1.25;\n color: #fff;\n}\n.ri-mobileHandoffDialog p {\n margin: 8px 0 16px;\n font-size: 13px;\n line-height: 1.5;\n color: #cbd5e1;\n}\n.ri-mobileHandoffQr {\n display: grid;\n place-items: center;\n padding: 10px;\n background: #fff;\n border-radius: 10px;\n}\n.ri-mobileHandoffDialog a {\n margin-top: 16px;\n font-size: 13px;\n font-weight: 600;\n color: #a5f3fc;\n text-decoration: underline;\n text-underline-offset: 3px;\n}\n.ri-mobileHandoffClose {\n position: absolute;\n top: 10px;\n right: 10px;\n display: grid;\n width: 30px;\n height: 30px;\n place-items: center;\n padding: 0;\n color: #cbd5e1;\n background: transparent;\n border: 0;\n border-radius: 6px;\n cursor: pointer;\n}\n.ri-mobileHandoffClose:hover {\n color: #fff;\n background: rgba(255, 255, 255, 0.1);\n}\n.ri-arHintOverlay {\n position: absolute;\n top: 18px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n width: min(calc(100% - 32px), 360px);\n pointer-events: none;\n}\n.ri-arHint {\n margin: 0 auto;\n width: fit-content;\n max-width: 100%;\n padding: 6px 12px;\n font-size: 11px;\n font-weight: 500;\n line-height: 1.2;\n text-align: center;\n color: rgba(255, 255, 255, 0.92);\n background: rgba(20, 20, 20, 0.58);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 999px;\n user-select: none;\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n}\n.ri-arOverlay {\n position: absolute;\n bottom: 24px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n width: min(calc(100% - 32px), 420px);\n pointer-events: none;\n}\n.ri-arControls {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n}\n.ri-arStatus,\n.ri-arActionButton {\n max-width: 100%;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.3;\n text-align: center;\n white-space: nowrap;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.16);\n border-radius: 19px;\n user-select: none;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n}\n.ri-arStatus {\n pointer-events: none;\n}\n.ri-arActionButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n cursor: pointer;\n pointer-events: auto;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n transition:\n background 0.15s ease,\n border-color 0.15s ease,\n color 0.15s ease,\n transform 0.15s ease;\n}\n.ri-arActionButton svg {\n flex-shrink: 0;\n}\n.ri-arActionButton:hover {\n background: rgba(40, 40, 40, 0.92);\n border-color: rgba(255, 255, 255, 0.3);\n}\n.ri-arActionButton:active {\n transform: scale(0.98);\n}\n.ri-arActionButton--primary {\n color: #06343c;\n background: #22d3ee;\n border-color: transparent;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n}\n.ri-arActionButton--primary:hover {\n background: #67e8f9;\n border-color: transparent;\n}\n.ri-arActionButton.ri-arActionButton--active,\n.ri-arActionButton[aria-pressed=true],\n.ri-arActionButton.ri-arActionButton--active:hover,\n.ri-arActionButton[aria-pressed=true]:hover,\n.ri-arActionButton.ri-arActionButton--active:active,\n.ri-arActionButton[aria-pressed=true]:active {\n color: #06343c;\n background: #22d3ee;\n border-color: transparent;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n}\n.ri-hiddenInput {\n display: none;\n}\n.ri-popover {\n position: absolute;\n z-index: 50;\n background: #ffffff;\n border: 1px solid #e5e7eb;\n border-radius: 16px;\n box-shadow: 0 24px 48px rgba(15, 23, 42, 0.18);\n color: #111827;\n}\n.ri-popover--inline {\n position: static;\n z-index: auto;\n width: 100%;\n border-radius: 10px;\n box-shadow: none;\n}\n.ri-popover--inline .react-colorful {\n width: 100%;\n}\n.ri-popover--inline {\n background: var(--ri-databinding-surface, rgba(255, 255, 255, 0.03));\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text, #f1f5f9);\n}\n.ri-popover--inline .ri-popoverHeader {\n border-bottom-color: var( --ri-databinding-border-strong, rgba(255, 255, 255, 0.08) );\n}\n.ri-popover--inline .ri-popoverLabel {\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-colorCode {\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-secondaryButton {\n background: var(--ri-databinding-surface-hover, rgba(255, 255, 255, 0.06));\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text, #f1f5f9);\n}\n.ri-popover--inline .ri-secondaryButton:hover {\n background: rgba(255, 255, 255, 0.12);\n}\n.ri-popover--inline .ri-previewFrame {\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n}\n.ri-popover--inline .ri-emptyState {\n border-color: var(--ri-databinding-border-hover, rgba(255, 255, 255, 0.15));\n color: var(--ri-databinding-text-subtle, #64748b);\n}\n.ri-popover--inline .ri-uploadButton {\n background: var(--ri-databinding-accent-gradient, #2563eb);\n color: #ffffff;\n}\n.ri-popover--inline .ri-uploadButton:hover {\n background: var(--ri-databinding-accent-gradient-active, #1d4ed8);\n}\n.ri-popover--inline .ri-removeButton {\n background: transparent;\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-removeButton:hover {\n background: var(--ri-databinding-surface-hover, rgba(255, 255, 255, 0.06));\n}\n.ri-popover--inline .ri-formatHint {\n color: var(--ri-databinding-text-subtle, #64748b);\n}\n.ri-popoverHeader {\n padding: 12px 16px;\n}\n.ri-popoverLabel {\n margin: 0 0 8px;\n font-size: 11px;\n font-weight: 700;\n color: #6b7280;\n text-transform: uppercase;\n letter-spacing: 0.16em;\n}\n.ri-inlineRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n margin-top: 8px;\n}\n.ri-colorCode {\n font-size: 12px;\n color: #9ca3af;\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Monaco,\n Consolas,\n monospace;\n text-transform: uppercase;\n}\n.ri-secondaryButton {\n border: 1px solid #e5e7eb;\n background: #f3f4f6;\n color: #374151;\n border-radius: 8px;\n padding: 6px 10px;\n font-size: 12px;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-secondaryButton:hover {\n background: #e5e7eb;\n}\n.ri-materialBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding-left: 12px;\n padding-right: 12px;\n padding-bottom: 12px;\n}\n.ri-previewFrame {\n position: relative;\n height: 112px;\n border-radius: 12px;\n overflow: hidden;\n border: 1px solid #e5e7eb;\n}\n.ri-previewImage {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n.ri-previewCaption {\n position: absolute;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n padding: 4px 8px;\n color: #ffffff;\n font-size: 10px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ri-emptyState {\n height: 112px;\n border-radius: 12px;\n border: 2px dashed #e5e7eb;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 4px;\n color: #d1d5db;\n text-align: center;\n}\n.ri-uploadButton {\n width: 100%;\n border: 0;\n border-radius: 10px;\n background: #111827;\n color: #ffffff;\n padding: 10px 12px;\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-uploadButton:hover {\n background: #374151;\n}\n.ri-removeButton {\n width: 100%;\n border-radius: 10px;\n border: 1px solid #e5e7eb;\n background: #ffffff;\n color: #6b7280;\n padding: 8px 12px;\n font-size: 12px;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-removeButton:hover {\n background: #f9fafb;\n}\n.ri-formatHint {\n margin: 0;\n text-align: center;\n font-size: 10px;\n color: #d1d5db;\n}\n.ri-buttonContainer {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 8px;\n position: absolute;\n bottom: 12px;\n left: 12px;\n right: 12px;\n z-index: 10;\n}\n.ri-tourControls {\n display: flex;\n gap: 6px;\n align-items: center;\n flex-shrink: 0;\n}\n.ri-rightControls {\n display: flex;\n gap: 6px;\n align-items: center;\n flex-shrink: 0;\n margin-left: auto;\n}\n.ri-rightControls:only-child {\n margin-left: auto;\n}\n.ri-navButtonSingle,\n.ri-tourButton,\n.ri-resetButton,\n.ri-downloadButton {\n height: 34px;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n color: rgba(255, 255, 255, 0.75);\n border-radius: 8px;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n cursor: pointer;\n transition:\n background 0.15s ease,\n color 0.15s ease,\n border-color 0.15s ease,\n transform 0.15s ease;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n}\n.ri-navButtonSingle:hover:not(:disabled),\n.ri-tourButton:hover:not(:disabled),\n.ri-resetButton:hover:not(:disabled) {\n background: rgba(40, 40, 40, 0.92);\n color: #fff;\n border-color: rgba(255, 255, 255, 0.25);\n}\n.ri-navButtonSingle:active:not(:disabled),\n.ri-tourButton:active:not(:disabled),\n.ri-resetButton:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-navButtonSingle:disabled,\n.ri-tourButton:disabled,\n.ri-resetButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.ri-navButtonSingle {\n padding: 0 16px;\n font-size: 13px;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 8px;\n white-space: nowrap;\n}\n.ri-tourButton {\n padding: 0 12px;\n font-size: 13px;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 4px;\n white-space: nowrap;\n}\n.ri-tourButton svg {\n width: 15px;\n height: 15px;\n flex-shrink: 0;\n}\n.ri-tourButton--stop {\n border-color: rgba(255, 68, 68, 0.4);\n color: #ff6b6b;\n}\n.ri-tourButton--stop:hover:not(:disabled) {\n background: rgba(255, 68, 68, 0.15);\n border-color: rgba(255, 68, 68, 0.6);\n color: #ff6b6b;\n}\n.ri-tourButton--stop:active:not(:disabled) {\n background: #ff4444;\n color: #fff;\n border-color: #ff4444;\n}\n.ri-tourButton--next {\n border-color: rgba(56, 219, 240, 0.3);\n color: #38dbf0;\n}\n.ri-tourButton--next:hover:not(:disabled) {\n background: rgba(56, 219, 240, 0.12);\n border-color: rgba(56, 219, 240, 0.5);\n color: #38dbf0;\n}\n.ri-tourButton--next:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-resetButton {\n width: 34px;\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.ri-resetButton svg {\n width: 15px;\n height: 15px;\n pointer-events: none;\n flex-shrink: 0;\n}\n.ri-resetButton:focus-visible {\n outline: 2px solid rgba(255, 255, 255, 0.5);\n outline-offset: 2px;\n}\n.ri-downloadWrapper {\n position: relative;\n display: inline-flex;\n flex-direction: column;\n align-items: stretch;\n}\n.ri-downloadButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 0 16px;\n font-size: 13px;\n font-weight: 500;\n white-space: nowrap;\n}\n.ri-downloadButton:hover:not(:disabled) {\n background: rgba(0, 0, 0, 0.72);\n border-color: rgba(255, 255, 255, 0.3);\n color: #fff;\n}\n.ri-downloadButton:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-downloadButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.ri-downloadButton__spinner {\n display: inline-block;\n width: 13px;\n height: 13px;\n border: 2px solid rgba(255, 255, 255, 0.3);\n border-top-color: #fff;\n border-radius: 50%;\n animation: ri-spin 0.7s linear infinite;\n flex-shrink: 0;\n}\n.ri-downloadMenu {\n position: absolute;\n bottom: calc(100% + 6px);\n right: 0;\n min-width: 168px;\n background: rgba(20, 20, 20, 0.92);\n border: 1px solid rgba(255, 255, 255, 0.14);\n border-radius: 10px;\n overflow: hidden;\n z-index: 100;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n animation: ri-menuReveal 0.12s ease;\n}\n@keyframes ri-menuReveal {\n from {\n opacity: 0;\n transform: translateY(4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n.ri-downloadMenuItem {\n display: flex;\n align-items: center;\n gap: 9px;\n width: 100%;\n padding: 10px 14px;\n font-size: 13px;\n font-weight: 400;\n color: #fff;\n background: transparent;\n border: none;\n cursor: pointer;\n text-align: left;\n transition: background 0.1s ease;\n text-wrap: nowrap;\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.ri-downloadMenuNotice {\n max-width: 220px;\n padding: 8px 14px 11px;\n color: #fca5a5;\n font-size: 11px;\n line-height: 1.4;\n}\n@keyframes ri-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.ri-annotationMarker {\n position: relative;\n width: 36px;\n height: 36px;\n border-radius: 50%;\n background: rgba(20, 20, 20, 0.82);\n border: 2px solid rgba(255, 255, 255, 0.9);\n color: #fff;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n font-weight: 700;\n cursor: pointer;\n z-index: 20;\n user-select: none;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);\n pointer-events: auto;\n transition: opacity 0.3s ease, transform 0.3s ease;\n}\n.ri-annotationMarker:hover {\n transform: scale(1.08);\n box-shadow: 0 12px 28px rgba(15, 23, 42, 0.18);\n background: rgba(40, 40, 40, 0.92);\n}\n.ri-annotationPopup {\n position: absolute;\n width: min(270px, calc(100vw - 32px));\n background: rgba(20, 20, 20, 0.85);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 16px;\n padding: 16px;\n z-index: 30;\n display: flex;\n flex-direction: column;\n gap: 12px;\n box-shadow: 0 24px 48px rgba(15, 23, 42, 0.18);\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n}\n.ri-annotationInput,\n.ri-annotationTextarea {\n width: 100%;\n border: 1px solid rgba(255, 255, 255, 0.12);\n background: rgba(255, 255, 255, 0.05);\n color: #e5e7eb;\n border-radius: 10px;\n padding: 10px 12px;\n outline: none;\n font-family: inherit;\n transition: border-color 0.2s ease, box-shadow 0.2s ease;\n}\n.ri-annotationInput {\n font-size: 14px;\n font-weight: 500;\n}\n.ri-annotationTextarea {\n font-size: 13px;\n resize: vertical;\n min-height: 110px;\n}\n.ri-annotationInput:focus,\n.ri-annotationTextarea:focus {\n border-color: #38dbf0;\n box-shadow: 0 0 0 3px rgba(56, 219, 240, 0.12);\n}\n.ri-annotationActions {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.ri-annotationOkBtn {\n border: 0;\n border-radius: 8px;\n background: rgba(56, 219, 240, 0.8);\n color: #06343c;\n padding: 8px 18px;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n transition: background-color 0.2s ease, opacity 0.2s ease;\n}\n.ri-annotationOkBtn:hover:not(:disabled) {\n background: #38dbf0;\n}\n.ri-annotationOkBtn:disabled,\n.ri-annotationOkBtn.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.ri-annotationCancelBtn {\n border: 1px solid rgba(255, 255, 255, 0.12);\n background: transparent;\n color: #9ca3af;\n border-radius: 8px;\n padding: 8px 12px;\n font-size: 13px;\n cursor: pointer;\n transition: background-color 0.2s ease, border-color 0.2s ease;\n}\n.ri-annotationCancelBtn:hover {\n background: rgba(255, 255, 255, 0.05);\n border-color: rgba(255, 255, 255, 0.2);\n}\n.ri-annotationIcons {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 12px;\n color: #9ca3af;\n}\n.ri-annotationIcons svg {\n flex-shrink: 0;\n}\n.ri-viewAnnotationPopup {\n position: relative;\n background: rgba(20, 20, 20, 0.85);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 8px;\n padding: 12px 16px;\n min-width: 200px;\n max-width: min(280px, calc(100vw - 48px));\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);\n pointer-events: auto;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n animation: ri-viewAnnotationFadeIn 0.15s ease-out;\n margin-left: 24px;\n margin-top: -20px;\n isolation: isolate;\n z-index: 1000;\n}\n.ri-viewAnnotationHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n}\n.ri-viewAnnotationTitle {\n margin: 0;\n color: #e2e8f0;\n font-size: 14px;\n font-weight: 600;\n}\n.ri-viewAnnotationClose {\n background: transparent;\n border: none;\n color: #b7bdc6;\n cursor: pointer;\n font-size: 18px;\n padding: 0 4px;\n transition: color 0.2s;\n line-height: 1;\n}\n.ri-viewAnnotationClose:hover {\n color: #e2e8f0;\n}\n.ri-viewAnnotationDescription {\n margin: 0;\n color: #cbd5e1;\n font-size: 13px;\n line-height: 1.4;\n word-wrap: break-word;\n}\n@keyframes ri-viewAnnotationFadeIn {\n from {\n opacity: 0;\n transform: translateY(-6px) scale(0.96);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n.ri-annotationNavSingle {\n position: absolute;\n bottom: 120px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n pointer-events: none;\n}\n.ri-annotationNavSingle > * {\n pointer-events: auto;\n}\n.ri-navBadge {\n background: rgba(255, 255, 255, 0.1);\n padding: 2px 10px;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 400;\n color: rgba(255, 255, 255, 0.7);\n}\n.ri-annotationTitleHint {\n background: rgba(0, 0, 0, 0.7);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n color: white;\n padding: 8px 20px;\n border-radius: 8px;\n font-size: 13px;\n border: 1px solid rgba(255, 255, 255, 0.08);\n text-align: center;\n max-width: 350px;\n animation: slideUp 0.3s ease;\n}\n.ri-annotationHintTitle {\n font-weight: 600;\n color: #e5e7eb;\n}\n.ri-annotationHintDesc {\n font-size: 12px;\n opacity: 0.6;\n margin-top: 2px;\n color: #d1d5db;\n}\n@keyframes slideUp {\n from {\n opacity: 0;\n transform: translateY(10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n.ri-canvasArea .bb-loadingOverlay {\n position: absolute;\n inset: 0;\n z-index: 12;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 16px;\n padding: 24px;\n text-align: center;\n border-radius: 14px;\n background: rgba(15, 23, 42, 0.46);\n backdrop-filter: blur(1px);\n}\n.ri-canvasArea .bb-loadingSpinner {\n width: 40px;\n height: 40px;\n border-radius: 999px;\n border: 3px solid rgba(226, 232, 240, 0.24);\n border-top-color: #93c5fd;\n animation: bb-spin 0.8s linear infinite;\n}\n.ri-canvasArea .bb-loadingMessage {\n max-width: 320px;\n color: #f7f8f9;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.5;\n}\n@keyframes bb-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.ri-largeModelBanner {\n position: absolute;\n top: 12px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 12px 7px 10px;\n border-radius: 8px;\n background: rgba(30, 20, 0, 0.88);\n border: 1px solid rgba(251, 191, 36, 0.35);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n color: rgb(251, 191, 36);\n font-size: 12px;\n font-weight: 500;\n line-height: 1.4;\n pointer-events: auto;\n max-width: calc(100% - 32px);\n white-space: normal;\n text-align: center;\n}\n.ri-largeModelBanner--offset {\n top: 64px;\n}\n.ri-largeModelBanner__icon {\n flex-shrink: 0;\n}\n.ri-largeModelBanner__dismiss {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 4px;\n padding: 2px;\n background: none;\n border: none;\n border-radius: 4px;\n color: rgba(251, 191, 36, 0.7);\n cursor: pointer;\n transition: color 0.15s ease, background 0.15s ease;\n}\n.ri-largeModelBanner__dismiss:hover {\n color: rgb(251, 191, 36);\n background: rgba(251, 191, 36, 0.12);\n}\n@media (max-width: 768px) {\n .ri-xrTools {\n gap: 6px;\n }\n .ri-xrButton {\n height: 34px;\n gap: 6px;\n padding: 0 8px;\n font-size: 11px;\n }\n .ri-xrButton svg {\n width: 14px;\n height: 14px;\n }\n .ri-xrButtonPrefix {\n display: none;\n }\n .ri-annotationNavSingle {\n bottom: 100px;\n }\n .ri-navButtonSingle {\n padding: 8px 20px;\n font-size: 13px;\n min-width: 110px;\n }\n .ri-annotationTitleHint {\n padding: 6px 14px;\n font-size: 12px;\n max-width: 250px;\n }\n .ri-viewAnnotationPopup {\n min-width: 160px;\n max-width: 220px;\n padding: 10px 12px;\n }\n .ri-viewAnnotationTitle {\n font-size: 13px;\n }\n .ri-viewAnnotationDescription {\n font-size: 12px;\n }\n .ri-buttonContainer {\n flex-wrap: wrap;\n gap: 6px;\n }\n .ri-tourControls {\n flex-wrap: wrap;\n }\n .ri-rightControls {\n flex-wrap: wrap;\n }\n}\n@media (max-width: 480px) {\n .ri-xrTools {\n gap: 5px;\n }\n .ri-xrButton {\n height: 34px;\n gap: 5px;\n font-size: 10px;\n }\n .ri-xrButton svg {\n width: 13px;\n height: 13px;\n }\n .ri-annotationNavSingle {\n bottom: 80px;\n }\n .ri-navButtonSingle {\n padding: 6px 16px;\n font-size: 12px;\n min-width: 90px;\n gap: 6px;\n }\n .ri-annotationTitleHint {\n padding: 4px 10px;\n font-size: 11px;\n max-width: 200px;\n }\n .ri-navBadge {\n font-size: 10px;\n padding: 1px 6px;\n }\n .ri-tourButton {\n padding: 0 8px;\n font-size: 12px;\n }\n .ri-tourButton span {\n display: none;\n }\n .ri-downloadButton {\n padding: 0 10px;\n font-size: 12px;\n }\n .ri-downloadButton span {\n display: none;\n }\n}\n');
|
|
204
204
|
|
|
205
205
|
// src/components/LicenseOverlay.tsx
|
|
206
206
|
import { AlertTriangle } from "lucide-react";
|
|
@@ -325,6 +325,208 @@ function exportSceneMesh({
|
|
|
325
325
|
});
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
+
// src/components/ModelViewer/internal/exportUvLayout.ts
|
|
329
|
+
import { strToU8, zipSync } from "fflate";
|
|
330
|
+
var SVG_SIZE = 2048;
|
|
331
|
+
var MESH_COLORS = [
|
|
332
|
+
"#00e5ff",
|
|
333
|
+
"#ff4fd8",
|
|
334
|
+
"#ffe45e",
|
|
335
|
+
"#7cff6b",
|
|
336
|
+
"#ff784f",
|
|
337
|
+
"#a78bfa",
|
|
338
|
+
"#38bdf8",
|
|
339
|
+
"#fb7185"
|
|
340
|
+
];
|
|
341
|
+
function escapeXml(value) {
|
|
342
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
343
|
+
}
|
|
344
|
+
function slugify(value, fallback) {
|
|
345
|
+
const slug = value.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
346
|
+
return slug || fallback;
|
|
347
|
+
}
|
|
348
|
+
function safeFilenameStem(value) {
|
|
349
|
+
return slugify(value, "model");
|
|
350
|
+
}
|
|
351
|
+
function getVertexIndex(geometry, elementIndex) {
|
|
352
|
+
return geometry.index ? Math.round(geometry.index.getX(elementIndex)) : elementIndex;
|
|
353
|
+
}
|
|
354
|
+
function readTriangle(geometry, uv, elementIndex) {
|
|
355
|
+
const a = getVertexIndex(geometry, elementIndex);
|
|
356
|
+
const b = getVertexIndex(geometry, elementIndex + 1);
|
|
357
|
+
const c = getVertexIndex(geometry, elementIndex + 2);
|
|
358
|
+
if (a >= uv.count || b >= uv.count || c >= uv.count) return null;
|
|
359
|
+
const values = [
|
|
360
|
+
{ index: a, u: uv.getX(a), v: uv.getY(a) },
|
|
361
|
+
{ index: b, u: uv.getX(b), v: uv.getY(b) },
|
|
362
|
+
{ index: c, u: uv.getX(c), v: uv.getY(c) }
|
|
363
|
+
];
|
|
364
|
+
return values.every(
|
|
365
|
+
(vertex) => Number.isFinite(vertex.u) && Number.isFinite(vertex.v)
|
|
366
|
+
) ? values : null;
|
|
367
|
+
}
|
|
368
|
+
function uvPointKey(vertex) {
|
|
369
|
+
return `${vertex.u.toFixed(6)},${vertex.v.toFixed(6)}`;
|
|
370
|
+
}
|
|
371
|
+
function addEdge(edges, start, end, indexed) {
|
|
372
|
+
let startKey;
|
|
373
|
+
let endKey;
|
|
374
|
+
if (indexed) {
|
|
375
|
+
startKey = start.index;
|
|
376
|
+
endKey = end.index;
|
|
377
|
+
} else {
|
|
378
|
+
startKey = uvPointKey(start);
|
|
379
|
+
endKey = uvPointKey(end);
|
|
380
|
+
}
|
|
381
|
+
const key = startKey < endKey ? `${startKey}|${endKey}` : `${endKey}|${startKey}`;
|
|
382
|
+
const existing = edges.get(key);
|
|
383
|
+
if (existing) {
|
|
384
|
+
existing.count += 1;
|
|
385
|
+
} else {
|
|
386
|
+
edges.set(key, { count: 1, start, end });
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function addTriangleEdges(edges, triangle, indexed) {
|
|
390
|
+
addEdge(edges, triangle[0], triangle[1], indexed);
|
|
391
|
+
addEdge(edges, triangle[1], triangle[2], indexed);
|
|
392
|
+
addEdge(edges, triangle[2], triangle[0], indexed);
|
|
393
|
+
}
|
|
394
|
+
function materialForIndex(material, materialIndex) {
|
|
395
|
+
if (!Array.isArray(material)) return material;
|
|
396
|
+
return material[materialIndex] ?? null;
|
|
397
|
+
}
|
|
398
|
+
function collectUvLayouts(scene) {
|
|
399
|
+
const layouts = /* @__PURE__ */ new Map();
|
|
400
|
+
let unnamedMeshIndex = 0;
|
|
401
|
+
let unnamedMaterialIndex = 0;
|
|
402
|
+
let meshColorIndex = 0;
|
|
403
|
+
scene.traverse((node) => {
|
|
404
|
+
const mesh = node;
|
|
405
|
+
if (!mesh.isMesh || !mesh.geometry || !mesh.material) return;
|
|
406
|
+
const geometry = mesh.geometry;
|
|
407
|
+
const uv = geometry.getAttribute("uv");
|
|
408
|
+
if (!uv || uv.itemSize < 2 || uv.count < 3) return;
|
|
409
|
+
unnamedMeshIndex += 1;
|
|
410
|
+
const meshName = mesh.name || `Mesh ${unnamedMeshIndex}`;
|
|
411
|
+
const elementCount = geometry.index?.count ?? uv.count;
|
|
412
|
+
const groups = geometry.groups.length > 0 ? geometry.groups : [{ start: 0, count: elementCount, materialIndex: 0 }];
|
|
413
|
+
for (const group of groups) {
|
|
414
|
+
const materialIndex = group.materialIndex ?? 0;
|
|
415
|
+
const material = materialForIndex(mesh.material, materialIndex);
|
|
416
|
+
if (!material) continue;
|
|
417
|
+
let layout = layouts.get(material.uuid);
|
|
418
|
+
if (!layout) {
|
|
419
|
+
unnamedMaterialIndex += 1;
|
|
420
|
+
layout = {
|
|
421
|
+
name: material.name || `Material ${unnamedMaterialIndex}`,
|
|
422
|
+
layers: /* @__PURE__ */ new Map()
|
|
423
|
+
};
|
|
424
|
+
layouts.set(material.uuid, layout);
|
|
425
|
+
}
|
|
426
|
+
let layer = layout.layers.get(mesh.uuid);
|
|
427
|
+
if (!layer) {
|
|
428
|
+
layer = {
|
|
429
|
+
name: meshName,
|
|
430
|
+
color: MESH_COLORS[meshColorIndex % MESH_COLORS.length],
|
|
431
|
+
edges: /* @__PURE__ */ new Map()
|
|
432
|
+
};
|
|
433
|
+
meshColorIndex += 1;
|
|
434
|
+
layout.layers.set(mesh.uuid, layer);
|
|
435
|
+
}
|
|
436
|
+
const start = Math.max(0, group.start);
|
|
437
|
+
const end = Math.min(elementCount, start + group.count);
|
|
438
|
+
for (let elementIndex = start; elementIndex + 2 < end; elementIndex += 3) {
|
|
439
|
+
const triangle = readTriangle(geometry, uv, elementIndex);
|
|
440
|
+
if (triangle) {
|
|
441
|
+
addTriangleEdges(layer.edges, triangle, Boolean(geometry.index));
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
return [...layouts.values()].filter(
|
|
447
|
+
(layout) => [...layout.layers.values()].some((layer) => layer.edges.size > 0)
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
function coordinate(value) {
|
|
451
|
+
return (value * SVG_SIZE).toFixed(2);
|
|
452
|
+
}
|
|
453
|
+
function edgePath(edge) {
|
|
454
|
+
return `M${coordinate(edge.start.u)} ${coordinate(1 - edge.start.v)}L${coordinate(edge.end.u)} ${coordinate(1 - edge.end.v)}`;
|
|
455
|
+
}
|
|
456
|
+
function renderSvg(layout) {
|
|
457
|
+
const materialName = escapeXml(layout.name);
|
|
458
|
+
const layers = [...layout.layers.values()].filter((layer) => layer.edges.size > 0).map((layer, index) => {
|
|
459
|
+
const name = escapeXml(layer.name);
|
|
460
|
+
const id = `${slugify(layer.name, "mesh")}-${index + 1}`;
|
|
461
|
+
const allEdges = [...layer.edges.values()];
|
|
462
|
+
const boundaryEdges = allEdges.filter((edge) => edge.count === 1);
|
|
463
|
+
const visibleEdges = boundaryEdges.length > 0 ? boundaryEdges : allEdges;
|
|
464
|
+
const paths = visibleEdges.map(edgePath).join("");
|
|
465
|
+
return [
|
|
466
|
+
` <g id="${id}" data-mesh="${name}" fill="none" stroke="${layer.color}"`,
|
|
467
|
+
' stroke-width="1" stroke-linejoin="round" vector-effect="non-scaling-stroke">',
|
|
468
|
+
` <title>${name}</title>`,
|
|
469
|
+
` <path d="${paths}"/>`,
|
|
470
|
+
" </g>"
|
|
471
|
+
].join("\n");
|
|
472
|
+
}).join("\n");
|
|
473
|
+
return [
|
|
474
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
475
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${SVG_SIZE}" height="${SVG_SIZE}" viewBox="0 0 ${SVG_SIZE} ${SVG_SIZE}">`,
|
|
476
|
+
` <title>${materialName} UV layout</title>`,
|
|
477
|
+
" <desc>UV0 island-outline template exported by React Immersive. The SVG canvas maps to the complete 0\u20131 texture area.</desc>",
|
|
478
|
+
' <rect width="2048" height="2048" fill="none" stroke="#94a3b8" stroke-width="2"/>',
|
|
479
|
+
layers,
|
|
480
|
+
"</svg>"
|
|
481
|
+
].join("\n");
|
|
482
|
+
}
|
|
483
|
+
function buildUvLayoutFiles(scene, downloadFilename = "model") {
|
|
484
|
+
const layouts = collectUvLayouts(scene);
|
|
485
|
+
const stem = safeFilenameStem(downloadFilename);
|
|
486
|
+
return layouts.map((layout, index) => {
|
|
487
|
+
const materialSlug = slugify(layout.name, `material-${index + 1}`);
|
|
488
|
+
const filename = layouts.length === 1 ? `${stem}-uv-layout.svg` : `${stem}-uv-${materialSlug}-${index + 1}.svg`;
|
|
489
|
+
return {
|
|
490
|
+
filename,
|
|
491
|
+
materialName: layout.name,
|
|
492
|
+
meshCount: [...layout.layers.values()].filter(
|
|
493
|
+
(layer) => layer.edges.size > 0
|
|
494
|
+
).length,
|
|
495
|
+
svg: renderSvg(layout)
|
|
496
|
+
};
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
function downloadBlob(blob, filename) {
|
|
500
|
+
const url = URL.createObjectURL(blob);
|
|
501
|
+
const anchor = document.createElement("a");
|
|
502
|
+
anchor.href = url;
|
|
503
|
+
anchor.download = filename;
|
|
504
|
+
document.body.appendChild(anchor);
|
|
505
|
+
anchor.click();
|
|
506
|
+
document.body.removeChild(anchor);
|
|
507
|
+
URL.revokeObjectURL(url);
|
|
508
|
+
}
|
|
509
|
+
function downloadUvLayouts(scene, downloadFilename = "model") {
|
|
510
|
+
const files = buildUvLayoutFiles(scene, downloadFilename);
|
|
511
|
+
if (files.length === 0) return files;
|
|
512
|
+
if (files.length === 1) {
|
|
513
|
+
downloadBlob(
|
|
514
|
+
new Blob([files[0].svg], { type: "image/svg+xml;charset=utf-8" }),
|
|
515
|
+
files[0].filename
|
|
516
|
+
);
|
|
517
|
+
return files;
|
|
518
|
+
}
|
|
519
|
+
const archive = Object.fromEntries(
|
|
520
|
+
files.map((file) => [file.filename, strToU8(file.svg)])
|
|
521
|
+
);
|
|
522
|
+
const zipped = zipSync(archive, { level: 6 });
|
|
523
|
+
downloadBlob(
|
|
524
|
+
new Blob([zipped], { type: "application/zip" }),
|
|
525
|
+
`${safeFilenameStem(downloadFilename)}-uv-layouts.zip`
|
|
526
|
+
);
|
|
527
|
+
return files;
|
|
528
|
+
}
|
|
529
|
+
|
|
328
530
|
// src/components/ModelViewer/internal/captureImage.ts
|
|
329
531
|
import { PerspectiveCamera, Vector2 } from "three";
|
|
330
532
|
function captureImage(capture, options = {}) {
|
|
@@ -3465,19 +3667,31 @@ function useCameraBiasedAnchor(annotation, worldPositionsRef) {
|
|
|
3465
3667
|
});
|
|
3466
3668
|
return anchorRef;
|
|
3467
3669
|
}
|
|
3468
|
-
function AnnotationMarker3D({
|
|
3670
|
+
function AnnotationMarker3D({
|
|
3671
|
+
annotation,
|
|
3672
|
+
occluders,
|
|
3673
|
+
worldPositionsRef,
|
|
3674
|
+
onClick,
|
|
3675
|
+
onHover,
|
|
3676
|
+
showAnnotationOnHover
|
|
3677
|
+
}) {
|
|
3469
3678
|
const [occluded, setOccluded] = useState4(false);
|
|
3470
3679
|
const anchorRef = useCameraBiasedAnchor(annotation, worldPositionsRef);
|
|
3471
3680
|
const [isHovered, setIsHovered] = useState4(false);
|
|
3472
3681
|
const [zIndex, setZIndex] = useState4(1);
|
|
3473
3682
|
const clickTimeoutRef = useRef6(null);
|
|
3474
3683
|
const markerRef = useRef6(null);
|
|
3684
|
+
const hoverTimeoutRef = useRef6(null);
|
|
3475
3685
|
useEffect8(() => {
|
|
3476
3686
|
return () => {
|
|
3477
3687
|
if (clickTimeoutRef.current) {
|
|
3478
3688
|
clearTimeout(clickTimeoutRef.current);
|
|
3479
3689
|
clickTimeoutRef.current = null;
|
|
3480
3690
|
}
|
|
3691
|
+
if (hoverTimeoutRef.current) {
|
|
3692
|
+
clearTimeout(hoverTimeoutRef.current);
|
|
3693
|
+
hoverTimeoutRef.current = null;
|
|
3694
|
+
}
|
|
3481
3695
|
};
|
|
3482
3696
|
}, []);
|
|
3483
3697
|
const handleClick = (event) => {
|
|
@@ -3496,13 +3710,29 @@ function AnnotationMarker3D({ annotation, occluders, worldPositionsRef, onClick,
|
|
|
3496
3710
|
event.stopPropagation();
|
|
3497
3711
|
setIsHovered(true);
|
|
3498
3712
|
setZIndex(100);
|
|
3713
|
+
if (hoverTimeoutRef.current) {
|
|
3714
|
+
clearTimeout(hoverTimeoutRef.current);
|
|
3715
|
+
hoverTimeoutRef.current = null;
|
|
3716
|
+
}
|
|
3499
3717
|
onHover?.(annotation);
|
|
3718
|
+
if (showAnnotationOnHover) {
|
|
3719
|
+
onClick?.(annotation);
|
|
3720
|
+
}
|
|
3500
3721
|
};
|
|
3501
3722
|
const handlePointerLeave = (event) => {
|
|
3502
3723
|
event.stopPropagation();
|
|
3503
3724
|
setIsHovered(false);
|
|
3504
3725
|
setZIndex(1);
|
|
3505
|
-
|
|
3726
|
+
if (hoverTimeoutRef.current) {
|
|
3727
|
+
clearTimeout(hoverTimeoutRef.current);
|
|
3728
|
+
}
|
|
3729
|
+
hoverTimeoutRef.current = window.setTimeout(() => {
|
|
3730
|
+
onHover?.(null);
|
|
3731
|
+
if (showAnnotationOnHover) {
|
|
3732
|
+
onHover?.(null);
|
|
3733
|
+
}
|
|
3734
|
+
hoverTimeoutRef.current = null;
|
|
3735
|
+
}, 50);
|
|
3506
3736
|
};
|
|
3507
3737
|
const handlePointerDown = (event) => {
|
|
3508
3738
|
event.stopPropagation();
|
|
@@ -3627,7 +3857,8 @@ function AnnotationsLayer({
|
|
|
3627
3857
|
onAnnotationClick,
|
|
3628
3858
|
onAnnotationHover,
|
|
3629
3859
|
onCloseAnnotation,
|
|
3630
|
-
hideCloseButton = false
|
|
3860
|
+
hideCloseButton = false,
|
|
3861
|
+
showAnnotationOnHover = false
|
|
3631
3862
|
}) {
|
|
3632
3863
|
const invalidate = useThree8((state) => state.invalidate);
|
|
3633
3864
|
const scene = useThree8((state) => state.scene);
|
|
@@ -3635,6 +3866,7 @@ function AnnotationsLayer({
|
|
|
3635
3866
|
const [isLocked, setIsLocked] = useState4(false);
|
|
3636
3867
|
const groupRef = useRef6(null);
|
|
3637
3868
|
const invalidateFrameRef = useRef6(null);
|
|
3869
|
+
const hoverCloseTimeoutRef = useRef6(null);
|
|
3638
3870
|
const occluders = useMemo8(() => {
|
|
3639
3871
|
const list = [];
|
|
3640
3872
|
scene.traverse((object) => {
|
|
@@ -3665,6 +3897,9 @@ function AnnotationsLayer({
|
|
|
3665
3897
|
if (invalidateFrameRef.current !== null) {
|
|
3666
3898
|
window.cancelAnimationFrame(invalidateFrameRef.current);
|
|
3667
3899
|
}
|
|
3900
|
+
if (hoverCloseTimeoutRef.current) {
|
|
3901
|
+
clearTimeout(hoverCloseTimeoutRef.current);
|
|
3902
|
+
}
|
|
3668
3903
|
};
|
|
3669
3904
|
}, []);
|
|
3670
3905
|
const activeAnnotationToShow = isLocked ? localActiveAnnotation : activeAnnotation || localActiveAnnotation;
|
|
@@ -3676,53 +3911,71 @@ function AnnotationsLayer({
|
|
|
3676
3911
|
(annotation) => annotation.id === activeAnnotationToShow.id
|
|
3677
3912
|
) ?? activeAnnotationToShow;
|
|
3678
3913
|
}, [activeAnnotationToShow, annotations]);
|
|
3679
|
-
const handleAnnotationClick = useCallback(
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3914
|
+
const handleAnnotationClick = useCallback(
|
|
3915
|
+
(annotation) => {
|
|
3916
|
+
if (isLocked && localActiveAnnotation?.id === annotation.id) {
|
|
3917
|
+
setIsLocked(false);
|
|
3918
|
+
setLocalActiveAnnotation(null);
|
|
3919
|
+
onCloseAnnotation?.();
|
|
3920
|
+
return;
|
|
3921
|
+
}
|
|
3922
|
+
setIsLocked(true);
|
|
3923
|
+
setLocalActiveAnnotation(annotation);
|
|
3924
|
+
onAnnotationClick?.(annotation);
|
|
3925
|
+
},
|
|
3926
|
+
[isLocked, localActiveAnnotation, onAnnotationClick, onCloseAnnotation]
|
|
3927
|
+
);
|
|
3928
|
+
const handleAnnotationHover = useCallback(
|
|
3929
|
+
(annotation) => {
|
|
3692
3930
|
onAnnotationHover?.(annotation);
|
|
3693
|
-
if (
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3931
|
+
if (showAnnotationOnHover && !isLocked) {
|
|
3932
|
+
if (hoverCloseTimeoutRef.current) {
|
|
3933
|
+
clearTimeout(hoverCloseTimeoutRef.current);
|
|
3934
|
+
hoverCloseTimeoutRef.current = null;
|
|
3935
|
+
}
|
|
3936
|
+
if (annotation) {
|
|
3937
|
+
setLocalActiveAnnotation(annotation);
|
|
3938
|
+
} else {
|
|
3939
|
+
hoverCloseTimeoutRef.current = window.setTimeout(() => {
|
|
3940
|
+
setLocalActiveAnnotation(null);
|
|
3941
|
+
onCloseAnnotation?.();
|
|
3942
|
+
hoverCloseTimeoutRef.current = null;
|
|
3943
|
+
}, 100);
|
|
3698
3944
|
}
|
|
3699
3945
|
}
|
|
3700
|
-
}
|
|
3701
|
-
|
|
3946
|
+
},
|
|
3947
|
+
[isLocked, onAnnotationHover, showAnnotationOnHover, onCloseAnnotation]
|
|
3948
|
+
);
|
|
3702
3949
|
const handleClose = useCallback(() => {
|
|
3703
3950
|
setIsLocked(false);
|
|
3704
3951
|
setLocalActiveAnnotation(null);
|
|
3705
3952
|
onCloseAnnotation?.();
|
|
3706
3953
|
}, [onCloseAnnotation]);
|
|
3707
|
-
const handleCanvasClick = useCallback(
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3954
|
+
const handleCanvasClick = useCallback(
|
|
3955
|
+
(event) => {
|
|
3956
|
+
const target = event.target;
|
|
3957
|
+
if (target.closest?.(".ri-annotationMarker") || target.closest?.(".ri-viewAnnotationPopup") || target.closest?.(".annotation-html-wrapper")) {
|
|
3958
|
+
return;
|
|
3959
|
+
}
|
|
3960
|
+
const canvas = canvasRootRef.current;
|
|
3961
|
+
if (canvas && canvas.contains(target)) {
|
|
3962
|
+
setIsLocked(false);
|
|
3963
|
+
setLocalActiveAnnotation(null);
|
|
3964
|
+
onCloseAnnotation?.();
|
|
3965
|
+
}
|
|
3966
|
+
},
|
|
3967
|
+
[canvasRootRef, onCloseAnnotation]
|
|
3968
|
+
);
|
|
3969
|
+
const handleEscapeKey = useCallback(
|
|
3970
|
+
(event) => {
|
|
3971
|
+
if (event.key === "Escape" && isLocked) {
|
|
3972
|
+
setIsLocked(false);
|
|
3973
|
+
setLocalActiveAnnotation(null);
|
|
3974
|
+
onCloseAnnotation?.();
|
|
3975
|
+
}
|
|
3976
|
+
},
|
|
3977
|
+
[isLocked, onCloseAnnotation]
|
|
3978
|
+
);
|
|
3726
3979
|
useEffect8(() => {
|
|
3727
3980
|
const canvas = canvasRootRef.current;
|
|
3728
3981
|
if (canvas) {
|
|
@@ -3772,7 +4025,8 @@ function AnnotationsLayer({
|
|
|
3772
4025
|
occluders,
|
|
3773
4026
|
worldPositionsRef,
|
|
3774
4027
|
onClick: handleAnnotationClick,
|
|
3775
|
-
onHover: handleAnnotationHover
|
|
4028
|
+
onHover: handleAnnotationHover,
|
|
4029
|
+
showAnnotationOnHover
|
|
3776
4030
|
},
|
|
3777
4031
|
stableKey
|
|
3778
4032
|
);
|
|
@@ -4130,6 +4384,7 @@ import {
|
|
|
4130
4384
|
Box3 as Box33,
|
|
4131
4385
|
CatmullRomCurve3,
|
|
4132
4386
|
Sphere,
|
|
4387
|
+
Spherical,
|
|
4133
4388
|
Vector3 as Vector36
|
|
4134
4389
|
} from "three";
|
|
4135
4390
|
var ENTER_BLEND_SECONDS = 1.2;
|
|
@@ -4138,18 +4393,75 @@ function CinematicCamera({
|
|
|
4138
4393
|
sceneRef,
|
|
4139
4394
|
config,
|
|
4140
4395
|
playing,
|
|
4396
|
+
resetOnPlay = false,
|
|
4141
4397
|
onFinished,
|
|
4142
|
-
onUserInteract
|
|
4398
|
+
onUserInteract,
|
|
4399
|
+
onStop
|
|
4143
4400
|
}) {
|
|
4144
4401
|
const invalidate = useThree9((state) => state.invalidate);
|
|
4145
4402
|
const waypoints = useMemo11(() => config.waypoints ?? [], [config.waypoints]);
|
|
4146
4403
|
const hasPath = waypoints.length >= 2;
|
|
4147
4404
|
const loop = config.loop ?? true;
|
|
4148
|
-
const
|
|
4405
|
+
const calculatedDuration = useMemo11(() => {
|
|
4406
|
+
if (waypoints.length < 2) return 24;
|
|
4407
|
+
let total = 0;
|
|
4408
|
+
for (let i = 0; i < waypoints.length - 1; i++) {
|
|
4409
|
+
total += waypoints[i].segmentDuration ?? 3.5;
|
|
4410
|
+
}
|
|
4411
|
+
if (loop && waypoints.length > 1) {
|
|
4412
|
+
total += waypoints[waypoints.length - 1].segmentDuration ?? 3.5;
|
|
4413
|
+
}
|
|
4414
|
+
return Math.max(total, 4);
|
|
4415
|
+
}, [waypoints, loop]);
|
|
4416
|
+
const duration = config.duration ?? calculatedDuration;
|
|
4149
4417
|
const signature = useMemo11(
|
|
4150
|
-
() => waypoints.map(
|
|
4418
|
+
() => waypoints.map(
|
|
4419
|
+
(waypoint) => [
|
|
4420
|
+
...waypoint.position,
|
|
4421
|
+
...waypoint.target,
|
|
4422
|
+
waypoint.azimuthAngle ?? "",
|
|
4423
|
+
waypoint.polarAngle ?? "",
|
|
4424
|
+
waypoint.segmentDuration ?? ""
|
|
4425
|
+
].join(",")
|
|
4426
|
+
).join(";"),
|
|
4151
4427
|
[waypoints]
|
|
4152
4428
|
);
|
|
4429
|
+
const segmentTimes = useMemo11(() => {
|
|
4430
|
+
const times = [];
|
|
4431
|
+
if (waypoints.length < 2) return times;
|
|
4432
|
+
let accumulated = 0;
|
|
4433
|
+
for (let i = 0; i < waypoints.length - 1; i++) {
|
|
4434
|
+
accumulated += waypoints[i].segmentDuration ?? 3.5;
|
|
4435
|
+
times.push(accumulated);
|
|
4436
|
+
}
|
|
4437
|
+
if (loop && waypoints.length > 1) {
|
|
4438
|
+
accumulated += waypoints[waypoints.length - 1].segmentDuration ?? 3.5;
|
|
4439
|
+
times.push(accumulated);
|
|
4440
|
+
}
|
|
4441
|
+
return times;
|
|
4442
|
+
}, [waypoints, loop]);
|
|
4443
|
+
const getCurveParameter = useMemo11(() => {
|
|
4444
|
+
return (elapsed) => {
|
|
4445
|
+
if (waypoints.length < 2 || segmentTimes.length === 0) {
|
|
4446
|
+
return elapsed / duration;
|
|
4447
|
+
}
|
|
4448
|
+
const segmentTotal = segmentTimes[segmentTimes.length - 1];
|
|
4449
|
+
const playbackElapsed = loop ? elapsed % duration : Math.min(elapsed, duration);
|
|
4450
|
+
const normalizedElapsed = duration > 0 ? playbackElapsed / duration * segmentTotal : 0;
|
|
4451
|
+
for (let i = 0; i < segmentTimes.length; i++) {
|
|
4452
|
+
if (normalizedElapsed <= segmentTimes[i]) {
|
|
4453
|
+
const prevTime = i === 0 ? 0 : segmentTimes[i - 1];
|
|
4454
|
+
const segmentDuration = segmentTimes[i] - prevTime;
|
|
4455
|
+
const progress = segmentDuration > 0 ? (normalizedElapsed - prevTime) / segmentDuration : 0;
|
|
4456
|
+
const totalSegments = loop ? waypoints.length : waypoints.length - 1;
|
|
4457
|
+
const baseU = i / totalSegments;
|
|
4458
|
+
const segmentU = 1 / totalSegments;
|
|
4459
|
+
return baseU + progress * segmentU;
|
|
4460
|
+
}
|
|
4461
|
+
}
|
|
4462
|
+
return 1;
|
|
4463
|
+
};
|
|
4464
|
+
}, [waypoints.length, segmentTimes, duration, loop]);
|
|
4153
4465
|
const curves = useMemo11(() => {
|
|
4154
4466
|
if (!hasPath) return null;
|
|
4155
4467
|
return {
|
|
@@ -4167,29 +4479,83 @@ function CinematicCamera({
|
|
|
4167
4479
|
)
|
|
4168
4480
|
};
|
|
4169
4481
|
}, [signature, hasPath, loop]);
|
|
4482
|
+
const orbitPath = useMemo11(() => {
|
|
4483
|
+
if (!hasPath || !waypoints.every(
|
|
4484
|
+
(waypoint) => Number.isFinite(waypoint.azimuthAngle) && Number.isFinite(waypoint.polarAngle)
|
|
4485
|
+
)) {
|
|
4486
|
+
return null;
|
|
4487
|
+
}
|
|
4488
|
+
const points = waypoints.map((waypoint) => ({
|
|
4489
|
+
azimuth: waypoint.azimuthAngle,
|
|
4490
|
+
polar: waypoint.polarAngle,
|
|
4491
|
+
distance: new Vector36(...waypoint.position).distanceTo(
|
|
4492
|
+
new Vector36(...waypoint.target)
|
|
4493
|
+
)
|
|
4494
|
+
}));
|
|
4495
|
+
for (let index = 1; index < points.length; index += 1) {
|
|
4496
|
+
const previous = points[index - 1].azimuth;
|
|
4497
|
+
const current = points[index].azimuth;
|
|
4498
|
+
points[index].azimuth = current + Math.round((previous - current) / (Math.PI * 2)) * Math.PI * 2;
|
|
4499
|
+
}
|
|
4500
|
+
return points;
|
|
4501
|
+
}, [signature, hasPath]);
|
|
4170
4502
|
const elapsedRef = useRef8(0);
|
|
4171
4503
|
const enterElapsedRef = useRef8(0);
|
|
4172
4504
|
const enterPos = useRef8(new Vector36());
|
|
4173
4505
|
const enterTarget = useRef8(new Vector36());
|
|
4174
4506
|
const startedRef = useRef8(false);
|
|
4175
4507
|
const pausedElapsedRef = useRef8(0);
|
|
4508
|
+
const hasFinishedRef = useRef8(false);
|
|
4176
4509
|
const orbitRef = useRef8(null);
|
|
4177
4510
|
const tmpPos = useMemo11(() => new Vector36(), []);
|
|
4178
4511
|
const tmpTarget = useMemo11(() => new Vector36(), []);
|
|
4512
|
+
const tmpSpherical = useMemo11(() => new Spherical(), []);
|
|
4513
|
+
useEffect11(() => {
|
|
4514
|
+
elapsedRef.current = 0;
|
|
4515
|
+
pausedElapsedRef.current = 0;
|
|
4516
|
+
enterElapsedRef.current = 0;
|
|
4517
|
+
hasFinishedRef.current = false;
|
|
4518
|
+
}, [signature, loop, duration]);
|
|
4519
|
+
useEffect11(() => {
|
|
4520
|
+
if (resetOnPlay) {
|
|
4521
|
+
pausedElapsedRef.current = 0;
|
|
4522
|
+
elapsedRef.current = 0;
|
|
4523
|
+
hasFinishedRef.current = false;
|
|
4524
|
+
}
|
|
4525
|
+
}, [resetOnPlay]);
|
|
4179
4526
|
useEffect11(() => {
|
|
4180
4527
|
if (!playing) {
|
|
4181
4528
|
startedRef.current = false;
|
|
4182
|
-
if (elapsedRef.current > 0) {
|
|
4529
|
+
if (elapsedRef.current > 0 && !resetOnPlay) {
|
|
4183
4530
|
pausedElapsedRef.current = elapsedRef.current;
|
|
4531
|
+
} else if (resetOnPlay) {
|
|
4532
|
+
pausedElapsedRef.current = 0;
|
|
4533
|
+
}
|
|
4534
|
+
if (elapsedRef.current > 0 || hasFinishedRef.current) {
|
|
4535
|
+
onStop?.();
|
|
4184
4536
|
}
|
|
4185
4537
|
return;
|
|
4186
4538
|
}
|
|
4539
|
+
if (elapsedRef.current >= duration && !loop) {
|
|
4540
|
+
elapsedRef.current = 0;
|
|
4541
|
+
pausedElapsedRef.current = 0;
|
|
4542
|
+
hasFinishedRef.current = false;
|
|
4543
|
+
}
|
|
4187
4544
|
const controls = controlsRef.current;
|
|
4188
4545
|
if (controls) {
|
|
4189
4546
|
controls.getPosition(enterPos.current);
|
|
4190
4547
|
controls.getTarget(enterTarget.current);
|
|
4191
4548
|
}
|
|
4192
|
-
|
|
4549
|
+
if (resetOnPlay || hasFinishedRef.current || pausedElapsedRef.current === 0) {
|
|
4550
|
+
elapsedRef.current = 0;
|
|
4551
|
+
pausedElapsedRef.current = 0;
|
|
4552
|
+
hasFinishedRef.current = false;
|
|
4553
|
+
} else if (pausedElapsedRef.current > 0 && pausedElapsedRef.current < duration) {
|
|
4554
|
+
elapsedRef.current = pausedElapsedRef.current;
|
|
4555
|
+
} else {
|
|
4556
|
+
elapsedRef.current = 0;
|
|
4557
|
+
pausedElapsedRef.current = 0;
|
|
4558
|
+
}
|
|
4193
4559
|
enterElapsedRef.current = 0;
|
|
4194
4560
|
if (!hasPath) {
|
|
4195
4561
|
const scene = sceneRef.current;
|
|
@@ -4203,12 +4569,26 @@ function CinematicCamera({
|
|
|
4203
4569
|
}
|
|
4204
4570
|
startedRef.current = true;
|
|
4205
4571
|
invalidate();
|
|
4206
|
-
}, [
|
|
4572
|
+
}, [
|
|
4573
|
+
playing,
|
|
4574
|
+
hasPath,
|
|
4575
|
+
controlsRef,
|
|
4576
|
+
sceneRef,
|
|
4577
|
+
invalidate,
|
|
4578
|
+
duration,
|
|
4579
|
+
loop,
|
|
4580
|
+
signature,
|
|
4581
|
+
resetOnPlay,
|
|
4582
|
+
onStop
|
|
4583
|
+
]);
|
|
4207
4584
|
useEffect11(() => {
|
|
4208
4585
|
if (!playing || !onUserInteract) return;
|
|
4209
4586
|
const controls = controlsRef.current;
|
|
4210
4587
|
if (!controls) return;
|
|
4211
|
-
const handle = () =>
|
|
4588
|
+
const handle = () => {
|
|
4589
|
+
if (hasFinishedRef.current) return;
|
|
4590
|
+
onUserInteract();
|
|
4591
|
+
};
|
|
4212
4592
|
controls.addEventListener("controlstart", handle);
|
|
4213
4593
|
return () => controls.removeEventListener("controlstart", handle);
|
|
4214
4594
|
}, [playing, controlsRef, onUserInteract]);
|
|
@@ -4218,12 +4598,33 @@ function CinematicCamera({
|
|
|
4218
4598
|
if (!controls) return;
|
|
4219
4599
|
elapsedRef.current += delta;
|
|
4220
4600
|
enterElapsedRef.current += delta;
|
|
4221
|
-
let u = elapsedRef.current
|
|
4222
|
-
const finished = !loop &&
|
|
4601
|
+
let u = getCurveParameter(elapsedRef.current);
|
|
4602
|
+
const finished = !loop && elapsedRef.current >= duration;
|
|
4223
4603
|
u = loop ? u % 1 : Math.min(u, 1);
|
|
4224
4604
|
if (curves) {
|
|
4225
|
-
curves.
|
|
4226
|
-
|
|
4605
|
+
curves.target.getPoint(u, tmpTarget);
|
|
4606
|
+
if (orbitPath) {
|
|
4607
|
+
const segmentCount = loop ? orbitPath.length : orbitPath.length - 1;
|
|
4608
|
+
const scaled = Math.min(u, 1) * segmentCount;
|
|
4609
|
+
const segment = Math.min(Math.floor(scaled), segmentCount - 1);
|
|
4610
|
+
const next = (segment + 1) % orbitPath.length;
|
|
4611
|
+
const localT = u >= 1 ? 1 : scaled - segment;
|
|
4612
|
+
const eased = localT * localT * (3 - 2 * localT);
|
|
4613
|
+
const start = orbitPath[segment];
|
|
4614
|
+
const end = orbitPath[next];
|
|
4615
|
+
const endAzimuth = end.azimuth + Math.round((start.azimuth - end.azimuth) / (Math.PI * 2)) * Math.PI * 2;
|
|
4616
|
+
const azimuth = start.azimuth + (endAzimuth - start.azimuth) * eased;
|
|
4617
|
+
const polar = start.polar + (end.polar - start.polar) * eased;
|
|
4618
|
+
const distance = start.distance + (end.distance - start.distance) * eased;
|
|
4619
|
+
tmpSpherical.set(
|
|
4620
|
+
Math.max(distance, 1e-6),
|
|
4621
|
+
Math.min(Math.max(polar, 1e-6), Math.PI - 1e-6),
|
|
4622
|
+
azimuth
|
|
4623
|
+
);
|
|
4624
|
+
tmpPos.setFromSpherical(tmpSpherical).add(tmpTarget);
|
|
4625
|
+
} else {
|
|
4626
|
+
curves.position.getPoint(u, tmpPos);
|
|
4627
|
+
}
|
|
4227
4628
|
} else {
|
|
4228
4629
|
const orbit = orbitRef.current;
|
|
4229
4630
|
if (!orbit) return;
|
|
@@ -4253,6 +4654,8 @@ function CinematicCamera({
|
|
|
4253
4654
|
);
|
|
4254
4655
|
if (finished) {
|
|
4255
4656
|
startedRef.current = false;
|
|
4657
|
+
hasFinishedRef.current = true;
|
|
4658
|
+
elapsedRef.current = 0;
|
|
4256
4659
|
pausedElapsedRef.current = 0;
|
|
4257
4660
|
onFinished?.();
|
|
4258
4661
|
return;
|
|
@@ -5276,6 +5679,7 @@ import {
|
|
|
5276
5679
|
Camera as CameraIcon,
|
|
5277
5680
|
Download,
|
|
5278
5681
|
Eraser,
|
|
5682
|
+
FileImage,
|
|
5279
5683
|
MapPin,
|
|
5280
5684
|
RotateCcw,
|
|
5281
5685
|
ChevronLeft,
|
|
@@ -5529,7 +5933,8 @@ function ModelViewer({
|
|
|
5529
5933
|
usdzUrl,
|
|
5530
5934
|
arScaleMode = "normalized",
|
|
5531
5935
|
mobileHandoffUrl,
|
|
5532
|
-
showAnnotationNavigation = true
|
|
5936
|
+
showAnnotationNavigation = true,
|
|
5937
|
+
showAnnotationOnHover = false
|
|
5533
5938
|
}) {
|
|
5534
5939
|
const { activeTab, activeSubTab } = useBindingBuilderContext();
|
|
5535
5940
|
const license = useLicense(licenseKey);
|
|
@@ -5599,6 +6004,7 @@ function ModelViewer({
|
|
|
5599
6004
|
!modelUrl || Boolean(onAutoFit) || Boolean(camera)
|
|
5600
6005
|
);
|
|
5601
6006
|
const [downloadMenuOpen, setDownloadMenuOpen] = useState10(false);
|
|
6007
|
+
const [uvExportError, setUvExportError] = useState10(null);
|
|
5602
6008
|
const [modelLoaded, setModelLoaded] = useState10(false);
|
|
5603
6009
|
const [modelLoadFailed, setModelLoadFailed] = useState10(false);
|
|
5604
6010
|
const [bindingsMismatch, setBindingsMismatch] = useState10(false);
|
|
@@ -5653,9 +6059,11 @@ function ModelViewer({
|
|
|
5653
6059
|
return { ...base ?? {}, ...cinematic };
|
|
5654
6060
|
}, [cinematic, resolvedSceneConfig]);
|
|
5655
6061
|
const [cinematicPlaying, setCinematicPlaying] = useState10(false);
|
|
6062
|
+
const [cinematicReset, setCinematicReset] = useState10(false);
|
|
5656
6063
|
const cinematicAutoStartedRef = useRef11(false);
|
|
5657
6064
|
useEffect15(() => {
|
|
5658
6065
|
setCinematicPlaying(false);
|
|
6066
|
+
setCinematicReset(false);
|
|
5659
6067
|
cinematicAutoStartedRef.current = false;
|
|
5660
6068
|
}, [modelUrl]);
|
|
5661
6069
|
useEffect15(() => {
|
|
@@ -5663,6 +6071,7 @@ function ModelViewer({
|
|
|
5663
6071
|
if (modelLoaded && cameraPositioned && !isAutoFraming) {
|
|
5664
6072
|
cinematicAutoStartedRef.current = true;
|
|
5665
6073
|
setCinematicPlaying(true);
|
|
6074
|
+
setCinematicReset(false);
|
|
5666
6075
|
}
|
|
5667
6076
|
}, [cinematicConfig, modelLoaded, cameraPositioned, isAutoFraming]);
|
|
5668
6077
|
const xrStore = useMemo13(() => {
|
|
@@ -6787,6 +7196,17 @@ function ModelViewer({
|
|
|
6787
7196
|
console.error("Screenshot failed:", err);
|
|
6788
7197
|
});
|
|
6789
7198
|
}, [downloadFilename]);
|
|
7199
|
+
const handleDownloadUvLayout = useCallback4(() => {
|
|
7200
|
+
const scene = sceneRef.current;
|
|
7201
|
+
if (!scene) return;
|
|
7202
|
+
const files = downloadUvLayouts(scene, downloadFilename);
|
|
7203
|
+
if (files.length === 0) {
|
|
7204
|
+
setUvExportError("This model does not contain UV coordinates.");
|
|
7205
|
+
return;
|
|
7206
|
+
}
|
|
7207
|
+
setUvExportError(null);
|
|
7208
|
+
setDownloadMenuOpen(false);
|
|
7209
|
+
}, [downloadFilename]);
|
|
6790
7210
|
const handleReset = useCallback4(async () => {
|
|
6791
7211
|
if (onAutoFit) {
|
|
6792
7212
|
await onAutoFit();
|
|
@@ -6816,6 +7236,20 @@ function ModelViewer({
|
|
|
6816
7236
|
console.error("Reset camera error:", error);
|
|
6817
7237
|
}
|
|
6818
7238
|
}, [onAutoFit, fitScene]);
|
|
7239
|
+
const handleCinematicPlay = useCallback4(() => {
|
|
7240
|
+
setCinematicReset(false);
|
|
7241
|
+
setCinematicPlaying(true);
|
|
7242
|
+
}, []);
|
|
7243
|
+
const handleCinematicPause = useCallback4(() => {
|
|
7244
|
+
setCinematicPlaying(false);
|
|
7245
|
+
}, []);
|
|
7246
|
+
const handleCinematicStop = useCallback4(() => {
|
|
7247
|
+
setCinematicPlaying(false);
|
|
7248
|
+
setCinematicReset(true);
|
|
7249
|
+
setTimeout(() => {
|
|
7250
|
+
handleReset();
|
|
7251
|
+
}, 100);
|
|
7252
|
+
}, [handleReset]);
|
|
6819
7253
|
const handleCameraChangeWithAnnotations = useCallback4(
|
|
6820
7254
|
(cameraValue, controls) => {
|
|
6821
7255
|
handleCameraChangeInternal(cameraValue, controls);
|
|
@@ -7113,7 +7547,8 @@ function ModelViewer({
|
|
|
7113
7547
|
modelLoaded,
|
|
7114
7548
|
onAnnotationClick: handleAnnotationMarkerClick,
|
|
7115
7549
|
onCloseAnnotation: closeViewingAnnotation,
|
|
7116
|
-
hideCloseButton: isGuidedTourActive
|
|
7550
|
+
hideCloseButton: isGuidedTourActive,
|
|
7551
|
+
showAnnotationOnHover
|
|
7117
7552
|
}
|
|
7118
7553
|
),
|
|
7119
7554
|
/* @__PURE__ */ jsx19(
|
|
@@ -7132,8 +7567,14 @@ function ModelViewer({
|
|
|
7132
7567
|
sceneRef,
|
|
7133
7568
|
config: cinematicConfig,
|
|
7134
7569
|
playing: cinematicPlaying,
|
|
7135
|
-
|
|
7136
|
-
|
|
7570
|
+
resetOnPlay: cinematicReset,
|
|
7571
|
+
onFinished: () => {
|
|
7572
|
+
setCinematicPlaying(false);
|
|
7573
|
+
setCinematicReset(false);
|
|
7574
|
+
},
|
|
7575
|
+
onUserInteract: () => {
|
|
7576
|
+
setCinematicPlaying(false);
|
|
7577
|
+
}
|
|
7137
7578
|
}
|
|
7138
7579
|
),
|
|
7139
7580
|
showMeasureTools && /* @__PURE__ */ jsx19(
|
|
@@ -7361,24 +7802,43 @@ function ModelViewer({
|
|
|
7361
7802
|
]
|
|
7362
7803
|
}
|
|
7363
7804
|
),
|
|
7364
|
-
cinematicConfig && /* @__PURE__ */
|
|
7805
|
+
cinematicConfig && /* @__PURE__ */ jsxs15(
|
|
7365
7806
|
"div",
|
|
7366
7807
|
{
|
|
7367
7808
|
className: "ri-cinematicTools",
|
|
7368
7809
|
style: {
|
|
7369
7810
|
top: `${topLeftRow((xrToolsVisible ? 1 : 0) + (showMeasureTools ? 1 : 0) + (showExplodeControls ? 1 : 0))}px`
|
|
7370
7811
|
},
|
|
7371
|
-
children:
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7812
|
+
children: [
|
|
7813
|
+
/* @__PURE__ */ jsx19(
|
|
7814
|
+
"button",
|
|
7815
|
+
{
|
|
7816
|
+
type: "button",
|
|
7817
|
+
className: `ri-measureButton ${cinematicPlaying ? "ri-measureButton--active" : ""}`,
|
|
7818
|
+
onClick: () => {
|
|
7819
|
+
if (cinematicPlaying) {
|
|
7820
|
+
setCinematicPlaying(false);
|
|
7821
|
+
} else {
|
|
7822
|
+
setCinematicReset(false);
|
|
7823
|
+
setCinematicPlaying(true);
|
|
7824
|
+
}
|
|
7825
|
+
},
|
|
7826
|
+
title: cinematicPlaying ? "Pause cinematic" : "Play cinematic",
|
|
7827
|
+
"aria-pressed": cinematicPlaying,
|
|
7828
|
+
children: cinematicPlaying ? /* @__PURE__ */ jsx19(Pause, { size: 16 }) : /* @__PURE__ */ jsx19(Play, { size: 16 })
|
|
7829
|
+
}
|
|
7830
|
+
),
|
|
7831
|
+
cinematicPlaying && /* @__PURE__ */ jsx19(
|
|
7832
|
+
"button",
|
|
7833
|
+
{
|
|
7834
|
+
type: "button",
|
|
7835
|
+
className: "ri-measureButton ri-measureButton--stop",
|
|
7836
|
+
onClick: handleCinematicStop,
|
|
7837
|
+
title: "Stop cinematic and reset camera",
|
|
7838
|
+
children: /* @__PURE__ */ jsx19(Square, { size: 16 })
|
|
7839
|
+
}
|
|
7840
|
+
)
|
|
7841
|
+
]
|
|
7382
7842
|
}
|
|
7383
7843
|
),
|
|
7384
7844
|
(showResetButton || showDownloadButton || showAnnotationNavigation && sortedAnnotations.length > 0) && /* @__PURE__ */ jsxs15("div", { className: "ri-buttonContainer", children: [
|
|
@@ -7481,13 +7941,38 @@ function ModelViewer({
|
|
|
7481
7941
|
"Download PNG"
|
|
7482
7942
|
]
|
|
7483
7943
|
}
|
|
7944
|
+
),
|
|
7945
|
+
/* @__PURE__ */ jsx19("div", { className: "ri-downloadMenuDivider" }),
|
|
7946
|
+
/* @__PURE__ */ jsxs15(
|
|
7947
|
+
"button",
|
|
7948
|
+
{
|
|
7949
|
+
className: "ri-downloadMenuItem",
|
|
7950
|
+
onClick: handleDownloadUvLayout,
|
|
7951
|
+
disabled: !sceneRef.current,
|
|
7952
|
+
children: [
|
|
7953
|
+
/* @__PURE__ */ jsx19(FileImage, { size: 14 }),
|
|
7954
|
+
"Download UV Layout"
|
|
7955
|
+
]
|
|
7956
|
+
}
|
|
7957
|
+
),
|
|
7958
|
+
uvExportError && /* @__PURE__ */ jsx19(
|
|
7959
|
+
"div",
|
|
7960
|
+
{
|
|
7961
|
+
className: "ri-downloadMenuNotice",
|
|
7962
|
+
role: "status",
|
|
7963
|
+
"aria-live": "polite",
|
|
7964
|
+
children: uvExportError
|
|
7965
|
+
}
|
|
7484
7966
|
)
|
|
7485
7967
|
] }),
|
|
7486
7968
|
/* @__PURE__ */ jsx19(
|
|
7487
7969
|
"button",
|
|
7488
7970
|
{
|
|
7489
7971
|
className: "ri-downloadButton",
|
|
7490
|
-
onClick: () =>
|
|
7972
|
+
onClick: () => {
|
|
7973
|
+
setUvExportError(null);
|
|
7974
|
+
setDownloadMenuOpen((v) => !v);
|
|
7975
|
+
},
|
|
7491
7976
|
disabled: isDownloading || !sceneRef.current,
|
|
7492
7977
|
title: "Download options",
|
|
7493
7978
|
children: isDownloading ? /* @__PURE__ */ jsxs15(Fragment7, { children: [
|
|
@@ -7618,7 +8103,7 @@ function ModelViewer({
|
|
|
7618
8103
|
}
|
|
7619
8104
|
|
|
7620
8105
|
// src/components/BindingBuilder/BindingBuilder.tsx
|
|
7621
|
-
import { useCallback as
|
|
8106
|
+
import { useCallback as useCallback9, useEffect as useEffect20, useMemo as useMemo16, useRef as useRef18, useState as useState25 } from "react";
|
|
7622
8107
|
import {
|
|
7623
8108
|
Redo2,
|
|
7624
8109
|
Undo2,
|
|
@@ -7665,14 +8150,16 @@ function buildCameraState(camera, controls) {
|
|
|
7665
8150
|
position: toTuple(position),
|
|
7666
8151
|
target: toTuple(target),
|
|
7667
8152
|
fov: "fov" in camera && typeof camera.fov === "number" ? camera.fov : null,
|
|
7668
|
-
zoom: "zoom" in camera && typeof camera.zoom === "number" ? camera.zoom : 1
|
|
8153
|
+
zoom: "zoom" in camera && typeof camera.zoom === "number" ? camera.zoom : 1,
|
|
8154
|
+
azimuthAngle: controls.azimuthAngle,
|
|
8155
|
+
polarAngle: controls.polarAngle
|
|
7669
8156
|
};
|
|
7670
8157
|
}
|
|
7671
8158
|
function isSameCameraState(left, right) {
|
|
7672
8159
|
if (!left) {
|
|
7673
8160
|
return false;
|
|
7674
8161
|
}
|
|
7675
|
-
return left.position[0] === right.position[0] && left.position[1] === right.position[1] && left.position[2] === right.position[2] && left.target[0] === right.target[0] && left.target[1] === right.target[1] && left.target[2] === right.target[2] && left.fov === right.fov && left.zoom === right.zoom;
|
|
8162
|
+
return left.position[0] === right.position[0] && left.position[1] === right.position[1] && left.position[2] === right.position[2] && left.target[0] === right.target[0] && left.target[1] === right.target[1] && left.target[2] === right.target[2] && left.fov === right.fov && left.zoom === right.zoom && left.azimuthAngle === right.azimuthAngle && left.polarAngle === right.polarAngle;
|
|
7676
8163
|
}
|
|
7677
8164
|
async function runSmoothCameraTransition2(controls, action, smoothTime = 0.6) {
|
|
7678
8165
|
const originalSmoothTime = controls.smoothTime;
|
|
@@ -7827,15 +8314,27 @@ function useViewerCamera() {
|
|
|
7827
8314
|
}
|
|
7828
8315
|
try {
|
|
7829
8316
|
markInitialAutoFitOverride(controls);
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
|
|
8317
|
+
const transitions = [
|
|
8318
|
+
controls.setLookAt(
|
|
8319
|
+
state.position[0],
|
|
8320
|
+
state.position[1],
|
|
8321
|
+
state.position[2],
|
|
8322
|
+
state.target[0],
|
|
8323
|
+
state.target[1],
|
|
8324
|
+
state.target[2],
|
|
8325
|
+
enableTransition
|
|
8326
|
+
)
|
|
8327
|
+
];
|
|
8328
|
+
if (state.azimuthAngle != null && state.polarAngle != null) {
|
|
8329
|
+
transitions.push(
|
|
8330
|
+
controls.rotateTo(
|
|
8331
|
+
state.azimuthAngle,
|
|
8332
|
+
state.polarAngle,
|
|
8333
|
+
enableTransition
|
|
8334
|
+
)
|
|
8335
|
+
);
|
|
8336
|
+
}
|
|
8337
|
+
await Promise.all(transitions);
|
|
7839
8338
|
const camera = controls.camera;
|
|
7840
8339
|
let projectionChanged = false;
|
|
7841
8340
|
if (state.fov !== null && "fov" in camera && typeof camera.fov === "number") {
|
|
@@ -7959,7 +8458,7 @@ function useViewerAnimations() {
|
|
|
7959
8458
|
}
|
|
7960
8459
|
|
|
7961
8460
|
// src/utils/modelFileResolver.ts
|
|
7962
|
-
import { strFromU8, strToU8, unzipSync } from "fflate";
|
|
8461
|
+
import { strFromU8, strToU8 as strToU82, unzipSync } from "fflate";
|
|
7963
8462
|
var SUPPORTED_MODEL_EXTENSIONS = [".glb", ".gltf", ".zip"];
|
|
7964
8463
|
var SUPPORTED_MODEL_MIME_TYPES = [
|
|
7965
8464
|
"model/gltf-binary",
|
|
@@ -8044,7 +8543,7 @@ async function resolveZippedGltfFile(file) {
|
|
|
8044
8543
|
try {
|
|
8045
8544
|
rewriteZipUris(gltf.buffers, rootDir, entries, urlsToRevoke);
|
|
8046
8545
|
rewriteZipUris(gltf.images, rootDir, entries, urlsToRevoke);
|
|
8047
|
-
const gltfBlob = new Blob([
|
|
8546
|
+
const gltfBlob = new Blob([strToU82(JSON.stringify(gltf))], {
|
|
8048
8547
|
type: "model/gltf+json"
|
|
8049
8548
|
});
|
|
8050
8549
|
const gltfUrl = URL.createObjectURL(gltfBlob);
|
|
@@ -8143,7 +8642,7 @@ function inferMimeType(path) {
|
|
|
8143
8642
|
}
|
|
8144
8643
|
|
|
8145
8644
|
// src/components/BindingBuilder/SceneSettings/SceneSettingsTab.tsx
|
|
8146
|
-
import { useState as
|
|
8645
|
+
import { useState as useState20, useEffect as useEffect18 } from "react";
|
|
8147
8646
|
|
|
8148
8647
|
// src/components/BindingBuilder/SceneSettings/GeneralSceneSettings.tsx
|
|
8149
8648
|
import { useState as useState14 } from "react";
|
|
@@ -8329,9 +8828,10 @@ function SliderRow({
|
|
|
8329
8828
|
max,
|
|
8330
8829
|
step,
|
|
8331
8830
|
formatValue,
|
|
8332
|
-
onChange
|
|
8831
|
+
onChange,
|
|
8832
|
+
disabled = false
|
|
8333
8833
|
}) {
|
|
8334
|
-
return /* @__PURE__ */ jsxs16("label", { className: "se-field"
|
|
8834
|
+
return /* @__PURE__ */ jsxs16("label", { className: `se-field ${disabled ? "se-field--disabled" : ""}`, children: [
|
|
8335
8835
|
/* @__PURE__ */ jsxs16("span", { className: "se-fieldLabel", children: [
|
|
8336
8836
|
label,
|
|
8337
8837
|
/* @__PURE__ */ jsx20("span", { className: "se-fieldValue", children: formatValue ? formatValue(value) : value })
|
|
@@ -8345,7 +8845,8 @@ function SliderRow({
|
|
|
8345
8845
|
max,
|
|
8346
8846
|
step,
|
|
8347
8847
|
value,
|
|
8348
|
-
onChange: (e) => onChange(Number(e.target.value))
|
|
8848
|
+
onChange: (e) => onChange(Number(e.target.value)),
|
|
8849
|
+
disabled
|
|
8349
8850
|
}
|
|
8350
8851
|
)
|
|
8351
8852
|
] });
|
|
@@ -11761,14 +12262,16 @@ function AnnotationsTab({ config, onChange }) {
|
|
|
11761
12262
|
}
|
|
11762
12263
|
|
|
11763
12264
|
// src/components/BindingBuilder/SceneSettings/CinematicTab.tsx
|
|
11764
|
-
import {
|
|
11765
|
-
import {
|
|
12265
|
+
import { useState as useState19, useCallback as useCallback7 } from "react";
|
|
12266
|
+
import { ArrowDown, ArrowUp, MapPin as MapPin2, Plus as Plus2, Trash2 as Trash24, Settings } from "lucide-react";
|
|
12267
|
+
import { Fragment as Fragment10, jsx as jsx31, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
11766
12268
|
var DEFAULT_CINEMATIC = {
|
|
11767
12269
|
enabled: false,
|
|
11768
12270
|
waypoints: [],
|
|
11769
12271
|
loop: true,
|
|
11770
12272
|
autoPlay: false
|
|
11771
12273
|
};
|
|
12274
|
+
var DEFAULT_SEGMENT_DURATION = 3.5;
|
|
11772
12275
|
function CinematicTab({
|
|
11773
12276
|
config,
|
|
11774
12277
|
onChange,
|
|
@@ -11777,31 +12280,76 @@ function CinematicTab({
|
|
|
11777
12280
|
}) {
|
|
11778
12281
|
const cinematic = config.cinematic ?? DEFAULT_CINEMATIC;
|
|
11779
12282
|
const { waypoints } = cinematic;
|
|
11780
|
-
const
|
|
12283
|
+
const calculateTotalDuration = useCallback7((wps, loop) => {
|
|
12284
|
+
if (wps.length < 2) return 0;
|
|
12285
|
+
let total = 0;
|
|
12286
|
+
for (let i = 0; i < wps.length - 1; i++) {
|
|
12287
|
+
const segDur = wps[i].segmentDuration ?? DEFAULT_SEGMENT_DURATION;
|
|
12288
|
+
total += segDur;
|
|
12289
|
+
}
|
|
12290
|
+
if (loop && wps.length > 1) {
|
|
12291
|
+
total += wps[wps.length - 1].segmentDuration ?? DEFAULT_SEGMENT_DURATION;
|
|
12292
|
+
}
|
|
12293
|
+
return Math.max(total, 0);
|
|
12294
|
+
}, []);
|
|
12295
|
+
const update = useCallback7((patch) => {
|
|
11781
12296
|
onChange({ ...config, cinematic: { ...cinematic, ...patch } });
|
|
11782
|
-
};
|
|
11783
|
-
const addWaypoint = () => {
|
|
12297
|
+
}, [config, cinematic, onChange]);
|
|
12298
|
+
const addWaypoint = useCallback7(() => {
|
|
11784
12299
|
if (!cameraState) return;
|
|
12300
|
+
const newWaypoint = {
|
|
12301
|
+
position: [...cameraState.position],
|
|
12302
|
+
target: [...cameraState.target],
|
|
12303
|
+
...cameraState.azimuthAngle != null ? { azimuthAngle: cameraState.azimuthAngle } : {},
|
|
12304
|
+
...cameraState.polarAngle != null ? { polarAngle: cameraState.polarAngle } : {},
|
|
12305
|
+
segmentDuration: DEFAULT_SEGMENT_DURATION
|
|
12306
|
+
};
|
|
12307
|
+
const newWaypoints = [...waypoints, newWaypoint];
|
|
12308
|
+
const newDuration = calculateTotalDuration(newWaypoints, cinematic.loop);
|
|
11785
12309
|
update({
|
|
11786
|
-
waypoints:
|
|
11787
|
-
|
|
11788
|
-
{
|
|
11789
|
-
position: [...cameraState.position],
|
|
11790
|
-
target: [...cameraState.target]
|
|
11791
|
-
}
|
|
11792
|
-
]
|
|
12310
|
+
waypoints: newWaypoints,
|
|
12311
|
+
duration: newDuration
|
|
11793
12312
|
});
|
|
11794
|
-
};
|
|
11795
|
-
const deleteWaypoint = (index) => {
|
|
11796
|
-
|
|
11797
|
-
|
|
11798
|
-
|
|
12313
|
+
}, [cameraState, waypoints, cinematic.loop, calculateTotalDuration, update]);
|
|
12314
|
+
const deleteWaypoint = useCallback7((index) => {
|
|
12315
|
+
const newWaypoints = waypoints.filter((_, i) => i !== index);
|
|
12316
|
+
const newDuration = calculateTotalDuration(newWaypoints, cinematic.loop);
|
|
12317
|
+
update({
|
|
12318
|
+
waypoints: newWaypoints,
|
|
12319
|
+
duration: newDuration
|
|
12320
|
+
});
|
|
12321
|
+
}, [waypoints, cinematic.loop, calculateTotalDuration, update]);
|
|
12322
|
+
const moveWaypoint = useCallback7((index, delta) => {
|
|
11799
12323
|
const next = index + delta;
|
|
11800
12324
|
if (next < 0 || next >= waypoints.length) return;
|
|
11801
12325
|
const reordered = [...waypoints];
|
|
11802
12326
|
[reordered[index], reordered[next]] = [reordered[next], reordered[index]];
|
|
11803
|
-
|
|
11804
|
-
|
|
12327
|
+
const newDuration = calculateTotalDuration(reordered, cinematic.loop);
|
|
12328
|
+
update({
|
|
12329
|
+
waypoints: reordered,
|
|
12330
|
+
duration: newDuration
|
|
12331
|
+
});
|
|
12332
|
+
}, [waypoints, cinematic.loop, calculateTotalDuration, update]);
|
|
12333
|
+
const updateWaypoint = useCallback7((index, updates) => {
|
|
12334
|
+
const newWaypoints = waypoints.map((wp, i) => {
|
|
12335
|
+
if (i === index) {
|
|
12336
|
+
return { ...wp, ...updates };
|
|
12337
|
+
}
|
|
12338
|
+
return wp;
|
|
12339
|
+
});
|
|
12340
|
+
const newDuration = calculateTotalDuration(newWaypoints, cinematic.loop);
|
|
12341
|
+
update({
|
|
12342
|
+
waypoints: newWaypoints,
|
|
12343
|
+
duration: newDuration
|
|
12344
|
+
});
|
|
12345
|
+
}, [waypoints, cinematic.loop, calculateTotalDuration, update]);
|
|
12346
|
+
const [customizeDurations, setCustomizeDurations] = useState19(false);
|
|
12347
|
+
const hasSegmentDurations = waypoints.some((w) => w.segmentDuration !== void 0);
|
|
12348
|
+
const getDuration = useCallback7((index) => {
|
|
12349
|
+
if (index >= waypoints.length - 1) return DEFAULT_SEGMENT_DURATION;
|
|
12350
|
+
return waypoints[index]?.segmentDuration ?? DEFAULT_SEGMENT_DURATION;
|
|
12351
|
+
}, [waypoints]);
|
|
12352
|
+
const hasEnoughWaypoints = waypoints.length >= 2;
|
|
11805
12353
|
return /* @__PURE__ */ jsxs27(Section, { title: "Cinematic Camera", children: [
|
|
11806
12354
|
/* @__PURE__ */ jsx31("p", { className: "se-cinematicHint", children: "Frame the model in the viewer, then capture it as a waypoint. The camera glides through the waypoints in order \u2014 enable and press play in the viewer to preview." }),
|
|
11807
12355
|
/* @__PURE__ */ jsxs27(
|
|
@@ -11818,62 +12366,165 @@ function CinematicTab({
|
|
|
11818
12366
|
]
|
|
11819
12367
|
}
|
|
11820
12368
|
),
|
|
11821
|
-
|
|
11822
|
-
/* @__PURE__ */
|
|
11823
|
-
/* @__PURE__ */ jsxs27("div", { className: "se-waypointBody", children: [
|
|
11824
|
-
/* @__PURE__ */ jsxs27("div", { className: "se-waypointCoords", children: [
|
|
11825
|
-
"pos [",
|
|
11826
|
-
waypoint.position.map((n) => n.toFixed(1)).join(", "),
|
|
11827
|
-
"]"
|
|
11828
|
-
] }),
|
|
11829
|
-
/* @__PURE__ */ jsxs27("div", { className: "se-waypointCoords", children: [
|
|
11830
|
-
"look [",
|
|
11831
|
-
waypoint.target.map((n) => n.toFixed(1)).join(", "),
|
|
11832
|
-
"]"
|
|
11833
|
-
] })
|
|
11834
|
-
] }),
|
|
11835
|
-
/* @__PURE__ */ jsxs27("div", { className: "se-waypointActions", children: [
|
|
11836
|
-
/* @__PURE__ */ jsx31(
|
|
11837
|
-
"button",
|
|
11838
|
-
{
|
|
11839
|
-
className: "se-waypointAction",
|
|
11840
|
-
onClick: () => onPreviewWaypoint?.(waypoint),
|
|
11841
|
-
title: "Fly camera to this waypoint",
|
|
11842
|
-
disabled: !onPreviewWaypoint,
|
|
11843
|
-
children: /* @__PURE__ */ jsx31(MapPin2, { size: 15 })
|
|
11844
|
-
}
|
|
11845
|
-
),
|
|
12369
|
+
/* @__PURE__ */ jsxs27("div", { className: "se-customizeToggle", children: [
|
|
12370
|
+
/* @__PURE__ */ jsxs27("label", { className: "se-toggleLabel", children: [
|
|
11846
12371
|
/* @__PURE__ */ jsx31(
|
|
11847
|
-
"
|
|
11848
|
-
{
|
|
11849
|
-
className: "se-waypointAction",
|
|
11850
|
-
onClick: () => moveWaypoint(index, -1),
|
|
11851
|
-
title: "Move up",
|
|
11852
|
-
disabled: index === 0,
|
|
11853
|
-
children: /* @__PURE__ */ jsx31(ArrowUp, { size: 15 })
|
|
11854
|
-
}
|
|
11855
|
-
),
|
|
11856
|
-
/* @__PURE__ */ jsx31(
|
|
11857
|
-
"button",
|
|
12372
|
+
"input",
|
|
11858
12373
|
{
|
|
11859
|
-
|
|
11860
|
-
|
|
11861
|
-
|
|
11862
|
-
disabled: index === waypoints.length - 1,
|
|
11863
|
-
children: /* @__PURE__ */ jsx31(ArrowDown, { size: 15 })
|
|
12374
|
+
type: "checkbox",
|
|
12375
|
+
checked: customizeDurations,
|
|
12376
|
+
onChange: (e) => setCustomizeDurations(e.target.checked)
|
|
11864
12377
|
}
|
|
11865
12378
|
),
|
|
11866
|
-
/* @__PURE__ */ jsx31(
|
|
11867
|
-
|
|
11868
|
-
|
|
11869
|
-
|
|
11870
|
-
|
|
11871
|
-
|
|
11872
|
-
|
|
11873
|
-
|
|
11874
|
-
|
|
12379
|
+
/* @__PURE__ */ jsx31(Settings, { size: 14 }),
|
|
12380
|
+
/* @__PURE__ */ jsx31("span", { children: "Customize durations between waypoints" })
|
|
12381
|
+
] }),
|
|
12382
|
+
customizeDurations && /* @__PURE__ */ jsxs27("span", { className: "se-toggleHint", children: [
|
|
12383
|
+
"Set duration for each segment between waypoints: 1\u21922, 2\u21923, etc. Default: ",
|
|
12384
|
+
DEFAULT_SEGMENT_DURATION,
|
|
12385
|
+
"s"
|
|
12386
|
+
] })
|
|
12387
|
+
] }),
|
|
12388
|
+
waypoints.length === 0 ? /* @__PURE__ */ jsx31("div", { className: "bb-emptyAnnotations", children: "No waypoints yet. Position the camera and add your first one." }) : /* @__PURE__ */ jsxs27(Fragment10, { children: [
|
|
12389
|
+
/* @__PURE__ */ jsx31("div", { className: "se-waypointList", children: waypoints.map((waypoint, index) => {
|
|
12390
|
+
const dur = getDuration(index);
|
|
12391
|
+
const isLast = index === waypoints.length - 1;
|
|
12392
|
+
const isCustom = waypoint.segmentDuration !== void 0 && waypoint.segmentDuration !== DEFAULT_SEGMENT_DURATION;
|
|
12393
|
+
return /* @__PURE__ */ jsxs27("div", { children: [
|
|
12394
|
+
/* @__PURE__ */ jsxs27("div", { className: "se-waypointCard", children: [
|
|
12395
|
+
/* @__PURE__ */ jsx31("div", { className: "se-waypointBadge", children: index + 1 }),
|
|
12396
|
+
/* @__PURE__ */ jsxs27("div", { className: "se-waypointBody", children: [
|
|
12397
|
+
/* @__PURE__ */ jsxs27("div", { className: "se-waypointCoords", children: [
|
|
12398
|
+
"pos [",
|
|
12399
|
+
waypoint.position.map((n) => n.toFixed(1)).join(", "),
|
|
12400
|
+
"]"
|
|
12401
|
+
] }),
|
|
12402
|
+
/* @__PURE__ */ jsxs27("div", { className: "se-waypointCoords", children: [
|
|
12403
|
+
"look [",
|
|
12404
|
+
waypoint.target.map((n) => n.toFixed(1)).join(", "),
|
|
12405
|
+
"]"
|
|
12406
|
+
] }),
|
|
12407
|
+
customizeDurations && !isLast && /* @__PURE__ */ jsxs27("div", { className: `se-customBadge ${isCustom ? "se-customBadge--custom" : "se-customBadge--default"}`, children: [
|
|
12408
|
+
dur.toFixed(1),
|
|
12409
|
+
"s",
|
|
12410
|
+
!isCustom && " (default)"
|
|
12411
|
+
] })
|
|
12412
|
+
] }),
|
|
12413
|
+
/* @__PURE__ */ jsxs27("div", { className: "se-waypointActions", children: [
|
|
12414
|
+
/* @__PURE__ */ jsx31(
|
|
12415
|
+
"button",
|
|
12416
|
+
{
|
|
12417
|
+
className: "se-waypointAction",
|
|
12418
|
+
onClick: () => onPreviewWaypoint?.(waypoint),
|
|
12419
|
+
title: "Fly camera to this waypoint",
|
|
12420
|
+
disabled: !onPreviewWaypoint,
|
|
12421
|
+
children: /* @__PURE__ */ jsx31(MapPin2, { size: 15 })
|
|
12422
|
+
}
|
|
12423
|
+
),
|
|
12424
|
+
/* @__PURE__ */ jsx31(
|
|
12425
|
+
"button",
|
|
12426
|
+
{
|
|
12427
|
+
className: "se-waypointAction",
|
|
12428
|
+
onClick: () => moveWaypoint(index, -1),
|
|
12429
|
+
title: "Move up",
|
|
12430
|
+
disabled: index === 0,
|
|
12431
|
+
children: /* @__PURE__ */ jsx31(ArrowUp, { size: 15 })
|
|
12432
|
+
}
|
|
12433
|
+
),
|
|
12434
|
+
/* @__PURE__ */ jsx31(
|
|
12435
|
+
"button",
|
|
12436
|
+
{
|
|
12437
|
+
className: "se-waypointAction",
|
|
12438
|
+
onClick: () => moveWaypoint(index, 1),
|
|
12439
|
+
title: "Move down",
|
|
12440
|
+
disabled: isLast,
|
|
12441
|
+
children: /* @__PURE__ */ jsx31(ArrowDown, { size: 15 })
|
|
12442
|
+
}
|
|
12443
|
+
),
|
|
12444
|
+
/* @__PURE__ */ jsx31(
|
|
12445
|
+
"button",
|
|
12446
|
+
{
|
|
12447
|
+
className: "se-waypointAction se-waypointAction--delete",
|
|
12448
|
+
onClick: () => deleteWaypoint(index),
|
|
12449
|
+
title: "Delete waypoint",
|
|
12450
|
+
children: /* @__PURE__ */ jsx31(Trash24, { size: 15 })
|
|
12451
|
+
}
|
|
12452
|
+
)
|
|
12453
|
+
] })
|
|
12454
|
+
] }),
|
|
12455
|
+
customizeDurations && !isLast && /* @__PURE__ */ jsxs27("div", { className: "se-segmentBetween", children: [
|
|
12456
|
+
/* @__PURE__ */ jsxs27("div", { className: "se-segmentLabel", children: [
|
|
12457
|
+
index + 1,
|
|
12458
|
+
" \u2192 ",
|
|
12459
|
+
index + 2,
|
|
12460
|
+
!waypoint.segmentDuration && " (default)"
|
|
12461
|
+
] }),
|
|
12462
|
+
/* @__PURE__ */ jsxs27("div", { className: "se-segmentControls", children: [
|
|
12463
|
+
/* @__PURE__ */ jsx31("span", { className: "se-durationLabel", children: "Duration:" }),
|
|
12464
|
+
/* @__PURE__ */ jsx31(
|
|
12465
|
+
"input",
|
|
12466
|
+
{
|
|
12467
|
+
type: "range",
|
|
12468
|
+
className: "se-segmentSlider",
|
|
12469
|
+
min: 1,
|
|
12470
|
+
max: 20,
|
|
12471
|
+
step: 0.5,
|
|
12472
|
+
value: dur,
|
|
12473
|
+
onChange: (e) => {
|
|
12474
|
+
const val = parseFloat(e.target.value);
|
|
12475
|
+
updateWaypoint(index, { segmentDuration: val });
|
|
12476
|
+
}
|
|
12477
|
+
}
|
|
12478
|
+
),
|
|
12479
|
+
/* @__PURE__ */ jsxs27("span", { className: "se-segmentValue", children: [
|
|
12480
|
+
dur.toFixed(1),
|
|
12481
|
+
"s"
|
|
12482
|
+
] }),
|
|
12483
|
+
/* @__PURE__ */ jsxs27("div", { className: "se-presetButtons", children: [
|
|
12484
|
+
[2, 4, 6, 8].map((preset) => /* @__PURE__ */ jsxs27(
|
|
12485
|
+
"button",
|
|
12486
|
+
{
|
|
12487
|
+
className: "se-presetButton",
|
|
12488
|
+
onClick: () => updateWaypoint(index, { segmentDuration: preset }),
|
|
12489
|
+
children: [
|
|
12490
|
+
preset,
|
|
12491
|
+
"s"
|
|
12492
|
+
]
|
|
12493
|
+
},
|
|
12494
|
+
`preset-${index}-${preset}`
|
|
12495
|
+
)),
|
|
12496
|
+
/* @__PURE__ */ jsx31(
|
|
12497
|
+
"button",
|
|
12498
|
+
{
|
|
12499
|
+
className: "se-presetButton se-presetButton--default",
|
|
12500
|
+
onClick: () => {
|
|
12501
|
+
const newWaypoints = waypoints.map((wp, i) => {
|
|
12502
|
+
if (i === index) {
|
|
12503
|
+
const { segmentDuration, ...rest } = wp;
|
|
12504
|
+
return rest;
|
|
12505
|
+
}
|
|
12506
|
+
return wp;
|
|
12507
|
+
});
|
|
12508
|
+
const newDuration = calculateTotalDuration(newWaypoints, cinematic.loop);
|
|
12509
|
+
update({
|
|
12510
|
+
waypoints: newWaypoints,
|
|
12511
|
+
duration: newDuration
|
|
12512
|
+
});
|
|
12513
|
+
},
|
|
12514
|
+
children: "Reset"
|
|
12515
|
+
}
|
|
12516
|
+
)
|
|
12517
|
+
] })
|
|
12518
|
+
] })
|
|
12519
|
+
] })
|
|
12520
|
+
] }, `waypoint-wrapper-${index}`);
|
|
12521
|
+
}) }),
|
|
12522
|
+
/* @__PURE__ */ jsxs27("div", { className: "se-totalDuration", children: [
|
|
12523
|
+
/* @__PURE__ */ jsx31("span", { className: "se-totalLabel", children: "Total duration:" }),
|
|
12524
|
+
/* @__PURE__ */ jsx31("span", { className: "se-totalValue", children: hasEnoughWaypoints ? (cinematic.duration ?? calculateTotalDuration(waypoints, cinematic.loop)).toFixed(1) + "s" : "-" }),
|
|
12525
|
+
!hasEnoughWaypoints && /* @__PURE__ */ jsx31("span", { className: "se-totalHint", children: "(need at least 2 waypoints)" })
|
|
11875
12526
|
] })
|
|
11876
|
-
] }
|
|
12527
|
+
] }),
|
|
11877
12528
|
/* @__PURE__ */ jsx31(
|
|
11878
12529
|
ToggleRow,
|
|
11879
12530
|
{
|
|
@@ -11887,7 +12538,11 @@ function CinematicTab({
|
|
|
11887
12538
|
{
|
|
11888
12539
|
label: "Loop",
|
|
11889
12540
|
checked: cinematic.loop,
|
|
11890
|
-
onChange: (loop) =>
|
|
12541
|
+
onChange: (loop) => {
|
|
12542
|
+
const newLoop = loop;
|
|
12543
|
+
const newDuration = calculateTotalDuration(waypoints, newLoop);
|
|
12544
|
+
update({ loop: newLoop, duration: newDuration });
|
|
12545
|
+
}
|
|
11891
12546
|
}
|
|
11892
12547
|
),
|
|
11893
12548
|
/* @__PURE__ */ jsx31(
|
|
@@ -11898,11 +12553,11 @@ function CinematicTab({
|
|
|
11898
12553
|
onChange: (autoPlay) => update({ autoPlay })
|
|
11899
12554
|
}
|
|
11900
12555
|
),
|
|
11901
|
-
/* @__PURE__ */ jsx31(
|
|
12556
|
+
!customizeDurations && hasEnoughWaypoints && /* @__PURE__ */ jsx31(
|
|
11902
12557
|
SliderRow,
|
|
11903
12558
|
{
|
|
11904
12559
|
label: "Duration",
|
|
11905
|
-
value: cinematic.duration ??
|
|
12560
|
+
value: cinematic.duration ?? calculateTotalDuration(waypoints, cinematic.loop),
|
|
11906
12561
|
min: 4,
|
|
11907
12562
|
max: 120,
|
|
11908
12563
|
step: 1,
|
|
@@ -11914,7 +12569,7 @@ function CinematicTab({
|
|
|
11914
12569
|
}
|
|
11915
12570
|
|
|
11916
12571
|
// src/styles/SceneSettingsTab.css
|
|
11917
|
-
styleInject('.se-tabContent {\n display: flex;\n flex-direction: column;\n}\n.se-tabContent * {\n box-sizing: border-box;\n}\n.se-subTabRow {\n display: flex;\n justify-content: space-between;\n gap: 6px;\n padding-bottom: 16px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.1);\n margin-bottom: 4px;\n}\n.se-subTab {\n padding: 8px 12px;\n display: flex;\n justify-items: center;\n border-radius: 10px;\n border: 1px solid rgba(148, 163, 184, 0.18);\n background: rgba(255, 255, 255, 0.04);\n color: #b3bfd2;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n cursor: pointer;\n transition:\n background-color 0.18s ease,\n border-color 0.18s ease,\n color 0.18s ease;\n}\n.se-subTab:hover {\n background: rgba(255, 255, 255, 0.08);\n border-color: rgba(148, 163, 184, 0.32);\n}\n.se-subTab--active {\n background: rgba(99, 102, 241, 0.18);\n border-color: rgba(99, 102, 241, 0.4);\n color: #f8fafc;\n}\n.se-generalContent {\n display: flex;\n flex-direction: column;\n padding-top: 0;\n}\n.se-placeholder {\n font-size: 13px;\n color: #93a4c0;\n}\n.se-section {\n margin-bottom: 10px;\n border-radius: 7px;\n background: rgba(255, 255, 255, 0.03);\n border: 1px solid rgba(148, 163, 184, 0.12);\n overflow: hidden;\n padding: 0;\n}\n.se-section:last-child {\n margin-bottom: 0;\n}\n.se-sectionTitle {\n margin: 0;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: #c3cbdb;\n padding: 11px 14px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.1);\n}\n.se-sectionBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 14px;\n}\n.se-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n}\n.se-fieldInline {\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n.se-fieldLabel {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n font-size: 12px;\n font-weight: 700;\n color: #d7dee9;\n}\n.se-fieldValue {\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n color: #93a4c0;\n}\n.se-slider {\n width: 100%;\n accent-color: #2aa8d8;\n cursor: pointer;\n}\n.se-checkbox {\n width: 18px;\n height: 18px;\n accent-color: #2aa8d8;\n cursor: pointer;\n}\n.se-select,\n.se-textInput,\n.se-numberInput {\n background: rgba(15, 23, 42, 0.28);\n border: 1px solid rgba(148, 163, 184, 0.2);\n border-radius: 7px;\n color: #f8fafc;\n font-size: 13px;\n padding: 11px 13px;\n outline: none;\n}\n.se-select:focus,\n.se-textInput:focus,\n.se-numberInput:focus {\n outline: none;\n border-color: rgba(42, 168, 216, 0.65);\n box-shadow: 0 0 0 4px rgba(42, 168, 216, 0.12);\n}\n.se-select {\n min-width: 130px;\n text-transform: capitalize;\n}\n.se-textInput {\n width: 100%;\n}\n.se-numberInput {\n width: 100%;\n}\n.se-swatch {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n min-width: 96px;\n height: 30px;\n padding: 0 8px;\n border-radius: 8px;\n border: 1px solid rgba(148, 163, 184, 0.35);\n cursor: pointer;\n box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);\n}\n.se-swatchCode {\n font-size: 11px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n color: #fff;\n mix-blend-mode: difference;\n}\n.se-colorPicker {\n margin-top: 6px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n width: fit-content;\n}\n.se-colorPicker .react-colorful {\n width: 100%;\n height: 140px;\n}\n.se-inlineColorPicker .react-colorful {\n width: 100%;\n height: 240px;\n}\n.se-colorPickerPopover {\n z-index: 1000;\n padding: 10px;\n border-radius: 8px;\n background: #1b2333;\n border: 1px solid rgba(148, 163, 184, 0.25);\n box-shadow: 0 12px 28px rgba(0, 0, 0, 0.4);\n}\n.se-colorPickerPopover .react-colorful {\n width: 200px;\n height: 160px;\n}\n.se-vectorRow {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 8px;\n}\n.se-vectorAxis {\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-size: 11px;\n color: #93a4c0;\n}\n.se-panel {\n margin-bottom: 10px;\n overflow: hidden;\n border: 1px solid rgba(148, 163, 184, 0.12);\n border-radius: 7px;\n background: rgba(255, 255, 255, 0.03);\n}\n.se-panel:last-child {\n margin-bottom: 0;\n}\n.se-panelHeader {\n display: flex;\n width: 100%;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n border: none;\n background: rgba(255, 255, 255, 0.03);\n padding: 11px 14px;\n color: #e5e7eb;\n cursor: pointer;\n text-align: left;\n}\n.se-panelHeader:hover {\n background: rgba(255, 255, 255, 0.06);\n}\n.se-panelHeaderMain {\n display: flex;\n min-width: 0;\n align-items: center;\n gap: 8px;\n}\n.se-panelChevron {\n flex-shrink: 0;\n color: #8b97ab;\n transform: rotate(-90deg);\n transition: transform 0.18s ease;\n}\n.se-panelChevron--open {\n transform: rotate(0deg);\n}\n.se-panelTitle {\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: #c3cbdb;\n}\n.se-panelHelp {\n display: inline-flex;\n flex-shrink: 0;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n margin-left: 2px;\n border-radius: 999px;\n color: #b9c0ca;\n cursor: help;\n transition:\n background 0.18s ease,\n color 0.18s ease,\n transform 0.18s ease;\n}\n.se-panelHelp:hover {\n background: rgba(255, 255, 255, 0.08);\n color: #eef2ff;\n transform: translateY(-0.5px);\n}\n.se-panelTrailing {\n display: flex;\n align-items: center;\n}\n.se-panelBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 14px;\n border-top: 1px solid rgba(148, 163, 184, 0.1);\n background: transparent;\n}\n.se-iconButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 30px;\n height: 30px;\n border: none;\n border-radius: 8px;\n background: rgba(255, 255, 255, 0.08);\n color: #d7dde8;\n cursor: pointer;\n}\n.se-iconButton:hover {\n background: rgba(255, 255, 255, 0.12);\n}\n.se-axisStepperGrid {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 12px;\n}\n.se-axisStepper {\n display: grid;\n grid-template-columns: 40px 1fr 40px;\n align-items: center;\n min-height: 30px;\n border-radius: 10px;\n background: rgba(17, 24, 39, 0.62);\n border: 1px solid rgba(255, 255, 255, 0.05);\n}\n.se-axisStepperButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n border: none;\n background: transparent;\n color: #f3f4f6;\n cursor: pointer;\n}\n.se-axisStepperButton:hover {\n background: rgba(255, 255, 255, 0.04);\n}\n.se-axisStepperLabel {\n text-align: center;\n font-size: 15px;\n color: #f3f4f6;\n}\n.se-segmentedControl {\n display: grid;\n grid-auto-flow: column;\n overflow: hidden;\n border-radius: 10px;\n background: rgba(17, 24, 39, 0.62);\n}\n.se-segmentedOption {\n min-height: 35px;\n border: none;\n background: transparent;\n color: #d1d5db;\n font-size: 14px;\n text-transform: uppercase;\n cursor: pointer;\n}\n.se-segmentedOption--active {\n background: #2aa8d8;\n color: #fff;\n}\n.se-helperCard {\n padding: 32px;\n text-align: center;\n background: rgba(15, 23, 42, 0.44);\n border-radius: 10px;\n border: 1px solid rgba(148, 163, 184, 0.15);\n color: #94a3b8;\n font-size: 14px;\n}\n.se-helperCard strong {\n font-size: 14px;\n}\n.se-helperCard span {\n font-size: 13px;\n line-height: 1.4;\n color: #c7d0df;\n}\n.se-switch {\n position: relative;\n display: inline-flex;\n align-items: center;\n}\n.se-switch input[type=checkbox] {\n position: absolute;\n left: 0;\n top: 0;\n z-index: 10;\n width: 100%;\n height: 100%;\n cursor: pointer;\n opacity: 0;\n}\n.se-switchTrack {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n.se-switchTrack::before {\n content: "";\n width: 36px;\n height: 20px;\n background: #4b5563;\n display: inline-block;\n border-radius: 20px;\n transition: background-color 0.2s ease-in;\n}\n.se-switchThumb {\n position: absolute;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n left: 2px;\n top: 2px;\n z-index: 2;\n background: #fff;\n box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);\n transition: 0.2s ease-in;\n}\n.se-switch input[type=checkbox]:hover + .se-switchTrack .se-switchThumb {\n box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.2), 0 3px 8px 0 rgba(0, 0, 0, 0.1);\n}\n.se-switch input[type=checkbox]:checked + .se-switchTrack::before {\n background: #376fcb;\n}\n.se-switch input[type=checkbox]:checked + .se-switchTrack .se-switchThumb {\n background: #4285f4;\n left: 18px;\n}\n.se-switchLabel {\n font-size: 12px;\n font-weight: 700;\n letter-spacing: 0.06em;\n color: #e5e7eb;\n}\n.se-switch input[type=checkbox]:checked ~ .se-switchTrack .se-switchLabel {\n color: #2aa8d8;\n}\n.se-lightList {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.se-lightRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px solid rgba(148, 163, 184, 0.1);\n background: rgba(255, 255, 255, 0.03);\n cursor: pointer;\n transition: background-color 0.15s ease, border-color 0.15s ease;\n}\n.se-lightRow:hover {\n background: rgba(255, 255, 255, 0.06);\n}\n.se-lightRow--active {\n background: rgba(42, 168, 216, 0.12);\n border-color: rgba(42, 168, 216, 0.35);\n}\n.se-lightRowLabel {\n font-size: 13px;\n color: #d8e0ee;\n}\n.se-lightActions {\n display: flex;\n gap: 8px;\n}\n.se-lightProps {\n display: flex;\n flex-direction: column;\n gap: 14px;\n padding-top: 4px;\n border-top: 1px solid rgba(148, 163, 184, 0.1);\n}\n.se-animationList {\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\n.se-animationCard {\n display: flex;\n flex-direction: column;\n gap: 14px;\n padding: 16px;\n border-radius: 14px;\n border: 1px solid rgba(148, 163, 184, 0.16);\n background: rgba(15, 23, 42, 0.48);\n}\n.se-animationCard--active {\n border-color: rgba(42, 168, 216, 0.45);\n box-shadow: inset 0 0 0 1px rgba(42, 168, 216, 0.16);\n}\n.se-animationHeader {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 12px;\n}\n.se-animationHeading {\n display: flex;\n min-width: 0;\n flex-direction: column;\n gap: 6px;\n}\n.se-animationTitleRow {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n}\n.se-animationStatus,\n.se-animationBadge {\n display: inline-flex;\n align-items: center;\n min-height: 22px;\n padding: 0 8px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n}\n.se-animationStatus {\n background: rgba(42, 168, 216, 0.16);\n color: #9fe8ff;\n}\n.se-animationBadge {\n background: rgba(99, 102, 241, 0.18);\n color: #d9ddff;\n}\n.se-animationSource {\n font-size: 12px;\n color: #93a4c0;\n word-break: break-word;\n}\n.se-animationMeta {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n.se-animationMeta span {\n border-radius: 999px;\n background: rgba(255, 255, 255, 0.06);\n padding: 4px 9px;\n font-size: 12px;\n color: #c6d0e2;\n}\n.se-animationDangerButton,\n.se-animationButton,\n.se-animationAutoplayButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n border: 1px solid rgba(148, 163, 184, 0.2);\n border-radius: 10px;\n background: rgba(255, 255, 255, 0.04);\n color: #e2e8f0;\n cursor: pointer;\n transition:\n background-color 0.18s ease,\n border-color 0.18s ease,\n color 0.18s ease;\n}\n.se-animationButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.se-animationDangerButton {\n width: 36px;\n height: 36px;\n color: #fca5a5;\n}\n.se-animationDangerButton:hover,\n.se-animationButton:hover,\n.se-animationAutoplayButton:hover {\n background: rgba(255, 255, 255, 0.08);\n border-color: rgba(148, 163, 184, 0.34);\n}\n.se-animationButtonRow {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n}\n.se-animationButton {\n min-height: 36px;\n padding: 0 14px;\n font-size: 13px;\n font-weight: 600;\n}\n.se-animationButton--icon {\n flex: 1;\n min-width: 0;\n padding: 0;\n height: 38px;\n}\n.se-animationSeekRow {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.se-animationSeek {\n flex: 1;\n min-width: 0;\n}\n.se-animationSeekTime {\n flex-shrink: 0;\n min-width: 86px;\n text-align: right;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n color: #97a3b6;\n}\n.se-animationGrid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 12px;\n}\n.se-animationSpeedRow {\n display: grid;\n grid-template-columns: minmax(0, 1fr) 88px;\n gap: 10px;\n align-items: center;\n}\n.se-animationSpeedInput {\n text-align: right;\n}\n.se-animationAutoplayButton {\n align-self: flex-start;\n min-height: 36px;\n padding: 0 14px;\n font-size: 12px;\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n}\n.se-animationAutoplayButton--active {\n background: rgba(42, 168, 216, 0.18);\n border-color: rgba(42, 168, 216, 0.42);\n color: #dff8ff;\n}\n.se-removedAnimations {\n overflow: hidden;\n border-radius: 12px;\n border: 1px solid rgba(148, 163, 184, 0.14);\n background: rgba(255, 255, 255, 0.03);\n}\n.se-removedAnimationList {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 14px;\n}\n.se-removedAnimationRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n border-radius: 10px;\n background: rgba(15, 23, 42, 0.42);\n padding: 12px;\n}\n.se-removedAnimationName {\n font-size: 13px;\n font-weight: 600;\n color: #e2e8f0;\n}\n@media (max-width: 900px) {\n .se-animationGrid {\n grid-template-columns: 1fr;\n }\n .se-animationSpeedRow {\n grid-template-columns: 1fr;\n }\n}\n.bb-viewSpotOverlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.6);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n backdrop-filter: blur(4px);\n animation: bb-fadeIn 0.2s ease;\n}\n.bb-viewSpotPopup {\n background: #1e1e20;\n border-radius: 12px;\n max-width: 480px;\n width: 90%;\n max-height: 80vh;\n overflow-y: auto;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);\n border: 1px solid #333335;\n animation: bb-slideUp 0.25s ease;\n}\n.bb-viewSpotHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px;\n border-bottom: 1px solid #2a2a2c;\n}\n.bb-viewSpotTitle {\n font-size: 18px;\n font-weight: 600;\n color: #e8e8ea;\n margin: 0;\n}\n.bb-viewSpotClose {\n background: none;\n border: none;\n color: #888;\n font-size: 24px;\n cursor: pointer;\n padding: 0 4px;\n line-height: 1;\n transition: color 0.2s;\n}\n.bb-viewSpotClose:hover {\n color: #e8e8ea;\n}\n.bb-viewSpotBody {\n padding: 24px;\n}\n.bb-viewSpotDescription {\n color: #c8c8ca;\n font-size: 14px;\n line-height: 1.6;\n margin-bottom: 20px;\n white-space: pre-wrap;\n word-break: break-word;\n}\n.bb-viewSpotPosition {\n background: #141416;\n border-radius: 8px;\n padding: 12px 16px;\n margin-bottom: 24px;\n font-size: 13px;\n color: #a0a0a2;\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n.bb-viewSpotLabel {\n font-weight: 500;\n color: #888;\n}\n.bb-viewSpotCode {\n background: #0a0a0c;\n padding: 4px 10px;\n border-radius: 4px;\n font-family:\n "Monaco",\n "Menlo",\n monospace;\n font-size: 12px;\n color: #8be9fd;\n}\n.bb-viewSpotActions {\n display: flex;\n justify-content: flex-end;\n gap: 10px;\n padding-top: 4px;\n}\n.bb-editAnnotationOverlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.6);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n}\n.bb-editAnnotationPopup {\n background: #0f141e;\n border-radius: 16px;\n padding: 24px;\n min-width: 200px;\n max-width: 400px;\n width: 90%;\n border: 1px solid rgba(148, 163, 184, 0.18);\n}\n.bb-editAnnotationHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 16px;\n padding-bottom: 12px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.12);\n}\n.bb-editAnnotationTitle {\n color: #f8fafc;\n font-size: 18px;\n font-weight: 600;\n margin: 0;\n}\n.bb-editAnnotationClose {\n background: none;\n border: none;\n color: #94a3b8;\n font-size: 24px;\n cursor: pointer;\n padding: 0 4px;\n}\n.bb-editAnnotationClose:hover {\n color: #f8fafc;\n}\n.bb-editAnnotationBody {\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\n.bb-editAnnotationActions {\n display: flex;\n gap: 8px;\n margin-top: 8px;\n}\n.se-cinematicHint {\n margin: 0 0 10px;\n font-size: 12px;\n line-height: 1.5;\n color: rgba(255, 255, 255, 0.55);\n}\n.bb-button.se-cinematicAdd {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n width: 100%;\n margin-bottom: 12px;\n background: #135bc0;\n color: white;\n}\n.bb-button.se-cinematicAdd:hover:not(:disabled) {\n background: #1a68d6;\n}\n.se-waypointList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-bottom: 14px;\n}\n.se-waypointCard {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 10px;\n border: 1px solid rgba(255, 255, 255, 0.1);\n border-radius: 8px;\n background: rgba(255, 255, 255, 0.03);\n}\n.se-waypointBadge {\n flex: none;\n width: 22px;\n height: 22px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n font-weight: 600;\n color: #06343c;\n background: #22d3ee;\n}\n.se-waypointBody {\n flex: 1;\n min-width: 0;\n}\n.se-waypointCoords {\n font-size: 11px;\n line-height: 1.4;\n color: rgba(255, 255, 255, 0.6);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.se-waypointActions {\n flex: none;\n display: flex;\n gap: 2px;\n}\n.se-waypointAction {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n padding: 0;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: rgba(255, 255, 255, 0.6);\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n}\n.se-waypointAction:hover:not(:disabled) {\n background: rgba(255, 255, 255, 0.1);\n color: #fff;\n}\n.se-waypointAction:disabled {\n opacity: 0.35;\n cursor: default;\n}\n.se-waypointAction--delete:hover:not(:disabled) {\n background: rgba(248, 113, 113, 0.16);\n color: #f87171;\n}\n.bb-cameraWaypoint {\n align-items: flex-start;\n margin-bottom: 8px;\n}\n.bb-cameraWaypoint .bb-cameraLiveBadge,\n.bb-cameraWaypoint .bb-cameraSavedBadge {\n flex: none;\n}\n.bb-cameraWaypointLabel {\n font-size: 11px;\n font-weight: 600;\n color: rgba(255, 255, 255, 0.8);\n margin-bottom: 2px;\n}\n.bb-cameraControls--centered {\n justify-content: center;\n margin: 12px 0;\n}\n');
|
|
12572
|
+
styleInject('.se-tabContent {\n display: flex;\n flex-direction: column;\n}\n.se-tabContent * {\n box-sizing: border-box;\n}\n.se-subTabRow {\n display: flex;\n justify-content: space-between;\n gap: 6px;\n padding-bottom: 16px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.1);\n margin-bottom: 4px;\n}\n.se-subTab {\n padding: 8px 12px;\n display: flex;\n justify-items: center;\n border-radius: 10px;\n border: 1px solid rgba(148, 163, 184, 0.18);\n background: rgba(255, 255, 255, 0.04);\n color: #b3bfd2;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n cursor: pointer;\n transition:\n background-color 0.18s ease,\n border-color 0.18s ease,\n color 0.18s ease;\n}\n.se-subTab:hover {\n background: rgba(255, 255, 255, 0.08);\n border-color: rgba(148, 163, 184, 0.32);\n}\n.se-subTab--active {\n background: rgba(99, 102, 241, 0.18);\n border-color: rgba(99, 102, 241, 0.4);\n color: #f8fafc;\n}\n.se-generalContent {\n display: flex;\n flex-direction: column;\n padding-top: 0;\n}\n.se-placeholder {\n font-size: 13px;\n color: #93a4c0;\n}\n.se-section {\n margin-bottom: 10px;\n border-radius: 7px;\n background: rgba(255, 255, 255, 0.03);\n border: 1px solid rgba(148, 163, 184, 0.12);\n overflow: hidden;\n padding: 0;\n}\n.se-section:last-child {\n margin-bottom: 0;\n}\n.se-sectionTitle {\n margin: 0;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: #c3cbdb;\n padding: 11px 14px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.1);\n}\n.se-sectionBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 14px;\n}\n.se-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n}\n.se-fieldInline {\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n.se-fieldLabel {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n font-size: 12px;\n font-weight: 700;\n color: #d7dee9;\n}\n.se-fieldValue {\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n color: #93a4c0;\n}\n.se-slider {\n width: 100%;\n accent-color: #2aa8d8;\n cursor: pointer;\n}\n.se-checkbox {\n width: 18px;\n height: 18px;\n accent-color: #2aa8d8;\n cursor: pointer;\n}\n.se-select,\n.se-textInput,\n.se-numberInput {\n background: rgba(15, 23, 42, 0.28);\n border: 1px solid rgba(148, 163, 184, 0.2);\n border-radius: 7px;\n color: #f8fafc;\n font-size: 13px;\n padding: 11px 13px;\n outline: none;\n}\n.se-select:focus,\n.se-textInput:focus,\n.se-numberInput:focus {\n outline: none;\n border-color: rgba(42, 168, 216, 0.65);\n box-shadow: 0 0 0 4px rgba(42, 168, 216, 0.12);\n}\n.se-select {\n min-width: 130px;\n text-transform: capitalize;\n}\n.se-textInput {\n width: 100%;\n}\n.se-numberInput {\n width: 100%;\n}\n.se-swatch {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n min-width: 96px;\n height: 30px;\n padding: 0 8px;\n border-radius: 8px;\n border: 1px solid rgba(148, 163, 184, 0.35);\n cursor: pointer;\n box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);\n}\n.se-swatchCode {\n font-size: 11px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n color: #fff;\n mix-blend-mode: difference;\n}\n.se-colorPicker {\n margin-top: 6px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n width: fit-content;\n}\n.se-colorPicker .react-colorful {\n width: 100%;\n height: 140px;\n}\n.se-inlineColorPicker .react-colorful {\n width: 100%;\n height: 240px;\n}\n.se-colorPickerPopover {\n z-index: 1000;\n padding: 10px;\n border-radius: 8px;\n background: #1b2333;\n border: 1px solid rgba(148, 163, 184, 0.25);\n box-shadow: 0 12px 28px rgba(0, 0, 0, 0.4);\n}\n.se-colorPickerPopover .react-colorful {\n width: 200px;\n height: 160px;\n}\n.se-vectorRow {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 8px;\n}\n.se-vectorAxis {\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-size: 11px;\n color: #93a4c0;\n}\n.se-panel {\n margin-bottom: 10px;\n overflow: hidden;\n border: 1px solid rgba(148, 163, 184, 0.12);\n border-radius: 7px;\n background: rgba(255, 255, 255, 0.03);\n}\n.se-panel:last-child {\n margin-bottom: 0;\n}\n.se-panelHeader {\n display: flex;\n width: 100%;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n border: none;\n background: rgba(255, 255, 255, 0.03);\n padding: 11px 14px;\n color: #e5e7eb;\n cursor: pointer;\n text-align: left;\n}\n.se-panelHeader:hover {\n background: rgba(255, 255, 255, 0.06);\n}\n.se-panelHeaderMain {\n display: flex;\n min-width: 0;\n align-items: center;\n gap: 8px;\n}\n.se-panelChevron {\n flex-shrink: 0;\n color: #8b97ab;\n transform: rotate(-90deg);\n transition: transform 0.18s ease;\n}\n.se-panelChevron--open {\n transform: rotate(0deg);\n}\n.se-panelTitle {\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: #c3cbdb;\n}\n.se-panelHelp {\n display: inline-flex;\n flex-shrink: 0;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n margin-left: 2px;\n border-radius: 999px;\n color: #b9c0ca;\n cursor: help;\n transition:\n background 0.18s ease,\n color 0.18s ease,\n transform 0.18s ease;\n}\n.se-panelHelp:hover {\n background: rgba(255, 255, 255, 0.08);\n color: #eef2ff;\n transform: translateY(-0.5px);\n}\n.se-panelTrailing {\n display: flex;\n align-items: center;\n}\n.se-panelBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 14px;\n border-top: 1px solid rgba(148, 163, 184, 0.1);\n background: transparent;\n}\n.se-iconButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 30px;\n height: 30px;\n border: none;\n border-radius: 8px;\n background: rgba(255, 255, 255, 0.08);\n color: #d7dde8;\n cursor: pointer;\n}\n.se-iconButton:hover {\n background: rgba(255, 255, 255, 0.12);\n}\n.se-axisStepperGrid {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 12px;\n}\n.se-axisStepper {\n display: grid;\n grid-template-columns: 40px 1fr 40px;\n align-items: center;\n min-height: 30px;\n border-radius: 10px;\n background: rgba(17, 24, 39, 0.62);\n border: 1px solid rgba(255, 255, 255, 0.05);\n}\n.se-axisStepperButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n border: none;\n background: transparent;\n color: #f3f4f6;\n cursor: pointer;\n}\n.se-axisStepperButton:hover {\n background: rgba(255, 255, 255, 0.04);\n}\n.se-axisStepperLabel {\n text-align: center;\n font-size: 15px;\n color: #f3f4f6;\n}\n.se-segmentedControl {\n display: grid;\n grid-auto-flow: column;\n overflow: hidden;\n border-radius: 10px;\n background: rgba(17, 24, 39, 0.62);\n}\n.se-segmentedOption {\n min-height: 35px;\n border: none;\n background: transparent;\n color: #d1d5db;\n font-size: 14px;\n text-transform: uppercase;\n cursor: pointer;\n}\n.se-segmentedOption--active {\n background: #2aa8d8;\n color: #fff;\n}\n.se-helperCard {\n padding: 32px;\n text-align: center;\n background: rgba(15, 23, 42, 0.44);\n border-radius: 10px;\n border: 1px solid rgba(148, 163, 184, 0.15);\n color: #94a3b8;\n font-size: 14px;\n}\n.se-helperCard strong {\n font-size: 14px;\n}\n.se-helperCard span {\n font-size: 13px;\n line-height: 1.4;\n color: #c7d0df;\n}\n.se-switch {\n position: relative;\n display: inline-flex;\n align-items: center;\n}\n.se-switch input[type=checkbox] {\n position: absolute;\n left: 0;\n top: 0;\n z-index: 10;\n width: 100%;\n height: 100%;\n cursor: pointer;\n opacity: 0;\n}\n.se-switchTrack {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n.se-switchTrack::before {\n content: "";\n width: 36px;\n height: 20px;\n background: #4b5563;\n display: inline-block;\n border-radius: 20px;\n transition: background-color 0.2s ease-in;\n}\n.se-switchThumb {\n position: absolute;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n left: 2px;\n top: 2px;\n z-index: 2;\n background: #fff;\n box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);\n transition: 0.2s ease-in;\n}\n.se-switch input[type=checkbox]:hover + .se-switchTrack .se-switchThumb {\n box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.2), 0 3px 8px 0 rgba(0, 0, 0, 0.1);\n}\n.se-switch input[type=checkbox]:checked + .se-switchTrack::before {\n background: #376fcb;\n}\n.se-switch input[type=checkbox]:checked + .se-switchTrack .se-switchThumb {\n background: #4285f4;\n left: 18px;\n}\n.se-switchLabel {\n font-size: 12px;\n font-weight: 700;\n letter-spacing: 0.06em;\n color: #e5e7eb;\n}\n.se-switch input[type=checkbox]:checked ~ .se-switchTrack .se-switchLabel {\n color: #2aa8d8;\n}\n.se-lightList {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.se-lightRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px solid rgba(148, 163, 184, 0.1);\n background: rgba(255, 255, 255, 0.03);\n cursor: pointer;\n transition: background-color 0.15s ease, border-color 0.15s ease;\n}\n.se-lightRow:hover {\n background: rgba(255, 255, 255, 0.06);\n}\n.se-lightRow--active {\n background: rgba(42, 168, 216, 0.12);\n border-color: rgba(42, 168, 216, 0.35);\n}\n.se-lightRowLabel {\n font-size: 13px;\n color: #d8e0ee;\n}\n.se-lightActions {\n display: flex;\n gap: 8px;\n}\n.se-lightProps {\n display: flex;\n flex-direction: column;\n gap: 14px;\n padding-top: 4px;\n border-top: 1px solid rgba(148, 163, 184, 0.1);\n}\n.se-animationList {\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\n.se-animationCard {\n display: flex;\n flex-direction: column;\n gap: 14px;\n padding: 16px;\n border-radius: 14px;\n border: 1px solid rgba(148, 163, 184, 0.16);\n background: rgba(15, 23, 42, 0.48);\n}\n.se-animationCard--active {\n border-color: rgba(42, 168, 216, 0.45);\n box-shadow: inset 0 0 0 1px rgba(42, 168, 216, 0.16);\n}\n.se-animationHeader {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 12px;\n}\n.se-animationHeading {\n display: flex;\n min-width: 0;\n flex-direction: column;\n gap: 6px;\n}\n.se-animationTitleRow {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n}\n.se-animationStatus,\n.se-animationBadge {\n display: inline-flex;\n align-items: center;\n min-height: 22px;\n padding: 0 8px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n}\n.se-animationStatus {\n background: rgba(42, 168, 216, 0.16);\n color: #9fe8ff;\n}\n.se-animationBadge {\n background: rgba(99, 102, 241, 0.18);\n color: #d9ddff;\n}\n.se-animationSource {\n font-size: 12px;\n color: #93a4c0;\n word-break: break-word;\n}\n.se-animationMeta {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n.se-animationMeta span {\n border-radius: 999px;\n background: rgba(255, 255, 255, 0.06);\n padding: 4px 9px;\n font-size: 12px;\n color: #c6d0e2;\n}\n.se-animationDangerButton,\n.se-animationButton,\n.se-animationAutoplayButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n border: 1px solid rgba(148, 163, 184, 0.2);\n border-radius: 10px;\n background: rgba(255, 255, 255, 0.04);\n color: #e2e8f0;\n cursor: pointer;\n transition:\n background-color 0.18s ease,\n border-color 0.18s ease,\n color 0.18s ease;\n}\n.se-animationButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.se-animationDangerButton {\n width: 36px;\n height: 36px;\n color: #fca5a5;\n}\n.se-animationDangerButton:hover,\n.se-animationButton:hover,\n.se-animationAutoplayButton:hover {\n background: rgba(255, 255, 255, 0.08);\n border-color: rgba(148, 163, 184, 0.34);\n}\n.se-animationButtonRow {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n}\n.se-animationButton {\n min-height: 36px;\n padding: 0 14px;\n font-size: 13px;\n font-weight: 600;\n}\n.se-animationButton--icon {\n flex: 1;\n min-width: 0;\n padding: 0;\n height: 38px;\n}\n.se-animationSeekRow {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.se-animationSeek {\n flex: 1;\n min-width: 0;\n}\n.se-animationSeekTime {\n flex-shrink: 0;\n min-width: 86px;\n text-align: right;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n color: #97a3b6;\n}\n.se-animationGrid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 12px;\n}\n.se-animationSpeedRow {\n display: grid;\n grid-template-columns: minmax(0, 1fr) 88px;\n gap: 10px;\n align-items: center;\n}\n.se-animationSpeedInput {\n text-align: right;\n}\n.se-animationAutoplayButton {\n align-self: flex-start;\n min-height: 36px;\n padding: 0 14px;\n font-size: 12px;\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n}\n.se-animationAutoplayButton--active {\n background: rgba(42, 168, 216, 0.18);\n border-color: rgba(42, 168, 216, 0.42);\n color: #dff8ff;\n}\n.se-removedAnimations {\n overflow: hidden;\n border-radius: 12px;\n border: 1px solid rgba(148, 163, 184, 0.14);\n background: rgba(255, 255, 255, 0.03);\n}\n.se-removedAnimationList {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 14px;\n}\n.se-removedAnimationRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n border-radius: 10px;\n background: rgba(15, 23, 42, 0.42);\n padding: 12px;\n}\n.se-removedAnimationName {\n font-size: 13px;\n font-weight: 600;\n color: #e2e8f0;\n}\n@media (max-width: 900px) {\n .se-animationGrid {\n grid-template-columns: 1fr;\n }\n .se-animationSpeedRow {\n grid-template-columns: 1fr;\n }\n}\n.bb-viewSpotOverlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.6);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n backdrop-filter: blur(4px);\n animation: bb-fadeIn 0.2s ease;\n}\n.bb-viewSpotPopup {\n background: #1e1e20;\n border-radius: 12px;\n max-width: 480px;\n width: 90%;\n max-height: 80vh;\n overflow-y: auto;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);\n border: 1px solid #333335;\n animation: bb-slideUp 0.25s ease;\n}\n.bb-viewSpotHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px;\n border-bottom: 1px solid #2a2a2c;\n}\n.bb-viewSpotTitle {\n font-size: 18px;\n font-weight: 600;\n color: #e8e8ea;\n margin: 0;\n}\n.bb-viewSpotClose {\n background: none;\n border: none;\n color: #888;\n font-size: 24px;\n cursor: pointer;\n padding: 0 4px;\n line-height: 1;\n transition: color 0.2s;\n}\n.bb-viewSpotClose:hover {\n color: #e8e8ea;\n}\n.bb-viewSpotBody {\n padding: 24px;\n}\n.bb-viewSpotDescription {\n color: #c8c8ca;\n font-size: 14px;\n line-height: 1.6;\n margin-bottom: 20px;\n white-space: pre-wrap;\n word-break: break-word;\n}\n.bb-viewSpotPosition {\n background: #141416;\n border-radius: 8px;\n padding: 12px 16px;\n margin-bottom: 24px;\n font-size: 13px;\n color: #a0a0a2;\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n.bb-viewSpotLabel {\n font-weight: 500;\n color: #888;\n}\n.bb-viewSpotCode {\n background: #0a0a0c;\n padding: 4px 10px;\n border-radius: 4px;\n font-family:\n "Monaco",\n "Menlo",\n monospace;\n font-size: 12px;\n color: #8be9fd;\n}\n.bb-viewSpotActions {\n display: flex;\n justify-content: flex-end;\n gap: 10px;\n padding-top: 4px;\n}\n.bb-editAnnotationOverlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.6);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n}\n.bb-editAnnotationPopup {\n background: #0f141e;\n border-radius: 16px;\n padding: 24px;\n min-width: 200px;\n max-width: 400px;\n width: 90%;\n border: 1px solid rgba(148, 163, 184, 0.18);\n}\n.bb-editAnnotationHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 16px;\n padding-bottom: 12px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.12);\n}\n.bb-editAnnotationTitle {\n color: #f8fafc;\n font-size: 18px;\n font-weight: 600;\n margin: 0;\n}\n.bb-editAnnotationClose {\n background: none;\n border: none;\n color: #94a3b8;\n font-size: 24px;\n cursor: pointer;\n padding: 0 4px;\n}\n.bb-editAnnotationClose:hover {\n color: #f8fafc;\n}\n.bb-editAnnotationBody {\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\n.bb-editAnnotationActions {\n display: flex;\n gap: 8px;\n margin-top: 8px;\n}\n.se-cinematicHint {\n margin: 0 0 10px;\n font-size: 12px;\n line-height: 1.5;\n color: rgba(255, 255, 255, 0.55);\n}\n.bb-button.se-cinematicAdd {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n width: 100%;\n margin-bottom: 12px;\n background: #135bc0;\n color: white;\n}\n.bb-button.se-cinematicAdd:hover:not(:disabled) {\n background: #1a68d6;\n}\n.se-waypointList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-bottom: 14px;\n}\n.se-waypointCard {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 12px;\n border: 1px solid rgba(255, 255, 255, 0.08);\n border-radius: 8px;\n background: rgba(255, 255, 255, 0.03);\n}\n.se-waypointBadge {\n flex: none;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n font-weight: 600;\n color: #06343c;\n background: #22d3ee;\n}\n.se-waypointBody {\n flex: 1;\n min-width: 0;\n}\n.se-waypointCoords {\n font-size: 11px;\n line-height: 1.4;\n color: rgba(255, 255, 255, 0.5);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.se-waypointActions {\n flex: none;\n display: flex;\n gap: 2px;\n}\n.se-waypointAction {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n padding: 0;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: rgba(255, 255, 255, 0.5);\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n}\n.se-waypointAction:hover:not(:disabled) {\n background: rgba(255, 255, 255, 0.08);\n color: #fff;\n}\n.se-waypointAction:disabled {\n opacity: 0.35;\n cursor: default;\n}\n.se-waypointAction--delete:hover:not(:disabled) {\n background: rgba(248, 113, 113, 0.16);\n color: #f87171;\n}\n.se-customBadge {\n display: inline-block;\n padding: 1px 8px;\n border-radius: 3px;\n background: rgba(34, 211, 238, 0.15);\n color: #22d3ee;\n font-size: 10px;\n font-weight: 600;\n margin-top: 4px;\n}\n.se-customizeToggle {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 8px 12px;\n background: rgba(255, 255, 255, 0.03);\n border-radius: 8px;\n border: 1px solid rgba(148, 163, 184, 0.08);\n margin: 8px 0 12px;\n}\n.se-toggleLabel {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n font-weight: 500;\n color: #f8fafc;\n cursor: pointer;\n user-select: none;\n}\n.se-toggleLabel input[type=checkbox] {\n width: 16px;\n height: 16px;\n accent-color: #22d3ee;\n cursor: pointer;\n}\n.se-toggleLabel svg {\n color: #22d3ee;\n width: 14px;\n height: 14px;\n}\n.se-toggleHint {\n font-size: 11px;\n color: rgba(255, 255, 255, 0.4);\n padding-left: 28px;\n}\n.se-segmentBetween {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px 14px;\n margin: 6px 0 6px 32px;\n background: rgba(255, 255, 255, 0.03);\n border-radius: 6px;\n border-left: 3px solid rgba(34, 211, 238, 0.25);\n}\n.se-segmentLabel {\n font-size: 11px;\n font-weight: 500;\n color: rgba(255, 255, 255, 0.45);\n}\n.se-segmentControls {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n.se-durationLabel {\n font-size: 10px;\n color: rgba(255, 255, 255, 0.35);\n font-weight: 500;\n white-space: nowrap;\n}\n.se-segmentSlider {\n flex: 1;\n min-width: 80px;\n max-width: 150px;\n height: 4px;\n -webkit-appearance: none;\n appearance: none;\n background: rgba(255, 255, 255, 0.1);\n border-radius: 2px;\n outline: none;\n}\n.se-segmentSlider::-webkit-slider-thumb {\n -webkit-appearance: none;\n appearance: none;\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: #22d3ee;\n cursor: pointer;\n border: 2px solid rgba(255, 255, 255, 0.2);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n}\n.se-segmentSlider::-moz-range-thumb {\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: #22d3ee;\n cursor: pointer;\n border: 2px solid rgba(255, 255, 255, 0.2);\n}\n.se-segmentValue {\n font-size: 12px;\n font-weight: 600;\n color: #22d3ee;\n min-width: 36px;\n text-align: center;\n}\n.se-presetButtons {\n display: flex;\n gap: 4px;\n}\n.se-presetButton {\n padding: 2px 8px;\n background: rgba(255, 255, 255, 0.06);\n border: 1px solid rgba(148, 163, 184, 0.15);\n border-radius: 4px;\n font-size: 10px;\n color: rgba(255, 255, 255, 0.6);\n cursor: pointer;\n transition: all 0.2s ease;\n font-weight: 500;\n}\n.se-presetButton:hover {\n background: #22d3ee;\n color: #06343c;\n border-color: #22d3ee;\n}\n.se-totalDuration {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 8px 4px;\n margin: 8px 0 12px;\n border-top: 1px solid rgba(148, 163, 184, 0.1);\n padding-top: 12px;\n}\n.se-totalLabel {\n font-size: 12px;\n color: rgba(255, 255, 255, 0.5);\n font-weight: 500;\n}\n.se-totalValue {\n font-size: 14px;\n font-weight: 600;\n color: #22d3ee;\n}\n.se-totalHint {\n font-size: 11px;\n color: rgba(255, 255, 255, 0.35);\n font-weight: 400;\n margin-left: 8px;\n}\n');
|
|
11918
12573
|
|
|
11919
12574
|
// src/components/BindingBuilder/SceneSettings/SceneSettingsTab.tsx
|
|
11920
12575
|
import {
|
|
@@ -11960,7 +12615,7 @@ function SceneSettingsTab({
|
|
|
11960
12615
|
animationClipDetails = [],
|
|
11961
12616
|
animationPlayback = FALLBACK_ANIMATION_PLAYBACK
|
|
11962
12617
|
}) {
|
|
11963
|
-
const [internalActiveSubTab, setInternalActiveSubTab] =
|
|
12618
|
+
const [internalActiveSubTab, setInternalActiveSubTab] = useState20("General");
|
|
11964
12619
|
const activeSubTab = controlledActiveSubTab ?? internalActiveSubTab;
|
|
11965
12620
|
const resolvedAnimationPlayback = animationPlayback ?? FALLBACK_ANIMATION_PLAYBACK;
|
|
11966
12621
|
const { tier } = useBindingBuilderContext();
|
|
@@ -12146,7 +12801,7 @@ function EnableToggle2({
|
|
|
12146
12801
|
}
|
|
12147
12802
|
|
|
12148
12803
|
// src/components/BindingBuilder/components/MaterialSection.tsx
|
|
12149
|
-
import { useState as
|
|
12804
|
+
import { useState as useState22 } from "react";
|
|
12150
12805
|
|
|
12151
12806
|
// src/components/ModelViewer/types/objectBinding.ts
|
|
12152
12807
|
var MATERIAL_SIDES = ["front", "back", "double"];
|
|
@@ -12193,7 +12848,7 @@ function MaterialPanelGroup({
|
|
|
12193
12848
|
}
|
|
12194
12849
|
|
|
12195
12850
|
// src/components/BindingBuilder/components/MaterialFields.tsx
|
|
12196
|
-
import { useRef as useRef15, useState as
|
|
12851
|
+
import { useRef as useRef15, useState as useState21 } from "react";
|
|
12197
12852
|
import { HexColorInput as HexColorInput3, HexColorPicker as HexColorPicker4 } from "react-colorful";
|
|
12198
12853
|
import { FileUp } from "lucide-react";
|
|
12199
12854
|
import { jsx as jsx35, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
@@ -12260,7 +12915,7 @@ function MaterialColorField({
|
|
|
12260
12915
|
fallback = "#ffffff",
|
|
12261
12916
|
onChange
|
|
12262
12917
|
}) {
|
|
12263
|
-
const [open, setOpen] =
|
|
12918
|
+
const [open, setOpen] = useState21(false);
|
|
12264
12919
|
const color = value || fallback;
|
|
12265
12920
|
const anchorRef = useRef15(null);
|
|
12266
12921
|
return /* @__PURE__ */ jsxs30("div", { className: "bb-field", children: [
|
|
@@ -12389,7 +13044,7 @@ function MaterialSliderField({
|
|
|
12389
13044
|
}
|
|
12390
13045
|
|
|
12391
13046
|
// src/components/BindingBuilder/components/MaterialSection.tsx
|
|
12392
|
-
import { Fragment as
|
|
13047
|
+
import { Fragment as Fragment11, jsx as jsx36, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
12393
13048
|
function MaterialSection({
|
|
12394
13049
|
selectedItem,
|
|
12395
13050
|
selectedMaterial,
|
|
@@ -12414,11 +13069,11 @@ function MaterialSection({
|
|
|
12414
13069
|
tier: "starter",
|
|
12415
13070
|
locked: !canUseStarterFeatures
|
|
12416
13071
|
};
|
|
12417
|
-
const [roughnessMode, setRoughnessMode] =
|
|
12418
|
-
const [anisotropyMode, setAnisotropyMode] =
|
|
13072
|
+
const [roughnessMode, setRoughnessMode] = useState22("roughness");
|
|
13073
|
+
const [anisotropyMode, setAnisotropyMode] = useState22(
|
|
12419
13074
|
"factor"
|
|
12420
13075
|
);
|
|
12421
|
-
return /* @__PURE__ */ jsxs31(
|
|
13076
|
+
return /* @__PURE__ */ jsxs31(Fragment11, { children: [
|
|
12422
13077
|
/* @__PURE__ */ jsx36(
|
|
12423
13078
|
MaterialPanelGroup,
|
|
12424
13079
|
{
|
|
@@ -12457,7 +13112,7 @@ function MaterialSection({
|
|
|
12457
13112
|
}))
|
|
12458
13113
|
}
|
|
12459
13114
|
),
|
|
12460
|
-
pbrWorkflow === "metalness" ? /* @__PURE__ */ jsxs31(
|
|
13115
|
+
pbrWorkflow === "metalness" ? /* @__PURE__ */ jsxs31(Fragment11, { children: [
|
|
12461
13116
|
/* @__PURE__ */ jsx36(
|
|
12462
13117
|
MaterialSliderField,
|
|
12463
13118
|
{
|
|
@@ -12481,7 +13136,7 @@ function MaterialSection({
|
|
|
12481
13136
|
onChange: (value) => setSelectedTexturePath(selectedItem, "metalnessMap", value)
|
|
12482
13137
|
}
|
|
12483
13138
|
)
|
|
12484
|
-
] }) : /* @__PURE__ */ jsxs31(
|
|
13139
|
+
] }) : /* @__PURE__ */ jsxs31(Fragment11, { children: [
|
|
12485
13140
|
/* @__PURE__ */ jsx36(
|
|
12486
13141
|
MaterialSliderField,
|
|
12487
13142
|
{
|
|
@@ -13454,7 +14109,7 @@ function MaterialPresetGallery({
|
|
|
13454
14109
|
}
|
|
13455
14110
|
|
|
13456
14111
|
// src/components/BindingBuilder/components/ToolbarExportMenu.tsx
|
|
13457
|
-
import { useEffect as useEffect19, useRef as useRef16, useState as
|
|
14112
|
+
import { useEffect as useEffect19, useRef as useRef16, useState as useState23 } from "react";
|
|
13458
14113
|
import { ChevronDown as ChevronDown5, Download as Download2, FileCode2, FileJson } from "lucide-react";
|
|
13459
14114
|
import { jsx as jsx38, jsxs as jsxs33 } from "react/jsx-runtime";
|
|
13460
14115
|
function ToolbarExportMenu({
|
|
@@ -13463,7 +14118,7 @@ function ToolbarExportMenu({
|
|
|
13463
14118
|
onExportJson,
|
|
13464
14119
|
onExportTs
|
|
13465
14120
|
}) {
|
|
13466
|
-
const [open, setOpen] =
|
|
14121
|
+
const [open, setOpen] = useState23(false);
|
|
13467
14122
|
const wrapperRef = useRef16(null);
|
|
13468
14123
|
useEffect19(() => {
|
|
13469
14124
|
if (!open) return;
|
|
@@ -13797,7 +14452,7 @@ function ActionsPanel({
|
|
|
13797
14452
|
|
|
13798
14453
|
// src/components/BindingBuilder/components/CameraPositionPanel.tsx
|
|
13799
14454
|
import { MapPin as MapPin3, Trash2 as Trash25 } from "lucide-react";
|
|
13800
|
-
import { Fragment as
|
|
14455
|
+
import { Fragment as Fragment12, jsx as jsx42, jsxs as jsxs37 } from "react/jsx-runtime";
|
|
13801
14456
|
function CameraPositionPanel({
|
|
13802
14457
|
selectedBinding,
|
|
13803
14458
|
open,
|
|
@@ -13816,7 +14471,7 @@ function CameraPositionPanel({
|
|
|
13816
14471
|
/* @__PURE__ */ jsx42("span", { className: "bb-cameraLiveBadge", children: "LIVE" }),
|
|
13817
14472
|
/* @__PURE__ */ jsxs37("div", { className: "se-waypointBody", children: [
|
|
13818
14473
|
/* @__PURE__ */ jsx42("div", { className: "bb-cameraWaypointLabel", children: "Current view" }),
|
|
13819
|
-
cameraState ? /* @__PURE__ */ jsxs37(
|
|
14474
|
+
cameraState ? /* @__PURE__ */ jsxs37(Fragment12, { children: [
|
|
13820
14475
|
/* @__PURE__ */ jsxs37("div", { className: "se-waypointCoords", children: [
|
|
13821
14476
|
"pos ",
|
|
13822
14477
|
formattedCameraState?.position
|
|
@@ -13902,10 +14557,10 @@ function CameraPositionPanel({
|
|
|
13902
14557
|
}
|
|
13903
14558
|
|
|
13904
14559
|
// src/components/BindingBuilder/components/SceneNodesPanel.tsx
|
|
13905
|
-
import { useMemo as useMemo15, useState as
|
|
14560
|
+
import { useMemo as useMemo15, useState as useState24 } from "react";
|
|
13906
14561
|
|
|
13907
14562
|
// src/components/BindingBuilder/utils/bindingFactory.ts
|
|
13908
|
-
function
|
|
14563
|
+
function slugify2(value) {
|
|
13909
14564
|
return value.trim().replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[_\s]+/g, "-").replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").toLowerCase();
|
|
13910
14565
|
}
|
|
13911
14566
|
function humanizeNodeName(value) {
|
|
@@ -13951,7 +14606,7 @@ function buildDefaultActions(type) {
|
|
|
13951
14606
|
function createDefaultBinding(nodeName) {
|
|
13952
14607
|
const type = inferType(nodeName);
|
|
13953
14608
|
return {
|
|
13954
|
-
id:
|
|
14609
|
+
id: slugify2(nodeName),
|
|
13955
14610
|
modelObjectId: nodeName,
|
|
13956
14611
|
type,
|
|
13957
14612
|
label: humanizeNodeName(nodeName),
|
|
@@ -13982,7 +14637,7 @@ function collectNodeNames(scene) {
|
|
|
13982
14637
|
}
|
|
13983
14638
|
|
|
13984
14639
|
// src/components/BindingBuilder/components/SceneNodesPanel.tsx
|
|
13985
|
-
import { Fragment as
|
|
14640
|
+
import { Fragment as Fragment13, jsx as jsx43, jsxs as jsxs38 } from "react/jsx-runtime";
|
|
13986
14641
|
function SceneNodesPanel({
|
|
13987
14642
|
bindings,
|
|
13988
14643
|
selectedItem,
|
|
@@ -13991,7 +14646,7 @@ function SceneNodesPanel({
|
|
|
13991
14646
|
isOpen = true,
|
|
13992
14647
|
onToggle
|
|
13993
14648
|
}) {
|
|
13994
|
-
const [search, setSearch] =
|
|
14649
|
+
const [search, setSearch] = useState24("");
|
|
13995
14650
|
const nodeKeys = useMemo15(() => Object.keys(bindings), [bindings]);
|
|
13996
14651
|
const filteredNodeKeys = useMemo15(() => {
|
|
13997
14652
|
const query = search.trim().toLowerCase();
|
|
@@ -14018,7 +14673,7 @@ function SceneNodesPanel({
|
|
|
14018
14673
|
children: "\u2190"
|
|
14019
14674
|
}
|
|
14020
14675
|
),
|
|
14021
|
-
isOpen && /* @__PURE__ */ jsxs38(
|
|
14676
|
+
isOpen && /* @__PURE__ */ jsxs38(Fragment13, { children: [
|
|
14022
14677
|
/* @__PURE__ */ jsxs38("div", { children: [
|
|
14023
14678
|
/* @__PURE__ */ jsx43("div", { className: "bb-sectionKicker", children: "Scene Nodes" }),
|
|
14024
14679
|
/* @__PURE__ */ jsxs38("div", { className: "bb-sectionHeading", children: [
|
|
@@ -14136,7 +14791,7 @@ function mergeAnimationConfig(sceneConfig, animationDetails) {
|
|
|
14136
14791
|
}
|
|
14137
14792
|
|
|
14138
14793
|
// src/components/BindingBuilder/hooks/useUndoableState.ts
|
|
14139
|
-
import { useCallback as
|
|
14794
|
+
import { useCallback as useCallback8, useReducer, useRef as useRef17 } from "react";
|
|
14140
14795
|
var MAX_HISTORY = 100;
|
|
14141
14796
|
function historyReducer(state, action) {
|
|
14142
14797
|
switch (action.type) {
|
|
@@ -14181,7 +14836,7 @@ function useUndoableState(initial, coalesceWindowMs = 600) {
|
|
|
14181
14836
|
future: []
|
|
14182
14837
|
});
|
|
14183
14838
|
const lastSetAt = useRef17(0);
|
|
14184
|
-
const setValue =
|
|
14839
|
+
const setValue = useCallback8(
|
|
14185
14840
|
(updater) => {
|
|
14186
14841
|
const now = Date.now();
|
|
14187
14842
|
const coalesce = now - lastSetAt.current < coalesceWindowMs;
|
|
@@ -14190,15 +14845,15 @@ function useUndoableState(initial, coalesceWindowMs = 600) {
|
|
|
14190
14845
|
},
|
|
14191
14846
|
[coalesceWindowMs]
|
|
14192
14847
|
);
|
|
14193
|
-
const undo =
|
|
14848
|
+
const undo = useCallback8(() => {
|
|
14194
14849
|
lastSetAt.current = 0;
|
|
14195
14850
|
dispatch({ type: "undo" });
|
|
14196
14851
|
}, []);
|
|
14197
|
-
const redo =
|
|
14852
|
+
const redo = useCallback8(() => {
|
|
14198
14853
|
lastSetAt.current = 0;
|
|
14199
14854
|
dispatch({ type: "redo" });
|
|
14200
14855
|
}, []);
|
|
14201
|
-
const reset =
|
|
14856
|
+
const reset = useCallback8((value) => {
|
|
14202
14857
|
lastSetAt.current = 0;
|
|
14203
14858
|
dispatch({ type: "reset", value });
|
|
14204
14859
|
}, []);
|
|
@@ -14214,7 +14869,7 @@ function useUndoableState(initial, coalesceWindowMs = 600) {
|
|
|
14214
14869
|
}
|
|
14215
14870
|
|
|
14216
14871
|
// src/components/BindingBuilder/utils/exportAssets.ts
|
|
14217
|
-
import { strToU8 as
|
|
14872
|
+
import { strToU8 as strToU83, zipSync as zipSync2 } from "fflate";
|
|
14218
14873
|
|
|
14219
14874
|
// src/components/BindingBuilder/utils/serialization.ts
|
|
14220
14875
|
function toBindingsJson(bindings) {
|
|
@@ -14290,11 +14945,11 @@ async function downloadExport(filename, content, mimeType, zipFilename, assets)
|
|
|
14290
14945
|
downloadTextFile(filename, content, mimeType);
|
|
14291
14946
|
return;
|
|
14292
14947
|
}
|
|
14293
|
-
const files = { [filename]:
|
|
14948
|
+
const files = { [filename]: strToU83(content) };
|
|
14294
14949
|
for (const asset of assets) {
|
|
14295
14950
|
files[asset.path] = new Uint8Array(await asset.blob.arrayBuffer());
|
|
14296
14951
|
}
|
|
14297
|
-
const zipped =
|
|
14952
|
+
const zipped = zipSync2(files, { level: 6 });
|
|
14298
14953
|
const blob = new Blob([zipped], { type: "application/zip" });
|
|
14299
14954
|
const url = URL.createObjectURL(blob);
|
|
14300
14955
|
const link = document.createElement("a");
|
|
@@ -14427,7 +15082,7 @@ function removeMaterialPanelFields(material, panel) {
|
|
|
14427
15082
|
}
|
|
14428
15083
|
|
|
14429
15084
|
// src/components/BindingBuilder/BindingBuilder.tsx
|
|
14430
|
-
import { Fragment as
|
|
15085
|
+
import { Fragment as Fragment14, jsx as jsx45, jsxs as jsxs40 } from "react/jsx-runtime";
|
|
14431
15086
|
var DEMO_MODEL_URL = "/room.glb";
|
|
14432
15087
|
function isPlainObject(value) {
|
|
14433
15088
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
@@ -14447,27 +15102,27 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14447
15102
|
const modelInputRef = useRef18(null);
|
|
14448
15103
|
const bindingsInputRef = useRef18(null);
|
|
14449
15104
|
const sceneConfigInputRef = useRef18(null);
|
|
14450
|
-
const [modelUrl, setModelUrl] =
|
|
14451
|
-
const [modelName, setModelName] =
|
|
14452
|
-
const [selectedItem, setSelectedItem] =
|
|
15105
|
+
const [modelUrl, setModelUrl] = useState25(null);
|
|
15106
|
+
const [modelName, setModelName] = useState25("");
|
|
15107
|
+
const [selectedItem, setSelectedItem] = useState25(null);
|
|
14453
15108
|
const bindingsHistory = useUndoableState({});
|
|
14454
15109
|
const bindings = bindingsHistory.value;
|
|
14455
15110
|
const setBindings = bindingsHistory.setValue;
|
|
14456
|
-
const [loading, setLoading] =
|
|
14457
|
-
const [isExporting, setIsExporting] =
|
|
14458
|
-
const [statusMessage, setStatusMessage] =
|
|
15111
|
+
const [loading, setLoading] = useState25(false);
|
|
15112
|
+
const [isExporting, setIsExporting] = useState25(false);
|
|
15113
|
+
const [statusMessage, setStatusMessage] = useState25(
|
|
14459
15114
|
"Load the demo model or upload a GLB/GLTF model to generate bindings."
|
|
14460
15115
|
);
|
|
14461
|
-
const [statusTone, setStatusTone] =
|
|
14462
|
-
const [statusBannerVisible, setStatusBannerVisible] =
|
|
14463
|
-
const [metricsDraft, setMetricsDraft] =
|
|
14464
|
-
const [metadataDraft, setMetadataDraft] =
|
|
14465
|
-
const [metricsError, setMetricsError] =
|
|
14466
|
-
const [metadataError, setMetadataError] =
|
|
14467
|
-
const [isPreviewDragActive, setIsPreviewDragActive] =
|
|
14468
|
-
const [isEditorCollapsed, setIsEditorCollapsed] =
|
|
14469
|
-
const [isNodesCollapsed, setIsNodesCollapsed] =
|
|
14470
|
-
const [openObjectSections, setOpenObjectSections] =
|
|
15116
|
+
const [statusTone, setStatusTone] = useState25("default");
|
|
15117
|
+
const [statusBannerVisible, setStatusBannerVisible] = useState25(false);
|
|
15118
|
+
const [metricsDraft, setMetricsDraft] = useState25("{}");
|
|
15119
|
+
const [metadataDraft, setMetadataDraft] = useState25("{}");
|
|
15120
|
+
const [metricsError, setMetricsError] = useState25(null);
|
|
15121
|
+
const [metadataError, setMetadataError] = useState25(null);
|
|
15122
|
+
const [isPreviewDragActive, setIsPreviewDragActive] = useState25(false);
|
|
15123
|
+
const [isEditorCollapsed, setIsEditorCollapsed] = useState25(false);
|
|
15124
|
+
const [isNodesCollapsed, setIsNodesCollapsed] = useState25(false);
|
|
15125
|
+
const [openObjectSections, setOpenObjectSections] = useState25({
|
|
14471
15126
|
identity: true,
|
|
14472
15127
|
basics: true,
|
|
14473
15128
|
material: true,
|
|
@@ -14476,7 +15131,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14476
15131
|
metrics: false,
|
|
14477
15132
|
metadata: false
|
|
14478
15133
|
});
|
|
14479
|
-
const [openMaterialPanels, setOpenMaterialPanels] =
|
|
15134
|
+
const [openMaterialPanels, setOpenMaterialPanels] = useState25({
|
|
14480
15135
|
pbr: true,
|
|
14481
15136
|
roughness: false,
|
|
14482
15137
|
emission: false,
|
|
@@ -14492,24 +15147,24 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14492
15147
|
});
|
|
14493
15148
|
const toggleObjectSection = (key) => setOpenObjectSections((prev) => ({ ...prev, [key]: !prev[key] }));
|
|
14494
15149
|
const toggleMaterialPanel = (key) => setOpenMaterialPanels((prev) => ({ ...prev, [key]: !prev[key] }));
|
|
14495
|
-
const [activeEditorTab, setActiveEditorTab] =
|
|
15150
|
+
const [activeEditorTab, setActiveEditorTab] = useState25(
|
|
14496
15151
|
"object"
|
|
14497
15152
|
);
|
|
14498
|
-
const [activeSubTab, setActiveSubTab] =
|
|
14499
|
-
const [pbrWorkflow, setPbrWorkflow] =
|
|
14500
|
-
const [materialPanelEnabled, setMaterialPanelEnabled] =
|
|
15153
|
+
const [activeSubTab, setActiveSubTab] = useState25("General");
|
|
15154
|
+
const [pbrWorkflow, setPbrWorkflow] = useState25("metalness");
|
|
15155
|
+
const [materialPanelEnabled, setMaterialPanelEnabled] = useState25(createMaterialPanelEnabledState());
|
|
14501
15156
|
const materialPanelItemRef = useRef18(null);
|
|
14502
|
-
const [zoomOnSelected, setZoomOnSelected] =
|
|
14503
|
-
const [highlightOnHover, setHighlightOnHover] =
|
|
14504
|
-
const [resetDialogOpen, setResetDialogOpen] =
|
|
14505
|
-
const [resetTarget, setResetTarget] =
|
|
15157
|
+
const [zoomOnSelected, setZoomOnSelected] = useState25(false);
|
|
15158
|
+
const [highlightOnHover, setHighlightOnHover] = useState25(true);
|
|
15159
|
+
const [resetDialogOpen, setResetDialogOpen] = useState25(false);
|
|
15160
|
+
const [resetTarget, setResetTarget] = useState25(
|
|
14506
15161
|
null
|
|
14507
15162
|
);
|
|
14508
15163
|
const sceneConfigHistory = useUndoableState(DEFAULT_SCENE_CONFIG);
|
|
14509
15164
|
const sceneConfig = sceneConfigHistory.value;
|
|
14510
15165
|
const setSceneConfig = sceneConfigHistory.setValue;
|
|
14511
|
-
const [activeAnnotation, setActiveAnnotation] =
|
|
14512
|
-
const [panelHoveredBinding, setPanelHoveredBinding] =
|
|
15166
|
+
const [activeAnnotation, setActiveAnnotation] = useState25(null);
|
|
15167
|
+
const [panelHoveredBinding, setPanelHoveredBinding] = useState25(null);
|
|
14513
15168
|
const license = useLicense(licenseKey);
|
|
14514
15169
|
const tier = normalizeTier(license.tier);
|
|
14515
15170
|
const bindingBuilderContextValue = useMemo16(
|
|
@@ -14535,14 +15190,16 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14535
15190
|
handleCameraChange,
|
|
14536
15191
|
handleViewerReady
|
|
14537
15192
|
} = useViewerCamera();
|
|
14538
|
-
const handlePreviewWaypoint =
|
|
15193
|
+
const handlePreviewWaypoint = useCallback9(
|
|
14539
15194
|
(waypoint) => {
|
|
14540
15195
|
setCameraState(
|
|
14541
15196
|
{
|
|
14542
15197
|
position: waypoint.position,
|
|
14543
15198
|
target: waypoint.target,
|
|
14544
15199
|
fov: cameraState?.fov ?? null,
|
|
14545
|
-
zoom: cameraState?.zoom ?? 1
|
|
15200
|
+
zoom: cameraState?.zoom ?? 1,
|
|
15201
|
+
azimuthAngle: waypoint.azimuthAngle,
|
|
15202
|
+
polarAngle: waypoint.polarAngle
|
|
14546
15203
|
},
|
|
14547
15204
|
true
|
|
14548
15205
|
);
|
|
@@ -14557,7 +15214,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14557
15214
|
position: formatVector3(cameraState.position),
|
|
14558
15215
|
target: formatVector3(cameraState.target)
|
|
14559
15216
|
} : null;
|
|
14560
|
-
const handleAnnotationsChange =
|
|
15217
|
+
const handleAnnotationsChange = useCallback9(
|
|
14561
15218
|
(newAnnotations) => {
|
|
14562
15219
|
setSceneConfig((prev) => {
|
|
14563
15220
|
const currentIds = prev.annotations.map((a) => a.id).join(",");
|
|
@@ -14573,7 +15230,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14573
15230
|
},
|
|
14574
15231
|
[]
|
|
14575
15232
|
);
|
|
14576
|
-
const handleActiveAnnotationChange =
|
|
15233
|
+
const handleActiveAnnotationChange = useCallback9(
|
|
14577
15234
|
(annotation) => {
|
|
14578
15235
|
setActiveAnnotation(
|
|
14579
15236
|
annotation ? {
|
|
@@ -14694,7 +15351,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14694
15351
|
});
|
|
14695
15352
|
}
|
|
14696
15353
|
};
|
|
14697
|
-
const applyBindingsFromScene =
|
|
15354
|
+
const applyBindingsFromScene = useCallback9(
|
|
14698
15355
|
(scene, nextModelName) => {
|
|
14699
15356
|
const nodeNames = collectNodeNames(scene);
|
|
14700
15357
|
bindingsHistory.reset(buildBindingsFromNodes(nodeNames));
|
|
@@ -14733,7 +15390,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14733
15390
|
bindingsHistory.reset({});
|
|
14734
15391
|
setSelectedItem(null);
|
|
14735
15392
|
};
|
|
14736
|
-
const handleViewerModelLoaded =
|
|
15393
|
+
const handleViewerModelLoaded = useCallback9(
|
|
14737
15394
|
(scene) => {
|
|
14738
15395
|
const pending = pendingLoadRef.current;
|
|
14739
15396
|
if (!pending || pending.requestId !== loadRequestRef.current) return;
|
|
@@ -14743,7 +15400,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14743
15400
|
},
|
|
14744
15401
|
[applyBindingsFromScene]
|
|
14745
15402
|
);
|
|
14746
|
-
const handleViewerLoadError =
|
|
15403
|
+
const handleViewerLoadError = useCallback9((error) => {
|
|
14747
15404
|
const pending = pendingLoadRef.current;
|
|
14748
15405
|
if (!pending || pending.requestId !== loadRequestRef.current) return;
|
|
14749
15406
|
pendingLoadRef.current = null;
|
|
@@ -14754,7 +15411,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14754
15411
|
setStatusTone("error");
|
|
14755
15412
|
setLoading(false);
|
|
14756
15413
|
}, []);
|
|
14757
|
-
const handleAnimationsReady =
|
|
15414
|
+
const handleAnimationsReady = useCallback9(
|
|
14758
15415
|
(controls) => {
|
|
14759
15416
|
viewerAnimations.handleAnimationsReady(controls);
|
|
14760
15417
|
const details = controls.clipDetails ?? controls.clips.map((sourceName) => ({ sourceName, duration: 0 }));
|
|
@@ -15055,7 +15712,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15055
15712
|
}));
|
|
15056
15713
|
setError(null);
|
|
15057
15714
|
};
|
|
15058
|
-
const handleViewerSelect =
|
|
15715
|
+
const handleViewerSelect = useCallback9(
|
|
15059
15716
|
(binding) => {
|
|
15060
15717
|
if (!binding) {
|
|
15061
15718
|
setSelectedItem(null);
|
|
@@ -15068,7 +15725,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15068
15725
|
},
|
|
15069
15726
|
[bindings]
|
|
15070
15727
|
);
|
|
15071
|
-
const handleSceneNodeSelect =
|
|
15728
|
+
const handleSceneNodeSelect = useCallback9(
|
|
15072
15729
|
async (nodeKey) => {
|
|
15073
15730
|
setSelectedItem(nodeKey);
|
|
15074
15731
|
setPanelHoveredBinding(null);
|
|
@@ -15076,7 +15733,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15076
15733
|
},
|
|
15077
15734
|
[focusObject]
|
|
15078
15735
|
);
|
|
15079
|
-
const saveCameraPositionForSelectedObject =
|
|
15736
|
+
const saveCameraPositionForSelectedObject = useCallback9(() => {
|
|
15080
15737
|
if (!selectedItem || !cameraState) {
|
|
15081
15738
|
return;
|
|
15082
15739
|
}
|
|
@@ -15091,7 +15748,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15091
15748
|
cameraState: currentCameraState
|
|
15092
15749
|
}));
|
|
15093
15750
|
}, [selectedItem, cameraState]);
|
|
15094
|
-
const goToSavedCameraPosition =
|
|
15751
|
+
const goToSavedCameraPosition = useCallback9(() => {
|
|
15095
15752
|
if (!savedCameraPosition?.position || !savedCameraPosition?.target) return;
|
|
15096
15753
|
setCameraState(
|
|
15097
15754
|
{
|
|
@@ -15103,28 +15760,28 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15103
15760
|
true
|
|
15104
15761
|
);
|
|
15105
15762
|
}, [savedCameraPosition, setCameraState, cameraState]);
|
|
15106
|
-
const clearSavedCameraPosition =
|
|
15763
|
+
const clearSavedCameraPosition = useCallback9(() => {
|
|
15107
15764
|
if (!selectedItem) return;
|
|
15108
15765
|
updateBinding(selectedItem, (binding) => ({
|
|
15109
15766
|
...binding,
|
|
15110
15767
|
cameraState: void 0
|
|
15111
15768
|
}));
|
|
15112
15769
|
}, [selectedItem]);
|
|
15113
|
-
const handleResetView =
|
|
15770
|
+
const handleResetView = useCallback9(async () => {
|
|
15114
15771
|
await resetView();
|
|
15115
15772
|
}, [resetView]);
|
|
15116
|
-
const handleFitScene =
|
|
15773
|
+
const handleFitScene = useCallback9(async () => {
|
|
15117
15774
|
await fitScene();
|
|
15118
15775
|
}, [fitScene]);
|
|
15119
|
-
const openResetDialog =
|
|
15776
|
+
const openResetDialog = useCallback9((target) => {
|
|
15120
15777
|
setResetTarget(target);
|
|
15121
15778
|
setResetDialogOpen(true);
|
|
15122
15779
|
}, []);
|
|
15123
|
-
const closeResetDialog =
|
|
15780
|
+
const closeResetDialog = useCallback9(() => {
|
|
15124
15781
|
setResetDialogOpen(false);
|
|
15125
15782
|
setResetTarget(null);
|
|
15126
15783
|
}, []);
|
|
15127
|
-
const executeReset =
|
|
15784
|
+
const executeReset = useCallback9(() => {
|
|
15128
15785
|
if (resetTarget === "bindings") {
|
|
15129
15786
|
if (!nodeKeys.length) {
|
|
15130
15787
|
closeResetDialog();
|
|
@@ -15221,7 +15878,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15221
15878
|
}
|
|
15222
15879
|
)
|
|
15223
15880
|
] }),
|
|
15224
|
-
!isEditorCollapsed && /* @__PURE__ */ jsxs40(
|
|
15881
|
+
!isEditorCollapsed && /* @__PURE__ */ jsxs40(Fragment14, { children: [
|
|
15225
15882
|
/* @__PURE__ */ jsxs40("div", { className: "bb-tabRow", children: [
|
|
15226
15883
|
/* @__PURE__ */ jsx45(
|
|
15227
15884
|
"button",
|
|
@@ -15242,7 +15899,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15242
15899
|
}
|
|
15243
15900
|
)
|
|
15244
15901
|
] }),
|
|
15245
|
-
/* @__PURE__ */ jsx45("section", { className: "bb-editor", children: activeEditorTab === "scene" ? /* @__PURE__ */ jsx45(
|
|
15902
|
+
/* @__PURE__ */ jsx45("section", { className: "bb-editor", children: activeEditorTab === "scene" ? /* @__PURE__ */ jsx45(Fragment14, { children: modelUrl ? /* @__PURE__ */ jsx45(
|
|
15246
15903
|
SceneSettingsTab,
|
|
15247
15904
|
{
|
|
15248
15905
|
config: sceneConfig,
|
|
@@ -15255,14 +15912,16 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15255
15912
|
// the whole settings panel for no reason.
|
|
15256
15913
|
activeSubTab === "Cinematic" && cameraState ? {
|
|
15257
15914
|
position: cameraState.position,
|
|
15258
|
-
target: cameraState.target
|
|
15915
|
+
target: cameraState.target,
|
|
15916
|
+
azimuthAngle: cameraState.azimuthAngle,
|
|
15917
|
+
polarAngle: cameraState.polarAngle
|
|
15259
15918
|
} : null
|
|
15260
15919
|
),
|
|
15261
15920
|
onPreviewWaypoint: handlePreviewWaypoint,
|
|
15262
15921
|
animationClipDetails: viewerAnimations.clipDetails,
|
|
15263
15922
|
animationPlayback: viewerAnimations
|
|
15264
15923
|
}
|
|
15265
|
-
) : /* @__PURE__ */ jsx45("div", { className: "bb-emptyEditor", children: "Upload a model to edit its scene settings." }) }) : !selectedBinding || !selectedItem ? /* @__PURE__ */ jsx45("div", { className: "bb-emptyEditor", children: "Select a generated node binding to edit its values." }) : /* @__PURE__ */ jsxs40(
|
|
15924
|
+
) : /* @__PURE__ */ jsx45("div", { className: "bb-emptyEditor", children: "Upload a model to edit its scene settings." }) }) : !selectedBinding || !selectedItem ? /* @__PURE__ */ jsx45("div", { className: "bb-emptyEditor", children: "Select a generated node binding to edit its values." }) : /* @__PURE__ */ jsxs40(Fragment14, { children: [
|
|
15266
15925
|
/* @__PURE__ */ jsx45("div", { className: "bb-editorHeader", children: /* @__PURE__ */ jsxs40("div", { children: [
|
|
15267
15926
|
/* @__PURE__ */ jsx45("div", { className: "bb-sectionKicker", children: "Editing Binding" }),
|
|
15268
15927
|
/* @__PURE__ */ jsx45("h2", { className: "bb-editorTitle", children: selectedBinding.label || selectedItem }),
|
|
@@ -15682,8 +16341,8 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15682
16341
|
// src/components/SimpleModelViewer/SimpleModelViewer.tsx
|
|
15683
16342
|
import {
|
|
15684
16343
|
useRef as useRef19,
|
|
15685
|
-
useState as
|
|
15686
|
-
useCallback as
|
|
16344
|
+
useState as useState26,
|
|
16345
|
+
useCallback as useCallback10,
|
|
15687
16346
|
useEffect as useEffect21,
|
|
15688
16347
|
useMemo as useMemo17,
|
|
15689
16348
|
Component as Component3,
|
|
@@ -15706,14 +16365,14 @@ styleInject('.smv-root {\n --smv-panel-font-family:\n Inter,\n "Segoe UI"
|
|
|
15706
16365
|
import {
|
|
15707
16366
|
X as X3,
|
|
15708
16367
|
Check,
|
|
15709
|
-
Settings,
|
|
16368
|
+
Settings as Settings2,
|
|
15710
16369
|
ChevronDown as ChevronDown6,
|
|
15711
16370
|
Boxes as Boxes2,
|
|
15712
16371
|
Trash2 as Trash26,
|
|
15713
16372
|
Upload as Upload2,
|
|
15714
16373
|
AlertTriangle as AlertTriangle4
|
|
15715
16374
|
} from "lucide-react";
|
|
15716
|
-
import { Fragment as
|
|
16375
|
+
import { Fragment as Fragment15, jsx as jsx46, jsxs as jsxs41 } from "react/jsx-runtime";
|
|
15717
16376
|
var HIGHLIGHT2 = { color: "#93c5fd", intensity: 0.05 };
|
|
15718
16377
|
var ENV_PRESETS = [
|
|
15719
16378
|
"apartment",
|
|
@@ -15773,7 +16432,7 @@ function InnerModel({
|
|
|
15773
16432
|
meshopt: true
|
|
15774
16433
|
});
|
|
15775
16434
|
const invalidate = useThree11((state) => state.invalidate);
|
|
15776
|
-
const [hoveredKey, setHoveredKey] =
|
|
16435
|
+
const [hoveredKey, setHoveredKey] = useState26(null);
|
|
15777
16436
|
const scene = useMemo17(
|
|
15778
16437
|
() => SkeletonUtils2.clone(rawScene),
|
|
15779
16438
|
[rawScene]
|
|
@@ -16075,7 +16734,7 @@ function EnvironmentSection({
|
|
|
16075
16734
|
title,
|
|
16076
16735
|
children
|
|
16077
16736
|
}) {
|
|
16078
|
-
const [collapsed, setCollapsed] =
|
|
16737
|
+
const [collapsed, setCollapsed] = useState26(false);
|
|
16079
16738
|
return /* @__PURE__ */ jsxs41("div", { className: "env-section", children: [
|
|
16080
16739
|
/* @__PURE__ */ jsxs41(
|
|
16081
16740
|
"button",
|
|
@@ -16114,7 +16773,7 @@ function EnvironmentPanel({
|
|
|
16114
16773
|
{
|
|
16115
16774
|
className: "side-panel__open-btn side-panel__open-btn--left",
|
|
16116
16775
|
onClick: () => onToggle(true),
|
|
16117
|
-
children: /* @__PURE__ */ jsx46(
|
|
16776
|
+
children: /* @__PURE__ */ jsx46(Settings2, { size: 20, strokeWidth: 1.8 })
|
|
16118
16777
|
}
|
|
16119
16778
|
),
|
|
16120
16779
|
/* @__PURE__ */ jsxs41(
|
|
@@ -16240,8 +16899,8 @@ function SceneObjectsPanel2({
|
|
|
16240
16899
|
onHover,
|
|
16241
16900
|
onToggleVisibility
|
|
16242
16901
|
}) {
|
|
16243
|
-
const [search, setSearch] =
|
|
16244
|
-
const [rowHovered, setRowHovered] =
|
|
16902
|
+
const [search, setSearch] = useState26("");
|
|
16903
|
+
const [rowHovered, setRowHovered] = useState26(null);
|
|
16245
16904
|
const entries = Object.values(meshKeys).filter(
|
|
16246
16905
|
(m) => m.label.toLowerCase().includes(search.toLowerCase())
|
|
16247
16906
|
);
|
|
@@ -16370,30 +17029,30 @@ function SimpleModelViewer({
|
|
|
16370
17029
|
const fileInputRef = useRef19(null);
|
|
16371
17030
|
const uploadedModelRef = useRef19(null);
|
|
16372
17031
|
const dragDepthRef = useRef19(0);
|
|
16373
|
-
const [loadedScene, setLoadedScene] =
|
|
16374
|
-
const [largeModelTriangles, setLargeModelTriangles] =
|
|
17032
|
+
const [loadedScene, setLoadedScene] = useState26(null);
|
|
17033
|
+
const [largeModelTriangles, setLargeModelTriangles] = useState26(
|
|
16375
17034
|
null
|
|
16376
17035
|
);
|
|
16377
|
-
const [largeModelNoticeDismissed, setLargeModelNoticeDismissed] =
|
|
16378
|
-
const [meshKeys, setMeshKeys] =
|
|
16379
|
-
const [hiddenKeys, setHiddenKeys] =
|
|
16380
|
-
const [selectedKey, setSelectedKey] =
|
|
16381
|
-
const [panelHoveredKey, setPanelHoveredKey] =
|
|
16382
|
-
const [uploadedModelUrl, setUploadedModelUrl] =
|
|
16383
|
-
const [uploadedModelName, setUploadedModelName] =
|
|
17036
|
+
const [largeModelNoticeDismissed, setLargeModelNoticeDismissed] = useState26(false);
|
|
17037
|
+
const [meshKeys, setMeshKeys] = useState26({});
|
|
17038
|
+
const [hiddenKeys, setHiddenKeys] = useState26(/* @__PURE__ */ new Set());
|
|
17039
|
+
const [selectedKey, setSelectedKey] = useState26(null);
|
|
17040
|
+
const [panelHoveredKey, setPanelHoveredKey] = useState26(null);
|
|
17041
|
+
const [uploadedModelUrl, setUploadedModelUrl] = useState26(null);
|
|
17042
|
+
const [uploadedModelName, setUploadedModelName] = useState26(
|
|
16384
17043
|
null
|
|
16385
17044
|
);
|
|
16386
|
-
const [modelLoadFailed, setModelLoadFailed] =
|
|
16387
|
-
const [isResolvingUpload, setIsResolvingUpload] =
|
|
16388
|
-
const [initialFitComplete, setInitialFitComplete] =
|
|
16389
|
-
const [isDragActive, setIsDragActive] =
|
|
16390
|
-
const [isPanelOpen, setIsPanelOpen] =
|
|
17045
|
+
const [modelLoadFailed, setModelLoadFailed] = useState26(false);
|
|
17046
|
+
const [isResolvingUpload, setIsResolvingUpload] = useState26(false);
|
|
17047
|
+
const [initialFitComplete, setInitialFitComplete] = useState26(false);
|
|
17048
|
+
const [isDragActive, setIsDragActive] = useState26(false);
|
|
17049
|
+
const [isPanelOpen, setIsPanelOpen] = useState26(
|
|
16391
17050
|
() => typeof window === "undefined" ? true : window.innerWidth > 900
|
|
16392
17051
|
);
|
|
16393
|
-
const [isEnvPanelOpen, setIsEnvPanelOpen] =
|
|
17052
|
+
const [isEnvPanelOpen, setIsEnvPanelOpen] = useState26(
|
|
16394
17053
|
() => typeof window === "undefined" ? true : window.innerWidth > 900
|
|
16395
17054
|
);
|
|
16396
|
-
const [envSettings, setEnvSettings] =
|
|
17055
|
+
const [envSettings, setEnvSettings] = useState26({
|
|
16397
17056
|
backgroundEnabled,
|
|
16398
17057
|
backgroundColor,
|
|
16399
17058
|
autoRotate,
|
|
@@ -16405,7 +17064,7 @@ function SimpleModelViewer({
|
|
|
16405
17064
|
directionalColor
|
|
16406
17065
|
});
|
|
16407
17066
|
const activeModelUrl = enableModelUpload ? uploadedModelUrl : modelUrl;
|
|
16408
|
-
const resetModelState =
|
|
17067
|
+
const resetModelState = useCallback10(() => {
|
|
16409
17068
|
setLoadedScene(null);
|
|
16410
17069
|
setLargeModelTriangles(null);
|
|
16411
17070
|
setLargeModelNoticeDismissed(false);
|
|
@@ -16416,11 +17075,11 @@ function SimpleModelViewer({
|
|
|
16416
17075
|
setModelLoadFailed(false);
|
|
16417
17076
|
setInitialFitComplete(false);
|
|
16418
17077
|
}, []);
|
|
16419
|
-
const revokeUploadedModelUrl =
|
|
17078
|
+
const revokeUploadedModelUrl = useCallback10(() => {
|
|
16420
17079
|
uploadedModelRef.current?.cleanup();
|
|
16421
17080
|
uploadedModelRef.current = null;
|
|
16422
17081
|
}, []);
|
|
16423
|
-
const clearUploadedModel =
|
|
17082
|
+
const clearUploadedModel = useCallback10(() => {
|
|
16424
17083
|
revokeUploadedModelUrl();
|
|
16425
17084
|
setUploadedModelUrl(null);
|
|
16426
17085
|
setUploadedModelName(null);
|
|
@@ -16449,7 +17108,7 @@ function SimpleModelViewer({
|
|
|
16449
17108
|
resetModelState,
|
|
16450
17109
|
revokeUploadedModelUrl
|
|
16451
17110
|
]);
|
|
16452
|
-
const handleUploadedModel =
|
|
17111
|
+
const handleUploadedModel = useCallback10(
|
|
16453
17112
|
async (file) => {
|
|
16454
17113
|
setIsResolvingUpload(true);
|
|
16455
17114
|
try {
|
|
@@ -16468,7 +17127,7 @@ function SimpleModelViewer({
|
|
|
16468
17127
|
},
|
|
16469
17128
|
[onLoadError, resetModelState, revokeUploadedModelUrl]
|
|
16470
17129
|
);
|
|
16471
|
-
const handleFileSelection =
|
|
17130
|
+
const handleFileSelection = useCallback10(
|
|
16472
17131
|
(files) => {
|
|
16473
17132
|
const file = files?.[0];
|
|
16474
17133
|
if (!file) return;
|
|
@@ -16481,7 +17140,7 @@ function SimpleModelViewer({
|
|
|
16481
17140
|
revokeUploadedModelUrl();
|
|
16482
17141
|
};
|
|
16483
17142
|
}, [revokeUploadedModelUrl]);
|
|
16484
|
-
const handleEnvSettingChange =
|
|
17143
|
+
const handleEnvSettingChange = useCallback10(
|
|
16485
17144
|
(key, value) => {
|
|
16486
17145
|
setEnvSettings((prev) => ({ ...prev, [key]: value }));
|
|
16487
17146
|
},
|
|
@@ -16493,12 +17152,12 @@ function SimpleModelViewer({
|
|
|
16493
17152
|
useEffect21(() => {
|
|
16494
17153
|
resetModelState();
|
|
16495
17154
|
}, [activeModelUrl, resetModelState]);
|
|
16496
|
-
const handleMeshesReady =
|
|
17155
|
+
const handleMeshesReady = useCallback10((meshes) => {
|
|
16497
17156
|
setMeshKeys(meshes);
|
|
16498
17157
|
setHiddenKeys(/* @__PURE__ */ new Set());
|
|
16499
17158
|
setSelectedKey(null);
|
|
16500
17159
|
}, []);
|
|
16501
|
-
const handleModelLoaded =
|
|
17160
|
+
const handleModelLoaded = useCallback10(
|
|
16502
17161
|
(scene) => {
|
|
16503
17162
|
setLoadedScene(scene);
|
|
16504
17163
|
setModelLoadFailed(false);
|
|
@@ -16509,14 +17168,14 @@ function SimpleModelViewer({
|
|
|
16509
17168
|
},
|
|
16510
17169
|
[onModelLoaded]
|
|
16511
17170
|
);
|
|
16512
|
-
const handleLoadError =
|
|
17171
|
+
const handleLoadError = useCallback10(
|
|
16513
17172
|
(error) => {
|
|
16514
17173
|
setModelLoadFailed(true);
|
|
16515
17174
|
onLoadError?.(error);
|
|
16516
17175
|
},
|
|
16517
17176
|
[onLoadError]
|
|
16518
17177
|
);
|
|
16519
|
-
const focusMesh =
|
|
17178
|
+
const focusMesh = useCallback10(
|
|
16520
17179
|
(key) => {
|
|
16521
17180
|
setSelectedKey(key);
|
|
16522
17181
|
if (!zoomOnSelected) return;
|
|
@@ -16532,14 +17191,14 @@ function SimpleModelViewer({
|
|
|
16532
17191
|
},
|
|
16533
17192
|
[zoomOnSelected]
|
|
16534
17193
|
);
|
|
16535
|
-
const handleToggleVisibility =
|
|
17194
|
+
const handleToggleVisibility = useCallback10((key) => {
|
|
16536
17195
|
setHiddenKeys((prev) => {
|
|
16537
17196
|
const next = new Set(prev);
|
|
16538
17197
|
next.has(key) ? next.delete(key) : next.add(key);
|
|
16539
17198
|
return next;
|
|
16540
17199
|
});
|
|
16541
17200
|
}, []);
|
|
16542
|
-
const handleDragEnter =
|
|
17201
|
+
const handleDragEnter = useCallback10(
|
|
16543
17202
|
(event) => {
|
|
16544
17203
|
if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
|
|
16545
17204
|
return;
|
|
@@ -16551,7 +17210,7 @@ function SimpleModelViewer({
|
|
|
16551
17210
|
},
|
|
16552
17211
|
[enableModelUpload]
|
|
16553
17212
|
);
|
|
16554
|
-
const handleDragOver =
|
|
17213
|
+
const handleDragOver = useCallback10(
|
|
16555
17214
|
(event) => {
|
|
16556
17215
|
if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
|
|
16557
17216
|
return;
|
|
@@ -16563,7 +17222,7 @@ function SimpleModelViewer({
|
|
|
16563
17222
|
},
|
|
16564
17223
|
[enableModelUpload]
|
|
16565
17224
|
);
|
|
16566
|
-
const handleDragLeave =
|
|
17225
|
+
const handleDragLeave = useCallback10(
|
|
16567
17226
|
(event) => {
|
|
16568
17227
|
if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
|
|
16569
17228
|
return;
|
|
@@ -16577,7 +17236,7 @@ function SimpleModelViewer({
|
|
|
16577
17236
|
},
|
|
16578
17237
|
[enableModelUpload]
|
|
16579
17238
|
);
|
|
16580
|
-
const handleDrop =
|
|
17239
|
+
const handleDrop = useCallback10(
|
|
16581
17240
|
(event) => {
|
|
16582
17241
|
if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
|
|
16583
17242
|
return;
|
|
@@ -16631,7 +17290,7 @@ function SimpleModelViewer({
|
|
|
16631
17290
|
}
|
|
16632
17291
|
)
|
|
16633
17292
|
] }),
|
|
16634
|
-
enableModelUpload ? /* @__PURE__ */ jsxs41(
|
|
17293
|
+
enableModelUpload ? /* @__PURE__ */ jsxs41(Fragment15, { children: [
|
|
16635
17294
|
/* @__PURE__ */ jsx46(
|
|
16636
17295
|
"input",
|
|
16637
17296
|
{
|
|
@@ -16645,7 +17304,7 @@ function SimpleModelViewer({
|
|
|
16645
17304
|
}
|
|
16646
17305
|
}
|
|
16647
17306
|
),
|
|
16648
|
-
activeModelUrl ? /* @__PURE__ */ jsx46("div", { className: "smv-upload-toolbar", children: /* @__PURE__ */ jsxs41(
|
|
17307
|
+
activeModelUrl ? /* @__PURE__ */ jsx46("div", { className: "smv-upload-toolbar", children: /* @__PURE__ */ jsxs41(Fragment15, { children: [
|
|
16649
17308
|
/* @__PURE__ */ jsx46("span", { className: "smv-upload-toolbar__name", children: uploadedModelName ?? "Uploaded model" }),
|
|
16650
17309
|
/* @__PURE__ */ jsxs41(
|
|
16651
17310
|
"button",
|
|
@@ -16806,18 +17465,18 @@ function SimpleModelViewer({
|
|
|
16806
17465
|
}
|
|
16807
17466
|
|
|
16808
17467
|
// src/hooks/useViewerSelection.ts
|
|
16809
|
-
import { useCallback as
|
|
17468
|
+
import { useCallback as useCallback11, useState as useState27 } from "react";
|
|
16810
17469
|
function useViewerSelection(initialSelectedObjectBinding = null) {
|
|
16811
|
-
const [selectedObjectBinding, setSelectedObjectBinding] =
|
|
17470
|
+
const [selectedObjectBinding, setSelectedObjectBinding] = useState27(
|
|
16812
17471
|
initialSelectedObjectBinding
|
|
16813
17472
|
);
|
|
16814
|
-
const handleObjectSelect =
|
|
17473
|
+
const handleObjectSelect = useCallback11(
|
|
16815
17474
|
(binding) => {
|
|
16816
17475
|
setSelectedObjectBinding(binding);
|
|
16817
17476
|
},
|
|
16818
17477
|
[setSelectedObjectBinding]
|
|
16819
17478
|
);
|
|
16820
|
-
const clearSelection =
|
|
17479
|
+
const clearSelection = useCallback11(() => {
|
|
16821
17480
|
setSelectedObjectBinding(null);
|
|
16822
17481
|
}, []);
|
|
16823
17482
|
return {
|
|
@@ -16855,14 +17514,14 @@ function useObjectBindingIds(objectBindings) {
|
|
|
16855
17514
|
}
|
|
16856
17515
|
|
|
16857
17516
|
// src/hooks/useObjectVisibility.ts
|
|
16858
|
-
import { useCallback as
|
|
17517
|
+
import { useCallback as useCallback12, useMemo as useMemo20 } from "react";
|
|
16859
17518
|
function getHiddenObjects(objectBindings) {
|
|
16860
17519
|
return Object.fromEntries(
|
|
16861
17520
|
Object.entries(objectBindings).filter(([, binding]) => binding.visible === false).map(([bindingKey]) => [bindingKey, true])
|
|
16862
17521
|
);
|
|
16863
17522
|
}
|
|
16864
17523
|
function useObjectVisibility(objectBindings, onObjectBindingsChange) {
|
|
16865
|
-
const updateVisibility =
|
|
17524
|
+
const updateVisibility = useCallback12(
|
|
16866
17525
|
(objectId, visible) => {
|
|
16867
17526
|
const bindingKey = resolveBindingKey(objectBindings, objectId);
|
|
16868
17527
|
if (!bindingKey) return;
|
|
@@ -16878,15 +17537,15 @@ function useObjectVisibility(objectBindings, onObjectBindingsChange) {
|
|
|
16878
17537
|
},
|
|
16879
17538
|
[objectBindings, onObjectBindingsChange]
|
|
16880
17539
|
);
|
|
16881
|
-
const hideObject =
|
|
17540
|
+
const hideObject = useCallback12(
|
|
16882
17541
|
(objectId) => updateVisibility(objectId, false),
|
|
16883
17542
|
[updateVisibility]
|
|
16884
17543
|
);
|
|
16885
|
-
const showObject =
|
|
17544
|
+
const showObject = useCallback12(
|
|
16886
17545
|
(objectId) => updateVisibility(objectId, true),
|
|
16887
17546
|
[updateVisibility]
|
|
16888
17547
|
);
|
|
16889
|
-
const toggleObjectVisibility =
|
|
17548
|
+
const toggleObjectVisibility = useCallback12(
|
|
16890
17549
|
(objectId) => {
|
|
16891
17550
|
const bindingKey = resolveBindingKey(objectBindings, objectId);
|
|
16892
17551
|
if (!bindingKey) return;
|
|
@@ -16902,7 +17561,7 @@ function useObjectVisibility(objectBindings, onObjectBindingsChange) {
|
|
|
16902
17561
|
},
|
|
16903
17562
|
[objectBindings, onObjectBindingsChange]
|
|
16904
17563
|
);
|
|
16905
|
-
const clearHiddenObjects =
|
|
17564
|
+
const clearHiddenObjects = useCallback12(() => {
|
|
16906
17565
|
onObjectBindingsChange(
|
|
16907
17566
|
Object.fromEntries(
|
|
16908
17567
|
Object.entries(objectBindings).map(([bindingKey, binding]) => [
|
|
@@ -16922,7 +17581,7 @@ function useObjectVisibility(objectBindings, onObjectBindingsChange) {
|
|
|
16922
17581
|
),
|
|
16923
17582
|
[hiddenObjects, objectBindings]
|
|
16924
17583
|
);
|
|
16925
|
-
const isObjectHidden =
|
|
17584
|
+
const isObjectHidden = useCallback12(
|
|
16926
17585
|
(objectId) => {
|
|
16927
17586
|
const bindingKey = resolveBindingKey(objectBindings, objectId);
|
|
16928
17587
|
return bindingKey ? objectBindings[bindingKey]?.visible === false : false;
|
|
@@ -16941,10 +17600,10 @@ function useObjectVisibility(objectBindings, onObjectBindingsChange) {
|
|
|
16941
17600
|
}
|
|
16942
17601
|
|
|
16943
17602
|
// src/hooks/useViewerHover.ts
|
|
16944
|
-
import { useCallback as
|
|
17603
|
+
import { useCallback as useCallback13, useState as useState28 } from "react";
|
|
16945
17604
|
function useViewerHover() {
|
|
16946
|
-
const [hoveredObjectBinding, setHoveredObjectBinding] =
|
|
16947
|
-
const handleHoveredObject =
|
|
17605
|
+
const [hoveredObjectBinding, setHoveredObjectBinding] = useState28(null);
|
|
17606
|
+
const handleHoveredObject = useCallback13(
|
|
16948
17607
|
(binding) => {
|
|
16949
17608
|
setHoveredObjectBinding(binding);
|
|
16950
17609
|
},
|
|
@@ -16957,18 +17616,18 @@ function useViewerHover() {
|
|
|
16957
17616
|
}
|
|
16958
17617
|
|
|
16959
17618
|
// src/hooks/useViewerModel.ts
|
|
16960
|
-
import { useCallback as
|
|
17619
|
+
import { useCallback as useCallback14, useState as useState29 } from "react";
|
|
16961
17620
|
import { Box3 as Box36, Vector3 as Vector310 } from "three";
|
|
16962
17621
|
function toTuple2(vector) {
|
|
16963
17622
|
return [vector.x, vector.y, vector.z];
|
|
16964
17623
|
}
|
|
16965
17624
|
function useViewerModel() {
|
|
16966
|
-
const [isLoading, setIsLoading] =
|
|
16967
|
-
const [isReady, setIsReady] =
|
|
16968
|
-
const [error, setError] =
|
|
16969
|
-
const [bounds, setBounds] =
|
|
16970
|
-
const [objectCount, setObjectCount] =
|
|
16971
|
-
const handleModelLoaded =
|
|
17625
|
+
const [isLoading, setIsLoading] = useState29(true);
|
|
17626
|
+
const [isReady, setIsReady] = useState29(false);
|
|
17627
|
+
const [error, setError] = useState29(null);
|
|
17628
|
+
const [bounds, setBounds] = useState29(null);
|
|
17629
|
+
const [objectCount, setObjectCount] = useState29(0);
|
|
17630
|
+
const handleModelLoaded = useCallback14((scene) => {
|
|
16972
17631
|
const box = new Box36().setFromObject(scene);
|
|
16973
17632
|
const min = box.min.clone();
|
|
16974
17633
|
const max = box.max.clone();
|
|
@@ -16993,14 +17652,14 @@ function useViewerModel() {
|
|
|
16993
17652
|
setIsLoading(false);
|
|
16994
17653
|
setIsReady(true);
|
|
16995
17654
|
}, []);
|
|
16996
|
-
const handleLoadError =
|
|
17655
|
+
const handleLoadError = useCallback14((nextError) => {
|
|
16997
17656
|
setError(nextError);
|
|
16998
17657
|
setBounds(null);
|
|
16999
17658
|
setObjectCount(0);
|
|
17000
17659
|
setIsLoading(false);
|
|
17001
17660
|
setIsReady(false);
|
|
17002
17661
|
}, []);
|
|
17003
|
-
const resetModelState =
|
|
17662
|
+
const resetModelState = useCallback14(() => {
|
|
17004
17663
|
setIsLoading(true);
|
|
17005
17664
|
setIsReady(false);
|
|
17006
17665
|
setError(null);
|
|
@@ -17020,10 +17679,10 @@ function useViewerModel() {
|
|
|
17020
17679
|
}
|
|
17021
17680
|
|
|
17022
17681
|
// src/hooks/useViewerActions.ts
|
|
17023
|
-
import { useCallback as
|
|
17682
|
+
import { useCallback as useCallback15 } from "react";
|
|
17024
17683
|
function useViewerActions(objectBindings, options = {}) {
|
|
17025
17684
|
const { onAction, onObjectBindingsChange } = options;
|
|
17026
|
-
const getActionsForObject =
|
|
17685
|
+
const getActionsForObject = useCallback15(
|
|
17027
17686
|
(objectId) => {
|
|
17028
17687
|
const bindingKey = resolveBindingKey(objectBindings, objectId);
|
|
17029
17688
|
if (!bindingKey) {
|
|
@@ -17033,7 +17692,7 @@ function useViewerActions(objectBindings, options = {}) {
|
|
|
17033
17692
|
},
|
|
17034
17693
|
[objectBindings]
|
|
17035
17694
|
);
|
|
17036
|
-
const runAction =
|
|
17695
|
+
const runAction = useCallback15(
|
|
17037
17696
|
(objectId, actionId) => {
|
|
17038
17697
|
const bindingKey = resolveBindingKey(objectBindings, objectId);
|
|
17039
17698
|
if (!bindingKey) {
|
|
@@ -17075,7 +17734,7 @@ function useViewerActions(objectBindings, options = {}) {
|
|
|
17075
17734
|
}
|
|
17076
17735
|
|
|
17077
17736
|
// src/hooks/useShareableViewerState.ts
|
|
17078
|
-
import { useCallback as
|
|
17737
|
+
import { useCallback as useCallback16, useEffect as useEffect22, useRef as useRef20, useState as useState30 } from "react";
|
|
17079
17738
|
function serializeState(cameraState, objectBindings, selectedObjectBinding) {
|
|
17080
17739
|
const hidden = Object.values(objectBindings).filter((binding) => binding.visible === false).map((binding) => binding.id);
|
|
17081
17740
|
return {
|
|
@@ -17096,7 +17755,7 @@ function useShareableViewerState({
|
|
|
17096
17755
|
autoSync = true
|
|
17097
17756
|
}) {
|
|
17098
17757
|
const lastWrittenRawRef = useRef20(null);
|
|
17099
|
-
const buildHash =
|
|
17758
|
+
const buildHash = useCallback16(() => {
|
|
17100
17759
|
const state = serializeState(
|
|
17101
17760
|
cameraState,
|
|
17102
17761
|
objectBindings,
|
|
@@ -17104,12 +17763,12 @@ function useShareableViewerState({
|
|
|
17104
17763
|
);
|
|
17105
17764
|
return `${paramKey}=${encodeURIComponent(JSON.stringify(state))}`;
|
|
17106
17765
|
}, [cameraState, objectBindings, selectedObjectBinding, paramKey]);
|
|
17107
|
-
const getShareUrl =
|
|
17766
|
+
const getShareUrl = useCallback16(() => {
|
|
17108
17767
|
const url = new URL(window.location.href);
|
|
17109
17768
|
url.hash = buildHash();
|
|
17110
17769
|
return url.toString();
|
|
17111
17770
|
}, [buildHash]);
|
|
17112
|
-
const syncUrl =
|
|
17771
|
+
const syncUrl = useCallback16(() => {
|
|
17113
17772
|
const url = new URL(window.location.href);
|
|
17114
17773
|
url.hash = buildHash();
|
|
17115
17774
|
window.history.replaceState(null, "", url.toString());
|
|
@@ -17127,7 +17786,7 @@ function useShareableViewerState({
|
|
|
17127
17786
|
objectBindings,
|
|
17128
17787
|
selectedObjectBinding
|
|
17129
17788
|
};
|
|
17130
|
-
const [restoreAttempted, setRestoreAttempted] =
|
|
17789
|
+
const [restoreAttempted, setRestoreAttempted] = useState30(false);
|
|
17131
17790
|
const lastSyncedHashRef = useRef20(null);
|
|
17132
17791
|
useEffect22(() => {
|
|
17133
17792
|
if (!autoSync || !restoreAttempted) return;
|
|
@@ -17153,7 +17812,7 @@ function useShareableViewerState({
|
|
|
17153
17812
|
}, [autoSync, restoreAttempted, debounceMs, paramKey]);
|
|
17154
17813
|
const lastRestoredRawRef = useRef20(null);
|
|
17155
17814
|
const restoringRawRef = useRef20(null);
|
|
17156
|
-
const restoreFromUrl =
|
|
17815
|
+
const restoreFromUrl = useCallback16(async () => {
|
|
17157
17816
|
setRestoreAttempted(true);
|
|
17158
17817
|
const rawHash = window.location.hash.startsWith("#") ? window.location.hash.slice(1) : window.location.hash;
|
|
17159
17818
|
const raw = new URLSearchParams(rawHash).get(paramKey);
|