1/* global document, localStorage, matchMedia, window, URLSearchParams, MutationObserver */ 2( function () { 3 'use strict'; 4 5 var modes = [ 'dark', 'light', 'auto' ]; 6 var labels = { 7 dark: 'Dark mode', 8 light: 'Light mode', 9 auto: 'Auto mode' 10 }; 11 var storageKey = 'waafaa-theme-mode'; 12 var mediaQuery = matchMedia( '(prefers-color-scheme: light)' ); 13 14 function getSystemTheme() { 15 return mediaQuery.matches ? 'light' : 'dark'; 16 } 17 18 function resolveTheme( mode ) { 19 return 'auto' === mode ? getSystemTheme() : mode; 20 } 21 22 function applyMode( button, mode ) { 23 var resolvedTheme = resolveTheme( mode ); 24 25 document.documentElement.dataset.themeMode = mode; 26 document.documentElement.dataset.theme = resolvedTheme; 27 28 if ( button ) { 29 button.dataset.mode = mode; 30 button.title = labels[ mode ]; 31 button.setAttribute( 'aria-label', 'Switch color mode. Current mode: ' + labels[ mode ] ); 32 } 33 34 try { 35 localStorage.setItem( storageKey, mode ); 36 } catch ( error ) { 37 return; 38 } 39 } 40 41 function getStoredMode() { 42 var storedMode = 'dark'; 43 44 try { 45 storedMode = localStorage.getItem( storageKey ) || 'dark'; 46 } catch ( error ) { 47 storedMode = 'dark'; 48 } 49 50 return modes.indexOf( storedMode ) >= 0 ? storedMode : 'dark'; 51 } 52 53 function enhanceModeButton( button ) { 54 if ( ! button || 'true' === button.dataset.waafaaModeReady ) { 55 return; 56 } 57 58 button.dataset.waafaaModeReady = 'true'; 59 button.classList.add( 'waafaa-mode-toggle' ); 60 button.setAttribute( 'role', 'button' ); 61 button.setAttribute( 'tabindex', '0' ); 62 button.innerHTML = '<svg class="waafaa-mode-toggle__icon waafaa-mode-toggle__icon--light" aria-hidden="true" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4.25"></circle><path d="M12 2.75v2.5M12 18.75v2.5M5.64 5.64l1.77 1.77M16.59 16.59l1.77 1.77M2.75 12h2.5M18.75 12h2.5M5.64 18.36l1.77-1.77M16.59 7.41l1.77-1.77"></path></svg><svg class="waafaa-mode-toggle__icon waafaa-mode-toggle__icon--dark" aria-hidden="true" viewBox="0 0 24 24"><path d="M20.25 14.35A7.9 7.9 0 0 1 9.65 3.75 8.75 8.75 0 1 0 20.25 14.35Z"></path></svg><svg class="waafaa-mode-toggle__icon waafaa-mode-toggle__icon--auto" aria-hidden="true" viewBox="0 0 24 24"><rect x="4" y="4.5" width="16" height="10.5" rx="2.2"></rect><path d="M9 20h6M12 15v5"></path></svg><span class="screen-reader-text">Switch color mode</span>'; 63 } 64 65 function initTestimonials() { 66 var sliders = document.querySelectorAll( '.waafaa-home-testimonials__slider' ); 67 68 sliders.forEach( function ( slider ) { 69 var track = slider.querySelector( '.waafaa-home-testimonials__cards' ); 70 var originalSlides = track ? Array.prototype.slice.call( track.children ) : []; 71 var pagination = document.createElement( 'div' ); 72 var index = 0; 73 var perView = 1; 74 var timer = null; 75 var gap = 20; 76 var clones = []; 77 var isDragging = false; 78 var pointerId = null; 79 var startX = 0; 80 var dragDelta = 0; 81 82 if ( ! track || originalSlides.length < 2 || 'true' === slider.dataset.waafaaSliderReady ) { 83 return; 84 } 85 86 slider.dataset.waafaaSliderReady = 'true'; 87 slider.classList.add( 'is-ready' ); 88 pagination.className = 'waafaa-home-testimonials__pagination'; 89 slider.appendChild( pagination ); 90 91 function stop() { 92 if ( timer ) { 93 window.clearInterval( timer ); 94 timer = null; 95 } 96 } 97 98 function start() { 99 if ( isDragging ) { 100 return; 101 } 102 103 stop(); 104 timer = window.setInterval( function () { 105 goTo( index + 1 ); 106 }, 3200 ); 107 } 108 109 function getPerView() { 110 return window.matchMedia( '(min-width: 768px)' ).matches ? 2 : 1; 111 } 112 113 function getStep() { 114 var firstSlide = track.children[ 0 ]; 115 116 if ( ! firstSlide ) { 117 return 0; 118 } 119 120 gap = parseFloat( window.getComputedStyle( track ).columnGap ) || 20; 121 return firstSlide.getBoundingClientRect().width + gap; 122 } 123 124 function updateDots() { 125 Array.prototype.forEach.call( pagination.children, function ( dot, dotIndex ) { 126 dot.classList.toggle( 'is-active', dotIndex === ( index % originalSlides.length ) ); 127 dot.setAttribute( 'aria-pressed', dotIndex === ( index % originalSlides.length ) ? 'true' : 'false' ); 128 } ); 129 } 130 131 function renderDots() { 132 pagination.innerHTML = '';
133 originalSlides.forEach( function ( slide, slideIndex ) { 134 var dot = document.createElement( 'button' ); 135 136 dot.className = 'waafaa-home-testimonials__dot'; 137 dot.type = 'button'; 138 dot.setAttribute( 'aria-label', 'Show testimonial ' + ( slideIndex + 1 ) ); 139 dot.addEventListener( 'click', function () { 140 goTo( slideIndex ); 141 start(); 142 } ); 143 pagination.appendChild( dot ); 144 } ); 145 } 146 147 function setSlideWidths() { 148 var availableWidth = slider.getBoundingClientRect().width - ( ( perView - 1 ) * gap ); 149 var slideWidth = Math.max( 0, availableWidth / perView ); 150 151 Array.prototype.forEach.call( track.children, function ( slide ) { 152 slide.style.flexBasis = slideWidth + 'px'; 153 } ); 154 } 155 156 function translate( animate ) { 157 track.style.transitionDuration = animate ? '800ms' : '0ms'; 158 track.style.transform = 'translate3d(-' + ( getStep() * index ) + 'px, 0, 0)'; 159 updateDots(); 160 } 161 162 function goTo( nextIndex ) { 163 index = nextIndex; 164 translate( true ); 165 } 166 167 function dragTo( delta ) { 168 var offset = ( getStep() * index ) - delta; 169 170 track.style.transitionDuration = '0ms'; 171 track.style.transform = 'translate3d(-' + offset + 'px, 0, 0)'; 172 } 173 174 function onPointerDown( event ) { 175 if ( event.target.closest( 'button' ) ) { 176 return; 177 } 178 179 isDragging = true; 180 pointerId = event.pointerId; 181 startX = event.clientX; 182 dragDelta = 0; 183 slider.classList.add( 'is-dragging' ); 184 stop(); 185 186 if ( track.setPointerCapture ) { 187 track.setPointerCapture( pointerId ); 188 } 189 } 190 191 function onPointerMove( event ) { 192 if ( ! isDragging || event.pointerId !== pointerId ) { 193 return; 194 } 195 196 dragDelta = event.clientX - startX; 197 198 if ( Math.abs( dragDelta ) > 6 ) { 199 event.preventDefault(); 200 } 201 202 dragTo( dragDelta ); 203 } 204 205 function onPointerUp( event ) { 206 var step = getStep(); 207 var threshold = Math.min( 90, step * 0.18 ); 208 209 if ( ! isDragging || event.pointerId !== pointerId ) { 210 return; 211 } 212 213 isDragging = false; 214 slider.classList.remove( 'is-dragging' ); 215 216 if ( track.releasePointerCapture ) { 217 track.releasePointerCapture( pointerId ); 218 } 219 220 pointerId = null; 221 222 if ( Math.abs( dragDelta ) > threshold ) { 223 goTo( dragDelta < 0 ? index + 1 : ( index > 0 ? index - 1 : originalSlides.length - 1 ) ); 224 } else { 225 translate( true ); 226 } 227 228 start(); 229 } 230 231 function rebuild() { 232 clones.forEach( function ( clone ) { 233 clone.remove(); 234 } ); 235 clones = []; 236 perView = getPerView(); 237 gap = parseFloat( window.getComputedStyle( track ).columnGap ) || 20; 238 originalSlides.slice( 0, perView ).forEach( function ( slide ) { 239 var clone = slide.cloneNode( true ); 240 241 clone.setAttribute( 'aria-hidden', 'true' ); 242 clone.classList.add( 'waafaa-home-quote--clone' ); 243 track.appendChild( clone ); 244 clones.push( clone ); 245 } ); 246 index = index % originalSlides.length; 247 setSlideWidths(); 248 renderDots(); 249 translate( false ); 250 } 251 252 track.addEventListener( 'transitionend', function () { 253 if ( index >= originalSlides.length ) { 254 index = 0; 255 translate( false ); 256 } 257 } ); 258 259 slider.addEventListener( 'mouseenter', stop ); 260 slider.addEventListener( 'mouseleave', start ); 261 slider.addEventListener( 'focusin', stop ); 262 slider.addEventListener( 'focusout', start ); 263 track.addEventListener( 'pointerdown', onPointerDown ); 264 track.addEventListener( 'pointermove', onPointerMove ); 265 track.addEventListener( 'pointerup', onPointerUp ); 266 track.addEventListener( 'pointercancel', onPointerUp ); 267 window.addEventListener( 'resize', rebuild ); 268 269 rebuild(); 270 start(); 271 } ); 272 } 273 274 function initWorkSlider() { 275 var sliders = document.querySelectorAll( '.waafaa-home-work__slider' ); 276 277 sliders.forEach( function ( slider ) { 278 var section = slider.closest( '.waafaa-home-work' ); 279 var track = slider.querySelector( '.waafaa-home-work__grid' ); 280 var originalSlides = track ? Array.prototype.slice.call( track.children ) : []; 281 var prevButton = section ? section.querySelector( '.waafaa-home-work__nav--prev .wp-block-button__link' ) : null; 282 var nextButton = section ? section.querySelector( '.waafaa-home-work__nav--next .wp-block-button__link' ) : null; 283 var index = 0; 284 var perView = 1; 285 var gap = 20; 286 var clones = []; 287 288 if ( ! track || originalSlides.length < 2 || ! prevButton || ! nextButton || 'true' === slider.dataset.waafaaSliderReady ) { 289 return; 290 } 291 292 slider.dataset.waafaaSliderReady = 'true'; 293 slider.classList.add( 'is-ready' ); 294 295 prevButton.setAttribute( 'role', 'button' ); 296 prevButton.setAttribute( 'aria-label', 'Show previous product' ); 297 nextButton.setAttribute( 'role', 'button' ); 298 nextButton.setAttribute( 'aria-label', 'Show next product' ); 299 300 function getPerView() { 301 if ( window.matchMedia( '(min-width: 1024px)' ).matches ) { 302 return 3; 303 } 304 305 if ( window.matchMedia( '(min-width: 680px)' ).matches ) { 306 return 2; 307 } 308 309 return 1; 310 } 311 312 function getStep() { 313 var firstSlide = track.children[ 0 ]; 314 315 if ( ! firstSlide ) { 316 return 0; 317 } 318 319 gap = parseFloat( window.getComputedStyle( track ).columnGap ) || 20; 320 return firstSlide.getBoundingClientRect().width + gap; 321 } 322 323 function setSlideWidths() { 324 var availableWidth = slider.getBoundingClientRect().width - ( ( perView - 1 ) * gap ); 325 var slideWidth = Math.max( 0, availableWidth / perView ); 326 327 Array.prototype.forEach.call( track.children, function ( slide ) { 328 slide.style.flexBasis = slideWidth + 'px'; 329 } ); 330 } 331 332 function translate( animate ) { 333 track.style.transitionDuration = animate ? '700ms' : '0ms'; 334 track.style.transform = 'translate3d(-' + ( getStep() * index ) + 'px, 0, 0)'; 335 } 336 337 function goTo( nextIndex ) { 338 index = nextIndex < 0 ? originalSlides.length - 1 : nextIndex; 339 translate( true ); 340 } 341 342 function rebuild() {
343 clones.forEach( function ( clone ) { 344 clone.remove(); 345 } ); 346 clones = []; 347 perView = Math.min( getPerView(), originalSlides.length ); 348 gap = parseFloat( window.getComputedStyle( track ).columnGap ) || 20; 349 originalSlides.slice( 0, perView ).forEach( function ( slide ) { 350 var clone = slide.cloneNode( true ); 351 352 clone.setAttribute( 'aria-hidden', 'true' ); 353 clone.classList.add( 'waafaa-home-work-card--clone' ); 354 Array.prototype.forEach.call( clone.querySelectorAll( 'a, button, input, textarea, select' ), function ( focusable ) { 355 focusable.setAttribute( 'tabindex', '-1' ); 356 } ); 357 track.appendChild( clone ); 358 clones.push( clone ); 359 } ); 360 index = index % originalSlides.length; 361 setSlideWidths(); 362 translate( false ); 363 } 364 365 track.addEventListener( 'transitionend', function () { 366 if ( index >= originalSlides.length ) { 367 index = 0; 368 translate( false ); 369 } 370 } ); 371 372 prevButton.addEventListener( 'click', function ( event ) { 373 event.preventDefault(); 374 goTo( index - 1 ); 375 } ); 376 377 nextButton.addEventListener( 'click', function ( event ) { 378 event.preventDefault(); 379 goTo( index + 1 ); 380 } ); 381 382 window.addEventListener( 'resize', rebuild ); 383 rebuild(); 384 } ); 385 } 386 387 /** 388 * Get Pro page: pre-select the plan the buyer picked on hide.waafaa.com 389 * (passed in as `?plan=monthly|yearly|lifetime`), then open the Freemius 390 * checkout when the FormGent checkout form submits successfully. 391 * 392 * The plan lives in our own `selectedPlan` state, seeded from the URL and kept 393 * in sync with the editable FormGent single-choice "plan" radio (matched by 394 * option index, since FormGent renders the radios via the Interactivity 395 * template and does not expose a plain value attribute). On submit-success â 396 * detected when FormGent removes `--hidden` from `.formgent-confirmation-wrap` 397 * â the captured plan/email/name open Freemius Checkout. When the purchase 398 * completes, the captured User Id field (the extension install id) plus the 399 * Freemius purchase payload are POSTed to the Pro API `/grant` endpoint, which 400 * verifies the purchase server-side and grants entitlement to that user_id. 401 * 402 * The optional "Reference" field (marketing attribution) rides along on that 403 * POST. The coupon is NOT sent from here: the Pro API reads the redeemed coupon 404 * back off the verified Freemius purchase, so the stored value is the one the 405 * buyer actually got the discount from. 406 */ 407 function initProCheckout() { 408 var root = document.querySelector( '.waafaa-pro-checkout' ); 409 410 if ( ! root ) { 411 return; 412 } 413 414 if ( 'true' === root.dataset.waafaaProCheckoutReady ) { 415 return; 416 } 417 418 root.dataset.waafaaProCheckoutReady = 'true'; 419 420 var config = window.waafaaFreemius || {}; 421 var planOrder = [ 'monthly', 'yearly', 'lifetime' ]; 422 // Map our plan keys to Freemius billing cycles (Freemius uses "annual"). 423 var billingCycles = { monthly: 'monthly', yearly: 'annual', lifetime: 'lifetime' }; 424 var params = new URLSearchParams( window.location.search ); 425 var passthroughUserId = params.get( 'user_id' ) || params.get( 'userId' ) || ''; 426 var selectedPlan = normalizePlan( params.get( 'plan' ) ) || 'yearly'; 427 var lastSubmission = null; 428 var checkoutHandler = null; 429 430 function normalizePlan( value ) { 431 value = ( value || '' ).toString().toLowerCase(); 432 return -1 !== planOrder.indexOf( value ) ? value : ''; 433 } 434 435 function getPlanBox() { 436 return root.querySelector( '.formgent-single-choice-plan' ); 437 } 438 439 function getPlanRadios() { 440 var box = getPlanBox(); 441 return box ? box.querySelectorAll( 'input[type="radio"]' ) : []; 442 } 443 444 function planRadio( plan ) { 445 var index = planOrder.indexOf( plan ); 446 var radios = getPlanRadios(); 447 448 return ( index >= 0 && radios.length > index ) ? radios[ index ] : null; 449 } 450 451 // The plan radios are server-rendered before the Interactivity API wires 452 // their directives, so an early click is reset by the first hydration 453 // render. Keep clicking the wanted plan until the selection holds for a few 454 // consecutive checks (i.e. has survived hydration), then stop. 455 function preselectPlan( plan ) { 456 var attempts = 0; 457 var stable = 0; 458 459 ( function attempt() { 460 var radio = planRadio( plan ); 461 462 if ( radio ) { 463 if ( radio.checked ) { 464 stable++; 465 466 if ( stable >= 3 ) { 467 return; 468 } 469 } else { 470 stable = 0; 471 radio.click(); 472 } 473 } 474 475 if ( attempts++ < 80 ) { 476 window.setTimeout( attempt, 150 ); 477 } 478 }() ); 479 } 480 481 function readSelectedPlan() { 482 var radios = getPlanRadios(); 483 var i; 484 485 for ( i = 0; i < radios.length; i++ ) { 486 if ( radios[ i ].checked ) { 487 return planOrder[ i ] || selectedPlan; 488 } 489 } 490 491 return selectedPlan; 492 } 493 494 function bindPlanChange() { 495 var attempts = 0; 496 497 ( function wait() { 498 var box = getPlanBox(); 499 500 if ( box && getPlanRadios().length >= planOrder.length ) { 501 box.addEventListener( 'change', function () { 502 selectedPlan = readSelectedPlan(); 503 } ); 504 return; 505 } 506 507 if ( attempts++ < 80 ) { 508 window.setTimeout( wait, 150 ); 509 } 510 }() ); 511 } 512 513 function ensureFreemius() { 514 if ( checkoutHandler ) { 515 return true; 516 } 517 518 if ( ! window.FS || ! window.FS.Checkout || ! config.productId || ! config.publicKey ) { 519 return false;
520 } 521 522 // Freemius Checkout uses the legacy param name `plugin_id` for the product id. 523 // Note: sandbox belongs in .open(), not here (see openCheckout()). 524 checkoutHandler = window.FS.Checkout.configure( { 525 plugin_id: config.productId, 526 public_key: config.publicKey 527 } ); 528 529 return true; 530 } 531 532 function trimValue( value ) { 533 return ( value || '' ).replace( /^\s+|\s+$/g, '' ); 534 } 535 536 // The Get Pro and Renew forms are the same checkout with different field 537 // sets, so they share this script and differ only by an id prefix: 538 // `pc*` on Get Pro, `rn*` on Renew (declared as data-waafaa-field-prefix 539 // on the section). Fields the Renew form does not have simply resolve to 540 // null, and the Pro API skips writing anything the checkout omitted â so a 541 // renewal never blanks the name or referrer captured on the first purchase. 542 var fieldPrefix = root.dataset.waafaaFieldPrefix || 'pc'; 543 544 function fieldById( id ) { 545 return root.querySelector( '#formgent-' + id ); 546 } 547 548 function prefixedField( suffix ) { 549 return fieldById( fieldPrefix + suffix ); 550 } 551 552 function snapshot() { 553 // The `:not()` matters: Reference is an email field too, so a bare 554 // `input[type="email"]` fallback could send the referrer's address as 555 // the buyer's and bind Pro to the wrong person. 556 var emailEl = prefixedField( 'Email0001' ) || root.querySelector( 'input[type="email"]:not(#formgent-' + fieldPrefix + 'Refer0001)' ); 557 var userIdEl = prefixedField( 'UserId001' ); 558 var firstEl = prefixedField( 'First0001' ); 559 var lastEl = prefixedField( 'Last00001' ); 560 var referenceEl = prefixedField( 'Refer0001' ); 561 var parts = []; 562 563 if ( firstEl && firstEl.value ) { 564 parts.push( trimValue( firstEl.value ) ); 565 } 566 567 if ( lastEl && lastEl.value ) { 568 parts.push( trimValue( lastEl.value ) ); 569 } 570 571 return { 572 email: emailEl ? trimValue( emailEl.value ) : '', 573 fullName: trimValue( parts.join( ' ' ) ), 574 userId: userIdEl ? trimValue( userIdEl.value ) : '', 575 reference: referenceEl ? trimValue( referenceEl.value ) : '', 576 plan: readSelectedPlan() 577 }; 578 } 579 580 // Notify the Pro API that a Freemius purchase completed for this install. 581 // The server re-verifies the purchase with Freemius (Bearer token) before 582 // granting, so a forged POST cannot unlock Pro. `keepalive` lets the request 583 // finish even if Freemius redirects/closes the page right after purchase. 584 function grant( submission, response ) { 585 var linkUserId = submission.userId || passthroughUserId; 586 587 if ( ! config.grantUrl || ! window.fetch ) { 588 return; 589 } 590 591 try { 592 window.fetch( config.grantUrl, { 593 method: 'POST', 594 headers: { 'Content-Type': 'application/json' }, 595 body: JSON.stringify( { 596 user_id: linkUserId, 597 plan: submission.plan, 598 email: submission.email, 599 full_name: submission.fullName, 600 reference: submission.reference, 601 freemius: response 602 } ), 603 keepalive: true 604 } ); 605 } catch ( err ) { 606 if ( window.console && window.console.warn ) { 607 window.console.warn( 'WaaFaa: could not notify the grant endpoint.', err ); 608 } 609 } 610 } 611 612 function openCheckout( submission ) { 613 var cycle = billingCycles[ submission.plan ] || 'annual'; 614 var options; 615 616 if ( ! ensureFreemius() ) { 617 if ( window.console && window.console.warn ) { 618 window.console.warn( 'WaaFaa: Freemius checkout is not configured yet (missing product id / public key). Selected plan:', submission.plan ); 619 } 620 621 return; 622 } 623 624 options = { 625 name: 'Hide Shorts Reels Pro', 626 billing_cycle: cycle, 627 licenses: 1, 628 purchaseCompleted: function ( response ) { 629 grant( submission, response ); 630 } 631 }; 632 633 // Sandbox (test-card) mode. The classic checkout.min.js loader reads FLAT 634 // `timestamp` + `sandbox_token` options (mapped internally to the s_ctx_ts 635 // & sandbox URL params) â NOT the nested { sandbox: { ctx, token } } object 636 // that the newer @freemius/checkout npm module uses. 637 if ( config.sandbox && config.sandbox.token ) { 638 options.timestamp = config.sandbox.ctx; 639 options.sandbox_token = config.sandbox.token; 640 } 641 642 if ( config.planId ) { 643 options.plan_id = config.planId; 644 } 645 646 if ( submission.email ) { 647 options.user_email = submission.email; 648 } 649 650 if ( submission.fullName ) { 651 options.user_firstname = submission.fullName; 652 } 653 654 checkoutHandler.open( options ); 655 } 656 657 preselectPlan( selectedPlan ); 658 bindPlanChange(); 659 660 root.addEventListener( 'click', function ( event ) { 661 var button = event.target && event.target.closest ? event.target.closest( '.formgent-btn, button[id^="formgent-fixed-submit-"]' ) : null; 662 663 if ( button ) { 664 lastSubmission = snapshot(); 665 } 666 }, true ); 667 668 ( function observeSuccess() { 669 var confirmation = root.querySelector( '.formgent-confirmation-wrap' ); 670 var observer; 671 672 if ( ! confirmation || ! window.MutationObserver ) { 673 return; 674 } 675 676 ensureFreemius(); 677 678 observer = new MutationObserver( function ( mutations ) { 679 var i; 680 var node; 681 682 for ( i = 0; i < mutations.length; i++ ) { 683 node = mutations[ i ].target; 684 685 if ( node.classList && ! node.classList.contains( 'formgent-confirmation-wrap--hidden' ) ) { 686 openCheckout( lastSubmission || snapshot() ); 687 break; 688 } 689 } 690 } ); 691 692 observer.observe( confirmation, { attributes: true, attributeFilter: [ 'class' ] } ); 693 }() ); 694 } 695 696 /** 697 * Pro for Bangladesh page: pre-select the plan radio from the `?plan=` URL 698 * param (the BD cards on hide.waafaa.com link with ?plan=monthly|yearly|lifetime). 699 * Manual form â no checkout â so this only mirrors the get-pro plan preselect
700 * (retry until the choice survives Interactivity hydration). 701 */ 702 function initProBdPlan() { 703 var root = document.querySelector( '.waafaa-pro-bd' ); 704 705 if ( ! root || 'true' === root.dataset.waafaaProBdReady ) { 706 return; 707 } 708 709 root.dataset.waafaaProBdReady = 'true'; 710 711 var planOrder = [ 'monthly', 'yearly', 'lifetime' ]; 712 var params = new URLSearchParams( window.location.search ); 713 var plan = ( params.get( 'plan' ) || '' ).toString().toLowerCase(); 714 var index = planOrder.indexOf( plan ); 715 var attempts = 0; 716 var stable = 0; 717 718 if ( index < 0 ) { 719 return; 720 } 721 722 function planRadios() { 723 var box = root.querySelector( '.formgent-single-choice-plan' ); 724 return box ? box.querySelectorAll( 'input[type="radio"]' ) : []; 725 } 726 727 ( function attempt() { 728 var radios = planRadios(); 729 var radio = radios.length > index ? radios[ index ] : null; 730 731 if ( radio ) { 732 if ( radio.checked ) { 733 stable++; 734 735 if ( stable >= 3 ) { 736 return; 737 } 738 } else { 739 stable = 0; 740 radio.click(); 741 } 742 } 743 744 if ( attempts++ < 80 ) { 745 window.setTimeout( attempt, 150 ); 746 } 747 }() ); 748 } 749 750 /** 751 * Get Pro / Request Free / Pro-for-Bangladesh pages: pre-fill the visible 752 * "User Id" field from the `?user_id=` URL param the extension appends when a 753 * user clicks "Upgrade to Pro" or "Apply for Free". The field stays fully 754 * editable â when the param is absent it is left empty for the user to paste 755 * their own id, and once the user starts typing we stop touching it. 756 * 757 * FormGent renders each field through the WordPress Interactivity API 758 * (`data-wp-on--input="actions.updateInput"` + 759 * `data-wp-bind--value="state.getValue"`), so two things matter: (a) the input 760 * is re-rendered from FormGent state after first paint, which wipes an early 761 * write â hence the retry until the value survives hydration; and (b) setting 762 * `input.value` alone never reaches FormGent's reactive store, so we dispatch 763 * a native `input` event to run `actions.updateInput`. Without that the field 764 * would look filled but submit empty and fail the required-field check. 765 */ 766 function initUserIdPrefill() { 767 var params = new URLSearchParams( window.location.search ); 768 var userId = ( params.get( 'user_id' ) || params.get( 'userId' ) || '' ).replace( /^\s+|\s+$/g, '' ); 769 770 if ( ! userId ) { 771 return; 772 } 773 774 // FormGent field ids that hold the extension install "User Id", one per 775 // form (get-pro, pro-bd, request-free). These must match the block ids in 776 // the theme demo importer's FormGent content. The first present wins. 777 var fieldIds = [ 'pcUserId001', 'rnUserId001', 'bdUserId001', '_ve8f8Br5_1J' ]; 778 var attempts = 0; 779 var stable = 0; 780 var userTouched = false; 781 var bound = null; 782 783 function findInput() { 784 var i; 785 var el; 786 787 for ( i = 0; i < fieldIds.length; i++ ) { 788 el = document.getElementById( 'formgent-' + fieldIds[ i ] ); 789 790 if ( el ) { 791 return el; 792 } 793 } 794 795 // Fallback if a form's field id ever drifts from the importer source: 796 // locate the field by its "User Id" label and read the input beside it. 797 var labels = document.querySelectorAll( '.formgent-field-label' ); 798 var text; 799 var field; 800 801 for ( i = 0; i < labels.length; i++ ) { 802 text = ( labels[ i ].textContent || '' ).toLowerCase().replace( /\s+/g, ' ' ); 803 804 if ( -1 !== text.indexOf( 'user id' ) ) { 805 field = labels[ i ].closest( '.formgent-field-single' ); 806 807 if ( field ) { 808 return field.querySelector( '.formgent-field-single__input' ); 809 } 810 } 811 } 812 813 return null; 814 } 815 816 function bindTouch( input ) { 817 if ( bound === input ) { 818 return; 819 } 820 821 bound = input; 822 input.addEventListener( 'keydown', function () { 823 userTouched = true; 824 } ); 825 } 826 827 ( function attempt() { 828 var input = findInput(); 829 830 if ( input ) { 831 bindTouch( input ); 832 833 if ( ! userTouched ) { 834 if ( input.value === userId ) { 835 stable++; 836 } else { 837 stable = 0; 838 input.value = userId; 839 input.dispatchEvent( new Event( 'input', { bubbles: true } ) ); 840 } 841 } 842 } 843 844 // Keep retrying through FormGent hydration, but stop once the value has 845 // held for a few checks or the user has begun editing the field. 846 if ( ! userTouched && stable < 3 && attempts++ < 80 ) { 847 window.setTimeout( attempt, 150 ); 848 } 849 }() ); 850 } 851 852 applyMode( null, getStoredMode() ); 853 854 /**
855 * Turn the pricing page's product jump links into a tab strip. 856 * 857 * The page ships as three stacked product sections with in-page anchors, which 858 * is what a visitor without JavaScript keeps: every plan is readable, just 859 * scrolled rather than switched. This upgrades that same markup in place â 860 * the anchors become tabs and only the selected product's plans stay in the 861 * document flow, so the page opens on one product instead of nine cards. 862 * 863 * Deep links keep working: /pricing/#pdfcraft opens on PDFCraft. 864 */ 865 function initPricingTabs() { 866 var list = document.querySelector( '.waafaa-pricing__jump' ); 867 var section = document.querySelector( '.waafaa-pricing' ); 868 var links; 869 var panels = []; 870 var panel; 871 var index; 872 var opening = 0; 873 874 if ( ! list || ! section || 'true' === list.dataset.waafaaTabsReady ) { 875 return; 876 } 877 878 links = [].slice.call( list.querySelectorAll( 'a[href^="#"]' ) ); 879 880 // Every tab must resolve to a panel, or the strip would hide content it 881 // cannot bring back. Anything less than a full set stays as plain anchors. 882 for ( index = 0; index < links.length; index++ ) { 883 panel = document.getElementById( links[ index ].getAttribute( 'href' ).slice( 1 ) ); 884 885 if ( ! panel ) { 886 return; 887 } 888 889 panels.push( panel ); 890 } 891 892 if ( panels.length < 2 ) { 893 return; 894 } 895 896 list.dataset.waafaaTabsReady = 'true'; 897 section.classList.add( 'is-tabbed' ); 898 list.setAttribute( 'role', 'tablist' ); 899 900 function activate( active, focus ) { 901 var i; 902 var selected; 903 904 for ( i = 0; i < links.length; i++ ) { 905 selected = i === active; 906 907 links[ i ].classList.toggle( 'is-active', selected ); 908 links[ i ].setAttribute( 'aria-selected', selected ? 'true' : 'false' ); 909 links[ i ].setAttribute( 'tabindex', selected ? '0' : '-1' ); 910 panels[ i ].hidden = ! selected; 911 } 912 913 if ( focus ) { 914 links[ active ].focus(); 915 } 916 } 917 918 for ( index = 0; index < links.length; index++ ) { 919 ( function ( i ) { 920 var id = links[ i ].getAttribute( 'href' ).slice( 1 ); 921 922 links[ i ].setAttribute( 'role', 'tab' ); 923 links[ i ].setAttribute( 'aria-controls', id ); 924 panels[ i ].setAttribute( 'role', 'tabpanel' ); 925 panels[ i ].setAttribute( 'aria-label', links[ i ].textContent ); 926 927 links[ i ].addEventListener( 'click', function ( event ) { 928 event.preventDefault(); 929 activate( i, false ); 930 931 // Move the hash without the jump a normal anchor would cause, 932 // so the tab strip stays where the reader is looking. 933 if ( window.history && window.history.replaceState ) { 934 window.history.replaceState( null, '', '#' + id ); 935 } 936 } ); 937 938 links[ i ].addEventListener( 'keydown', function ( event ) { 939 var next = -1; 940 941 if ( 'ArrowRight' === event.key || 'ArrowDown' === event.key ) { 942 next = ( i + 1 ) % links.length; 943 } else if ( 'ArrowLeft' === event.key || 'ArrowUp' === event.key ) { 944 next = ( i - 1 + links.length ) % links.length; 945 } else if ( 'Home' === event.key ) { 946 next = 0; 947 } else if ( 'End' === event.key ) { 948 next = links.length - 1; 949 } 950 951 if ( next < 0 ) { 952 return; 953 } 954 955 event.preventDefault(); 956 activate( next, true ); 957 } ); 958 }( index ) ); 959 } 960 961 // Open on the deep-linked product when there is one, otherwise the first. 962 for ( index = 0; index < links.length; index++ ) { 963 if ( links[ index ].getAttribute( 'href' ) === window.location.hash ) { 964 opening = index; 965 } 966 } 967 968 activate( opening, false ); 969 } 970 971 document.addEventListener( 'DOMContentLoaded', function () { 972 var modeButton = document.querySelector( '.waafaa-header__mode' ); 973 974 document.documentElement.classList.add( 'waafaa-theme-ready' ); 975 initTestimonials(); 976 initWorkSlider(); 977 initPricingTabs(); 978 initProCheckout(); 979 initProBdPlan(); 980 initUserIdPrefill(); 981 982 if ( ! modeButton ) { 983 return; 984 } 985 986 enhanceModeButton( modeButton ); 987 applyMode( modeButton, getStoredMode() ); 988 989 modeButton.addEventListener( 'click', function ( event ) { 990 var currentIndex; 991 var nextMode; 992 993 event.preventDefault(); 994 currentIndex = modes.indexOf( modeButton.dataset.mode ); 995 nextMode = modes[ ( currentIndex + 1 ) % modes.length ]; 996 applyMode( modeButton, nextMode ); 997 } ); 998 999 modeButton.addEventListener( 'keydown', function ( event ) { 1000 if ( 'Enter' === event.key || ' ' === event.key ) { 1001 event.preventDefault(); 1002 modeButton.click(); 1003 } 1004 } ); 1005 1006 mediaQuery.addEventListener( 'change', function () { 1007 if ( 'auto' === modeButton.dataset.mode ) { 1008 applyMode( modeButton, 'auto' ); 1009 } 1010 } ); 1011 } ); 1012}() );
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.