dsh-3d-viewer 0.1.0 → 0.2.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.
Files changed (47) hide show
  1. package/README.md +180 -107
  2. package/README.zh.md +180 -107
  3. package/THIRD_PARTY_NOTICES.md +54 -52
  4. package/assets/hdri/LICENSE.md +17 -16
  5. package/dist/client.fbx.js +14 -0
  6. package/dist/client.gltf.js +14 -0
  7. package/dist/client.js +4472 -0
  8. package/dist/client.obj.js +19 -0
  9. package/dist/client.stl.js +14 -0
  10. package/dist/client.tex-exr.js +14 -0
  11. package/dist/client.tex-hdr.js +18 -0
  12. package/dist/client.tex-tga.js +14 -0
  13. package/index.js +167 -7
  14. package/package.json +72 -48
  15. package/assets/preview.png +0 -0
  16. package/build.mjs +0 -49
  17. package/client.js +0 -4462
  18. package/scripts/embed-hdri.mjs +0 -70
  19. package/src/bytes.js +0 -158
  20. package/src/client.js +0 -57
  21. package/src/formats.js +0 -100
  22. package/src/generated/hdri-data.js +0 -8
  23. package/src/loaders/gltf.js +0 -251
  24. package/src/loaders/model-loader.js +0 -232
  25. package/src/loaders/workspace.js +0 -295
  26. package/src/model.js +0 -417
  27. package/src/scene/animation.js +0 -149
  28. package/src/scene/blue-noise.js +0 -48
  29. package/src/scene/camera.js +0 -83
  30. package/src/scene/createSceneView.js +0 -375
  31. package/src/scene/environment.js +0 -187
  32. package/src/scene/ground.js +0 -286
  33. package/src/scene/lighting.js +0 -41
  34. package/src/scene/materials.js +0 -263
  35. package/src/scene/postprocessing.js +0 -155
  36. package/src/scene/render-loop.js +0 -98
  37. package/src/scene/viewer-error.js +0 -16
  38. package/src/ui/AnimationBar.js +0 -95
  39. package/src/ui/ModelViewer.js +0 -278
  40. package/src/ui/ViewerSurface.js +0 -87
  41. package/src/ui/chrome.js +0 -220
  42. package/src/ui/dictionaries.js +0 -99
  43. package/src/ui/icons.js +0 -88
  44. package/src/ui/options.js +0 -23
  45. package/src/ui/primitives.js +0 -88
  46. package/src/ui/styles.js +0 -89
  47. package/src/vendor/FBXLoaderPbr.js +0 -4772
