senangwebs-tour 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,14 @@
1
1
  # SenangWebs Tour (SWT)
2
2
 
3
3
  A powerful, data-driven 360° virtual tour system built on A-Frame WebVR. Create immersive virtual tours with a visual editor, or integrate the lightweight viewer library into your own projects.
4
- ![SenangWebs Tour Preview](https://raw.githubusercontent.com/a-hakim/senangwebs-tour/master/swt_preview.png)
4
+
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE.md)
6
+ [![Built with A-Frame](https://img.shields.io/badge/Built%20with-AFrame-ef2d5e.svg)](https://aframe.io/)
7
+ [![Built with SenangStart Icons](https://img.shields.io/badge/Built%20with-SenangStart%20Icons-2563EB.svg)](https://github.com/bookklik-technologies/senangstart-icons)
8
+
9
+ | viewer | editor |
10
+ | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
11
+ | ![SenangWebs Tour Preview](https://raw.githubusercontent.com/a-hakim/senangwebs-tour/master/swt_preview2.png) | ![SenangWebs Tour Preview](https://raw.githubusercontent.com/a-hakim/senangwebs-tour/master/swt_preview1.png) |
5
12
 
6
13
  ## Table of Contents
7
14
 
@@ -23,6 +30,7 @@ A powerful, data-driven 360° virtual tour system built on A-Frame WebVR. Create
23
30
  ## Features
24
31
 
25
32
  ### Core Capabilities
33
+
26
34
  - **Visual Editor** - No-code tour builder with click-to-place hotspot interface
27
35
  - **Viewer Library** - Lightweight (12KB minified) JavaScript library for embedding tours
28
36
  - **Standalone Viewer** - Self-contained HTML viewer with drag-and-drop JSON support
@@ -31,6 +39,7 @@ A powerful, data-driven 360° virtual tour system built on A-Frame WebVR. Create
31
39
  - **Multiple Export Formats** - JSON configuration or standalone HTML files
32
40
 
33
41
  ### Developer-Friendly
42
+
34
43
  - **Two Integration Modes** - Declarative (HTML attributes) or Programmatic (JavaScript API)
35
44
  - **Modular Architecture** - Six-controller editor pattern with clear separation of concerns
36
45
  - **Event System** - React to scene loads, hotspot clicks, and navigation events
@@ -41,21 +50,24 @@ A powerful, data-driven 360° virtual tour system built on A-Frame WebVR. Create
41
50
 
42
51
  ### Using the Visual Editor (No Coding Required)
43
52
 
44
- 1. **Start Local Server** (required for A-Frame CORS):
53
+ 1. **Start Local Server**:
54
+
45
55
  ```bash
46
56
  npm install
47
- npm run serve
57
+ npm run dev
48
58
  ```
49
59
 
50
60
  2. **Open Editor**: Navigate to `http://localhost:8080/examples/editor.html`
51
61
 
52
62
  3. **Create Your Tour**:
63
+
53
64
  - Click **"Add Scene"** and upload 360° panorama images (JPG/PNG)
54
65
  - Click **"Add Hotspot"** then click on the preview to place navigation points
55
66
  - Configure hotspot properties: color, scale, tooltip text, target scene
56
67
  - See changes instantly in the live A-Frame preview
57
68
 
58
69
  4. **Export Your Tour**:
70
+
59
71
  - **JSON Export** → Portable config file for library integration
60
72
  - **Viewer Export** → Self-contained HTML file (no dependencies needed)
61
73
 
@@ -68,52 +80,55 @@ A powerful, data-driven 360° virtual tour system built on A-Frame WebVR. Create
68
80
  ```html
69
81
  <!DOCTYPE html>
70
82
  <html>
71
- <head>
72
- <script src="https://aframe.io/releases/1.7.0/aframe.min.js"></script>
73
- <script src="https://cdn.jsdelivr.net/gh/a-hakim/senangwebs-tour@latest/dist/swt.min.js"></script>
74
- </head>
75
- <body>
76
- <a-scene id="tour-scene">
77
- <a-camera><a-cursor></a-cursor></a-camera>
78
- </a-scene>
79
-
80
- <script>
81
- const config = {
82
- initialScene: "room1",
83
- scenes: {
84
- room1: {
85
- name: "Living Room",
86
- panorama: "path/to/panorama1.jpg",
87
- hotspots: [{
88
- position: { x: 5, y: 0, z: -5 },
89
- action: { type: "navigateTo", target: "room2" },
90
- appearance: { color: "#FF6B6B", scale: 1.5 },
91
- tooltip: { text: "Go to Kitchen" }
92
- }]
83
+ <head>
84
+ <script src="https://unpkg.com/senangwebs-tour@latest/dist/swt.min.js"></script>
85
+ </head>
86
+ <body>
87
+ <a-scene id="tour-scene">
88
+ <a-camera><a-cursor></a-cursor></a-camera>
89
+ </a-scene>
90
+
91
+ <script>
92
+ const config = {
93
+ initialScene: "room1",
94
+ scenes: {
95
+ room1: {
96
+ name: "Living Room",
97
+ panorama: "path/to/panorama1.jpg",
98
+ hotspots: [
99
+ {
100
+ position: { x: 5, y: 0, z: -5 },
101
+ action: { type: "navigateTo", target: "room2" },
102
+ appearance: { color: "#FF6B6B", scale: 1.5 },
103
+ tooltip: { text: "Go to Kitchen" },
104
+ },
105
+ ],
106
+ },
107
+ room2: {
108
+ name: "Kitchen",
109
+ panorama: "path/to/panorama2.jpg",
110
+ hotspots: [
111
+ {
112
+ position: { x: -5, y: 0, z: 5 },
113
+ action: { type: "navigateTo", target: "room1" },
114
+ appearance: { color: "#4ECDC4", scale: 1.5 },
115
+ tooltip: { text: "Back to Living Room" },
116
+ },
117
+ ],
118
+ },
93
119
  },
94
- room2: {
95
- name: "Kitchen",
96
- panorama: "path/to/panorama2.jpg",
97
- hotspots: [{
98
- position: { x: -5, y: 0, z: 5 },
99
- action: { type: "navigateTo", target: "room1" },
100
- appearance: { color: "#4ECDC4", scale: 1.5 },
101
- tooltip: { text: "Back to Living Room" }
102
- }]
103
- }
104
- }
105
- };
106
-
107
- const scene = document.querySelector('#tour-scene');
108
- scene.addEventListener('loaded', () => {
109
- const tour = new SWT.Tour(scene, config);
110
- tour.addEventListener('scene-loaded', (e) => {
111
- console.log('Now viewing:', e.detail.sceneName);
120
+ };
121
+
122
+ const scene = document.querySelector("#tour-scene");
123
+ scene.addEventListener("loaded", () => {
124
+ const tour = new SWT.Tour(scene, config);
125
+ tour.addEventListener("scene-loaded", (e) => {
126
+ console.log("Now viewing:", e.detail.sceneName);
127
+ });
128
+ tour.start();
112
129
  });
113
- tour.start();
114
- });
115
- </script>
116
- </body>
130
+ </script>
131
+ </body>
117
132
  </html>
118
133
  ```
119
134
 
@@ -128,22 +143,26 @@ Build your own tour editor using the `swt-editor.js` bundle. Two initialization
128
143
  ```html
129
144
  <!DOCTYPE html>
130
145
  <html>
131
- <head>
132
- <script src="https://aframe.io/releases/1.7.0/aframe.min.js"></script>
133
- <script src="dist/swt.js"></script>
134
- <script src="dist/swt-editor.js"></script>
135
- <link rel="stylesheet" href="dist/swt-editor.css">
136
- </head>
137
- <body>
138
- <!-- Auto-initializes on page load -->
139
- <div data-swt-editor
140
- data-swt-auto-init="true"
141
- data-swt-project-name="My Virtual Tour">
142
- <div data-swt-scene-list></div>
143
- <div data-swt-preview-area></div>
144
- <div data-swt-properties-panel></div>
145
- </div>
146
- </body>
146
+ <head>
147
+ <script src="https://unpkg.com/senangwebs-tour@latest/dist/swt.js"></script>
148
+ <script src="https://unpkg.com/senangwebs-tour@latest/dist/swt-editor.js"></script>
149
+ <link
150
+ rel="stylesheet"
151
+ href="https://unpkg.com/senangwebs-tour@latest/dist/swt-editor.css"
152
+ />
153
+ </head>
154
+ <body>
155
+ <!-- Auto-initializes on page load -->
156
+ <div
157
+ data-swt-editor
158
+ data-swt-auto-init="true"
159
+ data-swt-project-name="My Virtual Tour"
160
+ >
161
+ <div data-swt-scene-list></div>
162
+ <div data-swt-preview-area></div>
163
+ <div data-swt-properties-panel></div>
164
+ </div>
165
+ </body>
147
166
  </html>
148
167
  ```
149
168
 
@@ -168,47 +187,50 @@ Build your own tour editor using the `swt-editor.js` bundle. Two initialization
168
187
  ```html
169
188
  <!DOCTYPE html>
170
189
  <html>
171
- <head>
172
- <script src="https://aframe.io/releases/1.7.0/aframe.min.js"></script>
173
- <script src="dist/swt.js"></script>
174
- <script src="dist/swt-editor.js"></script>
175
- <link rel="stylesheet" href="dist/swt-editor.css">
176
- </head>
177
- <body>
178
- <div id="editor-container">
179
- <div id="scenes"></div>
180
- <div id="preview"></div>
181
- <div id="properties"></div>
182
- </div>
183
-
184
- <script>
185
- // Create editor instance with custom config
186
- const editor = new TourEditor({
187
- projectName: 'My Custom Tour',
188
- autoSave: true,
189
- autoSaveInterval: 30000
190
- });
191
-
192
- // Initialize with DOM elements
193
- editor.init({
194
- sceneListElement: document.getElementById('scenes'),
195
- previewElement: document.getElementById('preview'),
196
- propertiesElement: document.getElementById('properties')
197
- });
198
-
199
- // Access editor programmatically
200
- editor.addEventListener('scene-added', (scene) => {
201
- console.log('New scene:', scene.name);
202
- });
203
-
204
- // Export tour configuration
205
- const config = editor.exportJSON();
206
- </script>
207
- </body>
190
+ <head>
191
+ <script src="https://unpkg.com/senangwebs-tour@latest/dist/swt.js"></script>
192
+ <script src="https://unpkg.com/senangwebs-tour@latest/dist/swt-editor.js"></script>
193
+ <link
194
+ rel="stylesheet"
195
+ href="https://unpkg.com/senangwebs-tour@latest/dist/swt-editor.css"
196
+ />
197
+ </head>
198
+ <body>
199
+ <div id="editor-container">
200
+ <div id="scenes"></div>
201
+ <div id="preview"></div>
202
+ <div id="properties"></div>
203
+ </div>
204
+
205
+ <script>
206
+ // Create editor instance with custom config
207
+ const editor = new TourEditor({
208
+ projectName: "My Custom Tour",
209
+ autoSave: true,
210
+ autoSaveInterval: 30000,
211
+ });
212
+
213
+ // Initialize with DOM elements
214
+ editor.init({
215
+ sceneListElement: document.getElementById("scenes"),
216
+ previewElement: document.getElementById("preview"),
217
+ propertiesElement: document.getElementById("properties"),
218
+ });
219
+
220
+ // Access editor programmatically
221
+ editor.addEventListener("scene-added", (scene) => {
222
+ console.log("New scene:", scene.name);
223
+ });
224
+
225
+ // Export tour configuration
226
+ const config = editor.exportJSON();
227
+ </script>
228
+ </body>
208
229
  </html>
209
230
  ```
210
231
 
211
232
  **Available Classes** (all attached to `window` after loading `swt-editor.js`):
233
+
212
234
  - `TourEditor` - Main coordinator, orchestrates all managers
213
235
  - `SceneManagerEditor` - Scene CRUD operations
214
236
  - `HotspotEditor` - Hotspot placement and editing
@@ -218,6 +240,7 @@ Build your own tour editor using the `swt-editor.js` bundle. Two initialization
218
240
  - `ExportManager` - JSON and HTML export
219
241
 
220
242
  **Examples:**
243
+
221
244
  - `examples/editor-declarative.html` - Declarative HTML-only mode
222
245
  - `examples/editor.html` - Full-featured programmatic editor
223
246
 
@@ -234,57 +257,6 @@ npm run build
234
257
 
235
258
  # Development mode (watch for changes)
236
259
  npm run dev
237
-
238
- # Serve locally (required for A-Frame CORS)
239
- npm run serve
240
- # Access at http://localhost:8080
241
- ```
242
-
243
- ### Build Output
244
-
245
- | File | Size | Format | Purpose |
246
- |------|------|--------|---------|
247
- | `dist/swt.js` | 26KB | UMD | Viewer library (development) |
248
- | `dist/swt.min.js` | 12KB | UMD | Viewer library (production) |
249
- | `dist/swt-editor.js` | 89KB | IIFE | Editor bundle (development) |
250
- | `dist/swt-editor.min.js` | 38KB | IIFE | Editor bundle (production) |
251
- | `dist/swt-editor.css` | 39KB | CSS | Editor styles (development) |
252
- | `dist/swt-editor.min.css` | 25KB | CSS | Editor styles (production) |
253
-
254
- All builds include **sourcemaps** for debugging.
255
-
256
- ### Project Structure
257
-
258
- ```
259
- src/
260
- ├── index.js # Viewer library entry (UMD export)
261
- ├── AssetManager.js # Panorama preloading
262
- ├── SceneManager.js # Sky entity, transitions, fades
263
- ├── HotspotManager.js # Hotspot creation & updates
264
- ├── components/
265
- │ └── hotspot-listener.js # A-Frame hotspot component
266
- └── editor/
267
- ├── editor-entry.js # Editor bundle entry (IIFE)
268
- ├── editor-entry.css # CSS bundle entry (@import)
269
- ├── css/
270
- │ └── main.css # Editor UI styles
271
- └── js/
272
- ├── editor.js # Main coordinator (TourEditor)
273
- ├── scene-manager.js # Scene CRUD (SceneManagerEditor)
274
- ├── hotspot-editor.js # Hotspot placement (HotspotEditor)
275
- ├── preview-controller.js # A-Frame preview (PreviewController)
276
- ├── ui-controller.js # DOM rendering (UIController)
277
- ├── storage-manager.js # LocalStorage (ProjectStorageManager)
278
- ├── export-manager.js # JSON/HTML export (ExportManager)
279
- ├── utils.js # Helper functions
280
- └── ui-init.js # DOMContentLoaded initialization
281
-
282
- examples/
283
- ├── example.html # Full viewer demo
284
- ├── example-simple.html # Minimal viewer demo
285
- ├── editor.html # Full editor demo
286
- ├── editor-declarative.html # Declarative editor demo
287
- └── viewer.html # Standalone drag-and-drop viewer
288
260
  ```
289
261
 
290
262
  ## API Documentation
@@ -294,10 +266,11 @@ examples/
294
266
  #### Constructor
295
267
 
296
268
  ```javascript
297
- new SWT.Tour(aframeSceneElement, tourConfiguration)
269
+ new SWT.Tour(aframeSceneElement, tourConfiguration);
298
270
  ```
299
271
 
300
272
  **Parameters:**
273
+
301
274
  - `aframeSceneElement` (HTMLElement) - A-Frame `<a-scene>` DOM element
302
275
  - `tourConfiguration` (Object) - Tour config (see structure below)
303
276
 
@@ -342,6 +315,7 @@ new SWT.Tour(aframeSceneElement, tourConfiguration)
342
315
  ```
343
316
 
344
317
  **Important Notes:**
318
+
345
319
  - `scenes` is an **object** (keys are scene IDs), not an array
346
320
  - Hotspot `position` is in 3D space (typically on 10-unit sphere surface)
347
321
  - Editor stores `imageUrl`, library expects `panorama` (export handles conversion)
@@ -352,11 +326,13 @@ new SWT.Tour(aframeSceneElement, tourConfiguration)
352
326
  #### Methods
353
327
 
354
328
  ##### `tour.start()`
329
+
355
330
  Initialize and start the tour. Loads the initial scene and sets up event listeners.
356
331
 
357
332
  **Returns:** `void`
358
333
 
359
334
  **Example:**
335
+
360
336
  ```javascript
361
337
  const tour = new SWT.Tour(sceneElement, config);
362
338
  tour.start();
@@ -365,39 +341,46 @@ tour.start();
365
341
  ---
366
342
 
367
343
  ##### `tour.navigateTo(sceneId)`
344
+
368
345
  Navigate to a specific scene by ID.
369
346
 
370
347
  **Parameters:**
348
+
371
349
  - `sceneId` (String) - Target scene ID (must exist in `scenes` object)
372
350
 
373
351
  **Returns:** `void`
374
352
 
375
353
  **Example:**
354
+
376
355
  ```javascript
377
- tour.navigateTo('bedroom'); // Loads scene with id "bedroom"
356
+ tour.navigateTo("bedroom"); // Loads scene with id "bedroom"
378
357
  ```
379
358
 
380
359
  ---
381
360
 
382
361
  ##### `tour.getCurrentSceneId()`
362
+
383
363
  Get the ID of the currently active scene.
384
364
 
385
365
  **Returns:** `String` - Current scene ID
386
366
 
387
367
  **Example:**
368
+
388
369
  ```javascript
389
370
  const currentScene = tour.getCurrentSceneId();
390
- console.log('Viewing:', currentScene); // "living-room"
371
+ console.log("Viewing:", currentScene); // "living-room"
391
372
  ```
392
373
 
393
374
  ---
394
375
 
395
376
  ##### `tour.destroy()`
377
+
396
378
  Clean up and remove the tour. Removes all hotspots, event listeners, and resets scene.
397
379
 
398
380
  **Returns:** `void`
399
381
 
400
382
  **Example:**
383
+
401
384
  ```javascript
402
385
  tour.destroy(); // Cleanup before removing from DOM
403
386
  ```
@@ -405,18 +388,21 @@ tour.destroy(); // Cleanup before removing from DOM
405
388
  ---
406
389
 
407
390
  ##### `tour.addEventListener(eventName, callback)`
391
+
408
392
  Listen to tour events. Custom event system (not DOM events).
409
393
 
410
394
  **Parameters:**
395
+
411
396
  - `eventName` (String) - Event name (see Events section)
412
397
  - `callback` (Function) - Handler function receiving event object
413
398
 
414
399
  **Returns:** `void`
415
400
 
416
401
  **Example:**
402
+
417
403
  ```javascript
418
- tour.addEventListener('scene-loaded', (event) => {
419
- console.log('Scene:', event.detail.sceneName);
404
+ tour.addEventListener("scene-loaded", (event) => {
405
+ console.log("Scene:", event.detail.sceneName);
420
406
  });
421
407
  ```
422
408
 
@@ -427,33 +413,39 @@ tour.addEventListener('scene-loaded', (event) => {
427
413
  All events include a `detail` object with event-specific data.
428
414
 
429
415
  ##### `tour-started`
416
+
430
417
  Fired when `tour.start()` is called.
431
418
 
432
419
  **Detail:**
420
+
433
421
  ```javascript
434
422
  {
435
- config: Object // Full tour configuration
423
+ config: Object; // Full tour configuration
436
424
  }
437
425
  ```
438
426
 
439
427
  ---
440
428
 
441
429
  ##### `scene-loading`
430
+
442
431
  Fired before a scene begins loading.
443
432
 
444
433
  **Detail:**
434
+
445
435
  ```javascript
446
436
  {
447
- sceneId: String // ID of scene being loaded
437
+ sceneId: String; // ID of scene being loaded
448
438
  }
449
439
  ```
450
440
 
451
441
  ---
452
442
 
453
443
  ##### `scene-loaded`
444
+
454
445
  Fired after a scene is fully loaded and rendered.
455
446
 
456
447
  **Detail:**
448
+
457
449
  ```javascript
458
450
  {
459
451
  sceneId: String, // ID of loaded scene
@@ -464,9 +456,11 @@ Fired after a scene is fully loaded and rendered.
464
456
  ---
465
457
 
466
458
  ##### `hotspot-activated`
459
+
467
460
  Fired when a hotspot is clicked/activated.
468
461
 
469
462
  **Detail:**
463
+
470
464
  ```javascript
471
465
  {
472
466
  hotspotId: String, // Hotspot ID
@@ -480,46 +474,48 @@ Fired when a hotspot is clicked/activated.
480
474
  #### Usage Example
481
475
 
482
476
  ```javascript
483
- const scene = document.querySelector('#vr-scene');
477
+ const scene = document.querySelector("#vr-scene");
484
478
  const tour = new SWT.Tour(scene, {
485
479
  initialScene: "room1",
486
480
  scenes: {
487
481
  room1: {
488
482
  name: "Living Room",
489
483
  panorama: "360-living-room.jpg",
490
- hotspots: [{
491
- position: { x: 5, y: 0, z: -5 },
492
- action: { type: "navigateTo", target: "room2" },
493
- appearance: { color: "#FF6B6B" },
494
- tooltip: { text: "Kitchen" }
495
- }]
484
+ hotspots: [
485
+ {
486
+ position: { x: 5, y: 0, z: -5 },
487
+ action: { type: "navigateTo", target: "room2" },
488
+ appearance: { color: "#FF6B6B" },
489
+ tooltip: { text: "Kitchen" },
490
+ },
491
+ ],
496
492
  },
497
493
  room2: {
498
494
  name: "Kitchen",
499
495
  panorama: "360-kitchen.jpg",
500
- hotspots: []
501
- }
502
- }
496
+ hotspots: [],
497
+ },
498
+ },
503
499
  });
504
500
 
505
501
  // Listen to events
506
- tour.addEventListener('tour-started', (e) => {
507
- console.log('Tour configuration:', e.detail.config);
502
+ tour.addEventListener("tour-started", (e) => {
503
+ console.log("Tour configuration:", e.detail.config);
508
504
  });
509
505
 
510
- tour.addEventListener('scene-loading', (e) => {
511
- console.log('Loading scene:', e.detail.sceneId);
506
+ tour.addEventListener("scene-loading", (e) => {
507
+ console.log("Loading scene:", e.detail.sceneId);
512
508
  // Show loading indicator
513
509
  });
514
510
 
515
- tour.addEventListener('scene-loaded', (e) => {
516
- console.log('Loaded:', e.detail.sceneName);
511
+ tour.addEventListener("scene-loaded", (e) => {
512
+ console.log("Loaded:", e.detail.sceneName);
517
513
  // Hide loading indicator
518
514
  });
519
515
 
520
- tour.addEventListener('hotspot-activated', (e) => {
521
- console.log('Hotspot clicked:', e.detail.hotspotId);
522
- console.log('Navigating to:', e.detail.action.target);
516
+ tour.addEventListener("hotspot-activated", (e) => {
517
+ console.log("Hotspot clicked:", e.detail.hotspotId);
518
+ console.log("Navigating to:", e.detail.action.target);
523
519
  });
524
520
 
525
521
  // Start the tour
@@ -527,7 +523,7 @@ tour.start();
527
523
 
528
524
  // Programmatic navigation
529
525
  setTimeout(() => {
530
- tour.navigateTo('room2');
526
+ tour.navigateTo("room2");
531
527
  }, 5000);
532
528
 
533
529
  // Cleanup
@@ -537,6 +533,7 @@ setTimeout(() => {
537
533
  ## Editor Features
538
534
 
539
535
  ### Visual Tour Creation
536
+
540
537
  - **Click-to-Place Hotspots** - Raycast-based placement on panorama sphere
541
538
  - **Real-Time Preview** - Instant A-Frame rendering as you edit
542
539
  - **Scene Management** - Add, remove, reorder scenes with thumbnails
@@ -544,19 +541,23 @@ setTimeout(() => {
544
541
  - **Position Validation** - Hotspots clamped to 10-unit sphere radius
545
542
 
546
543
  ### Hotspot Configuration
544
+
547
545
  - **3D Position** - Click-to-place or manual X/Y/Z coordinate input
548
546
  - **Navigation Target** - Link to any scene in the tour
549
547
  - **Visual Customization** - Color picker and scale slider
550
548
  - **Tooltips** - Custom hover text for each hotspot
551
549
 
552
550
  ### Data Management
551
+
553
552
  - **LocalStorage Persistence** - Auto-save projects (configurable interval)
554
553
  - **Import/Export** - Load and save tour JSON configurations
555
554
  - **Data URLs** - Panoramas embedded as base64 (no external files needed)
556
555
  - **Thumbnail Generation** - Auto-generated scene previews (100x50px)
557
556
 
558
557
  ### Export Options
558
+
559
559
  1. **JSON Export** - Portable configuration file for library integration
560
+
560
561
  - Converts editor's `imageUrl` to library's `panorama` format
561
562
  - Compatible with `SWT.Tour` viewer library
562
563
  - Use in custom integrations or standalone viewer
@@ -568,6 +569,7 @@ setTimeout(() => {
568
569
  - Drag-and-drop ready for distribution
569
570
 
570
571
  ### Developer Tools
572
+
571
573
  - **ES6 Module Architecture** - Six-controller pattern with clear separation
572
574
  - **Sourcemaps** - Debug original ES6 source in browser DevTools
573
575
  - **Two Init Modes** - Declarative (HTML) or Programmatic (JS API)
@@ -576,21 +578,23 @@ setTimeout(() => {
576
578
 
577
579
  ## Browser Compatibility
578
580
 
579
- | Browser | Version | Notes |
580
- |---------|---------|-------|
581
- | Chrome | 90+ | Recommended - best performance |
582
- | Firefox | 88+ | Full support |
583
- | Safari | 14+ | WebGL support required |
584
- | Edge | 90+ | Chromium-based |
585
- | Mobile Safari | iOS 14+ | Touch and gyroscope support |
586
- | Chrome Mobile | Android 90+ | Touch and gyroscope support |
581
+ | Browser | Version | Notes |
582
+ | ------------- | ----------- | ------------------------------ |
583
+ | Chrome | 90+ | Recommended - best performance |
584
+ | Firefox | 88+ | Full support |
585
+ | Safari | 14+ | WebGL support required |
586
+ | Edge | 90+ | Chromium-based |
587
+ | Mobile Safari | iOS 14+ | Touch and gyroscope support |
588
+ | Chrome Mobile | Android 90+ | Touch and gyroscope support |
587
589
 
588
590
  **Requirements:**
591
+
589
592
  - WebGL 1.0 or higher
590
593
  - ES6 module support (for editor)
591
594
  - LocalStorage (for editor persistence)
592
595
 
593
596
  **VR Headsets:**
597
+
594
598
  - Oculus Quest 1/2/3
595
599
  - Meta Quest Pro
596
600
  - HTC Vive
@@ -612,37 +616,6 @@ Contributions are welcome! Here's how you can help:
612
616
  3. **Submit PRs** - Fork, create a feature branch, and submit a pull request
613
617
  4. **Improve Docs** - Help make documentation clearer and more comprehensive
614
618
 
615
- ### Development Workflow
616
-
617
- ```bash
618
- # Fork and clone the repository
619
- git clone https://github.com/your-username/senangwebs-tour.git
620
- cd senangwebs-tour
621
-
622
- # Install dependencies
623
- npm install
624
-
625
- # Start development server with watch mode
626
- npm run dev
627
-
628
- # In another terminal, serve the examples
629
- npm run serve
630
-
631
- # Make changes, test in browser at http://localhost:8080
632
- # Build for production
633
- npm run build
634
-
635
- # Submit a pull request
636
- ```
637
-
638
- ### Code Guidelines
639
- - Use ES6+ syntax (modules, classes, arrow functions)
640
- - Follow existing naming conventions (see copilot-instructions.md)
641
- - No debug `console.log()` - only `console.error()` for critical errors
642
- - All editor classes must use `export default`
643
- - Debounce text inputs (300ms) using `debounce()` from utils.js
644
- - Test in Chrome, Firefox, and Safari before submitting
645
-
646
619
  ## License
647
620
 
648
621
  MIT License - see [LICENSE.md](./LICENSE.md) for details.