Enhanced Character.AI Text Color and Font

Enhanced script to change text colors, fonts, and sizes with a modern UI using Tailwind CSS. Now includes more font choices and highlights chosen words. Added line-height and spacing fix.

// ==UserScript==
// @name        Enhanced Character.AI Text Color and Font
// @namespace   EnhancedCharacterAITextColorFont
// @match       https://old.character.ai/*
// @grant       none
// @license     MIT
// @version     1.4
// @description Enhanced script to change text colors, fonts, and sizes with a modern UI using Tailwind CSS. Now includes more font choices and highlights chosen words. Added line-height and spacing fix.
// @icon        https://i.imgur.com/ynjBqKW.png
// ==/UserScript==

(function () {
  // Add Tailwind CSS to the document
  const tailwindCSS = document.createElement('link');
  tailwindCSS.href = 'https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css';
  tailwindCSS.rel = 'stylesheet';
  document.head.appendChild(tailwindCSS);

  // Add Google Fonts
  const googleFonts = document.createElement('link');
  googleFonts.href = 'https://fonts.googleapis.com/css2?family=Noto+Sans&family=Roboto&family=Josefin+Sans&family=Jetbrains+Mono&family=Open+Sans&family=Montserrat&family=Montserrat+Alternates&family=Lato&family=PT+Sans&family=Nunito+Sans&family=Courier+Prime&family=Averia+Serif+Libre&family=Dancing+Script&display=swap';
  googleFonts.rel = 'stylesheet';
  document.head.appendChild(googleFonts);

  // Add additional fonts not available on Google Fonts
  const additionalFonts = document.createElement('style');
  additionalFonts.innerHTML = `
    @font-face {
      font-family: 'Open Dyslexic';
      src: url('https://cdn.jsdelivr.net/gh/antijingoist/open-dyslexic/fonts/otf/OpenDyslexic-Regular.otf') format('opentype');
    }
    @font-face {
      font-family: 'Anime Ace';
      src: url('https://fonts.cdnfonts.com/s/19459/Anime_Ace_2_0.ttf') format('truetype');
    }
    @font-face {
      font-family: 'Manga Temple';
      src: url('https://dl.dafont.com/dl/?f=manga_temple') format('truetype');
    }
    @font-face {
      font-family: 'Enfantine';
      src: url('https://fonts.cdnfonts.com/s/13734/Enfantine.ttf') format('truetype');
    }
    @font-face {
      font-family: 'Kirsty';
      src: url('https://dl.dafont.com/dl/?f=kirsty') format('truetype');
    }
    @font-face {
      font-family: 'Planewalker';
      src: url('https://dl.dafont.com/dl/?f=planewalker') format('truetype');
    }
    @font-face {
      font-family: 'Medieval Sharp';
      src: url('https://dl.dafont.com/dl/?f=medieval_sharp') format('truetype');
    }
    @font-face {
      font-family: 'Berenika Book';
      src: url('https://dl.dafont.com/dl/?f=berenika_book') format('truetype');
    }
    @font-face {
      font-family: 'Klaudia';
      src: url('https://dl.dafont.com/dl/?f=klaudia') format('truetype');
    }
  `;
  document.head.appendChild(additionalFonts);

  var plaintextColor = localStorage.getItem('plaintext_color') || '#958C7F';
  var fontFamily = localStorage.getItem('font_family') || 'Noto Sans, sans-serif';
  var fontSize = localStorage.getItem('font_size') || '16px';
  var lineHeight = '1.5'; // Added line-height for better spacing
  var marginBottom = '1em'; // Added margin-bottom to ensure spacing between blocks

  var css = `
    p, .swiper-no-swiping div {
      color: ${plaintextColor} !important;
      font-family: ${fontFamily} !important;
      font-size: ${fontSize} !important;
      line-height: ${lineHeight} !important;
      margin-bottom: ${marginBottom} !important;
    }
  `;

  var head = document.getElementsByTagName("head")[0];
  var style = document.createElement("style");
  style.setAttribute("type", "text/css");
  style.innerHTML = css;
  head.appendChild(style);
})();

