Replaces the Train buttons in Torn Gym with 16 clickable, customizable train icons. Every train plays the same custom whistle sound (plays once per click), mobile-safe settings modal, and a monochrome settings icon placed next to the Gym title (with safe fallback).
// ==UserScript==
// @name Train button 1.12
// @namespace http://tampermonkey.net/
// @version 1.13
// @description Replaces the Train buttons in Torn Gym with 16 clickable, customizable train icons. Every train plays the same custom whistle sound (plays once per click), mobile-safe settings modal, and a monochrome settings icon placed next to the Gym title (with safe fallback).
// @author Slenk
// @match https://www.torn.com/gym.php*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// --- Hangmotor: minden vonathoz UGYANAZ a saját (feltöltött) hang szól ---
// Nincs többé szintetizált sípolás - egy közös mp3 fájlt játszunk le minden kattintásra.
const TRAIN_SOUND_URL = 'https://media.vocaroo.com/mp3/16iqFB1aQ7iP';
let trainAudio = null;
function getTrainAudio() {
if (!trainAudio) {
trainAudio = new Audio(TRAIN_SOUND_URL);
trainAudio.preload = 'auto';
}
return trainAudio;
}
function playTrainSound() {
try {
const audio = getTrainAudio();
// Ha gyorsan egymás után kattintanak, mindig az elejéről szóljon újra.
audio.currentTime = 0;
const playPromise = audio.play();
if (playPromise && typeof playPromise.catch === 'function') {
playPromise.catch((e) => console.warn('Train sound play error:', e));
}
} catch (e) {
console.warn('Train sound error:', e);
}
}
// Az alapértelmezett színek a csatolt kép alapján
const trainDefaultColors = {
train1: '#F1C40F',
train2: '#F1C40F',
train3: '#F1C40F',
train4: '#F1C40F',
train5: '#F1C40F',
train6: '#3498DB',
train7: '#ECF0F1',
train8: '#7F8C8D',
train9: '#2980B9',
train10: '#27AE60',
train11: '#E74C3C',
train12: '#2ECC71',
train13: '#ECF0F1',
train14: '#27AE60',
train15: '#95A5A6',
train16: '#229954'
};
function loadColors() {
let saved = {};
try { saved = JSON.parse(GM_getValue('trainColors', '{}')); } catch (e) { saved = {}; }
return Object.assign({}, trainDefaultColors, saved);
}
let settings = {
trainType: GM_getValue('trainType', 'train1'),
trainColors: loadColors()
};
const trainIcons = {
train1: `<svg class="train-icon-btn" viewBox="0 0 100 60" width="60" height="36">
<path d="M10,45 L10,10 L40,10 L40,25 L75,25 L75,45 Z" fill="COLOR_PLACEHOLDER" stroke="#333" stroke-width="2"/>
<rect x="8" y="5" width="34" height="5" fill="#C0392B" stroke="#333" stroke-width="2"/>
<path d="M55,25 L55,8 L65,8 L65,25 Z" fill="#D35400" stroke="#333" stroke-width="2"/>
<rect x="15" y="15" width="15" height="15" fill="#FFF" stroke="#333" stroke-width="2"/>
<polygon points="75,45 90,45 85,35 75,35" fill="#C0392B" stroke="#333" stroke-width="2"/>
<circle cx="25" cy="50" r="8" fill="#E74C3C" stroke="#333" stroke-width="2"/>
<circle cx="45" cy="50" r="6" fill="#E74C3C" stroke="#333" stroke-width="2"/>
<circle cx="65" cy="50" r="6" fill="#E74C3C" stroke="#333" stroke-width="2"/>
<line x1="25" y1="50" x2="65" y2="50" stroke="#DDD" stroke-width="2"/>
</svg>`,
train2: `<svg class="train-icon-btn" viewBox="0 0 100 60" width="60" height="36">
<path d="M10,40 L10,15 C10,10 15,10 20,10 L80,10 C90,10 95,20 95,40 Z" fill="#FFF" stroke="#333" stroke-width="2"/>
<path d="M10,45 L95,45 C95,35 95,35 95,35 L10,35 Z" fill="COLOR_PLACEHOLDER" stroke="#333" stroke-width="2"/>
<rect x="25" y="15" width="10" height="20" fill="COLOR_PLACEHOLDER" stroke="#333" stroke-width="2"/>
<rect x="65" y="15" width="10" height="20" fill="COLOR_PLACEHOLDER" stroke="#333" stroke-width="2"/>
<rect x="12" y="18" width="10" height="10" fill="#333" rx="1"/>
<rect x="40" y="18" width="20" height="10" fill="#333" rx="1"/>
<path d="M80,18 L88,18 C90,18 90,25 88,28 L80,28 Z" fill="#333"/>
<circle cx="25" cy="50" r="6" fill="#555" stroke="#333" stroke-width="2"/>
<circle cx="40" cy="50" r="6" fill="#555" stroke="#333" stroke-width="2"/>
<circle cx="60" cy="50" r="6" fill="#555" stroke="#333" stroke-width="2"/>
<circle cx="75" cy="50" r="6" fill="#555" stroke="#333" stroke-width="2"/>
</svg>`,
train3: `<svg class="train-icon-btn" viewBox="0 0 100 60" width="60" height="36">
<path d="M10,40 L10,15 C10,12 12,10 15,10 L35,10 L35,25 L80,25 L80,40 Z" fill="COLOR_PLACEHOLDER" stroke="#333" stroke-width="2"/>
<rect x="8" y="6" width="30" height="5" fill="#E74C3C" stroke="#333" stroke-width="2" rx="2"/>
<rect x="45" y="25" width="4" height="15" fill="#F1C40F" stroke="#333" stroke-width="1"/>
<rect x="65" y="25" width="4" height="15" fill="#F1C40F" stroke="#333" stroke-width="1"/>
<path d="M68,25 L70,10 L82,10 L84,25 Z" fill="#2980B9" stroke="#333" stroke-width="2"/>
<rect x="15" y="15" width="7" height="10" fill="#FFF" stroke="#333" stroke-width="2"/>
<rect x="25" y="15" width="7" height="10" fill="#FFF" stroke="#333" stroke-width="2"/>
<polygon points="80,40 95,50 80,50" fill="#E74C3C" stroke="#333" stroke-width="2"/>
<circle cx="20" cy="45" r="9" fill="#CCC" stroke="#333" stroke-width="2"/>
<circle cx="45" cy="48" r="6" fill="#CCC" stroke="#333" stroke-width="2"/>
<circle cx="65" cy="48" r="6" fill="#CCC" stroke="#333" stroke-width="2"/>
<circle cx="85" cy="48" r="6" fill="#CCC" stroke="#333" stroke-width="2"/>
<line x1="20" y1="45" x2="85" y2="48" stroke="#777" stroke-width="2"/>
</svg>`,
train4: `<svg class="train-icon-btn" viewBox="0 0 100 60" width="60" height="36">
<path d="M5,45 L10,15 L35,15 L40,5 L80,5 L85,25 L95,25 L95,45 Z" fill="COLOR_PLACEHOLDER" stroke="#222" stroke-width="3"/>
<circle cx="15" cy="25" r="1.5" fill="#FFF"/><circle cx="25" cy="25" r="1.5" fill="#FFF"/><circle cx="35" cy="25" r="1.5" fill="#FFF"/>
<rect x="50" y="12" width="10" height="8" fill="#111" stroke="#555" stroke-width="2"/>
<rect x="65" y="12" width="10" height="8" fill="#111" stroke="#555" stroke-width="2"/>
<path d="M95,45 L100,55 L85,55 L85,45 Z" fill="#444" stroke="#222" stroke-width="2"/>
<circle cx="25" cy="50" r="7" fill="#555" stroke="#222" stroke-width="2"/>
<circle cx="50" cy="50" r="7" fill="#555" stroke="#222" stroke-width="2"/>
<circle cx="75" cy="50" r="7" fill="#555" stroke="#222" stroke-width="2"/>
<line x1="25" y1="50" x2="75" y2="50" stroke="#888" stroke-width="3"/>
</svg>`,
train5: `<svg class="train-icon-btn" viewBox="0 0 100 60" width="60" height="36">
<path d="M5,45 L5,25 C5,15 20,15 30,15 L70,15 C90,15 95,35 95,45 Z" fill="COLOR_PLACEHOLDER" stroke="#333" stroke-width="2"/>
<path d="M25,20 L65,20 C80,20 85,28 85,30 L20,30 C20,25 22,20 25,20 Z" fill="#222"/>
<line x1="5" y1="38" x2="92" y2="38" stroke="#FFF" stroke-width="1.5"/>
<path d="M15,45 L25,45 A 5 5 0 0 1 35 45 L55,45 A 5 5 0 0 1 65 45 L75,45 A 5 5 0 0 1 85 45 L95,45 L95,50 L5,50 Z" fill="#444" stroke="#333" stroke-width="2"/>
<circle cx="30" cy="48" r="4" fill="#111"/>
<circle cx="60" cy="48" r="4" fill="#111"/>
<circle cx="80" cy="48" r="4" fill="#111"/>
</svg>`,
train6: `<svg class="train-icon-btn" viewBox="0 0 100 60" width="60" height="36">
<path d="M10,42 L10,12 C10,10 12,8 15,8 L85,8 C92,8 95,12 95,42 Z" fill="COLOR_PLACEHOLDER" stroke="#222" stroke-width="2"/>
<rect x="18" y="15" width="14" height="20" fill="#AEE6FA" stroke="#222" stroke-width="1.5"/>
<rect x="42" y="15" width="14" height="20" fill="#AEE6FA" stroke="#222" stroke-width="1.5"/>
<rect x="66" y="15" width="14" height="20" fill="#AEE6FA" stroke="#222" stroke-width="1.5"/>
<rect x="10" y="38" width="85" height="4" fill="#E67E22"/>
<circle cx="28" cy="48" r="5" fill="#555" stroke="#222" stroke-width="1.5"/>
<circle cx="72" cy="48" r="5" fill="#555" stroke="#222" stroke-width="1.5"/>
<path d="M95,40 L100,40 L100,44 L95,44" fill="#555" stroke="#222"/>
</svg>`,
train7: `<svg class="train-icon-btn" viewBox="0 0 100 60" width="60" height="36">
<path d="M15,40 L15,15 C15,10 25,10 40,10 L65,10 C85,10 95,25 95,40 Z" fill="COLOR_PLACEHOLDER" stroke="#222" stroke-width="2"/>
<rect x="22" y="18" width="8" height="10" fill="#AEE6FA" stroke="#222" stroke-width="1.5"/>
<rect x="35" y="18" width="8" height="10" fill="#AEE6FA" stroke="#222" stroke-width="1.5"/>
<rect x="48" y="18" width="8" height="10" fill="#AEE6FA" stroke="#222" stroke-width="1.5"/>
<path d="M61,18 L73,18 C76,18 80,25 80,28 L61,28 Z" fill="#AEE6FA" stroke="#222" stroke-width="1.5"/>
<line x1="20" y1="32" x2="85" y2="32" stroke="#E74C3C" stroke-width="1.5"/>
<rect x="10" y="40" width="85" height="6" fill="#7F8C8D" stroke="#222" stroke-width="1.5"/>
</svg>`,
train8: `<svg class="train-icon-btn" viewBox="0 0 100 60" width="60" height="36">
<rect x="15" y="10" width="20" height="25" fill="COLOR_PLACEHOLDER" stroke="#222" stroke-width="2"/>
<rect x="35" y="18" width="40" height="17" fill="COLOR_PLACEHOLDER" stroke="#222" stroke-width="2"/>
<rect x="12" y="8" width="26" height="4" fill="#D35400" stroke="#222" stroke-width="1.5"/>
<path d="M58,18 L58,8 L66,8 L66,18 Z" fill="#2980B9" stroke="#222" stroke-width="1.5"/>
<rect x="20" y="16" width="10" height="10" fill="#AEE6FA" stroke="#222" stroke-width="1.5"/>
<polygon points="75,35 85,35 90,45 75,45" fill="#D35400" stroke="#222" stroke-width="1.5"/>
<circle cx="28" cy="45" r="7" fill="#555" stroke="#222" stroke-width="2"/>
<circle cx="48" cy="45" r="7" fill="#555" stroke="#222" stroke-width="2"/>
<circle cx="68" cy="45" r="7" fill="#555" stroke="#222" stroke-width="2"/>
<line x1="28" y1="45" x2="68" y2="45" stroke="#DDD" stroke-width="2"/>
</svg>`,
train9: `<svg class="train-icon-btn" viewBox="0 0 100 60" width="60" height="36">
<path d="M12,38 L12,12 C12,10 14,10 16,10 L84,10 C86,10 88,10 88,12 L88,38 Z" fill="COLOR_PLACEHOLDER" stroke="#222" stroke-width="2"/>
<rect x="14" y="8" width="26" height="4" fill="#D35400" stroke="#222" stroke-width="1.5"/>
<rect x="20" y="16" width="8" height="10" fill="#AEE6FA" stroke="#222" stroke-width="1.5"/>
<rect x="32" y="16" width="8" height="10" fill="#AEE6FA" stroke="#222" stroke-width="1.5"/>
<line x1="60" y1="16" x2="60" y2="30" stroke="#333" stroke-width="1.5"/>
<line x1="66" y1="16" x2="66" y2="30" stroke="#333" stroke-width="1.5"/>
<line x1="72" y1="16" x2="72" y2="30" stroke="#333" stroke-width="1.5"/>
<line x1="78" y1="16" x2="78" y2="30" stroke="#333" stroke-width="1.5"/>
<rect x="8" y="38" width="84" height="4" fill="#333"/>
<circle cx="25" cy="48" r="6" fill="#7F8C8D" stroke="#222" stroke-width="1.5"/>
<circle cx="45" cy="48" r="6" fill="#7F8C8D" stroke="#222" stroke-width="1.5"/>
<circle cx="65" cy="48" r="6" fill="#7F8C8D" stroke="#222" stroke-width="1.5"/>
</svg>`,
train10: `<svg class="train-icon-btn" viewBox="0 0 100 60" width="60" height="36">
<polyline points="25,12 35,4 45,12" fill="none" stroke="#555" stroke-width="2"/>
<polyline points="55,12 65,4 75,12" fill="none" stroke="#555" stroke-width="2"/>
<path d="M15,40 L15,18 C15,14 18,12 22,12 L78,12 C82,12 85,14 85,18 L85,40 Z" fill="COLOR_PLACEHOLDER" stroke="#222" stroke-width="2"/>
<rect x="25" y="18" width="10" height="8" fill="#AEE6FA" stroke="#222" stroke-width="1.5"/>
<rect x="45" y="18" width="10" height="8" fill="#AEE6FA" stroke="#222" stroke-width="1.5"/>
<rect x="65" y="18" width="10" height="8" fill="#AEE6FA" stroke="#222" stroke-width="1.5"/>
<rect x="15" y="34" width="70" height="6" fill="#2C3E50"/>
<polygon points="15,40 10,48 20,48" fill="#D35400" stroke="#222" stroke-width="1.5"/>
<polygon points="85,40 90,48 80,48" fill="#D35400" stroke="#222" stroke-width="1.5"/>
<circle cx="30" cy="48" r="5" fill="#7F8C8D" stroke="#222" stroke-width="1.5"/>
<circle cx="50" cy="48" r="5" fill="#7F8C8D" stroke="#222" stroke-width="1.5"/>
<circle cx="70" cy="48" r="5" fill="#7F8C8D" stroke="#222" stroke-width="1.5"/>
</svg>`,
train11: `<svg class="train-icon-btn" viewBox="0 0 100 60" width="60" height="36">
<path d="M5,42 L5,10 L75,10 C90,10 95,30 95,42 Z" fill="COLOR_PLACEHOLDER" stroke="#222" stroke-width="2"/>
<rect x="12" y="18" width="8" height="18" fill="#FFF" stroke="#222" stroke-width="1.5"/>
<rect x="25" y="14" width="10" height="8" fill="#333" stroke="#222" stroke-width="1"/>
<rect x="40" y="14" width="10" height="8" fill="#333" stroke="#222" stroke-width="1"/>
<rect x="55" y="14" width="10" height="8" fill="#333" stroke="#222" stroke-width="1"/>
<rect x="25" y="28" width="10" height="8" fill="#333" stroke="#222" stroke-width="1"/>
<rect x="40" y="28" width="10" height="8" fill="#333" stroke="#222" stroke-width="1"/>
<rect x="55" y="28" width="10" height="8" fill="#333" stroke="#222" stroke-width="1"/>
<path d="M75,20 L85,20 C88,20 90,28 90,32 L75,32 Z" fill="#333"/>
<circle cx="20" cy="48" r="5" fill="#7F8C8D" stroke="#222" stroke-width="1.5"/>
<circle cx="50" cy="48" r="5" fill="#7F8C8D" stroke="#222" stroke-width="1.5"/>
<circle cx="80" cy="48" r="5" fill="#7F8C8D" stroke="#222" stroke-width="1.5"/>
</svg>`,
train12: `<svg class="train-icon-btn" viewBox="0 0 100 60" width="60" height="36">
<rect x="55" y="18" width="35" height="18" fill="COLOR_PLACEHOLDER" stroke="#222" stroke-width="2"/>
<rect x="15" y="10" width="40" height="26" fill="COLOR_PLACEHOLDER" stroke="#222" stroke-width="2"/>
<path d="M80,18 L80,10 L88,10 L88,18 Z" fill="#2C3E50" stroke="#222" stroke-width="1.5"/>
<rect x="20" y="14" width="8" height="12" fill="#AEE6FA" stroke="#222" stroke-width="1.5"/>
<rect x="32" y="14" width="8" height="12" fill="#AEE6FA" stroke="#222" stroke-width="1.5"/>
<rect x="44" y="14" width="8" height="12" fill="#AEE6FA" stroke="#222" stroke-width="1.5"/>
<polygon points="90,36 100,45 85,45" fill="#E74C3C" stroke="#222" stroke-width="1.5"/>
<circle cx="28" cy="44" r="9" fill="#E74C3C" stroke="#222" stroke-width="2"/>
<circle cx="55" cy="46" r="6" fill="#E74C3C" stroke="#222" stroke-width="1.5"/>
<circle cx="72" cy="46" r="6" fill="#E74C3C" stroke="#222" stroke-width="1.5"/>
<circle cx="89" cy="46" r="6" fill="#E74C3C" stroke="#222" stroke-width="1.5"/>
<line x1="28" y1="44" x2="89" y2="46" stroke="#ECF0F1" stroke-width="2"/>
</svg>`,
train13: `<svg class="train-icon-btn" viewBox="0 0 100 60" width="60" height="36">
<path d="M5,40 L5,20 C5,15 15,15 30,15 L60,15 C85,15 95,30 95,40 Z" fill="COLOR_PLACEHOLDER" stroke="#222" stroke-width="2"/>
<line x1="5" y1="32" x2="90" y2="32" stroke="#2980B9" stroke-width="3"/>
<rect x="15" y="20" width="8" height="8" rx="2" fill="#333" stroke="#222" stroke-width="1"/>
<rect x="30" y="20" width="8" height="8" rx="2" fill="#333" stroke="#222" stroke-width="1"/>
<rect x="45" y="20" width="8" height="8" rx="2" fill="#333" stroke="#222" stroke-width="1"/>
<rect x="60" y="20" width="8" height="8" rx="2" fill="#333" stroke="#222" stroke-width="1"/>
<path d="M75,20 L85,20 C88,20 90,25 90,28 L75,28 Z" fill="#333"/>
<circle cx="20" cy="48" r="4" fill="#7F8C8D" stroke="#222" stroke-width="1.5"/>
<circle cx="45" cy="48" r="4" fill="#7F8C8D" stroke="#222" stroke-width="1.5"/>
<circle cx="70" cy="48" r="4" fill="#7F8C8D" stroke="#222" stroke-width="1.5"/>
</svg>`,
train14: `<svg class="train-icon-btn" viewBox="0 0 100 60" width="60" height="36">
<rect x="15" y="25" width="70" height="15" fill="COLOR_PLACEHOLDER" stroke="#222" stroke-width="2"/>
<rect x="35" y="10" width="30" height="15" fill="COLOR_PLACEHOLDER" stroke="#222" stroke-width="2"/>
<rect x="40" y="14" width="8" height="8" fill="#AEE6FA" stroke="#222" stroke-width="1.5"/>
<rect x="52" y="14" width="8" height="8" fill="#AEE6FA" stroke="#222" stroke-width="1.5"/>
<path d="M42,10 L45,5 L55,5 L58,10 Z" fill="#2C3E50" stroke="#222"/>
<line x1="15" y1="20" x2="85" y2="20" stroke="#F1C40F" stroke-width="1.5"/>
<line x1="20" y1="20" x2="20" y2="25" stroke="#F1C40F" stroke-width="1.5"/>
<line x1="30" y1="20" x2="30" y2="25" stroke="#F1C40F" stroke-width="1.5"/>
<line x1="70" y1="20" x2="70" y2="25" stroke="#F1C40F" stroke-width="1.5"/>
<line x1="80" y1="20" x2="80" y2="25" stroke="#F1C40F" stroke-width="1.5"/>
<rect x="10" y="40" width="80" height="4" fill="#E74C3C" stroke="#222" stroke-width="1.5"/>
<circle cx="25" cy="48" r="5" fill="#555" stroke="#222" stroke-width="1.5"/>
<circle cx="50" cy="48" r="5" fill="#555" stroke="#222" stroke-width="1.5"/>
<circle cx="75" cy="48" r="5" fill="#555" stroke="#222" stroke-width="1.5"/>
</svg>`,
train15: `<svg class="train-icon-btn" viewBox="0 0 100 60" width="60" height="36">
<path d="M10,30 L10,15 C10,10 15,10 25,10 L75,10 C85,10 95,20 95,30 Z" fill="COLOR_PLACEHOLDER" stroke="#222" stroke-width="2"/>
<path d="M10,42 L10,30 L95,30 L95,42 Z" fill="#E74C3C" stroke="#222" stroke-width="2"/>
<rect x="30" y="16" width="10" height="10" fill="#333" stroke="#222" stroke-width="1.5"/>
<rect x="45" y="16" width="10" height="10" fill="#333" stroke="#222" stroke-width="1.5"/>
<rect x="60" y="16" width="10" height="10" fill="#333" stroke="#222" stroke-width="1.5"/>
<path d="M78,16 L88,16 C90,16 92,20 92,26 L78,26 Z" fill="#333"/>
<rect x="15" y="16" width="10" height="20" fill="#FFF" stroke="#222" stroke-width="1.5"/>
<circle cx="25" cy="48" r="5" fill="#555" stroke="#222" stroke-width="1.5"/>
<circle cx="45" cy="48" r="5" fill="#555" stroke="#222" stroke-width="1.5"/>
<circle cx="75" cy="48" r="5" fill="#555" stroke="#222" stroke-width="1.5"/>
</svg>`,
train16: `<svg class="train-icon-btn" viewBox="0 0 100 60" width="60" height="36">
<polyline points="25,12 35,4 45,12" fill="none" stroke="#555" stroke-width="2"/>
<polyline points="55,12 65,4 75,12" fill="none" stroke="#555" stroke-width="2"/>
<path d="M15,40 L15,18 C15,12 18,12 25,12 L75,12 C82,12 85,15 90,25 L95,40 Z" fill="COLOR_PLACEHOLDER" stroke="#222" stroke-width="2"/>
<rect x="25" y="18" width="40" height="8" fill="#333" stroke="#222" stroke-width="1.5"/>
<line x1="15" y1="32" x2="92" y2="32" stroke="#E74C3C" stroke-width="3"/>
<path d="M88,40 L95,40 L95,45 C95,48 90,48 85,45 Z" fill="#E74C3C" stroke="#222" stroke-width="1.5"/>
<circle cx="30" cy="48" r="5" fill="#7F8C8D" stroke="#222" stroke-width="1.5"/>
<circle cx="50" cy="48" r="5" fill="#7F8C8D" stroke="#222" stroke-width="1.5"/>
<circle cx="70" cy="48" r="5" fill="#7F8C8D" stroke="#222" stroke-width="1.5"/>
</svg>`
};
// Monokróm vonat + fogaskerék ikon a beállítás gombhoz
const settingsIconSvg = `<svg viewBox="0 0 64 64" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round" style="display:block;">
<rect x="5" y="24" width="30" height="18" rx="3"/>
<rect x="11" y="28" width="8" height="7"/>
<rect x="23" y="28" width="8" height="7"/>
<circle cx="13" cy="46" r="3.5"/>
<circle cx="27" cy="46" r="3.5"/>
<line x1="5" y1="24" x2="5" y2="18"/>
<line x1="5" y1="18" x2="13" y2="18"/>
<circle cx="49" cy="18" r="7"/>
<circle cx="49" cy="18" r="2.3" fill="currentColor" stroke="none"/>
<line x1="49" y1="7" x2="49" y2="11"/>
<line x1="49" y1="25" x2="49" y2="29"/>
<line x1="38" y1="18" x2="42" y2="18"/>
<line x1="56" y1="18" x2="60" y2="18"/>
<line x1="40.7" y1="9.7" x2="43.5" y2="12.5"/>
<line x1="54.5" y1="23.5" x2="57.3" y2="26.3"/>
<line x1="40.7" y1="26.3" x2="43.5" y2="23.5"/>
<line x1="54.5" y1="12.5" x2="57.3" y2="9.7"/>
</svg>`;
function addStyles() {
if (document.getElementById('train-custom-styles')) return;
const style = document.createElement('style');
style.id = 'train-custom-styles';
style.innerHTML = `
#train-settings-launcher {
display: inline-flex; align-items: center; justify-content: center;
width: 30px; height: 30px; box-sizing: border-box;
background-color: rgba(60,60,60,0.85); color: #ddd;
border: 1px solid rgba(255,255,255,0.25); border-radius: 6px;
cursor: pointer; padding: 0; line-height: 0; margin-left: 6px;
}
#train-settings-launcher:hover { background-color: rgba(90,90,90,0.9); color:#fff; }
/* Fallback pozíció, ha nem sikerül a Gym cím mellé illeszteni */
#train-settings-launcher.train-launcher-fallback {
position: fixed;
right: max(12px, env(safe-area-inset-right));
bottom: max(12px, env(safe-area-inset-bottom));
z-index: 99999;
box-shadow: 0 2px 6px rgba(0,0,0,0.5);
}
#train-modal-overlay {
display: none; position: fixed; top: 0; left: 0;
width: 100%; height: 100%; background: rgba(0,0,0,0.6); z-index: 100000;
}
#train-settings-modal {
position: fixed; top: 50%; left: 50%;
transform: translate(-50%, -50%);
background: #fff; color: #333; border-radius: 10px; padding: 16px;
width: min(380px, 92vw);
max-height: 85vh; overflow-y: auto; -webkit-overflow-scrolling: touch;
box-sizing: border-box;
box-shadow: 0 5px 15px rgba(0,0,0,0.7); font-family: Arial, sans-serif;
}
#train-settings-modal h3 { margin: 0 0 15px 0; text-align: center; border-bottom: 2px solid #ddd; padding-bottom: 10px; }
#train-preview-wrapper { display: flex; justify-content: center; align-items: center; background: #f4f4f4; border: 1px solid #ddd; border-radius: 6px; padding: 10px; margin-bottom: 15px; }
#train-preview svg { width: 120px; height: 72px; max-width: 100%; }
.train-radio-group {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(88px, 1fr));
gap: 8px; margin-bottom: 15px;
}
.train-radio-group label { display: flex; align-items: center; cursor: pointer; font-size: 12px; }
.train-radio-group input { margin-right: 5px; cursor: pointer; }
.train-color-group { margin-bottom: 20px; }
.train-color-group label { display: block; font-weight: bold; margin-bottom: 5px; }
.train-color-row { display: flex; gap: 8px; }
#train-color-picker { flex: 1; height: 40px; cursor: pointer; border: 1px solid #ccc; border-radius: 4px; min-width: 0; }
#train-reset-color-btn { flex: 0 0 auto; background-color: #7f8c8d; color: #fff; border: none; border-radius: 4px; padding: 0 12px; cursor: pointer; font-weight: bold; font-size: 12px; }
#train-reset-color-btn:hover { background-color: #95a5a6; }
.train-modal-buttons { display: flex; gap: 10px; }
.train-btn { flex: 1; padding: 10px; border: none; border-radius: 5px; cursor: pointer; font-weight: bold; color: #fff; }
#train-save-btn { background-color: #27ae60; }
#train-save-btn:hover { background-color: #2ecc71; }
#train-cancel-btn { background-color: #c0392b; }
#train-cancel-btn:hover { background-color: #e74c3c; }
.train-icon-btn { transition: transform 0.1s ease-in-out, filter 0.1s ease-in-out; cursor: pointer; display: block; margin: 0 auto; overflow: visible; }
.train-icon-btn:hover { transform: scale(1.15); filter: brightness(1.1); }
.train-icon-btn:active { transform: scale(0.95); }
.custom-train-wrapper { display: inline-flex; justify-content: center; align-items: center; width: 100%; height: 100%; }
.custom-train-wrapper.train-disabled .train-icon-btn:hover { transform: none; filter: grayscale(1) brightness(0.85); }
.custom-train-wrapper.train-disabled .train-icon-btn:active { transform: none; }
.custom-train-wrapper.train-disabled { cursor: not-allowed; }
@media (max-width: 420px) {
#train-settings-modal { padding: 12px; }
.train-radio-group { grid-template-columns: repeat(auto-fit, minmax(76px, 1fr)); font-size: 11px; }
}
`;
document.head.appendChild(style);
}
function updatePreview() {
const checked = document.querySelector('input[name="trainType"]:checked');
const type = checked ? checked.value : settings.trainType;
const color = document.getElementById('train-color-picker').value;
const preview = document.getElementById('train-preview');
if (preview) {
preview.innerHTML = (trainIcons[type] || trainIcons.train1).replace(/COLOR_PLACEHOLDER/g, color);
}
}
function buildRadioGroup() {
let html = '';
for (let i = 1; i <= 16; i++) {
const type = 'train' + i;
html += `<label><input type="radio" name="trainType" value="${type}" ${settings.trainType === type ? 'checked' : ''}> Train ${i}</label>`;
}
return html;
}
// Megpróbáljuk a Gym cím / City-tutorial gombok mellé illeszteni a launcher-t.
// A Torn oldal DOM-ja időnként változik, ezért több lehetséges helyet is megnézünk,
// és ha egyik sem található, marad a mobil-biztos, sarokba rögzített változat.
function tryAttachToTitleBar(launcher) {
const containerSelectors = [
'.tutorial-button-icon',
'[class*="titleButtons"]',
'[class*="topButtons"]',
'[class*="content-title"] [class*="right"]',
'[class*="titleWrapper"] [class*="buttons"]'
];
for (const sel of containerSelectors) {
const container = document.querySelector(sel);
if (container && container.parentElement) {
container.parentElement.insertBefore(launcher, container);
return true;
}
}
// Fallback: keresünk egy elemet, aminek pontosan "Gym" a szövege (a cím maga)
const candidates = document.querySelectorAll('div, span, h1, h2, h3, h4, h5');
for (const el of candidates) {
if (el.children.length === 0 && el.textContent.trim() === 'Gym') {
el.insertAdjacentElement('afterend', launcher);
return true;
}
}
return false;
}
function createUI() {
if (!document.body || document.getElementById('train-settings-launcher')) return;
addStyles();
const launcher = document.createElement('button');
launcher.id = 'train-settings-launcher';
launcher.type = 'button';
launcher.title = 'Train button settings';
launcher.setAttribute('aria-label', 'Train button settings');
launcher.innerHTML = settingsIconSvg;
const attached = tryAttachToTitleBar(launcher);
if (!attached) {
launcher.classList.add('train-launcher-fallback');
document.body.appendChild(launcher);
}
const overlay = document.createElement('div');
overlay.id = 'train-modal-overlay';
const modal = document.createElement('div');
modal.id = 'train-settings-modal';
modal.innerHTML = `
<h3>Train Settings</h3>
<div id="train-preview-wrapper"><div id="train-preview"></div></div>
<div class="train-radio-group">${buildRadioGroup()}</div>
<div class="train-color-group">
<label for="train-color-picker">Main Color:</label>
<div class="train-color-row">
<input type="color" id="train-color-picker" value="${settings.trainColors[settings.trainType] || trainDefaultColors[settings.trainType] || '#F1C40F'}">
<button id="train-reset-color-btn" type="button">Reset to Default</button>
</div>
</div>
<div class="train-modal-buttons">
<button id="train-save-btn" class="train-btn">Save</button>
<button id="train-cancel-btn" class="train-btn">Cancel</button>
</div>
`;
overlay.appendChild(modal);
document.body.appendChild(overlay);
function openModal() {
overlay.style.display = 'block';
if (!trainIcons[settings.trainType]) {
settings.trainType = 'train1';
GM_setValue('trainType', 'train1');
}
const radio = document.querySelector(`input[name="trainType"][value="${settings.trainType}"]`);
if (radio) radio.checked = true;
document.getElementById('train-color-picker').value = settings.trainColors[settings.trainType] || trainDefaultColors[settings.trainType] || '#F1C40F';
updatePreview();
}
function closeModal() {
overlay.style.display = 'none';
}
launcher.addEventListener('click', openModal);
document.getElementById('train-cancel-btn').addEventListener('click', closeModal);
overlay.addEventListener('click', (e) => { if (e.target === overlay) closeModal(); });
modal.querySelectorAll('input[name="trainType"]').forEach((input) => {
input.addEventListener('change', () => {
const type = input.value;
document.getElementById('train-color-picker').value = settings.trainColors[type] || trainDefaultColors[type] || '#F1C40F';
updatePreview();
});
});
document.getElementById('train-color-picker').addEventListener('input', updatePreview);
document.getElementById('train-reset-color-btn').addEventListener('click', () => {
const checked = document.querySelector('input[name="trainType"]:checked');
const type = checked ? checked.value : settings.trainType;
document.getElementById('train-color-picker').value = trainDefaultColors[type] || '#F1C40F';
updatePreview();
});
document.getElementById('train-save-btn').addEventListener('click', () => {
const selectedTrain = document.querySelector('input[name="trainType"]:checked').value;
const selectedColor = document.getElementById('train-color-picker').value;
settings.trainColors[selectedTrain] = selectedColor;
settings.trainType = selectedTrain;
GM_setValue('trainType', selectedTrain);
GM_setValue('trainColors', JSON.stringify(settings.trainColors));
closeModal();
document.querySelectorAll('.custom-train-wrapper').forEach((wrapper) => {
wrapper.innerHTML = getIconHtml();
});
});
if (typeof GM_registerMenuCommand !== 'undefined') {
GM_registerMenuCommand('Open Train Button Settings', openModal);
}
}
function getIconHtml() {
const safeTrainType = trainIcons[settings.trainType] ? settings.trainType : 'train1';
const color = settings.trainColors[safeTrainType] || trainDefaultColors[safeTrainType] || '#F1C40F';
return trainIcons[safeTrainType].replace(/COLOR_PLACEHOLDER/g, color);
}
function isDisabled(el) {
if (!el) return false;
if (el.disabled) return true;
if (el.getAttribute('aria-disabled') === 'true') return true;
if (el.classList && el.classList.contains('disabled')) return true;
if (typeof el.className === 'string' && el.className.toLowerCase().indexOf('disabled') !== -1) return true;
return false;
}
function updateDisabledStates() {
document.querySelectorAll('[data-train-replaced="true"]').forEach((el) => {
const wrapper = el.querySelector('.custom-train-wrapper');
if (!wrapper) return;
if (isDisabled(el)) {
wrapper.classList.add('train-disabled');
} else {
wrapper.classList.remove('train-disabled');
}
});
}
function replaceTrainButtons() {
if (!document.body) return;
createUI();
const elements = document.querySelectorAll('button, a, [class*="btn"]');
elements.forEach((el) => {
if (el.textContent.trim().toUpperCase() === 'TRAIN' && !el.dataset.trainReplaced) {
if (el.querySelectorAll('input, select, textarea').length > 0) return;
el.dataset.trainReplaced = 'true';
el.style.color = 'transparent';
el.style.fontSize = '0px';
el.style.backgroundColor = 'transparent';
el.style.backgroundImage = 'none';
el.style.border = 'none';
el.style.boxShadow = 'none';
el.style.padding = '0';
el.setAttribute('aria-label', 'Train');
const iconWrapper = document.createElement('span');
iconWrapper.className = 'custom-train-wrapper';
iconWrapper.title = 'Train';
iconWrapper.innerHTML = getIconHtml();
if (isDisabled(el)) {
iconWrapper.classList.add('train-disabled');
}
// Minden egyes kattintás pontosan egyszer szólaltatja meg a közös hangot,
// így ha valaki többször nyom rá, annyiszor is hallható lesz.
iconWrapper.addEventListener('click', () => {
if (isDisabled(el)) return;
playTrainSound();
});
el.appendChild(iconWrapper);
}
});
updateDisabledStates();
}
let scanScheduled = false;
function scheduleScan() {
if (scanScheduled) return;
scanScheduled = true;
setTimeout(() => {
scanScheduled = false;
replaceTrainButtons();
}, 150);
}
function startObserving() {
if (!document.body) {
setTimeout(startObserving, 100);
return;
}
const observer = new MutationObserver(() => scheduleScan());
observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'disabled', 'aria-disabled'] });
replaceTrainButtons();
}
startObserving();
setInterval(replaceTrainButtons, 2000);
})();