// ==UserScript== // @name c.ai X Text Color // @namespace c.ai X Text Color // @match https://character.ai/* // @grant none // @license MIT // @version 3.3 // @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 // @downloadURL none // ==/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'; var defaultColor = '#A2A2AC'; // Default color if 'plaintext_color' is not set 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; // Retrieve the selected font and font size from local storage, or use defaults var selectedFont = localStorage.getItem('selected_font') || '__Inter_918210'; var fontSize = localStorage.getItem('font_size') || '16px'; // Default font size // Create the CSS style using the selected font, stored colors, and font size var css = ` p[node='[object Object]'] { color: ${color} !important; font-family: '${selectedFont}', 'Noto Sans', sans-serif !important; font-size: ${fontSize} !important; } p, textarea, button, div.text-sm { font-family: '${selectedFont}', '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 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, `$1`); // text = text.replace(/(["“”«»][^"]*?,["“”«»])/g, `$1`); if (wordRegex) { text = text.replace(wordRegex, `$1`); } // Restore .katex elements and tags [...katexReplacements, ...aTagsReplacements].forEach(({ html, placeholder, tag }) => { text = text.replace(placeholder, html || tag.outerHTML); }); // Update the innerHTML and mark the

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); }); // Create a new button for custom font selection const fontSelectorButton = document.createElement('button'); fontSelectorButton.style.marginBottom = '20px'; fontSelectorButton.style.borderRadius = '3px'; fontSelectorButton.style.width = '120px'; fontSelectorButton.style.marginLeft = '0px'; fontSelectorButton.style.height = '30px'; fontSelectorButton.style.border = 'none'; fontSelectorButton.style.textAlign = 'left'; fontSelectorButton.style.paddingLeft = '-10px'; fontSelectorButton.innerText = 'Select Font'; // Create a dropdown for font selection const fontDropdown = document.createElement('select'); fontDropdown.style.marginLeft = '5px'; fontDropdown.style.marginBottom = '20px'; fontDropdown.style.borderRadius = '3px'; fontDropdown.style.paddingLeft = '-20px'; fontDropdown.style.height = '30px'; // List of font options const fonts = [ { name: 'Onest', value: '__Onest_b2ce1d' }, { name: 'Inter', value: '__Inter_918210' }, { name: 'Noto Sans', value: 'Noto Sans' }, { name: 'Arial', value: 'Arial' }, { name: 'Times New Roman', value: 'Times New Roman' }, { name: 'Verdana', value: 'Verdana' }, { name: 'Roboto', value: 'Roboto' } ]; // Add fonts to the dropdown fonts.forEach(font => { const option = document.createElement('option'); option.value = font.value; option.text = font.name; fontDropdown.appendChild(option); }); // Load saved font from local storage const savedFont = localStorage.getItem('selected_font'); if (savedFont) { fontDropdown.value = savedFont; } // Create a dropdown for font size selection const fontSizeDropdown = document.createElement('select'); fontSizeDropdown.style.marginLeft = '5px'; fontSizeDropdown.style.marginBottom = '20px'; fontSizeDropdown.style.borderRadius = '3px'; fontSizeDropdown.style.height = '30px'; // List of font size options const fontSizes = [ { name: '12px', value: '12px' }, { name: '14px', value: '14px' }, { name: '16px', value: '16px' }, // Default font size { name: '18px', value: '18px' }, { name: '20px', value: '20px' }, { name: '24px', value: '24px' }, { name: '28px', value: '28px' } ]; // Add font sizes to the dropdown fontSizes.forEach(size => { const option = document.createElement('option'); option.value = size.value; option.text = size.name; fontSizeDropdown.appendChild(option); }); // Load saved font size from local storage const savedFontSize = localStorage.getItem('font_size') || '16px'; fontSizeDropdown.value = savedFontSize; // Add event listener to save the selected font size to local storage and apply it fontSizeDropdown.addEventListener('change', function () { const selectedFontSize = fontSizeDropdown.value; localStorage.setItem('font_size', selectedFontSize); // Apply the selected font size to the document document.documentElement.style.setProperty('--font-size', selectedFontSize); // Update CSS dynamically const css = ` p[node='[object Object]'] { font-size: ${selectedFontSize} !important; } p, textarea, button, div.text-sm { /* Other styles here, without font-size modification */ } `; // Apply the new style const styleSheet = document.createElement('style'); styleSheet.type = 'text/css'; styleSheet.innerText = css; document.head.appendChild(styleSheet); alert('Font size changed to ' + selectedFontSize); }); // Add event listener to save the selected font to local storage and apply it fontDropdown.addEventListener('change', function () { const selectedFont = fontDropdown.value; localStorage.setItem('selected_font', selectedFont); // Apply the selected font to the document document.documentElement.style.setProperty('--font-family', selectedFont); // Update CSS dynamically const css = ` p[node='[object Object]'], p, textarea, button, div.text-sm { font-family: '${selectedFont}', sans-serif !important; } `; // Apply the new style const styleSheet = document.createElement('style'); styleSheet.type = 'text/css'; styleSheet.innerText = css; document.head.appendChild(styleSheet); alert('Font changed to ' + fontDropdown.options[fontDropdown.selectedIndex].text); }); // Append the button and dropdowns to the panel panel.appendChild(fontSelectorButton); panel.appendChild(fontDropdown); panel.appendChild(fontSizeDropdown); // 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); }); } 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('All colors will be replaced with Discord pallet. Proceed?'); 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': '#3E4047' }; // 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('All colors will be replaced with ChatGPT pallet. Proceed?'); 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': '#323232' }; // 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('All colors will be replaced with old.character.ai pallet. Proceed?'); 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(document.createElement('br')); 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.');