chunks.js

I’ll build this as a safe “RX Theme chunks controller” file: performance helpers, lazy loading, accessibility, sticky header, search, dark mode, back-to-top, reading progress, TOC, comments, and small WordPress-friendly utilities.

Below is a large advanced inc/chunks/chunks.js file for your RX Theme. It includes many safe frontend functions: performance, lazy loading, accessibility, dark mode, sticky header, mobile menu, search overlay, reading progress, table of contents, accordion, tabs, copy buttons, back-to-top, external-link handling, smooth scroll, comments helper, share buttons, image lightbox, and more.

Create this file:

rx-theme/inc/chunks/chunks.js

Then paste all code below.

/*!
 * RX Theme - chunks.js
 * Advanced frontend controller for RX Theme
 * Author: RxHarun
 * Purpose: Performance, accessibility, UX, SEO-supportive frontend features.
 */

(function () {
  'use strict';

  /**
   * ============================================================
   * RX GLOBAL OBJECT
   * ============================================================
   */

  const RXTheme = {
    version: '1.0.0',
    selectors: {
      body: 'body',
      html: 'html',
      siteHeader: '.site-header, #site-header, header[role="banner"]',
      mainNav: '.main-navigation, .primary-navigation, #site-navigation',
      menuToggle: '.menu-toggle, .nav-toggle, .mobile-menu-toggle',
      searchToggle: '.search-toggle, .rx-search-toggle',
      searchOverlay: '.rx-search-overlay, .search-overlay',
      searchClose: '.rx-search-close, .search-close',
      backToTop: '.rx-back-to-top, .back-to-top',
      readingProgress: '.rx-reading-progress',
      toc: '.rx-table-of-contents, .table-of-contents',
      content: '.entry-content, .post-content, article',
      accordion: '.rx-accordion',
      tabs: '.rx-tabs',
      copyButton: '.rx-copy-button',
      lazyImage: 'img[loading="lazy"], img[data-src]',
      lightboxImage: '.entry-content img, .post-content img',
      commentForm: '#commentform',
      externalLinks: 'a[href^="http"]'
    },

    settings: {
      stickyHeaderOffset: 120,
      backToTopOffset: 500,
      readingProgressEnabled: true,
      smoothScrollOffset: 90,
      darkModeStorageKey: 'rx_theme_dark_mode',
      fontStorageKey: 'rx_theme_font_size',
      animationClass: 'rx-animate-in',
      activeClass: 'is-active',
      openClass: 'is-open',
      fixedClass: 'is-fixed',
      hiddenClass: 'is-hidden',
      loadedClass: 'is-loaded'
    },

    state: {
      lastScrollY: 0,
      ticking: false,
      isMobileMenuOpen: false,
      isSearchOpen: false,
      reducedMotion: false
    },

    init() {
      this.cache();
      this.detectPreferences();
      this.addBodyClasses();
      this.bindGlobalEvents();

      this.initNoJS();
      this.initStickyHeader();
      this.initMobileMenu();
      this.initSearchOverlay();
      this.initBackToTop();
      this.initReadingProgress();
      this.initSmoothScroll();
      this.initLazyImages();
      this.initExternalLinks();
      this.initTableOfContents();
      this.initAccordions();
      this.initTabs();
      this.initCopyButtons();
      this.initShareButtons();
      this.initCommentFormEnhancer();
      this.initImageLightbox();
      this.initFormValidation();
      this.initDarkMode();
      this.initFontSizeControls();
      this.initScrollAnimations();
      this.initPrintHelpers();
      this.initResponsiveTables();
      this.initVideoEmbeds();
      this.initCodeBlocks();
      this.initBreadcrumbEnhancer();
      this.initKeyboardAccessibility();
      this.initSkipLinks();
      this.initCookieNotice();
      this.initNewsletterBox();
      this.initAjaxLoadMorePlaceholder();
      this.initServiceWorkerHelper();

      this.markLoaded();
    },

    cache() {
      this.html = document.documentElement;
      this.body = document.body;
      this.header = document.querySelector(this.selectors.siteHeader);
      this.mainNav = document.querySelector(this.selectors.mainNav);
    },

    detectPreferences() {
      this.state.reducedMotion = window.matchMedia &&
        window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    },

    addBodyClasses() {
      if (!this.body) return;

      this.body.classList.add('rx-js-enabled');

      if ('IntersectionObserver' in window) {
        this.body.classList.add('rx-supports-intersection-observer');
      }

      if ('localStorage' in window) {
        this.body.classList.add('rx-supports-local-storage');
      }

      if (this.state.reducedMotion) {
        this.body.classList.add('rx-reduced-motion');
      }
    },

    bindGlobalEvents() {
      window.addEventListener('scroll', () => {
        this.requestTick();
      }, { passive: true });

      window.addEventListener('resize', this.debounce(() => {
        this.handleResize();
      }, 200));

      document.addEventListener('keydown', (event) => {
        this.handleGlobalKeydown(event);
      });
    },

    requestTick() {
      if (!this.state.ticking) {
        window.requestAnimationFrame(() => {
          this.handleScroll();
          this.state.ticking = false;
        });

        this.state.ticking = true;
      }
    },

    handleScroll() {
      const scrollY = window.scrollY || window.pageYOffset;

      this.updateStickyHeader(scrollY);
      this.updateBackToTop(scrollY);
      this.updateReadingProgress(scrollY);

      this.state.lastScrollY = scrollY;
    },

    handleResize() {
      this.closeMobileMenuOnDesktop();
      this.refreshReadingProgress();
      this.refreshResponsiveTables();
    },

    handleGlobalKeydown(event) {
      if (event.key === 'Escape') {
        this.closeSearchOverlay();
        this.closeMobileMenu();
        this.closeLightbox();
      }
    },

    markLoaded() {
      window.addEventListener('load', () => {
        this.body.classList.add(this.settings.loadedClass);
      });
    },

    /**
     * ============================================================
     * BASIC NO-JS SUPPORT
     * ============================================================
     */

    initNoJS() {
      if (!this.html) return;
      this.html.classList.remove('no-js');
      this.html.classList.add('js');
    },

    /**
     * ============================================================
     * STICKY HEADER
     * ============================================================
     */

    initStickyHeader() {
      if (!this.header) return;

      this.header.classList.add('rx-sticky-ready');

      const placeholder = document.createElement('div');
      placeholder.className = 'rx-header-placeholder';
      placeholder.style.display = 'none';

      this.header.parentNode.insertBefore(placeholder, this.header.nextSibling);
      this.headerPlaceholder = placeholder;
    },

    updateStickyHeader(scrollY) {
      if (!this.header) return;

      const offset = this.settings.stickyHeaderOffset;

      if (scrollY > offset) {
        this.header.classList.add(this.settings.fixedClass);

        if (this.headerPlaceholder) {
          this.headerPlaceholder.style.height = `${this.header.offsetHeight}px`;
          this.headerPlaceholder.style.display = 'block';
        }
      } else {
        this.header.classList.remove(this.settings.fixedClass);

        if (this.headerPlaceholder) {
          this.headerPlaceholder.style.display = 'none';
        }
      }

      if (scrollY > this.state.lastScrollY && scrollY > offset + 100) {
        this.header.classList.add('is-scrolling-down');
        this.header.classList.remove('is-scrolling-up');
      } else {
        this.header.classList.add('is-scrolling-up');
        this.header.classList.remove('is-scrolling-down');
      }
    },

    /**
     * ============================================================
     * MOBILE MENU
     * ============================================================
     */

    initMobileMenu() {
      const toggles = document.querySelectorAll(this.selectors.menuToggle);
      if (!toggles.length || !this.mainNav) return;

      toggles.forEach((toggle) => {
        toggle.setAttribute('aria-expanded', 'false');
        toggle.setAttribute('aria-controls', this.mainNav.id || 'site-navigation');

        if (!this.mainNav.id) {
          this.mainNav.id = 'site-navigation';
        }

        toggle.addEventListener('click', (event) => {
          event.preventDefault();
          this.toggleMobileMenu(toggle);
        });
      });

      this.initSubMenuToggles();
    },

    toggleMobileMenu(toggle) {
      if (this.state.isMobileMenuOpen) {
        this.closeMobileMenu();
      } else {
        this.openMobileMenu(toggle);
      }
    },

    openMobileMenu(toggle) {
      this.state.isMobileMenuOpen = true;
      this.body.classList.add('rx-menu-open');
      this.mainNav.classList.add(this.settings.openClass);

      document.querySelectorAll(this.selectors.menuToggle).forEach((btn) => {
        btn.setAttribute('aria-expanded', 'true');
      });

      if (toggle) {
        toggle.classList.add(this.settings.activeClass);
      }
    },

    closeMobileMenu() {
      if (!this.mainNav) return;

      this.state.isMobileMenuOpen = false;
      this.body.classList.remove('rx-menu-open');
      this.mainNav.classList.remove(this.settings.openClass);

      document.querySelectorAll(this.selectors.menuToggle).forEach((btn) => {
        btn.setAttribute('aria-expanded', 'false');
        btn.classList.remove(this.settings.activeClass);
      });
    },

    closeMobileMenuOnDesktop() {
      if (window.innerWidth >= 992) {
        this.closeMobileMenu();
      }
    },

    initSubMenuToggles() {
      if (!this.mainNav) return;

      const menuItems = this.mainNav.querySelectorAll('.menu-item-has-children');

      menuItems.forEach((item, index) => {
        const link = item.querySelector('a');
        const submenu = item.querySelector('.sub-menu, .children');

        if (!link || !submenu) return;

        const button = document.createElement('button');
        button.className = 'rx-submenu-toggle';
        button.setAttribute('aria-expanded', 'false');
        button.setAttribute('aria-label', 'Open submenu');
        button.innerHTML = '<span aria-hidden="true">+</span>';

        const submenuId = submenu.id || `rx-submenu-${index}`;
        submenu.id = submenuId;
        button.setAttribute('aria-controls', submenuId);

        link.insertAdjacentElement('afterend', button);

        button.addEventListener('click', () => {
          const expanded = button.getAttribute('aria-expanded') === 'true';

          button.setAttribute('aria-expanded', expanded ? 'false' : 'true');
          button.setAttribute('aria-label', expanded ? 'Open submenu' : 'Close submenu');

          item.classList.toggle(this.settings.openClass, !expanded);
          submenu.classList.toggle(this.settings.openClass, !expanded);

          button.innerHTML = expanded
            ? '<span aria-hidden="true">+</span>'
            : '<span aria-hidden="true">−</span>';
        });
      });
    },

    /**
     * ============================================================
     * SEARCH OVERLAY
     * ============================================================
     */

    initSearchOverlay() {
      const toggles = document.querySelectorAll(this.selectors.searchToggle);
      const closes = document.querySelectorAll(this.selectors.searchClose);
      const overlay = document.querySelector(this.selectors.searchOverlay);

      if (!overlay) return;

      this.searchOverlay = overlay;

      toggles.forEach((toggle) => {
        toggle.setAttribute('aria-expanded', 'false');

        toggle.addEventListener('click', (event) => {
          event.preventDefault();
          this.openSearchOverlay(toggle);
        });
      });

      closes.forEach((close) => {
        close.addEventListener('click', (event) => {
          event.preventDefault();
          this.closeSearchOverlay();
        });
      });

      overlay.addEventListener('click', (event) => {
        if (event.target === overlay) {
          this.closeSearchOverlay();
        }
      });
    },

    openSearchOverlay(toggle) {
      if (!this.searchOverlay) return;

      this.state.isSearchOpen = true;
      this.body.classList.add('rx-search-open');
      this.searchOverlay.classList.add(this.settings.openClass);
      this.searchOverlay.removeAttribute('hidden');

      document.querySelectorAll(this.selectors.searchToggle).forEach((btn) => {
        btn.setAttribute('aria-expanded', 'true');
      });

      if (toggle) {
        toggle.classList.add(this.settings.activeClass);
      }

      const input = this.searchOverlay.querySelector('input[type="search"], input[name="s"]');

      if (input) {
        setTimeout(() => input.focus(), 80);
      }
    },

    closeSearchOverlay() {
      if (!this.searchOverlay) return;

      this.state.isSearchOpen = false;
      this.body.classList.remove('rx-search-open');
      this.searchOverlay.classList.remove(this.settings.openClass);

      document.querySelectorAll(this.selectors.searchToggle).forEach((btn) => {
        btn.setAttribute('aria-expanded', 'false');
        btn.classList.remove(this.settings.activeClass);
      });

      setTimeout(() => {
        if (!this.state.isSearchOpen) {
          this.searchOverlay.setAttribute('hidden', 'hidden');
        }
      }, 250);
    },

    /**
     * ============================================================
     * BACK TO TOP BUTTON
     * ============================================================
     */

    initBackToTop() {
      let button = document.querySelector(this.selectors.backToTop);

      if (!button) {
        button = document.createElement('button');
        button.className = 'rx-back-to-top';
        button.setAttribute('type', 'button');
        button.setAttribute('aria-label', 'Back to top');
        button.innerHTML = '↑';
        document.body.appendChild(button);
      }

      this.backToTopButton = button;

      button.addEventListener('click', () => {
        window.scrollTo({
          top: 0,
          behavior: this.state.reducedMotion ? 'auto' : 'smooth'
        });
      });
    },

    updateBackToTop(scrollY) {
      if (!this.backToTopButton) return;

      if (scrollY > this.settings.backToTopOffset) {
        this.backToTopButton.classList.add(this.settings.activeClass);
      } else {
        this.backToTopButton.classList.remove(this.settings.activeClass);
      }
    },

    /**
     * ============================================================
     * READING PROGRESS BAR
     * ============================================================
     */

    initReadingProgress() {
      if (!this.settings.readingProgressEnabled) return;

      let bar = document.querySelector(this.selectors.readingProgress);

      if (!bar) {
        bar = document.createElement('div');
        bar.className = 'rx-reading-progress';
        bar.setAttribute('aria-hidden', 'true');

        const inner = document.createElement('span');
        inner.className = 'rx-reading-progress-inner';

        bar.appendChild(inner);
        document.body.appendChild(bar);
      }

      this.readingProgress = bar.querySelector('.rx-reading-progress-inner') || bar;
      this.refreshReadingProgress();
    },

    refreshReadingProgress() {
      this.documentHeight = Math.max(
        document.body.scrollHeight,
        document.documentElement.scrollHeight
      ) - window.innerHeight;
    },

    updateReadingProgress(scrollY) {
      if (!this.readingProgress || !this.documentHeight) return;

      const percent = Math.min(100, Math.max(0, (scrollY / this.documentHeight) * 100));
      this.readingProgress.style.transform = `scaleX(${percent / 100})`;
    },

    /**
     * ============================================================
     * SMOOTH SCROLL
     * ============================================================
     */

    initSmoothScroll() {
      const links = document.querySelectorAll('a[href^="#"]:not([href="#"])');

      links.forEach((link) => {
        link.addEventListener('click', (event) => {
          const targetId = decodeURIComponent(link.getAttribute('href'));
          const target = document.querySelector(targetId);

          if (!target) return;

          event.preventDefault();

          const offset = this.settings.smoothScrollOffset;
          const top = target.getBoundingClientRect().top + window.scrollY - offset;

          window.scrollTo({
            top,
            behavior: this.state.reducedMotion ? 'auto' : 'smooth'
          });

          target.setAttribute('tabindex', '-1');
          target.focus({ preventScroll: true });

          history.pushState(null, '', targetId);
        });
      });
    },

    /**
     * ============================================================
     * LAZY IMAGES
     * ============================================================
     */

    initLazyImages() {
      const images = document.querySelectorAll('img[data-src], img[data-srcset]');

      if (!images.length) return;

      if ('IntersectionObserver' in window) {
        const observer = new IntersectionObserver((entries, obs) => {
          entries.forEach((entry) => {
            if (!entry.isIntersecting) return;

            const img = entry.target;
            this.loadLazyImage(img);
            obs.unobserve(img);
          });
        }, {
          rootMargin: '250px 0px'
        });

        images.forEach((img) => observer.observe(img));
      } else {
        images.forEach((img) => this.loadLazyImage(img));
      }
    },

    loadLazyImage(img) {
      if (!img) return;

      if (img.dataset.src) {
        img.src = img.dataset.src;
      }

      if (img.dataset.srcset) {
        img.srcset = img.dataset.srcset;
      }

      img.classList.add('rx-lazy-loading');

      img.addEventListener('load', () => {
        img.classList.remove('rx-lazy-loading');
        img.classList.add('rx-lazy-loaded');
      }, { once: true });
    },

    /**
     * ============================================================
     * EXTERNAL LINKS
     * ============================================================
     */

    initExternalLinks() {
      const links = document.querySelectorAll(this.selectors.externalLinks);
      const currentHost = window.location.hostname;

      links.forEach((link) => {
        try {
          const url = new URL(link.href);

          if (url.hostname !== currentHost) {
            link.setAttribute('target', '_blank');
            link.setAttribute('rel', 'noopener noreferrer');

            if (!link.querySelector('.screen-reader-text')) {
              const text = document.createElement('span');
              text.className = 'screen-reader-text';
              text.textContent = ' opens in a new tab';
              link.appendChild(text);
            }
          }
        } catch (error) {
          // Ignore invalid URLs.
        }
      });
    },

    /**
     * ============================================================
     * TABLE OF CONTENTS
     * ============================================================
     */

    initTableOfContents() {
      const content = document.querySelector(this.selectors.content);
      const toc = document.querySelector(this.selectors.toc);

      if (!content || !toc) return;

      const headings = content.querySelectorAll('h2, h3');

      if (!headings.length) {
        toc.style.display = 'none';
        return;
      }

      const list = document.createElement('ol');
      list.className = 'rx-toc-list';

      headings.forEach((heading, index) => {
        if (!heading.id) {
          heading.id = this.slugify(heading.textContent || `section-${index + 1}`);
        }

        const item = document.createElement('li');
        item.className = `rx-toc-item rx-toc-${heading.tagName.toLowerCase()}`;

        const link = document.createElement('a');
        link.href = `#${heading.id}`;
        link.textContent = heading.textContent;

        item.appendChild(link);
        list.appendChild(item);
      });

      toc.appendChild(list);
      this.observeTOCHeadings(headings, toc);
    },

    observeTOCHeadings(headings, toc) {
      if (!('IntersectionObserver' in window)) return;

      const links = toc.querySelectorAll('a');

      const observer = new IntersectionObserver((entries) => {
        entries.forEach((entry) => {
          if (!entry.isIntersecting) return;

          links.forEach((link) => {
            link.classList.toggle(
              this.settings.activeClass,
              link.getAttribute('href') === `#${entry.target.id}`
            );
          });
        });
      }, {
        rootMargin: '-20% 0px -70% 0px'
      });

      headings.forEach((heading) => observer.observe(heading));
    },

    /**
     * ============================================================
     * ACCORDIONS
     * ============================================================
     */

    initAccordions() {
      const accordions = document.querySelectorAll(this.selectors.accordion);

      accordions.forEach((accordion, accordionIndex) => {
        const items = accordion.querySelectorAll('.rx-accordion-item');

        items.forEach((item, itemIndex) => {
          const header = item.querySelector('.rx-accordion-header');
          const panel = item.querySelector('.rx-accordion-panel');

          if (!header || !panel) return;

          const button = header.tagName.toLowerCase() === 'button'
            ? header
            : header.querySelector('button') || this.wrapHeaderWithButton(header);

          const panelId = panel.id || `rx-accordion-panel-${accordionIndex}-${itemIndex}`;
          panel.id = panelId;

          button.setAttribute('aria-expanded', 'false');
          button.setAttribute('aria-controls', panelId);
          panel.setAttribute('hidden', 'hidden');

          button.addEventListener('click', () => {
            const expanded = button.getAttribute('aria-expanded') === 'true';

            if (accordion.dataset.single === 'true') {
              this.closeAllAccordionItems(accordion);
            }

            button.setAttribute('aria-expanded', expanded ? 'false' : 'true');
            panel.toggleAttribute('hidden', expanded);
            item.classList.toggle(this.settings.openClass, !expanded);
          });
        });
      });
    },

    wrapHeaderWithButton(header) {
      const button = document.createElement('button');
      button.className = 'rx-accordion-button';
      button.type = 'button';
      button.innerHTML = header.innerHTML;
      header.innerHTML = '';
      header.appendChild(button);
      return button;
    },

    closeAllAccordionItems(accordion) {
      accordion.querySelectorAll('.rx-accordion-item').forEach((item) => {
        const button = item.querySelector('[aria-expanded]');
        const panel = item.querySelector('.rx-accordion-panel');

        if (button) {
          button.setAttribute('aria-expanded', 'false');
        }

        if (panel) {
          panel.setAttribute('hidden', 'hidden');
        }

        item.classList.remove(this.settings.openClass);
      });
    },

    /**
     * ============================================================
     * TABS
     * ============================================================
     */

    initTabs() {
      const tabsGroups = document.querySelectorAll(this.selectors.tabs);

      tabsGroups.forEach((tabs, groupIndex) => {
        const tabButtons = tabs.querySelectorAll('[role="tab"], .rx-tab-button');
        const panels = tabs.querySelectorAll('[role="tabpanel"], .rx-tab-panel');

        if (!tabButtons.length || !panels.length) return;

        tabButtons.forEach((button, index) => {
          const panel = panels[index];
          if (!panel) return;

          const tabId = button.id || `rx-tab-${groupIndex}-${index}`;
          const panelId = panel.id || `rx-tab-panel-${groupIndex}-${index}`;

          button.id = tabId;
          panel.id = panelId;

          button.setAttribute('role', 'tab');
          button.setAttribute('aria-controls', panelId);
          button.setAttribute('aria-selected', index === 0 ? 'true' : 'false');
          button.setAttribute('tabindex', index === 0 ? '0' : '-1');

          panel.setAttribute('role', 'tabpanel');
          panel.setAttribute('aria-labelledby', tabId);

          if (index !== 0) {
            panel.setAttribute('hidden', 'hidden');
          }

          button.addEventListener('click', () => {
            this.activateTab(tabButtons, panels, index);
          });

          button.addEventListener('keydown', (event) => {
            this.handleTabKeydown(event, tabButtons, panels, index);
          });
        });
      });
    },

    activateTab(buttons, panels, activeIndex) {
      buttons.forEach((button, index) => {
        const selected = index === activeIndex;

        button.setAttribute('aria-selected', selected ? 'true' : 'false');
        button.setAttribute('tabindex', selected ? '0' : '-1');
        button.classList.toggle(this.settings.activeClass, selected);

        if (panels[index]) {
          panels[index].toggleAttribute('hidden', !selected);
          panels[index].classList.toggle(this.settings.activeClass, selected);
        }
      });

      buttons[activeIndex].focus();
    },

    handleTabKeydown(event, buttons, panels, currentIndex) {
      let nextIndex = currentIndex;

      if (event.key === 'ArrowRight') {
        nextIndex = (currentIndex + 1) % buttons.length;
      } else if (event.key === 'ArrowLeft') {
        nextIndex = (currentIndex - 1 + buttons.length) % buttons.length;
      } else if (event.key === 'Home') {
        nextIndex = 0;
      } else if (event.key === 'End') {
        nextIndex = buttons.length - 1;
      } else {
        return;
      }

      event.preventDefault();
      this.activateTab(buttons, panels, nextIndex);
    },

    /**
     * ============================================================
     * COPY BUTTONS
     * ============================================================
     */

    initCopyButtons() {
      const codeBlocks = document.querySelectorAll('pre code');

      codeBlocks.forEach((code) => {
        const pre = code.parentElement;

        if (!pre || pre.classList.contains('rx-copy-ready')) return;

        pre.classList.add('rx-copy-ready');

        const button = document.createElement('button');
        button.type = 'button';
        button.className = 'rx-copy-button';
        button.textContent = 'Copy';
        button.setAttribute('aria-label', 'Copy code');

        pre.appendChild(button);

        button.addEventListener('click', async () => {
          const text = code.innerText;

          try {
            await navigator.clipboard.writeText(text);
            button.textContent = 'Copied';
            button.classList.add('is-copied');

            setTimeout(() => {
              button.textContent = 'Copy';
              button.classList.remove('is-copied');
            }, 1800);
          } catch (error) {
            button.textContent = 'Failed';
          }
        });
      });
    },

    /**
     * ============================================================
     * SHARE BUTTONS
     * ============================================================
     */

    initShareButtons() {
      const shareButtons = document.querySelectorAll('[data-rx-share]');

      shareButtons.forEach((button) => {
        button.addEventListener('click', async (event) => {
          event.preventDefault();

          const title = button.dataset.title || document.title;
          const url = button.dataset.url || window.location.href;
          const text = button.dataset.text || '';

          if (navigator.share) {
            try {
              await navigator.share({ title, text, url });
            } catch (error) {
              // User cancelled share.
            }
          } else {
            try {
              await navigator.clipboard.writeText(url);
              button.classList.add('is-copied');
              button.textContent = button.dataset.copiedText || 'Link copied';

              setTimeout(() => {
                button.classList.remove('is-copied');
                button.textContent = button.dataset.defaultText || 'Share';
              }, 1800);
            } catch (error) {
              window.prompt('Copy this link:', url);
            }
          }
        });
      });
    },

    /**
     * ============================================================
     * COMMENT FORM ENHANCER
     * ============================================================
     */

    initCommentFormEnhancer() {
      const form = document.querySelector(this.selectors.commentForm);
      if (!form) return;

      const textarea = form.querySelector('textarea');

      if (textarea) {
        this.addTextareaCounter(textarea, 3000);
        this.autoResizeTextarea(textarea);
      }

      form.addEventListener('submit', (event) => {
        const requiredFields = form.querySelectorAll('[required]');
        let valid = true;

        requiredFields.forEach((field) => {
          if (!field.value.trim()) {
            valid = false;
            field.classList.add('has-error');
            field.setAttribute('aria-invalid', 'true');
          } else {
            field.classList.remove('has-error');
            field.removeAttribute('aria-invalid');
          }
        });

        if (!valid) {
          event.preventDefault();

          const firstError = form.querySelector('.has-error');
          if (firstError) firstError.focus();
        }
      });
    },

    addTextareaCounter(textarea, max) {
      const counter = document.createElement('div');
      counter.className = 'rx-character-counter';
      counter.setAttribute('aria-live', 'polite');

      textarea.insertAdjacentElement('afterend', counter);

      const update = () => {
        const count = textarea.value.length;
        counter.textContent = `${count}/${max} characters`;

        if (count > max) {
          counter.classList.add('is-over-limit');
        } else {
          counter.classList.remove('is-over-limit');
        }
      };

      textarea.addEventListener('input', update);
      update();
    },

    autoResizeTextarea(textarea) {
      const resize = () => {
        textarea.style.height = 'auto';
        textarea.style.height = `${textarea.scrollHeight}px`;
      };

      textarea.addEventListener('input', resize);
      resize();
    },

    /**
     * ============================================================
     * IMAGE LIGHTBOX
     * ============================================================
     */

    initImageLightbox() {
      const images = document.querySelectorAll(this.selectors.lightboxImage);

      if (!images.length) return;

      const overlay = document.createElement('div');
      overlay.className = 'rx-lightbox';
      overlay.setAttribute('hidden', 'hidden');
      overlay.innerHTML = `
        <button type="button" class="rx-lightbox-close" aria-label="Close image">×</button>
        <img class="rx-lightbox-image" alt="">
      `;

      document.body.appendChild(overlay);
      this.lightbox = overlay;
      this.lightboxImage = overlay.querySelector('.rx-lightbox-image');

      images.forEach((img) => {
        if (img.closest('a')) return;

        img.classList.add('rx-lightbox-trigger');
        img.setAttribute('tabindex', '0');
        img.setAttribute('role', 'button');

        img.addEventListener('click', () => this.openLightbox(img));
        img.addEventListener('keydown', (event) => {
          if (event.key === 'Enter' || event.key === ' ') {
            event.preventDefault();
            this.openLightbox(img);
          }
        });
      });

      overlay.addEventListener('click', (event) => {
        if (
          event.target === overlay ||
          event.target.classList.contains('rx-lightbox-close')
        ) {
          this.closeLightbox();
        }
      });
    },

    openLightbox(img) {
      if (!this.lightbox || !this.lightboxImage) return;

      this.lightboxImage.src = img.currentSrc || img.src;
      this.lightboxImage.alt = img.alt || '';
      this.lightbox.removeAttribute('hidden');
      this.lightbox.classList.add(this.settings.openClass);
      this.body.classList.add('rx-lightbox-open');

      const close = this.lightbox.querySelector('.rx-lightbox-close');
      if (close) close.focus();
    },

    closeLightbox() {
      if (!this.lightbox) return;

      this.lightbox.classList.remove(this.settings.openClass);
      this.body.classList.remove('rx-lightbox-open');

      setTimeout(() => {
        this.lightbox.setAttribute('hidden', 'hidden');
      }, 200);
    },

    /**
     * ============================================================
     * FORM VALIDATION
     * ============================================================
     */

    initFormValidation() {
      const forms = document.querySelectorAll('form.rx-validate, form[data-rx-validate]');

      forms.forEach((form) => {
        form.addEventListener('submit', (event) => {
          const invalidFields = [];

          form.querySelectorAll('input, select, textarea').forEach((field) => {
            if (!this.validateField(field)) {
              invalidFields.push(field);
            }
          });

          if (invalidFields.length) {
            event.preventDefault();
            invalidFields[0].focus();
          }
        });

        form.querySelectorAll('input, select, textarea').forEach((field) => {
          field.addEventListener('blur', () => this.validateField(field));
          field.addEventListener('input', () => {
            if (field.classList.contains('has-error')) {
              this.validateField(field);
            }
          });
        });
      });
    },

    validateField(field) {
      if (!field.willValidate) return true;

      const isValid = field.checkValidity();

      field.classList.toggle('has-error', !isValid);
      field.setAttribute('aria-invalid', isValid ? 'false' : 'true');

      let message = field.parentNode.querySelector('.rx-field-error');

      if (!isValid) {
        if (!message) {
          message = document.createElement('span');
          message.className = 'rx-field-error';
          field.insertAdjacentElement('afterend', message);
        }

        message.textContent = field.validationMessage;
      } else if (message) {
        message.remove();
      }

      return isValid;
    },

    /**
     * ============================================================
     * DARK MODE
     * ============================================================
     */

    initDarkMode() {
      const toggles = document.querySelectorAll('[data-rx-dark-toggle], .rx-dark-toggle');
      const saved = this.storageGet(this.settings.darkModeStorageKey);

      if (saved === 'dark') {
        this.html.classList.add('rx-dark-mode');
      } else if (saved === 'light') {
        this.html.classList.remove('rx-dark-mode');
      } else if (
        window.matchMedia &&
        window.matchMedia('(prefers-color-scheme: dark)').matches
      ) {
        this.html.classList.add('rx-dark-mode');
      }

      toggles.forEach((toggle) => {
        toggle.addEventListener('click', () => {
          const isDark = this.html.classList.toggle('rx-dark-mode');
          this.storageSet(this.settings.darkModeStorageKey, isDark ? 'dark' : 'light');
          toggle.setAttribute('aria-pressed', isDark ? 'true' : 'false');
        });
      });
    },

    /**
     * ============================================================
     * FONT SIZE CONTROLS
     * ============================================================
     */

    initFontSizeControls() {
      const increase = document.querySelectorAll('[data-rx-font-increase]');
      const decrease = document.querySelectorAll('[data-rx-font-decrease]');
      const reset = document.querySelectorAll('[data-rx-font-reset]');

      let size = parseInt(this.storageGet(this.settings.fontStorageKey) || '100', 10);
      this.applyFontSize(size);

      increase.forEach((btn) => {
        btn.addEventListener('click', () => {
          size = Math.min(size + 10, 140);
          this.applyFontSize(size);
        });
      });

      decrease.forEach((btn) => {
        btn.addEventListener('click', () => {
          size = Math.max(size - 10, 80);
          this.applyFontSize(size);
        });
      });

      reset.forEach((btn) => {
        btn.addEventListener('click', () => {
          size = 100;
          this.applyFontSize(size);
        });
      });
    },

    applyFontSize(size) {
      this.html.style.setProperty('--rx-user-font-size', `${size}%`);
      this.storageSet(this.settings.fontStorageKey, String(size));
    },

    /**
     * ============================================================
     * SCROLL ANIMATIONS
     * ============================================================
     */

    initScrollAnimations() {
      const elements = document.querySelectorAll('[data-rx-animate], .rx-animate');

      if (!elements.length || this.state.reducedMotion) return;

      if ('IntersectionObserver' in window) {
        const observer = new IntersectionObserver((entries, obs) => {
          entries.forEach((entry) => {
            if (!entry.isIntersecting) return;

            entry.target.classList.add(this.settings.animationClass);
            obs.unobserve(entry.target);
          });
        }, {
          threshold: 0.15
        });

        elements.forEach((el) => observer.observe(el));
      } else {
        elements.forEach((el) => el.classList.add(this.settings.animationClass));
      }
    },

    /**
     * ============================================================
     * PRINT HELPERS
     * ============================================================
     */

    initPrintHelpers() {
      const printButtons = document.querySelectorAll('[data-rx-print], .rx-print-button');

      printButtons.forEach((button) => {
        button.addEventListener('click', (event) => {
          event.preventDefault();
          window.print();
        });
      });
    },

    /**
     * ============================================================
     * RESPONSIVE TABLES
     * ============================================================
     */

    initResponsiveTables() {
      const content = document.querySelector(this.selectors.content);
      if (!content) return;

      const tables = content.querySelectorAll('table');

      tables.forEach((table) => {
        if (table.parentElement.classList.contains('rx-table-wrap')) return;

        const wrapper = document.createElement('div');
        wrapper.className = 'rx-table-wrap';
        wrapper.setAttribute('tabindex', '0');

        table.parentNode.insertBefore(wrapper, table);
        wrapper.appendChild(table);
      });

      this.refreshResponsiveTables();
    },

    refreshResponsiveTables() {
      const wrappers = document.querySelectorAll('.rx-table-wrap');

      wrappers.forEach((wrapper) => {
        const isScrollable = wrapper.scrollWidth > wrapper.clientWidth;
        wrapper.classList.toggle('is-scrollable', isScrollable);
      });
    },

    /**
     * ============================================================
     * VIDEO EMBEDS
     * ============================================================
     */

    initVideoEmbeds() {
      const iframes = document.querySelectorAll(
        'iframe[src*="youtube.com"], iframe[src*="youtu.be"], iframe[src*="vimeo.com"]'
      );

      iframes.forEach((iframe) => {
        if (iframe.parentElement.classList.contains('rx-video-wrap')) return;

        const wrapper = document.createElement('div');
        wrapper.className = 'rx-video-wrap';

        iframe.parentNode.insertBefore(wrapper, iframe);
        wrapper.appendChild(iframe);

        iframe.setAttribute('loading', 'lazy');
      });
    },

    /**
     * ============================================================
     * CODE BLOCK ENHANCER
     * ============================================================
     */

    initCodeBlocks() {
      const codeBlocks = document.querySelectorAll('pre');

      codeBlocks.forEach((pre) => {
        pre.classList.add('rx-code-block');

        if (!pre.querySelector('.rx-code-label')) {
          const code = pre.querySelector('code');
          const className = code ? code.className : '';
          const match = className.match(/language-([a-z0-9]+)/i);

          if (match) {
            const label = document.createElement('span');
            label.className = 'rx-code-label';
            label.textContent = match[1].toUpperCase();
            pre.appendChild(label);
          }
        }
      });
    },

    /**
     * ============================================================
     * BREADCRUMB ENHANCER
     * ============================================================
     */

    initBreadcrumbEnhancer() {
      const breadcrumbs = document.querySelectorAll('.breadcrumb, .breadcrumbs, .rx-breadcrumbs');

      breadcrumbs.forEach((breadcrumb) => {
        breadcrumb.setAttribute('aria-label', 'Breadcrumb');
        breadcrumb.setAttribute('role', 'navigation');

        const links = breadcrumb.querySelectorAll('a');

        links.forEach((link, index) => {
          if (index === links.length - 1) {
            link.setAttribute('aria-current', 'page');
          }
        });
      });
    },

    /**
     * ============================================================
     * KEYBOARD ACCESSIBILITY
     * ============================================================
     */

    initKeyboardAccessibility() {
      document.addEventListener('keydown', (event) => {
        if (event.key === 'Tab') {
          this.body.classList.add('rx-keyboard-user');
        }
      });

      document.addEventListener('mousedown', () => {
        this.body.classList.remove('rx-keyboard-user');
      });
    },

    /**
     * ============================================================
     * SKIP LINKS
     * ============================================================
     */

    initSkipLinks() {
      const skipLink = document.querySelector('.skip-link, .screen-reader-text[href="#content"]');

      if (!skipLink) return;

      skipLink.addEventListener('click', () => {
        const target = document.querySelector(skipLink.getAttribute('href'));

        if (target) {
          target.setAttribute('tabindex', '-1');
          target.focus();
        }
      });
    },

    /**
     * ============================================================
     * COOKIE NOTICE
     * ============================================================
     */

    initCookieNotice() {
      const notice = document.querySelector('[data-rx-cookie-notice], .rx-cookie-notice');
      if (!notice) return;

      const key = 'rx_cookie_notice_accepted';

      if (this.storageGet(key) === 'yes') {
        notice.remove();
        return;
      }

      const accept = notice.querySelector('[data-rx-cookie-accept], .rx-cookie-accept');

      if (accept) {
        accept.addEventListener('click', () => {
          this.storageSet(key, 'yes');
          notice.classList.add('is-accepted');

          setTimeout(() => notice.remove(), 250);
        });
      }
    },

    /**
     * ============================================================
     * NEWSLETTER BOX
     * ============================================================
     */

    initNewsletterBox() {
      const boxes = document.querySelectorAll('[data-rx-newsletter], .rx-newsletter');

      boxes.forEach((box) => {
        const form = box.querySelector('form');
        const email = box.querySelector('input[type="email"]');

        if (!form || !email) return;

        form.addEventListener('submit', (event) => {
          if (!email.checkValidity()) {
            event.preventDefault();
            email.classList.add('has-error');
            email.focus();
          }
        });
      });
    },

    /**
     * ============================================================
     * AJAX LOAD MORE PLACEHOLDER
     * ============================================================
     * This prepares frontend button behavior.
     * Real AJAX needs wp_localize_script + admin-ajax.php PHP handler.
     */

    initAjaxLoadMorePlaceholder() {
      const buttons = document.querySelectorAll('[data-rx-load-more]');

      buttons.forEach((button) => {
        button.addEventListener('click', () => {
          if (!window.rxThemeAjax || !window.rxThemeAjax.ajaxUrl) {
            button.classList.add('is-disabled');
            button.textContent = 'Load more is not connected';
            return;
          }

          button.classList.add('is-loading');
          button.setAttribute('disabled', 'disabled');
        });
      });
    },

    /**
     * ============================================================
     * SERVICE WORKER HELPER
     * ============================================================
     * Only registers if rx-sw.js exists and browser supports it.
     */

    initServiceWorkerHelper() {
      if (!('serviceWorker' in navigator)) return;
      if (!window.rxThemeSettings || !window.rxThemeSettings.enableServiceWorker) return;

      window.addEventListener('load', () => {
        navigator.serviceWorker.register('/rx-sw.js').catch(() => {
          // Silent fail. Service worker is optional.
        });
      });
    },

    /**
     * ============================================================
     * UTILITIES
     * ============================================================
     */

    slugify(text) {
      return String(text)
        .toLowerCase()
        .trim()
        .replace(/&/g, 'and')
        .replace(/[\s\W-]+/g, '-')
        .replace(/^-+|-+$/g, '');
    },

    debounce(callback, delay) {
      let timeout;

      return function (...args) {
        clearTimeout(timeout);

        timeout = setTimeout(() => {
          callback.apply(this, args);
        }, delay);
      };
    },

    throttle(callback, delay) {
      let waiting = false;

      return function (...args) {
        if (waiting) return;

        callback.apply(this, args);
        waiting = true;

        setTimeout(() => {
          waiting = false;
        }, delay);
      };
    },

    storageGet(key) {
      try {
        return window.localStorage.getItem(key);
      } catch (error) {
        return null;
      }
    },

    storageSet(key, value) {
      try {
        window.localStorage.setItem(key, value);
      } catch (error) {
        // localStorage may be blocked.
      }
    }
  };

  /**
   * ============================================================
   * DOM READY
   * ============================================================
   */

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

  window.RXTheme = RXTheme;
})();

