@preference-sl/pref-viewer 2.18.0-beta.1 → 3.0.0-beta.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 (35) hide show
  1. package/README.md +267 -31
  2. package/index.d.ts +75 -6
  3. package/package.json +8 -2
  4. package/src/babylonjs-animation-controller.js +18 -10
  5. package/src/babylonjs-animation-opening-menu.js +80 -14
  6. package/src/babylonjs-animation-opening.js +3 -3
  7. package/src/babylonjs-ar-floor-bounds.js +169 -0
  8. package/src/babylonjs-ar-glass.js +128 -0
  9. package/src/babylonjs-ar-placement-feedback.js +449 -0
  10. package/src/babylonjs-ar-placement.js +1038 -0
  11. package/src/babylonjs-camera-wheel-motion.js +35 -2
  12. package/src/babylonjs-controller.js +507 -125
  13. package/src/babylonjs-orbital-pointers-input.js +186 -0
  14. package/src/{render-math.js → babylonjs-render-math.js} +11 -11
  15. package/src/babylonjs-selection-controller.js +4 -4
  16. package/src/babylonjs-view-cube-canvas-controller.js +1287 -0
  17. package/src/babylonjs-view-cube-canvas-orbit-math.js +220 -0
  18. package/src/babylonjs-view-cube-canvas-renderer.js +775 -0
  19. package/src/babylonjs-view-cube-controller.js +74 -1103
  20. package/src/babylonjs-view-cube-svg-controller.js +1012 -0
  21. package/src/file-storage.js +7 -4
  22. package/src/gltf-resolver.js +13 -8
  23. package/src/localization/translations.js +12 -0
  24. package/src/panzoom-controller.js +4 -4
  25. package/src/pref-viewer-2d.js +2 -2
  26. package/src/pref-viewer-3d.js +55 -9
  27. package/src/pref-viewer-controls-presentation.js +292 -0
  28. package/src/pref-viewer-dialog.js +86 -7
  29. package/src/pref-viewer-menu-3d.js +134 -17
  30. package/src/pref-viewer-style-controller.js +320 -0
  31. package/src/pref-viewer.js +133 -109
  32. package/src/quick-look-action.js +280 -0
  33. package/src/styles.js +702 -98
  34. package/src/babylonjs-view-cube-renderer.js +0 -548
  35. package/src/view-cube-orbit-math.js +0 -199
package/src/styles.js CHANGED
@@ -15,38 +15,13 @@ const PrefViewerDefaultStyleVariables = {
15
15
  };
16
16
 
17
17
  /**
18
- * Reads persisted runtime style overrides in browser contexts.
19
- * Only `colorPrimary` is consumed by `PrefViewerStyleVariables`; other keys are ignored by the host component.
20
- * @returns {object} Parsed persisted style overrides, or an empty object when unavailable or invalid.
21
- */
22
- function readPersistedStyleVariables() {
23
- if (typeof window === "undefined") {
24
- return {};
25
- }
26
- try {
27
- const serialized = window.localStorage?.getItem(PREF_VIEWER_STYLES_STORAGE_KEY);
28
- return serialized ? JSON.parse(serialized) : {};
29
- } catch (_error) {
30
- return {};
31
- }
32
- }
33
-
34
- /** @type {object} Persisted raw style overrides loaded during module initialization. */
35
- const persistedStyleVariables = readPersistedStyleVariables();
36
-
37
- /**
38
- * Shared style variables consumed by PrefViewer CSS templates and Babylon defaults.
39
- * `colorPrimary` can be overridden at runtime through `<pref-viewer styles>`, `viewer.styles`, `viewer.setStyles()`, or `loadConfig({ styles })`.
18
+ * Default style variables consumed by internal CSS templates and Babylon defaults.
19
+ * Root viewer overrides are instance-owned by PrefViewerStyleController and never mutate this object.
40
20
  * @type {{colorPrimary: string, fontFamily: string, fontSize: string}}
41
21
  */
42
- export const PrefViewerStyleVariables = {
43
- ...PrefViewerDefaultStyleVariables,
44
- colorPrimary: typeof persistedStyleVariables.colorPrimary === "string" && persistedStyleVariables.colorPrimary.trim().length > 0
45
- ? persistedStyleVariables.colorPrimary.trim()
46
- : PrefViewerDefaultStyleVariables.colorPrimary,
47
- };
22
+ export const PrefViewerStyleVariables = { ...PrefViewerDefaultStyleVariables };
48
23
 
49
- /** Root `<pref-viewer>` host styles. Uses `--pref-viewer-color-primary` so `setStyles({ colorPrimary })` can update mounted instances. */
24
+ /** Root host layout; API appearance tokens are initialized on the private shadow wrapper. */
50
25
  export const PrefViewerStyles = `
51
26
  :host {
52
27
  all: initial;
@@ -150,38 +125,513 @@ export const PrefViewer3DStyles = `
150
125
  }