package/src/ui/icons.js DELETED
@@ -1,88 +0,0 @@
1
- import * as React from 'react';
2
-
3
- const h = React.createElement;
4
-
5
- export function CubeIcon() {
6
- return h('svg', {
7
- viewBox: '0 0 16 16',
8
- fill: 'none',
9
- stroke: 'currentColor',
10
- strokeWidth: 1.3,
11
- strokeLinejoin: 'round',
12
- 'aria-hidden': true,
13
- },
14
- h('path', { d: 'M8 1.8 14 5.2v5.6L8 14.2 2 10.8V5.2Z' }),
15
- h('path', { d: 'M2.3 5.4 8 8.6l5.7-3.2M8 8.6v5.2' }));
16
- }
17
-
18
- export function FrameIcon() {
19
- return h('svg', {
20
- viewBox: '0 0 16 16',
21
- fill: 'none',
22
- stroke: 'currentColor',
23
- strokeWidth: 1.4,
24
- strokeLinecap: 'round',
25
- 'aria-hidden': true,
26
- },
27
- h('path', { d: 'M6 2H2v4M10 2h4v4M6 14H2v-4M10 14h4v-4' }),
28
- h('circle', { cx: 8, cy: 8, r: 2.1 }));
29
- }
30
-
31
- export function GridIcon() {
32
- return h('svg', {
33
- viewBox: '0 0 16 16',
34
- fill: 'none',
35
- stroke: 'currentColor',
36
- strokeWidth: 1.3,
37
- strokeLinecap: 'round',
38
- 'aria-hidden': true,
39
- },
40
- h('path', { d: 'M2 6h12M2 10h12M6 2v12M10 2v12' }),
41
- h('rect', { x: 2, y: 2, width: 12, height: 12, rx: 1.5 }));
42
- }
43
-
44
- export function OrbitIcon() {
45
- return h('svg', {
46
- viewBox: '0 0 16 16',
47
- fill: 'none',
48
- stroke: 'currentColor',
49
- strokeWidth: 1.35,
50
- strokeLinecap: 'round',
51
- strokeLinejoin: 'round',
52
- 'aria-hidden': true,
53
- },
54
- h('ellipse', { cx: 8, cy: 8, rx: 6.2, ry: 3.1, transform: 'rotate(-28 8 8)' }),
55
- h('path', { d: 'm12.4 3.9 1.3 2.4-2.7.2' }),
56
- h('circle', { cx: 8, cy: 8, r: 1.7 }));
57
- }
58
-
59
- export function WireframeIcon() {
60
- return h('svg', {
61
- viewBox: '0 0 16 16',
62
- fill: 'none',
63
- stroke: 'currentColor',
64
- strokeWidth: 1.2,
65
- strokeLinejoin: 'round',
66
- 'aria-hidden': true,
67
- },
68
- h('path', { d: 'M8 1.8 14 5.2v5.6L8 14.2 2 10.8V5.2Z' }),
69
- h('path', { d: 'M2 5.2 8 8.6l6-3.4M8 8.6v5.6M2 10.8 8 8.6l6 2.2' }));
70
- }
71
-
72
- export function PlayIcon() {
73
- return h('svg', {
74
- viewBox: '0 0 16 16',
75
- fill: 'currentColor',
76
- 'aria-hidden': true,
77
- }, h('path', { d: 'M5.2 3.3v9.4L13.1 8Z' }));
78
- }
79
-
80
- export function PauseIcon() {
81
- return h('svg', {
82
- viewBox: '0 0 16 16',
83
- fill: 'currentColor',
84
- 'aria-hidden': true,
85
- },
86
- h('rect', { x: 4.1, y: 3.2, width: 2.7, height: 9.6, rx: 0.7 }),
87
- h('rect', { x: 9.2, y: 3.2, width: 2.7, height: 9.6, rx: 0.7 }));
88
- }
package/src/ui/options.js DELETED
@@ -1,23 +0,0 @@
1
- export const MATERIAL_OPTIONS = Object.freeze([
2
- { id: 'original', labelKey: 'materialOriginal', className: 'dsh3dv-ball--original' },
3
- { id: 'diffuse', labelKey: 'materialDiffuse', className: 'dsh3dv-ball--diffuse' },
4
- { id: 'plastic', labelKey: 'materialPlastic', className: 'dsh3dv-ball--plastic' },
5
- { id: 'metal', labelKey: 'materialMetal', className: 'dsh3dv-ball--metal' },
6
- { id: 'metal-polished', labelKey: 'materialMetalPolished', className: 'dsh3dv-ball--polished' },
7
- { id: 'clay', labelKey: 'materialClay', className: 'dsh3dv-ball--clay' },
8
- ]);
9
-
10
- export const ENVIRONMENT_OPTIONS = Object.freeze([
11
- { id: 'nature', labelKey: 'envNature', className: 'dsh3dv-env--nature' },
12
- { id: 'city', labelKey: 'envCity', className: 'dsh3dv-env--city' },
13
- { id: 'indoor', labelKey: 'envIndoor', className: 'dsh3dv-env--indoor' },
14
- { id: 'studio', labelKey: 'envStudio', className: 'dsh3dv-env--studio' },
15
- ]);
16
-
17
- export const DEFAULT_SETTINGS = Object.freeze({
18
- grid: true,
19
- autoRotate: false,
20
- wireframe: false,
21
- material: 'original',
22
- environment: 'nature',
23
- });
@@ -1,88 +0,0 @@
1
- import * as React from 'react';
2
-
3
- const h = React.createElement;
4
-
5
- const COUNT_FORMATTER = (() => {
6
- try {
7
- return new Intl.NumberFormat(undefined, { maximumFractionDigits: 0 });
8
- } catch {
9
- return null;
10
- }
11
- })();
12
-
13
- /**
14
- * Format a mesh/vertex/triangle count once with a shared formatter.
15
- *
16
- * @param {number} value Count to format.
17
- * @returns {string} Localized count.
18
- */
19
- export function formatCount(value) {
20
- return COUNT_FORMATTER === null ? String(value) : COUNT_FORMATTER.format(value);
21
- }
22
-
23
- /**
24
- * Format a byte count using binary units.
25
- *
26
- * @param {number} bytes Byte count.
27
- * @returns {string} Localized size such as `1.36 KB` or `2.52 MB`.
28
- */
29
- export function formatBytes(bytes) {
30
- const value = Number(bytes);
31
- if (!Number.isFinite(value) || value < 0) return '—';
32
- if (value < 1024) return `${formatCount(Math.round(value))} B`;
33
-
34
- const units = ['KB', 'MB', 'GB', 'TB'];
35
- let scaled = value;
36
- let unit = -1;
37
- do {
38
- scaled /= 1024;
39
- unit += 1;
40
- } while (scaled >= 1024 && unit < units.length - 1);
41
-
42
- const digits = scaled >= 100 ? 0 : scaled >= 10 ? 1 : 2;
43
- return `${scaled.toFixed(digits)} ${units[unit]}`;
44
- }
45
-
46
- /**
47
- * Format an animation time with two decimals under ten seconds.
48
- *
49
- * @param {number} value Seconds.
50
- * @returns {string} Formatted seconds.
51
- */
52
- export function formatSeconds(value) {
53
- const seconds = Number(value);
54
- if (!Number.isFinite(seconds) || seconds < 0) return '0.00';
55
- return seconds.toFixed(seconds < 10 ? 2 : 1);
56
- }
57
-
58
- /**
59
- * Map a viewer failure to a localized sentence.
60
- *
61
- * @param {{ code?: string } | null} failure Failure state.
62
- * @param {(key: string) => string} t Translator.
63
- * @returns {string} Localized message.
64
- */
65
- export function errorText(failure, t) {
66
- if (failure === null || failure === undefined) return t('renderFailed');
67
- if (failure.code === 'webgl') return t('webglUnavailable');
68
- if (failure.code === 'parse') return t('parseFailed');
69
- return t('renderFailed');
70
- }
71
-
72
- export function ToolButton({ label, active, onClick, children }) {
73
- const props = {
74
- type: 'button',
75
- className: 'dsh3dv-tool',
76
- title: label,
77
- 'aria-label': label,
78
- onClick,
79
- };
80
- if (active !== undefined) props['aria-pressed'] = active;
81
- return h('button', props, children);
82
- }
83
-
84
- export function StatRow({ label, value }) {
85
- return h('div', { className: 'dsh3dv-stat' },
86
- h('span', { className: 'dsh3dv-stat-label' }, label),
87
- h('span', { className: 'dsh3dv-stat-value' }, value));
88
- }
package/src/ui/styles.js DELETED
@@ -1,89 +0,0 @@
1
- export const CSS = `
2
- .dsh3dv-root{position:relative;box-sizing:border-box;width:100%;height:100%;min-height:280px;overflow:hidden;isolation:isolate;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family,"Segoe UI Variable Text","Segoe UI",Inter,-apple-system,BlinkMacSystemFont,"SF Pro Text","Helvetica Neue",Arial,"Microsoft YaHei",sans-serif);white-space:normal;background:#fff}
3
- .dsh3dv-stage{position:absolute;inset:0}
4
- .dsh3dv-canvas{display:block;width:100%;height:100%;outline:none;touch-action:none;cursor:grab}
5
- .dsh3dv-canvas:active{cursor:grabbing}
6
- .dsh3dv-canvas:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}
7
- .dsh3dv-top{position:absolute;top:10px;right:10px;left:10px;z-index:2;display:flex;align-items:flex-start;justify-content:space-between;gap:8px;pointer-events:none}
8
- .dsh3dv-top-actions{display:flex;align-items:center;gap:6px;min-width:0;pointer-events:none}
9
- .dsh3dv-badge,.dsh3dv-stats,.dsh3dv-hint{pointer-events:none}
10
- .dsh3dv-toolbar{pointer-events:auto}
11
- .dsh3dv-badge{display:inline-flex;align-items:center;gap:5px;padding:4px 8px 4px 7px;border:1px solid var(--dsw-alias-border-l1);border-radius:999px;background:var(--dsw-alias-bg-overlay);color:var(--dsw-alias-label-secondary);font-size:11px;font-weight:600;letter-spacing:.03em;line-height:1;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}
12
- .dsh3dv-badge svg{width:13px;height:13px}
13
- .dsh3dv-toolbar{display:flex;gap:2px;padding:3px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-overlay);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}
14
- .dsh3dv-tool{display:grid;place-items:center;width:28px;height:28px;padding:0;border:0;border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}
15
- .dsh3dv-tool:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}
16
- .dsh3dv-tool[aria-pressed="true"]{color:var(--dsw-alias-brand-primary);background:var(--dsw-alias-bg-layer-2)}
17
- .dsh3dv-tool:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
18
- .dsh3dv-tool svg{width:15px;height:15px}
19
- .dsh3dv-hud{position:absolute;top:46px;left:10px;z-index:2;display:flex;flex-direction:column;align-items:flex-start;gap:6px;max-width:calc(100% - 20px);pointer-events:none}
20
- .dsh3dv-stats{display:flex;flex-direction:column;gap:2px;min-width:146px;max-width:100%;padding:7px 9px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;background:var(--dsw-alias-bg-overlay);color:var(--dsw-alias-label-secondary);font-size:11px;line-height:1.45;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}
21
- .dsh3dv-stat{display:flex;align-items:baseline;justify-content:space-between;gap:16px;white-space:nowrap}
22
- .dsh3dv-stat-label{color:var(--dsw-alias-label-secondary)}
23
- .dsh3dv-stat-value{color:var(--dsw-alias-label-primary);font-weight:600;font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1}
24
- .dsh3dv-view-controls{position:relative;display:flex;flex-direction:row;align-items:center;gap:4px;max-width:none;pointer-events:auto}
25
- .dsh3dv-picker{position:relative;min-width:0}
26
- .dsh3dv-picker-trigger{display:flex;align-items:center;justify-content:center;gap:0;width:34px;height:34px;padding:3px;border:0;border-radius:9px;background:var(--dsw-alias-bg-overlay);color:var(--dsw-alias-label-primary);font:inherit;text-align:left;white-space:nowrap;cursor:pointer;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}
27
- .dsh3dv-picker-trigger:hover{background:var(--dsw-alias-bg-layer-2)}
28
- .dsh3dv-picker-trigger:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
29
- .dsh3dv-picker-trigger[aria-expanded="true"]{background:var(--dsw-alias-bg-layer-2)}
30
- .dsh3dv-picker-swatch{flex:none;box-sizing:border-box;pointer-events:none}
31
- .dsh3dv-picker-swatch.dsh3dv-ball{width:24px;height:24px}
32
- .dsh3dv-picker-swatch.dsh3dv-env{width:24px;height:24px;border-radius:6px}
33
- .dsh3dv-picker-menu{position:absolute;top:calc(100% + 6px);right:0;left:auto;z-index:5;display:flex;flex-wrap:nowrap;gap:4px;width:max-content;max-width:300px;box-sizing:border-box;padding:7px;border:1px solid var(--dsw-alias-border-l1);border-radius:10px;background:var(--dsw-alias-bg-overlay);box-shadow:0 8px 22px rgba(20,28,40,.16);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px)}
34
- .dsh3dv-picker-option{display:flex;flex:none;flex-direction:column;align-items:center;gap:4px;width:42px;padding:3px;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer}
35
- .dsh3dv-picker-option:hover{background:var(--dsw-alias-bg-layer-2)}
36
- .dsh3dv-picker-option:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
37
- .dsh3dv-picker-option[aria-checked="true"]{background:var(--dsw-alias-bg-layer-2)}
38
- .dsh3dv-picker-option-swatch{box-sizing:border-box;pointer-events:none}
39
- .dsh3dv-picker-option-swatch.dsh3dv-ball,.dsh3dv-picker-option-swatch.dsh3dv-env{width:28px;height:28px}
40
- .dsh3dv-picker-option-swatch.dsh3dv-env{border-radius:7px}
41
- .dsh3dv-picker-option-label{max-width:42px;overflow:hidden;color:var(--dsw-alias-label-secondary);font-size:9px;line-height:1.25;text-align:center;white-space:nowrap;text-overflow:ellipsis}
42
- .dsh3dv-ball{position:relative;flex:none;width:30px;height:30px;padding:0;border:0;border-radius:50%;cursor:pointer;box-shadow:inset 2px 3px 6px rgba(255,255,255,.35);transition:transform .12s ease}
43
- .dsh3dv-ball::after{content:"";position:absolute;left:26%;top:17%;width:34%;height:21%;border-radius:50%;background:rgba(255,255,255,.5);filter:blur(1px);transform:rotate(-24deg);pointer-events:none}
44
- .dsh3dv-ball--original{background:radial-gradient(circle at 32% 26%,#fff7ec 0%,#e7cdb8 18%,#9ea6b8 48%,#4f5d70 78%,#5a6676 100%)}
45
- .dsh3dv-ball--diffuse{background:radial-gradient(circle at 32% 26%,#ffffff 0%,#ececeb 30%,#c9c9c5 68%,#918f8a 100%)}
46
- .dsh3dv-ball--plastic{background:radial-gradient(circle at 30% 22%,#ffffff 0%,#ffffff 8%,#eef4fa 18%,#c9d5e2 45%,#8997a8 78%,#b9c6d5 100%)}
47
- .dsh3dv-ball--metal{background:linear-gradient(145deg,#f8fbff 0%,#dce6f2 16%,#77879b 38%,#1d2632 56%,#0a0f16 66%,#a9bbcF 82%,#ffffff 100%)}
48
- .dsh3dv-ball--polished{background:radial-gradient(circle at 28% 22%,#ffffff 0%,#eef3fa 10%,#b6c3d3 28%,#5d6c80 58%,#3f4b5c 100%)}
49
- .dsh3dv-ball--clay{background:radial-gradient(circle at 32% 26%,#f3d6c2 0%,#dfa98a 30%,#b46f52 64%,#7f4635 100%)}
50
- .dsh3dv-env{flex:none;width:32px;height:32px;padding:0;border:0;border-radius:8px;box-shadow:inset 0 0 0 1px rgba(255,255,255,.18)}
51
- .dsh3dv-env--nature{background:radial-gradient(circle at 72% 24%,#fff6c8 0 9%,transparent 10%),linear-gradient(180deg,#7cb9e4 0%,#d9ebf3 46%,#758653 47%,#435535 100%)}
52
- .dsh3dv-env--city{background:linear-gradient(180deg,#e2b27d 0%,#a4adb7 43%,#65717f 48%,#3f4a58 100%)}
53
- .dsh3dv-env--indoor{background:linear-gradient(135deg,#f2e6d7 0%,#d2b9a3 38%,#94745e 70%,#7c604f 100%)}
54
- .dsh3dv-env--studio{background:linear-gradient(135deg,#f7fbff 0%,#c2cedd 30%,#8393a7 58%,#4d5969 100%)}
55
- .dsh3dv-hint{position:absolute;bottom:10px;left:50%;z-index:2;max-width:calc(100% - 20px);overflow:hidden;padding:5px 9px;border:1px solid var(--dsw-alias-border-l1);border-radius:999px;background:var(--dsw-alias-bg-overlay);color:var(--dsw-alias-label-secondary);font-size:11px;line-height:1.2;white-space:nowrap;text-overflow:ellipsis;transform:translateX(-50%);pointer-events:none;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}
56
- .dsh3dv-anim{position:absolute;bottom:42px;left:50%;z-index:2;display:flex;align-items:center;gap:5px;box-sizing:border-box;max-width:calc(100% - 20px);padding:3px 8px 3px 3px;border:1px solid var(--dsw-alias-border-l1);border-radius:999px;background:var(--dsw-alias-bg-overlay);color:var(--dsw-alias-label-primary);font-size:11px;line-height:1.2;transform:translateX(-50%);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);pointer-events:auto}
57
- .dsh3dv-play{display:grid;place-items:center;flex:none;width:24px;height:24px;padding:0;border:0;border-radius:50%;background:transparent;color:var(--dsw-alias-label-primary);cursor:pointer}
58
- .dsh3dv-play:hover{background:var(--dsw-alias-bg-layer-2)}
59
- .dsh3dv-play:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
60
- .dsh3dv-play svg{width:12px;height:12px}
61
- .dsh3dv-anim-name{min-width:0;max-width:110px;overflow:hidden;color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis}
62
- .dsh3dv-anim-select{min-width:0;max-width:120px;border:0;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:11px;cursor:pointer;outline:none}
63
- .dsh3dv-anim-select:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
64
- .dsh3dv-progress{flex:1 1 64px;min-width:40px;max-width:160px;height:12px;margin:0;-webkit-appearance:none;appearance:none;background:transparent;cursor:pointer}
65
- .dsh3dv-progress:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
66
- .dsh3dv-progress::-webkit-slider-runnable-track{height:2px;border-radius:999px;background:var(--dsw-alias-border-l2)}
67
- .dsh3dv-progress::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:9px;height:9px;margin-top:-3.5px;border:0;border-radius:50%;background:var(--dsw-alias-label-secondary)}
68
- .dsh3dv-progress::-moz-range-track{height:2px;border-radius:999px;background:var(--dsw-alias-border-l2)}
69
- .dsh3dv-progress::-moz-range-thumb{width:9px;height:9px;border:0;border-radius:50%;background:var(--dsw-alias-label-secondary)}
70
- .dsh3dv-anim-time{flex:none;color:var(--dsw-alias-label-secondary);font-size:10px;font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1;white-space:nowrap}
71
- .dsh3dv-overlay{position:absolute;inset:0;z-index:3;display:grid;place-items:center;padding:20px;box-sizing:border-box;pointer-events:none}
72
- .dsh3dv-panel{box-sizing:border-box;width:min(320px,100%);padding:16px;border:1px solid var(--dsw-alias-border-l1);border-radius:12px;background:var(--dsw-alias-bg-overlay);color:var(--dsw-alias-label-primary);text-align:center;pointer-events:auto;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px)}
73
- .dsh3dv-panel--loading{display:flex;align-items:center;gap:9px;width:auto;max-width:100%;padding:9px 12px;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.3}
74
- .dsh3dv-panel-icon{width:34px;height:34px;margin:0 auto 10px;color:var(--dsw-alias-label-secondary);opacity:.7}
75
- .dsh3dv-panel--error .dsh3dv-panel-icon{color:var(--dsw-alias-state-error-primary);opacity:1}
76
- .dsh3dv-panel-title{margin:0 0 5px;font-size:13px;font-weight:600;line-height:1.35}
77
- .dsh3dv-panel-text{margin:0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.55;white-space:normal;word-break:break-word}
78
- .dsh3dv-actions{display:flex;justify-content:center;margin-top:12px}
79
- .dsh3dv-action{box-sizing:border-box;padding:6px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:7px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-family:inherit;font-size:12px;line-height:1.2;cursor:pointer}
80
- .dsh3dv-action:hover{background:var(--dsw-alias-bg-layer-1)}
81
- .dsh3dv-action:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}
82
- .dsh3dv-details{margin-top:10px;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:1.4;text-align:left}
83
- .dsh3dv-details summary{cursor:pointer;user-select:none}
84
- .dsh3dv-details pre{max-height:140px;margin:8px 0 0;overflow:auto;padding:8px;border-radius:6px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);font-size:10px;line-height:1.4;white-space:pre-wrap;word-break:break-word}
85
- .dsh3dv-root button,.dsh3dv-root select,.dsh3dv-root option,.dsh3dv-root input,.dsh3dv-root textarea,.dsh3dv-root summary,.dsh3dv-root pre,.dsh3dv-root code{font-family:inherit}
86
- .dsh3dv-spinner{flex:none;width:14px;height:14px;border:2px solid var(--dsw-alias-border-l2);border-top-color:var(--dsw-alias-brand-primary);border-radius:50%;animation:dsh3dv-spin .8s linear infinite}
87
- @keyframes dsh3dv-spin{to{transform:rotate(360deg)}}
88
- @media (prefers-reduced-motion:reduce){.dsh3dv-spinner{animation-duration:2.4s}}
89
- `;