PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.65
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.65
51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 51.1.46 51.1.47 51.1.49 All 37 releases
king-addons / includes / widgets / Age_Gate / script.js

script.js in King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder 51.1.65, at includes/widgets/Age_Gate/script.js

593 lines 21.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Age Gate widget behavior.
3 *
4 * This script renders a global overlay. It must initialize once per page load,
5 * and should also work inside Elementor preview without duplicating DOM nodes.
6 */
7 (function () {
8 "use strict";
9
10 const FLAG = "kingAddonsAgeGateInitialized";
11
12 // Module-scoped references used by helpers like setStatus()/closeOverlay().
13 // These are assigned during init() and remain available for button handlers.
14 let payload = null;
15 let root = null;
16 let state = null;
17
18 /**
19 * Initialize Age Gate once.
20 *
21 * @returns {void}
22 */
23 const init = () => {
24 payload = window.kingAddonsAgeGate;
25 if (!payload || !payload.enabled) {
26 return;
27 }
28
29 if (window[FLAG]) {
30 return;
31 }
32
33 if (!document.body) {
34 // Defer until body is available.
35 setTimeout(init, 0);
36 return;
37 }
38
39 window[FLAG] = true;
40
41 root = ensureRoot();
42 const card = root.querySelector('.king-addons-age-gate__card');
43 const content = root.querySelector('.king-addons-age-gate__content');
44 state = {
45 status: payload.status || '',
46 cookieDays: resolveCookieDays(payload),
47 };
48
49 if (!card || !content) {
50 return;
51 }
52
53 applyDesign(root, card, payload);
54
55 const needsBlock =
56 payload.behaviour &&
57 payload.behaviour.denyAction === 'block' &&
58 state.status === 'denied';
59
60 if (!payload.shouldRender && !needsBlock) {
61 return;
62 }
63
64 lockScroll();
65 showOverlay(root, payload);
66
67 if (payload.elementorTemplate) {
68 const templateWrapper = document.createElement('div');
69 templateWrapper.className = 'king-addons-age-gate__template';
70 templateWrapper.innerHTML = payload.elementorTemplate;
71 content.appendChild(templateWrapper);
72 }
73
74 if (needsBlock) {
75 renderBlocked(content, payload);
76 return;
77 }
78
79 if (payload.mode === 'dob' && payload.isPremium) {
80 renderDob(content, payload, state);
81 } else {
82 renderYesNo(content, payload, state);
83 }
84 };
85
86 const initWhenReady = () => {
87 if (document.readyState === "loading") {
88 document.addEventListener("DOMContentLoaded", init);
89 return;
90 }
91 init();
92 };
93
94 initWhenReady();
95
96 // Elementor preview support: run init after Elementor frontend bootstrap.
97 if (typeof jQuery !== "undefined") {
98 jQuery(window).on("elementor/frontend/init", function () {
99 init();
100 });
101 }
102
103 /**
104 * Creates root container if not present.
105 */
106 function ensureRoot() {
107 let rootEl = document.getElementById('king-addons-age-gate');
108 if (!rootEl) {
109 rootEl = document.createElement('div');
110 rootEl.id = 'king-addons-age-gate';
111 rootEl.className = 'king-addons-age-gate';
112 rootEl.innerHTML = '<div class="king-addons-age-gate__overlay"></div><div class="king-addons-age-gate__card"><div class="king-addons-age-gate__content"></div></div>';
113 document.body.appendChild(rootEl);
114 }
115 return rootEl;
116 }
117
118 /**
119 * Applies design variables to the overlay.
120 */
121 function applyDesign(rootEl, cardEl, data) {
122 const design = data.design || {};
123
124 // Reset template + animation modifiers (important for Elementor preview re-init).
125 const templateClasses = [
126 'king-addons-age-gate--template-center-card',
127 'king-addons-age-gate--template-bottom-card',
128 'king-addons-age-gate--template-top-card',
129 'king-addons-age-gate--template-side-left',
130 'king-addons-age-gate--template-side-right',
131 'king-addons-age-gate--template-fullscreen',
132 ];
133 templateClasses.forEach((cls) => rootEl.classList.remove(cls));
134 Array.from(rootEl.classList)
135 .filter((cls) => cls.indexOf('king-addons-age-gate--animate-') === 0)
136 .forEach((cls) => rootEl.classList.remove(cls));
137
138 cardEl.classList.remove('king-addons-age-gate__card--bottom');
139 cardEl.classList.remove('king-addons-age-gate__card--top');
140
141 const existingBg = cardEl.querySelector('.king-addons-age-gate__background');
142 if (existingBg) {
143 existingBg.remove();
144 }
145
146 const overlayColor = (design.overlayColor || '#0d0d0d').toString();
147 const overlayOpacity = clamp01(design.overlayOpacity ?? 0.7);
148 rootEl.style.setProperty('--ka-age-overlay', overlayColor);
149 rootEl.style.setProperty('--ka-age-overlay-opacity', overlayOpacity);
150 rootEl.style.setProperty('--ka-age-overlay-rgba', toRgba(overlayColor, overlayOpacity) || ('rgba(13, 13, 13, ' + overlayOpacity + ')'));
151 rootEl.style.setProperty('--ka-age-card-bg', design.cardBackground || '#ffffff');
152 rootEl.style.setProperty('--ka-age-card-width', (design.cardWidth || 520) + 'px');
153 rootEl.style.setProperty('--ka-age-text', design.textColor || '#111827');
154 rootEl.style.setProperty('--ka-age-title-size', (design.titleSize || 24) + 'px');
155 rootEl.style.setProperty('--ka-age-body-size', (design.bodySize || 16) + 'px');
156 rootEl.style.setProperty('--ka-age-title-weight', design.titleWeight || 700);
157 rootEl.style.setProperty('--ka-age-body-weight', design.bodyWeight || 400);
158 rootEl.style.setProperty('--ka-age-btn-yes-color', design.buttonYesColor || '#ffffff');
159 rootEl.style.setProperty('--ka-age-btn-yes-bg', design.buttonYesBg || '#10b981');
160 rootEl.style.setProperty('--ka-age-btn-no-color', design.buttonNoColor || '#ffffff');
161 rootEl.style.setProperty('--ka-age-btn-no-bg', design.buttonNoBg || '#ef4444');
162 rootEl.style.setProperty('--ka-age-btn-yes-hover-bg', design.buttonYesHoverBg || design.buttonYesBg || '#0f9c75');
163 rootEl.style.setProperty('--ka-age-btn-no-hover-bg', design.buttonNoHoverBg || design.buttonNoBg || '#d92d20');
164 rootEl.style.setProperty('--ka-age-btn-yes-hover-color', design.buttonYesHoverColor || design.buttonYesColor || '#ffffff');
165 rootEl.style.setProperty('--ka-age-btn-no-hover-color', design.buttonNoHoverColor || design.buttonNoColor || '#ffffff');
166
167 const template = (design.template || 'center-card').toString();
168 rootEl.classList.add('king-addons-age-gate--template-' + template);
169
170 // Back-compat: old settings may still use cardAlign.
171 if (design.cardAlign === 'bottom' || template === 'bottom-card') {
172 cardEl.classList.add('king-addons-age-gate__card--bottom');
173 }
174
175 if (template === 'top-card' || design.cardAlign === 'top') {
176 cardEl.classList.add('king-addons-age-gate__card--top');
177 }
178
179 if (design.backgroundImage) {
180 const bg = document.createElement('div');
181 bg.className = 'king-addons-age-gate__background';
182 bg.style.backgroundImage = 'url(' + design.backgroundImage + ')';
183 cardEl.prepend(bg);
184 }
185
186 if (design.animation && design.animation !== 'none') {
187 rootEl.classList.add('king-addons-age-gate--animate-' + design.animation);
188 }
189 }
190
191 function clamp01(value) {
192 const numberValue = typeof value === 'number' ? value : parseFloat(value);
193 if (!Number.isFinite(numberValue)) {
194 return 0.7;
195 }
196 return Math.min(1, Math.max(0, numberValue));
197 }
198
199 function toRgba(color, alpha) {
200 const c = (color || '').toString().trim();
201 if (!c) {
202 return null;
203 }
204
205 // #RGB / #RRGGBB
206 if (c[0] === '#') {
207 let hex = c.slice(1);
208 if (hex.length === 3) {
209 hex = hex.split('').map((ch) => ch + ch).join('');
210 }
211 if (hex.length !== 6) {
212 return null;
213 }
214 const r = parseInt(hex.slice(0, 2), 16);
215 const g = parseInt(hex.slice(2, 4), 16);
216 const b = parseInt(hex.slice(4, 6), 16);
217 if ([r, g, b].some((n) => Number.isNaN(n))) {
218 return null;
219 }
220 return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')';
221 }
222
223 // rgb(r, g, b)
224 const rgbMatch = c.match(/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/i);
225 if (rgbMatch) {
226 return 'rgba(' + rgbMatch[1] + ', ' + rgbMatch[2] + ', ' + rgbMatch[3] + ', ' + alpha + ')';
227 }
228
229 // rgba(r, g, b, a) -> replace alpha
230 const rgbaMatch = c.match(/^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([0-9.]+)\s*\)$/i);
231 if (rgbaMatch) {
232 return 'rgba(' + rgbaMatch[1] + ', ' + rgbaMatch[2] + ', ' + rgbaMatch[3] + ', ' + alpha + ')';
233 }
234
235 return null;
236 }
237
238 /**
239 * Shows the overlay.
240 */
241 function showOverlay(rootEl, data) {
242 rootEl.classList.add('king-addons-age-gate--visible');
243 rootEl.setAttribute('aria-hidden', 'false');
244 const overlay = rootEl.querySelector('.king-addons-age-gate__overlay');
245 if (overlay) {
246 overlay.setAttribute('aria-hidden', 'true');
247 }
248 }
249
250 /**
251 * Renders simple Yes/No mode.
252 */
253 function renderYesNo(wrapper, data, localState) {
254 wrapper.innerHTML = '';
255 const title = document.createElement('h2');
256 title.className = 'king-addons-age-gate__title';
257 title.textContent = data.texts.title;
258
259 const subtitle = document.createElement('p');
260 subtitle.className = 'king-addons-age-gate__subtitle';
261 subtitle.textContent = data.mode === 'minimum'
262 ? data.texts.subtitle + ' (' + data.minAge + '+)'
263 : data.texts.subtitle;
264
265 const buttons = document.createElement('div');
266 buttons.className = 'king-addons-age-gate__buttons';
267
268 const yesBtn = buildButton('king-addons-age-gate__button king-addons-age-gate__button--allow', data.texts.yes);
269 const noBtn = buildButton('king-addons-age-gate__button king-addons-age-gate__button--deny', data.texts.no);
270
271 const consent = buildConsent(data);
272
273 yesBtn.addEventListener('click', () => {
274 if (consent && !consent.checkbox.checked) {
275 showMessage(wrapper, data.behaviour.blockMessage || 'Please confirm consent.');
276 return;
277 }
278 setStatus('allowed', localState.cookieDays);
279 closeOverlay();
280 });
281
282 noBtn.addEventListener('click', () => {
283 setStatus('denied', localState.cookieDays);
284 if (data.behaviour.denyAction === 'redirect' && data.behaviour.denyRedirectUrl) {
285 window.location.href = data.behaviour.denyRedirectUrl;
286 } else {
287 renderBlocked(wrapper, data);
288 }
289 });
290
291 buttons.appendChild(yesBtn);
292 buttons.appendChild(noBtn);
293
294 appendLogo(wrapper, data.design);
295 wrapper.appendChild(title);
296 wrapper.appendChild(subtitle);
297 if (consent) {
298 wrapper.appendChild(consent.container);
299 }
300 wrapper.appendChild(buttons);
301 }
302
303 /**
304 * Renders DOB form.
305 */
306 function renderDob(wrapper, data, localState) {
307 wrapper.innerHTML = '';
308 const dobConfig = data.dob || { format: 'dmy', errors: { invalid: 'Invalid date.', denied: 'Access denied.' } };
309
310 const title = document.createElement('h2');
311 title.className = 'king-addons-age-gate__title';
312 title.textContent = data.texts.title;
313
314 const subtitle = document.createElement('p');
315 subtitle.className = 'king-addons-age-gate__subtitle';
316 subtitle.textContent = data.texts.subtitle + ' (' + data.minAge + '+)';
317
318 const grid = document.createElement('div');
319 grid.className = 'king-addons-age-gate__field-grid';
320
321 const inputs = buildDobInputs(dobConfig.format || 'dmy');
322 inputs.forEach((input) => grid.appendChild(input));
323
324 const consent = buildConsent(data);
325
326 const buttons = document.createElement('div');
327 buttons.className = 'king-addons-age-gate__buttons';
328 const submitBtn = buildButton('king-addons-age-gate__button king-addons-age-gate__button--allow', data.texts.yes);
329 const declineBtn = buildButton('king-addons-age-gate__button king-addons-age-gate__button--deny', data.texts.no);
330
331 const message = document.createElement('div');
332 message.className = 'king-addons-age-gate__message';
333 message.style.display = 'none';
334
335 submitBtn.addEventListener('click', () => {
336 if (consent && !consent.checkbox.checked) {
337 showMessage(wrapper, data.behaviour.blockMessage || dobConfig.errors.invalid);
338 return;
339 }
340 message.style.display = 'none';
341 const params = new URLSearchParams();
342 params.append('action', data.ajax.action);
343 params.append('nonce', data.ajax.nonce);
344 params.append('day', inputs.day.value || '');
345 params.append('month', inputs.month.value || '');
346 params.append('year', inputs.year.value || '');
347
348 submitBtn.disabled = true;
349 submitBtn.textContent = data.texts.yes + '...';
350
351 fetch(data.ajax.url, {
352 method: 'POST',
353 headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'},
354 body: params.toString(),
355 }).then((response) => response.json())
356 .then((resp) => {
357 if (resp && resp.success) {
358 setStatus('allowed', localState.cookieDays);
359 closeOverlay();
360 } else {
361 const msg = resp && resp.data && resp.data.message ? resp.data.message : dobConfig.errors.denied;
362 showInlineMessage(msg);
363 setStatus('denied', localState.cookieDays);
364 }
365 })
366 .catch(() => {
367 showInlineMessage(dobConfig.errors.invalid);
368 })
369 .finally(() => {
370 submitBtn.disabled = false;
371 submitBtn.textContent = data.texts.yes;
372 });
373 });
374
375 declineBtn.addEventListener('click', () => {
376 setStatus('denied', localState.cookieDays);
377 if (data.behaviour.denyAction === 'redirect' && data.behaviour.denyRedirectUrl) {
378 window.location.href = data.behaviour.denyRedirectUrl;
379 } else {
380 renderBlocked(wrapper, data);
381 }
382 });
383
384 buttons.appendChild(submitBtn);
385 buttons.appendChild(declineBtn);
386
387 appendLogo(wrapper, data.design);
388 wrapper.appendChild(title);
389 wrapper.appendChild(subtitle);
390 wrapper.appendChild(grid);
391 if (consent) {
392 wrapper.appendChild(consent.container);
393 }
394 wrapper.appendChild(buttons);
395 wrapper.appendChild(message);
396
397 function showInlineMessage(text) {
398 message.textContent = text;
399 message.style.display = 'block';
400 }
401 }
402
403 /**
404 * Builds DOB inputs based on format.
405 */
406 function buildDobInputs(format) {
407 const day = document.createElement('input');
408 day.type = 'number';
409 day.min = '1';
410 day.max = '31';
411 day.placeholder = 'DD';
412 day.className = 'king-addons-age-gate__input';
413
414 const month = document.createElement('input');
415 month.type = 'number';
416 month.min = '1';
417 month.max = '12';
418 month.placeholder = 'MM';
419 month.className = 'king-addons-age-gate__input';
420
421 const year = document.createElement('input');
422 year.type = 'number';
423 year.min = '1900';
424 year.placeholder = 'YYYY';
425 year.className = 'king-addons-age-gate__input';
426
427 const parts = [];
428 if (format === 'mdy') {
429 parts.push(month, day, year);
430 } else if (format === 'ymd') {
431 parts.push(year, month, day);
432 } else {
433 parts.push(day, month, year);
434 }
435
436 parts.day = day;
437 parts.month = month;
438 parts.year = year;
439 return parts;
440 }
441
442 /**
443 * Renders blocked message.
444 */
445 function renderBlocked(wrapper, data) {
446 wrapper.innerHTML = '';
447 const title = document.createElement('h2');
448 title.className = 'king-addons-age-gate__title';
449 title.textContent = data.texts.title;
450
451 const message = document.createElement('p');
452 message.className = 'king-addons-age-gate__message';
453 message.style.display = 'block';
454 message.textContent = data.behaviour.blockMessage || data.texts.block;
455
456 appendLogo(wrapper, data.design);
457 wrapper.appendChild(title);
458 wrapper.appendChild(message);
459
460 if (data.behaviour.denyRedirectUrl) {
461 const buttons = document.createElement('div');
462 buttons.className = 'king-addons-age-gate__buttons';
463 const goBtn = buildButton('king-addons-age-gate__button king-addons-age-gate__button--allow', data.texts.no);
464 goBtn.addEventListener('click', () => {
465 window.location.href = data.behaviour.denyRedirectUrl;
466 });
467 buttons.appendChild(goBtn);
468 wrapper.appendChild(buttons);
469 }
470 }
471
472 /**
473 * Builds consent checkbox if required.
474 */
475 function buildConsent(data) {
476 if (!data.behaviour || !data.behaviour.consentCheckbox) {
477 return null;
478 }
479 const container = document.createElement('label');
480 container.className = 'king-addons-age-gate__subtitle';
481 const checkbox = document.createElement('input');
482 checkbox.type = 'checkbox';
483 checkbox.style.marginRight = '8px';
484 container.appendChild(checkbox);
485 const labelText = data.behaviour.consentLabel || 'I agree to the policy.';
486 container.appendChild(document.createTextNode(labelText));
487 return {container, checkbox};
488 }
489
490 /**
491 * Utility to create button.
492 */
493 function buildButton(classes, text) {
494 const button = document.createElement('button');
495 button.type = 'button';
496 button.className = classes;
497 button.textContent = text;
498 return button;
499 }
500
501 /**
502 * Appends logo if provided.
503 */
504 function appendLogo(wrapper, design) {
505 if (!design || !design.logo) {
506 return;
507 }
508 const logo = document.createElement('img');
509 logo.src = design.logo;
510 logo.alt = '';
511 logo.className = 'king-addons-age-gate__logo';
512 wrapper.appendChild(logo);
513 }
514
515 /**
516 * Shows a toast-like message within wrapper.
517 */
518 function showMessage(wrapper, text) {
519 let message = wrapper.querySelector('.king-addons-age-gate__message');
520 if (!message) {
521 message = document.createElement('div');
522 message.className = 'king-addons-age-gate__message';
523 wrapper.appendChild(message);
524 }
525 message.textContent = text;
526 message.style.display = 'block';
527 }
528
529 /**
530 * Sets cookie and updates state.
531 */
532 function setStatus(status, days) {
533 if (payload && payload.cookie && payload.cookie.name) {
534 const revision = payload.cookie.revision ? payload.cookie.revision : '';
535 const value = encodeURIComponent(status + '|' + revision);
536 let cookie = payload.cookie.name + '=' + value + '; path=/; SameSite=Lax';
537 if (payload.cookie.domain) {
538 cookie += '; domain=' + payload.cookie.domain;
539 }
540 if (days > 0) {
541 const date = new Date();
542 date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
543 cookie += '; expires=' + date.toUTCString();
544 }
545 document.cookie = cookie;
546 }
547
548 if (state) {
549 state.status = status;
550 }
551 }
552
553 /**
554 * Locks document scroll.
555 */
556 function lockScroll() {
557 document.documentElement.dataset.kaAgeGateScroll = document.documentElement.style.overflow || '';
558 document.documentElement.style.overflow = 'hidden';
559 }
560
561 /**
562 * Restores scroll and hides overlay.
563 */
564 function closeOverlay() {
565 const previous = document.documentElement.dataset.kaAgeGateScroll || '';
566 document.documentElement.style.overflow = previous;
567 if (root) {
568 root.classList.remove('king-addons-age-gate--visible');
569 }
570 }
571
572 /**
573 * Cookie days resolver based on behaviour.
574 */
575 function resolveCookieDays(data) {
576 if (data.behaviour && data.behaviour.repeatMode) {
577 if (data.behaviour.repeatMode === 'session') {
578 return 0;
579 }
580 if (data.behaviour.repeatMode === 'once') {
581 return 3650;
582 }
583 if (data.behaviour.repeatMode === 'days') {
584 return data.behaviour.repeatDays || data.cookie.days || 30;
585 }
586 }
587 return data.cookie && data.cookie.days ? data.cookie.days : 30;
588 }
589 })();
590
591
592
593