jQuery(document).ready(function ($) { if (typeof window.LinnoOnboarding === 'undefined') { console.error('Linno Onboarding library not loaded'); return; } const { registerOnboarding, engine, tracker } = window.LinnoOnboarding; const content = { 'real-estate': { title: "Show your property without another site visit", sub: "Most agents publish their first property tour in minutes.", cta: "USE PROPERTY TEMPLATE", publishText: "Publish property tour", prev: "This is how buyers will experience your listing", succ: "Your tour is published!", succSub: "Your virtual tour has been created and is ready to be displayed on your website.", icon: "🏠" }, 'hotel': { title: "Let guests experience your hotel before they book", sub: "Virtual tours help increase trust and direct bookings.", cta: "USE HOTEL TEMPLATE", publishText: "Publish hotel tour", prev: "This is how guests will see your room", succ: "Your tour is published!", succSub: "Your virtual tour has been created and is ready to be displayed on your website.", icon: "🏨" }, 'school': { title: "Showcase your campus to prospective students", sub: "Virtual campus tours help increase enrollment and engagement.", cta: "USE CAMPUS TEMPLATE", publishText: "Publish campus tour", prev: "This is how students will explore your campus", succ: "Your tour is published!", succSub: "Your virtual tour has been created and is ready to be displayed on your website.", icon: "🏫" }, 'automotive': { title: "Showcase your vehicles in stunning detail", sub: "Virtual tours help customers explore every angle before purchase.", cta: "USE AUTOMOTIVE TEMPLATE", publishText: "Publish automotive tour", prev: "This is how customers will explore your vehicles", succ: "Your tour is published!", succSub: "Your virtual tour has been created and is ready to be displayed on your website.", icon: "🚗" }, 'ecommerce': { title: "Create immersive shopping experiences", sub: "Virtual tours help customers visualize products in real spaces.", cta: "USE E-COMMERCE TEMPLATE", publishText: "Publish ecommerce tour", prev: "This is how customers will shop your products", succ: "Your tour is published!", succSub: "Your virtual tour has been created and is ready to be displayed on your website.", icon: "🛍" }, 'exhibitions': { title: "Bring your art and exhibitions to life", sub: "Virtual tours help visitors explore galleries from anywhere.", cta: "USE EXHIBITION TEMPLATE", publishText: "Publish exhibition tour", prev: "This is how visitors will experience your exhibition", succ: "Your tour is published!", succSub: "Your virtual tour has been created and is ready to be displayed on your website.", icon: "🎨" }, 'offices': { title: "Showcase your workspace to potential clients", sub: "Virtual office tours help build trust and attract talent.", cta: "USE OFFICE TEMPLATE", publishText: "Publish office tour", prev: "This is how visitors will explore your office", succ: "Your tour is published!", succSub: "Your virtual tour has been created and is ready to be displayed on your website.", icon: "🏢" }, 'training': { title: "Create engaging onboarding experiences", sub: "Virtual training tours help new employees learn faster.", cta: "USE TRAINING TEMPLATE", publishText: "Publish training tour", prev: "This is how trainees will experience your program", succ: "Your tour is published!", succSub: "Your virtual tour has been created and is ready to be displayed on your website.", icon: "📘" }, 'showrooms': { title: "Transform your showroom into a virtual experience", sub: "Virtual showrooms help customers explore products anytime.", cta: "USE SHOWROOM TEMPLATE", publishText: "Publish showroom tour", prev: "This is how customers will browse your showroom", succ: "Your tour is published!", succSub: "Your virtual tour has been created and is ready to be displayed on your website.", icon: "🏬" }, 'tourism': { title: "Let travelers explore destinations before they arrive", sub: "Virtual tours help increase bookings and visitor engagement.", cta: "USE TOURISM TEMPLATE", publishText: "Publish tourism tour", prev: "This is how travelers will discover your destination", succ: "🎉 Your tour is published!", succSub: "Your virtual tour has been created and is ready to be displayed on your website.", icon: "🏖" }, 'fitness': { title: "Showcase your fitness facilities", sub: "Virtual tours help attract new members and showcase amenities.", cta: "USE FITNESS TEMPLATE", publishText: "Publish fitness tour", prev: "This is how members will explore your facility", succ: "Your tour is published!", succSub: "Your virtual tour has been created and is ready to be displayed on your website.", icon: "🏋️" }, 'museums': { title: "Make cultural sites accessible to everyone", sub: "Virtual museum tours help reach global audiences.", cta: "USE MUSEUM TEMPLATE", publishText: "Publish museum tour", prev: "This is how visitors will explore your museum", succ: "Your tour is published!", succSub: "Your virtual tour has been created and is ready to be displayed on your website.", icon: "🏛" } }; // Pro / hotspot-limit config (populated via wp_localize_script) const wpvrWizardCfg = (typeof wpvrSetupWizardData !== 'undefined') ? wpvrSetupWizardData : {}; const isPro = !!wpvrWizardCfg.is_pro; const HOTSPOT_LIMIT = isPro ? Infinity : (parseInt(wpvrWizardCfg.hotspot_limit, 10) || 5); const wizardStrings = wpvrWizardCfg.wizard_strings || {}; let industrySkipped = false; // true when user chose to upload own image from step 1 let bypassTemplateOnBack = false; // true when returning backward through the bypassed template step let selectedIndustry = null; let uploadedImageUrl = null; let uploadedImageFile = null; let uploadedImageId = null; // WordPress attachment ID let panoramaViewer = null; let hotspots = []; let pendingHotspotCoords = null; let panoramaMouseDown = null; let panoramaIsDragging = false; let templateData = null; // Store template tour object let mediaUploader = null; // WordPress media uploader instance // Skip wizard and redirect to tour listing function skipWizard() { const listingUrl = ajaxurl.replace('admin-ajax.php', 'edit.php?post_type=wpvr_item'); window.location.href = listingUrl; } // Bind "Remind me later" button (exits the wizard, redirects to listing) $('#wizard-exit').on('click', function (e) { e.preventDefault(); skipWizard(); }); // Generate random string for scene IDs (matching WPVR format) function generateRandomString(length) { var result = ''; var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; var charactersLength = characters.length; for (var i = 0; i < length; i++) { var randomIndex = Math.floor(Math.random() * charactersLength); result += characters.charAt(randomIndex); } return result; } // Universal open WP Media method for our workflow function openWPMediaForDirectUpload(callback) { // Create WordPress media uploader with standard frame const uploader = wp.media({ title: 'Select or Upload 360° Image', button: { text: 'Select Image' }, multiple: false, library: { type: 'image' }, state: 'library' }); // When image is selected uploader.on('select', function () { const attachment = uploader.state().get('selection').first().toJSON(); callback(attachment.url, attachment.id); }); uploader.open(); } // ── Skip-mode UI helper ───────────────────────────────────────── // Toggles the sidebar Template nav item and the progress bars in // step-preview and step-success between the 4-step default and the // 3-step "no template" variant. function updateSkipModeUI(isSkipped) { const filled = '
'; const empty = ''; if (isSkipped) { // Hide template nav, renumber remaining steps as 1→2→3 $('.nav-item[data-step="step-template"]').hide(); $('.nav-item[data-step="step-preview"]').attr('data-number', '2'); $('.nav-item[data-step="step-success"]').attr('data-number', '3'); // Progress bars — 3-step mode $('#step-vertical .step-progress').html( filled + empty + empty + '1/3' ); $('#step-preview .step-progress').html( filled + filled + empty + '2/3' ); $('#step-success .step-progress').html( filled + filled + filled + '3/3' ); } else { // Show template nav, restore original numbers 1→2→3→4 $('.nav-item[data-step="step-template"]').show(); $('.nav-item[data-step="step-preview"]').attr('data-number', '3'); $('.nav-item[data-step="step-success"]').attr('data-number', '4'); // Progress bars — 4-step mode $('#step-vertical .step-progress').html( filled + empty + empty + empty + '1/4' ); $('#step-preview .step-progress').html( filled + filled + filled + empty + '3/4' ); $('#step-success .step-progress').html( filled + filled + filled + filled + '4/4' ); } } // Helper function to update dynamic content based on selected industry function updateDynamicContent() { const data = content[selectedIndustry]; if (!data) return; $('.dynamic-title').text(data.title); $('.dynamic-sub').text(data.sub); $('#use-template-btn').text(data.cta); $('.dynamic-prev-title').text(data.prev); $('.dynamic-success-title').text(data.succ); $('.dynamic-success-sub').text(data.succSub); $('#publish-btn').text(data.publishText); } function updateIconPlaceholder() { const data = content[selectedIndustry]; if (!data) return; $('#template-icon-placeholder').text(data.icon); } // Navigation helper function function navigateTo(stepId) { // Show/Hide Steps Container (contains sidebar + content) if (stepId === 'step-welcome') { $('#steps-container').hide(); $('#welcome-content').show(); $('#onboarding-app').removeClass('sidebar-visible'); } else { $('#steps-container').css('display', 'flex'); $('#welcome-content').hide(); $('#onboarding-app').addClass('sidebar-visible'); } // Toggle Active Step $('.step').removeClass('active'); $('#' + stepId).addClass('active'); // Update Sidebar Navigation UI $('.nav-item').removeClass('active'); $(`.nav-item[data-step="${stepId}"]`).addClass('active'); // Mark previous steps as completed updateNavProgress(stepId); // Trigger celebration effect on success step (only once) if (stepId === 'step-success' && !celebrationTriggered) { // Small delay to ensure step is fully rendered setTimeout(() => { triggerCelebration(); }, 300); } else if (stepId !== 'step-success') { // Remove confetti if navigating away from success $('.confetti-container').remove(); // Reset celebration trigger if going back (optional - comment out if you want it to only play once ever) // celebrationTriggered = false; } } function updateNavProgress(currentId) { // Dynamically build steps array depending on 'industrySkipped' or direct upload path let steps = ['step-vertical', 'step-template', 'step-preview', 'step-success']; if (industrySkipped || (uploadedImageUrl && !templateData)) { // Hide the template nav item in the DOM $('.nav-item[data-step="step-template"]').hide(); steps = ['step-vertical', 'step-preview', 'step-success']; } else { $('.nav-item[data-step="step-template"]').show(); } const currentIndex = steps.indexOf(currentId); $('.nav-item').each(function () { const stepId = $(this).data('step'); // Skip checking hidden steps if (steps.indexOf(stepId) === -1) { $(this).removeClass('completed active'); return; } const stepIndex = steps.indexOf(stepId); if (stepId === currentId) { $(this).removeClass('completed').addClass('active'); } else { $(this).removeClass('active'); if (stepIndex < currentIndex && stepIndex !== -1) { $(this).addClass('completed'); } else { $(this).removeClass('completed'); } } }); } // Track if celebration has been triggered to prevent replay let celebrationTriggered = false; function triggerCelebration() { // Check if already triggered if (celebrationTriggered) { return; } // Check for prefers-reduced-motion const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; if (prefersReducedMotion) { return; } // Mark as triggered celebrationTriggered = true; // Remove any existing confetti $('.confetti-container').remove(); // Create confetti container const confettiContainer = $(''); $('body').append(confettiContainer); // Create lightweight confetti particles (fewer particles for performance) const colors = ['#3F04FE', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6']; const particleCount = 30; // Reduced from 50 for better performance // Get check circle position for burst origin const checkCircle = $('.check-circle'); let centerX = window.innerWidth / 2; let centerY = window.innerHeight / 2; if (checkCircle.length) { const offset = checkCircle.offset(); if (offset) { centerX = offset.left + checkCircle.outerWidth() / 2; centerY = offset.top + checkCircle.outerHeight() / 2; } } for (let i = 0; i < particleCount; i++) { const confetti = $(''); const color = colors[Math.floor(Math.random() * colors.length)]; // Calculate random direction and distance const angle = (Math.PI * 2 * i) / particleCount + (Math.random() * 0.5); const distance = 100 + Math.random() * 150; const tx = Math.cos(angle) * distance; const ty = Math.sin(angle) * distance; const rotation = 360 + Math.random() * 360; confetti.css({ 'background': color, 'left': centerX + 'px', 'top': centerY + 'px', '--tx': tx + 'px', '--ty': ty + 'px', '--rot': rotation + 'deg', 'width': (6 + Math.random() * 6) + 'px', 'height': (6 + Math.random() * 6) + 'px', 'border-radius': Math.random() > 0.5 ? '50%' : '0' }); confettiContainer.append(confetti); } // Remove confetti after animation completes (1.2s duration) setTimeout(function () { confettiContainer.remove(); }, 1500); } // Register onboarding with Linno Onboarding library registerOnboarding({ plugin: 'wpvr-onboarding', version: '8.5.54', theme: { color: '#3F04FE' }, // WordPress Telemetry Integration telemetry: { onSetupStarted: (data) => { console.log('📊 Telemetry: setup_started', data); }, onSetupCompleted: (data) => { console.log('Setup completed event emitted by wizard flow'); }, onFirstStrikeCompleted: (data) => { console.log('First strike is tracked on first tour publish'); } }, steps: [ { id: 'vertical', title: 'Industry', description: 'What are you creating a virtual tour for?', canGoBack: false, canSkip: false, mount: (container, context) => { navigateTo('step-vertical'); // Reset global state each time we visit the first step from scratch industrySkipped = false; bypassTemplateOnBack = false; updateSkipModeUI(false); setTimeout(() => { // Initially disable next button until industry is selected const nextBtn = $('.next-btn[data-next="step-template"]'); if (!selectedIndustry) { nextBtn.prop('disabled', true).addClass('disabled'); } // Select Industry Flow $('.v-card').off('click').on('click', function () { const newIndustry = $(this).data('vertical'); // Reset all template/upload/hotspot state when the industry changes if (selectedIndustry && selectedIndustry !== newIndustry) { uploadedImageUrl = null; uploadedImageFile = null; uploadedImageId = null; templateData = null; hotspots = []; mediaUploader = null; if (panoramaViewer) { try { panoramaViewer.destroy(); } catch (e) { } panoramaViewer = null; } } $('.v-card').removeClass('active'); $(this).addClass('active'); selectedIndustry = newIndustry; // Pre-fetch template in the background here (optional) to make next step faster // Auto-proceed to step 2 (Template) context.goNext(); }); // Skip / Upload My Own — triggers wp.media immediately $('#skip-industry-link').off('click').on('click', function (e) { e.preventDefault(); openWPMediaForDirectUpload(function (url, id) { if (url) { industrySkipped = true; selectedIndustry = null; uploadedImageUrl = url; uploadedImageId = id; uploadedImageFile = null; templateData = null; hotspots = []; $('.v-card').removeClass('active'); // Apply 3-step UI before navigating updateSkipModeUI(true); // goNext() lands on template mount which will auto-skip forward context.goNext(); } }); }); // Fallback continue btn $('.next-btn[data-next="step-template"]').off('click').on('click', () => { context.goNext(); }); }, 0); } }, { id: 'template', title: 'Template', description: 'Choose your template', canGoBack: true, canSkip: false, onNext: (context) => { if (!uploadedImageUrl) { alert('Please upload a 360° image or use a template before proceeding.'); return false; } return true; }, mount: (container, context) => { // ── Bypass logic ──────────────────────────────────────────── // If industry was skipped (forward pass) → jump straight to preview if (industrySkipped) { updateSkipModeUI(true); context.goNext(); return; } // If returning backward through a previously-skipped template → go to industry if (bypassTemplateOnBack) { bypassTemplateOnBack = false; updateSkipModeUI(false); context.goBack(); return; } // ── Normal template step ───────────────────────────────── navigateTo('step-template'); updateDynamicContent(); updateIconPlaceholder(); setTimeout(() => { const $useTemplateBtn = $('#use-template-btn'); $useTemplateBtn.removeData('default-text'); const defaultUseTemplateText = $useTemplateBtn.text(); $useTemplateBtn.data('default-text', defaultUseTemplateText); $useTemplateBtn.prop('disabled', false).removeClass('is-loading'); // Always show template box side-by-side with upload button $('#template-preview-box').show(); $('#upload-section').show(); // Bind navigation buttons $('.btn-back').off('click').on('click', () => { context.goBack(); }); $('.next-btn[data-next="step-preview"]').not('#use-template-btn').off('click').on('click', () => { context.goNext(); }); // Bind WP media direct trigger — both the zone and the browse link $('#template-upload-zone, #btn-upload-own-image').off('click.tmplUpload').on('click.tmplUpload', function (e) { e.preventDefault(); openWPMediaForDirectUpload(function (url, id) { if (url) { uploadedImageUrl = url; uploadedImageId = id; uploadedImageFile = null; templateData = null; hotspots = []; // Jump immediately to step-preview context.goNext(); } }); }); // Use template button handler - call remote API $('#use-template-btn').off('click.template').on('click.template', function (e) { e.preventDefault(); // Show loading state const $btn = $(this); const originalText = $btn.text(); $btn.prop('disabled', true).addClass('is-loading').text('Importing template & media...'); // Call AJAX to fetch template $.ajax({ url: ajaxurl, method: 'POST', data: { action: 'wpvr_fetch_template', industry: selectedIndustry, security: wpvrNonce }, success: function (response) { if (response.success && response.data.template) { templateData = response.data.template; if (templateData.post_id) { window.wpvrCreatedTourId = templateData.post_id; window.wpvrCreatedTourUrl = templateData.view_url || ''; window.wpvrCreatedTourEditUrl = templateData.edit_url || ''; } // If template has an image URL, use it hotspots = []; if (templateData.image_url) { uploadedImageUrl = templateData.image_url; } else { // Fallback placeholder uploadedImageUrl = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAwIiBoZWlnaHQ9IjQwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iODAwIiBoZWlnaHQ9IjQwMCIgZmlsbD0iI2YzZjRmNiIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMjQiIGZpbGw9IiM2YjcyODAiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIj5UZW1wbGF0ZSBQcmV2aWV3PC90ZXh0Pjwvc3ZnPg=='; } // Auto proceed to next step setTimeout(() => { context.goNext(); }, 500); } else { alert('Failed to load template. Please try again.'); $btn.prop('disabled', false).removeClass('is-loading').text(originalText); } }, error: function (xhr, status, error) { console.error('Template fetch error:', error); alert('Failed to load template. Please try again.'); $btn.prop('disabled', false).removeClass('is-loading').text(originalText); } }); }); }, 0); } }, { id: 'preview', title: 'Preview', description: 'This is how buyers will experience your listing', canGoBack: true, canSkip: false, onNext: (context) => { // Validate that user has either uploaded an image or used a template if (!uploadedImageUrl) { alert('Please upload a 360° image or use a template before proceeding.'); return false; } return true; }, mount: (container, context) => { navigateTo('step-preview'); updateDynamicContent(); // Defensive: re-apply skip mode UI in case of any timing issues updateSkipModeUI(industrySkipped); setTimeout(() => { // Back button: if user came through skip-mode, go back to industry rather than template $('.btn-back').off('click').on('click', () => { if (industrySkipped) { // Signal template mount to pass straight through backward bypassTemplateOnBack = true; industrySkipped = false; context.goBack(); // lands on template → template auto-calls goBack() to industry } else { context.goBack(); } }); $('#publish-btn').off('click').on('click', function () { const $btn = $(this); const originalText = $btn.text(); $btn.prop('disabled', true).text('Publishing...'); // Build panodata object matching WPVR format // Generate scene ID (8 character random string) const sceneId = generateRandomString(8); // Create scene object matching WPVR structure const scene = { 'scene-type': 'equirectangular', 'scene-id': sceneId, 'hotspot-list': [], 'dscene': 'off', 'scene-attachment-url': uploadedImageUrl }; // Add hotspots if any (matching WPVR format) if (hotspots.length > 0) { hotspots.forEach(function (hotspot, index) { const hotspotId = generateRandomString(8); scene['hotspot-list'].push({ 'hotspot-title': hotspotId, 'hotspot-pitch': hotspot.pitch, 'hotspot-yaw': hotspot.yaw, 'hotspot-customclass': '', 'hotspot-scene': '', 'hotspot-url': '', 'hotspot-content': '', 'hotspot-hover': '' + (hotspot.text || '') + '
', 'hotspot-type': hotspot.type || 'info', 'hotspot-scene-list': 'none', 'wpvr_url_open': {} }); }); } // Build panodata structure matching WPVR format let panodata = { 'autoLoad': true, 'showControls': true, 'customcontrol': '', 'genericform': 'off', 'genericformshortcode': '', 'preview': '', 'defaultscene': sceneId, // Set default scene to generated scene ID 'scenefadeduration': '', 'panodata': { 'scene-list': { '1': scene // WPVR uses numeric keys starting from 1 } }, 'previewtext': 'Click To Load Panorama' }; // If template data exists, merge it with panodata if (templateData && templateData.panodata) { // Merge template settings if (templateData.autoLoad !== undefined) panodata.autoLoad = templateData.autoLoad; if (templateData.showControls !== undefined) panodata.showControls = templateData.showControls; if (templateData.defaultscene) panodata.defaultscene = templateData.defaultscene; // Merge template panodata scene-list if exists if (templateData.panodata && templateData.panodata['scene-list']) { // Merge template scenes with our scene Object.assign(panodata.panodata['scene-list'], templateData.panodata['scene-list']); // Ensure our scene is the first one (key 1) panodata.panodata['scene-list']['1'] = scene; panodata.defaultscene = sceneId; } } // Create tour via AJAX $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'wpvr_create_tour_from_wizard', panodata: JSON.stringify(panodata), templateMeta: templateData && templateData.meta ? JSON.stringify(templateData.meta) : '', existing_post_id: templateData && templateData.post_id ? templateData.post_id : '', title: 'My ' + ( selectedIndustry === 'real-estate' ? 'Property' : selectedIndustry === 'hotel' ? 'Hotel' : selectedIndustry === 'school' ? 'Campus' : selectedIndustry === 'automotive' ? 'Automotive' : selectedIndustry === 'ecommerce' ? 'E-commerce' : selectedIndustry === 'exhibitions' ? 'Exhibition' : selectedIndustry === 'offices' ? 'Office' : selectedIndustry === 'training' ? 'Training' : selectedIndustry === 'showrooms' ? 'Showroom' : selectedIndustry === 'tourism' ? 'Tourism' : selectedIndustry === 'fitness' ? 'Fitness' : selectedIndustry === 'museums' ? 'Museum' : 'Virtual' ) + ' Tour', industry: selectedIndustry, security: wpvrNonce }, success: function (response) { if (response.success) { // Store tour ID and URLs for later use window.wpvrCreatedTourId = response.data.post_id; window.wpvrCreatedTourUrl = response.data.view_url; window.wpvrCreatedTourEditUrl = response.data.edit_url; // Proceed to next step context.goNext(); } else { alert('Failed to create tour: ' + (response.data.message || 'Unknown error')); $btn.prop('disabled', false).text(originalText); } }, error: function (xhr, status, error) { console.error('Tour creation error:', error); alert('Failed to create tour. Please try again.'); $btn.prop('disabled', false).text(originalText); } }); }); // Bind skip button $('#skip-preview').off('click').on('click', function (e) { e.preventDefault(); skipWizard(); }); // Initialize panorama viewer if image is uploaded if (uploadedImageUrl) { initPanoramaViewer(); } else { $('#panorama-container').hide(); $('#preview-image-placeholder').show(); } renderHotspotList(); }, 0); } }, { id: 'success', title: 'Publish', description: 'Your property tour is live!', canGoBack: true, canSkip: false, mount: (container, context) => { navigateTo('step-success'); updateDynamicContent(); // Change pro badge color to #01B5FF on last step setTimeout(() => { $('.pro-badge').css({ 'background': '#01B5FF', 'background-color': '#01B5FF' }); }, 0); // Automatically complete onboarding when user reaches the last step setTimeout(() => { // Complete the final step to trigger onboarding completion and firstStrike context.completeStep(); }, 100); setTimeout(() => { // Bind Consent Checkbox $('#consent-checkbox').off('change').on('change', function () { const isChecked = $(this).is(':checked'); $.ajax({ url: ajaxurl, type: 'POST', data: { action: 'wpvr_save_opt_in_toggle', opt_in: isChecked ? 1 : 0, security: wpvrNonce }, success: function (response) { console.log('Opt-in preference updated:', response); } }); }); // Fire it initially if checked (as it's default checked in template) if ($('#consent-checkbox').is(':checked')) { $('#consent-checkbox').trigger('change'); } // Clear localStorage if (typeof Storage !== 'undefined') { localStorage.clear(); } // Bind back button (restart) $('.btn-back').off('click').on('click', () => { location.reload(); }); // Update button text and show shortcode if tour is created if (window.wpvrCreatedTourId) { // Show shortcode instructions - insert before primary CTA const shortcode = '[wpvr id="' + window.wpvrCreatedTourId + '"]'; const shortcodeHtml = 'Copy this shortcode and paste it into any page, post, or widget area to display your virtual tour.
' + '' + shortcode + '' +
'' +
'' + (wizardStrings.limit_line1 || 'You can add up to ' + HOTSPOT_LIMIT + ' hotspots on each scene in the Free version.') + '
' + '' + (wizardStrings.limit_line2 || 'Upgrade to Pro to add an unlimited number of hotspots.') + '
' ); // Apply the discount label CSS custom property var $label = $popup.find('.wpvr-premium-feature__discount-price-label'); if ($label.length) { $label[0].style.setProperty('--discount-content-value', '"' + ($label.data('discount') || '') + '"'); } $popup.show(); } // Hide the premium upgrade popup and restore original heading text function hideHotspotLimitNotice() { var $popup = $('#wpvr_premium_feature_popup'); var $heading = $popup.find('.wpvr-premium-feature__heading'); var $subheading = $popup.find('.wpvr-premium-feature__subheading'); if ($heading.data('original-text')) { $heading.text($heading.data('original-text')); } if ($subheading.data('original-html')) { $subheading.html($subheading.data('original-html')); } $popup.hide(); } // Save hotspot function saveHotspot() { const text = $('#hotspot-text').val().trim(); if (!isPro && hotspots.length >= HOTSPOT_LIMIT) { hideHotspotModal(); showHotspotLimitNotice(); return; } if (!text) { alert('Please enter hotspot text'); return; } if (!pendingHotspotCoords) { alert('Error: No coordinates available'); return; } // Create hotspot object const hotspot = { pitch: pendingHotspotCoords.pitch, yaw: pendingHotspotCoords.yaw, type: "info", text: text }; // Add to hotspots array hotspots.push(hotspot); // Reinitialize viewer with updated hotspots initPanoramaViewer(); // Update hotspot list renderHotspotList(); // Hide modal hideHotspotModal(); } // Render hotspot list below panorama with remove buttons function renderHotspotList() { const $list = $('#hotspot-list'); if (!$list.length) return; $list.empty(); if (hotspots.length === 0) { return; } const $label = $('Hotspot
'); const $items = $('