Now enqueue it in your functions.php:

function rx_theme_enqueue_chunks_js() {
    wp_enqueue_script(
        'rx-theme-chunks',
        get_template_directory_uri() . '/inc/chunks/chunks.js',
        array(),
        wp_get_theme()->get('Version'),
        true
    );
}
add_action('wp_enqueue_scripts', 'rx_theme_enqueue_chunks_js');

For best performance, also create this folder:

rx-theme/inc/chunks/

Your file path should be exactly:

rx-theme/inc/chunks/chunks.js


can i add more chunk.js file serially 1,2 , 3, ………………………………………………..50

Yes, my friend, you can add many chunk.js files serially, like:

rx-theme/inc/chunks/chunk-1.js
rx-theme/inc/chunks/chunk-2.js
rx-theme/inc/chunks/chunk-3.js
...
rx-theme/inc/chunks/chunk-50.js

But important: do not load all 50 files on every page unless needed, because too many JavaScript files can slow your site. Best system is:

  1. Global chunks load everywhere.
  2. Page-specific chunks load only on required pages.
  3. Feature chunks load only when that feature exists.

Best folder structure

rx-theme/
├── functions.php
├── inc/
│   ├── chunks/
│   │   ├── chunk-1-core.js
│   │   ├── chunk-2-header.js
│   │   ├── chunk-3-mobile-menu.js
│   │   ├── chunk-4-search.js
│   │   ├── chunk-5-dark-mode.js
│   │   ├── chunk-6-back-to-top.js
│   │   ├── chunk-7-reading-progress.js
│   │   ├── chunk-8-lazyload.js
│   │   ├── chunk-9-toc.js
│   │   ├── chunk-10-accordion.js
│   │   ├── chunk-11-tabs.js
│   │   ├── chunk-12-copy-code.js
│   │   ├── chunk-13-lightbox.js
│   │   ├── chunk-14-share.js
│   │   ├── chunk-15-comments.js
│   │   ├── chunk-16-forms.js
│   │   ├── chunk-17-table.js
│   │   ├── chunk-18-video.js
│   │   ├── chunk-19-scroll-animation.js
│   │   ├── chunk-20-print.js
│   │   └── ... chunk-50-extra.js

