chunk-025-cookie-notice.js

I’ll build this as a standalone advanced cookie-notice chunk for RX Theme: privacy-safe defaults, accessibility, consent categories, Google Consent Mode hooks, localStorage/cookie fallback, and reusable public API.

My dear friend, create this file:

assets/static-js/static-chunks-js/chunk-025-cookie-notice.js

Then paste this full advanced code inside it.

/*!
 * RX Theme - Advanced Cookie Notice Chunk
 * File: assets/static-js/static-chunks-js/chunk-025-cookie-notice.js
 * Version: 1.0.0
 *
 * Features:
 * - Lightweight vanilla JavaScript
 * - GDPR-style consent banner
 * - Accept all / reject all / save preferences
 * - Consent categories: necessary, analytics, marketing, preferences
 * - LocalStorage + cookie fallback
 * - Google Consent Mode compatible dataLayer push
 * - Accessibility friendly modal
 * - Keyboard support
 * - Public API: window.RXCookieNotice
 */

(function () {
  'use strict';

  /**
   * Prevent double loading
   */
  if (window.RXCookieNotice && window.RXCookieNotice.__loaded) {
    return;
  }

  /**
   * Main configuration
   */
  var RX_COOKIE_CONFIG = {
    storageKey: 'rx_theme_cookie_consent',
    cookieName: 'rx_theme_cookie_consent',
    version: '1.0.0',
    expireDays: 180,
    showDelay: 600,

    bannerPosition: 'bottom', // bottom, top, center
    autoBlock: false,

    siteName: document.title || 'RX Theme',

    categories: {
      necessary: {
        label: 'Necessary cookies',
        description:
          'These cookies are required for basic website functions such as security, page navigation, form protection, login state, and accessibility.',
        required: true,
        defaultValue: true
      },
      preferences: {
        label: 'Preference cookies',
        description:
          'These cookies remember choices such as theme mode, language, layout, font size, and other display preferences.',
        required: false,
        defaultValue: false
      },
      analytics: {
        label: 'Analytics cookies',
        description:
          'These cookies help us understand how visitors use the website, which pages are helpful, and how we can improve speed and content quality.',
        required: false,
        defaultValue: false
      },
      marketing: {
        label: 'Marketing cookies',
        description:
          'These cookies may be used to measure campaigns, personalize content, or support third-party advertising tools.',
        required: false,
        defaultValue: false
      }
    },

    text: {
      title: 'Cookie preferences',
      message:
        'We use cookies to improve your browsing experience, measure website performance, and provide useful content. You can accept all cookies, reject optional cookies, or choose your preferences.',
      acceptAll: 'Accept all',
      rejectAll: 'Reject optional',
      customize: 'Customize',
      save: 'Save preferences',
      close: 'Close',
      back: 'Back',
      privacyPolicy: 'Privacy Policy',
      manageButton: 'Cookie settings'
    },

    links: {
      privacyPolicyUrl: '/privacy-policy/'
    },

    classNames: {
      root: 'rx-cookie-notice',
      overlay: 'rx-cookie-overlay',
      banner: 'rx-cookie-banner',
      modal: 'rx-cookie-modal',
      hidden: 'rx-cookie-hidden',
      manageButton: 'rx-cookie-manage-button'
    },

    debug: false
  };

  /**
   * Utility helpers
   */
  var RXCookieUtils = {
    log: function () {
      if (!RX_COOKIE_CONFIG.debug) return;
      if (window.console && typeof window.console.log === 'function') {
        console.log.apply(console, ['[RX Cookie Notice]'].concat([].slice.call(arguments)));
      }
    },

    now: function () {
      return new Date().toISOString();
    },

    safeJsonParse: function (value) {
      try {
        return JSON.parse(value);
      } catch (error) {
        return null;
      }
    },

    safeJsonStringify: function (value) {
      try {
        return JSON.stringify(value);
      } catch (error) {
        return '';
      }
    },

    escapeHtml: function (value) {
      return String(value || '')
        .replace(/&/g, '&')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#039;');
    },

    hasLocalStorage: function () {
      try {
        var key = '__rx_cookie_test__';
        window.localStorage.setItem(key, key);
        window.localStorage.removeItem(key);
        return true;
      } catch (error) {
        return false;
      }
    },

    setCookie: function (name, value, days) {
      var expires = '';
      var secure = window.location.protocol === 'https:' ? '; Secure' : '';

      if (days) {
        var date = new Date();
        date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
        expires = '; expires=' + date.toUTCString();
      }

      document.cookie =
        encodeURIComponent(name) +
        '=' +
        encodeURIComponent(value) +
        expires +
        '; path=/; SameSite=Lax' +
        secure;
    },

    getCookie: function (name) {
      var nameEQ = encodeURIComponent(name) + '=';
      var cookies = document.cookie ? document.cookie.split(';') : [];

      for (var i = 0; i < cookies.length; i++) {
        var cookie = cookies[i];

        while (cookie.charAt(0) === ' ') {
          cookie = cookie.substring(1);
        }

        if (cookie.indexOf(nameEQ) === 0) {
          return decodeURIComponent(cookie.substring(nameEQ.length));
        }
      }

      return null;
    },

    removeCookie: function (name) {
      document.cookie =
        encodeURIComponent(name) +
        '=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; SameSite=Lax';
    },

    dispatch: function (eventName, detail) {
      var event;

      try {
        event = new CustomEvent(eventName, {
          detail: detail || {},
          bubbles: true
        });
      } catch (error) {
        event = document.createEvent('CustomEvent');
        event.initCustomEvent(eventName, true, true, detail || {});
      }

      document.dispatchEvent(event);
    },

    focusFirst: function (container) {
      if (!container) return;

      var focusable = container.querySelector(
        'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
      );

      if (focusable) {
        focusable.focus();
      }
    },

    trapFocus: function (container, event) {
      if (!container || event.key !== 'Tab') return;

      var focusable = container.querySelectorAll(
        'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
      );

      if (!focusable.length) return;

      var first = focusable[0];
      var last = focusable[focusable.length - 1];

      if (event.shiftKey && document.activeElement === first) {
        event.preventDefault();
        last.focus();
      } else if (!event.shiftKey && document.activeElement === last) {
        event.preventDefault();
        first.focus();
      }
    }
  };

  /**
   * Storage layer
   */
  var RXCookieStorage = {
    get: function () {
      var raw = null;

      if (RXCookieUtils.hasLocalStorage()) {
        raw = window.localStorage.getItem(RX_COOKIE_CONFIG.storageKey);
      }

      if (!raw) {
        raw = RXCookieUtils.getCookie(RX_COOKIE_CONFIG.cookieName);
      }

      if (!raw) return null;

      var parsed = RXCookieUtils.safeJsonParse(raw);

      if (!parsed || !parsed.categories) {
        return null;
      }

      return parsed;
    },

    set: function (data) {
      var value = RXCookieUtils.safeJsonStringify(data);

      if (!value) return false;

      if (RXCookieUtils.hasLocalStorage()) {
        window.localStorage.setItem(RX_COOKIE_CONFIG.storageKey, value);
      }

      RXCookieUtils.setCookie(
        RX_COOKIE_CONFIG.cookieName,
        value,
        RX_COOKIE_CONFIG.expireDays
      );

      return true;
    },

    clear: function () {
      if (RXCookieUtils.hasLocalStorage()) {
        window.localStorage.removeItem(RX_COOKIE_CONFIG.storageKey);
      }

      RXCookieUtils.removeCookie(RX_COOKIE_CONFIG.cookieName);
    }
  };

  /**
   * Consent logic
   */
  var RXConsent = {
    getDefaultCategories: function () {
      var result = {};
      var categories = RX_COOKIE_CONFIG.categories;

      Object.keys(categories).forEach(function (key) {
        result[key] = !!categories[key].defaultValue;
      });

      return result;
    },

    getAllAcceptedCategories: function () {
      var result = {};
      var categories = RX_COOKIE_CONFIG.categories;

      Object.keys(categories).forEach(function (key) {
        result[key] = true;
      });

      return result;
    },

    getOnlyRequiredCategories: function () {
      var result = {};
      var categories = RX_COOKIE_CONFIG.categories;

      Object.keys(categories).forEach(function (key) {
        result[key] = !!categories[key].required;
      });

      return result;
    },

    normalizeCategories: function (input) {
      var result = {};
      var categories = RX_COOKIE_CONFIG.categories;

      Object.keys(categories).forEach(function (key) {
        if (categories[key].required) {
          result[key] = true;
        } else {
          result[key] = !!input[key];
        }
      });

      return result;
    },

    createConsentObject: function (categories, source) {
      return {
        version: RX_COOKIE_CONFIG.version,
        siteName: RX_COOKIE_CONFIG.siteName,
        source: source || 'unknown',
        categories: RXConsent.normalizeCategories(categories || {}),
        createdAt: RXCookieUtils.now(),
        updatedAt: RXCookieUtils.now()
      };
    },

    isExpiredOrOutdated: function (consent) {
      if (!consent) return true;
      if (consent.version !== RX_COOKIE_CONFIG.version) return true;
      if (!consent.createdAt) return false;

      var created = new Date(consent.createdAt).getTime();
      var now = Date.now();
      var maxAge = RX_COOKIE_CONFIG.expireDays * 24 * 60 * 60 * 1000;

      return now - created > maxAge;
    },

    hasConsent: function () {
      var consent = RXCookieStorage.get();
      return !!consent && !RXConsent.isExpiredOrOutdated(consent);
    },

    getConsent: function () {
      var consent = RXCookieStorage.get();

      if (!consent || RXConsent.isExpiredOrOutdated(consent)) {
        return null;
      }

      return consent;
    },

    saveConsent: function (categories, source) {
      var existing = RXCookieStorage.get();
      var consent = RXConsent.createConsentObject(categories, source);

      if (existing && existing.createdAt) {
        consent.createdAt = existing.createdAt;
      }

      consent.updatedAt = RXCookieUtils.now();

      RXCookieStorage.set(consent);
      RXConsent.applyConsent(consent);

      RXCookieUtils.dispatch('rxCookieConsentUpdated', consent);

      return consent;
    },

    acceptAll: function () {
      return RXConsent.saveConsent(
        RXConsent.getAllAcceptedCategories(),
        'accept_all'
      );
    },

    rejectAll: function () {
      return RXConsent.saveConsent(
        RXConsent.getOnlyRequiredCategories(),
        'reject_all'
      );
    },

    reset: function () {
      RXCookieStorage.clear();
      RXCookieUtils.dispatch('rxCookieConsentReset', {});
    },

    isAllowed: function (category) {
      var consent = RXConsent.getConsent();

      if (!consent || !consent.categories) {
        return !!RX_COOKIE_CONFIG.categories[category]?.required;
      }

      return !!consent.categories[category];
    },

    applyConsent: function (consent) {
      if (!consent || !consent.categories) return;

      RXConsent.updateHtmlAttributes(consent);
      RXConsent.pushDataLayer(consent);
      RXConsent.runAllowedScripts(consent);
    },

    updateHtmlAttributes: function (consent) {
      var root = document.documentElement;

      root.setAttribute('data-rx-cookie-consent', 'set');

      Object.keys(consent.categories).forEach(function (key) {
        root.setAttribute(
          'data-rx-consent-' + key,
          consent.categories[key] ? 'yes' : 'no'
        );
      });
    },

    pushDataLayer: function (consent) {
      window.dataLayer = window.dataLayer || [];

      var analyticsGranted = consent.categories.analytics ? 'granted' : 'denied';
      var marketingGranted = consent.categories.marketing ? 'granted' : 'denied';
      var preferencesGranted = consent.categories.preferences ? 'granted' : 'denied';

      window.dataLayer.push({
        event: 'rx_cookie_consent_update',
        rx_cookie_consent: consent.categories
      });

      window.dataLayer.push({
        event: 'default_consent',
        ad_storage: marketingGranted,
        ad_user_data: marketingGranted,
        ad_personalization: marketingGranted,
        analytics_storage: analyticsGranted,
        functionality_storage: preferencesGranted,
        personalization_storage: preferencesGranted,
        security_storage: 'granted'
      });

      if (typeof window.gtag === 'function') {
        window.gtag('consent', 'update', {
          ad_storage: marketingGranted,
          ad_user_data: marketingGranted,
          ad_personalization: marketingGranted,
          analytics_storage: analyticsGranted,
          functionality_storage: preferencesGranted,
          personalization_storage: preferencesGranted,
          security_storage: 'granted'
        });
      }
    },

    runAllowedScripts: function (consent) {
      var scripts = document.querySelectorAll('script[type="text/plain"][data-rx-cookie-category]');

      scripts.forEach(function (script) {
        var category = script.getAttribute('data-rx-cookie-category');

        if (!category || !consent.categories[category]) {
          return;
        }

        if (script.getAttribute('data-rx-cookie-loaded') === '1') {
          return;
        }

        var newScript = document.createElement('script');

        Array.prototype.slice.call(script.attributes).forEach(function (attr) {
          if (
            attr.name !== 'type' &&
            attr.name !== 'data-rx-cookie-category' &&
            attr.name !== 'data-rx-cookie-loaded'
          ) {
            newScript.setAttribute(attr.name, attr.value);
          }
        });

        if (script.src) {
          newScript.src = script.src;
        } else {
          newScript.text = script.text || script.textContent || script.innerHTML || '';
        }

        script.setAttribute('data-rx-cookie-loaded', '1');
        script.parentNode.insertBefore(newScript, script.nextSibling);
      });
    }
  };

  /**
   * UI manager
   */
  var RXCookieUI = {
    root: null,
    banner: null,
    modal: null,
    overlay: null,
    lastFocusedElement: null,

    init: function () {
      RXCookieUI.injectStyles();
      RXCookieUI.createManageButton();

      if (!RXConsent.hasConsent()) {
        window.setTimeout(function () {
          RXCookieUI.showBanner();
        }, RX_COOKIE_CONFIG.showDelay);
      } else {
        RXConsent.applyConsent(RXConsent.getConsent());
      }

      RXCookieUI.bindGlobalEvents();
    },

    injectStyles: function () {
      if (document.getElementById('rx-cookie-notice-style')) {
        return;
      }

      var css = `
        .rx-cookie-hidden {
          display: none !important;
        }

        .rx-cookie-notice,
        .rx-cookie-notice *,
        .rx-cookie-overlay,
        .rx-cookie-overlay * {
          box-sizing: border-box;
        }

        .rx-cookie-banner {
          position: fixed;
          left: 16px;
          right: 16px;
          bottom: 16px;
          z-index: 999999;
          max-width: 1120px;
          margin: 0 auto;
          padding: 20px;
          border-radius: 18px;
          background: #ffffff;
          color: #172033;
          box-shadow: 0 20px 60px rgba(15, 23, 42, 0.24);
          border: 1px solid rgba(15, 23, 42, 0.10);
          font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
        }

        .rx-cookie-banner[data-position="top"] {
          top: 16px;
          bottom: auto;
        }

        .rx-cookie-banner[data-position="center"] {
          top: 50%;
          bottom: auto;
          transform: translateY(-50%);
          max-width: 720px;
        }

        .rx-cookie-banner__inner {
          display: grid;
          grid-template-columns: 1fr auto;
          gap: 20px;
          align-items: center;
        }

        .rx-cookie-banner__title {
          margin: 0 0 8px;
          font-size: 18px;
          line-height: 1.3;
          font-weight: 700;
        }

        .rx-cookie-banner__message {
          margin: 0;
          font-size: 14px;
          line-height: 1.7;
          color: #475569;
        }

        .rx-cookie-banner__link {
          color: #075985;
          text-decoration: underline;
          text-underline-offset: 3px;
        }

        .rx-cookie-actions {
          display: flex;
          flex-wrap: wrap;
          gap: 10px;
          justify-content: flex-end;
        }

        .rx-cookie-btn {
          display: inline-flex;
          align-items: center;
          justify-content: center;
          min-height: 42px;
          padding: 10px 16px;
          border-radius: 999px;
          border: 1px solid transparent;
          background: #f1f5f9;
          color: #0f172a;
          font-size: 14px;
          font-weight: 700;
          line-height: 1.2;
          cursor: pointer;
          transition: transform 160ms ease, box-shadow 160ms ease, background 160ms ease;
        }

        .rx-cookie-btn:hover {
          transform: translateY(-1px);
          box-shadow: 0 8px 18px rgba(15, 23, 42, 0.15);
        }

        .rx-cookie-btn:focus {
          outline: 3px solid rgba(14, 165, 233, 0.35);
          outline-offset: 2px;
        }

        .rx-cookie-btn--primary {
          background: #0f766e;
          color: #ffffff;
        }

        .rx-cookie-btn--dark {
          background: #0f172a;
          color: #ffffff;
        }

        .rx-cookie-btn--ghost {
          background: transparent;
          color: #334155;
          border-color: rgba(15, 23, 42, 0.15);
        }

        .rx-cookie-overlay {
          position: fixed;
          inset: 0;
          z-index: 1000000;
          display: flex;
          align-items: center;
          justify-content: center;
          padding: 20px;
          background: rgba(15, 23, 42, 0.58);
          backdrop-filter: blur(6px);
        }

        .rx-cookie-modal {
          width: min(720px, 100%);
          max-height: min(760px, 90vh);
          overflow: auto;
          border-radius: 22px;
          background: #ffffff;
          color: #172033;
          box-shadow: 0 30px 90px rgba(0, 0, 0, 0.35);
          font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
        }

        .rx-cookie-modal__header {
          display: flex;
          justify-content: space-between;
          gap: 16px;
          align-items: flex-start;
          padding: 24px 24px 12px;
          border-bottom: 1px solid rgba(15, 23, 42, 0.08);
        }

        .rx-cookie-modal__title {
          margin: 0;
          font-size: 22px;
          line-height: 1.3;
        }

        .rx-cookie-modal__desc {
          margin: 10px 0 0;
          color: #475569;
          font-size: 14px;
          line-height: 1.7;
        }

        .rx-cookie-modal__close {
          width: 40px;
          height: 40px;
          border-radius: 999px;
          border: 1px solid rgba(15, 23, 42, 0.12);
          background: #f8fafc;
          color: #0f172a;
          cursor: pointer;
          font-size: 20px;
          line-height: 1;
        }

        .rx-cookie-modal__body {
          padding: 18px 24px;
        }

        .rx-cookie-category {
          display: grid;
          grid-template-columns: 1fr auto;
          gap: 16px;
          padding: 16px 0;
          border-bottom: 1px solid rgba(15, 23, 42, 0.08);
        }

        .rx-cookie-category:last-child {
          border-bottom: 0;
        }

        .rx-cookie-category__title {
          margin: 0 0 6px;
          font-size: 16px;
          line-height: 1.4;
          font-weight: 700;
        }

        .rx-cookie-category__desc {
          margin: 0;
          color: #64748b;
          font-size: 14px;
          line-height: 1.7;
        }

        .rx-cookie-switch {
          position: relative;
          display: inline-flex;
          width: 54px;
          height: 30px;
          flex: 0 0 auto;
        }

        .rx-cookie-switch input {
          position: absolute;
          opacity: 0;
          pointer-events: none;
        }

        .rx-cookie-slider {
          position: absolute;
          inset: 0;
          cursor: pointer;
          border-radius: 999px;
          background: #cbd5e1;
          transition: background 160ms ease;
        }

        .rx-cookie-slider::before {
          content: "";
          position: absolute;
          width: 24px;
          height: 24px;
          left: 3px;
          top: 3px;
          border-radius: 50%;
          background: #ffffff;
          box-shadow: 0 2px 8px rgba(15, 23, 42, 0.25);
          transition: transform 160ms ease;
        }

        .rx-cookie-switch input:checked + .rx-cookie-slider {
          background: #0f766e;
        }

        .rx-cookie-switch input:checked + .rx-cookie-slider::before {
          transform: translateX(24px);
        }

        .rx-cookie-switch input:disabled + .rx-cookie-slider {
          cursor: not-allowed;
          opacity: 0.7;
        }

        .rx-cookie-modal__footer {
          display: flex;
          flex-wrap: wrap;
          justify-content: flex-end;
          gap: 10px;
          padding: 18px 24px 24px;
          border-top: 1px solid rgba(15, 23, 42, 0.08);
        }

        .rx-cookie-manage-button {
          position: fixed;
          left: 16px;
          bottom: 16px;
          z-index: 999998;
          width: 46px;
          height: 46px;
          border-radius: 999px;
          border: 1px solid rgba(15, 23, 42, 0.12);
          background: #ffffff;
          color: #0f172a;
          box-shadow: 0 10px 28px rgba(15, 23, 42, 0.18);
          cursor: pointer;
          font-size: 20px;
          display: none;
          align-items: center;
          justify-content: center;
        }

        .rx-cookie-manage-button.is-visible {
          display: inline-flex;
        }

        @media (max-width: 768px) {
          .rx-cookie-banner {
            left: 10px;
            right: 10px;
            bottom: 10px;
            padding: 16px;
            border-radius: 16px;
          }

          .rx-cookie-banner__inner {
            grid-template-columns: 1fr;
          }

          .rx-cookie-actions {
            justify-content: stretch;
          }

          .rx-cookie-btn {
            width: 100%;
          }

          .rx-cookie-category {
            grid-template-columns: 1fr;
          }

          .rx-cookie-modal__footer {
            justify-content: stretch;
          }
        }

        @media (prefers-color-scheme: dark) {
          .rx-cookie-banner,
          .rx-cookie-modal,
          .rx-cookie-manage-button {
            background: #0f172a;
            color: #f8fafc;
            border-color: rgba(255, 255, 255, 0.12);
          }

          .rx-cookie-banner__message,
          .rx-cookie-modal__desc,
          .rx-cookie-category__desc {
            color: #cbd5e1;
          }

          .rx-cookie-btn--ghost {
            color: #f8fafc;
            border-color: rgba(255, 255, 255, 0.18);
          }

          .rx-cookie-modal__close {
            background: #1e293b;
            color: #f8fafc;
            border-color: rgba(255, 255, 255, 0.14);
          }

          .rx-cookie-modal__header,
          .rx-cookie-modal__footer,
          .rx-cookie-category {
            border-color: rgba(255, 255, 255, 0.10);
          }
        }
      `;

      var style = document.createElement('style');
      style.id = 'rx-cookie-notice-style';
      style.textContent = css;
      document.head.appendChild(style);
    },

    createBanner: function () {
      if (RXCookieUI.banner) return RXCookieUI.banner;

      var config = RX_COOKIE_CONFIG;
      var privacyLink = '';

      if (config.links.privacyPolicyUrl) {
        privacyLink =
          ' <a class="rx-cookie-banner__link" href="' +
          RXCookieUtils.escapeHtml(config.links.privacyPolicyUrl) +
          '">' +
          RXCookieUtils.escapeHtml(config.text.privacyPolicy) +
          '</a>';
      }

      var banner = document.createElement('div');
      banner.className = config.classNames.banner;
      banner.setAttribute('role', 'region');
      banner.setAttribute('aria-label', 'Cookie notice');
      banner.setAttribute('data-position', config.bannerPosition);

      banner.innerHTML =
        '<div class="rx-cookie-banner__inner">' +
        '<div class="rx-cookie-banner__content">' +
        '<h2 class="rx-cookie-banner__title">' +
        RXCookieUtils.escapeHtml(config.text.title) +
        '</h2>' +
        '<p class="rx-cookie-banner__message">' +
        RXCookieUtils.escapeHtml(config.text.message) +
        privacyLink +
        '</p>' +
        '</div>' +
        '<div class="rx-cookie-actions">' +
        '<button type="button" class="rx-cookie-btn rx-cookie-btn--ghost" data-rx-cookie-action="customize">' +
        RXCookieUtils.escapeHtml(config.text.customize) +
        '</button>' +
        '<button type="button" class="rx-cookie-btn" data-rx-cookie-action="reject">' +
        RXCookieUtils.escapeHtml(config.text.rejectAll) +
        '</button>' +
        '<button type="button" class="rx-cookie-btn rx-cookie-btn--primary" data-rx-cookie-action="accept">' +
        RXCookieUtils.escapeHtml(config.text.acceptAll) +
        '</button>' +
        '</div>' +
        '</div>';

      document.body.appendChild(banner);
      RXCookieUI.banner = banner;

      banner.addEventListener('click', RXCookieUI.handleBannerClick);

      return banner;
    },

    showBanner: function () {
      var banner = RXCookieUI.createBanner();
      banner.classList.remove(RX_COOKIE_CONFIG.classNames.hidden);
    },

    hideBanner: function () {
      if (RXCookieUI.banner) {
        RXCookieUI.banner.classList.add(RX_COOKIE_CONFIG.classNames.hidden);
      }

      RXCookieUI.showManageButton();
    },

    handleBannerClick: function (event) {
      var button = event.target.closest('[data-rx-cookie-action]');
      if (!button) return;

      var action = button.getAttribute('data-rx-cookie-action');

      if (action === 'accept') {
        RXConsent.acceptAll();
        RXCookieUI.hideBanner();
      }

      if (action === 'reject') {
        RXConsent.rejectAll();
        RXCookieUI.hideBanner();
      }

      if (action === 'customize') {
        RXCookieUI.openModal();
      }
    },

    createModal: function () {
      var config = RX_COOKIE_CONFIG;

      if (RXCookieUI.overlay) return RXCookieUI.overlay;

      var overlay = document.createElement('div');
      overlay.className = config.classNames.overlay + ' ' + config.classNames.hidden;
      overlay.setAttribute('role', 'presentation');

      var modal = document.createElement('div');
      modal.className = config.classNames.modal;
      modal.setAttribute('role', 'dialog');
      modal.setAttribute('aria-modal', 'true');
      modal.setAttribute('aria-labelledby', 'rx-cookie-modal-title');

      var currentConsent = RXConsent.getConsent();
      var activeCategories = currentConsent
        ? currentConsent.categories
        : RXConsent.getDefaultCategories();

      var categoriesHtml = Object.keys(config.categories)
        .map(function (key) {
          var item = config.categories[key];
          var checked = activeCategories[key] ? 'checked' : '';
          var disabled = item.required ? 'disabled' : '';
          var requiredText = item.required ? ' Required' : '';

          return (
            '<div class="rx-cookie-category">' +
            '<div>' +
            '<h3 class="rx-cookie-category__title">' +
            RXCookieUtils.escapeHtml(item.label) +
            '</h3>' +
            '<p class="rx-cookie-category__desc">' +
            RXCookieUtils.escapeHtml(item.description + requiredText) +
            '</p>' +
            '</div>' +
            '<label class="rx-cookie-switch" aria-label="' +
            RXCookieUtils.escapeHtml(item.label) +
            '">' +
            '<input type="checkbox" data-rx-cookie-category-input="' +
            RXCookieUtils.escapeHtml(key) +
            '" ' +
            checked +
            ' ' +
            disabled +
            '>' +
            '<span class="rx-cookie-slider"></span>' +
            '</label>' +
            '</div>'
          );
        })
        .join('');

      modal.innerHTML =
        '<div class="rx-cookie-modal__header">' +
        '<div>' +
        '<h2 class="rx-cookie-modal__title" id="rx-cookie-modal-title">' +
        RXCookieUtils.escapeHtml(config.text.title) +
        '</h2>' +
        '<p class="rx-cookie-modal__desc">' +
        RXCookieUtils.escapeHtml(config.text.message) +
        '</p>' +
        '</div>' +
        '<button type="button" class="rx-cookie-modal__close" data-rx-cookie-modal-close aria-label="' +
        RXCookieUtils.escapeHtml(config.text.close) +
        '">×</button>' +
        '</div>' +
        '<div class="rx-cookie-modal__body">' +
        categoriesHtml +
        '</div>' +
        '<div class="rx-cookie-modal__footer">' +
        '<button type="button" class="rx-cookie-btn rx-cookie-btn--ghost" data-rx-cookie-modal-action="reject">' +
        RXCookieUtils.escapeHtml(config.text.rejectAll) +
        '</button>' +
        '<button type="button" class="rx-cookie-btn rx-cookie-btn--dark" data-rx-cookie-modal-action="save">' +
        RXCookieUtils.escapeHtml(config.text.save) +
        '</button>' +
        '<button type="button" class="rx-cookie-btn rx-cookie-btn--primary" data-rx-cookie-modal-action="accept">' +
        RXCookieUtils.escapeHtml(config.text.acceptAll) +
        '</button>' +
        '</div>';

      overlay.appendChild(modal);
      document.body.appendChild(overlay);

      RXCookieUI.overlay = overlay;
      RXCookieUI.modal = modal;

      overlay.addEventListener('click', RXCookieUI.handleModalClick);
      document.addEventListener('keydown', RXCookieUI.handleKeydown);

      return overlay;
    },

    openModal: function () {
      var overlay = RXCookieUI.createModal();

      RXCookieUI.lastFocusedElement = document.activeElement;

      RXCookieUI.syncModalInputs();

      overlay.classList.remove(RX_COOKIE_CONFIG.classNames.hidden);
      document.documentElement.style.overflow = 'hidden';

      window.setTimeout(function () {
        RXCookieUtils.focusFirst(RXCookieUI.modal);
      }, 30);
    },

    closeModal: function () {
      if (RXCookieUI.overlay) {
        RXCookieUI.overlay.classList.add(RX_COOKIE_CONFIG.classNames.hidden);
      }

      document.documentElement.style.overflow = '';

      if (
        RXCookieUI.lastFocusedElement &&
        typeof RXCookieUI.lastFocusedElement.focus === 'function'
      ) {
        RXCookieUI.lastFocusedElement.focus();
      }
    },

    syncModalInputs: function () {
      if (!RXCookieUI.modal) return;

      var currentConsent = RXConsent.getConsent();
      var activeCategories = currentConsent
        ? currentConsent.categories
        : RXConsent.getDefaultCategories();

      var inputs = RXCookieUI.modal.querySelectorAll('[data-rx-cookie-category-input]');

      inputs.forEach(function (input) {
        var key = input.getAttribute('data-rx-cookie-category-input');

        if (RX_COOKIE_CONFIG.categories[key]?.required) {
          input.checked = true;
          input.disabled = true;
        } else {
          input.checked = !!activeCategories[key];
        }
      });
    },

    getModalValues: function () {
      var values = {};
      var inputs = RXCookieUI.modal.querySelectorAll('[data-rx-cookie-category-input]');

      inputs.forEach(function (input) {
        var key = input.getAttribute('data-rx-cookie-category-input');
        values[key] = !!input.checked;
      });

      return RXConsent.normalizeCategories(values);
    },

    handleModalClick: function (event) {
      var closeButton = event.target.closest('[data-rx-cookie-modal-close]');
      var actionButton = event.target.closest('[data-rx-cookie-modal-action]');

      if (event.target === RXCookieUI.overlay || closeButton) {
        RXCookieUI.closeModal();
        return;
      }

      if (!actionButton) return;

      var action = actionButton.getAttribute('data-rx-cookie-modal-action');

      if (action === 'accept') {
        RXConsent.acceptAll();
        RXCookieUI.hideBanner();
        RXCookieUI.closeModal();
      }

      if (action === 'reject') {
        RXConsent.rejectAll();
        RXCookieUI.hideBanner();
        RXCookieUI.closeModal();
      }

      if (action === 'save') {
        RXConsent.saveConsent(RXCookieUI.getModalValues(), 'save_preferences');
        RXCookieUI.hideBanner();
        RXCookieUI.closeModal();
      }
    },

    handleKeydown: function (event) {
      if (!RXCookieUI.overlay) return;
      if (RXCookieUI.overlay.classList.contains(RX_COOKIE_CONFIG.classNames.hidden)) return;

      if (event.key === 'Escape') {
        RXCookieUI.closeModal();
      }

      RXCookieUtils.trapFocus(RXCookieUI.modal, event);
    },

    createManageButton: function () {
      if (document.querySelector('.' + RX_COOKIE_CONFIG.classNames.manageButton)) {
        return;
      }

      var button = document.createElement('button');
      button.type = 'button';
      button.className = RX_COOKIE_CONFIG.classNames.manageButton;
      button.setAttribute('aria-label', RX_COOKIE_CONFIG.text.manageButton);
      button.title = RX_COOKIE_CONFIG.text.manageButton;
      button.innerHTML = '🍪';

      button.addEventListener('click', function () {
        RXCookieUI.openModal();
      });

      document.body.appendChild(button);

      if (RXConsent.hasConsent()) {
        RXCookieUI.showManageButton();
      }
    },

    showManageButton: function () {
      var button = document.querySelector('.' + RX_COOKIE_CONFIG.classNames.manageButton);

      if (button) {
        button.classList.add('is-visible');
      }
    },

    bindGlobalEvents: function () {
      document.addEventListener('rxOpenCookieSettings', function () {
        RXCookieUI.openModal();
      });

      document.addEventListener('click', function (event) {
        var opener = event.target.closest('[data-rx-open-cookie-settings]');
        if (!opener) return;

        event.preventDefault();
        RXCookieUI.openModal();
      });
    }
  };

  /**
   * Public API
   */
  window.RXCookieNotice = {
    __loaded: true,

    config: RX_COOKIE_CONFIG,

    init: function (customConfig) {
      if (customConfig && typeof customConfig === 'object') {
        RXCookieNotice.configure(customConfig);
      }

      if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', RXCookieUI.init);
      } else {
        RXCookieUI.init();
      }

      return window.RXCookieNotice;
    },

    configure: function (customConfig) {
      customConfig = customConfig || {};

      Object.keys(customConfig).forEach(function (key) {
        if (
          typeof customConfig[key] === 'object' &&
          customConfig[key] !== null &&
          !Array.isArray(customConfig[key]) &&
          typeof RX_COOKIE_CONFIG[key] === 'object'
        ) {
          RX_COOKIE_CONFIG[key] = Object.assign({}, RX_COOKIE_CONFIG[key], customConfig[key]);
        } else {
          RX_COOKIE_CONFIG[key] = customConfig[key];
        }
      });

      return window.RXCookieNotice;
    },

    openSettings: function () {
      RXCookieUI.openModal();
    },

    closeSettings: function () {
      RXCookieUI.closeModal();
    },

    acceptAll: function () {
      var consent = RXConsent.acceptAll();
      RXCookieUI.hideBanner();
      return consent;
    },

    rejectAll: function () {
      var consent = RXConsent.rejectAll();
      RXCookieUI.hideBanner();
      return consent;
    },

    save: function (categories) {
      var consent = RXConsent.saveConsent(categories || {}, 'api_save');
      RXCookieUI.hideBanner();
      return consent;
    },

    getConsent: function () {
      return RXConsent.getConsent();
    },

    hasConsent: function () {
      return RXConsent.hasConsent();
    },

    isAllowed: function (category) {
      return RXConsent.isAllowed(category);
    },

    reset: function () {
      RXConsent.reset();
      RXCookieUI.showBanner();
    }
  };

  /**
   * Auto init
   */
  window.RXCookieNotice.init();

})();

To open cookie settings from any footer link or button, use this HTML:

<a href="#" data-rx-open-cookie-settings>
  Cookie settings
</a>

To block optional scripts until consent, use this pattern:

<script
  type="text/plain"
  data-rx-cookie-category="analytics"
  src="https://example.com/analytics.js">
</script>

Available categories:

necessary
preferences
analytics
marketing

For your RX Theme, this chunk is best loaded with defer:

wp_enqueue_script(
    'rx-cookie-notice',
    get_template_directory_uri() . '/assets/static-js/static-chunks-js/chunk-025-cookie-notice.js',
    array(),
    '1.0.0',
    true
);

This file is now ready as an advanced reusable cookie notice system for your RX Theme.

Leave a Reply

Your email address will not be published. Required fields are marked *