c.ai X Text Color

Lets you change the text colors as you wish and highlight chosen words

目前为 2024-08-30 提交的版本。查看 最新版本

// ==UserScript==
// @name        c.ai X Text Color
// @namespace   c.ai X Text Color
// @match       https://character.ai/*
// @grant       none
// @license     MIT
// @version     2.8
// @author      Vishanka via chatGPT
// @description Lets you change the text colors as you wish and highlight chosen words
// @icon        https://i.imgur.com/ynjBqKW.png
// ==/UserScript==


(function () {

  const currentTheme = document.documentElement.classList.contains('dark') ? 'dark' : 'light';
  var plaintextColor = localStorage.getItem('plaintext_color');
  var italicColor = localStorage.getItem('italic_color');
  var charbubbleColor = localStorage.getItem('charbubble_color') || '#26272B';
  var userbubbleColor = localStorage.getItem('userbubble_color') || '#303136';
  // Default color if 'plaintext_color' is not set
  var defaultColor = '#A2A2AC';
  var GuideColor = localStorage.getItem('guide_color') || '#131316';
  var BodyColor = localStorage.getItem('body_color') || '#18181B';
  var InputColor = localStorage.getItem('input_color') || '#202024';
  var AccentColor = localStorage.getItem('accent_color') || '#26272b';

  // Use the retrieved color or default color
  var color = plaintextColor || defaultColor;

  // Create the CSS style
  var css = "p[node='[object Object]'] { color: " + color + " !important; font-family: '__Inter_918210','Noto Sans', sans-serif !important; } p, textarea, button, div.text-sm { font-family: '__Inter_918210','Noto Sans', sans-serif !important; } em { color: " + italicColor + " !important; }";

 css += `.mt-1.bg-surface-elevation-2 { background-color: ${charbubbleColor}; } .mt-1.bg-surface-elevation-3 { background-color: ${userbubbleColor}; }`;



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


  // Function to update CSS variables
  function updateCSSVariable(variableName, value) {
    document.documentElement.style.setProperty(variableName, value);
  }

if (currentTheme === 'dark') {
  // Update the specific CSS variables
updateCSSVariable('--G800', AccentColor);
updateCSSVariable('--G850', InputColor);
updateCSSVariable('--G900', BodyColor);
updateCSSVariable('--G950', GuideColor);


  updateCSSVariable('--G50', '#fafafa');
  updateCSSVariable('--G100', '#f4f4f5');
  updateCSSVariable('--G150', '#ececee');
}
else {
  // Update CSS variables for light theme (or any other theme)
updateCSSVariable('--G850', '#202024');
updateCSSVariable('--G900', '#18181B');
updateCSSVariable('--G950', '#131316');
  updateCSSVariable('--G50', InputColor);
  updateCSSVariable('--G100', BodyColor);
  updateCSSVariable('--G150', GuideColor);
}
})();