function changeColors() {
  const pTags = document.getElementsByTagName("p");
  for (let i = 0; i < pTags.length; i++) {
    const pTag = pTags[i];
    if (
      pTag.dataset.colorChanged === "true" ||
      pTag.querySelector("code") ||
      pTag.querySelector("img")
    ) {
      continue;
    }
    let text = pTag.innerHTML;

    const aTags = pTag.getElementsByTagName("a");
    for (let j = 0; j < aTags.length; j++) {
      const aTag = aTags[j];
      text = text.replace(aTag.outerHTML, "REPLACE_ME_" + j);
    }

    text = text.replace(/(["“”«»].*?["“”«»])/g, `<span style="color: ${localStorage.getItem('quotationmarks_color') || '#FFFFFF'}">$1</span>`);
    text = text.replace(/<em>(.*?)<\/em>/g,`<span style="color: ${localStorage.getItem('italic_color') || '#E0DF7F'}; font-style: italic;">$1</span>`);

    var wordlist_cc = JSON.parse(localStorage.getItem('wordlist_cc')) || [];
    if (wordlist_cc.length > 0) {
      var wordRegex = new RegExp('\\b(' + wordlist_cc.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|') + ')\\b', 'gi');
      text = text.replace(wordRegex, `<span style="color: ${localStorage.getItem('custom_color') || '#FFFFFF'}">$1</span>`);
    }

    for (let j = 0; j < aTags.length; j++) {
      const aTag = aTags[j];
      text = text.replace("REPLACE_ME_" + j, aTag.outerHTML);
    }

    pTag.innerHTML = text;
    pTag.dataset.colorChanged = "true";
  }

  console.log("Changed colors");
}

const observer = new MutationObserver(changeColors);
observer.observe(document, { subtree: true, childList: true });
changeColors();

function createButton(symbol, onClick, extraClasses = '') {
  const button = document.createElement('button');
  button.innerHTML = symbol;
  button.className = `relative bg-none border-none text-lg cursor-pointer ${extraClasses}`;
  button.addEventListener('click', onClick);
  return button;
}

function createColorPanel() {
  const panel = document.createElement('div');
  panel.id = 'colorPanel';
  panel.className = 'fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-gray-800 rounded-lg p-6 shadow-lg text-white z-50';

  const categories = ['italic', 'quotationmarks', 'plaintext', 'custom'];
  const colorPickers = {};

  categories.forEach(category => {
    const colorPicker = document.createElement('input');
    colorPicker.type = 'color';
    const storedColor = localStorage.getItem(`${category}_color`);
    if (storedColor) {
      colorPicker.value = storedColor;
    }

    colorPickers[category] = colorPicker;

    const colorDiv = document.createElement('div');
    colorDiv.className = 'relative w-5 h-5 ml-2 mt-1 bg-current inline-block mr-2 cursor-pointer border border-black';
    colorDiv.style.backgroundColor = colorPicker.value;

    colorDiv.addEventListener('click', function () {
      colorPicker.click();
    });

    colorPicker.addEventListener('input', function () {
      colorDiv.style.backgroundColor = colorPicker.value;
    });

    const label = document.createElement('label');
    label.className = 'inline-block w-32';
    label.appendChild(document.createTextNode(`${category}: `));

    const resetButton = createButton('↺', function () {
      colorPicker.value = getDefaultColor(category);
      colorDiv.style.backgroundColor = colorPicker.value;
    }, 'relative top-1');

    const containerDiv = document.createElement('div');
    containerDiv.appendChild(label);
    containerDiv.appendChild(colorDiv);
    containerDiv.appendChild(resetButton);

    panel.appendChild(containerDiv);
    panel.appendChild(document.createElement('br'));
  });

  // Font selector
  const fontSelector = document.createElement('select');
  fontSelector.className = 'mt-4 bg-gray-700 border-none text-white rounded p-2';
  const fonts = ['Noto Sans', 'Roboto', 'Josefin Sans', 'Jetbrains Mono', 'Open Sans', 'Montserrat', 'Montserrat Alternates', 'Lato', 'PT Sans', 'Nunito Sans', 'Courier Prime', 'Averia Serif Libre', 'Dancing Script', 'Open Dyslexic', 'Anime Ace', 'Manga Temple', 'Enfantine', 'Kirsty', 'Planewalker', 'Medieval Sharp', 'Berenika Book', 'Klaudia'];
  fonts.forEach(font => {
    const option = document.createElement('option');
    option.value = font;
    option.textContent = font;
    fontSelector.appendChild(option);
  });
  fontSelector.value = localStorage.getItem('font_family') || 'Noto Sans';

  const fontSizeInput = document.createElement('input');
  fontSizeInput.type = 'number';
  fontSizeInput.className = 'mt-4 bg-gray-700 border-none text-white rounded p-2 w-20';
  fontSizeInput.value = localStorage.getItem('font_size') ? parseInt(localStorage.getItem('font_size')) : 16;

  panel.appendChild(document.createTextNode('Font: '));
  panel.appendChild(fontSelector);
  panel.appendChild(document.createElement('br'));
  panel.appendChild(document.createTextNode('Font Size: '));
  panel.appendChild(fontSizeInput);
  panel.appendChild(document.createElement('br'));

  // Custom word list input
  const wordListInput = document.createElement('input');
  wordListInput.type = 'text';
  wordListInput.placeholder = 'Separate words with commas';
  wordListInput.className = 'mt-4 bg-gray-700 border-none text-white rounded p-2 w-full';
  panel.appendChild(wordListInput);
  panel.appendChild(document.createElement('br'));

  const wordListContainer = document.createElement('div');
  wordListContainer.className = 'flex flex-wrap mt-4';
  panel.appendChild(wordListContainer);

  const wordListArray = JSON.parse(localStorage.getItem('wordlist_cc')) || [];

  function createWordButton(word) {
    const wordButton = createButton(`${word} ✕`, function() {
      const index = wordListArray.indexOf(word);
      if (index !== -1) {
        wordListArray.splice(index, 1);
        updateWordListButtons();
      }
    }, 'bg-blue-500 hover:bg-blue-600 text-white rounded px-2 py-1 m-1');

    return wordButton;
  }

  function updateWordListButtons() {
    wordListContainer.innerHTML = '';
    wordListArray.forEach(word => {
      const wordButton = createWordButton(word);
      wordListContainer.appendChild(wordButton);
    });
  }

  const addWordsButton = document.createElement('button');
  addWordsButton.textContent = 'Add';
  addWordsButton.className = 'bg-green-500 hover:bg-green-600 text-white rounded px-4 py-2 mt-4';
  addWordsButton.addEventListener('click', function() {
    const newWords = wordListInput.value.split(',').map(word => word.trim()).filter(word => word);
    newWords.forEach(word => {
      if (!wordListArray.includes(word)) {
        wordListArray.push(word);
      }
    });
    localStorage.setItem('wordlist_cc', JSON.stringify(wordListArray));
    updateWordListButtons();
    wordListInput.value = '';
  });

  panel.appendChild(addWordsButton);

  // Update buttons initially
  updateWordListButtons();

  const buttonContainer = document.createElement('div');
  buttonContainer.className = 'mt-6 flex justify-between';

  const okButton = document.createElement('button');
  okButton.textContent = 'Confirm';
  okButton.className = 'w-24 h-10 rounded bg-green-500 hover:bg-green-600 text-white';
  okButton.addEventListener('click', function () {
    categories.forEach(category => {
      const oldValue = localStorage.getItem(`${category}_color`);
      const newValue = colorPickers[category].value;

      if (oldValue !== newValue) {
        localStorage.setItem(`${category}_color`, newValue);
        if (category === 'plaintext') {
          window.location.reload();
        }
      }
    });

    localStorage.setItem('font_family', fontSelector.value);
    localStorage.setItem('font_size', fontSizeInput.value + 'px');
    localStorage.setItem('wordlist_cc', JSON.stringify(wordListArray));
    window.location.reload();
  });

  const cancelButton = document.createElement('button');
  cancelButton.textContent = 'Cancel';
  cancelButton.className = 'w-24 h-10 rounded bg-red-500 hover:bg-red-600 text-white';
  cancelButton.addEventListener('click', function () {
    panel.remove();
  });

  buttonContainer.appendChild(okButton);
  buttonContainer.appendChild(cancelButton);
  panel.appendChild(buttonContainer);

  document.body.appendChild(panel);
}

