@liveroom-tech/react-immersive 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -0
- package/dist/bvhWorkerCode-JRKQF4QH.mjs +7 -0
- package/dist/index.js +373 -156
- package/dist/index.mjs +314 -107
- package/package.json +4 -2
package/dist/index.mjs
CHANGED
|
@@ -10,11 +10,166 @@ import {
|
|
|
10
10
|
Suspense as Suspense2
|
|
11
11
|
} from "react";
|
|
12
12
|
import { createPortal } from "react-dom";
|
|
13
|
+
|
|
14
|
+
// src/utils/bvh.ts
|
|
15
|
+
import {
|
|
16
|
+
Box3,
|
|
17
|
+
BufferAttribute,
|
|
18
|
+
Mesh
|
|
19
|
+
} from "three";
|
|
20
|
+
import {
|
|
21
|
+
acceleratedRaycast,
|
|
22
|
+
computeBoundsTree,
|
|
23
|
+
disposeBoundsTree,
|
|
24
|
+
MeshBVH
|
|
25
|
+
} from "three-mesh-bvh";
|
|
26
|
+
var raycastPatched = false;
|
|
27
|
+
function enableAcceleratedRaycast() {
|
|
28
|
+
if (raycastPatched) return;
|
|
29
|
+
Mesh.prototype.raycast = acceleratedRaycast;
|
|
30
|
+
raycastPatched = true;
|
|
31
|
+
}
|
|
32
|
+
function collectGeometries(root) {
|
|
33
|
+
const seen = /* @__PURE__ */ new Set();
|
|
34
|
+
root.traverse((node) => {
|
|
35
|
+
const geometry = node.geometry;
|
|
36
|
+
if (!geometry || !geometry.attributes?.position) return;
|
|
37
|
+
if (geometry.boundsTree) return;
|
|
38
|
+
seen.add(geometry);
|
|
39
|
+
});
|
|
40
|
+
return [...seen];
|
|
41
|
+
}
|
|
42
|
+
function countTriangles(geometries) {
|
|
43
|
+
let total = 0;
|
|
44
|
+
for (const geometry of geometries) {
|
|
45
|
+
const count = geometry.index ? geometry.index.count : geometry.attributes.position.count;
|
|
46
|
+
total += count / 3;
|
|
47
|
+
}
|
|
48
|
+
return total;
|
|
49
|
+
}
|
|
50
|
+
var MAX_BVH_TRIANGLES = 12e6;
|
|
51
|
+
function exceedsBvhBudget(root) {
|
|
52
|
+
return countSceneTriangles(root) > MAX_BVH_TRIANGLES;
|
|
53
|
+
}
|
|
54
|
+
function countSceneTriangles(root) {
|
|
55
|
+
const seen = /* @__PURE__ */ new Set();
|
|
56
|
+
root.traverse((node) => {
|
|
57
|
+
const geometry = node.geometry;
|
|
58
|
+
if (geometry?.attributes?.position) seen.add(geometry);
|
|
59
|
+
});
|
|
60
|
+
return countTriangles([...seen]);
|
|
61
|
+
}
|
|
62
|
+
function buildOneInWorker(worker, geometry) {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const handleMessage = (event) => {
|
|
65
|
+
const data = event.data;
|
|
66
|
+
if (!data || !data.error && !data.serialized) return;
|
|
67
|
+
worker.removeEventListener("message", handleMessage);
|
|
68
|
+
worker.removeEventListener("error", handleError);
|
|
69
|
+
if (data.error) {
|
|
70
|
+
reject(new Error(String(data.error)));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const { serialized } = data;
|
|
74
|
+
const bvh = MeshBVH.deserialize(serialized, geometry, { setIndex: false });
|
|
75
|
+
if (serialized.index) {
|
|
76
|
+
if (geometry.index) {
|
|
77
|
+
geometry.index.array = serialized.index;
|
|
78
|
+
} else {
|
|
79
|
+
geometry.setIndex(new BufferAttribute(serialized.index, 1, false));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
geometry.boundingBox = bvh.getBoundingBox(new Box3());
|
|
83
|
+
resolve(bvh);
|
|
84
|
+
};
|
|
85
|
+
const handleError = (event) => {
|
|
86
|
+
worker.removeEventListener("message", handleMessage);
|
|
87
|
+
worker.removeEventListener("error", handleError);
|
|
88
|
+
reject(new Error(event.message || "BVH worker error"));
|
|
89
|
+
};
|
|
90
|
+
worker.addEventListener("message", handleMessage);
|
|
91
|
+
worker.addEventListener("error", handleError);
|
|
92
|
+
const index = geometry.index ? geometry.index.array.slice() : null;
|
|
93
|
+
const position = geometry.attributes.position.array.slice();
|
|
94
|
+
const transfer = [position, ...index ? [index] : []].map(
|
|
95
|
+
(arr) => arr.buffer
|
|
96
|
+
);
|
|
97
|
+
worker.postMessage(
|
|
98
|
+
{
|
|
99
|
+
index,
|
|
100
|
+
position,
|
|
101
|
+
options: { groups: [...geometry.groups] }
|
|
102
|
+
},
|
|
103
|
+
transfer
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
async function createBvhWorker() {
|
|
108
|
+
if (typeof window === "undefined" || typeof Worker === "undefined") {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const { bvhWorkerCode } = await import("./bvhWorkerCode-JRKQF4QH.mjs");
|
|
113
|
+
const blob = new Blob([bvhWorkerCode], { type: "text/javascript" });
|
|
114
|
+
const url = URL.createObjectURL(blob);
|
|
115
|
+
const worker = new Worker(url);
|
|
116
|
+
return {
|
|
117
|
+
worker,
|
|
118
|
+
dispose: () => {
|
|
119
|
+
worker.terminate();
|
|
120
|
+
URL.revokeObjectURL(url);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
} catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function buildSceneBVH(root, signal) {
|
|
128
|
+
const geometries = collectGeometries(root);
|
|
129
|
+
if (geometries.length === 0) return;
|
|
130
|
+
if (countTriangles(geometries) > MAX_BVH_TRIANGLES) return;
|
|
131
|
+
enableAcceleratedRaycast();
|
|
132
|
+
const workerHandle = await createBvhWorker();
|
|
133
|
+
if (signal?.aborted) {
|
|
134
|
+
workerHandle?.dispose();
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
for (const geometry of geometries) {
|
|
139
|
+
if (signal?.aborted) return;
|
|
140
|
+
let tree;
|
|
141
|
+
if (workerHandle) {
|
|
142
|
+
try {
|
|
143
|
+
tree = await buildOneInWorker(workerHandle.worker, geometry);
|
|
144
|
+
} catch {
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (tree === void 0) {
|
|
148
|
+
tree = computeBoundsTree.call(geometry);
|
|
149
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
150
|
+
}
|
|
151
|
+
if (signal?.aborted) return;
|
|
152
|
+
geometry.boundsTree = tree;
|
|
153
|
+
}
|
|
154
|
+
} finally {
|
|
155
|
+
workerHandle?.dispose();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function disposeSceneBVH(root) {
|
|
159
|
+
root.traverse((node) => {
|
|
160
|
+
const geometry = node.geometry;
|
|
161
|
+
if (!geometry?.boundsTree) return;
|
|
162
|
+
disposeBoundsTree.call(geometry);
|
|
163
|
+
geometry.boundsTree = void 0;
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/components/ModelViewer/ModelViewer.tsx
|
|
13
168
|
import { useFrame as useFrame8, useThree as useThree10 } from "@react-three/fiber";
|
|
14
169
|
import { createXRStore } from "@react-three/xr";
|
|
15
170
|
import { QRCodeSVG } from "qrcode.react";
|
|
16
171
|
import {
|
|
17
|
-
Box3 as
|
|
172
|
+
Box3 as Box34,
|
|
18
173
|
PerspectiveCamera as PerspectiveCamera3,
|
|
19
174
|
Raycaster,
|
|
20
175
|
Sphere as Sphere2,
|
|
@@ -45,7 +200,7 @@ function styleInject(css, { insertAt } = {}) {
|
|
|
45
200
|
}
|
|
46
201
|
|
|
47
202
|
// src/styles/ModelViewer.css
|
|
48
|
-
styleInject('.ri-viewer--license-checking,\n.ri-viewer--license-invalid {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n background-color: #f9f9f9;\n}\n.ri-viewer {\n --ri-font-family:\n Inter,\n "Segoe UI",\n Helvetica,\n Arial,\n sans-serif;\n --ri-viewer-bg: #1c1c1d;\n --ri-viewer-text: #111827;\n --ri-panel-bg: #ffffff;\n --ri-panel-border: #e5e7eb;\n --ri-panel-title: #111827;\n --ri-panel-muted-text: #4b5563;\n --ri-panel-subtle-text: #9ca3af;\n --ri-panel-surface: #f9fafb;\n --ri-panel-surface-hover: #f3f4f6;\n --ri-panel-surface-border: #e5e7eb;\n --ri-panel-button-text: #1f2937;\n --ri-panel-accent: #2563eb;\n --ri-panel-accent-contrast: #ffffff;\n --ri-license-overlay-bg: rgba(0, 0, 0, 0.04);\n --ri-license-overlay-border: rgba(0, 0, 0, 0.08);\n --ri-license-overlay-text: #888;\n --ri-license-overlay-error-bg: rgba(192, 57, 43, 0.06);\n --ri-license-overlay-error-border: rgba(192, 57, 43, 0.2);\n --ri-license-overlay-error-text: #c0392b;\n --ri-sidepanel-font-family:\n Inter,\n "Segoe UI",\n Helvetica,\n Arial,\n sans-serif;\n --ri-sidepanel-bg: var(--ri-databinding-bg, #111318);\n --ri-sidepanel-open-btn-bg: rgba(20, 20, 20, 0.82);\n --ri-sidepanel-open-btn-bg-hover: #27282b7e;\n --ri-sidepanel-border: rgba(255, 255, 255, 0.06);\n --ri-sidepanel-divider: rgba(255, 255, 255, 0.04);\n --ri-sidepanel-border-strong: rgba(255, 255, 255, 0.08);\n --ri-sidepanel-border-active: rgba(255, 255, 255, 0.18);\n --ri-sidepanel-shadow-left: 4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-sidepanel-shadow-right: -4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-sidepanel-text: var(--ri-databinding-text, #f1f5f9);\n --ri-sidepanel-text-emphasis: var(--ri-databinding-text-hover, #cbd5e1);\n --ri-sidepanel-text-muted: var(--ri-databinding-text-muted, #94a3b8);\n --ri-sidepanel-text-subtle: var(--ri-databinding-text-subtle, #64748b);\n --ri-sidepanel-placeholder: var(--ri-databinding-text-faint, #475569);\n --ri-sidepanel-surface: rgba(255, 255, 255, 0.05);\n --ri-sidepanel-surface-hover: rgba(255, 255, 255, 0.1);\n --ri-sidepanel-accent: #3b82f6;\n --ri-sidepanel-accent-soft: rgba(59, 130, 246, 0.12);\n --ri-databinding-bg: #111318;\n --ri-databinding-border: rgba(255, 255, 255, 0.06);\n --ri-databinding-border-strong: rgba(255, 255, 255, 0.08);\n --ri-databinding-border-hover: rgba(255, 255, 255, 0.15);\n --ri-databinding-shadow: 4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-databinding-font-family:\n "Inter",\n "Segoe UI",\n sans-serif;\n --ri-databinding-mono-font-family:\n "JetBrains Mono",\n "Fira Code",\n "Menlo",\n monospace;\n --ri-databinding-text: #f1f5f9;\n --ri-databinding-text-muted: #94a3b8;\n --ri-databinding-text-subtle: #64748b;\n --ri-databinding-text-faint: #475569;\n --ri-databinding-text-hover: #cbd5e1;\n --ri-databinding-surface: rgba(255, 255, 255, 0.03);\n --ri-databinding-surface-hover: rgba(255, 255, 255, 0.06);\n --ri-databinding-accent: #93c5fd;\n --ri-databinding-accent-glow: rgba(147, 197, 253, 0.8);\n --ri-databinding-accent-border: rgba(96, 165, 250, 0.55);\n --ri-databinding-accent-gradient:\n linear-gradient(\n \n 135deg,\n #1e3a8a 0%,\n #1d4ed8 60%,\n #2563eb 100% );\n --ri-databinding-accent-gradient-active:\n linear-gradient(\n \n 135deg,\n #1e3a8a 0%,\n #1e40af 100% );\n display: flex;\n flex-direction: row;\n width: 100%;\n height: 100%;\n min-height: 0;\n overflow: hidden;\n background: var(--ri-viewer-bg);\n position: relative;\n color: var(--ri-viewer-text);\n font-family: var(--ri-font-family);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n text-rendering: optimizeLegibility;\n font-synthesis: none;\n}\n.ri-viewer * {\n box-sizing: border-box;\n}\n.ri-viewerLeft {\n display: flex;\n flex: 1;\n min-height: 0;\n min-width: 0;\n position: relative;\n}\n.ri-viewerRight {\n display: flex;\n flex-shrink: 0;\n min-width: 0;\n}\n.ri-panel {\n width: 280px;\n min-height: 0;\n background: var(--ri-panel-bg);\n border-right: 1px solid var(--ri-panel-border);\n display: flex;\n flex-direction: column;\n padding: 20px;\n overflow-y: auto;\n flex-shrink: 0;\n transition: margin-left 0.3s ease, transform 0.3s ease;\n}\n.ri-panel--open {\n transform: translateX(0);\n margin-left: 0;\n}\n.ri-panel--closed {\n transform: translateX(-100%);\n margin-left: -280px;\n}\n.ri-panelHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 12px;\n}\n.ri-panelTitle {\n margin: 0;\n font-size: 15px;\n font-weight: 700;\n color: var(--ri-panel-title);\n line-height: 1.4;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.ri-closeButton {\n border: 0;\n background: transparent;\n color: var(--ri-panel-subtle-text);\n font-size: 22px;\n line-height: 1;\n padding: 0;\n cursor: pointer;\n flex-shrink: 0;\n transition: color 0.2s ease;\n}\n.ri-closeButton:hover {\n color: var(--ri-panel-muted-text);\n}\n.ri-json {\n margin: 0 0 16px;\n padding: 12px;\n background: var(--ri-panel-surface);\n border: 1px solid var(--ri-panel-surface-border);\n border-radius: 12px;\n font-size: 12px;\n line-height: 1.55;\n color: var(--ri-panel-button-text);\n overflow-x: auto;\n white-space: pre-wrap;\n word-break: break-word;\n}\n.ri-divider {\n border-top: 1px solid var(--ri-panel-border);\n margin-bottom: 12px;\n}\n.ri-sectionLabel {\n margin-bottom: 8px;\n font-size: 11px;\n font-weight: 700;\n color: var(--ri-panel-subtle-text);\n text-transform: uppercase;\n letter-spacing: 0.16em;\n}\n.ri-actionList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.ri-actionButton {\n width: 100%;\n border-radius: 8px;\n border: 1px solid var(--ri-panel-surface-border);\n background: var(--ri-panel-surface);\n color: var(--ri-panel-button-text);\n padding: 10px 12px;\n text-align: left;\n font-size: 14px;\n line-height: 1.4;\n cursor: pointer;\n transition:\n background-color 0.2s ease,\n border-color 0.2s ease,\n color 0.2s ease;\n}\n.ri-actionButton:hover {\n background: var(--ri-panel-surface-hover);\n border-color: var(--ri-panel-border);\n}\n.ri-actionButton--active {\n background: var(--ri-panel-accent);\n border-color: var(--ri-panel-accent);\n color: var(--ri-panel-accent-contrast);\n}\n.ri-actionButton--active:hover {\n background: var(--ri-panel-accent);\n border-color: var(--ri-panel-accent);\n}\n.ri-canvasArea {\n flex: 1 1 auto;\n position: relative;\n min-width: 0;\n}\n.ri-canvasArea--measuring {\n cursor: crosshair;\n}\n.ri-canvasArea canvas {\n opacity: 1;\n transition: opacity 0.18s ease;\n}\n.ri-canvasArea--positioning canvas {\n opacity: 0;\n transition: none;\n}\n.ri-bindingsMismatchBanner {\n position: absolute;\n top: 12px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 12px 7px 10px;\n border-radius: 8px;\n background: rgba(30, 20, 0, 0.88);\n border: 1px solid rgba(251, 191, 36, 0.35);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n color: rgb(251, 191, 36);\n font-size: 12px;\n font-weight: 500;\n line-height: 1.4;\n white-space: nowrap;\n pointer-events: auto;\n max-width: calc(100% - 32px);\n white-space: normal;\n text-align: center;\n}\n.ri-bindingsMismatchBanner__icon {\n flex-shrink: 0;\n}\n.ri-bindingsMismatchBanner__dismiss {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 4px;\n padding: 2px;\n background: none;\n border: none;\n border-radius: 4px;\n color: rgba(251, 191, 36, 0.7);\n cursor: pointer;\n transition: color 0.15s ease, background 0.15s ease;\n}\n.ri-bindingsMismatchBanner__dismiss:hover {\n color: rgb(251, 191, 36);\n background: rgba(251, 191, 36, 0.12);\n}\n.ri-measureTools {\n position: absolute;\n top: 16px;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-measureButton {\n width: 34px;\n height: 34px;\n padding: 0;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n color: rgba(255, 255, 255, 0.75);\n border-radius: 8px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n touch-action: manipulation;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n transition:\n background 0.15s ease,\n color 0.15s ease,\n border-color 0.15s ease;\n}\n.ri-measureButton:hover {\n background: rgba(40, 40, 40, 0.92);\n color: #fff;\n border-color: rgba(255, 255, 255, 0.25);\n}\n.ri-measureButton:active {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-measureButton--active {\n background: #22d3ee;\n border-color: #22d3ee;\n color: #06343c;\n}\n.ri-measureButton--active:hover {\n background: #38dbf0;\n color: #06343c;\n}\n.ri-measureHint {\n padding: 6px 10px;\n border-radius: 8px;\n font-size: 12px;\n line-height: 1;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n white-space: nowrap;\n}\n.ri-explodeTools {\n position: absolute;\n top: 16px;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-explodeSlider {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 34px;\n padding: 0 12px;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 8px;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n}\n.ri-explodeSlider input[type=range] {\n width: 120px;\n accent-color: #22d3ee;\n cursor: pointer;\n}\n.ri-cinematicTools {\n position: absolute;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-measureDot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: #22d3ee;\n border: 2px solid #fff;\n box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.4);\n pointer-events: none;\n}\n.ri-measureLabel {\n padding: 3px 7px;\n border-radius: 6px;\n font-size: 12px;\n font-weight: 600;\n line-height: 1;\n white-space: nowrap;\n color: #06343c;\n background: #22d3ee;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n pointer-events: none;\n user-select: none;\n}\n.ri-xrTools {\n position: absolute;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-xrButton {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n height: 34px;\n padding: 0 10px;\n font-size: 13px;\n font-weight: 500;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.16);\n border-radius: 8px;\n cursor: pointer;\n touch-action: manipulation;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n transition: background 0.15s ease, border-color 0.15s ease;\n}\n.ri-xrButton:hover {\n background: rgba(40, 40, 40, 0.92);\n border-color: rgba(255, 255, 255, 0.3);\n}\n.ri-mobileHandoffBackdrop {\n position: absolute;\n z-index: 30;\n inset: 0;\n display: grid;\n place-items: center;\n padding: 20px;\n background: rgba(2, 6, 23, 0.68);\n backdrop-filter: blur(5px);\n -webkit-backdrop-filter: blur(5px);\n}\n.ri-mobileHandoffDialog {\n position: relative;\n display: flex;\n width: min(100%, 300px);\n flex-direction: column;\n align-items: center;\n padding: 16px;\n text-align: center;\n color: #e2e8f0;\n background: #0f172a;\n border: 1px solid rgba(148, 163, 184, 0.3);\n border-radius: 16px;\n box-shadow: 0 24px 80px rgba(0, 0, 0, 0.5);\n}\n.ri-mobileHandoffDialog > svg {\n color: #67e8f9;\n}\n.ri-mobileHandoffDialog h2 {\n margin: 12px 0 0;\n font-size: 18px;\n line-height: 1.25;\n color: #fff;\n}\n.ri-mobileHandoffDialog p {\n margin: 8px 0 16px;\n font-size: 13px;\n line-height: 1.5;\n color: #cbd5e1;\n}\n.ri-mobileHandoffQr {\n display: grid;\n place-items: center;\n padding: 10px;\n background: #fff;\n border-radius: 10px;\n}\n.ri-mobileHandoffDialog a {\n margin-top: 16px;\n font-size: 13px;\n font-weight: 600;\n color: #a5f3fc;\n text-decoration: underline;\n text-underline-offset: 3px;\n}\n.ri-mobileHandoffClose {\n position: absolute;\n top: 10px;\n right: 10px;\n display: grid;\n width: 30px;\n height: 30px;\n place-items: center;\n padding: 0;\n color: #cbd5e1;\n background: transparent;\n border: 0;\n border-radius: 6px;\n cursor: pointer;\n}\n.ri-mobileHandoffClose:hover {\n color: #fff;\n background: rgba(255, 255, 255, 0.1);\n}\n.ri-arHintOverlay {\n position: absolute;\n top: 18px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n width: min(calc(100% - 32px), 360px);\n pointer-events: none;\n}\n.ri-arHint {\n margin: 0 auto;\n width: fit-content;\n max-width: 100%;\n padding: 6px 12px;\n font-size: 11px;\n font-weight: 500;\n line-height: 1.2;\n text-align: center;\n color: rgba(255, 255, 255, 0.92);\n background: rgba(20, 20, 20, 0.58);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 999px;\n user-select: none;\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n}\n.ri-arOverlay {\n position: absolute;\n bottom: 24px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n width: min(calc(100% - 32px), 420px);\n pointer-events: none;\n}\n.ri-arControls {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n}\n.ri-arStatus,\n.ri-arActionButton {\n max-width: 100%;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.3;\n text-align: center;\n white-space: nowrap;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.16);\n border-radius: 19px;\n user-select: none;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n}\n.ri-arStatus {\n pointer-events: none;\n}\n.ri-arActionButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n cursor: pointer;\n pointer-events: auto;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n transition:\n background 0.15s ease,\n border-color 0.15s ease,\n color 0.15s ease,\n transform 0.15s ease;\n}\n.ri-arActionButton svg {\n flex-shrink: 0;\n}\n.ri-arActionButton:hover {\n background: rgba(40, 40, 40, 0.92);\n border-color: rgba(255, 255, 255, 0.3);\n}\n.ri-arActionButton:active {\n transform: scale(0.98);\n}\n.ri-arActionButton--primary {\n color: #06343c;\n background: #22d3ee;\n border-color: transparent;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n}\n.ri-arActionButton--primary:hover {\n background: #67e8f9;\n border-color: transparent;\n}\n.ri-arActionButton.ri-arActionButton--active,\n.ri-arActionButton[aria-pressed=true],\n.ri-arActionButton.ri-arActionButton--active:hover,\n.ri-arActionButton[aria-pressed=true]:hover,\n.ri-arActionButton.ri-arActionButton--active:active,\n.ri-arActionButton[aria-pressed=true]:active {\n color: #06343c;\n background: #22d3ee;\n border-color: transparent;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n}\n.ri-hiddenInput {\n display: none;\n}\n.ri-popover {\n position: absolute;\n z-index: 50;\n background: #ffffff;\n border: 1px solid #e5e7eb;\n border-radius: 16px;\n box-shadow: 0 24px 48px rgba(15, 23, 42, 0.18);\n color: #111827;\n}\n.ri-popover--inline {\n position: static;\n z-index: auto;\n width: 100%;\n border-radius: 10px;\n box-shadow: none;\n}\n.ri-popover--inline .react-colorful {\n width: 100%;\n}\n.ri-popover--inline {\n background: var(--ri-databinding-surface, rgba(255, 255, 255, 0.03));\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text, #f1f5f9);\n}\n.ri-popover--inline .ri-popoverHeader {\n border-bottom-color: var( --ri-databinding-border-strong, rgba(255, 255, 255, 0.08) );\n}\n.ri-popover--inline .ri-popoverLabel {\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-colorCode {\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-secondaryButton {\n background: var(--ri-databinding-surface-hover, rgba(255, 255, 255, 0.06));\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text, #f1f5f9);\n}\n.ri-popover--inline .ri-secondaryButton:hover {\n background: rgba(255, 255, 255, 0.12);\n}\n.ri-popover--inline .ri-previewFrame {\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n}\n.ri-popover--inline .ri-emptyState {\n border-color: var(--ri-databinding-border-hover, rgba(255, 255, 255, 0.15));\n color: var(--ri-databinding-text-subtle, #64748b);\n}\n.ri-popover--inline .ri-uploadButton {\n background: var(--ri-databinding-accent-gradient, #2563eb);\n color: #ffffff;\n}\n.ri-popover--inline .ri-uploadButton:hover {\n background: var(--ri-databinding-accent-gradient-active, #1d4ed8);\n}\n.ri-popover--inline .ri-removeButton {\n background: transparent;\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-removeButton:hover {\n background: var(--ri-databinding-surface-hover, rgba(255, 255, 255, 0.06));\n}\n.ri-popover--inline .ri-formatHint {\n color: var(--ri-databinding-text-subtle, #64748b);\n}\n.ri-popoverHeader {\n padding: 12px 16px;\n}\n.ri-popoverLabel {\n margin: 0 0 8px;\n font-size: 11px;\n font-weight: 700;\n color: #6b7280;\n text-transform: uppercase;\n letter-spacing: 0.16em;\n}\n.ri-inlineRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n margin-top: 8px;\n}\n.ri-colorCode {\n font-size: 12px;\n color: #9ca3af;\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Monaco,\n Consolas,\n monospace;\n text-transform: uppercase;\n}\n.ri-secondaryButton {\n border: 1px solid #e5e7eb;\n background: #f3f4f6;\n color: #374151;\n border-radius: 8px;\n padding: 6px 10px;\n font-size: 12px;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-secondaryButton:hover {\n background: #e5e7eb;\n}\n.ri-materialBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding-left: 12px;\n padding-right: 12px;\n padding-bottom: 12px;\n}\n.ri-previewFrame {\n position: relative;\n height: 112px;\n border-radius: 12px;\n overflow: hidden;\n border: 1px solid #e5e7eb;\n}\n.ri-previewImage {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n.ri-previewCaption {\n position: absolute;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n padding: 4px 8px;\n color: #ffffff;\n font-size: 10px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ri-emptyState {\n height: 112px;\n border-radius: 12px;\n border: 2px dashed #e5e7eb;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 4px;\n color: #d1d5db;\n text-align: center;\n}\n.ri-uploadButton {\n width: 100%;\n border: 0;\n border-radius: 10px;\n background: #111827;\n color: #ffffff;\n padding: 10px 12px;\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-uploadButton:hover {\n background: #374151;\n}\n.ri-removeButton {\n width: 100%;\n border-radius: 10px;\n border: 1px solid #e5e7eb;\n background: #ffffff;\n color: #6b7280;\n padding: 8px 12px;\n font-size: 12px;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-removeButton:hover {\n background: #f9fafb;\n}\n.ri-formatHint {\n margin: 0;\n text-align: center;\n font-size: 10px;\n color: #d1d5db;\n}\n.ri-buttonContainer {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 8px;\n position: absolute;\n bottom: 12px;\n left: 12px;\n right: 12px;\n z-index: 10;\n}\n.ri-tourControls {\n display: flex;\n gap: 6px;\n align-items: center;\n flex-shrink: 0;\n}\n.ri-rightControls {\n display: flex;\n gap: 6px;\n align-items: center;\n flex-shrink: 0;\n margin-left: auto;\n}\n.ri-rightControls:only-child {\n margin-left: auto;\n}\n.ri-navButtonSingle,\n.ri-tourButton,\n.ri-resetButton,\n.ri-downloadButton {\n height: 34px;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n color: rgba(255, 255, 255, 0.75);\n border-radius: 8px;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n cursor: pointer;\n transition:\n background 0.15s ease,\n color 0.15s ease,\n border-color 0.15s ease,\n transform 0.15s ease;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n}\n.ri-navButtonSingle:hover:not(:disabled),\n.ri-tourButton:hover:not(:disabled),\n.ri-resetButton:hover:not(:disabled) {\n background: rgba(40, 40, 40, 0.92);\n color: #fff;\n border-color: rgba(255, 255, 255, 0.25);\n}\n.ri-navButtonSingle:active:not(:disabled),\n.ri-tourButton:active:not(:disabled),\n.ri-resetButton:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-navButtonSingle:disabled,\n.ri-tourButton:disabled,\n.ri-resetButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.ri-navButtonSingle {\n padding: 0 16px;\n font-size: 13px;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 8px;\n white-space: nowrap;\n}\n.ri-tourButton {\n padding: 0 12px;\n font-size: 13px;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 4px;\n white-space: nowrap;\n}\n.ri-tourButton svg {\n width: 15px;\n height: 15px;\n flex-shrink: 0;\n}\n.ri-tourButton--stop {\n border-color: rgba(255, 68, 68, 0.4);\n color: #ff6b6b;\n}\n.ri-tourButton--stop:hover:not(:disabled) {\n background: rgba(255, 68, 68, 0.15);\n border-color: rgba(255, 68, 68, 0.6);\n color: #ff6b6b;\n}\n.ri-tourButton--stop:active:not(:disabled) {\n background: #ff4444;\n color: #fff;\n border-color: #ff4444;\n}\n.ri-tourButton--next {\n border-color: rgba(56, 219, 240, 0.3);\n color: #38dbf0;\n}\n.ri-tourButton--next:hover:not(:disabled) {\n background: rgba(56, 219, 240, 0.12);\n border-color: rgba(56, 219, 240, 0.5);\n color: #38dbf0;\n}\n.ri-tourButton--next:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-resetButton {\n width: 34px;\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.ri-resetButton svg {\n width: 15px;\n height: 15px;\n pointer-events: none;\n flex-shrink: 0;\n}\n.ri-resetButton:focus-visible {\n outline: 2px solid rgba(255, 255, 255, 0.5);\n outline-offset: 2px;\n}\n.ri-downloadWrapper {\n position: relative;\n display: inline-flex;\n flex-direction: column;\n align-items: stretch;\n}\n.ri-downloadButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 0 16px;\n font-size: 13px;\n font-weight: 500;\n white-space: nowrap;\n}\n.ri-downloadButton:hover:not(:disabled) {\n background: rgba(0, 0, 0, 0.72);\n border-color: rgba(255, 255, 255, 0.3);\n color: #fff;\n}\n.ri-downloadButton:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-downloadButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.ri-downloadButton__spinner {\n display: inline-block;\n width: 13px;\n height: 13px;\n border: 2px solid rgba(255, 255, 255, 0.3);\n border-top-color: #fff;\n border-radius: 50%;\n animation: ri-spin 0.7s linear infinite;\n flex-shrink: 0;\n}\n.ri-downloadMenu {\n position: absolute;\n bottom: calc(100% + 6px);\n right: 0;\n min-width: 168px;\n background: rgba(20, 20, 20, 0.92);\n border: 1px solid rgba(255, 255, 255, 0.14);\n border-radius: 10px;\n overflow: hidden;\n z-index: 100;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n animation: ri-menuReveal 0.12s ease;\n}\n@keyframes ri-menuReveal {\n from {\n opacity: 0;\n transform: translateY(4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n.ri-downloadMenuItem {\n display: flex;\n align-items: center;\n gap: 9px;\n width: 100%;\n padding: 10px 14px;\n font-size: 13px;\n font-weight: 400;\n color: #fff;\n background: transparent;\n border: none;\n cursor: pointer;\n text-align: left;\n transition: background 0.1s ease;\n}\n.ri-downloadMenuItem:hover:not(:disabled) {\n background: rgba(255, 255, 255, 0.1);\n}\n.ri-downloadMenuItem:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.ri-downloadMenuDivider {\n height: 1px;\n background: rgba(255, 255, 255, 0.1);\n margin: 0 10px;\n}\n@keyframes ri-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.ri-annotationMarker {\n position: relative;\n width: 36px;\n height: 36px;\n border-radius: 50%;\n background: rgba(20, 20, 20, 0.82);\n border: 2px solid rgba(255, 255, 255, 0.9);\n color: #fff;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n font-weight: 700;\n cursor: pointer;\n z-index: 20;\n user-select: none;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);\n pointer-events: auto;\n transition: opacity 0.3s ease, transform 0.3s ease;\n}\n.ri-annotationMarker:hover {\n transform: scale(1.08);\n box-shadow: 0 12px 28px rgba(15, 23, 42, 0.18);\n background: rgba(40, 40, 40, 0.92);\n}\n.ri-annotationPopup {\n position: absolute;\n width: min(270px, calc(100vw - 32px));\n background: rgba(20, 20, 20, 0.85);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 16px;\n padding: 16px;\n z-index: 30;\n display: flex;\n flex-direction: column;\n gap: 12px;\n box-shadow: 0 24px 48px rgba(15, 23, 42, 0.18);\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n}\n.ri-annotationInput,\n.ri-annotationTextarea {\n width: 100%;\n border: 1px solid rgba(255, 255, 255, 0.12);\n background: rgba(255, 255, 255, 0.05);\n color: #e5e7eb;\n border-radius: 10px;\n padding: 10px 12px;\n outline: none;\n font-family: inherit;\n transition: border-color 0.2s ease, box-shadow 0.2s ease;\n}\n.ri-annotationInput {\n font-size: 14px;\n font-weight: 500;\n}\n.ri-annotationTextarea {\n font-size: 13px;\n resize: vertical;\n min-height: 110px;\n}\n.ri-annotationInput:focus,\n.ri-annotationTextarea:focus {\n border-color: #38dbf0;\n box-shadow: 0 0 0 3px rgba(56, 219, 240, 0.12);\n}\n.ri-annotationActions {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.ri-annotationOkBtn {\n border: 0;\n border-radius: 8px;\n background: rgba(56, 219, 240, 0.8);\n color: #06343c;\n padding: 8px 18px;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n transition: background-color 0.2s ease, opacity 0.2s ease;\n}\n.ri-annotationOkBtn:hover:not(:disabled) {\n background: #38dbf0;\n}\n.ri-annotationOkBtn:disabled,\n.ri-annotationOkBtn.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.ri-annotationCancelBtn {\n border: 1px solid rgba(255, 255, 255, 0.12);\n background: transparent;\n color: #9ca3af;\n border-radius: 8px;\n padding: 8px 12px;\n font-size: 13px;\n cursor: pointer;\n transition: background-color 0.2s ease, border-color 0.2s ease;\n}\n.ri-annotationCancelBtn:hover {\n background: rgba(255, 255, 255, 0.05);\n border-color: rgba(255, 255, 255, 0.2);\n}\n.ri-annotationIcons {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 12px;\n color: #9ca3af;\n}\n.ri-annotationIcons svg {\n flex-shrink: 0;\n}\n.ri-viewAnnotationPopup {\n position: relative;\n background: rgba(20, 20, 20, 0.85);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 8px;\n padding: 12px 16px;\n min-width: 200px;\n max-width: min(280px, calc(100vw - 48px));\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);\n pointer-events: auto;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n animation: ri-viewAnnotationFadeIn 0.15s ease-out;\n margin-left: 24px;\n margin-top: -20px;\n isolation: isolate;\n z-index: 1000;\n}\n.ri-viewAnnotationHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n}\n.ri-viewAnnotationTitle {\n margin: 0;\n color: #e2e8f0;\n font-size: 14px;\n font-weight: 600;\n}\n.ri-viewAnnotationClose {\n background: transparent;\n border: none;\n color: #b7bdc6;\n cursor: pointer;\n font-size: 18px;\n padding: 0 4px;\n transition: color 0.2s;\n line-height: 1;\n}\n.ri-viewAnnotationClose:hover {\n color: #e2e8f0;\n}\n.ri-viewAnnotationDescription {\n margin: 0;\n color: #cbd5e1;\n font-size: 13px;\n line-height: 1.4;\n word-wrap: break-word;\n}\n@keyframes ri-viewAnnotationFadeIn {\n from {\n opacity: 0;\n transform: translateY(-6px) scale(0.96);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n.ri-annotationNavSingle {\n position: absolute;\n bottom: 120px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n pointer-events: none;\n}\n.ri-annotationNavSingle > * {\n pointer-events: auto;\n}\n.ri-navBadge {\n background: rgba(255, 255, 255, 0.1);\n padding: 2px 10px;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 400;\n color: rgba(255, 255, 255, 0.7);\n}\n.ri-annotationTitleHint {\n background: rgba(0, 0, 0, 0.7);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n color: white;\n padding: 8px 20px;\n border-radius: 8px;\n font-size: 13px;\n border: 1px solid rgba(255, 255, 255, 0.08);\n text-align: center;\n max-width: 350px;\n animation: slideUp 0.3s ease;\n}\n.ri-annotationHintTitle {\n font-weight: 600;\n color: #e5e7eb;\n}\n.ri-annotationHintDesc {\n font-size: 12px;\n opacity: 0.6;\n margin-top: 2px;\n color: #d1d5db;\n}\n@keyframes slideUp {\n from {\n opacity: 0;\n transform: translateY(10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n@media (max-width: 768px) {\n .ri-xrTools {\n gap: 6px;\n }\n .ri-xrButton {\n height: 34px;\n gap: 6px;\n padding: 0 8px;\n font-size: 11px;\n }\n .ri-xrButton svg {\n width: 14px;\n height: 14px;\n }\n .ri-xrButtonPrefix {\n display: none;\n }\n .ri-annotationNavSingle {\n bottom: 100px;\n }\n .ri-navButtonSingle {\n padding: 8px 20px;\n font-size: 13px;\n min-width: 110px;\n }\n .ri-annotationTitleHint {\n padding: 6px 14px;\n font-size: 12px;\n max-width: 250px;\n }\n .ri-viewAnnotationPopup {\n min-width: 160px;\n max-width: 220px;\n padding: 10px 12px;\n }\n .ri-viewAnnotationTitle {\n font-size: 13px;\n }\n .ri-viewAnnotationDescription {\n font-size: 12px;\n }\n .ri-buttonContainer {\n flex-wrap: wrap;\n gap: 6px;\n }\n .ri-tourControls {\n flex-wrap: wrap;\n }\n .ri-rightControls {\n flex-wrap: wrap;\n }\n}\n@media (max-width: 480px) {\n .ri-xrTools {\n gap: 5px;\n }\n .ri-xrButton {\n height: 34px;\n gap: 5px;\n font-size: 10px;\n }\n .ri-xrButton svg {\n width: 13px;\n height: 13px;\n }\n .ri-annotationNavSingle {\n bottom: 80px;\n }\n .ri-navButtonSingle {\n padding: 6px 16px;\n font-size: 12px;\n min-width: 90px;\n gap: 6px;\n }\n .ri-annotationTitleHint {\n padding: 4px 10px;\n font-size: 11px;\n max-width: 200px;\n }\n .ri-navBadge {\n font-size: 10px;\n padding: 1px 6px;\n }\n .ri-tourButton {\n padding: 0 8px;\n font-size: 12px;\n }\n .ri-tourButton span {\n display: none;\n }\n .ri-downloadButton {\n padding: 0 10px;\n font-size: 12px;\n }\n .ri-downloadButton span {\n display: none;\n }\n}\n.ri-canvasArea .bb-loadingOverlay {\n position: absolute;\n inset: 0;\n z-index: 12;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 16px;\n padding: 24px;\n text-align: center;\n border-radius: 14px;\n background: rgba(15, 23, 42, 0.46);\n backdrop-filter: blur(1px);\n}\n.ri-canvasArea .bb-loadingSpinner {\n width: 40px;\n height: 40px;\n border-radius: 999px;\n border: 3px solid rgba(226, 232, 240, 0.24);\n border-top-color: #93c5fd;\n animation: bb-spin 0.8s linear infinite;\n}\n.ri-canvasArea .bb-loadingMessage {\n max-width: 320px;\n color: #f7f8f9;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.5;\n}\n@keyframes bb-spin {\n to {\n transform: rotate(360deg);\n }\n}\n');
|
|
203
|
+
styleInject('.ri-viewer--license-checking,\n.ri-viewer--license-invalid {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n background-color: #f9f9f9;\n}\n.ri-viewer {\n --ri-font-family:\n Inter,\n "Segoe UI",\n Helvetica,\n Arial,\n sans-serif;\n --ri-viewer-bg: #1c1c1d;\n --ri-viewer-text: #111827;\n --ri-panel-bg: #ffffff;\n --ri-panel-border: #e5e7eb;\n --ri-panel-title: #111827;\n --ri-panel-muted-text: #4b5563;\n --ri-panel-subtle-text: #9ca3af;\n --ri-panel-surface: #f9fafb;\n --ri-panel-surface-hover: #f3f4f6;\n --ri-panel-surface-border: #e5e7eb;\n --ri-panel-button-text: #1f2937;\n --ri-panel-accent: #2563eb;\n --ri-panel-accent-contrast: #ffffff;\n --ri-license-overlay-bg: rgba(0, 0, 0, 0.04);\n --ri-license-overlay-border: rgba(0, 0, 0, 0.08);\n --ri-license-overlay-text: #888;\n --ri-license-overlay-error-bg: rgba(192, 57, 43, 0.06);\n --ri-license-overlay-error-border: rgba(192, 57, 43, 0.2);\n --ri-license-overlay-error-text: #c0392b;\n --ri-sidepanel-font-family:\n Inter,\n "Segoe UI",\n Helvetica,\n Arial,\n sans-serif;\n --ri-sidepanel-bg: var(--ri-databinding-bg, #111318);\n --ri-sidepanel-open-btn-bg: rgba(20, 20, 20, 0.82);\n --ri-sidepanel-open-btn-bg-hover: #27282b7e;\n --ri-sidepanel-border: rgba(255, 255, 255, 0.06);\n --ri-sidepanel-divider: rgba(255, 255, 255, 0.04);\n --ri-sidepanel-border-strong: rgba(255, 255, 255, 0.08);\n --ri-sidepanel-border-active: rgba(255, 255, 255, 0.18);\n --ri-sidepanel-shadow-left: 4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-sidepanel-shadow-right: -4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-sidepanel-text: var(--ri-databinding-text, #f1f5f9);\n --ri-sidepanel-text-emphasis: var(--ri-databinding-text-hover, #cbd5e1);\n --ri-sidepanel-text-muted: var(--ri-databinding-text-muted, #94a3b8);\n --ri-sidepanel-text-subtle: var(--ri-databinding-text-subtle, #64748b);\n --ri-sidepanel-placeholder: var(--ri-databinding-text-faint, #475569);\n --ri-sidepanel-surface: rgba(255, 255, 255, 0.05);\n --ri-sidepanel-surface-hover: rgba(255, 255, 255, 0.1);\n --ri-sidepanel-accent: #3b82f6;\n --ri-sidepanel-accent-soft: rgba(59, 130, 246, 0.12);\n --ri-databinding-bg: #111318;\n --ri-databinding-border: rgba(255, 255, 255, 0.06);\n --ri-databinding-border-strong: rgba(255, 255, 255, 0.08);\n --ri-databinding-border-hover: rgba(255, 255, 255, 0.15);\n --ri-databinding-shadow: 4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-databinding-font-family:\n "Inter",\n "Segoe UI",\n sans-serif;\n --ri-databinding-mono-font-family:\n "JetBrains Mono",\n "Fira Code",\n "Menlo",\n monospace;\n --ri-databinding-text: #f1f5f9;\n --ri-databinding-text-muted: #94a3b8;\n --ri-databinding-text-subtle: #64748b;\n --ri-databinding-text-faint: #475569;\n --ri-databinding-text-hover: #cbd5e1;\n --ri-databinding-surface: rgba(255, 255, 255, 0.03);\n --ri-databinding-surface-hover: rgba(255, 255, 255, 0.06);\n --ri-databinding-accent: #93c5fd;\n --ri-databinding-accent-glow: rgba(147, 197, 253, 0.8);\n --ri-databinding-accent-border: rgba(96, 165, 250, 0.55);\n --ri-databinding-accent-gradient:\n linear-gradient(\n \n 135deg,\n #1e3a8a 0%,\n #1d4ed8 60%,\n #2563eb 100% );\n --ri-databinding-accent-gradient-active:\n linear-gradient(\n \n 135deg,\n #1e3a8a 0%,\n #1e40af 100% );\n display: flex;\n flex-direction: row;\n width: 100%;\n height: 100%;\n min-height: 0;\n overflow: hidden;\n background: var(--ri-viewer-bg);\n position: relative;\n color: var(--ri-viewer-text);\n font-family: var(--ri-font-family);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n text-rendering: optimizeLegibility;\n font-synthesis: none;\n}\n.ri-viewer * {\n box-sizing: border-box;\n}\n.ri-viewerLeft {\n display: flex;\n flex: 1;\n min-height: 0;\n min-width: 0;\n position: relative;\n}\n.ri-viewerRight {\n display: flex;\n flex-shrink: 0;\n min-width: 0;\n}\n.ri-panel {\n width: 280px;\n min-height: 0;\n background: var(--ri-panel-bg);\n border-right: 1px solid var(--ri-panel-border);\n display: flex;\n flex-direction: column;\n padding: 20px;\n overflow-y: auto;\n flex-shrink: 0;\n transition: margin-left 0.3s ease, transform 0.3s ease;\n}\n.ri-panel--open {\n transform: translateX(0);\n margin-left: 0;\n}\n.ri-panel--closed {\n transform: translateX(-100%);\n margin-left: -280px;\n}\n.ri-panelHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 12px;\n}\n.ri-panelTitle {\n margin: 0;\n font-size: 15px;\n font-weight: 700;\n color: var(--ri-panel-title);\n line-height: 1.4;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.ri-closeButton {\n border: 0;\n background: transparent;\n color: var(--ri-panel-subtle-text);\n font-size: 22px;\n line-height: 1;\n padding: 0;\n cursor: pointer;\n flex-shrink: 0;\n transition: color 0.2s ease;\n}\n.ri-closeButton:hover {\n color: var(--ri-panel-muted-text);\n}\n.ri-json {\n margin: 0 0 16px;\n padding: 12px;\n background: var(--ri-panel-surface);\n border: 1px solid var(--ri-panel-surface-border);\n border-radius: 12px;\n font-size: 12px;\n line-height: 1.55;\n color: var(--ri-panel-button-text);\n overflow-x: auto;\n white-space: pre-wrap;\n word-break: break-word;\n}\n.ri-divider {\n border-top: 1px solid var(--ri-panel-border);\n margin-bottom: 12px;\n}\n.ri-sectionLabel {\n margin-bottom: 8px;\n font-size: 11px;\n font-weight: 700;\n color: var(--ri-panel-subtle-text);\n text-transform: uppercase;\n letter-spacing: 0.16em;\n}\n.ri-actionList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.ri-actionButton {\n width: 100%;\n border-radius: 8px;\n border: 1px solid var(--ri-panel-surface-border);\n background: var(--ri-panel-surface);\n color: var(--ri-panel-button-text);\n padding: 10px 12px;\n text-align: left;\n font-size: 14px;\n line-height: 1.4;\n cursor: pointer;\n transition:\n background-color 0.2s ease,\n border-color 0.2s ease,\n color 0.2s ease;\n}\n.ri-actionButton:hover {\n background: var(--ri-panel-surface-hover);\n border-color: var(--ri-panel-border);\n}\n.ri-actionButton--active {\n background: var(--ri-panel-accent);\n border-color: var(--ri-panel-accent);\n color: var(--ri-panel-accent-contrast);\n}\n.ri-actionButton--active:hover {\n background: var(--ri-panel-accent);\n border-color: var(--ri-panel-accent);\n}\n.ri-canvasArea {\n flex: 1 1 auto;\n position: relative;\n min-width: 0;\n}\n.ri-canvasArea--measuring {\n cursor: crosshair;\n}\n.ri-canvasArea canvas {\n opacity: 1;\n transition: opacity 0.18s ease;\n}\n.ri-canvasArea--positioning canvas {\n opacity: 0;\n transition: none;\n}\n.ri-bindingsMismatchBanner {\n position: absolute;\n top: 12px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 12px 7px 10px;\n border-radius: 8px;\n background: rgba(30, 20, 0, 0.88);\n border: 1px solid rgba(251, 191, 36, 0.35);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n color: rgb(251, 191, 36);\n font-size: 12px;\n font-weight: 500;\n line-height: 1.4;\n white-space: nowrap;\n pointer-events: auto;\n max-width: calc(100% - 32px);\n white-space: normal;\n text-align: center;\n}\n.ri-bindingsMismatchBanner__icon {\n flex-shrink: 0;\n}\n.ri-bindingsMismatchBanner__dismiss {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 4px;\n padding: 2px;\n background: none;\n border: none;\n border-radius: 4px;\n color: rgba(251, 191, 36, 0.7);\n cursor: pointer;\n transition: color 0.15s ease, background 0.15s ease;\n}\n.ri-bindingsMismatchBanner__dismiss:hover {\n color: rgb(251, 191, 36);\n background: rgba(251, 191, 36, 0.12);\n}\n.ri-measureTools {\n position: absolute;\n top: 16px;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-measureButton {\n width: 34px;\n height: 34px;\n padding: 0;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n color: rgba(255, 255, 255, 0.75);\n border-radius: 8px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n touch-action: manipulation;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n transition:\n background 0.15s ease,\n color 0.15s ease,\n border-color 0.15s ease;\n}\n.ri-measureButton:hover {\n background: rgba(40, 40, 40, 0.92);\n color: #fff;\n border-color: rgba(255, 255, 255, 0.25);\n}\n.ri-measureButton:active {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-measureButton--active {\n background: #22d3ee;\n border-color: #22d3ee;\n color: #06343c;\n}\n.ri-measureButton--active:hover {\n background: #38dbf0;\n color: #06343c;\n}\n.ri-measureHint {\n padding: 6px 10px;\n border-radius: 8px;\n font-size: 12px;\n line-height: 1;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n white-space: nowrap;\n}\n.ri-explodeTools {\n position: absolute;\n top: 16px;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-explodeSlider {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 34px;\n padding: 0 12px;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 8px;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n}\n.ri-explodeSlider input[type=range] {\n width: 120px;\n accent-color: #22d3ee;\n cursor: pointer;\n}\n.ri-cinematicTools {\n position: absolute;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-measureDot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: #22d3ee;\n border: 2px solid #fff;\n box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.4);\n pointer-events: none;\n}\n.ri-measureLabel {\n padding: 3px 7px;\n border-radius: 6px;\n font-size: 12px;\n font-weight: 600;\n line-height: 1;\n white-space: nowrap;\n color: #06343c;\n background: #22d3ee;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n pointer-events: none;\n user-select: none;\n}\n.ri-xrTools {\n position: absolute;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-xrButton {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n height: 34px;\n padding: 0 10px;\n font-size: 13px;\n font-weight: 500;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.16);\n border-radius: 8px;\n cursor: pointer;\n touch-action: manipulation;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n transition: background 0.15s ease, border-color 0.15s ease;\n}\n.ri-xrButton:hover {\n background: rgba(40, 40, 40, 0.92);\n border-color: rgba(255, 255, 255, 0.3);\n}\n.ri-mobileHandoffBackdrop {\n position: absolute;\n z-index: 30;\n inset: 0;\n display: grid;\n place-items: center;\n padding: 20px;\n background: rgba(2, 6, 23, 0.68);\n backdrop-filter: blur(5px);\n -webkit-backdrop-filter: blur(5px);\n}\n.ri-mobileHandoffDialog {\n position: relative;\n display: flex;\n width: min(100%, 300px);\n flex-direction: column;\n align-items: center;\n padding: 16px;\n text-align: center;\n color: #e2e8f0;\n background: #0f172a;\n border: 1px solid rgba(148, 163, 184, 0.3);\n border-radius: 16px;\n box-shadow: 0 24px 80px rgba(0, 0, 0, 0.5);\n}\n.ri-mobileHandoffDialog > svg {\n color: #67e8f9;\n}\n.ri-mobileHandoffDialog h2 {\n margin: 12px 0 0;\n font-size: 18px;\n line-height: 1.25;\n color: #fff;\n}\n.ri-mobileHandoffDialog p {\n margin: 8px 0 16px;\n font-size: 13px;\n line-height: 1.5;\n color: #cbd5e1;\n}\n.ri-mobileHandoffQr {\n display: grid;\n place-items: center;\n padding: 10px;\n background: #fff;\n border-radius: 10px;\n}\n.ri-mobileHandoffDialog a {\n margin-top: 16px;\n font-size: 13px;\n font-weight: 600;\n color: #a5f3fc;\n text-decoration: underline;\n text-underline-offset: 3px;\n}\n.ri-mobileHandoffClose {\n position: absolute;\n top: 10px;\n right: 10px;\n display: grid;\n width: 30px;\n height: 30px;\n place-items: center;\n padding: 0;\n color: #cbd5e1;\n background: transparent;\n border: 0;\n border-radius: 6px;\n cursor: pointer;\n}\n.ri-mobileHandoffClose:hover {\n color: #fff;\n background: rgba(255, 255, 255, 0.1);\n}\n.ri-arHintOverlay {\n position: absolute;\n top: 18px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n width: min(calc(100% - 32px), 360px);\n pointer-events: none;\n}\n.ri-arHint {\n margin: 0 auto;\n width: fit-content;\n max-width: 100%;\n padding: 6px 12px;\n font-size: 11px;\n font-weight: 500;\n line-height: 1.2;\n text-align: center;\n color: rgba(255, 255, 255, 0.92);\n background: rgba(20, 20, 20, 0.58);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 999px;\n user-select: none;\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n}\n.ri-arOverlay {\n position: absolute;\n bottom: 24px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n width: min(calc(100% - 32px), 420px);\n pointer-events: none;\n}\n.ri-arControls {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n}\n.ri-arStatus,\n.ri-arActionButton {\n max-width: 100%;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.3;\n text-align: center;\n white-space: nowrap;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.16);\n border-radius: 19px;\n user-select: none;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n}\n.ri-arStatus {\n pointer-events: none;\n}\n.ri-arActionButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n cursor: pointer;\n pointer-events: auto;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n transition:\n background 0.15s ease,\n border-color 0.15s ease,\n color 0.15s ease,\n transform 0.15s ease;\n}\n.ri-arActionButton svg {\n flex-shrink: 0;\n}\n.ri-arActionButton:hover {\n background: rgba(40, 40, 40, 0.92);\n border-color: rgba(255, 255, 255, 0.3);\n}\n.ri-arActionButton:active {\n transform: scale(0.98);\n}\n.ri-arActionButton--primary {\n color: #06343c;\n background: #22d3ee;\n border-color: transparent;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n}\n.ri-arActionButton--primary:hover {\n background: #67e8f9;\n border-color: transparent;\n}\n.ri-arActionButton.ri-arActionButton--active,\n.ri-arActionButton[aria-pressed=true],\n.ri-arActionButton.ri-arActionButton--active:hover,\n.ri-arActionButton[aria-pressed=true]:hover,\n.ri-arActionButton.ri-arActionButton--active:active,\n.ri-arActionButton[aria-pressed=true]:active {\n color: #06343c;\n background: #22d3ee;\n border-color: transparent;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n}\n.ri-hiddenInput {\n display: none;\n}\n.ri-popover {\n position: absolute;\n z-index: 50;\n background: #ffffff;\n border: 1px solid #e5e7eb;\n border-radius: 16px;\n box-shadow: 0 24px 48px rgba(15, 23, 42, 0.18);\n color: #111827;\n}\n.ri-popover--inline {\n position: static;\n z-index: auto;\n width: 100%;\n border-radius: 10px;\n box-shadow: none;\n}\n.ri-popover--inline .react-colorful {\n width: 100%;\n}\n.ri-popover--inline {\n background: var(--ri-databinding-surface, rgba(255, 255, 255, 0.03));\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text, #f1f5f9);\n}\n.ri-popover--inline .ri-popoverHeader {\n border-bottom-color: var( --ri-databinding-border-strong, rgba(255, 255, 255, 0.08) );\n}\n.ri-popover--inline .ri-popoverLabel {\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-colorCode {\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-secondaryButton {\n background: var(--ri-databinding-surface-hover, rgba(255, 255, 255, 0.06));\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text, #f1f5f9);\n}\n.ri-popover--inline .ri-secondaryButton:hover {\n background: rgba(255, 255, 255, 0.12);\n}\n.ri-popover--inline .ri-previewFrame {\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n}\n.ri-popover--inline .ri-emptyState {\n border-color: var(--ri-databinding-border-hover, rgba(255, 255, 255, 0.15));\n color: var(--ri-databinding-text-subtle, #64748b);\n}\n.ri-popover--inline .ri-uploadButton {\n background: var(--ri-databinding-accent-gradient, #2563eb);\n color: #ffffff;\n}\n.ri-popover--inline .ri-uploadButton:hover {\n background: var(--ri-databinding-accent-gradient-active, #1d4ed8);\n}\n.ri-popover--inline .ri-removeButton {\n background: transparent;\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-removeButton:hover {\n background: var(--ri-databinding-surface-hover, rgba(255, 255, 255, 0.06));\n}\n.ri-popover--inline .ri-formatHint {\n color: var(--ri-databinding-text-subtle, #64748b);\n}\n.ri-popoverHeader {\n padding: 12px 16px;\n}\n.ri-popoverLabel {\n margin: 0 0 8px;\n font-size: 11px;\n font-weight: 700;\n color: #6b7280;\n text-transform: uppercase;\n letter-spacing: 0.16em;\n}\n.ri-inlineRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n margin-top: 8px;\n}\n.ri-colorCode {\n font-size: 12px;\n color: #9ca3af;\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Monaco,\n Consolas,\n monospace;\n text-transform: uppercase;\n}\n.ri-secondaryButton {\n border: 1px solid #e5e7eb;\n background: #f3f4f6;\n color: #374151;\n border-radius: 8px;\n padding: 6px 10px;\n font-size: 12px;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-secondaryButton:hover {\n background: #e5e7eb;\n}\n.ri-materialBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding-left: 12px;\n padding-right: 12px;\n padding-bottom: 12px;\n}\n.ri-previewFrame {\n position: relative;\n height: 112px;\n border-radius: 12px;\n overflow: hidden;\n border: 1px solid #e5e7eb;\n}\n.ri-previewImage {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n.ri-previewCaption {\n position: absolute;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n padding: 4px 8px;\n color: #ffffff;\n font-size: 10px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ri-emptyState {\n height: 112px;\n border-radius: 12px;\n border: 2px dashed #e5e7eb;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 4px;\n color: #d1d5db;\n text-align: center;\n}\n.ri-uploadButton {\n width: 100%;\n border: 0;\n border-radius: 10px;\n background: #111827;\n color: #ffffff;\n padding: 10px 12px;\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-uploadButton:hover {\n background: #374151;\n}\n.ri-removeButton {\n width: 100%;\n border-radius: 10px;\n border: 1px solid #e5e7eb;\n background: #ffffff;\n color: #6b7280;\n padding: 8px 12px;\n font-size: 12px;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-removeButton:hover {\n background: #f9fafb;\n}\n.ri-formatHint {\n margin: 0;\n text-align: center;\n font-size: 10px;\n color: #d1d5db;\n}\n.ri-buttonContainer {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 8px;\n position: absolute;\n bottom: 12px;\n left: 12px;\n right: 12px;\n z-index: 10;\n}\n.ri-tourControls {\n display: flex;\n gap: 6px;\n align-items: center;\n flex-shrink: 0;\n}\n.ri-rightControls {\n display: flex;\n gap: 6px;\n align-items: center;\n flex-shrink: 0;\n margin-left: auto;\n}\n.ri-rightControls:only-child {\n margin-left: auto;\n}\n.ri-navButtonSingle,\n.ri-tourButton,\n.ri-resetButton,\n.ri-downloadButton {\n height: 34px;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n color: rgba(255, 255, 255, 0.75);\n border-radius: 8px;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n cursor: pointer;\n transition:\n background 0.15s ease,\n color 0.15s ease,\n border-color 0.15s ease,\n transform 0.15s ease;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n}\n.ri-navButtonSingle:hover:not(:disabled),\n.ri-tourButton:hover:not(:disabled),\n.ri-resetButton:hover:not(:disabled) {\n background: rgba(40, 40, 40, 0.92);\n color: #fff;\n border-color: rgba(255, 255, 255, 0.25);\n}\n.ri-navButtonSingle:active:not(:disabled),\n.ri-tourButton:active:not(:disabled),\n.ri-resetButton:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-navButtonSingle:disabled,\n.ri-tourButton:disabled,\n.ri-resetButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.ri-navButtonSingle {\n padding: 0 16px;\n font-size: 13px;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 8px;\n white-space: nowrap;\n}\n.ri-tourButton {\n padding: 0 12px;\n font-size: 13px;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 4px;\n white-space: nowrap;\n}\n.ri-tourButton svg {\n width: 15px;\n height: 15px;\n flex-shrink: 0;\n}\n.ri-tourButton--stop {\n border-color: rgba(255, 68, 68, 0.4);\n color: #ff6b6b;\n}\n.ri-tourButton--stop:hover:not(:disabled) {\n background: rgba(255, 68, 68, 0.15);\n border-color: rgba(255, 68, 68, 0.6);\n color: #ff6b6b;\n}\n.ri-tourButton--stop:active:not(:disabled) {\n background: #ff4444;\n color: #fff;\n border-color: #ff4444;\n}\n.ri-tourButton--next {\n border-color: rgba(56, 219, 240, 0.3);\n color: #38dbf0;\n}\n.ri-tourButton--next:hover:not(:disabled) {\n background: rgba(56, 219, 240, 0.12);\n border-color: rgba(56, 219, 240, 0.5);\n color: #38dbf0;\n}\n.ri-tourButton--next:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-resetButton {\n width: 34px;\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.ri-resetButton svg {\n width: 15px;\n height: 15px;\n pointer-events: none;\n flex-shrink: 0;\n}\n.ri-resetButton:focus-visible {\n outline: 2px solid rgba(255, 255, 255, 0.5);\n outline-offset: 2px;\n}\n.ri-downloadWrapper {\n position: relative;\n display: inline-flex;\n flex-direction: column;\n align-items: stretch;\n}\n.ri-downloadButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 0 16px;\n font-size: 13px;\n font-weight: 500;\n white-space: nowrap;\n}\n.ri-downloadButton:hover:not(:disabled) {\n background: rgba(0, 0, 0, 0.72);\n border-color: rgba(255, 255, 255, 0.3);\n color: #fff;\n}\n.ri-downloadButton:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-downloadButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.ri-downloadButton__spinner {\n display: inline-block;\n width: 13px;\n height: 13px;\n border: 2px solid rgba(255, 255, 255, 0.3);\n border-top-color: #fff;\n border-radius: 50%;\n animation: ri-spin 0.7s linear infinite;\n flex-shrink: 0;\n}\n.ri-downloadMenu {\n position: absolute;\n bottom: calc(100% + 6px);\n right: 0;\n min-width: 168px;\n background: rgba(20, 20, 20, 0.92);\n border: 1px solid rgba(255, 255, 255, 0.14);\n border-radius: 10px;\n overflow: hidden;\n z-index: 100;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n animation: ri-menuReveal 0.12s ease;\n}\n@keyframes ri-menuReveal {\n from {\n opacity: 0;\n transform: translateY(4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n.ri-downloadMenuItem {\n display: flex;\n align-items: center;\n gap: 9px;\n width: 100%;\n padding: 10px 14px;\n font-size: 13px;\n font-weight: 400;\n color: #fff;\n background: transparent;\n border: none;\n cursor: pointer;\n text-align: left;\n transition: background 0.1s ease;\n}\n.ri-downloadMenuItem:hover:not(:disabled) {\n background: rgba(255, 255, 255, 0.1);\n}\n.ri-downloadMenuItem:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.ri-downloadMenuDivider {\n height: 1px;\n background: rgba(255, 255, 255, 0.1);\n margin: 0 10px;\n}\n@keyframes ri-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.ri-annotationMarker {\n position: relative;\n width: 36px;\n height: 36px;\n border-radius: 50%;\n background: rgba(20, 20, 20, 0.82);\n border: 2px solid rgba(255, 255, 255, 0.9);\n color: #fff;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n font-weight: 700;\n cursor: pointer;\n z-index: 20;\n user-select: none;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);\n pointer-events: auto;\n transition: opacity 0.3s ease, transform 0.3s ease;\n}\n.ri-annotationMarker:hover {\n transform: scale(1.08);\n box-shadow: 0 12px 28px rgba(15, 23, 42, 0.18);\n background: rgba(40, 40, 40, 0.92);\n}\n.ri-annotationPopup {\n position: absolute;\n width: min(270px, calc(100vw - 32px));\n background: rgba(20, 20, 20, 0.85);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 16px;\n padding: 16px;\n z-index: 30;\n display: flex;\n flex-direction: column;\n gap: 12px;\n box-shadow: 0 24px 48px rgba(15, 23, 42, 0.18);\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n}\n.ri-annotationInput,\n.ri-annotationTextarea {\n width: 100%;\n border: 1px solid rgba(255, 255, 255, 0.12);\n background: rgba(255, 255, 255, 0.05);\n color: #e5e7eb;\n border-radius: 10px;\n padding: 10px 12px;\n outline: none;\n font-family: inherit;\n transition: border-color 0.2s ease, box-shadow 0.2s ease;\n}\n.ri-annotationInput {\n font-size: 14px;\n font-weight: 500;\n}\n.ri-annotationTextarea {\n font-size: 13px;\n resize: vertical;\n min-height: 110px;\n}\n.ri-annotationInput:focus,\n.ri-annotationTextarea:focus {\n border-color: #38dbf0;\n box-shadow: 0 0 0 3px rgba(56, 219, 240, 0.12);\n}\n.ri-annotationActions {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.ri-annotationOkBtn {\n border: 0;\n border-radius: 8px;\n background: rgba(56, 219, 240, 0.8);\n color: #06343c;\n padding: 8px 18px;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n transition: background-color 0.2s ease, opacity 0.2s ease;\n}\n.ri-annotationOkBtn:hover:not(:disabled) {\n background: #38dbf0;\n}\n.ri-annotationOkBtn:disabled,\n.ri-annotationOkBtn.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.ri-annotationCancelBtn {\n border: 1px solid rgba(255, 255, 255, 0.12);\n background: transparent;\n color: #9ca3af;\n border-radius: 8px;\n padding: 8px 12px;\n font-size: 13px;\n cursor: pointer;\n transition: background-color 0.2s ease, border-color 0.2s ease;\n}\n.ri-annotationCancelBtn:hover {\n background: rgba(255, 255, 255, 0.05);\n border-color: rgba(255, 255, 255, 0.2);\n}\n.ri-annotationIcons {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 12px;\n color: #9ca3af;\n}\n.ri-annotationIcons svg {\n flex-shrink: 0;\n}\n.ri-viewAnnotationPopup {\n position: relative;\n background: rgba(20, 20, 20, 0.85);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 8px;\n padding: 12px 16px;\n min-width: 200px;\n max-width: min(280px, calc(100vw - 48px));\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);\n pointer-events: auto;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n animation: ri-viewAnnotationFadeIn 0.15s ease-out;\n margin-left: 24px;\n margin-top: -20px;\n isolation: isolate;\n z-index: 1000;\n}\n.ri-viewAnnotationHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n}\n.ri-viewAnnotationTitle {\n margin: 0;\n color: #e2e8f0;\n font-size: 14px;\n font-weight: 600;\n}\n.ri-viewAnnotationClose {\n background: transparent;\n border: none;\n color: #b7bdc6;\n cursor: pointer;\n font-size: 18px;\n padding: 0 4px;\n transition: color 0.2s;\n line-height: 1;\n}\n.ri-viewAnnotationClose:hover {\n color: #e2e8f0;\n}\n.ri-viewAnnotationDescription {\n margin: 0;\n color: #cbd5e1;\n font-size: 13px;\n line-height: 1.4;\n word-wrap: break-word;\n}\n@keyframes ri-viewAnnotationFadeIn {\n from {\n opacity: 0;\n transform: translateY(-6px) scale(0.96);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n.ri-annotationNavSingle {\n position: absolute;\n bottom: 120px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n pointer-events: none;\n}\n.ri-annotationNavSingle > * {\n pointer-events: auto;\n}\n.ri-navBadge {\n background: rgba(255, 255, 255, 0.1);\n padding: 2px 10px;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 400;\n color: rgba(255, 255, 255, 0.7);\n}\n.ri-annotationTitleHint {\n background: rgba(0, 0, 0, 0.7);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n color: white;\n padding: 8px 20px;\n border-radius: 8px;\n font-size: 13px;\n border: 1px solid rgba(255, 255, 255, 0.08);\n text-align: center;\n max-width: 350px;\n animation: slideUp 0.3s ease;\n}\n.ri-annotationHintTitle {\n font-weight: 600;\n color: #e5e7eb;\n}\n.ri-annotationHintDesc {\n font-size: 12px;\n opacity: 0.6;\n margin-top: 2px;\n color: #d1d5db;\n}\n@keyframes slideUp {\n from {\n opacity: 0;\n transform: translateY(10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n@media (max-width: 768px) {\n .ri-xrTools {\n gap: 6px;\n }\n .ri-xrButton {\n height: 34px;\n gap: 6px;\n padding: 0 8px;\n font-size: 11px;\n }\n .ri-xrButton svg {\n width: 14px;\n height: 14px;\n }\n .ri-xrButtonPrefix {\n display: none;\n }\n .ri-annotationNavSingle {\n bottom: 100px;\n }\n .ri-navButtonSingle {\n padding: 8px 20px;\n font-size: 13px;\n min-width: 110px;\n }\n .ri-annotationTitleHint {\n padding: 6px 14px;\n font-size: 12px;\n max-width: 250px;\n }\n .ri-viewAnnotationPopup {\n min-width: 160px;\n max-width: 220px;\n padding: 10px 12px;\n }\n .ri-viewAnnotationTitle {\n font-size: 13px;\n }\n .ri-viewAnnotationDescription {\n font-size: 12px;\n }\n .ri-buttonContainer {\n flex-wrap: wrap;\n gap: 6px;\n }\n .ri-tourControls {\n flex-wrap: wrap;\n }\n .ri-rightControls {\n flex-wrap: wrap;\n }\n}\n@media (max-width: 480px) {\n .ri-xrTools {\n gap: 5px;\n }\n .ri-xrButton {\n height: 34px;\n gap: 5px;\n font-size: 10px;\n }\n .ri-xrButton svg {\n width: 13px;\n height: 13px;\n }\n .ri-annotationNavSingle {\n bottom: 80px;\n }\n .ri-navButtonSingle {\n padding: 6px 16px;\n font-size: 12px;\n min-width: 90px;\n gap: 6px;\n }\n .ri-annotationTitleHint {\n padding: 4px 10px;\n font-size: 11px;\n max-width: 200px;\n }\n .ri-navBadge {\n font-size: 10px;\n padding: 1px 6px;\n }\n .ri-tourButton {\n padding: 0 8px;\n font-size: 12px;\n }\n .ri-tourButton span {\n display: none;\n }\n .ri-downloadButton {\n padding: 0 10px;\n font-size: 12px;\n }\n .ri-downloadButton span {\n display: none;\n }\n}\n.ri-canvasArea .bb-loadingOverlay {\n position: absolute;\n inset: 0;\n z-index: 12;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 16px;\n padding: 24px;\n text-align: center;\n border-radius: 14px;\n background: rgba(15, 23, 42, 0.46);\n backdrop-filter: blur(1px);\n}\n.ri-canvasArea .bb-loadingSpinner {\n width: 40px;\n height: 40px;\n border-radius: 999px;\n border: 3px solid rgba(226, 232, 240, 0.24);\n border-top-color: #93c5fd;\n animation: bb-spin 0.8s linear infinite;\n}\n.ri-canvasArea .bb-loadingMessage {\n max-width: 320px;\n color: #f7f8f9;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.5;\n}\n@keyframes bb-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.ri-largeModelBanner {\n position: absolute;\n top: 12px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 12px 7px 10px;\n border-radius: 8px;\n background: rgba(30, 20, 0, 0.88);\n border: 1px solid rgba(251, 191, 36, 0.35);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n color: rgb(251, 191, 36);\n font-size: 12px;\n font-weight: 500;\n line-height: 1.4;\n pointer-events: auto;\n max-width: calc(100% - 32px);\n white-space: normal;\n text-align: center;\n}\n.ri-largeModelBanner--offset {\n top: 64px;\n}\n.ri-largeModelBanner__icon {\n flex-shrink: 0;\n}\n.ri-largeModelBanner__dismiss {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 4px;\n padding: 2px;\n background: none;\n border: none;\n border-radius: 4px;\n color: rgba(251, 191, 36, 0.7);\n cursor: pointer;\n transition: color 0.15s ease, background 0.15s ease;\n}\n.ri-largeModelBanner__dismiss:hover {\n color: rgb(251, 191, 36);\n background: rgba(251, 191, 36, 0.12);\n}\n');
|
|
49
204
|
|
|
50
205
|
// src/components/LicenseOverlay.tsx
|
|
51
206
|
import { AlertTriangle } from "lucide-react";
|
|
@@ -862,7 +1017,7 @@ var Exposure = forwardRef(
|
|
|
862
1017
|
import { useFrame as useFrame2, useThree as useThree2 } from "@react-three/fiber";
|
|
863
1018
|
import * as PostProcessing from "@react-three/postprocessing";
|
|
864
1019
|
import { BlendFunction as BlendFunction2 } from "postprocessing";
|
|
865
|
-
import { Color as Color3, MathUtils, Mesh, Vector2 as Vector22, Vector3 } from "three";
|
|
1020
|
+
import { Color as Color3, MathUtils, Mesh as Mesh2, Vector2 as Vector22, Vector3 } from "three";
|
|
866
1021
|
import { useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2 } from "react";
|
|
867
1022
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
868
1023
|
var LensFlareEffect2 = PostProcessing.LensFlareEffect;
|
|
@@ -958,7 +1113,7 @@ function SafeLensFlare({
|
|
|
958
1113
|
if (object) {
|
|
959
1114
|
if (object.userData?.lensflare === "no-occlusion") {
|
|
960
1115
|
target = 0;
|
|
961
|
-
} else if (object instanceof
|
|
1116
|
+
} else if (object instanceof Mesh2) {
|
|
962
1117
|
if (object.material.uniforms?._transmission?.value > 0.2) {
|
|
963
1118
|
target = 0.2;
|
|
964
1119
|
} else if (object.material._transmission && object.material._transmission > 0.2) {
|
|
@@ -1897,7 +2052,7 @@ import {
|
|
|
1897
2052
|
AnimationMixer,
|
|
1898
2053
|
LoopOnce,
|
|
1899
2054
|
LoopRepeat,
|
|
1900
|
-
Box3,
|
|
2055
|
+
Box3 as Box32,
|
|
1901
2056
|
Vector3 as Vector32,
|
|
1902
2057
|
Matrix4,
|
|
1903
2058
|
MathUtils as MathUtils2
|
|
@@ -2512,60 +2667,6 @@ function refreshSkinnedBounds(root) {
|
|
|
2512
2667
|
});
|
|
2513
2668
|
}
|
|
2514
2669
|
|
|
2515
|
-
// src/utils/bvh.ts
|
|
2516
|
-
import { Mesh as Mesh4 } from "three";
|
|
2517
|
-
import {
|
|
2518
|
-
acceleratedRaycast,
|
|
2519
|
-
computeBoundsTree,
|
|
2520
|
-
disposeBoundsTree
|
|
2521
|
-
} from "three-mesh-bvh";
|
|
2522
|
-
var raycastPatched = false;
|
|
2523
|
-
function enableAcceleratedRaycast() {
|
|
2524
|
-
if (raycastPatched) return;
|
|
2525
|
-
Mesh4.prototype.raycast = acceleratedRaycast;
|
|
2526
|
-
raycastPatched = true;
|
|
2527
|
-
}
|
|
2528
|
-
function collectGeometries(root) {
|
|
2529
|
-
const seen = /* @__PURE__ */ new Set();
|
|
2530
|
-
root.traverse((node) => {
|
|
2531
|
-
const geometry = node.geometry;
|
|
2532
|
-
if (!geometry || !geometry.attributes?.position) return;
|
|
2533
|
-
if (geometry.boundsTree) return;
|
|
2534
|
-
seen.add(geometry);
|
|
2535
|
-
});
|
|
2536
|
-
return [...seen];
|
|
2537
|
-
}
|
|
2538
|
-
function countTriangles(geometries) {
|
|
2539
|
-
let total = 0;
|
|
2540
|
-
for (const geometry of geometries) {
|
|
2541
|
-
const count = geometry.index ? geometry.index.count : geometry.attributes.position.count;
|
|
2542
|
-
total += count / 3;
|
|
2543
|
-
}
|
|
2544
|
-
return total;
|
|
2545
|
-
}
|
|
2546
|
-
var MAX_BVH_TRIANGLES = 12e6;
|
|
2547
|
-
async function buildSceneBVH(root, signal) {
|
|
2548
|
-
const geometries = collectGeometries(root);
|
|
2549
|
-
if (geometries.length === 0) return;
|
|
2550
|
-
if (countTriangles(geometries) > MAX_BVH_TRIANGLES) return;
|
|
2551
|
-
enableAcceleratedRaycast();
|
|
2552
|
-
for (const geometry of geometries) {
|
|
2553
|
-
if (signal?.aborted) return;
|
|
2554
|
-
const tree = computeBoundsTree.call(geometry);
|
|
2555
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
2556
|
-
if (signal?.aborted) return;
|
|
2557
|
-
geometry.boundsTree = tree;
|
|
2558
|
-
}
|
|
2559
|
-
}
|
|
2560
|
-
function disposeSceneBVH(root) {
|
|
2561
|
-
root.traverse((node) => {
|
|
2562
|
-
const geometry = node.geometry;
|
|
2563
|
-
if (!geometry?.boundsTree) return;
|
|
2564
|
-
disposeBoundsTree.call(geometry);
|
|
2565
|
-
geometry.boundsTree = void 0;
|
|
2566
|
-
});
|
|
2567
|
-
}
|
|
2568
|
-
|
|
2569
2670
|
// src/components/Model.tsx
|
|
2570
2671
|
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
2571
2672
|
function firstMaterial(node) {
|
|
@@ -2623,11 +2724,11 @@ function Model({
|
|
|
2623
2724
|
}, [scene]);
|
|
2624
2725
|
const shellNodeNames = useMemo7(() => {
|
|
2625
2726
|
const shell = /* @__PURE__ */ new Set();
|
|
2626
|
-
const sceneBox = new
|
|
2727
|
+
const sceneBox = new Box32();
|
|
2627
2728
|
const meshNodes = [];
|
|
2628
2729
|
for (const [name, node] of Object.entries(nodeMap)) {
|
|
2629
2730
|
if (!isMeshNode(node)) continue;
|
|
2630
|
-
const box = new
|
|
2731
|
+
const box = new Box32().setFromObject(node);
|
|
2631
2732
|
if (box.isEmpty()) continue;
|
|
2632
2733
|
meshNodes.push({ name, box });
|
|
2633
2734
|
sceneBox.union(box);
|
|
@@ -2758,9 +2859,11 @@ function Model({
|
|
|
2758
2859
|
if (rendererMode === "matcap") return perNodeMatcapMaterials[nodeName];
|
|
2759
2860
|
return perNodeMaterials[nodeName];
|
|
2760
2861
|
};
|
|
2862
|
+
const onModelLoadedRef = useRef5(onModelLoaded);
|
|
2863
|
+
onModelLoadedRef.current = onModelLoaded;
|
|
2761
2864
|
useEffect7(() => {
|
|
2762
|
-
|
|
2763
|
-
}, [scene
|
|
2865
|
+
onModelLoadedRef.current?.(scene);
|
|
2866
|
+
}, [scene]);
|
|
2764
2867
|
useEffect7(() => {
|
|
2765
2868
|
if (!nodeRefsOut) return;
|
|
2766
2869
|
for (const [nodeName, binding] of Object.entries(objectBindings)) {
|
|
@@ -3146,7 +3249,7 @@ function Model({
|
|
|
3146
3249
|
}
|
|
3147
3250
|
currentExplodeRef.current = 0;
|
|
3148
3251
|
scene.updateWorldMatrix(true, true);
|
|
3149
|
-
const sceneBox = new
|
|
3252
|
+
const sceneBox = new Box32().setFromObject(scene);
|
|
3150
3253
|
const modelCenter = sceneBox.getCenter(new Vector32());
|
|
3151
3254
|
const radius = sceneBox.getSize(new Vector32()).length() * 0.5;
|
|
3152
3255
|
explodeStrengthRef.current = Number.isFinite(radius) ? radius : 0;
|
|
@@ -3172,7 +3275,7 @@ function Model({
|
|
|
3172
3275
|
ancestor = ancestor.parent;
|
|
3173
3276
|
}
|
|
3174
3277
|
if (nested) continue;
|
|
3175
|
-
const box = new
|
|
3278
|
+
const box = new Box32().setFromObject(node);
|
|
3176
3279
|
if (box.isEmpty()) continue;
|
|
3177
3280
|
box.getCenter(partCenter);
|
|
3178
3281
|
const worldDir = partCenter.clone().sub(modelCenter);
|
|
@@ -3251,13 +3354,21 @@ function Model({
|
|
|
3251
3354
|
setHoveredItem(null);
|
|
3252
3355
|
onItemHover?.(null);
|
|
3253
3356
|
};
|
|
3357
|
+
const pointerRaycastEnabled = useMemo7(
|
|
3358
|
+
() => !exceedsBvhBudget(scene),
|
|
3359
|
+
[scene]
|
|
3360
|
+
);
|
|
3254
3361
|
return /* @__PURE__ */ jsx8("group", { ref: groupRef, children: /* @__PURE__ */ jsx8(
|
|
3255
3362
|
"primitive",
|
|
3256
3363
|
{
|
|
3257
3364
|
object: scene,
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3365
|
+
...pointerRaycastEnabled ? {
|
|
3366
|
+
onClick: handleClick,
|
|
3367
|
+
...highlightOnHover ? {
|
|
3368
|
+
onPointerOver: handlePointerOver,
|
|
3369
|
+
onPointerOut: handlePointerOut
|
|
3370
|
+
} : {}
|
|
3371
|
+
} : {}
|
|
3261
3372
|
}
|
|
3262
3373
|
) });
|
|
3263
3374
|
}
|
|
@@ -3998,7 +4109,7 @@ var XRModelPresenter_default = XRModelPresenter;
|
|
|
3998
4109
|
import { useEffect as useEffect11, useMemo as useMemo11, useRef as useRef8 } from "react";
|
|
3999
4110
|
import { useFrame as useFrame7, useThree as useThree9 } from "@react-three/fiber";
|
|
4000
4111
|
import {
|
|
4001
|
-
Box3 as
|
|
4112
|
+
Box3 as Box33,
|
|
4002
4113
|
CatmullRomCurve3,
|
|
4003
4114
|
Sphere,
|
|
4004
4115
|
Vector3 as Vector36
|
|
@@ -4061,7 +4172,7 @@ function CinematicCamera({
|
|
|
4061
4172
|
if (!hasPath) {
|
|
4062
4173
|
const scene = sceneRef.current;
|
|
4063
4174
|
if (scene) {
|
|
4064
|
-
const sphere = new
|
|
4175
|
+
const sphere = new Box33().setFromObject(scene).getBoundingSphere(new Sphere());
|
|
4065
4176
|
orbitRef.current = {
|
|
4066
4177
|
center: sphere.center.clone(),
|
|
4067
4178
|
radius: sphere.radius > 0 ? sphere.radius : 1
|
|
@@ -5301,7 +5412,7 @@ var DEFAULT_AR_OVERLAY_STATE = {
|
|
|
5301
5412
|
isModelPlaced: false
|
|
5302
5413
|
};
|
|
5303
5414
|
function computeMeasureBox(scene) {
|
|
5304
|
-
const box = new
|
|
5415
|
+
const box = new Box34().setFromObject(scene);
|
|
5305
5416
|
const size = new Vector37();
|
|
5306
5417
|
box.getSize(size);
|
|
5307
5418
|
return {
|
|
@@ -5311,7 +5422,7 @@ function computeMeasureBox(scene) {
|
|
|
5311
5422
|
};
|
|
5312
5423
|
}
|
|
5313
5424
|
function computeModelBounds(scene) {
|
|
5314
|
-
const box = new
|
|
5425
|
+
const box = new Box34().setFromObject(scene);
|
|
5315
5426
|
if (box.isEmpty()) return null;
|
|
5316
5427
|
const center = box.getCenter(new Vector37());
|
|
5317
5428
|
const radius = box.getSize(new Vector37()).length() / 2;
|
|
@@ -5321,7 +5432,7 @@ function computeModelBounds(scene) {
|
|
|
5321
5432
|
function fitClippingPlanesToScene(scene, controls) {
|
|
5322
5433
|
const camera = controls?.camera;
|
|
5323
5434
|
if (!(camera instanceof PerspectiveCamera3)) return;
|
|
5324
|
-
const box = new
|
|
5435
|
+
const box = new Box34().setFromObject(scene);
|
|
5325
5436
|
const size = box.getSize(new Vector37()).length();
|
|
5326
5437
|
if (!Number.isFinite(size) || size <= 0) return;
|
|
5327
5438
|
camera.near = size / 100;
|
|
@@ -5469,6 +5580,11 @@ function ModelViewer({
|
|
|
5469
5580
|
const [modelLoadFailed, setModelLoadFailed] = useState10(false);
|
|
5470
5581
|
const [bindingsMismatch, setBindingsMismatch] = useState10(false);
|
|
5471
5582
|
const [mismatchDismissed, setMismatchDismissed] = useState10(false);
|
|
5583
|
+
const [largeModelTriangles, setLargeModelTriangles] = useState10(
|
|
5584
|
+
null
|
|
5585
|
+
);
|
|
5586
|
+
const [largeModelNoticeDismissed, setLargeModelNoticeDismissed] = useState10(false);
|
|
5587
|
+
const [loaderMessage, setLoaderMessage] = useState10("Loading model...");
|
|
5472
5588
|
const currentObjectBindingsRef = useRef11({});
|
|
5473
5589
|
const captureInitialCameraState = useCallback4(() => {
|
|
5474
5590
|
const controls = controlsRef.current;
|
|
@@ -5749,7 +5865,7 @@ function ModelViewer({
|
|
|
5749
5865
|
controls.smoothTime = 0.6;
|
|
5750
5866
|
const storedNormal = annotation.worldNormal;
|
|
5751
5867
|
if (scene) {
|
|
5752
|
-
const box = new
|
|
5868
|
+
const box = new Box34().setFromObject(scene);
|
|
5753
5869
|
const modelCenter = new Vector37();
|
|
5754
5870
|
box.getCenter(modelCenter);
|
|
5755
5871
|
const modelSize = new Vector37();
|
|
@@ -6056,7 +6172,7 @@ function ModelViewer({
|
|
|
6056
6172
|
if (canvasContainer && cameraControls && object) {
|
|
6057
6173
|
const canvasRect = canvasContainer.getBoundingClientRect();
|
|
6058
6174
|
const objectCenter = new Vector37();
|
|
6059
|
-
new
|
|
6175
|
+
new Box34().setFromObject(object).getCenter(objectCenter);
|
|
6060
6176
|
objectCenter.project(cameraControls);
|
|
6061
6177
|
const projectedX = (objectCenter.x + 1) / 2 * canvasRect.width + (canvasRect.left - rect.left);
|
|
6062
6178
|
const projectedY = (1 - objectCenter.y) / 2 * canvasRect.height + (canvasRect.top - rect.top);
|
|
@@ -6287,7 +6403,7 @@ function ModelViewer({
|
|
|
6287
6403
|
direction.set(0, 0, 1);
|
|
6288
6404
|
}
|
|
6289
6405
|
direction.normalize();
|
|
6290
|
-
const sphere = new
|
|
6406
|
+
const sphere = new Box34().setFromObject(scene).getBoundingSphere(new Sphere2());
|
|
6291
6407
|
const farDistance = Math.max(sphere.radius * 10, 20);
|
|
6292
6408
|
const farPosition = target.clone().add(direction.multiplyScalar(farDistance));
|
|
6293
6409
|
await controls.setLookAt(
|
|
@@ -6310,6 +6426,21 @@ function ModelViewer({
|
|
|
6310
6426
|
paddingRight: 0.5
|
|
6311
6427
|
})
|
|
6312
6428
|
);
|
|
6429
|
+
await new Promise((resolveSettled) => {
|
|
6430
|
+
const startedAt = performance.now();
|
|
6431
|
+
const settle = () => {
|
|
6432
|
+
const active = controls.active;
|
|
6433
|
+
if (active === false || performance.now() - startedAt > 8e3) {
|
|
6434
|
+
controls.dispatchEvent({ type: "update" });
|
|
6435
|
+
rendererCaptureRef.current?.invalidate();
|
|
6436
|
+
resolveSettled();
|
|
6437
|
+
} else {
|
|
6438
|
+
rendererCaptureRef.current?.invalidate();
|
|
6439
|
+
setTimeout(settle, 100);
|
|
6440
|
+
}
|
|
6441
|
+
};
|
|
6442
|
+
setTimeout(settle, 100);
|
|
6443
|
+
});
|
|
6313
6444
|
fitAttemptedRef.current = true;
|
|
6314
6445
|
captureInitialCameraState();
|
|
6315
6446
|
notifyViewerReady();
|
|
@@ -6330,6 +6461,7 @@ function ModelViewer({
|
|
|
6330
6461
|
setModelLoaded(false);
|
|
6331
6462
|
setModelLoadFailed(false);
|
|
6332
6463
|
setBindingsMismatch(false);
|
|
6464
|
+
setLoaderMessage("Loading model...");
|
|
6333
6465
|
if (!modelUrl) {
|
|
6334
6466
|
setIsAutoFraming(false);
|
|
6335
6467
|
setCameraPositioned(true);
|
|
@@ -6357,6 +6489,7 @@ function ModelViewer({
|
|
|
6357
6489
|
notifyViewerReady();
|
|
6358
6490
|
setIsAutoFraming(false);
|
|
6359
6491
|
setCameraPositioned(true);
|
|
6492
|
+
rendererCaptureRef.current?.invalidate();
|
|
6360
6493
|
return;
|
|
6361
6494
|
}
|
|
6362
6495
|
fitInFlightRef.current = true;
|
|
@@ -6517,6 +6650,14 @@ function ModelViewer({
|
|
|
6517
6650
|
sceneRef.current = scene;
|
|
6518
6651
|
setMeasureBox(computeMeasureBox(scene));
|
|
6519
6652
|
setModelBounds(computeModelBounds(scene));
|
|
6653
|
+
const triangles = countSceneTriangles(scene);
|
|
6654
|
+
setLargeModelTriangles(triangles > MAX_BVH_TRIANGLES ? triangles : null);
|
|
6655
|
+
setLargeModelNoticeDismissed(false);
|
|
6656
|
+
if (triangles > MAX_BVH_TRIANGLES) {
|
|
6657
|
+
setLoaderMessage(
|
|
6658
|
+
`Preparing large model (${Math.round(triangles / 1e6)}M triangles) \u2014 the first display can take a while on this device...`
|
|
6659
|
+
);
|
|
6660
|
+
}
|
|
6520
6661
|
setModelLoaded(true);
|
|
6521
6662
|
setModelLoadFailed(false);
|
|
6522
6663
|
setMismatchDismissed(false);
|
|
@@ -6839,6 +6980,33 @@ function ModelViewer({
|
|
|
6839
6980
|
}
|
|
6840
6981
|
)
|
|
6841
6982
|
] }),
|
|
6983
|
+
largeModelTriangles !== null && !largeModelNoticeDismissed && /* @__PURE__ */ jsxs15(
|
|
6984
|
+
"div",
|
|
6985
|
+
{
|
|
6986
|
+
className: `ri-largeModelBanner ${bindingsMismatch && !mismatchDismissed ? "ri-largeModelBanner--offset" : ""}`,
|
|
6987
|
+
children: [
|
|
6988
|
+
/* @__PURE__ */ jsx19(AlertTriangle2, { size: 14, className: "ri-largeModelBanner__icon" }),
|
|
6989
|
+
/* @__PURE__ */ jsxs15("span", { children: [
|
|
6990
|
+
"Very large model (",
|
|
6991
|
+
Math.round(largeModelTriangles / 1e6),
|
|
6992
|
+
"M triangles) \u2014 it may render and move slowly on this device, and click/hover selection on the model is disabled. Use the objects panel to select parts."
|
|
6993
|
+
] }),
|
|
6994
|
+
/* @__PURE__ */ jsx19(
|
|
6995
|
+
"button",
|
|
6996
|
+
{
|
|
6997
|
+
type: "button",
|
|
6998
|
+
className: "ri-largeModelBanner__dismiss",
|
|
6999
|
+
onClick: (e) => {
|
|
7000
|
+
e.stopPropagation();
|
|
7001
|
+
setLargeModelNoticeDismissed(true);
|
|
7002
|
+
},
|
|
7003
|
+
"aria-label": "Dismiss",
|
|
7004
|
+
children: /* @__PURE__ */ jsx19(X, { size: 12 })
|
|
7005
|
+
}
|
|
7006
|
+
)
|
|
7007
|
+
]
|
|
7008
|
+
}
|
|
7009
|
+
),
|
|
6842
7010
|
/* @__PURE__ */ jsx19(
|
|
6843
7011
|
"input",
|
|
6844
7012
|
{
|
|
@@ -6972,13 +7140,7 @@ function ModelViewer({
|
|
|
6972
7140
|
]
|
|
6973
7141
|
}
|
|
6974
7142
|
),
|
|
6975
|
-
/* @__PURE__ */ jsx19(
|
|
6976
|
-
LoadingOverlay,
|
|
6977
|
-
{
|
|
6978
|
-
visible: showModelLoader,
|
|
6979
|
-
message: "Loading model..."
|
|
6980
|
-
}
|
|
6981
|
-
),
|
|
7143
|
+
/* @__PURE__ */ jsx19(LoadingOverlay, { visible: showModelLoader, message: loaderMessage }),
|
|
6982
7144
|
xrToolsVisible && /* @__PURE__ */ jsxs15(
|
|
6983
7145
|
"div",
|
|
6984
7146
|
{
|
|
@@ -14512,11 +14674,12 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14512
14674
|
},
|
|
14513
14675
|
[bindingsHistory, setSceneConfig]
|
|
14514
14676
|
);
|
|
14515
|
-
const generateBindingsForModel = (nextModelUrl, nextModelName) => {
|
|
14677
|
+
const generateBindingsForModel = (nextModelUrl, nextModelName, fileSizeBytes) => {
|
|
14516
14678
|
const requestId = ++loadRequestRef.current;
|
|
14517
14679
|
setLoading(true);
|
|
14680
|
+
const sizeMB = fileSizeBytes ? fileSizeBytes / 1048576 : 0;
|
|
14518
14681
|
setStatusMessage(
|
|
14519
|
-
`Reading ${nextModelName} and generating starter bindings...`
|
|
14682
|
+
sizeMB >= 100 ? `Reading ${nextModelName} (${Math.round(sizeMB)}MB \u2014 large file, this can take a while)...` : `Reading ${nextModelName} and generating starter bindings...`
|
|
14520
14683
|
);
|
|
14521
14684
|
setStatusTone("default");
|
|
14522
14685
|
setModelName(nextModelName);
|
|
@@ -14586,7 +14749,11 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14586
14749
|
}
|
|
14587
14750
|
uploadedModelRef.current?.cleanup();
|
|
14588
14751
|
uploadedModelRef.current = resolvedModel;
|
|
14589
|
-
await generateBindingsForModel(
|
|
14752
|
+
await generateBindingsForModel(
|
|
14753
|
+
resolvedModel.url,
|
|
14754
|
+
resolvedModel.name,
|
|
14755
|
+
file.size
|
|
14756
|
+
);
|
|
14590
14757
|
};
|
|
14591
14758
|
const handleUploadModel = async (event) => {
|
|
14592
14759
|
const file = event.target.files?.[0];
|
|
@@ -15402,11 +15569,12 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15402
15569
|
onLoadError: handleViewerLoadError,
|
|
15403
15570
|
licenseKey,
|
|
15404
15571
|
sceneConfig,
|
|
15572
|
+
renderMode: "always",
|
|
15405
15573
|
disableZoom: false,
|
|
15406
15574
|
zoomOnSelected,
|
|
15407
15575
|
highlightOnHover,
|
|
15408
15576
|
panelHoveredObject: highlightOnHover ? panelHoveredBinding : null,
|
|
15409
|
-
|
|
15577
|
+
showLoadingOverlay: false
|
|
15410
15578
|
}
|
|
15411
15579
|
) : /* @__PURE__ */ jsx45(
|
|
15412
15580
|
"div",
|
|
@@ -15491,14 +15659,14 @@ import { CameraControls as CameraControls2, Environment as Environment2 } from "
|
|
|
15491
15659
|
import { useFrame as useFrame9, useThree as useThree11 } from "@react-three/fiber";
|
|
15492
15660
|
import { Canvas as Canvas3 } from "@react-three/fiber";
|
|
15493
15661
|
import {
|
|
15494
|
-
Box3 as
|
|
15662
|
+
Box3 as Box35,
|
|
15495
15663
|
PerspectiveCamera as PerspectiveCamera4,
|
|
15496
15664
|
Vector3 as Vector39
|
|
15497
15665
|
} from "three";
|
|
15498
15666
|
import { SkeletonUtils as SkeletonUtils2 } from "three-stdlib";
|
|
15499
15667
|
|
|
15500
15668
|
// src/styles/SimpleModelViewer.css
|
|
15501
|
-
styleInject('.smv-root {\n --smv-panel-font-family:\n Inter,\n "Segoe UI",\n Helvetica,\n Arial,\n sans-serif;\n --smv-panel-bg: #141824;\n --smv-panel-bg-strong: #1a1f2e;\n --smv-panel-bg-strong-hover: #252b3b;\n --smv-panel-border: rgba(255, 255, 255, 0.06);\n --smv-panel-border-strong: rgba(255, 255, 255, 0.08);\n --smv-panel-border-active: rgba(255, 255, 255, 0.18);\n --smv-panel-shadow-left: 4px 0 24px rgba(0, 0, 0, 0.35);\n --smv-panel-shadow-right: -4px 0 24px rgba(0, 0, 0, 0.35);\n --smv-panel-text: #f1f5f9;\n --smv-panel-text-emphasis: #ffffff;\n --smv-panel-text-muted: #94a3b8;\n --smv-panel-text-subtle: #64748b;\n --smv-panel-surface: rgba(255, 255, 255, 0.05);\n --smv-panel-surface-hover: rgba(255, 255, 255, 0.1);\n --smv-panel-surface-soft: rgba(255, 255, 255, 0.04);\n --smv-panel-accent: #3b82f6;\n --smv-panel-accent-contrast: #ffffff;\n position: relative;\n width: 100%;\n height: 100%;\n min-height: 0;\n overflow: hidden;\n display: flex;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n text-rendering: optimizeLegibility;\n font-synthesis: none;\n}\n.smv-canvasArea {\n flex: 1;\n position: relative;\n min-width: 0;\n}\n.smv-upload-input {\n display: none;\n}\n.smv-upload-toolbar {\n position: absolute;\n top: 12px;\n left: 50%;\n z-index: 120;\n display: flex;\n align-items: center;\n gap: 10px;\n max-width: calc(100% - 32px);\n padding: 5px 6px;\n border: 1px solid var(--smv-panel-border-strong);\n border-radius: 10px;\n background: rgba(20, 24, 36, 0.88);\n color: var(--smv-panel-text);\n box-shadow: 0 16px 30px rgba(0, 0, 0, 0.28);\n backdrop-filter: blur(14px);\n transform: translateX(-50%);\n}\n.smv-upload-toolbar__name {\n max-width: 260px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--smv-panel-text-muted);\n font-size: 12px;\n font-weight: 500;\n}\n.smv-upload-toolbar__button {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n min-height: 34px;\n padding: 0 14px;\n border: 1px solid rgba(255, 255, 255, 0.08);\n border-radius: 10px;\n background: rgba(59, 130, 246, 0.18);\n color: var(--smv-panel-text-emphasis);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n}\n.smv-upload-toolbar__button--ghost {\n background: var(--smv-panel-surface);\n color: var(--smv-panel-text-muted);\n}\n.smv-upload-empty-state {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n padding: 24px;\n}\n.smv-upload-empty-state__card {\n width: min(440px, 100%);\n padding: 28px;\n border: 1px dashed rgba(255, 255, 255, 0.2);\n border-radius: 8px;\n color: var(--smv-panel-text);\n text-align: center;\n box-shadow: 0 24px 40px rgba(0, 0, 0, 0.28);\n backdrop-filter: blur(14px);\n}\n.smv-upload-empty-state__icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 56px;\n height: 56px;\n margin-bottom: 18px;\n border-radius: 10px;\n background: rgba(59, 130, 246, 0.18);\n color: #bfdbfe;\n}\n.smv-upload-empty-state__title {\n margin: 0 0 10px;\n color: var(--smv-panel-text-emphasis);\n font-size: 24px;\n line-height: 1.15;\n}\n.smv-upload-empty-state__body {\n margin: 0 0 18px;\n color: var(--smv-panel-text-muted);\n font-size: 14px;\n font-weight: 700;\n line-height: 1.6;\n}\n.smv-upload-empty-state__body code {\n color: var(--smv-panel-text-emphasis);\n font-family: var(--smv-panel-font-family);\n}\n.smv-upload-empty-state__button {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n min-height: 40px;\n padding: 0 18px;\n border: none;\n border-radius: 8px;\n background: #3b82f6;\n color: #eff6ff;\n font-size: 13px;\n font-weight: 700;\n cursor: pointer;\n}\n.smv-upload-drop-overlay {\n position: absolute;\n inset: 0;\n z-index: 130;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: rgba(8, 12, 20, 0.48);\n backdrop-filter: blur(4px);\n}\n.smv-upload-drop-overlay__card {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n padding: 16px 20px;\n border: 1px dashed rgba(191, 219, 254, 0.75);\n border-radius: 10px;\n background: rgba(30, 41, 59, 0.92);\n color: #dbeafe;\n font-size: 14px;\n font-weight: 700;\n box-shadow: 0 18px 36px rgba(0, 0, 0, 0.28);\n}\n.smv-canvasArea .bb-loadingOverlay {\n position: absolute;\n inset: 0;\n z-index: 110;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 16px;\n padding: 24px;\n text-align: center;\n border-radius: 14px;\n background: rgba(15, 23, 42, 0.46);\n backdrop-filter: blur(1px);\n}\n.smv-canvasArea .bb-loadingSpinner {\n width: 40px;\n height: 40px;\n border-radius: 999px;\n border: 3px solid rgba(226, 232, 240, 0.24);\n border-top-color: #93c5fd;\n animation: bb-spin 0.8s linear infinite;\n}\n.smv-canvasArea .bb-loadingMessage {\n max-width: 320px;\n color: #f7f8fa;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.5;\n}\n@keyframes bb-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.right-panel-wrapper {\n height: 100%;\n transition: width 0.45s cubic-bezier(0.4, 0, 0.2, 1);\n overflow: visible;\n}\n.right-panel-wrapper--open {\n width: 300px;\n flex-shrink: 0;\n}\n.right-panel-wrapper--closed {\n width: 0;\n flex-shrink: 0;\n}\n.left-panel-wrapper {\n height: 100%;\n transition: width 0.45s cubic-bezier(0.4, 0, 0.2, 1);\n overflow: visible;\n order: -1;\n}\n.left-panel-wrapper--open {\n width: 280px;\n flex-shrink: 0;\n}\n.left-panel-wrapper--closed {\n width: 0;\n flex-shrink: 0;\n}\n.side-panel__open-btn.side-panel__open-btn--left {\n left: 12px;\n right: auto;\n}\n.side-panel.side-panel--left {\n left: 0;\n right: auto;\n width: 280px;\n transform: translateX(-100%);\n border-left: none;\n border-right: 1px solid var(--smv-panel-border);\n box-shadow: var(--smv-panel-shadow-left);\n}\n.side-panel.side-panel--open-left {\n transform: translateX(0);\n}\n.side-panel.side-panel--closed-left {\n transform: translateX(-100%);\n}\n.env-controls {\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n flex: 1;\n}\n.env-section {\n border-bottom: 1px solid var(--smv-panel-border-strong);\n}\n.env-section__header {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n padding: 9px 14px;\n border: none;\n background: var(--smv-panel-surface-soft);\n color: var(--smv-panel-text-muted);\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n cursor: pointer;\n}\n.env-section__chevron {\n transition: transform 0.15s;\n}\n.env-section__chevron--collapsed {\n transform: rotate(-90deg);\n}\n.env-section__body {\n display: flex;\n flex-direction: column;\n}\n.env-row {\n display: flex;\n gap: 6px;\n align-items: center;\n min-height: 32px;\n border-bottom: 1px solid var(--smv-panel-surface);\n}\n.env-row__label {\n flex: 0 0 42%;\n padding: 0 0 0 14px;\n font-size: 11.5px;\n font-weight: 500;\n color: var(--smv-panel-text-muted);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.env-row__value {\n flex: 1;\n display: flex;\n align-items: center;\n padding: 5px 10px 5px 8px;\n min-width: 0;\n}\n.env-row__value--slider {\n gap: 6px;\n}\n.env-checkbox-btn {\n display: flex;\n align-items: center;\n cursor: pointer;\n background: none;\n border: none;\n padding: 0;\n}\n.env-checkbox {\n width: 15px;\n height: 15px;\n border-radius: 3px;\n border: 1.5px solid var(--smv-panel-border-active);\n background: var(--smv-panel-surface-soft);\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.15s, border-color 0.15s;\n}\n.env-checkbox--checked {\n background: var(--smv-panel-accent);\n border-color: var(--smv-panel-accent);\n}\n.env-select {\n width: 100%;\n padding: 5px 8px;\n border-radius: 4px;\n border: none;\n background: var(--smv-panel-surface-hover);\n color: var(--smv-panel-text);\n font-size: 11.5px;\n outline: none;\n box-sizing: border-box;\n text-transform: capitalize;\n}\n.env-color-swatch {\n position: relative;\n width: 100%;\n height: 22px;\n border-radius: 4px;\n border: 1px solid rgba(255, 255, 255, 0.15);\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n cursor: pointer;\n}\n.env-color-swatch__hex {\n font-size: 10.5px;\n font-weight: 700;\n color: #0f172a;\n text-shadow: 0 1px 1px rgba(255, 255, 255, 0.35);\n pointer-events: none;\n text-transform: uppercase;\n}\n.env-color-swatch__input {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n border: none;\n padding: 0;\n opacity: 0;\n cursor: pointer;\n}\n.env-slider {\n flex: 1;\n accent-color: var(--smv-panel-accent);\n cursor: pointer;\n min-width: 0;\n}\n.env-slider__value {\n flex-shrink: 0;\n min-width: 32px;\n padding: 2px 5px;\n border-radius: 4px;\n background: var(--smv-panel-surface-hover);\n color: var(--smv-panel-accent);\n font-size: 10.5px;\n font-weight: 600;\n text-align: center;\n}\n.side-panel__open-btn {\n position: absolute;\n top: 12px;\n right: 12px;\n display: flex;\n align-items: center;\n gap: 7px;\n padding: 7px 13px;\n border-radius: 7px;\n border: 1px solid var(--smv-panel-border-strong);\n background: var(--smv-panel-bg-strong);\n cursor: pointer;\n font-size: 13px;\n font-weight: 500;\n color: var(--smv-panel-text);\n z-index: 190;\n transition: background 0.15s;\n}\n.side-panel__open-btn:hover {\n background: var(--smv-panel-bg-strong-hover);\n}\n.side-panel {\n position: absolute;\n top: 0;\n right: 0;\n width: 300px;\n height: 100%;\n background: var(--smv-panel-bg);\n display: flex;\n flex-direction: column;\n font-family: var(--smv-panel-font-family);\n transform: translateX(100%);\n transition: transform 0.45s cubic-bezier(0.4, 0, 0.2, 1);\n z-index: 200;\n border-left: 1px solid var(--smv-panel-border);\n box-shadow: var(--smv-panel-shadow-right);\n}\n.side-panel--open {\n transform: translateX(0);\n}\n.side-panel--closed {\n transform: translateX(100%);\n}\n.side-panel__header {\n padding: 24px 20px 16px 20px;\n border-bottom: 1px solid var(--smv-panel-border-strong);\n}\n.side-panel__header-top {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 4px;\n}\n.side-panel__eyebrow {\n font-size: 15px;\n font-weight: 700;\n letter-spacing: 0.12em;\n color: var(--smv-panel-text-subtle);\n text-transform: uppercase;\n}\n.side-panel__title {\n font-size: 22px;\n font-weight: 700;\n color: var(--smv-panel-text);\n margin: 0;\n line-height: 1.2;\n}\n.side-panel__close-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 30px;\n height: 30px;\n border-radius: 8px;\n border: 1px solid var(--smv-panel-border-strong);\n background: var(--smv-panel-surface);\n cursor: pointer;\n color: var(--smv-panel-text-muted);\n transition: background 0.15s, color 0.15s;\n flex-shrink: 0;\n}\n.side-panel__close-btn:hover {\n background: var(--smv-panel-surface-hover);\n color: var(--smv-panel-text);\n}\n.side-panel__search-wrap {\n padding: 14px 16px;\n border-bottom: 1px solid var(--smv-panel-border-strong);\n}\n.side-panel__search {\n width: 100%;\n padding: 10px 14px;\n border-radius: 10px;\n border: 1px solid var(--smv-panel-border-strong);\n background: var(--smv-panel-surface);\n color: var(--smv-panel-text);\n font-size: 13px;\n outline: none;\n box-sizing: border-box;\n transition: border-color 0.15s, background 0.15s;\n}\n.side-panel__search::placeholder {\n color: var(--smv-panel-text-subtle);\n}\n.side-panel__search:focus {\n border-color: var(--smv-panel-border-active);\n background: var(--smv-panel-surface-hover);\n}\n.side-panel__list {\n display: flex;\n flex-direction: column;\n padding: 10px 0;\n overflow-y: auto;\n flex: 1;\n}\n.side-panel__list::-webkit-scrollbar {\n width: 4px;\n}\n.side-panel__list::-webkit-scrollbar-track {\n background: transparent;\n}\n.side-panel__list::-webkit-scrollbar-thumb {\n background: var(--smv-panel-surface-hover);\n border-radius: 4px;\n}\n.side-panel__row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 20px;\n cursor: pointer;\n transition: background 0.12s;\n border-bottom: 1px solid var(--smv-panel-surface-soft);\n}\n.side-panel__row:hover,\n.side-panel__row--hovered {\n background: var(--smv-panel-surface);\n}\n.side-panel__label {\n font-size: 14px;\n font-weight: 600;\n color: var(--smv-panel-text);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n flex: 1;\n}\n.side-panel__label--hovered {\n color: var(--smv-panel-text-emphasis);\n}\n.side-panel__vis-btn {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n cursor: pointer;\n background: none;\n border: none;\n padding: 0;\n margin-left: 10px;\n}\n.side-panel__checkbox {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1.5px solid var(--smv-panel-border-active);\n background: transparent;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.15s, border-color 0.15s;\n}\n.side-panel__checkbox--checked {\n background: var(--smv-panel-accent);\n border-color: var(--smv-panel-accent);\n}\n@media (max-width: 900px) {\n .smv-upload-toolbar {\n top: 56px;\n gap: 8px;\n max-width: calc(100% - 24px);\n padding: 8px 10px;\n }\n .smv-upload-toolbar__name {\n max-width: 140px;\n }\n .right-panel-wrapper--open,\n .left-panel-wrapper--open {\n width: 0;\n }\n .side-panel,\n .side-panel.side-panel--left {\n width: min(300px, 85vw);\n }\n}\n');
|
|
15669
|
+
styleInject('.smv-root {\n --smv-panel-font-family:\n Inter,\n "Segoe UI",\n Helvetica,\n Arial,\n sans-serif;\n --smv-panel-bg: #141824;\n --smv-panel-bg-strong: #1a1f2e;\n --smv-panel-bg-strong-hover: #252b3b;\n --smv-panel-border: rgba(255, 255, 255, 0.06);\n --smv-panel-border-strong: rgba(255, 255, 255, 0.08);\n --smv-panel-border-active: rgba(255, 255, 255, 0.18);\n --smv-panel-shadow-left: 4px 0 24px rgba(0, 0, 0, 0.35);\n --smv-panel-shadow-right: -4px 0 24px rgba(0, 0, 0, 0.35);\n --smv-panel-text: #f1f5f9;\n --smv-panel-text-emphasis: #ffffff;\n --smv-panel-text-muted: #94a3b8;\n --smv-panel-text-subtle: #64748b;\n --smv-panel-surface: rgba(255, 255, 255, 0.05);\n --smv-panel-surface-hover: rgba(255, 255, 255, 0.1);\n --smv-panel-surface-soft: rgba(255, 255, 255, 0.04);\n --smv-panel-accent: #3b82f6;\n --smv-panel-accent-contrast: #ffffff;\n position: relative;\n width: 100%;\n height: 100%;\n min-height: 0;\n overflow: hidden;\n display: flex;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n text-rendering: optimizeLegibility;\n font-synthesis: none;\n}\n.smv-canvasArea {\n flex: 1;\n position: relative;\n min-width: 0;\n}\n.smv-upload-input {\n display: none;\n}\n.smv-upload-toolbar {\n position: absolute;\n top: 12px;\n left: 50%;\n z-index: 120;\n display: flex;\n align-items: center;\n gap: 10px;\n max-width: calc(100% - 32px);\n padding: 5px 6px;\n border: 1px solid var(--smv-panel-border-strong);\n border-radius: 10px;\n background: rgba(20, 24, 36, 0.88);\n color: var(--smv-panel-text);\n box-shadow: 0 16px 30px rgba(0, 0, 0, 0.28);\n backdrop-filter: blur(14px);\n transform: translateX(-50%);\n}\n.smv-upload-toolbar__name {\n max-width: 260px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--smv-panel-text-muted);\n font-size: 12px;\n font-weight: 500;\n}\n.smv-upload-toolbar__button {\n display: inline-flex;\n align-items: center;\n gap: 7px;\n min-height: 34px;\n padding: 0 14px;\n border: 1px solid rgba(255, 255, 255, 0.08);\n border-radius: 10px;\n background: rgba(59, 130, 246, 0.18);\n color: var(--smv-panel-text-emphasis);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n}\n.smv-upload-toolbar__button--ghost {\n background: var(--smv-panel-surface);\n color: var(--smv-panel-text-muted);\n}\n.smv-upload-empty-state {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n padding: 24px;\n}\n.smv-upload-empty-state__card {\n width: min(440px, 100%);\n padding: 28px;\n border: 1px dashed rgba(255, 255, 255, 0.2);\n border-radius: 8px;\n color: var(--smv-panel-text);\n text-align: center;\n box-shadow: 0 24px 40px rgba(0, 0, 0, 0.28);\n backdrop-filter: blur(14px);\n}\n.smv-upload-empty-state__icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 56px;\n height: 56px;\n margin-bottom: 18px;\n border-radius: 10px;\n background: rgba(59, 130, 246, 0.18);\n color: #bfdbfe;\n}\n.smv-upload-empty-state__title {\n margin: 0 0 10px;\n color: var(--smv-panel-text-emphasis);\n font-size: 24px;\n line-height: 1.15;\n}\n.smv-upload-empty-state__body {\n margin: 0 0 18px;\n color: var(--smv-panel-text-muted);\n font-size: 14px;\n font-weight: 700;\n line-height: 1.6;\n}\n.smv-upload-empty-state__body code {\n color: var(--smv-panel-text-emphasis);\n font-family: var(--smv-panel-font-family);\n}\n.smv-upload-empty-state__button {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n min-height: 40px;\n padding: 0 18px;\n border: none;\n border-radius: 8px;\n background: #3b82f6;\n color: #eff6ff;\n font-size: 13px;\n font-weight: 700;\n cursor: pointer;\n}\n.smv-upload-drop-overlay {\n position: absolute;\n inset: 0;\n z-index: 130;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: rgba(8, 12, 20, 0.48);\n backdrop-filter: blur(4px);\n}\n.smv-upload-drop-overlay__card {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n padding: 16px 20px;\n border: 1px dashed rgba(191, 219, 254, 0.75);\n border-radius: 10px;\n background: rgba(30, 41, 59, 0.92);\n color: #dbeafe;\n font-size: 14px;\n font-weight: 700;\n box-shadow: 0 18px 36px rgba(0, 0, 0, 0.28);\n}\n.smv-canvasArea .bb-loadingOverlay {\n position: absolute;\n inset: 0;\n z-index: 110;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 16px;\n padding: 24px;\n text-align: center;\n border-radius: 14px;\n background: rgba(15, 23, 42, 0.46);\n backdrop-filter: blur(1px);\n}\n.smv-canvasArea .bb-loadingSpinner {\n width: 40px;\n height: 40px;\n border-radius: 999px;\n border: 3px solid rgba(226, 232, 240, 0.24);\n border-top-color: #93c5fd;\n animation: bb-spin 0.8s linear infinite;\n}\n.smv-canvasArea .bb-loadingMessage {\n max-width: 320px;\n color: #f7f8fa;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.5;\n}\n@keyframes bb-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.right-panel-wrapper {\n height: 100%;\n transition: width 0.45s cubic-bezier(0.4, 0, 0.2, 1);\n overflow: visible;\n}\n.right-panel-wrapper--open {\n width: 300px;\n flex-shrink: 0;\n}\n.right-panel-wrapper--closed {\n width: 0;\n flex-shrink: 0;\n}\n.left-panel-wrapper {\n height: 100%;\n transition: width 0.45s cubic-bezier(0.4, 0, 0.2, 1);\n overflow: visible;\n order: -1;\n}\n.left-panel-wrapper--open {\n width: 280px;\n flex-shrink: 0;\n}\n.left-panel-wrapper--closed {\n width: 0;\n flex-shrink: 0;\n}\n.side-panel__open-btn.side-panel__open-btn--left {\n left: 12px;\n right: auto;\n}\n.side-panel.side-panel--left {\n left: 0;\n right: auto;\n width: 280px;\n transform: translateX(-100%);\n border-left: none;\n border-right: 1px solid var(--smv-panel-border);\n box-shadow: var(--smv-panel-shadow-left);\n}\n.side-panel.side-panel--open-left {\n transform: translateX(0);\n}\n.side-panel.side-panel--closed-left {\n transform: translateX(-100%);\n}\n.env-controls {\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n flex: 1;\n}\n.env-section {\n border-bottom: 1px solid var(--smv-panel-border-strong);\n}\n.env-section__header {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n padding: 9px 14px;\n border: none;\n background: var(--smv-panel-surface-soft);\n color: var(--smv-panel-text-muted);\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n cursor: pointer;\n}\n.env-section__chevron {\n transition: transform 0.15s;\n}\n.env-section__chevron--collapsed {\n transform: rotate(-90deg);\n}\n.env-section__body {\n display: flex;\n flex-direction: column;\n}\n.env-row {\n display: flex;\n gap: 6px;\n align-items: center;\n min-height: 32px;\n border-bottom: 1px solid var(--smv-panel-surface);\n}\n.env-row__label {\n flex: 0 0 42%;\n padding: 0 0 0 14px;\n font-size: 11.5px;\n font-weight: 500;\n color: var(--smv-panel-text-muted);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.env-row__value {\n flex: 1;\n display: flex;\n align-items: center;\n padding: 5px 10px 5px 8px;\n min-width: 0;\n}\n.env-row__value--slider {\n gap: 6px;\n}\n.env-checkbox-btn {\n display: flex;\n align-items: center;\n cursor: pointer;\n background: none;\n border: none;\n padding: 0;\n}\n.env-checkbox {\n width: 15px;\n height: 15px;\n border-radius: 3px;\n border: 1.5px solid var(--smv-panel-border-active);\n background: var(--smv-panel-surface-soft);\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.15s, border-color 0.15s;\n}\n.env-checkbox--checked {\n background: var(--smv-panel-accent);\n border-color: var(--smv-panel-accent);\n}\n.env-select {\n width: 100%;\n padding: 5px 8px;\n border-radius: 4px;\n border: none;\n background: var(--smv-panel-surface-hover);\n color: var(--smv-panel-text);\n font-size: 11.5px;\n outline: none;\n box-sizing: border-box;\n text-transform: capitalize;\n}\n.env-color-swatch {\n position: relative;\n width: 100%;\n height: 22px;\n border-radius: 4px;\n border: 1px solid rgba(255, 255, 255, 0.15);\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n cursor: pointer;\n}\n.env-color-swatch__hex {\n font-size: 10.5px;\n font-weight: 700;\n color: #0f172a;\n text-shadow: 0 1px 1px rgba(255, 255, 255, 0.35);\n pointer-events: none;\n text-transform: uppercase;\n}\n.env-color-swatch__input {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n border: none;\n padding: 0;\n opacity: 0;\n cursor: pointer;\n}\n.env-slider {\n flex: 1;\n accent-color: var(--smv-panel-accent);\n cursor: pointer;\n min-width: 0;\n}\n.env-slider__value {\n flex-shrink: 0;\n min-width: 32px;\n padding: 2px 5px;\n border-radius: 4px;\n background: var(--smv-panel-surface-hover);\n color: var(--smv-panel-accent);\n font-size: 10.5px;\n font-weight: 600;\n text-align: center;\n}\n.side-panel__open-btn {\n position: absolute;\n top: 12px;\n right: 12px;\n display: flex;\n align-items: center;\n gap: 7px;\n padding: 7px 13px;\n border-radius: 7px;\n border: 1px solid var(--smv-panel-border-strong);\n background: var(--smv-panel-bg-strong);\n cursor: pointer;\n font-size: 13px;\n font-weight: 500;\n color: var(--smv-panel-text);\n z-index: 190;\n transition: background 0.15s;\n}\n.side-panel__open-btn:hover {\n background: var(--smv-panel-bg-strong-hover);\n}\n.side-panel {\n position: absolute;\n top: 0;\n right: 0;\n width: 300px;\n height: 100%;\n background: var(--smv-panel-bg);\n display: flex;\n flex-direction: column;\n font-family: var(--smv-panel-font-family);\n transform: translateX(100%);\n transition: transform 0.45s cubic-bezier(0.4, 0, 0.2, 1);\n z-index: 200;\n border-left: 1px solid var(--smv-panel-border);\n box-shadow: var(--smv-panel-shadow-right);\n}\n.side-panel--open {\n transform: translateX(0);\n}\n.side-panel--closed {\n transform: translateX(100%);\n}\n.side-panel__header {\n padding: 24px 20px 16px 20px;\n border-bottom: 1px solid var(--smv-panel-border-strong);\n}\n.side-panel__header-top {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 4px;\n}\n.side-panel__eyebrow {\n font-size: 15px;\n font-weight: 700;\n letter-spacing: 0.12em;\n color: var(--smv-panel-text-subtle);\n text-transform: uppercase;\n}\n.side-panel__title {\n font-size: 22px;\n font-weight: 700;\n color: var(--smv-panel-text);\n margin: 0;\n line-height: 1.2;\n}\n.side-panel__close-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 30px;\n height: 30px;\n border-radius: 8px;\n border: 1px solid var(--smv-panel-border-strong);\n background: var(--smv-panel-surface);\n cursor: pointer;\n color: var(--smv-panel-text-muted);\n transition: background 0.15s, color 0.15s;\n flex-shrink: 0;\n}\n.side-panel__close-btn:hover {\n background: var(--smv-panel-surface-hover);\n color: var(--smv-panel-text);\n}\n.side-panel__search-wrap {\n padding: 14px 16px;\n border-bottom: 1px solid var(--smv-panel-border-strong);\n}\n.side-panel__search {\n width: 100%;\n padding: 10px 14px;\n border-radius: 10px;\n border: 1px solid var(--smv-panel-border-strong);\n background: var(--smv-panel-surface);\n color: var(--smv-panel-text);\n font-size: 13px;\n outline: none;\n box-sizing: border-box;\n transition: border-color 0.15s, background 0.15s;\n}\n.side-panel__search::placeholder {\n color: var(--smv-panel-text-subtle);\n}\n.side-panel__search:focus {\n border-color: var(--smv-panel-border-active);\n background: var(--smv-panel-surface-hover);\n}\n.side-panel__list {\n display: flex;\n flex-direction: column;\n padding: 10px 0;\n overflow-y: auto;\n flex: 1;\n}\n.side-panel__list::-webkit-scrollbar {\n width: 4px;\n}\n.side-panel__list::-webkit-scrollbar-track {\n background: transparent;\n}\n.side-panel__list::-webkit-scrollbar-thumb {\n background: var(--smv-panel-surface-hover);\n border-radius: 4px;\n}\n.side-panel__row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 20px;\n cursor: pointer;\n transition: background 0.12s;\n border-bottom: 1px solid var(--smv-panel-surface-soft);\n}\n.side-panel__row:hover,\n.side-panel__row--hovered {\n background: var(--smv-panel-surface);\n}\n.side-panel__label {\n font-size: 14px;\n font-weight: 600;\n color: var(--smv-panel-text);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n flex: 1;\n}\n.side-panel__label--hovered {\n color: var(--smv-panel-text-emphasis);\n}\n.side-panel__vis-btn {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n cursor: pointer;\n background: none;\n border: none;\n padding: 0;\n margin-left: 10px;\n}\n.side-panel__checkbox {\n width: 16px;\n height: 16px;\n border-radius: 4px;\n border: 1.5px solid var(--smv-panel-border-active);\n background: transparent;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.15s, border-color 0.15s;\n}\n.side-panel__checkbox--checked {\n background: var(--smv-panel-accent);\n border-color: var(--smv-panel-accent);\n}\n@media (max-width: 900px) {\n .smv-upload-toolbar {\n top: 56px;\n gap: 8px;\n max-width: calc(100% - 24px);\n padding: 8px 10px;\n }\n .smv-upload-toolbar__name {\n max-width: 140px;\n }\n .right-panel-wrapper--open,\n .left-panel-wrapper--open {\n width: 0;\n }\n .side-panel,\n .side-panel.side-panel--left {\n width: min(300px, 85vw);\n }\n}\n.smv-largeModelBanner {\n position: absolute;\n bottom: 12px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 12px 7px 10px;\n border-radius: 8px;\n background: rgba(30, 20, 0, 0.88);\n border: 1px solid rgba(251, 191, 36, 0.35);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n color: rgb(251, 191, 36);\n font-size: 12px;\n font-weight: 500;\n line-height: 1.4;\n pointer-events: auto;\n max-width: calc(100% - 32px);\n text-align: center;\n}\n.smv-largeModelBanner__icon {\n flex-shrink: 0;\n}\n.smv-largeModelBanner__dismiss {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 4px;\n padding: 2px;\n background: none;\n border: none;\n border-radius: 4px;\n color: rgba(251, 191, 36, 0.7);\n cursor: pointer;\n transition: color 0.15s ease, background 0.15s ease;\n}\n.smv-largeModelBanner__dismiss:hover {\n color: rgb(251, 191, 36);\n background: rgba(251, 191, 36, 0.12);\n}\n');
|
|
15502
15670
|
|
|
15503
15671
|
// src/components/SimpleModelViewer/SimpleModelViewer.tsx
|
|
15504
15672
|
import {
|
|
@@ -15508,7 +15676,8 @@ import {
|
|
|
15508
15676
|
ChevronDown as ChevronDown6,
|
|
15509
15677
|
Boxes as Boxes2,
|
|
15510
15678
|
Trash2 as Trash26,
|
|
15511
|
-
Upload as Upload2
|
|
15679
|
+
Upload as Upload2,
|
|
15680
|
+
AlertTriangle as AlertTriangle4
|
|
15512
15681
|
} from "lucide-react";
|
|
15513
15682
|
import { Fragment as Fragment14, jsx as jsx46, jsxs as jsxs41 } from "react/jsx-runtime";
|
|
15514
15683
|
var HIGHLIGHT2 = { color: "#93c5fd", intensity: 0.05 };
|
|
@@ -15531,7 +15700,7 @@ function applyClippingPlanes(scene, controls) {
|
|
|
15531
15700
|
if (!controls) return;
|
|
15532
15701
|
const camera = controls.camera;
|
|
15533
15702
|
if (!(camera instanceof PerspectiveCamera4)) return;
|
|
15534
|
-
const size = new
|
|
15703
|
+
const size = new Box35().setFromObject(scene).getSize(new Vector39()).length();
|
|
15535
15704
|
if (!Number.isFinite(size) || size <= 0) return;
|
|
15536
15705
|
camera.near = size / 100;
|
|
15537
15706
|
camera.far = size * 100;
|
|
@@ -15615,6 +15784,18 @@ function InnerModel({
|
|
|
15615
15784
|
onMeshesReady(meshMap);
|
|
15616
15785
|
onModelLoaded(scene);
|
|
15617
15786
|
}, [url, scene, meshMap, onMeshesReady, onModelLoaded]);
|
|
15787
|
+
useEffect21(() => {
|
|
15788
|
+
const controller = new AbortController();
|
|
15789
|
+
void buildSceneBVH(scene, controller.signal);
|
|
15790
|
+
return () => {
|
|
15791
|
+
controller.abort();
|
|
15792
|
+
disposeSceneBVH(scene);
|
|
15793
|
+
};
|
|
15794
|
+
}, [scene]);
|
|
15795
|
+
const pointerRaycastEnabled = useMemo17(
|
|
15796
|
+
() => !exceedsBvhBudget(scene),
|
|
15797
|
+
[scene]
|
|
15798
|
+
);
|
|
15618
15799
|
useEffect21(() => {
|
|
15619
15800
|
for (const [key, { ref }] of Object.entries(meshMap)) {
|
|
15620
15801
|
nodeRefsOut.current[key] = ref;
|
|
@@ -15670,18 +15851,15 @@ function InnerModel({
|
|
|
15670
15851
|
}
|
|
15671
15852
|
return null;
|
|
15672
15853
|
};
|
|
15673
|
-
|
|
15674
|
-
|
|
15675
|
-
|
|
15676
|
-
|
|
15677
|
-
|
|
15678
|
-
|
|
15679
|
-
|
|
15680
|
-
|
|
15681
|
-
onMeshClick(key);
|
|
15682
|
-
},
|
|
15854
|
+
const pointerHandlers = pointerRaycastEnabled ? {
|
|
15855
|
+
onClick: (e) => {
|
|
15856
|
+
const key = resolveKey(e.object);
|
|
15857
|
+
if (!key) return;
|
|
15858
|
+
e.stopPropagation();
|
|
15859
|
+
onMeshClick(key);
|
|
15860
|
+
},
|
|
15861
|
+
...highlightOnHover ? {
|
|
15683
15862
|
onPointerOver: (e) => {
|
|
15684
|
-
if (!highlightOnHover) return;
|
|
15685
15863
|
const key = resolveKey(e.object);
|
|
15686
15864
|
if (!key) return;
|
|
15687
15865
|
e.stopPropagation();
|
|
@@ -15689,13 +15867,13 @@ function InnerModel({
|
|
|
15689
15867
|
setHoveredKey(key);
|
|
15690
15868
|
},
|
|
15691
15869
|
onPointerOut: (e) => {
|
|
15692
|
-
if (!highlightOnHover) return;
|
|
15693
15870
|
e.stopPropagation();
|
|
15694
15871
|
document.body.style.cursor = "default";
|
|
15695
15872
|
setHoveredKey(null);
|
|
15696
15873
|
}
|
|
15697
|
-
}
|
|
15698
|
-
|
|
15874
|
+
} : {}
|
|
15875
|
+
} : {};
|
|
15876
|
+
return /* @__PURE__ */ jsx46("primitive", { object: scene, ...pointerHandlers });
|
|
15699
15877
|
}
|
|
15700
15878
|
function CameraFitter({
|
|
15701
15879
|
scene,
|
|
@@ -16159,6 +16337,10 @@ function SimpleModelViewer({
|
|
|
16159
16337
|
const uploadedModelRef = useRef19(null);
|
|
16160
16338
|
const dragDepthRef = useRef19(0);
|
|
16161
16339
|
const [loadedScene, setLoadedScene] = useState25(null);
|
|
16340
|
+
const [largeModelTriangles, setLargeModelTriangles] = useState25(
|
|
16341
|
+
null
|
|
16342
|
+
);
|
|
16343
|
+
const [largeModelNoticeDismissed, setLargeModelNoticeDismissed] = useState25(false);
|
|
16162
16344
|
const [meshKeys, setMeshKeys] = useState25({});
|
|
16163
16345
|
const [hiddenKeys, setHiddenKeys] = useState25(/* @__PURE__ */ new Set());
|
|
16164
16346
|
const [selectedKey, setSelectedKey] = useState25(null);
|
|
@@ -16191,6 +16373,8 @@ function SimpleModelViewer({
|
|
|
16191
16373
|
const activeModelUrl = enableModelUpload ? uploadedModelUrl : modelUrl;
|
|
16192
16374
|
const resetModelState = useCallback9(() => {
|
|
16193
16375
|
setLoadedScene(null);
|
|
16376
|
+
setLargeModelTriangles(null);
|
|
16377
|
+
setLargeModelNoticeDismissed(false);
|
|
16194
16378
|
setMeshKeys({});
|
|
16195
16379
|
setHiddenKeys(/* @__PURE__ */ new Set());
|
|
16196
16380
|
setSelectedKey(null);
|
|
@@ -16284,6 +16468,9 @@ function SimpleModelViewer({
|
|
|
16284
16468
|
(scene) => {
|
|
16285
16469
|
setLoadedScene(scene);
|
|
16286
16470
|
setModelLoadFailed(false);
|
|
16471
|
+
const triangles = countSceneTriangles(scene);
|
|
16472
|
+
setLargeModelTriangles(triangles > MAX_BVH_TRIANGLES ? triangles : null);
|
|
16473
|
+
setLargeModelNoticeDismissed(false);
|
|
16287
16474
|
onModelLoaded?.(scene);
|
|
16288
16475
|
},
|
|
16289
16476
|
[onModelLoaded]
|
|
@@ -16392,6 +16579,24 @@ function SimpleModelViewer({
|
|
|
16392
16579
|
}
|
|
16393
16580
|
),
|
|
16394
16581
|
/* @__PURE__ */ jsxs41("div", { className: "smv-canvasArea", children: [
|
|
16582
|
+
largeModelTriangles !== null && !largeModelNoticeDismissed && /* @__PURE__ */ jsxs41("div", { className: "smv-largeModelBanner", children: [
|
|
16583
|
+
/* @__PURE__ */ jsx46(AlertTriangle4, { size: 14, className: "smv-largeModelBanner__icon" }),
|
|
16584
|
+
/* @__PURE__ */ jsxs41("span", { children: [
|
|
16585
|
+
"Very large model detected (",
|
|
16586
|
+
Math.round(largeModelTriangles / 1e6),
|
|
16587
|
+
"M triangles) \u2014 it may render and move slowly on this device, and click/hover selection on the model is disabled. Use the scene objects panel to select parts."
|
|
16588
|
+
] }),
|
|
16589
|
+
/* @__PURE__ */ jsx46(
|
|
16590
|
+
"button",
|
|
16591
|
+
{
|
|
16592
|
+
type: "button",
|
|
16593
|
+
className: "smv-largeModelBanner__dismiss",
|
|
16594
|
+
onClick: () => setLargeModelNoticeDismissed(true),
|
|
16595
|
+
"aria-label": "Dismiss",
|
|
16596
|
+
children: /* @__PURE__ */ jsx46(X3, { size: 12 })
|
|
16597
|
+
}
|
|
16598
|
+
)
|
|
16599
|
+
] }),
|
|
16395
16600
|
enableModelUpload ? /* @__PURE__ */ jsxs41(Fragment14, { children: [
|
|
16396
16601
|
/* @__PURE__ */ jsx46(
|
|
16397
16602
|
"input",
|
|
@@ -16426,7 +16631,9 @@ function SimpleModelViewer({
|
|
|
16426
16631
|
Canvas3,
|
|
16427
16632
|
{
|
|
16428
16633
|
dpr: [1, 2],
|
|
16634
|
+
frameloop: "demand",
|
|
16429
16635
|
resize: { debounce: 0, scroll: false },
|
|
16636
|
+
raycaster: { firstHitOnly: true },
|
|
16430
16637
|
style: { background: envSettings.backgroundColor },
|
|
16431
16638
|
camera: { position: [0, 0, 5], fov: 45, near: 0.1, far: 1e3 },
|
|
16432
16639
|
children: /* @__PURE__ */ jsxs41(Suspense3, { fallback: null, children: [
|
|
@@ -16717,7 +16924,7 @@ function useViewerHover() {
|
|
|
16717
16924
|
|
|
16718
16925
|
// src/hooks/useViewerModel.ts
|
|
16719
16926
|
import { useCallback as useCallback13, useState as useState28 } from "react";
|
|
16720
|
-
import { Box3 as
|
|
16927
|
+
import { Box3 as Box36, Vector3 as Vector310 } from "three";
|
|
16721
16928
|
function toTuple2(vector) {
|
|
16722
16929
|
return [vector.x, vector.y, vector.z];
|
|
16723
16930
|
}
|
|
@@ -16728,7 +16935,7 @@ function useViewerModel() {
|
|
|
16728
16935
|
const [bounds, setBounds] = useState28(null);
|
|
16729
16936
|
const [objectCount, setObjectCount] = useState28(0);
|
|
16730
16937
|
const handleModelLoaded = useCallback13((scene) => {
|
|
16731
|
-
const box = new
|
|
16938
|
+
const box = new Box36().setFromObject(scene);
|
|
16732
16939
|
const min = box.min.clone();
|
|
16733
16940
|
const max = box.max.clone();
|
|
16734
16941
|
const center = new Vector310();
|