function changeColors() {
  const pTags = document.getElementsByTagName("p");
  const quotationMarksColor = localStorage.getItem('quotationmarks_color') || '#FFFFFF';
  const customColor = localStorage.getItem('custom_color') || '#FFFFFF';
  const wordlistCc = JSON.parse(localStorage.getItem('wordlist_cc')) || [];

  const wordRegex = wordlistCc.length > 0 ? new RegExp('\\b(' + wordlistCc.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|') + ')\\b', 'gi') : null;

  Array.from(pTags).forEach((pTag) => {
    if (
      pTag.dataset.colorChanged === "true" ||
      pTag.querySelector("code") ||
      pTag.querySelector("img") ||
      pTag.querySelector("textarea") ||
      pTag.querySelector("button") ||
      pTag.querySelector("div")
    ) {
      return; // Skip iteration
    }

    let text = pTag.innerHTML;

    // Save .katex elements' original HTML and replace with placeholders
    const katexElems = Array.from(pTag.querySelectorAll(".katex"));
    const katexReplacements = katexElems.map((elem, index) => {
      const placeholder = `KATEX_PLACEHOLDER_${index}`;
      text = text.replace(elem.outerHTML, placeholder);
      return { html: elem.outerHTML, placeholder };
    });

    // Handle <a> tags by removing them temporarily and saving their HTML for later restoration
    const aTags = Array.from(pTag.getElementsByTagName("a"));
    const aTagsReplacements = aTags.map((aTag, j) => {
      const placeholder = `REPLACE_ME_${j}`;
      text = text.replace(aTag.outerHTML, placeholder);
      return { tag: aTag, placeholder };
    });

    // Change text within quotation marks and for specific words based on the regex
    text = text.replace(/(["“”«»].*?["“”«»])/g, `<span style="color: ${quotationMarksColor}">$1</span>`);
    text = text.replace(/(["“”«»][^"]*?,["“”«»])/g, `<span style="color: #E0DF7F">$1</span>`);

    if (wordRegex) {
      text = text.replace(wordRegex, `<span style="color: ${customColor}">$1</span>`);
    }

    // Restore .katex elements and <a> tags
    [...katexReplacements, ...aTagsReplacements].forEach(({ html, placeholder, tag }) => {
      text = text.replace(placeholder, html || tag.outerHTML);
    });

    // Update the innerHTML and mark the <p> tag to avoid re-processing
    pTag.innerHTML = text;
    pTag.dataset.colorChanged = "true";
  });

  console.log("Changed colors");
}

const divElements = document.querySelectorAll('div');

divElements.forEach(div => {
    const observer = new MutationObserver(changeColors);
    observer.observe(div, { subtree: true, childList: true });
});



function createButton(symbol, onClick) {
    const colorpalettebutton = document.createElement('button');
    colorpalettebutton.innerHTML = symbol;
    colorpalettebutton.style.position = 'relative';
    colorpalettebutton.style.background = 'none';
    colorpalettebutton.style.border = 'none';
    colorpalettebutton.style.fontSize = '18px';
    colorpalettebutton.style.top = '-5px';
    colorpalettebutton.style.cursor = 'pointer';
    colorpalettebutton.addEventListener('click', onClick);
    return colorpalettebutton;
}

// Function to create the color selector panel
function createColorPanel() {
    const panel = document.createElement('div');
    panel.id = 'colorPanel';
    panel.style.position = 'fixed';
    panel.style.top = '50%';
    panel.style.left = '50%';
    panel.style.transform = 'translate(-50%, -50%)';
    const currentTheme = document.documentElement.classList.contains('dark') ? 'dark' : 'light';
if (currentTheme === 'dark') {
    panel.style.backgroundColor = 'rgba(19, 19, 22, 0.95)';
} else {
     panel.style.backgroundColor = 'rgba(214, 214, 221, 0.95)';
}
    panel.style.border = 'none';
    panel.style.borderRadius = '5px';
    panel.style.padding = '20px';
//    panel.style.border = '2px solid #000';
    panel.style.zIndex = '9999';

    const categories = ['italic', 'quotationmarks', 'plaintext', 'custom', 'charbubble', 'userbubble', 'guide', 'body', 'input', 'accent'];

    const colorPickers = {};

    // Set a fixed width for the labels
    const labelWidth = '150px';




categories.forEach(category => {
    const colorPicker = document.createElement('input');
    colorPicker.type = 'color';

    // Retrieve stored color from local storage
    const storedColor = localStorage.getItem(`${category}_color`);
    if (storedColor) {
        colorPicker.value = storedColor;
    }

    colorPickers[category] = colorPicker;

    // Create a div to hold color picker
    const colorDiv = document.createElement('div');
    colorDiv.style.position = 'relative';
    colorDiv.style.width = '20px';
    colorDiv.style.height = '20px';
    colorDiv.style.marginLeft = '10px';
    colorDiv.style.top = '0px';
    colorDiv.style.backgroundColor = colorPicker.value;
    colorDiv.style.display = 'inline-block';
    colorDiv.style.marginRight = '10px';
    colorDiv.style.cursor = 'pointer';
    colorDiv.style.border = '1px solid black';

    // Event listener to open color picker when the color square is clicked
    colorDiv.addEventListener('click', function () {
        colorPicker.click();
    });

    // Event listener to update the color div when the color changes
    colorPicker.addEventListener('input', function () {
        colorDiv.style.backgroundColor = colorPicker.value;
    });

    const label = document.createElement('label');
    label.style.width = labelWidth; // Set fixed width for the label
    label.style.margin = '0'; // Reduce label margin
    label.style.padding = '0'; // Reduce label padding
    label.appendChild(document.createTextNode(`${category}: `));

    // Reset button for each color picker
    const resetButton = createButton('↺', function () {
        colorPicker.value = getDefaultColor(category);
        colorDiv.style.backgroundColor = colorPicker.value;
    });
    resetButton.style.position = 'relative';
    resetButton.style.top = '-2px';
    resetButton.style.margin = '0'; // Reduce button margin
    resetButton.style.padding = '0'; // Reduce button padding

    // Create a div to hold label, color picker, and reset button
    const containerDiv = document.createElement('div');
    containerDiv.style.margin = '2px 0'; // Reduce vertical margin between rows
    containerDiv.style.padding = '0'; // Reduce padding within each row
    containerDiv.style.display = 'flex'; // Flex display for better control over spacing
    containerDiv.style.alignItems = 'center'; // Center align items vertically

    containerDiv.appendChild(label);
    containerDiv.appendChild(colorDiv);
    containerDiv.appendChild(resetButton);

    panel.appendChild(containerDiv);
});








    // Custom word list input
    const wordListInput = document.createElement('input');
    wordListInput.type = 'text';
    wordListInput.placeholder = 'Separate words with commas';
    wordListInput.style.width = '250px';
    wordListInput.style.height = '35px';
    wordListInput.style.borderRadius = '3px';
    wordListInput.style.marginBottom = '10px';
    panel.appendChild(wordListInput);
    panel.appendChild(document.createElement('br'));

    const wordListContainer = document.createElement('div');
    wordListContainer.style.display = 'flex';
    wordListContainer.style.flexWrap = 'wrap';
    wordListContainer.style.maxWidth = '300px'; // Set a fixed maximum width for the container

    // Display custom word list buttons
    const wordListArray = JSON.parse(localStorage.getItem('wordlist_cc')) || [];
    const wordListButtons = [];

function createWordButton(word) {
    const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);

    const removeSymbol = isMobile ? '×' : '🞮';

    const wordButton = createButton(`${word} ${removeSymbol}`, function() {
        // Remove the word from the list and update the panel
        const index = wordListArray.indexOf(word);
        if (index !== -1) {
            wordListArray.splice(index, 1);
            updateWordListButtons();
        }
    });

// Word Buttons
    wordButton.style.borderRadius = '3px';
    wordButton.style.border = 'none';
if (currentTheme === 'dark') {
    wordButton.style.backgroundColor = '#26272B';
} else {
    wordButton.style.backgroundColor = '#E4E4E7';
}
    wordButton.style.marginBottom = '5px';
    wordButton.style.marginRight = '5px';
    wordButton.style.fontSize = '16px';
    wordButton.classList.add('word-button');
    return wordButton;
}

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

    // Append wordListContainer to the panel



updateWordListButtons();

// Add Words button
const addWordsButton = document.createElement('button');
addWordsButton.textContent = 'Add';
addWordsButton.style.marginTop = '-8px';
addWordsButton.style.marginLeft = '5px';
addWordsButton.style.borderRadius = '3px';
addWordsButton.style.border = 'none';
if (currentTheme === 'dark') {
addWordsButton.style.backgroundColor = '#26272B';
} else {
addWordsButton.style.backgroundColor = '#E4E4E7';
}
addWordsButton.addEventListener('click', function() {
    // Get the input value, split into words, and add to wordListArray
    const wordListValue = wordListInput.value;
const newWords = wordListValue.split(',').map(word => word.trim().toLowerCase()).filter(word => word !== ''); // Convert to lowercase and remove empty entries
    wordListArray.push(...newWords);

    // Update the word list buttons in the panel
    updateWordListButtons();
});

// Create a div to group the input and button on the same line
const inputButtonContainer = document.createElement('div');
inputButtonContainer.style.display = 'flex';
inputButtonContainer.style.alignItems = 'center';

inputButtonContainer.appendChild(wordListInput);
inputButtonContainer.appendChild(addWordsButton);

// Append the container to the panel
panel.appendChild(inputButtonContainer);
    panel.appendChild(wordListContainer);
// Create initial word list buttons
updateWordListButtons();


    // OK button
    const okButton = document.createElement('button');
    okButton.textContent = 'Confirm';
    okButton.style.marginTop = '-20px';
    okButton.style.width = '75px';
    okButton.style.height = '35px';
    okButton.style.marginRight = '5px';
    okButton.style.borderRadius = '3px';
    okButton.style.border = 'none';
if (currentTheme === 'dark') {
    okButton.style.backgroundColor = '#26272B';
} else {
    okButton.style.backgroundColor = '#D9D9DF';
}

okButton.style.position = 'relative';
okButton.style.left = '24%';
//okButton.style.transform = 'translateX(-50%)';
    okButton.addEventListener('click', function() {
        // Save selected colors to local storage
        categories.forEach(category => {
            const oldValue = localStorage.getItem(`${category}_color`);
            const newValue = colorPickers[category].value;

            if (oldValue !== newValue) {
                localStorage.setItem(`${category}_color`, newValue);

                // If 'plaintext' color is changed, auto-reload the page
            if (category === 'plaintext' || category === 'guide' || category === 'body' || category === 'input') {
                window.location.reload();
            }
            }
        });


// Save custom word list to local storage
const wordListValue = wordListInput.value;
const newWords = wordListValue.split(',').map(word => word.trim().toLowerCase()).filter(word => word !== ''); // Convert to lowercase and remove empty entries
const uniqueNewWords = Array.from(new Set(newWords)); // Remove duplicates

// Check for existing words and add only new ones
uniqueNewWords.forEach(newWord => {
  if (!wordListArray.includes(newWord)) {
    wordListArray.push(newWord);
  }
});

localStorage.setItem('wordlist_cc', JSON.stringify(wordListArray));

updateWordListButtons();

        // Close the panel
        panel.remove();
    });

    // Cancel button
    const cancelButton = document.createElement('button');
    cancelButton.textContent = 'Cancel';
    cancelButton.style.marginTop = '-20px';
    cancelButton.style.borderRadius = '3px';
    cancelButton.style.width = '75px';
    cancelButton.style.marginLeft = '5px';
    cancelButton.style.height = '35px';
    cancelButton.style.border = 'none';
if (currentTheme === 'dark') {
    cancelButton.style.backgroundColor = '#5E5E5E';
} else {
    cancelButton.style.backgroundColor = '#CBD2D4';
}
    cancelButton.style.position = 'relative';
    cancelButton.style.left = '25%';
    cancelButton.addEventListener('click', function() {
        // Close the panel without saving
        panel.remove();
    });

// ==== PRESETS ========
// Create button
const preset1 = document.createElement('button');
preset1.style.marginBottom = '20px';
preset1.style.borderRadius = '3px';
preset1.style.width = '30px';
preset1.style.marginLeft = '5px';
preset1.style.height = '30px';
preset1.style.border = 'none';

// Set image as button background
preset1.style.backgroundImage = "url('https://i.imgur.com/91Z4AwP.png')";
preset1.style.backgroundSize = 'contain';
preset1.style.backgroundRepeat = 'no-repeat';
preset1.style.backgroundPosition = 'center';




// Event listener for button click
preset1.addEventListener('click', function () {

  // Show confirmation dialog
  const userConfirmed = confirm('Are you sure you want to replace the colors?');



if (userConfirmed) {

  function updateCSSVariable(variableName, value) {
    document.documentElement.style.setProperty(variableName, value);
  }
updateCSSVariable('--G850', '#383A40'); //input
updateCSSVariable('--G900', '#313338'); //body
updateCSSVariable('--G950', '#232428'); //guide
    // Hardcode the selected colors to local storage
    const hardcodedColors = {
        'guide': '#232428',
        'input': '#383A40',
        'body': '#313338',
        'charbubble': '#383A40',
        'userbubble': '#41434A',
        'accent': '#535457'
    };

    // Save hardcoded values to local storage
    Object.keys(hardcodedColors).forEach(category => {
        const newValue = hardcodedColors[category];
        localStorage.setItem(`${category}_color`, newValue);
    });
                window.location.reload();
}
});

const preset2 = document.createElement('button');
preset2.style.marginBottom = '20px';
preset2.style.borderRadius = '3px';
preset2.style.width = '30px';
preset2.style.marginLeft = '5px';
preset2.style.height = '30px';
preset2.style.border = 'none';

// Set image as button background
preset2.style.backgroundImage = "url('https://i.imgur.com/PSkZ4Yq.png')";
preset2.style.backgroundSize = 'contain';
preset2.style.backgroundRepeat = 'no-repeat';
preset2.style.backgroundPosition = 'center';




// Event listener for button click
preset2.addEventListener('click', function () {

  // Show confirmation dialog
  const userConfirmed = confirm('Are you sure you want to replace the colors?');



if (userConfirmed) {

  function updateCSSVariable(variableName, value) {
    document.documentElement.style.setProperty(variableName, value);
  }
updateCSSVariable('--G850', '#2F2F2F'); //input
updateCSSVariable('--G900', '#212121'); //body
updateCSSVariable('--G950', '#171717'); //guide
    // Hardcode the selected colors to local storage
    const hardcodedColors = {
        'guide': '#171717',
        'input': '#2F2F2F',
        'body': '#212121',
        'charbubble': '#212121',
        'userbubble': '#2F2F2F',
        'accent': '#676767'
    };

    // Save hardcoded values to local storage
    Object.keys(hardcodedColors).forEach(category => {
        const newValue = hardcodedColors[category];
        localStorage.setItem(`${category}_color`, newValue);
    });
                window.location.reload();
}
});

const preset3 = document.createElement('button');
preset3.style.marginBottom = '20px';
preset3.style.borderRadius = '3px';
preset3.style.width = '30px';
preset3.style.marginLeft = '5px';
preset3.style.height = '30px';
preset3.style.border = 'none';

// Set image as button background
preset3.style.backgroundImage = "url('https://i.imgur.com/wWpHDIj.png')";
preset3.style.backgroundSize = 'contain';
preset3.style.backgroundRepeat = 'no-repeat';
preset3.style.backgroundPosition = 'center';




// Event listener for button click
preset3.addEventListener('click', function () {

  // Show confirmation dialog
  const userConfirmed = confirm('Are you sure you want to replace the colors?');



if (userConfirmed) {

  function updateCSSVariable(variableName, value) {
    document.documentElement.style.setProperty(variableName, value);
  }
updateCSSVariable('--G850', '#242525'); //input
updateCSSVariable('--G900', '#242525'); //body
updateCSSVariable('--G950', '#2B2C2D'); //guide
    // Hardcode the selected colors to local storage
    const hardcodedColors = {
        'guide': '#2B2C2D',
        'input': '#242525',
        'body': '#242525',
        'charbubble': '#242525',
        'userbubble': '#2B2C2D',
        'accent': '#363838'
    };

    // Save hardcoded values to local storage
    Object.keys(hardcodedColors).forEach(category => {
        const newValue = hardcodedColors[category];
        localStorage.setItem(`${category}_color`, newValue);
    });
                window.location.reload();
}
});



panel.appendChild(preset1);
panel.appendChild(preset2);
panel.appendChild(preset3);
    panel.appendChild(document.createElement('br'));
    panel.appendChild(okButton);
    panel.appendChild(cancelButton);

    document.body.appendChild(panel);
}



// Function to get the default color for a category
function getDefaultColor(category) {
const currentTheme = document.documentElement.classList.contains('dark') ? 'dark' : 'light';
if (currentTheme === 'dark') {
    const defaultColors = {
        'italic': '#E0DF7F',
        'quotationmarks': '#FFFFFF',
        'plaintext': '#A2A2AC',
        'custom': '#E0DF7F',
        'charbubble': '#26272B',
        'userbubble': '#303136',
        'guide': '#131316',
        'input': '#202024',
        'body': '#18181B',
        'accent': '#26272B'
    };
    return defaultColors[category];
}
 else {
    const defaultColors = {
        'italic': '#4F7AA6',
        'quotationmarks': '#000000',
        'plaintext': '#374151',
        'custom': '#4F7AA6',
        'charbubble': '#E4E4E7',
        'userbubble': '#D9D9DF',
        'guide': '#FAFAFA',
        'input': '#F4F4F5',
        'body': '#ECECEE',
        'accent': '#26272B'
};
    return defaultColors[category];
}
}



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

// Set the background image of the button to the provided image
mainButton.style.backgroundImage = "url('https://i.imgur.com/yBgJ3za.png')";
mainButton.style.backgroundSize = "cover";
mainButton.style.position = "fixed"; // Use "fixed" for a position relative to the viewport
mainButton.style.top = "10px"; // Adjust the top position as needed
mainButton.style.right = "10px"; // Adjust the right position as needed
mainButton.style.width = "22px";  // Adjust the width and height as needed
mainButton.style.height = "22px"; // Adjust the width and height as needed

// Function to insert the mainButton into the body of the document
function insertMainButton() {
    document.body.appendChild(mainButton);
}

// Call the function to insert the mainButton into the body
insertMainButton();

console.info('c.ai Text Color Button appended to the top right corner.');

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址