chunk-034-reading-time.js

I’ll build this as a standalone advanced reading-time chunk for RX Theme: safe DOM-ready loading, WordPress-friendly selectors, Bengali/English text support, progress bar, analytics-ready events, and reusable API methods.

Friend, create this file:

assets/static-js/static-chunks-js/chunk-034-reading-time.js

Then paste this full advanced code:

/*!
 * RX Theme - Advanced Reading Time Chunk
 * File: assets/static-js/static-chunks-js/chunk-034-reading-time.js
 * Purpose: Reading time, word count, progress bar, scroll progress, completion tracking
 * Author: RX Theme
 */

(function RXReadingTimeChunk(window, document) {
  'use strict';

  if (!window || !document) {
    return;
  }

  var RX_READING_TIME_VERSION = '1.0.0';

  var DEFAULTS = {
    contentSelectors: [
      '.rx-single-content',
      '.entry-content',
      '.post-content',
      '.article-content',
      'article .content',
      'article',
      'main'
    ],

    titleSelectors: [
      '.entry-title',
      '.post-title',
      'h1'
    ],

    outputSelectors: [
      '[data-rx-reading-time]',
      '.rx-reading-time',
      '.reading-time'
    ],

    excludeSelectors: [
      'script',
      'style',
      'noscript',
      'iframe',
      'svg',
      'canvas',
      'form',
      'nav',
      'aside',
      '.rx-no-reading-time',
      '.no-reading-time',
      '.wp-block-embed',
      '.sharedaddy',
      '.post-navigation',
      '.comments-area',
      '.comment-respond',
      '.related-posts',
      '.rx-related-posts',
      '.rx-ad',
      '.ads',
      '.advertisement'
    ],

    wordsPerMinute: 220,
    bengaliWordsPerMinute: 180,
    codeWordsPerMinute: 120,

    imageSeconds: 8,
    tableSeconds: 12,
    videoSeconds: 20,
    audioSeconds: 15,
    embedSeconds: 18,

    minimumMinute: 1,
    roundMode: 'ceil',

    showWordCount: true,
    showCharacterCount: false,
    showProgressBar: true,
    showFloatingProgress: false,
    showCompletionMessage: false,

    autoInject: true,
    injectPosition: 'before-content',

    progressBarId: 'rx-reading-progress-bar',
    progressBarClass: 'rx-reading-progress-bar',

    floatingProgressId: 'rx-floating-reading-progress',
    floatingProgressClass: 'rx-floating-reading-progress',

    completionThreshold: 90,
    saveProgress: true,
    storagePrefix: 'rx_reading_progress_',

    enableMutationObserver: true,
    mutationDebounce: 600,

    enableEvents: true,
    eventPrefix: 'rxReadingTime',

    labels: {
      minute: 'min read',
      minutes: 'min read',
      lessThanMinute: 'Less than 1 min read',
      word: 'word',
      words: 'words',
      character: 'character',
      characters: 'characters',
      completed: 'Reading completed',
      progress: 'Reading progress'
    },

    banglaLabels: {
      minute: 'মিনিট পড়া',
      minutes: 'মিনিট পড়া',
      lessThanMinute: '১ মিনিটের কম',
      word: 'শব্দ',
      words: 'শব্দ',
      character: 'অক্ষর',
      characters: 'অক্ষর',
      completed: 'পড়া সম্পন্ন',
      progress: 'পড়ার অগ্রগতি'
    },

    debug: false
  };

  var state = {
    initialized: false,
    contentElement: null,
    titleElement: null,
    outputElements: [],
    progressBar: null,
    floatingProgress: null,
    mutationObserver: null,
    scrollTicking: false,
    mutationTimer: null,
    completed: false,
    data: {
      words: 0,
      bengaliWords: 0,
      englishWords: 0,
      characters: 0,
      images: 0,
      tables: 0,
      videos: 0,
      audios: 0,
      embeds: 0,
      codeBlocks: 0,
      seconds: 0,
      minutes: 1,
      progress: 0,
      language: 'en'
    }
  };

  function log() {
    if (!DEFAULTS.debug || !window.console) {
      return;
    }

    var args = Array.prototype.slice.call(arguments);
    args.unshift('[RX Reading Time]');
    window.console.log.apply(window.console, args);
  }

  function extend(target) {
    var sources = Array.prototype.slice.call(arguments, 1);

    sources.forEach(function mergeSource(source) {
      if (!source) {
        return;
      }

      Object.keys(source).forEach(function copyKey(key) {
        if (
          Object.prototype.toString.call(source[key]) === '[object Object]' &&
          Object.prototype.toString.call(target[key]) === '[object Object]'
        ) {
          target[key] = extend({}, target[key], source[key]);
        } else {
          target[key] = source[key];
        }
      });
    });

    return target;
  }

  function ready(callback) {
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', callback, { once: true });
    } else {
      callback();
    }
  }

  function queryFirst(selectors, root) {
    root = root || document;

    for (var i = 0; i < selectors.length; i += 1) {
      var element = root.querySelector(selectors[i]);

      if (element) {
        return element;
      }
    }

    return null;
  }

  function queryAll(selectors, root) {
    root = root || document;

    var results = [];

    selectors.forEach(function eachSelector(selector) {
      var nodes = root.querySelectorAll(selector);

      Array.prototype.forEach.call(nodes, function eachNode(node) {
        if (results.indexOf(node) === -1) {
          results.push(node);
        }
      });
    });

    return results;
  }

  function getConfigFromWindow() {
    var config = {};

    if (window.rxReadingTimeConfig && typeof window.rxReadingTimeConfig === 'object') {
      config = window.rxReadingTimeConfig;
    }

    return config;
  }

  function getConfigFromDataset(element) {
    if (!element || !element.dataset) {
      return {};
    }

    var config = {};

    if (element.dataset.rxWordsPerMinute) {
      config.wordsPerMinute = parseInt(element.dataset.rxWordsPerMinute, 10);
    }

    if (element.dataset.rxReadingAutoInject) {
      config.autoInject = element.dataset.rxReadingAutoInject !== 'false';
    }

    if (element.dataset.rxReadingProgressBar) {
      config.showProgressBar = element.dataset.rxReadingProgressBar !== 'false';
    }

    if (element.dataset.rxReadingWordCount) {
      config.showWordCount = element.dataset.rxReadingWordCount !== 'false';
    }

    if (element.dataset.rxReadingPosition) {
      config.injectPosition = element.dataset.rxReadingPosition;
    }

    return config;
  }

  function normalizeWhitespace(text) {
    return String(text || '')
      .replace(/\u00a0/g, ' ')
      .replace(/\s+/g, ' ')
      .trim();
  }

  function cloneContentForText(element, config) {
    var clone = element.cloneNode(true);

    config.excludeSelectors.forEach(function removeSelector(selector) {
      var nodes = clone.querySelectorAll(selector);

      Array.prototype.forEach.call(nodes, function removeNode(node) {
        if (node && node.parentNode) {
          node.parentNode.removeChild(node);
        }
      });
    });

    return clone;
  }

  function isBengaliText(text) {
    return /[\u0980-\u09FF]/.test(text || '');
  }

  function countBengaliWords(text) {
    var matches = String(text || '').match(/[\u0980-\u09FF]+/g);
    return matches ? matches.length : 0;
  }

  function countEnglishWords(text) {
    var cleaned = String(text || '')
      .replace(/[\u0980-\u09FF]+/g, ' ')
      .replace(/[^\w\s'-]/g, ' ');

    var matches = cleaned.match(/[A-Za-z0-9]+(?:['-][A-Za-z0-9]+)?/g);
    return matches ? matches.length : 0;
  }

  function countCharacters(text) {
    return normalizeWhitespace(text).replace(/\s/g, '').length;
  }

  function countElements(element, selector) {
    return element.querySelectorAll(selector).length;
  }

  function countCodeWords(element) {
    var codeText = '';

    Array.prototype.forEach.call(element.querySelectorAll('pre, code'), function eachCode(node) {
      codeText += ' ' + node.textContent;
    });

    return normalizeWhitespace(codeText).split(/\s+/).filter(Boolean).length;
  }

  function calculateReadingData(element, config) {
    var clone = cloneContentForText(element, config);
    var text = normalizeWhitespace(clone.textContent || '');

    var bengaliWords = countBengaliWords(text);
    var englishWords = countEnglishWords(text);
    var totalWords = bengaliWords + englishWords;
    var characters = countCharacters(text);

    var images = countElements(element, 'img, picture, figure');
    var tables = countElements(element, 'table');
    var videos = countElements(element, 'video');
    var audios = countElements(element, 'audio');
    var embeds = countElements(element, 'iframe, embed, object');
    var codeBlocks = countElements(element, 'pre, code');
    var codeWords = countCodeWords(element);

    var normalWords = Math.max(totalWords - codeWords, 0);

    var normalSeconds = 0;

    if (bengaliWords > englishWords) {
      normalSeconds = (normalWords / config.bengaliWordsPerMinute) * 60;
    } else {
      normalSeconds = (normalWords / config.wordsPerMinute) * 60;
    }

    var codeSeconds = (codeWords / config.codeWordsPerMinute) * 60;
    var mediaSeconds =
      images * config.imageSeconds +
      tables * config.tableSeconds +
      videos * config.videoSeconds +
      audios * config.audioSeconds +
      embeds * config.embedSeconds;

    var totalSeconds = normalSeconds + codeSeconds + mediaSeconds;

    var minutes;

    if (config.roundMode === 'floor') {
      minutes = Math.floor(totalSeconds / 60);
    } else if (config.roundMode === 'round') {
      minutes = Math.round(totalSeconds / 60);
    } else {
      minutes = Math.ceil(totalSeconds / 60);
    }

    minutes = Math.max(minutes, config.minimumMinute);

    return {
      words: totalWords,
      bengaliWords: bengaliWords,
      englishWords: englishWords,
      characters: characters,
      images: images,
      tables: tables,
      videos: videos,
      audios: audios,
      embeds: embeds,
      codeBlocks: codeBlocks,
      codeWords: codeWords,
      seconds: Math.round(totalSeconds),
      minutes: minutes,
      progress: state.data.progress || 0,
      language: isBengaliText(text) && bengaliWords > englishWords ? 'bn' : 'en'
    };
  }

  function getLabels(data, config) {
    return data.language === 'bn' ? config.banglaLabels : config.labels;
  }

  function formatReadingTime(data, config) {
    var labels = getLabels(data, config);

    if (data.seconds < 60) {
      return labels.lessThanMinute;
    }

    var label = data.minutes === 1 ? labels.minute : labels.minutes;

    return data.minutes + ' ' + label;
  }

  function formatWordCount(data, config) {
    var labels = getLabels(data, config);
    var label = data.words === 1 ? labels.word : labels.words;

    return data.words.toLocaleString() + ' ' + label;
  }

  function formatCharacterCount(data, config) {
    var labels = getLabels(data, config);
    var label = data.characters === 1 ? labels.character : labels.characters;

    return data.characters.toLocaleString() + ' ' + label;
  }

  function buildOutputText(data, config) {
    var parts = [formatReadingTime(data, config)];

    if (config.showWordCount) {
      parts.push(formatWordCount(data, config));
    }

    if (config.showCharacterCount) {
      parts.push(formatCharacterCount(data, config));
    }

    return parts.join(' · ');
  }

  function buildOutputHTML(data, config) {
    var readingTime = formatReadingTime(data, config);
    var html = '';

    html += '<span class="rx-reading-time__time" aria-label="Estimated reading time">';
    html += escapeHTML(readingTime);
    html += '</span>';

    if (config.showWordCount) {
      html += '<span class="rx-reading-time__separator" aria-hidden="true"> · </span>';
      html += '<span class="rx-reading-time__words">';
      html += escapeHTML(formatWordCount(data, config));
      html += '</span>';
    }

    if (config.showCharacterCount) {
      html += '<span class="rx-reading-time__separator" aria-hidden="true"> · </span>';
      html += '<span class="rx-reading-time__characters">';
      html += escapeHTML(formatCharacterCount(data, config));
      html += '</span>';
    }

    return html;
  }

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

  function createInjectedOutput(contentElement) {
    var wrapper = document.createElement('div');

    wrapper.className = 'rx-reading-time rx-reading-time--auto';
    wrapper.setAttribute('data-rx-reading-time', '');
    wrapper.setAttribute('role', 'note');
    wrapper.setAttribute('aria-live', 'polite');

    return wrapper;
  }

  function injectOutputElement(contentElement, config) {
    if (!config.autoInject) {
      return null;
    }

    var existing = queryFirst(config.outputSelectors, document);

    if (existing) {
      return existing;
    }

    var output = createInjectedOutput(contentElement);

    if (config.injectPosition === 'after-title' && state.titleElement && state.titleElement.parentNode) {
      state.titleElement.parentNode.insertBefore(output, state.titleElement.nextSibling);
      return output;
    }

    if (config.injectPosition === 'after-content' && contentElement.parentNode) {
      contentElement.parentNode.insertBefore(output, contentElement.nextSibling);
      return output;
    }

    if (contentElement.parentNode) {
      contentElement.parentNode.insertBefore(output, contentElement);
      return output;
    }

    return null;
  }

  function renderOutputs(data, config) {
    state.outputElements.forEach(function renderOne(output) {
      if (!output) {
        return;
      }

      output.innerHTML = buildOutputHTML(data, config);
      output.setAttribute('data-rx-reading-minutes', String(data.minutes));
      output.setAttribute('data-rx-reading-seconds', String(data.seconds));
      output.setAttribute('data-rx-reading-words', String(data.words));
      output.setAttribute('data-rx-reading-characters', String(data.characters));
      output.setAttribute('data-rx-reading-language', data.language);
      output.setAttribute('title', buildOutputText(data, config));
    });
  }

  function createProgressBar(config) {
    if (!config.showProgressBar) {
      return null;
    }

    var existing = document.getElementById(config.progressBarId);

    if (existing) {
      return existing;
    }

    var bar = document.createElement('div');
    var inner = document.createElement('div');

    bar.id = config.progressBarId;
    bar.className = config.progressBarClass;
    bar.setAttribute('aria-hidden', 'true');

    inner.className = config.progressBarClass + '__inner';
    inner.style.width = '0%';

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

    return bar;
  }

  function createFloatingProgress(config) {
    if (!config.showFloatingProgress) {
      return null;
    }

    var existing = document.getElementById(config.floatingProgressId);

    if (existing) {
      return existing;
    }

    var box = document.createElement('div');

    box.id = config.floatingProgressId;
    box.className = config.floatingProgressClass;
    box.setAttribute('role', 'status');
    box.setAttribute('aria-live', 'polite');
    box.textContent = '0%';

    document.body.appendChild(box);

    return box;
  }

  function getContentProgress(contentElement) {
    if (!contentElement) {
      return 0;
    }

    var rect = contentElement.getBoundingClientRect();
    var viewportHeight = window.innerHeight || document.documentElement.clientHeight;
    var contentHeight = contentElement.offsetHeight || rect.height;

    if (contentHeight <= 0) {
      return 0;
    }

    var contentTop = rect.top + window.pageYOffset;
    var contentBottom = contentTop + contentHeight;
    var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
    var start = contentTop - viewportHeight * 0.15;
    var end = contentBottom - viewportHeight * 0.85;

    if (scrollTop <= start) {
      return 0;
    }

    if (scrollTop >= end) {
      return 100;
    }

    var progress = ((scrollTop - start) / (end - start)) * 100;

    return Math.max(0, Math.min(100, progress));
  }

  function updateProgressUI(progress, config) {
    progress = Math.round(progress);

    if (state.progressBar) {
      var inner = state.progressBar.querySelector('.' + config.progressBarClass + '__inner');

      if (inner) {
        inner.style.width = progress + '%';
      }

      state.progressBar.setAttribute('data-progress', String(progress));
    }

    if (state.floatingProgress) {
      var labels = getLabels(state.data, config);

      state.floatingProgress.textContent = labels.progress + ': ' + progress + '%';
      state.floatingProgress.setAttribute('data-progress', String(progress));
    }
  }

  function getStorageKey() {
    var path = window.location.pathname || 'home';
    return DEFAULTS.storagePrefix + path.replace(/[^\w-]/g, '_');
  }

  function saveProgress(progress, config) {
    if (!config.saveProgress || !window.localStorage) {
      return;
    }

    try {
      window.localStorage.setItem(getStorageKey(), JSON.stringify({
        progress: progress,
        updatedAt: Date.now(),
        url: window.location.href
      }));
    } catch (error) {
      log('Local storage save failed', error);
    }
  }

  function readSavedProgress(config) {
    if (!config.saveProgress || !window.localStorage) {
      return null;
    }

    try {
      var value = window.localStorage.getItem(getStorageKey());

      if (!value) {
        return null;
      }

      return JSON.parse(value);
    } catch (error) {
      log('Local storage read failed', error);
      return null;
    }
  }

  function dispatchEvent(name, detail, config) {
    if (!config.enableEvents) {
      return;
    }

    var eventName = config.eventPrefix + ':' + name;

    try {
      var event = new CustomEvent(eventName, {
        bubbles: true,
        cancelable: false,
        detail: detail
      });

      document.dispatchEvent(event);
    } catch (error) {
      log('Event dispatch failed', error);
    }
  }

  function onScroll(config) {
    if (state.scrollTicking) {
      return;
    }

    state.scrollTicking = true;

    window.requestAnimationFrame(function scrollFrame() {
      var progress = getContentProgress(state.contentElement);

      state.data.progress = progress;

      updateProgressUI(progress, config);

      if (progress > 0) {
        saveProgress(progress, config);
      }

      if (!state.completed && progress >= config.completionThreshold) {
        state.completed = true;

        dispatchEvent('completed', {
          version: RX_READING_TIME_VERSION,
          data: state.data,
          progress: progress
        }, config);

        if (config.showCompletionMessage) {
          showCompletionMessage(config);
        }
      }

      dispatchEvent('progress', {
        version: RX_READING_TIME_VERSION,
        progress: progress,
        data: state.data
      }, config);

      state.scrollTicking = false;
    });
  }

  function showCompletionMessage(config) {
    var labels = getLabels(state.data, config);
    var message = document.createElement('div');

    message.className = 'rx-reading-completed-message';
    message.setAttribute('role', 'status');
    message.setAttribute('aria-live', 'polite');
    message.textContent = labels.completed;

    document.body.appendChild(message);

    window.setTimeout(function removeMessage() {
      if (message && message.parentNode) {
        message.parentNode.removeChild(message);
      }
    }, 3000);
  }

  function setupMutationObserver(config) {
    if (!config.enableMutationObserver || !window.MutationObserver || !state.contentElement) {
      return;
    }

    if (state.mutationObserver) {
      state.mutationObserver.disconnect();
    }

    state.mutationObserver = new MutationObserver(function onMutate() {
      window.clearTimeout(state.mutationTimer);

      state.mutationTimer = window.setTimeout(function recalcAfterMutation() {
        recalculate(config);
      }, config.mutationDebounce);
    });

    state.mutationObserver.observe(state.contentElement, {
      childList: true,
      subtree: true,
      characterData: true
    });
  }

  function addBaseStyles(config) {
    if (document.getElementById('rx-reading-time-style')) {
      return;
    }

    var style = document.createElement('style');

    style.id = 'rx-reading-time-style';
    style.textContent =
      '.rx-reading-time{' +
      'display:flex;' +
      'align-items:center;' +
      'gap:.25rem;' +
      'font-size:.9375rem;' +
      'line-height:1.5;' +
      'color:var(--rx-color-muted,#64748b);' +
      'margin:0 0 1rem;' +
      '}' +

      '.rx-reading-time__time{' +
      'font-weight:600;' +
      'color:var(--rx-color-text,#1e293b);' +
      '}' +

      '.rx-reading-time__separator{' +
      'color:var(--rx-color-border,#94a3b8);' +
      '}' +

      '.rx-reading-progress-bar{' +
      'position:fixed;' +
      'top:0;' +
      'left:0;' +
      'right:0;' +
      'z-index:99999;' +
      'height:3px;' +
      'background:transparent;' +
      'pointer-events:none;' +
      '}' +

      '.rx-reading-progress-bar__inner{' +
      'display:block;' +
      'height:100%;' +
      'width:0%;' +
      'background:var(--rx-primary,#0ea5e9);' +
      'transition:width .12s linear;' +
      '}' +

      '.rx-floating-reading-progress{' +
      'position:fixed;' +
      'right:1rem;' +
      'bottom:1rem;' +
      'z-index:99998;' +
      'padding:.5rem .75rem;' +
      'border-radius:999px;' +
      'font-size:.8125rem;' +
      'line-height:1;' +
      'color:var(--rx-floating-progress-color,#fff);' +
      'background:var(--rx-floating-progress-bg,rgba(15,23,42,.88));' +
      'box-shadow:0 10px 25px rgba(15,23,42,.15);' +
      '}' +

      '.rx-reading-completed-message{' +
      'position:fixed;' +
      'left:50%;' +
      'bottom:2rem;' +
      'z-index:99999;' +
      'transform:translateX(-50%);' +
      'padding:.75rem 1rem;' +
      'border-radius:.75rem;' +
      'font-size:.9375rem;' +
      'color:#fff;' +
      'background:rgba(15,23,42,.92);' +
      'box-shadow:0 20px 40px rgba(15,23,42,.25);' +
      '}' +

      '@media (prefers-reduced-motion:reduce){' +
      '.rx-reading-progress-bar__inner{transition:none;}' +
      '}';

    document.head.appendChild(style);
  }

  function recalculate(config) {
    if (!state.contentElement) {
      return null;
    }

    state.data = calculateReadingData(state.contentElement, config);

    renderOutputs(state.data, config);
    onScroll(config);

    dispatchEvent('updated', {
      version: RX_READING_TIME_VERSION,
      data: state.data
    }, config);

    return state.data;
  }

  function destroy() {
    window.removeEventListener('scroll', state._scrollHandler);
    window.removeEventListener('resize', state._resizeHandler);

    if (state.mutationObserver) {
      state.mutationObserver.disconnect();
      state.mutationObserver = null;
    }

    state.initialized = false;
  }

  function init(userConfig) {
    if (state.initialized) {
      return state.data;
    }

    var windowConfig = getConfigFromWindow();

    var contentElement = queryFirst(
      userConfig && userConfig.contentSelectors
        ? userConfig.contentSelectors
        : windowConfig.contentSelectors || DEFAULTS.contentSelectors,
      document
    );

    var datasetConfig = getConfigFromDataset(contentElement);

    var config = extend({}, DEFAULTS, windowConfig, datasetConfig, userConfig || {});

    DEFAULTS.debug = config.debug;
    DEFAULTS.storagePrefix = config.storagePrefix;

    state.contentElement = contentElement;
    state.titleElement = queryFirst(config.titleSelectors, document);

    if (!state.contentElement) {
      log('No content element found.');
      return null;
    }

    addBaseStyles(config);

    var outputElements = queryAll(config.outputSelectors, document);
    var injected = injectOutputElement(state.contentElement, config);

    if (injected && outputElements.indexOf(injected) === -1) {
      outputElements.push(injected);
    }

    state.outputElements = outputElements;
    state.progressBar = createProgressBar(config);
    state.floatingProgress = createFloatingProgress(config);

    var saved = readSavedProgress(config);

    if (saved && typeof saved.progress === 'number') {
      state.data.progress = saved.progress;
    }

    recalculate(config);

    state._scrollHandler = function scrollHandler() {
      onScroll(config);
    };

    state._resizeHandler = function resizeHandler() {
      onScroll(config);
    };

    window.addEventListener('scroll', state._scrollHandler, { passive: true });
    window.addEventListener('resize', state._resizeHandler, { passive: true });

    setupMutationObserver(config);

    state.initialized = true;

    dispatchEvent('ready', {
      version: RX_READING_TIME_VERSION,
      data: state.data
    }, config);

    return state.data;
  }

  window.RXReadingTime = {
    version: RX_READING_TIME_VERSION,
    init: init,
    recalculate: function publicRecalculate(customConfig) {
      var config = extend({}, DEFAULTS, getConfigFromWindow(), customConfig || {});
      return recalculate(config);
    },
    destroy: destroy,
    getData: function getData() {
      return extend({}, state.data);
    },
    getProgress: function getProgress() {
      return state.data.progress || 0;
    },
    formatReadingTime: function publicFormatReadingTime(data, customConfig) {
      var config = extend({}, DEFAULTS, getConfigFromWindow(), customConfig || {});
      return formatReadingTime(data || state.data, config);
    }
  };

  ready(function autoStart() {
    init();
  });

})(window, document);

Optional HTML output position

Add this inside your single post template where you want reading time to show:

<div class="rx-reading-time" data-rx-reading-time></div>

Optional global setting before loading this chunk

Add before enqueueing this JS, or localize it from WordPress:

<script>
window.rxReadingTimeConfig = {
  wordsPerMinute: 220,
  bengaliWordsPerMinute: 180,
  showWordCount: true,
  showCharacterCount: false,
  showProgressBar: true,
  showFloatingProgress: false,
  autoInject: true,
  injectPosition: 'after-title'
};
</script>

WordPress enqueue example

wp_enqueue_script(
    'rx-reading-time',
    get_template_directory_uri() . '/assets/static-js/static-chunks-js/chunk-034-reading-time.js',
    array(),
    RX_THEME_VERSION,
    true
);

This file is safe as a static chunk because it has no jQuery dependency, loads after DOM ready, auto-detects article content, supports Bengali and English reading speed, creates a top progress bar, saves reading progress, and exposes window.RXReadingTime for future RX Theme advanced features.

Leave a Reply

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