@liveroom-tech/react-immersive 1.3.4 → 1.4.0
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 +60 -1
- package/dist/index.d.mts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +539 -67
- package/dist/index.mjs +541 -66
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -242,7 +242,7 @@ function styleInject(css, { insertAt } = {}) {
|
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
// src/styles/ModelViewer.css
|
|
245
|
-
styleInject('.ri-viewer--license-checking,\n.ri-viewer--license-invalid {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n background-color: #f9f9f9;\n}\n.ri-viewer {\n --ri-font-family:\n Inter,\n "Segoe UI",\n Helvetica,\n Arial,\n sans-serif;\n --ri-viewer-bg: #1c1c1d;\n --ri-viewer-text: #111827;\n --ri-panel-bg: #ffffff;\n --ri-panel-border: #e5e7eb;\n --ri-panel-title: #111827;\n --ri-panel-muted-text: #4b5563;\n --ri-panel-subtle-text: #9ca3af;\n --ri-panel-surface: #f9fafb;\n --ri-panel-surface-hover: #f3f4f6;\n --ri-panel-surface-border: #e5e7eb;\n --ri-panel-button-text: #1f2937;\n --ri-panel-accent: #2563eb;\n --ri-panel-accent-contrast: #ffffff;\n --ri-license-overlay-bg: rgba(0, 0, 0, 0.04);\n --ri-license-overlay-border: rgba(0, 0, 0, 0.08);\n --ri-license-overlay-text: #888;\n --ri-license-overlay-error-bg: rgba(192, 57, 43, 0.06);\n --ri-license-overlay-error-border: rgba(192, 57, 43, 0.2);\n --ri-license-overlay-error-text: #c0392b;\n --ri-sidepanel-font-family:\n Inter,\n "Segoe UI",\n Helvetica,\n Arial,\n sans-serif;\n --ri-sidepanel-bg: var(--ri-databinding-bg, #111318);\n --ri-sidepanel-open-btn-bg: rgba(20, 20, 20, 0.82);\n --ri-sidepanel-open-btn-bg-hover: #27282b7e;\n --ri-sidepanel-border: rgba(255, 255, 255, 0.06);\n --ri-sidepanel-divider: rgba(255, 255, 255, 0.04);\n --ri-sidepanel-border-strong: rgba(255, 255, 255, 0.08);\n --ri-sidepanel-border-active: rgba(255, 255, 255, 0.18);\n --ri-sidepanel-shadow-left: 4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-sidepanel-shadow-right: -4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-sidepanel-text: var(--ri-databinding-text, #f1f5f9);\n --ri-sidepanel-text-emphasis: var(--ri-databinding-text-hover, #cbd5e1);\n --ri-sidepanel-text-muted: var(--ri-databinding-text-muted, #94a3b8);\n --ri-sidepanel-text-subtle: var(--ri-databinding-text-subtle, #64748b);\n --ri-sidepanel-placeholder: var(--ri-databinding-text-faint, #475569);\n --ri-sidepanel-surface: rgba(255, 255, 255, 0.05);\n --ri-sidepanel-surface-hover: rgba(255, 255, 255, 0.1);\n --ri-sidepanel-accent: #3b82f6;\n --ri-sidepanel-accent-soft: rgba(59, 130, 246, 0.12);\n --ri-databinding-bg: #111318;\n --ri-databinding-border: rgba(255, 255, 255, 0.06);\n --ri-databinding-border-strong: rgba(255, 255, 255, 0.08);\n --ri-databinding-border-hover: rgba(255, 255, 255, 0.15);\n --ri-databinding-shadow: 4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-databinding-font-family:\n "Inter",\n "Segoe UI",\n sans-serif;\n --ri-databinding-mono-font-family:\n "JetBrains Mono",\n "Fira Code",\n "Menlo",\n monospace;\n --ri-databinding-text: #f1f5f9;\n --ri-databinding-text-muted: #94a3b8;\n --ri-databinding-text-subtle: #64748b;\n --ri-databinding-text-faint: #475569;\n --ri-databinding-text-hover: #cbd5e1;\n --ri-databinding-surface: rgba(255, 255, 255, 0.03);\n --ri-databinding-surface-hover: rgba(255, 255, 255, 0.06);\n --ri-databinding-accent: #93c5fd;\n --ri-databinding-accent-glow: rgba(147, 197, 253, 0.8);\n --ri-databinding-accent-border: rgba(96, 165, 250, 0.55);\n --ri-databinding-accent-gradient:\n linear-gradient(\n \n 135deg,\n #1e3a8a 0%,\n #1d4ed8 60%,\n #2563eb 100% );\n --ri-databinding-accent-gradient-active:\n linear-gradient(\n \n 135deg,\n #1e3a8a 0%,\n #1e40af 100% );\n display: flex;\n flex-direction: row;\n width: 100%;\n height: 100%;\n min-height: 0;\n overflow: hidden;\n background: var(--ri-viewer-bg);\n position: relative;\n color: var(--ri-viewer-text);\n font-family: var(--ri-font-family);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n text-rendering: optimizeLegibility;\n font-synthesis: none;\n}\n.ri-viewer * {\n box-sizing: border-box;\n}\n.ri-viewerLeft {\n display: flex;\n flex: 1;\n min-height: 0;\n min-width: 0;\n position: relative;\n}\n.ri-viewerRight {\n display: flex;\n flex-shrink: 0;\n min-width: 0;\n}\n.ri-panel {\n width: 280px;\n min-height: 0;\n background: var(--ri-panel-bg);\n border-right: 1px solid var(--ri-panel-border);\n display: flex;\n flex-direction: column;\n padding: 20px;\n overflow-y: auto;\n flex-shrink: 0;\n transition: margin-left 0.3s ease, transform 0.3s ease;\n}\n.ri-panel--open {\n transform: translateX(0);\n margin-left: 0;\n}\n.ri-panel--closed {\n transform: translateX(-100%);\n margin-left: -280px;\n}\n.ri-panelHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 12px;\n}\n.ri-panelTitle {\n margin: 0;\n font-size: 15px;\n font-weight: 700;\n color: var(--ri-panel-title);\n line-height: 1.4;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.ri-closeButton {\n border: 0;\n background: transparent;\n color: var(--ri-panel-subtle-text);\n font-size: 22px;\n line-height: 1;\n padding: 0;\n cursor: pointer;\n flex-shrink: 0;\n transition: color 0.2s ease;\n}\n.ri-closeButton:hover {\n color: var(--ri-panel-muted-text);\n}\n.ri-json {\n margin: 0 0 16px;\n padding: 12px;\n background: var(--ri-panel-surface);\n border: 1px solid var(--ri-panel-surface-border);\n border-radius: 12px;\n font-size: 12px;\n line-height: 1.55;\n color: var(--ri-panel-button-text);\n overflow-x: auto;\n white-space: pre-wrap;\n word-break: break-word;\n}\n.ri-divider {\n border-top: 1px solid var(--ri-panel-border);\n margin-bottom: 12px;\n}\n.ri-sectionLabel {\n margin-bottom: 8px;\n font-size: 11px;\n font-weight: 700;\n color: var(--ri-panel-subtle-text);\n text-transform: uppercase;\n letter-spacing: 0.16em;\n}\n.ri-actionList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.ri-actionButton {\n width: 100%;\n border-radius: 8px;\n border: 1px solid var(--ri-panel-surface-border);\n background: var(--ri-panel-surface);\n color: var(--ri-panel-button-text);\n padding: 10px 12px;\n text-align: left;\n font-size: 14px;\n line-height: 1.4;\n cursor: pointer;\n transition:\n background-color 0.2s ease,\n border-color 0.2s ease,\n color 0.2s ease;\n}\n.ri-actionButton:hover {\n background: var(--ri-panel-surface-hover);\n border-color: var(--ri-panel-border);\n}\n.ri-actionButton--active {\n background: var(--ri-panel-accent);\n border-color: var(--ri-panel-accent);\n color: var(--ri-panel-accent-contrast);\n}\n.ri-actionButton--active:hover {\n background: var(--ri-panel-accent);\n border-color: var(--ri-panel-accent);\n}\n.ri-canvasArea {\n flex: 1 1 auto;\n position: relative;\n min-width: 0;\n}\n.ri-canvasArea--measuring {\n cursor: crosshair;\n}\n.ri-canvasArea canvas {\n opacity: 1;\n transition: opacity 0.18s ease;\n}\n.ri-canvasArea--positioning canvas {\n opacity: 0;\n transition: none;\n}\n.ri-bindingsMismatchBanner {\n position: absolute;\n top: 12px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 12px 7px 10px;\n border-radius: 8px;\n background: rgba(30, 20, 0, 0.88);\n border: 1px solid rgba(251, 191, 36, 0.35);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n color: rgb(251, 191, 36);\n font-size: 12px;\n font-weight: 500;\n line-height: 1.4;\n white-space: nowrap;\n pointer-events: auto;\n max-width: calc(100% - 32px);\n white-space: normal;\n text-align: center;\n}\n.ri-bindingsMismatchBanner__icon {\n flex-shrink: 0;\n}\n.ri-bindingsMismatchBanner__dismiss {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 4px;\n padding: 2px;\n background: none;\n border: none;\n border-radius: 4px;\n color: rgba(251, 191, 36, 0.7);\n cursor: pointer;\n transition: color 0.15s ease, background 0.15s ease;\n}\n.ri-bindingsMismatchBanner__dismiss:hover {\n color: rgb(251, 191, 36);\n background: rgba(251, 191, 36, 0.12);\n}\n.ri-measureTools {\n position: absolute;\n top: 16px;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-measureButton {\n width: 34px;\n height: 34px;\n padding: 0;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n color: rgba(255, 255, 255, 0.75);\n border-radius: 8px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n touch-action: manipulation;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n transition:\n background 0.15s ease,\n color 0.15s ease,\n border-color 0.15s ease;\n}\n.ri-measureButton:hover {\n background: rgba(40, 40, 40, 0.92);\n color: #fff;\n border-color: rgba(255, 255, 255, 0.25);\n}\n.ri-measureButton:active {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-measureButton--active {\n background: #22d3ee;\n border-color: #22d3ee;\n color: #06343c;\n}\n.ri-measureButton--active:hover {\n background: #38dbf0;\n color: #06343c;\n}\n.ri-measureHint {\n padding: 6px 10px;\n border-radius: 8px;\n font-size: 12px;\n line-height: 1;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n white-space: nowrap;\n}\n.ri-explodeTools {\n position: absolute;\n top: 16px;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-explodeSlider {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 34px;\n padding: 0 12px;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 8px;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n}\n.ri-explodeSlider input[type=range] {\n width: 120px;\n accent-color: #22d3ee;\n cursor: pointer;\n}\n.ri-cinematicTools {\n position: absolute;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-measureDot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: #22d3ee;\n border: 2px solid #fff;\n box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.4);\n pointer-events: none;\n}\n.ri-measureLabel {\n padding: 3px 7px;\n border-radius: 6px;\n font-size: 12px;\n font-weight: 600;\n line-height: 1;\n white-space: nowrap;\n color: #06343c;\n background: #22d3ee;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n pointer-events: none;\n user-select: none;\n}\n.ri-xrTools {\n position: absolute;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-xrButton {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n height: 34px;\n padding: 0 10px;\n font-size: 13px;\n font-weight: 500;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.16);\n border-radius: 8px;\n cursor: pointer;\n touch-action: manipulation;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n transition: background 0.15s ease, border-color 0.15s ease;\n}\n.ri-xrButton:hover {\n background: rgba(40, 40, 40, 0.92);\n border-color: rgba(255, 255, 255, 0.3);\n}\n.ri-mobileHandoffBackdrop {\n position: absolute;\n z-index: 30;\n inset: 0;\n display: grid;\n place-items: center;\n padding: 20px;\n background: rgba(2, 6, 23, 0.68);\n backdrop-filter: blur(5px);\n -webkit-backdrop-filter: blur(5px);\n}\n.ri-mobileHandoffDialog {\n position: relative;\n display: flex;\n width: min(100%, 300px);\n flex-direction: column;\n align-items: center;\n padding: 16px;\n text-align: center;\n color: #e2e8f0;\n background: #0f172a;\n border: 1px solid rgba(148, 163, 184, 0.3);\n border-radius: 16px;\n box-shadow: 0 24px 80px rgba(0, 0, 0, 0.5);\n}\n.ri-mobileHandoffDialog > svg {\n color: #67e8f9;\n}\n.ri-mobileHandoffDialog h2 {\n margin: 12px 0 0;\n font-size: 18px;\n line-height: 1.25;\n color: #fff;\n}\n.ri-mobileHandoffDialog p {\n margin: 8px 0 16px;\n font-size: 13px;\n line-height: 1.5;\n color: #cbd5e1;\n}\n.ri-mobileHandoffQr {\n display: grid;\n place-items: center;\n padding: 10px;\n background: #fff;\n border-radius: 10px;\n}\n.ri-mobileHandoffDialog a {\n margin-top: 16px;\n font-size: 13px;\n font-weight: 600;\n color: #a5f3fc;\n text-decoration: underline;\n text-underline-offset: 3px;\n}\n.ri-mobileHandoffClose {\n position: absolute;\n top: 10px;\n right: 10px;\n display: grid;\n width: 30px;\n height: 30px;\n place-items: center;\n padding: 0;\n color: #cbd5e1;\n background: transparent;\n border: 0;\n border-radius: 6px;\n cursor: pointer;\n}\n.ri-mobileHandoffClose:hover {\n color: #fff;\n background: rgba(255, 255, 255, 0.1);\n}\n.ri-arHintOverlay {\n position: absolute;\n top: 18px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n width: min(calc(100% - 32px), 360px);\n pointer-events: none;\n}\n.ri-arHint {\n margin: 0 auto;\n width: fit-content;\n max-width: 100%;\n padding: 6px 12px;\n font-size: 11px;\n font-weight: 500;\n line-height: 1.2;\n text-align: center;\n color: rgba(255, 255, 255, 0.92);\n background: rgba(20, 20, 20, 0.58);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 999px;\n user-select: none;\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n}\n.ri-arOverlay {\n position: absolute;\n bottom: 24px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n width: min(calc(100% - 32px), 420px);\n pointer-events: none;\n}\n.ri-arControls {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n}\n.ri-arStatus,\n.ri-arActionButton {\n max-width: 100%;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.3;\n text-align: center;\n white-space: nowrap;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.16);\n border-radius: 19px;\n user-select: none;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n}\n.ri-arStatus {\n pointer-events: none;\n}\n.ri-arActionButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n cursor: pointer;\n pointer-events: auto;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n transition:\n background 0.15s ease,\n border-color 0.15s ease,\n color 0.15s ease,\n transform 0.15s ease;\n}\n.ri-arActionButton svg {\n flex-shrink: 0;\n}\n.ri-arActionButton:hover {\n background: rgba(40, 40, 40, 0.92);\n border-color: rgba(255, 255, 255, 0.3);\n}\n.ri-arActionButton:active {\n transform: scale(0.98);\n}\n.ri-arActionButton--primary {\n color: #06343c;\n background: #22d3ee;\n border-color: transparent;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n}\n.ri-arActionButton--primary:hover {\n background: #67e8f9;\n border-color: transparent;\n}\n.ri-arActionButton.ri-arActionButton--active,\n.ri-arActionButton[aria-pressed=true],\n.ri-arActionButton.ri-arActionButton--active:hover,\n.ri-arActionButton[aria-pressed=true]:hover,\n.ri-arActionButton.ri-arActionButton--active:active,\n.ri-arActionButton[aria-pressed=true]:active {\n color: #06343c;\n background: #22d3ee;\n border-color: transparent;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n}\n.ri-hiddenInput {\n display: none;\n}\n.ri-popover {\n position: absolute;\n z-index: 50;\n background: #ffffff;\n border: 1px solid #e5e7eb;\n border-radius: 16px;\n box-shadow: 0 24px 48px rgba(15, 23, 42, 0.18);\n color: #111827;\n}\n.ri-popover--inline {\n position: static;\n z-index: auto;\n width: 100%;\n border-radius: 10px;\n box-shadow: none;\n}\n.ri-popover--inline .react-colorful {\n width: 100%;\n}\n.ri-popover--inline {\n background: var(--ri-databinding-surface, rgba(255, 255, 255, 0.03));\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text, #f1f5f9);\n}\n.ri-popover--inline .ri-popoverHeader {\n border-bottom-color: var( --ri-databinding-border-strong, rgba(255, 255, 255, 0.08) );\n}\n.ri-popover--inline .ri-popoverLabel {\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-colorCode {\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-secondaryButton {\n background: var(--ri-databinding-surface-hover, rgba(255, 255, 255, 0.06));\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text, #f1f5f9);\n}\n.ri-popover--inline .ri-secondaryButton:hover {\n background: rgba(255, 255, 255, 0.12);\n}\n.ri-popover--inline .ri-previewFrame {\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n}\n.ri-popover--inline .ri-emptyState {\n border-color: var(--ri-databinding-border-hover, rgba(255, 255, 255, 0.15));\n color: var(--ri-databinding-text-subtle, #64748b);\n}\n.ri-popover--inline .ri-uploadButton {\n background: var(--ri-databinding-accent-gradient, #2563eb);\n color: #ffffff;\n}\n.ri-popover--inline .ri-uploadButton:hover {\n background: var(--ri-databinding-accent-gradient-active, #1d4ed8);\n}\n.ri-popover--inline .ri-removeButton {\n background: transparent;\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-removeButton:hover {\n background: var(--ri-databinding-surface-hover, rgba(255, 255, 255, 0.06));\n}\n.ri-popover--inline .ri-formatHint {\n color: var(--ri-databinding-text-subtle, #64748b);\n}\n.ri-popoverHeader {\n padding: 12px 16px;\n}\n.ri-popoverLabel {\n margin: 0 0 8px;\n font-size: 11px;\n font-weight: 700;\n color: #6b7280;\n text-transform: uppercase;\n letter-spacing: 0.16em;\n}\n.ri-inlineRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n margin-top: 8px;\n}\n.ri-colorCode {\n font-size: 12px;\n color: #9ca3af;\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Monaco,\n Consolas,\n monospace;\n text-transform: uppercase;\n}\n.ri-secondaryButton {\n border: 1px solid #e5e7eb;\n background: #f3f4f6;\n color: #374151;\n border-radius: 8px;\n padding: 6px 10px;\n font-size: 12px;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-secondaryButton:hover {\n background: #e5e7eb;\n}\n.ri-materialBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding-left: 12px;\n padding-right: 12px;\n padding-bottom: 12px;\n}\n.ri-previewFrame {\n position: relative;\n height: 112px;\n border-radius: 12px;\n overflow: hidden;\n border: 1px solid #e5e7eb;\n}\n.ri-previewImage {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n.ri-previewCaption {\n position: absolute;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n padding: 4px 8px;\n color: #ffffff;\n font-size: 10px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ri-emptyState {\n height: 112px;\n border-radius: 12px;\n border: 2px dashed #e5e7eb;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 4px;\n color: #d1d5db;\n text-align: center;\n}\n.ri-uploadButton {\n width: 100%;\n border: 0;\n border-radius: 10px;\n background: #111827;\n color: #ffffff;\n padding: 10px 12px;\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-uploadButton:hover {\n background: #374151;\n}\n.ri-removeButton {\n width: 100%;\n border-radius: 10px;\n border: 1px solid #e5e7eb;\n background: #ffffff;\n color: #6b7280;\n padding: 8px 12px;\n font-size: 12px;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-removeButton:hover {\n background: #f9fafb;\n}\n.ri-formatHint {\n margin: 0;\n text-align: center;\n font-size: 10px;\n color: #d1d5db;\n}\n.ri-buttonContainer {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 8px;\n position: absolute;\n bottom: 12px;\n left: 12px;\n right: 12px;\n z-index: 10;\n}\n.ri-tourControls {\n display: flex;\n gap: 6px;\n align-items: center;\n flex-shrink: 0;\n}\n.ri-rightControls {\n display: flex;\n gap: 6px;\n align-items: center;\n flex-shrink: 0;\n margin-left: auto;\n}\n.ri-rightControls:only-child {\n margin-left: auto;\n}\n.ri-navButtonSingle,\n.ri-tourButton,\n.ri-resetButton,\n.ri-downloadButton {\n height: 34px;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n color: rgba(255, 255, 255, 0.75);\n border-radius: 8px;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n cursor: pointer;\n transition:\n background 0.15s ease,\n color 0.15s ease,\n border-color 0.15s ease,\n transform 0.15s ease;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n}\n.ri-navButtonSingle:hover:not(:disabled),\n.ri-tourButton:hover:not(:disabled),\n.ri-resetButton:hover:not(:disabled) {\n background: rgba(40, 40, 40, 0.92);\n color: #fff;\n border-color: rgba(255, 255, 255, 0.25);\n}\n.ri-navButtonSingle:active:not(:disabled),\n.ri-tourButton:active:not(:disabled),\n.ri-resetButton:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-navButtonSingle:disabled,\n.ri-tourButton:disabled,\n.ri-resetButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.ri-navButtonSingle {\n padding: 0 16px;\n font-size: 13px;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 8px;\n white-space: nowrap;\n}\n.ri-tourButton {\n padding: 0 12px;\n font-size: 13px;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 4px;\n white-space: nowrap;\n}\n.ri-tourButton svg {\n width: 15px;\n height: 15px;\n flex-shrink: 0;\n}\n.ri-tourButton--stop {\n border-color: rgba(255, 68, 68, 0.4);\n color: #ff6b6b;\n}\n.ri-tourButton--stop:hover:not(:disabled) {\n background: rgba(255, 68, 68, 0.15);\n border-color: rgba(255, 68, 68, 0.6);\n color: #ff6b6b;\n}\n.ri-tourButton--stop:active:not(:disabled) {\n background: #ff4444;\n color: #fff;\n border-color: #ff4444;\n}\n.ri-tourButton--next {\n border-color: rgba(56, 219, 240, 0.3);\n color: #38dbf0;\n}\n.ri-tourButton--next:hover:not(:disabled) {\n background: rgba(56, 219, 240, 0.12);\n border-color: rgba(56, 219, 240, 0.5);\n color: #38dbf0;\n}\n.ri-tourButton--next:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-resetButton {\n width: 34px;\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.ri-resetButton svg {\n width: 15px;\n height: 15px;\n pointer-events: none;\n flex-shrink: 0;\n}\n.ri-resetButton:focus-visible {\n outline: 2px solid rgba(255, 255, 255, 0.5);\n outline-offset: 2px;\n}\n.ri-downloadWrapper {\n position: relative;\n display: inline-flex;\n flex-direction: column;\n align-items: stretch;\n}\n.ri-downloadButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 0 16px;\n font-size: 13px;\n font-weight: 500;\n white-space: nowrap;\n}\n.ri-downloadButton:hover:not(:disabled) {\n background: rgba(0, 0, 0, 0.72);\n border-color: rgba(255, 255, 255, 0.3);\n color: #fff;\n}\n.ri-downloadButton:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-downloadButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.ri-downloadButton__spinner {\n display: inline-block;\n width: 13px;\n height: 13px;\n border: 2px solid rgba(255, 255, 255, 0.3);\n border-top-color: #fff;\n border-radius: 50%;\n animation: ri-spin 0.7s linear infinite;\n flex-shrink: 0;\n}\n.ri-downloadMenu {\n position: absolute;\n bottom: calc(100% + 6px);\n right: 0;\n min-width: 168px;\n background: rgba(20, 20, 20, 0.92);\n border: 1px solid rgba(255, 255, 255, 0.14);\n border-radius: 10px;\n overflow: hidden;\n z-index: 100;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n animation: ri-menuReveal 0.12s ease;\n}\n@keyframes ri-menuReveal {\n from {\n opacity: 0;\n transform: translateY(4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n.ri-downloadMenuItem {\n display: flex;\n align-items: center;\n gap: 9px;\n width: 100%;\n padding: 10px 14px;\n font-size: 13px;\n font-weight: 400;\n color: #fff;\n background: transparent;\n border: none;\n cursor: pointer;\n text-align: left;\n transition: background 0.1s ease;\n}\n.ri-downloadMenuItem:hover:not(:disabled) {\n background: rgba(255, 255, 255, 0.1);\n}\n.ri-downloadMenuItem:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.ri-downloadMenuDivider {\n height: 1px;\n background: rgba(255, 255, 255, 0.1);\n margin: 0 10px;\n}\n@keyframes ri-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.ri-annotationMarker {\n position: relative;\n width: 36px;\n height: 36px;\n border-radius: 50%;\n background: rgba(20, 20, 20, 0.82);\n border: 2px solid rgba(255, 255, 255, 0.9);\n color: #fff;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n font-weight: 700;\n cursor: pointer;\n z-index: 20;\n user-select: none;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);\n pointer-events: auto;\n transition: opacity 0.3s ease, transform 0.3s ease;\n}\n.ri-annotationMarker:hover {\n transform: scale(1.08);\n box-shadow: 0 12px 28px rgba(15, 23, 42, 0.18);\n background: rgba(40, 40, 40, 0.92);\n}\n.ri-annotationPopup {\n position: absolute;\n width: min(270px, calc(100vw - 32px));\n background: rgba(20, 20, 20, 0.85);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 16px;\n padding: 16px;\n z-index: 30;\n display: flex;\n flex-direction: column;\n gap: 12px;\n box-shadow: 0 24px 48px rgba(15, 23, 42, 0.18);\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n}\n.ri-annotationInput,\n.ri-annotationTextarea {\n width: 100%;\n border: 1px solid rgba(255, 255, 255, 0.12);\n background: rgba(255, 255, 255, 0.05);\n color: #e5e7eb;\n border-radius: 10px;\n padding: 10px 12px;\n outline: none;\n font-family: inherit;\n transition: border-color 0.2s ease, box-shadow 0.2s ease;\n}\n.ri-annotationInput {\n font-size: 14px;\n font-weight: 500;\n}\n.ri-annotationTextarea {\n font-size: 13px;\n resize: vertical;\n min-height: 110px;\n}\n.ri-annotationInput:focus,\n.ri-annotationTextarea:focus {\n border-color: #38dbf0;\n box-shadow: 0 0 0 3px rgba(56, 219, 240, 0.12);\n}\n.ri-annotationActions {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.ri-annotationOkBtn {\n border: 0;\n border-radius: 8px;\n background: rgba(56, 219, 240, 0.8);\n color: #06343c;\n padding: 8px 18px;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n transition: background-color 0.2s ease, opacity 0.2s ease;\n}\n.ri-annotationOkBtn:hover:not(:disabled) {\n background: #38dbf0;\n}\n.ri-annotationOkBtn:disabled,\n.ri-annotationOkBtn.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.ri-annotationCancelBtn {\n border: 1px solid rgba(255, 255, 255, 0.12);\n background: transparent;\n color: #9ca3af;\n border-radius: 8px;\n padding: 8px 12px;\n font-size: 13px;\n cursor: pointer;\n transition: background-color 0.2s ease, border-color 0.2s ease;\n}\n.ri-annotationCancelBtn:hover {\n background: rgba(255, 255, 255, 0.05);\n border-color: rgba(255, 255, 255, 0.2);\n}\n.ri-annotationIcons {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 12px;\n color: #9ca3af;\n}\n.ri-annotationIcons svg {\n flex-shrink: 0;\n}\n.ri-viewAnnotationPopup {\n position: relative;\n background: rgba(20, 20, 20, 0.85);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 8px;\n padding: 12px 16px;\n min-width: 200px;\n max-width: min(280px, calc(100vw - 48px));\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);\n pointer-events: auto;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n animation: ri-viewAnnotationFadeIn 0.15s ease-out;\n margin-left: 24px;\n margin-top: -20px;\n isolation: isolate;\n z-index: 1000;\n}\n.ri-viewAnnotationHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n}\n.ri-viewAnnotationTitle {\n margin: 0;\n color: #e2e8f0;\n font-size: 14px;\n font-weight: 600;\n}\n.ri-viewAnnotationClose {\n background: transparent;\n border: none;\n color: #b7bdc6;\n cursor: pointer;\n font-size: 18px;\n padding: 0 4px;\n transition: color 0.2s;\n line-height: 1;\n}\n.ri-viewAnnotationClose:hover {\n color: #e2e8f0;\n}\n.ri-viewAnnotationDescription {\n margin: 0;\n color: #cbd5e1;\n font-size: 13px;\n line-height: 1.4;\n word-wrap: break-word;\n}\n@keyframes ri-viewAnnotationFadeIn {\n from {\n opacity: 0;\n transform: translateY(-6px) scale(0.96);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n.ri-annotationNavSingle {\n position: absolute;\n bottom: 120px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n pointer-events: none;\n}\n.ri-annotationNavSingle > * {\n pointer-events: auto;\n}\n.ri-navBadge {\n background: rgba(255, 255, 255, 0.1);\n padding: 2px 10px;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 400;\n color: rgba(255, 255, 255, 0.7);\n}\n.ri-annotationTitleHint {\n background: rgba(0, 0, 0, 0.7);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n color: white;\n padding: 8px 20px;\n border-radius: 8px;\n font-size: 13px;\n border: 1px solid rgba(255, 255, 255, 0.08);\n text-align: center;\n max-width: 350px;\n animation: slideUp 0.3s ease;\n}\n.ri-annotationHintTitle {\n font-weight: 600;\n color: #e5e7eb;\n}\n.ri-annotationHintDesc {\n font-size: 12px;\n opacity: 0.6;\n margin-top: 2px;\n color: #d1d5db;\n}\n@keyframes slideUp {\n from {\n opacity: 0;\n transform: translateY(10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n.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');
|
|
245
|
+
styleInject('.ri-viewer--license-checking,\n.ri-viewer--license-invalid {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n background-color: #f9f9f9;\n}\n.ri-viewer {\n --ri-font-family:\n Inter,\n "Segoe UI",\n Helvetica,\n Arial,\n sans-serif;\n --ri-viewer-bg: #1c1c1d;\n --ri-viewer-text: #111827;\n --ri-panel-bg: #ffffff;\n --ri-panel-border: #e5e7eb;\n --ri-panel-title: #111827;\n --ri-panel-muted-text: #4b5563;\n --ri-panel-subtle-text: #9ca3af;\n --ri-panel-surface: #f9fafb;\n --ri-panel-surface-hover: #f3f4f6;\n --ri-panel-surface-border: #e5e7eb;\n --ri-panel-button-text: #1f2937;\n --ri-panel-accent: #2563eb;\n --ri-panel-accent-contrast: #ffffff;\n --ri-license-overlay-bg: rgba(0, 0, 0, 0.04);\n --ri-license-overlay-border: rgba(0, 0, 0, 0.08);\n --ri-license-overlay-text: #888;\n --ri-license-overlay-error-bg: rgba(192, 57, 43, 0.06);\n --ri-license-overlay-error-border: rgba(192, 57, 43, 0.2);\n --ri-license-overlay-error-text: #c0392b;\n --ri-sidepanel-font-family:\n Inter,\n "Segoe UI",\n Helvetica,\n Arial,\n sans-serif;\n --ri-sidepanel-bg: var(--ri-databinding-bg, #111318);\n --ri-sidepanel-open-btn-bg: rgba(20, 20, 20, 0.82);\n --ri-sidepanel-open-btn-bg-hover: #27282b7e;\n --ri-sidepanel-border: rgba(255, 255, 255, 0.06);\n --ri-sidepanel-divider: rgba(255, 255, 255, 0.04);\n --ri-sidepanel-border-strong: rgba(255, 255, 255, 0.08);\n --ri-sidepanel-border-active: rgba(255, 255, 255, 0.18);\n --ri-sidepanel-shadow-left: 4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-sidepanel-shadow-right: -4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-sidepanel-text: var(--ri-databinding-text, #f1f5f9);\n --ri-sidepanel-text-emphasis: var(--ri-databinding-text-hover, #cbd5e1);\n --ri-sidepanel-text-muted: var(--ri-databinding-text-muted, #94a3b8);\n --ri-sidepanel-text-subtle: var(--ri-databinding-text-subtle, #64748b);\n --ri-sidepanel-placeholder: var(--ri-databinding-text-faint, #475569);\n --ri-sidepanel-surface: rgba(255, 255, 255, 0.05);\n --ri-sidepanel-surface-hover: rgba(255, 255, 255, 0.1);\n --ri-sidepanel-accent: #3b82f6;\n --ri-sidepanel-accent-soft: rgba(59, 130, 246, 0.12);\n --ri-databinding-bg: #111318;\n --ri-databinding-border: rgba(255, 255, 255, 0.06);\n --ri-databinding-border-strong: rgba(255, 255, 255, 0.08);\n --ri-databinding-border-hover: rgba(255, 255, 255, 0.15);\n --ri-databinding-shadow: 4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-databinding-font-family:\n "Inter",\n "Segoe UI",\n sans-serif;\n --ri-databinding-mono-font-family:\n "JetBrains Mono",\n "Fira Code",\n "Menlo",\n monospace;\n --ri-databinding-text: #f1f5f9;\n --ri-databinding-text-muted: #94a3b8;\n --ri-databinding-text-subtle: #64748b;\n --ri-databinding-text-faint: #475569;\n --ri-databinding-text-hover: #cbd5e1;\n --ri-databinding-surface: rgba(255, 255, 255, 0.03);\n --ri-databinding-surface-hover: rgba(255, 255, 255, 0.06);\n --ri-databinding-accent: #93c5fd;\n --ri-databinding-accent-glow: rgba(147, 197, 253, 0.8);\n --ri-databinding-accent-border: rgba(96, 165, 250, 0.55);\n --ri-databinding-accent-gradient:\n linear-gradient(\n \n 135deg,\n #1e3a8a 0%,\n #1d4ed8 60%,\n #2563eb 100% );\n --ri-databinding-accent-gradient-active:\n linear-gradient(\n \n 135deg,\n #1e3a8a 0%,\n #1e40af 100% );\n display: flex;\n flex-direction: row;\n width: 100%;\n height: 100%;\n min-height: 0;\n overflow: hidden;\n background: var(--ri-viewer-bg);\n position: relative;\n color: var(--ri-viewer-text);\n font-family: var(--ri-font-family);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n text-rendering: optimizeLegibility;\n font-synthesis: none;\n}\n.ri-viewer * {\n box-sizing: border-box;\n}\n.ri-viewerLeft {\n display: flex;\n flex: 1;\n min-height: 0;\n min-width: 0;\n position: relative;\n}\n.ri-viewerRight {\n display: flex;\n flex-shrink: 0;\n min-width: 0;\n}\n.ri-panel {\n width: 280px;\n min-height: 0;\n background: var(--ri-panel-bg);\n border-right: 1px solid var(--ri-panel-border);\n display: flex;\n flex-direction: column;\n padding: 20px;\n overflow-y: auto;\n flex-shrink: 0;\n transition: margin-left 0.3s ease, transform 0.3s ease;\n}\n.ri-panel--open {\n transform: translateX(0);\n margin-left: 0;\n}\n.ri-panel--closed {\n transform: translateX(-100%);\n margin-left: -280px;\n}\n.ri-panelHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 12px;\n}\n.ri-panelTitle {\n margin: 0;\n font-size: 15px;\n font-weight: 700;\n color: var(--ri-panel-title);\n line-height: 1.4;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.ri-closeButton {\n border: 0;\n background: transparent;\n color: var(--ri-panel-subtle-text);\n font-size: 22px;\n line-height: 1;\n padding: 0;\n cursor: pointer;\n flex-shrink: 0;\n transition: color 0.2s ease;\n}\n.ri-closeButton:hover {\n color: var(--ri-panel-muted-text);\n}\n.ri-json {\n margin: 0 0 16px;\n padding: 12px;\n background: var(--ri-panel-surface);\n border: 1px solid var(--ri-panel-surface-border);\n border-radius: 12px;\n font-size: 12px;\n line-height: 1.55;\n color: var(--ri-panel-button-text);\n overflow-x: auto;\n white-space: pre-wrap;\n word-break: break-word;\n}\n.ri-divider {\n border-top: 1px solid var(--ri-panel-border);\n margin-bottom: 12px;\n}\n.ri-sectionLabel {\n margin-bottom: 8px;\n font-size: 11px;\n font-weight: 700;\n color: var(--ri-panel-subtle-text);\n text-transform: uppercase;\n letter-spacing: 0.16em;\n}\n.ri-actionList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.ri-actionButton {\n width: 100%;\n border-radius: 8px;\n border: 1px solid var(--ri-panel-surface-border);\n background: var(--ri-panel-surface);\n color: var(--ri-panel-button-text);\n padding: 10px 12px;\n text-align: left;\n font-size: 14px;\n line-height: 1.4;\n cursor: pointer;\n transition:\n background-color 0.2s ease,\n border-color 0.2s ease,\n color 0.2s ease;\n}\n.ri-actionButton:hover {\n background: var(--ri-panel-surface-hover);\n border-color: var(--ri-panel-border);\n}\n.ri-actionButton--active {\n background: var(--ri-panel-accent);\n border-color: var(--ri-panel-accent);\n color: var(--ri-panel-accent-contrast);\n}\n.ri-actionButton--active:hover {\n background: var(--ri-panel-accent);\n border-color: var(--ri-panel-accent);\n}\n.ri-canvasArea {\n flex: 1 1 auto;\n position: relative;\n min-width: 0;\n}\n.ri-canvasArea--measuring {\n cursor: crosshair;\n}\n.ri-canvasArea canvas {\n opacity: 1;\n transition: opacity 0.18s ease;\n}\n.ri-canvasArea--positioning canvas {\n opacity: 0;\n transition: none;\n}\n.ri-bindingsMismatchBanner {\n position: absolute;\n top: 12px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 12px 7px 10px;\n border-radius: 8px;\n background: rgba(30, 20, 0, 0.88);\n border: 1px solid rgba(251, 191, 36, 0.35);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n color: rgb(251, 191, 36);\n font-size: 12px;\n font-weight: 500;\n line-height: 1.4;\n white-space: nowrap;\n pointer-events: auto;\n max-width: calc(100% - 32px);\n white-space: normal;\n text-align: center;\n}\n.ri-bindingsMismatchBanner__icon {\n flex-shrink: 0;\n}\n.ri-bindingsMismatchBanner__dismiss {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 4px;\n padding: 2px;\n background: none;\n border: none;\n border-radius: 4px;\n color: rgba(251, 191, 36, 0.7);\n cursor: pointer;\n transition: color 0.15s ease, background 0.15s ease;\n}\n.ri-bindingsMismatchBanner__dismiss:hover {\n color: rgb(251, 191, 36);\n background: rgba(251, 191, 36, 0.12);\n}\n.ri-measureTools,\n.ri-uvCheckerTools {\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.ri-uvCheckerBanner {\n position: absolute;\n top: 12px;\n right: 12px;\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 8px;\n max-width: min(360px, calc(100% - 24px));\n padding: 7px 11px;\n border: 1px solid rgba(244, 114, 182, 0.4);\n border-radius: 8px;\n background: rgba(40, 10, 35, 0.9);\n color: rgb(249, 168, 212);\n font-size: 12px;\n font-weight: 500;\n line-height: 1.4;\n text-align: left;\n pointer-events: none;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n}\n.ri-uvCheckerBanner__icon {\n flex-shrink: 0;\n}\n@media (max-width: 768px) {\n .ri-xrTools {\n gap: 6px;\n }\n .ri-xrButton {\n height: 34px;\n gap: 6px;\n padding: 0 8px;\n font-size: 11px;\n }\n .ri-xrButton svg {\n width: 14px;\n height: 14px;\n }\n .ri-xrButtonPrefix {\n display: none;\n }\n .ri-annotationNavSingle {\n bottom: 100px;\n }\n .ri-navButtonSingle {\n padding: 8px 20px;\n font-size: 13px;\n min-width: 110px;\n }\n .ri-annotationTitleHint {\n padding: 6px 14px;\n font-size: 12px;\n max-width: 250px;\n }\n .ri-viewAnnotationPopup {\n min-width: 160px;\n max-width: 220px;\n padding: 10px 12px;\n }\n .ri-viewAnnotationTitle {\n font-size: 13px;\n }\n .ri-viewAnnotationDescription {\n font-size: 12px;\n }\n .ri-buttonContainer {\n flex-wrap: wrap;\n gap: 6px;\n }\n .ri-tourControls {\n flex-wrap: wrap;\n }\n .ri-rightControls {\n flex-wrap: wrap;\n }\n}\n@media (max-width: 480px) {\n .ri-xrTools {\n gap: 5px;\n }\n .ri-xrButton {\n height: 34px;\n gap: 5px;\n font-size: 10px;\n }\n .ri-xrButton svg {\n width: 13px;\n height: 13px;\n }\n .ri-annotationNavSingle {\n bottom: 80px;\n }\n .ri-navButtonSingle {\n padding: 6px 16px;\n font-size: 12px;\n min-width: 90px;\n gap: 6px;\n }\n .ri-annotationTitleHint {\n padding: 4px 10px;\n font-size: 11px;\n max-width: 200px;\n }\n .ri-navBadge {\n font-size: 10px;\n padding: 1px 6px;\n }\n .ri-tourButton {\n padding: 0 8px;\n font-size: 12px;\n }\n .ri-tourButton span {\n display: none;\n }\n .ri-downloadButton {\n padding: 0 10px;\n font-size: 12px;\n }\n .ri-downloadButton span {\n display: none;\n }\n}\n');
|
|
246
246
|
|
|
247
247
|
// src/components/LicenseOverlay.tsx
|
|
248
248
|
var import_lucide_react = require("lucide-react");
|
|
@@ -367,6 +367,208 @@ function exportSceneMesh({
|
|
|
367
367
|
});
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
+
// src/components/ModelViewer/internal/exportUvLayout.ts
|
|
371
|
+
var import_fflate = require("fflate");
|
|
372
|
+
var SVG_SIZE = 2048;
|
|
373
|
+
var MESH_COLORS = [
|
|
374
|
+
"#00e5ff",
|
|
375
|
+
"#ff4fd8",
|
|
376
|
+
"#ffe45e",
|
|
377
|
+
"#7cff6b",
|
|
378
|
+
"#ff784f",
|
|
379
|
+
"#a78bfa",
|
|
380
|
+
"#38bdf8",
|
|
381
|
+
"#fb7185"
|
|
382
|
+
];
|
|
383
|
+
function escapeXml(value) {
|
|
384
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
385
|
+
}
|
|
386
|
+
function slugify(value, fallback) {
|
|
387
|
+
const slug = value.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
388
|
+
return slug || fallback;
|
|
389
|
+
}
|
|
390
|
+
function safeFilenameStem(value) {
|
|
391
|
+
return slugify(value, "model");
|
|
392
|
+
}
|
|
393
|
+
function getVertexIndex(geometry, elementIndex) {
|
|
394
|
+
return geometry.index ? Math.round(geometry.index.getX(elementIndex)) : elementIndex;
|
|
395
|
+
}
|
|
396
|
+
function readTriangle(geometry, uv, elementIndex) {
|
|
397
|
+
const a = getVertexIndex(geometry, elementIndex);
|
|
398
|
+
const b = getVertexIndex(geometry, elementIndex + 1);
|
|
399
|
+
const c = getVertexIndex(geometry, elementIndex + 2);
|
|
400
|
+
if (a >= uv.count || b >= uv.count || c >= uv.count) return null;
|
|
401
|
+
const values = [
|
|
402
|
+
{ index: a, u: uv.getX(a), v: uv.getY(a) },
|
|
403
|
+
{ index: b, u: uv.getX(b), v: uv.getY(b) },
|
|
404
|
+
{ index: c, u: uv.getX(c), v: uv.getY(c) }
|
|
405
|
+
];
|
|
406
|
+
return values.every(
|
|
407
|
+
(vertex) => Number.isFinite(vertex.u) && Number.isFinite(vertex.v)
|
|
408
|
+
) ? values : null;
|
|
409
|
+
}
|
|
410
|
+
function uvPointKey(vertex) {
|
|
411
|
+
return `${vertex.u.toFixed(6)},${vertex.v.toFixed(6)}`;
|
|
412
|
+
}
|
|
413
|
+
function addEdge(edges, start, end, indexed) {
|
|
414
|
+
let startKey;
|
|
415
|
+
let endKey;
|
|
416
|
+
if (indexed) {
|
|
417
|
+
startKey = start.index;
|
|
418
|
+
endKey = end.index;
|
|
419
|
+
} else {
|
|
420
|
+
startKey = uvPointKey(start);
|
|
421
|
+
endKey = uvPointKey(end);
|
|
422
|
+
}
|
|
423
|
+
const key = startKey < endKey ? `${startKey}|${endKey}` : `${endKey}|${startKey}`;
|
|
424
|
+
const existing = edges.get(key);
|
|
425
|
+
if (existing) {
|
|
426
|
+
existing.count += 1;
|
|
427
|
+
} else {
|
|
428
|
+
edges.set(key, { count: 1, start, end });
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
function addTriangleEdges(edges, triangle, indexed) {
|
|
432
|
+
addEdge(edges, triangle[0], triangle[1], indexed);
|
|
433
|
+
addEdge(edges, triangle[1], triangle[2], indexed);
|
|
434
|
+
addEdge(edges, triangle[2], triangle[0], indexed);
|
|
435
|
+
}
|
|
436
|
+
function materialForIndex(material, materialIndex) {
|
|
437
|
+
if (!Array.isArray(material)) return material;
|
|
438
|
+
return material[materialIndex] ?? null;
|
|
439
|
+
}
|
|
440
|
+
function collectUvLayouts(scene) {
|
|
441
|
+
const layouts = /* @__PURE__ */ new Map();
|
|
442
|
+
let unnamedMeshIndex = 0;
|
|
443
|
+
let unnamedMaterialIndex = 0;
|
|
444
|
+
let meshColorIndex = 0;
|
|
445
|
+
scene.traverse((node) => {
|
|
446
|
+
const mesh = node;
|
|
447
|
+
if (!mesh.isMesh || !mesh.geometry || !mesh.material) return;
|
|
448
|
+
const geometry = mesh.geometry;
|
|
449
|
+
const uv = geometry.getAttribute("uv");
|
|
450
|
+
if (!uv || uv.itemSize < 2 || uv.count < 3) return;
|
|
451
|
+
unnamedMeshIndex += 1;
|
|
452
|
+
const meshName = mesh.name || `Mesh ${unnamedMeshIndex}`;
|
|
453
|
+
const elementCount = geometry.index?.count ?? uv.count;
|
|
454
|
+
const groups = geometry.groups.length > 0 ? geometry.groups : [{ start: 0, count: elementCount, materialIndex: 0 }];
|
|
455
|
+
for (const group of groups) {
|
|
456
|
+
const materialIndex = group.materialIndex ?? 0;
|
|
457
|
+
const material = materialForIndex(mesh.material, materialIndex);
|
|
458
|
+
if (!material) continue;
|
|
459
|
+
let layout = layouts.get(material.uuid);
|
|
460
|
+
if (!layout) {
|
|
461
|
+
unnamedMaterialIndex += 1;
|
|
462
|
+
layout = {
|
|
463
|
+
name: material.name || `Material ${unnamedMaterialIndex}`,
|
|
464
|
+
layers: /* @__PURE__ */ new Map()
|
|
465
|
+
};
|
|
466
|
+
layouts.set(material.uuid, layout);
|
|
467
|
+
}
|
|
468
|
+
let layer = layout.layers.get(mesh.uuid);
|
|
469
|
+
if (!layer) {
|
|
470
|
+
layer = {
|
|
471
|
+
name: meshName,
|
|
472
|
+
color: MESH_COLORS[meshColorIndex % MESH_COLORS.length],
|
|
473
|
+
edges: /* @__PURE__ */ new Map()
|
|
474
|
+
};
|
|
475
|
+
meshColorIndex += 1;
|
|
476
|
+
layout.layers.set(mesh.uuid, layer);
|
|
477
|
+
}
|
|
478
|
+
const start = Math.max(0, group.start);
|
|
479
|
+
const end = Math.min(elementCount, start + group.count);
|
|
480
|
+
for (let elementIndex = start; elementIndex + 2 < end; elementIndex += 3) {
|
|
481
|
+
const triangle = readTriangle(geometry, uv, elementIndex);
|
|
482
|
+
if (triangle) {
|
|
483
|
+
addTriangleEdges(layer.edges, triangle, Boolean(geometry.index));
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
return [...layouts.values()].filter(
|
|
489
|
+
(layout) => [...layout.layers.values()].some((layer) => layer.edges.size > 0)
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
function coordinate(value) {
|
|
493
|
+
return (value * SVG_SIZE).toFixed(2);
|
|
494
|
+
}
|
|
495
|
+
function edgePath(edge) {
|
|
496
|
+
return `M${coordinate(edge.start.u)} ${coordinate(1 - edge.start.v)}L${coordinate(edge.end.u)} ${coordinate(1 - edge.end.v)}`;
|
|
497
|
+
}
|
|
498
|
+
function renderSvg(layout) {
|
|
499
|
+
const materialName = escapeXml(layout.name);
|
|
500
|
+
const layers = [...layout.layers.values()].filter((layer) => layer.edges.size > 0).map((layer, index) => {
|
|
501
|
+
const name = escapeXml(layer.name);
|
|
502
|
+
const id = `${slugify(layer.name, "mesh")}-${index + 1}`;
|
|
503
|
+
const allEdges = [...layer.edges.values()];
|
|
504
|
+
const boundaryEdges = allEdges.filter((edge) => edge.count === 1);
|
|
505
|
+
const visibleEdges = boundaryEdges.length > 0 ? boundaryEdges : allEdges;
|
|
506
|
+
const paths = visibleEdges.map(edgePath).join("");
|
|
507
|
+
return [
|
|
508
|
+
` <g id="${id}" data-mesh="${name}" fill="none" stroke="${layer.color}"`,
|
|
509
|
+
' stroke-width="1" stroke-linejoin="round" vector-effect="non-scaling-stroke">',
|
|
510
|
+
` <title>${name}</title>`,
|
|
511
|
+
` <path d="${paths}"/>`,
|
|
512
|
+
" </g>"
|
|
513
|
+
].join("\n");
|
|
514
|
+
}).join("\n");
|
|
515
|
+
return [
|
|
516
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
517
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${SVG_SIZE}" height="${SVG_SIZE}" viewBox="0 0 ${SVG_SIZE} ${SVG_SIZE}">`,
|
|
518
|
+
` <title>${materialName} UV layout</title>`,
|
|
519
|
+
" <desc>UV0 island-outline template exported by React Immersive. The SVG canvas maps to the complete 0\u20131 texture area.</desc>",
|
|
520
|
+
' <rect width="2048" height="2048" fill="none" stroke="#94a3b8" stroke-width="2"/>',
|
|
521
|
+
layers,
|
|
522
|
+
"</svg>"
|
|
523
|
+
].join("\n");
|
|
524
|
+
}
|
|
525
|
+
function buildUvLayoutFiles(scene, downloadFilename = "model") {
|
|
526
|
+
const layouts = collectUvLayouts(scene);
|
|
527
|
+
const stem = safeFilenameStem(downloadFilename);
|
|
528
|
+
return layouts.map((layout, index) => {
|
|
529
|
+
const materialSlug = slugify(layout.name, `material-${index + 1}`);
|
|
530
|
+
const filename = layouts.length === 1 ? `${stem}-uv-layout.svg` : `${stem}-uv-${materialSlug}-${index + 1}.svg`;
|
|
531
|
+
return {
|
|
532
|
+
filename,
|
|
533
|
+
materialName: layout.name,
|
|
534
|
+
meshCount: [...layout.layers.values()].filter(
|
|
535
|
+
(layer) => layer.edges.size > 0
|
|
536
|
+
).length,
|
|
537
|
+
svg: renderSvg(layout)
|
|
538
|
+
};
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
function downloadBlob(blob, filename) {
|
|
542
|
+
const url = URL.createObjectURL(blob);
|
|
543
|
+
const anchor = document.createElement("a");
|
|
544
|
+
anchor.href = url;
|
|
545
|
+
anchor.download = filename;
|
|
546
|
+
document.body.appendChild(anchor);
|
|
547
|
+
anchor.click();
|
|
548
|
+
document.body.removeChild(anchor);
|
|
549
|
+
URL.revokeObjectURL(url);
|
|
550
|
+
}
|
|
551
|
+
function downloadUvLayouts(scene, downloadFilename = "model") {
|
|
552
|
+
const files = buildUvLayoutFiles(scene, downloadFilename);
|
|
553
|
+
if (files.length === 0) return files;
|
|
554
|
+
if (files.length === 1) {
|
|
555
|
+
downloadBlob(
|
|
556
|
+
new Blob([files[0].svg], { type: "image/svg+xml;charset=utf-8" }),
|
|
557
|
+
files[0].filename
|
|
558
|
+
);
|
|
559
|
+
return files;
|
|
560
|
+
}
|
|
561
|
+
const archive = Object.fromEntries(
|
|
562
|
+
files.map((file) => [file.filename, (0, import_fflate.strToU8)(file.svg)])
|
|
563
|
+
);
|
|
564
|
+
const zipped = (0, import_fflate.zipSync)(archive, { level: 6 });
|
|
565
|
+
downloadBlob(
|
|
566
|
+
new Blob([zipped], { type: "application/zip" }),
|
|
567
|
+
`${safeFilenameStem(downloadFilename)}-uv-layouts.zip`
|
|
568
|
+
);
|
|
569
|
+
return files;
|
|
570
|
+
}
|
|
571
|
+
|
|
370
572
|
// src/components/ModelViewer/internal/captureImage.ts
|
|
371
573
|
var import_three2 = require("three");
|
|
372
574
|
function captureImage(capture, options = {}) {
|
|
@@ -442,6 +644,11 @@ var LIGHT_TYPES = [
|
|
|
442
644
|
"point",
|
|
443
645
|
"hemisphere"
|
|
444
646
|
];
|
|
647
|
+
var SCENE_MODEL_RENDERERS = [
|
|
648
|
+
"pbr",
|
|
649
|
+
"matcap",
|
|
650
|
+
"uv-checker"
|
|
651
|
+
];
|
|
445
652
|
var ENVIRONMENT_PRESETS = [
|
|
446
653
|
"apartment",
|
|
447
654
|
"city",
|
|
@@ -999,6 +1206,73 @@ function getMatcapTexture() {
|
|
|
999
1206
|
sharedMatcapTexture = texture;
|
|
1000
1207
|
return texture;
|
|
1001
1208
|
}
|
|
1209
|
+
var sharedUvCheckerTexture = null;
|
|
1210
|
+
function getUvCheckerTexture() {
|
|
1211
|
+
if (sharedUvCheckerTexture) return sharedUvCheckerTexture;
|
|
1212
|
+
const size = 1024;
|
|
1213
|
+
const cells = 8;
|
|
1214
|
+
const cellSize = size / cells;
|
|
1215
|
+
const colors = ["#ef4444", "#f59e0b", "#22c55e", "#6366f1"];
|
|
1216
|
+
const canvas = document.createElement("canvas");
|
|
1217
|
+
canvas.width = size;
|
|
1218
|
+
canvas.height = size;
|
|
1219
|
+
const ctx = canvas.getContext("2d");
|
|
1220
|
+
ctx.textAlign = "center";
|
|
1221
|
+
ctx.textBaseline = "middle";
|
|
1222
|
+
for (let row = 0; row < cells; row += 1) {
|
|
1223
|
+
for (let column = 0; column < cells; column += 1) {
|
|
1224
|
+
const x = column * cellSize;
|
|
1225
|
+
const y = row * cellSize;
|
|
1226
|
+
const colorIndex = (Math.floor(column / 2) + Math.floor(row / 2)) % 4;
|
|
1227
|
+
ctx.fillStyle = colors[colorIndex];
|
|
1228
|
+
ctx.fillRect(x, y, cellSize, cellSize);
|
|
1229
|
+
ctx.strokeStyle = "rgba(0, 0, 0, 0.28)";
|
|
1230
|
+
ctx.lineWidth = 2;
|
|
1231
|
+
for (let subdivision = 1; subdivision < 4; subdivision += 1) {
|
|
1232
|
+
const offset = subdivision * cellSize / 4;
|
|
1233
|
+
ctx.beginPath();
|
|
1234
|
+
ctx.moveTo(x + offset, y);
|
|
1235
|
+
ctx.lineTo(x + offset, y + cellSize);
|
|
1236
|
+
ctx.moveTo(x, y + offset);
|
|
1237
|
+
ctx.lineTo(x + cellSize, y + offset);
|
|
1238
|
+
ctx.stroke();
|
|
1239
|
+
}
|
|
1240
|
+
ctx.strokeStyle = "#09090b";
|
|
1241
|
+
ctx.lineWidth = 7;
|
|
1242
|
+
ctx.strokeRect(x, y, cellSize, cellSize);
|
|
1243
|
+
const startX = x + cellSize * 0.23;
|
|
1244
|
+
const startY = y + cellSize * 0.72;
|
|
1245
|
+
const endX = x + cellSize * 0.72;
|
|
1246
|
+
const endY = y + cellSize * 0.23;
|
|
1247
|
+
ctx.lineCap = "round";
|
|
1248
|
+
ctx.lineJoin = "round";
|
|
1249
|
+
ctx.beginPath();
|
|
1250
|
+
ctx.moveTo(startX, startY);
|
|
1251
|
+
ctx.lineTo(endX, endY);
|
|
1252
|
+
ctx.lineTo(endX - cellSize * 0.2, endY);
|
|
1253
|
+
ctx.moveTo(endX, endY);
|
|
1254
|
+
ctx.lineTo(endX, endY + cellSize * 0.2);
|
|
1255
|
+
ctx.stroke();
|
|
1256
|
+
const tileNumber = row * cells + column + 1;
|
|
1257
|
+
ctx.font = `700 ${Math.round(cellSize * 0.2)}px ui-monospace, SFMono-Regular, Menlo, monospace`;
|
|
1258
|
+
ctx.fillStyle = "#ffffff";
|
|
1259
|
+
ctx.strokeStyle = "rgba(0, 0, 0, 0.85)";
|
|
1260
|
+
ctx.lineWidth = 6;
|
|
1261
|
+
const label = String(tileNumber).padStart(2, "0");
|
|
1262
|
+
ctx.strokeText(label, x + cellSize / 2, y + cellSize * 0.82);
|
|
1263
|
+
ctx.fillText(label, x + cellSize / 2, y + cellSize * 0.82);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
const texture = new import_three4.CanvasTexture(canvas);
|
|
1267
|
+
texture.colorSpace = import_three4.SRGBColorSpace;
|
|
1268
|
+
texture.wrapS = import_three4.RepeatWrapping;
|
|
1269
|
+
texture.wrapT = import_three4.RepeatWrapping;
|
|
1270
|
+
texture.flipY = false;
|
|
1271
|
+
texture.anisotropy = 8;
|
|
1272
|
+
texture.needsUpdate = true;
|
|
1273
|
+
sharedUvCheckerTexture = texture;
|
|
1274
|
+
return texture;
|
|
1275
|
+
}
|
|
1002
1276
|
|
|
1003
1277
|
// src/components/viewer/Exposure.tsx
|
|
1004
1278
|
var import_react3 = require("react");
|
|
@@ -2895,9 +3169,11 @@ function Model({
|
|
|
2895
3169
|
node.visible = binding.visible !== false;
|
|
2896
3170
|
const material = perNodeMaterials[nodeName];
|
|
2897
3171
|
if (!material) continue;
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
3172
|
+
if (rendererMode !== "uv-checker") {
|
|
3173
|
+
const activeMaterial = materialForNode(nodeName);
|
|
3174
|
+
if (activeMaterial) {
|
|
3175
|
+
node.material = activeMaterial;
|
|
3176
|
+
}
|
|
2901
3177
|
}
|
|
2902
3178
|
if (binding.visible === false) continue;
|
|
2903
3179
|
const basicMaterial = perNodeBasicMaterials[nodeName];
|
|
@@ -2992,6 +3268,39 @@ function Model({
|
|
|
2992
3268
|
rendererMode,
|
|
2993
3269
|
invalidate
|
|
2994
3270
|
]);
|
|
3271
|
+
(0, import_react11.useEffect)(() => {
|
|
3272
|
+
if (rendererMode !== "uv-checker") return;
|
|
3273
|
+
const checkerTexture = getUvCheckerTexture();
|
|
3274
|
+
const swaps = [];
|
|
3275
|
+
scene.traverse((object) => {
|
|
3276
|
+
if (!isMeshNode(object)) return;
|
|
3277
|
+
const original = object.material;
|
|
3278
|
+
const sourceMaterials = Array.isArray(original) ? original : [original];
|
|
3279
|
+
const hasUv0 = Boolean(object.geometry.getAttribute("uv"));
|
|
3280
|
+
const diagnosticMaterials = sourceMaterials.map((source, index) => {
|
|
3281
|
+
const material = new import_three14.MeshBasicMaterial({
|
|
3282
|
+
color: hasUv0 ? "#ffffff" : "#ff00ff",
|
|
3283
|
+
map: hasUv0 ? checkerTexture : null,
|
|
3284
|
+
side: source.side
|
|
3285
|
+
});
|
|
3286
|
+
material.name = `${object.name || "mesh"}-uv-checker-${index}`;
|
|
3287
|
+
material.toneMapped = false;
|
|
3288
|
+
return material;
|
|
3289
|
+
});
|
|
3290
|
+
const diagnostic = Array.isArray(original) ? diagnosticMaterials : diagnosticMaterials[0];
|
|
3291
|
+
object.material = diagnostic;
|
|
3292
|
+
swaps.push({ node: object, original, diagnostic });
|
|
3293
|
+
});
|
|
3294
|
+
invalidate();
|
|
3295
|
+
return () => {
|
|
3296
|
+
for (const { node, original, diagnostic } of swaps) {
|
|
3297
|
+
node.material = original;
|
|
3298
|
+
const materials = Array.isArray(diagnostic) ? diagnostic : [diagnostic];
|
|
3299
|
+
for (const material of materials) material.dispose();
|
|
3300
|
+
}
|
|
3301
|
+
invalidate();
|
|
3302
|
+
};
|
|
3303
|
+
}, [scene, rendererMode, invalidate]);
|
|
2995
3304
|
(0, import_react11.useEffect)(() => {
|
|
2996
3305
|
if (!wireframeEnabled) return;
|
|
2997
3306
|
const added = [];
|
|
@@ -3438,19 +3747,31 @@ function useCameraBiasedAnchor(annotation, worldPositionsRef) {
|
|
|
3438
3747
|
});
|
|
3439
3748
|
return anchorRef;
|
|
3440
3749
|
}
|
|
3441
|
-
function AnnotationMarker3D({
|
|
3750
|
+
function AnnotationMarker3D({
|
|
3751
|
+
annotation,
|
|
3752
|
+
occluders,
|
|
3753
|
+
worldPositionsRef,
|
|
3754
|
+
onClick,
|
|
3755
|
+
onHover,
|
|
3756
|
+
showAnnotationOnHover
|
|
3757
|
+
}) {
|
|
3442
3758
|
const [occluded, setOccluded] = (0, import_react12.useState)(false);
|
|
3443
3759
|
const anchorRef = useCameraBiasedAnchor(annotation, worldPositionsRef);
|
|
3444
3760
|
const [isHovered, setIsHovered] = (0, import_react12.useState)(false);
|
|
3445
3761
|
const [zIndex, setZIndex] = (0, import_react12.useState)(1);
|
|
3446
3762
|
const clickTimeoutRef = (0, import_react12.useRef)(null);
|
|
3447
3763
|
const markerRef = (0, import_react12.useRef)(null);
|
|
3764
|
+
const hoverTimeoutRef = (0, import_react12.useRef)(null);
|
|
3448
3765
|
(0, import_react12.useEffect)(() => {
|
|
3449
3766
|
return () => {
|
|
3450
3767
|
if (clickTimeoutRef.current) {
|
|
3451
3768
|
clearTimeout(clickTimeoutRef.current);
|
|
3452
3769
|
clickTimeoutRef.current = null;
|
|
3453
3770
|
}
|
|
3771
|
+
if (hoverTimeoutRef.current) {
|
|
3772
|
+
clearTimeout(hoverTimeoutRef.current);
|
|
3773
|
+
hoverTimeoutRef.current = null;
|
|
3774
|
+
}
|
|
3454
3775
|
};
|
|
3455
3776
|
}, []);
|
|
3456
3777
|
const handleClick = (event) => {
|
|
@@ -3469,13 +3790,29 @@ function AnnotationMarker3D({ annotation, occluders, worldPositionsRef, onClick,
|
|
|
3469
3790
|
event.stopPropagation();
|
|
3470
3791
|
setIsHovered(true);
|
|
3471
3792
|
setZIndex(100);
|
|
3793
|
+
if (hoverTimeoutRef.current) {
|
|
3794
|
+
clearTimeout(hoverTimeoutRef.current);
|
|
3795
|
+
hoverTimeoutRef.current = null;
|
|
3796
|
+
}
|
|
3472
3797
|
onHover?.(annotation);
|
|
3798
|
+
if (showAnnotationOnHover) {
|
|
3799
|
+
onClick?.(annotation);
|
|
3800
|
+
}
|
|
3473
3801
|
};
|
|
3474
3802
|
const handlePointerLeave = (event) => {
|
|
3475
3803
|
event.stopPropagation();
|
|
3476
3804
|
setIsHovered(false);
|
|
3477
3805
|
setZIndex(1);
|
|
3478
|
-
|
|
3806
|
+
if (hoverTimeoutRef.current) {
|
|
3807
|
+
clearTimeout(hoverTimeoutRef.current);
|
|
3808
|
+
}
|
|
3809
|
+
hoverTimeoutRef.current = window.setTimeout(() => {
|
|
3810
|
+
onHover?.(null);
|
|
3811
|
+
if (showAnnotationOnHover) {
|
|
3812
|
+
onHover?.(null);
|
|
3813
|
+
}
|
|
3814
|
+
hoverTimeoutRef.current = null;
|
|
3815
|
+
}, 50);
|
|
3479
3816
|
};
|
|
3480
3817
|
const handlePointerDown = (event) => {
|
|
3481
3818
|
event.stopPropagation();
|
|
@@ -3600,7 +3937,8 @@ function AnnotationsLayer({
|
|
|
3600
3937
|
onAnnotationClick,
|
|
3601
3938
|
onAnnotationHover,
|
|
3602
3939
|
onCloseAnnotation,
|
|
3603
|
-
hideCloseButton = false
|
|
3940
|
+
hideCloseButton = false,
|
|
3941
|
+
showAnnotationOnHover = false
|
|
3604
3942
|
}) {
|
|
3605
3943
|
const invalidate = (0, import_fiber8.useThree)((state) => state.invalidate);
|
|
3606
3944
|
const scene = (0, import_fiber8.useThree)((state) => state.scene);
|
|
@@ -3608,6 +3946,7 @@ function AnnotationsLayer({
|
|
|
3608
3946
|
const [isLocked, setIsLocked] = (0, import_react12.useState)(false);
|
|
3609
3947
|
const groupRef = (0, import_react12.useRef)(null);
|
|
3610
3948
|
const invalidateFrameRef = (0, import_react12.useRef)(null);
|
|
3949
|
+
const hoverCloseTimeoutRef = (0, import_react12.useRef)(null);
|
|
3611
3950
|
const occluders = (0, import_react12.useMemo)(() => {
|
|
3612
3951
|
const list = [];
|
|
3613
3952
|
scene.traverse((object) => {
|
|
@@ -3638,6 +3977,9 @@ function AnnotationsLayer({
|
|
|
3638
3977
|
if (invalidateFrameRef.current !== null) {
|
|
3639
3978
|
window.cancelAnimationFrame(invalidateFrameRef.current);
|
|
3640
3979
|
}
|
|
3980
|
+
if (hoverCloseTimeoutRef.current) {
|
|
3981
|
+
clearTimeout(hoverCloseTimeoutRef.current);
|
|
3982
|
+
}
|
|
3641
3983
|
};
|
|
3642
3984
|
}, []);
|
|
3643
3985
|
const activeAnnotationToShow = isLocked ? localActiveAnnotation : activeAnnotation || localActiveAnnotation;
|
|
@@ -3649,53 +3991,71 @@ function AnnotationsLayer({
|
|
|
3649
3991
|
(annotation) => annotation.id === activeAnnotationToShow.id
|
|
3650
3992
|
) ?? activeAnnotationToShow;
|
|
3651
3993
|
}, [activeAnnotationToShow, annotations]);
|
|
3652
|
-
const handleAnnotationClick = (0, import_react12.useCallback)(
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3994
|
+
const handleAnnotationClick = (0, import_react12.useCallback)(
|
|
3995
|
+
(annotation) => {
|
|
3996
|
+
if (isLocked && localActiveAnnotation?.id === annotation.id) {
|
|
3997
|
+
setIsLocked(false);
|
|
3998
|
+
setLocalActiveAnnotation(null);
|
|
3999
|
+
onCloseAnnotation?.();
|
|
4000
|
+
return;
|
|
4001
|
+
}
|
|
4002
|
+
setIsLocked(true);
|
|
4003
|
+
setLocalActiveAnnotation(annotation);
|
|
4004
|
+
onAnnotationClick?.(annotation);
|
|
4005
|
+
},
|
|
4006
|
+
[isLocked, localActiveAnnotation, onAnnotationClick, onCloseAnnotation]
|
|
4007
|
+
);
|
|
4008
|
+
const handleAnnotationHover = (0, import_react12.useCallback)(
|
|
4009
|
+
(annotation) => {
|
|
3665
4010
|
onAnnotationHover?.(annotation);
|
|
3666
|
-
if (
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
4011
|
+
if (showAnnotationOnHover && !isLocked) {
|
|
4012
|
+
if (hoverCloseTimeoutRef.current) {
|
|
4013
|
+
clearTimeout(hoverCloseTimeoutRef.current);
|
|
4014
|
+
hoverCloseTimeoutRef.current = null;
|
|
4015
|
+
}
|
|
4016
|
+
if (annotation) {
|
|
4017
|
+
setLocalActiveAnnotation(annotation);
|
|
4018
|
+
} else {
|
|
4019
|
+
hoverCloseTimeoutRef.current = window.setTimeout(() => {
|
|
4020
|
+
setLocalActiveAnnotation(null);
|
|
4021
|
+
onCloseAnnotation?.();
|
|
4022
|
+
hoverCloseTimeoutRef.current = null;
|
|
4023
|
+
}, 100);
|
|
3671
4024
|
}
|
|
3672
4025
|
}
|
|
3673
|
-
}
|
|
3674
|
-
|
|
4026
|
+
},
|
|
4027
|
+
[isLocked, onAnnotationHover, showAnnotationOnHover, onCloseAnnotation]
|
|
4028
|
+
);
|
|
3675
4029
|
const handleClose = (0, import_react12.useCallback)(() => {
|
|
3676
4030
|
setIsLocked(false);
|
|
3677
4031
|
setLocalActiveAnnotation(null);
|
|
3678
4032
|
onCloseAnnotation?.();
|
|
3679
4033
|
}, [onCloseAnnotation]);
|
|
3680
|
-
const handleCanvasClick = (0, import_react12.useCallback)(
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
4034
|
+
const handleCanvasClick = (0, import_react12.useCallback)(
|
|
4035
|
+
(event) => {
|
|
4036
|
+
const target = event.target;
|
|
4037
|
+
if (target.closest?.(".ri-annotationMarker") || target.closest?.(".ri-viewAnnotationPopup") || target.closest?.(".annotation-html-wrapper")) {
|
|
4038
|
+
return;
|
|
4039
|
+
}
|
|
4040
|
+
const canvas = canvasRootRef.current;
|
|
4041
|
+
if (canvas && canvas.contains(target)) {
|
|
4042
|
+
setIsLocked(false);
|
|
4043
|
+
setLocalActiveAnnotation(null);
|
|
4044
|
+
onCloseAnnotation?.();
|
|
4045
|
+
}
|
|
4046
|
+
},
|
|
4047
|
+
[canvasRootRef, onCloseAnnotation]
|
|
4048
|
+
);
|
|
4049
|
+
const handleEscapeKey = (0, import_react12.useCallback)(
|
|
4050
|
+
(event) => {
|
|
4051
|
+
if (event.key === "Escape" && isLocked) {
|
|
4052
|
+
setIsLocked(false);
|
|
4053
|
+
setLocalActiveAnnotation(null);
|
|
4054
|
+
onCloseAnnotation?.();
|
|
4055
|
+
}
|
|
4056
|
+
},
|
|
4057
|
+
[isLocked, onCloseAnnotation]
|
|
4058
|
+
);
|
|
3699
4059
|
(0, import_react12.useEffect)(() => {
|
|
3700
4060
|
const canvas = canvasRootRef.current;
|
|
3701
4061
|
if (canvas) {
|
|
@@ -3745,7 +4105,8 @@ function AnnotationsLayer({
|
|
|
3745
4105
|
occluders,
|
|
3746
4106
|
worldPositionsRef,
|
|
3747
4107
|
onClick: handleAnnotationClick,
|
|
3748
|
-
onHover: handleAnnotationHover
|
|
4108
|
+
onHover: handleAnnotationHover,
|
|
4109
|
+
showAnnotationOnHover
|
|
3749
4110
|
},
|
|
3750
4111
|
stableKey
|
|
3751
4112
|
);
|
|
@@ -5533,6 +5894,16 @@ function computeMeasureBox(scene) {
|
|
|
5533
5894
|
size: [size.x, size.y, size.z]
|
|
5534
5895
|
};
|
|
5535
5896
|
}
|
|
5897
|
+
function computeUvMeshStats(scene) {
|
|
5898
|
+
const stats = { total: 0, missing: 0 };
|
|
5899
|
+
scene.traverse((node) => {
|
|
5900
|
+
const mesh = node;
|
|
5901
|
+
if (!mesh.isMesh || !mesh.geometry) return;
|
|
5902
|
+
stats.total += 1;
|
|
5903
|
+
if (!mesh.geometry.getAttribute("uv")) stats.missing += 1;
|
|
5904
|
+
});
|
|
5905
|
+
return stats;
|
|
5906
|
+
}
|
|
5536
5907
|
function computeModelBounds(scene) {
|
|
5537
5908
|
const box = new import_three19.Box3().setFromObject(scene);
|
|
5538
5909
|
if (box.isEmpty()) return null;
|
|
@@ -5611,6 +5982,7 @@ function ModelViewer({
|
|
|
5611
5982
|
ktx2TranscoderPath,
|
|
5612
5983
|
meshopt = true,
|
|
5613
5984
|
showMeasureTools = false,
|
|
5985
|
+
showUvCheckerButton = false,
|
|
5614
5986
|
showExplodeControls = false,
|
|
5615
5987
|
cinematic,
|
|
5616
5988
|
measurementUnit = "m",
|
|
@@ -5618,7 +5990,8 @@ function ModelViewer({
|
|
|
5618
5990
|
usdzUrl,
|
|
5619
5991
|
arScaleMode = "normalized",
|
|
5620
5992
|
mobileHandoffUrl,
|
|
5621
|
-
showAnnotationNavigation = true
|
|
5993
|
+
showAnnotationNavigation = true,
|
|
5994
|
+
showAnnotationOnHover = false
|
|
5622
5995
|
}) {
|
|
5623
5996
|
const { activeTab, activeSubTab } = useBindingBuilderContext();
|
|
5624
5997
|
const license = useLicense(licenseKey);
|
|
@@ -5626,6 +5999,24 @@ function ModelViewer({
|
|
|
5626
5999
|
() => sceneConfig ?? DEFAULT_SCENE_CONFIG,
|
|
5627
6000
|
[sceneConfig]
|
|
5628
6001
|
);
|
|
6002
|
+
const [uvCheckerOverride, setUvCheckerOverride] = (0, import_react22.useState)(
|
|
6003
|
+
null
|
|
6004
|
+
);
|
|
6005
|
+
const configuredUvChecker = resolvedSceneConfig.model.renderer === "uv-checker";
|
|
6006
|
+
const uvCheckerActive = showUvCheckerButton ? uvCheckerOverride ?? configuredUvChecker : configuredUvChecker;
|
|
6007
|
+
const renderedSceneConfig = (0, import_react22.useMemo)(() => {
|
|
6008
|
+
const renderer = uvCheckerActive ? "uv-checker" : configuredUvChecker ? "pbr" : resolvedSceneConfig.model.renderer;
|
|
6009
|
+
if (renderer === resolvedSceneConfig.model.renderer) {
|
|
6010
|
+
return resolvedSceneConfig;
|
|
6011
|
+
}
|
|
6012
|
+
return {
|
|
6013
|
+
...resolvedSceneConfig,
|
|
6014
|
+
model: { ...resolvedSceneConfig.model, renderer }
|
|
6015
|
+
};
|
|
6016
|
+
}, [configuredUvChecker, resolvedSceneConfig, uvCheckerActive]);
|
|
6017
|
+
(0, import_react22.useEffect)(() => {
|
|
6018
|
+
setUvCheckerOverride(null);
|
|
6019
|
+
}, [modelUrl, resolvedSceneConfig.model.renderer, showUvCheckerButton]);
|
|
5629
6020
|
const viewerBackdropColor = (0, import_react22.useMemo)(() => {
|
|
5630
6021
|
if (backgroundColor) return backgroundColor;
|
|
5631
6022
|
const bg = resolvedSceneConfig.background;
|
|
@@ -5688,6 +6079,7 @@ function ModelViewer({
|
|
|
5688
6079
|
!modelUrl || Boolean(onAutoFit) || Boolean(camera)
|
|
5689
6080
|
);
|
|
5690
6081
|
const [downloadMenuOpen, setDownloadMenuOpen] = (0, import_react22.useState)(false);
|
|
6082
|
+
const [uvExportError, setUvExportError] = (0, import_react22.useState)(null);
|
|
5691
6083
|
const [modelLoaded, setModelLoaded] = (0, import_react22.useState)(false);
|
|
5692
6084
|
const [modelLoadFailed, setModelLoadFailed] = (0, import_react22.useState)(false);
|
|
5693
6085
|
const [bindingsMismatch, setBindingsMismatch] = (0, import_react22.useState)(false);
|
|
@@ -5696,6 +6088,10 @@ function ModelViewer({
|
|
|
5696
6088
|
null
|
|
5697
6089
|
);
|
|
5698
6090
|
const [largeModelNoticeDismissed, setLargeModelNoticeDismissed] = (0, import_react22.useState)(false);
|
|
6091
|
+
const [uvMeshStats, setUvMeshStats] = (0, import_react22.useState)({
|
|
6092
|
+
total: 0,
|
|
6093
|
+
missing: 0
|
|
6094
|
+
});
|
|
5699
6095
|
const [loaderMessage, setLoaderMessage] = (0, import_react22.useState)("Loading model...");
|
|
5700
6096
|
const currentObjectBindingsRef = (0, import_react22.useRef)({});
|
|
5701
6097
|
const captureInitialCameraState = (0, import_react22.useCallback)(() => {
|
|
@@ -6576,6 +6972,7 @@ function ModelViewer({
|
|
|
6576
6972
|
setModelLoaded(false);
|
|
6577
6973
|
setModelLoadFailed(false);
|
|
6578
6974
|
setBindingsMismatch(false);
|
|
6975
|
+
setUvMeshStats({ total: 0, missing: 0 });
|
|
6579
6976
|
setLoaderMessage("Loading model...");
|
|
6580
6977
|
if (!modelUrl) {
|
|
6581
6978
|
setIsAutoFraming(false);
|
|
@@ -6765,6 +7162,7 @@ function ModelViewer({
|
|
|
6765
7162
|
sceneRef.current = scene;
|
|
6766
7163
|
setMeasureBox(computeMeasureBox(scene));
|
|
6767
7164
|
setModelBounds(computeModelBounds(scene));
|
|
7165
|
+
setUvMeshStats(computeUvMeshStats(scene));
|
|
6768
7166
|
const triangles = countSceneTriangles(scene);
|
|
6769
7167
|
setLargeModelTriangles(triangles > MAX_BVH_TRIANGLES ? triangles : null);
|
|
6770
7168
|
setLargeModelNoticeDismissed(false);
|
|
@@ -6879,6 +7277,17 @@ function ModelViewer({
|
|
|
6879
7277
|
console.error("Screenshot failed:", err);
|
|
6880
7278
|
});
|
|
6881
7279
|
}, [downloadFilename]);
|
|
7280
|
+
const handleDownloadUvLayout = (0, import_react22.useCallback)(() => {
|
|
7281
|
+
const scene = sceneRef.current;
|
|
7282
|
+
if (!scene) return;
|
|
7283
|
+
const files = downloadUvLayouts(scene, downloadFilename);
|
|
7284
|
+
if (files.length === 0) {
|
|
7285
|
+
setUvExportError("This model does not contain UV coordinates.");
|
|
7286
|
+
return;
|
|
7287
|
+
}
|
|
7288
|
+
setUvExportError(null);
|
|
7289
|
+
setDownloadMenuOpen(false);
|
|
7290
|
+
}, [downloadFilename]);
|
|
6882
7291
|
const handleReset = (0, import_react22.useCallback)(async () => {
|
|
6883
7292
|
if (onAutoFit) {
|
|
6884
7293
|
await onAutoFit();
|
|
@@ -7136,6 +7545,20 @@ function ModelViewer({
|
|
|
7136
7545
|
]
|
|
7137
7546
|
}
|
|
7138
7547
|
),
|
|
7548
|
+
renderedSceneConfig.model.renderer === "uv-checker" && uvMeshStats.missing > 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "ri-uvCheckerBanner", role: "status", children: [
|
|
7549
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
7550
|
+
import_lucide_react3.AlertTriangle,
|
|
7551
|
+
{
|
|
7552
|
+
size: 14,
|
|
7553
|
+
className: "ri-uvCheckerBanner__icon"
|
|
7554
|
+
}
|
|
7555
|
+
),
|
|
7556
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { children: [
|
|
7557
|
+
uvMeshStats.missing === uvMeshStats.total ? "UV Checker: this model has no UV0 coordinates." : `UV Checker: ${uvMeshStats.missing} of ${uvMeshStats.total} meshes have no UV0 coordinates.`,
|
|
7558
|
+
" ",
|
|
7559
|
+
"Missing UVs are shown in magenta."
|
|
7560
|
+
] })
|
|
7561
|
+
] }),
|
|
7139
7562
|
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
7140
7563
|
"input",
|
|
7141
7564
|
{
|
|
@@ -7152,7 +7575,7 @@ function ModelViewer({
|
|
|
7152
7575
|
camera,
|
|
7153
7576
|
backgroundColor,
|
|
7154
7577
|
shadows: shadows && resolvedSceneConfig.shadows.enabled,
|
|
7155
|
-
sceneConfig:
|
|
7578
|
+
sceneConfig: renderedSceneConfig,
|
|
7156
7579
|
selectedObjectRef: currentSelectedObjectRef,
|
|
7157
7580
|
hoveredObjectRef: currentHoveredObjectRef,
|
|
7158
7581
|
highlightOnHover,
|
|
@@ -7200,7 +7623,7 @@ function ModelViewer({
|
|
|
7200
7623
|
nodeRefsOut: nodeRefsMap,
|
|
7201
7624
|
materialRefsOut,
|
|
7202
7625
|
panelHoveredItem: panelHoveredObject ?? panelHoveredItem,
|
|
7203
|
-
sceneConfig:
|
|
7626
|
+
sceneConfig: renderedSceneConfig,
|
|
7204
7627
|
zoomOnSelected,
|
|
7205
7628
|
highlightOnHover,
|
|
7206
7629
|
decoderConfig,
|
|
@@ -7219,7 +7642,8 @@ function ModelViewer({
|
|
|
7219
7642
|
modelLoaded,
|
|
7220
7643
|
onAnnotationClick: handleAnnotationMarkerClick,
|
|
7221
7644
|
onCloseAnnotation: closeViewingAnnotation,
|
|
7222
|
-
hideCloseButton: isGuidedTourActive
|
|
7645
|
+
hideCloseButton: isGuidedTourActive,
|
|
7646
|
+
showAnnotationOnHover
|
|
7223
7647
|
}
|
|
7224
7648
|
),
|
|
7225
7649
|
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
@@ -7391,11 +7815,34 @@ function ModelViewer({
|
|
|
7391
7815
|
)
|
|
7392
7816
|
}
|
|
7393
7817
|
),
|
|
7818
|
+
showUvCheckerButton && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
7819
|
+
"div",
|
|
7820
|
+
{
|
|
7821
|
+
className: "ri-uvCheckerTools",
|
|
7822
|
+
style: { top: `${topLeftRow(xrToolsVisible ? 1 : 0)}px` },
|
|
7823
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
7824
|
+
"button",
|
|
7825
|
+
{
|
|
7826
|
+
type: "button",
|
|
7827
|
+
className: `ri-measureButton ${uvCheckerActive ? "ri-measureButton--active" : ""}`,
|
|
7828
|
+
onClick: () => setUvCheckerOverride(
|
|
7829
|
+
(previous) => !(previous ?? configuredUvChecker)
|
|
7830
|
+
),
|
|
7831
|
+
title: uvCheckerActive ? "Disable UV checker" : "Enable UV checker",
|
|
7832
|
+
"aria-label": uvCheckerActive ? "Disable UV checker" : "Enable UV checker",
|
|
7833
|
+
"aria-pressed": uvCheckerActive,
|
|
7834
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react3.Grid3X3, { size: 16 })
|
|
7835
|
+
}
|
|
7836
|
+
)
|
|
7837
|
+
}
|
|
7838
|
+
),
|
|
7394
7839
|
showMeasureTools && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
7395
7840
|
"div",
|
|
7396
7841
|
{
|
|
7397
7842
|
className: "ri-measureTools",
|
|
7398
|
-
style: {
|
|
7843
|
+
style: {
|
|
7844
|
+
top: `${topLeftRow((xrToolsVisible ? 1 : 0) + (showUvCheckerButton ? 1 : 0))}px`
|
|
7845
|
+
},
|
|
7399
7846
|
children: [
|
|
7400
7847
|
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
7401
7848
|
"button",
|
|
@@ -7438,7 +7885,7 @@ function ModelViewer({
|
|
|
7438
7885
|
{
|
|
7439
7886
|
className: "ri-explodeTools",
|
|
7440
7887
|
style: {
|
|
7441
|
-
top: `${topLeftRow((xrToolsVisible ? 1 : 0) + (showMeasureTools ? 1 : 0))}px`
|
|
7888
|
+
top: `${topLeftRow((xrToolsVisible ? 1 : 0) + (showUvCheckerButton ? 1 : 0) + (showMeasureTools ? 1 : 0))}px`
|
|
7442
7889
|
},
|
|
7443
7890
|
children: [
|
|
7444
7891
|
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
@@ -7478,7 +7925,7 @@ function ModelViewer({
|
|
|
7478
7925
|
{
|
|
7479
7926
|
className: "ri-cinematicTools",
|
|
7480
7927
|
style: {
|
|
7481
|
-
top: `${topLeftRow((xrToolsVisible ? 1 : 0) + (showMeasureTools ? 1 : 0) + (showExplodeControls ? 1 : 0))}px`
|
|
7928
|
+
top: `${topLeftRow((xrToolsVisible ? 1 : 0) + (showUvCheckerButton ? 1 : 0) + (showMeasureTools ? 1 : 0) + (showExplodeControls ? 1 : 0))}px`
|
|
7482
7929
|
},
|
|
7483
7930
|
children: [
|
|
7484
7931
|
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
@@ -7612,13 +8059,38 @@ function ModelViewer({
|
|
|
7612
8059
|
"Download PNG"
|
|
7613
8060
|
]
|
|
7614
8061
|
}
|
|
8062
|
+
),
|
|
8063
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "ri-downloadMenuDivider" }),
|
|
8064
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
8065
|
+
"button",
|
|
8066
|
+
{
|
|
8067
|
+
className: "ri-downloadMenuItem",
|
|
8068
|
+
onClick: handleDownloadUvLayout,
|
|
8069
|
+
disabled: !sceneRef.current,
|
|
8070
|
+
children: [
|
|
8071
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react3.FileImage, { size: 14 }),
|
|
8072
|
+
"Download UV Layout"
|
|
8073
|
+
]
|
|
8074
|
+
}
|
|
8075
|
+
),
|
|
8076
|
+
uvExportError && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
8077
|
+
"div",
|
|
8078
|
+
{
|
|
8079
|
+
className: "ri-downloadMenuNotice",
|
|
8080
|
+
role: "status",
|
|
8081
|
+
"aria-live": "polite",
|
|
8082
|
+
children: uvExportError
|
|
8083
|
+
}
|
|
7615
8084
|
)
|
|
7616
8085
|
] }),
|
|
7617
8086
|
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
7618
8087
|
"button",
|
|
7619
8088
|
{
|
|
7620
8089
|
className: "ri-downloadButton",
|
|
7621
|
-
onClick: () =>
|
|
8090
|
+
onClick: () => {
|
|
8091
|
+
setUvExportError(null);
|
|
8092
|
+
setDownloadMenuOpen((v) => !v);
|
|
8093
|
+
},
|
|
7622
8094
|
disabled: isDownloading || !sceneRef.current,
|
|
7623
8095
|
title: "Download options",
|
|
7624
8096
|
children: isDownloading ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
|
|
@@ -8097,7 +8569,7 @@ function useViewerAnimations() {
|
|
|
8097
8569
|
}
|
|
8098
8570
|
|
|
8099
8571
|
// src/utils/modelFileResolver.ts
|
|
8100
|
-
var
|
|
8572
|
+
var import_fflate2 = require("fflate");
|
|
8101
8573
|
var SUPPORTED_MODEL_EXTENSIONS = [".glb", ".gltf", ".zip"];
|
|
8102
8574
|
var SUPPORTED_MODEL_MIME_TYPES = [
|
|
8103
8575
|
"model/gltf-binary",
|
|
@@ -8155,7 +8627,7 @@ async function resolveStandaloneGltfFile(file) {
|
|
|
8155
8627
|
};
|
|
8156
8628
|
}
|
|
8157
8629
|
async function resolveZippedGltfFile(file) {
|
|
8158
|
-
const zip = (0,
|
|
8630
|
+
const zip = (0, import_fflate2.unzipSync)(new Uint8Array(await file.arrayBuffer()));
|
|
8159
8631
|
const entries = /* @__PURE__ */ new Map();
|
|
8160
8632
|
for (const [rawPath, bytes] of Object.entries(zip)) {
|
|
8161
8633
|
const path = normalizeZipPath(rawPath);
|
|
@@ -8176,13 +8648,13 @@ async function resolveZippedGltfFile(file) {
|
|
|
8176
8648
|
if (!gltfBytes) {
|
|
8177
8649
|
throw new Error("Invalid GLTF zip: no .gltf found.");
|
|
8178
8650
|
}
|
|
8179
|
-
const gltf = parseGltfJson((0,
|
|
8651
|
+
const gltf = parseGltfJson((0, import_fflate2.strFromU8)(gltfBytes), gltfPath);
|
|
8180
8652
|
const rootDir = getDirname(gltfPath);
|
|
8181
8653
|
const urlsToRevoke = [];
|
|
8182
8654
|
try {
|
|
8183
8655
|
rewriteZipUris(gltf.buffers, rootDir, entries, urlsToRevoke);
|
|
8184
8656
|
rewriteZipUris(gltf.images, rootDir, entries, urlsToRevoke);
|
|
8185
|
-
const gltfBlob = new Blob([(0,
|
|
8657
|
+
const gltfBlob = new Blob([(0, import_fflate2.strToU8)(JSON.stringify(gltf))], {
|
|
8186
8658
|
type: "model/gltf+json"
|
|
8187
8659
|
});
|
|
8188
8660
|
const gltfUrl = URL.createObjectURL(gltfBlob);
|
|
@@ -8796,13 +9268,13 @@ function GeneralSceneSettings({ config, onChange }) {
|
|
|
8796
9268
|
),
|
|
8797
9269
|
/* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "se-field", children: [
|
|
8798
9270
|
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "se-fieldLabel", children: "Renderer" }),
|
|
8799
|
-
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "se-segmentedControl", children:
|
|
9271
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "se-segmentedControl se-segmentedControl--renderer", children: SCENE_MODEL_RENDERERS.map((option) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
8800
9272
|
"button",
|
|
8801
9273
|
{
|
|
8802
9274
|
type: "button",
|
|
8803
9275
|
className: `se-segmentedOption ${model.renderer === option ? "se-segmentedOption--active" : ""}`,
|
|
8804
9276
|
onClick: () => setModel({ ...model, renderer: option }),
|
|
8805
|
-
children: option.toUpperCase()
|
|
9277
|
+
children: option === "uv-checker" ? "UV CHECKER" : option.toUpperCase()
|
|
8806
9278
|
},
|
|
8807
9279
|
option
|
|
8808
9280
|
)) })
|
|
@@ -12203,7 +12675,7 @@ function CinematicTab({
|
|
|
12203
12675
|
}
|
|
12204
12676
|
|
|
12205
12677
|
// src/styles/SceneSettingsTab.css
|
|
12206
|
-
styleInject('.se-tabContent {\n display: flex;\n flex-direction: column;\n}\n.se-tabContent * {\n box-sizing: border-box;\n}\n.se-subTabRow {\n display: flex;\n justify-content: space-between;\n gap: 6px;\n padding-bottom: 16px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.1);\n margin-bottom: 4px;\n}\n.se-subTab {\n padding: 8px 12px;\n display: flex;\n justify-items: center;\n border-radius: 10px;\n border: 1px solid rgba(148, 163, 184, 0.18);\n background: rgba(255, 255, 255, 0.04);\n color: #b3bfd2;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n cursor: pointer;\n transition:\n background-color 0.18s ease,\n border-color 0.18s ease,\n color 0.18s ease;\n}\n.se-subTab:hover {\n background: rgba(255, 255, 255, 0.08);\n border-color: rgba(148, 163, 184, 0.32);\n}\n.se-subTab--active {\n background: rgba(99, 102, 241, 0.18);\n border-color: rgba(99, 102, 241, 0.4);\n color: #f8fafc;\n}\n.se-generalContent {\n display: flex;\n flex-direction: column;\n padding-top: 0;\n}\n.se-placeholder {\n font-size: 13px;\n color: #93a4c0;\n}\n.se-section {\n margin-bottom: 10px;\n border-radius: 7px;\n background: rgba(255, 255, 255, 0.03);\n border: 1px solid rgba(148, 163, 184, 0.12);\n overflow: hidden;\n padding: 0;\n}\n.se-section:last-child {\n margin-bottom: 0;\n}\n.se-sectionTitle {\n margin: 0;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: #c3cbdb;\n padding: 11px 14px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.1);\n}\n.se-sectionBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 14px;\n}\n.se-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n}\n.se-fieldInline {\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n.se-fieldLabel {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n font-size: 12px;\n font-weight: 700;\n color: #d7dee9;\n}\n.se-fieldValue {\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n color: #93a4c0;\n}\n.se-slider {\n width: 100%;\n accent-color: #2aa8d8;\n cursor: pointer;\n}\n.se-checkbox {\n width: 18px;\n height: 18px;\n accent-color: #2aa8d8;\n cursor: pointer;\n}\n.se-select,\n.se-textInput,\n.se-numberInput {\n background: rgba(15, 23, 42, 0.28);\n border: 1px solid rgba(148, 163, 184, 0.2);\n border-radius: 7px;\n color: #f8fafc;\n font-size: 13px;\n padding: 11px 13px;\n outline: none;\n}\n.se-select:focus,\n.se-textInput:focus,\n.se-numberInput:focus {\n outline: none;\n border-color: rgba(42, 168, 216, 0.65);\n box-shadow: 0 0 0 4px rgba(42, 168, 216, 0.12);\n}\n.se-select {\n min-width: 130px;\n text-transform: capitalize;\n}\n.se-textInput {\n width: 100%;\n}\n.se-numberInput {\n width: 100%;\n}\n.se-swatch {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n min-width: 96px;\n height: 30px;\n padding: 0 8px;\n border-radius: 8px;\n border: 1px solid rgba(148, 163, 184, 0.35);\n cursor: pointer;\n box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);\n}\n.se-swatchCode {\n font-size: 11px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n color: #fff;\n mix-blend-mode: difference;\n}\n.se-colorPicker {\n margin-top: 6px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n width: fit-content;\n}\n.se-colorPicker .react-colorful {\n width: 100%;\n height: 140px;\n}\n.se-inlineColorPicker .react-colorful {\n width: 100%;\n height: 240px;\n}\n.se-colorPickerPopover {\n z-index: 1000;\n padding: 10px;\n border-radius: 8px;\n background: #1b2333;\n border: 1px solid rgba(148, 163, 184, 0.25);\n box-shadow: 0 12px 28px rgba(0, 0, 0, 0.4);\n}\n.se-colorPickerPopover .react-colorful {\n width: 200px;\n height: 160px;\n}\n.se-vectorRow {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 8px;\n}\n.se-vectorAxis {\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-size: 11px;\n color: #93a4c0;\n}\n.se-panel {\n margin-bottom: 10px;\n overflow: hidden;\n border: 1px solid rgba(148, 163, 184, 0.12);\n border-radius: 7px;\n background: rgba(255, 255, 255, 0.03);\n}\n.se-panel:last-child {\n margin-bottom: 0;\n}\n.se-panelHeader {\n display: flex;\n width: 100%;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n border: none;\n background: rgba(255, 255, 255, 0.03);\n padding: 11px 14px;\n color: #e5e7eb;\n cursor: pointer;\n text-align: left;\n}\n.se-panelHeader:hover {\n background: rgba(255, 255, 255, 0.06);\n}\n.se-panelHeaderMain {\n display: flex;\n min-width: 0;\n align-items: center;\n gap: 8px;\n}\n.se-panelChevron {\n flex-shrink: 0;\n color: #8b97ab;\n transform: rotate(-90deg);\n transition: transform 0.18s ease;\n}\n.se-panelChevron--open {\n transform: rotate(0deg);\n}\n.se-panelTitle {\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: #c3cbdb;\n}\n.se-panelHelp {\n display: inline-flex;\n flex-shrink: 0;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n margin-left: 2px;\n border-radius: 999px;\n color: #b9c0ca;\n cursor: help;\n transition:\n background 0.18s ease,\n color 0.18s ease,\n transform 0.18s ease;\n}\n.se-panelHelp:hover {\n background: rgba(255, 255, 255, 0.08);\n color: #eef2ff;\n transform: translateY(-0.5px);\n}\n.se-panelTrailing {\n display: flex;\n align-items: center;\n}\n.se-panelBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 14px;\n border-top: 1px solid rgba(148, 163, 184, 0.1);\n background: transparent;\n}\n.se-iconButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 30px;\n height: 30px;\n border: none;\n border-radius: 8px;\n background: rgba(255, 255, 255, 0.08);\n color: #d7dde8;\n cursor: pointer;\n}\n.se-iconButton:hover {\n background: rgba(255, 255, 255, 0.12);\n}\n.se-axisStepperGrid {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 12px;\n}\n.se-axisStepper {\n display: grid;\n grid-template-columns: 40px 1fr 40px;\n align-items: center;\n min-height: 30px;\n border-radius: 10px;\n background: rgba(17, 24, 39, 0.62);\n border: 1px solid rgba(255, 255, 255, 0.05);\n}\n.se-axisStepperButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n border: none;\n background: transparent;\n color: #f3f4f6;\n cursor: pointer;\n}\n.se-axisStepperButton:hover {\n background: rgba(255, 255, 255, 0.04);\n}\n.se-axisStepperLabel {\n text-align: center;\n font-size: 15px;\n color: #f3f4f6;\n}\n.se-segmentedControl {\n display: grid;\n grid-auto-flow: column;\n overflow: hidden;\n border-radius: 10px;\n background: rgba(17, 24, 39, 0.62);\n}\n.se-segmentedOption {\n min-height: 35px;\n border: none;\n background: transparent;\n color: #d1d5db;\n font-size: 14px;\n text-transform: uppercase;\n cursor: pointer;\n}\n.se-segmentedOption--active {\n background: #2aa8d8;\n color: #fff;\n}\n.se-helperCard {\n padding: 32px;\n text-align: center;\n background: rgba(15, 23, 42, 0.44);\n border-radius: 10px;\n border: 1px solid rgba(148, 163, 184, 0.15);\n color: #94a3b8;\n font-size: 14px;\n}\n.se-helperCard strong {\n font-size: 14px;\n}\n.se-helperCard span {\n font-size: 13px;\n line-height: 1.4;\n color: #c7d0df;\n}\n.se-switch {\n position: relative;\n display: inline-flex;\n align-items: center;\n}\n.se-switch input[type=checkbox] {\n position: absolute;\n left: 0;\n top: 0;\n z-index: 10;\n width: 100%;\n height: 100%;\n cursor: pointer;\n opacity: 0;\n}\n.se-switchTrack {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n.se-switchTrack::before {\n content: "";\n width: 36px;\n height: 20px;\n background: #4b5563;\n display: inline-block;\n border-radius: 20px;\n transition: background-color 0.2s ease-in;\n}\n.se-switchThumb {\n position: absolute;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n left: 2px;\n top: 2px;\n z-index: 2;\n background: #fff;\n box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);\n transition: 0.2s ease-in;\n}\n.se-switch input[type=checkbox]:hover + .se-switchTrack .se-switchThumb {\n box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.2), 0 3px 8px 0 rgba(0, 0, 0, 0.1);\n}\n.se-switch input[type=checkbox]:checked + .se-switchTrack::before {\n background: #376fcb;\n}\n.se-switch input[type=checkbox]:checked + .se-switchTrack .se-switchThumb {\n background: #4285f4;\n left: 18px;\n}\n.se-switchLabel {\n font-size: 12px;\n font-weight: 700;\n letter-spacing: 0.06em;\n color: #e5e7eb;\n}\n.se-switch input[type=checkbox]:checked ~ .se-switchTrack .se-switchLabel {\n color: #2aa8d8;\n}\n.se-lightList {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.se-lightRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px solid rgba(148, 163, 184, 0.1);\n background: rgba(255, 255, 255, 0.03);\n cursor: pointer;\n transition: background-color 0.15s ease, border-color 0.15s ease;\n}\n.se-lightRow:hover {\n background: rgba(255, 255, 255, 0.06);\n}\n.se-lightRow--active {\n background: rgba(42, 168, 216, 0.12);\n border-color: rgba(42, 168, 216, 0.35);\n}\n.se-lightRowLabel {\n font-size: 13px;\n color: #d8e0ee;\n}\n.se-lightActions {\n display: flex;\n gap: 8px;\n}\n.se-lightProps {\n display: flex;\n flex-direction: column;\n gap: 14px;\n padding-top: 4px;\n border-top: 1px solid rgba(148, 163, 184, 0.1);\n}\n.se-animationList {\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\n.se-animationCard {\n display: flex;\n flex-direction: column;\n gap: 14px;\n padding: 16px;\n border-radius: 14px;\n border: 1px solid rgba(148, 163, 184, 0.16);\n background: rgba(15, 23, 42, 0.48);\n}\n.se-animationCard--active {\n border-color: rgba(42, 168, 216, 0.45);\n box-shadow: inset 0 0 0 1px rgba(42, 168, 216, 0.16);\n}\n.se-animationHeader {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 12px;\n}\n.se-animationHeading {\n display: flex;\n min-width: 0;\n flex-direction: column;\n gap: 6px;\n}\n.se-animationTitleRow {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n}\n.se-animationStatus,\n.se-animationBadge {\n display: inline-flex;\n align-items: center;\n min-height: 22px;\n padding: 0 8px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n}\n.se-animationStatus {\n background: rgba(42, 168, 216, 0.16);\n color: #9fe8ff;\n}\n.se-animationBadge {\n background: rgba(99, 102, 241, 0.18);\n color: #d9ddff;\n}\n.se-animationSource {\n font-size: 12px;\n color: #93a4c0;\n word-break: break-word;\n}\n.se-animationMeta {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n.se-animationMeta span {\n border-radius: 999px;\n background: rgba(255, 255, 255, 0.06);\n padding: 4px 9px;\n font-size: 12px;\n color: #c6d0e2;\n}\n.se-animationDangerButton,\n.se-animationButton,\n.se-animationAutoplayButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n border: 1px solid rgba(148, 163, 184, 0.2);\n border-radius: 10px;\n background: rgba(255, 255, 255, 0.04);\n color: #e2e8f0;\n cursor: pointer;\n transition:\n background-color 0.18s ease,\n border-color 0.18s ease,\n color 0.18s ease;\n}\n.se-animationButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.se-animationDangerButton {\n width: 36px;\n height: 36px;\n color: #fca5a5;\n}\n.se-animationDangerButton:hover,\n.se-animationButton:hover,\n.se-animationAutoplayButton:hover {\n background: rgba(255, 255, 255, 0.08);\n border-color: rgba(148, 163, 184, 0.34);\n}\n.se-animationButtonRow {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n}\n.se-animationButton {\n min-height: 36px;\n padding: 0 14px;\n font-size: 13px;\n font-weight: 600;\n}\n.se-animationButton--icon {\n flex: 1;\n min-width: 0;\n padding: 0;\n height: 38px;\n}\n.se-animationSeekRow {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.se-animationSeek {\n flex: 1;\n min-width: 0;\n}\n.se-animationSeekTime {\n flex-shrink: 0;\n min-width: 86px;\n text-align: right;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n color: #97a3b6;\n}\n.se-animationGrid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 12px;\n}\n.se-animationSpeedRow {\n display: grid;\n grid-template-columns: minmax(0, 1fr) 88px;\n gap: 10px;\n align-items: center;\n}\n.se-animationSpeedInput {\n text-align: right;\n}\n.se-animationAutoplayButton {\n align-self: flex-start;\n min-height: 36px;\n padding: 0 14px;\n font-size: 12px;\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n}\n.se-animationAutoplayButton--active {\n background: rgba(42, 168, 216, 0.18);\n border-color: rgba(42, 168, 216, 0.42);\n color: #dff8ff;\n}\n.se-removedAnimations {\n overflow: hidden;\n border-radius: 12px;\n border: 1px solid rgba(148, 163, 184, 0.14);\n background: rgba(255, 255, 255, 0.03);\n}\n.se-removedAnimationList {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 14px;\n}\n.se-removedAnimationRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n border-radius: 10px;\n background: rgba(15, 23, 42, 0.42);\n padding: 12px;\n}\n.se-removedAnimationName {\n font-size: 13px;\n font-weight: 600;\n color: #e2e8f0;\n}\n@media (max-width: 900px) {\n .se-animationGrid {\n grid-template-columns: 1fr;\n }\n .se-animationSpeedRow {\n grid-template-columns: 1fr;\n }\n}\n.bb-viewSpotOverlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.6);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n backdrop-filter: blur(4px);\n animation: bb-fadeIn 0.2s ease;\n}\n.bb-viewSpotPopup {\n background: #1e1e20;\n border-radius: 12px;\n max-width: 480px;\n width: 90%;\n max-height: 80vh;\n overflow-y: auto;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);\n border: 1px solid #333335;\n animation: bb-slideUp 0.25s ease;\n}\n.bb-viewSpotHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px;\n border-bottom: 1px solid #2a2a2c;\n}\n.bb-viewSpotTitle {\n font-size: 18px;\n font-weight: 600;\n color: #e8e8ea;\n margin: 0;\n}\n.bb-viewSpotClose {\n background: none;\n border: none;\n color: #888;\n font-size: 24px;\n cursor: pointer;\n padding: 0 4px;\n line-height: 1;\n transition: color 0.2s;\n}\n.bb-viewSpotClose:hover {\n color: #e8e8ea;\n}\n.bb-viewSpotBody {\n padding: 24px;\n}\n.bb-viewSpotDescription {\n color: #c8c8ca;\n font-size: 14px;\n line-height: 1.6;\n margin-bottom: 20px;\n white-space: pre-wrap;\n word-break: break-word;\n}\n.bb-viewSpotPosition {\n background: #141416;\n border-radius: 8px;\n padding: 12px 16px;\n margin-bottom: 24px;\n font-size: 13px;\n color: #a0a0a2;\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n.bb-viewSpotLabel {\n font-weight: 500;\n color: #888;\n}\n.bb-viewSpotCode {\n background: #0a0a0c;\n padding: 4px 10px;\n border-radius: 4px;\n font-family:\n "Monaco",\n "Menlo",\n monospace;\n font-size: 12px;\n color: #8be9fd;\n}\n.bb-viewSpotActions {\n display: flex;\n justify-content: flex-end;\n gap: 10px;\n padding-top: 4px;\n}\n.bb-editAnnotationOverlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.6);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n}\n.bb-editAnnotationPopup {\n background: #0f141e;\n border-radius: 16px;\n padding: 24px;\n min-width: 200px;\n max-width: 400px;\n width: 90%;\n border: 1px solid rgba(148, 163, 184, 0.18);\n}\n.bb-editAnnotationHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 16px;\n padding-bottom: 12px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.12);\n}\n.bb-editAnnotationTitle {\n color: #f8fafc;\n font-size: 18px;\n font-weight: 600;\n margin: 0;\n}\n.bb-editAnnotationClose {\n background: none;\n border: none;\n color: #94a3b8;\n font-size: 24px;\n cursor: pointer;\n padding: 0 4px;\n}\n.bb-editAnnotationClose:hover {\n color: #f8fafc;\n}\n.bb-editAnnotationBody {\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\n.bb-editAnnotationActions {\n display: flex;\n gap: 8px;\n margin-top: 8px;\n}\n.se-cinematicHint {\n margin: 0 0 10px;\n font-size: 12px;\n line-height: 1.5;\n color: rgba(255, 255, 255, 0.55);\n}\n.bb-button.se-cinematicAdd {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n width: 100%;\n margin-bottom: 12px;\n background: #135bc0;\n color: white;\n}\n.bb-button.se-cinematicAdd:hover:not(:disabled) {\n background: #1a68d6;\n}\n.se-waypointList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-bottom: 14px;\n}\n.se-waypointCard {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 12px;\n border: 1px solid rgba(255, 255, 255, 0.08);\n border-radius: 8px;\n background: rgba(255, 255, 255, 0.03);\n}\n.se-waypointBadge {\n flex: none;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n font-weight: 600;\n color: #06343c;\n background: #22d3ee;\n}\n.se-waypointBody {\n flex: 1;\n min-width: 0;\n}\n.se-waypointCoords {\n font-size: 11px;\n line-height: 1.4;\n color: rgba(255, 255, 255, 0.5);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.se-waypointActions {\n flex: none;\n display: flex;\n gap: 2px;\n}\n.se-waypointAction {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n padding: 0;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: rgba(255, 255, 255, 0.5);\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n}\n.se-waypointAction:hover:not(:disabled) {\n background: rgba(255, 255, 255, 0.08);\n color: #fff;\n}\n.se-waypointAction:disabled {\n opacity: 0.35;\n cursor: default;\n}\n.se-waypointAction--delete:hover:not(:disabled) {\n background: rgba(248, 113, 113, 0.16);\n color: #f87171;\n}\n.se-customBadge {\n display: inline-block;\n padding: 1px 8px;\n border-radius: 3px;\n background: rgba(34, 211, 238, 0.15);\n color: #22d3ee;\n font-size: 10px;\n font-weight: 600;\n margin-top: 4px;\n}\n.se-customizeToggle {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 8px 12px;\n background: rgba(255, 255, 255, 0.03);\n border-radius: 8px;\n border: 1px solid rgba(148, 163, 184, 0.08);\n margin: 8px 0 12px;\n}\n.se-toggleLabel {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n font-weight: 500;\n color: #f8fafc;\n cursor: pointer;\n user-select: none;\n}\n.se-toggleLabel input[type=checkbox] {\n width: 16px;\n height: 16px;\n accent-color: #22d3ee;\n cursor: pointer;\n}\n.se-toggleLabel svg {\n color: #22d3ee;\n width: 14px;\n height: 14px;\n}\n.se-toggleHint {\n font-size: 11px;\n color: rgba(255, 255, 255, 0.4);\n padding-left: 28px;\n}\n.se-segmentBetween {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px 14px;\n margin: 6px 0 6px 32px;\n background: rgba(255, 255, 255, 0.03);\n border-radius: 6px;\n border-left: 3px solid rgba(34, 211, 238, 0.25);\n}\n.se-segmentLabel {\n font-size: 11px;\n font-weight: 500;\n color: rgba(255, 255, 255, 0.45);\n}\n.se-segmentControls {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n.se-durationLabel {\n font-size: 10px;\n color: rgba(255, 255, 255, 0.35);\n font-weight: 500;\n white-space: nowrap;\n}\n.se-segmentSlider {\n flex: 1;\n min-width: 80px;\n max-width: 150px;\n height: 4px;\n -webkit-appearance: none;\n appearance: none;\n background: rgba(255, 255, 255, 0.1);\n border-radius: 2px;\n outline: none;\n}\n.se-segmentSlider::-webkit-slider-thumb {\n -webkit-appearance: none;\n appearance: none;\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: #22d3ee;\n cursor: pointer;\n border: 2px solid rgba(255, 255, 255, 0.2);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n}\n.se-segmentSlider::-moz-range-thumb {\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: #22d3ee;\n cursor: pointer;\n border: 2px solid rgba(255, 255, 255, 0.2);\n}\n.se-segmentValue {\n font-size: 12px;\n font-weight: 600;\n color: #22d3ee;\n min-width: 36px;\n text-align: center;\n}\n.se-presetButtons {\n display: flex;\n gap: 4px;\n}\n.se-presetButton {\n padding: 2px 8px;\n background: rgba(255, 255, 255, 0.06);\n border: 1px solid rgba(148, 163, 184, 0.15);\n border-radius: 4px;\n font-size: 10px;\n color: rgba(255, 255, 255, 0.6);\n cursor: pointer;\n transition: all 0.2s ease;\n font-weight: 500;\n}\n.se-presetButton:hover {\n background: #22d3ee;\n color: #06343c;\n border-color: #22d3ee;\n}\n.se-totalDuration {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 8px 4px;\n margin: 8px 0 12px;\n border-top: 1px solid rgba(148, 163, 184, 0.1);\n padding-top: 12px;\n}\n.se-totalLabel {\n font-size: 12px;\n color: rgba(255, 255, 255, 0.5);\n font-weight: 500;\n}\n.se-totalValue {\n font-size: 14px;\n font-weight: 600;\n color: #22d3ee;\n}\n.se-totalHint {\n font-size: 11px;\n color: rgba(255, 255, 255, 0.35);\n font-weight: 400;\n margin-left: 8px;\n}\n');
|
|
12678
|
+
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-segmentedControl--renderer {\n grid-template-columns: repeat(3, minmax(0, 1fr));\n}\n.se-segmentedControl--renderer .se-segmentedOption {\n padding-inline: 6px;\n font-size: 12px;\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');
|
|
12207
12679
|
|
|
12208
12680
|
// src/components/BindingBuilder/SceneSettings/SceneSettingsTab.tsx
|
|
12209
12681
|
var import_lucide_react11 = require("lucide-react");
|
|
@@ -14187,7 +14659,7 @@ function CameraPositionPanel({
|
|
|
14187
14659
|
var import_react37 = require("react");
|
|
14188
14660
|
|
|
14189
14661
|
// src/components/BindingBuilder/utils/bindingFactory.ts
|
|
14190
|
-
function
|
|
14662
|
+
function slugify2(value) {
|
|
14191
14663
|
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();
|
|
14192
14664
|
}
|
|
14193
14665
|
function humanizeNodeName(value) {
|
|
@@ -14233,7 +14705,7 @@ function buildDefaultActions(type) {
|
|
|
14233
14705
|
function createDefaultBinding(nodeName) {
|
|
14234
14706
|
const type = inferType(nodeName);
|
|
14235
14707
|
return {
|
|
14236
|
-
id:
|
|
14708
|
+
id: slugify2(nodeName),
|
|
14237
14709
|
modelObjectId: nodeName,
|
|
14238
14710
|
type,
|
|
14239
14711
|
label: humanizeNodeName(nodeName),
|
|
@@ -14496,7 +14968,7 @@ function useUndoableState(initial, coalesceWindowMs = 600) {
|
|
|
14496
14968
|
}
|
|
14497
14969
|
|
|
14498
14970
|
// src/components/BindingBuilder/utils/exportAssets.ts
|
|
14499
|
-
var
|
|
14971
|
+
var import_fflate3 = require("fflate");
|
|
14500
14972
|
|
|
14501
14973
|
// src/components/BindingBuilder/utils/serialization.ts
|
|
14502
14974
|
function toBindingsJson(bindings) {
|
|
@@ -14572,11 +15044,11 @@ async function downloadExport(filename, content, mimeType, zipFilename, assets)
|
|
|
14572
15044
|
downloadTextFile(filename, content, mimeType);
|
|
14573
15045
|
return;
|
|
14574
15046
|
}
|
|
14575
|
-
const files = { [filename]: (0,
|
|
15047
|
+
const files = { [filename]: (0, import_fflate3.strToU8)(content) };
|
|
14576
15048
|
for (const asset of assets) {
|
|
14577
15049
|
files[asset.path] = new Uint8Array(await asset.blob.arrayBuffer());
|
|
14578
15050
|
}
|
|
14579
|
-
const zipped = (0,
|
|
15051
|
+
const zipped = (0, import_fflate3.zipSync)(files, { level: 6 });
|
|
14580
15052
|
const blob = new Blob([zipped], { type: "application/zip" });
|
|
14581
15053
|
const url = URL.createObjectURL(blob);
|
|
14582
15054
|
const link = document.createElement("a");
|