storysplat-viewer 0.1.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 (41) hide show
  1. package/README.md +659 -0
  2. package/dist/esm/dist/styles/viewer.css +2 -0
  3. package/dist/esm/dist/styles/viewer.css.map +1 -0
  4. package/dist/esm/index.js +187107 -0
  5. package/dist/esm/index.js.map +1 -0
  6. package/dist/types/core/cameraManager.d.ts +35 -0
  7. package/dist/types/core/lightingManager.d.ts +9 -0
  8. package/dist/types/core/renderLoopManager.d.ts +24 -0
  9. package/dist/types/core/sceneManager.d.ts +19 -0
  10. package/dist/types/core/splatLoader.d.ts +12 -0
  11. package/dist/types/features/analyticsManager.d.ts +102 -0
  12. package/dist/types/features/animatedGifTexture.d.ts +37 -0
  13. package/dist/types/features/audioManager.d.ts +72 -0
  14. package/dist/types/features/collisionManager.d.ts +51 -0
  15. package/dist/types/features/customMeshManager.d.ts +15 -0
  16. package/dist/types/features/hotspotManager.d.ts +16 -0
  17. package/dist/types/features/navigationManager.d.ts +142 -0
  18. package/dist/types/features/particleManager.d.ts +14 -0
  19. package/dist/types/features/sceneFeatures.d.ts +14 -0
  20. package/dist/types/features/skyboxManager.d.ts +8 -0
  21. package/dist/types/features/splatSwapManager.d.ts +87 -0
  22. package/dist/types/features/xrManager.d.ts +12 -0
  23. package/dist/types/index.d.ts +55 -0
  24. package/dist/types/types/dataTypes.d.ts +248 -0
  25. package/dist/types/types/hotspotTypes.d.ts +82 -0
  26. package/dist/types/types/index.d.ts +6 -0
  27. package/dist/types/types/interactionTypes.d.ts +43 -0
  28. package/dist/types/types/navigationTypes.d.ts +41 -0
  29. package/dist/types/types/sceneFeatures.d.ts +186 -0
  30. package/dist/types/types/viewerOptions.d.ts +245 -0
  31. package/dist/types/types.d.ts +106 -0
  32. package/dist/types/ui/helpPanel.d.ts +32 -0
  33. package/dist/types/ui/muteButton.d.ts +30 -0
  34. package/dist/types/ui/preloader.d.ts +40 -0
  35. package/dist/types/ui/startButton.d.ts +29 -0
  36. package/dist/types/ui/uiManager.d.ts +97 -0
  37. package/dist/types/ui/watermark.d.ts +18 -0
  38. package/dist/types/utils/helpers.d.ts +81 -0
  39. package/dist/types/viewer-simple.d.ts +30 -0
  40. package/dist/types/viewer.d.ts +105 -0
  41. package/package.json +47 -0
