@liveroom-tech/react-immersive 2.0.0 → 2.1.1
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 +37 -4
- package/dist/index.d.mts +24 -3
- package/dist/index.d.ts +24 -3
- package/dist/index.js +533 -299
- package/dist/index.mjs +458 -226
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -54,9 +54,11 @@ __export(index_exports, {
|
|
|
54
54
|
SimpleModelViewer: () => SimpleModelViewer,
|
|
55
55
|
material: () => material,
|
|
56
56
|
patchBindings: () => patchBindings,
|
|
57
|
+
patchSceneConfig: () => patchSceneConfig,
|
|
57
58
|
useObjectBinding: () => useObjectBinding,
|
|
58
59
|
useObjectBindingIds: () => useObjectBindingIds,
|
|
59
60
|
useObjectBindings: () => useObjectBindings,
|
|
61
|
+
useSceneConfig: () => useSceneConfig,
|
|
60
62
|
useShareableViewerState: () => useShareableViewerState,
|
|
61
63
|
useViewerActions: () => useViewerActions,
|
|
62
64
|
useViewerAnimations: () => useViewerAnimations,
|
|
@@ -5492,32 +5494,37 @@ function ObjectBindingDataPanel({
|
|
|
5492
5494
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("hr", { className: "left-panel__divider" }),
|
|
5493
5495
|
objectToRender.actions && objectToRender.actions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
|
|
5494
5496
|
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "left-panel__actions-label", children: "Actions" }),
|
|
5495
|
-
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "left-panel__actions-list", children: objectToRender.actions.map((action) => {
|
|
5497
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "left-panel__actions-list", children: objectToRender.actions.map((action, index) => {
|
|
5496
5498
|
const isActive = currentAction?.action.id === action.id && (currentAction.binding === objectToRender || currentAction.binding?.id === objectToRender.id || currentAction.binding?.modelObjectId === objectToRender.modelObjectId || currentAction.objectId === objectToRender.id || currentAction.objectId === objectToRender.modelObjectId);
|
|
5497
5499
|
const extra = renderActionExtra?.(action.id);
|
|
5498
|
-
return
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5500
|
+
return (
|
|
5501
|
+
// Keyed by position, not action.id: ids are author-supplied
|
|
5502
|
+
// and not guaranteed unique within a binding, and colliding
|
|
5503
|
+
// keys make React drop one of the buttons.
|
|
5504
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react18.Fragment, { children: [
|
|
5505
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
5506
|
+
"button",
|
|
5507
|
+
{
|
|
5508
|
+
onClick: (e) => onAction({
|
|
5509
|
+
objectId: objectToRender.modelObjectId || objectToRender.id || "",
|
|
5510
|
+
action,
|
|
5511
|
+
binding: objectToRender,
|
|
5512
|
+
screenX: e.clientX,
|
|
5513
|
+
screenY: e.clientY
|
|
5514
|
+
}),
|
|
5515
|
+
className: [
|
|
5516
|
+
"left-panel__action-btn",
|
|
5517
|
+
`left-panel__action-btn--${action.id}`,
|
|
5518
|
+
isActive ? "left-panel__action-btn--active" : ""
|
|
5519
|
+
].filter(Boolean).join(" "),
|
|
5520
|
+
"data-action-id": action.id,
|
|
5521
|
+
"data-label": action.label,
|
|
5522
|
+
children: action.label
|
|
5523
|
+
}
|
|
5524
|
+
),
|
|
5525
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ActionExtra, { content: extra })
|
|
5526
|
+
] }, index)
|
|
5527
|
+
);
|
|
5521
5528
|
}) })
|
|
5522
5529
|
] })
|
|
5523
5530
|
] })
|
|
@@ -8223,11 +8230,11 @@ function ModelViewer({
|
|
|
8223
8230
|
}
|
|
8224
8231
|
|
|
8225
8232
|
// src/components/BindingBuilder/BindingBuilder.tsx
|
|
8226
|
-
var
|
|
8233
|
+
var import_react40 = require("react");
|
|
8227
8234
|
var import_lucide_react16 = require("lucide-react");
|
|
8228
8235
|
|
|
8229
8236
|
// src/styles/BindingBuilder.css
|
|
8230
|
-
styleInject('.bb-root {\n --bb-font-family:\n Inter,\n "Segoe UI",\n Helvetica,\n Arial,\n sans-serif;\n --bb-app-bg:\n radial-gradient(\n \n circle at top left,\n rgba(226, 232, 240, 0.8),\n transparent 30% ),\n linear-gradient(\n 135deg,\n #f6f1e8 0%,\n #ebe8df 40%,\n #e9edf2 100%);\n --bb-app-text: #111827;\n --bb-sidebar-bg:\n linear-gradient(\n 180deg,\n rgba(20, 27, 45, 0.98),\n rgba(25, 34, 57, 0.95)),\n #121826;\n --bb-sidebar-text: #f8fafc;\n --bb-sidebar-muted-text: #b3bfd2;\n --bb-sidebar-subtle-text: #94a3b8;\n --bb-sidebar-border: rgba(148, 163, 184, 0.18);\n --bb-sidebar-border-hover: rgba(148, 163, 184, 0.32);\n --bb-sidebar-divider: rgba(148, 163, 184, 0.14);\n --bb-sidebar-surface: rgba(255, 255, 255, 0.04);\n --bb-sidebar-surface-hover: rgba(255, 255, 255, 0.08);\n --bb-sidebar-surface-strong: rgba(255, 255, 255, 0.05);\n --bb-sidebar-eyebrow-bg: rgba(148, 163, 184, 0.16);\n --bb-sidebar-eyebrow-text: #cbd5e1;\n --bb-sidebar-left-shadow: 18px 0 42px rgba(15, 23, 42, 0.12);\n --bb-sidebar-right-shadow: -4px 0 24px rgba(0, 0, 0, 0.35);\n --bb-sidebar-chip-bg: rgba(15, 23, 42, 0.42);\n --bb-sidebar-chip-border: rgba(148, 163, 184, 0.14);\n --bb-sidebar-accent: rgba(99, 102, 241, 0.18);\n --bb-sidebar-accent-border: rgba(99, 102, 241, 0.4);\n --bb-sidebar-accent-text: #f8fafc;\n --bb-menu-bg: #1a1f2e;\n --bb-menu-shadow: 0 16px 32px rgba(15, 23, 42, 0.4);\n display: flex;\n height: 100%;\n min-height: 0;\n width: 100%;\n background: var(--bb-app-bg);\n color: var(--bb-app-text);\n font-family: var(--bb-font-family);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n text-rendering: optimizeLegibility;\n font-synthesis: none;\n}\n.bb-root * {\n box-sizing: border-box;\n}\n.bb-root input,\n.bb-root select,\n.bb-root textarea,\n.bb-root button {\n font-family: inherit;\n}\n.bb-sidebar {\n width: 400px;\n min-height: 0;\n flex-shrink: 0;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n background: var(--bb-sidebar-bg);\n color: var(--bb-sidebar-text);\n transition: width 0.24s ease;\n}\n.bb-sidebar--left {\n border-right: 1px solid var(--bb-sidebar-border);\n box-shadow: var(--bb-sidebar-left-shadow);\n}\n.bb-sidebar--right {\n width: 300px;\n border-left: 1px solid var(--bb-sidebar-border);\n box-shadow: var(--bb-sidebar-right-shadow);\n}\n.bb-sidebar--collapsed {\n width: 36px;\n}\n.bb-sidebar--collapsed .bb-header {\n padding: 5px;\n display: flex;\n justify-content: center;\n}\n.bb-sidebar--collapsed .bb-header--sidebar {\n min-height: 36px;\n}\n.bb-sidebar--collapsed .bb-sidebarToggle {\n width: 26px;\n height: 26px;\n font-size: 12px;\n}\n.bb-header {\n padding: 5px 20px;\n border-bottom: 1px solid var(--bb-sidebar-divider);\n}\n.bb-header--sidebar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n min-height: 55px;\n}\n.bb-eyebrow {\n display: inline-block;\n margin-bottom: 12px;\n padding: 6px 10px;\n border-radius: 999px;\n background: var(--bb-sidebar-eyebrow-bg);\n color: var(--bb-sidebar-eyebrow-text);\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.18em;\n text-transform: uppercase;\n}\n.bb-title {\n margin: 0 0 0px;\n font-size: 20px;\n line-height: 0.95;\n letter-spacing: -0.04em;\n font-weight: 800;\n color: var(--bb-sidebar-text);\n}\n.bb-sidebarToggle {\n width: 36px;\n height: 36px;\n flex-shrink: 0;\n border-radius: 6px;\n border: 1px solid var(--bb-sidebar-border);\n background: var(--bb-sidebar-surface-strong);\n color: var(--bb-sidebar-text);\n font-size: 16px;\n font-weight: 800;\n cursor: pointer;\n transition:\n transform 0.18s ease,\n background-color 0.18s ease,\n border-color 0.18s ease;\n}\n.bb-sidebarToggle:hover {\n transform: translateY(-1px);\n background: var(--bb-sidebar-surface-hover);\n border-color: var(--bb-sidebar-border-hover);\n}\n.bb-subtitle {\n margin: 0;\n max-width: 32ch;\n font-size: 15px;\n line-height: 1.7;\n color: var(--bb-sidebar-muted-text);\n}\n.bb-statRow {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 10px;\n margin-top: 22px;\n}\n.bb-statChip {\n display: flex;\n flex-direction: column;\n gap: 4px;\n min-width: 0;\n padding: 12px 14px;\n border-radius: 8px;\n background: var(--bb-sidebar-chip-bg);\n border: 1px solid var(--bb-sidebar-chip-border);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);\n}\n.bb-statChip strong {\n font-size: 13px;\n color: var(--bb-sidebar-text);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.bb-statLabel {\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--bb-sidebar-subtle-text);\n}\n.bb-tabRow {\n display: flex;\n gap: 6px;\n padding: 10px 20px 10px;\n}\n.bb-tab {\n flex: 1;\n padding: 6px 10px;\n border-radius: 6px;\n border: 1px solid var(--bb-sidebar-border);\n background: var(--bb-sidebar-surface);\n color: var(--bb-sidebar-muted-text);\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.bb-tab:hover {\n background: var(--bb-sidebar-surface-hover);\n border-color: var(--bb-sidebar-border-hover);\n}\n.bb-tab--active {\n background: var(--bb-sidebar-accent);\n border-color: var(--bb-sidebar-accent-border);\n color: var(--bb-sidebar-accent-text);\n}\n.bb-toolbar {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 10px;\n padding: 8px 20px 8px;\n border-bottom: 1px solid var(--bb-sidebar-divider);\n flex-wrap: wrap;\n}\n.bb-toolbarGroup {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n.bb-toolbarDivider {\n width: 1px;\n align-self: stretch;\n background: var(--bb-sidebar-divider);\n margin: 0;\n}\n.bb-iconButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n padding: 0;\n border-radius: 7px;\n border: 1px solid var(--bb-sidebar-border);\n background: var(--bb-sidebar-surface);\n color: var(--bb-sidebar-text);\n cursor: pointer;\n transition: background-color 0.18s ease, border-color 0.18s ease;\n}\n.bb-iconButton:hover:not(:disabled) {\n background: var(--bb-sidebar-surface-hover);\n border-color: var(--bb-sidebar-border-hover);\n}\n.bb-iconButton:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.bb-exportMenuWrapper {\n position: relative;\n display: inline-flex;\n}\n.bb-exportMenu {\n position: absolute;\n bottom: calc(100% + 6px);\n right: 0;\n z-index: 20;\n display: flex;\n flex-direction: column;\n min-width: 150px;\n padding: 6px;\n border-radius: 9px;\n border: 1px solid var(--bb-sidebar-border);\n background: var(--bb-menu-bg);\n box-shadow: var(--bb-menu-shadow);\n}\n.bb-exportMenuItem {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 10px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--bb-sidebar-text);\n font-size: 12px;\n font-weight: 600;\n text-align: left;\n cursor: pointer;\n}\n.bb-exportMenuItem:hover {\n background: var(--bb-sidebar-surface-hover);\n}\n@media (max-width: 640px) {\n .bb-toolbarDivider {\n width: 100%;\n height: 1px;\n }\n}\n.bb-button,\n.bb-fileLabel {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n min-height: 32px;\n padding: 0 12px;\n border-radius: 7px;\n border: 1px solid var(--bb-sidebar-border);\n background: var(--bb-sidebar-surface);\n color: var(--bb-sidebar-text);\n font-size: 12px;\n font-weight: 700;\n cursor: pointer;\n transition:\n transform 0.18s ease,\n background-color 0.18s ease,\n border-color 0.18s ease,\n box-shadow 0.18s ease;\n}\n.bb-button:hover,\n.bb-fileLabel:hover {\n transform: translateY(-1px);\n background: rgba(255, 255, 255, 0.08);\n border-color: rgba(148, 163, 184, 0.32);\n box-shadow: 0 10px 18px rgba(15, 23, 42, 0.18);\n}\n.bb-button:disabled,\n.bb-fileLabel:has(.bb-hiddenInput:disabled) {\n opacity: 0.55;\n cursor: not-allowed;\n transform: none;\n box-shadow: none;\n}\n.bb-button--primary {\n background: #135bc0;\n border-color: transparent;\n color: #ffffff;\n}\n.bb-button--primary:hover {\n background: #1a68d6;\n}\n.bb-button--ghost {\n background: rgba(248, 250, 252, 0.08);\n}\n.bb-hiddenInput {\n display: none;\n}\n.bb-statusCard {\n margin-top: 4px;\n padding: 14px 16px;\n border-radius: 9px;\n background: rgba(15, 23, 42, 0.44);\n border: 1px solid rgba(148, 163, 184, 0.15);\n}\n.bb-statusCard--error {\n background: rgba(127, 29, 29, 0.32);\n border-color: rgba(248, 113, 113, 0.24);\n}\n.bb-statusCard--success {\n background: rgba(6, 78, 59, 0.34);\n border-color: rgba(52, 211, 153, 0.24);\n}\n.bb-statusTitle {\n margin-bottom: 6px;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: #cbd5e1;\n}\n.bb-status {\n font-size: 13px;\n line-height: 1.55;\n color: #e2e8f0;\n}\n.bb-status--error {\n color: #fecaca;\n}\n.bb-status--success {\n color: #bbf7d0;\n}\n.bb-input--error {\n border-color: #dc2626;\n}\n.bb-nodeList {\n display: flex;\n flex-direction: column;\n flex: 1 1 auto;\n min-height: 0;\n overflow: hidden;\n background: #141824;\n}\n.bb-sectionHead {\n padding: 24px 20px 16px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.07);\n}\n.bb-sectionKicker {\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: #64748b;\n}\n.bb-sectionHeading {\n margin-top: 4px;\n font-size: 22px;\n font-weight: 700;\n line-height: 1.2;\n color: #f1f5f9;\n}\n.bb-search {\n padding: 14px 16px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.07);\n}\n.bb-input,\n.bb-select,\n.bb-textarea {\n width: 100%;\n border-radius: 10px;\n border: 1px solid rgba(255, 255, 255, 0.08);\n background: rgba(255, 255, 255, 0.05);\n color: #e2e8f0;\n padding: 10px 14px;\n font-size: 13px;\n line-height: 1.45;\n}\n.bb-input::placeholder,\n.bb-textarea::placeholder {\n color: #475569;\n}\n.bb-input--compact {\n width: 64px;\n max-width: 64px;\n flex: 0 0 auto;\n padding: 7px 4px;\n text-align: center;\n}\n.bb-input:focus,\n.bb-select:focus,\n.bb-textarea:focus {\n outline: none;\n border-color: rgba(255, 255, 255, 0.18);\n background: rgba(255, 255, 255, 0.08);\n box-shadow: none;\n}\n.bb-textarea {\n min-height: 110px;\n resize: vertical;\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Monaco,\n Consolas,\n monospace;\n}\n.bb-nodeItems {\n min-height: 0;\n overflow-y: auto;\n padding: 10px 0;\n}\n.bb-nodeButton {\n width: 100%;\n border: 0;\n border-radius: 0;\n background: transparent;\n padding: 12px 20px;\n margin-bottom: 0;\n text-align: left;\n cursor: pointer;\n transition: background-color 0.12s ease, color 0.12s ease;\n border-bottom: 1px solid rgba(255, 255, 255, 0.04);\n}\n.bb-nodeButton:hover {\n background: rgba(255, 255, 255, 0.05);\n}\n.bb-nodeButton--active {\n background: rgba(255, 255, 255, 0.05);\n box-shadow: none;\n}\n.bb-nodeName {\n display: block;\n margin-bottom: 2px;\n font-size: 14px;\n font-weight: 600;\n color: #e2e8f0;\n}\n.bb-nodeMeta {\n display: block;\n font-size: 11px;\n color: #94a3b8;\n}\n.bb-editor {\n flex: 1 1 auto;\n min-height: 0;\n height: 100%;\n overflow-y: auto;\n padding: 16px 16px 28px;\n overscroll-behavior: contain;\n}\n.bb-editorHeader {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 18px;\n padding: 18px;\n border-radius: 11px;\n background: rgba(255, 255, 255, 0.05);\n border: 1px solid rgba(148, 163, 184, 0.12);\n}\n.bb-editorTitle {\n margin: 8px 0 0;\n font-size: 24px;\n line-height: 1.1;\n color: #f8fafc;\n}\n.bb-editorMeta {\n display: flex;\n flex-wrap: wrap;\n gap: 8px 12px;\n margin-top: 10px;\n font-size: 12px;\n color: #94a3b8;\n}\n.bb-editorBadge {\n flex-shrink: 0;\n padding: 8px 12px;\n border-radius: 999px;\n background: rgba(251, 191, 36, 0.12);\n border: 1px solid rgba(251, 191, 36, 0.2);\n color: #fcd34d;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n}\n.bb-emptyEditor {\n padding: 28px 8px;\n font-size: 14px;\n color: #a7b1c3;\n}\n.bb-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}\n.bb-sectionHeaderButton {\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 cursor: pointer;\n text-align: left;\n color: inherit;\n}\n.bb-sectionHeaderButton:hover {\n background: rgba(255, 255, 255, 0.06);\n}\n.bb-sectionHeaderMain {\n display: flex;\n min-width: 0;\n align-items: center;\n gap: 8px;\n}\n.bb-sectionChevron {\n flex-shrink: 0;\n color: #8b97ab;\n transform: rotate(-90deg);\n transition: transform 0.18s ease;\n}\n.bb-sectionChevron--open {\n transform: rotate(0deg);\n}\n.bb-sectionBody {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 14px;\n border-top: 1px solid rgba(148, 163, 184, 0.1);\n}\n.bb-sectionTitle {\n margin: 0;\n font-size: 11px;\n font-weight: 700;\n color: #c3cbdb;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n}\n.bb-sectionHelp {\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.bb-sectionHelp:hover {\n background: rgba(255, 255, 255, 0.08);\n color: #eef2ff;\n transform: translateY(-0.5px);\n}\n.bb-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 10px;\n}\n.bb-presetGallery {\n display: flex;\n justify-items: center;\n flex-wrap: wrap;\n gap: 4px;\n margin-bottom: 14px;\n}\n.bb-presetSwatch {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 5px;\n width: 64px;\n padding: 6px 4px;\n border-radius: 8px;\n border: 1px solid rgba(148, 163, 184, 0.18);\n background: rgba(148, 163, 184, 0.06);\n cursor: pointer;\n transition: border-color 0.15s, background 0.15s;\n}\n.bb-presetSwatch:hover:not(:disabled) {\n border-color: rgba(148, 163, 184, 0.4);\n background: rgba(148, 163, 184, 0.12);\n}\n.bb-presetSwatch:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.bb-presetSwatchColor {\n width: 32px;\n height: 32px;\n border-radius: 999px;\n border: 1px solid rgba(255, 255, 255, 0.18);\n}\n.bb-presetSwatchLabel {\n font-size: 10px;\n font-weight: 600;\n color: #cbd5e1;\n text-align: center;\n line-height: 1.2;\n}\n.bb-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n}\n.bb-field--full {\n grid-column: 1 / -1;\n}\n.bb-label {\n font-size: 12px;\n font-weight: 700;\n color: #d7dee9;\n}\n.bb-note {\n font-size: 11px;\n color: #8b97ab;\n}\n.bb-tierLock {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 16px 14px;\n border-radius: 7px;\n background: rgba(251, 191, 36, 0.08);\n border: 1px dashed rgba(251, 191, 36, 0.3);\n color: #fcd34d;\n}\n.bb-tierLockMessage {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n}\n.bb-root--licenseChecking,\n.bb-root--licenseInvalid {\n align-items: center;\n justify-content: center;\n}\n.bb-readonly {\n background: rgba(148, 163, 184, 0.08);\n color: #9ca3af;\n}\n.bb-checkboxRow {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n}\n.bb-checkbox {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 6px 5px;\n border-radius: 7px;\n background: rgba(15, 23, 42, 0.22);\n border: 1px solid rgba(148, 163, 184, 0.12);\n font-size: 13px;\n color: #d7dee9;\n}\n.bb-checkbox input {\n accent-color: #f59e0b;\n}\n.bb-sliderField {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.bb-slider {\n flex: 1 1 auto;\n min-width: 0;\n accent-color: #2aa8d8;\n}\n.bb-slider--unset {\n opacity: 0.45;\n}\n.bb-fileField {\n position: relative;\n display: flex;\n align-items: center;\n}\n.bb-fileField-input {\n width: 100%;\n padding-right: 40px;\n}\n.bb-fileFieldButton {\n position: absolute;\n right: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n border: none;\n border-radius: 5px;\n background: transparent;\n color: #aab4c5;\n cursor: pointer;\n}\n.bb-fileFieldButton:hover {\n background: rgba(255, 255, 255, 0.08);\n color: #f8fafc;\n}\n.bb-materialPanel {\n margin-top: 8px;\n border: 1px solid rgba(148, 163, 184, 0.14);\n border-radius: 6px;\n background: rgba(15, 23, 42, 0.28);\n overflow: hidden;\n}\n.bb-materialPanel:first-of-type {\n margin-top: 0;\n}\n.bb-materialPanelHeader {\n display: flex;\n width: 100%;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n border: none;\n padding: 10px 12px;\n background: rgba(255, 255, 255, 0.03);\n cursor: pointer;\n text-align: left;\n color: inherit;\n}\n.bb-materialPanelHeader:hover {\n background: rgba(255, 255, 255, 0.06);\n}\n.bb-materialPanelTitleWrap {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n color: #dbe4f0;\n}\n.bb-materialPanelChevron {\n flex-shrink: 0;\n color: #8b97ab;\n transform: rotate(-90deg);\n transition: transform 0.18s ease;\n}\n.bb-materialPanelChevron--open {\n transform: rotate(0deg);\n}\n.bb-materialPanelTitle {\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n}\n.bb-materialPanelHelp {\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.bb-materialPanelHelp:hover {\n background: rgba(255, 255, 255, 0.08);\n color: #eef2ff;\n transform: translateY(-0.5px);\n}\n.bb-materialPanelBody {\n padding: 12px;\n border-top: 1px solid rgba(148, 163, 184, 0.1);\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n.bb-actionList {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n.bb-actionCard {\n border: 1px solid rgba(148, 163, 184, 0.14);\n border-radius: 9px;\n padding: 14px;\n background: rgba(15, 23, 42, 0.2);\n}\n.bb-actionCardHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n margin-bottom: 12px;\n}\n.bb-actionCardTitle {\n font-size: 13px;\n font-weight: 700;\n color: #f8fafc;\n}\n.bb-actionCardMeta {\n flex-shrink: 0;\n padding: 5px 9px;\n border-radius: 999px;\n background: rgba(148, 163, 184, 0.12);\n color: #94a3b8;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n}\n.bb-actionGrid {\n display: grid;\n grid-template-columns: 1fr;\n gap: 10px;\n}\n.bb-actionFooter {\n margin-top: 12px;\n}\n.bb-actionRemove {\n width: 100%;\n}\n.bb-previewShell {\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n padding: 5px;\n overflow: hidden;\n}\n.bb-previewHeader {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 16px;\n padding: 0 4px 10px;\n}\n.bb-previewControls {\n display: flex;\n align-items: flex-start;\n justify-content: flex-end;\n gap: 10px;\n flex-wrap: wrap;\n}\n.bb-previewTitle {\n margin: 2px 0 0;\n font-size: 20px;\n line-height: 1.05;\n letter-spacing: -0.03em;\n color: #1f2937;\n}\n.bb-previewToggles {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 2px;\n}\n.bb-checkbox--compact {\n gap: 5px;\n padding: 0;\n background: transparent;\n border: none;\n font-size: 10px;\n font-weight: 500;\n line-height: 1;\n color: #4b5563;\n}\n.bb-checkbox--compact:hover {\n background: transparent;\n transform: none;\n}\n.bb-checkbox--compact input[type=checkbox] {\n width: 11px;\n height: 11px;\n}\n.bb-previewMeta {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-end;\n gap: 8px;\n}\n.bb-removeModelButton {\n background-color: rgb(174, 54, 54);\n min-height: 34px;\n padding: 0 14px;\n}\n.bb-removeModelButton:hover {\n background-color: rgb(163, 20, 20);\n}\n.bb-previewMeta span {\n display: inline-flex;\n align-items: center;\n min-height: 34px;\n padding: 0 12px;\n border-radius: 999px;\n background: rgba(255, 255, 255, 0.7);\n border: 1px solid rgba(148, 163, 184, 0.18);\n color: #475569;\n font-size: 12px;\n font-weight: 700;\n}\n.bb-preview {\n position: relative;\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n border-radius: 14px;\n background:\n radial-gradient(\n circle at top left,\n rgba(255, 255, 255, 0.92),\n rgba(236, 240, 244, 0.82)),\n #f8fafc;\n border: 1px solid rgba(148, 163, 184, 0.24);\n box-shadow: 0 24px 48px rgba(148, 163, 184, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.7);\n}\n.bb-loadingOverlay {\n position: absolute;\n inset: 0;\n z-index: 5;\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(248, 250, 252, 0.82);\n backdrop-filter: blur(2px);\n}\n.bb-loadingSpinner {\n width: 40px;\n height: 40px;\n border-radius: 999px;\n border: 3px solid rgba(148, 163, 184, 0.3);\n border-top-color: #2563eb;\n animation: bb-spin 0.8s linear infinite;\n}\n@keyframes bb-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.bb-loadingMessage {\n max-width: 320px;\n color: #475569;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.5;\n}\n.bb-previewStatus {\n max-width: 280px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.bb-previewStatus--success {\n background: rgba(34, 197, 94, 0.12) !important;\n border-color: rgba(34, 197, 94, 0.3) !important;\n color: #15803d !important;\n}\n.bb-previewStatus--error {\n background: rgba(239, 68, 68, 0.12) !important;\n border-color: rgba(239, 68, 68, 0.3) !important;\n color: #b91c1c !important;\n}\n.bb-previewModelName {\n max-width: 200px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.bb-previewPlaceholder {\n position: relative;\n display: flex;\n height: 100%;\n width: 100%;\n align-items: center;\n justify-content: center;\n padding: 40px;\n text-align: center;\n color: #64748b;\n font-size: 15px;\n line-height: 1.7;\n border-radius: 14px;\n border: 2px dashed rgba(148, 163, 184, 0.24);\n background:\n radial-gradient(\n circle at top,\n rgba(251, 191, 36, 0.12),\n transparent 30%),\n linear-gradient(\n 180deg,\n rgba(255, 255, 255, 0.82),\n rgba(238, 242, 248, 0.9));\n transition:\n border-color 0.2s ease,\n background 0.2s ease,\n transform 0.2s ease,\n box-shadow 0.2s ease;\n}\n.bb-previewPlaceholder > div {\n max-width: 420px;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 14px;\n}\n.bb-previewPlaceholderTitle {\n font-size: 28px;\n line-height: 1.05;\n letter-spacing: -0.03em;\n font-weight: 800;\n color: #1f2937;\n}\n.bb-previewPlaceholderButton {\n margin-top: 8px;\n border: 0;\n border-radius: 999px;\n padding: 13px 22px;\n background: #135bc0;\n color: #ffffff;\n font-size: 14px;\n font-weight: 800;\n cursor: pointer;\n transition:\n transform 0.18s ease,\n box-shadow 0.18s ease,\n opacity 0.18s ease;\n}\n.bb-previewPlaceholderButton:hover:not(:disabled) {\n transform: translateY(-1px);\n box-shadow: 0 14px 30px rgba(19, 91, 192, 0.28);\n}\n.bb-previewPlaceholderButton:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n.bb-previewPlaceholder--dragActive {\n border-color: rgba(245, 158, 11, 0.75);\n background:\n radial-gradient(\n circle at top,\n rgba(251, 191, 36, 0.22),\n transparent 32%),\n linear-gradient(\n 180deg,\n rgba(255, 252, 245, 0.95),\n rgba(255, 244, 230, 0.92));\n transform: scale(0.995);\n box-shadow: inset 0 0 0 1px rgba(245, 158, 11, 0.14);\n}\n.bb-sectionHeader {\n cursor: pointer;\n user-select: none;\n}\n.bb-iconButton {\n background: none;\n border: none;\n cursor: pointer;\n font-size: 16px;\n color: var(--bb-sidebar-subtle-text);\n transition: color 0.2s ease;\n padding: 4px 8px;\n border-radius: 4px;\n}\n.bb-iconButton:hover {\n color: var(--bb-sidebar-text);\n background: var(--bb-sidebar-surface-strong);\n}\n.bb-cameraLive,\n.bb-cameraSaved {\n background: rgba(15, 23, 42, 0.44);\n border-radius: 8px;\n padding: 14px;\n margin-bottom: 16px;\n border: 1px solid rgba(148, 163, 184, 0.15);\n transition: all 0.2s ease;\n}\n.bb-cameraLive:hover,\n.bb-cameraSaved:hover {\n border-color: rgba(148, 163, 184, 0.25);\n background: rgba(15, 23, 42, 0.52);\n}\n.bb-cameraLiveHeader,\n.bb-cameraSavedHeader {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 12px;\n padding-bottom: 8px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.15);\n}\n.bb-cameraLiveBadge {\n background:\n linear-gradient(\n 135deg,\n #10b981,\n #059669);\n color: white;\n font-size: 10px;\n font-weight: 700;\n padding: 3px 8px;\n border-radius: 10px;\n letter-spacing: 0.5px;\n text-transform: uppercase;\n}\n.bb-cameraSavedBadge {\n background:\n linear-gradient(\n 135deg,\n #3b82f6,\n #2563eb);\n color: white;\n font-size: 10px;\n font-weight: 700;\n padding: 3px 8px;\n border-radius: 10px;\n letter-spacing: 0.5px;\n text-transform: uppercase;\n}\n.bb-cameraLiveLabel,\n.bb-cameraSavedLabel {\n font-size: 12px;\n font-weight: 600;\n color: #cbd5e1;\n}\n.bb-cameraValues {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.bb-cameraRow {\n display: flex;\n align-items: center;\n gap: 10px;\n font-size: 12px;\n flex-wrap: wrap;\n}\n.bb-cameraLabel {\n font-weight: 600;\n color: #94a3b8;\n min-width: 58px;\n font-size: 11px;\n letter-spacing: 0.02em;\n}\n.bb-cameraCode {\n font-family:\n "Monaco",\n "Menlo",\n "Ubuntu Mono",\n "Consolas",\n monospace;\n font-size: 11px;\n background: rgba(0, 0, 0, 0.3);\n padding: 3px 8px;\n border-radius: 4px;\n color: #fcd34d;\n letter-spacing: 0.02em;\n word-break: break-all;\n}\n.bb-cameraPlaceholder {\n color: #94a3b8;\n font-size: 12px;\n text-align: center;\n padding: 16px;\n font-style: italic;\n}\n.bb-cameraControls {\n display: flex;\n gap: 10px;\n margin-bottom: 14px;\n flex-wrap: wrap;\n}\n.bb-button--primary {\n background: #135bc0;\n border-color: transparent;\n color: #ffffff;\n}\n.bb-button--primary:hover:not(:disabled) {\n background: #1a68d6;\n transform: translateY(-1px);\n}\n.bb-button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n transform: none;\n}\n.bb-cameraMessage {\n padding: 10px 14px;\n border-radius: 6px;\n margin-bottom: 14px;\n font-size: 12px;\n font-weight: 500;\n animation: slideIn 0.3s ease-out;\n}\n.bb-cameraMessage--success {\n background: rgba(16, 185, 129, 0.15);\n color: #6ee7b7;\n border: 1px solid rgba(16, 185, 129, 0.3);\n}\n.bb-cameraMessage--error {\n background: rgba(239, 68, 68, 0.15);\n color: #ffe5e55c;\n border: 1px solid rgba(255, 248, 248, 0.3);\n}\n@keyframes slideIn {\n from {\n opacity: 0;\n transform: translateY(-8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n.bb-readonly {\n background: rgba(148, 163, 184, 0.08);\n color: #9ca3af;\n cursor: default;\n}\n@media (max-width: 1280px) {\n .bb-cameraRow {\n flex-direction: column;\n align-items: flex-start;\n gap: 4px;\n }\n .bb-cameraLabel {\n min-width: auto;\n }\n .bb-cameraControls {\n flex-direction: column;\n }\n .bb-cameraControls .bb-button {\n width: 100%;\n }\n}\n@media (max-width: 980px) {\n .bb-sidebar--left {\n width: 100%;\n max-height: 42vh;\n }\n .bb-sidebar--collapsed {\n width: 100%;\n }\n .bb-previewShell {\n padding: 14px;\n }\n .bb-preview {\n min-height: 44vh;\n }\n .bb-previewHeader {\n flex-direction: column;\n align-items: flex-start;\n }\n .bb-previewControls {\n width: 100%;\n justify-content: flex-start;\n }\n .bb-previewMeta {\n justify-content: flex-start;\n }\n .bb-grid,\n .bb-actionGrid,\n .bb-statRow {\n grid-template-columns: 1fr;\n }\n}\n.bb-annotationsInfo {\n margin-top: 8px;\n}\n.bb-emptyAnnotations {\n padding: 32px;\n text-align: center;\n background: #f5f5f5;\n border-radius: 4px;\n color: #666;\n font-size: 14px;\n}\n.bb-annotationsList {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n.bb-annotationCard {\n background: #f9f9f9;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n padding: 12px;\n transition: all 0.2s ease;\n}\n.bb-annotationCard:hover {\n background: #ffffff;\n border-color: #4caf50;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n}\n.bb-annotationHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n}\n.bb-annotationBadge {\n background: #4caf50;\n color: white;\n padding: 2px 8px;\n border-radius: 2px;\n font-size: 11px;\n font-weight: 600;\n}\n.bb-annotationActions {\n display: flex;\n gap: 8px;\n}\n.bb-annotationEdit,\n.bb-annotationDelete {\n background: transparent;\n border: none;\n cursor: pointer;\n padding: 4px;\n border-radius: 2px;\n font-size: 14px;\n transition: all 0.2s ease;\n}\n.bb-annotationEdit:hover {\n background: #e8f5e9;\n transform: scale(1.05);\n}\n.bb-annotationDelete:hover {\n background: #ffebee;\n transform: scale(1.05);\n}\n.bb-annotationTitle {\n font-weight: 600;\n font-size: 14px;\n color: #333;\n margin-bottom: 6px;\n}\n.bb-annotationDescription {\n font-size: 13px;\n color: #666;\n margin-bottom: 8px;\n line-height: 1.4;\n}\n.bb-annotationPosition {\n font-size: 11px;\n font-family: monospace;\n color: #999;\n background: #f0f0f0;\n padding: 4px 6px;\n border-radius: 2px;\n display: inline-block;\n}\n.bb-modalOverlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n}\n.bb-modal {\n background: white;\n border-radius: 6px;\n width: 500px;\n max-width: 90%;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);\n animation: modalSlideIn 0.2s ease;\n}\n@keyframes modalSlideIn {\n from {\n transform: translateY(-20px);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n.bb-modalHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 16px 20px;\n border-bottom: 1px solid #e0e0e0;\n}\n.bb-modalHeader h3 {\n margin: 0;\n font-size: 18px;\n font-weight: 600;\n}\n.bb-modalClose {\n background: transparent;\n border: none;\n font-size: 20px;\n cursor: pointer;\n color: #999;\n transition: color 0.2s ease;\n}\n.bb-modalClose:hover {\n color: #333;\n}\n.bb-modalBody {\n padding: 20px;\n}\n.bb-modalFooter {\n display: flex;\n justify-content: flex-end;\n gap: 12px;\n padding: 16px 20px;\n border-top: 1px solid #e0e0e0;\n}\n.bb-annotationsInfo {\n margin-top: 8px;\n}\n.bb-emptyAnnotations {\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.bb-annotationsList {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n.bb-annotationCard {\n background: rgba(15, 23, 42, 0.44);\n border-left: 4px solid #22d3ee;\n border-radius: 8px;\n padding: 16px;\n transition: all 0.2s ease;\n border: 1px solid rgba(148, 163, 184, 0.15);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03);\n}\n.bb-annotationCard:hover {\n transform: translateX(4px);\n background: rgba(15, 23, 42, 0.6);\n border-color: rgba(148, 163, 184, 0.25);\n box-shadow: 0 4px 12px rgba(15, 23, 42, 0.3);\n}\n.bb-annotationBadge {\n display: inline-block;\n background: #22d3ee;\n color: #06343c;\n padding: 2px 10px;\n border-radius: 10px;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.5px;\n margin-bottom: 12px;\n text-transform: uppercase;\n}\n.bb-annotationTitle {\n font-weight: 700;\n font-size: 15px;\n color: #f8fafc;\n margin-bottom: 8px;\n line-height: 1.3;\n}\n.bb-annotationDescription {\n font-size: 13px;\n color: #cbd5e1;\n margin-bottom: 12px;\n line-height: 1.5;\n word-wrap: break-word;\n white-space: pre-wrap;\n}\n.bb-annotationPosition {\n font-size: 11px;\n font-family:\n "Monaco",\n "Menlo",\n "Ubuntu Mono",\n "Consolas",\n monospace;\n color: #94a3b8;\n background: rgba(0, 0, 0, 0.3);\n padding: 4px 8px;\n border-radius: 4px;\n display: inline-block;\n margin-top: 4px;\n letter-spacing: 0.02em;\n}\n.bb-annotationHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n}\n.bb-annotationActions {\n display: flex;\n gap: 4px;\n}\n.bb-annotationAction {\n background: none;\n border: none;\n color: #94a3b8;\n cursor: pointer;\n padding: 4px;\n border-radius: 2px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s;\n}\n.bb-annotationAction--edit:hover {\n border-radius: 50%;\n background: rgba(59, 130, 246, 0.2);\n color: #3b82f6;\n}\n.bb-annotationAction--delete:hover {\n border-radius: 50%;\n background: rgba(239, 68, 68, 0.2);\n color: #ef4444;\n}\n.bb-editSpotOverlay {\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-editSpotPopup {\n background:\n linear-gradient(\n 180deg,\n rgba(20, 27, 45, 0.98),\n rgba(25, 34, 57, 0.95)),\n #121826;\n border-radius: 8px;\n padding: 24px;\n min-width: 400px;\n max-width: 500px;\n border: 1px solid rgba(148, 163, 184, 0.18);\n box-shadow: 0 24px 48px rgba(15, 23, 42, 0.3);\n}\n.bb-editSpotHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 16px;\n}\n.bb-editSpotTitle {\n color: #f8fafc;\n font-size: 18px;\n font-weight: 600;\n margin: 0;\n}\n.bb-editSpotClose {\n background: none;\n border: none;\n color: #94a3b8;\n font-size: 24px;\n cursor: pointer;\n padding: 0 4px;\n transition: color 0.2s ease;\n}\n.bb-editSpotClose:hover {\n color: #f8fafc;\n}\n.bb-editSpotBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n.bb-editSpotBody .bb-input,\n.bb-editSpotBody .bb-textarea {\n background: rgba(15, 23, 42, 0.4);\n border-color: rgba(148, 163, 184, 0.2);\n color: #f8fafc;\n}\n.bb-editSpotBody .bb-input:focus,\n.bb-editSpotBody .bb-textarea:focus {\n border-color: rgba(251, 191, 36, 0.65);\n box-shadow: 0 0 0 4px rgba(251, 191, 36, 0.12);\n}\n.bb-editSpotBody .bb-label {\n color: #d7dee9;\n}\n.bb-editSpotActions {\n display: flex;\n gap: 8px;\n margin-top: 8px;\n}\n.bb-editSpotActions .bb-button {\n background: rgba(255, 255, 255, 0.04);\n border: 1px solid rgba(148, 163, 184, 0.18);\n color: #e5edf7;\n}\n.bb-editSpotActions .bb-button:hover {\n background: rgba(255, 255, 255, 0.08);\n border-color: rgba(148, 163, 184, 0.32);\n transform: translateY(-1px);\n}\n.bb-button--danger {\n background: #ef4444;\n color: white;\n border: none;\n padding: 8px 16px;\n border-radius: 7px;\n cursor: pointer;\n font-size: 13px;\n font-weight: 700;\n display: flex;\n align-items: center;\n gap: 4px;\n transition: all 0.2s ease;\n}\n.bb-button--danger:hover {\n background: #dc2626;\n transform: translateY(-1px);\n box-shadow: 0 10px 18px rgba(239, 68, 68, 0.25);\n}\n.bb-button--primary {\n background: #135bc0;\n border-color: transparent;\n color: #ffffff;\n}\n.bb-button--primary:hover {\n background: #1a68d6;\n}\n.bb-annotationCard {\n background: rgba(15, 23, 42, 0.44);\n border-left: 4px solid #22d3ee;\n border-radius: 8px;\n padding: 16px;\n transition: all 0.2s ease;\n border: 1px solid rgba(148, 163, 184, 0.15);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03);\n}\n.bb-annotationCard:hover {\n transform: translateX(4px);\n background: rgba(15, 23, 42, 0.6);\n border-color: rgba(148, 163, 184, 0.25);\n box-shadow: 0 4px 12px rgba(15, 23, 42, 0.3);\n}\n.bb-annotationBadge {\n display: inline-block;\n background: #22d3ee;\n color: #06343c;\n padding: 2px 10px;\n border-radius: 10px;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.5px;\n margin-bottom: 12px;\n text-transform: uppercase;\n}\n.bb-annotationTitle {\n font-weight: 700;\n font-size: 15px;\n color: #f8fafc;\n margin-bottom: 8px;\n line-height: 1.3;\n}\n.bb-annotationDescription {\n font-size: 13px;\n color: #cbd5e1;\n margin-bottom: 12px;\n line-height: 1.5;\n word-wrap: break-word;\n white-space: pre-wrap;\n}\n.bb-annotationPosition {\n font-size: 11px;\n font-family:\n "Monaco",\n "Menlo",\n "Ubuntu Mono",\n "Consolas",\n monospace;\n color: #94a3b8;\n background: rgba(0, 0, 0, 0.3);\n padding: 4px 8px;\n border-radius: 4px;\n display: inline-block;\n margin-top: 4px;\n letter-spacing: 0.02em;\n}\n.bb-emptyAnnotations {\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@media (max-width: 1280px) {\n .bb-cameraRow {\n flex-direction: column;\n align-items: flex-start;\n gap: 4px;\n }\n .bb-cameraLabel {\n min-width: auto;\n }\n .bb-cameraControls {\n flex-direction: column;\n }\n .bb-cameraControls .bb-button {\n width: 100%;\n }\n}\n@media (max-width: 1120px) {\n .bb-sidebar--left {\n width: 320px;\n }\n .bb-sidebar--right {\n width: 220px;\n }\n .bb-grid {\n grid-template-columns: 1fr;\n }\n .bb-statRow {\n grid-template-columns: 1fr;\n }\n}\n@media (max-width: 980px) {\n .bb-root {\n flex-direction: column;\n height: auto;\n min-height: 0;\n }\n .bb-sidebar--left {\n width: 100% !important;\n max-height: 45vh;\n border-right: none;\n border-bottom: 1px solid rgba(148, 163, 184, 0.18);\n box-shadow: 0 18px 42px rgba(15, 23, 42, 0.12);\n }\n .bb-sidebar--right {\n width: 100% !important;\n max-height: 45vh;\n border-left: none;\n border-top: 1px solid rgba(148, 163, 184, 0.18);\n box-shadow: 0 -18px 42px rgba(15, 23, 42, 0.12);\n order: 3;\n }\n .bb-sidebar--collapsed {\n width: 100% !important;\n max-height: 72px;\n overflow: hidden;\n }\n .bb-sidebar--collapsed .bb-nodeList,\n .bb-sidebar--collapsed .bb-tabRow,\n .bb-sidebar--collapsed .bb-toolbar {\n display: none;\n }\n .bb-previewShell {\n flex: 1 1 auto;\n min-height: 50vh;\n padding: 14px;\n order: 2;\n }\n .bb-preview {\n min-height: 44vh;\n border-radius: 10px;\n }\n .bb-previewHeader {\n flex-direction: column;\n align-items: flex-start;\n gap: 12px;\n }\n .bb-previewControls {\n width: 100%;\n justify-content: flex-start;\n flex-wrap: wrap;\n }\n .bb-previewMeta {\n justify-content: flex-start;\n }\n .bb-previewMeta span {\n font-size: 11px;\n padding: 0 10px;\n min-height: 30px;\n }\n .bb-grid,\n .bb-actionGrid,\n .bb-statRow {\n grid-template-columns: 1fr;\n }\n .bb-editor {\n padding: 12px 12px 20px;\n }\n .bb-editorHeader {\n flex-direction: column;\n align-items: stretch;\n padding: 14px;\n }\n .bb-editorTitle {\n font-size: 20px;\n }\n .bb-editorBadge {\n align-self: flex-start;\n }\n .bb-toolbar {\n flex-direction: column;\n gap: 12px;\n padding: 16px 20px 18px;\n }\n .bb-toolbarDivider {\n width: 100%;\n height: 1px;\n margin: 0;\n }\n .bb-tabRow {\n padding: 12px 16px 0;\n gap: 6px;\n }\n .bb-tab {\n font-size: 10px;\n padding: 8px 10px;\n }\n .bb-header {\n padding: 14px 16px 12px;\n }\n .bb-header--sidebar {\n min-height: 60px;\n }\n .bb-title {\n font-size: 17px;\n }\n .bb-subtitle {\n font-size: 13px;\n }\n .bb-statChip {\n padding: 10px 12px;\n }\n .bb-statChip strong {\n font-size: 12px;\n }\n .bb-nodeButton {\n padding: 10px 12px;\n }\n .bb-nodeName {\n font-size: 12px;\n }\n .bb-nodeMeta {\n font-size: 10px;\n }\n .bb-section {\n padding: 14px;\n }\n .bb-sectionTitle {\n font-size: 9px;\n }\n .bb-field {\n gap: 4px;\n }\n .bb-label {\n font-size: 11px;\n }\n .bb-input,\n .bb-select,\n .bb-textarea {\n padding: 9px 11px;\n font-size: 12px;\n }\n .bb-textarea {\n min-height: 80px;\n }\n .bb-button,\n .bb-fileLabel {\n min-height: 36px;\n padding: 0 14px;\n font-size: 12px;\n }\n .bb-previewPlaceholderTitle {\n font-size: 22px;\n }\n .bb-previewPlaceholderButton {\n padding: 10px 18px;\n font-size: 13px;\n }\n .bb-editSpotPopup {\n min-width: 90%;\n max-width: 90%;\n padding: 20px;\n }\n .bb-modal {\n width: 95%;\n max-width: 95%;\n }\n .bb-annotationCard {\n padding: 12px;\n }\n .bb-annotationTitle {\n font-size: 14px;\n }\n .bb-annotationDescription {\n font-size: 12px;\n }\n}\n@media (max-width: 640px) {\n .bb-root {\n flex-direction: column;\n height: auto;\n min-height: 0;\n }\n .bb-sidebar--left {\n max-height: 40vh;\n width: 100% !important;\n }\n .bb-sidebar--right {\n max-height: 40vh;\n width: 100% !important;\n }\n .bb-previewShell {\n min-height: 40vh;\n padding: 10px;\n }\n .bb-preview {\n min-height: 35vh;\n border-radius: 8px;\n }\n .bb-previewHeader {\n flex-direction: column;\n align-items: flex-start;\n gap: 8px;\n padding: 0 0 12px;\n }\n .bb-previewTitle {\n font-size: 17px;\n }\n .bb-previewControls {\n gap: 6px;\n }\n .bb-previewMeta span {\n font-size: 10px;\n padding: 0 8px;\n min-height: 26px;\n }\n .bb-editor {\n padding: 10px 10px 16px;\n }\n .bb-editorHeader {\n padding: 12px;\n flex-direction: column;\n align-items: stretch;\n }\n .bb-editorTitle {\n font-size: 18px;\n }\n .bb-editorMeta {\n font-size: 11px;\n gap: 6px 8px;\n }\n .bb-editorBadge {\n font-size: 9px;\n padding: 4px 10px;\n align-self: flex-start;\n }\n .bb-section {\n padding: 12px;\n margin-bottom: 12px;\n }\n .bb-sectionTitle {\n font-size: 9px;\n margin-bottom: 10px;\n }\n .bb-grid {\n gap: 8px;\n }\n .bb-field--full {\n grid-column: 1 / -1;\n }\n .bb-label {\n font-size: 11px;\n }\n .bb-input,\n .bb-select,\n .bb-textarea {\n padding: 8px 10px;\n font-size: 12px;\n border-radius: 6px;\n }\n .bb-textarea {\n min-height: 70px;\n }\n .bb-button,\n .bb-fileLabel {\n min-height: 34px;\n padding: 0 12px;\n font-size: 11px;\n border-radius: 6px;\n }\n .bb-toolbar {\n padding: 12px 14px 14px;\n gap: 10px;\n }\n .bb-tabRow {\n padding: 10px 12px 0;\n gap: 4px;\n }\n .bb-tab {\n font-size: 9px;\n padding: 6px 8px;\n border-radius: 4px;\n }\n .bb-header {\n padding: 10px 12px 8px;\n }\n .bb-header--sidebar {\n min-height: 52px;\n }\n .bb-title {\n font-size: 15px;\n }\n .bb-subtitle {\n font-size: 12px;\n }\n .bb-eyebrow {\n font-size: 9px;\n padding: 4px 8px;\n margin-bottom: 8px;\n }\n .bb-statRow {\n gap: 6px;\n margin-top: 14px;\n }\n .bb-statChip {\n padding: 8px 10px;\n border-radius: 6px;\n }\n .bb-statChip strong {\n font-size: 11px;\n }\n .bb-statLabel {\n font-size: 9px;\n }\n .bb-sidebarToggle {\n width: 30px;\n height: 30px;\n font-size: 14px;\n border-radius: 5px;\n }\n .bb-nodeItems {\n padding: 4px 6px;\n }\n .bb-nodeButton {\n padding: 8px 10px;\n border-radius: 6px;\n margin-bottom: 4px;\n }\n .bb-nodeName {\n font-size: 11px;\n }\n .bb-nodeMeta {\n font-size: 10px;\n }\n .bb-search {\n padding: 8px 10px;\n }\n .bb-statusCard {\n padding: 10px 12px;\n border-radius: 7px;\n }\n .bb-status {\n font-size: 12px;\n }\n .bb-statusTitle {\n font-size: 10px;\n }\n .bb-checkboxRow {\n gap: 6px;\n }\n .bb-checkbox {\n padding: 6px 10px;\n font-size: 12px;\n border-radius: 5px;\n }\n .bb-actionCard {\n padding: 10px;\n border-radius: 7px;\n }\n .bb-actionCardTitle {\n font-size: 12px;\n }\n .bb-actionCardMeta {\n font-size: 9px;\n padding: 3px 7px;\n }\n .bb-previewPlaceholder {\n padding: 24px;\n border-radius: 8px;\n }\n .bb-previewPlaceholderTitle {\n font-size: 18px;\n }\n .bb-previewPlaceholderButton {\n padding: 8px 14px;\n font-size: 12px;\n border-radius: 999px;\n }\n .bb-editSpotPopup {\n min-width: 95%;\n max-width: 95%;\n padding: 16px;\n border-radius: 6px;\n }\n .bb-editSpotTitle {\n font-size: 16px;\n }\n .bb-editSpotActions .bb-button {\n padding: 8px 12px;\n font-size: 12px;\n }\n .bb-modal {\n width: 98%;\n max-width: 98%;\n border-radius: 5px;\n }\n .bb-modalHeader {\n padding: 12px 16px;\n }\n .bb-modalHeader h3 {\n font-size: 16px;\n }\n .bb-modalBody {\n padding: 14px;\n }\n .bb-modalFooter {\n padding: 12px 16px;\n flex-direction: column;\n gap: 8px;\n }\n .bb-modalFooter .bb-button {\n width: 100%;\n justify-content: center;\n }\n .bb-annotationCard {\n padding: 10px;\n border-left-width: 3px;\n border-radius: 6px;\n }\n .bb-annotationTitle {\n font-size: 13px;\n }\n .bb-annotationDescription {\n font-size: 11px;\n }\n .bb-annotationPosition {\n font-size: 10px;\n padding: 3px 6px;\n }\n .bb-annotationBadge {\n font-size: 9px;\n padding: 2px 8px;\n }\n .bb-emptyAnnotations {\n padding: 20px;\n font-size: 12px;\n border-radius: 7px;\n }\n .bb-removeModelButton {\n min-height: 30px;\n padding: 0 10px;\n font-size: 11px;\n }\n .bb-annotationsList {\n gap: 8px;\n }\n .bb-cameraLive,\n .bb-cameraSaved {\n padding: 10px;\n border-radius: 6px;\n }\n .bb-cameraLiveHeader,\n .bb-cameraSavedHeader {\n flex-wrap: wrap;\n }\n .bb-cameraLiveLabel,\n .bb-cameraSavedLabel {\n font-size: 11px;\n }\n .bb-cameraRow {\n font-size: 11px;\n gap: 4px;\n }\n .bb-cameraCode {\n font-size: 10px;\n padding: 2px 6px;\n }\n .bb-cameraControls {\n flex-direction: column;\n gap: 6px;\n }\n .bb-cameraControls .bb-button {\n width: 100%;\n justify-content: center;\n }\n .bb-cameraMessage {\n font-size: 11px;\n padding: 8px 12px;\n }\n .bb-readonly {\n font-size: 11px;\n padding: 6px 8px;\n }\n}\n@media (max-width: 420px) {\n .bb-sidebar--left {\n max-height: 35vh;\n }\n .bb-sidebar--right {\n max-height: 35vh;\n }\n .bb-previewShell {\n min-height: 35vh;\n padding: 6px;\n }\n .bb-preview {\n min-height: 30vh;\n border-radius: 6px;\n }\n .bb-previewTitle {\n font-size: 15px;\n }\n .bb-previewMeta span {\n font-size: 9px;\n padding: 0 6px;\n min-height: 22px;\n }\n .bb-editorTitle {\n font-size: 16px;\n }\n .bb-title {\n font-size: 13px;\n }\n .bb-tab {\n font-size: 8px;\n padding: 5px 6px;\n }\n .bb-button,\n .bb-fileLabel {\n font-size: 10px;\n min-height: 30px;\n padding: 0 10px;\n }\n .bb-input,\n .bb-select,\n .bb-textarea {\n font-size: 11px;\n padding: 6px 8px;\n }\n .bb-statChip {\n padding: 6px 8px;\n }\n .bb-statChip strong {\n font-size: 10px;\n }\n .bb-previewPlaceholderTitle {\n font-size: 15px;\n }\n .bb-previewPlaceholderButton {\n font-size: 11px;\n padding: 6px 12px;\n }\n .bb-editSpotPopup {\n padding: 14px;\n border-radius: 5px;\n }\n .bb-editSpotTitle {\n font-size: 14px;\n }\n .bb-editSpotBody .bb-input,\n .bb-editSpotBody .bb-textarea {\n font-size: 12px;\n padding: 6px 8px;\n }\n .bb-modalHeader h3 {\n font-size: 14px;\n }\n .bb-annotationTitle {\n font-size: 12px;\n }\n}\n@media (max-width: 980px) {\n .bb-sidebar--collapsed .bb-header--sidebar {\n min-height: 52px;\n }\n .bb-sidebar--collapsed .bb-title {\n font-size: 14px;\n }\n .bb-sidebar--collapsed .bb-sidebarToggle {\n width: 28px;\n height: 28px;\n font-size: 12px;\n }\n}\n@media (hover: none) and (pointer: coarse) {\n .bb-nodeButton:hover,\n .bb-button:hover,\n .bb-fileLabel:hover,\n .bb-tab:hover,\n .bb-sidebarToggle:hover,\n .bb-annotationCard:hover {\n transform: none;\n }\n .bb-nodeButton:active,\n .bb-button:active,\n .bb-fileLabel:active,\n .bb-tab:active,\n .bb-sidebarToggle:active {\n transform: scale(0.97);\n transition: transform 0.1s ease;\n }\n}\n@media print {\n .bb-root {\n height: auto;\n min-height: 0;\n background: white;\n }\n .bb-sidebar--left,\n .bb-sidebar--right {\n width: 100% !important;\n max-height: none !important;\n background: white !important;\n color: black !important;\n border: 1px solid #ddd !important;\n box-shadow: none !important;\n }\n .bb-previewShell {\n min-height: auto !important;\n padding: 10px !important;\n }\n .bb-preview {\n min-height: auto !important;\n border-radius: 4px !important;\n border: 1px solid #ddd !important;\n box-shadow: none !important;\n }\n .bb-button,\n .bb-fileLabel,\n .bb-sidebarToggle,\n .bb-tab,\n .bb-annotationAction,\n .bb-iconButton {\n display: none !important;\n }\n .bb-nodeList {\n overflow: visible !important;\n }\n .bb-previewPlaceholderButton {\n display: none !important;\n }\n}\n.bb-checkboxRow {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n align-items: center;\n padding: 4px 0;\n}\n.bb-checkbox {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n border-radius: 7px;\n font-weight: 600;\n font-size: 13px;\n color: #f3eeee;\n cursor: pointer;\n user-select: none;\n transition: all 0.2s ease;\n}\n.bb-checkbox input[type=checkbox] {\n width: 16px;\n height: 16px;\n accent-color: #f59e0b;\n cursor: pointer;\n flex-shrink: 0;\n}\n.bb-editorDivider {\n height: 1px;\n background: rgba(148, 163, 184, 0.14);\n margin: 4px 0 16px 0;\n width: 100%;\n}\n.bb-preview {\n position: relative;\n width: 100%;\n height: 100%;\n overflow: hidden;\n}\n.ri-viewer {\n width: 100% !important;\n height: 100% !important;\n position: relative !important;\n}\n.ri-buttonContainer {\n position: absolute !important;\n bottom: 20px !important;\n right: 20px !important;\n z-index: 10000 !important;\n display: flex !important;\n gap: 10px !important;\n pointer-events: auto !important;\n}\n.ri-downloadButton {\n display: flex !important;\n align-items: center !important;\n gap: 6px !important;\n padding: 10px 12px !important;\n background: #2f302f74 !important;\n color: white !important;\n border: none !important;\n border-radius: 6px !important;\n cursor: pointer !important;\n font-size: 12px !important;\n font-weight: 500 !important;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;\n opacity: 1 !important;\n visibility: visible !important;\n}\n.ri-downloadButton svg {\n width: 14px !important;\n height: 14px !important;\n}\n.ri-resetButton {\n display: flex !important;\n align-items: center !important;\n justify-content: center !important;\n padding: 6px !important;\n background: #2f302f74 !important;\n border: none !important;\n border-radius: 6px !important;\n cursor: pointer !important;\n opacity: 1 !important;\n visibility: visible !important;\n}\n.ri-resetButton svg {\n width: 14px !important;\n height: 14px !important;\n}\n.bb-reset-overlay {\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-fade-in 0.2s ease;\n}\n.bb-reset-dialog {\n background: #1a1f2e;\n border-radius: 12px;\n max-width: 480px;\n width: 90%;\n border: 1px solid rgba(148, 163, 184, 0.18);\n box-shadow: 0 24px 48px rgba(0, 0, 0, 0.5);\n animation: bb-slide-up 0.25s ease;\n}\n.bb-reset-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px 16px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.1);\n}\n.bb-reset-title {\n display: flex;\n align-items: center;\n gap: 10px;\n margin: 0;\n font-size: 18px;\n font-weight: 700;\n color: #f8fafc;\n}\n.bb-reset-title .bb-icon-warning {\n color: #f59e0b;\n flex-shrink: 0;\n}\n.bb-reset-close {\n background: transparent;\n border: none;\n color: #94a3b8;\n cursor: pointer;\n padding: 4px;\n border-radius: 4px;\n transition: all 0.2s ease;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.bb-reset-close:hover {\n color: #f8fafc;\n background: rgba(255, 255, 255, 0.06);\n}\n.bb-reset-body {\n padding: 20px 24px 24px;\n}\n.bb-reset-message {\n margin: 0 0 12px 0;\n font-size: 15px;\n color: #e2e8f0;\n line-height: 1.5;\n}\n.bb-reset-message strong {\n color: #f8fafc;\n}\n.bb-reset-details {\n color: #94a3b8;\n text-align: center;\n font-size: 13px;\n line-height: 1.6;\n margin: 0 0 12px 0;\n}\n.bb-reset-warning {\n display: flex;\n align-items: center;\n gap: 8px;\n color: #f59e0b;\n font-size: 13px;\n font-weight: 600;\n margin: 4px 0 0 0;\n padding: 10px 12px;\n background: rgba(245, 158, 11, 0.08);\n border-radius: 6px;\n border: 1px solid rgba(245, 158, 11, 0.15);\n}\n.bb-reset-footer {\n display: flex;\n justify-content: flex-end;\n gap: 10px;\n padding: 16px 24px 20px;\n border-top: 1px solid rgba(148, 163, 184, 0.1);\n}\n.bb-reset-btn {\n padding: 8px 20px;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s ease;\n border: 1px solid rgba(148, 163, 184, 0.18);\n background: rgba(255, 255, 255, 0.04);\n color: #e2e8f0;\n}\n.bb-reset-btn:hover {\n background: rgba(255, 255, 255, 0.08);\n transform: translateY(-1px);\n}\n.bb-reset-btn-danger {\n padding: 8px 20px;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s ease;\n border: none;\n background: #dc2626;\n color: white;\n}\n.bb-reset-btn-danger:hover {\n background: #b91c1c;\n transform: translateY(-1px);\n box-shadow: 0 4px 12px rgba(220, 38, 38, 0.3);\n}\n@keyframes bb-fade-in {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n@keyframes bb-slide-up {\n from {\n opacity: 0;\n transform: translateY(20px) scale(0.96);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n');
|
|
8237
|
+
styleInject('.bb-root {\n --bb-font-family:\n Inter,\n "Segoe UI",\n Helvetica,\n Arial,\n sans-serif;\n --bb-app-bg:\n radial-gradient(\n \n circle at top left,\n rgba(226, 232, 240, 0.8),\n transparent 30% ),\n linear-gradient(\n 135deg,\n #f6f1e8 0%,\n #ebe8df 40%,\n #e9edf2 100%);\n --bb-app-text: #111827;\n --bb-sidebar-bg:\n linear-gradient(\n 180deg,\n rgba(20, 27, 45, 0.98),\n rgba(25, 34, 57, 0.95)),\n #121826;\n --bb-sidebar-text: #f8fafc;\n --bb-sidebar-muted-text: #b3bfd2;\n --bb-sidebar-subtle-text: #94a3b8;\n --bb-sidebar-border: rgba(148, 163, 184, 0.18);\n --bb-sidebar-border-hover: rgba(148, 163, 184, 0.32);\n --bb-sidebar-divider: rgba(148, 163, 184, 0.14);\n --bb-sidebar-surface: rgba(255, 255, 255, 0.04);\n --bb-sidebar-surface-hover: rgba(255, 255, 255, 0.08);\n --bb-sidebar-surface-strong: rgba(255, 255, 255, 0.05);\n --bb-sidebar-eyebrow-bg: rgba(148, 163, 184, 0.16);\n --bb-sidebar-eyebrow-text: #cbd5e1;\n --bb-sidebar-left-shadow: 18px 0 42px rgba(15, 23, 42, 0.12);\n --bb-sidebar-right-shadow: -4px 0 24px rgba(0, 0, 0, 0.35);\n --bb-sidebar-chip-bg: rgba(15, 23, 42, 0.42);\n --bb-sidebar-chip-border: rgba(148, 163, 184, 0.14);\n --bb-sidebar-accent: rgba(99, 102, 241, 0.18);\n --bb-sidebar-accent-border: rgba(99, 102, 241, 0.4);\n --bb-sidebar-accent-text: #f8fafc;\n --bb-menu-bg: #1a1f2e;\n --bb-menu-shadow: 0 16px 32px rgba(15, 23, 42, 0.4);\n display: flex;\n height: 100%;\n min-height: 0;\n width: 100%;\n background: var(--bb-app-bg);\n color: var(--bb-app-text);\n font-family: var(--bb-font-family);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n text-rendering: optimizeLegibility;\n font-synthesis: none;\n}\n.bb-root * {\n box-sizing: border-box;\n}\n.bb-root input,\n.bb-root select,\n.bb-root textarea,\n.bb-root button {\n font-family: inherit;\n}\n.bb-sidebar {\n width: 400px;\n min-height: 0;\n flex-shrink: 0;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n background: var(--bb-sidebar-bg);\n color: var(--bb-sidebar-text);\n transition: width 0.24s ease;\n}\n.bb-sidebar--left {\n border-right: 1px solid var(--bb-sidebar-border);\n box-shadow: var(--bb-sidebar-left-shadow);\n}\n.bb-sidebar--right {\n width: 300px;\n border-left: 1px solid var(--bb-sidebar-border);\n box-shadow: var(--bb-sidebar-right-shadow);\n}\n.bb-sidebar--collapsed {\n width: 36px;\n}\n.bb-sidebar--collapsed .bb-header {\n padding: 5px;\n display: flex;\n justify-content: center;\n}\n.bb-sidebar--collapsed .bb-header--sidebar {\n min-height: 36px;\n}\n.bb-sidebar--collapsed .bb-sidebarToggle {\n width: 26px;\n height: 26px;\n font-size: 12px;\n}\n.bb-header {\n padding: 5px 20px;\n border-bottom: 1px solid var(--bb-sidebar-divider);\n}\n.bb-header--sidebar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n min-height: 55px;\n}\n.bb-eyebrow {\n display: inline-block;\n margin-bottom: 12px;\n padding: 6px 10px;\n border-radius: 999px;\n background: var(--bb-sidebar-eyebrow-bg);\n color: var(--bb-sidebar-eyebrow-text);\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.18em;\n text-transform: uppercase;\n}\n.bb-title {\n margin: 0 0 0px;\n font-size: 20px;\n line-height: 0.95;\n letter-spacing: -0.04em;\n font-weight: 800;\n color: var(--bb-sidebar-text);\n}\n.bb-sidebarToggle {\n width: 36px;\n height: 36px;\n flex-shrink: 0;\n border-radius: 6px;\n border: 1px solid var(--bb-sidebar-border);\n background: var(--bb-sidebar-surface-strong);\n color: var(--bb-sidebar-text);\n font-size: 16px;\n font-weight: 800;\n cursor: pointer;\n transition:\n transform 0.18s ease,\n background-color 0.18s ease,\n border-color 0.18s ease;\n}\n.bb-sidebarToggle:hover {\n transform: translateY(-1px);\n background: var(--bb-sidebar-surface-hover);\n border-color: var(--bb-sidebar-border-hover);\n}\n.bb-subtitle {\n margin: 0;\n max-width: 32ch;\n font-size: 15px;\n line-height: 1.7;\n color: var(--bb-sidebar-muted-text);\n}\n.bb-statRow {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 10px;\n margin-top: 22px;\n}\n.bb-statChip {\n display: flex;\n flex-direction: column;\n gap: 4px;\n min-width: 0;\n padding: 12px 14px;\n border-radius: 8px;\n background: var(--bb-sidebar-chip-bg);\n border: 1px solid var(--bb-sidebar-chip-border);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);\n}\n.bb-statChip strong {\n font-size: 13px;\n color: var(--bb-sidebar-text);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.bb-statLabel {\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: var(--bb-sidebar-subtle-text);\n}\n.bb-tabRow {\n display: flex;\n gap: 6px;\n padding: 10px 20px 10px;\n}\n.bb-tab {\n flex: 1;\n padding: 6px 10px;\n border-radius: 6px;\n border: 1px solid var(--bb-sidebar-border);\n background: var(--bb-sidebar-surface);\n color: var(--bb-sidebar-muted-text);\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.bb-tab:hover {\n background: var(--bb-sidebar-surface-hover);\n border-color: var(--bb-sidebar-border-hover);\n}\n.bb-tab--active {\n background: var(--bb-sidebar-accent);\n border-color: var(--bb-sidebar-accent-border);\n color: var(--bb-sidebar-accent-text);\n}\n.bb-toolbar {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 10px;\n padding: 8px 20px 8px;\n border-bottom: 1px solid var(--bb-sidebar-divider);\n flex-wrap: wrap;\n}\n.bb-toolbarGroup {\n display: flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n.bb-toolbarDivider {\n width: 1px;\n align-self: stretch;\n background: var(--bb-sidebar-divider);\n margin: 0;\n}\n.bb-iconButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n padding: 0;\n border-radius: 7px;\n border: 1px solid var(--bb-sidebar-border);\n background: var(--bb-sidebar-surface);\n color: var(--bb-sidebar-text);\n cursor: pointer;\n transition: background-color 0.18s ease, border-color 0.18s ease;\n}\n.bb-iconButton:hover:not(:disabled) {\n background: var(--bb-sidebar-surface-hover);\n border-color: var(--bb-sidebar-border-hover);\n}\n.bb-iconButton:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.bb-exportMenuWrapper {\n position: relative;\n display: inline-flex;\n}\n.bb-exportMenu {\n position: absolute;\n bottom: calc(100% + 6px);\n right: 0;\n z-index: 20;\n display: flex;\n flex-direction: column;\n min-width: 150px;\n padding: 6px;\n border-radius: 9px;\n border: 1px solid var(--bb-sidebar-border);\n background: var(--bb-menu-bg);\n box-shadow: var(--bb-menu-shadow);\n}\n.bb-exportMenuItem {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 10px;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: var(--bb-sidebar-text);\n font-size: 12px;\n font-weight: 600;\n text-align: left;\n cursor: pointer;\n}\n.bb-exportMenuItem:hover {\n background: var(--bb-sidebar-surface-hover);\n}\n@media (max-width: 640px) {\n .bb-toolbarDivider {\n width: 100%;\n height: 1px;\n }\n}\n.bb-button,\n.bb-fileLabel {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n min-height: 32px;\n padding: 0 12px;\n border-radius: 7px;\n border: 1px solid var(--bb-sidebar-border);\n background: var(--bb-sidebar-surface);\n color: var(--bb-sidebar-text);\n font-size: 12px;\n font-weight: 700;\n cursor: pointer;\n transition:\n transform 0.18s ease,\n background-color 0.18s ease,\n border-color 0.18s ease,\n box-shadow 0.18s ease;\n}\n.bb-button:hover,\n.bb-fileLabel:hover {\n transform: translateY(-1px);\n background: rgba(255, 255, 255, 0.08);\n border-color: rgba(148, 163, 184, 0.32);\n box-shadow: 0 10px 18px rgba(15, 23, 42, 0.18);\n}\n.bb-button:disabled,\n.bb-fileLabel:has(.bb-hiddenInput:disabled) {\n opacity: 0.55;\n cursor: not-allowed;\n transform: none;\n box-shadow: none;\n}\n.bb-button--primary {\n background: #135bc0;\n border-color: transparent;\n color: #ffffff;\n}\n.bb-button--primary:hover {\n background: #1a68d6;\n}\n.bb-button--ghost {\n background: rgba(248, 250, 252, 0.08);\n}\n.bb-hiddenInput {\n display: none;\n}\n.bb-statusCard {\n margin-top: 4px;\n padding: 14px 16px;\n border-radius: 9px;\n background: rgba(15, 23, 42, 0.44);\n border: 1px solid rgba(148, 163, 184, 0.15);\n}\n.bb-statusCard--error {\n background: rgba(127, 29, 29, 0.32);\n border-color: rgba(248, 113, 113, 0.24);\n}\n.bb-statusCard--success {\n background: rgba(6, 78, 59, 0.34);\n border-color: rgba(52, 211, 153, 0.24);\n}\n.bb-statusTitle {\n margin-bottom: 6px;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: #cbd5e1;\n}\n.bb-status {\n font-size: 13px;\n line-height: 1.55;\n color: #e2e8f0;\n}\n.bb-status--error {\n color: #fecaca;\n}\n.bb-status--success {\n color: #bbf7d0;\n}\n.bb-input--error {\n border-color: #dc2626;\n}\n.bb-nodeList {\n display: flex;\n flex-direction: column;\n flex: 1 1 auto;\n min-height: 0;\n overflow: hidden;\n background: #141824;\n}\n.bb-sectionHead {\n padding: 24px 20px 16px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.07);\n}\n.bb-sectionKicker {\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n color: #64748b;\n}\n.bb-sectionHeading {\n margin-top: 4px;\n font-size: 22px;\n font-weight: 700;\n line-height: 1.2;\n color: #f1f5f9;\n}\n.bb-search {\n padding: 14px 16px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.07);\n}\n.bb-input,\n.bb-select,\n.bb-textarea {\n width: 100%;\n border-radius: 10px;\n border: 1px solid rgba(255, 255, 255, 0.08);\n background: rgba(255, 255, 255, 0.05);\n color: #e2e8f0;\n padding: 10px 14px;\n font-size: 13px;\n line-height: 1.45;\n}\n.bb-input::placeholder,\n.bb-textarea::placeholder {\n color: #475569;\n}\n.bb-input--compact {\n width: 64px;\n max-width: 64px;\n flex: 0 0 auto;\n padding: 7px 4px;\n text-align: center;\n}\n.bb-input:focus,\n.bb-select:focus,\n.bb-textarea:focus {\n outline: none;\n border-color: rgba(255, 255, 255, 0.18);\n background: rgba(255, 255, 255, 0.08);\n box-shadow: none;\n}\n.bb-textarea {\n min-height: 110px;\n resize: vertical;\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Monaco,\n Consolas,\n monospace;\n}\n.bb-nodeItems {\n min-height: 0;\n overflow-y: auto;\n padding: 10px 0;\n}\n.bb-nodeButton {\n width: 100%;\n border: 0;\n border-radius: 0;\n background: transparent;\n padding: 12px 20px;\n margin-bottom: 0;\n text-align: left;\n cursor: pointer;\n transition: background-color 0.12s ease, color 0.12s ease;\n border-bottom: 1px solid rgba(255, 255, 255, 0.04);\n}\n.bb-nodeButton:hover {\n background: rgba(255, 255, 255, 0.05);\n}\n.bb-nodeButton--active {\n background: rgba(255, 255, 255, 0.05);\n box-shadow: none;\n}\n.bb-nodeName {\n display: block;\n margin-bottom: 2px;\n font-size: 14px;\n font-weight: 600;\n color: #e2e8f0;\n}\n.bb-nodeMeta {\n display: block;\n font-size: 11px;\n color: #94a3b8;\n}\n.bb-editor {\n flex: 1 1 auto;\n min-height: 0;\n height: 100%;\n overflow-y: auto;\n padding: 16px 16px 28px;\n overscroll-behavior: contain;\n}\n.bb-editorHeader {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 18px;\n padding: 18px;\n border-radius: 11px;\n background: rgba(255, 255, 255, 0.05);\n border: 1px solid rgba(148, 163, 184, 0.12);\n}\n.bb-editorTitle {\n margin: 8px 0 0;\n font-size: 24px;\n line-height: 1.1;\n color: #f8fafc;\n}\n.bb-editorMeta {\n display: flex;\n flex-wrap: wrap;\n gap: 8px 12px;\n margin-top: 10px;\n font-size: 12px;\n color: #94a3b8;\n}\n.bb-editorBadge {\n flex-shrink: 0;\n padding: 8px 12px;\n border-radius: 999px;\n background: rgba(251, 191, 36, 0.12);\n border: 1px solid rgba(251, 191, 36, 0.2);\n color: #fcd34d;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n}\n.bb-emptyEditor {\n padding: 28px 8px;\n font-size: 14px;\n color: #a7b1c3;\n}\n.bb-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}\n.bb-sectionHeaderButton {\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 cursor: pointer;\n text-align: left;\n color: inherit;\n}\n.bb-sectionHeaderButton:hover {\n background: rgba(255, 255, 255, 0.06);\n}\n.bb-sectionHeaderMain {\n display: flex;\n min-width: 0;\n align-items: center;\n gap: 8px;\n}\n.bb-sectionChevron {\n flex-shrink: 0;\n color: #8b97ab;\n transform: rotate(-90deg);\n transition: transform 0.18s ease;\n}\n.bb-sectionChevron--open {\n transform: rotate(0deg);\n}\n.bb-sectionBody {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 14px;\n border-top: 1px solid rgba(148, 163, 184, 0.1);\n}\n.bb-sectionTitle {\n margin: 0;\n font-size: 11px;\n font-weight: 700;\n color: #c3cbdb;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n}\n.bb-sectionHelp {\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.bb-sectionHelp:hover {\n background: rgba(255, 255, 255, 0.08);\n color: #eef2ff;\n transform: translateY(-0.5px);\n}\n.bb-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 10px;\n}\n.bb-presetGallery {\n display: flex;\n justify-items: center;\n flex-wrap: wrap;\n gap: 4px;\n margin-bottom: 14px;\n}\n.bb-presetSwatch {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 5px;\n width: 64px;\n padding: 6px 4px;\n border-radius: 8px;\n border: 1px solid rgba(148, 163, 184, 0.18);\n background: rgba(148, 163, 184, 0.06);\n cursor: pointer;\n transition: border-color 0.15s, background 0.15s;\n}\n.bb-presetSwatch:hover:not(:disabled) {\n border-color: rgba(148, 163, 184, 0.4);\n background: rgba(148, 163, 184, 0.12);\n}\n.bb-presetSwatch:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.bb-presetSwatchColor {\n width: 32px;\n height: 32px;\n border-radius: 999px;\n border: 1px solid rgba(255, 255, 255, 0.18);\n}\n.bb-presetSwatchLabel {\n font-size: 10px;\n font-weight: 600;\n color: #cbd5e1;\n text-align: center;\n line-height: 1.2;\n}\n.bb-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n}\n.bb-field--full {\n grid-column: 1 / -1;\n}\n.bb-label {\n font-size: 12px;\n font-weight: 700;\n color: #d7dee9;\n}\n.bb-note {\n font-size: 11px;\n color: #8b97ab;\n}\n.bb-linkButton {\n padding: 0;\n border: 0;\n background: none;\n color: inherit;\n font: inherit;\n font-weight: 700;\n text-decoration: underline;\n cursor: pointer;\n}\n.bb-linkButton:hover {\n color: #f8fafc;\n}\n.bb-tierLock {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 16px 14px;\n border-radius: 7px;\n background: rgba(251, 191, 36, 0.08);\n border: 1px dashed rgba(251, 191, 36, 0.3);\n color: #fcd34d;\n}\n.bb-tierLockMessage {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n}\n.bb-root--licenseChecking,\n.bb-root--licenseInvalid {\n align-items: center;\n justify-content: center;\n}\n.bb-readonly {\n background: rgba(148, 163, 184, 0.08);\n color: #9ca3af;\n}\n.bb-checkboxRow {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n}\n.bb-checkbox {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 6px 5px;\n border-radius: 7px;\n background: rgba(15, 23, 42, 0.22);\n border: 1px solid rgba(148, 163, 184, 0.12);\n font-size: 13px;\n color: #d7dee9;\n}\n.bb-checkbox input {\n accent-color: #f59e0b;\n}\n.bb-sliderField {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.bb-slider {\n flex: 1 1 auto;\n min-width: 0;\n accent-color: #2aa8d8;\n}\n.bb-slider--unset {\n opacity: 0.45;\n}\n.bb-fileField {\n position: relative;\n display: flex;\n align-items: center;\n}\n.bb-fileField-input {\n width: 100%;\n padding-right: 40px;\n}\n.bb-fileFieldButton {\n position: absolute;\n right: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n border: none;\n border-radius: 5px;\n background: transparent;\n color: #aab4c5;\n cursor: pointer;\n}\n.bb-fileFieldButton:hover {\n background: rgba(255, 255, 255, 0.08);\n color: #f8fafc;\n}\n.bb-materialPanel {\n margin-top: 8px;\n border: 1px solid rgba(148, 163, 184, 0.14);\n border-radius: 6px;\n background: rgba(15, 23, 42, 0.28);\n overflow: hidden;\n}\n.bb-materialPanel:first-of-type {\n margin-top: 0;\n}\n.bb-materialPanelHeader {\n display: flex;\n width: 100%;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n border: none;\n padding: 10px 12px;\n background: rgba(255, 255, 255, 0.03);\n cursor: pointer;\n text-align: left;\n color: inherit;\n}\n.bb-materialPanelHeader:hover {\n background: rgba(255, 255, 255, 0.06);\n}\n.bb-materialPanelTitleWrap {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n color: #dbe4f0;\n}\n.bb-materialPanelChevron {\n flex-shrink: 0;\n color: #8b97ab;\n transform: rotate(-90deg);\n transition: transform 0.18s ease;\n}\n.bb-materialPanelChevron--open {\n transform: rotate(0deg);\n}\n.bb-materialPanelTitle {\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.1em;\n text-transform: uppercase;\n}\n.bb-materialPanelHelp {\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.bb-materialPanelHelp:hover {\n background: rgba(255, 255, 255, 0.08);\n color: #eef2ff;\n transform: translateY(-0.5px);\n}\n.bb-materialPanelBody {\n padding: 12px;\n border-top: 1px solid rgba(148, 163, 184, 0.1);\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n.bb-actionList {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n.bb-actionCard {\n border: 1px solid rgba(148, 163, 184, 0.14);\n border-radius: 9px;\n padding: 14px;\n background: rgba(15, 23, 42, 0.2);\n}\n.bb-actionCardHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n margin-bottom: 12px;\n}\n.bb-actionCardTitle {\n font-size: 13px;\n font-weight: 700;\n color: #f8fafc;\n}\n.bb-actionCardMeta {\n flex-shrink: 0;\n padding: 5px 9px;\n border-radius: 999px;\n background: rgba(148, 163, 184, 0.12);\n color: #94a3b8;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n}\n.bb-actionGrid {\n display: grid;\n grid-template-columns: 1fr;\n gap: 10px;\n}\n.bb-actionFooter {\n margin-top: 12px;\n}\n.bb-actionRemove {\n width: 100%;\n}\n.bb-previewShell {\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n padding: 5px;\n overflow: hidden;\n}\n.bb-previewHeader {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 16px;\n padding: 0 4px 10px;\n}\n.bb-previewControls {\n display: flex;\n align-items: flex-start;\n justify-content: flex-end;\n gap: 10px;\n flex-wrap: wrap;\n}\n.bb-previewTitle {\n margin: 2px 0 0;\n font-size: 20px;\n line-height: 1.05;\n letter-spacing: -0.03em;\n color: #1f2937;\n}\n.bb-previewToggles {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 2px;\n}\n.bb-checkbox--compact {\n gap: 5px;\n padding: 0;\n background: transparent;\n border: none;\n font-size: 10px;\n font-weight: 500;\n line-height: 1;\n color: #4b5563;\n}\n.bb-checkbox--compact:hover {\n background: transparent;\n transform: none;\n}\n.bb-checkbox--compact input[type=checkbox] {\n width: 11px;\n height: 11px;\n}\n.bb-previewMeta {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-end;\n gap: 8px;\n}\n.bb-removeModelButton {\n background-color: rgb(174, 54, 54);\n min-height: 34px;\n padding: 0 14px;\n}\n.bb-removeModelButton:hover {\n background-color: rgb(163, 20, 20);\n}\n.bb-previewMeta span {\n display: inline-flex;\n align-items: center;\n min-height: 34px;\n padding: 0 12px;\n border-radius: 999px;\n background: rgba(255, 255, 255, 0.7);\n border: 1px solid rgba(148, 163, 184, 0.18);\n color: #475569;\n font-size: 12px;\n font-weight: 700;\n}\n.bb-preview {\n position: relative;\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n border-radius: 14px;\n background:\n radial-gradient(\n circle at top left,\n rgba(255, 255, 255, 0.92),\n rgba(236, 240, 244, 0.82)),\n #f8fafc;\n border: 1px solid rgba(148, 163, 184, 0.24);\n box-shadow: 0 24px 48px rgba(148, 163, 184, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.7);\n}\n.bb-loadingOverlay {\n position: absolute;\n inset: 0;\n z-index: 5;\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(248, 250, 252, 0.82);\n backdrop-filter: blur(2px);\n}\n.bb-loadingSpinner {\n width: 40px;\n height: 40px;\n border-radius: 999px;\n border: 3px solid rgba(148, 163, 184, 0.3);\n border-top-color: #2563eb;\n animation: bb-spin 0.8s linear infinite;\n}\n@keyframes bb-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.bb-loadingMessage {\n max-width: 320px;\n color: #475569;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.5;\n}\n.bb-previewStatus {\n max-width: 280px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.bb-previewStatus--success {\n background: rgba(34, 197, 94, 0.12) !important;\n border-color: rgba(34, 197, 94, 0.3) !important;\n color: #15803d !important;\n}\n.bb-previewStatus--error {\n background: rgba(239, 68, 68, 0.12) !important;\n border-color: rgba(239, 68, 68, 0.3) !important;\n color: #b91c1c !important;\n}\n.bb-previewModelName {\n max-width: 200px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.bb-previewPlaceholder {\n position: relative;\n display: flex;\n height: 100%;\n width: 100%;\n align-items: center;\n justify-content: center;\n padding: 40px;\n text-align: center;\n color: #64748b;\n font-size: 15px;\n line-height: 1.7;\n border-radius: 14px;\n border: 2px dashed rgba(148, 163, 184, 0.24);\n background:\n radial-gradient(\n circle at top,\n rgba(251, 191, 36, 0.12),\n transparent 30%),\n linear-gradient(\n 180deg,\n rgba(255, 255, 255, 0.82),\n rgba(238, 242, 248, 0.9));\n transition:\n border-color 0.2s ease,\n background 0.2s ease,\n transform 0.2s ease,\n box-shadow 0.2s ease;\n}\n.bb-previewPlaceholder > div {\n max-width: 420px;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 14px;\n}\n.bb-previewPlaceholderTitle {\n font-size: 28px;\n line-height: 1.05;\n letter-spacing: -0.03em;\n font-weight: 800;\n color: #1f2937;\n}\n.bb-previewPlaceholderButton {\n margin-top: 8px;\n border: 0;\n border-radius: 999px;\n padding: 13px 22px;\n background: #135bc0;\n color: #ffffff;\n font-size: 14px;\n font-weight: 800;\n cursor: pointer;\n transition:\n transform 0.18s ease,\n box-shadow 0.18s ease,\n opacity 0.18s ease;\n}\n.bb-previewPlaceholderButton:hover:not(:disabled) {\n transform: translateY(-1px);\n box-shadow: 0 14px 30px rgba(19, 91, 192, 0.28);\n}\n.bb-previewPlaceholderButton:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n.bb-previewPlaceholder--dragActive {\n border-color: rgba(245, 158, 11, 0.75);\n background:\n radial-gradient(\n circle at top,\n rgba(251, 191, 36, 0.22),\n transparent 32%),\n linear-gradient(\n 180deg,\n rgba(255, 252, 245, 0.95),\n rgba(255, 244, 230, 0.92));\n transform: scale(0.995);\n box-shadow: inset 0 0 0 1px rgba(245, 158, 11, 0.14);\n}\n.bb-sectionHeader {\n cursor: pointer;\n user-select: none;\n}\n.bb-iconButton {\n background: none;\n border: none;\n cursor: pointer;\n font-size: 16px;\n color: var(--bb-sidebar-subtle-text);\n transition: color 0.2s ease;\n padding: 4px 8px;\n border-radius: 4px;\n}\n.bb-iconButton:hover {\n color: var(--bb-sidebar-text);\n background: var(--bb-sidebar-surface-strong);\n}\n.bb-cameraLive,\n.bb-cameraSaved {\n background: rgba(15, 23, 42, 0.44);\n border-radius: 8px;\n padding: 14px;\n margin-bottom: 16px;\n border: 1px solid rgba(148, 163, 184, 0.15);\n transition: all 0.2s ease;\n}\n.bb-cameraLive:hover,\n.bb-cameraSaved:hover {\n border-color: rgba(148, 163, 184, 0.25);\n background: rgba(15, 23, 42, 0.52);\n}\n.bb-cameraLiveHeader,\n.bb-cameraSavedHeader {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 12px;\n padding-bottom: 8px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.15);\n}\n.bb-cameraLiveBadge {\n background:\n linear-gradient(\n 135deg,\n #10b981,\n #059669);\n color: white;\n font-size: 10px;\n font-weight: 700;\n padding: 3px 8px;\n border-radius: 10px;\n letter-spacing: 0.5px;\n text-transform: uppercase;\n}\n.bb-cameraSavedBadge {\n background:\n linear-gradient(\n 135deg,\n #3b82f6,\n #2563eb);\n color: white;\n font-size: 10px;\n font-weight: 700;\n padding: 3px 8px;\n border-radius: 10px;\n letter-spacing: 0.5px;\n text-transform: uppercase;\n}\n.bb-cameraLiveLabel,\n.bb-cameraSavedLabel {\n font-size: 12px;\n font-weight: 600;\n color: #cbd5e1;\n}\n.bb-cameraValues {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.bb-cameraRow {\n display: flex;\n align-items: center;\n gap: 10px;\n font-size: 12px;\n flex-wrap: wrap;\n}\n.bb-cameraLabel {\n font-weight: 600;\n color: #94a3b8;\n min-width: 58px;\n font-size: 11px;\n letter-spacing: 0.02em;\n}\n.bb-cameraCode {\n font-family:\n "Monaco",\n "Menlo",\n "Ubuntu Mono",\n "Consolas",\n monospace;\n font-size: 11px;\n background: rgba(0, 0, 0, 0.3);\n padding: 3px 8px;\n border-radius: 4px;\n color: #fcd34d;\n letter-spacing: 0.02em;\n word-break: break-all;\n}\n.bb-cameraPlaceholder {\n color: #94a3b8;\n font-size: 12px;\n text-align: center;\n padding: 16px;\n font-style: italic;\n}\n.bb-cameraControls {\n display: flex;\n gap: 10px;\n margin-bottom: 14px;\n flex-wrap: wrap;\n}\n.bb-button--primary {\n background: #135bc0;\n border-color: transparent;\n color: #ffffff;\n}\n.bb-button--primary:hover:not(:disabled) {\n background: #1a68d6;\n transform: translateY(-1px);\n}\n.bb-button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n transform: none;\n}\n.bb-cameraMessage {\n padding: 10px 14px;\n border-radius: 6px;\n margin-bottom: 14px;\n font-size: 12px;\n font-weight: 500;\n animation: slideIn 0.3s ease-out;\n}\n.bb-cameraMessage--success {\n background: rgba(16, 185, 129, 0.15);\n color: #6ee7b7;\n border: 1px solid rgba(16, 185, 129, 0.3);\n}\n.bb-cameraMessage--error {\n background: rgba(239, 68, 68, 0.15);\n color: #ffe5e55c;\n border: 1px solid rgba(255, 248, 248, 0.3);\n}\n@keyframes slideIn {\n from {\n opacity: 0;\n transform: translateY(-8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n.bb-readonly {\n background: rgba(148, 163, 184, 0.08);\n color: #9ca3af;\n cursor: default;\n}\n@media (max-width: 1280px) {\n .bb-cameraRow {\n flex-direction: column;\n align-items: flex-start;\n gap: 4px;\n }\n .bb-cameraLabel {\n min-width: auto;\n }\n .bb-cameraControls {\n flex-direction: column;\n }\n .bb-cameraControls .bb-button {\n width: 100%;\n }\n}\n@media (max-width: 980px) {\n .bb-sidebar--left {\n width: 100%;\n max-height: 42vh;\n }\n .bb-sidebar--collapsed {\n width: 100%;\n }\n .bb-previewShell {\n padding: 14px;\n }\n .bb-preview {\n min-height: 44vh;\n }\n .bb-previewHeader {\n flex-direction: column;\n align-items: flex-start;\n }\n .bb-previewControls {\n width: 100%;\n justify-content: flex-start;\n }\n .bb-previewMeta {\n justify-content: flex-start;\n }\n .bb-grid,\n .bb-actionGrid,\n .bb-statRow {\n grid-template-columns: 1fr;\n }\n}\n.bb-annotationsInfo {\n margin-top: 8px;\n}\n.bb-emptyAnnotations {\n padding: 32px;\n text-align: center;\n background: #f5f5f5;\n border-radius: 4px;\n color: #666;\n font-size: 14px;\n}\n.bb-annotationsList {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n.bb-annotationCard {\n background: #f9f9f9;\n border: 1px solid #e0e0e0;\n border-radius: 4px;\n padding: 12px;\n transition: all 0.2s ease;\n}\n.bb-annotationCard:hover {\n background: #ffffff;\n border-color: #4caf50;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n}\n.bb-annotationHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n}\n.bb-annotationBadge {\n background: #4caf50;\n color: white;\n padding: 2px 8px;\n border-radius: 2px;\n font-size: 11px;\n font-weight: 600;\n}\n.bb-annotationActions {\n display: flex;\n gap: 8px;\n}\n.bb-annotationEdit,\n.bb-annotationDelete {\n background: transparent;\n border: none;\n cursor: pointer;\n padding: 4px;\n border-radius: 2px;\n font-size: 14px;\n transition: all 0.2s ease;\n}\n.bb-annotationEdit:hover {\n background: #e8f5e9;\n transform: scale(1.05);\n}\n.bb-annotationDelete:hover {\n background: #ffebee;\n transform: scale(1.05);\n}\n.bb-annotationTitle {\n font-weight: 600;\n font-size: 14px;\n color: #333;\n margin-bottom: 6px;\n}\n.bb-annotationDescription {\n font-size: 13px;\n color: #666;\n margin-bottom: 8px;\n line-height: 1.4;\n}\n.bb-annotationPosition {\n font-size: 11px;\n font-family: monospace;\n color: #999;\n background: #f0f0f0;\n padding: 4px 6px;\n border-radius: 2px;\n display: inline-block;\n}\n.bb-modalOverlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n}\n.bb-modal {\n background: white;\n border-radius: 6px;\n width: 500px;\n max-width: 90%;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);\n animation: modalSlideIn 0.2s ease;\n}\n@keyframes modalSlideIn {\n from {\n transform: translateY(-20px);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n.bb-modalHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 16px 20px;\n border-bottom: 1px solid #e0e0e0;\n}\n.bb-modalHeader h3 {\n margin: 0;\n font-size: 18px;\n font-weight: 600;\n}\n.bb-modalClose {\n background: transparent;\n border: none;\n font-size: 20px;\n cursor: pointer;\n color: #999;\n transition: color 0.2s ease;\n}\n.bb-modalClose:hover {\n color: #333;\n}\n.bb-modalBody {\n padding: 20px;\n}\n.bb-modalFooter {\n display: flex;\n justify-content: flex-end;\n gap: 12px;\n padding: 16px 20px;\n border-top: 1px solid #e0e0e0;\n}\n.bb-annotationsInfo {\n margin-top: 8px;\n}\n.bb-emptyAnnotations {\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.bb-annotationsList {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n.bb-annotationCard {\n background: rgba(15, 23, 42, 0.44);\n border-left: 4px solid #22d3ee;\n border-radius: 8px;\n padding: 16px;\n transition: all 0.2s ease;\n border: 1px solid rgba(148, 163, 184, 0.15);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03);\n}\n.bb-annotationCard:hover {\n transform: translateX(4px);\n background: rgba(15, 23, 42, 0.6);\n border-color: rgba(148, 163, 184, 0.25);\n box-shadow: 0 4px 12px rgba(15, 23, 42, 0.3);\n}\n.bb-annotationBadge {\n display: inline-block;\n background: #22d3ee;\n color: #06343c;\n padding: 2px 10px;\n border-radius: 10px;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.5px;\n margin-bottom: 12px;\n text-transform: uppercase;\n}\n.bb-annotationTitle {\n font-weight: 700;\n font-size: 15px;\n color: #f8fafc;\n margin-bottom: 8px;\n line-height: 1.3;\n}\n.bb-annotationDescription {\n font-size: 13px;\n color: #cbd5e1;\n margin-bottom: 12px;\n line-height: 1.5;\n word-wrap: break-word;\n white-space: pre-wrap;\n}\n.bb-annotationPosition {\n font-size: 11px;\n font-family:\n "Monaco",\n "Menlo",\n "Ubuntu Mono",\n "Consolas",\n monospace;\n color: #94a3b8;\n background: rgba(0, 0, 0, 0.3);\n padding: 4px 8px;\n border-radius: 4px;\n display: inline-block;\n margin-top: 4px;\n letter-spacing: 0.02em;\n}\n.bb-annotationHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n}\n.bb-annotationActions {\n display: flex;\n gap: 4px;\n}\n.bb-annotationAction {\n background: none;\n border: none;\n color: #94a3b8;\n cursor: pointer;\n padding: 4px;\n border-radius: 2px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s;\n}\n.bb-annotationAction--edit:hover {\n border-radius: 50%;\n background: rgba(59, 130, 246, 0.2);\n color: #3b82f6;\n}\n.bb-annotationAction--delete:hover {\n border-radius: 50%;\n background: rgba(239, 68, 68, 0.2);\n color: #ef4444;\n}\n.bb-editSpotOverlay {\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-editSpotPopup {\n background:\n linear-gradient(\n 180deg,\n rgba(20, 27, 45, 0.98),\n rgba(25, 34, 57, 0.95)),\n #121826;\n border-radius: 8px;\n padding: 24px;\n min-width: 400px;\n max-width: 500px;\n border: 1px solid rgba(148, 163, 184, 0.18);\n box-shadow: 0 24px 48px rgba(15, 23, 42, 0.3);\n}\n.bb-editSpotHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 16px;\n}\n.bb-editSpotTitle {\n color: #f8fafc;\n font-size: 18px;\n font-weight: 600;\n margin: 0;\n}\n.bb-editSpotClose {\n background: none;\n border: none;\n color: #94a3b8;\n font-size: 24px;\n cursor: pointer;\n padding: 0 4px;\n transition: color 0.2s ease;\n}\n.bb-editSpotClose:hover {\n color: #f8fafc;\n}\n.bb-editSpotBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n.bb-editSpotBody .bb-input,\n.bb-editSpotBody .bb-textarea {\n background: rgba(15, 23, 42, 0.4);\n border-color: rgba(148, 163, 184, 0.2);\n color: #f8fafc;\n}\n.bb-editSpotBody .bb-input:focus,\n.bb-editSpotBody .bb-textarea:focus {\n border-color: rgba(251, 191, 36, 0.65);\n box-shadow: 0 0 0 4px rgba(251, 191, 36, 0.12);\n}\n.bb-editSpotBody .bb-label {\n color: #d7dee9;\n}\n.bb-editSpotActions {\n display: flex;\n gap: 8px;\n margin-top: 8px;\n}\n.bb-editSpotActions .bb-button {\n background: rgba(255, 255, 255, 0.04);\n border: 1px solid rgba(148, 163, 184, 0.18);\n color: #e5edf7;\n}\n.bb-editSpotActions .bb-button:hover {\n background: rgba(255, 255, 255, 0.08);\n border-color: rgba(148, 163, 184, 0.32);\n transform: translateY(-1px);\n}\n.bb-button--danger {\n background: #ef4444;\n color: white;\n border: none;\n padding: 8px 16px;\n border-radius: 7px;\n cursor: pointer;\n font-size: 13px;\n font-weight: 700;\n display: flex;\n align-items: center;\n gap: 4px;\n transition: all 0.2s ease;\n}\n.bb-button--danger:hover {\n background: #dc2626;\n transform: translateY(-1px);\n box-shadow: 0 10px 18px rgba(239, 68, 68, 0.25);\n}\n.bb-button--primary {\n background: #135bc0;\n border-color: transparent;\n color: #ffffff;\n}\n.bb-button--primary:hover {\n background: #1a68d6;\n}\n.bb-annotationCard {\n background: rgba(15, 23, 42, 0.44);\n border-left: 4px solid #22d3ee;\n border-radius: 8px;\n padding: 16px;\n transition: all 0.2s ease;\n border: 1px solid rgba(148, 163, 184, 0.15);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03);\n}\n.bb-annotationCard:hover {\n transform: translateX(4px);\n background: rgba(15, 23, 42, 0.6);\n border-color: rgba(148, 163, 184, 0.25);\n box-shadow: 0 4px 12px rgba(15, 23, 42, 0.3);\n}\n.bb-annotationBadge {\n display: inline-block;\n background: #22d3ee;\n color: #06343c;\n padding: 2px 10px;\n border-radius: 10px;\n font-size: 10px;\n font-weight: 700;\n letter-spacing: 0.5px;\n margin-bottom: 12px;\n text-transform: uppercase;\n}\n.bb-annotationTitle {\n font-weight: 700;\n font-size: 15px;\n color: #f8fafc;\n margin-bottom: 8px;\n line-height: 1.3;\n}\n.bb-annotationDescription {\n font-size: 13px;\n color: #cbd5e1;\n margin-bottom: 12px;\n line-height: 1.5;\n word-wrap: break-word;\n white-space: pre-wrap;\n}\n.bb-annotationPosition {\n font-size: 11px;\n font-family:\n "Monaco",\n "Menlo",\n "Ubuntu Mono",\n "Consolas",\n monospace;\n color: #94a3b8;\n background: rgba(0, 0, 0, 0.3);\n padding: 4px 8px;\n border-radius: 4px;\n display: inline-block;\n margin-top: 4px;\n letter-spacing: 0.02em;\n}\n.bb-emptyAnnotations {\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@media (max-width: 1280px) {\n .bb-cameraRow {\n flex-direction: column;\n align-items: flex-start;\n gap: 4px;\n }\n .bb-cameraLabel {\n min-width: auto;\n }\n .bb-cameraControls {\n flex-direction: column;\n }\n .bb-cameraControls .bb-button {\n width: 100%;\n }\n}\n@media (max-width: 1120px) {\n .bb-sidebar--left {\n width: 320px;\n }\n .bb-sidebar--right {\n width: 220px;\n }\n .bb-grid {\n grid-template-columns: 1fr;\n }\n .bb-statRow {\n grid-template-columns: 1fr;\n }\n}\n@media (max-width: 980px) {\n .bb-root {\n flex-direction: column;\n height: auto;\n min-height: 0;\n }\n .bb-sidebar--left {\n width: 100% !important;\n max-height: 45vh;\n border-right: none;\n border-bottom: 1px solid rgba(148, 163, 184, 0.18);\n box-shadow: 0 18px 42px rgba(15, 23, 42, 0.12);\n }\n .bb-sidebar--right {\n width: 100% !important;\n max-height: 45vh;\n border-left: none;\n border-top: 1px solid rgba(148, 163, 184, 0.18);\n box-shadow: 0 -18px 42px rgba(15, 23, 42, 0.12);\n order: 3;\n }\n .bb-sidebar--collapsed {\n width: 100% !important;\n max-height: 72px;\n overflow: hidden;\n }\n .bb-sidebar--collapsed .bb-nodeList,\n .bb-sidebar--collapsed .bb-tabRow,\n .bb-sidebar--collapsed .bb-toolbar {\n display: none;\n }\n .bb-previewShell {\n flex: 1 1 auto;\n min-height: 50vh;\n padding: 14px;\n order: 2;\n }\n .bb-preview {\n min-height: 44vh;\n border-radius: 10px;\n }\n .bb-previewHeader {\n flex-direction: column;\n align-items: flex-start;\n gap: 12px;\n }\n .bb-previewControls {\n width: 100%;\n justify-content: flex-start;\n flex-wrap: wrap;\n }\n .bb-previewMeta {\n justify-content: flex-start;\n }\n .bb-previewMeta span {\n font-size: 11px;\n padding: 0 10px;\n min-height: 30px;\n }\n .bb-grid,\n .bb-actionGrid,\n .bb-statRow {\n grid-template-columns: 1fr;\n }\n .bb-editor {\n padding: 12px 12px 20px;\n }\n .bb-editorHeader {\n flex-direction: column;\n align-items: stretch;\n padding: 14px;\n }\n .bb-editorTitle {\n font-size: 20px;\n }\n .bb-editorBadge {\n align-self: flex-start;\n }\n .bb-toolbar {\n flex-direction: column;\n gap: 12px;\n padding: 16px 20px 18px;\n }\n .bb-toolbarDivider {\n width: 100%;\n height: 1px;\n margin: 0;\n }\n .bb-tabRow {\n padding: 12px 16px 0;\n gap: 6px;\n }\n .bb-tab {\n font-size: 10px;\n padding: 8px 10px;\n }\n .bb-header {\n padding: 14px 16px 12px;\n }\n .bb-header--sidebar {\n min-height: 60px;\n }\n .bb-title {\n font-size: 17px;\n }\n .bb-subtitle {\n font-size: 13px;\n }\n .bb-statChip {\n padding: 10px 12px;\n }\n .bb-statChip strong {\n font-size: 12px;\n }\n .bb-nodeButton {\n padding: 10px 12px;\n }\n .bb-nodeName {\n font-size: 12px;\n }\n .bb-nodeMeta {\n font-size: 10px;\n }\n .bb-section {\n padding: 14px;\n }\n .bb-sectionTitle {\n font-size: 9px;\n }\n .bb-field {\n gap: 4px;\n }\n .bb-label {\n font-size: 11px;\n }\n .bb-input,\n .bb-select,\n .bb-textarea {\n padding: 9px 11px;\n font-size: 12px;\n }\n .bb-textarea {\n min-height: 80px;\n }\n .bb-button,\n .bb-fileLabel {\n min-height: 36px;\n padding: 0 14px;\n font-size: 12px;\n }\n .bb-previewPlaceholderTitle {\n font-size: 22px;\n }\n .bb-previewPlaceholderButton {\n padding: 10px 18px;\n font-size: 13px;\n }\n .bb-editSpotPopup {\n min-width: 90%;\n max-width: 90%;\n padding: 20px;\n }\n .bb-modal {\n width: 95%;\n max-width: 95%;\n }\n .bb-annotationCard {\n padding: 12px;\n }\n .bb-annotationTitle {\n font-size: 14px;\n }\n .bb-annotationDescription {\n font-size: 12px;\n }\n}\n@media (max-width: 640px) {\n .bb-root {\n flex-direction: column;\n height: auto;\n min-height: 0;\n }\n .bb-sidebar--left {\n max-height: 40vh;\n width: 100% !important;\n }\n .bb-sidebar--right {\n max-height: 40vh;\n width: 100% !important;\n }\n .bb-previewShell {\n min-height: 40vh;\n padding: 10px;\n }\n .bb-preview {\n min-height: 35vh;\n border-radius: 8px;\n }\n .bb-previewHeader {\n flex-direction: column;\n align-items: flex-start;\n gap: 8px;\n padding: 0 0 12px;\n }\n .bb-previewTitle {\n font-size: 17px;\n }\n .bb-previewControls {\n gap: 6px;\n }\n .bb-previewMeta span {\n font-size: 10px;\n padding: 0 8px;\n min-height: 26px;\n }\n .bb-editor {\n padding: 10px 10px 16px;\n }\n .bb-editorHeader {\n padding: 12px;\n flex-direction: column;\n align-items: stretch;\n }\n .bb-editorTitle {\n font-size: 18px;\n }\n .bb-editorMeta {\n font-size: 11px;\n gap: 6px 8px;\n }\n .bb-editorBadge {\n font-size: 9px;\n padding: 4px 10px;\n align-self: flex-start;\n }\n .bb-section {\n padding: 12px;\n margin-bottom: 12px;\n }\n .bb-sectionTitle {\n font-size: 9px;\n margin-bottom: 10px;\n }\n .bb-grid {\n gap: 8px;\n }\n .bb-field--full {\n grid-column: 1 / -1;\n }\n .bb-label {\n font-size: 11px;\n }\n .bb-input,\n .bb-select,\n .bb-textarea {\n padding: 8px 10px;\n font-size: 12px;\n border-radius: 6px;\n }\n .bb-textarea {\n min-height: 70px;\n }\n .bb-button,\n .bb-fileLabel {\n min-height: 34px;\n padding: 0 12px;\n font-size: 11px;\n border-radius: 6px;\n }\n .bb-toolbar {\n padding: 12px 14px 14px;\n gap: 10px;\n }\n .bb-tabRow {\n padding: 10px 12px 0;\n gap: 4px;\n }\n .bb-tab {\n font-size: 9px;\n padding: 6px 8px;\n border-radius: 4px;\n }\n .bb-header {\n padding: 10px 12px 8px;\n }\n .bb-header--sidebar {\n min-height: 52px;\n }\n .bb-title {\n font-size: 15px;\n }\n .bb-subtitle {\n font-size: 12px;\n }\n .bb-eyebrow {\n font-size: 9px;\n padding: 4px 8px;\n margin-bottom: 8px;\n }\n .bb-statRow {\n gap: 6px;\n margin-top: 14px;\n }\n .bb-statChip {\n padding: 8px 10px;\n border-radius: 6px;\n }\n .bb-statChip strong {\n font-size: 11px;\n }\n .bb-statLabel {\n font-size: 9px;\n }\n .bb-sidebarToggle {\n width: 30px;\n height: 30px;\n font-size: 14px;\n border-radius: 5px;\n }\n .bb-nodeItems {\n padding: 4px 6px;\n }\n .bb-nodeButton {\n padding: 8px 10px;\n border-radius: 6px;\n margin-bottom: 4px;\n }\n .bb-nodeName {\n font-size: 11px;\n }\n .bb-nodeMeta {\n font-size: 10px;\n }\n .bb-search {\n padding: 8px 10px;\n }\n .bb-statusCard {\n padding: 10px 12px;\n border-radius: 7px;\n }\n .bb-status {\n font-size: 12px;\n }\n .bb-statusTitle {\n font-size: 10px;\n }\n .bb-checkboxRow {\n gap: 6px;\n }\n .bb-checkbox {\n padding: 6px 10px;\n font-size: 12px;\n border-radius: 5px;\n }\n .bb-actionCard {\n padding: 10px;\n border-radius: 7px;\n }\n .bb-actionCardTitle {\n font-size: 12px;\n }\n .bb-actionCardMeta {\n font-size: 9px;\n padding: 3px 7px;\n }\n .bb-previewPlaceholder {\n padding: 24px;\n border-radius: 8px;\n }\n .bb-previewPlaceholderTitle {\n font-size: 18px;\n }\n .bb-previewPlaceholderButton {\n padding: 8px 14px;\n font-size: 12px;\n border-radius: 999px;\n }\n .bb-editSpotPopup {\n min-width: 95%;\n max-width: 95%;\n padding: 16px;\n border-radius: 6px;\n }\n .bb-editSpotTitle {\n font-size: 16px;\n }\n .bb-editSpotActions .bb-button {\n padding: 8px 12px;\n font-size: 12px;\n }\n .bb-modal {\n width: 98%;\n max-width: 98%;\n border-radius: 5px;\n }\n .bb-modalHeader {\n padding: 12px 16px;\n }\n .bb-modalHeader h3 {\n font-size: 16px;\n }\n .bb-modalBody {\n padding: 14px;\n }\n .bb-modalFooter {\n padding: 12px 16px;\n flex-direction: column;\n gap: 8px;\n }\n .bb-modalFooter .bb-button {\n width: 100%;\n justify-content: center;\n }\n .bb-annotationCard {\n padding: 10px;\n border-left-width: 3px;\n border-radius: 6px;\n }\n .bb-annotationTitle {\n font-size: 13px;\n }\n .bb-annotationDescription {\n font-size: 11px;\n }\n .bb-annotationPosition {\n font-size: 10px;\n padding: 3px 6px;\n }\n .bb-annotationBadge {\n font-size: 9px;\n padding: 2px 8px;\n }\n .bb-emptyAnnotations {\n padding: 20px;\n font-size: 12px;\n border-radius: 7px;\n }\n .bb-removeModelButton {\n min-height: 30px;\n padding: 0 10px;\n font-size: 11px;\n }\n .bb-annotationsList {\n gap: 8px;\n }\n .bb-cameraLive,\n .bb-cameraSaved {\n padding: 10px;\n border-radius: 6px;\n }\n .bb-cameraLiveHeader,\n .bb-cameraSavedHeader {\n flex-wrap: wrap;\n }\n .bb-cameraLiveLabel,\n .bb-cameraSavedLabel {\n font-size: 11px;\n }\n .bb-cameraRow {\n font-size: 11px;\n gap: 4px;\n }\n .bb-cameraCode {\n font-size: 10px;\n padding: 2px 6px;\n }\n .bb-cameraControls {\n flex-direction: column;\n gap: 6px;\n }\n .bb-cameraControls .bb-button {\n width: 100%;\n justify-content: center;\n }\n .bb-cameraMessage {\n font-size: 11px;\n padding: 8px 12px;\n }\n .bb-readonly {\n font-size: 11px;\n padding: 6px 8px;\n }\n}\n@media (max-width: 420px) {\n .bb-sidebar--left {\n max-height: 35vh;\n }\n .bb-sidebar--right {\n max-height: 35vh;\n }\n .bb-previewShell {\n min-height: 35vh;\n padding: 6px;\n }\n .bb-preview {\n min-height: 30vh;\n border-radius: 6px;\n }\n .bb-previewTitle {\n font-size: 15px;\n }\n .bb-previewMeta span {\n font-size: 9px;\n padding: 0 6px;\n min-height: 22px;\n }\n .bb-editorTitle {\n font-size: 16px;\n }\n .bb-title {\n font-size: 13px;\n }\n .bb-tab {\n font-size: 8px;\n padding: 5px 6px;\n }\n .bb-button,\n .bb-fileLabel {\n font-size: 10px;\n min-height: 30px;\n padding: 0 10px;\n }\n .bb-input,\n .bb-select,\n .bb-textarea {\n font-size: 11px;\n padding: 6px 8px;\n }\n .bb-statChip {\n padding: 6px 8px;\n }\n .bb-statChip strong {\n font-size: 10px;\n }\n .bb-previewPlaceholderTitle {\n font-size: 15px;\n }\n .bb-previewPlaceholderButton {\n font-size: 11px;\n padding: 6px 12px;\n }\n .bb-editSpotPopup {\n padding: 14px;\n border-radius: 5px;\n }\n .bb-editSpotTitle {\n font-size: 14px;\n }\n .bb-editSpotBody .bb-input,\n .bb-editSpotBody .bb-textarea {\n font-size: 12px;\n padding: 6px 8px;\n }\n .bb-modalHeader h3 {\n font-size: 14px;\n }\n .bb-annotationTitle {\n font-size: 12px;\n }\n}\n@media (max-width: 980px) {\n .bb-sidebar--collapsed .bb-header--sidebar {\n min-height: 52px;\n }\n .bb-sidebar--collapsed .bb-title {\n font-size: 14px;\n }\n .bb-sidebar--collapsed .bb-sidebarToggle {\n width: 28px;\n height: 28px;\n font-size: 12px;\n }\n}\n@media (hover: none) and (pointer: coarse) {\n .bb-nodeButton:hover,\n .bb-button:hover,\n .bb-fileLabel:hover,\n .bb-tab:hover,\n .bb-sidebarToggle:hover,\n .bb-annotationCard:hover {\n transform: none;\n }\n .bb-nodeButton:active,\n .bb-button:active,\n .bb-fileLabel:active,\n .bb-tab:active,\n .bb-sidebarToggle:active {\n transform: scale(0.97);\n transition: transform 0.1s ease;\n }\n}\n@media print {\n .bb-root {\n height: auto;\n min-height: 0;\n background: white;\n }\n .bb-sidebar--left,\n .bb-sidebar--right {\n width: 100% !important;\n max-height: none !important;\n background: white !important;\n color: black !important;\n border: 1px solid #ddd !important;\n box-shadow: none !important;\n }\n .bb-previewShell {\n min-height: auto !important;\n padding: 10px !important;\n }\n .bb-preview {\n min-height: auto !important;\n border-radius: 4px !important;\n border: 1px solid #ddd !important;\n box-shadow: none !important;\n }\n .bb-button,\n .bb-fileLabel,\n .bb-sidebarToggle,\n .bb-tab,\n .bb-annotationAction,\n .bb-iconButton {\n display: none !important;\n }\n .bb-nodeList {\n overflow: visible !important;\n }\n .bb-previewPlaceholderButton {\n display: none !important;\n }\n}\n.bb-checkboxRow {\n display: flex;\n flex-wrap: wrap;\n gap: 12px;\n align-items: center;\n padding: 4px 0;\n}\n.bb-checkbox {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n border-radius: 7px;\n font-weight: 600;\n font-size: 13px;\n color: #f3eeee;\n cursor: pointer;\n user-select: none;\n transition: all 0.2s ease;\n}\n.bb-checkbox input[type=checkbox] {\n width: 16px;\n height: 16px;\n accent-color: #f59e0b;\n cursor: pointer;\n flex-shrink: 0;\n}\n.bb-editorDivider {\n height: 1px;\n background: rgba(148, 163, 184, 0.14);\n margin: 4px 0 16px 0;\n width: 100%;\n}\n.bb-preview {\n position: relative;\n width: 100%;\n height: 100%;\n overflow: hidden;\n}\n.ri-viewer {\n width: 100% !important;\n height: 100% !important;\n position: relative !important;\n}\n.ri-buttonContainer {\n position: absolute !important;\n bottom: 20px !important;\n right: 20px !important;\n z-index: 10000 !important;\n display: flex !important;\n gap: 10px !important;\n pointer-events: auto !important;\n}\n.ri-downloadButton {\n display: flex !important;\n align-items: center !important;\n gap: 6px !important;\n padding: 10px 12px !important;\n background: #2f302f74 !important;\n color: white !important;\n border: none !important;\n border-radius: 6px !important;\n cursor: pointer !important;\n font-size: 12px !important;\n font-weight: 500 !important;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;\n opacity: 1 !important;\n visibility: visible !important;\n}\n.ri-downloadButton svg {\n width: 14px !important;\n height: 14px !important;\n}\n.ri-resetButton {\n display: flex !important;\n align-items: center !important;\n justify-content: center !important;\n padding: 6px !important;\n background: #2f302f74 !important;\n border: none !important;\n border-radius: 6px !important;\n cursor: pointer !important;\n opacity: 1 !important;\n visibility: visible !important;\n}\n.ri-resetButton svg {\n width: 14px !important;\n height: 14px !important;\n}\n.bb-reset-overlay {\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-fade-in 0.2s ease;\n}\n.bb-reset-dialog {\n background: #1a1f2e;\n border-radius: 12px;\n max-width: 480px;\n width: 90%;\n border: 1px solid rgba(148, 163, 184, 0.18);\n box-shadow: 0 24px 48px rgba(0, 0, 0, 0.5);\n animation: bb-slide-up 0.25s ease;\n}\n.bb-reset-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px 16px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.1);\n}\n.bb-reset-title {\n display: flex;\n align-items: center;\n gap: 10px;\n margin: 0;\n font-size: 18px;\n font-weight: 700;\n color: #f8fafc;\n}\n.bb-reset-title .bb-icon-warning {\n color: #f59e0b;\n flex-shrink: 0;\n}\n.bb-reset-close {\n background: transparent;\n border: none;\n color: #94a3b8;\n cursor: pointer;\n padding: 4px;\n border-radius: 4px;\n transition: all 0.2s ease;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.bb-reset-close:hover {\n color: #f8fafc;\n background: rgba(255, 255, 255, 0.06);\n}\n.bb-reset-body {\n padding: 20px 24px 24px;\n}\n.bb-reset-message {\n margin: 0 0 12px 0;\n font-size: 15px;\n color: #e2e8f0;\n line-height: 1.5;\n}\n.bb-reset-message strong {\n color: #f8fafc;\n}\n.bb-reset-details {\n color: #94a3b8;\n text-align: center;\n font-size: 13px;\n line-height: 1.6;\n margin: 0 0 12px 0;\n}\n.bb-reset-warning {\n display: flex;\n align-items: center;\n gap: 8px;\n color: #f59e0b;\n font-size: 13px;\n font-weight: 600;\n margin: 4px 0 0 0;\n padding: 10px 12px;\n background: rgba(245, 158, 11, 0.08);\n border-radius: 6px;\n border: 1px solid rgba(245, 158, 11, 0.15);\n}\n.bb-reset-footer {\n display: flex;\n justify-content: flex-end;\n gap: 10px;\n padding: 16px 24px 20px;\n border-top: 1px solid rgba(148, 163, 184, 0.1);\n}\n.bb-reset-btn {\n padding: 8px 20px;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s ease;\n border: 1px solid rgba(148, 163, 184, 0.18);\n background: rgba(255, 255, 255, 0.04);\n color: #e2e8f0;\n}\n.bb-reset-btn:hover {\n background: rgba(255, 255, 255, 0.08);\n transform: translateY(-1px);\n}\n.bb-reset-btn-danger {\n padding: 8px 20px;\n border-radius: 6px;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s ease;\n border: none;\n background: #dc2626;\n color: white;\n}\n.bb-reset-btn-danger:hover {\n background: #b91c1c;\n transform: translateY(-1px);\n box-shadow: 0 4px 12px rgba(220, 38, 38, 0.3);\n}\n@keyframes bb-fade-in {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n@keyframes bb-slide-up {\n from {\n opacity: 0;\n transform: translateY(20px) scale(0.96);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n');
|
|
8231
8238
|
|
|
8232
8239
|
// src/hooks/useViewerCamera.ts
|
|
8233
8240
|
var import_react23 = require("react");
|
|
@@ -11722,7 +11729,7 @@ function LightingTab({ config, onChange }) {
|
|
|
11722
11729
|
{
|
|
11723
11730
|
className: "se-segmentedControl",
|
|
11724
11731
|
style: { gridAutoColumns: "1fr" },
|
|
11725
|
-
children: ["shadowCatcher"
|
|
11732
|
+
children: ["shadowCatcher"].map((m) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
|
|
11726
11733
|
"button",
|
|
11727
11734
|
{
|
|
11728
11735
|
type: "button",
|
|
@@ -14457,8 +14464,99 @@ function BasicsPanel({
|
|
|
14457
14464
|
] });
|
|
14458
14465
|
}
|
|
14459
14466
|
|
|
14467
|
+
// src/components/BindingBuilder/components/ActionsPanel.tsx
|
|
14468
|
+
var import_react37 = require("react");
|
|
14469
|
+
|
|
14470
|
+
// src/components/BindingBuilder/utils/actionCatalog.ts
|
|
14471
|
+
var ACTION_CATALOG = [
|
|
14472
|
+
{
|
|
14473
|
+
id: "change-color",
|
|
14474
|
+
label: "Change Color",
|
|
14475
|
+
handling: "viewer",
|
|
14476
|
+
description: "Opens a color picker docked under the action and commits the result to style.material.baseColor."
|
|
14477
|
+
},
|
|
14478
|
+
{
|
|
14479
|
+
id: "change-material",
|
|
14480
|
+
label: "Change Material",
|
|
14481
|
+
handling: "viewer",
|
|
14482
|
+
description: "Opens the material picker so the viewer can swap the object's texture."
|
|
14483
|
+
},
|
|
14484
|
+
{
|
|
14485
|
+
id: "toggle-visibility",
|
|
14486
|
+
label: "Toggle Visibility",
|
|
14487
|
+
handling: "viewer",
|
|
14488
|
+
description: "Flips the binding's visible flag."
|
|
14489
|
+
},
|
|
14490
|
+
{
|
|
14491
|
+
id: "toggle-light",
|
|
14492
|
+
label: "Toggle Light",
|
|
14493
|
+
handling: "host",
|
|
14494
|
+
description: "Seeded on light bindings. Handle it in your onAction callback."
|
|
14495
|
+
},
|
|
14496
|
+
{
|
|
14497
|
+
id: "change-light-color",
|
|
14498
|
+
label: "Change Light Color",
|
|
14499
|
+
handling: "host",
|
|
14500
|
+
description: "Seeded on light bindings. Handle it in your onAction callback."
|
|
14501
|
+
},
|
|
14502
|
+
{
|
|
14503
|
+
id: "set-brightness",
|
|
14504
|
+
label: "Set Brightness",
|
|
14505
|
+
handling: "host",
|
|
14506
|
+
description: "Seeded on light bindings. Handle it in your onAction callback."
|
|
14507
|
+
}
|
|
14508
|
+
];
|
|
14509
|
+
var CUSTOM_ACTION_ID = "__custom__";
|
|
14510
|
+
function findCatalogAction(id) {
|
|
14511
|
+
return ACTION_CATALOG.find((entry) => entry.id === id);
|
|
14512
|
+
}
|
|
14513
|
+
var VIEWER_ACTION_IDS = ACTION_CATALOG.filter(
|
|
14514
|
+
(entry) => entry.handling === "viewer"
|
|
14515
|
+
).map((entry) => entry.id);
|
|
14516
|
+
var AUTO_LABELS = /* @__PURE__ */ new Set([
|
|
14517
|
+
"",
|
|
14518
|
+
"New Action",
|
|
14519
|
+
...ACTION_CATALOG.map((entry) => entry.label)
|
|
14520
|
+
]);
|
|
14521
|
+
function isAutoFilledLabel(label) {
|
|
14522
|
+
return AUTO_LABELS.has(label.trim());
|
|
14523
|
+
}
|
|
14524
|
+
function levenshtein(a, b) {
|
|
14525
|
+
let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
14526
|
+
for (let i = 1; i <= a.length; i += 1) {
|
|
14527
|
+
const current = [i];
|
|
14528
|
+
for (let j = 1; j <= b.length; j += 1) {
|
|
14529
|
+
const substitution = previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1);
|
|
14530
|
+
current[j] = Math.min(current[j - 1] + 1, previous[j] + 1, substitution);
|
|
14531
|
+
}
|
|
14532
|
+
previous = current;
|
|
14533
|
+
}
|
|
14534
|
+
return previous[b.length];
|
|
14535
|
+
}
|
|
14536
|
+
function suggestActionId(id) {
|
|
14537
|
+
if (findCatalogAction(id)) return null;
|
|
14538
|
+
const candidate = id.trim().toLowerCase();
|
|
14539
|
+
if (!candidate) return null;
|
|
14540
|
+
const normalized = candidate.replace(/[^a-z0-9]/g, "");
|
|
14541
|
+
let best = null;
|
|
14542
|
+
for (const entry of ACTION_CATALOG) {
|
|
14543
|
+
const distance = Math.min(
|
|
14544
|
+
levenshtein(candidate, entry.id),
|
|
14545
|
+
levenshtein(normalized, entry.id.replace(/[^a-z0-9]/g, ""))
|
|
14546
|
+
);
|
|
14547
|
+
if (!best || distance < best.distance) {
|
|
14548
|
+
best = { id: entry.id, distance };
|
|
14549
|
+
}
|
|
14550
|
+
}
|
|
14551
|
+
return best && best.distance <= 2 ? best.id : null;
|
|
14552
|
+
}
|
|
14553
|
+
|
|
14460
14554
|
// src/components/BindingBuilder/components/ActionsPanel.tsx
|
|
14461
14555
|
var import_jsx_runtime41 = require("react/jsx-runtime");
|
|
14556
|
+
var VIEWER_ACTIONS = ACTION_CATALOG.filter(
|
|
14557
|
+
(entry) => entry.handling === "viewer"
|
|
14558
|
+
);
|
|
14559
|
+
var HOST_ACTIONS = ACTION_CATALOG.filter((entry) => entry.handling === "host");
|
|
14462
14560
|
function ActionsPanel({
|
|
14463
14561
|
selectedItem,
|
|
14464
14562
|
selectedBinding,
|
|
@@ -14466,85 +14564,183 @@ function ActionsPanel({
|
|
|
14466
14564
|
onToggle,
|
|
14467
14565
|
updateBinding
|
|
14468
14566
|
}) {
|
|
14567
|
+
const [customRows, setCustomRows] = (0, import_react37.useState)(/* @__PURE__ */ new Set());
|
|
14568
|
+
(0, import_react37.useEffect)(() => {
|
|
14569
|
+
setCustomRows(/* @__PURE__ */ new Set());
|
|
14570
|
+
}, [selectedItem]);
|
|
14571
|
+
const actions = selectedBinding.actions ?? [];
|
|
14572
|
+
const updateAction = (index, patch) => updateBinding(selectedItem, (binding) => ({
|
|
14573
|
+
...binding,
|
|
14574
|
+
actions: (binding.actions ?? []).map(
|
|
14575
|
+
(entry, entryIndex) => entryIndex === index ? patch(entry) : entry
|
|
14576
|
+
)
|
|
14577
|
+
}));
|
|
14578
|
+
const removeAction = (index) => {
|
|
14579
|
+
updateBinding(selectedItem, (binding) => ({
|
|
14580
|
+
...binding,
|
|
14581
|
+
actions: (binding.actions ?? []).filter(
|
|
14582
|
+
(_, entryIndex) => entryIndex !== index
|
|
14583
|
+
)
|
|
14584
|
+
}));
|
|
14585
|
+
setCustomRows((previous) => {
|
|
14586
|
+
const next = /* @__PURE__ */ new Set();
|
|
14587
|
+
previous.forEach((row) => {
|
|
14588
|
+
if (row < index) next.add(row);
|
|
14589
|
+
else if (row > index) next.add(row - 1);
|
|
14590
|
+
});
|
|
14591
|
+
return next;
|
|
14592
|
+
});
|
|
14593
|
+
};
|
|
14594
|
+
const setCustomRow = (index, custom) => setCustomRows((previous) => {
|
|
14595
|
+
const next = new Set(previous);
|
|
14596
|
+
if (custom) next.add(index);
|
|
14597
|
+
else next.delete(index);
|
|
14598
|
+
return next;
|
|
14599
|
+
});
|
|
14469
14600
|
return /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(BBSection, { title: "Actions", open, onToggle, children: [
|
|
14470
|
-
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "bb-actionList", children:
|
|
14471
|
-
|
|
14472
|
-
|
|
14473
|
-
|
|
14474
|
-
|
|
14475
|
-
|
|
14476
|
-
|
|
14477
|
-
|
|
14478
|
-
|
|
14479
|
-
|
|
14480
|
-
|
|
14481
|
-
|
|
14482
|
-
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "bb-
|
|
14483
|
-
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "bb-
|
|
14484
|
-
/* @__PURE__ */ (0, import_jsx_runtime41.
|
|
14485
|
-
|
|
14486
|
-
|
|
14487
|
-
|
|
14488
|
-
|
|
14489
|
-
value: action.id,
|
|
14490
|
-
onChange: (event) => updateBinding(selectedItem, (binding) => ({
|
|
14491
|
-
...binding,
|
|
14492
|
-
actions: (binding.actions ?? []).map(
|
|
14493
|
-
(entry, entryIndex) => entryIndex === index ? { ...entry, id: event.target.value } : entry
|
|
14494
|
-
)
|
|
14495
|
-
}))
|
|
14496
|
-
}
|
|
14497
|
-
)
|
|
14601
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "bb-actionList", children: actions.map((action, index) => {
|
|
14602
|
+
const catalogEntry = findCatalogAction(action.id);
|
|
14603
|
+
const isCustom = customRows.has(index) || !catalogEntry;
|
|
14604
|
+
const trimmedId = action.id.trim();
|
|
14605
|
+
const suggestion = catalogEntry ? null : suggestActionId(action.id);
|
|
14606
|
+
const isDuplicate = trimmedId.length > 0 && actions.some(
|
|
14607
|
+
(other, otherIndex) => otherIndex !== index && other.id.trim() === trimmedId
|
|
14608
|
+
);
|
|
14609
|
+
return (
|
|
14610
|
+
// Keyed by position, not action.id: the id is an editable field, so
|
|
14611
|
+
// keying on it would remount the card on every keystroke and drop
|
|
14612
|
+
// input focus.
|
|
14613
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "bb-actionCard", children: [
|
|
14614
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "bb-actionCardHeader", children: [
|
|
14615
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "bb-actionCardTitle", children: [
|
|
14616
|
+
"Action ",
|
|
14617
|
+
index + 1
|
|
14618
|
+
] }),
|
|
14619
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "bb-actionCardMeta", children: action.type })
|
|
14498
14620
|
] }),
|
|
14499
|
-
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "bb-
|
|
14500
|
-
/* @__PURE__ */ (0, import_jsx_runtime41.
|
|
14501
|
-
|
|
14502
|
-
|
|
14503
|
-
|
|
14504
|
-
|
|
14505
|
-
|
|
14506
|
-
|
|
14507
|
-
|
|
14508
|
-
|
|
14509
|
-
(
|
|
14510
|
-
|
|
14511
|
-
|
|
14512
|
-
|
|
14513
|
-
|
|
14514
|
-
|
|
14515
|
-
|
|
14516
|
-
|
|
14517
|
-
|
|
14518
|
-
|
|
14519
|
-
|
|
14520
|
-
|
|
14521
|
-
|
|
14522
|
-
|
|
14523
|
-
|
|
14524
|
-
|
|
14621
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "bb-actionGrid", children: [
|
|
14622
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "bb-field", children: [
|
|
14623
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("label", { className: "bb-label", children: "Action ID" }),
|
|
14624
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
|
|
14625
|
+
"select",
|
|
14626
|
+
{
|
|
14627
|
+
className: "bb-select",
|
|
14628
|
+
value: isCustom ? CUSTOM_ACTION_ID : action.id,
|
|
14629
|
+
onChange: (event) => {
|
|
14630
|
+
const value = event.target.value;
|
|
14631
|
+
if (value === CUSTOM_ACTION_ID) {
|
|
14632
|
+
setCustomRow(index, true);
|
|
14633
|
+
return;
|
|
14634
|
+
}
|
|
14635
|
+
setCustomRow(index, false);
|
|
14636
|
+
const picked = findCatalogAction(value);
|
|
14637
|
+
updateAction(index, (current) => ({
|
|
14638
|
+
...current,
|
|
14639
|
+
id: value,
|
|
14640
|
+
// Keep wording the author typed themselves.
|
|
14641
|
+
label: picked && isAutoFilledLabel(current.label) ? picked.label : current.label
|
|
14642
|
+
}));
|
|
14643
|
+
},
|
|
14644
|
+
children: [
|
|
14645
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("optgroup", { label: "Handled by the viewer", children: VIEWER_ACTIONS.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("option", { value: entry.id, children: entry.id }, entry.id)) }),
|
|
14646
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("optgroup", { label: "Handled by your app", children: HOST_ACTIONS.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("option", { value: entry.id, children: entry.id }, entry.id)) }),
|
|
14647
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("option", { value: CUSTOM_ACTION_ID, children: "Custom\u2026" })
|
|
14648
|
+
]
|
|
14649
|
+
}
|
|
14525
14650
|
)
|
|
14526
|
-
})
|
|
14527
|
-
|
|
14528
|
-
|
|
14529
|
-
|
|
14530
|
-
|
|
14531
|
-
|
|
14651
|
+
] }),
|
|
14652
|
+
isCustom ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "bb-field", children: [
|
|
14653
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("label", { className: "bb-label", children: "Custom action ID" }),
|
|
14654
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
14655
|
+
"input",
|
|
14656
|
+
{
|
|
14657
|
+
className: "bb-input",
|
|
14658
|
+
value: action.id,
|
|
14659
|
+
placeholder: "open-datasheet",
|
|
14660
|
+
onChange: (event) => {
|
|
14661
|
+
setCustomRow(index, true);
|
|
14662
|
+
updateAction(index, (entry) => ({
|
|
14663
|
+
...entry,
|
|
14664
|
+
id: event.target.value
|
|
14665
|
+
}));
|
|
14666
|
+
}
|
|
14667
|
+
}
|
|
14668
|
+
)
|
|
14669
|
+
] }) : null,
|
|
14670
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("div", { className: "bb-field", children: [
|
|
14671
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("label", { className: "bb-label", children: "Label" }),
|
|
14672
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
14673
|
+
"input",
|
|
14674
|
+
{
|
|
14675
|
+
className: "bb-input",
|
|
14676
|
+
value: action.label,
|
|
14677
|
+
onChange: (event) => updateAction(index, (entry) => ({
|
|
14678
|
+
...entry,
|
|
14679
|
+
label: event.target.value
|
|
14680
|
+
}))
|
|
14681
|
+
}
|
|
14682
|
+
)
|
|
14683
|
+
] }),
|
|
14684
|
+
catalogEntry ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "bb-note", children: catalogEntry.description }) : trimmedId.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "bb-note", style: { color: "#fca5a5" }, children: "An action ID is required \u2014 the button renders but can never be identified in onAction." }) : /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { className: "bb-note", style: { color: "#fcd34d" }, children: [
|
|
14685
|
+
"Unrecognised ID \u2014 the viewer has no built-in behaviour for it, so your app must handle it in onAction.",
|
|
14686
|
+
suggestion ? /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_jsx_runtime41.Fragment, { children: [
|
|
14687
|
+
" Did you mean ",
|
|
14688
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
14689
|
+
"button",
|
|
14690
|
+
{
|
|
14691
|
+
type: "button",
|
|
14692
|
+
className: "bb-linkButton",
|
|
14693
|
+
onClick: () => {
|
|
14694
|
+
setCustomRow(index, false);
|
|
14695
|
+
const picked = findCatalogAction(suggestion);
|
|
14696
|
+
updateAction(index, (current) => ({
|
|
14697
|
+
...current,
|
|
14698
|
+
id: suggestion,
|
|
14699
|
+
label: picked && isAutoFilledLabel(current.label) ? picked.label : current.label
|
|
14700
|
+
}));
|
|
14701
|
+
},
|
|
14702
|
+
children: suggestion
|
|
14703
|
+
}
|
|
14704
|
+
),
|
|
14705
|
+
"?"
|
|
14706
|
+
] }) : null
|
|
14707
|
+
] }),
|
|
14708
|
+
isDuplicate ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("span", { className: "bb-note", style: { color: "#fcd34d" }, children: "Another action on this binding uses the same ID \u2014 they will render as two identical buttons." }) : null
|
|
14709
|
+
] }),
|
|
14710
|
+
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { className: "bb-actionFooter", children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
14711
|
+
"button",
|
|
14712
|
+
{
|
|
14713
|
+
type: "button",
|
|
14714
|
+
className: "bb-button bb-button--ghost bb-actionRemove",
|
|
14715
|
+
onClick: () => removeAction(index),
|
|
14716
|
+
children: "Remove Action"
|
|
14717
|
+
}
|
|
14718
|
+
) })
|
|
14719
|
+
] }, index)
|
|
14720
|
+
);
|
|
14721
|
+
}) }),
|
|
14532
14722
|
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("div", { style: { marginTop: 10 }, children: /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
|
|
14533
14723
|
"button",
|
|
14534
14724
|
{
|
|
14535
14725
|
type: "button",
|
|
14536
14726
|
className: "bb-button",
|
|
14537
|
-
onClick: () =>
|
|
14538
|
-
|
|
14539
|
-
|
|
14540
|
-
|
|
14541
|
-
|
|
14542
|
-
|
|
14543
|
-
|
|
14544
|
-
|
|
14545
|
-
|
|
14546
|
-
|
|
14547
|
-
|
|
14727
|
+
onClick: () => {
|
|
14728
|
+
const unused = VIEWER_ACTIONS.find(
|
|
14729
|
+
(entry) => !actions.some((action) => action.id === entry.id)
|
|
14730
|
+
);
|
|
14731
|
+
updateBinding(selectedItem, (binding) => ({
|
|
14732
|
+
...binding,
|
|
14733
|
+
actions: [
|
|
14734
|
+
...binding.actions ?? [],
|
|
14735
|
+
{
|
|
14736
|
+
id: unused?.id ?? "",
|
|
14737
|
+
label: unused?.label ?? "New Action",
|
|
14738
|
+
type: "command"
|
|
14739
|
+
}
|
|
14740
|
+
]
|
|
14741
|
+
}));
|
|
14742
|
+
if (!unused) setCustomRow(actions.length, true);
|
|
14743
|
+
},
|
|
14548
14744
|
children: "Add Action"
|
|
14549
14745
|
}
|
|
14550
14746
|
) })
|
|
@@ -14658,7 +14854,7 @@ function CameraPositionPanel({
|
|
|
14658
14854
|
}
|
|
14659
14855
|
|
|
14660
14856
|
// src/components/BindingBuilder/components/SceneNodesPanel.tsx
|
|
14661
|
-
var
|
|
14857
|
+
var import_react38 = require("react");
|
|
14662
14858
|
|
|
14663
14859
|
// src/components/BindingBuilder/utils/bindingFactory.ts
|
|
14664
14860
|
function slugify2(value) {
|
|
@@ -14747,9 +14943,9 @@ function SceneNodesPanel({
|
|
|
14747
14943
|
isOpen = true,
|
|
14748
14944
|
onToggle
|
|
14749
14945
|
}) {
|
|
14750
|
-
const [search, setSearch] = (0,
|
|
14751
|
-
const nodeKeys = (0,
|
|
14752
|
-
const filteredNodeKeys = (0,
|
|
14946
|
+
const [search, setSearch] = (0, import_react38.useState)("");
|
|
14947
|
+
const nodeKeys = (0, import_react38.useMemo)(() => Object.keys(bindings), [bindings]);
|
|
14948
|
+
const filteredNodeKeys = (0, import_react38.useMemo)(() => {
|
|
14753
14949
|
const query = search.trim().toLowerCase();
|
|
14754
14950
|
if (!query) return nodeKeys;
|
|
14755
14951
|
return nodeKeys.filter((nodeKey) => {
|
|
@@ -14892,7 +15088,7 @@ function mergeAnimationConfig(sceneConfig, animationDetails) {
|
|
|
14892
15088
|
}
|
|
14893
15089
|
|
|
14894
15090
|
// src/components/BindingBuilder/hooks/useUndoableState.ts
|
|
14895
|
-
var
|
|
15091
|
+
var import_react39 = require("react");
|
|
14896
15092
|
var MAX_HISTORY = 100;
|
|
14897
15093
|
function historyReducer(state, action) {
|
|
14898
15094
|
switch (action.type) {
|
|
@@ -14931,13 +15127,13 @@ function historyReducer(state, action) {
|
|
|
14931
15127
|
}
|
|
14932
15128
|
}
|
|
14933
15129
|
function useUndoableState(initial, coalesceWindowMs = 600) {
|
|
14934
|
-
const [state, dispatch] = (0,
|
|
15130
|
+
const [state, dispatch] = (0, import_react39.useReducer)(historyReducer, {
|
|
14935
15131
|
past: [],
|
|
14936
15132
|
present: initial,
|
|
14937
15133
|
future: []
|
|
14938
15134
|
});
|
|
14939
|
-
const lastSetAt = (0,
|
|
14940
|
-
const setValue = (0,
|
|
15135
|
+
const lastSetAt = (0, import_react39.useRef)(0);
|
|
15136
|
+
const setValue = (0, import_react39.useCallback)(
|
|
14941
15137
|
(updater) => {
|
|
14942
15138
|
const now = Date.now();
|
|
14943
15139
|
const coalesce = now - lastSetAt.current < coalesceWindowMs;
|
|
@@ -14946,15 +15142,15 @@ function useUndoableState(initial, coalesceWindowMs = 600) {
|
|
|
14946
15142
|
},
|
|
14947
15143
|
[coalesceWindowMs]
|
|
14948
15144
|
);
|
|
14949
|
-
const undo = (0,
|
|
15145
|
+
const undo = (0, import_react39.useCallback)(() => {
|
|
14950
15146
|
lastSetAt.current = 0;
|
|
14951
15147
|
dispatch({ type: "undo" });
|
|
14952
15148
|
}, []);
|
|
14953
|
-
const redo = (0,
|
|
15149
|
+
const redo = (0, import_react39.useCallback)(() => {
|
|
14954
15150
|
lastSetAt.current = 0;
|
|
14955
15151
|
dispatch({ type: "redo" });
|
|
14956
15152
|
}, []);
|
|
14957
|
-
const reset = (0,
|
|
15153
|
+
const reset = (0, import_react39.useCallback)((value) => {
|
|
14958
15154
|
lastSetAt.current = 0;
|
|
14959
15155
|
dispatch({ type: "reset", value });
|
|
14960
15156
|
}, []);
|
|
@@ -15200,39 +15396,39 @@ function mergeSceneConfigSection(current, incoming) {
|
|
|
15200
15396
|
return next;
|
|
15201
15397
|
}
|
|
15202
15398
|
function BindingBuilder({ licenseKey }) {
|
|
15203
|
-
const modelInputRef = (0,
|
|
15204
|
-
const bindingsInputRef = (0,
|
|
15205
|
-
const sceneConfigInputRef = (0,
|
|
15206
|
-
const [modelUrl, setModelUrl] = (0,
|
|
15207
|
-
const [modelName, setModelName] = (0,
|
|
15208
|
-
const [selectedItem, setSelectedItem] = (0,
|
|
15399
|
+
const modelInputRef = (0, import_react40.useRef)(null);
|
|
15400
|
+
const bindingsInputRef = (0, import_react40.useRef)(null);
|
|
15401
|
+
const sceneConfigInputRef = (0, import_react40.useRef)(null);
|
|
15402
|
+
const [modelUrl, setModelUrl] = (0, import_react40.useState)(null);
|
|
15403
|
+
const [modelName, setModelName] = (0, import_react40.useState)("");
|
|
15404
|
+
const [selectedItem, setSelectedItem] = (0, import_react40.useState)(null);
|
|
15209
15405
|
const bindingsHistory = useUndoableState({});
|
|
15210
15406
|
const bindings = bindingsHistory.value;
|
|
15211
15407
|
const setBindings = bindingsHistory.setValue;
|
|
15212
|
-
const [loading, setLoading] = (0,
|
|
15213
|
-
const [isExporting, setIsExporting] = (0,
|
|
15214
|
-
const [statusMessage, setStatusMessage] = (0,
|
|
15408
|
+
const [loading, setLoading] = (0, import_react40.useState)(false);
|
|
15409
|
+
const [isExporting, setIsExporting] = (0, import_react40.useState)(false);
|
|
15410
|
+
const [statusMessage, setStatusMessage] = (0, import_react40.useState)(
|
|
15215
15411
|
"Load the demo model or upload a GLB/GLTF model to generate bindings."
|
|
15216
15412
|
);
|
|
15217
|
-
const [statusTone, setStatusTone] = (0,
|
|
15218
|
-
const [statusBannerVisible, setStatusBannerVisible] = (0,
|
|
15219
|
-
const [metricsDraft, setMetricsDraft] = (0,
|
|
15220
|
-
const [metadataDraft, setMetadataDraft] = (0,
|
|
15221
|
-
const [metricsError, setMetricsError] = (0,
|
|
15222
|
-
const [metadataError, setMetadataError] = (0,
|
|
15223
|
-
const [isPreviewDragActive, setIsPreviewDragActive] = (0,
|
|
15224
|
-
const [isEditorCollapsed, setIsEditorCollapsed] = (0,
|
|
15225
|
-
const [isNodesCollapsed, setIsNodesCollapsed] = (0,
|
|
15226
|
-
const [openObjectSections, setOpenObjectSections] = (0,
|
|
15413
|
+
const [statusTone, setStatusTone] = (0, import_react40.useState)("default");
|
|
15414
|
+
const [statusBannerVisible, setStatusBannerVisible] = (0, import_react40.useState)(false);
|
|
15415
|
+
const [metricsDraft, setMetricsDraft] = (0, import_react40.useState)("{}");
|
|
15416
|
+
const [metadataDraft, setMetadataDraft] = (0, import_react40.useState)("{}");
|
|
15417
|
+
const [metricsError, setMetricsError] = (0, import_react40.useState)(null);
|
|
15418
|
+
const [metadataError, setMetadataError] = (0, import_react40.useState)(null);
|
|
15419
|
+
const [isPreviewDragActive, setIsPreviewDragActive] = (0, import_react40.useState)(false);
|
|
15420
|
+
const [isEditorCollapsed, setIsEditorCollapsed] = (0, import_react40.useState)(false);
|
|
15421
|
+
const [isNodesCollapsed, setIsNodesCollapsed] = (0, import_react40.useState)(false);
|
|
15422
|
+
const [openObjectSections, setOpenObjectSections] = (0, import_react40.useState)({
|
|
15227
15423
|
identity: true,
|
|
15228
15424
|
basics: true,
|
|
15229
|
-
material:
|
|
15425
|
+
material: false,
|
|
15230
15426
|
actions: false,
|
|
15231
15427
|
camera: false,
|
|
15232
15428
|
metrics: false,
|
|
15233
15429
|
metadata: false
|
|
15234
15430
|
});
|
|
15235
|
-
const [openMaterialPanels, setOpenMaterialPanels] = (0,
|
|
15431
|
+
const [openMaterialPanels, setOpenMaterialPanels] = (0, import_react40.useState)({
|
|
15236
15432
|
pbr: true,
|
|
15237
15433
|
roughness: false,
|
|
15238
15434
|
emission: false,
|
|
@@ -15248,27 +15444,27 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15248
15444
|
});
|
|
15249
15445
|
const toggleObjectSection = (key) => setOpenObjectSections((prev) => ({ ...prev, [key]: !prev[key] }));
|
|
15250
15446
|
const toggleMaterialPanel = (key) => setOpenMaterialPanels((prev) => ({ ...prev, [key]: !prev[key] }));
|
|
15251
|
-
const [activeEditorTab, setActiveEditorTab] = (0,
|
|
15447
|
+
const [activeEditorTab, setActiveEditorTab] = (0, import_react40.useState)(
|
|
15252
15448
|
"object"
|
|
15253
15449
|
);
|
|
15254
|
-
const [activeSubTab, setActiveSubTab] = (0,
|
|
15255
|
-
const [pbrWorkflow, setPbrWorkflow] = (0,
|
|
15256
|
-
const [materialPanelEnabled, setMaterialPanelEnabled] = (0,
|
|
15257
|
-
const materialPanelItemRef = (0,
|
|
15258
|
-
const [zoomOnSelected, setZoomOnSelected] = (0,
|
|
15259
|
-
const [highlightOnHover, setHighlightOnHover] = (0,
|
|
15260
|
-
const [resetDialogOpen, setResetDialogOpen] = (0,
|
|
15261
|
-
const [resetTarget, setResetTarget] = (0,
|
|
15450
|
+
const [activeSubTab, setActiveSubTab] = (0, import_react40.useState)("General");
|
|
15451
|
+
const [pbrWorkflow, setPbrWorkflow] = (0, import_react40.useState)("metalness");
|
|
15452
|
+
const [materialPanelEnabled, setMaterialPanelEnabled] = (0, import_react40.useState)(createMaterialPanelEnabledState());
|
|
15453
|
+
const materialPanelItemRef = (0, import_react40.useRef)(null);
|
|
15454
|
+
const [zoomOnSelected, setZoomOnSelected] = (0, import_react40.useState)(false);
|
|
15455
|
+
const [highlightOnHover, setHighlightOnHover] = (0, import_react40.useState)(true);
|
|
15456
|
+
const [resetDialogOpen, setResetDialogOpen] = (0, import_react40.useState)(false);
|
|
15457
|
+
const [resetTarget, setResetTarget] = (0, import_react40.useState)(
|
|
15262
15458
|
null
|
|
15263
15459
|
);
|
|
15264
15460
|
const sceneConfigHistory = useUndoableState(DEFAULT_SCENE_CONFIG);
|
|
15265
15461
|
const sceneConfig = sceneConfigHistory.value;
|
|
15266
15462
|
const setSceneConfig = sceneConfigHistory.setValue;
|
|
15267
|
-
const [activeAnnotation, setActiveAnnotation] = (0,
|
|
15268
|
-
const [panelHoveredBinding, setPanelHoveredBinding] = (0,
|
|
15463
|
+
const [activeAnnotation, setActiveAnnotation] = (0, import_react40.useState)(null);
|
|
15464
|
+
const [panelHoveredBinding, setPanelHoveredBinding] = (0, import_react40.useState)(null);
|
|
15269
15465
|
const license = useLicense(licenseKey);
|
|
15270
15466
|
const tier = normalizeTier(license.tier);
|
|
15271
|
-
const bindingBuilderContextValue = (0,
|
|
15467
|
+
const bindingBuilderContextValue = (0, import_react40.useMemo)(
|
|
15272
15468
|
() => ({
|
|
15273
15469
|
isInsideEditor: true,
|
|
15274
15470
|
activeTab: activeEditorTab,
|
|
@@ -15277,11 +15473,11 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15277
15473
|
}),
|
|
15278
15474
|
[activeEditorTab, activeSubTab, tier]
|
|
15279
15475
|
);
|
|
15280
|
-
const uploadedModelRef = (0,
|
|
15281
|
-
const previewDragDepthRef = (0,
|
|
15282
|
-
const loadRequestRef = (0,
|
|
15283
|
-
const pendingLoadRef = (0,
|
|
15284
|
-
const loadedSceneRef = (0,
|
|
15476
|
+
const uploadedModelRef = (0, import_react40.useRef)(null);
|
|
15477
|
+
const previewDragDepthRef = (0, import_react40.useRef)(0);
|
|
15478
|
+
const loadRequestRef = (0, import_react40.useRef)(0);
|
|
15479
|
+
const pendingLoadRef = (0, import_react40.useRef)(null);
|
|
15480
|
+
const loadedSceneRef = (0, import_react40.useRef)(null);
|
|
15285
15481
|
const {
|
|
15286
15482
|
cameraState,
|
|
15287
15483
|
resetView,
|
|
@@ -15291,7 +15487,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15291
15487
|
handleCameraChange,
|
|
15292
15488
|
handleViewerReady
|
|
15293
15489
|
} = useViewerCamera();
|
|
15294
|
-
const handlePreviewWaypoint = (0,
|
|
15490
|
+
const handlePreviewWaypoint = (0, import_react40.useCallback)(
|
|
15295
15491
|
(waypoint) => {
|
|
15296
15492
|
setCameraState(
|
|
15297
15493
|
{
|
|
@@ -15315,7 +15511,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15315
15511
|
position: formatVector3(cameraState.position),
|
|
15316
15512
|
target: formatVector3(cameraState.target)
|
|
15317
15513
|
} : null;
|
|
15318
|
-
const handleAnnotationsChange = (0,
|
|
15514
|
+
const handleAnnotationsChange = (0, import_react40.useCallback)(
|
|
15319
15515
|
(newAnnotations) => {
|
|
15320
15516
|
setSceneConfig((prev) => {
|
|
15321
15517
|
const currentIds = prev.annotations.map((a) => a.id).join(",");
|
|
@@ -15331,7 +15527,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15331
15527
|
},
|
|
15332
15528
|
[]
|
|
15333
15529
|
);
|
|
15334
|
-
const handleActiveAnnotationChange = (0,
|
|
15530
|
+
const handleActiveAnnotationChange = (0, import_react40.useCallback)(
|
|
15335
15531
|
(annotation) => {
|
|
15336
15532
|
setActiveAnnotation(
|
|
15337
15533
|
annotation ? {
|
|
@@ -15341,7 +15537,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15341
15537
|
},
|
|
15342
15538
|
[]
|
|
15343
15539
|
);
|
|
15344
|
-
(0,
|
|
15540
|
+
(0, import_react40.useEffect)(() => {
|
|
15345
15541
|
if (!selectedBinding) {
|
|
15346
15542
|
setMetricsDraft("{}");
|
|
15347
15543
|
setMetadataDraft("{}");
|
|
@@ -15354,7 +15550,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15354
15550
|
setMetricsError(null);
|
|
15355
15551
|
setMetadataError(null);
|
|
15356
15552
|
}, [selectedBinding]);
|
|
15357
|
-
(0,
|
|
15553
|
+
(0, import_react40.useEffect)(() => {
|
|
15358
15554
|
const derived = createMaterialPanelEnabledState(selectedMaterial);
|
|
15359
15555
|
if (materialPanelItemRef.current !== selectedItem) {
|
|
15360
15556
|
materialPanelItemRef.current = selectedItem;
|
|
@@ -15373,12 +15569,12 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15373
15569
|
return changed ? next : prev;
|
|
15374
15570
|
});
|
|
15375
15571
|
}, [selectedItem, selectedMaterial]);
|
|
15376
|
-
(0,
|
|
15572
|
+
(0, import_react40.useEffect)(() => {
|
|
15377
15573
|
return () => {
|
|
15378
15574
|
uploadedModelRef.current?.cleanup();
|
|
15379
15575
|
};
|
|
15380
15576
|
}, []);
|
|
15381
|
-
(0,
|
|
15577
|
+
(0, import_react40.useEffect)(() => {
|
|
15382
15578
|
const handleKeyDown = (event) => {
|
|
15383
15579
|
if (event.key.toLowerCase() !== "z" || !(event.metaKey || event.ctrlKey)) {
|
|
15384
15580
|
return;
|
|
@@ -15399,7 +15595,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15399
15595
|
window.addEventListener("keydown", handleKeyDown);
|
|
15400
15596
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
15401
15597
|
}, [activeEditorTab, bindingsHistory, sceneConfigHistory]);
|
|
15402
|
-
(0,
|
|
15598
|
+
(0, import_react40.useEffect)(() => {
|
|
15403
15599
|
if (loading || statusTone === "default" || statusTone === "success") return;
|
|
15404
15600
|
setStatusBannerVisible(true);
|
|
15405
15601
|
const timeout = window.setTimeout(
|
|
@@ -15408,8 +15604,8 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15408
15604
|
);
|
|
15409
15605
|
return () => window.clearTimeout(timeout);
|
|
15410
15606
|
}, [statusMessage, statusTone, loading]);
|
|
15411
|
-
const nodeKeys = (0,
|
|
15412
|
-
const idError = (0,
|
|
15607
|
+
const nodeKeys = (0, import_react40.useMemo)(() => Object.keys(bindings), [bindings]);
|
|
15608
|
+
const idError = (0, import_react40.useMemo)(() => {
|
|
15413
15609
|
if (!selectedItem || !selectedBinding) return null;
|
|
15414
15610
|
const trimmed = selectedBinding.id.trim();
|
|
15415
15611
|
if (!trimmed) return "ID is required.";
|
|
@@ -15452,7 +15648,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15452
15648
|
});
|
|
15453
15649
|
}
|
|
15454
15650
|
};
|
|
15455
|
-
const applyBindingsFromScene = (0,
|
|
15651
|
+
const applyBindingsFromScene = (0, import_react40.useCallback)(
|
|
15456
15652
|
(scene, nextModelName) => {
|
|
15457
15653
|
const nodeNames = collectNodeNames(scene);
|
|
15458
15654
|
bindingsHistory.reset(buildBindingsFromNodes(nodeNames));
|
|
@@ -15491,7 +15687,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15491
15687
|
bindingsHistory.reset({});
|
|
15492
15688
|
setSelectedItem(null);
|
|
15493
15689
|
};
|
|
15494
|
-
const handleViewerModelLoaded = (0,
|
|
15690
|
+
const handleViewerModelLoaded = (0, import_react40.useCallback)(
|
|
15495
15691
|
(scene) => {
|
|
15496
15692
|
const pending = pendingLoadRef.current;
|
|
15497
15693
|
if (!pending || pending.requestId !== loadRequestRef.current) return;
|
|
@@ -15501,7 +15697,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15501
15697
|
},
|
|
15502
15698
|
[applyBindingsFromScene]
|
|
15503
15699
|
);
|
|
15504
|
-
const handleViewerLoadError = (0,
|
|
15700
|
+
const handleViewerLoadError = (0, import_react40.useCallback)((error) => {
|
|
15505
15701
|
const pending = pendingLoadRef.current;
|
|
15506
15702
|
if (!pending || pending.requestId !== loadRequestRef.current) return;
|
|
15507
15703
|
pendingLoadRef.current = null;
|
|
@@ -15512,7 +15708,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15512
15708
|
setStatusTone("error");
|
|
15513
15709
|
setLoading(false);
|
|
15514
15710
|
}, []);
|
|
15515
|
-
const handleAnimationsReady = (0,
|
|
15711
|
+
const handleAnimationsReady = (0, import_react40.useCallback)(
|
|
15516
15712
|
(controls) => {
|
|
15517
15713
|
viewerAnimations.handleAnimationsReady(controls);
|
|
15518
15714
|
const details = controls.clipDetails ?? controls.clips.map((sourceName) => ({ sourceName, duration: 0 }));
|
|
@@ -15813,7 +16009,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15813
16009
|
}));
|
|
15814
16010
|
setError(null);
|
|
15815
16011
|
};
|
|
15816
|
-
const handleViewerSelect = (0,
|
|
16012
|
+
const handleViewerSelect = (0, import_react40.useCallback)(
|
|
15817
16013
|
(binding) => {
|
|
15818
16014
|
if (!binding) {
|
|
15819
16015
|
setSelectedItem(null);
|
|
@@ -15826,7 +16022,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15826
16022
|
},
|
|
15827
16023
|
[bindings]
|
|
15828
16024
|
);
|
|
15829
|
-
const handleSceneNodeSelect = (0,
|
|
16025
|
+
const handleSceneNodeSelect = (0, import_react40.useCallback)(
|
|
15830
16026
|
async (nodeKey) => {
|
|
15831
16027
|
setSelectedItem(nodeKey);
|
|
15832
16028
|
setPanelHoveredBinding(null);
|
|
@@ -15834,7 +16030,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15834
16030
|
},
|
|
15835
16031
|
[focusObject]
|
|
15836
16032
|
);
|
|
15837
|
-
const saveCameraPositionForSelectedObject = (0,
|
|
16033
|
+
const saveCameraPositionForSelectedObject = (0, import_react40.useCallback)(() => {
|
|
15838
16034
|
if (!selectedItem || !cameraState) {
|
|
15839
16035
|
return;
|
|
15840
16036
|
}
|
|
@@ -15849,7 +16045,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15849
16045
|
cameraState: currentCameraState
|
|
15850
16046
|
}));
|
|
15851
16047
|
}, [selectedItem, cameraState]);
|
|
15852
|
-
const goToSavedCameraPosition = (0,
|
|
16048
|
+
const goToSavedCameraPosition = (0, import_react40.useCallback)(() => {
|
|
15853
16049
|
if (!savedCameraPosition?.position || !savedCameraPosition?.target) return;
|
|
15854
16050
|
setCameraState(
|
|
15855
16051
|
{
|
|
@@ -15861,28 +16057,28 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15861
16057
|
true
|
|
15862
16058
|
);
|
|
15863
16059
|
}, [savedCameraPosition, setCameraState, cameraState]);
|
|
15864
|
-
const clearSavedCameraPosition = (0,
|
|
16060
|
+
const clearSavedCameraPosition = (0, import_react40.useCallback)(() => {
|
|
15865
16061
|
if (!selectedItem) return;
|
|
15866
16062
|
updateBinding(selectedItem, (binding) => ({
|
|
15867
16063
|
...binding,
|
|
15868
16064
|
cameraState: void 0
|
|
15869
16065
|
}));
|
|
15870
16066
|
}, [selectedItem]);
|
|
15871
|
-
const handleResetView = (0,
|
|
16067
|
+
const handleResetView = (0, import_react40.useCallback)(async () => {
|
|
15872
16068
|
await resetView();
|
|
15873
16069
|
}, [resetView]);
|
|
15874
|
-
const handleFitScene = (0,
|
|
16070
|
+
const handleFitScene = (0, import_react40.useCallback)(async () => {
|
|
15875
16071
|
await fitScene();
|
|
15876
16072
|
}, [fitScene]);
|
|
15877
|
-
const openResetDialog = (0,
|
|
16073
|
+
const openResetDialog = (0, import_react40.useCallback)((target) => {
|
|
15878
16074
|
setResetTarget(target);
|
|
15879
16075
|
setResetDialogOpen(true);
|
|
15880
16076
|
}, []);
|
|
15881
|
-
const closeResetDialog = (0,
|
|
16077
|
+
const closeResetDialog = (0, import_react40.useCallback)(() => {
|
|
15882
16078
|
setResetDialogOpen(false);
|
|
15883
16079
|
setResetTarget(null);
|
|
15884
16080
|
}, []);
|
|
15885
|
-
const executeReset = (0,
|
|
16081
|
+
const executeReset = (0, import_react40.useCallback)(() => {
|
|
15886
16082
|
if (resetTarget === "bindings") {
|
|
15887
16083
|
if (!nodeKeys.length) {
|
|
15888
16084
|
closeResetDialog();
|
|
@@ -16440,7 +16636,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
16440
16636
|
}
|
|
16441
16637
|
|
|
16442
16638
|
// src/components/SimpleModelViewer/SimpleModelViewer.tsx
|
|
16443
|
-
var
|
|
16639
|
+
var import_react41 = require("react");
|
|
16444
16640
|
var import_drei6 = require("@react-three/drei");
|
|
16445
16641
|
var import_fiber12 = require("@react-three/fiber");
|
|
16446
16642
|
var import_fiber13 = require("@react-three/fiber");
|
|
@@ -16480,7 +16676,7 @@ function applyClippingPlanes(scene, controls) {
|
|
|
16480
16676
|
camera.updateProjectionMatrix();
|
|
16481
16677
|
controls.maxDistance = size * 10;
|
|
16482
16678
|
}
|
|
16483
|
-
var ModelErrorBoundary2 = class extends
|
|
16679
|
+
var ModelErrorBoundary2 = class extends import_react41.Component {
|
|
16484
16680
|
constructor() {
|
|
16485
16681
|
super(...arguments);
|
|
16486
16682
|
this.state = { hasError: false };
|
|
@@ -16512,12 +16708,12 @@ function InnerModel({
|
|
|
16512
16708
|
meshopt: true
|
|
16513
16709
|
});
|
|
16514
16710
|
const invalidate = (0, import_fiber12.useThree)((state) => state.invalidate);
|
|
16515
|
-
const [hoveredKey, setHoveredKey] = (0,
|
|
16516
|
-
const scene = (0,
|
|
16711
|
+
const [hoveredKey, setHoveredKey] = (0, import_react41.useState)(null);
|
|
16712
|
+
const scene = (0, import_react41.useMemo)(
|
|
16517
16713
|
() => import_three_stdlib4.SkeletonUtils.clone(rawScene),
|
|
16518
16714
|
[rawScene]
|
|
16519
16715
|
);
|
|
16520
|
-
const meshMap = (0,
|
|
16716
|
+
const meshMap = (0, import_react41.useMemo)(() => {
|
|
16521
16717
|
const map = {};
|
|
16522
16718
|
scene.traverse((obj) => {
|
|
16523
16719
|
if (!isMeshNode2(obj)) return;
|
|
@@ -16526,7 +16722,7 @@ function InnerModel({
|
|
|
16526
16722
|
});
|
|
16527
16723
|
return map;
|
|
16528
16724
|
}, [scene]);
|
|
16529
|
-
const perKeyMaterials = (0,
|
|
16725
|
+
const perKeyMaterials = (0, import_react41.useMemo)(() => {
|
|
16530
16726
|
const map = {};
|
|
16531
16727
|
for (const [key, { ref }] of Object.entries(meshMap)) {
|
|
16532
16728
|
const original = Array.isArray(ref.material) ? ref.material[0] : ref.material;
|
|
@@ -16542,14 +16738,14 @@ function InnerModel({
|
|
|
16542
16738
|
}
|
|
16543
16739
|
return map;
|
|
16544
16740
|
}, [meshMap]);
|
|
16545
|
-
(0,
|
|
16741
|
+
(0, import_react41.useEffect)(
|
|
16546
16742
|
() => () => {
|
|
16547
16743
|
Object.values(perKeyMaterials).forEach((m) => m.dispose());
|
|
16548
16744
|
},
|
|
16549
16745
|
[perKeyMaterials]
|
|
16550
16746
|
);
|
|
16551
|
-
const notifiedUrl = (0,
|
|
16552
|
-
(0,
|
|
16747
|
+
const notifiedUrl = (0, import_react41.useRef)(null);
|
|
16748
|
+
(0, import_react41.useEffect)(() => {
|
|
16553
16749
|
if (notifiedUrl.current === url) return;
|
|
16554
16750
|
notifiedUrl.current = url;
|
|
16555
16751
|
scene.updateWorldMatrix(true, true);
|
|
@@ -16557,7 +16753,7 @@ function InnerModel({
|
|
|
16557
16753
|
onMeshesReady(meshMap);
|
|
16558
16754
|
onModelLoaded(scene);
|
|
16559
16755
|
}, [url, scene, meshMap, onMeshesReady, onModelLoaded]);
|
|
16560
|
-
(0,
|
|
16756
|
+
(0, import_react41.useEffect)(() => {
|
|
16561
16757
|
const controller = new AbortController();
|
|
16562
16758
|
void buildSceneBVH(scene, controller.signal);
|
|
16563
16759
|
return () => {
|
|
@@ -16565,26 +16761,26 @@ function InnerModel({
|
|
|
16565
16761
|
disposeSceneBVH(scene);
|
|
16566
16762
|
};
|
|
16567
16763
|
}, [scene]);
|
|
16568
|
-
const pointerRaycastEnabled = (0,
|
|
16764
|
+
const pointerRaycastEnabled = (0, import_react41.useMemo)(
|
|
16569
16765
|
() => !exceedsBvhBudget(scene),
|
|
16570
16766
|
[scene]
|
|
16571
16767
|
);
|
|
16572
|
-
(0,
|
|
16768
|
+
(0, import_react41.useEffect)(() => {
|
|
16573
16769
|
for (const [key, { ref }] of Object.entries(meshMap)) {
|
|
16574
16770
|
nodeRefsOut.current[key] = ref;
|
|
16575
16771
|
}
|
|
16576
16772
|
}, [meshMap, nodeRefsOut]);
|
|
16577
|
-
(0,
|
|
16773
|
+
(0, import_react41.useEffect)(() => {
|
|
16578
16774
|
if (!highlightOnHover && hoveredKey !== null) {
|
|
16579
16775
|
setHoveredKey(null);
|
|
16580
16776
|
}
|
|
16581
16777
|
}, [highlightOnHover, hoveredKey]);
|
|
16582
|
-
(0,
|
|
16778
|
+
(0, import_react41.useEffect)(() => {
|
|
16583
16779
|
return () => {
|
|
16584
16780
|
document.body.style.cursor = "default";
|
|
16585
16781
|
};
|
|
16586
16782
|
}, []);
|
|
16587
|
-
(0,
|
|
16783
|
+
(0, import_react41.useEffect)(() => {
|
|
16588
16784
|
for (const [key, { ref }] of Object.entries(meshMap)) {
|
|
16589
16785
|
ref.visible = !hiddenKeys.has(key);
|
|
16590
16786
|
const mat = perKeyMaterials[key];
|
|
@@ -16654,11 +16850,11 @@ function CameraFitter({
|
|
|
16654
16850
|
refitOnResize,
|
|
16655
16851
|
onInitialFitComplete
|
|
16656
16852
|
}) {
|
|
16657
|
-
const fitted = (0,
|
|
16853
|
+
const fitted = (0, import_react41.useRef)(false);
|
|
16658
16854
|
const invalidate = (0, import_fiber12.useThree)((state) => state.invalidate);
|
|
16659
16855
|
const size = (0, import_fiber12.useThree)((state) => state.size);
|
|
16660
|
-
const resizeTimerRef = (0,
|
|
16661
|
-
(0,
|
|
16856
|
+
const resizeTimerRef = (0, import_react41.useRef)(null);
|
|
16857
|
+
(0, import_react41.useEffect)(() => {
|
|
16662
16858
|
fitted.current = false;
|
|
16663
16859
|
invalidate();
|
|
16664
16860
|
}, [invalidate, scene]);
|
|
@@ -16678,7 +16874,7 @@ function CameraFitter({
|
|
|
16678
16874
|
}).catch(() => {
|
|
16679
16875
|
}).finally(onInitialFitComplete);
|
|
16680
16876
|
});
|
|
16681
|
-
(0,
|
|
16877
|
+
(0, import_react41.useEffect)(() => {
|
|
16682
16878
|
if (!refitOnResize || !fitted.current || !scene || !controlsRef.current) {
|
|
16683
16879
|
return;
|
|
16684
16880
|
}
|
|
@@ -16706,7 +16902,7 @@ function CameraFitter({
|
|
|
16706
16902
|
function ExposureController({ exposure }) {
|
|
16707
16903
|
const gl = (0, import_fiber12.useThree)((state) => state.gl);
|
|
16708
16904
|
const invalidate = (0, import_fiber12.useThree)((state) => state.invalidate);
|
|
16709
|
-
(0,
|
|
16905
|
+
(0, import_react41.useEffect)(() => {
|
|
16710
16906
|
gl.toneMappingExposure = exposure;
|
|
16711
16907
|
invalidate();
|
|
16712
16908
|
}, [gl, exposure, invalidate]);
|
|
@@ -16814,7 +17010,7 @@ function EnvironmentSection({
|
|
|
16814
17010
|
title,
|
|
16815
17011
|
children
|
|
16816
17012
|
}) {
|
|
16817
|
-
const [collapsed, setCollapsed] = (0,
|
|
17013
|
+
const [collapsed, setCollapsed] = (0, import_react41.useState)(false);
|
|
16818
17014
|
return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "env-section", children: [
|
|
16819
17015
|
/* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
|
|
16820
17016
|
"button",
|
|
@@ -16979,8 +17175,8 @@ function SceneObjectsPanel2({
|
|
|
16979
17175
|
onHover,
|
|
16980
17176
|
onToggleVisibility
|
|
16981
17177
|
}) {
|
|
16982
|
-
const [search, setSearch] = (0,
|
|
16983
|
-
const [rowHovered, setRowHovered] = (0,
|
|
17178
|
+
const [search, setSearch] = (0, import_react41.useState)("");
|
|
17179
|
+
const [rowHovered, setRowHovered] = (0, import_react41.useState)(null);
|
|
16984
17180
|
const entries = Object.values(meshKeys).filter(
|
|
16985
17181
|
(m) => m.label.toLowerCase().includes(search.toLowerCase())
|
|
16986
17182
|
);
|
|
@@ -17104,35 +17300,35 @@ function SimpleModelViewer({
|
|
|
17104
17300
|
onModelLoaded,
|
|
17105
17301
|
onLoadError
|
|
17106
17302
|
}) {
|
|
17107
|
-
const cameraControlsRef = (0,
|
|
17108
|
-
const nodeRefsOut = (0,
|
|
17109
|
-
const fileInputRef = (0,
|
|
17110
|
-
const uploadedModelRef = (0,
|
|
17111
|
-
const dragDepthRef = (0,
|
|
17112
|
-
const [loadedScene, setLoadedScene] = (0,
|
|
17113
|
-
const [largeModelTriangles, setLargeModelTriangles] = (0,
|
|
17303
|
+
const cameraControlsRef = (0, import_react41.useRef)(null);
|
|
17304
|
+
const nodeRefsOut = (0, import_react41.useRef)({});
|
|
17305
|
+
const fileInputRef = (0, import_react41.useRef)(null);
|
|
17306
|
+
const uploadedModelRef = (0, import_react41.useRef)(null);
|
|
17307
|
+
const dragDepthRef = (0, import_react41.useRef)(0);
|
|
17308
|
+
const [loadedScene, setLoadedScene] = (0, import_react41.useState)(null);
|
|
17309
|
+
const [largeModelTriangles, setLargeModelTriangles] = (0, import_react41.useState)(
|
|
17114
17310
|
null
|
|
17115
17311
|
);
|
|
17116
|
-
const [largeModelNoticeDismissed, setLargeModelNoticeDismissed] = (0,
|
|
17117
|
-
const [meshKeys, setMeshKeys] = (0,
|
|
17118
|
-
const [hiddenKeys, setHiddenKeys] = (0,
|
|
17119
|
-
const [selectedKey, setSelectedKey] = (0,
|
|
17120
|
-
const [panelHoveredKey, setPanelHoveredKey] = (0,
|
|
17121
|
-
const [uploadedModelUrl, setUploadedModelUrl] = (0,
|
|
17122
|
-
const [uploadedModelName, setUploadedModelName] = (0,
|
|
17312
|
+
const [largeModelNoticeDismissed, setLargeModelNoticeDismissed] = (0, import_react41.useState)(false);
|
|
17313
|
+
const [meshKeys, setMeshKeys] = (0, import_react41.useState)({});
|
|
17314
|
+
const [hiddenKeys, setHiddenKeys] = (0, import_react41.useState)(/* @__PURE__ */ new Set());
|
|
17315
|
+
const [selectedKey, setSelectedKey] = (0, import_react41.useState)(null);
|
|
17316
|
+
const [panelHoveredKey, setPanelHoveredKey] = (0, import_react41.useState)(null);
|
|
17317
|
+
const [uploadedModelUrl, setUploadedModelUrl] = (0, import_react41.useState)(null);
|
|
17318
|
+
const [uploadedModelName, setUploadedModelName] = (0, import_react41.useState)(
|
|
17123
17319
|
null
|
|
17124
17320
|
);
|
|
17125
|
-
const [modelLoadFailed, setModelLoadFailed] = (0,
|
|
17126
|
-
const [isResolvingUpload, setIsResolvingUpload] = (0,
|
|
17127
|
-
const [initialFitComplete, setInitialFitComplete] = (0,
|
|
17128
|
-
const [isDragActive, setIsDragActive] = (0,
|
|
17129
|
-
const [isPanelOpen, setIsPanelOpen] = (0,
|
|
17321
|
+
const [modelLoadFailed, setModelLoadFailed] = (0, import_react41.useState)(false);
|
|
17322
|
+
const [isResolvingUpload, setIsResolvingUpload] = (0, import_react41.useState)(false);
|
|
17323
|
+
const [initialFitComplete, setInitialFitComplete] = (0, import_react41.useState)(false);
|
|
17324
|
+
const [isDragActive, setIsDragActive] = (0, import_react41.useState)(false);
|
|
17325
|
+
const [isPanelOpen, setIsPanelOpen] = (0, import_react41.useState)(
|
|
17130
17326
|
() => typeof window === "undefined" ? true : window.innerWidth > 900
|
|
17131
17327
|
);
|
|
17132
|
-
const [isEnvPanelOpen, setIsEnvPanelOpen] = (0,
|
|
17328
|
+
const [isEnvPanelOpen, setIsEnvPanelOpen] = (0, import_react41.useState)(
|
|
17133
17329
|
() => typeof window === "undefined" ? true : window.innerWidth > 900
|
|
17134
17330
|
);
|
|
17135
|
-
const [envSettings, setEnvSettings] = (0,
|
|
17331
|
+
const [envSettings, setEnvSettings] = (0, import_react41.useState)({
|
|
17136
17332
|
backgroundEnabled,
|
|
17137
17333
|
backgroundColor,
|
|
17138
17334
|
autoRotate,
|
|
@@ -17144,7 +17340,7 @@ function SimpleModelViewer({
|
|
|
17144
17340
|
directionalColor
|
|
17145
17341
|
});
|
|
17146
17342
|
const activeModelUrl = enableModelUpload ? uploadedModelUrl : modelUrl;
|
|
17147
|
-
const resetModelState = (0,
|
|
17343
|
+
const resetModelState = (0, import_react41.useCallback)(() => {
|
|
17148
17344
|
setLoadedScene(null);
|
|
17149
17345
|
setLargeModelTriangles(null);
|
|
17150
17346
|
setLargeModelNoticeDismissed(false);
|
|
@@ -17155,11 +17351,11 @@ function SimpleModelViewer({
|
|
|
17155
17351
|
setModelLoadFailed(false);
|
|
17156
17352
|
setInitialFitComplete(false);
|
|
17157
17353
|
}, []);
|
|
17158
|
-
const revokeUploadedModelUrl = (0,
|
|
17354
|
+
const revokeUploadedModelUrl = (0, import_react41.useCallback)(() => {
|
|
17159
17355
|
uploadedModelRef.current?.cleanup();
|
|
17160
17356
|
uploadedModelRef.current = null;
|
|
17161
17357
|
}, []);
|
|
17162
|
-
const clearUploadedModel = (0,
|
|
17358
|
+
const clearUploadedModel = (0, import_react41.useCallback)(() => {
|
|
17163
17359
|
revokeUploadedModelUrl();
|
|
17164
17360
|
setUploadedModelUrl(null);
|
|
17165
17361
|
setUploadedModelName(null);
|
|
@@ -17188,7 +17384,7 @@ function SimpleModelViewer({
|
|
|
17188
17384
|
resetModelState,
|
|
17189
17385
|
revokeUploadedModelUrl
|
|
17190
17386
|
]);
|
|
17191
|
-
const handleUploadedModel = (0,
|
|
17387
|
+
const handleUploadedModel = (0, import_react41.useCallback)(
|
|
17192
17388
|
async (file) => {
|
|
17193
17389
|
setIsResolvingUpload(true);
|
|
17194
17390
|
try {
|
|
@@ -17207,7 +17403,7 @@ function SimpleModelViewer({
|
|
|
17207
17403
|
},
|
|
17208
17404
|
[onLoadError, resetModelState, revokeUploadedModelUrl]
|
|
17209
17405
|
);
|
|
17210
|
-
const handleFileSelection = (0,
|
|
17406
|
+
const handleFileSelection = (0, import_react41.useCallback)(
|
|
17211
17407
|
(files) => {
|
|
17212
17408
|
const file = files?.[0];
|
|
17213
17409
|
if (!file) return;
|
|
@@ -17215,29 +17411,29 @@ function SimpleModelViewer({
|
|
|
17215
17411
|
},
|
|
17216
17412
|
[handleUploadedModel]
|
|
17217
17413
|
);
|
|
17218
|
-
(0,
|
|
17414
|
+
(0, import_react41.useEffect)(() => {
|
|
17219
17415
|
return () => {
|
|
17220
17416
|
revokeUploadedModelUrl();
|
|
17221
17417
|
};
|
|
17222
17418
|
}, [revokeUploadedModelUrl]);
|
|
17223
|
-
const handleEnvSettingChange = (0,
|
|
17419
|
+
const handleEnvSettingChange = (0, import_react41.useCallback)(
|
|
17224
17420
|
(key, value) => {
|
|
17225
17421
|
setEnvSettings((prev) => ({ ...prev, [key]: value }));
|
|
17226
17422
|
},
|
|
17227
17423
|
[]
|
|
17228
17424
|
);
|
|
17229
|
-
(0,
|
|
17425
|
+
(0, import_react41.useEffect)(() => {
|
|
17230
17426
|
setEnvSettings((prev) => ({ ...prev, backgroundColor }));
|
|
17231
17427
|
}, [backgroundColor]);
|
|
17232
|
-
(0,
|
|
17428
|
+
(0, import_react41.useEffect)(() => {
|
|
17233
17429
|
resetModelState();
|
|
17234
17430
|
}, [activeModelUrl, resetModelState]);
|
|
17235
|
-
const handleMeshesReady = (0,
|
|
17431
|
+
const handleMeshesReady = (0, import_react41.useCallback)((meshes) => {
|
|
17236
17432
|
setMeshKeys(meshes);
|
|
17237
17433
|
setHiddenKeys(/* @__PURE__ */ new Set());
|
|
17238
17434
|
setSelectedKey(null);
|
|
17239
17435
|
}, []);
|
|
17240
|
-
const handleModelLoaded = (0,
|
|
17436
|
+
const handleModelLoaded = (0, import_react41.useCallback)(
|
|
17241
17437
|
(scene) => {
|
|
17242
17438
|
setLoadedScene(scene);
|
|
17243
17439
|
setModelLoadFailed(false);
|
|
@@ -17248,14 +17444,14 @@ function SimpleModelViewer({
|
|
|
17248
17444
|
},
|
|
17249
17445
|
[onModelLoaded]
|
|
17250
17446
|
);
|
|
17251
|
-
const handleLoadError = (0,
|
|
17447
|
+
const handleLoadError = (0, import_react41.useCallback)(
|
|
17252
17448
|
(error) => {
|
|
17253
17449
|
setModelLoadFailed(true);
|
|
17254
17450
|
onLoadError?.(error);
|
|
17255
17451
|
},
|
|
17256
17452
|
[onLoadError]
|
|
17257
17453
|
);
|
|
17258
|
-
const focusMesh = (0,
|
|
17454
|
+
const focusMesh = (0, import_react41.useCallback)(
|
|
17259
17455
|
(key) => {
|
|
17260
17456
|
setSelectedKey(key);
|
|
17261
17457
|
if (!zoomOnSelected) return;
|
|
@@ -17271,14 +17467,14 @@ function SimpleModelViewer({
|
|
|
17271
17467
|
},
|
|
17272
17468
|
[zoomOnSelected]
|
|
17273
17469
|
);
|
|
17274
|
-
const handleToggleVisibility = (0,
|
|
17470
|
+
const handleToggleVisibility = (0, import_react41.useCallback)((key) => {
|
|
17275
17471
|
setHiddenKeys((prev) => {
|
|
17276
17472
|
const next = new Set(prev);
|
|
17277
17473
|
next.has(key) ? next.delete(key) : next.add(key);
|
|
17278
17474
|
return next;
|
|
17279
17475
|
});
|
|
17280
17476
|
}, []);
|
|
17281
|
-
const handleDragEnter = (0,
|
|
17477
|
+
const handleDragEnter = (0, import_react41.useCallback)(
|
|
17282
17478
|
(event) => {
|
|
17283
17479
|
if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
|
|
17284
17480
|
return;
|
|
@@ -17290,7 +17486,7 @@ function SimpleModelViewer({
|
|
|
17290
17486
|
},
|
|
17291
17487
|
[enableModelUpload]
|
|
17292
17488
|
);
|
|
17293
|
-
const handleDragOver = (0,
|
|
17489
|
+
const handleDragOver = (0, import_react41.useCallback)(
|
|
17294
17490
|
(event) => {
|
|
17295
17491
|
if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
|
|
17296
17492
|
return;
|
|
@@ -17302,7 +17498,7 @@ function SimpleModelViewer({
|
|
|
17302
17498
|
},
|
|
17303
17499
|
[enableModelUpload]
|
|
17304
17500
|
);
|
|
17305
|
-
const handleDragLeave = (0,
|
|
17501
|
+
const handleDragLeave = (0, import_react41.useCallback)(
|
|
17306
17502
|
(event) => {
|
|
17307
17503
|
if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
|
|
17308
17504
|
return;
|
|
@@ -17316,7 +17512,7 @@ function SimpleModelViewer({
|
|
|
17316
17512
|
},
|
|
17317
17513
|
[enableModelUpload]
|
|
17318
17514
|
);
|
|
17319
|
-
const handleDrop = (0,
|
|
17515
|
+
const handleDrop = (0, import_react41.useCallback)(
|
|
17320
17516
|
(event) => {
|
|
17321
17517
|
if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
|
|
17322
17518
|
return;
|
|
@@ -17409,7 +17605,7 @@ function SimpleModelViewer({
|
|
|
17409
17605
|
raycaster: { firstHitOnly: true },
|
|
17410
17606
|
style: { background: envSettings.backgroundColor },
|
|
17411
17607
|
camera: { position: [0, 0, 5], fov: 45, near: 0.1, far: 1e3 },
|
|
17412
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
|
|
17608
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(import_react41.Suspense, { fallback: null, children: [
|
|
17413
17609
|
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
17414
17610
|
"ambientLight",
|
|
17415
17611
|
{
|
|
@@ -17427,7 +17623,7 @@ function SimpleModelViewer({
|
|
|
17427
17623
|
}
|
|
17428
17624
|
),
|
|
17429
17625
|
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("directionalLight", { position: [-5, 5, 5], intensity: 0.5 }),
|
|
17430
|
-
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
17626
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_react41.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
17431
17627
|
import_drei6.Environment,
|
|
17432
17628
|
{
|
|
17433
17629
|
preset: envSettings.envPreset,
|
|
@@ -17446,7 +17642,7 @@ function SimpleModelViewer({
|
|
|
17446
17642
|
ModelErrorBoundary2,
|
|
17447
17643
|
{
|
|
17448
17644
|
onLoadError: handleLoadError,
|
|
17449
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
17645
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_react41.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
17450
17646
|
InnerModel,
|
|
17451
17647
|
{
|
|
17452
17648
|
url: activeModelUrl,
|
|
@@ -17545,18 +17741,18 @@ function SimpleModelViewer({
|
|
|
17545
17741
|
}
|
|
17546
17742
|
|
|
17547
17743
|
// src/hooks/useViewerSelection.ts
|
|
17548
|
-
var
|
|
17744
|
+
var import_react42 = require("react");
|
|
17549
17745
|
function useViewerSelection(initialSelectedObjectBinding = null) {
|
|
17550
|
-
const [selectedObjectBinding, setSelectedObjectBinding] = (0,
|
|
17746
|
+
const [selectedObjectBinding, setSelectedObjectBinding] = (0, import_react42.useState)(
|
|
17551
17747
|
initialSelectedObjectBinding
|
|
17552
17748
|
);
|
|
17553
|
-
const handleObjectSelect = (0,
|
|
17749
|
+
const handleObjectSelect = (0, import_react42.useCallback)(
|
|
17554
17750
|
(binding) => {
|
|
17555
17751
|
setSelectedObjectBinding(binding);
|
|
17556
17752
|
},
|
|
17557
17753
|
[setSelectedObjectBinding]
|
|
17558
17754
|
);
|
|
17559
|
-
const clearSelection = (0,
|
|
17755
|
+
const clearSelection = (0, import_react42.useCallback)(() => {
|
|
17560
17756
|
setSelectedObjectBinding(null);
|
|
17561
17757
|
}, []);
|
|
17562
17758
|
return {
|
|
@@ -17568,9 +17764,9 @@ function useViewerSelection(initialSelectedObjectBinding = null) {
|
|
|
17568
17764
|
}
|
|
17569
17765
|
|
|
17570
17766
|
// src/hooks/useObjectBinding.ts
|
|
17571
|
-
var
|
|
17767
|
+
var import_react43 = require("react");
|
|
17572
17768
|
function useObjectBinding(objectBindings, objectId) {
|
|
17573
|
-
return (0,
|
|
17769
|
+
return (0, import_react43.useMemo)(() => {
|
|
17574
17770
|
if (!objectId) {
|
|
17575
17771
|
return null;
|
|
17576
17772
|
}
|
|
@@ -17585,26 +17781,23 @@ function useObjectBinding(objectBindings, objectId) {
|
|
|
17585
17781
|
}
|
|
17586
17782
|
|
|
17587
17783
|
// src/hooks/useObjectBindingIds.ts
|
|
17588
|
-
var
|
|
17784
|
+
var import_react44 = require("react");
|
|
17589
17785
|
function useObjectBindingIds(objectBindings) {
|
|
17590
|
-
return (0,
|
|
17786
|
+
return (0, import_react44.useMemo)(
|
|
17591
17787
|
() => Object.values(objectBindings).map((binding) => binding.modelObjectId),
|
|
17592
17788
|
[objectBindings]
|
|
17593
17789
|
);
|
|
17594
17790
|
}
|
|
17595
17791
|
|
|
17596
17792
|
// src/hooks/useObjectBindings.ts
|
|
17597
|
-
var
|
|
17793
|
+
var import_react45 = require("react");
|
|
17598
17794
|
|
|
17599
|
-
// src/utils/
|
|
17795
|
+
// src/utils/deepMerge.ts
|
|
17600
17796
|
function isPlainObject2(value) {
|
|
17601
17797
|
if (typeof value !== "object" || value === null) return false;
|
|
17602
17798
|
const prototype = Object.getPrototypeOf(value);
|
|
17603
17799
|
return prototype === Object.prototype || prototype === null;
|
|
17604
17800
|
}
|
|
17605
|
-
function isBindingEditArray(value) {
|
|
17606
|
-
return Array.isArray(value) && value.length > 0 && typeof value[0] !== "string";
|
|
17607
|
-
}
|
|
17608
17801
|
function deepMerge(base, patch) {
|
|
17609
17802
|
if (!isPlainObject2(patch)) return patch;
|
|
17610
17803
|
const target = isPlainObject2(base) ? { ...base } : {};
|
|
@@ -17614,6 +17807,11 @@ function deepMerge(base, patch) {
|
|
|
17614
17807
|
}
|
|
17615
17808
|
return target;
|
|
17616
17809
|
}
|
|
17810
|
+
|
|
17811
|
+
// src/utils/patchBindings.ts
|
|
17812
|
+
function isBindingEditArray(value) {
|
|
17813
|
+
return Array.isArray(value) && value.length > 0 && typeof value[0] !== "string";
|
|
17814
|
+
}
|
|
17617
17815
|
function patchBindings(bindings, objectIdOrEdits, patch) {
|
|
17618
17816
|
const edits = isBindingEditArray(objectIdOrEdits) ? objectIdOrEdits : [{ ids: objectIdOrEdits, patch: patch ?? {} }];
|
|
17619
17817
|
let next = bindings;
|
|
@@ -17642,8 +17840,8 @@ function getHiddenObjects(objectBindings) {
|
|
|
17642
17840
|
);
|
|
17643
17841
|
}
|
|
17644
17842
|
function useObjectBindings(initialBindings) {
|
|
17645
|
-
const [objectBindings, setObjectBindings] = (0,
|
|
17646
|
-
const
|
|
17843
|
+
const [objectBindings, setObjectBindings] = (0, import_react45.useState)(initialBindings);
|
|
17844
|
+
const updateObjectBindings = (0, import_react45.useCallback)(
|
|
17647
17845
|
(objectIdOrEdits, patchValue) => {
|
|
17648
17846
|
setObjectBindings(
|
|
17649
17847
|
(current) => patchValue === void 0 ? patchBindings(current, objectIdOrEdits) : patchBindings(
|
|
@@ -17655,34 +17853,34 @@ function useObjectBindings(initialBindings) {
|
|
|
17655
17853
|
},
|
|
17656
17854
|
[]
|
|
17657
17855
|
);
|
|
17658
|
-
const setMaterial = (0,
|
|
17659
|
-
(objectId, value) =>
|
|
17660
|
-
[
|
|
17856
|
+
const setMaterial = (0, import_react45.useCallback)(
|
|
17857
|
+
(objectId, value) => updateObjectBindings(objectId, material(value)),
|
|
17858
|
+
[updateObjectBindings]
|
|
17661
17859
|
);
|
|
17662
|
-
const setBaseColor = (0,
|
|
17860
|
+
const setBaseColor = (0, import_react45.useCallback)(
|
|
17663
17861
|
(objectId, color) => setMaterial(objectId, { baseColor: color }),
|
|
17664
17862
|
[setMaterial]
|
|
17665
17863
|
);
|
|
17666
|
-
const resetMaterial = (0,
|
|
17667
|
-
(objectId) =>
|
|
17668
|
-
[
|
|
17864
|
+
const resetMaterial = (0, import_react45.useCallback)(
|
|
17865
|
+
(objectId) => updateObjectBindings(objectId, { style: { material: void 0 } }),
|
|
17866
|
+
[updateObjectBindings]
|
|
17669
17867
|
);
|
|
17670
|
-
const getMaterial = (0,
|
|
17868
|
+
const getMaterial = (0, import_react45.useCallback)(
|
|
17671
17869
|
(objectId) => {
|
|
17672
17870
|
const bindingKey = resolveBindingKey(objectBindings, objectId);
|
|
17673
17871
|
return bindingKey ? objectBindings[bindingKey]?.style?.material ?? null : null;
|
|
17674
17872
|
},
|
|
17675
17873
|
[objectBindings]
|
|
17676
17874
|
);
|
|
17677
|
-
const hideObject = (0,
|
|
17678
|
-
(objectId) =>
|
|
17679
|
-
[
|
|
17875
|
+
const hideObject = (0, import_react45.useCallback)(
|
|
17876
|
+
(objectId) => updateObjectBindings(objectId, { visible: false }),
|
|
17877
|
+
[updateObjectBindings]
|
|
17680
17878
|
);
|
|
17681
|
-
const showObject = (0,
|
|
17682
|
-
(objectId) =>
|
|
17683
|
-
[
|
|
17879
|
+
const showObject = (0, import_react45.useCallback)(
|
|
17880
|
+
(objectId) => updateObjectBindings(objectId, { visible: true }),
|
|
17881
|
+
[updateObjectBindings]
|
|
17684
17882
|
);
|
|
17685
|
-
const toggleObjectVisibility = (0,
|
|
17883
|
+
const toggleObjectVisibility = (0, import_react45.useCallback)(
|
|
17686
17884
|
(objectId) => {
|
|
17687
17885
|
setObjectBindings((current) => {
|
|
17688
17886
|
const objectIds = Array.isArray(objectId) ? objectId : [objectId];
|
|
@@ -17701,22 +17899,22 @@ function useObjectBindings(initialBindings) {
|
|
|
17701
17899
|
},
|
|
17702
17900
|
[setObjectBindings]
|
|
17703
17901
|
);
|
|
17704
|
-
const clearHiddenObjects = (0,
|
|
17902
|
+
const clearHiddenObjects = (0, import_react45.useCallback)(() => {
|
|
17705
17903
|
setObjectBindings(
|
|
17706
17904
|
(current) => patchBindings(current, Object.keys(current), { visible: true })
|
|
17707
17905
|
);
|
|
17708
17906
|
}, [setObjectBindings]);
|
|
17709
|
-
const hiddenObjects = (0,
|
|
17907
|
+
const hiddenObjects = (0, import_react45.useMemo)(
|
|
17710
17908
|
() => getHiddenObjects(objectBindings),
|
|
17711
17909
|
[objectBindings]
|
|
17712
17910
|
);
|
|
17713
|
-
const hiddenObjectIds = (0,
|
|
17911
|
+
const hiddenObjectIds = (0, import_react45.useMemo)(
|
|
17714
17912
|
() => Object.keys(hiddenObjects).map(
|
|
17715
17913
|
(bindingKey) => objectBindings[bindingKey]?.id ?? bindingKey
|
|
17716
17914
|
),
|
|
17717
17915
|
[hiddenObjects, objectBindings]
|
|
17718
17916
|
);
|
|
17719
|
-
const isObjectHidden = (0,
|
|
17917
|
+
const isObjectHidden = (0, import_react45.useCallback)(
|
|
17720
17918
|
(objectId) => {
|
|
17721
17919
|
const bindingKey = resolveBindingKey(objectBindings, objectId);
|
|
17722
17920
|
return bindingKey ? objectBindings[bindingKey]?.visible === false : false;
|
|
@@ -17726,7 +17924,7 @@ function useObjectBindings(initialBindings) {
|
|
|
17726
17924
|
return {
|
|
17727
17925
|
objectBindings,
|
|
17728
17926
|
setObjectBindings,
|
|
17729
|
-
|
|
17927
|
+
updateObjectBindings,
|
|
17730
17928
|
setMaterial,
|
|
17731
17929
|
setBaseColor,
|
|
17732
17930
|
resetMaterial,
|
|
@@ -17741,11 +17939,45 @@ function useObjectBindings(initialBindings) {
|
|
|
17741
17939
|
};
|
|
17742
17940
|
}
|
|
17743
17941
|
|
|
17942
|
+
// src/hooks/useSceneConfig.ts
|
|
17943
|
+
var import_react46 = require("react");
|
|
17944
|
+
|
|
17945
|
+
// src/utils/patchSceneConfig.ts
|
|
17946
|
+
function patchSceneConfig(sceneConfig, patch) {
|
|
17947
|
+
return deepMerge(sceneConfig, patch);
|
|
17948
|
+
}
|
|
17949
|
+
|
|
17950
|
+
// src/hooks/useSceneConfig.ts
|
|
17951
|
+
function useSceneConfig(initialSceneConfig) {
|
|
17952
|
+
const initialSceneConfigRef = (0, import_react46.useRef)(null);
|
|
17953
|
+
const [sceneConfig, setSceneConfig] = (0, import_react46.useState)(() => {
|
|
17954
|
+
const initial = typeof initialSceneConfig === "function" ? initialSceneConfig() : initialSceneConfig;
|
|
17955
|
+
initialSceneConfigRef.current = initial;
|
|
17956
|
+
return initial;
|
|
17957
|
+
});
|
|
17958
|
+
const updateSceneConfig = (0, import_react46.useCallback)((update) => {
|
|
17959
|
+
setSceneConfig((current) => {
|
|
17960
|
+
const patch = typeof update === "function" ? update(current) : update;
|
|
17961
|
+
return patchSceneConfig(current, patch);
|
|
17962
|
+
});
|
|
17963
|
+
}, []);
|
|
17964
|
+
const resetSceneConfig = (0, import_react46.useCallback)(() => {
|
|
17965
|
+
const initial = initialSceneConfigRef.current;
|
|
17966
|
+
if (initial) setSceneConfig(initial);
|
|
17967
|
+
}, []);
|
|
17968
|
+
return {
|
|
17969
|
+
sceneConfig,
|
|
17970
|
+
setSceneConfig,
|
|
17971
|
+
updateSceneConfig,
|
|
17972
|
+
resetSceneConfig
|
|
17973
|
+
};
|
|
17974
|
+
}
|
|
17975
|
+
|
|
17744
17976
|
// src/hooks/useViewerHover.ts
|
|
17745
|
-
var
|
|
17977
|
+
var import_react47 = require("react");
|
|
17746
17978
|
function useViewerHover() {
|
|
17747
|
-
const [hoveredObjectBinding, setHoveredObjectBinding] = (0,
|
|
17748
|
-
const handleHoveredObject = (0,
|
|
17979
|
+
const [hoveredObjectBinding, setHoveredObjectBinding] = (0, import_react47.useState)(null);
|
|
17980
|
+
const handleHoveredObject = (0, import_react47.useCallback)(
|
|
17749
17981
|
(binding) => {
|
|
17750
17982
|
setHoveredObjectBinding(binding);
|
|
17751
17983
|
},
|
|
@@ -17758,18 +17990,18 @@ function useViewerHover() {
|
|
|
17758
17990
|
}
|
|
17759
17991
|
|
|
17760
17992
|
// src/hooks/useViewerModel.ts
|
|
17761
|
-
var
|
|
17993
|
+
var import_react48 = require("react");
|
|
17762
17994
|
var import_three22 = require("three");
|
|
17763
17995
|
function toTuple2(vector) {
|
|
17764
17996
|
return [vector.x, vector.y, vector.z];
|
|
17765
17997
|
}
|
|
17766
17998
|
function useViewerModel() {
|
|
17767
|
-
const [isLoading, setIsLoading] = (0,
|
|
17768
|
-
const [isReady, setIsReady] = (0,
|
|
17769
|
-
const [error, setError] = (0,
|
|
17770
|
-
const [bounds, setBounds] = (0,
|
|
17771
|
-
const [objectCount, setObjectCount] = (0,
|
|
17772
|
-
const handleModelLoaded = (0,
|
|
17999
|
+
const [isLoading, setIsLoading] = (0, import_react48.useState)(true);
|
|
18000
|
+
const [isReady, setIsReady] = (0, import_react48.useState)(false);
|
|
18001
|
+
const [error, setError] = (0, import_react48.useState)(null);
|
|
18002
|
+
const [bounds, setBounds] = (0, import_react48.useState)(null);
|
|
18003
|
+
const [objectCount, setObjectCount] = (0, import_react48.useState)(0);
|
|
18004
|
+
const handleModelLoaded = (0, import_react48.useCallback)((scene) => {
|
|
17773
18005
|
const box = new import_three22.Box3().setFromObject(scene);
|
|
17774
18006
|
const min = box.min.clone();
|
|
17775
18007
|
const max = box.max.clone();
|
|
@@ -17794,14 +18026,14 @@ function useViewerModel() {
|
|
|
17794
18026
|
setIsLoading(false);
|
|
17795
18027
|
setIsReady(true);
|
|
17796
18028
|
}, []);
|
|
17797
|
-
const handleLoadError = (0,
|
|
18029
|
+
const handleLoadError = (0, import_react48.useCallback)((nextError) => {
|
|
17798
18030
|
setError(nextError);
|
|
17799
18031
|
setBounds(null);
|
|
17800
18032
|
setObjectCount(0);
|
|
17801
18033
|
setIsLoading(false);
|
|
17802
18034
|
setIsReady(false);
|
|
17803
18035
|
}, []);
|
|
17804
|
-
const resetModelState = (0,
|
|
18036
|
+
const resetModelState = (0, import_react48.useCallback)(() => {
|
|
17805
18037
|
setIsLoading(true);
|
|
17806
18038
|
setIsReady(false);
|
|
17807
18039
|
setError(null);
|
|
@@ -17821,10 +18053,10 @@ function useViewerModel() {
|
|
|
17821
18053
|
}
|
|
17822
18054
|
|
|
17823
18055
|
// src/hooks/useViewerActions.ts
|
|
17824
|
-
var
|
|
18056
|
+
var import_react49 = require("react");
|
|
17825
18057
|
function useViewerActions(objectBindings, options = {}) {
|
|
17826
18058
|
const { onAction, onObjectBindingsChange } = options;
|
|
17827
|
-
const getActionsForObject = (0,
|
|
18059
|
+
const getActionsForObject = (0, import_react49.useCallback)(
|
|
17828
18060
|
(objectId) => {
|
|
17829
18061
|
const bindingKey = resolveBindingKey(objectBindings, objectId);
|
|
17830
18062
|
if (!bindingKey) {
|
|
@@ -17834,7 +18066,7 @@ function useViewerActions(objectBindings, options = {}) {
|
|
|
17834
18066
|
},
|
|
17835
18067
|
[objectBindings]
|
|
17836
18068
|
);
|
|
17837
|
-
const runAction = (0,
|
|
18069
|
+
const runAction = (0, import_react49.useCallback)(
|
|
17838
18070
|
(objectId, actionId) => {
|
|
17839
18071
|
const bindingKey = resolveBindingKey(objectBindings, objectId);
|
|
17840
18072
|
if (!bindingKey) {
|
|
@@ -17876,7 +18108,7 @@ function useViewerActions(objectBindings, options = {}) {
|
|
|
17876
18108
|
}
|
|
17877
18109
|
|
|
17878
18110
|
// src/hooks/useShareableViewerState.ts
|
|
17879
|
-
var
|
|
18111
|
+
var import_react50 = require("react");
|
|
17880
18112
|
function serializeState(cameraState, objectBindings, selectedObjectBinding) {
|
|
17881
18113
|
const hidden = Object.values(objectBindings).filter((binding) => binding.visible === false).map((binding) => binding.id);
|
|
17882
18114
|
return {
|
|
@@ -17896,8 +18128,8 @@ function useShareableViewerState({
|
|
|
17896
18128
|
debounceMs = 400,
|
|
17897
18129
|
autoSync = true
|
|
17898
18130
|
}) {
|
|
17899
|
-
const lastWrittenRawRef = (0,
|
|
17900
|
-
const buildHash = (0,
|
|
18131
|
+
const lastWrittenRawRef = (0, import_react50.useRef)(null);
|
|
18132
|
+
const buildHash = (0, import_react50.useCallback)(() => {
|
|
17901
18133
|
const state = serializeState(
|
|
17902
18134
|
cameraState,
|
|
17903
18135
|
objectBindings,
|
|
@@ -17905,12 +18137,12 @@ function useShareableViewerState({
|
|
|
17905
18137
|
);
|
|
17906
18138
|
return `${paramKey}=${encodeURIComponent(JSON.stringify(state))}`;
|
|
17907
18139
|
}, [cameraState, objectBindings, selectedObjectBinding, paramKey]);
|
|
17908
|
-
const getShareUrl = (0,
|
|
18140
|
+
const getShareUrl = (0, import_react50.useCallback)(() => {
|
|
17909
18141
|
const url = new URL(window.location.href);
|
|
17910
18142
|
url.hash = buildHash();
|
|
17911
18143
|
return url.toString();
|
|
17912
18144
|
}, [buildHash]);
|
|
17913
|
-
const syncUrl = (0,
|
|
18145
|
+
const syncUrl = (0, import_react50.useCallback)(() => {
|
|
17914
18146
|
const url = new URL(window.location.href);
|
|
17915
18147
|
url.hash = buildHash();
|
|
17916
18148
|
window.history.replaceState(null, "", url.toString());
|
|
@@ -17918,7 +18150,7 @@ function useShareableViewerState({
|
|
|
17918
18150
|
paramKey
|
|
17919
18151
|
);
|
|
17920
18152
|
}, [buildHash, paramKey]);
|
|
17921
|
-
const latestStateRef = (0,
|
|
18153
|
+
const latestStateRef = (0, import_react50.useRef)({
|
|
17922
18154
|
cameraState,
|
|
17923
18155
|
objectBindings,
|
|
17924
18156
|
selectedObjectBinding
|
|
@@ -17928,9 +18160,9 @@ function useShareableViewerState({
|
|
|
17928
18160
|
objectBindings,
|
|
17929
18161
|
selectedObjectBinding
|
|
17930
18162
|
};
|
|
17931
|
-
const [restoreAttempted, setRestoreAttempted] = (0,
|
|
17932
|
-
const lastSyncedHashRef = (0,
|
|
17933
|
-
(0,
|
|
18163
|
+
const [restoreAttempted, setRestoreAttempted] = (0, import_react50.useState)(false);
|
|
18164
|
+
const lastSyncedHashRef = (0, import_react50.useRef)(null);
|
|
18165
|
+
(0, import_react50.useEffect)(() => {
|
|
17934
18166
|
if (!autoSync || !restoreAttempted) return;
|
|
17935
18167
|
const tick = () => {
|
|
17936
18168
|
const current = latestStateRef.current;
|
|
@@ -17952,9 +18184,9 @@ function useShareableViewerState({
|
|
|
17952
18184
|
const id = window.setInterval(tick, debounceMs);
|
|
17953
18185
|
return () => window.clearInterval(id);
|
|
17954
18186
|
}, [autoSync, restoreAttempted, debounceMs, paramKey]);
|
|
17955
|
-
const lastRestoredRawRef = (0,
|
|
17956
|
-
const restoringRawRef = (0,
|
|
17957
|
-
const restoreFromUrl = (0,
|
|
18187
|
+
const lastRestoredRawRef = (0, import_react50.useRef)(null);
|
|
18188
|
+
const restoringRawRef = (0, import_react50.useRef)(null);
|
|
18189
|
+
const restoreFromUrl = (0, import_react50.useCallback)(async () => {
|
|
17958
18190
|
setRestoreAttempted(true);
|
|
17959
18191
|
const rawHash = window.location.hash.startsWith("#") ? window.location.hash.slice(1) : window.location.hash;
|
|
17960
18192
|
const raw = new URLSearchParams(rawHash).get(paramKey);
|
|
@@ -18007,7 +18239,7 @@ function useShareableViewerState({
|
|
|
18007
18239
|
setCameraState,
|
|
18008
18240
|
setSelectedObjectBinding
|
|
18009
18241
|
]);
|
|
18010
|
-
(0,
|
|
18242
|
+
(0, import_react50.useEffect)(() => {
|
|
18011
18243
|
const handleHashChange = () => {
|
|
18012
18244
|
void restoreFromUrl();
|
|
18013
18245
|
};
|
|
@@ -18027,9 +18259,11 @@ function useShareableViewerState({
|
|
|
18027
18259
|
SimpleModelViewer,
|
|
18028
18260
|
material,
|
|
18029
18261
|
patchBindings,
|
|
18262
|
+
patchSceneConfig,
|
|
18030
18263
|
useObjectBinding,
|
|
18031
18264
|
useObjectBindingIds,
|
|
18032
18265
|
useObjectBindings,
|
|
18266
|
+
useSceneConfig,
|
|
18033
18267
|
useShareableViewerState,
|
|
18034
18268
|
useViewerActions,
|
|
18035
18269
|
useViewerAnimations,
|