Time Zone Display

add your friends local times to the clock in torn!

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         Time Zone Display
// @namespace    http://tampermonkey.net/
// @version      1.8.2
// @description  add your friends local times to the clock in torn!
// @author       Pint-Shot-Riot
// @match        https://www.torn.com/*
// @grant        none
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    const styleContent = `
        #ups-tz-dropdown {
            position: fixed; top: 55px; right: 10px;
            background: rgba(30, 30, 30, 0.95); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
            border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 14px; padding: 12px; z-index: 999999;
            box-shadow: 0 10px 30px rgba(0,0,0,0.6); min-width: 210px; display: none; color: #fff;
            animation: upsSlideIn 0.2s ease-out;
            pointer-events: auto !important;
        }

        @media screen and (max-width: 600px) {
            #ups-tz-dropdown { min-width: 160px; padding: 8px; top: 50px; right: 5px; }
            .ups-tz-item { padding: 6px 4px; }
            .ups-tz-label { font-size: 10px; }
            .ups-tz-time { font-size: 14px; }
            .ups-tz-manage-btn { padding: 8px; margin-top: 6px; font-size: 10px; }
        }

        /* Torn-Style Nameplate Clock */
        .ups-nameplate-time {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            margin-left: 10px;
            padding: 0 8px;
            height: 24px;
            background: linear-gradient(180deg, #444 0%, #222 100%);
            border: 1px solid #000;
            border-radius: 4px;
            color: #fff;
            font-family: 'Orbitron', 'Roboto', monospace;
            font-size: 13px;
            font-weight: 700;
            text-shadow: 0 1px 0 rgba(0,0,0,0.5);
            box-shadow: inset 0 1px 0 rgba(255,255,255,0.1);
            vertical-align: middle;
        }

        .ups-quick-add-wrap { display: inline-flex; align-items: center; gap: 5px; margin: 2px 5px; vertical-align: middle; }
        .ups-quick-add {
            display: inline-flex; align-items: center; justify-content: center;
            background: linear-gradient(180deg, #444 0%, #222 100%);
            color: #ccc; border-radius: 4px; padding: 4px 10px; height: 24px;
            font-size: 10px; font-weight: bold; cursor: pointer;
            border: 1px solid #000; text-shadow: 0 -1px 0 rgba(0,0,0,0.5);
            transition: color 0.2s; text-transform: uppercase; font-family: Arial, sans-serif;
        }
        .ups-quick-add:hover { color: #fff; background: linear-gradient(180deg, #555 0%, #333 100%); }

        @keyframes upsSlideIn { from { opacity: 0; transform: translateY(-10px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } }

        .ups-tz-header { display: flex; justify-content: flex-end; margin-bottom: -5px; }
        .ups-tz-close { cursor: pointer; color: #666; font-size: 20px; font-weight: bold; padding: 0 5px; user-select: none; }
        .ups-tz-close:hover { color: #fff; }
        .ups-tz-item { display: flex; justify-content: space-between; align-items: center; padding: 10px 6px; border-bottom: 1px solid rgba(255,255,255,0.05); }
        .ups-tz-label { color: #aaa; font-size: 11px; text-transform: uppercase; font-weight: 600; }
        .ups-tz-time { color: #fff; font-size: 16px; font-weight: 700; font-family: monospace; }
        .ups-tz-manage-btn { background: rgba(255,255,255,0.15); color: #fff; text-align: center; padding: 12px; margin-top: 10px; border-radius: 10px; cursor: pointer; font-size: 11px; font-weight: bold; }

        #tz-manager-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: #111; z-index: 1000001; padding: 15px; box-sizing: border-box; overflow-y: auto; }
        .ups-card { background: #1a1a1a; border-radius: 18px; padding: 20px; border: 1px solid #2a2a2a; margin-bottom: 20px; }
        .ups-input { background: #000; color: #fff; border: 1px solid #333; padding: 15px; width: 100%; margin-bottom: 15px; border-radius: 10px; box-sizing: border-box; font-size: 16px; }
        .ups-btn-save { width: 100%; padding: 16px; background: #6b8e23; color: #fff; border: none; border-radius: 10px; font-weight: 800; font-size: 14px; cursor: pointer; }

        .ups-table { width: 100%; border-collapse: separate; border-spacing: 0 10px; }
        .ups-table tr { background: #222; }
        .ups-table td { padding: 15px; color: #fff; border-radius: 12px; }

        .del-btn, .move-btn { cursor: pointer !important; border: 1px solid rgba(255,255,255,0.1); }
        .del-btn { color: #ff4444; background: rgba(255, 68, 68, 0.1); padding: 8px 14px; border-radius: 8px; font-size: 11px; margin-left: 5px; }
        .move-btn { color: #fff; background: rgba(255, 255, 255, 0.1); padding: 8px 12px; border-radius: 8px; font-size: 11px; margin-left: 4px; }

        .ups-data-box { background: #000; color: #0f0; font-family: monospace; font-size: 12px; width: 100%; height: 60px; border: 1px solid #333; border-radius: 8px; padding: 10px; box-sizing: border-box; resize: none; margin-bottom: 10px; -webkit-user-select: text; user-select: text; }
        .ups-data-btns { display: flex; gap: 10px; }
        .ups-btn-small { flex: 1; padding: 10px; background: #444; color: #fff; border: none; border-radius: 6px; cursor: pointer; font-size: 11px; font-weight: bold; }
    `;

    function getZones() {
        const raw = localStorage.getItem("upscript_tz_v2");
        return raw ? JSON.parse(raw) : [{label: "Torn", timezone: "UTC"}];
    }

    function saveZones(zones) {
        localStorage.setItem("upscript_tz_v2", JSON.stringify(zones));
        renderList();
        updateIOBox();
    }

    function updateIOBox() {
        const io = document.getElementById("ups-io-box");
        if (io) io.value = btoa(JSON.stringify(getZones()));
    }

    function isValidTimezone(tz) {
        try { if (!tz) return false; Intl.DateTimeFormat(undefined, {timeZone: tz}); return true; } 
        catch (ex) { return false; }
    }

    function findTimezoneSmartly(input) {
        if (!input) return null;
        const clean = input.trim().toLowerCase().replace(/\s+/g, '_');
        const offsetMatch = clean.match(/^(?:utc|gmt)?([+-]\d+)/);
        if (offsetMatch) {
            const hours = parseInt(offsetMatch[1]);
            const inverseSign = hours > 0 ? "-" : "+";
            return `Etc/GMT${inverseSign}${Math.abs(hours)}`;
        }
        const allZones = Intl.supportedValuesOf('timeZone');
        let match = allZones.find(z => z.toLowerCase() === clean || z.toLowerCase().endsWith('/' + clean));
        if (!match) { match = allZones.find(z => z.toLowerCase().includes(clean)); }
        return match || null;
    }

    function formatZoneID(str) {
        if (!str) return "";
        if (str.startsWith('Etc/GMT')) return str;
        if (!str.includes('/')) return str;
        return str.split('/').map(part =>
            part.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join('_')
        ).join('/');
    }

    function renderList() {
        const list = document.getElementById("tz-list");
        if (!list) return;
        list.innerHTML = "";
        getZones().forEach((z, i) => {
            const row = list.insertRow();
            row.innerHTML = `
                <td><span style="font-size:14px; font-weight:700;">${z.label}</span></td>
                <td style="text-align:right">
                    <button class="move-btn up-btn" data-index="${i}">▲</button>
                    <button class="move-btn down-btn" data-index="${i}">▼</button>
                    <button class="del-btn" data-index="${i}">REMOVE</button>
                </td>`;
        });
    }

    function openManager(prefillLabel = "", prefillZone = "") {
        if (document.getElementById("tz-manager-overlay")) return;
        const overlay = document.createElement("div");
        overlay.id = "tz-manager-overlay";
        overlay.innerHTML = `
            <div style="max-width: 450px; margin: auto;">
                <h2 style="color:#fff; text-align:center;">Time Zones</h2>
                <div class="ups-card">
                    <input id="new-label" class="ups-input" placeholder="Name" value="${prefillLabel}">
                    <input id="new-zone" class="ups-input" placeholder="City or Offset" value="${prefillZone}">
                    <button id="add-tz" class="ups-btn-save">ADD TIMEZONE</button>
                </div>
                <table class="ups-table" id="tz-list"></table>
                <div class="ups-card" style="margin-top:20px;">
                    <h3 style="color:#fff; font-size:12px; margin-bottom:10px; text-transform:uppercase;">Import / Export</h3>
                    <textarea id="ups-io-box" class="ups-data-box" readonly></textarea>
                    <div class="ups-data-btns">
                        <button id="ups-copy-btn" class="ups-btn-small">COPY CODE</button>
                        <button id="ups-import-btn" class="ups-btn-small">IMPORT CODE</button>
                    </div>
                </div>
                <button id="close-tz" style="width:100%; color:#666; background:none; border:none; padding:20px; cursor:pointer; font-weight:bold;">CLOSE</button>
            </div>
        `;
        document.body.appendChild(overlay);
        renderList();
        updateIOBox();

        overlay.addEventListener('click', (e) => {
            if (e.target.id === 'close-tz') overlay.remove();
            if (e.target.id === 'ups-copy-btn') {
                const box = document.getElementById("ups-io-box");
                box.select(); document.execCommand('copy');
                e.target.textContent = "COPIED!"; setTimeout(() => e.target.textContent = "COPY CODE", 2000);
            }
            if (e.target.id === 'ups-import-btn') {
                const code = prompt("Paste your export code here:");
                if (code) {
                    try { const decoded = JSON.parse(atob(code)); if (Array.isArray(decoded)) saveZones(decoded); } 
                    catch (err) { alert("Invalid code format."); }
                }
            }
            if (e.target.id === 'add-tz') {
                let l = document.getElementById("new-label").value.trim();
                let zRaw = document.getElementById("new-zone").value.trim();
                if (l && zRaw) {
                    let zFinal = formatZoneID(findTimezoneSmartly(zRaw) || zRaw);
                    if (isValidTimezone(zFinal)) {
                        const zones = getZones();
                        zones.push({label: l, timezone: zFinal});
                        saveZones(zones);
                        document.getElementById("new-label").value = "";
                        document.getElementById("new-zone").value = "";
                        injectProfileFeatures(); 
                    } else { alert("Invalid city or timezone."); }
                }
            }
            const btn = e.target.closest('button');
            if (btn && btn.dataset.index !== undefined) {
                const index = parseInt(btn.dataset.index);
                let zones = getZones();
                if (btn.classList.contains('del-btn')) zones.splice(index, 1);
                else if (btn.classList.contains('up-btn') && index > 0) [zones[index], zones[index - 1]] = [zones[index - 1], zones[index]];
                else if (btn.classList.contains('down-btn') && index < zones.length - 1) [zones[index], zones[index + 1]] = [zones[index + 1], zones[index]];
                saveZones(zones);
                injectProfileFeatures();
            }
        });
    }

    function injectProfileFeatures() {
        if (!window.location.href.includes('profiles.php')) return;

        const nameElem = document.querySelector('.profile-wrapper .name') || document.querySelector('h1');
        if (!nameElem) return;

        const playerName = nameElem.textContent.trim().split('[')[0].trim();
        const zones = getZones();
        const savedZone = zones.find(z => z.label.toLowerCase() === playerName.toLowerCase());

        // 1. NAMEPLATE DISPLAY
        if (savedZone && !document.querySelector('.ups-nameplate-time')) {
            const plateTime = document.createElement('span');
            plateTime.className = 'ups-nameplate-time';
            const updatePlate = () => {
                plateTime.innerText = new Date().toLocaleTimeString("en-GB", { timeZone: savedZone.timezone, hour12: false, hour: '2-digit', minute: '2-digit' });
            };
            updatePlate();
            setInterval(updatePlate, 15000);
            nameElem.appendChild(plateTime);
        }

        // 2. QUICK ADD BUTTON
        if (!document.querySelector('.ups-quick-add-wrap')) {
            const target = document.querySelector('.actions-container') || 
                           document.querySelector('.profile-wrapper .actions') || 
                           document.querySelector('.basic-info');

            if (target) {
                let foundCity = "";
                document.querySelectorAll('.profile-container .info-section ul li, .basic-info li').forEach(li => {
                    if (li.textContent.includes('Location:')) foundCity = li.textContent.replace('Location:', '').trim();
                });

                const wrap = document.createElement('div');
                wrap.className = 'ups-quick-add-wrap';
                const addBtn = document.createElement('button');
                addBtn.className = 'ups-quick-add';
                addBtn.innerText = savedZone ? 'EDIT TZ' : 'ADD TZ';
                addBtn.onclick = (e) => { e.preventDefault(); openManager(playerName, foundCity); };
                wrap.appendChild(addBtn);

                if (target.classList.contains('actions') || target.classList.contains('actions-container')) target.appendChild(wrap);
                else target.insertBefore(wrap, target.firstChild);
            }
        }
    }

    const style = document.createElement("style");
    style.textContent = styleContent;
    document.head.appendChild(style);

    const dropdown = document.createElement("div");
    dropdown.id = "ups-tz-dropdown";
    document.body.appendChild(dropdown);

    dropdown.addEventListener('click', (e) => {
        if (e.target.id === 'ups-tz-close-btn') dropdown.style.display = 'none';
        else if (e.target.id === 'ups-manage-trigger') { dropdown.style.display = 'none'; openManager(); }
    });

    document.addEventListener('mousedown', (e) => {
        const clock = e.target.closest('#server-time') || e.target.closest('[class*="clock"]');
        if (clock) {
            if (dropdown.style.display === "block") dropdown.style.display = "none";
            else { updateDropdownContent(); dropdown.style.display = "block"; }
        } else if (!e.target.closest('#ups-tz-dropdown')) dropdown.style.display = "none";
    });

    function updateDropdownContent() {
        let html = `<div class="ups-tz-header"><span class="ups-tz-close" id="ups-tz-close-btn">&times;</span></div>`;
        getZones().forEach(z => {
            try {
                const time = new Date().toLocaleTimeString("en-GB", { timeZone: z.timezone, hour12: false, hour: '2-digit', minute: '2-digit' });
                html += `<div class="ups-tz-item"><span class="ups-tz-label">${z.label}</span><span class="ups-tz-time">${time}</span></div>`;
            } catch (e) { html += `<div class="ups-tz-item"><span class="ups-tz-label">${z.label}</span><span style="color:#ff4444">ERR</span></div>`; }
        });
        html += `<div class="ups-tz-manage-btn" id="ups-manage-trigger">SETTINGS</div>`;
        dropdown.innerHTML = html;
    }

    const observer = new MutationObserver(injectProfileFeatures);
    observer.observe(document.body, { childList: true, subtree: true });
    injectProfileFeatures();

})();