PluginProbe
WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress / 8.5.79
WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress v8.5.79
9.1.3 9.1.2 9.1.1 9.1.0 9.0.3 9.0.2 9.0.1 9.0.0 8.5.79 8.5.78 8.5.77 8.5.76 8.5.75 8.5.74 8.5.73 8.5.72 8.5.71 8.5.70 8.5.69 8.5.68 8.5.35 8.5.36 8.5.37 8.5.38 8.5.39 All 222 releases
wpvr / admin / js / wpvr-tour-guide.js

wpvr-tour-guide.js in WPVR – 360 Panorama viewer and Virtual Tour Builder for WordPress 8.5.79, at admin/js/wpvr-tour-guide.js

617 lines 36.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function( $ ) {
2 'use strict';
3
4 $(document).ready(function(){
5
6 var main_tour = new Shepherd.Tour();
7 var guide_tranlation = window.wpvr_tour_guide_obj.Tour_Guide_Translation;
8
9 // ── Styles ───────────────────────────────────────────────────────────
10 var styleEl = document.createElement('style');
11 styleEl.innerHTML = `
12 /* ─── Modal ────────────────────────────────────────────────────── */
13 .shepherd-element .shepherd-content {
14 border-radius: 16px !important;
15 background: #F6F4FF !important;
16 box-shadow: 0 12px 48px rgba(63,4,254,.20) !important;
17 min-width: 400px;
18 max-width: 480px;
19 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
20 }
21 .shepherd-background-step-1 .shepherd-content {
22 background-image: url('` + window.wpvr_tour_guide_obj.step1_bg_image + `') !important;
23 background-position: center !important;
24 background-repeat: no-repeat !important;
25 }
26 .shepherd-cancel-icon { display: none !important; }
27 .shepherd-header { padding: 22px 22px 6px; }
28 .shepherd-text { padding: 6px 22px 14px; }
29
30 /* ─── Typography ───────────────────────────────────────────────── */
31 p.wpvr-guided-tour-title {
32 font-weight: 700; font-size: 20px; line-height: 1.25;
33 text-align: center; color: #3F04FE; margin: 0 0 10px;
34 }
35 p.wpvr-guided-tour-title-left {
36 font-weight: 700; font-size: 18px; line-height: 1.25;
37 color: #3F04FE; margin: 0 0 10px;
38 }
39 p.wpvr-guided-tour-description {
40 font-weight: 400; font-size: 14px; line-height: 1.65;
41 text-align: center; color: #4b5563; margin: 0;
42 }
43 p.wpvr-guided-tour-description strong,
44 p.wpvr-guided-tour-description span { color: #3F04FE; font-weight: 600; }
45
46 /* ─── Instruction lists ─────────────────────────────────────────── */
47 .hotspot-instructions { text-align: left; }
48 .hotspot-intro { color:#4b5563; font-size:14px; line-height:1.65; margin-bottom:10px; }
49 .hotspot-steps { padding-left:18px; margin:0; color:#4b5563; font-size:14px; line-height:1.9; }
50 .hotspot-steps strong { color: #3F04FE; }
51 .help-icon {
52 display:inline-flex; align-items:center; justify-content:center;
53 width:15px; height:15px; background:#3F04FE; color:#fff;
54 border-radius:50%; font-size:10px; font-weight:700; cursor:help; vertical-align:middle;
55 }
56
57 /* ─── Progress bar ──────────────────────────────────────────────── */
58 .wpvr-tour-progress {
59 height:4px; background:#e5e7eb; border-radius:99px;
60 margin:0 22px 14px; overflow:hidden;
61 }
62 .wpvr-tour-progress-fill {
63 height:100%; background:linear-gradient(90deg,#3F04FE,#7c3aed);
64 border-radius:99px; transition:width .35s ease;
65 }
66
67 /* ─── Footer ────────────────────────────────────────────────────── */
68 .shepherd-footer { padding: 0 22px 22px; }
69 .shepherd-buttons {
70 display:flex; align-items:center; list-style:none; padding:0; margin:0;
71 }
72 .shepherd-buttons li { flex: 0 0 auto; }
73 .shepherd-buttons li.wpvr-counter-li { flex:1; text-align:center; }
74 .wpvr-step-counter {
75 font-size:12px; font-weight:600; color:#9ca3af; letter-spacing:0.4px;
76 }
77
78 /* ─── Skip Tour — text link ─────────────────────────────────────── */
79 a.shepherd-button.wpvr-btn-skip-tour {
80 background:none !important; border:none !important; box-shadow:none !important;
81 color:#9ca3af !important; font-size:13px !important; font-weight:500 !important;
82 padding:0 !important; cursor:pointer !important;
83 text-decoration:underline !important; text-underline-offset:2px !important;
84 line-height:1 !important; display:inline-flex !important; align-items:center !important;
85 }
86 a.shepherd-button.wpvr-btn-skip-tour:hover { color:#6b7280 !important; }
87
88 /* ─── Skip Step — ghost ─────────────────────────────────────────── */
89 a.shepherd-button.wpvr-btn-skip-step {
90 background:transparent !important; border:1.5px solid #d1d5db !important;
91 border-radius:8px !important; color:#374151 !important;
92 font-size:13px !important; font-weight:500 !important;
93 padding:6px 16px !important; cursor:pointer !important;
94 line-height:1.4 !important; margin-right:8px !important;
95 display:inline-flex !important; align-items:center !important;
96 }
97 a.shepherd-button.wpvr-btn-skip-step:hover {
98 border-color:#9ca3af !important; color:#111827 !important;
99 }
100
101 /* ─── Next — solid primary ──────────────────────────────────────── */
102 a.shepherd-button.wpvr-btn-next {
103 background:#3F04FE !important; border:2px solid #3F04FE !important;
104 border-radius:8px !important; color:#fff !important;
105 font-size:13px !important; font-weight:600 !important;
106 padding:6px 18px !important; cursor:pointer !important;
107 line-height:1.4 !important; display:inline-flex !important;
108 align-items:center !important; gap:6px !important; text-decoration:none !important;
109 }
110 a.shepherd-button.wpvr-btn-next:hover {
111 background:#2b00cc !important; border-color:#2b00cc !important;
112 }
113
114 /* ─── Next — DISABLED state ─────────────────────────────────────── */
115 a.shepherd-button.wpvr-btn-next.wpvr-btn-next--disabled {
116 background:#c4b5fd !important; border-color:#c4b5fd !important;
117 color:#fff !important; cursor:not-allowed !important;
118 pointer-events:none !important; opacity:.7 !important;
119 }
120
121 .wpvr-next-arrow svg { display:block; }
122 `;
123 document.head.appendChild(styleEl);
124
125 // ── Tour defaults ────────────────────────────────────────────────────
126 main_tour.options.defaults = {
127 classes: 'shepherd-theme-arrows-plain-buttons shepherd-main-tour shadow-md',
128 showCancelLink: false,
129 useModalOverlay: true,
130 scrollTo: true,
131 tetherOptions: {
132 constraints: [{ to: 'scrollParent', attachment: 'together', pin: false }]
133 }
134 };
135
136 // ── Strings ──────────────────────────────────────────────────────────
137 var nextTxt = guide_tranlation.next_button_text;
138 var skipTourTxt = guide_tranlation.end_tour || 'Skip Tour';
139 var skipStepTxt = 'Skip';
140 var doneTxt = guide_tranlation.done_text || 'Done';
141
142 var arrowSvg = '<span class="wpvr-next-arrow"><svg width="13" height="11" viewBox="0 0 13 11" fill="none" xmlns="http://www.w3.org/2000/svg">' +
143 '<path d="M7.5 9.5L11.5 5.5L7.5 1.5" stroke="white" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>' +
144 '<path d="M1.5 5.5H11.5" stroke="white" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span>';
145
146 // ── Footer builder ───────────────────────────────────────────────────
147 function buildFooter(stepEl, currentStep, totalSteps) {
148 var footer = stepEl.querySelector('footer');
149 if (!footer) return;
150
151 var existingBar = stepEl.querySelector('.wpvr-tour-progress');
152 if (existingBar) existingBar.remove();
153 var barEl = document.createElement('div');
154 barEl.className = 'wpvr-tour-progress';
155 barEl.innerHTML = '<div class="wpvr-tour-progress-fill" style="width:' +
156 Math.round((currentStep / totalSteps) * 100) + '%"></div>';
157 footer.parentNode.insertBefore(barEl, footer);
158
159 var buttonsList = footer.querySelector('.shepherd-buttons');
160 if (!buttonsList) return;
161 var existingCounter = buttonsList.querySelector('.wpvr-counter-li');
162 if (existingCounter) existingCounter.remove();
163 var counterLi = document.createElement('li');
164 counterLi.className = 'wpvr-counter-li';
165 counterLi.innerHTML = '<span class="wpvr-step-counter">' + currentStep + ' / ' + totalSteps + '</span>';
166 var firstBtn = buttonsList.querySelector('li');
167 if (firstBtn && firstBtn.nextSibling) {
168 buttonsList.insertBefore(counterLi, firstBtn.nextSibling);
169 } else {
170 buttonsList.appendChild(counterLi);
171 }
172
173 var nextBtn = buttonsList.querySelector('.wpvr-btn-next');
174 if (nextBtn && !nextBtn.querySelector('.wpvr-next-arrow')) {
175 nextBtn.insertAdjacentHTML('beforeend', arrowSvg);
176 }
177 }
178
179 // ── Disable / enable Next ────────────────────────────────────────────
180 function disableNext(stepEl) {
181 var btn = stepEl && stepEl.querySelector('a.wpvr-btn-next');
182 if (btn) btn.classList.add('wpvr-btn-next--disabled');
183 }
184 function enableNext(stepEl) {
185 var btn = stepEl && stepEl.querySelector('a.wpvr-btn-next');
186 if (btn) btn.classList.remove('wpvr-btn-next--disabled');
187 }
188
189 /**
190 * Poll checkFn every 400 ms; call onMet() once when it returns true.
191 * Automatically stops when the step element leaves the DOM (user skipped).
192 */
193 function watchCondition(stepEl, checkFn, onMet) {
194 if (checkFn()) { onMet(); return; }
195 var timer = setInterval(function() {
196 if (!stepEl.parentNode || main_tour.canceled) {
197 clearInterval(timer); return;
198 }
199 if (checkFn()) { clearInterval(timer); onMet(); }
200 }, 400);
201 }
202
203 // ── Show handler with disabled Next + watcher ────────────────────────
204 function onShowWithCondition(checkFn) {
205 return function() {
206 scroll_to_popup();
207 var idx = main_tour.steps.indexOf(main_tour.getCurrentStep()) + 1;
208 var stepEl = this.el;
209 buildFooter(stepEl, idx, main_tour.steps.length);
210 disableNext(stepEl);
211 watchCondition(stepEl, checkFn, function() { enableNext(stepEl); });
212 };
213 }
214
215 // ── Button factories ─────────────────────────────────────────────────
216 function btnSkipTour() {
217 return { classes: 'wpvr-btn-skip-tour', text: skipTourTxt, action: main_tour.cancel };
218 }
219 function btnSkipStep() {
220 return { classes: 'wpvr-btn-skip-step', text: skipStepTxt, action: main_tour.next };
221 }
222 function btnNext(action) {
223 return { classes: 'wpvr-btn-next', text: nextTxt, action: action || main_tour.next };
224 }
225 function btnDone() {
226 return { classes: 'wpvr-btn-next', text: doneTxt, action: function() { main_tour.complete(); } };
227 }
228
229 // Generic show handler (no required action — Next always enabled)
230 function onShow() {
231 return function() {
232 scroll_to_popup();
233 var idx = main_tour.steps.indexOf(main_tour.getCurrentStep()) + 1;
234 buildFooter(this.el, idx, main_tour.steps.length);
235 };
236 }
237
238 // ════════════════════════════════════════════════════════════════════
239 // STEP 1 — Name Your Tour
240 // Next disabled until #title has text
241 // ════════════════════════════════════════════════════════════════════
242 main_tour.addStep('tour_title', {
243 title: '',
244 text: "<p class='wpvr-guided-tour-title'>Name Your Tour</p>" +
245 "<p class='wpvr-guided-tour-description'>Give your virtual tour a memorable title. This helps you find it later in your tour list.</p>",
246 attachTo: '#post-body-content bottom',
247 classes: 'shepherd-theme-arrows-plain-buttons shepherd-main-tour super-index',
248 buttons: [ btnSkipTour(), btnSkipStep(), btnNext() ],
249 when: {
250 show: function() {
251 scroll_to_popup();
252 var idx = main_tour.steps.indexOf(main_tour.getCurrentStep()) + 1;
253 var stepEl = this.el;
254 buildFooter(stepEl, idx, main_tour.steps.length);
255
256 disableNext(stepEl);
257 var titleEl = document.getElementById('title');
258 function check() { return titleEl && titleEl.value.trim().length > 0; }
259 watchCondition(stepEl, check, function() { enableNext(stepEl); });
260
261 if (titleEl) {
262 $(titleEl).on('input.wpvr_tour', function() {
263 if (check()) { enableNext(stepEl); $(titleEl).off('input.wpvr_tour'); }
264 });
265 }
266 }
267 }
268 });
269
270 // ════════════════════════════════════════════════════════════════════
271 // STEP 2 — Great! (confirmation)
272 // ════════════════════════════════════════════════════════════════════
273 main_tour.addStep('tour_title_given', {
274 title: '',
275 text: "<p class='wpvr-guided-tour-title'>Great!</p>" +
276 "<p class='wpvr-guided-tour-description'>Your tour has a name. You're ready to start building your first scene!</p>",
277 attachTo: '#post-body-content bottom',
278 classes: 'shepherd-theme-arrows-plain-buttons shepherd-main-tour super-index shepherd-background-step-1',
279 buttons: [ btnSkipTour(), btnSkipStep(), btnNext() ],
280 when: { show: onShow() }
281 });
282
283 // ════════════════════════════════════════════════════════════════════
284 // STEP 3 — Upload Scene
285 // Next disabled until .scene-attachment-url has a value
286 // ════════════════════════════════════════════════════════════════════
287 main_tour.addStep('upload_image', {
288 title: '',
289 text: "<p class='wpvr-guided-tour-title'>Add Your First Scene</p>" +
290 "<p class='wpvr-guided-tour-description'>Click the upload area and choose your panorama image. Scene IDs are auto-generated but you can customise them (e.g. <strong>S1</strong>).</p>",
291 classes: 'shepherd-theme-arrows-plain-buttons shepherd-main-tour super-index',
292 attachTo: '.single-scene.rex-pano-tab.active .scene-upload right',
293 buttons: [ btnSkipTour(), btnSkipStep(), btnNext() ],
294 when: {
295 show: onShowWithCondition(function() {
296 var inp = document.querySelector('.single-scene.rex-pano-tab.active .scene-attachment-url');
297 return inp && inp.value.trim() !== '';
298 })
299 }
300 });
301
302 // Auto-resume after media library upload completes
303 $('.single-scene.rex-pano-tab.active .scene-upload').on('click', function() {
304 main_tour.hide();
305 var $wrap = $(this).closest('.single-scene.rex-pano-tab.active');
306 var $urlInput = $wrap.find('.scene-attachment-url');
307 var initialValue = $urlInput.val();
308
309 var resumeTimer = setInterval(function() {
310 if (main_tour.canceled) { clearInterval(resumeTimer); return; }
311 var val = $urlInput.val();
312 if (val && val.trim() !== '' && val !== initialValue) {
313 clearInterval(resumeTimer);
314 $wrap.find('.wpvr_continue_guide').remove();
315 main_tour.show('upload_image_complete');
316 $('body').addClass('shepherd-active');
317 }
318 }, 500);
319 setTimeout(function() { clearInterval(resumeTimer); }, 600000);
320
321 if ($wrap.find('.wpvr_continue_guide').length === 0 && !main_tour.canceled) {
322 $wrap.append('<span class="wpvr_continue_guide">Continue guide</span>');
323 }
324 });
325 $(document).on('click', 'span.wpvr_continue_guide', function() {
326 $(this).remove();
327 main_tour.show('upload_image_complete');
328 $('body').addClass('shepherd-active');
329 });
330
331 // ════════════════════════════════════════════════════════════════════
332 // STEP 4 — Awesome! Scene ready (confirmation)
333 // ════════════════════════════════════════════════════════════════════
334 main_tour.addStep('upload_image_complete', {
335 title: '',
336 text: "<p class='wpvr-guided-tour-title'>Awesome!</p>" +
337 "<p class='wpvr-guided-tour-description'>Your first scene is ready — and it's already previewing in 360°! Now let's make your tour interactive with a hotspot.</p>",
338 classes: 'shepherd-theme-arrows-plain-buttons shepherd-main-tour super-index shepherd-background-step-1',
339 attachTo: '.single-scene.rex-pano-tab.active .scene-upload right',
340 buttons: [ btnSkipTour(), btnSkipStep(), btnNext() ],
341 when: { show: onShow() }
342 });
343
344 // ════════════════════════════════════════════════════════════════════
345 // STEP 5 — Open the Hotspot tab
346 // Next disabled until user clicks the hotspot tab (li.hotspot.active)
347 // ════════════════════════════════════════════════════════════════════
348 main_tour.addStep('navigate_hotspot_tab', {
349 title: '',
350 text: "<p class='wpvr-guided-tour-title'>Open the Hotspot Tab</p>" +
351 "<p class='wpvr-guided-tour-description'>Click the <strong>Hotspot</strong> tab in the panel to enter the hotspot editor.</p>",
352 classes: 'shepherd-theme-arrows-plain-buttons shepherd-main-tour super-index',
353 attachTo: '.rex-pano-nav-menu.main-nav ul li.hotspot bottom',
354 buttons: [ btnSkipTour(), btnSkipStep(), btnNext() ],
355 when: {
356 show: onShowWithCondition(function() {
357 return document.querySelector('.rex-pano-nav-menu.main-nav ul li.hotspot.active') !== null;
358 })
359 }
360 });
361
362 // ════════════════════════════════════════════════════════════════════
363 // STEP 6 — Hotspot instructions
364 // ════════════════════════════════════════════════════════════════════
365 main_tour.addStep('add_hotspot_section', {
366 title: '',
367 text: "<p class='wpvr-guided-tour-title-left'>Add Your First Hotspot</p>" +
368 "<div class='hotspot-instructions'>" +
369 "<p class='hotspot-intro'>Hotspots make your tour interactive — show text, images, videos or links when visitors click them.</p>" +
370 "<ol class='hotspot-steps'>" +
371 "<li><strong>Drag</strong> the panorama to find the exact spot.</li>" +
372 "<li>Click the <strong><i class='fas fa-plus-circle' style='color:#3F04FE;'></i> add-pitch icon</strong> to lock in the coordinates.</li>" +
373 "<li>Set <strong>click</strong> and <strong>hover</strong> content.</li>" +
374 "</ol></div>",
375 classes: 'shepherd-theme-arrows-plain-buttons shepherd-main-tour super-index',
376 attachTo: '#wpvr_item_builder__box left',
377 buttons: [ btnSkipTour(), btnSkipStep(), btnNext() ],
378 when: {
379 show: function() {
380 scroll_to_popup();
381 var idx = main_tour.steps.indexOf(main_tour.getCurrentStep()) + 1;
382 buildFooter(this.el, idx, main_tour.steps.length);
383 // Ensure hotspot tab is active
384 setTimeout(function() {
385 var tab = document.querySelector('.rex-pano-nav-menu.main-nav ul li.hotspot span');
386 if (tab) tab.click();
387 }, 150);
388 }
389 }
390 });
391
392 // ════════════════════════════════════════════════════════════════════
393 // STEP 7 — Drag panorama to find the spot
394 // Next disabled until #panodata contains Pitch: and Yaw: values
395 // ════════════════════════════════════════════════════════════════════
396 main_tour.addStep('drag_to_spot', {
397 title: '',
398 text: "<p class='wpvr-guided-tour-title'>Find Your Perfect Spot</p>" +
399 "<p class='wpvr-guided-tour-description'>" +
400 "<strong>Click and drag</strong> inside the 360° preview to look around. " +
401 "Once the panorama shows where you want to place the hotspot, <strong>Next</strong> will unlock." +
402 "</p>",
403 classes: 'shepherd-theme-arrows-plain-buttons shepherd-main-tour super-index',
404 attachTo: '#wpvr_item_builder__box left',
405 buttons: [ btnSkipTour(), btnSkipStep(), btnNext() ],
406 when: {
407 show: onShowWithCondition(function() {
408 var panodata = document.getElementById('panodata');
409 if (!panodata) return false;
410 var text = panodata.textContent || '';
411 return text.indexOf('Pitch:') !== -1 && text.indexOf('Yaw:') !== -1;
412 })
413 }
414 });
415
416 // ════════════════════════════════════════════════════════════════════
417 // STEP 8 — Click the add-pitch icon (.toppitch)
418 // Next DISABLED until input.hotspot-pitch has a non-empty value.
419 // Attached to .add-pitch so Shepherd cuts the overlay hole there.
420 // ════════════════════════════════════════════════════════════════════
421 main_tour.addStep('set_hotspot_pitch', {
422 title: '',
423 text: "<p class='wpvr-guided-tour-title'>Set Hotspot Coordinates</p>" +
424 "<p class='wpvr-guided-tour-description'>" +
425 "Click the <strong><i class='fas fa-plus-circle' style='color:#3F04FE;'></i> blue plus icon</strong> " +
426 "next to the coordinates in the panorama. " +
427 "It will instantly capture the <strong>Pitch &amp; Yaw</strong> for your hotspot. " +
428 "Next will unlock automatically once they are set." +
429 "</p>",
430 classes: 'shepherd-theme-arrows-plain-buttons shepherd-main-tour super-index',
431 attachTo: '.add-pitch left',
432 tetherOptions: {
433 offset: '0 50px',
434 constraints: [{ to: 'scrollParent', attachment: 'together', pin: false }]
435 },
436 buttons: [ btnSkipTour(), btnSkipStep(), btnNext() ],
437 when: {
438 show: onShowWithCondition(function() {
439 var pitchInput = document.querySelector('#scene-1-hotspot-1 input.hotspot-pitch');
440 return pitchInput && pitchInput.value.trim() !== '';
441 })
442 }
443 });
444
445 // ════════════════════════════════════════════════════════════════════
446 // STEP 9 — Set Hotspot Content (click + hover in one step)
447 // Next disabled until any of: URL, on-click content, or on-hover
448 // content has a value.
449 // ════════════════════════════════════════════════════════════════════
450 main_tour.addStep('on_click_content_info', {
451 title: '',
452 text: "<p class='wpvr-guided-tour-title-left'>Set Hotspot Content</p>" +
453 "<p class='wpvr-guided-tour-description' style='text-align:left;'>" +
454 "Fill in what visitors see when interacting with the hotspot:<br><br>" +
455 "• <strong>URL</strong> — link to an external page<br>" +
456 "• <strong>On Click Content</strong> — text, images, or video shown on click<br>" +
457 "• <strong>On Hover Content</strong> — tooltip preview on hover<br><br>" +
458 "Fill in at least one field, then proceed." +
459 "</p>",
460 classes: 'shepherd-theme-arrows-plain-buttons shepherd-main-tour super-index',
461 attachTo: '#scene-1-hotspot-1 .hotspot-type.hotspot-setting:not(.hotspot-hover) top',
462 buttons: [ btnSkipTour(), btnSkipStep(), btnNext() ],
463 when: {
464 show: onShowWithCondition(function() {
465 var container = document.getElementById('scene-1-hotspot-1');
466 if (!container) return false;
467
468 // Check URL input
469 var urlInput = container.querySelector('input[name*="hotspot-url"]');
470 if (urlInput && urlInput.value.trim() !== '') return true;
471
472 // Check on-click content textarea (may be summernote)
473 var clickTextarea = container.querySelector('.hotspot-content textarea');
474 if (clickTextarea && clickTextarea.value.trim() !== '') return true;
475
476 // Check summernote rich editor for on-click content
477 var clickEditor = container.querySelector('.hotspot-content .note-editable');
478 if (clickEditor) {
479 var html = clickEditor.innerHTML.trim();
480 if (html !== '' && html !== '<br>' && html !== '<p><br></p>') return true;
481 }
482
483 // Check on-hover content textarea
484 var hoverTextarea = container.querySelector('.hotspot-hover textarea');
485 if (hoverTextarea && hoverTextarea.value.trim() !== '') return true;
486
487 // Check summernote rich editor for on-hover content
488 var hoverEditor = container.querySelector('.hotspot-hover .note-editable');
489 if (hoverEditor) {
490 var html2 = hoverEditor.innerHTML.trim();
491 if (html2 !== '' && html2 !== '<br>' && html2 !== '<p><br></p>') return true;
492 }
493
494 return false;
495 })
496 }
497 });
498
499 // ════════════════════════════════════════════════════════════════════
500 // STEP 10 — Hotspot done (confirmation)
501 // ════════════════════════════════════════════════════════════════════
502 main_tour.addStep('add_hotspot_section_completed', {
503 title: '',
504 text: "<p class='wpvr-guided-tour-title'>Nice!</p>" +
505 "<p class='wpvr-guided-tour-description'>You added your first interactive hotspot. Your tour is now engaging and ready to publish!</p>",
506 classes: 'shepherd-theme-arrows-plain-buttons shepherd-main-tour super-index shepherd-background-step-1',
507 attachTo: '#wpvr_item_builder__box left',
508 buttons: [ btnSkipTour(), btnSkipStep(), btnNext() ],
509 when: { show: onShow() }
510 });
511
512 // ════════════════════════════════════════════════════════════════════
513 // STEP 11 — Publish & Embed
514 // Next disabled until the Publish button is clicked (post is saved)
515 // Detected by checking if the URL changes to include "post=..." (new
516 // post gets a real ID) or if #original_post_status becomes "publish".
517 // ════════════════════════════════════════════════════════════════════
518 main_tour.addStep('publish_tour', {
519 title: '',
520 text: "<p class='wpvr-guided-tour-title-left'>Publish &amp; Embed</p>" +
521 "<div class='hotspot-instructions'>" +
522 "<p class='hotspot-intro'>Click <strong>Publish</strong> to make your tour live, then embed it anywhere:</p>" +
523 "<ol class='hotspot-steps'>" +
524 "<li>Shortcode</li>" +
525 "<li>WPVR Gutenberg block</li>" +
526 "<li><strong>Supported builders:</strong> Elementor, Divi, Oxygen, Visual Composer, Bricks</li>" +
527 "</ol></div>",
528 classes: 'shepherd-theme-arrows-plain-buttons shepherd-main-tour super-index',
529 attachTo: '#publish left',
530 buttons: [ btnSkipTour(), btnSkipStep(), btnNext() ],
531 when: {
532 show: onShowWithCondition(function() {
533 // Check if the post status has changed to "publish"
534 var statusEl = document.getElementById('original_post_status');
535 if (statusEl && statusEl.value === 'publish') return true;
536
537 // Also check if the URL now has a post= param (meaning it was saved)
538 if (window.location.href.indexOf('post=') !== -1 &&
539 window.location.href.indexOf('action=edit') !== -1) return true;
540
541 return false;
542 })
543 }
544 });
545
546 // ════════════════════════════════════════════════════════════════════
547 // STEP 12 — Congratulations
548 // ════════════════════════════════════════════════════════════════════
549 main_tour.addStep('completed_guided_tour', {
550 title: '',
551 text: "<p class='wpvr-guided-tour-title'>Congratulations! 🎉</p>" +
552 "<p class='wpvr-guided-tour-description'>Your first virtual tour is live and interactive. Visitors can now explore it in 360°!</p>",
553 classes: 'shepherd-theme-arrows-plain-buttons shepherd-main-tour super-index shepherd-background-step-1',
554 attachTo: '#publish left',
555 buttons: [ btnSkipTour(), btnDone() ],
556 when: { show: onShow() }
557 });
558
559 // ── Scroll helper ────────────────────────────────────────────────────
560 function scroll_to_popup(step) {
561 main_tour.going_somewhere = false;
562 if (!step) step = main_tour.getCurrentStep();
563 var popup = $(step.el);
564 $('body, html').animate({ scrollTop: popup.offset().top - 50 }, 500, function() {
565 window.scrollTo(0, popup.offset().top - 50);
566 });
567 }
568
569 // ── Start ────────────────────────────────────────────────────────────
570 var isAutoStart = window.wpvr_new_user_tour && window.wpvr_new_user_tour.autoStart;
571 if (isAutoStart || getParameterByName('wpvr-guide-tour') === '1') {
572 main_tour.start();
573 }
574
575 main_tour.on('cancel', cancel_tour);
576 main_tour.on('cancel', function() { if (isAutoStart) wpvr_dismiss_new_user_tour(); });
577 main_tour.on('complete', function() { if (isAutoStart) wpvr_dismiss_new_user_tour(); });
578
579 function wpvr_dismiss_new_user_tour() {
580 if (!window.wpvr_new_user_tour) return;
581 $.post(window.wpvr_new_user_tour.ajaxUrl, {
582 action: window.wpvr_new_user_tour.action,
583 nonce: window.wpvr_new_user_tour.nonce
584 });
585 }
586
587 function cancel_tour() {
588 main_tour.canceled = true;
589 var param = getParameterByName('wpvr-guide-tour');
590 if (param === '1') {
591 var newUrl = updateParam('wpvr-guide-tour', 0);
592 if (window.history && window.history.pushState) {
593 window.history.pushState({ path: newUrl }, '', newUrl);
594 }
595 }
596 }
597
598 function getParameterByName(name, url) {
599 url = url || window.location.href;
600 name = name.replace(/[\[\]]/g, '\\$&');
601 var rx = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
602 var results = rx.exec(url);
603 if (!results) return null;
604 if (!results[2]) return '';
605 return decodeURIComponent(results[2].replace(/\+/g, ' '));
606 }
607
608 function updateParam(name, value, url) {
609 url = url || window.location.href;
610 var u = new URL(url);
611 u.searchParams.delete(name);
612 return u.toString();
613 }
614
615 });
616
617 })( jQuery );