function getDefaultColor(category) {
  const defaultColors = {
    'italic': '#E0DF7F',
    'quotationmarks': '#FFFFFF',
    'plaintext': '#958C7F',
    'custom': '#E0DF7F'
  };
  return defaultColors[category];
}

const mainButton = createButton('', function () {
  const colorPanelExists = document.getElementById('colorPanel');
  if (!colorPanelExists) {
    createColorPanel();
  }
});

mainButton.style.backgroundImage = "url('https://i.imgur.com/yBgJ3za.png')";
mainButton.style.backgroundSize = "cover";
mainButton.style.top = "0px";
mainButton.style.width = "22px";
mainButton.style.height = "22px";

function insertMainButton() {
  const targetSelector = '.chat2 > div:nth-child(1) > div:nth-child(1) > div:nth-child(3) > div:nth-child(1)';
  const targetPanel1 = document.querySelector(targetSelector);

  if (targetPanel1) {
    if (!document.querySelector('.color-palette-button')) {
      mainButton.classList.add('color-palette-button');
      targetPanel1.insertBefore(mainButton, targetPanel1.firstChild);
    }
  } else {
    const observer = new MutationObserver(() => {
      const updatedTargetPanel = document.querySelector(targetSelector);
      if (updatedTargetPanel) {
        mainButton.classList.add('color-palette-button');
        updatedTargetPanel.insertBefore(mainButton, updatedTargetPanel.firstChild);
        observer.disconnect();
      }
    });

    observer.observe(document.body, { subtree: true, childList: true });
    console.error('Target panel not found. Waiting for changes...');
  }
}

setInterval(insertMainButton, 1000);
insertMainButton();