CB Emote Size Checker (250x80)

Highlights oversized emotes on Chaturbate by showing actual image dimensions and changing background color (max: 250x80)

// ==UserScript==
// @name         CB Emote Size Checker (250x80)
// @namespace    aravvn.tools
// @version      1.2.1
// @description  Highlights oversized emotes on Chaturbate by showing actual image dimensions and changing background color (max: 250x80)
// @author       aravvn
// @license      CC-BY-NC-SA-4.0
// @match        https://chaturbate.com/*
// @match        https://*.chaturbate.com/*
// @grant        none
// @run-at       document-idle
// @icon         https://chaturbate.com/favicon.ico
// @noframes
// ==/UserScript==

(function () {
  'use strict';

  const THRESHOLD_WIDTH = 250;
  const THRESHOLD_HEIGHT = 80;
  const observedWrappers = new WeakSet();

  function createSizeLabel(wrapper) {
    let label = wrapper.querySelector('.emote-size-label');
    if (!label) {
      label = document.createElement('div');
      label.className = 'emote-size-label';
      Object.assign(label.style, {
        position: 'absolute',
        bottom: '4px',
        right: '6px',
        fontSize: '10px',
        fontFamily: 'monospace',
        padding: '2px 4px',
        background: 'rgba(0, 0, 0, 0.5)',
        color: '#fff',
        borderRadius: '3px',
        pointerEvents: 'none',
        zIndex: '10'
      });
      wrapper.style.position = 'relative';
      wrapper.appendChild(label);
    }
    return label;
  }

  function checkImageSize(wrapper, img) {
    if (!img) return;

    const evaluate = () => {
      const width = img.naturalWidth;
      const height = img.naturalHeight;
      if (!width || !height) return;

      const label = createSizeLabel(wrapper);
      label.textContent = `${width}×${height}`;

      if (width > THRESHOLD_WIDTH || height > THRESHOLD_HEIGHT) {
        wrapper.style.backgroundColor = 'rgba(255, 0, 0, 0.2)';
        label.style.backgroundColor = 'rgba(255, 0, 0, 0.6)';
      } else {
        wrapper.style.backgroundColor = 'rgba(0, 255, 0, 0.1)';
        label.style.backgroundColor = 'rgba(0, 128, 0, 0.6)';
      }
    };

    if (img.complete) {
      evaluate();
    } else {
      img.onload = evaluate;
    }
  }

  function observeImgChanges(wrapper) {
    if (observedWrappers.has(wrapper)) return;
    observedWrappers.add(wrapper);

    const img = wrapper.querySelector('img');
    if (!img) return;

    checkImageSize(wrapper, img);

    const imgObserver = new MutationObserver(() => {
      checkImageSize(wrapper, img);
    });

    imgObserver.observe(img, { attributes: true, attributeFilter: ['src'] });
  }

  function scanAndWatch() {
    document.querySelectorAll('.previewWrapper').forEach(observeImgChanges);
  }

  const domObserver = new MutationObserver(scanAndWatch);
  domObserver.observe(document.body, { childList: true, subtree: true });

  scanAndWatch();
})();