@liveroom-tech/react-immersive 1.2.0 → 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 -168
- package/dist/index.mjs +314 -119
- 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,72 +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
|
-
async function buildSceneBVH(root, signal) {
|
|
2539
|
-
const geometries = collectGeometries(root);
|
|
2540
|
-
if (geometries.length === 0) return;
|
|
2541
|
-
enableAcceleratedRaycast();
|
|
2542
|
-
let worker = null;
|
|
2543
|
-
if (typeof window !== "undefined" && typeof Worker !== "undefined") {
|
|
2544
|
-
try {
|
|
2545
|
-
const mod = await import("three-mesh-bvh/src/workers/ParallelMeshBVHWorker.js");
|
|
2546
|
-
worker = new mod.ParallelMeshBVHWorker();
|
|
2547
|
-
} catch {
|
|
2548
|
-
worker = null;
|
|
2549
|
-
}
|
|
2550
|
-
}
|
|
2551
|
-
try {
|
|
2552
|
-
for (const geometry of geometries) {
|
|
2553
|
-
if (signal?.aborted) return;
|
|
2554
|
-
let tree;
|
|
2555
|
-
if (worker) {
|
|
2556
|
-
try {
|
|
2557
|
-
tree = await worker.generate(geometry);
|
|
2558
|
-
} catch {
|
|
2559
|
-
}
|
|
2560
|
-
}
|
|
2561
|
-
if (tree === void 0) {
|
|
2562
|
-
tree = computeBoundsTree.call(geometry);
|
|
2563
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
2564
|
-
}
|
|
2565
|
-
if (signal?.aborted) return;
|
|
2566
|
-
geometry.boundsTree = tree;
|
|
2567
|
-
}
|
|
2568
|
-
} finally {
|
|
2569
|
-
worker?.dispose?.();
|
|
2570
|
-
}
|
|
2571
|
-
}
|
|
2572
|
-
function disposeSceneBVH(root) {
|
|
2573
|
-
root.traverse((node) => {
|
|
2574
|
-
const geometry = node.geometry;
|
|
2575
|
-
if (!geometry?.boundsTree) return;
|
|
2576
|
-
disposeBoundsTree.call(geometry);
|
|
2577
|
-
geometry.boundsTree = void 0;
|
|
2578
|
-
});
|
|
2579
|
-
}
|
|
2580
|
-
|
|
2581
2670
|
// src/components/Model.tsx
|
|
2582
2671
|
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
2583
2672
|
function firstMaterial(node) {
|
|
@@ -2635,11 +2724,11 @@ function Model({
|
|
|
2635
2724
|
}, [scene]);
|
|
2636
2725
|
const shellNodeNames = useMemo7(() => {
|
|
2637
2726
|
const shell = /* @__PURE__ */ new Set();
|
|
2638
|
-
const sceneBox = new
|
|
2727
|
+
const sceneBox = new Box32();
|
|
2639
2728
|
const meshNodes = [];
|
|
2640
2729
|
for (const [name, node] of Object.entries(nodeMap)) {
|
|
2641
2730
|
if (!isMeshNode(node)) continue;
|
|
2642
|
-
const box = new
|
|
2731
|
+
const box = new Box32().setFromObject(node);
|
|
2643
2732
|
if (box.isEmpty()) continue;
|
|
2644
2733
|
meshNodes.push({ name, box });
|
|
2645
2734
|
sceneBox.union(box);
|
|
@@ -2770,9 +2859,11 @@ function Model({
|
|
|
2770
2859
|
if (rendererMode === "matcap") return perNodeMatcapMaterials[nodeName];
|
|
2771
2860
|
return perNodeMaterials[nodeName];
|
|
2772
2861
|
};
|
|
2862
|
+
const onModelLoadedRef = useRef5(onModelLoaded);
|
|
2863
|
+
onModelLoadedRef.current = onModelLoaded;
|
|
2773
2864
|
useEffect7(() => {
|
|
2774
|
-
|
|
2775
|
-
}, [scene
|
|
2865
|
+
onModelLoadedRef.current?.(scene);
|
|
2866
|
+
}, [scene]);
|
|
2776
2867
|
useEffect7(() => {
|
|
2777
2868
|
if (!nodeRefsOut) return;
|
|
2778
2869
|
for (const [nodeName, binding] of Object.entries(objectBindings)) {
|
|
@@ -3158,7 +3249,7 @@ function Model({
|
|
|
3158
3249
|
}
|
|
3159
3250
|
currentExplodeRef.current = 0;
|
|
3160
3251
|
scene.updateWorldMatrix(true, true);
|
|
3161
|
-
const sceneBox = new
|
|
3252
|
+
const sceneBox = new Box32().setFromObject(scene);
|
|
3162
3253
|
const modelCenter = sceneBox.getCenter(new Vector32());
|
|
3163
3254
|
const radius = sceneBox.getSize(new Vector32()).length() * 0.5;
|
|
3164
3255
|
explodeStrengthRef.current = Number.isFinite(radius) ? radius : 0;
|
|
@@ -3184,7 +3275,7 @@ function Model({
|
|
|
3184
3275
|
ancestor = ancestor.parent;
|
|
3185
3276
|
}
|
|
3186
3277
|
if (nested) continue;
|
|
3187
|
-
const box = new
|
|
3278
|
+
const box = new Box32().setFromObject(node);
|
|
3188
3279
|
if (box.isEmpty()) continue;
|
|
3189
3280
|
box.getCenter(partCenter);
|
|
3190
3281
|
const worldDir = partCenter.clone().sub(modelCenter);
|
|
@@ -3263,13 +3354,21 @@ function Model({
|
|
|
3263
3354
|
setHoveredItem(null);
|
|
3264
3355
|
onItemHover?.(null);
|
|
3265
3356
|
};
|
|
3357
|
+
const pointerRaycastEnabled = useMemo7(
|
|
3358
|
+
() => !exceedsBvhBudget(scene),
|
|
3359
|
+
[scene]
|
|
3360
|
+
);
|
|
3266
3361
|
return /* @__PURE__ */ jsx8("group", { ref: groupRef, children: /* @__PURE__ */ jsx8(
|
|
3267
3362
|
"primitive",
|
|
3268
3363
|
{
|
|
3269
3364
|
object: scene,
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3365
|
+
...pointerRaycastEnabled ? {
|
|
3366
|
+
onClick: handleClick,
|
|
3367
|
+
...highlightOnHover ? {
|
|
3368
|
+
onPointerOver: handlePointerOver,
|
|
3369
|
+
onPointerOut: handlePointerOut
|
|
3370
|
+
} : {}
|
|
3371
|
+
} : {}
|
|
3273
3372
|
}
|
|
3274
3373
|
) });
|
|
3275
3374
|
}
|
|
@@ -4010,7 +4109,7 @@ var XRModelPresenter_default = XRModelPresenter;
|
|
|
4010
4109
|
import { useEffect as useEffect11, useMemo as useMemo11, useRef as useRef8 } from "react";
|
|
4011
4110
|
import { useFrame as useFrame7, useThree as useThree9 } from "@react-three/fiber";
|
|
4012
4111
|
import {
|
|
4013
|
-
Box3 as
|
|
4112
|
+
Box3 as Box33,
|
|
4014
4113
|
CatmullRomCurve3,
|
|
4015
4114
|
Sphere,
|
|
4016
4115
|
Vector3 as Vector36
|
|
@@ -4073,7 +4172,7 @@ function CinematicCamera({
|
|
|
4073
4172
|
if (!hasPath) {
|
|
4074
4173
|
const scene = sceneRef.current;
|
|
4075
4174
|
if (scene) {
|
|
4076
|
-
const sphere = new
|
|
4175
|
+
const sphere = new Box33().setFromObject(scene).getBoundingSphere(new Sphere());
|
|
4077
4176
|
orbitRef.current = {
|
|
4078
4177
|
center: sphere.center.clone(),
|
|
4079
4178
|
radius: sphere.radius > 0 ? sphere.radius : 1
|
|
@@ -5313,7 +5412,7 @@ var DEFAULT_AR_OVERLAY_STATE = {
|
|
|
5313
5412
|
isModelPlaced: false
|
|
5314
5413
|
};
|
|
5315
5414
|
function computeMeasureBox(scene) {
|
|
5316
|
-
const box = new
|
|
5415
|
+
const box = new Box34().setFromObject(scene);
|
|
5317
5416
|
const size = new Vector37();
|
|
5318
5417
|
box.getSize(size);
|
|
5319
5418
|
return {
|
|
@@ -5323,7 +5422,7 @@ function computeMeasureBox(scene) {
|
|
|
5323
5422
|
};
|
|
5324
5423
|
}
|
|
5325
5424
|
function computeModelBounds(scene) {
|
|
5326
|
-
const box = new
|
|
5425
|
+
const box = new Box34().setFromObject(scene);
|
|
5327
5426
|
if (box.isEmpty()) return null;
|
|
5328
5427
|
const center = box.getCenter(new Vector37());
|
|
5329
5428
|
const radius = box.getSize(new Vector37()).length() / 2;
|
|
@@ -5333,7 +5432,7 @@ function computeModelBounds(scene) {
|
|
|
5333
5432
|
function fitClippingPlanesToScene(scene, controls) {
|
|
5334
5433
|
const camera = controls?.camera;
|
|
5335
5434
|
if (!(camera instanceof PerspectiveCamera3)) return;
|
|
5336
|
-
const box = new
|
|
5435
|
+
const box = new Box34().setFromObject(scene);
|
|
5337
5436
|
const size = box.getSize(new Vector37()).length();
|
|
5338
5437
|
if (!Number.isFinite(size) || size <= 0) return;
|
|
5339
5438
|
camera.near = size / 100;
|
|
@@ -5481,6 +5580,11 @@ function ModelViewer({
|
|
|
5481
5580
|
const [modelLoadFailed, setModelLoadFailed] = useState10(false);
|
|
5482
5581
|
const [bindingsMismatch, setBindingsMismatch] = useState10(false);
|
|
5483
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...");
|
|
5484
5588
|
const currentObjectBindingsRef = useRef11({});
|
|
5485
5589
|
const captureInitialCameraState = useCallback4(() => {
|
|
5486
5590
|
const controls = controlsRef.current;
|
|
@@ -5761,7 +5865,7 @@ function ModelViewer({
|
|
|
5761
5865
|
controls.smoothTime = 0.6;
|
|
5762
5866
|
const storedNormal = annotation.worldNormal;
|
|
5763
5867
|
if (scene) {
|
|
5764
|
-
const box = new
|
|
5868
|
+
const box = new Box34().setFromObject(scene);
|
|
5765
5869
|
const modelCenter = new Vector37();
|
|
5766
5870
|
box.getCenter(modelCenter);
|
|
5767
5871
|
const modelSize = new Vector37();
|
|
@@ -6068,7 +6172,7 @@ function ModelViewer({
|
|
|
6068
6172
|
if (canvasContainer && cameraControls && object) {
|
|
6069
6173
|
const canvasRect = canvasContainer.getBoundingClientRect();
|
|
6070
6174
|
const objectCenter = new Vector37();
|
|
6071
|
-
new
|
|
6175
|
+
new Box34().setFromObject(object).getCenter(objectCenter);
|
|
6072
6176
|
objectCenter.project(cameraControls);
|
|
6073
6177
|
const projectedX = (objectCenter.x + 1) / 2 * canvasRect.width + (canvasRect.left - rect.left);
|
|
6074
6178
|
const projectedY = (1 - objectCenter.y) / 2 * canvasRect.height + (canvasRect.top - rect.top);
|
|
@@ -6299,7 +6403,7 @@ function ModelViewer({
|
|
|
6299
6403
|
direction.set(0, 0, 1);
|
|
6300
6404
|
}
|
|
6301
6405
|
direction.normalize();
|
|
6302
|
-
const sphere = new
|
|
6406
|
+
const sphere = new Box34().setFromObject(scene).getBoundingSphere(new Sphere2());
|
|
6303
6407
|
const farDistance = Math.max(sphere.radius * 10, 20);
|
|
6304
6408
|
const farPosition = target.clone().add(direction.multiplyScalar(farDistance));
|
|
6305
6409
|
await controls.setLookAt(
|
|
@@ -6322,6 +6426,21 @@ function ModelViewer({
|
|
|
6322
6426
|
paddingRight: 0.5
|
|
6323
6427
|
})
|
|
6324
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
|
+
});
|
|
6325
6444
|
fitAttemptedRef.current = true;
|
|
6326
6445
|
captureInitialCameraState();
|
|
6327
6446
|
notifyViewerReady();
|
|
@@ -6342,6 +6461,7 @@ function ModelViewer({
|
|
|
6342
6461
|
setModelLoaded(false);
|
|
6343
6462
|
setModelLoadFailed(false);
|
|
6344
6463
|
setBindingsMismatch(false);
|
|
6464
|
+
setLoaderMessage("Loading model...");
|
|
6345
6465
|
if (!modelUrl) {
|
|
6346
6466
|
setIsAutoFraming(false);
|
|
6347
6467
|
setCameraPositioned(true);
|
|
@@ -6369,6 +6489,7 @@ function ModelViewer({
|
|
|
6369
6489
|
notifyViewerReady();
|
|
6370
6490
|
setIsAutoFraming(false);
|
|
6371
6491
|
setCameraPositioned(true);
|
|
6492
|
+
rendererCaptureRef.current?.invalidate();
|
|
6372
6493
|
return;
|
|
6373
6494
|
}
|
|
6374
6495
|
fitInFlightRef.current = true;
|
|
@@ -6529,6 +6650,14 @@ function ModelViewer({
|
|
|
6529
6650
|
sceneRef.current = scene;
|
|
6530
6651
|
setMeasureBox(computeMeasureBox(scene));
|
|
6531
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
|
+
}
|
|
6532
6661
|
setModelLoaded(true);
|
|
6533
6662
|
setModelLoadFailed(false);
|
|
6534
6663
|
setMismatchDismissed(false);
|
|
@@ -6851,6 +6980,33 @@ function ModelViewer({
|
|
|
6851
6980
|
}
|
|
6852
6981
|
)
|
|
6853
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
|
+
),
|
|
6854
7010
|
/* @__PURE__ */ jsx19(
|
|
6855
7011
|
"input",
|
|
6856
7012
|
{
|
|
@@ -6984,13 +7140,7 @@ function ModelViewer({
|
|
|
6984
7140
|
]
|
|
6985
7141
|
}
|
|
6986
7142
|
),
|
|
6987
|
-
/* @__PURE__ */ jsx19(
|
|
6988
|
-
LoadingOverlay,
|
|
6989
|
-
{
|
|
6990
|
-
visible: showModelLoader,
|
|
6991
|
-
message: "Loading model..."
|
|
6992
|
-
}
|
|
6993
|
-
),
|
|
7143
|
+
/* @__PURE__ */ jsx19(LoadingOverlay, { visible: showModelLoader, message: loaderMessage }),
|
|
6994
7144
|
xrToolsVisible && /* @__PURE__ */ jsxs15(
|
|
6995
7145
|
"div",
|
|
6996
7146
|
{
|
|
@@ -14524,11 +14674,12 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14524
14674
|
},
|
|
14525
14675
|
[bindingsHistory, setSceneConfig]
|
|
14526
14676
|
);
|
|
14527
|
-
const generateBindingsForModel = (nextModelUrl, nextModelName) => {
|
|
14677
|
+
const generateBindingsForModel = (nextModelUrl, nextModelName, fileSizeBytes) => {
|
|
14528
14678
|
const requestId = ++loadRequestRef.current;
|
|
14529
14679
|
setLoading(true);
|
|
14680
|
+
const sizeMB = fileSizeBytes ? fileSizeBytes / 1048576 : 0;
|
|
14530
14681
|
setStatusMessage(
|
|
14531
|
-
`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...`
|
|
14532
14683
|
);
|
|
14533
14684
|
setStatusTone("default");
|
|
14534
14685
|
setModelName(nextModelName);
|
|
@@ -14598,7 +14749,11 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14598
14749
|
}
|
|
14599
14750
|
uploadedModelRef.current?.cleanup();
|
|
14600
14751
|
uploadedModelRef.current = resolvedModel;
|
|
14601
|
-
await generateBindingsForModel(
|
|
14752
|
+
await generateBindingsForModel(
|
|
14753
|
+
resolvedModel.url,
|
|
14754
|
+
resolvedModel.name,
|
|
14755
|
+
file.size
|
|
14756
|
+
);
|
|
14602
14757
|
};
|
|
14603
14758
|
const handleUploadModel = async (event) => {
|
|
14604
14759
|
const file = event.target.files?.[0];
|
|
@@ -15414,11 +15569,12 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15414
15569
|
onLoadError: handleViewerLoadError,
|
|
15415
15570
|
licenseKey,
|
|
15416
15571
|
sceneConfig,
|
|
15572
|
+
renderMode: "always",
|
|
15417
15573
|
disableZoom: false,
|
|
15418
15574
|
zoomOnSelected,
|
|
15419
15575
|
highlightOnHover,
|
|
15420
15576
|
panelHoveredObject: highlightOnHover ? panelHoveredBinding : null,
|
|
15421
|
-
|
|
15577
|
+
showLoadingOverlay: false
|
|
15422
15578
|
}
|
|
15423
15579
|
) : /* @__PURE__ */ jsx45(
|
|
15424
15580
|
"div",
|
|
@@ -15503,14 +15659,14 @@ import { CameraControls as CameraControls2, Environment as Environment2 } from "
|
|
|
15503
15659
|
import { useFrame as useFrame9, useThree as useThree11 } from "@react-three/fiber";
|
|
15504
15660
|
import { Canvas as Canvas3 } from "@react-three/fiber";
|
|
15505
15661
|
import {
|
|
15506
|
-
Box3 as
|
|
15662
|
+
Box3 as Box35,
|
|
15507
15663
|
PerspectiveCamera as PerspectiveCamera4,
|
|
15508
15664
|
Vector3 as Vector39
|
|
15509
15665
|
} from "three";
|
|
15510
15666
|
import { SkeletonUtils as SkeletonUtils2 } from "three-stdlib";
|
|
15511
15667
|
|
|
15512
15668
|
// src/styles/SimpleModelViewer.css
|
|
15513
|
-
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');
|
|
15514
15670
|
|
|
15515
15671
|
// src/components/SimpleModelViewer/SimpleModelViewer.tsx
|
|
15516
15672
|
import {
|
|
@@ -15520,7 +15676,8 @@ import {
|
|
|
15520
15676
|
ChevronDown as ChevronDown6,
|
|
15521
15677
|
Boxes as Boxes2,
|
|
15522
15678
|
Trash2 as Trash26,
|
|
15523
|
-
Upload as Upload2
|
|
15679
|
+
Upload as Upload2,
|
|
15680
|
+
AlertTriangle as AlertTriangle4
|
|
15524
15681
|
} from "lucide-react";
|
|
15525
15682
|
import { Fragment as Fragment14, jsx as jsx46, jsxs as jsxs41 } from "react/jsx-runtime";
|
|
15526
15683
|
var HIGHLIGHT2 = { color: "#93c5fd", intensity: 0.05 };
|
|
@@ -15543,7 +15700,7 @@ function applyClippingPlanes(scene, controls) {
|
|
|
15543
15700
|
if (!controls) return;
|
|
15544
15701
|
const camera = controls.camera;
|
|
15545
15702
|
if (!(camera instanceof PerspectiveCamera4)) return;
|
|
15546
|
-
const size = new
|
|
15703
|
+
const size = new Box35().setFromObject(scene).getSize(new Vector39()).length();
|
|
15547
15704
|
if (!Number.isFinite(size) || size <= 0) return;
|
|
15548
15705
|
camera.near = size / 100;
|
|
15549
15706
|
camera.far = size * 100;
|
|
@@ -15627,6 +15784,18 @@ function InnerModel({
|
|
|
15627
15784
|
onMeshesReady(meshMap);
|
|
15628
15785
|
onModelLoaded(scene);
|
|
15629
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
|
+
);
|
|
15630
15799
|
useEffect21(() => {
|
|
15631
15800
|
for (const [key, { ref }] of Object.entries(meshMap)) {
|
|
15632
15801
|
nodeRefsOut.current[key] = ref;
|
|
@@ -15682,18 +15851,15 @@ function InnerModel({
|
|
|
15682
15851
|
}
|
|
15683
15852
|
return null;
|
|
15684
15853
|
};
|
|
15685
|
-
|
|
15686
|
-
|
|
15687
|
-
|
|
15688
|
-
|
|
15689
|
-
|
|
15690
|
-
|
|
15691
|
-
|
|
15692
|
-
|
|
15693
|
-
onMeshClick(key);
|
|
15694
|
-
},
|
|
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 ? {
|
|
15695
15862
|
onPointerOver: (e) => {
|
|
15696
|
-
if (!highlightOnHover) return;
|
|
15697
15863
|
const key = resolveKey(e.object);
|
|
15698
15864
|
if (!key) return;
|
|
15699
15865
|
e.stopPropagation();
|
|
@@ -15701,13 +15867,13 @@ function InnerModel({
|
|
|
15701
15867
|
setHoveredKey(key);
|
|
15702
15868
|
},
|
|
15703
15869
|
onPointerOut: (e) => {
|
|
15704
|
-
if (!highlightOnHover) return;
|
|
15705
15870
|
e.stopPropagation();
|
|
15706
15871
|
document.body.style.cursor = "default";
|
|
15707
15872
|
setHoveredKey(null);
|
|
15708
15873
|
}
|
|
15709
|
-
}
|
|
15710
|
-
|
|
15874
|
+
} : {}
|
|
15875
|
+
} : {};
|
|
15876
|
+
return /* @__PURE__ */ jsx46("primitive", { object: scene, ...pointerHandlers });
|
|
15711
15877
|
}
|
|
15712
15878
|
function CameraFitter({
|
|
15713
15879
|
scene,
|
|
@@ -16171,6 +16337,10 @@ function SimpleModelViewer({
|
|
|
16171
16337
|
const uploadedModelRef = useRef19(null);
|
|
16172
16338
|
const dragDepthRef = useRef19(0);
|
|
16173
16339
|
const [loadedScene, setLoadedScene] = useState25(null);
|
|
16340
|
+
const [largeModelTriangles, setLargeModelTriangles] = useState25(
|
|
16341
|
+
null
|
|
16342
|
+
);
|
|
16343
|
+
const [largeModelNoticeDismissed, setLargeModelNoticeDismissed] = useState25(false);
|
|
16174
16344
|
const [meshKeys, setMeshKeys] = useState25({});
|
|
16175
16345
|
const [hiddenKeys, setHiddenKeys] = useState25(/* @__PURE__ */ new Set());
|
|
16176
16346
|
const [selectedKey, setSelectedKey] = useState25(null);
|
|
@@ -16203,6 +16373,8 @@ function SimpleModelViewer({
|
|
|
16203
16373
|
const activeModelUrl = enableModelUpload ? uploadedModelUrl : modelUrl;
|
|
16204
16374
|
const resetModelState = useCallback9(() => {
|
|
16205
16375
|
setLoadedScene(null);
|
|
16376
|
+
setLargeModelTriangles(null);
|
|
16377
|
+
setLargeModelNoticeDismissed(false);
|
|
16206
16378
|
setMeshKeys({});
|
|
16207
16379
|
setHiddenKeys(/* @__PURE__ */ new Set());
|
|
16208
16380
|
setSelectedKey(null);
|
|
@@ -16296,6 +16468,9 @@ function SimpleModelViewer({
|
|
|
16296
16468
|
(scene) => {
|
|
16297
16469
|
setLoadedScene(scene);
|
|
16298
16470
|
setModelLoadFailed(false);
|
|
16471
|
+
const triangles = countSceneTriangles(scene);
|
|
16472
|
+
setLargeModelTriangles(triangles > MAX_BVH_TRIANGLES ? triangles : null);
|
|
16473
|
+
setLargeModelNoticeDismissed(false);
|
|
16299
16474
|
onModelLoaded?.(scene);
|
|
16300
16475
|
},
|
|
16301
16476
|
[onModelLoaded]
|
|
@@ -16404,6 +16579,24 @@ function SimpleModelViewer({
|
|
|
16404
16579
|
}
|
|
16405
16580
|
),
|
|
16406
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
|
+
] }),
|
|
16407
16600
|
enableModelUpload ? /* @__PURE__ */ jsxs41(Fragment14, { children: [
|
|
16408
16601
|
/* @__PURE__ */ jsx46(
|
|
16409
16602
|
"input",
|
|
@@ -16438,7 +16631,9 @@ function SimpleModelViewer({
|
|
|
16438
16631
|
Canvas3,
|
|
16439
16632
|
{
|
|
16440
16633
|
dpr: [1, 2],
|
|
16634
|
+
frameloop: "demand",
|
|
16441
16635
|
resize: { debounce: 0, scroll: false },
|
|
16636
|
+
raycaster: { firstHitOnly: true },
|
|
16442
16637
|
style: { background: envSettings.backgroundColor },
|
|
16443
16638
|
camera: { position: [0, 0, 5], fov: 45, near: 0.1, far: 1e3 },
|
|
16444
16639
|
children: /* @__PURE__ */ jsxs41(Suspense3, { fallback: null, children: [
|
|
@@ -16729,7 +16924,7 @@ function useViewerHover() {
|
|
|
16729
16924
|
|
|
16730
16925
|
// src/hooks/useViewerModel.ts
|
|
16731
16926
|
import { useCallback as useCallback13, useState as useState28 } from "react";
|
|
16732
|
-
import { Box3 as
|
|
16927
|
+
import { Box3 as Box36, Vector3 as Vector310 } from "three";
|
|
16733
16928
|
function toTuple2(vector) {
|
|
16734
16929
|
return [vector.x, vector.y, vector.z];
|
|
16735
16930
|
}
|
|
@@ -16740,7 +16935,7 @@ function useViewerModel() {
|
|
|
16740
16935
|
const [bounds, setBounds] = useState28(null);
|
|
16741
16936
|
const [objectCount, setObjectCount] = useState28(0);
|
|
16742
16937
|
const handleModelLoaded = useCallback13((scene) => {
|
|
16743
|
-
const box = new
|
|
16938
|
+
const box = new Box36().setFromObject(scene);
|
|
16744
16939
|
const min = box.min.clone();
|
|
16745
16940
|
const max = box.max.clone();
|
|
16746
16941
|
const center = new Vector310();
|