151
126
  `;
152
127
 
128
+ /** Scoped presentation rules shared by root and standalone viewers; sizes are CSS pixels, independent of DPR. */
129
+ export const ViewerControlsPresentationStyles = `
130
+ [data-controls-density] {
131
+ --pref-viewer-controls-toggle-bg-color: rgba(19, 21, 28, 0.6);
132
+ --pref-viewer-controls-toggle-bg-hover-color: rgba(19, 21, 28, 0.85);
133
+ --pref-viewer-controls-toggle-border-color: rgba(255, 255, 255, 0.25);
134
+ --pref-viewer-controls-toggle-border-width: 1px;
135
+ --pref-viewer-controls-toggle-height: calc(var(--pref-viewer-controls-toggle-icon-size) + var(--pref-viewer-controls-toggle-spacing) * 2 + var(--pref-viewer-controls-toggle-border-width) * 2);
136
+ --pref-viewer-controls-toggle-radius: 50%;
137
+ --pref-viewer-controls-toggle-transition-duration: 0.2s;
138
+ --pref-viewer-controls-edge-spacing: 16px;
139
+ --pref-viewer-controls-overlay-gap: 12px;
140
+ --pref-viewer-controls-ar-font-size: 16px;
141
+ --pref-viewer-controls-ar-width: var(--pref-viewer-controls-toggle-height);
142
+ --pref-viewer-controls-dialog-spacing: 16px;
143
+ --pref-viewer-controls-dialog-font-size: ${PrefViewerStyleVariables.fontSize};
144
+ --pref-viewer-controls-dialog-title-font-size: calc(1.1 * ${PrefViewerStyleVariables.fontSize});
145
+ --pref-viewer-controls-dialog-section-font-size: calc(1.05 * ${PrefViewerStyleVariables.fontSize});
146
+ --pref-viewer-controls-dialog-button-font-size: calc(0.95 * ${PrefViewerStyleVariables.fontSize});
147
+ --pref-viewer-controls-dialog-width: auto;
148
+ --pref-viewer-controls-dialog-control-height: 0px;
149
+ --pref-viewer-controls-dialog-option-size: auto;
150
+ --pref-viewer-controls-panel-bg-color: rgba(19, 21, 28, 0.85);
151
+ }
152
+
153
+ [data-controls-density="compact"] {
154
+ --pref-viewer-controls-cube-size: 120px;
155
+ --pref-viewer-controls-toggle-spacing: 10px;
156
+ --pref-viewer-controls-toggle-icon-size: 24px;
157
+ --pref-viewer-controls-button-size: 32px;
158
+ --pref-viewer-controls-icon-size: 24px;
159
+ --pref-viewer-controls-gap: 2px;
160
+ }
161
+
162
+ [data-controls-density="comfortable"] {
163
+ --pref-viewer-controls-cube-size: 140px;
164
+ --pref-viewer-controls-toggle-spacing: 9px;
165
+ --pref-viewer-controls-toggle-icon-size: 28px;
166
+ --pref-viewer-controls-button-size: 40px;
167
+ --pref-viewer-controls-icon-size: 28px;
168
+ --pref-viewer-controls-gap: 4px;
169
+ --pref-viewer-controls-menu-font-size-large: 18px;
170
+ --pref-viewer-controls-menu-font-size-medium: 16px;
171
+ --pref-viewer-controls-menu-font-size-small: 14px;
172
+ --pref-viewer-controls-menu-slider-thumb-size: 28px;
173
+ --pref-viewer-controls-dialog-font-size: 16px;
174
+ --pref-viewer-controls-dialog-title-font-size: 18px;
175
+ --pref-viewer-controls-dialog-section-font-size: 16px;
176
+ --pref-viewer-controls-dialog-button-font-size: 16px;
177
+ --pref-viewer-controls-dialog-width: 480px;
178
+ --pref-viewer-controls-dialog-control-height: 48px;
179
+ --pref-viewer-controls-dialog-option-size: 20px;
180
+ }
181
+
182
+ [data-controls-density] .menu-close {
183
+ display: none;
184
+ }
185
+
186
+ :is([data-controls-density="comfortable"], [data-controls-touch="true"]) pref-viewer-menu-3d>.menu-wrapper>.menu-toggle {
187
+ opacity: 1;
188
+ transform: none;
189
+ pointer-events: auto;
190
+ }
191
+
192
+ [data-controls-density="comfortable"] .pref-viewer-view-cube {
193
+ --cube-text-font-size: var(--pref-viewer-view-cube-text-font-size, 10px);
194
+ }
195
+
196
+ [data-controls-density="comfortable"] pref-viewer-menu-3d {
197
+ inset: 0;
198
+ width: auto;
199
+ height: auto;
200
+ }
201
+
202
+ [data-controls-density="comfortable"] pref-viewer-menu-3d>.menu-wrapper {
203
+ height: 100%;
204
+ }
205
+
206
+ [data-controls-density="comfortable"] pref-viewer-menu-3d>.menu-wrapper>.menu-toggle {
207
+ position: absolute;
208
+ right: 16px;
209
+ top: var(--menu-top);
210
+ width: var(--toggle-height);
211
+ height: var(--toggle-height);
212
+ box-sizing: border-box;
213
+ }
214
+
215
+ [data-controls-density="comfortable"] pref-viewer-menu-3d>.menu-wrapper>.menu-panel {
216
+ position: absolute;
217
+ right: 16px;
218
+ top: calc(var(--menu-top) + 56px);
219
+ max-width: calc(100% - 32px);
220
+ max-height: max(0px, calc(var(--pref-viewer-controls-height) - var(--menu-top) - 72px));
221
+ overscroll-behavior: contain;
222
+ box-sizing: border-box;
223
+ }
224
+
225
+ [data-controls-density="comfortable"][data-controls-narrow="true"] pref-viewer-menu-3d>.menu-wrapper>.menu-panel {
226
+ top: auto;
227
+ bottom: 16px;
228
+ left: 16px;
229
+ right: 16px;
230
+ width: auto;
231
+ max-height: max(0px, calc(var(--pref-viewer-controls-height) - 32px));
232
+ }
233
+
234
+ [data-controls-density] pref-viewer-menu-3d[data-controls-menu-side="beside-cube"] {
235
+ --menu-top: var(--toggle-margin);
236
+ --menu-side-right: var(--pref-viewer-controls-cube-actual-size, var(--pref-viewer-controls-cube-size));
237
+ --menu-side-panel-width: max(0px, calc(var(--pref-viewer-controls-width) - var(--menu-side-right) - var(--toggle-margin)));
238
+
239
+ left: auto;
240
+ right: var(--menu-side-right);
241
+ }
242
+
243
+ [data-controls-density] pref-viewer-menu-3d[data-controls-menu-side="beside-cube"]>.menu-wrapper>.menu-panel {
244
+ max-width: var(--menu-side-panel-width);
245
+ box-sizing: border-box;
246
+ }
247
+
248
+ [data-controls-density="comfortable"] pref-viewer-menu-3d[data-controls-menu-side="beside-cube"] {
249
+ inset: 0;
250
+ }
251
+
252
+ [data-controls-density="comfortable"] pref-viewer-menu-3d[data-controls-menu-side="beside-cube"]>.menu-wrapper>.menu-toggle,
253
+ [data-controls-density="comfortable"] pref-viewer-menu-3d[data-controls-menu-side="beside-cube"]>.menu-wrapper>.menu-panel {
254
+ left: auto;
255
+ right: var(--menu-side-right);
256
+ }
257
+
258
+ [data-controls-density="comfortable"] pref-viewer-menu-3d[data-controls-menu-side="beside-cube"]>.menu-wrapper>.menu-panel {
259
+ top: calc(var(--menu-top) + 56px);
260
+ bottom: auto;
261
+ width: var(--panel-width);
262
+ max-height: max(0px, calc(var(--pref-viewer-controls-height) - var(--menu-top) - 72px));
263
+ }
264
+
265
+ [data-controls-density="comfortable"] pref-viewer-menu-3d .menu-header {
266
+ position: relative;
267
+ padding-right: 48px;
268
+ }
269
+
270
+ :is([data-controls-density="comfortable"], [data-controls-touch="true"]) pref-viewer-menu-3d .menu-close {
271
+ display: grid;
272
+ place-items: center;
273
+ position: absolute;
274
+ right: 0;
275
+ top: 0;
276
+ width: 48px;
277
+ height: 48px;
278
+ font-size: 28px;
279
+ color: inherit;
280
+ background: transparent;
281
+ border: 1px solid currentColor;
282
+ border-radius: 8px;
283
+ cursor: pointer;
284
+ }
285
+
286
+ [data-controls-touch="true"] pref-viewer-menu-3d .menu-header {
287
+ position: relative;
288
+ padding-right: 48px;
289
+ }
290
+
291
+ [data-controls-density="comfortable"] pref-viewer-menu-3d .menu-switch-control {
292
+ min-height: 48px;
293
+ }
294
+
295
+ [data-controls-density="comfortable"] pref-viewer-menu-3d .menu-switch-control input {
296
+ width: 100%;
297
+ height: 100%;
298
+ }
299
+
300
+ [data-controls-density="comfortable"] pref-viewer-menu-3d .menu-switch-visual {
301
+ top: 10px !important;
302
+ bottom: 10px !important;
303
+ }
304
+
305
+ [data-controls-density="comfortable"] pref-viewer-menu-3d>.menu-wrapper>.menu-panel>:is(.menu-color-picker,.menu-lighting-slider)>input[type="range"] {
306
+ height: 48px;
307
+ background-size: 100% 12px;
308
+ background-repeat: no-repeat;
309
+ background-position: center;
310
+ border: none;
311
+ touch-action: pan-y;
312
+ }
313
+
314
+ [data-controls-density="comfortable"] pref-viewer-menu-3d .lighting-play-button {
315
+ min-width: 48px;
316
+ min-height: 48px;
317
+ }
318
+
319
+ [data-controls-density] .xr-button-overlay {
320
+ bottom: var(--pref-viewer-controls-edge-spacing) !important;
321
+ right: var(--pref-viewer-controls-edge-spacing) !important;
322
+ }
323
+
324
+ [data-controls-density] :is(button.babylonVRicon, a.babylonVRicon) {
325
+ width: var(--pref-viewer-controls-ar-width);
326
+ height: var(--pref-viewer-controls-toggle-height);
327
+ box-sizing: border-box;
328
+ margin: 0;
329
+ padding: var(--pref-viewer-controls-toggle-spacing, 10px);
330
+ border: var(--pref-viewer-controls-toggle-border-width) solid var(--pref-viewer-controls-toggle-border-color);
331
+ border-radius: var(--pref-viewer-controls-toggle-radius);
332
+ background-color: var(--pref-viewer-controls-toggle-bg-color, rgba(19, 21, 28, 0.6));
333
+ background-origin: content-box;
334
+ background-size: var(--pref-viewer-controls-toggle-icon-size) auto;
335
+ font-size: var(--pref-viewer-controls-ar-font-size);
336
+ transition: background-color var(--pref-viewer-controls-toggle-transition-duration) ease;
337
+ }
338
+
339
+ [data-controls-density] :is(button.babylonVRicon, a.babylonVRicon):hover,
340
+ [data-controls-density] :is(button.babylonVRicon, a.babylonVRicon):focus-visible {
341
+ transform: none;
342
+ background-color: var(--pref-viewer-controls-toggle-bg-hover-color, rgba(19, 21, 28, 0.85));
343
+ }
344
+
345
+ [data-controls-density] .pref-viewer-quick-look {
346
+ bottom: var(--pref-viewer-controls-edge-spacing);
347
+ right: var(--pref-viewer-controls-edge-spacing);
348
+ }
349
+
350
+ [data-controls-density] div.pref-viewer-3d.animation-menu {
351
+ width: min(var(--width), calc(100% - 24px));
352
+ left: 50%;
353
+ right: auto;
354
+ transform: translateX(-50%);
355
+ bottom: var(--pref-viewer-controls-playback-bottom, var(--pref-viewer-controls-edge-spacing));
356
+ max-height: max(0px, calc(var(--pref-viewer-controls-height) - var(--pref-viewer-controls-playback-bottom, var(--pref-viewer-controls-edge-spacing)) - var(--pref-viewer-controls-edge-spacing)));
357
+ overflow: auto;
358
+ overscroll-behavior: contain;
359
+ box-sizing: border-box;
360
+ }
361
+
362
+ [data-controls-density="comfortable"] div.pref-viewer-3d.animation-menu {
363
+ animation: none;
364
+ }
365
+
366
+ [data-controls-density] div.pref-viewer-3d.animation-menu>.animation-menu-buttons {
367
+ flex-wrap: wrap;
368
+ justify-content: center;
369
+ }
370
+
371
+ [data-controls-density] div.pref-viewer-3d.animation-menu>.animation-menu-buttons>button {
372
+ flex: 0 0 var(--button-size);
373
+ width: var(--button-size);
374
+ height: var(--button-size);
375
+ box-sizing: border-box;
376
+ }
377
+
378
+ [data-controls-density="comfortable"] div.pref-viewer-3d.animation-menu>.animation-menu-buttons>button>svg {
379
+ width: 28px;
380
+ height: 28px;
381
+ }
382
+
383
+ [data-controls-density="comfortable"] div.pref-viewer-3d.animation-menu>.animation-menu-selector>button {
384
+ min-height: 48px;
385
+ font-size: 16px;
386
+ overflow-wrap: anywhere;
387
+ }
388
+
389
+ `;
390
+
391
+ /**
392
+ * Shared SVG/Canvas ViewCube palette. CSS tokens and renderer defaults originate here.
393
+ * Static immutable defaults own no graphics resources; host CSS overrides are resolved by each presentation.
394
+ */
395
+ export class PrefViewerViewCubePalette {
396
+ /** Base gray for Canvas edge prisms, independent of SVG borders and corner spheres. */
397
+ static #canvasEdgeColor = "#d5d5da";
398
+ /** Darker Canvas corner base, with the same subtle blue-channel boost as the edge color. */
399
+ static #canvasCornerColor = "#c8c6c8";
400
+ static #darkColors = Object.freeze({
401
+ fill: "#2f363a",
402
+ border: "#9ba4a9",
403
+ text: "#e6e3da",
404
+ edge: "#56606a",
405
+ corner: "#6b7886",
406
+ });
407
+ static #defaults = Object.freeze({
408
+ fill: "#eef0f3",
409
+ border: "#78838f",
410
+ text: "#29323b",
411
+ hoverOpacity: 0.5,
412
+ currentOpacity: 0.15,
413
+ });
414
+
415
+ /** @returns {Readonly<object>} Fresh frozen renderer defaults with independent edge/corner base colors and the default primary tint. */
416
+ static get colors() {
417
+ const defaults = PrefViewerViewCubePalette.#defaults;
418
+ return Object.freeze({ ...defaults, edge: PrefViewerViewCubePalette.#canvasEdgeColor, corner: PrefViewerViewCubePalette.#canvasCornerColor, hover: PrefViewerDefaultStyleVariables.colorPrimary, current: PrefViewerDefaultStyleVariables.colorPrimary });
419
+ }
420
+
421
+ /** @returns {Readonly<object>} Optional immutable dark palette for internal renderer hosts. */
422
+ static get darkColors() {
423
+ return PrefViewerViewCubePalette.#darkColors;
424
+ }
425
+
426
+ /** @returns {string} Shared inheritable palette aliases with independent Canvas edge and corner defaults. */
427
+ static get styles() {
428
+ const defaults = PrefViewerViewCubePalette.#defaults;
429
+ return `
430
+ .pref-viewer-view-cube {
431
+ --cube-fill-color: var(--pref-viewer-view-cube-fill-color, ${defaults.fill});
432
+ --cube-border-color: var(--pref-viewer-view-cube-border-color, ${defaults.border});
433
+ --cube-text-color: var(--pref-viewer-view-cube-text-color, ${defaults.text});
434
+ --cube-hover-color: var(--pref-viewer-view-cube-hover-color, var(--pref-viewer-color-primary, ${PrefViewerStyleVariables.colorPrimary}));
435
+ --cube-hover-opacity: var(--pref-viewer-view-cube-hover-opacity, ${defaults.hoverOpacity});
436
+ --cube-active-color: var(--pref-viewer-view-cube-active-color, var(--pref-viewer-color-primary, ${PrefViewerStyleVariables.colorPrimary}));
437
+ --cube-active-opacity: var(--pref-viewer-view-cube-active-opacity, ${defaults.currentOpacity});
438
+ }
439
+
440
+ .pref-viewer-view-cube[data-renderer="canvas"] {
441
+ --cube-edge-color: var(--pref-viewer-view-cube-edge-color, ${PrefViewerViewCubePalette.#canvasEdgeColor});
442
+ --cube-corner-color: var(--pref-viewer-view-cube-corner-color, ${PrefViewerViewCubePalette.#canvasCornerColor});
443
+ }
444
+ `;
445
+ }
446
+ }
447
+
448
+ /** Shared label typography for SVG text and Canvas texture painting; owns no DOM or graphics resources. */
449
+ export class PrefViewerViewCubeTypography {
450
+ /** @returns {Readonly<object>} Typography in SVG reference units, before texture scaling. */
451
+ static get defaults() {
452
+ return Object.freeze({ fontFamily: PrefViewerDefaultStyleVariables.fontFamily, fontSize: 9, fontWeight: "600", letterSpacing: 1, textTransform: "uppercase" });
453
+ }
454
+
455
+ /** @returns {string} Shared variables and an invisible computed-style probe for Canvas labels. */
456
+ static get styles() {
457
+ const defaults = PrefViewerViewCubeTypography.defaults;
458
+ return `
459
+ .pref-viewer-view-cube {
460
+ --cube-text-font-family: var(--pref-viewer-view-cube-text-font-family, ${PrefViewerStyleVariables.fontFamily});
461
+ --cube-text-font-size: var(--pref-viewer-view-cube-text-font-size, ${defaults.fontSize}px);
462
+ --cube-text-font-weight: var(--pref-viewer-view-cube-text-font-weight, ${defaults.fontWeight});
463
+ --cube-text-letter-spacing: var(--pref-viewer-view-cube-text-letter-spacing, ${defaults.letterSpacing}px);
464
+ --cube-text-transform: var(--pref-viewer-view-cube-text-transform, ${defaults.textTransform});
465
+ }
466
+
467
+ .pref-viewer-view-cube .pref-viewer-view-cube-label-style {
468
+ position: absolute;
469
+ visibility: hidden;
470
+ pointer-events: none;
471
+ font-family: var(--cube-text-font-family);
472
+ font-size: var(--cube-text-font-size);
473
+ font-weight: var(--cube-text-font-weight);
474
+ letter-spacing: var(--cube-text-letter-spacing);
475
+ text-transform: var(--cube-text-transform);
476
+ }
477
+ `;
478
+ }
479
+ }
480
+
481
+ /** Shared SVG/Canvas CSS sizing; presentation density supplies the requested side. */
482
+ export const PrefViewerViewCubeSizingStyles = `
483
+ .pref-viewer-view-cube {
484
+ --cube-size: var(--pref-viewer-controls-cube-size, 120px);
485
+
486
+ width: var(--cube-size);
487
+ height: var(--cube-size);
488
+ }
489
+ `;
490
+
491
+ /** Rotation cursor image and hotspot shared by both ViewCube implementations. */
492
+ export const PrefViewerViewCubeCursorStyles = `
493
+ .pref-viewer-view-cube[data-renderer="svg"],
494
+ .pref-viewer-view-cube[data-renderer="canvas"] {
495
+ --cube-rotation-cursor: url("data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22UTF-8%22%3F%3E%0A%3Csvg%20id%3D%22rotate_cube%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2232%22%20height%3D%2232%22%20version%3D%221.1%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20viewBox%3D%220%200%2032%2032%22%3E%0A%20%20%3C!--%20Generator%3A%20Adobe%20Illustrator%2031.0.0%2C%20SVG%20Export%20Plug-In%20.%20SVG%20Version%3A%202.1.5%20Build%2064)%20%20--%3E%0A%20%20%3Cdefs%3E%0A%20%20%20%20%3Cfilter%20id%3D%22drop-shadow-1%22%20x%3D%220%22%20y%3D%221%22%20width%3D%2234%22%20height%3D%2232%22%20filterUnits%3D%22userSpaceOnUse%22%3E%0A%20%20%20%20%20%20%3CfeOffset%20dx%3D%221%22%20dy%3D%221%22%2F%3E%0A%20%20%20%20%20%20%3CfeGaussianBlur%20result%3D%22blur%22%20stdDeviation%3D%221%22%2F%3E%0A%20%20%20%20%20%20%3CfeFlood%20flood-color%3D%22%23000%22%20flood-opacity%3D%22.25%22%2F%3E%0A%20%20%20%20%20%20%3CfeComposite%20in2%3D%22blur%22%20operator%3D%22in%22%2F%3E%0A%20%20%20%20%20%20%3CfeComposite%20in%3D%22SourceGraphic%22%2F%3E%0A%20%20%20%20%3C%2Ffilter%3E%0A%20%20%3C%2Fdefs%3E%0A%20%20%3Cg%20filter%3D%22url(%23drop-shadow-1)%22%3E%0A%20%20%20%20%3Cpath%20d%3D%22M16%2C24c-.094%2C0-.182-.026-.257-.071l-6.493-3.746c-.155-.089-.25-.254-.25-.433v-7.491c-.001-.084.018-.169.06-.247h0s0%2C0%2C.001-.001c0%2C0%2C0-.002%2C0-.001l.012-.021s.002-.002.001-.001c.047-.076.111-.135.183-.176l6.493-3.745c.155-.089.345-.089.5%2C0l6.492%2C3.745c.073.041.137.1.184.176h0s0%2C0%2C0%2C.001l.012.021h0s.001%2C0%2C.001%2C0h0c.043.08.063.165.061.249v7.491c0%2C.179-.096.344-.25.433l-6.493%2C3.746C16.182%2C23.974%2C16.094%2C24%2C16%2C24ZM11.97%2C3.499c-4.106%2C0-7.47%2C3.365-7.47%2C7.501h-2.293l2.793%2C2.793%2C2.793-2.793h-2.293c0-3.584%2C2.916-6.5%2C6.5-6.5L11.97%2C3.499ZM20.03%2C28.501c4.106-.001%2C7.47-3.365%2C7.47-7.501h2.293l-2.793-2.793-2.793%2C2.793h2.293c0%2C3.584-2.916%2C6.5-6.5%2C6.5L20.03%2C28.501Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%20%20%3Cpolygon%20points%3D%2216.5%2016.289%2016.5%2022.635%2022%2019.461%2022%2013.116%2016.5%2016.289%22%20fill%3D%22%23fff%22%2F%3E%0A%20%20%3Cpolygon%20points%3D%2210.501%2012.25%2016%2015.423%2021.499%2012.25%2016%209.077%2010.501%2012.25%22%20fill%3D%22%23fff%22%2F%3E%0A%20%20%3Cpolygon%20points%3D%2210%2019.461%2015.5%2022.635%2015.5%2016.289%2010%2013.116%2010%2019.461%22%20fill%3D%22%23fff%22%2F%3E%0A%3C%2Fsvg%3E") 16 16, grabbing;
496
+ }
497
+ `;
498
+
499
+ export const PrefViewerViewCubeStyles = `${PrefViewerViewCubePalette.styles}${PrefViewerViewCubeTypography.styles}${PrefViewerViewCubeSizingStyles}${PrefViewerViewCubeCursorStyles}
500
+ .pref-viewer-view-cube[data-renderer="svg"] {
501
+ --cube-border-width: 1.3px;
502
+
503
+ position: absolute;
504
+ right: 0px;
505
+ top: 0px;
506
+ z-index: 900;
507
+ pointer-events: none;
508
+ }
509
+
510
+ .pref-viewer-view-cube[data-renderer="svg"],
511
+ .pref-viewer-view-cube[data-renderer="svg"] * {
512
+ -webkit-tap-highlight-color: transparent;
513
+ -webkit-user-select: none;
514
+ user-select: none;
515
+ }
516
+
517
+ .pref-viewer-view-cube[data-renderer="svg"][hidden] {
518
+ display: none;
519
+ }
520
+
521
+ .pref-viewer-view-cube[data-renderer="svg"] svg {
522
+ touch-action: none;
523
+ width: 100%;
524
+ height: 100%;
525
+ overflow: visible;
526
+ pointer-events: none;
527
+ }
528
+
529
+ .pref-viewer-view-cube[data-renderer="svg"] polygon {
530
+ stroke-width: var(--cube-border-width);
531
+ }
532
+
533
+ .pref-viewer-view-cube[data-renderer="svg"] .cube-shell {
534
+ fill: var(--cube-fill-color);
535
+ stroke: var(--cube-border-color);
536
+ pointer-events: none;
537
+ }
538
+
539
+ .pref-viewer-view-cube[data-renderer="svg"] polygon[data-region] {
540
+ fill: transparent;
541
+ stroke: transparent;
542
+ pointer-events: visiblePainted;
543
+ cursor: pointer;
544
+ }
545
+
546
+ .pref-viewer-view-cube[data-renderer="svg"][data-dragging="true"],
547
+ .pref-viewer-view-cube[data-renderer="svg"][data-dragging="true"] svg,
548
+ .pref-viewer-view-cube[data-renderer="svg"][data-dragging="true"] svg * {
549
+ cursor: var(--cube-rotation-cursor);
550
+ }
551
+
552
+ .pref-viewer-view-cube[data-renderer="svg"][data-locked="true"] polygon {
553
+ cursor: default;
554
+ }
555
+
556
+ .pref-viewer-view-cube[data-renderer="svg"] polygon[data-current] {
557
+ fill: var(--cube-active-color);
558
+ fill-opacity: var(--cube-active-opacity);
559
+ stroke: transparent;
560
+ }
561
+
562
+ .pref-viewer-view-cube[data-renderer="svg"] polygon[data-hover] {
563
+ fill: var(--cube-hover-color);
564
+ fill-opacity: var(--cube-hover-opacity);
565
+ stroke: transparent;
566
+ }
567
+
568
+ .pref-viewer-view-cube[data-renderer="svg"] .hover-outline {
569
+ display: none;
570
+ fill: none;
571
+ stroke: var(--cube-hover-color);
572
+ stroke-opacity: 1;
573
+ stroke-width: var(--cube-border-width);
574
+ pointer-events: none;
575
+ }
576
+
577
+ .pref-viewer-view-cube[data-renderer="svg"] .hover-outline[data-hover] {
578
+ display: inline;
579
+ }
580
+
581
+ .pref-viewer-view-cube[data-renderer="svg"] text {
582
+ -webkit-user-select: none;
583
+ user-select: none;
584
+ pointer-events: none;
585
+ fill: var(--cube-text-color);
586
+ font-family: var(--cube-text-font-family);
587
+ font-size: var(--cube-text-font-size);
588
+ font-weight: var(--cube-text-font-weight);
589
+ letter-spacing: var(--cube-text-letter-spacing);
590
+ text-transform: var(--cube-text-transform);
591
+ text-anchor: middle;
592
+ dominant-baseline: middle;
593
+ }
594
+ `;
595
+
153
596
  export const PrefViewerMenu3DStyles = `
154
597
  pref-viewer-menu-3d {
155
598
  --font-family: ${PrefViewerStyleVariables.fontFamily};
156
599
  --font-weight-bold: 600;
157
600
  --font-weight-medium: 500;
158
- --font-size-large: calc(1.1 * ${PrefViewerStyleVariables.fontSize});
159
- --font-size-medium: calc(0.95 * ${PrefViewerStyleVariables.fontSize});
160
- --font-size-small: calc(0.75 * ${PrefViewerStyleVariables.fontSize});
601
+ --font-size-large: var(--pref-viewer-controls-menu-font-size-large, calc(1.1 * ${PrefViewerStyleVariables.fontSize}));
602
+ --font-size-medium: var(--pref-viewer-controls-menu-font-size-medium, calc(0.95 * ${PrefViewerStyleVariables.fontSize}));
603
+ --font-size-small: var(--pref-viewer-controls-menu-font-size-small, calc(0.75 * ${PrefViewerStyleVariables.fontSize}));
161
604
  --text-color: #ffffff;
162
605
  --muted-color: #b2b2b2;
163
606
  --accent-color: var(--pref-viewer-color-primary, ${PrefViewerStyleVariables.colorPrimary});
164
607
 
165
608
  --separator-border: 1px solid rgba(255, 255, 255, 0.1);
166
609
 
167
- --toggle-margin: 16px;
168
- --toggle-bg-color: rgba(19, 21, 28, 0.6);
169
- --toggle-bg-hover-color: rgba(19, 21, 28, 0.85);
170
- --toggle-border-color: rgba(255, 255, 255, 0.25);
171
- --toggle-spacing: 10px;
610
+ --toggle-margin: var(--pref-viewer-controls-edge-spacing, 16px);
611
+ --toggle-spacing: var(--pref-viewer-controls-toggle-spacing, 10px);
612
+ --toggle-icon-size: var(--pref-viewer-controls-toggle-icon-size, 24px);
613
+ --toggle-bg-color: var(--pref-viewer-controls-toggle-bg-color, rgba(19, 21, 28, 0.6));
614
+ --toggle-bg-hover-color: var(--pref-viewer-controls-toggle-bg-hover-color, rgba(19, 21, 28, 0.85));
615
+ --toggle-border-color: var(--pref-viewer-controls-toggle-border-color, rgba(255, 255, 255, 0.25));
616
+ --toggle-border-width: var(--pref-viewer-controls-toggle-border-width, 1px);
617
+ --toggle-height: var(--pref-viewer-controls-toggle-height, calc(var(--toggle-icon-size) + var(--toggle-spacing) * 2 + var(--toggle-border-width) * 2));
618
+ --toggle-radius: var(--pref-viewer-controls-toggle-radius, calc((var(--toggle-icon-size) + var(--toggle-spacing) * 2) / 2));
619
+ --toggle-transition-duration: var(--pref-viewer-controls-toggle-transition-duration, 0.2s);
172
620
  --toggle-icon-color: #ffffff;
173
- --toggle-icon-size: 24px;
174
621
 
175
622
  --button-font-color: #000000;
176
623
  --button-font-size: var(--font-size-medium);
177
624
  --button-font-weight: 600;
178
625
  --button-shadow: 0 8px 16px rgba(0, 0, 0, 0.35);
626
+ --button-feedback-bg-color: rgba(255, 255, 255, 0.15);
627
+ --button-feedback-border-color: rgba(255, 255, 255, 0.3);
628
+ --control-feedback-shadow: 0 0 0 3px rgba(255, 255, 255, 0.35);
179
629
  --button-radius: 8px;
180
630
 
631
+ --panel-bg-color: var(--pref-viewer-controls-panel-bg-color, rgba(19, 21, 28, 0.85));
181
632
  --panel-width: 300px;
182
633
  --panel-spacing: 16px;
183
634
  --panel-spacing-large: calc(var(--panel-spacing) * 1.5);
184
- --panel-bg-color: rgba(19, 21, 28, 0.85);
185
635
  --panel-border-color: rgba(255, 255, 255, 0.25);
186
636
  --panel-shadow: 0 16px 32px rgba(0, 0, 0, 0.65);
187
637
  --panel-radius: calc(var(--button-radius) + var(--panel-spacing));
@@ -195,15 +645,17 @@ export const PrefViewerMenu3DStyles = `
195
645
  --switch-control-width: calc((var(--switch-control-thumb-size) * 2) + (var(--switch-control-bar-spacing) * 2));
196
646
  --switch-control-height: calc(var(--switch-control-thumb-size) + (var(--switch-control-bar-spacing) * 2));
197
647
 
198
- --slider-thumb-size: 18px;
648
+ --slider-thumb-size: var(--pref-viewer-controls-menu-slider-thumb-size, 18px);
199
649
  --slider-thumb-color: #ffffff;
200
650
  --slider-thumb-border: 1px solid rgba(0, 0, 0, 0.25);
201
651
  --slider-thumb-shadow: 0 1px 4px rgba(0, 0, 0, 0.45);
202
652
  --slider-bar-height: 12px;
203
653
  --slider-bar-border: 1px solid rgba(255, 255, 255, 0.5);
204
654
 
655
+ --menu-top: var(--pref-viewer-render-menu-top, var(--toggle-margin));
656
+
205
657
  position: absolute;
206
- top: var(--pref-viewer-render-menu-top, var(--toggle-margin));
658
+ top: var(--menu-top);
207
659
  right: var(--toggle-margin);
208
660
  z-index: 1200;
209
661
  /* The closed panel keeps its place in the flex column (opacity:0, not display:none), so
@@ -218,6 +670,11 @@ export const PrefViewerMenu3DStyles = `
218
670
  user-select: none;
219
671
  }
220
672
 
673
+ pref-viewer-menu-3d,
674
+ pref-viewer-menu-3d * {
675
+ -webkit-tap-highlight-color: transparent;
676
+ }
677
+
221
678
  pref-viewer-menu-3d>.menu-wrapper {
222
679
  display: flex;
223
680
  flex-direction: column;
@@ -225,22 +682,22 @@ export const PrefViewerMenu3DStyles = `
225
682
  }
226
683
 
227
684
  pref-viewer-menu-3d>.menu-wrapper>.menu-toggle {
228
- padding: var(--toggle-spacing);
229
- border-radius: calc((var(--toggle-icon-size) + var(--toggle-spacing) * 2) / 2);
230
- border: 1px solid var(--toggle-border-color);
231
- background: var(--toggle-bg-color);
685
+ padding: var(--toggle-spacing, 10px);
686
+ border-radius: var(--toggle-radius, calc((var(--toggle-icon-size, 24px) + var(--toggle-spacing, 10px) * 2) / 2));
687
+ border: var(--toggle-border-width, 1px) solid var(--toggle-border-color, rgba(255, 255, 255, 0.25));
688
+ background: var(--toggle-bg-color, rgba(19, 21, 28, 0.6));
232
689
  display: grid;
233
690
  place-items: center;
234
691
  cursor: pointer;
235
692
  opacity: 0;
236
693
  transform: translateY(calc(-1 * var(--toggle-margin) / 2));
237
- transition: opacity 0.2s ease, transform 0.2s ease, background 0.2s ease;
694
+ transition: opacity var(--toggle-transition-duration, 0.2s) ease, transform var(--toggle-transition-duration, 0.2s) ease, background var(--toggle-transition-duration, 0.2s) ease;
238
695
  pointer-events: none;
239
696
  }
240
697
 
241
698
  pref-viewer-menu-3d>.menu-wrapper>.menu-toggle>svg {
242
- width: var(--toggle-icon-size);
243
- height: var(--toggle-icon-size);
699
+ width: var(--toggle-icon-size, 24px);
700
+ height: var(--toggle-icon-size, 24px);
244
701
  fill: var(--toggle-icon-color);
245
702
  }
246
703
 
@@ -250,18 +707,18 @@ export const PrefViewerMenu3DStyles = `
250
707
  pointer-events: auto;
251
708
  }
252
709
 
253
- pref-viewer-menu-3d>.menu-wrapper>.menu-toggle:hover,
710
+ pref-viewer-menu-3d>.menu-wrapper>.menu-toggle:is(:active, [data-touch-pressed]),
254
711
  pref-viewer-menu-3d>.menu-wrapper>.menu-toggle:focus-visible {
255
- background: var(--toggle-bg-hover-color);
712
+ background: var(--toggle-bg-hover-color, rgba(19, 21, 28, 0.85));
256
713
  outline: none;
257
714
  }
258
715
 
259
716
  pref-viewer-menu-3d>.menu-wrapper>.menu-panel {
260
717
  width: var(--panel-width);
261
- max-height: min(70vh, calc(var(--pref-viewer-viewport-height, 100vh) - var(--pref-viewer-render-menu-top, var(--toggle-margin)) - 84px));
718
+ max-height: min(70vh, calc(var(--pref-viewer-viewport-height, 100vh) - var(--menu-top) - 84px));
262
719
  overflow-y: auto;
263
720
  border-radius: var(--panel-radius);
264
- background: var(--panel-bg-color);
721
+ background: var(--panel-bg-color, rgba(19, 21, 28, 0.85));
265
722
  border: 1px solid var(--panel-border-color);
266
723
  box-shadow: var(--panel-shadow);
267
724
  padding: 18px 20px 16px;
@@ -577,26 +1034,51 @@ export const PrefViewerMenu3DStyles = `
577
1034
  cursor: pointer;
578
1035
  }
579
1036
 
580
- pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-lighting-slider>.lighting-slider-actions>.lighting-play-button:hover,
581
- pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-lighting-slider>.lighting-slider-actions>.lighting-play-button:focus-visible {
582
- background: rgba(255, 255, 255, 0.15);
583
- border-color: rgba(255, 255, 255, 0.3);
584
- outline: none;
585
- box-shadow: var(--button-shadow);
586
- }
587
-
588
1037
  pref-viewer-menu-3d>.menu-wrapper>.menu-panel>.menu-footer {
589
1038
  margin-top: calc(var(--panel-spacing) / 2);
590
1039
  display: flex;
591
1040
  flex-direction: column;
592
1041
  gap: calc(var(--panel-spacing) / 2);
593
1042
  }
1043
+
1044
+ pref-viewer-menu-3d>.menu-wrapper>.menu-panel :is(.lighting-play-button, .menu-close):not(:disabled):is(:active, :focus-visible, [data-touch-pressed]) {
1045
+ background: var(--button-feedback-bg-color);
1046
+ border-color: var(--button-feedback-border-color);
1047
+ box-shadow: var(--button-shadow);
1048
+ outline: none;
1049
+ }
1050
+
1051
+ pref-viewer-menu-3d .menu-switch:not([data-disabled]):has(input:enabled):is(:active, [data-touch-pressed]) .menu-switch-visual,
1052
+ pref-viewer-menu-3d .menu-switch:not([data-disabled]):has(input:enabled) input:focus-visible + .menu-switch-visual,
1053
+ pref-viewer-menu-3d .menu-panel input[type="range"]:enabled:is(:active, :focus-visible, [data-touch-pressed]) {
1054
+ box-shadow: var(--control-feedback-shadow);
1055
+ outline: none;
1056
+ }
1057
+
1058
+ @media (hover: hover) and (pointer: fine) {
1059
+ pref-viewer-menu-3d>.menu-wrapper>.menu-toggle:hover {
1060
+ background: var(--toggle-bg-hover-color);
1061
+ }
1062
+
1063
+ pref-viewer-menu-3d>.menu-wrapper>.menu-panel :is(.lighting-play-button, .menu-close):not(:disabled):hover {
1064
+ background: var(--button-feedback-bg-color);
1065
+ border-color: var(--button-feedback-border-color);
1066
+ box-shadow: var(--button-shadow);
1067
+ }
1068
+
1069
+ pref-viewer-menu-3d .menu-switch:not([data-disabled]):has(input:enabled):hover .menu-switch-visual,
1070
+ pref-viewer-menu-3d .menu-panel input[type="range"]:enabled:hover {
1071
+ box-shadow: var(--control-feedback-shadow);
1072
+ }
1073
+ }
1074
+
594
1075
  `;
595
1076
 
596
1077
  export const PrefViewer3DAnimationMenuStyles = `
597
1078
  div.pref-viewer-3d.animation-menu, div.pref-viewer-3d.animation-menu * {
598
1079
  padding: 0;
599
1080
  margin: 0;
1081
+ -webkit-tap-highlight-color: transparent;
600
1082
  }
601
1083
 
602
1084
  div.pref-viewer-3d.animation-menu {
@@ -608,10 +1090,12 @@ export const PrefViewer3DAnimationMenuStyles = `
608
1090
  --color-disabled: #bdbdbd;
609
1091
  --color-icon: #FFFFFF;
610
1092
  --color-border: #FFFFFF;
611
- --button-size: 32px;
1093
+ --control-focus-outline: 2px solid var(--color-border);
1094
+ --control-focus-offset: 2px;
1095
+ --button-size: var(--pref-viewer-controls-button-size, 32px);
612
1096
  --icon-size: 24px;
613
1097
  --button-loop-margin-left: 3px;
614
- --button-spacing: 2px;
1098
+ --button-spacing: var(--pref-viewer-controls-gap, 2px);
615
1099
  --slider-thumb-width: 20px;
616
1100
  --slider-bar-height: 8px;
617
1101
  --slider-bar-offset: 10px;
@@ -633,12 +1117,16 @@ export const PrefViewer3DAnimationMenuStyles = `
633
1117
  }
634
1118
 
635
1119
  @keyframes fadeTo085 {
636
- from { opacity: 0; }
637
- to { opacity: 0.85; }
1120
+ from {
1121
+ opacity: 0;
1122
+ }
1123
+
1124
+ to {
1125
+ opacity: 0.85;
1126
+ }
638
1127
  }
639
1128
 
640
1129
  div.pref-viewer-3d.animation-menu>div.animation-menu-selector {
641
- fore
642
1130
  padding: 0;
643
1131
  margin: 0;
644
1132
  display: flex;
@@ -667,18 +1155,10 @@ export const PrefViewer3DAnimationMenuStyles = `
667
1155
  background: var(--color-active);
668
1156
  }
669
1157
 
670
- div.pref-viewer-3d.animation-menu>div.animation-menu-selector>button.active:hover {
671
- background: var(--color-active-hover);
672
- }
673
-
674
1158
  div.pref-viewer-3d.animation-menu>div.animation-menu-selector>button:not(.active) {
675
1159
  cursor: pointer;
676
1160
  }
677
1161
 
678
- div.pref-viewer-3d.animation-menu>div.animation-menu-selector>button:not(.active):hover {
679
- background: var(--color-enabled-hover);
680
- }
681
-
682
1162
  div.pref-viewer-3d.animation-menu>div.animation-menu-buttons {
683
1163
  padding: 0;
684
1164
  margin: 0;
@@ -708,21 +1188,12 @@ export const PrefViewer3DAnimationMenuStyles = `
708
1188
  background: var(--color-active);
709
1189
  }
710
1190
 
711
- div.pref-viewer-3d.animation-menu>div.animation-menu-buttons>button.active:hover {
712
- background: var(--color-active-hover);
713
- }
714
-
715
1191
  div.pref-viewer-3d.animation-menu>div.animation-menu-buttons>button.enabled {
716
1192
  background: var(--color-enabled);
717
1193
  cursor: pointer;
718
1194
  }
719
1195
 
720
- div.pref-viewer-3d.animation-menu>div.animation-menu-buttons>button.enabled:hover {
721
- background: var(--color-enabled-hover);
722
- }
723
-
724
- div.pref-viewer-3d.animation-menu>div.animation-menu-buttons>button.disabled,
725
- div.pref-viewer-3d.animation-menu>div.animation-menu-buttons>button.disabled:hover {
1196
+ div.pref-viewer-3d.animation-menu>div.animation-menu-buttons>button.disabled {
726
1197
  background: var(--color-disabled);
727
1198
  }
728
1199
 
@@ -766,10 +1237,6 @@ export const PrefViewer3DAnimationMenuStyles = `
766
1237
  transition: background .2s;
767
1238
  }
768
1239
 
769
- div.pref-viewer-3d.animation-menu>input[type="range"]::-webkit-slider-thumb:hover {
770
- background: var(--color-active-hover);
771
- }
772
-
773
1240
  div.pref-viewer-3d.animation-menu>input[type="range"]::-moz-range-track {
774
1241
  height: var(--slider-thumb-width);
775
1242
  width: var(--slider-thumb-width);
@@ -784,16 +1251,68 @@ export const PrefViewer3DAnimationMenuStyles = `
784
1251
  div.pref-viewer-3d.animation-menu>input[type="range"]::-moz-range-track:hover {
785
1252
  background: var(--color-active-hover);
786
1253
  }
1254
+ div.pref-viewer-3d.animation-menu>:is(.animation-menu-buttons, .animation-menu-selector)>button:not(.disabled):not(:disabled).active:is(:active, :focus-visible, [data-touch-pressed]) {
1255
+ background: var(--color-active-hover);
1256
+ }
1257
+
1258
+ div.pref-viewer-3d.animation-menu>:is(.animation-menu-buttons, .animation-menu-selector)>button:not(.disabled):not(:disabled):not(.active):is(:active, :focus-visible, [data-touch-pressed]) {
1259
+ background: var(--color-enabled-hover);
1260
+ }
1261
+
1262
+ div.pref-viewer-3d.animation-menu :is(button, input[type="range"]):enabled:focus-visible {
1263
+ outline: var(--control-focus-outline);
1264
+ outline-offset: var(--control-focus-offset);
1265
+ }
1266
+
1267
+ div.pref-viewer-3d.animation-menu>input[type="range"]:enabled:is(:active, :focus-visible, [data-touch-pressed])::-webkit-slider-thumb {
1268
+ background: var(--color-active-hover);
1269
+ }
1270
+
1271
+ div.pref-viewer-3d.animation-menu>input[type="range"]:enabled:is(:active, :focus-visible, [data-touch-pressed])::-moz-range-thumb {
1272
+ background: var(--color-active-hover);
1273
+ }
1274
+
1275
+ @media (hover: hover) and (pointer: fine) {
1276
+ div.pref-viewer-3d.animation-menu>:is(.animation-menu-buttons, .animation-menu-selector)>button:not(.disabled):not(:disabled).active:hover {
1277
+ background: var(--color-active-hover);
1278
+ }
1279
+
1280
+ div.pref-viewer-3d.animation-menu>:is(.animation-menu-buttons, .animation-menu-selector)>button:not(.disabled):not(:disabled):not(.active):hover {
1281
+ background: var(--color-enabled-hover);
1282
+ }
1283
+
1284
+ div.pref-viewer-3d.animation-menu>input[type="range"]:enabled:hover::-webkit-slider-thumb {
1285
+ background: var(--color-active-hover);
1286
+ }
1287
+
1288
+ div.pref-viewer-3d.animation-menu>input[type="range"]:enabled:hover::-moz-range-thumb {
1289
+ background: var(--color-active-hover);
1290
+ }
1291
+ }
1292
+
787
1293
  `;
788
1294
 
789
1295
  export const PrefViewerDialogStyles = `
790
1296
  pref-viewer-dialog {
791
1297
  --color-primary: var(--pref-viewer-color-primary, ${PrefViewerStyleVariables.colorPrimary});
792
- --dialog-general-space: 16px;
1298
+ --dialog-general-space: var(--pref-viewer-controls-dialog-spacing, 16px);
1299
+ --dialog-font-size: var(--pref-viewer-controls-dialog-font-size, ${PrefViewerStyleVariables.fontSize});
1300
+ --dialog-title-font-size: var(--pref-viewer-controls-dialog-title-font-size, calc(1.1 * ${PrefViewerStyleVariables.fontSize}));
1301
+ --dialog-section-font-size: var(--pref-viewer-controls-dialog-section-font-size, calc(1.05 * ${PrefViewerStyleVariables.fontSize}));
1302
+ --dialog-button-font-size: var(--pref-viewer-controls-dialog-button-font-size, calc(0.95 * ${PrefViewerStyleVariables.fontSize}));
1303
+ --dialog-width: var(--pref-viewer-controls-dialog-width, auto);
1304
+ --dialog-min-width: 320px;
1305
+ --dialog-control-height: var(--pref-viewer-controls-dialog-control-height, 0px);
1306
+ --dialog-option-size: var(--pref-viewer-controls-dialog-option-size, auto);
1307
+ --dialog-option-gap: 8px;
793
1308
  --dialog-bg-color: #ffffff;
794
1309
  --dialog-backdrop-color: rgba(0, 0, 0, 0.25);
795
1310
  --dialog-border-color: #e7e7e7;
796
1311
  --dialog-box-shadow: 0 4px 24px rgba(0, 0, 0, 0.25);
1312
+ --dialog-control-feedback-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
1313
+ --dialog-control-feedback-shadow: 0 0 0 3px var(--dialog-control-feedback-color);
1314
+ --dialog-control-feedback-outline: 3px solid var(--dialog-control-feedback-color);
1315
+ --dialog-control-feedback-offset: 1px;
797
1316
  --button-default-bg-color: #bbbbbb;
798
1317
  --button-default-bg-color-hover: color-mix(in oklab, var(--button-default-bg-color), black 10%);
799
1318
  --button-primary-bg-color: var(--color-primary);
@@ -804,12 +1323,18 @@ export const PrefViewerDialogStyles = `
804
1323
  --dialog-border-radius: calc(var(--button-border-radius) + var(--dialog-general-space));
805
1324
  }
806
1325
 
807
- pref-viewer-dialog:not {
1326
+ pref-viewer-dialog,
1327
+ pref-viewer-dialog * {
1328
+ -webkit-tap-highlight-color: transparent;
1329
+ }
1330
+
1331
+ pref-viewer-dialog:not([open]) {
808
1332
  display: none;
809
1333
  }
810
1334
 
811
1335
  pref-viewer-dialog[open] {
812
1336
  font-family: ${PrefViewerStyleVariables.fontFamily};
1337
+ font-size: var(--dialog-font-size);
813
1338
  grid-row: 1;
814
1339
  grid-column: 1;
815
1340
  overflow: hidden;
@@ -835,10 +1360,14 @@ export const PrefViewerDialogStyles = `
835
1360
  border-radius: var(--dialog-border-radius);
836
1361
  box-shadow: var(--dialog-box-shadow);
837
1362
  padding: 0;
838
- min-width: 320px;
839
- max-width: 90%;
840
- max-height: 90%;
1363
+ box-sizing: border-box;
1364
+ width: var(--dialog-width);
1365
+ min-width: min(var(--dialog-min-width), calc(100% - var(--dialog-general-space) * 2));
1366
+ max-width: min(90%, calc(100% - var(--dialog-general-space) * 2));
1367
+ max-height: max(0px, calc(100% - var(--dialog-general-space) * 2));
841
1368
  overflow: auto;
1369
+ overscroll-behavior: contain;
1370
+ overflow-wrap: anywhere;
842
1371
  }
843
1372
 
844
1373
  pref-viewer-dialog .dialog-header {
@@ -849,7 +1378,7 @@ export const PrefViewerDialogStyles = `
849
1378
  pref-viewer-dialog .dialog-header h3 {
850
1379
  margin: 0;
851
1380
  font-weight: 500;
852
- font-size: calc(1.1 * ${PrefViewerStyleVariables.fontSize});
1381
+ font-size: var(--dialog-title-font-size);
853
1382
  }
854
1383
 
855
1384
  pref-viewer-dialog .dialog-content {
@@ -859,20 +1388,23 @@ export const PrefViewerDialogStyles = `
859
1388
  pref-viewer-dialog .dialog-content h4 {
860
1389
  margin: 0;
861
1390
  font-weight: 500;
862
- font-size: calc(1.05 * ${PrefViewerStyleVariables.fontSize});
1391
+ font-size: var(--dialog-section-font-size);
863
1392
  }
864
1393
 
865
1394
  pref-viewer-dialog .dialog-footer {
866
1395
  border-top: 1px solid var(--dialog-border-color);
867
1396
  padding: var(--dialog-general-space);
868
1397
  display: flex;
1398
+ flex-wrap: wrap;
869
1399
  gap: var(--dialog-general-space);
870
1400
  justify-content: stretch;
871
1401
  }
872
1402
 
873
1403
  pref-viewer-dialog .dialog-footer button {
874
- width: 100%;
875
- font-size: calc(0.95 * ${PrefViewerStyleVariables.fontSize});
1404
+ flex: 1 1 100px;
1405
+ min-height: var(--dialog-control-height);
1406
+ font-family: inherit;
1407
+ font-size: var(--dialog-button-font-size);
876
1408
  font-weight: 600;
877
1409
  padding: var(--button-padding-vertical) var(--button-padding-horizontal);
878
1410
  border-radius: var(--button-border-radius);
@@ -886,11 +1418,83 @@ export const PrefViewerDialogStyles = `
886
1418
  background: var(--button-primary-bg-color);
887
1419
  }
888
1420
 
889
- pref-viewer-dialog .dialog-footer button:hover {
1421
+ pref-viewer-dialog .dialog-content label {
1422
+ display: inline-flex;
1423
+ flex-wrap: wrap;
1424
+ align-items: center;
1425
+ gap: var(--dialog-option-gap);
1426
+ min-height: var(--dialog-control-height);
1427
+ max-width: 100%;
1428
+ }
1429
+
1430
+ pref-viewer-dialog .dialog-content :is(button, input, select, textarea) {
1431
+ font: inherit;
1432
+ box-sizing: border-box;
1433
+ min-width: 0;
1434
+ max-width: 100%;
1435
+ }
1436
+
1437
+ pref-viewer-dialog .dialog-content :is(button, select, input:not([type="radio"]):not([type="checkbox"]):not([type="hidden"])) {
1438
+ min-height: var(--dialog-control-height);
1439
+ }
1440
+
1441
+ pref-viewer-dialog .dialog-content :is(input[type="radio"], input[type="checkbox"]) {
1442
+ width: var(--dialog-option-size);
1443
+ height: var(--dialog-option-size);
1444
+ flex-shrink: 0;
1445
+ }
1446
+
1447
+ pref-viewer-dialog .download-dialog-form {
1448
+ display: flex;
1449
+ flex-direction: column;
1450
+ gap: var(--dialog-general-space);
1451
+ }
1452
+
1453
+ pref-viewer-dialog .download-dialog-options {
1454
+ display: flex;
1455
+ flex-wrap: wrap;
1456
+ gap: var(--dialog-option-gap) var(--dialog-general-space);
1457
+ }
1458
+ pref-viewer-dialog :is(.dialog-content, .dialog-footer) button:enabled:is(:active, :focus-visible, [data-touch-pressed]) {
890
1459
  background: var(--button-default-bg-color-hover);
1460
+ box-shadow: var(--dialog-control-feedback-shadow);
891
1461
  }
892
1462
 
893
- pref-viewer-dialog .dialog-footer button.primary:hover {
1463
+ pref-viewer-dialog :is(.dialog-content, .dialog-footer) button:enabled.primary:is(:active, :focus-visible, [data-touch-pressed]) {
894
1464
  background: var(--button-primary-bg-color-hover);
895
1465
  }
1466
+
1467
+ pref-viewer-dialog[data-keyboard-navigation] :is(input:not([type="hidden"]), select, textarea):enabled:focus,
1468
+ pref-viewer-dialog :is(input:not([type="hidden"]), select, textarea):enabled:is(:active, :focus-visible, [data-touch-pressed]),
1469
+ pref-viewer-dialog a[href]:not([aria-disabled="true"]):is(:active, :focus-visible, [data-touch-pressed]) {
1470
+ outline: var(--dialog-control-feedback-outline);
1471
+ outline-offset: var(--dialog-control-feedback-offset);
1472
+ }
1473
+
1474
+ pref-viewer-dialog :is(input[type="radio"], input[type="checkbox"]) {
1475
+ accent-color: var(--color-primary);
1476
+ }
1477
+
1478
+ pref-viewer-dialog input[type="radio"] {
1479
+ border-radius: 50%;
1480
+ }
1481
+
1482
+ @media (hover: hover) and (pointer: fine) {
1483
+ pref-viewer-dialog :is(.dialog-content, .dialog-footer) button:enabled:hover {
1484
+ background: var(--button-default-bg-color-hover);
1485
+ box-shadow: var(--dialog-control-feedback-shadow);
1486
+ }
1487
+
1488
+ pref-viewer-dialog :is(.dialog-content, .dialog-footer) button:enabled.primary:hover {
1489
+ background: var(--button-primary-bg-color-hover);
1490
+ }
1491
+
1492
+ pref-viewer-dialog :is(input:not([type="hidden"]), select, textarea):enabled:hover,
1493
+ pref-viewer-dialog label:hover :is(input[type="radio"], input[type="checkbox"]):enabled,
1494
+ pref-viewer-dialog a[href]:not([aria-disabled="true"]):hover {
1495
+ outline: var(--dialog-control-feedback-outline);
1496
+ outline-offset: var(--dialog-control-feedback-offset);
1497
+ }
1498
+ }
1499
+
896
1500
  `;