chunk-041-medical-card.js

I’ll build this as a safe, standalone advanced frontend chunk for RX Theme medical cards: filtering, search, tabs, accordion, reading progress, lazy reveal, copy/share, accessibility, and graceful fallback without breaking WordPress.

Friend, create this file:

rx-theme/assets/static-js/static-chunks-js/chunk-041-medical-card.js

Paste this full advanced code:

/*!
 * RX Theme - Medical Card Advanced Chunk
 * File: assets/static-js/static-chunks-js/chunk-041-medical-card.js
 * Version: 1.0.0
 *
 * Features:
 * - Medical card search
 * - Category filter
 * - Severity filter
 * - Sorting
 * - Favorite cards with localStorage
 * - Compare select
 * - Expand/collapse details
 * - Accordion support
 * - Tabs support
 * - Copy card link
 * - Native share support
 * - Print card
 * - Lazy reveal animation
 * - Reading progress inside cards
 * - Accessibility improvements
 * - Safe no-dependency JavaScript
 */

(function () {
  'use strict';

  const RXMedicalCard = {
    config: {
      rootSelector: '[data-rx-medical-cards]',
      cardSelector: '[data-rx-medical-card]',
      searchSelector: '[data-rx-medical-search]',
      categorySelector: '[data-rx-medical-category]',
      severitySelector: '[data-rx-medical-severity]',
      sortSelector: '[data-rx-medical-sort]',
      resetSelector: '[data-rx-medical-reset]',
      countSelector: '[data-rx-medical-count]',
      emptySelector: '[data-rx-medical-empty]',
      compareBarSelector: '[data-rx-compare-bar]',
      compareCountSelector: '[data-rx-compare-count]',
      storagePrefix: 'rx_theme_medical_card_',
      maxCompare: 3,
      revealClass: 'is-rx-visible',
      hiddenClass: 'is-rx-hidden',
      activeClass: 'is-rx-active',
      favoriteClass: 'is-rx-favorite',
      selectedClass: 'is-rx-selected',
      animationDelay: 80
    },

    state: {
      roots: [],
      favorites: new Set(),
      compareItems: new Set(),
      reduceMotion: false
    },

    init() {
      this.state.reduceMotion = window.matchMedia &&
        window.matchMedia('(prefers-reduced-motion: reduce)').matches;

      this.loadFavorites();
      this.state.roots = Array.from(document.querySelectorAll(this.config.rootSelector));

      if (!this.state.roots.length) return;

      this.state.roots.forEach((root) => {
        this.prepareRoot(root);
        this.bindRootEvents(root);
        this.prepareCards(root);
        this.applyFilters(root);
      });

      this.bindGlobalEvents();
      this.observeCards();
      this.dispatch('rxMedicalCardsReady', {
        roots: this.state.roots.length
      });
    },

    prepareRoot(root) {
      if (!root.id) {
        root.id = 'rx-medical-cards-' + this.randomId();
      }

      root.setAttribute('data-rx-ready', 'true');

      const search = root.querySelector(this.config.searchSelector);
      if (search) {
        search.setAttribute('autocomplete', 'off');
        search.setAttribute('spellcheck', 'false');
        search.setAttribute('aria-label', search.getAttribute('aria-label') || 'Search medical cards');
      }

      const empty = root.querySelector(this.config.emptySelector);
      if (empty) {
        empty.hidden = true;
        empty.setAttribute('role', 'status');
        empty.setAttribute('aria-live', 'polite');
      }

      const count = root.querySelector(this.config.countSelector);
      if (count) {
        count.setAttribute('role', 'status');
        count.setAttribute('aria-live', 'polite');
      }
    },

    prepareCards(root) {
      const cards = this.getCards(root);

      cards.forEach((card, index) => {
        if (!card.id) {
          card.id = 'rx-medical-card-' + this.randomId();
        }

        card.dataset.rxIndex = String(index);
        card.setAttribute('tabindex', '0');

        this.prepareFavoriteButton(card);
        this.prepareCompareButton(card);
        this.prepareToggleButton(card);
        this.prepareShareButton(card);
        this.prepareCopyButton(card);
        this.preparePrintButton(card);
        this.prepareReadingProgress(card);
        this.prepareAccordions(card);
        this.prepareTabs(card);
        this.restoreFavoriteUI(card);
      });
    },

    bindRootEvents(root) {
      const search = root.querySelector(this.config.searchSelector);
      const category = root.querySelector(this.config.categorySelector);
      const severity = root.querySelector(this.config.severitySelector);
      const sort = root.querySelector(this.config.sortSelector);
      const reset = root.querySelector(this.config.resetSelector);

      if (search) {
        search.addEventListener('input', this.debounce(() => {
          this.applyFilters(root);
        }, 180));
      }

      if (category) {
        category.addEventListener('change', () => this.applyFilters(root));
      }

      if (severity) {
        severity.addEventListener('change', () => this.applyFilters(root));
      }

      if (sort) {
        sort.addEventListener('change', () => this.applySort(root));
      }

      if (reset) {
        reset.addEventListener('click', () => this.resetRoot(root));
      }

      root.addEventListener('click', (event) => {
        const favoriteBtn = event.target.closest('[data-rx-card-favorite]');
        const compareBtn = event.target.closest('[data-rx-card-compare]');
        const toggleBtn = event.target.closest('[data-rx-card-toggle]');
        const shareBtn = event.target.closest('[data-rx-card-share]');
        const copyBtn = event.target.closest('[data-rx-card-copy]');
        const printBtn = event.target.closest('[data-rx-card-print]');
        const accordionBtn = event.target.closest('[data-rx-accordion-button]');
        const tabBtn = event.target.closest('[data-rx-tab-button]');

        if (favoriteBtn) {
          event.preventDefault();
          this.toggleFavorite(favoriteBtn.closest(this.config.cardSelector));
        }

        if (compareBtn) {
          event.preventDefault();
          this.toggleCompare(compareBtn.closest(this.config.cardSelector));
        }

        if (toggleBtn) {
          event.preventDefault();
          this.toggleCardDetails(toggleBtn.closest(this.config.cardSelector), toggleBtn);
        }

        if (shareBtn) {
          event.preventDefault();
          this.shareCard(shareBtn.closest(this.config.cardSelector));
        }

        if (copyBtn) {
          event.preventDefault();
          this.copyCardLink(copyBtn.closest(this.config.cardSelector), copyBtn);
        }

        if (printBtn) {
          event.preventDefault();
          this.printCard(printBtn.closest(this.config.cardSelector));
        }

        if (accordionBtn) {
          event.preventDefault();
          this.toggleAccordion(accordionBtn);
        }

        if (tabBtn) {
          event.preventDefault();
          this.activateTab(tabBtn);
        }
      });

      root.addEventListener('keydown', (event) => {
        if (event.key !== 'Enter' && event.key !== ' ') return;

        const card = event.target.closest(this.config.cardSelector);
        if (!card || event.target.closest('button, a, input, select, textarea')) return;

        event.preventDefault();
        const toggleBtn = card.querySelector('[data-rx-card-toggle]');
        this.toggleCardDetails(card, toggleBtn);
      });
    },

    bindGlobalEvents() {
      document.addEventListener('scroll', this.throttle(() => {
        this.updateReadingProgress();
      }, 100), { passive: true });

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

      document.addEventListener('visibilitychange', () => {
        if (document.visibilityState === 'visible') {
          this.loadFavorites();
          this.state.roots.forEach((root) => {
            this.getCards(root).forEach((card) => this.restoreFavoriteUI(card));
          });
        }
      });
    },

    getCards(root) {
      return Array.from(root.querySelectorAll(this.config.cardSelector));
    },

    applyFilters(root) {
      const search = root.querySelector(this.config.searchSelector);
      const category = root.querySelector(this.config.categorySelector);
      const severity = root.querySelector(this.config.severitySelector);

      const query = search ? search.value.trim().toLowerCase() : '';
      const selectedCategory = category ? category.value.trim().toLowerCase() : '';
      const selectedSeverity = severity ? severity.value.trim().toLowerCase() : '';

      let visibleCount = 0;

      this.getCards(root).forEach((card) => {
        const title = this.getText(card, '[data-rx-card-title]');
        const summary = this.getText(card, '[data-rx-card-summary]');
        const keywords = (card.dataset.rxKeywords || '').toLowerCase();
        const cardCategory = (card.dataset.rxCategory || '').toLowerCase();
        const cardSeverity = (card.dataset.rxSeverity || '').toLowerCase();

        const searchableText = `${title} ${summary} ${keywords} ${cardCategory} ${cardSeverity}`.toLowerCase();

        const matchSearch = !query || searchableText.includes(query);
        const matchCategory = !selectedCategory || selectedCategory === 'all' || cardCategory === selectedCategory;
        const matchSeverity = !selectedSeverity || selectedSeverity === 'all' || cardSeverity === selectedSeverity;

        const isVisible = matchSearch && matchCategory && matchSeverity;

        card.hidden = !isVisible;
        card.classList.toggle(this.config.hiddenClass, !isVisible);

        if (isVisible) visibleCount++;
      });

      this.updateCount(root, visibleCount);
      this.updateEmptyState(root, visibleCount);
      this.applySort(root);
      this.dispatch('rxMedicalCardsFiltered', {
        rootId: root.id,
        visibleCount
      });
    },

    applySort(root) {
      const sort = root.querySelector(this.config.sortSelector);
      if (!sort) return;

      const sortValue = sort.value || 'default';
      const cards = this.getCards(root);
      const parent = cards[0] ? cards[0].parentNode : null;

      if (!parent) return;

      const sortedCards = cards.slice().sort((a, b) => {
        if (sortValue === 'title-asc') {
          return this.getCardTitle(a).localeCompare(this.getCardTitle(b));
        }

        if (sortValue === 'title-desc') {
          return this.getCardTitle(b).localeCompare(this.getCardTitle(a));
        }

        if (sortValue === 'severity-high') {
          return this.getSeverityScore(b) - this.getSeverityScore(a);
        }

        if (sortValue === 'severity-low') {
          return this.getSeverityScore(a) - this.getSeverityScore(b);
        }

        if (sortValue === 'newest') {
          return this.getDateScore(b) - this.getDateScore(a);
        }

        if (sortValue === 'oldest') {
          return this.getDateScore(a) - this.getDateScore(b);
        }

        return Number(a.dataset.rxIndex || 0) - Number(b.dataset.rxIndex || 0);
      });

      sortedCards.forEach((card) => parent.appendChild(card));
    },

    resetRoot(root) {
      const search = root.querySelector(this.config.searchSelector);
      const category = root.querySelector(this.config.categorySelector);
      const severity = root.querySelector(this.config.severitySelector);
      const sort = root.querySelector(this.config.sortSelector);

      if (search) search.value = '';
      if (category) category.value = 'all';
      if (severity) severity.value = 'all';
      if (sort) sort.value = 'default';

      this.applyFilters(root);
    },

    updateCount(root, visibleCount) {
      const count = root.querySelector(this.config.countSelector);
      if (!count) return;

      const total = this.getCards(root).length;
      count.textContent = `${visibleCount} of ${total} medical cards shown`;
    },

    updateEmptyState(root, visibleCount) {
      const empty = root.querySelector(this.config.emptySelector);
      if (!empty) return;

      empty.hidden = visibleCount !== 0;
    },

    prepareFavoriteButton(card) {
      let btn = card.querySelector('[data-rx-card-favorite]');
      if (!btn) return;

      btn.setAttribute('type', 'button');
      btn.setAttribute('aria-label', 'Save this medical card');
      btn.setAttribute('aria-pressed', 'false');
    },

    prepareCompareButton(card) {
      let btn = card.querySelector('[data-rx-card-compare]');
      if (!btn) return;

      btn.setAttribute('type', 'button');
      btn.setAttribute('aria-label', 'Add this medical card to compare');
      btn.setAttribute('aria-pressed', 'false');
    },

    prepareToggleButton(card) {
      const btn = card.querySelector('[data-rx-card-toggle]');
      const details = card.querySelector('[data-rx-card-details]');

      if (!btn || !details) return;

      if (!details.id) {
        details.id = 'rx-card-details-' + this.randomId();
      }

      btn.setAttribute('type', 'button');
      btn.setAttribute('aria-expanded', 'false');
      btn.setAttribute('aria-controls', details.id);

      details.hidden = true;
    },

    prepareShareButton(card) {
      const btn = card.querySelector('[data-rx-card-share]');
      if (!btn) return;

      btn.setAttribute('type', 'button');
      btn.setAttribute('aria-label', 'Share this medical card');

      if (!navigator.share) {
        btn.dataset.rxFallbackShare = 'true';
      }
    },

    prepareCopyButton(card) {
      const btn = card.querySelector('[data-rx-card-copy]');
      if (!btn) return;

      btn.setAttribute('type', 'button');
      btn.setAttribute('aria-label', 'Copy this card link');
    },

    preparePrintButton(card) {
      const btn = card.querySelector('[data-rx-card-print]');
      if (!btn) return;

      btn.setAttribute('type', 'button');
      btn.setAttribute('aria-label', 'Print this medical card');
    },

    prepareReadingProgress(card) {
      const progress = card.querySelector('[data-rx-card-progress]');
      if (!progress) return;

      progress.setAttribute('role', 'progressbar');
      progress.setAttribute('aria-valuemin', '0');
      progress.setAttribute('aria-valuemax', '100');
      progress.setAttribute('aria-valuenow', '0');

      const bar = progress.querySelector('[data-rx-card-progress-bar]');
      if (bar) {
        bar.style.width = '0%';
      }
    },

    prepareAccordions(card) {
      const buttons = Array.from(card.querySelectorAll('[data-rx-accordion-button]'));

      buttons.forEach((button) => {
        const panel = this.getAccordionPanel(button);
        if (!panel) return;

        if (!panel.id) {
          panel.id = 'rx-accordion-panel-' + this.randomId();
        }

        button.setAttribute('type', 'button');
        button.setAttribute('aria-expanded', 'false');
        button.setAttribute('aria-controls', panel.id);

        panel.hidden = true;
      });
    },

    prepareTabs(card) {
      const tabButtons = Array.from(card.querySelectorAll('[data-rx-tab-button]'));
      if (!tabButtons.length) return;

      tabButtons.forEach((button, index) => {
        const panel = this.getTabPanel(button);
        if (!panel) return;

        if (!button.id) {
          button.id = 'rx-tab-button-' + this.randomId();
        }

        if (!panel.id) {
          panel.id = 'rx-tab-panel-' + this.randomId();
        }

        button.setAttribute('type', 'button');
        button.setAttribute('role', 'tab');
        button.setAttribute('aria-controls', panel.id);
        button.setAttribute('aria-selected', index === 0 ? 'true' : 'false');

        panel.setAttribute('role', 'tabpanel');
        panel.setAttribute('aria-labelledby', button.id);
        panel.hidden = index !== 0;

        button.classList.toggle(this.config.activeClass, index === 0);
        panel.classList.toggle(this.config.activeClass, index === 0);
      });
    },

    toggleFavorite(card) {
      if (!card) return;

      const key = this.getCardKey(card);

      if (this.state.favorites.has(key)) {
        this.state.favorites.delete(key);
      } else {
        this.state.favorites.add(key);
      }

      this.saveFavorites();
      this.restoreFavoriteUI(card);

      this.dispatch('rxMedicalCardFavoriteChanged', {
        cardId: card.id,
        favorite: this.state.favorites.has(key)
      });
    },

    restoreFavoriteUI(card) {
      const btn = card.querySelector('[data-rx-card-favorite]');
      if (!btn) return;

      const key = this.getCardKey(card);
      const isFavorite = this.state.favorites.has(key);

      card.classList.toggle(this.config.favoriteClass, isFavorite);
      btn.setAttribute('aria-pressed', String(isFavorite));
      btn.dataset.rxFavoriteState = isFavorite ? 'saved' : 'unsaved';

      const label = btn.querySelector('[data-rx-card-favorite-label]');
      if (label) {
        label.textContent = isFavorite ? 'Saved' : 'Save';
      }
    },

    toggleCompare(card) {
      if (!card) return;

      const key = this.getCardKey(card);
      const btn = card.querySelector('[data-rx-card-compare]');

      if (this.state.compareItems.has(key)) {
        this.state.compareItems.delete(key);
      } else {
        if (this.state.compareItems.size >= this.config.maxCompare) {
          this.showNotice(card, `You can compare up to ${this.config.maxCompare} cards.`);
          return;
        }

        this.state.compareItems.add(key);
      }

      const selected = this.state.compareItems.has(key);

      card.classList.toggle(this.config.selectedClass, selected);

      if (btn) {
        btn.setAttribute('aria-pressed', String(selected));
      }

      this.updateCompareBar();

      this.dispatch('rxMedicalCardCompareChanged', {
        cardId: card.id,
        selected
      });
    },

    updateCompareBar() {
      const bars = Array.from(document.querySelectorAll(this.config.compareBarSelector));

      bars.forEach((bar) => {
        const count = bar.querySelector(this.config.compareCountSelector);
        const number = this.state.compareItems.size;

        bar.hidden = number === 0;

        if (count) {
          count.textContent = String(number);
        }
      });
    },

    toggleCardDetails(card, btn) {
      if (!card) return;

      const details = card.querySelector('[data-rx-card-details]');
      if (!details) return;

      const isOpen = !details.hidden;

      details.hidden = isOpen;
      card.classList.toggle(this.config.activeClass, !isOpen);

      if (btn) {
        btn.setAttribute('aria-expanded', String(!isOpen));

        const label = btn.querySelector('[data-rx-card-toggle-label]');
        if (label) {
          label.textContent = isOpen ? 'Read more' : 'Show less';
        }
      }

      if (!isOpen) {
        this.smoothFocus(details);
      }

      this.dispatch('rxMedicalCardToggle', {
        cardId: card.id,
        open: !isOpen
      });
    },

    toggleAccordion(button) {
      const panel = this.getAccordionPanel(button);
      if (!panel) return;

      const isOpen = !panel.hidden;

      panel.hidden = isOpen;
      button.setAttribute('aria-expanded', String(!isOpen));
      button.classList.toggle(this.config.activeClass, !isOpen);
      panel.classList.toggle(this.config.activeClass, !isOpen);
    },

    activateTab(button) {
      const card = button.closest(this.config.cardSelector);
      if (!card) return;

      const group = button.closest('[data-rx-tabs]');
      if (!group) return;

      const buttons = Array.from(group.querySelectorAll('[data-rx-tab-button]'));

      buttons.forEach((btn) => {
        const panel = this.getTabPanel(btn);
        const active = btn === button;

        btn.setAttribute('aria-selected', String(active));
        btn.classList.toggle(this.config.activeClass, active);

        if (panel) {
          panel.hidden = !active;
          panel.classList.toggle(this.config.activeClass, active);
        }
      });
    },

    async shareCard(card) {
      if (!card) return;

      const title = this.getCardTitle(card);
      const text = this.getText(card, '[data-rx-card-summary]');
      const url = this.getCardUrl(card);

      if (navigator.share) {
        try {
          await navigator.share({ title, text, url });
          this.dispatch('rxMedicalCardShared', { cardId: card.id });
          return;
        } catch (error) {
          if (error && error.name === 'AbortError') return;
        }
      }

      this.copyText(url);
      this.showNotice(card, 'Card link copied.');
    },

    copyCardLink(card, button) {
      if (!card) return;

      const url = this.getCardUrl(card);

      this.copyText(url).then(() => {
        this.showButtonFeedback(button, 'Copied');
        this.showNotice(card, 'Card link copied.');
      });
    },

    printCard(card) {
      if (!card) return;

      const printWindow = window.open('', '_blank', 'noopener,noreferrer,width=900,height=700');
      if (!printWindow) {
        window.print();
        return;
      }

      const title = this.escapeHtml(this.getCardTitle(card));
      const content = card.cloneNode(true);

      content.querySelectorAll('button, [data-rx-card-actions]').forEach((element) => {
        element.remove();
      });

      printWindow.document.write(`
        <!doctype html>
        <html>
        <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <title>${title}</title>
          <style>
            body {
              font-family: Arial, sans-serif;
              line-height: 1.6;
              color: #111;
              padding: 24px;
            }
            h1, h2, h3 {
              line-height: 1.3;
            }
            .rx-print-note {
              margin-top: 24px;
              font-size: 13px;
              color: #555;
              border-top: 1px solid #ddd;
              padding-top: 12px;
            }
          </style>
        </head>
        <body>
          ${content.outerHTML}
          <div class="rx-print-note">
            Medical information is for education only. Always follow a qualified clinician’s advice.
          </div>
        </body>
        </html>
      `);

      printWindow.document.close();
      printWindow.focus();
      printWindow.print();
    },

    updateReadingProgress() {
      const cards = Array.from(document.querySelectorAll(this.config.cardSelector));

      cards.forEach((card) => {
        const progress = card.querySelector('[data-rx-card-progress]');
        const bar = card.querySelector('[data-rx-card-progress-bar]');

        if (!progress || !bar) return;

        const rect = card.getBoundingClientRect();
        const viewportHeight = window.innerHeight || document.documentElement.clientHeight;

        const total = rect.height + viewportHeight;
        const passed = viewportHeight - rect.top;
        const percent = Math.max(0, Math.min(100, Math.round((passed / total) * 100)));

        bar.style.width = percent + '%';
        progress.setAttribute('aria-valuenow', String(percent));
      });
    },

    observeCards() {
      const cards = Array.from(document.querySelectorAll(this.config.cardSelector));

      if (this.state.reduceMotion) {
        cards.forEach((card) => card.classList.add(this.config.revealClass));
        return;
      }

      if (!('IntersectionObserver' in window)) {
        cards.forEach((card) => card.classList.add(this.config.revealClass));
        return;
      }

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

          const card = entry.target;
          const index = Number(card.dataset.rxIndex || 0);

          window.setTimeout(() => {
            card.classList.add(this.config.revealClass);
          }, Math.min(index * this.config.animationDelay, 500));

          observer.unobserve(card);
        });
      }, {
        root: null,
        threshold: 0.12
      });

      cards.forEach((card) => observer.observe(card));
    },

    equalizeCardHeights() {
      this.state.roots.forEach((root) => {
        const cards = this.getCards(root).filter((card) => !card.hidden);
        const titles = cards.map((card) => card.querySelector('[data-rx-card-title]')).filter(Boolean);

        titles.forEach((title) => {
          title.style.minHeight = '';
        });

        if (window.innerWidth < 768) return;

        let maxHeight = 0;

        titles.forEach((title) => {
          maxHeight = Math.max(maxHeight, title.offsetHeight);
        });

        titles.forEach((title) => {
          title.style.minHeight = maxHeight + 'px';
        });
      });
    },

    getAccordionPanel(button) {
      const id = button.getAttribute('aria-controls') || button.dataset.rxAccordionTarget;

      if (id) {
        return document.getElementById(id.replace('#', ''));
      }

      return button.nextElementSibling;
    },

    getTabPanel(button) {
      const id = button.getAttribute('aria-controls') || button.dataset.rxTabTarget;

      if (id) {
        return document.getElementById(id.replace('#', ''));
      }

      const group = button.closest('[data-rx-tabs]');
      if (!group) return null;

      const index = Array.from(group.querySelectorAll('[data-rx-tab-button]')).indexOf(button);
      return group.querySelectorAll('[data-rx-tab-panel]')[index] || null;
    },

    getText(parent, selector) {
      const element = parent.querySelector(selector);
      return element ? element.textContent.trim() : '';
    },

    getCardTitle(card) {
      return this.getText(card, '[data-rx-card-title]') ||
        card.getAttribute('aria-label') ||
        'Medical Card';
    },

    getSeverityScore(card) {
      const severity = (card.dataset.rxSeverity || '').toLowerCase();

      const scores = {
        critical: 5,
        severe: 4,
        moderate: 3,
        mild: 2,
        low: 1,
        normal: 0
      };

      return scores[severity] || 0;
    },

    getDateScore(card) {
      const rawDate = card.dataset.rxDate || card.getAttribute('datetime') || '';
      const time = Date.parse(rawDate);

      return Number.isNaN(time) ? 0 : time;
    },

    getCardKey(card) {
      return card.dataset.rxCardId || card.id || this.getCardTitle(card).toLowerCase().replace(/\s+/g, '-');
    },

    getCardUrl(card) {
      const base = window.location.href.split('#')[0];
      return `${base}#${card.id}`;
    },

    loadFavorites() {
      try {
        const raw = localStorage.getItem(this.config.storagePrefix + 'favorites');
        const list = raw ? JSON.parse(raw) : [];
        this.state.favorites = new Set(Array.isArray(list) ? list : []);
      } catch (error) {
        this.state.favorites = new Set();
      }
    },

    saveFavorites() {
      try {
        localStorage.setItem(
          this.config.storagePrefix + 'favorites',
          JSON.stringify(Array.from(this.state.favorites))
        );
      } catch (error) {
        // localStorage may be blocked. UI will still work during current page load.
      }
    },

    async copyText(text) {
      if (navigator.clipboard && window.isSecureContext) {
        return navigator.clipboard.writeText(text);
      }

      const textarea = document.createElement('textarea');
      textarea.value = text;
      textarea.setAttribute('readonly', '');
      textarea.style.position = 'fixed';
      textarea.style.left = '-9999px';
      textarea.style.top = '-9999px';

      document.body.appendChild(textarea);
      textarea.select();

      try {
        document.execCommand('copy');
      } finally {
        textarea.remove();
      }
    },

    showButtonFeedback(button, text) {
      if (!button) return;

      const oldText = button.textContent;
      button.textContent = text;

      window.setTimeout(() => {
        button.textContent = oldText;
      }, 1200);
    },

    showNotice(card, message) {
      let notice = card.querySelector('[data-rx-card-notice]');

      if (!notice) {
        notice = document.createElement('div');
        notice.setAttribute('data-rx-card-notice', '');
        notice.setAttribute('role', 'status');
        notice.setAttribute('aria-live', 'polite');
        notice.className = 'rx-card-notice';
        card.appendChild(notice);
      }

      notice.textContent = message;
      notice.hidden = false;

      window.clearTimeout(notice.rxNoticeTimer);
      notice.rxNoticeTimer = window.setTimeout(() => {
        notice.hidden = true;
      }, 2400);
    },

    smoothFocus(element) {
      if (!element) return;

      if (!this.state.reduceMotion && typeof element.scrollIntoView === 'function') {
        element.scrollIntoView({
          behavior: 'smooth',
          block: 'nearest'
        });
      }

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

    debounce(callback, wait) {
      let timeout;

      return function debounced(...args) {
        window.clearTimeout(timeout);
        timeout = window.setTimeout(() => callback.apply(this, args), wait);
      };
    },

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

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

        waiting = true;

        window.setTimeout(() => {
          callback.apply(this, args);
          waiting = false;
        }, wait);
      };
    },

    randomId() {
      return Math.random().toString(36).slice(2, 10);
    },

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

    dispatch(name, detail) {
      document.dispatchEvent(new CustomEvent(name, {
        bubbles: true,
        detail
      }));
    }
  };

  window.RXMedicalCard = RXMedicalCard;

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

