PageSourceSearch

https://waafaa.com/wp-content/themes/waafaa-theme/assets/js/waafaa-theme.js?ver=1789661761

js waafaa.com collected 2026-09-24 14:56:54 UTC 30,247 bytes, 1,012 lines download raw bytes

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.