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.
@@ -1 +1 @@
1
- *{box-sizing:border-box;margin:0;padding:0}:root{--bg-primary:#09090b;--bg-secondary:#18181b;--bg-tertiary:#1f1f22;--bg-hover:#232426;--border-color:#2a2a2d;--text-primary:#fff;--text-secondary:#a7cfc6;--text-muted:#7a8086;--accent-primary:#0f9;--accent-hover:#00d882;--accent-cyan:#00d1e6;--success:#006045;--danger:#ef4444;--warning:#f59e0b}body{background:var(--bg-primary);color:var(--text-primary);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;height:100vh;overflow:hidden}.editor-header{background:var(--bg-secondary);border-bottom:1px solid var(--border-color);height:60px;justify-content:space-between;padding:12px 20px}.editor-header,.header-left{align-items:center;display:flex}.header-left{gap:16px}.logo{align-items:center;display:flex;font-size:18px;font-weight:600;gap:8px}.logo-icon{background:linear-gradient(135deg,var(--accent-primary),var(--accent-cyan));border-radius:4px;height:24px;width:24px}.project-info{align-items:center;border-left:1px solid var(--border-color);display:flex;gap:8px;padding-left:16px}.project-label{color:var(--text-muted);font-size:12px;letter-spacing:.5px;text-transform:uppercase}.project-name{color:var(--text-primary);font-size:14px;font-weight:500}.header-actions{gap:8px}.btn,.header-actions{align-items:center;display:flex}.btn{border:none;border-radius:6px;cursor:pointer;font-size:14px;font-weight:500;gap:6px;padding:8px 16px;transition:all .2s}.btn-secondary{background:var(--bg-tertiary);border:1px solid var(--border-color);color:var(--text-primary)}.btn-secondary:hover{background:var(--bg-hover)}.btn-primary{background:var(--accent-primary);color:var(--bg-primary)}.btn-primary:hover{background:var(--accent-hover)}.btn-success{background:var(--success);color:#fff}.btn-success:hover{opacity:.9}.btn-icon{height:16px;width:16px}.editor-main{display:flex;height:calc(100vh - 60px);position:relative}.sidebar{background:var(--bg-secondary);border-right:1px solid var(--border-color);display:flex;flex-direction:column;width:280px}.sidebar-header{align-items:center;border-bottom:1px solid var(--border-color);display:flex;justify-content:space-between;padding:12px 16px}.sidebar-title{color:var(--text-secondary);font-size:14px;font-weight:600;letter-spacing:.5px;text-transform:uppercase}.btn-add{align-items:center;background:var(--accent-primary);border:none;border-radius:6px;color:var(--bg-primary);cursor:pointer;display:flex;font-size:14px;font-weight:500;gap:6px;padding:8px 16px;transition:all .2s}.btn-add:hover{background:var(--accent-hover)}.scenes-list{flex:1;overflow-y:auto;padding:12px}.scene-card{background:var(--bg-tertiary);border:2px solid transparent;border-radius:8px;cursor:pointer;margin-bottom:12px;overflow:hidden;position:relative;transition:all .2s}.scene-card img{aspect-ratio:2/1;object-fit:cover;width:100%}.scene-card:hover{border-color:var(--border-color)}.scene-card.active{background:rgba(59,130,246,.1);border-color:var(--accent-primary)}.scene-thumbnail{background:var(--bg-primary);border-radius:4px;height:120px;margin-bottom:8px;overflow:hidden;position:relative;width:100%}.scene-thumbnail img{height:100%;object-fit:cover;width:100%}.scene-info{display:flex;flex-direction:column;gap:4px;margin:6px 8px}.scene-name{color:var(--text-primary);font-size:14px;font-weight:500}.scene-meta{color:var(--text-muted);font-size:12px}.scene-actions{display:flex;gap:4px;margin:6px 8px}.btn-icon-small{background:var(--bg-hover);border:1px solid var(--border-color);border-radius:4px;color:var(--text-secondary);cursor:pointer;font-size:11px;padding:4px 8px;transition:all .2s}.btn-icon-small:hover{background:var(--bg-tertiary);color:var(--text-primary)}.btn-icon{background:transparent;border:1px solid var(--border-color);border-radius:4px;color:var(--text-secondary);cursor:pointer;font-size:12px;height:32px;transition:all .2s;width:32px}.btn-icon:hover{background:var(--danger);border-color:var(--danger);color:#fff}.drag-handle{fill:var(--text-secondary);align-items:center;background:rgba(0,0,0,.75);border-radius:4px;cursor:move;display:flex;height:32px;justify-content:center;padding:8px;position:absolute;right:8px;top:8px;width:32px;z-index:10}.canvas-area{background:var(--bg-primary);display:flex;flex:1;flex-direction:column}.canvas-area.preview-active{height:100%;left:0;position:absolute;top:0;width:100vw;z-index:100}.toolbar{align-items:center;background:var(--bg-secondary);border-bottom:1px solid var(--border-color);display:flex;justify-content:center;padding:12px 16px}.toolbar-tabs{display:flex;gap:4px;justify-content:space-evenly;width:100%}.tab{background:transparent;border:none;border-radius:6px;color:var(--text-secondary);cursor:pointer;font-size:14px;font-weight:500;padding:8px 16px;transition:all .2s}.tab:hover{background:var(--bg-tertiary);color:var(--text-primary)}.tab.active{background:var(--accent-primary);color:var(--bg-primary)}.toolbar-controls,.zoom-control{align-items:center;display:flex;gap:8px}.zoom-control{background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:6px;padding:4px 12px}.zoom-btn{background:none;border:none;color:var(--text-secondary);cursor:pointer;font-size:16px;padding:4px;transition:color .2s}.zoom-btn:hover{color:var(--text-primary)}.zoom-value{color:var(--text-secondary);font-size:12px;min-width:40px;text-align:center}.canvas-container{align-items:center;display:flex;flex:1;justify-content:center;overflow:hidden;padding:20px;position:relative}.preview-container{flex:1}.preview-area,.preview-container{height:100%;position:relative;width:100%}#preview{height:100%;width:100%}.preview-empty{align-items:center;color:var(--text-muted);display:flex;flex-direction:column;height:100%;justify-content:center}.preview-empty p{margin:4px 0}.canvas-viewport{aspect-ratio:16/9;background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:8px;max-width:1200px;overflow:hidden;position:relative;width:100%}.canvas-viewport img{height:100%;object-fit:cover;width:100%}.hotspot-marker{border:3px solid #fff;border-radius:50%;box-shadow:0 2px 8px rgba(0,0,0,.3);cursor:pointer;height:40px;position:absolute;transition:all .2s;width:40px}.hotspot-marker:hover{box-shadow:0 4px 12px rgba(0,0,0,.5);transform:scale(1.2)}.hotspot-marker.selected{border-width:4px;box-shadow:0 0 0 4px hsla(0,0%,100%,.3)}.empty-state{color:var(--text-muted);padding:40px 20px;text-align:center}.empty-state p{margin:8px 0}.empty-state .hint{color:var(--text-muted);font-size:12px}.properties-panel{background:var(--bg-secondary);border-left:1px solid var(--border-color);display:flex;flex-direction:column;width:320px}.panel-header{border-bottom:1px solid var(--border-color);padding:16px}.panel-title{color:var(--text-secondary);font-size:14px;font-weight:600;letter-spacing:.5px;text-transform:uppercase}.panel-content{flex:1;overflow-y:auto;padding:16px}.form-group{margin-bottom:20px}.form-label{color:var(--text-secondary);display:block;font-size:12px;font-weight:500;letter-spacing:.5px;margin-bottom:8px;text-transform:uppercase}.form-input{background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:6px;color:var(--text-primary);font-size:14px;padding:10px 12px;transition:all .2s;width:100%}.form-input:focus{background:var(--bg-primary);border-color:var(--accent-primary);outline:none}.form-textarea{font-family:inherit;min-height:80px;resize:vertical}.form-select{background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:6px;color:var(--text-primary);cursor:pointer;font-size:14px;padding:10px 12px;width:100%}.form-checkbox{align-items:center;cursor:pointer;display:flex;gap:8px}.form-checkbox input{cursor:pointer;height:18px;width:18px}.form-checkbox label{color:var(--text-primary);cursor:pointer;font-size:14px}.color-picker-group{align-items:center;display:flex;gap:12px}.color-preview{border:2px solid var(--border-color);border-radius:6px;cursor:pointer;height:40px;width:40px}.color-input{flex:1}.position-grid{display:grid;gap:8px;grid-template-columns:repeat(3,1fr)}.position-input{background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:4px;color:var(--text-primary);font-size:12px;padding:8px;text-align:center}.hotspot-controls{display:flex;gap:8px;justify-content:space-between;margin-bottom:20px}.hotspot-list{display:flex;flex-direction:column;gap:8px;margin-bottom:16px}.hotspot-item{align-items:center;background:var(--bg-tertiary);border:2px solid transparent;border-radius:6px;cursor:pointer;display:flex;gap:12px;padding:12px;transition:all .2s}.hotspot-item:hover{border-color:var(--border-color)}.hotspot-item.active{background:rgba(59,130,246,.1);border-color:var(--accent-primary)}.hotspot-color{border:2px solid var(--border-color);border-radius:4px;flex-shrink:0;height:24px;width:24px}.hotspot-info{flex:1;min-width:0}.hotspot-title{color:var(--text-primary);font-size:14px;font-weight:500}.hotspot-target,.hotspot-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.hotspot-target{color:var(--text-muted);font-size:12px}.hotspot-actions{display:flex;gap:4px}.btn-delete{background:transparent;border:1px solid var(--border-color);border-radius:4px;color:var(--danger);cursor:pointer;font-size:12px;padding:4px 8px;transition:all .2s}.btn-delete:hover{background:var(--danger);color:#fff}.section{margin-bottom:20px}.section h3{color:var(--text-secondary);font-size:14px;font-weight:600;letter-spacing:.5px;margin-bottom:16px;text-transform:uppercase}.btn-block{width:100%}.toast{background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:8px;bottom:20px;box-shadow:0 4px 12px rgba(0,0,0,.3);color:var(--text-primary);opacity:0;padding:12px 20px;pointer-events:none;position:fixed;right:20px;transform:translateY(20px);transition:all .3s;z-index:10000}.toast.show{opacity:1;pointer-events:auto;transform:translateY(0)}.toast.success{background:rgba(16,185,129,.1);border-color:var(--success)}.toast.error{background:rgba(239,68,68,.1);border-color:var(--danger)}.toast.info{background:rgba(6,182,212,.1);border-color:var(--accent-cyan)}.modal{align-items:center;background:rgba(0,0,0,.7);display:none;height:100%;justify-content:center;left:0;position:fixed;top:0;width:100%;z-index:9999}.modal.show{display:flex}.modal-content{background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:12px;display:flex;flex-direction:column;max-height:90vh;max-width:600px;overflow:hidden;width:90%}.modal-header{align-items:center;border-bottom:1px solid var(--border-color);display:flex;justify-content:space-between;padding:20px}.modal-header h3{color:var(--text-primary);font-size:18px;font-weight:600}.modal-close{align-items:center;background:none;border:none;border-radius:4px;color:var(--text-secondary);cursor:pointer;display:flex;font-size:24px;height:32px;justify-content:center;padding:0;transition:all .2s;width:32px}.modal-close:hover{background:var(--bg-hover);color:var(--text-primary)}.modal-body{flex:1;overflow-y:auto;padding:20px}.modal-body h4{color:var(--text-primary);font-size:16px;font-weight:600;margin-bottom:12px;margin-top:20px}.modal-body h4:first-child{margin-top:0}.modal-body p{line-height:1.6}.modal-body ol,.modal-body p,.modal-body ul{color:var(--text-secondary);margin-bottom:12px}.modal-body ol,.modal-body ul{line-height:1.8;padding-left:24px}.modal-body li{margin-bottom:8px}.modal-body kbd{background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:4px;font-family:monospace;font-size:12px;padding:2px 6px}.modal-footer{border-top:1px solid var(--border-color);display:flex;gap:8px;justify-content:flex-end;padding:16px 20px}.export-info{background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:6px;margin-bottom:16px;padding:16px}.export-info p{font-size:13px;margin-bottom:8px}.export-info p:last-child{margin-bottom:0}.export-preview{background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:6px;color:var(--text-primary);font-family:monospace;font-size:12px;height:200px;padding:12px;resize:vertical;width:100%}.btn-active{animation:pulse 2s infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.7}}.dragging{opacity:.5}::-webkit-scrollbar{height:8px;width:8px}::-webkit-scrollbar-track{background:var(--bg-primary)}::-webkit-scrollbar-thumb{background:var(--bg-tertiary);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:var(--bg-hover)}.preview-loading{align-items:center;backdrop-filter:blur(4px);background:rgba(9,9,11,.85);bottom:0;display:flex;flex-direction:column;justify-content:center;left:0;position:absolute;right:0;top:0;transition:opacity .3s ease;z-index:1000}.preview-loading.hidden{opacity:0;pointer-events:none}.loading-spinner{animation:spin .8s linear infinite;border:3px solid var(--border-color);border-radius:50%;border-top-color:var(--accent-primary);height:48px;width:48px}.loading-text{color:var(--text-secondary);font-size:14px;font-weight:500;margin-top:16px}@keyframes spin{to{transform:rotate(1turn)}}@media (max-width:1024px){.sidebar{width:240px}.properties-panel{width:280px}}
1
+ @import url("https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap");*{box-sizing:border-box;font-family:var(--font-family);margin:0;padding:0}:root{--font-family:"Outfit",sans-serif;--bg-primary:#09090b;--bg-secondary:#18181b;--bg-tertiary:#1f1f22;--bg-hover:#232426;--border-color:#2a2a2d;--text-primary:#fff;--text-secondary:#d4d4d4;--text-muted:#7a8086;--accent-primary:#0f9;--accent-hover:#00d882;--accent-cyan:#00d1e6;--success:#006045;--danger:#ef4444;--warning:#f59e0b}body{background:var(--bg-primary);color:var(--text-primary);height:100vh;overflow:hidden}.editor-header{background:var(--bg-secondary);border-bottom:1px solid var(--border-color);height:60px;justify-content:space-between;padding:12px 20px}.editor-header,.header-left{align-items:center;display:flex}.header-left{gap:16px}.logo{align-items:center;display:flex;font-size:18px;font-weight:600;gap:8px}.logo-icon{background:linear-gradient(135deg,var(--accent-primary),var(--accent-cyan));border-radius:4px;height:24px;width:24px}.project-info{align-items:center;border-left:1px solid var(--border-color);display:flex;gap:8px;padding-left:16px}.project-label{color:var(--text-muted);font-size:12px;letter-spacing:.5px;text-transform:uppercase}.project-name{color:var(--text-primary);font-size:14px;font-weight:500}.header-actions{gap:8px}.btn,.header-actions{align-items:center;display:flex}.btn{border:none;border-radius:6px;cursor:pointer;font-size:14px;font-weight:500;gap:6px;padding:8px 16px;transition:all .2s}.btn-secondary{background:var(--bg-tertiary);border:1px solid var(--border-color);color:var(--text-primary)}.btn-secondary:hover{background:var(--bg-hover)}.btn-primary{background:var(--accent-primary);color:var(--bg-primary)}.btn-primary:hover{background:var(--accent-hover)}.btn-success{background:var(--success);color:#fff}.btn-success:hover{opacity:.9}.btn-icon{height:16px;width:16px}.editor-main{display:flex;height:calc(100vh - 60px);position:relative}.sidebar{background:var(--bg-secondary);border-right:1px solid var(--border-color);display:flex;flex-direction:column;width:280px}.sidebar-header{align-items:center;border-bottom:1px solid var(--border-color);display:flex;justify-content:space-between;padding:12px 16px}.sidebar-title{color:var(--text-secondary);font-size:14px;font-weight:600;letter-spacing:.5px;text-transform:uppercase}.btn-add{align-items:center;background:var(--accent-primary);border:none;border-radius:6px;color:var(--bg-primary);cursor:pointer;display:flex;font-size:14px;font-weight:500;gap:6px;padding:8px 16px;transition:all .2s}.btn-add:hover{background:var(--accent-hover)}.scenes-list{flex:1;overflow-y:auto;padding:12px}.scene-card{background:var(--bg-tertiary);border:2px solid transparent;border-radius:8px;cursor:pointer;margin-bottom:12px;overflow:hidden;position:relative;transition:all .2s}.scene-card img{aspect-ratio:2/1;object-fit:cover;width:100%}.scene-card:hover{border-color:var(--border-color)}.scene-card.active{background:rgba(59,130,246,.1);border-color:var(--accent-primary)}.scene-thumbnail{background:var(--bg-primary);border-radius:4px;height:120px;margin-bottom:8px;overflow:hidden;position:relative;width:100%}.scene-thumbnail img{height:100%;object-fit:cover;width:100%}.scene-info{display:flex;flex-direction:column;gap:4px;margin:6px 8px}.scene-name{color:var(--text-primary);font-size:14px;font-weight:500}.scene-meta{color:var(--text-muted);font-size:12px}.scene-actions{display:flex;gap:4px;margin:6px 8px}.btn-icon-small{background:var(--bg-hover);border:1px solid var(--border-color);border-radius:4px;color:var(--text-secondary);cursor:pointer;font-size:11px;padding:4px 8px;transition:all .2s}.btn-icon-small:hover{background:var(--bg-tertiary);color:var(--text-primary)}.btn-icon{align-items:center;background:transparent;border:1px solid var(--border-color);border-radius:4px;color:var(--text-secondary);cursor:pointer;display:flex;font-size:16px;height:32px;justify-content:center;transition:all .2s;width:32px}.btn-icon:hover{background:var(--danger);border-color:var(--danger);color:#fff}.drag-handle{fill:var(--text-secondary);align-items:center;background:rgba(0,0,0,.75);border-radius:4px;cursor:move;display:flex;height:32px;justify-content:center;padding:8px;position:absolute;right:8px;top:8px;width:32px;z-index:10}.canvas-area{background:var(--bg-primary);display:flex;flex:1;flex-direction:column}.canvas-area.preview-active{height:100%;left:0;position:absolute;top:0;width:100vw;z-index:100}.toolbar{align-items:center;background:var(--bg-secondary);border-bottom:1px solid var(--border-color);display:flex;justify-content:center;padding:12px 16px}.toolbar-tabs{display:grid;gap:8px;grid-template-columns:repeat(3,1fr);justify-content:space-evenly;width:100%}.tab{align-items:center;background:transparent;border:none;border-radius:6px;color:var(--text-secondary);cursor:pointer;display:flex;flex-direction:column;font-size:14px;font-weight:500;justify-content:center;padding:6px 8px;transition:all .2s}.tab ss-icon{font-size:20px}.tab:hover{background:var(--bg-tertiary);color:var(--text-primary)}.tab.active{background:var(--accent-primary);color:var(--bg-primary)}.toolbar-controls,.zoom-control{align-items:center;display:flex;gap:8px}.zoom-control{background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:6px;padding:4px 12px}.zoom-btn{background:none;border:none;color:var(--text-secondary);cursor:pointer;font-size:16px;padding:4px;transition:color .2s}.zoom-btn:hover{color:var(--text-primary)}.zoom-value{color:var(--text-secondary);font-size:12px;min-width:40px;text-align:center}.canvas-container{align-items:center;display:flex;flex:1;justify-content:center;overflow:hidden;padding:20px;position:relative}.preview-container{flex:1}.preview-area,.preview-container{height:100%;position:relative;width:100%}#preview{height:100%;width:100%}.preview-empty{align-items:center;color:var(--text-muted);display:flex;flex-direction:column;height:100%;justify-content:center}.preview-empty p{margin:4px 0}.canvas-viewport{aspect-ratio:16/9;background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:8px;max-width:1200px;overflow:hidden;position:relative;width:100%}.canvas-viewport img{height:100%;object-fit:cover;width:100%}.hotspot-marker{border:3px solid #fff;border-radius:50%;box-shadow:0 2px 8px rgba(0,0,0,.3);cursor:pointer;height:40px;position:absolute;transition:all .2s;width:40px}.hotspot-marker:hover{box-shadow:0 4px 12px rgba(0,0,0,.5);transform:scale(1.2)}.hotspot-marker.selected{border-width:4px;box-shadow:0 0 0 4px hsla(0,0%,100%,.3)}.empty-state{color:var(--text-muted);padding:40px 20px;text-align:center}.empty-state p{margin:8px 0}.empty-state .hint{color:var(--text-muted);font-size:12px}.properties-panel{background:var(--bg-secondary);border-left:1px solid var(--border-color);display:flex;flex-direction:column;width:320px}.panel-header{border-bottom:1px solid var(--border-color);padding:16px}.panel-title{color:var(--text-secondary);font-size:14px;font-weight:600;letter-spacing:.5px;text-transform:uppercase}.panel-content{flex:1;overflow-y:auto;padding:16px}.form-group{margin-bottom:20px}.form-label{color:var(--text-secondary);display:block;font-size:12px;font-weight:500;letter-spacing:.5px;margin-bottom:8px;text-transform:uppercase}.form-input{background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:6px;color:var(--text-primary);font-size:14px;padding:10px 12px;transition:all .2s;width:100%}.form-input:focus{background:var(--bg-primary);border-color:var(--accent-primary);outline:none}.form-textarea{font-family:inherit;min-height:80px;resize:vertical}.form-select{background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:6px;color:var(--text-primary);cursor:pointer;font-size:14px;padding:10px 12px;width:100%}.form-checkbox{align-items:center;cursor:pointer;display:flex;gap:8px}.form-checkbox input{cursor:pointer;height:18px;width:18px}.form-checkbox label{color:var(--text-primary);cursor:pointer;font-size:14px}.color-picker-group{align-items:center;display:flex;gap:12px}.color-preview{border:2px solid var(--border-color);border-radius:6px;cursor:pointer;height:40px;width:40px}.color-input{flex:1}.position-grid{display:grid;gap:8px;grid-template-columns:repeat(3,1fr)}.position-input{background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:4px;color:var(--text-primary);font-size:12px;padding:8px;text-align:center}.hotspot-controls{display:flex;gap:8px;justify-content:space-between;margin-bottom:20px}.hotspot-list{display:flex;flex-direction:column;gap:8px;margin-bottom:16px}.hotspot-item{align-items:center;background:var(--bg-tertiary);border:2px solid transparent;border-radius:6px;cursor:pointer;display:flex;gap:12px;padding:12px;transition:all .2s}.hotspot-item:hover{border-color:var(--border-color)}.hotspot-item.active{background:rgba(59,130,246,.1);border-color:var(--accent-primary)}.hotspot-color{border:2px solid var(--border-color);border-radius:4px;flex-shrink:0;height:24px;width:24px}.hotspot-info{flex:1;min-width:0}.hotspot-title{color:var(--text-primary);font-size:14px;font-weight:500}.hotspot-target,.hotspot-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.hotspot-target{color:var(--text-muted);font-size:12px}.hotspot-actions{display:flex;gap:4px}.btn-delete{align-items:center;background:transparent;border:1px solid var(--danger);border-radius:4px;color:var(--danger);cursor:pointer;display:flex;font-size:16px;height:32px;justify-content:center;transition:all .2s;width:32px}.btn-delete:hover{background:var(--danger);color:#fff}.section{margin-bottom:20px}.section h3{color:var(--text-secondary);font-size:14px;font-weight:600;letter-spacing:.5px;margin-bottom:16px;text-transform:uppercase}.btn-block{width:100%}.toast{background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:8px;bottom:20px;box-shadow:0 4px 12px rgba(0,0,0,.3);color:var(--text-primary);opacity:0;padding:12px 20px;pointer-events:none;position:fixed;right:20px;transform:translateY(20px);transition:all .3s;z-index:10000}.toast.show{opacity:1;pointer-events:auto;transform:translateY(0)}.toast.success{background:rgba(16,185,129,.1);border-color:var(--success)}.toast.error{background:rgba(239,68,68,.1);border-color:var(--danger)}.toast.info{background:rgba(6,182,212,.1);border-color:var(--accent-cyan)}.modal{align-items:center;background:rgba(0,0,0,.7);display:none;height:100%;justify-content:center;left:0;position:fixed;top:0;width:100%;z-index:9999}.modal.show{display:flex}.modal-content{background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:12px;display:flex;flex-direction:column;max-height:90vh;max-width:600px;overflow:hidden;width:90%}.modal-header{align-items:center;border-bottom:1px solid var(--border-color);display:flex;justify-content:space-between;padding:20px}.modal-header h3{color:var(--text-primary);font-size:18px;font-weight:600}.modal-close{align-items:center;background:none;border:none;border-radius:4px;color:var(--text-secondary);cursor:pointer;display:flex;font-size:24px;height:32px;justify-content:center;padding:0;transition:all .2s;width:32px}.modal-close:hover{background:var(--bg-hover);color:var(--text-primary)}.modal-body{flex:1;overflow-y:auto;padding:20px}.modal-body h4{color:var(--text-primary);font-size:16px;font-weight:600;margin-bottom:12px;margin-top:20px}.modal-body h4:first-child{margin-top:0}.modal-body p{line-height:1.6}.modal-body ol,.modal-body p,.modal-body ul{color:var(--text-secondary);margin-bottom:12px}.modal-body ol,.modal-body ul{line-height:1.8;padding-left:24px}.modal-body li{margin-bottom:8px}.modal-body kbd{background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:4px;font-family:monospace;font-size:12px;padding:2px 6px}.modal-footer{border-top:1px solid var(--border-color);display:flex;gap:8px;justify-content:flex-end;padding:16px 20px}.export-info{background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:6px;margin-bottom:16px;padding:16px}.export-info p{font-size:13px;margin-bottom:8px}.export-info p:last-child{margin-bottom:0}.export-preview{background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:6px;color:var(--text-primary);font-family:monospace;font-size:12px;height:200px;padding:12px;resize:vertical;width:100%}.btn-active{animation:pulse 2s infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.7}}.dragging{opacity:.5}::-webkit-scrollbar{height:8px;width:8px}::-webkit-scrollbar-track{background:var(--bg-primary)}::-webkit-scrollbar-thumb{background:var(--bg-tertiary);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:var(--bg-hover)}.preview-loading{align-items:center;backdrop-filter:blur(4px);background:rgba(9,9,11,.85);bottom:0;display:flex;flex-direction:column;justify-content:center;left:0;position:absolute;right:0;top:0;transition:opacity .3s ease;z-index:1000}.preview-loading.hidden{opacity:0;pointer-events:none}.loading-spinner{animation:spin .8s linear infinite;border:3px solid var(--border-color);border-radius:50%;border-top-color:var(--accent-primary);height:48px;width:48px}.loading-text{color:var(--text-secondary);font-size:14px;font-weight:500;margin-top:16px}@keyframes spin{to{transform:rotate(1turn)}}@media (max-width:1024px){.sidebar{width:240px}.properties-panel{width:280px}}
@@ -1 +1 @@
1
- !function(){"use strict";function e(e="id"){return`${e}_${Date.now()}_${Math.random().toString(36).substr(2,9)}`}function t(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_+|_+$/g,"")}async function n(e,t=200,n=150){return new Promise((o,r)=>{const s=new FileReader;s.onload=e=>{const s=new Image;s.onload=()=>{const e=document.createElement("canvas"),r=e.getContext("2d");let i=s.width,a=s.height;i>a?i>t&&(a*=t/i,i=t):a>n&&(i*=n/a,a=n),e.width=i,e.height=a,r.drawImage(s,0,0,i,a),o(e.toDataURL("image/jpeg",.7))},s.onerror=r,s.src=e.target.result},s.onerror=r,s.readAsDataURL(e)})}async function o(e){return new Promise((t,n)=>{const o=new FileReader;o.onload=e=>t(e.target.result),o.onerror=n,o.readAsDataURL(e)})}function r(e,t="info",n=3e3){const o=document.getElementById("toast");o.textContent=e,o.className=`toast ${t}`,o.classList.add("show"),setTimeout(()=>{o.classList.remove("show")},n)}function s(e){const t=document.getElementById(e);t&&t.classList.add("show")}function i(e,t){const n=new Blob([e],{type:"text/plain"}),o=URL.createObjectURL(n),r=document.createElement("a");r.href=o,r.download=t,document.body.appendChild(r),r.click(),document.body.removeChild(r),URL.revokeObjectURL(o)}function a(e,t){let n;return function(...o){clearTimeout(n),n=setTimeout(()=>{clearTimeout(n),e(...o)},t)}}var c=Object.freeze({__proto__:null,debounce:a,deepClone:function(e){return JSON.parse(JSON.stringify(e))},downloadTextAsFile:i,formatFileSize:function(e){if(0===e)return"0 Bytes";const t=Math.floor(Math.log(e)/Math.log(1024));return Math.round(e/Math.pow(1024,t)*100)/100+" "+["Bytes","KB","MB","GB"][t]},generateId:e,generateThumbnail:n,hideModal:function(e){const t=document.getElementById(e);t&&t.classList.remove("show")},isValidEmail:function(e){return/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e)},loadImageAsDataUrl:o,parsePosition:function(e){const t=e.split(" ").map(Number);return{x:t[0]||0,y:t[1]||0,z:t[2]||0}},positionToString:function(e){return`${e.x.toFixed(1)} ${e.y.toFixed(1)} ${e.z.toFixed(1)}`},sanitizeId:t,showModal:s,showToast:r});let d=class{constructor(e={}){this.config={title:e.projectName||"My Virtual Tour",description:"",initialSceneId:"",autoRotate:!1,showCompass:!1},this.options={sceneListElement:e.sceneListElement||null,previewElement:e.previewElement||null,propertiesElement:e.propertiesElement||null,autoSave:void 0!==e.autoSave&&e.autoSave,autoSaveInterval:e.autoSaveInterval||3e4,...e},this.storageManager=new ProjectStorageManager,this.sceneManager=new SceneManagerEditor(this),this.hotspotEditor=new HotspotEditor(this),this.previewController=new PreviewController(this),this.uiController=new UIController(this),this.exportManager=new ExportManager(this),this.hasUnsavedChanges=!1,this.lastRenderedSceneIndex=-1,this.listenersSetup=!1}async init(e={}){e&&Object.keys(e).length>0&&(Object.assign(this.options,e),e.projectName&&(this.config.title=e.projectName));if(!await this.previewController.init())return console.error("Failed to initialize preview controller"),showToast("Failed to initialize preview","error"),!1;if(this.setupEventListeners(),this.storageManager.hasProject())try{const e=this.storageManager.loadProject();e&&e.scenes&&e.scenes.length>0?this.loadProject():(console.error("Invalid or empty project data, clearing storage"),this.storageManager.clearProject())}catch(e){console.error("Error loading saved project:",e),this.storageManager.clearProject()}return this.options.autoSave&&this.storageManager.startAutoSave(()=>{this.saveProject()},this.options.autoSaveInterval),0===this.sceneManager.getScenes().length&&this.render(),showToast("Editor ready","success"),!0}setupEventListeners(){if(this.listenersSetup)return;const e=document.querySelectorAll("#addSceneBtn"),t=document.querySelectorAll("#sceneUpload"),n=document.querySelectorAll("#importBtn"),o=document.querySelectorAll("#importUpload");(e.length>1||t.length>1||n.length>1||o.length>1)&&console.error("Duplicate IDs found in DOM. This will cause double-trigger issues."),document.getElementById("newBtn")?.addEventListener("click",()=>this.newProject()),document.getElementById("saveBtn")?.addEventListener("click",()=>this.saveProject()),document.getElementById("exportBtn")?.addEventListener("click",()=>this.exportManager.showExportPreview()),document.getElementById("importBtn")?.addEventListener("click",()=>this.importProject()),document.getElementById("helpBtn")?.addEventListener("click",()=>s("helpModal")),document.getElementById("addSceneBtn")?.addEventListener("click",()=>{const e=document.getElementById("sceneUpload");e&&e.click()}),document.getElementById("sceneUpload")?.addEventListener("change",e=>{e.target.files&&e.target.files.length>0&&(this.handleSceneUpload(e.target.files),setTimeout(()=>{e.target.value=""},100))}),document.getElementById("addHotspotBtn")?.addEventListener("click",()=>{this.hotspotEditor.enablePlacementMode()}),document.getElementById("clearHotspotsBtn")?.addEventListener("click",()=>{this.hotspotEditor.clearAllHotspots()&&this.render()}),document.querySelectorAll(".tab-btn").forEach(e=>{e.addEventListener("click",()=>{this.uiController.switchTab(e.dataset.tab)})}),document.getElementById("hotspotTitle")?.addEventListener("input",a(e=>{this.updateCurrentHotspot("title",e.target.value)},300)),document.getElementById("hotspotDescription")?.addEventListener("input",a(e=>{this.updateCurrentHotspot("description",e.target.value)},300)),document.getElementById("hotspotTarget")?.addEventListener("change",e=>{this.updateCurrentHotspot("targetSceneId",e.target.value)}),document.getElementById("hotspotColor")?.addEventListener("input",e=>{this.updateCurrentHotspot("color",e.target.value)}),document.getElementById("hotspotPosX")?.addEventListener("input",a(e=>{this.updateCurrentHotspotPosition("x",parseFloat(e.target.value)||0)},300)),document.getElementById("hotspotPosY")?.addEventListener("input",a(e=>{this.updateCurrentHotspotPosition("y",parseFloat(e.target.value)||0)},300)),document.getElementById("hotspotPosZ")?.addEventListener("input",a(e=>{this.updateCurrentHotspotPosition("z",parseFloat(e.target.value)||0)},300)),document.getElementById("sceneId")?.addEventListener("input",a(e=>{this.updateCurrentScene("id",sanitizeId(e.target.value))},300)),document.getElementById("sceneName")?.addEventListener("input",a(e=>{this.updateCurrentScene("name",e.target.value)},300)),document.getElementById("sceneImageUrl")?.addEventListener("input",a(e=>{this.updateCurrentSceneImage(e.target.value)},300)),document.getElementById("tourTitle")?.addEventListener("input",a(e=>{this.config.title=e.target.value,this.markUnsavedChanges();const t=document.getElementById("project-name");t&&t.value!==e.target.value&&(t.value=e.target.value)},300)),document.getElementById("project-name")?.addEventListener("input",a(e=>{this.config.title=e.target.value,this.markUnsavedChanges();const t=document.getElementById("tourTitle");t&&t.value!==e.target.value&&(t.value=e.target.value)},300)),document.getElementById("tourDescription")?.addEventListener("input",a(e=>{this.config.description=e.target.value,this.markUnsavedChanges()},300)),document.getElementById("tourInitialScene")?.addEventListener("change",e=>{this.config.initialSceneId=e.target.value,this.markUnsavedChanges()}),document.getElementById("tourAutoRotate")?.addEventListener("change",e=>{this.config.autoRotate=e.target.checked,this.markUnsavedChanges()}),document.getElementById("tourShowCompass")?.addEventListener("change",e=>{this.config.showCompass=e.target.checked,this.markUnsavedChanges()}),document.getElementById("exportJsonBtn")?.addEventListener("click",()=>{this.exportManager.exportJSON()}),document.getElementById("copyJsonBtn")?.addEventListener("click",()=>{this.exportManager.copyJSON()}),document.getElementById("exportViewerBtn")?.addEventListener("click",()=>{this.exportManager.exportViewerHTML()}),document.querySelectorAll(".modal-close").forEach(e=>{e.addEventListener("click",()=>{const t=e.closest(".modal");t&&hideModal(t.id)})}),document.getElementById("importUpload")?.addEventListener("change",e=>{e.target.files&&e.target.files.length>0&&(this.handleImportFile(e.target.files[0]),setTimeout(()=>{e.target.value=""},100))}),window.addEventListener("beforeunload",e=>{this.hasUnsavedChanges&&(e.preventDefault(),e.returnValue="")}),this.listenersSetup=!0}async handleSceneUpload(e){if(e&&0!==e.length){this.uiController.setLoading(!0);for(const t of e)t.type.startsWith("image/")?await this.sceneManager.addScene(t):showToast(`${t.name} is not an image`,"error");this.uiController.setLoading(!1),this.render(),this.markUnsavedChanges()}}addHotspotAtPosition(e){const t=Math.sqrt(e.x*e.x+e.y*e.y+e.z*e.z);if(t>5){const n=5/t;e.x*=n,e.y*=n,e.z*=n,e.x=parseFloat(e.x.toFixed(2)),e.y=parseFloat(e.y.toFixed(2)),e.z=parseFloat(e.z.toFixed(2))}this.hotspotEditor.addHotspot(e)?(this.lastRenderedSceneIndex=-1,this.render(),this.markUnsavedChanges()):console.error("Failed to add hotspot")}selectScene(e){if(this.sceneManager.setCurrentScene(e)){this.lastRenderedSceneIndex=-1,this.hotspotEditor.currentHotspotIndex=-1;const t=this.sceneManager.getCurrentScene();t&&(this.previewController.loadScene(t,!1),this.lastRenderedSceneIndex=e),this.uiController.renderSceneList(),this.uiController.updateSceneProperties(t),this.uiController.renderHotspotList(),this.uiController.updateHotspotProperties(null),this.uiController.updateInitialSceneOptions(),this.uiController.updateTargetSceneOptions()}}selectHotspot(e){if(this.hotspotEditor.setCurrentHotspot(e)){const t=this.hotspotEditor.getHotspot(e);this.uiController.renderHotspotList(),this.uiController.updateHotspotProperties(t),this.uiController.updateTargetSceneOptions(),this.uiController.switchTab("hotspot"),t&&t.position&&this.previewController.pointCameraToHotspot(t.position)}}removeScene(e){this.sceneManager.removeScene(e)&&(this.render(),this.markUnsavedChanges())}removeHotspot(e){this.hotspotEditor.removeHotspot(e)&&(this.lastRenderedSceneIndex=-1,this.render(),this.markUnsavedChanges())}duplicateHotspot(e){this.hotspotEditor.duplicateHotspot(e)&&(this.lastRenderedSceneIndex=-1,this.render(),this.markUnsavedChanges())}reorderScenes(e,t){this.sceneManager.reorderScenes(e,t)&&(this.render(),this.markUnsavedChanges())}async updateCurrentHotspot(e,t){const n=this.hotspotEditor.currentHotspotIndex;this.hotspotEditor.updateHotspot(n,e,t)&&(await this.previewController.updateHotspotMarker(n),this.uiController.renderHotspotList(),this.markUnsavedChanges())}async updateCurrentHotspotPosition(e,t){const n=this.hotspotEditor.currentHotspotIndex,o=this.hotspotEditor.getHotspot(n);if(o){o.position||(o.position={x:0,y:0,z:0}),o.position[e]=t;const r=o.position,s=Math.sqrt(r.x*r.x+r.y*r.y+r.z*r.z);if(s>10){const t=10/s;r.x*=t,r.y*=t,r.z*=t,document.getElementById(`hotspotPos${e.toUpperCase()}`).value=r[e].toFixed(2),showToast("Position clamped to 10-unit radius","info")}await this.previewController.updateHotspotMarker(n),this.uiController.renderHotspotList(),this.markUnsavedChanges()}}updateCurrentScene(e,t){const n=this.sceneManager.currentSceneIndex;this.sceneManager.updateScene(n,e,t)&&(this.uiController.renderSceneList(),this.markUnsavedChanges())}async updateCurrentSceneImage(e){const t=this.sceneManager.currentSceneIndex;if(!(t<0)&&this.sceneManager.updateScene(t,"imageUrl",e)){const n=this.sceneManager.getCurrentScene();n&&(n.thumbnail=e),this.uiController.renderSceneList(),this.lastRenderedSceneIndex=-1,n&&(await this.previewController.loadScene(n),this.lastRenderedSceneIndex=t,showToast("Scene image updated","success")),this.markUnsavedChanges()}}render(){this.uiController.renderSceneList(),this.uiController.renderHotspotList();const e=this.sceneManager.getCurrentScene(),t=this.hotspotEditor.getCurrentHotspot();if(this.uiController.updateSceneProperties(e),this.uiController.updateHotspotProperties(t),this.uiController.updateTourProperties(this.config),this.uiController.updateInitialSceneOptions(),this.uiController.updateTargetSceneOptions(),e){const n=document.querySelector(".preview-empty");n&&(n.style.display="none");const o=this.sceneManager.currentSceneIndex;o!==this.lastRenderedSceneIndex&&(this.previewController.loadScene(e),this.lastRenderedSceneIndex=o),t&&this.previewController.highlightHotspot(this.hotspotEditor.currentHotspotIndex)}else{const e=document.querySelector(".preview-empty");e&&(e.style.display="flex"),this.lastRenderedSceneIndex=-1}}saveProject(){const e={config:this.config,scenes:this.sceneManager.getAllScenes()};return!!this.storageManager.saveProject(e)&&(this.hasUnsavedChanges=!1,showToast("Project saved","success"),!0)}loadProject(){const e=this.storageManager.loadProject();return!!e&&(this.config=e.config||this.config,this.sceneManager.loadScenes(e.scenes||[]),this.hasUnsavedChanges=!1,this.render(),showToast("Project loaded","success"),!0)}newProject(){return!(this.hasUnsavedChanges&&!confirm("You have unsaved changes. Create new project?"))&&(this.config={title:"My Virtual Tour",description:"",initialSceneId:"",autoRotate:!1,showCompass:!1},this.sceneManager.clearScenes(),this.hasUnsavedChanges=!1,this.render(),showToast("New project created","success"),!0)}importProject(){const e=document.getElementById("importUpload");e&&e.click()}async handleImportFile(e){try{this.uiController.setLoading(!0);const t=await this.storageManager.importFromFile(e);this.config=t.config||t,this.sceneManager.loadScenes(t.scenes||[]),this.hasUnsavedChanges=!0,this.render(),this.uiController.setLoading(!1),showToast("Project imported successfully","success")}catch(e){this.uiController.setLoading(!1),console.error("Import failed:",e)}}markUnsavedChanges(){this.hasUnsavedChanges=!0}};document.addEventListener("DOMContentLoaded",async()=>{document.querySelector("[data-swt-editor]")||(window.editor=new d,await window.editor.init())}),window.addEventListener("DOMContentLoaded",()=>{const e=function(){const e=document.querySelector("[data-swt-editor]");if(!e)return null;if("true"!==e.getAttribute("data-swt-auto-init"))return null;const t=e.querySelector("[data-swt-scene-list]"),n=e.querySelector("[data-swt-preview-area]"),o=e.querySelector("[data-swt-properties-panel]"),r=e.getAttribute("data-swt-project-name")||"My Virtual Tour",s="true"===e.getAttribute("data-swt-auto-save"),i=parseInt(e.getAttribute("data-swt-auto-save-interval"))||3e4,a=new TourEditor({projectName:r,autoSave:s,autoSaveInterval:i});return t&&(a.options.sceneListElement=t),n&&(a.options.previewElement=n),o&&(a.options.propertiesElement=o),a.init().catch(e=>{console.error("Failed to initialize declarative editor:",e)}),a}();e&&(window.editor=e);const t=document.getElementById("hotspotColor"),n=document.getElementById("hotspotColorText");t&&n&&(t.addEventListener("input",e=>{n.value=e.target.value}),n.addEventListener("input",e=>{/^#[0-9A-F]{6}$/i.test(e.target.value)&&(t.value=e.target.value)})),document.addEventListener("keydown",e=>{(e.ctrlKey||e.metaKey)&&"s"===e.key&&(e.preventDefault(),window.editor&&window.editor.saveProject()),(e.ctrlKey||e.metaKey)&&"e"===e.key&&(e.preventDefault(),window.editor&&window.editor.exportManager.showExportPreview()),"Escape"===e.key&&document.querySelectorAll(".modal.show").forEach(e=>{e.classList.remove("show")})});const o=document.getElementById("previewBtn");o&&o.addEventListener("click",()=>{const e=document.getElementById("preview"),t=document.getElementById("canvasArea");e&&t&&(t.classList.toggle("preview-active"),window.editor&&window.editor.previewController&&window.editor.previewController.refresh())}),document.querySelectorAll(".modal").forEach(e=>{e.addEventListener("click",t=>{t.target===e&&e.classList.remove("show")})});const r=document.querySelectorAll(".tab"),s=document.querySelectorAll(".tab-content");r.forEach(e=>{e.addEventListener("click",()=>{const t=e.dataset.tab;r.forEach(e=>e.classList.remove("active")),e.classList.add("active"),s.forEach(e=>{e.style.display="none"});const n=document.getElementById(t+"Tab");n&&(n.style.display="block");const o=document.getElementById("panelTitle");if(o)switch(t){case"scene":o.textContent="Scene Properties";break;case"hotspot":o.textContent="Hotspot Properties";break;case"tour":o.textContent="Tour Settings"}})})}),Object.assign(window,c),window.ProjectStorageManager=class{constructor(){this.storageKey="swt_project",this.autoSaveInterval=null,this.autoSaveDelay=3e4}saveProject(e){try{const t=JSON.stringify(e);return localStorage.setItem(this.storageKey,t),localStorage.setItem(this.storageKey+"_lastSaved",(new Date).toISOString()),!0}catch(e){return console.error("Failed to save project:",e),"QuotaExceededError"===e.name&&r("Storage quota exceeded. Project too large!","error"),!1}}loadProject(){try{const e=localStorage.getItem(this.storageKey);if(e){const t=JSON.parse(e);return this.validateProjectData(t)?t:(console.error("Invalid project data structure"),null)}return null}catch(e){return console.error("Failed to load project:",e),r("Failed to load project","error"),null}}validateProjectData(e){if(!e||"object"!=typeof e)return!1;if(!e.scenes||!Array.isArray(e.scenes))return!1;for(const t of e.scenes){if(!t||"object"!=typeof t)return!1;if(!t.id||"string"!=typeof t.id)return!1;if(!t.imageUrl||"string"!=typeof t.imageUrl)return!1;Array.isArray(t.hotspots)||(t.hotspots=[])}return e.config&&"object"==typeof e.config||(e.config={title:"My Virtual Tour"}),!0}clearProject(){try{return localStorage.removeItem(this.storageKey),localStorage.removeItem(this.storageKey+"_lastSaved"),!0}catch(e){return console.error("Failed to clear project:",e),!1}}hasProject(){return null!==localStorage.getItem(this.storageKey)}getLastSavedDate(){const e=localStorage.getItem(this.storageKey+"_lastSaved");return e?new Date(e):null}startAutoSave(e){this.stopAutoSave(),this.autoSaveInterval=setInterval(()=>{e()},this.autoSaveDelay)}stopAutoSave(){this.autoSaveInterval&&(clearInterval(this.autoSaveInterval),this.autoSaveInterval=null)}exportToFile(e,t="tour.json"){try{return i(JSON.stringify(e,null,2),t),!0}catch(e){return console.error("Failed to export project:",e),r("Failed to export project","error"),!1}}async importFromFile(e){return new Promise((t,n)=>{const o=new FileReader;o.onload=e=>{try{const n=JSON.parse(e.target.result);t(n)}catch(e){console.error("Failed to parse project file:",e),r("Invalid project file","error"),n(e)}},o.onerror=()=>{console.error("Failed to read file:",o.error),r("Failed to read file","error"),n(o.error)},o.readAsText(e)})}},window.SceneManagerEditor=class{constructor(e){this.editor=e,this.scenes=[],this.currentSceneIndex=-1}async addScene(e){try{const s=await n(e),i=await o(e),a={id:t(e.name.replace(/\.[^/.]+$/,"")),name:e.name.replace(/\.[^/.]+$/,""),imageUrl:i,thumbnail:s,hotspots:[]};return this.scenes.push(a),this.currentSceneIndex=this.scenes.length-1,r(`Scene "${a.name}" added successfully`,"success"),a}catch(e){return console.error("Failed to add scene:",e),r("Failed to add scene","error"),null}}removeScene(e){if(e>=0&&e<this.scenes.length){const t=this.scenes[e];return!!confirm(`Are you sure you want to delete scene "${t.name}"?`)&&(this.scenes.splice(e,1),this.currentSceneIndex===e?this.currentSceneIndex=Math.min(this.currentSceneIndex,this.scenes.length-1):this.currentSceneIndex>e&&this.currentSceneIndex--,r(`Scene "${t.name}" removed`,"success"),!0)}return!1}getScene(e){return this.scenes[e]||null}getSceneById(e){return this.scenes.find(t=>t.id===e)||null}updateScene(e,t,n){return e>=0&&e<this.scenes.length&&(this.scenes[e][t]=n,"id"===t&&this.scenes.forEach(t=>{t.hotspots.forEach(t=>{t.targetSceneId===this.scenes[e].id&&(t.targetSceneId=n)})}),!0)}reorderScenes(e,t){if(e>=0&&e<this.scenes.length&&t>=0&&t<this.scenes.length){const n=this.scenes.splice(e,1)[0];return this.scenes.splice(t,0,n),this.currentSceneIndex===e?this.currentSceneIndex=t:e<this.currentSceneIndex&&t>=this.currentSceneIndex?this.currentSceneIndex--:e>this.currentSceneIndex&&t<=this.currentSceneIndex&&this.currentSceneIndex++,!0}return!1}getCurrentScene(){return this.getScene(this.currentSceneIndex)}setCurrentScene(e){return e>=0&&e<this.scenes.length&&(this.currentSceneIndex=e,!0)}getAllScenes(){return this.scenes}getScenes(){return this.scenes}clearScenes(){return!(this.scenes.length>0&&!confirm("Are you sure you want to clear all scenes?"))&&(this.scenes=[],this.currentSceneIndex=-1,!0)}loadScenes(e){this.scenes=e||[],this.currentSceneIndex=this.scenes.length>0?0:-1}},window.HotspotEditor=class{constructor(e){this.editor=e,this.currentHotspotIndex=-1,this.placementMode=!1}enablePlacementMode(){if(!this.editor.sceneManager.getCurrentScene())return r("Please select a scene first","error"),!1;this.placementMode=!0,document.body.style.cursor="crosshair";const e=document.getElementById("preview");e&&(e.style.border="3px solid #4CC3D9",e.style.boxShadow="0 0 20px rgba(76, 195, 217, 0.5)");const t=document.getElementById("addHotspotBtn");return t&&(t.textContent="Click on Preview...",t.classList.add("btn-active")),r("Click on the 360° preview to place hotspot","info",5e3),!0}disablePlacementMode(){this.placementMode=!1,document.body.style.cursor="default";const e=document.getElementById("preview");e&&(e.style.border="",e.style.boxShadow="");const t=document.getElementById("addHotspotBtn");t&&(t.textContent="+ Add Hotspot",t.classList.remove("btn-active"))}addHotspot(t,n=""){const o=this.editor.sceneManager.getCurrentScene();if(!o)return r("No scene selected","error"),null;const s={id:e("hotspot"),type:"navigation",position:t,targetSceneId:n,title:"New Hotspot",description:"",color:"#00ff00",icon:""};return o.hotspots.push(s),this.currentHotspotIndex=o.hotspots.length-1,this.disablePlacementMode(),r("Hotspot added","success"),s}removeHotspot(e){const t=this.editor.sceneManager.getCurrentScene();return!(!t||e<0||e>=t.hotspots.length)&&(!!confirm("Are you sure you want to delete this hotspot?")&&(t.hotspots.splice(e,1),this.currentHotspotIndex===e?this.currentHotspotIndex=-1:this.currentHotspotIndex>e&&this.currentHotspotIndex--,r("Hotspot removed","success"),!0))}updateHotspot(e,t,n){const o=this.editor.sceneManager.getCurrentScene();return!(!o||e<0||e>=o.hotspots.length)&&(o.hotspots[e][t]=n,!0)}getHotspot(e){const t=this.editor.sceneManager.getCurrentScene();return!t||e<0||e>=t.hotspots.length?null:t.hotspots[e]}updateHotspotPosition(e,t){return this.updateHotspot(e,"position",t)}getCurrentHotspot(){const e=this.editor.sceneManager.getCurrentScene();return!e||this.currentHotspotIndex<0?null:e.hotspots[this.currentHotspotIndex]||null}setCurrentHotspot(e){const t=this.editor.sceneManager.getCurrentScene();return!t||e<0||e>=t.hotspots.length?(this.currentHotspotIndex=-1,!1):(this.currentHotspotIndex=e,!0)}getAllHotspots(){const e=this.editor.sceneManager.getCurrentScene();return e?e.hotspots:[]}duplicateHotspot(t){const n=this.editor.sceneManager.getCurrentScene();if(!n||t<0||t>=n.hotspots.length)return null;const o=n.hotspots[t],s=deepClone(o);return s.id=e("hotspot"),s.title=o.title+" (Copy)",s.position={x:o.position.x+.5,y:o.position.y,z:o.position.z},n.hotspots.push(s),this.currentHotspotIndex=n.hotspots.length-1,r("Hotspot duplicated","success"),s}clearAllHotspots(){const e=this.editor.sceneManager.getCurrentScene();return!!e&&(0===e.hotspots.length||!!confirm("Are you sure you want to remove all hotspots from this scene?")&&(e.hotspots=[],this.currentHotspotIndex=-1,r("All hotspots removed","success"),!0))}},window.PreviewController=class{constructor(e){this.editor=e,this.tour=null,this.isInitialized=!1,this.previewContainer=null,this.hasLoadedScene=!1}async init(){return this.previewContainer=document.getElementById("preview"),this.previewContainer?("undefined"==typeof AFRAME&&await this.waitForLibrary("AFRAME",5e3),"undefined"==typeof SWT&&await this.waitForLibrary("SWT",5e3),this.isInitialized=!0,!0):(console.error("Preview element not found"),!1)}async waitForLibrary(e,t=5e3){const n=Date.now();for(;void 0===window[e];){if(Date.now()-n>t)throw new Error(`Timeout waiting for ${e} to load`);await new Promise(e=>setTimeout(e,100))}}async loadScene(e,t=!0){if(!this.isInitialized||!e)return;if(!e.imageUrl||!e.id)return void console.error("Invalid scene data:",e);this.showLoading();let n=null;if(t&&this.tour&&(n=this.getCameraRotation()),this.tour){try{this.tour.destroy()}catch(e){console.error("Error destroying tour:",e)}this.tour=null}if(this.hasLoadedScene){const e=this.previewContainer.querySelector("a-scene");if(e)try{this.previewContainer.removeChild(e)}catch(e){console.error("Error removing scene:",e)}for(;this.previewContainer.firstChild;)this.previewContainer.removeChild(this.previewContainer.firstChild)}else{Array.from(this.previewContainer.children).forEach(e=>{"a-scene"!==e.tagName.toLowerCase()&&this.previewContainer.removeChild(e)})}const o=this.createLoadingOverlay();this.previewContainer.appendChild(o);const s=document.createElement("a-scene");s.id="preview-scene",s.setAttribute("embedded",""),s.setAttribute("vr-mode-ui","enabled: false;"),this.previewContainer.appendChild(s),await new Promise(e=>setTimeout(e,100)),(e.hotspots||[]).map(e=>({id:e.id,position:e.position,action:{type:"navigation"===e.type?"navigateTo":e.type,target:e.targetSceneId},appearance:{color:e.color||"#00ff00",icon:e.icon||null,scale:e.scale||"1 1 1"},tooltip:{text:e.title||"Hotspot"}})),e.id,e.name,e.imageUrl;const i={};(this.editor.sceneManager.scenes||[]).forEach(e=>{const t=(e.hotspots||[]).map(e=>({id:e.id,position:e.position,action:{type:"navigation"===e.type?"navigateTo":e.type,target:e.targetSceneId},appearance:{color:e.color||"#00ff00",icon:e.icon||null,scale:e.scale||"1 1 1"},tooltip:{text:e.title||"Hotspot"}}));i[e.id]={id:e.id,name:e.name,panorama:e.imageUrl,hotspots:t}});const a={title:e.name,initialScene:e.id,scenes:i,settings:{autoRotate:!1,showCompass:!1}};try{this.tour=new SWT.Tour(s,a),this.tour.addEventListener("tour-started",e=>{}),this.tour.addEventListener("scene-loaded",e=>{}),this.tour.addEventListener("hotspot-activated",e=>{const t=e.detail?.hotspotId;if(t){const e=this.editor.sceneManager.getCurrentScene();if(e){const n=e.hotspots.findIndex(e=>e.id===t);n>=0&&this.editor.selectHotspot(n)}}}),await this.tour.start(),this.hasLoadedScene=!0,this.hideLoading(),n&&t&&this.setCameraRotation(n),setTimeout(()=>{this.setupClickHandler()},500)}catch(e){console.error("Failed to load preview:",e),r("Failed to load preview: "+e.message,"error"),this.hideLoading()}}setupClickHandler(){if(!this.tour)return;const e=this.previewContainer.querySelector("a-scene");e?(this.clickHandler&&e.removeEventListener("click",this.clickHandler),this.clickHandler=t=>{if(!this.editor.hotspotEditor.placementMode)return;let n=t.detail?.intersection;if(!n){const o=e.querySelector("[camera]"),s=e.querySelector("a-sky");if(!o||!s)return void r("Scene not ready, please try again","warning");const i=e.canvas.getBoundingClientRect(),a={x:(t.clientX-i.left)/i.width*2-1,y:-(t.clientY-i.top)/i.height*2+1},c=new THREE.Raycaster,d=o.object3D;c.setFromCamera(a,d.children[0]);const l=c.intersectObject(s.object3D,!0);if(!(l.length>0))return void r("Click on the panorama image","warning");n=l[0]}const o=n.point,s={x:parseFloat(o.x.toFixed(2)),y:parseFloat(o.y.toFixed(2)),z:parseFloat(o.z.toFixed(2))};this.editor.addHotspotAtPosition(s)},e.addEventListener("click",this.clickHandler)):setTimeout(()=>this.setupClickHandler(),200)}getCameraRotation(){const e=this.previewContainer?.querySelector("a-scene");if(!e)return null;const t=e.querySelector("[camera]");if(!t)return null;const n=t.object3D.rotation;return{x:n.x,y:n.y,z:n.z}}setCameraRotation(e){if(!e)return;const t=this.previewContainer?.querySelector("a-scene");if(!t)return;const n=t.querySelector("[camera]");if(!n)return;const o=()=>{n.object3D&&n.object3D.rotation.set(e.x,e.y,e.z)};o(),setTimeout(o,100),setTimeout(o,300)}async refresh(){const e=this.editor.sceneManager.getCurrentScene();if(e){const t=this.getCameraRotation();await this.loadScene(e),t&&this.setCameraRotation(t)}}resetCamera(){const e=this.previewContainer?.querySelector("[camera]");e&&e.setAttribute("rotation","0 0 0")}pointCameraToHotspot(e){if(!e)return;const t=this.previewContainer?.querySelector("a-scene");if(!t)return;const n=t.querySelector("[camera]");if(!n||!n.object3D)return;const o=n.object3D.position,r=new THREE.Vector3(e.x-o.x,e.y-o.y,e.z-o.z),s=r.length(),i=Math.asin(r.y/s)*(180/Math.PI),a=Math.atan2(r.x,r.z)*(180/Math.PI);this.animateCameraRotation(n,{x:i,y:a,z:0})}animateCameraRotation(e,t,n=800){if(!e||!e.object3D)return;const o=e.object3D.rotation.x*(180/Math.PI),r=e.object3D.rotation.y*(180/Math.PI),s=e.object3D.rotation.z*(180/Math.PI);let i=t.y-r;for(;i>180;)i-=360;for(;i<-180;)i+=360;const a=r+i,c=Date.now(),d=()=>{const i=Date.now()-c,l=Math.min(i/n,1),u=l<.5?2*l*l:1-Math.pow(-2*l+2,2)/2,h=o+(t.x-o)*u,p=r+(a-r)*u,g=s+(t.z-s)*u;e.object3D.rotation.set(h*(Math.PI/180),p*(Math.PI/180),g*(Math.PI/180)),l<1&&requestAnimationFrame(d)};d()}highlightHotspot(e){}async updateHotspotMarker(e){const t=this.editor.sceneManager.getCurrentScene();if(!t||!this.tour)return;t.hotspots[e]&&await this.refresh()}createLoadingOverlay(){const e=document.createElement("div");return e.className="preview-loading",e.innerHTML='\n <div class="loading-spinner"></div>\n <div class="loading-text">Loading scene...</div>\n ',e}showLoading(){const e=this.previewContainer?.querySelector(".preview-loading");e&&e.classList.remove("hidden")}hideLoading(){const e=this.previewContainer?.querySelector(".preview-loading");e&&(e.classList.add("hidden"),setTimeout(()=>{e.parentNode&&e.parentNode.removeChild(e)},300))}},window.UIController=class{constructor(e){this.editor=e,this.sceneList=document.getElementById("sceneList"),this.hotspotList=document.getElementById("hotspotList"),this.draggedElement=null}renderSceneList(){if(!this.sceneList)return;this.sceneList.innerHTML="";const e=this.editor.sceneManager.getAllScenes(),t=this.editor.sceneManager.currentSceneIndex;if(0===e.length){const e=document.createElement("div");return e.className="empty-state",e.innerHTML='\n <p>No scenes yet</p>\n <p class="hint">Click "Add Scene" to upload a 360° panorama</p>\n ',void this.sceneList.appendChild(e)}e.forEach((e,n)=>{const o=this.createSceneCard(e,n,n===t);this.sceneList.appendChild(o)})}createSceneCard(e,t,n){const o=document.createElement("div");o.className="scene-card"+(n?" active":""),o.draggable=!0,o.dataset.index=t;const r=document.createElement("div");r.className="drag-handle",r.innerHTML='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">\x3c!--!Font Awesome Free v6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--\x3e<path d="M0 96C0 78.3 14.3 64 32 64l384 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 128C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32l384 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 288c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32L32 448c-17.7 0-32-14.3-32-32s14.3-32 32-32l384 0c17.7 0 32 14.3 32 32z"/></svg>';const s=document.createElement("img");s.src=e.thumbnail||e.imageUrl,s.alt=e.name;const i=document.createElement("div");i.className="scene-info";const a=document.createElement("div");a.className="scene-name",a.textContent=e.name;const c=document.createElement("div");c.className="scene-meta",c.textContent=`${e.hotspots.length} hotspot${1!==e.hotspots.length?"s":""}`,i.appendChild(a),i.appendChild(c);const d=document.createElement("div");d.className="scene-actions";const l=document.createElement("button");return l.className="btn-icon",l.innerHTML="🗑️",l.title="Delete scene",l.onclick=e=>{e.stopPropagation(),this.editor.removeScene(t)},d.appendChild(l),o.appendChild(r),o.appendChild(s),o.appendChild(i),o.appendChild(d),o.onclick=()=>{this.editor.selectScene(t)},this.setupDragAndDrop(o),o}setupDragAndDrop(e){e.addEventListener("dragstart",t=>{this.draggedElement=e,e.classList.add("dragging"),t.dataTransfer.effectAllowed="move"}),e.addEventListener("dragend",()=>{e.classList.remove("dragging"),this.draggedElement=null}),e.addEventListener("dragover",t=>{if(t.preventDefault(),t.dataTransfer.dropEffect="move",this.draggedElement&&this.draggedElement!==e){const n=e.getBoundingClientRect(),o=n.y+n.height/2;t.clientY-o>0?(e.style.borderBottom="2px solid var(--accent-color)",e.style.borderTop=""):(e.style.borderTop="2px solid var(--accent-color)",e.style.borderBottom="")}}),e.addEventListener("dragleave",()=>{e.style.borderTop="",e.style.borderBottom=""}),e.addEventListener("drop",t=>{if(t.preventDefault(),e.style.borderTop="",e.style.borderBottom="",this.draggedElement&&this.draggedElement!==e){const t=parseInt(this.draggedElement.dataset.index),n=parseInt(e.dataset.index);this.editor.reorderScenes(t,n)}})}renderHotspotList(){if(!this.hotspotList)return;this.hotspotList.innerHTML="";const e=this.editor.hotspotEditor.getAllHotspots(),t=this.editor.hotspotEditor.currentHotspotIndex;if(0===e.length){const e=document.createElement("div");return e.className="empty-state",e.textContent='No hotspots. Click "Add Hotspot" to create one.',void this.hotspotList.appendChild(e)}e.forEach((e,n)=>{const o=this.createHotspotItem(e,n,n===t);this.hotspotList.appendChild(o)})}createHotspotItem(e,t,n){const o=document.createElement("div");o.className="hotspot-item"+(n?" active":"");const r=document.createElement("div");r.className="hotspot-color",r.style.backgroundColor=e.color;const s=document.createElement("div");s.className="hotspot-info";const i=document.createElement("div");i.className="hotspot-title",i.textContent=e.title||"Untitled Hotspot";const a=document.createElement("div");if(a.className="hotspot-target",e.targetSceneId){const t=this.editor.sceneManager.getSceneById(e.targetSceneId);a.textContent=t?`→ ${t.name}`:`→ ${e.targetSceneId}`}else a.textContent="No target";s.appendChild(i),s.appendChild(a);const c=document.createElement("div");c.className="hotspot-actions";const d=document.createElement("button");return d.className="btn-delete",d.innerHTML="🗑️",d.title="Delete",d.onclick=e=>{e.stopPropagation(),this.editor.removeHotspot(t)},c.appendChild(d),o.appendChild(r),o.appendChild(s),o.appendChild(c),o.onclick=()=>{this.editor.selectHotspot(t)},o}updateHotspotProperties(e){const t=document.getElementById("hotspotAll"),n=document.getElementById("hotspotProperties");if(!e){t&&(t.style.display="block"),n&&(n.style.display="none"),document.getElementById("hotspotTitle").value="",document.getElementById("hotspotDescription").value="",document.getElementById("hotspotTarget").value="",document.getElementById("hotspotColor").value="#00ff00";const e=document.getElementById("hotspotColorText");return e&&(e.value="#00ff00"),document.getElementById("hotspotPosX").value="",document.getElementById("hotspotPosY").value="",void(document.getElementById("hotspotPosZ").value="")}t&&(t.style.display="block"),n&&(n.style.display="block"),document.getElementById("hotspotTitle").value=e.title||"",document.getElementById("hotspotDescription").value=e.description||"",document.getElementById("hotspotTarget").value=e.targetSceneId||"",document.getElementById("hotspotColor").value=e.color||"#00ff00";const o=document.getElementById("hotspotColorText");o&&(o.value=e.color||"#00ff00");const r=e.position||{x:0,y:0,z:0};document.getElementById("hotspotPosX").value=r.x,document.getElementById("hotspotPosY").value=r.y,document.getElementById("hotspotPosZ").value=r.z,this.updateTargetSceneOptions()}updateSceneProperties(e){if(!e)return document.getElementById("sceneId").value="",document.getElementById("sceneName").value="",void(document.getElementById("sceneImageUrl").value="");document.getElementById("sceneId").value=e.id||"",document.getElementById("sceneName").value=e.name||"",document.getElementById("sceneImageUrl").value=e.imageUrl||""}updateTourProperties(e){document.getElementById("tourTitle").value=e.title||"",document.getElementById("tourDescription").value=e.description||"",document.getElementById("tourInitialScene").value=e.initialSceneId||"",document.getElementById("tourAutoRotate").checked=e.autoRotate||!1,document.getElementById("tourShowCompass").checked=e.showCompass||!1;const t=document.getElementById("project-name");t&&(t.value=e.title||"")}updateTargetSceneOptions(){const e=document.getElementById("hotspotTarget");if(!e)return;const t=this.editor.sceneManager.getAllScenes(),n=e.value;e.innerHTML='<option value="">Select target scene...</option>',t.forEach(t=>{const n=document.createElement("option");n.value=t.id,n.textContent=t.name,e.appendChild(n)}),e.value=n}updateInitialSceneOptions(){const e=document.getElementById("tourInitialScene");if(!e)return;const t=this.editor.sceneManager.getAllScenes(),n=e.value;e.innerHTML='<option value="">Select initial scene...</option>',t.forEach(t=>{const n=document.createElement("option");n.value=t.id,n.textContent=t.name,e.appendChild(n)}),e.value=n}setLoading(e){const t=document.querySelector(".loading-indicator");t&&(t.style.display=e?"block":"none")}switchTab(e){document.querySelectorAll(".tab-btn").forEach(t=>{t.classList.toggle("active",t.dataset.tab===e)}),document.querySelectorAll(".tab-content").forEach(t=>{t.classList.toggle("active",t.id===e+"Tab")})}},window.ExportManager=class{constructor(e){this.editor=e}generateJSON(){const e=this.editor.sceneManager.getAllScenes(),t=this.editor.config,n=e.map(e=>({id:e.id,name:e.name,imageUrl:e.imageUrl,hotspots:e.hotspots.map(e=>({id:e.id,type:e.type||"navigation",position:e.position,targetSceneId:e.targetSceneId||"",title:e.title||"",description:e.description||"",color:e.color||"#00ff00",icon:e.icon||""}))}));let o=t.initialSceneId;!o&&e.length>0&&(o=e[0].id);return{title:t.title||"Virtual Tour",description:t.description||"",initialSceneId:o,scenes:n,settings:{autoRotate:t.autoRotate||!1,showCompass:t.showCompass||!1}}}exportJSON(){try{const e=this.generateJSON(),t=JSON.stringify(e,null,2);return i(t,sanitizeId(e.title||"tour")+".json"),showToast("Tour exported successfully","success"),!0}catch(e){return console.error("Export failed:",e),showToast("Export failed","error"),!1}}async copyJSON(){try{const e=this.generateJSON(),t=JSON.stringify(e,null,2),n=await copyToClipboard(t);return n?showToast("JSON copied to clipboard","success"):showToast("Failed to copy to clipboard","error"),n}catch(e){return console.error("Copy failed:",e),showToast("Copy failed","error"),!1}}generateViewerHTML(){const e=this.generateJSON();return`<!DOCTYPE html>\n<html lang="en">\n<head>\n <meta charset="UTF-8">\n <meta name="viewport" content="width=device-width, initial-scale=1.0">\n <title>${e.title}</title>\n <script src="https://aframe.io/releases/1.7.0/aframe.min.js"><\/script>\n <script src="dist/swt.min.js"><\/script>\n <style>\n body {\n margin: 0;\n overflow: hidden;\n font-family: Arial, sans-serif;\n }\n \n #loading {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: #000;\n display: flex;\n align-items: center;\n justify-content: center;\n color: #fff;\n z-index: 1000;\n }\n \n #loading.hidden {\n display: none;\n }\n \n .spinner {\n border: 4px solid rgba(255,255,255,0.3);\n border-top: 4px solid #fff;\n border-radius: 50%;\n width: 40px;\n height: 40px;\n animation: spin 1s linear infinite;\n margin-right: 15px;\n }\n \n @keyframes spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n }\n \n #ui {\n position: fixed;\n bottom: 20px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 100;\n display: flex;\n gap: 10px;\n }\n \n .btn {\n background: rgba(0,0,0,0.7);\n color: #fff;\n border: none;\n padding: 10px 20px;\n border-radius: 5px;\n cursor: pointer;\n font-size: 14px;\n }\n \n .btn:hover {\n background: rgba(0,0,0,0.9);\n }\n </style>\n</head>\n<body>\n <div id="loading">\n <div class="spinner"></div>\n <span>Loading Tour...</span>\n </div>\n \n <div id="tour-container"></div>\n \n <div id="ui" style="display: none;">\n <button class="btn" id="resetBtn">Reset View</button>\n <span class="btn" id="sceneInfo"></span>\n </div>\n\n <script>\n // Tour configuration\n const tourConfig = ${JSON.stringify(e,null,8)};\n \n // Initialize tour\n let tour;\n \n document.addEventListener('DOMContentLoaded', async () => {\n try {\n // Create tour instance\n tour = new SenangWebsTour('tour-container', tourConfig);\n \n // Listen to events\n tour.on('sceneChanged', (sceneId) => {\nupdateSceneInfo();\n });\n \n tour.on('ready', () => {\ndocument.getElementById('loading').classList.add('hidden');\n document.getElementById('ui').style.display = 'flex';\n updateSceneInfo();\n });\n \n tour.on('error', (error) => {\n console.error('Tour error:', error);\n alert('Failed to load tour: ' + error.message);\n });\n \n // Start tour\n await tour.start();\n \n // Setup UI\n document.getElementById('resetBtn').addEventListener('click', () => {\n const camera = document.querySelector('[camera]');\n if (camera) {\n camera.setAttribute('rotation', '0 0 0');\n }\n });\n \n } catch (error) {\n console.error('Failed to initialize tour:', error);\n alert('Failed to initialize tour: ' + error.message);\n }\n });\n \n function updateSceneInfo() {\n const sceneId = tour.getCurrentSceneId();\n const scene = tourConfig.scenes.find(s => s.id === sceneId);\n if (scene) {\n document.getElementById('sceneInfo').textContent = scene.name;\n }\n }\n <\/script>\n</body>\n</html>`}exportViewerHTML(){try{const e=this.generateViewerHTML(),t=this.generateJSON();return i(e,sanitizeId(t.title||"tour")+"-viewer.html"),showToast("Viewer HTML exported successfully","success"),!0}catch(e){return console.error("Export viewer failed:",e),showToast("Export viewer failed","error"),!1}}showExportPreview(){try{const e=this.generateJSON(),t=JSON.stringify(e,null,2),n=document.getElementById("exportPreview");return n&&(n.textContent=t),s("exportModal"),!0}catch(e){return console.error("Failed to show export preview:",e),showToast("Failed to generate preview","error"),!1}}},window.TourEditor=d}();
1
+ !function(){"use strict";function e(e="id"){return`${e}_${Date.now()}_${Math.random().toString(36).substr(2,9)}`}function t(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_+|_+$/g,"")}async function n(e,t=200,n=150){return new Promise((o,r)=>{const s=new FileReader;s.onload=e=>{const s=new Image;s.onload=()=>{const e=document.createElement("canvas"),r=e.getContext("2d");let i=s.width,a=s.height;i>a?i>t&&(a*=t/i,i=t):a>n&&(i*=n/a,a=n),e.width=i,e.height=a,r.drawImage(s,0,0,i,a),o(e.toDataURL("image/jpeg",.7))},s.onerror=r,s.src=e.target.result},s.onerror=r,s.readAsDataURL(e)})}async function o(e){return new Promise((t,n)=>{const o=new FileReader;o.onload=e=>t(e.target.result),o.onerror=n,o.readAsDataURL(e)})}function r(e,t="info",n=3e3){const o=document.getElementById("toast");o.textContent=e,o.className=`toast ${t}`,o.classList.add("show"),setTimeout(()=>{o.classList.remove("show")},n)}function s(e){const t=document.getElementById(e);t&&t.classList.add("show")}function i(e,t){const n=new Blob([e],{type:"text/plain"}),o=URL.createObjectURL(n),r=document.createElement("a");r.href=o,r.download=t,document.body.appendChild(r),r.click(),document.body.removeChild(r),URL.revokeObjectURL(o)}function a(e,t){let n;return function(...o){clearTimeout(n),n=setTimeout(()=>{clearTimeout(n),e(...o)},t)}}var c=Object.freeze({__proto__:null,debounce:a,deepClone:function(e){return JSON.parse(JSON.stringify(e))},downloadTextAsFile:i,formatFileSize:function(e){if(0===e)return"0 Bytes";const t=Math.floor(Math.log(e)/Math.log(1024));return Math.round(e/Math.pow(1024,t)*100)/100+" "+["Bytes","KB","MB","GB"][t]},generateId:e,generateThumbnail:n,hideModal:function(e){const t=document.getElementById(e);t&&t.classList.remove("show")},isValidEmail:function(e){return/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e)},loadImageAsDataUrl:o,parsePosition:function(e){const t=e.split(" ").map(Number);return{x:t[0]||0,y:t[1]||0,z:t[2]||0}},positionToString:function(e){return`${e.x.toFixed(1)} ${e.y.toFixed(1)} ${e.z.toFixed(1)}`},sanitizeId:t,showModal:s,showToast:r});let d=class{constructor(e={}){this.config={title:e.projectName||"My Virtual Tour",description:"",initialSceneId:"",autoRotate:!1,showCompass:!1},this.options={sceneListElement:e.sceneListElement||null,previewElement:e.previewElement||null,propertiesElement:e.propertiesElement||null,autoSave:void 0!==e.autoSave&&e.autoSave,autoSaveInterval:e.autoSaveInterval||3e4,...e},this.storageManager=new ProjectStorageManager,this.sceneManager=new SceneManagerEditor(this),this.hotspotEditor=new HotspotEditor(this),this.previewController=new PreviewController(this),this.uiController=new UIController(this),this.exportManager=new ExportManager(this),this.hasUnsavedChanges=!1,this.lastRenderedSceneIndex=-1,this.listenersSetup=!1}async init(e={}){e&&Object.keys(e).length>0&&(Object.assign(this.options,e),e.projectName&&(this.config.title=e.projectName));if(!await this.previewController.init())return console.error("Failed to initialize preview controller"),showToast("Failed to initialize preview","error"),!1;if(this.setupEventListeners(),this.storageManager.hasProject())try{const e=this.storageManager.loadProject();e&&e.scenes&&e.scenes.length>0?this.loadProject():(console.error("Invalid or empty project data, clearing storage"),this.storageManager.clearProject())}catch(e){console.error("Error loading saved project:",e),this.storageManager.clearProject()}return this.options.autoSave&&this.storageManager.startAutoSave(()=>{this.saveProject()},this.options.autoSaveInterval),0===this.sceneManager.getScenes().length&&this.render(),showToast("Editor ready","success"),!0}setupEventListeners(){if(this.listenersSetup)return;const e=document.querySelectorAll("#addSceneBtn"),t=document.querySelectorAll("#sceneUpload"),n=document.querySelectorAll("#importBtn"),o=document.querySelectorAll("#importUpload");(e.length>1||t.length>1||n.length>1||o.length>1)&&console.error("Duplicate IDs found in DOM. This will cause double-trigger issues."),document.getElementById("newBtn")?.addEventListener("click",()=>this.newProject()),document.getElementById("saveBtn")?.addEventListener("click",()=>this.saveProject()),document.getElementById("exportBtn")?.addEventListener("click",()=>this.exportManager.showExportPreview()),document.getElementById("importBtn")?.addEventListener("click",()=>this.importProject()),document.getElementById("helpBtn")?.addEventListener("click",()=>s("helpModal")),document.getElementById("addSceneBtn")?.addEventListener("click",()=>{const e=document.getElementById("sceneUpload");e&&e.click()}),document.getElementById("sceneUpload")?.addEventListener("change",e=>{e.target.files&&e.target.files.length>0&&(this.handleSceneUpload(e.target.files),setTimeout(()=>{e.target.value=""},100))}),document.getElementById("addHotspotBtn")?.addEventListener("click",()=>{this.hotspotEditor.enablePlacementMode()}),document.getElementById("clearHotspotsBtn")?.addEventListener("click",()=>{this.hotspotEditor.clearAllHotspots()&&this.render()}),document.querySelectorAll(".tab-btn").forEach(e=>{e.addEventListener("click",()=>{this.uiController.switchTab(e.dataset.tab)})}),document.getElementById("hotspotTitle")?.addEventListener("input",a(e=>{this.updateCurrentHotspot("title",e.target.value)},300)),document.getElementById("hotspotDescription")?.addEventListener("input",a(e=>{this.updateCurrentHotspot("description",e.target.value)},300)),document.getElementById("hotspotTarget")?.addEventListener("change",e=>{this.updateCurrentHotspot("targetSceneId",e.target.value)}),document.getElementById("hotspotColor")?.addEventListener("input",e=>{this.updateCurrentHotspot("color",e.target.value)}),document.getElementById("hotspotPosX")?.addEventListener("input",a(e=>{this.updateCurrentHotspotPosition("x",parseFloat(e.target.value)||0)},300)),document.getElementById("hotspotPosY")?.addEventListener("input",a(e=>{this.updateCurrentHotspotPosition("y",parseFloat(e.target.value)||0)},300)),document.getElementById("hotspotPosZ")?.addEventListener("input",a(e=>{this.updateCurrentHotspotPosition("z",parseFloat(e.target.value)||0)},300)),document.getElementById("sceneId")?.addEventListener("input",a(e=>{this.updateCurrentScene("id",sanitizeId(e.target.value))},300)),document.getElementById("sceneName")?.addEventListener("input",a(e=>{this.updateCurrentScene("name",e.target.value)},300)),document.getElementById("sceneImageUrl")?.addEventListener("input",a(e=>{this.updateCurrentSceneImage(e.target.value)},300)),document.getElementById("tourTitle")?.addEventListener("input",a(e=>{this.config.title=e.target.value,this.markUnsavedChanges();const t=document.getElementById("project-name");t&&t.value!==e.target.value&&(t.value=e.target.value)},300)),document.getElementById("project-name")?.addEventListener("input",a(e=>{this.config.title=e.target.value,this.markUnsavedChanges();const t=document.getElementById("tourTitle");t&&t.value!==e.target.value&&(t.value=e.target.value)},300)),document.getElementById("tourDescription")?.addEventListener("input",a(e=>{this.config.description=e.target.value,this.markUnsavedChanges()},300)),document.getElementById("tourInitialScene")?.addEventListener("change",e=>{this.config.initialSceneId=e.target.value,this.markUnsavedChanges()}),document.getElementById("tourAutoRotate")?.addEventListener("change",e=>{this.config.autoRotate=e.target.checked,this.markUnsavedChanges()}),document.getElementById("tourShowCompass")?.addEventListener("change",e=>{this.config.showCompass=e.target.checked,this.markUnsavedChanges()}),document.getElementById("exportJsonBtn")?.addEventListener("click",()=>{this.exportManager.exportJSON()}),document.getElementById("copyJsonBtn")?.addEventListener("click",()=>{this.exportManager.copyJSON()}),document.getElementById("exportViewerBtn")?.addEventListener("click",()=>{this.exportManager.exportViewerHTML()}),document.querySelectorAll(".modal-close").forEach(e=>{e.addEventListener("click",()=>{const t=e.closest(".modal");t&&hideModal(t.id)})}),document.getElementById("importUpload")?.addEventListener("change",e=>{e.target.files&&e.target.files.length>0&&(this.handleImportFile(e.target.files[0]),setTimeout(()=>{e.target.value=""},100))}),window.addEventListener("beforeunload",e=>{this.hasUnsavedChanges&&(e.preventDefault(),e.returnValue="")}),this.listenersSetup=!0}async handleSceneUpload(e){if(e&&0!==e.length){this.uiController.setLoading(!0);for(const t of e)t.type.startsWith("image/")?await this.sceneManager.addScene(t):showToast(`${t.name} is not an image`,"error");this.uiController.setLoading(!1),this.render(),this.markUnsavedChanges()}}addHotspotAtPosition(e){const t=Math.sqrt(e.x*e.x+e.y*e.y+e.z*e.z);if(t>5){const n=5/t;e.x*=n,e.y*=n,e.z*=n,e.x=parseFloat(e.x.toFixed(2)),e.y=parseFloat(e.y.toFixed(2)),e.z=parseFloat(e.z.toFixed(2))}this.hotspotEditor.addHotspot(e)?(this.lastRenderedSceneIndex=-1,this.render(),this.markUnsavedChanges()):console.error("Failed to add hotspot")}selectScene(e){if(this.sceneManager.setCurrentScene(e)){this.lastRenderedSceneIndex=-1,this.hotspotEditor.currentHotspotIndex=-1;const t=this.sceneManager.getCurrentScene();t&&(this.previewController.loadScene(t,!1),this.lastRenderedSceneIndex=e),this.uiController.renderSceneList(),this.uiController.updateSceneProperties(t),this.uiController.renderHotspotList(),this.uiController.updateHotspotProperties(null),this.uiController.updateInitialSceneOptions(),this.uiController.updateTargetSceneOptions()}}selectHotspot(e){if(this.hotspotEditor.setCurrentHotspot(e)){const t=this.hotspotEditor.getHotspot(e);this.uiController.renderHotspotList(),this.uiController.updateHotspotProperties(t),this.uiController.updateTargetSceneOptions(),this.uiController.switchTab("hotspot"),t&&t.position&&this.previewController.pointCameraToHotspot(t.position)}}removeScene(e){this.sceneManager.removeScene(e)&&(this.render(),this.markUnsavedChanges())}removeHotspot(e){this.hotspotEditor.removeHotspot(e)&&(this.lastRenderedSceneIndex=-1,this.render(),this.markUnsavedChanges())}duplicateHotspot(e){this.hotspotEditor.duplicateHotspot(e)&&(this.lastRenderedSceneIndex=-1,this.render(),this.markUnsavedChanges())}reorderScenes(e,t){this.sceneManager.reorderScenes(e,t)&&(this.render(),this.markUnsavedChanges())}async updateCurrentHotspot(e,t){const n=this.hotspotEditor.currentHotspotIndex;this.hotspotEditor.updateHotspot(n,e,t)&&(await this.previewController.updateHotspotMarker(n),this.uiController.renderHotspotList(),this.markUnsavedChanges())}async updateCurrentHotspotPosition(e,t){const n=this.hotspotEditor.currentHotspotIndex,o=this.hotspotEditor.getHotspot(n);if(o){o.position||(o.position={x:0,y:0,z:0}),o.position[e]=t;const r=o.position,s=Math.sqrt(r.x*r.x+r.y*r.y+r.z*r.z);if(s>10){const t=10/s;r.x*=t,r.y*=t,r.z*=t,document.getElementById(`hotspotPos${e.toUpperCase()}`).value=r[e].toFixed(2),showToast("Position clamped to 10-unit radius","info")}await this.previewController.updateHotspotMarker(n),this.uiController.renderHotspotList(),this.markUnsavedChanges()}}updateCurrentScene(e,t){const n=this.sceneManager.currentSceneIndex;this.sceneManager.updateScene(n,e,t)&&(this.uiController.renderSceneList(),this.markUnsavedChanges())}async updateCurrentSceneImage(e){const t=this.sceneManager.currentSceneIndex;if(!(t<0)&&this.sceneManager.updateScene(t,"imageUrl",e)){const n=this.sceneManager.getCurrentScene();n&&(n.thumbnail=e),this.uiController.renderSceneList(),this.lastRenderedSceneIndex=-1,n&&(await this.previewController.loadScene(n),this.lastRenderedSceneIndex=t,showToast("Scene image updated","success")),this.markUnsavedChanges()}}render(){this.uiController.renderSceneList(),this.uiController.renderHotspotList();const e=this.sceneManager.getCurrentScene(),t=this.hotspotEditor.getCurrentHotspot();if(this.uiController.updateSceneProperties(e),this.uiController.updateHotspotProperties(t),this.uiController.updateTourProperties(this.config),this.uiController.updateInitialSceneOptions(),this.uiController.updateTargetSceneOptions(),e){const n=document.querySelector(".preview-empty");n&&(n.style.display="none");const o=this.sceneManager.currentSceneIndex;o!==this.lastRenderedSceneIndex&&(this.previewController.loadScene(e),this.lastRenderedSceneIndex=o),t&&this.previewController.highlightHotspot(this.hotspotEditor.currentHotspotIndex)}else{const e=document.querySelector(".preview-empty");e&&(e.style.display="flex"),this.lastRenderedSceneIndex=-1}}saveProject(){const e={config:this.config,scenes:this.sceneManager.getAllScenes()};return!!this.storageManager.saveProject(e)&&(this.hasUnsavedChanges=!1,showToast("Project saved","success"),!0)}loadProject(){const e=this.storageManager.loadProject();return!!e&&(this.config=e.config||this.config,this.sceneManager.loadScenes(e.scenes||[]),this.hasUnsavedChanges=!1,this.render(),showToast("Project loaded","success"),!0)}newProject(){return!(this.hasUnsavedChanges&&!confirm("You have unsaved changes. Create new project?"))&&(this.config={title:"My Virtual Tour",description:"",initialSceneId:"",autoRotate:!1,showCompass:!1},this.sceneManager.clearScenes(),this.hasUnsavedChanges=!1,this.render(),showToast("New project created","success"),!0)}importProject(){const e=document.getElementById("importUpload");e&&e.click()}async handleImportFile(e){try{this.uiController.setLoading(!0);const t=await this.storageManager.importFromFile(e);this.config=t.config||t,this.sceneManager.loadScenes(t.scenes||[]),this.hasUnsavedChanges=!0,this.render(),this.uiController.setLoading(!1),showToast("Project imported successfully","success")}catch(e){this.uiController.setLoading(!1),console.error("Import failed:",e)}}markUnsavedChanges(){this.hasUnsavedChanges=!0}};document.addEventListener("DOMContentLoaded",async()=>{document.querySelector("[data-swt-editor]")||(window.editor=new d,await window.editor.init())}),window.addEventListener("DOMContentLoaded",()=>{const e=function(){const e=document.querySelector("[data-swt-editor]");if(!e)return null;if("true"!==e.getAttribute("data-swt-auto-init"))return null;const t=e.querySelector("[data-swt-scene-list]"),n=e.querySelector("[data-swt-preview-area]"),o=e.querySelector("[data-swt-properties-panel]"),r=e.getAttribute("data-swt-project-name")||"My Virtual Tour",s="true"===e.getAttribute("data-swt-auto-save"),i=parseInt(e.getAttribute("data-swt-auto-save-interval"))||3e4,a=new TourEditor({projectName:r,autoSave:s,autoSaveInterval:i});return t&&(a.options.sceneListElement=t),n&&(a.options.previewElement=n),o&&(a.options.propertiesElement=o),a.init().catch(e=>{console.error("Failed to initialize declarative editor:",e)}),a}();e&&(window.editor=e);const t=document.getElementById("hotspotColor"),n=document.getElementById("hotspotColorText");t&&n&&(t.addEventListener("input",e=>{n.value=e.target.value}),n.addEventListener("input",e=>{/^#[0-9A-F]{6}$/i.test(e.target.value)&&(t.value=e.target.value)})),document.addEventListener("keydown",e=>{(e.ctrlKey||e.metaKey)&&"s"===e.key&&(e.preventDefault(),window.editor&&window.editor.saveProject()),(e.ctrlKey||e.metaKey)&&"e"===e.key&&(e.preventDefault(),window.editor&&window.editor.exportManager.showExportPreview()),"Escape"===e.key&&document.querySelectorAll(".modal.show").forEach(e=>{e.classList.remove("show")})});const o=document.getElementById("previewBtn");o&&o.addEventListener("click",()=>{const e=document.getElementById("preview"),t=document.getElementById("canvasArea");e&&t&&(t.classList.toggle("preview-active"),window.editor&&window.editor.previewController&&window.editor.previewController.refresh())}),document.querySelectorAll(".modal").forEach(e=>{e.addEventListener("click",t=>{t.target===e&&e.classList.remove("show")})});const r=document.querySelectorAll(".tab"),s=document.querySelectorAll(".tab-content");r.forEach(e=>{e.addEventListener("click",()=>{const t=e.dataset.tab;r.forEach(e=>e.classList.remove("active")),e.classList.add("active"),s.forEach(e=>{e.style.display="none"});const n=document.getElementById(t+"Tab");n&&(n.style.display="block");const o=document.getElementById("panelTitle");if(o)switch(t){case"scene":o.textContent="Scene Properties";break;case"hotspot":o.textContent="Hotspot Properties";break;case"tour":o.textContent="Tour Settings"}})})}),Object.assign(window,c),window.ProjectStorageManager=class{constructor(){this.storageKey="swt_project",this.autoSaveInterval=null,this.autoSaveDelay=3e4}saveProject(e){try{const t=JSON.stringify(e);return localStorage.setItem(this.storageKey,t),localStorage.setItem(this.storageKey+"_lastSaved",(new Date).toISOString()),!0}catch(e){return console.error("Failed to save project:",e),"QuotaExceededError"===e.name&&r("Storage quota exceeded. Project too large!","error"),!1}}loadProject(){try{const e=localStorage.getItem(this.storageKey);if(e){const t=JSON.parse(e);return this.validateProjectData(t)?t:(console.error("Invalid project data structure"),null)}return null}catch(e){return console.error("Failed to load project:",e),r("Failed to load project","error"),null}}validateProjectData(e){if(!e||"object"!=typeof e)return!1;if(!e.scenes||!Array.isArray(e.scenes))return!1;for(const t of e.scenes){if(!t||"object"!=typeof t)return!1;if(!t.id||"string"!=typeof t.id)return!1;if(!t.imageUrl||"string"!=typeof t.imageUrl)return!1;Array.isArray(t.hotspots)||(t.hotspots=[])}return e.config&&"object"==typeof e.config||(e.config={title:"My Virtual Tour"}),!0}clearProject(){try{return localStorage.removeItem(this.storageKey),localStorage.removeItem(this.storageKey+"_lastSaved"),!0}catch(e){return console.error("Failed to clear project:",e),!1}}hasProject(){return null!==localStorage.getItem(this.storageKey)}getLastSavedDate(){const e=localStorage.getItem(this.storageKey+"_lastSaved");return e?new Date(e):null}startAutoSave(e){this.stopAutoSave(),this.autoSaveInterval=setInterval(()=>{e()},this.autoSaveDelay)}stopAutoSave(){this.autoSaveInterval&&(clearInterval(this.autoSaveInterval),this.autoSaveInterval=null)}exportToFile(e,t="tour.json"){try{return i(JSON.stringify(e,null,2),t),!0}catch(e){return console.error("Failed to export project:",e),r("Failed to export project","error"),!1}}async importFromFile(e){return new Promise((t,n)=>{const o=new FileReader;o.onload=e=>{try{const n=JSON.parse(e.target.result);t(n)}catch(e){console.error("Failed to parse project file:",e),r("Invalid project file","error"),n(e)}},o.onerror=()=>{console.error("Failed to read file:",o.error),r("Failed to read file","error"),n(o.error)},o.readAsText(e)})}},window.SceneManagerEditor=class{constructor(e){this.editor=e,this.scenes=[],this.currentSceneIndex=-1}async addScene(e){try{const s=await n(e),i=await o(e),a={id:t(e.name.replace(/\.[^/.]+$/,"")),name:e.name.replace(/\.[^/.]+$/,""),imageUrl:i,thumbnail:s,hotspots:[]};return this.scenes.push(a),this.currentSceneIndex=this.scenes.length-1,r(`Scene "${a.name}" added successfully`,"success"),a}catch(e){return console.error("Failed to add scene:",e),r("Failed to add scene","error"),null}}removeScene(e){if(e>=0&&e<this.scenes.length){const t=this.scenes[e];return!!confirm(`Are you sure you want to delete scene "${t.name}"?`)&&(this.scenes.splice(e,1),this.currentSceneIndex===e?this.currentSceneIndex=Math.min(this.currentSceneIndex,this.scenes.length-1):this.currentSceneIndex>e&&this.currentSceneIndex--,r(`Scene "${t.name}" removed`,"success"),!0)}return!1}getScene(e){return this.scenes[e]||null}getSceneById(e){return this.scenes.find(t=>t.id===e)||null}updateScene(e,t,n){return e>=0&&e<this.scenes.length&&(this.scenes[e][t]=n,"id"===t&&this.scenes.forEach(t=>{t.hotspots.forEach(t=>{t.targetSceneId===this.scenes[e].id&&(t.targetSceneId=n)})}),!0)}reorderScenes(e,t){if(e>=0&&e<this.scenes.length&&t>=0&&t<this.scenes.length){const n=this.scenes.splice(e,1)[0];return this.scenes.splice(t,0,n),this.currentSceneIndex===e?this.currentSceneIndex=t:e<this.currentSceneIndex&&t>=this.currentSceneIndex?this.currentSceneIndex--:e>this.currentSceneIndex&&t<=this.currentSceneIndex&&this.currentSceneIndex++,!0}return!1}getCurrentScene(){return this.getScene(this.currentSceneIndex)}setCurrentScene(e){return e>=0&&e<this.scenes.length&&(this.currentSceneIndex=e,!0)}getAllScenes(){return this.scenes}getScenes(){return this.scenes}clearScenes(){return!(this.scenes.length>0&&!confirm("Are you sure you want to clear all scenes?"))&&(this.scenes=[],this.currentSceneIndex=-1,!0)}loadScenes(e){this.scenes=e||[],this.currentSceneIndex=this.scenes.length>0?0:-1}},window.HotspotEditor=class{constructor(e){this.editor=e,this.currentHotspotIndex=-1,this.placementMode=!1}enablePlacementMode(){if(!this.editor.sceneManager.getCurrentScene())return r("Please select a scene first","error"),!1;this.placementMode=!0,document.body.style.cursor="crosshair";const e=document.getElementById("preview");e&&(e.style.border="3px solid #4CC3D9",e.style.boxShadow="0 0 20px rgba(76, 195, 217, 0.5)");const t=document.getElementById("addHotspotBtn");return t&&(t.textContent="Click on Preview...",t.classList.add("btn-active")),r("Click on the 360° preview to place hotspot","info",5e3),!0}disablePlacementMode(){this.placementMode=!1,document.body.style.cursor="default";const e=document.getElementById("preview");e&&(e.style.border="",e.style.boxShadow="");const t=document.getElementById("addHotspotBtn");t&&(t.textContent="+ Add Hotspot",t.classList.remove("btn-active"))}addHotspot(t,n=""){const o=this.editor.sceneManager.getCurrentScene();if(!o)return r("No scene selected","error"),null;const s={id:e("hotspot"),type:"navigation",position:t,targetSceneId:n,title:"New Hotspot",description:"",color:"#00ff00",icon:""};return o.hotspots.push(s),this.currentHotspotIndex=o.hotspots.length-1,this.disablePlacementMode(),r("Hotspot added","success"),s}removeHotspot(e){const t=this.editor.sceneManager.getCurrentScene();return!(!t||e<0||e>=t.hotspots.length)&&(!!confirm("Are you sure you want to delete this hotspot?")&&(t.hotspots.splice(e,1),this.currentHotspotIndex===e?this.currentHotspotIndex=-1:this.currentHotspotIndex>e&&this.currentHotspotIndex--,r("Hotspot removed","success"),!0))}updateHotspot(e,t,n){const o=this.editor.sceneManager.getCurrentScene();return!(!o||e<0||e>=o.hotspots.length)&&(o.hotspots[e][t]=n,!0)}getHotspot(e){const t=this.editor.sceneManager.getCurrentScene();return!t||e<0||e>=t.hotspots.length?null:t.hotspots[e]}updateHotspotPosition(e,t){return this.updateHotspot(e,"position",t)}getCurrentHotspot(){const e=this.editor.sceneManager.getCurrentScene();return!e||this.currentHotspotIndex<0?null:e.hotspots[this.currentHotspotIndex]||null}setCurrentHotspot(e){const t=this.editor.sceneManager.getCurrentScene();return!t||e<0||e>=t.hotspots.length?(this.currentHotspotIndex=-1,!1):(this.currentHotspotIndex=e,!0)}getAllHotspots(){const e=this.editor.sceneManager.getCurrentScene();return e?e.hotspots:[]}duplicateHotspot(t){const n=this.editor.sceneManager.getCurrentScene();if(!n||t<0||t>=n.hotspots.length)return null;const o=n.hotspots[t],s=deepClone(o);return s.id=e("hotspot"),s.title=o.title+" (Copy)",s.position={x:o.position.x+.5,y:o.position.y,z:o.position.z},n.hotspots.push(s),this.currentHotspotIndex=n.hotspots.length-1,r("Hotspot duplicated","success"),s}clearAllHotspots(){const e=this.editor.sceneManager.getCurrentScene();return!!e&&(0===e.hotspots.length||!!confirm("Are you sure you want to remove all hotspots from this scene?")&&(e.hotspots=[],this.currentHotspotIndex=-1,r("All hotspots removed","success"),!0))}},window.PreviewController=class{constructor(e){this.editor=e,this.tour=null,this.isInitialized=!1,this.previewContainer=null,this.hasLoadedScene=!1}async init(){return this.previewContainer=document.getElementById("preview"),this.previewContainer?("undefined"==typeof AFRAME&&await this.waitForLibrary("AFRAME",5e3),"undefined"==typeof SWT&&await this.waitForLibrary("SWT",5e3),this.isInitialized=!0,!0):(console.error("Preview element not found"),!1)}async waitForLibrary(e,t=5e3){const n=Date.now();for(;void 0===window[e];){if(Date.now()-n>t)throw new Error(`Timeout waiting for ${e} to load`);await new Promise(e=>setTimeout(e,100))}}async loadScene(e,t=!0){if(!this.isInitialized||!e)return;if(!e.imageUrl||!e.id)return void console.error("Invalid scene data:",e);this.showLoading();let n=null;if(t&&this.tour&&(n=this.getCameraRotation()),this.tour){try{this.tour.destroy()}catch(e){console.error("Error destroying tour:",e)}this.tour=null}if(this.hasLoadedScene){const e=this.previewContainer.querySelector("a-scene");if(e)try{this.previewContainer.removeChild(e)}catch(e){console.error("Error removing scene:",e)}for(;this.previewContainer.firstChild;)this.previewContainer.removeChild(this.previewContainer.firstChild)}else{Array.from(this.previewContainer.children).forEach(e=>{"a-scene"!==e.tagName.toLowerCase()&&this.previewContainer.removeChild(e)})}const o=this.createLoadingOverlay();this.previewContainer.appendChild(o);const s=document.createElement("a-scene");s.id="preview-scene",s.setAttribute("embedded",""),s.setAttribute("vr-mode-ui","enabled: false;"),this.previewContainer.appendChild(s),await new Promise(e=>setTimeout(e,100)),(e.hotspots||[]).map(e=>({id:e.id,position:e.position,action:{type:"navigation"===e.type?"navigateTo":e.type,target:e.targetSceneId},appearance:{color:e.color||"#00ff00",icon:e.icon||null,scale:e.scale||"1 1 1"},tooltip:{text:e.title||"Hotspot"}})),e.id,e.name,e.imageUrl;const i={};(this.editor.sceneManager.scenes||[]).forEach(e=>{const t=(e.hotspots||[]).map(e=>({id:e.id,position:e.position,action:{type:"navigation"===e.type?"navigateTo":e.type,target:e.targetSceneId},appearance:{color:e.color||"#00ff00",icon:e.icon||null,scale:e.scale||"1 1 1"},tooltip:{text:e.title||"Hotspot"}}));i[e.id]={id:e.id,name:e.name,panorama:e.imageUrl,hotspots:t}});const a={title:e.name,initialScene:e.id,scenes:i,settings:{autoRotate:!1,showCompass:!1}};try{this.tour=new SWT.Tour(s,a),this.tour.addEventListener("tour-started",e=>{}),this.tour.addEventListener("scene-loaded",e=>{}),this.tour.addEventListener("hotspot-activated",e=>{const t=e.detail?.hotspotId;if(t){const e=this.editor.sceneManager.getCurrentScene();if(e){const n=e.hotspots.findIndex(e=>e.id===t);n>=0&&this.editor.selectHotspot(n)}}}),await this.tour.start(),this.hasLoadedScene=!0,this.hideLoading(),n&&t&&this.setCameraRotation(n),setTimeout(()=>{this.setupClickHandler()},500)}catch(e){console.error("Failed to load preview:",e),r("Failed to load preview: "+e.message,"error"),this.hideLoading()}}setupClickHandler(){if(!this.tour)return;const e=this.previewContainer.querySelector("a-scene");e?(this.clickHandler&&e.removeEventListener("click",this.clickHandler),this.clickHandler=t=>{if(!this.editor.hotspotEditor.placementMode)return;let n=t.detail?.intersection;if(!n){const o=e.querySelector("[camera]"),s=e.querySelector("a-sky");if(!o||!s)return void r("Scene not ready, please try again","warning");const i=e.canvas.getBoundingClientRect(),a={x:(t.clientX-i.left)/i.width*2-1,y:-(t.clientY-i.top)/i.height*2+1},c=new THREE.Raycaster,d=o.object3D;c.setFromCamera(a,d.children[0]);const l=c.intersectObject(s.object3D,!0);if(!(l.length>0))return void r("Click on the panorama image","warning");n=l[0]}const o=n.point,s={x:parseFloat(o.x.toFixed(2)),y:parseFloat(o.y.toFixed(2)),z:parseFloat(o.z.toFixed(2))};this.editor.addHotspotAtPosition(s)},e.addEventListener("click",this.clickHandler)):setTimeout(()=>this.setupClickHandler(),200)}getCameraRotation(){const e=this.previewContainer?.querySelector("a-scene");if(!e)return null;const t=e.querySelector("[camera]");if(!t)return null;const n=t.components?.["look-controls"];if(n&&n.pitchObject&&n.yawObject){return{x:n.pitchObject.rotation.x,y:n.yawObject.rotation.y,z:0}}const o=t.object3D.rotation;return{x:o.x,y:o.y,z:o.z}}setCameraRotation(e){if(!e)return;const t=this.previewContainer?.querySelector("a-scene");if(!t)return;if(!t.querySelector("[camera]"))return;const n=()=>{const t=this.previewContainer?.querySelector("[camera]");if(!t)return;const n=t.components?.["look-controls"];n&&n.pitchObject&&n.yawObject?(n.pitchObject.rotation.x=e.x,n.yawObject.rotation.y=e.y):t.object3D&&t.object3D.rotation.set(e.x,e.y,e.z)};n(),setTimeout(n,100),setTimeout(n,300),setTimeout(n,500)}async refresh(){const e=this.editor.sceneManager.getCurrentScene();e&&await this.loadScene(e,!0)}resetCamera(){const e=this.previewContainer?.querySelector("[camera]");e&&e.setAttribute("rotation","0 0 0")}pointCameraToHotspot(e){if(!e)return;const t=this.previewContainer?.querySelector("a-scene");if(!t)return;const n=t.querySelector("[camera]");if(!n||!n.object3D)return;const o=n.object3D.position,r=new THREE.Vector3(e.x-o.x,e.y-o.y,e.z-o.z),s=r.length(),i=Math.asin(r.y/s)*(180/Math.PI),a=Math.atan2(r.x,r.z)*(180/Math.PI);this.animateCameraRotation(n,{x:i,y:a,z:0})}animateCameraRotation(e,t,n=800){if(!e||!e.object3D)return;const o=e.object3D.rotation.x*(180/Math.PI),r=e.object3D.rotation.y*(180/Math.PI),s=e.object3D.rotation.z*(180/Math.PI);let i=t.y-r;for(;i>180;)i-=360;for(;i<-180;)i+=360;const a=r+i,c=Date.now(),d=()=>{const i=Date.now()-c,l=Math.min(i/n,1),u=l<.5?2*l*l:1-Math.pow(-2*l+2,2)/2,h=o+(t.x-o)*u,p=r+(a-r)*u,g=s+(t.z-s)*u;e.object3D.rotation.set(h*(Math.PI/180),p*(Math.PI/180),g*(Math.PI/180)),l<1&&requestAnimationFrame(d)};d()}highlightHotspot(e){}async updateHotspotMarker(e){const t=this.editor.sceneManager.getCurrentScene();if(!t||!this.tour)return;t.hotspots[e]&&await this.refresh()}createLoadingOverlay(){const e=document.createElement("div");return e.className="preview-loading",e.innerHTML='\n <div class="loading-spinner"></div>\n <div class="loading-text">Loading scene...</div>\n ',e}showLoading(){const e=this.previewContainer?.querySelector(".preview-loading");e&&e.classList.remove("hidden")}hideLoading(){const e=this.previewContainer?.querySelector(".preview-loading");e&&(e.classList.add("hidden"),setTimeout(()=>{e.parentNode&&e.parentNode.removeChild(e)},300))}},window.UIController=class{constructor(e){this.editor=e,this.sceneList=document.getElementById("sceneList"),this.hotspotList=document.getElementById("hotspotList"),this.draggedElement=null}renderSceneList(){if(!this.sceneList)return;this.sceneList.innerHTML="";const e=this.editor.sceneManager.getAllScenes(),t=this.editor.sceneManager.currentSceneIndex;if(0===e.length){const e=document.createElement("div");return e.className="empty-state",e.innerHTML='\n <p>No scenes yet</p>\n <p class="hint">Click "Add Scene" to upload a 360° panorama</p>\n ',void this.sceneList.appendChild(e)}e.forEach((e,n)=>{const o=this.createSceneCard(e,n,n===t);this.sceneList.appendChild(o)})}createSceneCard(e,t,n){const o=document.createElement("div");o.className="scene-card"+(n?" active":""),o.draggable=!0,o.dataset.index=t;const r=document.createElement("div");r.className="drag-handle",r.innerHTML='<ss-icon icon="arrow-up-down-left-right" thickness="2.2"></ss-icon>';const s=document.createElement("img");s.src=e.thumbnail||e.imageUrl,s.alt=e.name;const i=document.createElement("div");i.className="scene-info";const a=document.createElement("div");a.className="scene-name",a.textContent=e.name;const c=document.createElement("div");c.className="scene-meta",c.textContent=`${e.hotspots.length} hotspot${1!==e.hotspots.length?"s":""}`,i.appendChild(a),i.appendChild(c);const d=document.createElement("div");d.className="scene-actions";const l=document.createElement("button");return l.className="btn-icon",l.innerHTML='<ss-icon icon="trash" thickness="2.2"></ss-icon>',l.title="Delete scene",l.onclick=e=>{e.stopPropagation(),this.editor.removeScene(t)},d.appendChild(l),o.appendChild(r),o.appendChild(s),o.appendChild(i),o.appendChild(d),o.onclick=()=>{this.editor.selectScene(t)},this.setupDragAndDrop(o),o}setupDragAndDrop(e){e.addEventListener("dragstart",t=>{this.draggedElement=e,e.classList.add("dragging"),t.dataTransfer.effectAllowed="move"}),e.addEventListener("dragend",()=>{e.classList.remove("dragging"),this.draggedElement=null}),e.addEventListener("dragover",t=>{if(t.preventDefault(),t.dataTransfer.dropEffect="move",this.draggedElement&&this.draggedElement!==e){const n=e.getBoundingClientRect(),o=n.y+n.height/2;t.clientY-o>0?(e.style.borderBottom="2px solid var(--accent-color)",e.style.borderTop=""):(e.style.borderTop="2px solid var(--accent-color)",e.style.borderBottom="")}}),e.addEventListener("dragleave",()=>{e.style.borderTop="",e.style.borderBottom=""}),e.addEventListener("drop",t=>{if(t.preventDefault(),e.style.borderTop="",e.style.borderBottom="",this.draggedElement&&this.draggedElement!==e){const t=parseInt(this.draggedElement.dataset.index),n=parseInt(e.dataset.index);this.editor.reorderScenes(t,n)}})}renderHotspotList(){if(!this.hotspotList)return;this.hotspotList.innerHTML="";const e=this.editor.hotspotEditor.getAllHotspots(),t=this.editor.hotspotEditor.currentHotspotIndex;if(0===e.length){const e=document.createElement("div");return e.className="empty-state",e.textContent='No hotspots. Click "Add Hotspot" to create one.',void this.hotspotList.appendChild(e)}e.forEach((e,n)=>{const o=this.createHotspotItem(e,n,n===t);this.hotspotList.appendChild(o)})}createHotspotItem(e,t,n){const o=document.createElement("div");o.className="hotspot-item"+(n?" active":"");const r=document.createElement("div");r.className="hotspot-color",r.style.backgroundColor=e.color;const s=document.createElement("div");s.className="hotspot-info";const i=document.createElement("div");i.className="hotspot-title",i.textContent=e.title||"Untitled Hotspot";const a=document.createElement("div");if(a.className="hotspot-target",e.targetSceneId){const t=this.editor.sceneManager.getSceneById(e.targetSceneId);a.textContent=t?`→ ${t.name}`:`→ ${e.targetSceneId}`}else a.textContent="No target";s.appendChild(i),s.appendChild(a);const c=document.createElement("div");c.className="hotspot-actions";const d=document.createElement("button");return d.className="btn-delete",d.innerHTML='<ss-icon icon="trash" thickness="2.2"></ss-icon>',d.title="Delete",d.onclick=e=>{e.stopPropagation(),this.editor.removeHotspot(t)},c.appendChild(d),o.appendChild(r),o.appendChild(s),o.appendChild(c),o.onclick=()=>{this.editor.selectHotspot(t)},o}updateHotspotProperties(e){const t=document.getElementById("hotspotAll"),n=document.getElementById("hotspotProperties");if(!e){t&&(t.style.display="block"),n&&(n.style.display="none"),document.getElementById("hotspotTitle").value="",document.getElementById("hotspotDescription").value="",document.getElementById("hotspotTarget").value="",document.getElementById("hotspotColor").value="#00ff00";const e=document.getElementById("hotspotColorText");return e&&(e.value="#00ff00"),document.getElementById("hotspotPosX").value="",document.getElementById("hotspotPosY").value="",void(document.getElementById("hotspotPosZ").value="")}t&&(t.style.display="block"),n&&(n.style.display="block"),document.getElementById("hotspotTitle").value=e.title||"",document.getElementById("hotspotDescription").value=e.description||"",document.getElementById("hotspotTarget").value=e.targetSceneId||"",document.getElementById("hotspotColor").value=e.color||"#00ff00";const o=document.getElementById("hotspotColorText");o&&(o.value=e.color||"#00ff00");const r=e.position||{x:0,y:0,z:0};document.getElementById("hotspotPosX").value=r.x,document.getElementById("hotspotPosY").value=r.y,document.getElementById("hotspotPosZ").value=r.z,this.updateTargetSceneOptions()}updateSceneProperties(e){if(!e)return document.getElementById("sceneId").value="",document.getElementById("sceneName").value="",void(document.getElementById("sceneImageUrl").value="");document.getElementById("sceneId").value=e.id||"",document.getElementById("sceneName").value=e.name||"",document.getElementById("sceneImageUrl").value=e.imageUrl||""}updateTourProperties(e){document.getElementById("tourTitle").value=e.title||"",document.getElementById("tourDescription").value=e.description||"",document.getElementById("tourInitialScene").value=e.initialSceneId||"",document.getElementById("tourAutoRotate").checked=e.autoRotate||!1,document.getElementById("tourShowCompass").checked=e.showCompass||!1;const t=document.getElementById("project-name");t&&(t.value=e.title||"")}updateTargetSceneOptions(){const e=document.getElementById("hotspotTarget");if(!e)return;const t=this.editor.sceneManager.getAllScenes(),n=e.value;e.innerHTML='<option value="">Select target scene...</option>',t.forEach(t=>{const n=document.createElement("option");n.value=t.id,n.textContent=t.name,e.appendChild(n)}),e.value=n}updateInitialSceneOptions(){const e=document.getElementById("tourInitialScene");if(!e)return;const t=this.editor.sceneManager.getAllScenes(),n=e.value;e.innerHTML='<option value="">Select initial scene...</option>',t.forEach(t=>{const n=document.createElement("option");n.value=t.id,n.textContent=t.name,e.appendChild(n)}),e.value=n}setLoading(e){const t=document.querySelector(".loading-indicator");t&&(t.style.display=e?"block":"none")}switchTab(e){document.querySelectorAll(".tab-btn").forEach(t=>{t.classList.toggle("active",t.dataset.tab===e)}),document.querySelectorAll(".tab-content").forEach(t=>{t.classList.toggle("active",t.id===e+"Tab")})}},window.ExportManager=class{constructor(e){this.editor=e}generateJSON(){const e=this.editor.sceneManager.getAllScenes(),t=this.editor.config,n={};e.forEach(e=>{n[e.id]={name:e.name,panorama:e.imageUrl,hotspots:e.hotspots.map(e=>{const t={position:e.position};return"navigation"===e.type&&e.targetSceneId?t.action={type:"navigateTo",target:e.targetSceneId}:"info"===e.type&&(t.action={type:"showInfo"}),t.appearance={color:e.color||"#FF6B6B",scale:"2 2 2"},e.title&&(t.tooltip={text:e.title}),t})}});let o=t.initialSceneId;!o&&e.length>0&&(o=e[0].id);return{initialScene:o,scenes:n}}exportJSON(){try{const e=this.generateJSON(),t=JSON.stringify(e,null,2),n=this.editor.config.title||"tour";return i(t,sanitizeId(n)+".json"),showToast("Tour exported successfully","success"),!0}catch(e){return console.error("Export failed:",e),showToast("Export failed","error"),!1}}async copyJSON(){try{const e=this.generateJSON(),t=JSON.stringify(e,null,2),n=await copyToClipboard(t);return n?showToast("JSON copied to clipboard","success"):showToast("Failed to copy to clipboard","error"),n}catch(e){return console.error("Copy failed:",e),showToast("Copy failed","error"),!1}}generateViewerHTML(){const e=this.generateJSON();return`<!DOCTYPE html>\n<html lang="en">\n <head>\n <meta charset="UTF-8" />\n <meta name="viewport" content="width=device-width, initial-scale=1.0" />\n <title>${this.editor.config.title||"Virtual Tour"}</title>\n <script src="https://unpkg.com/senangwebs-tour@latest/dist/swt.min.js"><\/script>\n </head>\n <body>\n <a-scene id="tour-container">\n <a-camera>\n <a-cursor></a-cursor>\n </a-camera>\n </a-scene>\n\n <script>\n // Tour configuration\n const tourConfig = ${JSON.stringify(e,null,8).split("\n").map((e,t)=>0===t?e:" "+e).join("\n")};\n\n // Initialize tour when scene is loaded\n const sceneEl = document.querySelector("#tour-container");\n sceneEl.addEventListener("loaded", () => {\n const tour = new SWT.Tour(sceneEl, tourConfig);\n tour.start();\n });\n <\/script>\n </body>\n</html>`}exportViewerHTML(){try{const e=this.generateViewerHTML(),t=this.editor.config.title||"tour";return i(e,sanitizeId(t)+"-viewer.html"),showToast("Viewer HTML exported successfully","success"),!0}catch(e){return console.error("Export viewer failed:",e),showToast("Export viewer failed","error"),!1}}showExportPreview(){try{const e=this.generateJSON(),t=JSON.stringify(e,null,2),n=document.getElementById("exportPreview");return n&&(n.textContent=t),s("exportModal"),!0}catch(e){return console.error("Failed to show export preview:",e),showToast("Failed to generate preview","error"),!1}}},window.TourEditor=d}();