Torn Travel Essentials

Draggable notes and a smart calculator. Compatible in PDA.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         Torn Travel Essentials
// @namespace    torn.travel.notes.manual
// @version      1.6.0
// @description  Draggable notes and a smart calculator. Compatible in PDA.
// @author       Ms_Mwywnn
// @match        https://www.torn.com/page.php*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    if (!window.location.search.includes("sid=travel")) return;

    const NOTE_KEY = "torn_travel_manual_notes";
    const POS_KEY = "torn_travel_note_position";
    const STATE_KEY = "torn_travel_note_open";

    function waitForTravelPage() {
        const check = setInterval(() => {
            if (document.body.innerText.includes("Traveling")) {
                clearInterval(check);
                init();
            }
        }, 500);
    }

    function getDestination() {
        const match = document.body.innerText.match(/Traveling to\s+([A-Za-z\s]+)/i);
        return match ? match[1].trim() : "Any";
    }

    function init() {
        const destination = getDestination();
        const allNotes = JSON.parse(localStorage.getItem(NOTE_KEY) || "{}");
        const savedPos = JSON.parse(localStorage.getItem(POS_KEY) || "{}");
        const isOpen = localStorage.getItem(STATE_KEY) !== "false";

        /* === TOGGLE TAB === */
        const tab = document.createElement("div");
        tab.textContent = "🌐";
        Object.assign(tab.style, {
            position: "fixed",
            right: "0",
            top: "50%",
            transform: "translateY(-50%)",
            background: "#0b0b0b",
            color: "#fff",
            padding: "10px",
            borderRadius: "8px 0 0 8px",
            cursor: "pointer",
            zIndex: "9999",
            border: "1px solid #1e90ff"
        });

        /* === PANEL === */
        const panel = document.createElement("div");
        Object.assign(panel.style, {
            position: "fixed",
            left: savedPos.left || "calc(100% - 280px)",
            top: savedPos.top || "120px",
            width: "260px",
            background: "#000",
            color: "#fff",
            border: "1px solid #1e90ff",
            borderRadius: "10px",
            padding: "10px",
            zIndex: "9999",
            display: isOpen ? "block" : "none",
            fontFamily: "Arial, sans-serif",
            touchAction: "none",
            boxSizing: "border-box"
        });

        document.body.appendChild(tab);
        document.body.appendChild(panel);

        const mainBtn = `
            width:100%;
            padding:12px;
            margin:6px 0;
            font-size:14px;
            font-weight:bold;
            background:#1e90ff;
            color:#000;
            border:none;
            border-radius:8px;
            cursor:pointer;
            box-sizing:border-box;
        `;

        const backBtn = `
            width:100%;
            padding:10px;
            margin-bottom:8px;
            font-size:13px;
            background:#333;
            color:#fff;
            border:none;
            border-radius:8px;
            cursor:pointer;
            box-sizing:border-box;
        `;

        /* === MENU === */
        function renderMenu() {
            panel.innerHTML = `
                <div id="dragHandle" style="font-weight:bold; margin-bottom:6px; cursor:grab;">
                   ✈️️ Travel Essentials
                </div>
                <div style="font-size:12px; margin-bottom:10px;">
                    Destination: <b>${destination}</b>
                </div>
                <button id="openNotes" style="${mainBtn}">📝 NOTES</button>
                <button id="openCalc" style="${mainBtn}">⌨️ CALCULATOR</button>
            `;

            panel.querySelector("#openNotes").onclick = renderNotes;
            panel.querySelector("#openCalc").onclick = renderCalculator;
        }

        /* === NOTES === */
        function renderNotes() {
            panel.innerHTML = `
                <button id="backMenu" style="${backBtn}">← BACK</button>
                <textarea id="travelNoteBox"
                    placeholder="Items to buy, quantities, next destination..."
                    style="
                        width:100%;
                        height:140px;
                        background:#0b0b0b;
                        color:#fff;
                        border:1px solid #444;
                        border-radius:8px;
                        padding:8px;
                        font-size:13px;
                        resize:none;
                        box-sizing:border-box;
                    "
                ></textarea>
                <button id="saveNote" style="${mainBtn}">SAVE</button>
                <button id="clearNote" style="${mainBtn}">CLEAR</button>
            `;

            const textarea = panel.querySelector("#travelNoteBox");
            textarea.value = allNotes[destination] || "";

            panel.querySelector("#backMenu").onclick = renderMenu;
            panel.querySelector("#saveNote").onclick = () => {
                allNotes[destination] = textarea.value;
                localStorage.setItem(NOTE_KEY, JSON.stringify(allNotes));
            };
            panel.querySelector("#clearNote").onclick = () => {
                textarea.value = "";
                delete allNotes[destination];
                localStorage.setItem(NOTE_KEY, JSON.stringify(allNotes));
            };
        }

        /* === CALCULATOR  === */
        function renderCalculator() {
            panel.innerHTML = `
                <button id="backMenu" style="${backBtn}">← BACK</button>

                <input id="calcInput"
                    placeholder="19*990"
                    style="
                        width:100%;
                        padding:10px;
                        font-size:14px;
                        background:#0b0b0b;
                        color:#fff;
                        border:1px solid #444;
                        border-radius:8px;
                        margin-bottom:10px;
                        box-sizing:border-box;
                    "
                />

                <div id="calcResult"
                    style="
                        width:100%;
                        min-height:48px;
                        display:flex;
                        align-items:center;
                        justify-content:center;
                        font-size:18px;
                        font-weight:bold;
                        background:#111;
                        border-radius:8px;
                        color:#1e90ff;
                        box-sizing:border-box;
                    "
                >
                    Result
                </div>
            `;

            const input = panel.querySelector("#calcInput");
            const result = panel.querySelector("#calcResult");

            input.addEventListener("input", () => {
                const raw = input.value.trim();
                if (!raw) {
                    result.textContent = "Result";
                    return;
                }

                const expr = raw.replace(/×/g, '*').replace(/÷/g, '/');
                const match = expr.match(/^(-?\d+(?:\.\d+)?)\s*([+\-*/])\s*(-?\d+(?:\.\d+)?)$/);

                if (!match) {
                    result.textContent = "Try: 1+1";
                    return;
                }

                const a = parseFloat(match[1]);
                const b = parseFloat(match[3]);
                let value;

                switch (match[2]) {
                    case '+': value = a + b; break;
                    case '-': value = a - b; break;
                    case '*': value = a * b; break;
                    case '/': value = b === 0 ? NaN : a / b; break;
                }

                result.textContent = Number.isFinite(value) ? value : "Invalid";
            });

            panel.querySelector("#backMenu").onclick = renderMenu;
        }

        renderMenu();

        /* === TOGGLE === */
        tab.onclick = () => {
            const open = panel.style.display === "none";
            panel.style.display = open ? "block" : "none";
            localStorage.setItem(STATE_KEY, open);
        };

        /* === DRAG LOGIC (RESTORED, STABLE) === */
        let dragging = false, offsetX = 0, offsetY = 0, raf = null;

        function clamp(val, min, max) {
            return Math.max(min, Math.min(max, val));
        }

        function startDrag(x, y) {
            dragging = true;
            document.body.style.overflow = "hidden";
            offsetX = x - panel.offsetLeft;
            offsetY = y - panel.offsetTop;
        }

        function moveDrag(x, y) {
            if (!dragging) return;
            if (raf) cancelAnimationFrame(raf);

            raf = requestAnimationFrame(() => {
                const maxX = window.innerWidth - panel.offsetWidth;
                const maxY = window.innerHeight - panel.offsetHeight;
                panel.style.left = `${clamp(x - offsetX, 0, maxX)}px`;
                panel.style.top = `${clamp(y - offsetY, 0, maxY)}px`;
            });
        }

        function endDrag() {
            if (!dragging) return;
            dragging = false;
            document.body.style.overflow = "";
            localStorage.setItem(POS_KEY, JSON.stringify({
                left: panel.style.left,
                top: panel.style.top
            }));
        }

        panel.addEventListener("mousedown", e => {
            if (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA" || e.target.tagName === "BUTTON") return;
            startDrag(e.clientX, e.clientY);
        });

        document.addEventListener("mousemove", e => moveDrag(e.clientX, e.clientY));
        document.addEventListener("mouseup", endDrag);

        panel.addEventListener("touchstart", e => {
            if (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA" || e.target.tagName === "BUTTON") return;
            const t = e.touches[0];
            startDrag(t.clientX, t.clientY);
        }, { passive: true });

        document.addEventListener("touchmove", e => {
            if (!dragging) return;
            const t = e.touches[0];
            moveDrag(t.clientX, t.clientY);
        }, { passive: true });

        document.addEventListener("touchend", endDrag);
    }

    waitForTravelPage();
})();