Simple enqueue system for 50 chunk files

Add this in functions.php:

/**
 * RX Theme - Load JS Chunks
 */
function rx_theme_enqueue_js_chunks() {

    $theme_version = wp_get_theme()->get( 'Version' );

    $chunks = array(
        'chunk-1-core',
        'chunk-2-header',
        'chunk-3-mobile-menu',
        'chunk-4-search',
        'chunk-5-dark-mode',
        'chunk-6-back-to-top',
        'chunk-7-reading-progress',
        'chunk-8-lazyload',
        'chunk-9-toc',
        'chunk-10-accordion',
        'chunk-11-tabs',
        'chunk-12-copy-code',
        'chunk-13-lightbox',
        'chunk-14-share',
        'chunk-15-comments',
        'chunk-16-forms',
        'chunk-17-table',
        'chunk-18-video',
        'chunk-19-scroll-animation',
        'chunk-20-print',
        'chunk-21-accessibility',
        'chunk-22-breadcrumb',
        'chunk-23-sticky-sidebar',
        'chunk-24-newsletter',
        'chunk-25-cookie',
        'chunk-26-popup',
        'chunk-27-load-more',
        'chunk-28-filter',
        'chunk-29-pagination',
        'chunk-30-related-posts',
        'chunk-31-schema-helper',
        'chunk-32-font-size',
        'chunk-33-theme-mode',
        'chunk-34-reading-time',
        'chunk-35-progress-circle',
        'chunk-36-notification',
        'chunk-37-marquee',
        'chunk-38-slider',
        'chunk-39-carousel',
        'chunk-40-gallery',
        'chunk-41-medical-card',
        'chunk-42-faq',
        'chunk-43-author-box',
        'chunk-44-scrollspy',
        'chunk-45-ajax-search',
        'chunk-46-pwa',
        'chunk-47-performance',
        'chunk-48-security-ui',
        'chunk-49-seo-ui',
        'chunk-50-final'
    );

    foreach ( $chunks as $chunk ) {
        $file_path = get_template_directory() . '/inc/chunks/' . $chunk . '.js';
        $file_uri  = get_template_directory_uri() . '/inc/chunks/' . $chunk . '.js';

        if ( file_exists( $file_path ) ) {
            wp_enqueue_script(
                'rx-theme-' . $chunk,
                $file_uri,
                array(),
                filemtime( $file_path ),
                true
            );
        }
    }
}
add_action( 'wp_enqueue_scripts', 'rx_theme_enqueue_js_chunks' );

