Torn: More Legible Player Names With Custom Color Outlines

More Legible Font and custom color outlines for player names on honor bars. No API key required. Set your player ID manually for full reliability.

  1. // ==UserScript==
  2. // @name Torn: More Legible Player Names With Custom Color Outlines
  3. // @namespace http://tampermonkey.net/
  4. // @version 2
  5. // @description More Legible Font and custom color outlines for player names on honor bars. No API key required. Set your player ID manually for full reliability.
  6. // @author Gingerbeardman
  7. // @match https://www.torn.com/*
  8. // @grant none
  9. // @license GNU GPLv3
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. 'use strict';
  14.  
  15. const MY_PLAYER_ID = 'USER ID #'; // ← Your player ID here
  16. const CONFIG_KEY = 'mlpn-config';
  17.  
  18. const COLOR_OPTIONS = [
  19. { name: 'Black (Default)', value: '#000000' },
  20. { name: 'Red', value: '#ff4d4d' },
  21. { name: 'Blue', value: '#310AF5' },
  22. { name: 'Green', value: '#3B9932' },
  23. { name: 'Orange', value: '#ff9c40' },
  24. { name: 'Purple', value: '#c080ff' },
  25. { name: 'Yellow', value: '#f5d142' },
  26. { name: 'Pink', value: '#ff69b4' },
  27. { name: 'Teal', value: '#00d9c0' },
  28. { name: 'White', value: '#ffffff' },
  29. { name: 'Custom…', value: 'custom' }
  30. ];
  31.  
  32. const loadFont = () => {
  33. const link = document.createElement('link');
  34. link.href = 'https://fonts.googleapis.com/css2?family=Manrope:wght@700&display=swap';
  35. link.rel = 'stylesheet';
  36. document.head.appendChild(link);
  37. };
  38.  
  39. const injectStyles = () => {
  40. const style = document.createElement('style');
  41. style.textContent = COLOR_OPTIONS.filter(c => c.value !== 'custom').map(c => {
  42. const hex = c.value.replace('#', '');
  43. return `.mlpn-color-${hex} .custom-honor-text {
  44. text-shadow:
  45. -1px -1px 0 ${c.value},
  46. 1px -1px 0 ${c.value},
  47. -1px 1px 0 ${c.value},
  48. 1px 1px 0 ${c.value} !important;
  49. }`;
  50. }).join('\n') + `
  51. .custom-honor-text {
  52. font-family: 'Manrope', sans-serif !important;
  53. font-weight: 700 !important;
  54. color: white !important;
  55. text-transform: uppercase !important;
  56. letter-spacing: 0.5px !important;
  57. pointer-events: none !important;
  58. position: absolute !important;
  59. top: 50%;
  60. left: 0;
  61. transform: translateY(-50%);
  62. width: 100% !important;
  63. display: flex !important;
  64. align-items: center;
  65. justify-content: center;
  66. text-align: center !important;
  67. line-height: 1 !important;
  68. margin: 0 !important;
  69. padding: 0 !important;
  70. z-index: 10 !important;
  71. }
  72.  
  73. .honor-text-svg {
  74. display: none !important;
  75. }
  76.  
  77. .mlpn-panel {
  78. position: absolute;
  79. top: 50px;
  80. left: 50%;
  81. transform: translateX(-50%);
  82. background: #222;
  83. color: white;
  84. border: 1px solid #444;
  85. padding: 12px;
  86. z-index: 99999;
  87. font-size: 14px;
  88. border-radius: 6px;
  89. width: max-content;
  90. }
  91.  
  92. .mlpn-panel label {
  93. display: block;
  94. margin-top: 10px;
  95. }
  96.  
  97. .mlpn-button {
  98. background: #339CFF;
  99. color: white;
  100. border: none;
  101. padding: 6px 10px;
  102. font-weight: bold;
  103. border-radius: 4px;
  104. cursor: pointer;
  105. margin-top: 10px;
  106. }
  107.  
  108. .mlpn-note {
  109. font-size: 12px;
  110. margin-top: 6px;
  111. color: #aaa;
  112. }
  113. `;
  114. document.head.appendChild(style);
  115. };
  116.  
  117. const getConfig = () => JSON.parse(localStorage.getItem(CONFIG_KEY) || '{}');
  118. const saveConfig = config => localStorage.setItem(CONFIG_KEY, JSON.stringify(config));
  119.  
  120. const createColorDropdown = (id, selectedValue) => {
  121. const select = document.createElement('select');
  122. select.id = id;
  123. COLOR_OPTIONS.forEach(c => {
  124. const opt = document.createElement('option');
  125. opt.value = c.value;
  126. opt.textContent = c.name;
  127. select.appendChild(opt);
  128. });
  129. select.value = COLOR_OPTIONS.some(c => c.value === selectedValue) ? selectedValue : 'custom';
  130. return select;
  131. };
  132.  
  133. const showSettingsPanel = (isOwn, playerId) => {
  134. const config = getConfig();
  135. const panel = document.createElement('div');
  136. panel.id = 'mlpn-panel';
  137. panel.className = 'mlpn-panel';
  138.  
  139. const closeBtn = document.createElement('button');
  140. closeBtn.className = 'mlpn-button';
  141. closeBtn.textContent = 'Done';
  142. closeBtn.onclick = () => panel.remove();
  143.  
  144. if (isOwn) {
  145. panel.innerHTML = `
  146. <label>Font Size:
  147. <input id="mlpn-font-size" type="number" min="8" max="24" value="${config.fontSize || 12}" />
  148. </label>
  149. <label>Color for Your Name:</label>
  150. `;
  151. const dropdown = createColorDropdown('mlpn-my-color', config.myColor || '#000000');
  152. const customInput = document.createElement('input');
  153. customInput.id = 'mlpn-my-custom';
  154. customInput.placeholder = '#hex';
  155. customInput.style.display = dropdown.value === 'custom' ? 'block' : 'none';
  156. customInput.value = config.myColor?.startsWith('#') ? config.myColor : '';
  157.  
  158. dropdown.onchange = () => {
  159. customInput.style.display = dropdown.value === 'custom' ? 'block' : 'none';
  160. config.myColor = dropdown.value === 'custom' ? customInput.value : dropdown.value;
  161. saveConfig(config);
  162. };
  163.  
  164. customInput.oninput = () => {
  165. config.myColor = customInput.value;
  166. saveConfig(config);
  167. };
  168.  
  169. const fontInput = panel.querySelector('#mlpn-font-size');
  170. fontInput.oninput = () => {
  171. config.fontSize = parseInt(fontInput.value);
  172. saveConfig(config);
  173. };
  174.  
  175. document.body.appendChild(panel);
  176. panel.appendChild(dropdown);
  177. panel.appendChild(customInput);
  178. } else {
  179. panel.innerHTML = `<label>Assign a color to this player's name:</label>`;
  180. const dropdown = createColorDropdown('mlpn-other-color', config.players?.[playerId] || '#000000');
  181. const customInput = document.createElement('input');
  182. customInput.id = 'mlpn-other-custom';
  183. customInput.placeholder = '#hex';
  184. customInput.style.display = dropdown.value === 'custom' ? 'block' : 'none';
  185. customInput.value = config.players?.[playerId]?.startsWith('#') ? config.players[playerId] : '';
  186.  
  187. dropdown.onchange = () => {
  188. if (!config.players) config.players = {};
  189. customInput.style.display = dropdown.value === 'custom' ? 'block' : 'none';
  190. config.players[playerId] = dropdown.value === 'custom' ? customInput.value : dropdown.value;
  191. saveConfig(config);
  192. };
  193.  
  194. customInput.oninput = () => {
  195. if (!config.players) config.players = {};
  196. config.players[playerId] = customInput.value;
  197. saveConfig(config);
  198. };
  199.  
  200. document.body.appendChild(panel);
  201. panel.appendChild(dropdown);
  202. panel.appendChild(customInput);
  203. }
  204.  
  205. const note = document.createElement('div');
  206. note.className = 'mlpn-note';
  207. note.textContent = 'Refresh the page to apply settings.';
  208. panel.appendChild(note);
  209. panel.appendChild(closeBtn);
  210. };
  211.  
  212. const getProfileIdFromUrl = () => {
  213. const match = window.location.href.match(/profiles\.php\?XID=(\d+)/);
  214. return match ? match[1] : null;
  215. };
  216.  
  217. const applyHonorStyles = () => {
  218. const config = getConfig();
  219. const fontSize = parseInt(config.fontSize) || 12;
  220. const profileId = getProfileIdFromUrl();
  221.  
  222. document.querySelectorAll('.honor-text-wrap').forEach(wrap => {
  223. if (wrap.querySelector('.custom-honor-text')) return;
  224.  
  225. const anchor = wrap.closest('a[href*="XID="]');
  226. let playerId = null;
  227.  
  228. if (anchor) {
  229. const match = anchor.href.match(/XID=(\d+)/);
  230. if (match) playerId = match[1];
  231. } else if (profileId) {
  232. playerId = profileId; // Fallback for profile pages
  233. }
  234.  
  235. const text = wrap.getAttribute('data-title') || wrap.getAttribute('aria-label') || wrap.innerText || '';
  236. const cleaned = text.trim().toUpperCase();
  237. if (!cleaned) return;
  238.  
  239. let color = '#000000';
  240. if (playerId === MY_PLAYER_ID && config.myColor) {
  241. color = config.myColor;
  242. } else if (config.players?.[playerId]) {
  243. color = config.players[playerId];
  244. }
  245.  
  246. const colorClass = `mlpn-color-${color.replace('#', '')}`;
  247. wrap.classList.add(colorClass);
  248.  
  249. if (fontSize > 12) wrap.style.height = `${fontSize + 6}px`;
  250.  
  251. const div = document.createElement('div');
  252. div.className = 'custom-honor-text';
  253. div.style.fontSize = `${fontSize}px`;
  254. div.textContent = cleaned;
  255. wrap.appendChild(div);
  256. });
  257. };
  258.  
  259. const injectSettingsButton = (isSelfProfile, profileId) => {
  260. if (document.getElementById('mlpn-settings-btn')) return;
  261.  
  262. const target = document.querySelector('.content-title');
  263. if (!target) return;
  264.  
  265. const btn = document.createElement('button');
  266. btn.id = 'mlpn-settings-btn';
  267. btn.className = 'mlpn-button';
  268. btn.textContent = 'Custom Player Names';
  269. btn.onclick = () => showSettingsPanel(isSelfProfile, profileId);
  270. target.appendChild(btn);
  271. };
  272.  
  273. const init = () => {
  274. const profileId = getProfileIdFromUrl();
  275. if (profileId) {
  276. injectSettingsButton(profileId === MY_PLAYER_ID, profileId);
  277. }
  278.  
  279. applyHonorStyles();
  280. new MutationObserver(applyHonorStyles).observe(document.body, { childList: true, subtree: true });
  281. };
  282.  
  283. loadFont();
  284. injectStyles();
  285. init();
  286. })();