// ==UserScript==
// @name Elethor Chameleon
// @namespace http://tampermonkey.net/
// @version 2.1
// @author Eugene
// @description Change colors on Elethor.com
// @match *://elethor.com/*
// @grant GM_addStyle
// @license GPL-3.0-or-later
// ==/UserScript==
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
(function() {
'use strict';
const defaultBackgroundColor = '#202c3c';
const defaultActionBarColor = '#39444e';
const defaultTopBarColor = '#505c6c';
const defaultTextColor = '#ffffff';
const defaultWarningTextColor = '#7a3c38';
const defaultSuccessTextColor = '#52b768';
let isColorUIOpen = false;
const DEBUG = true;
function debug(message) {
if (DEBUG) {
console.log(`[Elethor Chameleon] ${message}`);
}
}
function setColors() {
debug("Setting colors");
const appElement = document.querySelector('#app[data-v-app]');
if (appElement) {
const backgroundColor = localStorage.getItem('backgroundColor') || defaultBackgroundColor;
appElement.style.backgroundColor = backgroundColor;
backgroundColorInput.value = backgroundColor;
debug("Background color set");
} else {
debug("App element not found");
}
const actionBarElement = document.querySelector('#currentAction');
if (actionBarElement) {
const actionBarColor = localStorage.getItem('actionBarColor') || defaultActionBarColor;
actionBarElement.style.backgroundColor = actionBarColor;
actionBarColorInput.value = actionBarColor;
debug("Action bar color set");
} else {
debug("Action bar element not found");
}
const topBarElement = document.querySelector('nav.navbar');
if (topBarElement) {
const topBarColor = localStorage.getItem('topBarColor') || defaultTopBarColor;
topBarElement.style.backgroundColor = topBarColor;
topBarColorInput.value = topBarColor;
debug("Top bar color set (navbar)");
} else {
debug("Top bar element (navbar) not found");
}
const textColor = localStorage.getItem('textColor') || defaultTextColor;
document.body.style.color = textColor;
textColorInput.value = textColor;
applyTextColorToAll(textColor);
debug("Text color set");
const warningTextColor = localStorage.getItem('warningTextColor') || defaultWarningTextColor;
warningTextColorInput.value = warningTextColor;
applyWarningTextColor(warningTextColor);
debug("Warning text color set");
const successTextColor = localStorage.getItem('successTextColor') || defaultSuccessTextColor;
successTextColorInput.value = successTextColor;
applySuccessTextColor(successTextColor);
debug("Success text color set");
}
function saveColorsToLocalStorage() {
debug("Saving colors to localStorage");
const backgroundColor = backgroundColorInput.value;
const actionBarColor = actionBarColorInput.value;
const topBarColor = topBarColorInput.value;
const textColor = textColorInput.value;
const warningTextColor = warningTextColorInput.value;
const successTextColor = successTextColorInput.value;
localStorage.setItem('backgroundColor', backgroundColor);
localStorage.setItem('actionBarColor', actionBarColor);
localStorage.setItem('topBarColor', topBarColor);
localStorage.setItem('textColor', textColor);
localStorage.setItem('warningTextColor', warningTextColor);
localStorage.setItem('successTextColor', successTextColor);
debug("Colors saved to localStorage");
}
function applyTextColorToAll(color) {
const elementsToColor = [
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'span', 'div', 'a', 'button'
];
elementsToColor.forEach(selector => {
const elements = document.querySelectorAll(selector);
elements.forEach(el => {
if (el.closest('tr th') &&
el.closest('tr')?.querySelector('th:nth-child(3)')?.textContent.includes('Experience')) {
return;
}
if (!el.className ||
((!el.className.includes('text-destructive') || el.className.includes('text-destructive-foreground')) &&
(!el.className.includes('text-success') || el.className.includes('text-success-foreground')))) {
el.style.color = color;
}
if (el.className && (el.className.includes('text-success-foreground') ||
el.className.includes('text-warning-foreground') ||
el.className.includes('text-destructive-foreground'))) {
el.style.color = color;
}
});
});
}
function applyWarningTextColor(color) {
const warningElements = document.querySelectorAll('[class*="text-destructive"]:not([class*="text-destructive-foreground"])');
warningElements.forEach(el => {
el.style.color = color;
});
debug(`Applied warning color ${color} to ${warningElements.length} text elements`);
const warningBgElements = document.querySelectorAll('[class*="text-destructive-foreground"], [class*="text-warning-foreground"]');
warningBgElements.forEach(el => {
if (el.classList.contains('bg-destructive') || el.classList.contains('bg-warning')) {
el.style.backgroundColor = color;
}
});
debug(`Applied warning color ${color} as background to ${warningBgElements.length} elements`);
}
function applySuccessTextColor(color) {
const successElements = document.querySelectorAll('[class*="text-success"]:not([class*="text-success-foreground"])');
successElements.forEach(el => {
el.style.color = color;
});
debug(`Applied success color ${color} to ${successElements.length} text elements`);
const successBgElements = document.querySelectorAll('[class*="text-success-foreground"]');
successBgElements.forEach(el => {
if (el.classList.contains('bg-success')) {
el.style.backgroundColor = color;
}
});
debug(`Applied success color ${color} as background to ${successBgElements.length} elements`);
}
function waitForElements() {
debug("Waiting for elements");
const interval = setInterval(() => {
const appElement = document.querySelector('#app[data-v-app]');
const actionBarElement = document.querySelector('#currentAction');
const topBarElement = document.querySelector('nav.navbar');
const navbar = document.querySelector('.navbar');
if (appElement) {
debug("App element found");
}
if (actionBarElement) {
debug("Action bar element found");
}
if (topBarElement) {
debug("Top bar element (navbar) found");
}
if (navbar) {
debug("Navbar found");
}
if ((appElement && actionBarElement && topBarElement) || navbar) {
debug("Essential elements found or navbar present");
clearInterval(interval);
setColors();
addOpenButton();
positionUI();
observePageChanges();
}
}, 1000);
}
function addOpenButton() {
debug("Adding open button");
let navbarItem = document.querySelector('a[href="/corporation"].navbar-item.is-skewed');
if (!navbarItem) {
debug("Corporation link not found, trying alternate methods");
const allNavbarItems = document.querySelectorAll('.navbar-item');
if (allNavbarItems.length > 0) {
navbarItem = allNavbarItems[allNavbarItems.length - 1];
debug("Using last navbar item as anchor");
} else {
const navbar = document.querySelector('.navbar');
if (navbar) {
debug("No navbar items found, adding to navbar directly");
const openButton = createOpenButton();
openButton.style.position = 'relative';
openButton.style.marginLeft = '10px';
navbar.appendChild(openButton);
return;
} else {
debug("No navbar found, creating floating button");
const openButton = createOpenButton();
openButton.style.position = 'fixed';
openButton.style.top = '10px';
openButton.style.right = '10px';
openButton.style.zIndex = '10001';
document.body.appendChild(openButton);
return;
}
}
}
if (navbarItem) {
debug("Adding button next to navbar item");
const openButton = createOpenButton();
navbarItem.parentNode.insertBefore(openButton, navbarItem.nextSibling);
} else {
debug("Failed to find any suitable location for button");
}
}
function createOpenButton() {
const openButton = document.createElement('button');
openButton.id = 'colorChangerOpenButton';
openButton.innerHTML = '🎨';
openButton.style.marginLeft = '10px';
const topBarElement = document.querySelector('nav.navbar');
const topBarColor = topBarElement ? topBarElement.style.backgroundColor : '#2596be';
openButton.style.backgroundColor = topBarColor;
openButton.style.color = '#fff';
openButton.style.border = 'none';
openButton.style.padding = '5px';
openButton.style.borderRadius = '3px';
openButton.style.cursor = 'pointer';
openButton.addEventListener('click', () => {
debug("Open button clicked");
uiContainer.style.display = uiContainer.style.display === 'none' ? 'flex' : 'none';
isColorUIOpen = uiContainer.style.display === 'flex';
positionUI();
});
return openButton;
}
function positionUI() {
debug("Positioning UI");
const topBarElement = document.querySelector('nav.navbar');
if (topBarElement) {
const { height } = topBarElement.getBoundingClientRect();
uiContainer.style.top = `${height + 5}px`;
debug(`UI positioned at ${height + 5}px from top`);
} else {
uiContainer.style.top = '50px';
debug("Using fallback UI position");
}
}
const uiContainer = document.createElement('div');
uiContainer.id = 'colorChangerUI';
uiContainer.style.position = 'fixed';
uiContainer.style.padding = '10px';
uiContainer.style.backgroundColor = '#505c6c';
uiContainer.style.border = '1px solid #ccc';
uiContainer.style.zIndex = '10000';
uiContainer.style.display = 'none';
uiContainer.style.flexDirection = 'row';
uiContainer.style.alignItems = 'center';
uiContainer.style.whiteSpace = 'nowrap';
uiContainer.style.color = '#ffffff';
uiContainer.style.fontSize = '12px';
uiContainer.style.right = '10px';
uiContainer.style.borderRadius = '5px';
uiContainer.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
uiContainer.style.flexWrap = 'wrap';
uiContainer.style.maxWidth = '800px';
const backgroundColorLabel = document.createElement('label');
backgroundColorLabel.textContent = 'Background Color: ';
uiContainer.appendChild(backgroundColorLabel);
const backgroundColorInput = document.createElement('input');
backgroundColorInput.type = 'color';
uiContainer.appendChild(backgroundColorInput);
const actionBarColorLabel = document.createElement('label');
actionBarColorLabel.textContent = 'Action Bar Color: ';
actionBarColorLabel.style.marginLeft = '10px';
uiContainer.appendChild(actionBarColorLabel);
const actionBarColorInput = document.createElement('input');
actionBarColorInput.type = 'color';
uiContainer.appendChild(actionBarColorInput);
const topBarColorLabel = document.createElement('label');
topBarColorLabel.textContent = 'Top Bar Color: ';
topBarColorLabel.style.marginLeft = '10px';
uiContainer.appendChild(topBarColorLabel);
const topBarColorInput = document.createElement('input');
topBarColorInput.type = 'color';
uiContainer.appendChild(topBarColorInput);
const textColorLabel = document.createElement('label');
textColorLabel.textContent = 'Text Color: ';
textColorLabel.style.marginLeft = '10px';
uiContainer.appendChild(textColorLabel);
const textColorInput = document.createElement('input');
textColorInput.type = 'color';
uiContainer.appendChild(textColorInput);
const warningTextColorLabel = document.createElement('label');
warningTextColorLabel.textContent = 'Warning Text: ';
warningTextColorLabel.style.marginLeft = '10px';
uiContainer.appendChild(warningTextColorLabel);
const warningTextColorInput = document.createElement('input');
warningTextColorInput.type = 'color';
warningTextColorInput.value = defaultWarningTextColor;
uiContainer.appendChild(warningTextColorInput);
const successTextColorLabel = document.createElement('label');
successTextColorLabel.textContent = 'Success Text: ';
successTextColorLabel.style.marginLeft = '10px';
uiContainer.appendChild(successTextColorLabel);
const successTextColorInput = document.createElement('input');
successTextColorInput.type = 'color';
successTextColorInput.value = defaultSuccessTextColor;
uiContainer.appendChild(successTextColorInput);
const buttonContainer = document.createElement('div');
buttonContainer.style.display = 'flex';
buttonContainer.style.marginTop = '8px';
buttonContainer.style.width = '100%';
buttonContainer.style.justifyContent = 'flex-end';
uiContainer.appendChild(buttonContainer);
const saveButton = document.createElement('button');
saveButton.textContent = 'Save';
saveButton.style.marginLeft = '10px';
saveButton.id = 'saveButton';
buttonContainer.appendChild(saveButton);
const resetButton = document.createElement('button');
resetButton.textContent = 'Reset';
resetButton.style.marginLeft = '5px';
resetButton.id = 'resetButton';
buttonContainer.appendChild(resetButton);
const exportButton = document.createElement('button');
exportButton.textContent = 'Export';
exportButton.style.marginLeft = '5px';
exportButton.style.backgroundColor = '#2596be';
exportButton.id = 'exportButton';
buttonContainer.appendChild(exportButton);
const importButton = document.createElement('button');
importButton.textContent = 'Import';
importButton.style.marginLeft = '5px';
importButton.style.backgroundColor = '#2596be';
importButton.id = 'importButton';
buttonContainer.appendChild(importButton);
exportButton.addEventListener('click', () => {
const colorScheme = {
backgroundColor: backgroundColorInput.value,
actionBarColor: actionBarColorInput.value,
topBarColor: topBarColorInput.value,
textColor: textColorInput.value,
warningTextColor: warningTextColorInput.value,
successTextColor: successTextColorInput.value
};
const colorSchemeString = JSON.stringify(colorScheme);
navigator.clipboard.writeText(colorSchemeString)
.then(() => {
debug('Color scheme exported to clipboard successfully');
exportButton.textContent = '✓ Copied!';
setTimeout(() => {
exportButton.textContent = 'Export';
}, 2000);
})
.catch(err => {
debug('Failed to copy: ' + err);
exportButton.textContent = '✗ Failed';
setTimeout(() => {
exportButton.textContent = 'Export';
}, 2000);
});
});
importButton.addEventListener('click', async () => {
try {
const text = await navigator.clipboard.readText();
const colorScheme = JSON.parse(text);
debug('Importing color scheme: ' + text);
backgroundColorInput.value = colorScheme.backgroundColor || defaultBackgroundColor;
actionBarColorInput.value = colorScheme.actionBarColor || defaultActionBarColor;
topBarColorInput.value = colorScheme.topBarColor || defaultTopBarColor;
textColorInput.value = colorScheme.textColor || defaultTextColor;
warningTextColorInput.value = colorScheme.warningTextColor || defaultWarningTextColor;
successTextColorInput.value = colorScheme.successTextColor || defaultSuccessTextColor;
const appElement = document.querySelector('#app[data-v-app]');
if (appElement) {
appElement.style.backgroundColor = backgroundColorInput.value;
}
const actionBarElement = document.querySelector('#currentAction');
if (actionBarElement) {
actionBarElement.style.backgroundColor = actionBarColorInput.value;
}
const topBarElement = document.querySelector('nav.navbar');
if (topBarElement) {
topBarElement.style.backgroundColor = topBarColorInput.value;
}
document.body.style.color = textColorInput.value;
applyTextColorToAll(textColorInput.value);
applyWarningTextColor(warningTextColorInput.value);
applySuccessTextColor(successTextColorInput.value);
saveColorsToLocalStorage();
importButton.textContent = '✓ Imported & Saved!';
setTimeout(() => {
importButton.textContent = 'Import';
}, 2000);
} catch (error) {
debug('Import error: ' + error);
alert('Failed to import color scheme. Please ensure the clipboard has a valid format.');
importButton.textContent = '✗ Failed';
setTimeout(() => {
importButton.textContent = 'Import';
}, 2000);
}
});
document.body.appendChild(uiContainer);
backgroundColorInput.addEventListener('input', () => {
const appElement = document.querySelector('#app[data-v-app]');
if (appElement) {
appElement.style.backgroundColor = backgroundColorInput.value;
}
});
actionBarColorInput.addEventListener('input', () => {
const actionBarElement = document.querySelector('#currentAction');
if (actionBarElement) {
actionBarElement.style.backgroundColor = actionBarColorInput.value;
}
});
topBarColorInput.addEventListener('input', () => {
const topBarElement = document.querySelector('nav.navbar');
if (topBarElement) {
topBarElement.style.backgroundColor = topBarColorInput.value;
}
});
textColorInput.addEventListener('input', () => {
document.body.style.color = textColorInput.value;
applyTextColorToAll(textColorInput.value);
});
warningTextColorInput.addEventListener('input', () => {
applyWarningTextColor(warningTextColorInput.value);
});
successTextColorInput.addEventListener('input', () => {
applySuccessTextColor(successTextColorInput.value);
});
saveButton.addEventListener('click', () => {
saveColorsToLocalStorage();
setColors();
saveButton.textContent = '✓ Saved!';
setTimeout(() => {
saveButton.textContent = 'Save';
}, 2000);
});
resetButton.addEventListener('click', () => {
localStorage.removeItem('backgroundColor');
localStorage.removeItem('actionBarColor');
localStorage.removeItem('topBarColor');
localStorage.removeItem('textColor');
localStorage.removeItem('warningTextColor');
localStorage.removeItem('successTextColor');
setColors();
resetButton.textContent = '✓ Reset!';
setTimeout(() => {
resetButton.textContent = 'Reset';
}, 2000);
});
GM_addStyle(`
#colorChangerUI input[type="color"] {
cursor: pointer;
margin-left: 5px;
border: none;
height: 20px;
width: 20px;
padding: 0;
background: none;
}
#colorChangerUI button {
cursor: pointer;
color: #fff;
border: none;
padding: 5px 10px;
border-radius: 3px;
font-size: 12px;
transition: all 0.2s ease;
}
#colorChangerUI button:hover {
opacity: 0.8;
transform: translateY(-1px);
}
#colorChangerUI button:active {
transform: translateY(1px);
}
#saveButton {
background-color: #4CAF50 !important;
}
#resetButton {
background-color: #f44336 !important;
}
#exportButton, #importButton {
background-color: #2196F3 !important;
}
#colorChangerOpenButton {
transition: transform 0.2s ease !important;
}
#colorChangerOpenButton:hover {
transform: scale(1.1) !important;
}
#colorChangerUI label {
margin-left: 10px;
}
#colorChangerUI label:first-child {
margin-left: 0;
}
`);
function observePageChanges() {
debug("Starting page observer");
const observer = new MutationObserver((mutations) => {
let needsUpdate = false;
let hasDestructiveText = false;
let hasSuccessText = false;
let hasForegroundClasses = false;
mutations.forEach((mutation) => {
if (mutation.addedNodes.length) {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1) {
if (node.className && node.className.includes) {
if (node.className.includes('text-destructive')) {
hasDestructiveText = true;
}
if (node.className.includes('text-success')) {
hasSuccessText = true;
}
if (node.className.includes('text-success-foreground') ||
node.className.includes('text-warning-foreground') ||
node.className.includes('text-destructive-foreground')) {
hasForegroundClasses = true;
}
}
const destructiveElements = node.querySelectorAll('[class*="text-destructive"]');
if (destructiveElements.length > 0) {
hasDestructiveText = true;
}
const successElements = node.querySelectorAll('[class*="text-success"]');
if (successElements.length > 0) {
hasSuccessText = true;
}
const foregroundElements = node.querySelectorAll(
'[class*="text-success-foreground"], [class*="text-warning-foreground"], [class*="text-destructive-foreground"]'
);
if (foregroundElements.length > 0) {
hasForegroundClasses = true;
}
}
});
if (hasDestructiveText || hasForegroundClasses) {
debug("Found new text-destructive or foreground elements, applying warning color");
applyWarningTextColor(warningTextColorInput.value);
}
if (hasSuccessText || hasForegroundClasses) {
debug("Found new text-success or foreground elements, applying success color");
applySuccessTextColor(successTextColorInput.value);
}
if (hasForegroundClasses) {
debug("Found new foreground elements, applying text color");
applyTextColorToAll(textColorInput.value);
}
needsUpdate = true;
}
});
if (needsUpdate) {
debug("Page changed, reapplying colors");
if (!isColorUIOpen) {
setColors();
} else {
debug("UI is open - skipping setColors() to prevent overriding user selections");
}
if (!document.querySelector('#colorChangerOpenButton')) {
debug("Button disappeared, adding again");
addOpenButton();
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', waitForElements);
} else {
waitForElements();
}
})();