Better advanced version: add defer

Add this also in functions.php:

/**
 * RX Theme - Add defer to RX chunk scripts
 */
function rx_theme_defer_chunk_scripts( $tag, $handle, $src ) {

    if ( strpos( $handle, 'rx-theme-chunk-' ) !== false ) {
        return '<script src="' . esc_url( $src ) . '" defer></script>' . "\n";
    }

    return $tag;
}
add_filter( 'script_loader_tag', 'rx_theme_defer_chunk_scripts', 10, 3 );

Even better: load some chunks only on single posts

Example:

function rx_theme_enqueue_single_post_chunks() {

    if ( ! is_single() ) {
        return;
    }

    $theme_version = wp_get_theme()->get( 'Version' );

    $single_chunks = array(
        'chunk-7-reading-progress',
        'chunk-9-toc',
        'chunk-12-copy-code',
        'chunk-13-lightbox',
        'chunk-14-share',
        'chunk-15-comments',
        'chunk-42-faq',
        'chunk-43-author-box'
    );

    foreach ( $single_chunks as $chunk ) {
        $file_path = get_template_directory() . '/inc/chunks/' . $chunk . '.js';
        $file_uri  = get_template_directory_uri() . '/inc/chunks/' . $chunk . '.js';

        if ( file_exists( $file_path ) ) {
            wp_enqueue_script(
                'rx-theme-single-' . $chunk,
                $file_uri,
                array(),
                filemtime( $file_path ),
                true
            );
        }
    }
}
add_action( 'wp_enqueue_scripts', 'rx_theme_enqueue_single_post_chunks' );

My best advice for RX Theme

Yes, you can create 50 chunk files, but this is best:

Load everywhere:
chunk-1-core.js
chunk-2-header.js
chunk-3-mobile-menu.js
chunk-4-search.js
chunk-5-dark-mode.js
chunk-6-back-to-top.js
chunk-8-lazyload.js
chunk-21-accessibility.js

Load only single post:
chunk-7-reading-progress.js
chunk-9-toc.js
chunk-12-copy-code.js
chunk-13-lightbox.js
chunk-14-share.js
chunk-15-comments.js
chunk-42-faq.js

Load only homepage:
chunk-38-slider.js
chunk-39-carousel.js
chunk-40-gallery.js

Load only archive/category:
chunk-27-load-more.js
chunk-28-filter.js
chunk-29-pagination.js

So, possible? Yes.
Recommended? Yes, but load conditionally.
Best for performance? Use many files during development, then later combine/minify for production.

Leave a Reply

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