package/README.md ADDED
@@ -0,0 +1,659 @@
1
+ # StorySplat Viewer
2
+
3
+ A powerful npm package for embedding and interacting with 3D Gaussian Splatting scenes in web applications. StorySplat Viewer provides a complete solution for displaying, navigating, and interacting with .splat files with built-in support for waypoints, hotspots, and multiple camera modes.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Overview](#overview)
8
+ - [Installation](#installation)
9
+ - [Quick Start](#quick-start)
10
+ - [API Reference](#api-reference)
11
+ - [Configuration Options](#configuration-options)
12
+ - [Examples](#examples)
13
+ - [Events and Callbacks](#events-and-callbacks)
14
+ - [Styling](#styling)
15
+ - [Migration Guide](#migration-guide)
16
+ - [Troubleshooting](#troubleshooting)
17
+
18
+ ## Overview
19
+
20
+ StorySplat Viewer is a TypeScript/JavaScript library that enables seamless integration of 3D Gaussian Splatting scenes into web applications. It provides:
21
+
22
+ - 🎥 Multiple camera modes (orbit, free, first-person)
23
+ - 🎯 Interactive hotspots with rich content
24
+ - 🚶 Waypoint-based navigation system
25
+ - 📱 Mobile-friendly touch controls
26
+ - 🎨 Customizable UI and styling
27
+ - 🔧 Comprehensive API for programmatic control
28
+ - âš¡ Optimized performance with BabylonJS engine
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ npm install @storysplat/viewer
34
+ ```
35
+
36
+ Or using yarn:
37
+
38
+ ```bash
39
+ yarn add @storysplat/viewer
40
+ ```
41
+
42
+ ## Quick Start
43
+
44
+ ```javascript
45
+ import { initializeViewer } from '@storysplat/viewer';
46
+ import '@storysplat/viewer/dist/styles/viewer.css';
47
+
48
+ // Initialize the viewer
49
+ const viewerElement = document.getElementById('storysplat-viewer');
50
+ const viewer = initializeViewer(viewerElement, {
51
+ splatUrl: 'https://example.com/scene.splat',
52
+ targetFps: 60,
53
+ fov: 60,
54
+ cameraMode: 'orbit'
55
+ });
56
+
57
+ // Load additional splats
58
+ await viewer.loadSplat('https://example.com/another.splat');
59
+
60
+ // Switch camera mode
61
+ viewer.setCameraMode('free');
62
+ ```
63
+
64
+ ## API Reference
65
+
66
+ ### initializeViewer
67
+
68
+ Creates and initializes a new StorySplat viewer instance.
69
+
70
+ ```typescript
71
+ function initializeViewer(
72
+ element: HTMLElement,
73
+ data: StorySplatData,
74
+ options?: Partial<ViewerOptions>
75
+ ): ViewerInstance
76
+ ```
77
+
78
+ **Parameters:**
79
+ - `element` (HTMLElement): The DOM element to render the viewer into
80
+ - `data` (StorySplatData): Scene data including splat URL and configuration
81
+ - `options` (Partial<ViewerOptions>): Optional viewer configuration
82
+
83
+ **Returns:** ViewerInstance object for controlling the viewer
84
+
85
+ ### ViewerInstance Interface
86
+
87
+ The main interface for interacting with the viewer after initialization.
88
+
89
+ ```typescript
90
+ interface ViewerInstance {
91
+ // Scene Management
92
+ loadSplat(url: string, options?: LoadOptions): Promise<void>;
93
+ clearScene(): void;
94
+ destroy(): void;
95
+
96
+ // Camera Controls
97
+ setCameraMode(mode: 'orbit' | 'free' | 'firstPerson'): void;
98
+ getCameraMode(): string;
99
+ setCameraPosition(x: number, y: number, z: number): void;
100
+ getCameraPosition(): { x: number; y: number; z: number };
101
+ setCameraTarget(x: number, y: number, z: number): void;
102
+ getCameraTarget(): { x: number; y: number; z: number };
103
+
104
+ // Navigation
105
+ navigateToWaypoint(waypointId: string): Promise<void>;
106
+ getWaypoints(): Waypoint[];
107
+ startWaypointTour(waypointIds?: string[]): void;
108
+ stopWaypointTour(): void;
109
+
110
+ // Hotspots
111
+ getHotspots(): Hotspot[];
112
+ showHotspot(hotspotId: string): void;
113
+ hideHotspot(hotspotId: string): void;
114
+ updateHotspot(hotspotId: string, data: Partial<Hotspot>): void;
115
+
116
+ // Rendering
117
+ setRenderingEnabled(enabled: boolean): void;
118
+ takeScreenshot(options?: ScreenshotOptions): Promise<Blob>;
119
+
120
+ // Scene Properties
121
+ setBackgroundColor(color: string): void;
122
+ setSkybox(skyboxUrl: string): void;
123
+ setFog(enabled: boolean, density?: number): void;
124
+
125
+ // Events
126
+ on(event: string, callback: Function): void;
127
+ off(event: string, callback: Function): void;
128
+ }
129
+ ```
130
+
131
+ ### Data Types
132
+
133
+ #### StorySplatData
134
+
135
+ ```typescript
136
+ interface StorySplatData {
137
+ splatUrl: string;
138
+ waypoints?: Waypoint[];
139
+ hotspots?: Hotspot[];
140
+ cameraSettings?: CameraSettings;
141
+ environmentSettings?: EnvironmentSettings;
142
+ meshes?: CustomMesh[];
143
+ particleSystems?: ParticleSystemConfig[];
144
+ }
145
+ ```
146
+
147
+ #### ViewerOptions
148
+
149
+ ```typescript
150
+ interface ViewerOptions {
151
+ // Camera settings
152
+ cameraMode?: 'orbit' | 'free' | 'firstPerson';
153
+ fov?: number;
154
+ initialCameraPosition?: { x: number; y: number; z: number };
155
+ initialCameraTarget?: { x: number; y: number; z: number };
156
+
157
+ // Rendering
158
+ targetFps?: number;
159
+ backgroundColor?: string;
160
+ skyboxUrl?: string;
161
+
162
+ // UI Options
163
+ showToolbar?: boolean;
164
+ showMobileControls?: boolean;
165
+ showLoadingIndicator?: boolean;
166
+ watermarkText?: string;
167
+
168
+ // Interaction
169
+ enableHotspots?: boolean;
170
+ enableWaypoints?: boolean;
171
+ enableCollisions?: boolean;
172
+
173
+ // Callbacks
174
+ onReady?: () => void;
175
+ onError?: (error: Error) => void;
176
+ onProgress?: (progress: number) => void;
177
+ onHotspotClick?: (hotspot: Hotspot) => void;
178
+ onWaypointReached?: (waypoint: Waypoint) => void;
179
+ }
180
+ ```
181
+
182
+ #### Waypoint
183
+
184
+ ```typescript
185
+ interface Waypoint {
186
+ id: string;
187
+ name: string;
188
+ position: { x: number; y: number; z: number };
189
+ lookAt?: { x: number; y: number; z: number };
190
+ duration?: number; // Animation duration in ms
191
+ easing?: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out';
192
+ }
193
+ ```
194
+
195
+ #### Hotspot
196
+
197
+ ```typescript
198
+ interface Hotspot {
199
+ id: string;
200
+ position: { x: number; y: number; z: number };
201
+ type: 'info' | 'link' | 'media' | 'custom';
202
+ title?: string;
203
+ content?: string;
204
+ mediaUrl?: string;
205
+ linkUrl?: string;
206
+ icon?: string;
207
+ color?: string;
208
+ size?: number;
209
+ alwaysVisible?: boolean;
210
+ }
211
+ ```
212
+
213
+ ## Configuration Options
214
+
215
+ ### Camera Settings
216
+
217
+ ```javascript
218
+ const viewer = initializeViewer(element, {
219
+ splatUrl: 'scene.splat'
220
+ }, {
221
+ cameraMode: 'orbit', // 'orbit' | 'free' | 'firstPerson'
222
+ fov: 60, // Field of view in degrees
223
+ initialCameraPosition: { x: 0, y: 5, z: 10 },
224
+ initialCameraTarget: { x: 0, y: 0, z: 0 }
225
+ });
226
+ ```
227
+
228
+ ### Rendering Options
229
+
230
+ ```javascript
231
+ const viewer = initializeViewer(element, data, {
232
+ targetFps: 60, // Target frame rate
233
+ backgroundColor: '#000000', // Background color
234
+ skyboxUrl: '/assets/skybox.dds', // Optional skybox
235
+ showLoadingIndicator: true // Show loading progress
236
+ });
237
+ ```
238
+
239
+ ### UI Options
240
+
241
+ ```javascript
242
+ const viewer = initializeViewer(element, data, {
243
+ showToolbar: true, // Show control toolbar
244
+ showMobileControls: true, // Auto-detect and show mobile controls
245
+ watermarkText: 'My Scene' // Custom watermark
246
+ });
247
+ ```
248
+
249
+ ## Examples
250
+
251
+ ### Basic Setup
252
+
253
+ ```javascript
254
+ import { initializeViewer } from '@storysplat/viewer';
255
+ import '@storysplat/viewer/dist/styles/viewer.css';
256
+
257
+ function setupViewer() {
258
+ const element = document.getElementById('storysplat-viewer');
259
+
260
+ const viewer = initializeViewer(element, {
261
+ splatUrl: '/models/scene.splat'
262
+ }, {
263
+ onReady: () => console.log('Viewer ready!'),
264
+ onError: (error) => console.error('Viewer error:', error)
265
+ });
266
+
267
+ return viewer;
268
+ }
269
+ ```
270
+
271
+ ### Camera Mode Switching
272
+
273
+ ```javascript
274
+ // Create camera mode buttons
275
+ const viewer = initializeViewer(element, { splatUrl: 'scene.splat' });
276
+
277
+ document.getElementById('orbit-btn').onclick = () => {
278
+ viewer.setCameraMode('orbit');
279
+ };
280
+
281
+ document.getElementById('free-btn').onclick = () => {
282
+ viewer.setCameraMode('free');
283
+ };
284
+
285
+ document.getElementById('fps-btn').onclick = () => {
286
+ viewer.setCameraMode('firstPerson');
287
+ };
288
+ ```
289
+
290
+ ### Waypoint Navigation
291
+
292
+ ```javascript
293
+ const viewer = initializeViewer(element, {
294
+ splatUrl: '/models/scene.splat',
295
+ waypoints: [
296
+ { id: 'entrance', name: 'Entrance', position: { x: 0, y: 0, z: 5 } },
297
+ { id: 'center', name: 'Center', position: { x: 0, y: 0, z: 0 } },
298
+ { id: 'exit', name: 'Exit', position: { x: 0, y: 0, z: -5 } }
299
+ ]
300
+ });
301
+
302
+ // Navigate to specific waypoint
303
+ await viewer.navigateToWaypoint('center');
304
+
305
+ // Start automated tour
306
+ viewer.startWaypointTour(['entrance', 'center', 'exit']);
307
+ ```
308
+
309
+ ### Hotspot Interaction
310
+
311
+ ```javascript
312
+ const viewer = initializeViewer(element, {
313
+ splatUrl: 'scene.splat',
314
+ hotspots: [
315
+ {
316
+ id: 'info-1',
317
+ position: { x: 2, y: 1, z: 0 },
318
+ type: 'info',
319
+ title: 'Information',
320
+ content: 'This is an information hotspot'
321
+ }
322
+ ]
323
+ }, {
324
+ onHotspotClick: (hotspot) => {
325
+ console.log('Hotspot clicked:', hotspot);
326
+ // Custom hotspot handling
327
+ if (hotspot.type === 'link') {
328
+ window.open(hotspot.linkUrl, '_blank');
329
+ }
330
+ }
331
+ });
332
+
333
+ // Add custom hotspot dynamically
334
+ viewer.updateHotspot('custom-1', {
335
+ position: { x: -2, y: 1, z: 0 },
336
+ type: 'info',
337
+ title: 'Dynamic Hotspot',
338
+ content: 'Added at runtime',
339
+ color: '#ff0000'
340
+ });
341
+ ```
342
+
343
+ ### Dynamic Splat Loading
344
+
345
+ ```javascript
346
+ const viewer = initializeViewer(element, {
347
+ splatUrl: '/models/base.splat'
348
+ });
349
+
350
+ // Load additional splats
351
+ async function loadScenes() {
352
+ await viewer.loadSplat('/models/building.splat');
353
+ await viewer.loadSplat('/models/furniture.splat', {
354
+ position: { x: 0, y: 0, z: 0 },
355
+ scale: 1.5,
356
+ rotation: { x: 0, y: 90, z: 0 }
357
+ });
358
+ }
359
+
360
+ // Clear and load new scene
361
+ async function switchScene() {
362
+ viewer.clearScene();
363
+ await viewer.loadSplat('/models/new-scene.splat');
364
+ }
365
+ ```
366
+
367
+ ### Custom UI Integration
368
+
369
+ ```javascript
370
+ const viewer = initializeViewer(element, data, {
371
+ showToolbar: false // Hide default toolbar
372
+ });
373
+
374
+ // Create custom controls
375
+ const customUI = {
376
+ updateCameraInfo() {
377
+ const pos = viewer.getCameraPosition();
378
+ document.getElementById('camera-info').textContent =
379
+ `Camera: ${pos.x.toFixed(2)}, ${pos.y.toFixed(2)}, ${pos.z.toFixed(2)}`;
380
+ },
381
+
382
+ takePhoto() {
383
+ viewer.takeScreenshot({ width: 1920, height: 1080 })
384
+ .then(blob => {
385
+ const url = URL.createObjectURL(blob);
386
+ window.open(url, '_blank');
387
+ });
388
+ }
389
+ };
390
+
391
+ // Update camera info on render
392
+ viewer.on('render', customUI.updateCameraInfo);
393
+ ```
394
+
395
+ ## Events and Callbacks
396
+
397
+ ### Available Events
398
+
399
+ ```javascript
400
+ viewer.on('ready', () => {
401
+ console.log('Viewer initialized and ready');
402
+ });
403
+
404
+ viewer.on('loadStart', (url) => {
405
+ console.log('Started loading:', url);
406
+ });
407
+
408
+ viewer.on('loadProgress', (progress) => {
409
+ console.log('Loading progress:', progress + '%');
410
+ });
411
+
412
+ viewer.on('loadComplete', () => {
413
+ console.log('Loading complete');
414
+ });
415
+
416
+ viewer.on('error', (error) => {
417
+ console.error('Viewer error:', error);
418
+ });
419
+
420
+ viewer.on('cameraMove', (position) => {
421
+ console.log('Camera moved to:', position);
422
+ });
423
+
424
+ viewer.on('hotspotClick', (hotspot) => {
425
+ console.log('Hotspot clicked:', hotspot);
426
+ });
427
+
428
+ viewer.on('waypointReached', (waypoint) => {
429
+ console.log('Reached waypoint:', waypoint);
430
+ });
431
+
432
+ viewer.on('render', (deltaTime) => {
433
+ // Called every frame
434
+ });
435
+ ```
436
+
437
+ ### Removing Event Listeners
438
+
439
+ ```javascript
440
+ const handler = (position) => console.log(position);
441
+ viewer.on('cameraMove', handler);
442
+
443
+ // Later...
444
+ viewer.off('cameraMove', handler);
445
+ ```
446
+
447
+ ## Styling
448
+
449
+ ### CSS Variables
450
+
451
+ The viewer uses CSS variables for theming:
452
+
453
+ ```css
454
+ #storysplat-viewer {
455
+ --storysplat-primary-color: #007bff;
456
+ --storysplat-background-color: #000000;
457
+ --storysplat-text-color: #ffffff;
458
+ --storysplat-toolbar-bg: rgba(0, 0, 0, 0.8);
459
+ --storysplat-button-hover: #0056b3;
460
+ --storysplat-hotspot-color: #ffc107;
461
+ }
462
+ ```
463
+
464
+ ### Custom Styling
465
+
466
+ ```css
467
+ /* Override toolbar styles */
468
+ .storysplat-toolbar {
469
+ background: linear-gradient(to bottom, rgba(0,0,0,0.9), rgba(0,0,0,0.7));
470
+ border-radius: 8px;
471
+ margin: 10px;
472
+ }
473
+
474
+ /* Custom hotspot appearance */
475
+ .storysplat-hotspot {
476
+ animation: pulse 2s infinite;
477
+ }
478
+
479
+ @keyframes pulse {
480
+ 0% { transform: scale(1); }
481
+ 50% { transform: scale(1.1); }
482
+ 100% { transform: scale(1); }
483
+ }
484
+
485
+ /* Mobile controls */
486
+ .storysplat-mobile-controls {
487
+ opacity: 0.8;
488
+ }
489
+ ```
490
+
491
+ ### Required CSS
492
+
493
+ Make sure to import the viewer CSS:
494
+
495
+ ```javascript
496
+ import '@storysplat/viewer/dist/styles/viewer.css';
497
+ ```
498
+
499
+ Or include it in your HTML:
500
+
501
+ ```html
502
+ <link rel="stylesheet" href="node_modules/@storysplat/viewer/dist/styles/viewer.css">
503
+ ```
504
+
505
+ ## Migration Guide
506
+
507
+ ### From Exported HTML to NPM Package
508
+
509
+ If you're migrating from StorySplat's exported HTML files to the npm package:
510
+
511
+ 1. **Extract Scene Data**
512
+ ```javascript
513
+ // From your exported HTML's scene configuration
514
+ const sceneData = {
515
+ splatUrl: 'path/to/your.splat',
516
+ waypoints: [...], // Copy waypoints array
517
+ hotspots: [...], // Copy hotspots array
518
+ cameraSettings: {
519
+ position: { x: 0, y: 5, z: 10 },
520
+ target: { x: 0, y: 0, z: 0 }
521
+ }
522
+ };
523
+ ```
524
+
525
+ 2. **Initialize Viewer**
526
+ ```javascript
527
+ const element = document.getElementById('viewer-container');
528
+ const viewer = initializeViewer(element, sceneData, {
529
+ // Match your export settings
530
+ backgroundColor: '#000000',
531
+ targetFps: 60,
532
+ cameraMode: 'orbit'
533
+ });
534
+ ```
535
+
536
+ 3. **Update Event Handlers**
537
+ ```javascript
538
+ // Old: onclick handlers in HTML
539
+ // New: Event listeners
540
+ viewer.on('hotspotClick', handleHotspotClick);
541
+ viewer.on('waypointReached', handleWaypointReached);
542
+ ```
543
+
544
+ 4. **Handle Assets**
545
+ ```javascript
546
+ // Ensure all assets use absolute URLs or are properly bundled
547
+ const sceneData = {
548
+ splatUrl: '/assets/scene.splat',
549
+ skyboxUrl: '/assets/skybox.dds'
550
+ };
551
+ ```
552
+
553
+ ## Troubleshooting
554
+
555
+ ### Common Issues
556
+
557
+ #### Viewer Not Rendering
558
+
559
+ ```javascript
560
+ // Check container exists and has dimensions
561
+ const container = document.getElementById('storysplat-viewer');
562
+ if (!container) {
563
+ console.error('Container element not found');
564
+ }
565
+
566
+ // Ensure container has explicit dimensions
567
+ #storysplat-viewer {
568
+ width: 100%;
569
+ height: 500px; /* Must have explicit height */
570
+ }
571
+ ```
572
+
573
+ #### CORS Errors
574
+
575
+ ```javascript
576
+ // For cross-origin splat files, ensure proper CORS headers
577
+ // Or use a proxy:
578
+ const viewer = initializeViewer(element, {
579
+ splatUrl: '/api/proxy?url=' + encodeURIComponent(splatUrl)
580
+ });
581
+ ```
582
+
583
+ #### Performance Issues
584
+
585
+ ```javascript
586
+ // Reduce target FPS for better performance
587
+ const viewer = initializeViewer(element, data, {
588
+ targetFps: 30, // Lower FPS
589
+ // Disable expensive features
590
+ skyboxUrl: null,
591
+ showLoadingIndicator: false
592
+ });
593
+
594
+ // Optimize rendering
595
+ viewer.setRenderingEnabled(false); // Pause when not visible
596
+ ```
597
+
598
+ #### Mobile Touch Controls Not Working
599
+
600
+ ```javascript
601
+ // Ensure mobile controls are enabled
602
+ const viewer = initializeViewer(element, data, {
603
+ showMobileControls: true,
604
+ // Force mobile controls for testing
605
+ forceMobileControls: true
606
+ });
607
+
608
+ // Check viewport meta tag
609
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
610
+ ```
611
+
612
+ #### Memory Leaks
613
+
614
+ ```javascript
615
+ // Always cleanup when done
616
+ window.addEventListener('beforeunload', () => {
617
+ viewer.destroy();
618
+ });
619
+
620
+ // In React/Vue/Angular components
621
+ componentWillUnmount() {
622
+ if (this.viewer) {
623
+ this.viewer.destroy();
624
+ }
625
+ }
626
+ ```
627
+
628
+ ### Debug Mode
629
+
630
+ Enable debug logging:
631
+
632
+ ```javascript
633
+ const viewer = initializeViewer(element, data, {
634
+ debug: true, // Enable debug logging
635
+ onDebug: (message) => console.log('[Debug]', message)
636
+ });
637
+ ```
638
+
639
+ ### Browser Compatibility
640
+
641
+ StorySplat Viewer requires:
642
+ - WebGL 2.0 support
643
+ - Modern JavaScript (ES2015+)
644
+ - Recommended browsers:
645
+ - Chrome 80+
646
+ - Firefox 75+
647
+ - Safari 13+
648
+ - Edge 80+
649
+
650
+ ### Support
651
+
652
+ For issues, feature requests, or questions:
653
+ - GitHub Issues: [github.com/storysplat/viewer/issues](https://github.com/storysplat/viewer/issues)
654
+ - Documentation: [docs.storysplat.com](https://docs.storysplat.com)
655
+ - Discord: [discord.gg/storysplat](https://discord.gg/storysplat)
656
+
657
+ ## License
658
+
659
+ MIT License - see LICENSE file for details
@@ -0,0 +1,2 @@
1
+ body,html{font-family:Arial,sans-serif;height:100%;margin:0;overflow:hidden;padding:0;width:100%}#renderCanvas{display:block;height:100%;touch-action:none;width:100%}@keyframes storysplat-viewer-fadeOut{0%{opacity:1}to{opacity:0}}#preloader{align-items:center;background-color:#1e1e1e;display:flex;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;height:100%;justify-content:center;left:0;position:fixed;top:0;transition:opacity .5s ease-out;width:100%;z-index:100000}#preloader.hidden{opacity:0;pointer-events:none}#preloader-content{align-items:center;display:flex;flex-direction:column;gap:20px;padding:40px}#preloader-media{align-items:center;display:flex;gap:40px}#preloader-image,#preloader-image-inverted{height:200px;object-fit:contain;width:auto}#preloader-image-inverted{filter:invert(1);margin-right:-100px}#preloader-lottie{height:200px;width:200px}@media (max-width:768px){#preloader-image{height:100px}#preloader-image-inverted{height:100px;margin-right:-50px}#preloader-lottie{height:100px;width:100px}}#preloader-progress{background:#ffffff1a;border-radius:2px;height:4px;margin-top:20px;overflow:hidden;width:200px}#preloader-progress-bar{background:var(--storysplat-viewer-accent-color,#4caf50);border-radius:2px;height:100%;position:relative;transition:width .3s ease-out;width:0}#preloader-progress-text{color:#fff;font-size:14px;left:50%;position:absolute;top:-25px;transform:translateX(-50%)}#hotspotContent{background-color:#000c;border-radius:10px;box-shadow:0 0 10px #00000080;color:#fff;display:none;flex-direction:column;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;max-width:300px;padding:20px;position:fixed;z-index:100001}#hotspotContent img{border-radius:5px;height:auto;margin-top:10px;max-width:100%}#hotspotContent.fullscreen{align-items:center!important;border-radius:0!important;bottom:0!important;display:flex!important;flex-direction:column!important;height:100%!important;justify-content:center!important;left:0!important;margin:0!important;max-width:none!important;right:0!important;top:0!important;width:100%!important}#hotspotContent.fullscreen img{height:80vh!important;margin-top:0!important;object-fit:contain!important;width:80%!important}#infoPopup{background-color:#000c;border-radius:10px;box-shadow:0 0 10px #00000080;font-size:16px;left:50%;max-width:80%;padding:20px;position:fixed;top:50%;transform:translate(-50%,-50%);z-index:100002}#infoPopup,.storysplat-hotspot-popup{color:#fff;display:none;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.storysplat-hotspot-popup{background-color:#000000bf;border:1px solid #444;border-radius:6px;box-shadow:0 4px 12px #0006;font-size:14px;line-height:1.5;max-height:450px;max-width:320px;overflow-y:auto;padding:15px;pointer-events:auto;position:absolute;z-index:100}.storysplat-hotspot-popup h3{border-bottom:1px solid #fff3;font-size:1.1em;margin-bottom:10px;margin-top:0;padding-bottom:5px}.storysplat-hotspot-popup p{margin-bottom:10px;margin-top:0;white-space:pre-wrap}.storysplat-hotspot-popup img{border-radius:4px;display:block;height:auto;margin-bottom:10px;margin-top:5px;max-width:100%}.storysplat-hotspot-popup iframe{border:none;height:250px;margin-bottom:10px;margin-top:5px;width:100%}.storysplat-hotspot-popup a{background-color:#007bff;border-radius:4px;color:#fff;display:inline-block;margin-top:10px;padding:8px 15px;text-decoration:none;transition:background-color .2s ease}.storysplat-hotspot-popup a:hover{background-color:#0056b3}.storysplat-hotspot-popup button{background:#323232cc;border:none;border-radius:4px;color:#fff;cursor:pointer;font-size:12px;line-height:1;padding:3px 7px;position:absolute;right:8px;top:8px;transition:background-color .2s ease}.storysplat-hotspot-popup button:hover{background:#505050e6}.storysplat-hotspot-popup::-webkit-scrollbar{width:6px}.storysplat-hotspot-popup::-webkit-scrollbar-track{background:#ffffff1a;border-radius:3px}.storysplat-hotspot-popup::-webkit-scrollbar-thumb{background:#ffffff4d;border-radius:3px}.storysplat-hotspot-popup::-webkit-scrollbar-thumb:hover{background:#ffffff80}.storysplat-progress-bar{background-color:#fff3;border-radius:3px;bottom:20px;height:6px;left:50%;max-width:600px;overflow:hidden;pointer-events:none;position:absolute;transform:translateX(-50%);width:80%;z-index:100}.storysplat-progress-indicator{background-color:var(--storysplat-viewer-accent-color,#fff);border-radius:3px;height:100%;transition:width .1s linear;width:0}.storysplat-scroll-controls{align-items:center;background:#000000b3;border-radius:8px;bottom:0;display:flex;flex-direction:column;gap:5px;left:50%;padding:10px;position:absolute;transform:translateX(-50%);width:150px;z-index:1000}.storysplat-scroll-percentage{color:#fff;font-size:18px}.storysplat-waypoint-info{background:#00000080;color:#fff;display:none;font-family:system-ui,-apple-system,sans-serif;left:0;pointer-events:none;position:absolute;right:0;text-align:center;top:0;z-index:1000}.storysplat-waypoint-info.hasContent{padding:50px 30px 30px}.storysplat-nav-buttons{bottom:40px;display:flex;gap:15px;left:50%;position:absolute;transform:translateX(-50%);z-index:101}.storysplat-nav-button{align-items:center;background-color:#0009;border:1px solid #ffffff4d;border-radius:4px;color:#fff;cursor:pointer;display:flex;font-size:14px;gap:5px;justify-content:center;padding:8px 16px;transition:background-color .2s ease,border-color .2s ease;user-select:none}.storysplat-nav-button:hover:not(:disabled){background-color:#000c;border-color:#fff9}.storysplat-nav-button:disabled{cursor:not-allowed;opacity:.4}.storysplat-camera-mode-toggle,.storysplat-walk-explore-toggle{background-color:#0009;border-radius:8px;display:flex;gap:8px;padding:8px;position:absolute;right:15px;top:15px;z-index:1001}.storysplat-mode-button{background-color:#0009;border:1px solid #ffffff4d;border-radius:4px;color:#fff;cursor:pointer;font-size:13px;padding:6px 12px;transition:background-color .2s ease,border-color .2s ease;user-select:none}.storysplat-mode-button:hover:not(.active){background-color:#fff3;border-color:#ffffff80}.storysplat-mode-button.active{background-color:var(--storysplat-viewer-accent-color,#fff);border-color:var(--storysplat-viewer-accent-color,#fff);color:#1e1e1e;cursor:default}.storysplat-camera-mode-toggle+.storysplat-walk-explore-toggle{top:60px}.storysplat-mobile-joystick{background-color:#0000004d;border-radius:50%;bottom:20px;display:none;height:100px;left:20px;position:absolute;width:100px;z-index:101}.storysplat-mobile-joystick .back{background-color:#0000004d!important;border:1px solid #fff3}.storysplat-mobile-joystick .front{background-color:#ffffff80!important}#storysplat-preloader{align-items:center;background-color:#000000d9;color:#fff;display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;height:100%;justify-content:center;left:0;opacity:1;position:absolute;top:0;transition:opacity .5s ease-out;width:100%;z-index:2000}#storysplat-preloader.hidden{opacity:0;pointer-events:none}#storysplat-preloader-logo{display:block;margin-bottom:20px;max-height:100px;max-width:150px}#storysplat-preloader-logo.empty{display:none}#storysplat-preloader-logo img{display:block;max-height:100%;max-width:100%}#storysplat-preloader-logo>div{align-items:center;background:#333;border-radius:5px;box-sizing:border-box;display:flex;height:100%;justify-content:center;overflow:hidden;padding:5px;text-align:center;text-overflow:ellipsis;white-space:nowrap;width:100%}#storysplat-preloader-progress-container{background-color:#555;border-radius:5px;height:10px;margin-bottom:10px;max-width:300px;overflow:hidden;width:80%}#storysplat-preloader-progress-bar{background-color:var(--storysplat-viewer-preloader-color,var(--storysplat-viewer-accent-color,#4caf50));height:100%;transition:width .3s ease;width:0}#storysplat-progress-text{font-size:14px;margin-top:5px}#startButtonContainer,#storysplat-start-overlay{align-items:center;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);background-color:#fff3;display:flex;height:100%;justify-content:center;left:0;opacity:1;position:absolute;top:0;transition:opacity .5s ease-out;width:100%;z-index:5000}#startButtonContainer.hidden,#storysplat-start-overlay.hidden{opacity:0;pointer-events:none}#startButton,#storysplat-start-button{background-color:#ffffff40;border:none;border-radius:5px;box-shadow:0 4px 8px #0000004d;color:#fff;cursor:pointer;font-size:24px;padding:20px 40px;transition:background-color .3s ease}#startButton:hover,#storysplat-start-button:hover{background-color:#ffffff59}#storysplat-watermark,.watermark{background-color:#00000080;border-radius:4px;bottom:10px;color:#ffffffb3;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:11px;padding:3px 8px;position:fixed;right:10px;text-decoration:none;transition:color .2s ease;z-index:1001}#storysplat-watermark:hover,.watermark:hover{color:#fff}#storysplat-help-button{align-items:center;background-color:#0009;border:1px solid #fff3;border-radius:50%;color:#fff;cursor:pointer;display:flex;font-size:20px;font-weight:700;height:36px;justify-content:center;left:10px;position:absolute;top:10px;transform:scale(1);transition:transform .1s ease-out;width:36px;z-index:1002}#storysplat-help-button:hover{transform:scale(1.1)}#storysplat-help-panel{background-color:#141414e6;border:1px solid #ffffff1a;border-radius:8px;box-shadow:0 5px 15px #0006;color:#eee;display:none;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;left:10px;line-height:1.6;max-height:calc(100vh - 70px);max-width:350px;opacity:0;overflow-y:auto;padding:15px 20px;position:absolute;top:55px;transform:translateY(-10px);transition:opacity .3s ease,transform .3s ease;z-index:1001}#storysplat-help-panel.visible{display:block;opacity:1;transform:translateY(0)}#storysplat-help-panel h3{border-bottom:1px solid #fff3;color:#fff;margin-bottom:15px;margin-top:0;padding-bottom:8px}#storysplat-help-panel p{margin-bottom:10px}#storysplat-help-panel ul{list-style:disc;margin:0 0 15px 20px;padding:0}#storysplat-help-panel::-webkit-scrollbar{width:6px}#storysplat-help-panel::-webkit-scrollbar-track{background:#ffffff1a;border-radius:3px}#storysplat-help-panel::-webkit-scrollbar-thumb{background:#fff6;border-radius:3px}#storysplat-help-panel::-webkit-scrollbar-thumb:hover{background:#fff9}#muteButton,.storysplat-mute-button{background:#0000004d;border:none;border-radius:3px;bottom:10px;color:#fff;cursor:pointer;font-size:12px;left:10px;padding:5px 8px;position:fixed;transition:background-color .2s ease;z-index:1000}#muteButton:hover,.storysplat-mute-button:hover{background:#00000080}#fullscreenButton{align-items:center;background-color:#00000080;border:none;border-radius:8px;cursor:pointer;display:flex;height:40px;justify-content:center;position:fixed;right:20px;top:20px;transition:background-color .3s ease;width:40px;z-index:1000}#fullscreenButton:hover{background-color:#000c}#fullscreenButton svg{fill:#fff;height:24px;width:24px}#waypointInfo{border-radius:4px;display:none;font-size:14px;margin-bottom:10px}#scrollControls{background-color:#000000b3;border-radius:10px;bottom:45px;box-shadow:0 4px 6px #0000001a;color:#fff;left:50%;max-width:350px;padding:15px;position:absolute;transform:translateX(-50%);z-index:1000}#scrollControls,#scrollControlsContent{align-items:center;display:flex;flex-direction:column}#scrollControlsContent{justify-content:center;transition:opacity .3s ease-in-out;width:100%}#scrollControlsContent.hidden{opacity:0;pointer-events:none;position:absolute}#scrollPercentage{font-size:18px;margin-bottom:10px}#progressBarContainer{background-color:#ffffff4d;border-radius:5px;height:10px;margin-bottom:10px;overflow:hidden;width:200px}#progressBar{height:100%;transition:width .3s ease;width:0}#progressBar,.button{background-color:var(--storysplat-viewer-accent-color,#4caf50)}.button{border:none;border-radius:5px;color:#fff;cursor:pointer;display:inline-block;font-size:16px;margin:4px 2px;padding:10px 20px;text-align:center;text-decoration:none;transition:background-color .3s}#scrollButtons{display:flex;justify-content:space-between;margin-bottom:0;width:100%;z-index:1000}#toggleCameraMode{background-color:var(--storysplat-viewer-accent-color,#4caf50);border:none;border-radius:5px;color:#fff;cursor:pointer;display:inline-block;font-size:16px;margin:4px 2px;padding:10px 20px;text-align:center;text-decoration:none;transition:background-color .3s}#toggleCameraMode:hover,.button:hover{filter:brightness(.9)}#walkToggleButton{background-color:#00000080;border:none;border-radius:5px;bottom:60px;color:#fff;cursor:pointer;display:none;font-size:14px;padding:10px 20px;position:fixed;right:10px;transition:all .3s ease;z-index:1000}#walkToggleButton.selected{background-color:var(--storysplat-viewer-accent-color,#4caf50)}#walkToggleButton:hover{filter:brightness(.9)}@media (max-width:768px){#scrollControls{bottom:20px;max-width:90%;padding:10px}#progressBarContainer{width:150px}#toggleCameraMode,.button{font-size:14px;padding:8px 15px}#scrollPercentage{font-size:16px}#storysplat-help-panel{max-height:calc(100vh - 100px);max-width:calc(100vw - 40px)}.storysplat-hotspot-popup{max-height:60vh;max-width:calc(100vw - 40px)}}.hidden{display:none!important}.fade-out{animation:storysplat-viewer-fadeOut .5s ease-out forwards}
2
+ /*# sourceMappingURL=viewer.css.map */