Elethor Chameleon

Change colors on Elethor.com

  1. // ==UserScript==
  2. // @name Elethor Chameleon
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.1
  5. // @author Eugene
  6. // @description Change colors on Elethor.com
  7. // @match *://elethor.com/*
  8. // @grant GM_addStyle
  9. // @license GPL-3.0-or-later
  10. // ==/UserScript==
  11. /*
  12. * This program is free software: you can redistribute it and/or modify
  13. * it under the terms of the GNU General Public License as published by
  14. * the Free Software Foundation, either version 3 of the License, or
  15. * (at your option) any later version.
  16. *
  17. * This program is distributed in the hope that it will be useful,
  18. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. * GNU General Public License for more details.
  21. *
  22. * You should have received a copy of the GNU General Public License
  23. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  24. */
  25. (function() {
  26. 'use strict';
  27.  
  28. const defaultBackgroundColor = '#202c3c';
  29. const defaultActionBarColor = '#39444e';
  30. const defaultTopBarColor = '#505c6c';
  31. const defaultTextColor = '#ffffff';
  32. const defaultWarningTextColor = '#7a3c38';
  33. const defaultSuccessTextColor = '#52b768';
  34. let isColorUIOpen = false;
  35.  
  36. const DEBUG = true;
  37.  
  38. function debug(message) {
  39. if (DEBUG) {
  40. console.log(`[Elethor Chameleon] ${message}`);
  41. }
  42. }
  43.  
  44. function setColors() {
  45. debug("Setting colors");
  46. const appElement = document.querySelector('#app[data-v-app]');
  47. if (appElement) {
  48. const backgroundColor = localStorage.getItem('backgroundColor') || defaultBackgroundColor;
  49. appElement.style.backgroundColor = backgroundColor;
  50. backgroundColorInput.value = backgroundColor;
  51. debug("Background color set");
  52. } else {
  53. debug("App element not found");
  54. }
  55.  
  56. const actionBarElement = document.querySelector('#currentAction');
  57. if (actionBarElement) {
  58. const actionBarColor = localStorage.getItem('actionBarColor') || defaultActionBarColor;
  59. actionBarElement.style.backgroundColor = actionBarColor;
  60. actionBarColorInput.value = actionBarColor;
  61. debug("Action bar color set");
  62. } else {
  63. debug("Action bar element not found");
  64. }
  65.  
  66. const topBarElement = document.querySelector('nav.navbar');
  67. if (topBarElement) {
  68. const topBarColor = localStorage.getItem('topBarColor') || defaultTopBarColor;
  69. topBarElement.style.backgroundColor = topBarColor;
  70. topBarColorInput.value = topBarColor;
  71. debug("Top bar color set (navbar)");
  72. } else {
  73. debug("Top bar element (navbar) not found");
  74. }
  75.  
  76. const textColor = localStorage.getItem('textColor') || defaultTextColor;
  77. document.body.style.color = textColor;
  78. textColorInput.value = textColor;
  79. applyTextColorToAll(textColor);
  80. debug("Text color set");
  81.  
  82. const warningTextColor = localStorage.getItem('warningTextColor') || defaultWarningTextColor;
  83. warningTextColorInput.value = warningTextColor;
  84. applyWarningTextColor(warningTextColor);
  85. debug("Warning text color set");
  86.  
  87. const successTextColor = localStorage.getItem('successTextColor') || defaultSuccessTextColor;
  88. successTextColorInput.value = successTextColor;
  89. applySuccessTextColor(successTextColor);
  90. debug("Success text color set");
  91. }
  92.  
  93. function saveColorsToLocalStorage() {
  94. debug("Saving colors to localStorage");
  95. const backgroundColor = backgroundColorInput.value;
  96. const actionBarColor = actionBarColorInput.value;
  97. const topBarColor = topBarColorInput.value;
  98. const textColor = textColorInput.value;
  99. const warningTextColor = warningTextColorInput.value;
  100. const successTextColor = successTextColorInput.value;
  101.  
  102. localStorage.setItem('backgroundColor', backgroundColor);
  103. localStorage.setItem('actionBarColor', actionBarColor);
  104. localStorage.setItem('topBarColor', topBarColor);
  105. localStorage.setItem('textColor', textColor);
  106. localStorage.setItem('warningTextColor', warningTextColor);
  107. localStorage.setItem('successTextColor', successTextColor);
  108.  
  109. debug("Colors saved to localStorage");
  110. }
  111.  
  112. function applyTextColorToAll(color) {
  113. const elementsToColor = [
  114. 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'span', 'div', 'a', 'button'
  115. ];
  116. elementsToColor.forEach(selector => {
  117. const elements = document.querySelectorAll(selector);
  118. elements.forEach(el => {
  119.  
  120. if (el.closest('tr th') &&
  121. el.closest('tr')?.querySelector('th:nth-child(3)')?.textContent.includes('Experience')) {
  122. return;
  123. }
  124.  
  125. if (!el.className ||
  126. ((!el.className.includes('text-destructive') || el.className.includes('text-destructive-foreground')) &&
  127. (!el.className.includes('text-success') || el.className.includes('text-success-foreground')))) {
  128. el.style.color = color;
  129. }
  130.  
  131. if (el.className && (el.className.includes('text-success-foreground') ||
  132. el.className.includes('text-warning-foreground') ||
  133. el.className.includes('text-destructive-foreground'))) {
  134. el.style.color = color;
  135. }
  136. });
  137. });
  138. }
  139.  
  140. function applyWarningTextColor(color) {
  141.  
  142. const warningElements = document.querySelectorAll('[class*="text-destructive"]:not([class*="text-destructive-foreground"])');
  143. warningElements.forEach(el => {
  144. el.style.color = color;
  145. });
  146. debug(`Applied warning color ${color} to ${warningElements.length} text elements`);
  147.  
  148. const warningBgElements = document.querySelectorAll('[class*="text-destructive-foreground"], [class*="text-warning-foreground"]');
  149. warningBgElements.forEach(el => {
  150. if (el.classList.contains('bg-destructive') || el.classList.contains('bg-warning')) {
  151. el.style.backgroundColor = color;
  152. }
  153. });
  154. debug(`Applied warning color ${color} as background to ${warningBgElements.length} elements`);
  155. }
  156.  
  157. function applySuccessTextColor(color) {
  158.  
  159. const successElements = document.querySelectorAll('[class*="text-success"]:not([class*="text-success-foreground"])');
  160. successElements.forEach(el => {
  161. el.style.color = color;
  162. });
  163. debug(`Applied success color ${color} to ${successElements.length} text elements`);
  164.  
  165. const successBgElements = document.querySelectorAll('[class*="text-success-foreground"]');
  166. successBgElements.forEach(el => {
  167. if (el.classList.contains('bg-success')) {
  168. el.style.backgroundColor = color;
  169. }
  170. });
  171. debug(`Applied success color ${color} as background to ${successBgElements.length} elements`);
  172. }
  173.  
  174. function waitForElements() {
  175. debug("Waiting for elements");
  176. const interval = setInterval(() => {
  177. const appElement = document.querySelector('#app[data-v-app]');
  178. const actionBarElement = document.querySelector('#currentAction');
  179. const topBarElement = document.querySelector('nav.navbar');
  180. const navbar = document.querySelector('.navbar');
  181.  
  182. if (appElement) {
  183. debug("App element found");
  184. }
  185. if (actionBarElement) {
  186. debug("Action bar element found");
  187. }
  188. if (topBarElement) {
  189. debug("Top bar element (navbar) found");
  190. }
  191. if (navbar) {
  192. debug("Navbar found");
  193. }
  194.  
  195. if ((appElement && actionBarElement && topBarElement) || navbar) {
  196. debug("Essential elements found or navbar present");
  197. clearInterval(interval);
  198. setColors();
  199. addOpenButton();
  200. positionUI();
  201.  
  202. observePageChanges();
  203. }
  204. }, 1000);
  205. }
  206.  
  207. function addOpenButton() {
  208. debug("Adding open button");
  209.  
  210. let navbarItem = document.querySelector('a[href="/corporation"].navbar-item.is-skewed');
  211.  
  212. if (!navbarItem) {
  213. debug("Corporation link not found, trying alternate methods");
  214. const allNavbarItems = document.querySelectorAll('.navbar-item');
  215.  
  216. if (allNavbarItems.length > 0) {
  217. navbarItem = allNavbarItems[allNavbarItems.length - 1];
  218. debug("Using last navbar item as anchor");
  219. } else {
  220.  
  221. const navbar = document.querySelector('.navbar');
  222. if (navbar) {
  223.  
  224. debug("No navbar items found, adding to navbar directly");
  225.  
  226. const openButton = createOpenButton();
  227.  
  228. openButton.style.position = 'relative';
  229. openButton.style.marginLeft = '10px';
  230. navbar.appendChild(openButton);
  231. return;
  232. } else {
  233. debug("No navbar found, creating floating button");
  234.  
  235. const openButton = createOpenButton();
  236. openButton.style.position = 'fixed';
  237. openButton.style.top = '10px';
  238. openButton.style.right = '10px';
  239. openButton.style.zIndex = '10001';
  240. document.body.appendChild(openButton);
  241. return;
  242. }
  243. }
  244. }
  245.  
  246. if (navbarItem) {
  247. debug("Adding button next to navbar item");
  248. const openButton = createOpenButton();
  249. navbarItem.parentNode.insertBefore(openButton, navbarItem.nextSibling);
  250. } else {
  251. debug("Failed to find any suitable location for button");
  252. }
  253. }
  254.  
  255. function createOpenButton() {
  256. const openButton = document.createElement('button');
  257. openButton.id = 'colorChangerOpenButton';
  258. openButton.innerHTML = '🎨';
  259. openButton.style.marginLeft = '10px';
  260.  
  261. const topBarElement = document.querySelector('nav.navbar');
  262. const topBarColor = topBarElement ? topBarElement.style.backgroundColor : '#2596be';
  263. openButton.style.backgroundColor = topBarColor;
  264. openButton.style.color = '#fff';
  265. openButton.style.border = 'none';
  266. openButton.style.padding = '5px';
  267. openButton.style.borderRadius = '3px';
  268. openButton.style.cursor = 'pointer';
  269.  
  270. openButton.addEventListener('click', () => {
  271. debug("Open button clicked");
  272. uiContainer.style.display = uiContainer.style.display === 'none' ? 'flex' : 'none';
  273. isColorUIOpen = uiContainer.style.display === 'flex';
  274. positionUI();
  275. });
  276.  
  277. return openButton;
  278. }
  279.  
  280. function positionUI() {
  281. debug("Positioning UI");
  282. const topBarElement = document.querySelector('nav.navbar');
  283. if (topBarElement) {
  284. const { height } = topBarElement.getBoundingClientRect();
  285. uiContainer.style.top = `${height + 5}px`;
  286. debug(`UI positioned at ${height + 5}px from top`);
  287. } else {
  288.  
  289. uiContainer.style.top = '50px';
  290. debug("Using fallback UI position");
  291. }
  292. }
  293.  
  294. const uiContainer = document.createElement('div');
  295. uiContainer.id = 'colorChangerUI';
  296. uiContainer.style.position = 'fixed';
  297. uiContainer.style.padding = '10px';
  298. uiContainer.style.backgroundColor = '#505c6c';
  299. uiContainer.style.border = '1px solid #ccc';
  300. uiContainer.style.zIndex = '10000';
  301. uiContainer.style.display = 'none';
  302. uiContainer.style.flexDirection = 'row';
  303. uiContainer.style.alignItems = 'center';
  304. uiContainer.style.whiteSpace = 'nowrap';
  305. uiContainer.style.color = '#ffffff';
  306. uiContainer.style.fontSize = '12px';
  307. uiContainer.style.right = '10px';
  308. uiContainer.style.borderRadius = '5px';
  309. uiContainer.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
  310. uiContainer.style.flexWrap = 'wrap';
  311. uiContainer.style.maxWidth = '800px';
  312.  
  313. const backgroundColorLabel = document.createElement('label');
  314. backgroundColorLabel.textContent = 'Background Color: ';
  315. uiContainer.appendChild(backgroundColorLabel);
  316.  
  317. const backgroundColorInput = document.createElement('input');
  318. backgroundColorInput.type = 'color';
  319. uiContainer.appendChild(backgroundColorInput);
  320.  
  321. const actionBarColorLabel = document.createElement('label');
  322. actionBarColorLabel.textContent = 'Action Bar Color: ';
  323. actionBarColorLabel.style.marginLeft = '10px';
  324. uiContainer.appendChild(actionBarColorLabel);
  325.  
  326. const actionBarColorInput = document.createElement('input');
  327. actionBarColorInput.type = 'color';
  328. uiContainer.appendChild(actionBarColorInput);
  329.  
  330. const topBarColorLabel = document.createElement('label');
  331. topBarColorLabel.textContent = 'Top Bar Color: ';
  332. topBarColorLabel.style.marginLeft = '10px';
  333. uiContainer.appendChild(topBarColorLabel);
  334.  
  335. const topBarColorInput = document.createElement('input');
  336. topBarColorInput.type = 'color';
  337. uiContainer.appendChild(topBarColorInput);
  338.  
  339. const textColorLabel = document.createElement('label');
  340. textColorLabel.textContent = 'Text Color: ';
  341. textColorLabel.style.marginLeft = '10px';
  342. uiContainer.appendChild(textColorLabel);
  343.  
  344. const textColorInput = document.createElement('input');
  345. textColorInput.type = 'color';
  346. uiContainer.appendChild(textColorInput);
  347.  
  348. const warningTextColorLabel = document.createElement('label');
  349. warningTextColorLabel.textContent = 'Warning Text: ';
  350. warningTextColorLabel.style.marginLeft = '10px';
  351. uiContainer.appendChild(warningTextColorLabel);
  352.  
  353. const warningTextColorInput = document.createElement('input');
  354. warningTextColorInput.type = 'color';
  355. warningTextColorInput.value = defaultWarningTextColor;
  356. uiContainer.appendChild(warningTextColorInput);
  357.  
  358. const successTextColorLabel = document.createElement('label');
  359. successTextColorLabel.textContent = 'Success Text: ';
  360. successTextColorLabel.style.marginLeft = '10px';
  361. uiContainer.appendChild(successTextColorLabel);
  362.  
  363. const successTextColorInput = document.createElement('input');
  364. successTextColorInput.type = 'color';
  365. successTextColorInput.value = defaultSuccessTextColor;
  366. uiContainer.appendChild(successTextColorInput);
  367.  
  368. const buttonContainer = document.createElement('div');
  369. buttonContainer.style.display = 'flex';
  370. buttonContainer.style.marginTop = '8px';
  371. buttonContainer.style.width = '100%';
  372. buttonContainer.style.justifyContent = 'flex-end';
  373. uiContainer.appendChild(buttonContainer);
  374.  
  375. const saveButton = document.createElement('button');
  376. saveButton.textContent = 'Save';
  377. saveButton.style.marginLeft = '10px';
  378. saveButton.id = 'saveButton';
  379. buttonContainer.appendChild(saveButton);
  380.  
  381. const resetButton = document.createElement('button');
  382. resetButton.textContent = 'Reset';
  383. resetButton.style.marginLeft = '5px';
  384. resetButton.id = 'resetButton';
  385. buttonContainer.appendChild(resetButton);
  386.  
  387. const exportButton = document.createElement('button');
  388. exportButton.textContent = 'Export';
  389. exportButton.style.marginLeft = '5px';
  390. exportButton.style.backgroundColor = '#2596be';
  391. exportButton.id = 'exportButton';
  392. buttonContainer.appendChild(exportButton);
  393.  
  394. const importButton = document.createElement('button');
  395. importButton.textContent = 'Import';
  396. importButton.style.marginLeft = '5px';
  397. importButton.style.backgroundColor = '#2596be';
  398. importButton.id = 'importButton';
  399. buttonContainer.appendChild(importButton);
  400.  
  401. exportButton.addEventListener('click', () => {
  402. const colorScheme = {
  403. backgroundColor: backgroundColorInput.value,
  404. actionBarColor: actionBarColorInput.value,
  405. topBarColor: topBarColorInput.value,
  406. textColor: textColorInput.value,
  407. warningTextColor: warningTextColorInput.value,
  408. successTextColor: successTextColorInput.value
  409. };
  410.  
  411. const colorSchemeString = JSON.stringify(colorScheme);
  412.  
  413. navigator.clipboard.writeText(colorSchemeString)
  414. .then(() => {
  415. debug('Color scheme exported to clipboard successfully');
  416.  
  417. exportButton.textContent = '✓ Copied!';
  418. setTimeout(() => {
  419. exportButton.textContent = 'Export';
  420. }, 2000);
  421. })
  422. .catch(err => {
  423. debug('Failed to copy: ' + err);
  424.  
  425. exportButton.textContent = '✗ Failed';
  426. setTimeout(() => {
  427. exportButton.textContent = 'Export';
  428. }, 2000);
  429. });
  430. });
  431.  
  432. importButton.addEventListener('click', async () => {
  433. try {
  434. const text = await navigator.clipboard.readText();
  435. const colorScheme = JSON.parse(text);
  436. debug('Importing color scheme: ' + text);
  437.  
  438. backgroundColorInput.value = colorScheme.backgroundColor || defaultBackgroundColor;
  439. actionBarColorInput.value = colorScheme.actionBarColor || defaultActionBarColor;
  440. topBarColorInput.value = colorScheme.topBarColor || defaultTopBarColor;
  441. textColorInput.value = colorScheme.textColor || defaultTextColor;
  442. warningTextColorInput.value = colorScheme.warningTextColor || defaultWarningTextColor;
  443. successTextColorInput.value = colorScheme.successTextColor || defaultSuccessTextColor;
  444.  
  445. const appElement = document.querySelector('#app[data-v-app]');
  446. if (appElement) {
  447. appElement.style.backgroundColor = backgroundColorInput.value;
  448. }
  449.  
  450. const actionBarElement = document.querySelector('#currentAction');
  451. if (actionBarElement) {
  452. actionBarElement.style.backgroundColor = actionBarColorInput.value;
  453. }
  454.  
  455. const topBarElement = document.querySelector('nav.navbar');
  456. if (topBarElement) {
  457. topBarElement.style.backgroundColor = topBarColorInput.value;
  458. }
  459.  
  460. document.body.style.color = textColorInput.value;
  461. applyTextColorToAll(textColorInput.value);
  462. applyWarningTextColor(warningTextColorInput.value);
  463. applySuccessTextColor(successTextColorInput.value);
  464.  
  465. saveColorsToLocalStorage();
  466.  
  467. importButton.textContent = '✓ Imported & Saved!';
  468. setTimeout(() => {
  469. importButton.textContent = 'Import';
  470. }, 2000);
  471.  
  472. } catch (error) {
  473. debug('Import error: ' + error);
  474. alert('Failed to import color scheme. Please ensure the clipboard has a valid format.');
  475.  
  476. importButton.textContent = '✗ Failed';
  477. setTimeout(() => {
  478. importButton.textContent = 'Import';
  479. }, 2000);
  480. }
  481. });
  482.  
  483. document.body.appendChild(uiContainer);
  484.  
  485. backgroundColorInput.addEventListener('input', () => {
  486. const appElement = document.querySelector('#app[data-v-app]');
  487. if (appElement) {
  488. appElement.style.backgroundColor = backgroundColorInput.value;
  489. }
  490. });
  491.  
  492. actionBarColorInput.addEventListener('input', () => {
  493. const actionBarElement = document.querySelector('#currentAction');
  494. if (actionBarElement) {
  495. actionBarElement.style.backgroundColor = actionBarColorInput.value;
  496. }
  497. });
  498.  
  499. topBarColorInput.addEventListener('input', () => {
  500. const topBarElement = document.querySelector('nav.navbar');
  501. if (topBarElement) {
  502. topBarElement.style.backgroundColor = topBarColorInput.value;
  503. }
  504. });
  505.  
  506. textColorInput.addEventListener('input', () => {
  507. document.body.style.color = textColorInput.value;
  508. applyTextColorToAll(textColorInput.value);
  509. });
  510.  
  511. warningTextColorInput.addEventListener('input', () => {
  512. applyWarningTextColor(warningTextColorInput.value);
  513. });
  514.  
  515. successTextColorInput.addEventListener('input', () => {
  516. applySuccessTextColor(successTextColorInput.value);
  517. });
  518.  
  519. saveButton.addEventListener('click', () => {
  520. saveColorsToLocalStorage();
  521.  
  522. setColors();
  523.  
  524. saveButton.textContent = '✓ Saved!';
  525. setTimeout(() => {
  526. saveButton.textContent = 'Save';
  527. }, 2000);
  528. });
  529.  
  530. resetButton.addEventListener('click', () => {
  531. localStorage.removeItem('backgroundColor');
  532. localStorage.removeItem('actionBarColor');
  533. localStorage.removeItem('topBarColor');
  534. localStorage.removeItem('textColor');
  535. localStorage.removeItem('warningTextColor');
  536. localStorage.removeItem('successTextColor');
  537. setColors();
  538.  
  539. resetButton.textContent = '✓ Reset!';
  540. setTimeout(() => {
  541. resetButton.textContent = 'Reset';
  542. }, 2000);
  543. });
  544.  
  545. GM_addStyle(`
  546. #colorChangerUI input[type="color"] {
  547. cursor: pointer;
  548. margin-left: 5px;
  549. border: none;
  550. height: 20px;
  551. width: 20px;
  552. padding: 0;
  553. background: none;
  554. }
  555. #colorChangerUI button {
  556. cursor: pointer;
  557. color: #fff;
  558. border: none;
  559. padding: 5px 10px;
  560. border-radius: 3px;
  561. font-size: 12px;
  562. transition: all 0.2s ease;
  563. }
  564. #colorChangerUI button:hover {
  565. opacity: 0.8;
  566. transform: translateY(-1px);
  567. }
  568. #colorChangerUI button:active {
  569. transform: translateY(1px);
  570. }
  571. #saveButton {
  572. background-color: #4CAF50 !important;
  573. }
  574. #resetButton {
  575. background-color: #f44336 !important;
  576. }
  577. #exportButton, #importButton {
  578. background-color: #2196F3 !important;
  579. }
  580. #colorChangerOpenButton {
  581. transition: transform 0.2s ease !important;
  582. }
  583. #colorChangerOpenButton:hover {
  584. transform: scale(1.1) !important;
  585. }
  586. #colorChangerUI label {
  587. margin-left: 10px;
  588. }
  589. #colorChangerUI label:first-child {
  590. margin-left: 0;
  591. }
  592. `);
  593.  
  594. function observePageChanges() {
  595. debug("Starting page observer");
  596. const observer = new MutationObserver((mutations) => {
  597. let needsUpdate = false;
  598. let hasDestructiveText = false;
  599. let hasSuccessText = false;
  600. let hasForegroundClasses = false;
  601.  
  602. mutations.forEach((mutation) => {
  603. if (mutation.addedNodes.length) {
  604.  
  605. mutation.addedNodes.forEach(node => {
  606. if (node.nodeType === 1) {
  607. if (node.className && node.className.includes) {
  608. if (node.className.includes('text-destructive')) {
  609. hasDestructiveText = true;
  610. }
  611. if (node.className.includes('text-success')) {
  612. hasSuccessText = true;
  613. }
  614. if (node.className.includes('text-success-foreground') ||
  615. node.className.includes('text-warning-foreground') ||
  616. node.className.includes('text-destructive-foreground')) {
  617. hasForegroundClasses = true;
  618. }
  619. }
  620.  
  621. const destructiveElements = node.querySelectorAll('[class*="text-destructive"]');
  622. if (destructiveElements.length > 0) {
  623. hasDestructiveText = true;
  624. }
  625.  
  626. const successElements = node.querySelectorAll('[class*="text-success"]');
  627. if (successElements.length > 0) {
  628. hasSuccessText = true;
  629. }
  630.  
  631. const foregroundElements = node.querySelectorAll(
  632. '[class*="text-success-foreground"], [class*="text-warning-foreground"], [class*="text-destructive-foreground"]'
  633. );
  634. if (foregroundElements.length > 0) {
  635. hasForegroundClasses = true;
  636. }
  637. }
  638. });
  639.  
  640. if (hasDestructiveText || hasForegroundClasses) {
  641. debug("Found new text-destructive or foreground elements, applying warning color");
  642. applyWarningTextColor(warningTextColorInput.value);
  643. }
  644.  
  645. if (hasSuccessText || hasForegroundClasses) {
  646. debug("Found new text-success or foreground elements, applying success color");
  647. applySuccessTextColor(successTextColorInput.value);
  648. }
  649.  
  650. if (hasForegroundClasses) {
  651. debug("Found new foreground elements, applying text color");
  652. applyTextColorToAll(textColorInput.value);
  653. }
  654.  
  655. needsUpdate = true;
  656. }
  657. });
  658.  
  659. if (needsUpdate) {
  660. debug("Page changed, reapplying colors");
  661.  
  662. if (!isColorUIOpen) {
  663. setColors();
  664. } else {
  665.  
  666. debug("UI is open - skipping setColors() to prevent overriding user selections");
  667. }
  668.  
  669. if (!document.querySelector('#colorChangerOpenButton')) {
  670. debug("Button disappeared, adding again");
  671. addOpenButton();
  672. }
  673. }
  674. });
  675.  
  676. observer.observe(document.body, { childList: true, subtree: true });
  677. }
  678.  
  679. if (document.readyState === 'loading') {
  680. document.addEventListener('DOMContentLoaded', waitForElements);
  681. } else {
  682. waitForElements();
  683. }
  684. })();