Character.ai Changer

Combined Background Changer and Color Customizer for Character.AI

  1. // ==UserScript==
  2. // @name Character.ai Changer
  3. // @namespace http://tampermonkey
  4. // @version 1.00.1V3
  5. // @description Combined Background Changer and Color Customizer for Character.AI
  6. // @author NotYou
  7. // @match https://character.ai/*
  8. // @match https://*.character.ai/*
  9. // @grant GM_addStyle
  10. // @grant GM_setValue
  11. // @grant GM_getValue
  12. // @run-at document-start
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. // ==========================================
  19. // SCRIPT 1: Character.AI Background Changer
  20. // ==========================================
  21.  
  22. (function() {
  23. 'use strict';
  24.  
  25. // Initialize logging
  26. console.log('Character.AI Background Changer initialized');
  27.  
  28. function log(message) {
  29. console.log(`[BG Changer]: ${message}`);
  30. }
  31.  
  32. // Create UI elements
  33. const controlPanel = document.createElement('div');
  34. const btnToggle = document.createElement('button');
  35. const statusIndicator = document.createElement('div');
  36.  
  37. // Set up control panel
  38. function setupControlPanel() {
  39. controlPanel.id = 'cai-bg-control-panel';
  40. controlPanel.style.position = 'fixed';
  41. controlPanel.style.bottom = '60px';
  42. controlPanel.style.right = '20px';
  43. controlPanel.style.width = '250px';
  44. controlPanel.style.backgroundColor = 'rgba(30, 30, 30, 0.9)';
  45. controlPanel.style.color = 'white';
  46. controlPanel.style.padding = '15px';
  47. controlPanel.style.borderRadius = '8px';
  48. controlPanel.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.5)';
  49. controlPanel.style.zIndex = '9999999';
  50. controlPanel.style.fontFamily = 'Arial, sans-serif';
  51. controlPanel.style.fontSize = '14px';
  52. controlPanel.style.display = 'none';
  53.  
  54. controlPanel.innerHTML = `
  55. <div id="panel-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
  56. <h3 style="margin: 0; font-size: 16px;">Background Changer</h3>
  57. <button id="close-panel" style="background: none; border: none; color: white; cursor: pointer; font-size: 18px;">×</button>
  58. </div>
  59.  
  60. <div id="panel-content">
  61. <div style="margin-bottom: 15px;">
  62. <label for="bg-url" style="display: block; margin-bottom: 5px;">Image URL:</label>
  63. <input type="text" id="bg-url" placeholder="https://example.com/image.jpg" style="width: 100%; padding: 5px; box-sizing: border-box; margin-bottom: 5px;">
  64. <button id="apply-url" style="padding: 5px 10px; background: #4a4a4a; border: none; color: white; cursor: pointer; border-radius: 3px;">Apply URL</button>
  65. </div>
  66.  
  67. <div style="margin-bottom: 15px;">
  68. <label for="bg-upload" style="display: block; margin-bottom: 5px;">Upload Image:</label>
  69. <input type="file" id="bg-upload" accept="image/*" style="width: 100%; margin-bottom: 5px;">
  70. </div>
  71.  
  72. <div style="margin-bottom: 15px;">
  73. <label for="bg-color" style="display: block; margin-bottom: 5px;">Background Color:</label>
  74. <div style="display: flex; align-items: center;">
  75. <input type="color" id="bg-color" value="#1a1a1a" style="margin-right: 10px;">
  76. <button id="apply-color" style="padding: 5px 10px; background: #4a4a4a; border: none; color: white; cursor: pointer; border-radius: 3px;">Apply Color</button>
  77. </div>
  78. </div>
  79.  
  80. <div style="text-align: center; margin-top: 20px;">
  81. <button id="reset-bg" style="padding: 8px 15px; background: #cc3333; border: none; color: white; cursor: pointer; border-radius: 3px;">Reset Background</button>
  82. </div>
  83. </div>
  84. `;
  85. }
  86.  
  87. // Set up toggle button
  88. function setupToggleButton() {
  89. btnToggle.id = 'cai-bg-toggle';
  90. btnToggle.textContent = 'Change BG';
  91. btnToggle.style.position = 'fixed';
  92. btnToggle.style.bottom = '20px';
  93. btnToggle.style.right = '20px';
  94. btnToggle.style.padding = '8px 12px';
  95. btnToggle.style.backgroundColor = '#3a3';
  96. btnToggle.style.color = 'white';
  97. btnToggle.style.border = 'none';
  98. btnToggle.style.borderRadius = '5px';
  99. btnToggle.style.fontSize = '14px';
  100. btnToggle.style.fontWeight = 'bold';
  101. btnToggle.style.cursor = 'pointer';
  102. btnToggle.style.zIndex = '9999997';
  103. btnToggle.style.boxShadow = '0 2px 5px rgba(0, 0, 0, 0.2)';
  104. }
  105.  
  106. // Set up status indicator
  107. function setupStatusIndicator() {
  108. statusIndicator.id = 'cai-bg-status';
  109. statusIndicator.style.position = 'fixed';
  110. statusIndicator.style.bottom = '20px';
  111. statusIndicator.style.left = '20px';
  112. statusIndicator.style.padding = '8px 12px';
  113. statusIndicator.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
  114. statusIndicator.style.color = 'white';
  115. statusIndicator.style.borderRadius = '4px';
  116. statusIndicator.style.fontSize = '14px';
  117. statusIndicator.style.zIndex = '9999998';
  118. statusIndicator.style.opacity = '0';
  119. statusIndicator.style.transition = 'opacity 0.5s';
  120. statusIndicator.textContent = 'Background changer ready';
  121. }
  122.  
  123. // Toggle panel visibility
  124. function togglePanel() {
  125. const isVisible = controlPanel.style.display === 'block';
  126.  
  127. if (isVisible) {
  128. controlPanel.style.display = 'none';
  129. btnToggle.style.backgroundColor = '#3a3';
  130. } else {
  131. controlPanel.style.display = 'block';
  132. btnToggle.style.backgroundColor = '#555';
  133. }
  134. }
  135.  
  136. // Show status message
  137. function showStatus(message, duration = 2000) {
  138. statusIndicator.textContent = message;
  139. statusIndicator.style.opacity = '1';
  140.  
  141. setTimeout(() => {
  142. statusIndicator.style.opacity = '0';
  143. }, duration);
  144. }
  145.  
  146. // Apply background from URL
  147. function applyUrlBackground() {
  148. const url = document.getElementById('bg-url').value.trim();
  149.  
  150. if (!url) {
  151. showStatus('Please enter a valid URL');
  152. return;
  153. }
  154.  
  155. applyBackground(`url(${url}) no-repeat center center fixed`, 'cover');
  156. showStatus('Background applied from URL');
  157. }
  158.  
  159. // Apply background from color picker
  160. function applyColorBackground() {
  161. const color = document.getElementById('bg-color').value;
  162. applyBackground(color);
  163. showStatus('Background color applied');
  164. }
  165.  
  166. // Apply background image from file upload
  167. function handleImageUpload(event) {
  168. const file = event.target.files[0];
  169.  
  170. if (!file) return;
  171.  
  172. // Check if file is an image
  173. if (!file.type.startsWith('image/')) {
  174. showStatus('Please select an image file');
  175. return;
  176. }
  177.  
  178. const reader = new FileReader();
  179.  
  180. reader.onload = function(e) {
  181. const imageUrl = e.target.result;
  182. applyBackground(`url(${imageUrl}) no-repeat center center fixed`, 'cover');
  183. showStatus('Background image applied');
  184.  
  185. // Save the image data to GM storage
  186. GM_setValue('bgImageData', imageUrl);
  187. };
  188.  
  189. reader.readAsDataURL(file);
  190. }
  191.  
  192. // Apply background
  193. function applyBackground(style, size = null) {
  194. document.body.style.background = style;
  195.  
  196. if (size) {
  197. document.body.style.backgroundSize = size;
  198. }
  199.  
  200. // Save settings
  201. GM_setValue('bgStyle', style);
  202. GM_setValue('bgSize', size || '');
  203. }
  204.  
  205. // Reset background
  206. function resetBackground() {
  207. document.body.style.background = '';
  208. document.body.style.backgroundSize = '';
  209.  
  210. // Clear saved values
  211. GM_setValue('bgStyle', '');
  212. GM_setValue('bgSize', '');
  213. GM_setValue('bgImageData', '');
  214.  
  215. // Reset form inputs
  216. const urlInput = document.getElementById('bg-url');
  217. const uploadInput = document.getElementById('bg-upload');
  218.  
  219. if (urlInput) urlInput.value = '';
  220. if (uploadInput) uploadInput.value = '';
  221.  
  222. showStatus('Background reset to default');
  223. }
  224.  
  225. // Restore saved background
  226. function restoreBackground() {
  227. const bgStyle = GM_getValue('bgStyle', '');
  228. const bgSize = GM_getValue('bgSize', '');
  229.  
  230. if (bgStyle) {
  231. document.body.style.background = bgStyle;
  232.  
  233. if (bgSize) {
  234. document.body.style.backgroundSize = bgSize;
  235. }
  236.  
  237. log('Restored saved background');
  238. }
  239. }
  240.  
  241. // Setup event listeners
  242. function setupEventListeners() {
  243. // Toggle button
  244. btnToggle.addEventListener('click', togglePanel);
  245.  
  246. // Close panel button
  247. document.getElementById('close-panel')?.addEventListener('click', () => {
  248. togglePanel();
  249. });
  250.  
  251. // Apply URL background
  252. document.getElementById('apply-url')?.addEventListener('click', applyUrlBackground);
  253.  
  254. // Apply color background
  255. document.getElementById('apply-color')?.addEventListener('click', applyColorBackground);
  256.  
  257. // Handle file upload
  258. document.getElementById('bg-upload')?.addEventListener('change', handleImageUpload);
  259.  
  260. // Reset background
  261. document.getElementById('reset-bg')?.addEventListener('click', resetBackground);
  262.  
  263. log('Event listeners set up');
  264. }
  265.  
  266. // DOM Ready helper function
  267. function domReady(callback) {
  268. if (document.readyState === "complete" || document.readyState === "interactive") {
  269. setTimeout(callback, 1);
  270. } else {
  271. document.addEventListener("DOMContentLoaded", callback);
  272. }
  273. }
  274.  
  275. // Initialize everything when DOM is ready
  276. function init() {
  277. if (!document.body) {
  278. setTimeout(init, 100);
  279. return;
  280. }
  281.  
  282. try {
  283. // Setup UI elements
  284. setupControlPanel();
  285. setupToggleButton();
  286. setupStatusIndicator();
  287.  
  288. // Add elements to the page
  289. document.body.appendChild(controlPanel);
  290. document.body.appendChild(btnToggle);
  291. document.body.appendChild(statusIndicator);
  292.  
  293. // Setup event listeners
  294. setupEventListeners();
  295.  
  296. // Restore saved background
  297. restoreBackground();
  298.  
  299. // Show initial status
  300. showStatus('Background changer ready');
  301.  
  302. log('Initialization complete');
  303. } catch (error) {
  304. console.error('[BG Changer Error]:', error);
  305. }
  306. }
  307.  
  308. // Run initialization when DOM is ready
  309. domReady(() => {
  310. init();
  311.  
  312. // Additional safety check
  313. setTimeout(() => {
  314. if (!document.getElementById('cai-bg-control-panel')) {
  315. log('Panel not found, retrying initialization');
  316. init();
  317. }
  318. }, 3000);
  319. });
  320.  
  321. // Backup initialization on window load
  322. window.addEventListener('load', () => {
  323. if (!document.getElementById('cai-bg-control-panel')) {
  324. log('Panel not found on window load, reinitializing');
  325. init();
  326. }
  327. });
  328. })();
  329.  
  330. // ==========================================
  331. // SCRIPT 2: Enhanced Color Customizer
  332. // ==========================================
  333.  
  334. (function() {
  335. 'use strict';
  336.  
  337. // Helper function to get the current theme
  338. function getCurrentTheme() {
  339. return document.documentElement.classList.contains('dark') ? 'dark' : 'light';
  340. }
  341.  
  342. // Default colors based on theme
  343. function getDefaultColors(theme) {
  344. const darkColors = {
  345. 'italic': '#E0DF7F',
  346. 'quotationmarks': '#FFFFFF',
  347. 'plaintext': '#A2A2AC',
  348. 'custom': '#E0DF7F',
  349. 'charbubble': '#26272B',
  350. 'userbubble': '#303136',
  351. 'guide': '#131316',
  352. 'input': '#202024',
  353. 'body': '#18181B',
  354. 'accent': '#26272B'
  355. };
  356.  
  357. const lightColors = {
  358. 'italic': '#4F7AA6',
  359. 'quotationmarks': '#000000',
  360. 'plaintext': '#374151',
  361. 'custom': '#4F7AA6',
  362. 'charbubble': '#E4E4E7',
  363. 'userbubble': '#D9D9DF',
  364. 'guide': '#FAFAFA',
  365. 'input': '#F4F4F5',
  366. 'body': '#ECECEE',
  367. 'accent': '#26272B'
  368. };
  369.  
  370. return theme === 'dark' ? darkColors : lightColors;
  371. }
  372.  
  373. // Load Comic Sans MS and other web fonts
  374. function loadWebFonts() {
  375. // Add Comic Sans MS
  376. GM_addStyle(`
  377. @import url('https://fonts.cdnfonts.com/css/comic-sans');
  378.  
  379. @font-face {
  380. font-family: 'Comic Sans MS';
  381. src: local('Comic Sans MS'),
  382. url('https://fonts.cdnfonts.com/s/11177/comici.woff') format('woff'),
  383. url('https://fonts.cdnfonts.com/s/11177/comicbd.woff') format('woff');
  384. font-display: swap;
  385. }
  386. `);
  387.  
  388. // Add Google Fonts
  389. const googleFontsLink = document.createElement('link');
  390. googleFontsLink.rel = 'stylesheet';
  391. googleFontsLink.href = 'https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&family=Open+Sans:wght@400;700&family=Lato:wght@400;700&family=Montserrat:wght@400;700&display=swap';
  392. document.head.appendChild(googleFontsLink);
  393. }
  394.  
  395. // Create main customization menu
  396. function createCustomizationMenu() {
  397. const theme = getCurrentTheme();
  398. const menuContainer = document.createElement('div');
  399. menuContainer.id = 'cai-color-customizer';
  400. menuContainer.style.cssText = `
  401. position: fixed;
  402. top: 50%;
  403. left: 50%;
  404. transform: translate(-50%, -50%);
  405. width: 900px;
  406. background-color: ${theme === 'dark' ? 'rgba(19, 19, 22, 0.95)' : 'rgba(214, 214, 221, 0.95)'};
  407. border-radius: 10px;
  408. padding: 20px;
  409. z-index: 9999;
  410. display: none;
  411. `;
  412.  
  413. // Tabs container
  414. const tabContainer = document.createElement('div');
  415. tabContainer.style.cssText = `
  416. display: flex;
  417. margin-bottom: 15px;
  418. `;
  419.  
  420. // Content container
  421. const contentContainer = document.createElement('div');
  422.  
  423. // Create tabs
  424. const tabs = [
  425. { name: 'Colors', id: 'colors' },
  426. { name: 'Typography', id: 'typography' },
  427. { name: 'Layout', id: 'layout' }
  428. ];
  429.  
  430. tabs.forEach(tab => {
  431. const tabButton = document.createElement('button');
  432. tabButton.textContent = tab.name;
  433. tabButton.style.cssText = `
  434. flex-grow: 1;
  435. padding: 10px;
  436. background-color: ${theme === 'dark' ? '#26272B' : '#E4E4E7'};
  437. border: none;
  438. margin-right: 5px;
  439. cursor: pointer;
  440. `;
  441.  
  442. tabButton.addEventListener('click', () => {
  443. // Hide all content
  444. contentContainer.innerHTML = '';
  445.  
  446. // Show specific tab content
  447. switch(tab.id) {
  448. case 'colors':
  449. renderColorTab(contentContainer);
  450. break;
  451. case 'typography':
  452. renderTypographyTab(contentContainer);
  453. break;
  454. case 'layout':
  455. renderLayoutTab(contentContainer);
  456. break;
  457. }
  458. });
  459.  
  460. tabContainer.appendChild(tabButton);
  461. });
  462.  
  463. // Add close button
  464. const closeButton = document.createElement('button');
  465. closeButton.textContent = '×';
  466. closeButton.style.cssText = `
  467. position: absolute;
  468. top: 10px;
  469. right: 10px;
  470. background: none;
  471. border: none;
  472. font-size: 20px;
  473. cursor: pointer;
  474. `;
  475. closeButton.addEventListener('click', () => {
  476. menuContainer.style.display = 'none';
  477. });
  478.  
  479. menuContainer.appendChild(closeButton);
  480. menuContainer.appendChild(tabContainer);
  481. menuContainer.appendChild(contentContainer);
  482.  
  483. document.body.appendChild(menuContainer);
  484.  
  485. // Auto-select the first tab on creation
  486. tabContainer.firstChild.click();
  487.  
  488. return menuContainer;
  489. }
  490.  
  491. // Render color customization tab
  492. function renderColorTab(container) {
  493. const theme = getCurrentTheme();
  494. const categories = ['italic', 'quotationmarks', 'plaintext', 'custom', 'charbubble', 'userbubble', 'guide', 'input', 'body', 'accent'];
  495.  
  496. categories.forEach(category => {
  497. const colorWrapper = document.createElement('div');
  498. colorWrapper.style.cssText = `
  499. display: flex;
  500. align-items: center;
  501. margin-bottom: 10px;
  502. `;
  503.  
  504. const label = document.createElement('label');
  505. label.textContent = category.charAt(0).toUpperCase() + category.slice(1);
  506. label.style.marginRight = '10px';
  507.  
  508. const colorPicker = document.createElement('input');
  509. colorPicker.type = 'color';
  510. colorPicker.value = GM_getValue(`${category}_color`, getDefaultColors(theme)[category]);
  511.  
  512. colorPicker.addEventListener('input', () => {
  513. GM_setValue(`${category}_color`, colorPicker.value);
  514. applyCustomColors();
  515. });
  516.  
  517. colorWrapper.appendChild(label);
  518. colorWrapper.appendChild(colorPicker);
  519. container.appendChild(colorWrapper);
  520. });
  521. }
  522.  
  523. // Render typography tab
  524. function renderTypographyTab(container) {
  525. // Typography container
  526. const typographyContainer = document.createElement('div');
  527. typographyContainer.style.cssText = `
  528. display: flex;
  529. flex-direction: column;
  530. gap: 15px;
  531. `;
  532.  
  533. // Font selector
  534. const fontSelectContainer = document.createElement('div');
  535. fontSelectContainer.style.cssText = `
  536. display: flex;
  537. flex-direction: column;
  538. gap: 5px;
  539. `;
  540.  
  541. const fontSelectLabel = document.createElement('label');
  542. fontSelectLabel.textContent = 'Font Family:';
  543.  
  544. const fontSelect = document.createElement('select');
  545. fontSelect.style.width = '100%';
  546.  
  547. // Common fonts including Comic Sans MS
  548. const fonts = [
  549. 'Comic Sans MS', 'Inter', 'Arial', 'Helvetica', 'Verdana', 'Tahoma',
  550. 'Times New Roman', 'Georgia', 'Garamond',
  551. 'Courier New', 'Consolas', 'Monaco',
  552. 'Roboto', 'Open Sans', 'Lato', 'Montserrat'
  553. ];
  554.  
  555. // Add default option
  556. const defaultOption = document.createElement('option');
  557. defaultOption.value = '';
  558. defaultOption.textContent = 'Default';
  559. fontSelect.appendChild(defaultOption);
  560.  
  561. // Add font options
  562. fonts.forEach(font => {
  563. const option = document.createElement('option');
  564. option.value = font;
  565. option.textContent = font;
  566. option.style.fontFamily = font;
  567. fontSelect.appendChild(option);
  568. });
  569.  
  570. // Set selected value from saved preference
  571. fontSelect.value = GM_getValue('selected_font', '');
  572.  
  573. fontSelect.addEventListener('change', () => {
  574. GM_setValue('selected_font', fontSelect.value);
  575. applyTypographySettings();
  576. });
  577.  
  578. fontSelectContainer.appendChild(fontSelectLabel);
  579. fontSelectContainer.appendChild(fontSelect);
  580. typographyContainer.appendChild(fontSelectContainer);
  581.  
  582. // Font Style
  583. const fontStyleContainer = document.createElement('div');
  584. fontStyleContainer.style.cssText = `
  585. display: flex;
  586. flex-direction: column;
  587. gap: 5px;
  588. margin-top: 10px;
  589. `;
  590.  
  591. const fontStyleLabel = document.createElement('label');
  592. fontStyleLabel.textContent = 'Font Style:';
  593.  
  594. const fontStyleOptions = document.createElement('div');
  595. fontStyleOptions.style.cssText = `
  596. display: flex;
  597. gap: 10px;
  598. `;
  599.  
  600. // Bold checkbox
  601. const boldContainer = document.createElement('div');
  602. const boldCheckbox = document.createElement('input');
  603. boldCheckbox.type = 'checkbox';
  604. boldCheckbox.id = 'font-bold';
  605. boldCheckbox.checked = GM_getValue('fontBold', false);
  606.  
  607. const boldLabel = document.createElement('label');
  608. boldLabel.textContent = 'Bold';
  609. boldLabel.htmlFor = 'font-bold';
  610.  
  611. boldCheckbox.addEventListener('change', () => {
  612. GM_setValue('fontBold', boldCheckbox.checked);
  613. applyTypographySettings();
  614. });
  615.  
  616. boldContainer.appendChild(boldCheckbox);
  617. boldContainer.appendChild(boldLabel);
  618.  
  619. // Italic checkbox
  620. const italicContainer = document.createElement('div');
  621. const italicCheckbox = document.createElement('input');
  622. italicCheckbox.type = 'checkbox';
  623. italicCheckbox.id = 'font-italic';
  624. italicCheckbox.checked = GM_getValue('fontItalic', false);
  625.  
  626. const italicLabel = document.createElement('label');
  627. italicLabel.textContent = 'Italic';
  628. italicLabel.htmlFor = 'font-italic';
  629.  
  630. italicCheckbox.addEventListener('change', () => {
  631. GM_setValue('fontItalic', italicCheckbox.checked);
  632. applyTypographySettings();
  633. });
  634.  
  635. italicContainer.appendChild(italicCheckbox);
  636. italicContainer.appendChild(italicLabel);
  637.  
  638. fontStyleOptions.appendChild(boldContainer);
  639. fontStyleOptions.appendChild(italicContainer);
  640. fontStyleContainer.appendChild(fontStyleLabel);
  641. fontStyleContainer.appendChild(fontStyleOptions);
  642. typographyContainer.appendChild(fontStyleContainer);
  643.  
  644. // Font Size Slider
  645. const fontSizeContainer = document.createElement('div');
  646. fontSizeContainer.style.cssText = `
  647. display: flex;
  648. flex-direction: column;
  649. gap: 5px;
  650. `;
  651.  
  652. const fontSizeLabel = document.createElement('label');
  653. fontSizeLabel.textContent = 'Font Size: ' + GM_getValue('fontSize', 16) + 'px';
  654.  
  655. const fontSizeSlider = document.createElement('input');
  656. fontSizeSlider.type = 'range';
  657. fontSizeSlider.min = 10;
  658. fontSizeSlider.max = 30;
  659. fontSizeSlider.value = GM_getValue('fontSize', 16);
  660. fontSizeSlider.style.width = '100%';
  661.  
  662. fontSizeSlider.addEventListener('input', () => {
  663. const newSize = fontSizeSlider.value;
  664. fontSizeLabel.textContent = 'Font Size: ' + newSize + 'px';
  665. GM_setValue('fontSize', newSize);
  666. applyTypographySettings();
  667. });
  668.  
  669. fontSizeContainer.appendChild(fontSizeLabel);
  670. fontSizeContainer.appendChild(fontSizeSlider);
  671. typographyContainer.appendChild(fontSizeContainer);
  672.  
  673. container.appendChild(typographyContainer);
  674. }
  675.  
  676. // Render layout tab
  677. function renderLayoutTab(container) {
  678. const layoutContainer = document.createElement('div');
  679. layoutContainer.style.cssText = `
  680. display: flex;
  681. flex-direction: column;
  682. gap: 15px;
  683. `;
  684.  
  685. // Avatar size control
  686. const avatarSizeContainer = document.createElement('div');
  687. avatarSizeContainer.style.cssText = `
  688. display: flex;
  689. align-items: center;
  690. gap: 10px;
  691. `;
  692.  
  693. const sizeLabel = document.createElement('label');
  694. sizeLabel.textContent = 'Avatar Image Size (px):';
  695.  
  696. const sizeInput = document.createElement('input');
  697. sizeInput.type = 'number';
  698. sizeInput.value = GM_getValue('image_size', '24');
  699. sizeInput.min = '16';
  700. sizeInput.max = '64';
  701. sizeInput.style.width = '60px';
  702.  
  703. sizeInput.addEventListener('change', () => {
  704. GM_setValue('image_size', sizeInput.value);
  705. applyLayoutSettings();
  706. });
  707.  
  708. avatarSizeContainer.appendChild(sizeLabel);
  709. avatarSizeContainer.appendChild(sizeInput);
  710. layoutContainer.appendChild(avatarSizeContainer);
  711.  
  712. // Chat bubble spacing control
  713. const spacingContainer = document.createElement('div');
  714. spacingContainer.style.cssText = `
  715. display: flex;
  716. align-items: center;
  717. gap: 10px;
  718. `;
  719.  
  720. const spacingLabel = document.createElement('label');
  721. spacingLabel.textContent = 'Message Spacing (px):';
  722.  
  723. const spacingInput = document.createElement('input');
  724. spacingInput.type = 'number';
  725. spacingInput.value = GM_getValue('message_spacing', '10');
  726. spacingInput.min = '0';
  727. spacingInput.max = '30';
  728. spacingInput.style.width = '60px';
  729.  
  730. spacingInput.addEventListener('change', () => {
  731. GM_setValue('message_spacing', spacingInput.value);
  732. applyLayoutSettings();
  733. });
  734.  
  735. spacingContainer.appendChild(spacingLabel);
  736. spacingContainer.appendChild(spacingInput);
  737. layoutContainer.appendChild(spacingContainer);
  738.  
  739. container.appendChild(layoutContainer);
  740. }
  741.  
  742. // Apply color customizations dynamically
  743. function applyCustomColors() {
  744. const theme = getCurrentTheme();
  745. const defaultColors = getDefaultColors(theme);
  746. const categories = ['italic', 'quotationmarks', 'plaintext', 'custom', 'charbubble', 'userbubble', 'guide', 'input', 'body', 'accent'];
  747.  
  748. // Remove any existing style element
  749. const existingStyle = document.getElementById('cai-custom-colors');
  750. if (existingStyle) {
  751. existingStyle.remove();
  752. }
  753.  
  754. const styleElement = document.createElement('style');
  755. styleElement.id = 'cai-custom-colors';
  756. let css = '';
  757.  
  758. categories.forEach(category => {
  759. const color = GM_getValue(`${category}_color`, defaultColors[category]);
  760.  
  761. switch(category) {
  762. case 'italic':
  763. css += `em { color: ${color} !important; } `;
  764. break;
  765. case 'quotationmarks':
  766. css += `blockquote { color: ${color} !important; } `;
  767. break;
  768. case 'plaintext':
  769. css += `p[node='[object Object]'] { color: ${color} !important; } `;
  770. break;
  771. case 'charbubble':
  772. css += `.mt-1.bg-surface-elevation-2 { background-color: ${color} !important; } `;
  773. break;
  774. case 'userbubble':
  775. css += `.mt-1.bg-surface-elevation-3 { background-color: ${color} !important; } `;
  776. break;
  777. case 'guide':
  778. css += `.overflow-y-auto { background-color: ${color} !important; } `;
  779. break;
  780. case 'input':
  781. css += `.w-full.border-none.bg-surface-elevation-1 { background-color: ${color} !important; } `;
  782. break;
  783. case 'body':
  784. css += `body { background-color: ${color} !important; } `;
  785. break;
  786. case 'accent':
  787. css += `.text-primary-600 { color: ${color} !important; } `;
  788. break;
  789. case 'custom':
  790. css += `code, pre { color: ${color} !important; } `;
  791. break;
  792. }
  793. });
  794.  
  795. styleElement.textContent = css;
  796. document.head.appendChild(styleElement);
  797. }
  798.  
  799. // Apply typography settings
  800. function applyTypographySettings() {
  801. // Remove any existing style element
  802. const existingStyle = document.getElementById('cai-custom-typography');
  803. if (existingStyle) {
  804. existingStyle.remove();
  805. }
  806.  
  807. const font = GM_getValue('selected_font', '');
  808. const fontSize = GM_getValue('fontSize', 16) + 'px';
  809. const isBold = GM_getValue('fontBold', false);
  810. const isItalic = GM_getValue('fontItalic', false);
  811.  
  812. const fontWeight = isBold ? 'bold' : 'normal';
  813. const fontStyle = isItalic ? 'italic' : 'normal';
  814.  
  815. const styleElement = document.createElement('style');
  816. styleElement.id = 'cai-custom-typography';
  817.  
  818. // Make sure we properly handle Comic Sans MS and other fonts with spaces
  819. const fontFamily = font ? `'${font}', ` : '';
  820.  
  821. styleElement.textContent = `
  822. p, textarea, button, div.text-sm, .markdown-content, .message-content,
  823. div[class*="text-base"], div[class*="text-md"], div[class*="text-lg"],
  824. span[class*="text-base"], span[class*="text-md"], span[class*="text-lg"] {
  825. font-family: ${fontFamily}'Noto Sans', sans-serif !important;
  826. font-size: ${fontSize} !important;
  827. font-weight: ${fontWeight} !important;
  828. font-style: ${fontStyle} !important;
  829. }
  830. `;
  831. document.head.appendChild(styleElement);
  832. }
  833.  
  834. // Apply layout settings
  835. function applyLayoutSettings() {
  836. // Remove any existing style element
  837. const existingStyle = document.getElementById('cai-custom-layout');
  838. if (existingStyle) {
  839. existingStyle.remove();
  840. }
  841.  
  842. const imageSize = GM_getValue('image_size', '24') + 'px';
  843. const messageSpacing = GM_getValue('message_spacing', '10') + 'px';
  844.  
  845. const styleElement = document.createElement('style');
  846. styleElement.id = 'cai-custom-layout';
  847.  
  848. styleElement.textContent = `
  849. .mt-0.hidden.md\\:flex.flex-col.gap-3.items-center img {
  850. width: ${imageSize} !important;
  851. height: ${imageSize} !important;
  852. }
  853. .mt-1.flex.w-full {
  854. margin-bottom: ${messageSpacing} !important;
  855. }
  856. `;
  857. document.head.appendChild(styleElement);
  858. }
  859.  
  860. // Apply all settings at once
  861. function applySettings() {
  862. applyCustomColors();
  863. applyTypographySettings();
  864. applyLayoutSettings();
  865. }
  866.  
  867. // Create global customization button
  868. function createCustomizationButton() {
  869. // Check if button already exists
  870. if (document.getElementById('cai-customizer-button')) {
  871. return;
  872. }
  873.  
  874. const button = document.createElement('button');
  875. button.id = 'cai-customizer-button';
  876. button.style.cssText = `
  877. position: fixed;
  878. top: 10px;
  879. right: 10px;
  880. width: 24px;
  881. height: 24px;
  882. background-image: url('https://i.imgur.com/yBgJ3za.png');
  883. background-size: cover;
  884. z-index: 9998;
  885. border: none;
  886. cursor: pointer;
  887. border-radius: 4px;
  888. `;
  889.  
  890. // Create menu only if it doesn't exist
  891. let menu = document.getElementById('cai-color-customizer');
  892. if (!menu) {
  893. menu = createCustomizationMenu();
  894. }
  895.  
  896. button.addEventListener('click', () => {
  897. menu.style.display = menu.style.display === 'none' ? 'block' : 'none';
  898. });
  899.  
  900. document.body.appendChild(button);
  901. }
  902.  
  903. // Global keyboard shortcut handler
  904. function handleKeyboardShortcut(event) {
  905. if (event.key === '`' && event.ctrlKey) {
  906. event.preventDefault();
  907. const menu = document.getElementById('cai-color-customizer');
  908. if (menu) {
  909. menu.style.display = menu.style.display === 'none' ? 'block' : 'none';
  910. }
  911. }
  912. }
  913.  
  914. // Wait for page to load before initialization
  915. function waitForPageLoad() {
  916. // Load web fonts first
  917. loadWebFonts();
  918.  
  919. // Check if we're on Character.AI or Discord
  920. if (window.location.hostname.includes('character.ai')) {
  921. // For Character.AI
  922. if (document.readyState === 'complete' || document.readyState === 'interactive') {
  923. initializeForCAI();
  924. } else {
  925. window.addEventListener('DOMContentLoaded', initializeForCAI);
  926. }
  927. } else if (window.location.hostname.includes('discord.com')) {
  928. // For Discord
  929. const checkInterval = setInterval(() => {
  930. if (document.querySelector('#app-mount')) {
  931. clearInterval(checkInterval);
  932. initializeForDiscord();
  933. }
  934. }, 500);
  935. }
  936. }
  937.  
  938. // Initialize for Character.AI
  939. function initializeForCAI() {
  940. // Ensure the button is created
  941. createCustomizationButton();
  942.  
  943. // Apply settings
  944. applySettings();
  945.  
  946. // Add keyboard shortcut listener
  947. document.addEventListener('keydown', handleKeyboardShortcut);
  948.  
  949. // Re-apply settings when theme changes
  950. const observer = new MutationObserver((mutations) => {
  951. mutations.forEach((mutation) => {
  952. if (mutation.attributeName === 'class' &&
  953. mutation.target === document.documentElement) {
  954. applySettings();
  955. }
  956. });
  957. });
  958.  
  959. observer.observe(document.documentElement, { attributes: true });
  960.  
  961. // Make sure button stays visible with periodic check
  962. setInterval(() => {
  963. if (!document.getElementById('cai-customizer-button')) {
  964. createCustomizationButton();
  965. }
  966. }, 5000);
  967. }
  968.  
  969. // Initialize for Discord
  970. function initializeForDiscord() {
  971. // Apply font settings if exists
  972. const savedFont = GM_getValue('discordFont', GM_getValue('selected_font', ''));
  973. const fontSize = GM_getValue('discordFontSize', GM_getValue('fontSize', 16));
  974. const fontWeight = GM_getValue('discordFontWeight', GM_getValue('fontBold', false) ? 'bold' : 'normal');
  975. const fontStyle = GM_getValue('fontItalic', false) ? 'italic' : 'normal';
  976. const fontColor = GM_getValue('discordFontColor', '#ffffff');
  977.  
  978. // Create style for Discord font customization
  979. const styleElement = document.createElement('style');
  980. styleElement.id = 'discord-font-customizer';
  981. styleElement.textContent = `
  982. .markup-eYLPri, .contents-2MsGLg, [class*="messageContent-"], [class*="channelName-"] {
  983. font-family: ${savedFont ? `'${savedFont}',` : ''} 'Whitney', 'Helvetica Neue', Helvetica, Arial, sans-serif !important;
  984. font-size: ${fontSize}px !important;
  985. font-weight: ${fontWeight} !important;
  986. font-style: ${fontStyle} !important;
  987. color: ${fontColor} !important;
  988. }
  989. `;
  990. document.head.appendChild(styleElement);
  991.  
  992. // Create customization button for Discord
  993. createCustomizationButton();
  994.  
  995. // Add keyboard shortcut listener for Discord
  996. document.addEventListener('keydown', handleKeyboardShortcut);
  997.  
  998. // Make sure button stays visible with periodic check
  999. setInterval(() => {
  1000. if (!document.getElementById('cai-customizer-button')) {
  1001. createCustomizationButton();
  1002. }
  1003. }, 5000);
  1004. }
  1005.  
  1006. // Start initialization
  1007. waitForPageLoad();
  1008.  
  1009. // Fallback initialization to ensure script runs
  1010. setTimeout(() => {
  1011. if (!document.getElementById('cai-customizer-button')) {
  1012. loadWebFonts();
  1013. createCustomizationButton();
  1014. applySettings();
  1015. document.addEventListener('keydown', handleKeyboardShortcut);
  1016. }
  1017. }, 3000);
  1018. })();
  1019.  
  1020.  
  1021. // ==========================================
  1022. // SHARED INITIALIZATION
  1023. // ==========================================
  1024.  
  1025. function initializeAll() {
  1026. // Call the initialization functions from both scripts if they exist
  1027. // For example:
  1028. // if (typeof initBackgroundChanger === 'function') {
  1029. // initBackgroundChanger();
  1030. // }
  1031. //
  1032. // if (typeof initColorCustomizer === 'function') {
  1033. // initColorCustomizer();
  1034. // }
  1035. }
  1036.  
  1037. // Run on page load
  1038. if (document.readyState === 'loading') {
  1039. document.addEventListener('DOMContentLoaded', initializeAll);
  1040. } else {
  1041. initializeAll();
  1042. }
  1043. })();

QingJ © 2025

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