Use this kind of HTML structure so the JS can work:

<div data-rx-medical-cards>
  <input data-rx-medical-search type="search" placeholder="Search disease, symptom, test..." />

  <select data-rx-medical-category>
    <option value="all">All Categories</option>
    <option value="orthopedic">Orthopedic</option>
    <option value="blood">Blood</option>
    <option value="eye">Eye</option>
  </select>

  <select data-rx-medical-severity>
    <option value="all">All Severity</option>
    <option value="mild">Mild</option>
    <option value="moderate">Moderate</option>
    <option value="severe">Severe</option>
  </select>

  <select data-rx-medical-sort>
    <option value="default">Default</option>
    <option value="title-asc">Title A-Z</option>
    <option value="title-desc">Title Z-A</option>
    <option value="severity-high">Severity High</option>
    <option value="severity-low">Severity Low</option>
    <option value="newest">Newest</option>
    <option value="oldest">Oldest</option>
  </select>

  <button data-rx-medical-reset type="button">Reset</button>

  <p data-rx-medical-count></p>
  <p data-rx-medical-empty>No medical card found.</p>

  <article
    data-rx-medical-card
    data-rx-card-id="card-001"
    data-rx-category="orthopedic"
    data-rx-severity="moderate"
    data-rx-date="2026-05-18"
    data-rx-keywords="disc pain spine nerve"
  >
    <div data-rx-card-progress>
      <span data-rx-card-progress-bar></span>
    </div>

    <h2 data-rx-card-title>Thoracic Disc Problem</h2>
    <p data-rx-card-summary>Simple educational summary for patients.</p>

    <div data-rx-card-actions>
      <button data-rx-card-favorite>
        <span data-rx-card-favorite-label>Save</span>
      </button>

      <button data-rx-card-compare>Compare</button>
      <button data-rx-card-share>Share</button>
      <button data-rx-card-copy>Copy Link</button>
      <button data-rx-card-print>Print</button>

      <button data-rx-card-toggle>
        <span data-rx-card-toggle-label>Read more</span>
      </button>
    </div>

    <div data-rx-card-details>
      <p>This section contains more medical education details.</p>

      <button data-rx-accordion-button>Symptoms</button>
      <div>
        <p>Symptoms explanation here.</p>
      </div>

      <div data-rx-tabs>
        <button data-rx-tab-button>Overview</button>
        <button data-rx-tab-button>Tests</button>

        <div data-rx-tab-panel>
          <p>Overview content.</p>
        </div>

        <div data-rx-tab-panel>
          <p>Diagnostic test content.</p>
        </div>
      </div>
    </div>
  </article>
</div>

Then enqueue it in RX Theme:

wp_enqueue_script(
    'rx-chunk-041-medical-card',
    get_template_directory_uri() . '/assets/static-js/static-chunks-js/chunk-041-medical-card.js',
    array(),
    wp_get_theme()->get('Version'),
    true
);

This file is safe as a frontend chunk because it uses no jQuery, no external library, no API call, and no blocking code.

Leave a Reply

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