c.ai X Font color etc for CAI Tools users

Lets you change the text colors, font type, and font size as you wish, with fixes for CAI Tools compatibility and fully eliminated twitching

  1. // ==UserScript==
  2. // @name c.ai X Font color etc for CAI Tools users
  3. // @namespace c.ai X Font color etc for CAI Tools users
  4. // @match https://character.ai/*
  5. // @match https://*.character.ai/*
  6. // @grant none
  7. // @license MIT
  8. // @version 1.1
  9. // @author LuxTallis based on Vishanka via chatGPT
  10. // @description Lets you change the text colors, font type, and font size as you wish, with fixes for CAI Tools compatibility and fully eliminated twitching
  11. // @icon https://i.imgur.com/ynjBqKW.png
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. const currentTheme = document.documentElement.classList.contains('dark') ? 'dark' : 'light';
  16. var plaintextColor = localStorage.getItem('plaintext_color') || '#A2A2AC';
  17. var italicColor = localStorage.getItem('italic_color') || '#E0DF7F';
  18. var quotationMarksColor = localStorage.getItem('quotationmarks_color') || '#FFFFFF';
  19. var customColor = localStorage.getItem('custom_color') || '#E0DF7F';
  20. var selectedFont = localStorage.getItem('selected_font') || 'Roboto';
  21. var fontSize = localStorage.getItem('font_size') || '16px';
  22. var lastAppliedStyles = null; // Track the last applied styles to avoid redundant updates
  23.  
  24. // List of fonts, excluding unavailable ones
  25. const fontList = [
  26. 'Roboto',
  27. 'Josefin Sans',
  28. 'JetBrains Mono',
  29. 'Open Sans',
  30. 'Montserrat',
  31. 'Montserrat Alternates',
  32. 'Lato',
  33. 'PT Sans',
  34. 'Nunito Sans',
  35. 'Courier Prime',
  36. 'Averia Serif Libre',
  37. 'Fira Code',
  38. 'Fira Sans',
  39. 'Anime Ace',
  40. 'Manga Temple',
  41. 'Dancing Script',
  42. 'Medieval Sharp'
  43. ];
  44.  
  45. // Create CSS with broader targeting and exclusions
  46. var css = `
  47. @import url('https://fonts.googleapis.com/css2?family=Roboto|Josefin+Sans|JetBrains+Mono|Open+Sans|Montserrat|Montserrat+Alternates|Lato|PT+Sans|Nunito+Sans|Courier+Prime|Averia+Serif+Libre|Fira+Code|Fira+Sans|Dancing+Script|MedievalSharp|Anime+Ace|Manga+Temple&display=swap');
  48.  
  49. body div[class*="swiper-slide"] p[node='[object Object]'],
  50. body #chat-messages div[class*="rounded-2xl"] p:not([title]),
  51. body .chat2 p:not(.no-color-override),
  52. body div[class*="message"] p,
  53. body div[class*="user-message"] p,
  54. body div[class*="bot-message"] p,
  55. body p:not(.cai-tools-managed):not(.no-color-override) {
  56. color: ${plaintextColor} !important;
  57. background: none !important;
  58. font-family: "${selectedFont}", sans-serif !important;
  59. font-size: ${fontSize} !important;
  60. }
  61. body div[class*="swiper-slide"] p[node='[object Object]'] em,
  62. body #chat-messages div[class*="rounded-2xl"] p:not([title]) em,
  63. body .chat2 p:not(.no-color-override) em,
  64. body div[class*="message"] p em,
  65. body div[class*="user-message"] p em,
  66. body div[class*="bot-message"] p em,
  67. body p:not(.cai-tools-managed):not(.no-color-override) em {
  68. color: ${italicColor} !important;
  69. font-family: "${selectedFont}", sans-serif !important;
  70. font-size: ${fontSize} !important;
  71. }
  72. `;
  73.  
  74. // Apply CSS with a unique ID
  75. function applyStyles() {
  76. const currentStyles = JSON.stringify({ css, plaintextColor, italicColor, quotationMarksColor, customColor, selectedFont, fontSize });
  77. if (lastAppliedStyles === currentStyles) {
  78. return;
  79. }
  80.  
  81. let style = document.getElementById('custom-text-color-style');
  82. if (!style) {
  83. style = document.createElement("style");
  84. style.id = 'custom-text-color-style';
  85. style.setAttribute("type", "text/css");
  86. document.head.appendChild(style);
  87. }
  88. style.innerHTML = css;
  89. lastAppliedStyles = currentStyles;
  90. }
  91.  
  92. // Apply styles initially after a delay to outpace CAI Tools
  93. setTimeout(applyStyles, 1000);
  94.  
  95. // Function to change colors for quotation marks and custom words
  96. function changeColors(targetPTags = null) {
  97. const pTags = targetPTags || document.querySelectorAll(
  98. 'p[node="[object Object]"], #chat-messages div[class*="rounded-2xl"] p:not([title]), .chat2 p:not(.no-color-override), div[class*="message"] p, div[class*="user-message"] p, div[class*="bot-message"] p, p:not(.cai-tools-managed):not(.no-color-override)'
  99. );
  100. const wordlistCc = JSON.parse(localStorage.getItem('wordlist_cc')) || [];
  101. const wordRegex = wordlistCc.length > 0
  102. ? new RegExp('\\b(' + wordlistCc.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|') + ')\\b', 'gi')
  103. : null;
  104.  
  105. Array.from(pTags).forEach((pTag) => {
  106. if (
  107. pTag.dataset.colorChanged === "true" ||
  108. pTag.querySelector("code") ||
  109. pTag.querySelector("img") ||
  110. pTag.querySelector("textarea") ||
  111. pTag.querySelector("button") ||
  112. pTag.querySelector("div") ||
  113. pTag.classList.contains('no-color-override') ||
  114. pTag.classList.contains('cai-tools-managed')
  115. ) {
  116. return;
  117. }
  118.  
  119. let text = pTag.innerHTML;
  120. const katexElems = Array.from(pTag.querySelectorAll(".katex"));
  121. const katexReplacements = katexElems.map((elem, index) => {
  122. const placeholder = `KATEX_PLACEHOLDER_${index}`;
  123. text = text.replace(elem.outerHTML, placeholder);
  124. return { html: elem.outerHTML, placeholder };
  125. });
  126.  
  127. const aTags = Array.from(pTag.getElementsByTagName("a"));
  128. const aTagsReplacements = aTags.map((aTag, j) => {
  129. const placeholder = `REPLACE_ME_${j}`;
  130. text = text.replace(aTag.outerHTML, placeholder);
  131. return { tag: aTag, placeholder };
  132. });
  133.  
  134. text = text.replace(/(["“”«»].*?["“”«»])/g, `<span style="color: ${quotationMarksColor} !important; font-family: '${selectedFont}', sans-serif !important; font-size: ${fontSize} !important;">$1</span>`);
  135. if (wordRegex) {
  136. text = text.replace(wordRegex, `<span style="color: ${customColor} !important; font-family: '${selectedFont}', sans-serif !important; font-size: ${fontSize} !important;">$1</span>`);
  137. }
  138.  
  139. [...katexReplacements, ...aTagsReplacements].forEach(({ html, placeholder, tag }) => {
  140. text = text.replace(placeholder, html || tag.outerHTML);
  141. });
  142.  
  143. pTag.innerHTML = text;
  144. pTag.dataset.colorChanged = "true";
  145. });
  146. }
  147.  
  148. // Function to check if a mutation is relevant to chat content (structural changes)
  149. function isRelevantMutation(mutation) {
  150. const target = mutation.target;
  151. const isRelevantTarget = (
  152. target.matches('#chat-messages, #chat-messages *') ||
  153. target.matches('.chat2, .chat2 *') ||
  154. target.matches('div[class*="message"], div[class*="message"] *') ||
  155. target.matches('div[class*="user-message"], div[class*="user-message"] *') ||
  156. target.matches('div[class*="bot-message"], div[class*="bot-message"] *') ||
  157. target.matches('div[class*="swiper-slide"], div[class*="swiper-slide"] *') ||
  158. target.matches('p:not(.cai-tools-managed):not(.no-color-override), p:not(.cai-tools-managed):not(.no-color-override) *')
  159. );
  160.  
  161. const hasRelevantNodes = mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0;
  162. return isRelevantTarget && hasRelevantNodes;
  163. }
  164.  
  165. // Single observer for structural changes
  166. const observerConfig = { childList: true, subtree: true };
  167. const chatContainer = document.querySelector('#chat-messages') || document.querySelector('.chat2') || document.body;
  168.  
  169. const observer = new MutationObserver((mutations) => {
  170. if (mutations.some(isRelevantMutation)) {
  171. applyStyles();
  172. changeColors();
  173.  
  174. // Start the generation monitoring loop if a new bot message appears
  175. const latestBotMessage = document.querySelector('div[class*="bot-message"]:last-child');
  176. if (latestBotMessage && !isMonitoringGeneration) {
  177. startGenerationMonitoring(latestBotMessage);
  178. }
  179. }
  180. });
  181. observer.observe(chatContainer, observerConfig);
  182.  
  183. // Variables for generation monitoring
  184. let isMonitoringGeneration = false;
  185. let lastContent = '';
  186. let idleCounter = 0;
  187. const IDLE_THRESHOLD = 60; // ~1 second at 60fps
  188.  
  189. // Monitor the latest bot message during generation using requestAnimationFrame
  190. function startGenerationMonitoring(botMessage) {
  191. if (isMonitoringGeneration) return;
  192. isMonitoringGeneration = true;
  193.  
  194. function monitor() {
  195. const pTags = botMessage.querySelectorAll('p:not(.cai-tools-managed):not(.no-color-override)');
  196. if (!pTags.length) {
  197. // If the message disappears (e.g., page navigation), stop monitoring
  198. isMonitoringGeneration = false;
  199. return;
  200. }
  201.  
  202. // Check if the content has changed
  203. const currentContent = Array.from(pTags).map(p => p.innerHTML).join('');
  204. if (currentContent !== lastContent) {
  205. lastContent = currentContent;
  206. idleCounter = 0; // Reset idle counter on content change
  207.  
  208. // Force reapplication of colors
  209. pTags.forEach(pTag => {
  210. pTag.dataset.colorChanged = "false";
  211. });
  212. changeColors(pTags);
  213. } else {
  214. idleCounter++;
  215. // Stop monitoring if no changes for ~1 second (generation likely finished)
  216. if (idleCounter >= IDLE_THRESHOLD) {
  217. isMonitoringGeneration = false;
  218. changeColors(pTags); // Final reapplication
  219. return;
  220. }
  221. }
  222.  
  223. // Continue monitoring
  224. if (isMonitoringGeneration) {
  225. requestAnimationFrame(monitor);
  226. }
  227. }
  228.  
  229. // Start the monitoring loop
  230. requestAnimationFrame(monitor);
  231. }
  232.  
  233. // Initial application of colors
  234. setTimeout(changeColors, 1000);
  235.  
  236. // Function to create buttons
  237. function createButton(symbol, onClick) {
  238. const button = document.createElement('button');
  239. button.innerHTML = symbol;
  240. button.style.position = 'relative';
  241. button.style.background = 'none';
  242. button.style.border = 'none';
  243. button.style.fontSize = '18px';
  244. button.style.top = '-5px';
  245. button.style.cursor = 'pointer';
  246. button.addEventListener('click', onClick);
  247. return button;
  248. }
  249.  
  250. // Function to create the color and font selector panel
  251. function createColorPanel() {
  252. const panel = document.createElement('div');
  253. panel.id = 'colorPanel';
  254. panel.style.position = 'fixed';
  255. panel.style.top = '50%';
  256. panel.style.left = '50%';
  257. panel.style.transform = 'translate(-50%, -50%)';
  258. panel.style.backgroundColor = currentTheme === 'dark' ? 'rgba(19, 19, 22, 0.95)' : 'rgba(214, 214, 221, 0.95)';
  259. panel.style.border = 'none';
  260. panel.style.borderRadius = '5px';
  261. panel.style.padding = '20px';
  262. panel.style.zIndex = '9999';
  263.  
  264. const categories = ['plaintext', 'italic', 'quotationmarks', 'custom'];
  265. const colorPickers = {};
  266. const transparentCheckboxes = {};
  267. const labelWidth = '150px';
  268.  
  269. // Color pickers
  270. categories.forEach(category => {
  271. const colorPicker = document.createElement('input');
  272. colorPicker.type = 'color';
  273. const storedColor = localStorage.getItem(`${category}_color`) || getDefaultColor(category);
  274. colorPicker.value = storedColor !== 'transparent' ? storedColor : '#000000';
  275. colorPickers[category] = colorPicker;
  276.  
  277. const colorDiv = document.createElement('div');
  278. colorDiv.style.position = 'relative';
  279. colorDiv.style.width = '20px';
  280. colorDiv.style.height = '20px';
  281. colorDiv.style.marginLeft = '10px';
  282. colorDiv.style.top = '0px';
  283. colorDiv.style.backgroundColor = storedColor === 'transparent' ? 'transparent' : colorPicker.value;
  284. colorDiv.style.display = 'inline-block';
  285. colorDiv.style.marginRight = '10px';
  286. colorDiv.style.cursor = 'pointer';
  287. colorDiv.style.border = '1px solid black';
  288.  
  289. colorDiv.addEventListener('click', function () {
  290. if (!transparentCheckboxes[category].checked) {
  291. colorPicker.click();
  292. }
  293. });
  294.  
  295. colorPicker.addEventListener('input', function () {
  296. if (!transparentCheckboxes[category].checked) {
  297. colorDiv.style.backgroundColor = colorPicker.value;
  298. localStorage.setItem(`${category}_color`, colorPicker.value);
  299. }
  300. });
  301.  
  302. const transparentCheckbox = document.createElement('input');
  303. transparentCheckbox.type = 'checkbox';
  304. transparentCheckbox.checked = storedColor === 'transparent';
  305. transparentCheckbox.title = 'Toggle transparency';
  306. transparentCheckbox.style.marginLeft = '10px';
  307. transparentCheckbox.style.marginRight = '5px';
  308. transparentCheckboxes[category] = transparentCheckbox;
  309.  
  310. transparentCheckbox.addEventListener('change', function () {
  311. if (transparentCheckbox.checked) {
  312. colorDiv.style.backgroundColor = 'transparent';
  313. localStorage.setItem(`${category}_color`, 'transparent');
  314. } else {
  315. colorDiv.style.backgroundColor = colorPicker.value;
  316. localStorage.setItem(`${category}_color`, colorPicker.value);
  317. }
  318. });
  319.  
  320. const label = document.createElement('label');
  321. label.style.width = labelWidth;
  322. label.style.margin = '0';
  323. label.style.padding = '0';
  324. label.appendChild(document.createTextNode(`${category}: `));
  325.  
  326. const resetButton = createButton('↺', function () {
  327. const defaultColor = getDefaultColor(category);
  328. colorPicker.value = defaultColor;
  329. colorDiv.style.backgroundColor = defaultColor;
  330. transparentCheckbox.checked = false;
  331. localStorage.setItem(`${category}_color`, defaultColor);
  332. });
  333. resetButton.style.position = 'relative';
  334. resetButton.style.top = '-2px';
  335. resetButton.style.margin = '0';
  336. resetButton.style.padding = '0';
  337.  
  338. const containerDiv = document.createElement('div');
  339. containerDiv.style.margin = '2px 0';
  340. containerDiv.style.padding = '0';
  341. containerDiv.style.display = 'flex';
  342. containerDiv.style.alignItems = 'center';
  343.  
  344. containerDiv.appendChild(label);
  345. containerDiv.appendChild(colorDiv);
  346. containerDiv.appendChild(transparentCheckbox);
  347. containerDiv.appendChild(resetButton);
  348.  
  349. panel.appendChild(containerDiv);
  350. });
  351.  
  352. // Font picker
  353. const fontLabel = document.createElement('label');
  354. fontLabel.style.width = labelWidth;
  355. fontLabel.style.margin = '0';
  356. fontLabel.style.padding = '0';
  357. fontLabel.appendChild(document.createTextNode('Font: '));
  358.  
  359. const fontSelect = document.createElement('select');
  360. fontSelect.style.width = '150px';
  361. fontSelect.style.height = '30px';
  362. fontSelect.style.borderRadius = '3px';
  363. fontList.forEach(font => {
  364. const option = document.createElement('option');
  365. option.value = font;
  366. option.text = font;
  367. if (font === selectedFont) option.selected = true;
  368. fontSelect.appendChild(option);
  369. });
  370.  
  371. const fontContainer = document.createElement('div');
  372. fontContainer.style.margin = '2px 0';
  373. fontContainer.style.padding = '0';
  374. fontContainer.style.display = 'flex';
  375. fontContainer.style.alignItems = 'center';
  376. fontContainer.appendChild(fontLabel);
  377. fontContainer.appendChild(fontSelect);
  378. panel.appendChild(fontContainer);
  379.  
  380. // Font size picker
  381. const sizeLabel = document.createElement('label');
  382. sizeLabel.style.width = labelWidth;
  383. sizeLabel.style.margin = '0';
  384. sizeLabel.style.padding = '0';
  385. sizeLabel.appendChild(document.createTextNode('Font Size: '));
  386.  
  387. const sizeInput = document.createElement('input');
  388. sizeInput.type = 'number';
  389. sizeInput.min = '8';
  390. sizeInput.max = '48';
  391. sizeInput.value = parseInt(fontSize);
  392. sizeInput.style.width = '60px';
  393. sizeInput.style.height = '30px';
  394. sizeInput.style.borderRadius = '3px';
  395.  
  396. const sizeContainer = document.createElement('div');
  397. sizeContainer.style.margin = '2px 0';
  398. sizeContainer.style.padding = '0';
  399. sizeContainer.style.display = 'flex';
  400. sizeContainer.style.alignItems = 'center';
  401. sizeContainer.appendChild(sizeLabel);
  402. sizeContainer.appendChild(sizeInput);
  403. panel.appendChild(sizeContainer);
  404.  
  405. // Custom word list input
  406. const wordListInput = document.createElement('input');
  407. wordListInput.type = 'text';
  408. wordListInput.placeholder = 'Separate words with commas';
  409. wordListInput.style.width = '250px';
  410. wordListInput.style.height = '35px';
  411. wordListInput.style.borderRadius = '3px';
  412. wordListInput.style.marginBottom = '10px';
  413. panel.appendChild(wordListInput);
  414. panel.appendChild(document.createElement('br'));
  415.  
  416. const wordListContainer = document.createElement('div');
  417. wordListContainer.style.display = 'flex';
  418. wordListContainer.style.flexWrap = 'wrap';
  419. wordListContainer.style.maxWidth = '300px';
  420.  
  421. const wordListArray = JSON.parse(localStorage.getItem('wordlist_cc')) || [];
  422.  
  423. function createWordButton(word) {
  424. const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
  425. const removeSymbol = isMobile ? '×' : '🞮';
  426. const wordButton = createButton(`${word} ${removeSymbol}`, function() {
  427. const index = wordListArray.indexOf(word);
  428. if (index !== -1) {
  429. wordListArray.splice(index, 1);
  430. updateWordListButtons();
  431. }
  432. });
  433. wordButton.style.borderRadius = '3px';
  434. wordButton.style.border = 'none';
  435. wordButton.style.backgroundColor = currentTheme === 'dark' ? '#26272B' : '#E4E4E7';
  436. wordButton.style.marginBottom = '5px';
  437. wordButton.style.marginRight = '5px';
  438. wordButton.style.fontSize = '16px';
  439. return wordButton;
  440. }
  441.  
  442. function updateWordListButtons() {
  443. wordListContainer.innerHTML = '';
  444. wordListArray.forEach(word => {
  445. const wordButton = createWordButton(word);
  446. wordListContainer.appendChild(wordButton);
  447. });
  448. }
  449.  
  450. updateWordListButtons();
  451.  
  452. const addWordsButton = document.createElement('button');
  453. addWordsButton.textContent = 'Add';
  454. addWordsButton.style.marginTop = '-8px';
  455. addWordsButton.style.marginLeft = '5px';
  456. addWordsButton.style.borderRadius = '3px';
  457. addWordsButton.style.border = 'none';
  458. addWordsButton.style.backgroundColor = currentTheme === 'dark' ? '#26272B' : '#E4E4E7';
  459. addWordsButton.addEventListener('click', function() {
  460. const wordListValue = wordListInput.value;
  461. const newWords = wordListValue.split(',').map(word => word.trim().toLowerCase()).filter(word => word !== '');
  462. wordListArray.push(...newWords);
  463. updateWordListButtons();
  464. });
  465.  
  466. const inputButtonContainer = document.createElement('div');
  467. inputButtonContainer.style.display = 'flex';
  468. inputButtonContainer.style.alignItems = 'center';
  469. inputButtonContainer.appendChild(wordListInput);
  470. inputButtonContainer.appendChild(addWordsButton);
  471. panel.appendChild(inputButtonContainer);
  472. panel.appendChild(wordListContainer);
  473.  
  474. // OK button
  475. const okButton = document.createElement('button');
  476. okButton.textContent = 'Confirm';
  477. okButton.style.marginTop = '-20px';
  478. okButton.style.width = '75px';
  479. okButton.style.height = '35px';
  480. okButton.style.marginRight = '5px';
  481. okButton.style.borderRadius = '3px';
  482. okButton.style.border = 'none';
  483. okButton.style.backgroundColor = currentTheme === 'dark' ? '#26272B' : '#D9D9DF';
  484. okButton.style.position = 'relative';
  485. okButton.style.left = '24%';
  486. okButton.addEventListener('click', function () {
  487. categories.forEach(category => {
  488. const colorPicker = colorPickers[category];
  489. const transparentCheckbox = transparentCheckboxes[category];
  490. const newValue = transparentCheckbox.checked ? 'transparent' : colorPicker.value;
  491. localStorage.setItem(`${category}_color`, newValue);
  492. if (category === 'plaintext') plaintextColor = newValue;
  493. else if (category === 'italic') italicColor = newValue;
  494. else if (category === 'quotationmarks') quotationMarksColor = newValue;
  495. else if (category === 'custom') customColor = newValue;
  496. });
  497.  
  498. selectedFont = fontSelect.value;
  499. localStorage.setItem('selected_font', selectedFont);
  500. fontSize = sizeInput.value + 'px';
  501. localStorage.setItem('font_size', fontSize);
  502.  
  503. // Update CSS dynamically
  504. css = `
  505. @import url('https://fonts.googleapis.com/css2?family=Roboto|Josefin+Sans|JetBrains+Mono|Open+Sans|Montserrat|Montserrat+Alternates|Lato|PT+Sans|Nunito+Sans|Courier+Prime|Averia+Serif+Libre|Fira+Code|Fira+Sans|Dancing+Script|MedievalSharp|Anime+Ace|Manga+Temple&display=swap');
  506.  
  507. body div[class*="swiper-slide"] p[node='[object Object]'],
  508. body #chat-messages div[class*="rounded-2xl"] p:not([title]),
  509. body .chat2 p:not(.no-color-override),
  510. body div[class*="message"] p,
  511. body div[class*="user-message"] p,
  512. body div[class*="bot-message"] p,
  513. body p:not(.cai-tools-managed):not(.no-color-override) {
  514. color: ${plaintextColor} !important;
  515. background: none !important;
  516. font-family: "${selectedFont}", sans-serif !important;
  517. font-size: ${fontSize} !important;
  518. }
  519. body div[class*="swiper-slide"] p[node='[object Object]'] em,
  520. body #chat-messages div[class*="rounded-2xl"] p:not([title]) em,
  521. body .chat2 p:not(.no-color-override) em,
  522. body div[class*="message"] p em,
  523. body div[class*="user-message"] p em,
  524. body div[class*="bot-message"] p em,
  525. body p:not(.cai-tools-managed):not(.no-color-override) em {
  526. color: ${italicColor} !important;
  527. font-family: "${selectedFont}", sans-serif !important;
  528. font-size: ${fontSize} !important;
  529. }
  530. `;
  531. applyStyles();
  532. changeColors();
  533. const wordListValue = wordListInput.value;
  534. const newWords = wordListValue.split(',').map(word => word.trim().toLowerCase()).filter(word => word !== '');
  535. const uniqueNewWords = Array.from(new Set(newWords));
  536. uniqueNewWords.forEach(newWord => {
  537. if (!wordListArray.includes(newWord)) {
  538. wordListArray.push(newWord);
  539. }
  540. });
  541. localStorage.setItem('wordlist_cc', JSON.stringify(wordListArray));
  542. updateWordListButtons();
  543. changeColors();
  544. panel.remove();
  545. });
  546.  
  547. // Cancel button
  548. const cancelButton = document.createElement('button');
  549. cancelButton.textContent = 'Cancel';
  550. cancelButton.style.marginTop = '-20px';
  551. cancelButton.style.borderRadius = '3px';
  552. cancelButton.style.width = '75px';
  553. cancelButton.style.marginLeft = '5px';
  554. cancelButton.style.height = '35px';
  555. cancelButton.style.border = 'none';
  556. cancelButton.style.backgroundColor = currentTheme === 'dark' ? '#5E5E5E' : '#CBD2D4';
  557. cancelButton.style.position = 'relative';
  558. cancelButton.style.left = '25%';
  559. cancelButton.addEventListener('click', function() {
  560. panel.remove();
  561. });
  562.  
  563. // Reset all button
  564. const resetAll = document.createElement('button');
  565. resetAll.style.marginBottom = '20px';
  566. resetAll.style.borderRadius = '3px';
  567. resetAll.style.width = '80px';
  568. resetAll.style.marginLeft = '5px';
  569. resetAll.style.height = '30px';
  570. resetAll.style.border = 'none';
  571. resetAll.textContent = 'Reset All';
  572. resetAll.addEventListener('click', function () {
  573. const resetConfirmed = confirm('This will reset all colors, font, and size to default. Proceed?');
  574. if (resetConfirmed) {
  575. categories.forEach(category => {
  576. const defaultColor = getDefaultColor(category);
  577. colorPickers[category].value = defaultColor;
  578. transparentCheckboxes[category].checked = false;
  579. localStorage.setItem(`${category}_color`, defaultColor);
  580. if (category === 'plaintext') plaintextColor = defaultColor;
  581. else if (category === 'italic') italicColor = defaultColor;
  582. else if (category === 'quotationmarks') quotationMarksColor = defaultColor;
  583. else if (category === 'custom') customColor = defaultColor;
  584. });
  585. selectedFont = 'Roboto';
  586. fontSelect.value = 'Roboto';
  587. localStorage.setItem('selected_font', 'Roboto');
  588. fontSize = '16px';
  589. sizeInput.value = '16';
  590. localStorage.setItem('font_size', '16px');
  591. localStorage.removeItem('wordlist_cc');
  592. wordListArray.length = 0;
  593. updateWordListButtons();
  594. css = `
  595. @import url('https://fonts.googleapis.com/css2?family=Roboto|Josefin+Sans|JetBrains+Mono|Open+Sans|Montserrat|Montserrat+Alternates|Lato|PT+Sans|Nunito+Sans|Courier+Prime|Averia+Serif+Libre|Fira+Code|Fira+Sans|Dancing+Script|MedievalSharp|Anime+Ace|Manga+Temple&display=swap');
  596.  
  597. body div[class*="swiper-slide"] p[node='[object Object]'],
  598. body #chat-messages div[class*="rounded-2xl"] p:not([title]),
  599. body .chat2 p:not(.no-color-override),
  600. body div[class*="message"] p,
  601. body div[class*="user-message"] p,
  602. body div[class*="bot-message"] p,
  603. body p:not(.cai-tools-managed):not(.no-color-override) {
  604. color: ${getDefaultColor('plaintext')} !important;
  605. background: none !important;
  606. font-family: "Roboto", sans-serif !important;
  607. font-size: 16px !important;
  608. }
  609. body div[class*="swiper-slide"] p[node='[object Object]'] em,
  610. body #chat-messages div[class*="rounded-2xl"] p:not([title]) em,
  611. body .chat2 p:not(.no-color-override) em,
  612. body div[class*="message"] p em,
  613. body div[class*="user-message"] p em,
  614. body div[class*="bot-message"] p em,
  615. body p:not(.cai-tools-managed):not(.no-color-override) em {
  616. color: ${getDefaultColor('italic')} !important;
  617. font-family: "Roboto", sans-serif !important;
  618. font-size: 16px !important;
  619. }
  620. `;
  621. applyStyles();
  622. changeColors();
  623. }
  624. });
  625.  
  626. panel.appendChild(document.createElement('br'));
  627. panel.appendChild(resetAll);
  628. panel.appendChild(document.createElement('br'));
  629. panel.appendChild(okButton);
  630. panel.appendChild(cancelButton);
  631. document.body.appendChild(panel);
  632. }
  633.  
  634. // Function to get default colors
  635. function getDefaultColor(category) {
  636. if (currentTheme === 'dark') {
  637. const defaultColors = {
  638. 'plaintext': '#A2A2AC',
  639. 'italic': '#E0DF7F',
  640. 'quotationmarks': '#FFFFFF',
  641. 'custom': '#E0DF7F'
  642. };
  643. return defaultColors[category];
  644. } else {
  645. const defaultColors = {
  646. 'plaintext': '#374151',
  647. 'italic': '#4F7AA6',
  648. 'quotationmarks': '#000000',
  649. 'custom': '#4F7AA6'
  650. };
  651. return defaultColors[category];
  652. }
  653. }
  654.  
  655. // Create and insert main button
  656. const mainButton = createButton('', function() {
  657. const colorPanelExists = document.getElementById('colorPanel');
  658. if (!colorPanelExists) {
  659. createColorPanel();
  660. }
  661. });
  662. mainButton.style.backgroundImage = "url('https://i.imgur.com/yBgJ3za.png')";
  663. mainButton.style.backgroundSize = "cover";
  664. mainButton.style.position = "fixed";
  665. mainButton.style.top = "135px";
  666. mainButton.style.right = "5px";
  667. mainButton.style.width = "22px";
  668. mainButton.style.height = "22px";
  669. mainButton.style.zIndex = '10000';
  670. document.body.appendChild(mainButton);
  671.  
  672. console.info('c.ai Text Color and Font Button appended to the top right corner.');
  673. })();