Customizes Torn sidebar notification colors and Ranked War weapon/armor rarity backgrounds.
// ==UserScript==
// @name Torn Color Customizer
// @namespace https://www.torn.com/
// @version 1.0.0
// @description Customizes Torn sidebar notification colors and Ranked War weapon/armor rarity backgrounds.
// @author Exiled [3527247]
// @match https://www.torn.com/*
// @match https://torn.com/*
// @run-at document-start
// @grant none
// ==/UserScript==
(() => {
'use strict';
const ID = 'tncr', VERSION = '1.0.0';
const KEYS = {
enabled: `${ID}-enabled`,
custom: `${ID}-custom`,
color: `${ID}-color`,
rwEnabled: `${ID}-rw-enabled`,
dot: `${ID}-dot`,
accountLinks: `${ID}-account-links`,
eventsLegacy: `${ID}-events`
};
const RW = {
yellow: {label: 'Yellow', default: '#FFFF00'},
orange: {label: 'Orange', default: '#D08000'},
red: {label: 'Red', default: '#E64D1A'}
};
const get = (key, fallback) => localStorage.getItem(key) ?? fallback;
const enabled = () => get(KEYS.enabled, '1') !== '0';
const custom = () => get(KEYS.custom, '0') === '1';
const savedColor = () => normalize(get(KEYS.color, '#829E2E')) || '#829E2E';
const rwEnabled = () => get(KEYS.rwEnabled, '0') === '1';
const dotEnabled = () => get(KEYS.dot, '1') !== '0';
const accountLinksEnabled = () => get(KEYS.accountLinks, get(KEYS.eventsLegacy, '0')) === '1';
const rwTierEnabled = tier => get(`${ID}-rw-${tier}-enabled`, '0') === '1';
const rwSavedColor = tier => normalize(get(`${ID}-rw-${tier}-color`, RW[tier].default)) || RW[tier].default;
const style = document.createElement('style');
style.textContent = `
html.${ID},html.${ID} body,html.${ID} #sidebarroot{
--sidebar-status-attention-dot-color:linear-gradient(180deg,#A3C248,#799427)!important;
--sidebar-status-attention-color:#829e2e!important;
--sidebar-area-bg-attention:rgba(121,148,39,.18)!important;
--sidebar-area-bg-attention-active:rgba(121,148,39,.28)!important
}
html.${ID} #sidebarroot [class*="area-mobile___"][class*="attention___"]{
--sidebar-status-attention-dot-color:linear-gradient(180deg,#AFC372,#798D3C)!important;
--sidebar-status-attention-color:#829e2e!important;
--sidebar-area-bg-attention:rgba(121,141,60,.14)!important;
--sidebar-area-bg-attention-active:rgba(133,166,35,.24)!important
}
html.${ID} body.dark-mode #sidebarroot [class*="area-mobile___"][class*="attention___"]{
--sidebar-status-attention-color:#a8bc6b!important;
--sidebar-area-bg-attention:rgba(175,195,114,.1)!important;
--sidebar-area-bg-attention-active:rgba(187,220,89,.2)!important
}
html.${ID}.${ID}-custom,html.${ID}.${ID}-custom body,html.${ID}.${ID}-custom #sidebarroot,
html.${ID}.${ID}-custom #sidebarroot [class*="area-mobile___"][class*="attention___"],
html.${ID}.${ID}-custom body.dark-mode #sidebarroot [class*="area-mobile___"][class*="attention___"]{
--sidebar-status-attention-dot-color:linear-gradient(180deg,var(--${ID}-light),var(--${ID}-dark))!important;
--sidebar-status-attention-color:var(--${ID}-color)!important;
--sidebar-area-bg-attention:var(--${ID}-bg)!important;
--sidebar-area-bg-attention-active:var(--${ID}-bg-active)!important
}
html.${ID} #sidebarroot [class*="area-desktop___"][class*="attention___"] [class*="linkName___"]{
color:var(--${ID}-desktop-label-color,inherit)!important
}
/* PDA labels use a direct text color instead of the shared attention variable. */
html.${ID} #sidebarroot [class*="area-mobile___"][class*="attention___"] a[class*="mobileLink___"] > span:last-child,
html.${ID} #sidebarroot [class*="area-mobile___"][class*="attention___"] a.sidebarMobileLink > span:last-child{
color:var(--sidebar-status-attention-color)!important
}
/* PDA draws the notification dot with a pseudo-element. */
html.${ID} #sidebarroot [class*="area-mobile___"][class*="attention___"]::after,
html.${ID} #sidebarroot [class*="area-mobile___"][class*="attention___"] > [class*="area-row___"]::after,
html.${ID} #sidebarroot [class*="area-mobile___"][class*="attention___"] a[class*="mobileLink___"]::after,
html.${ID} #sidebarroot [class*="area-mobile___"][class*="attention___"] a.sidebarMobileLink::after{
background:var(--sidebar-status-attention-dot-color)!important;
background-image:var(--sidebar-status-attention-dot-color)!important
}
/* Remove Torn's dark inset rim from recolored dots. */
html.${ID} #sidebarroot [class*="attention___"]::before,
html.${ID} #sidebarroot [class*="attention___"]::after,
html.${ID} #sidebarroot [class*="attention___"] > [class*="area-row___"]::before,
html.${ID} #sidebarroot [class*="attention___"] > [class*="area-row___"]::after{
box-shadow:none!important;
filter:none!important
}
html.${ID}.${ID}-no-dot,html.${ID}.${ID}-no-dot body,html.${ID}.${ID}-no-dot #sidebarroot,
html.${ID}.${ID}-no-dot #sidebarroot [class*="attention___"]{
--sidebar-status-attention-dot-color:transparent!important
}
/* Hide the PDA dot when disabled. */
html.${ID}.${ID}-no-dot #sidebarroot [class*="area-mobile___"][class*="attention___"]::after,
html.${ID}.${ID}-no-dot #sidebarroot [class*="area-mobile___"][class*="attention___"] > [class*="area-row___"]::after,
html.${ID}.${ID}-no-dot #sidebarroot [class*="area-mobile___"][class*="attention___"] a[class*="mobileLink___"]::after,
html.${ID}.${ID}-no-dot #sidebarroot [class*="area-mobile___"][class*="attention___"] a.sidebarMobileLink::after{
content:none!important;
display:none!important;
background:none!important;
box-shadow:none!important
}
html.${ID}:not(.${ID}-custom) #sidebarroot [class*="area-desktop___"][class*="attention___"] [class*="defaultIcon___"] svg :is(path,polygon,rect,circle,ellipse){fill:url(#sidebar_svg_gradient_regular_desktop_green)!important}
html.${ID}:not(.${ID}-custom) #sidebarroot [class*="area-mobile___"][class*="attention___"] [class*="defaultIcon___"] svg :is(path,polygon,rect,circle,ellipse){fill:url(#sidebar_svg_gradient_regular_green_mobile)!important}
html.${ID}:not(.${ID}-custom) #sidebarroot [class*="area-mobile___"][class*="attention___"]:is(:hover,:active) [class*="defaultIcon___"] svg :is(path,polygon,rect,circle,ellipse){fill:url(#sidebar_svg_gradient_regular_green_mobile_hover)!important}
html.${ID}.${ID}-custom #sidebarroot [class*="area-desktop___"][class*="attention___"] [class*="defaultIcon___"] svg :is(path,polygon,rect,circle,ellipse){fill:url(#${ID}-desktop)!important}
html.${ID}.${ID}-custom #sidebarroot [class*="area-mobile___"][class*="attention___"] [class*="defaultIcon___"] svg :is(path,polygon,rect,circle,ellipse){fill:url(#${ID}-mobile)!important}
html.${ID}.${ID}-custom #sidebarroot [class*="area-mobile___"][class*="attention___"]:is(:hover,:active) [class*="defaultIcon___"] svg :is(path,polygon,rect,circle,ellipse){fill:url(#${ID}-mobile-hover)!important}
/* Messages, Events, and Awards use different notification markup on PC and PDA. */
html.${ID}.${ID}-account-links :is(#nav-messages,#nav-events,#nav-awards)[class*="positive___"]{
color:var(--sidebar-status-attention-color)!important
}
html.${ID}.${ID}-account-links :is(#nav-messages,#nav-events,#nav-awards)[class*="positive___"] svg,
html.${ID}.${ID}-account-links :is(#nav-messages,#nav-events,#nav-awards)[class*="positive___"] svg :is(path,polygon,rect,circle,ellipse){
fill:var(--sidebar-status-attention-color)!important;
color:var(--sidebar-status-attention-color)!important
}
html.${ID}.${ID}-account-links :is(#nav-messages,#nav-events,#nav-awards)[class*="positive___"] [class*="amount___"]{
color:var(--sidebar-status-attention-color)!important;
-webkit-text-fill-color:var(--sidebar-status-attention-color)!important;
background:transparent!important;
border:0!important;
box-shadow:none!important;
filter:none!important
}
/* PDA: the nav ID is on the outer mobile area. A live notification has available___ and mobileAmount___. */
html.${ID}.${ID}-account-links #sidebarroot :is(#nav-messages,#nav-events,#nav-awards)[class*="area-mobile___"][class*="available___"]:has([class*="mobileAmount___"]){
--${ID}-account-mobile-color:var(--sidebar-status-attention-color)
}
html.${ID}.${ID}-account-links body.dark-mode #sidebarroot :is(#nav-messages,#nav-events,#nav-awards)[class*="area-mobile___"][class*="available___"]:has([class*="mobileAmount___"]){
--${ID}-account-mobile-color:#a8bc6b
}
html.${ID}.${ID}-account-links.${ID}-custom #sidebarroot :is(#nav-messages,#nav-events,#nav-awards)[class*="area-mobile___"][class*="available___"]:has([class*="mobileAmount___"]),
html.${ID}.${ID}-account-links.${ID}-custom body.dark-mode #sidebarroot :is(#nav-messages,#nav-events,#nav-awards)[class*="area-mobile___"][class*="available___"]:has([class*="mobileAmount___"]){
--${ID}-account-mobile-color:var(--${ID}-color)
}
html.${ID}.${ID}-account-links #sidebarroot :is(#nav-messages,#nav-events,#nav-awards)[class*="area-mobile___"][class*="available___"]:has([class*="mobileAmount___"]) [class*="defaultIcon___"] svg,
html.${ID}.${ID}-account-links #sidebarroot :is(#nav-messages,#nav-events,#nav-awards)[class*="area-mobile___"][class*="available___"]:has([class*="mobileAmount___"]) [class*="defaultIcon___"] svg :is(path,polygon,rect,circle,ellipse){
fill:var(--${ID}-account-mobile-color)!important;
color:var(--${ID}-account-mobile-color)!important
}
html.${ID}.${ID}-account-links #sidebarroot :is(#nav-messages,#nav-events,#nav-awards)[class*="area-mobile___"][class*="available___"]:has([class*="mobileAmount___"]) [class*="mobileAmount___"],
html.${ID}.${ID}-account-links #sidebarroot :is(#nav-messages,#nav-events,#nav-awards)[class*="area-mobile___"][class*="available___"]:has([class*="mobileAmount___"]) a[class*="mobileLink___"] > span:last-child{
color:var(--${ID}-account-mobile-color)!important;
-webkit-text-fill-color:var(--${ID}-account-mobile-color)!important
}
html.${ID}.${ID}-account-links #sidebarroot :is(#nav-messages,#nav-events,#nav-awards)[class*="area-mobile___"][class*="available___"]:has([class*="mobileAmount___"]) [class*="mobileAmount___"]{
background:transparent!important;
border:0!important;
box-shadow:none!important;
filter:none!important
}
html.${ID}-rw.${ID}-rw-yellow .display-bonus--yellow{
--db-bgc:var(--${ID}-rw-yellow-row)!important;
--db-outline:var(--${ID}-rw-yellow-color)!important
}
html.${ID}-rw.${ID}-rw-orange .display-bonus--orange{
--db-bgc:var(--${ID}-rw-orange-row)!important;
--db-outline:var(--${ID}-rw-orange-color)!important
}
html.${ID}-rw.${ID}-rw-red .display-bonus--red{
--db-bgc:var(--${ID}-rw-red-row)!important;
--db-outline:var(--${ID}-rw-red-color)!important
}
html.${ID}-rw.${ID}-rw-yellow :is(.glow-yellow,.glow-yellow-background){
background-color:var(--${ID}-rw-yellow-soft)!important;
background-image:radial-gradient(circle at 50% 45%,var(--${ID}-rw-yellow-glow) 0%,var(--${ID}-rw-yellow-soft) 48%,transparent 78%)!important;
box-shadow:inset 0 0 30px var(--${ID}-rw-yellow-mid),0 0 10px var(--${ID}-rw-yellow-soft)!important
}
html.${ID}-rw.${ID}-rw-orange :is(.glow-orange,.glow-orange-background){
background-color:var(--${ID}-rw-orange-soft)!important;
background-image:radial-gradient(circle at 50% 45%,var(--${ID}-rw-orange-glow) 0%,var(--${ID}-rw-orange-soft) 48%,transparent 78%)!important;
box-shadow:inset 0 0 30px var(--${ID}-rw-orange-mid),0 0 10px var(--${ID}-rw-orange-soft)!important
}
html.${ID}-rw.${ID}-rw-red :is(.glow-red,.glow-red-background){
background-color:var(--${ID}-rw-red-soft)!important;
background-image:radial-gradient(circle at 50% 45%,var(--${ID}-rw-red-glow) 0%,var(--${ID}-rw-red-soft) 48%,transparent 78%)!important;
box-shadow:inset 0 0 30px var(--${ID}-rw-red-mid),0 0 10px var(--${ID}-rw-red-soft)!important
}
html.${ID}-rw.${ID}-rw-yellow :is(.glow-yellow,.glow-yellow-background)::before,html.${ID}-rw.${ID}-rw-yellow :is(.glow-yellow,.glow-yellow-background)::after{background-color:var(--${ID}-rw-yellow-soft)!important;background-image:radial-gradient(circle at 50% 45%,var(--${ID}-rw-yellow-glow) 0%,var(--${ID}-rw-yellow-soft) 48%,transparent 78%)!important;box-shadow:inset 0 0 30px var(--${ID}-rw-yellow-mid)!important}
html.${ID}-rw.${ID}-rw-orange :is(.glow-orange,.glow-orange-background)::before,html.${ID}-rw.${ID}-rw-orange :is(.glow-orange,.glow-orange-background)::after{background-color:var(--${ID}-rw-orange-soft)!important;background-image:radial-gradient(circle at 50% 45%,var(--${ID}-rw-orange-glow) 0%,var(--${ID}-rw-orange-soft) 48%,transparent 78%)!important;box-shadow:inset 0 0 30px var(--${ID}-rw-orange-mid)!important}
html.${ID}-rw.${ID}-rw-red :is(.glow-red,.glow-red-background)::before,html.${ID}-rw.${ID}-rw-red :is(.glow-red,.glow-red-background)::after{background-color:var(--${ID}-rw-red-soft)!important;background-image:radial-gradient(circle at 50% 45%,var(--${ID}-rw-red-glow) 0%,var(--${ID}-rw-red-soft) 48%,transparent 78%)!important;box-shadow:inset 0 0 30px var(--${ID}-rw-red-mid)!important}
html.${ID}-rw.${ID}-rw-yellow li.item-weapon:has(.glow-yellow),html.${ID}-rw.${ID}-rw-yellow li.item-armor:has(.glow-yellow){background-color:var(--${ID}-rw-yellow-soft)!important;background-image:radial-gradient(circle at 50% 45%,var(--${ID}-rw-yellow-glow) 0%,var(--${ID}-rw-yellow-soft) 52%,transparent 82%)!important;box-shadow:inset 0 0 24px var(--${ID}-rw-yellow-mid)!important}
html.${ID}-rw.${ID}-rw-orange li.item-weapon:has(.glow-orange),html.${ID}-rw.${ID}-rw-orange li.item-armor:has(.glow-orange){background-color:var(--${ID}-rw-orange-soft)!important;background-image:radial-gradient(circle at 50% 45%,var(--${ID}-rw-orange-glow) 0%,var(--${ID}-rw-orange-soft) 52%,transparent 82%)!important;box-shadow:inset 0 0 24px var(--${ID}-rw-orange-mid)!important}
html.${ID}-rw.${ID}-rw-red li.item-weapon:has(.glow-red),html.${ID}-rw.${ID}-rw-red li.item-armor:has(.glow-red){background-color:var(--${ID}-rw-red-soft)!important;background-image:radial-gradient(circle at 50% 45%,var(--${ID}-rw-red-glow) 0%,var(--${ID}-rw-red-soft) 52%,transparent 82%)!important;box-shadow:inset 0 0 24px var(--${ID}-rw-red-mid)!important}
#${ID}-divider{background:rgba(0,0,0,.18)!important;display:block!important;flex:0 0 1px!important;height:1px!important;margin:12px 0!important;min-height:1px!important;padding:0!important;width:100%!important}
body.dark-mode #${ID}-divider{background:rgba(255,255,255,.16)!important}
#${ID}-entry button{width:100%}
#${ID}-panel[hidden],#${ID}-rw-options[hidden],.${ID}-rw-color[hidden]{display:none!important}
#${ID}-panel{background:#f2f2f2;border:1px solid #aaa;border-radius:7px;box-shadow:0 5px 22px #0008;color:#222;left:50%;max-height:calc(100vh - 100px);max-width:calc(100vw - 24px);overflow:auto;padding:14px;position:fixed;top:72px;transform:translateX(-50%);width:350px;z-index:999999}
body.dark-mode #${ID}-panel{background:#2d2d2d;border-color:#555;color:#ddd}
#${ID}-panel header{align-items:center;display:flex;font-size:15px;font-weight:700;justify-content:space-between;margin-bottom:14px}
#${ID}-close{background:none;border:0;color:inherit;cursor:pointer;font-size:22px;line-height:1;padding:0 2px}
#${ID}-panel label{align-items:center;display:flex;gap:9px;margin:9px 0}
#${ID}-modes{border:0;margin:12px 0 0;padding:0}
#${ID}-modes legend,.${ID}-rw-tier legend{font-size:12px;font-weight:700;margin-bottom:5px}
#${ID}-modes[aria-disabled="true"]{opacity:.45;pointer-events:none}
#${ID}-colors,.${ID}-rw-color{align-items:center;display:flex;gap:8px;margin:10px 0 0 25px}
#${ID}-colors[aria-disabled="true"]{opacity:.45;pointer-events:none}
#${ID}-picker,.${ID}-rw-picker{background:none;border:0;cursor:pointer;height:34px;padding:0;width:42px}
#${ID}-hex,.${ID}-rw-hex{background:transparent;border:1px solid #888;border-radius:4px;color:inherit;font:13px monospace;padding:7px;width:88px}
.${ID}-invalid{border-color:#d33!important;box-shadow:0 0 0 1px #d33}
#${ID}-panel p{font-size:12px;line-height:1.4;margin:12px 0 0;opacity:.75}
#${ID}-panel-divider{border:0;border-top:1px solid rgba(127,127,127,.35);height:0;margin:16px 0}
#${ID}-rw-title{font-size:13px;font-weight:700;margin:0 0 8px}
#${ID}-rw-options{margin-top:8px}
.${ID}-rw-tier{border:1px solid rgba(127,127,127,.35);border-radius:6px;margin:10px 0;padding:8px 10px 10px}
.${ID}-rw-tier legend{padding:0 5px}
.${ID}-actions{display:flex;justify-content:flex-end;margin-top:12px}
.${ID}-reset{background:rgba(127,127,127,.12);border:1px solid rgba(127,127,127,.5);border-radius:5px;color:inherit;cursor:pointer;font:inherit;padding:6px 10px}
.${ID}-reset:hover{background:rgba(127,127,127,.22)}
#${ID}-panel footer{border-top:1px solid rgba(127,127,127,.3);font-size:11px;margin-top:14px;padding-top:9px;opacity:.7}
@media (max-width:600px){#${ID}-panel{box-sizing:border-box;max-height:calc(100vh - 24px);top:12px;width:calc(100vw - 24px)}#${ID}-hex,.${ID}-rw-hex{font-size:16px}}
`;
(document.head || document.documentElement).append(style);
function normalize(value) {
const match = String(value).trim().match(/^#?([\da-f]{3}|[\da-f]{6})$/i);
if (!match) return '';
let hex = match[1];
if (hex.length === 3) hex = [...hex].map(x => x + x).join('');
return `#${hex.toUpperCase()}`;
}
const rgb = hex => hex.slice(1).match(/../g).map(x => parseInt(x, 16));
const mix = (hex, target, amount) => `#${rgb(hex).map((v, i) => Math.round(v + (target[i] - v) * amount).toString(16).padStart(2, '0')).join('').toUpperCase()}`;
const rgba = (hex, alpha) => `rgba(${rgb(hex).join(',')},${alpha})`;
const ensureGradients = () => {
if (document.getElementById(`${ID}-gradients`) || !document.body) return;
document.body.insertAdjacentHTML('afterbegin', `<svg id="${ID}-gradients" width="0" height="0" style="position:absolute"><defs>
<linearGradient id="${ID}-desktop" x1=".5" y1="1" x2=".5" y2="0"><stop offset="0"/><stop offset="1"/></linearGradient>
<linearGradient id="${ID}-mobile" x1=".5" y1="0" x2=".5" y2="1"><stop offset="0"/><stop offset="1"/></linearGradient>
<linearGradient id="${ID}-mobile-hover" x1=".5" y1="0" x2=".5" y2="1"><stop offset="0"/><stop offset="1"/></linearGradient>
</defs></svg>`);
};
const applyColor = color => {
ensureGradients();
const root = document.documentElement;
const dark = mix(color, [0,0,0], .22), light = mix(color, [255,255,255], .28);
const hoverLight = mix(color, [255,255,255], .42), hoverDark = mix(color, [0,0,0], .06);
root.style.setProperty(`--${ID}-color`, color);
root.style.setProperty(`--${ID}-dark`, dark);
root.style.setProperty(`--${ID}-light`, light);
root.style.setProperty(`--${ID}-bg`, rgba(color, .18));
root.style.setProperty(`--${ID}-bg-active`, rgba(color, .28));
const setStops = (gradient, a, b) => {
const stops = document.getElementById(gradient)?.querySelectorAll('stop');
if (stops?.length === 2) {
stops[0].setAttribute('stop-color', a);
stops[1].setAttribute('stop-color', b);
}
};
setStops(`${ID}-desktop`, dark, light);
setStops(`${ID}-mobile`, light, dark);
setStops(`${ID}-mobile-hover`, hoverLight, hoverDark);
};
const rwValues = color => ({
row: rgba(color, .20),
soft: rgba(color, .18),
mid: rgba(color, .38),
glow: rgba(mix(color, [255,255,255], .35), .62)
});
const applyRwColor = (tier, color) => {
const root = document.documentElement, values = rwValues(color);
root.style.setProperty(`--${ID}-rw-${tier}-color`, color);
root.style.setProperty(`--${ID}-rw-${tier}-row`, values.row);
root.style.setProperty(`--${ID}-rw-${tier}-soft`, values.soft);
root.style.setProperty(`--${ID}-rw-${tier}-mid`, values.mid);
root.style.setProperty(`--${ID}-rw-${tier}-glow`, values.glow);
};
const matchesIn = (scope, selector) => {
const found = [];
if (scope?.nodeType === 1 && scope.matches(selector)) found.push(scope);
scope?.querySelectorAll?.(selector).forEach(el => found.push(el));
return found;
};
const applyRwDom = (scope = document) => {
if (!document.body) return;
for (const tier of Object.keys(RW)) {
const active = rwEnabled() && rwTierEnabled(tier);
const color = rwSavedColor(tier), values = rwValues(color);
for (const row of matchesIn(scope, `.display-bonus--${tier}`)) {
if (active) {
row.dataset.tncrRwRow = tier;
row.style.setProperty('--db-bgc', values.row, 'important');
row.style.setProperty('--db-outline', color, 'important');
row.style.setProperty('background-color', values.row, 'important');
row.style.setProperty('outline-color', color, 'important');
} else if (row.dataset.tncrRwRow === tier) {
delete row.dataset.tncrRwRow;
row.style.removeProperty('--db-bgc');
row.style.removeProperty('--db-outline');
row.style.removeProperty('background-color');
row.style.removeProperty('outline-color');
}
}
// Item components use either a single glow class or separate background and border classes.
const previewSelector = `.glow-${tier},.glow-${tier}-background`;
for (const preview of matchesIn(scope, previewSelector)) {
if (active) {
preview.dataset.tncrRwPreview = tier;
preview.style.setProperty('background-color', values.soft, 'important');
preview.style.setProperty('background-image', `radial-gradient(circle at 50% 45%,${values.glow} 0%,${values.soft} 48%,transparent 78%)`, 'important');
preview.style.setProperty('box-shadow', `inset 0 0 30px ${values.mid},0 0 10px ${values.soft}`, 'important');
} else if (preview.dataset.tncrRwPreview === tier) {
delete preview.dataset.tncrRwPreview;
preview.style.removeProperty('background-color');
preview.style.removeProperty('background-image');
preview.style.removeProperty('box-shadow');
}
}
for (const border of matchesIn(scope, `.glow-${tier}-border`)) {
if (active) {
border.dataset.tncrRwBorder = tier;
border.style.setProperty('border-color', color, 'important');
border.style.setProperty('outline-color', color, 'important');
border.style.setProperty('box-shadow', `0 0 10px ${values.mid},inset 0 0 8px ${values.soft}`, 'important');
} else if (border.dataset.tncrRwBorder === tier) {
delete border.dataset.tncrRwBorder;
border.style.removeProperty('border-color');
border.style.removeProperty('outline-color');
border.style.removeProperty('box-shadow');
}
}
}
};
const syncDesktopLabelColor = () => {
const label = document.querySelector('#sidebarroot [class*="area-desktop___"]:not([class*="attention___"]) [class*="linkName___"]');
if (label) document.documentElement.style.setProperty(`--${ID}-desktop-label-color`, getComputedStyle(label).color);
};
const sync = () => {
const root = document.documentElement;
syncDesktopLabelColor();
root.classList.toggle(ID, enabled());
root.classList.toggle(`${ID}-custom`, enabled() && custom());
root.classList.toggle(`${ID}-no-dot`, enabled() && !dotEnabled());
root.classList.toggle(`${ID}-account-links`, enabled() && accountLinksEnabled());
root.classList.toggle(`${ID}-rw`, rwEnabled());
applyColor(savedColor());
const enabledInput = document.getElementById(`${ID}-enabled`);
const customInput = document.getElementById(`${ID}-custom`);
const picker = document.getElementById(`${ID}-picker`);
const hex = document.getElementById(`${ID}-hex`);
const colors = document.getElementById(`${ID}-colors`);
const greenInput = document.getElementById(`${ID}-green`);
const dotInput = document.getElementById(`${ID}-dot`);
const accountLinksInput = document.getElementById(`${ID}-account-links`);
if (enabledInput) enabledInput.checked = enabled();
if (customInput) customInput.checked = custom();
if (greenInput) greenInput.checked = !custom();
if (dotInput) dotInput.checked = dotEnabled();
if (accountLinksInput) accountLinksInput.checked = accountLinksEnabled();
if (picker) picker.value = savedColor();
if (hex && document.activeElement !== hex) hex.value = savedColor();
const modeDisabled = !enabled();
[greenInput, customInput, dotInput, accountLinksInput].forEach(input => { if (input) input.disabled = modeDisabled; });
if (picker) picker.disabled = modeDisabled || !custom();
if (hex) hex.disabled = modeDisabled || !custom();
const modes = document.getElementById(`${ID}-modes`);
if (modes) modes.setAttribute('aria-disabled', String(modeDisabled));
if (colors) colors.setAttribute('aria-disabled', String(modeDisabled || !custom()));
const rwMaster = document.getElementById(`${ID}-rw-enabled`);
const rwOptions = document.getElementById(`${ID}-rw-options`);
if (rwMaster) rwMaster.checked = rwEnabled();
if (rwOptions) rwOptions.hidden = !rwEnabled();
for (const tier of Object.keys(RW)) {
const tierOn = rwTierEnabled(tier);
const color = rwSavedColor(tier);
applyRwColor(tier, color);
root.classList.toggle(`${ID}-rw-${tier}`, rwEnabled() && tierOn);
const toggle = document.getElementById(`${ID}-rw-${tier}-enabled`);
const row = document.getElementById(`${ID}-rw-${tier}-color`);
const tierPicker = document.getElementById(`${ID}-rw-${tier}-picker`);
const tierHex = document.getElementById(`${ID}-rw-${tier}-hex`);
if (toggle) toggle.checked = tierOn;
if (row) row.hidden = !tierOn;
if (tierPicker) tierPicker.value = color;
if (tierHex && document.activeElement !== tierHex) tierHex.value = color;
}
applyRwDom();
};
sync();
const saveToggle = (key, value) => {
localStorage.setItem(key, value ? '1' : '0');
sync();
};
const saveColor = value => {
const color = normalize(value), hex = document.getElementById(`${ID}-hex`);
hex?.classList.toggle(`${ID}-invalid`, !color);
if (!color) return;
localStorage.setItem(KEYS.color, color);
applyColor(color);
const picker = document.getElementById(`${ID}-picker`);
if (picker) picker.value = color;
if (hex && document.activeElement !== hex) hex.value = color;
};
const saveRwColor = (tier, value) => {
const color = normalize(value);
const hex = document.getElementById(`${ID}-rw-${tier}-hex`);
hex?.classList.toggle(`${ID}-invalid`, !color);
if (!color) return;
localStorage.setItem(`${ID}-rw-${tier}-color`, color);
applyRwColor(tier, color);
applyRwDom();
const picker = document.getElementById(`${ID}-rw-${tier}-picker`);
if (picker) picker.value = color;
if (hex && document.activeElement !== hex) hex.value = color;
};
const resetNotifications = () => {
localStorage.setItem(KEYS.enabled, '1');
localStorage.setItem(KEYS.custom, '0');
localStorage.setItem(KEYS.color, '#829E2E');
localStorage.setItem(KEYS.dot, '1');
localStorage.setItem(KEYS.accountLinks, '0');
localStorage.removeItem(KEYS.eventsLegacy);
sync();
};
const resetRw = () => {
localStorage.setItem(KEYS.rwEnabled, '0');
for (const tier of Object.keys(RW)) {
localStorage.setItem(`${ID}-rw-${tier}-enabled`, '0');
localStorage.setItem(`${ID}-rw-${tier}-color`, RW[tier].default);
}
sync();
};
const rwTierMarkup = tier => {
const {label} = RW[tier];
return `<fieldset class="${ID}-rw-tier"><legend>${label} RW class</legend>
<label><input id="${ID}-rw-${tier}-enabled" type="checkbox"> Customize ${label.toLowerCase()} class</label>
<div id="${ID}-rw-${tier}-color" class="${ID}-rw-color" hidden>
<input id="${ID}-rw-${tier}-picker" class="${ID}-rw-picker" type="color" aria-label="Choose ${label.toLowerCase()} RW color">
<input id="${ID}-rw-${tier}-hex" class="${ID}-rw-hex" type="text" maxlength="7" spellcheck="false" aria-label="${label} RW color hex code">
</div></fieldset>`;
};
const ensurePanel = () => {
let panel = document.getElementById(`${ID}-panel`);
if (panel) return panel;
panel = document.createElement('section');
panel.id = `${ID}-panel`;
panel.hidden = true;
panel.setAttribute('role', 'dialog');
panel.setAttribute('aria-label', 'Torn color settings');
panel.innerHTML = `<header><span>Torn Color Customizer</span><button type="button" id="${ID}-close" aria-label="Close">X</button></header>
<label><input id="${ID}-enabled" type="checkbox"> Enable notification color changes</label>
<fieldset id="${ID}-modes"><legend>Color mode</legend>
<label><input id="${ID}-green" name="${ID}-mode" type="radio" value="green"> Original Torn green</label>
<label><input id="${ID}-custom" name="${ID}-mode" type="radio" value="custom"> Custom color</label>
</fieldset>
<div id="${ID}-colors"><input id="${ID}-picker" type="color" aria-label="Choose notification color"><input id="${ID}-hex" type="text" maxlength="7" spellcheck="false" aria-label="Notification color hex code"></div>
<label><input id="${ID}-dot" type="checkbox"> Show notification dot</label>
<label><input id="${ID}-account-links" type="checkbox"> Include Messages, Events, and Awards</label>
<p>Choose Torn's original green or a custom color.</p>
<div class="${ID}-actions"><button type="button" id="${ID}-reset-notifications" class="${ID}-reset">Reset notification colors</button></div>
<hr id="${ID}-panel-divider">
<div id="${ID}-rw-title">RW weapon / armor colors</div>
<label><input id="${ID}-rw-enabled" type="checkbox"> Enable RW color changer</label>
<div id="${ID}-rw-options" hidden>
${Object.keys(RW).map(rwTierMarkup).join('')}
<p>Each enabled class changes auction rows plus matching small item images and expanded large previews across Torn.</p>
<div class="${ID}-actions"><button type="button" id="${ID}-reset-rw" class="${ID}-reset">Reset RW colors</button></div>
</div>
<footer>Version ${VERSION}<br>Developed by Exiled [3527247]</footer>`;
document.body.append(panel);
panel.querySelector(`#${ID}-enabled`).onchange = e => saveToggle(KEYS.enabled, e.target.checked);
panel.querySelector(`#${ID}-green`).onchange = e => { if (e.target.checked) saveToggle(KEYS.custom, false); };
panel.querySelector(`#${ID}-custom`).onchange = e => { if (e.target.checked) saveToggle(KEYS.custom, true); };
panel.querySelector(`#${ID}-dot`).onchange = e => saveToggle(KEYS.dot, e.target.checked);
panel.querySelector(`#${ID}-account-links`).onchange = e => saveToggle(KEYS.accountLinks, e.target.checked);
panel.querySelector(`#${ID}-reset-notifications`).onclick = resetNotifications;
panel.querySelector(`#${ID}-reset-rw`).onclick = resetRw;
panel.querySelector(`#${ID}-picker`).oninput = e => saveColor(e.target.value);
panel.querySelector(`#${ID}-hex`).oninput = e => saveColor(e.target.value);
panel.querySelector(`#${ID}-hex`).onblur = e => {
e.target.value = savedColor();
e.target.classList.remove(`${ID}-invalid`);
};
panel.querySelector(`#${ID}-rw-enabled`).onchange = e => saveToggle(KEYS.rwEnabled, e.target.checked);
for (const tier of Object.keys(RW)) {
panel.querySelector(`#${ID}-rw-${tier}-enabled`).onchange = e => saveToggle(`${ID}-rw-${tier}-enabled`, e.target.checked);
panel.querySelector(`#${ID}-rw-${tier}-picker`).oninput = e => saveRwColor(tier, e.target.value);
panel.querySelector(`#${ID}-rw-${tier}-hex`).oninput = e => saveRwColor(tier, e.target.value);
panel.querySelector(`#${ID}-rw-${tier}-hex`).onblur = e => {
e.target.value = rwSavedColor(tier);
e.target.classList.remove(`${ID}-invalid`);
};
}
panel.querySelector(`#${ID}-close`).onclick = () => panel.hidden = true;
sync();
return panel;
};
const addUI = () => {
const settings = document.getElementById('settings_panel');
if (!settings || document.getElementById(`${ID}-entry`)) return;
const content = [...settings.children].find(el => el.tagName === 'DIV');
if (!content) return;
const sections = [...content.children].filter(el => el.nodeType === 1);
const version = sections.find(el => /version/i.test(el.textContent) && /copyright/i.test(el.textContent));
if (!version) return;
const nativeButton = content.querySelector('button[class]');
const nativeTitle = nativeButton?.querySelector('span[class]');
const divider = document.createElement('div');
divider.id = `${ID}-divider`;
divider.setAttribute('aria-hidden', 'true');
const entry = document.createElement('div');
entry.id = `${ID}-entry`;
entry.className = version.className;
const button = document.createElement('button');
button.type = 'button';
button.id = `${ID}-open`;
button.setAttribute('aria-label', 'Open Torn Color Customizer settings');
if (nativeButton) button.className = nativeButton.className;
else button.style.cssText = 'background:none;border:1px solid #777;border-radius:4px;color:inherit;cursor:pointer;padding:8px 10px';
const label = document.createElement('span');
if (nativeTitle) label.className = nativeTitle.className;
label.textContent = 'Torn Color Customizer';
button.append(label);
button.onclick = () => ensurePanel().hidden = false;
entry.append(button);
version.insertAdjacentElement('afterend', divider);
divider.insertAdjacentElement('afterend', entry);
};
const start = () => {
ensureGradients();
ensurePanel();
addUI();
syncDesktopLabelColor();
applyRwDom();
new MutationObserver(records => {
addUI();
syncDesktopLabelColor();
for (const record of records) {
if (record.type === 'attributes') applyRwDom(record.target);
else record.addedNodes.forEach(node => applyRwDom(node));
}
}).observe(document.body, {childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: ['class', 'aria-label']});
};
document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', start, {once: true}) : start();
})();