Torn Smart FAK Button

Smart hospital FAK button. Shows hospital time and auto-uses best med item. Applies education + faction medical effectiveness bonuses via API key.

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Torn Smart FAK Button
// @namespace    torn.smartfak
// @version      4.41
// @author       BBSmalls [3908857]
// @description  Smart hospital FAK button. Shows hospital time and auto-uses best med item. Applies education + faction medical effectiveness bonuses via API key.
// @match        https://www.torn.com/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function () {
    'use strict';

    const POS_KEY   = 'smartFakPosition';
    const MODE_KEY  = 'smartFakMode';
    const BLOOD_KEY = 'smartFakBloodType';
    const VIS_KEY   = 'smartFakVisibility';
    const MORPH_KEY = 'smartFakMorphine';
    const API_KEY   = 'smartFakApiKey';
    const SETUP_KEY = 'smartFakSetupDone';

    const PERSONAL_URL = 'https://www.torn.com/item.php';
    const FACTION_URL  = 'https://www.torn.com/factions.php?step=your&type=1#/tab=armoury';

    let ITEMS = {
        'Small First Aid Kit': { id: 68, removes: 1200, cd: 600,  baseRemoves: 1200 },
        'First Aid Kit':       { id: 67, removes: 2400, cd: 900,  baseRemoves: 2400 },
        'Morphine':            { id: 66, removes: 4200, cd: 1200, baseRemoves: 4200 },
        'Blood Bag':           { id: null, removes: 7200, cd: 1800, baseRemoves: 7200 }
    };

    const BLOOD_TYPES   = ['Disabled', 'A+', 'A-', 'B+', 'B-', 'AB+', 'AB-', 'O+', 'O-'];
    const BLOOD_BAG_IDS = { 'A+':732,'A-':733,'B+':734,'B-':735,'AB+':736,'AB-':737,'O+':738,'O-':739 };

    let cachedTimer                 = 0;
    let isDragging                  = false;
    let totalMedicalBonus           = 0;
    let visibilityLockedUntilReload = false;
    let lastHospitalStatus          = null;

    const getMode        = () => localStorage.getItem(MODE_KEY)  || 'Personal Items';
    const saveMode       = mode => localStorage.setItem(MODE_KEY, mode);
    const getBloodType   = () => localStorage.getItem(BLOOD_KEY) || 'Disabled';
    const saveBloodType  = type => localStorage.setItem(BLOOD_KEY, type);
    const getVisibility  = () => localStorage.getItem(VIS_KEY)   || 'All Pages';
    const saveVisibility = vis  => localStorage.setItem(VIS_KEY, vis);
    const getMorphine    = () => localStorage.getItem(MORPH_KEY) || 'Disabled';
    const saveMorphine   = val  => localStorage.setItem(MORPH_KEY, val);
    const getApiKey      = () => localStorage.getItem(API_KEY)   || '';
    const isSetupDone    = () => localStorage.getItem(SETUP_KEY) === 'true';
    const markSetupDone  = () => localStorage.setItem(SETUP_KEY, 'true');

    const saveApiKey = key => {
        const trimmed = (key || '').trim();
        trimmed === '' ? localStorage.removeItem(API_KEY) : localStorage.setItem(API_KEY, trimmed);
    };

    const bloodEnabled    = () => getBloodType() !== 'Disabled';
    const morphineEnabled = () => getMorphine() === 'Enabled';

    const formatTime = sec => {
        if (sec <= 0) return '';
        const m = Math.floor(sec / 60), s = sec % 60;
        return m > 0 ? `${m}m ${s.toString().padStart(2, '0')}s` : `${s}s`;
    };

    const formatCDTime = sec => {
        if (sec <= 0) return '0:00:00';
        const h = Math.floor(sec / 3600);
        const m = Math.floor((sec % 3600) / 60);
        const s = sec % 60;
        return `${h}:${m.toString().padStart(2,'0')}:${s.toString().padStart(2,'0')}`;
    };

    function inHospitalNow()    { return !!document.querySelector('a[aria-label^="Hospital:"]'); }
    function isOnPersonalPage() { return window.location.href.toLowerCase().includes('item.php'); }
    function isOnFactionPage()  {
        const url = window.location.href.toLowerCase();
        return url.includes('factions.php') && url.includes('tab=armoury');
    }

    // ─── SessionStorage Medical Cooldown ─────────────────────────────────────

    function getSidebarData() {
        try {
            const key = Object.keys(sessionStorage).find(k => /sidebarData\d+/.test(k));
            if (!key) return null;
            return JSON.parse(sessionStorage.getItem(key));
        } catch { return null; }
    }

    function hmsToMs(hms) {
        if (!hms) return 0;
        const parts = hms.split(':').map(Number);
        if (parts.length === 3) {
            const [h, m, s] = parts;
            return ((h * 60 + m) * 60 + s) * 1000;
        }
        return 0;
    }

    function getMedicalCooldownInfo() {
        const data = getSidebarData();
        if (!data) return null;
        const med = data?.statusIcons?.icons?.medical_cooldown;
        if (!med) return null;
        const nowSec       = Date.now() / 1000;
        const remainingSec = Math.max(0, Math.round(med.timerExpiresAt - nowSec));
        return { remainingSec, isActive: remainingSec > 0 };
    }

    // ─── API Key Dialog ──────────────────────────────────────────────────────

    function openApiKeyDialog() {
        const existing = document.getElementById('smart-fak-api-dialog');
        if (existing) existing.remove();

        const overlay = document.createElement('div');
        overlay.id = 'smart-fak-api-dialog';
        overlay.innerHTML = `
            <div id="smart-fak-api-dialog-box">
                <div id="smart-fak-api-dialog-title">Smart FAK: API Key</div>
                <div id="smart-fak-api-dialog-desc">Your API key is used to determine your education and faction perks to update item use thresholds accordingly. Requires Minimal Access or higher key, and updates every 15 seconds.</div>
                <input type="text" id="smart-fak-api-input" maxlength="16"
                    placeholder="Enter your API Key"
                    value="${getApiKey()}" />
                <div id="smart-fak-api-dialog-buttons">
                    <button id="smart-fak-api-cancel">Cancel</button>
                    <button id="smart-fak-api-save">Save</button>
                </div>
            </div>
        `;

        document.body.appendChild(overlay);

        const input     = overlay.querySelector('#smart-fak-api-input');
        const saveBtn   = overlay.querySelector('#smart-fak-api-save');
        const cancelBtn = overlay.querySelector('#smart-fak-api-cancel');

        function updateBorder() {
            input.style.borderColor = input.value.trim().length === 16 ? '#fff3' : '#f44';
        }
        updateBorder();
        input.addEventListener('input', updateBorder);

        cancelBtn.addEventListener('click', () => overlay.remove());

        saveBtn.addEventListener('click', async () => {
            const val = input.value.trim();

            if (val === '') {
                saveApiKey('');
                overlay.remove();
                updatePerkDisplay();
                return;
            }

            if (val.length !== 16) {
                input.style.borderColor = '#f44';
                input.focus();
                alert("API Key must be exactly 16 characters long.");
                return;
            }

            saveBtn.textContent = 'Checking…';
            saveBtn.disabled    = true;
            cancelBtn.disabled  = true;

            const valid = await validateApiKey(val);
            if (!valid.success) {
                saveBtn.textContent = 'Save';
                saveBtn.disabled    = false;
                cancelBtn.disabled  = false;
                input.style.borderColor = '#f44';

                let msg = 'Invalid API Key';
                if (valid.code === 2)  msg = 'Error - Invalid key';
                if (valid.code === 16) msg = 'Error - Key access too low (needs Minimal Access or higher)';
                alert(msg);
                input.focus();
                return;
            }

            saveApiKey(val);
            overlay.remove();
            updatePerkDisplay();
            fetchPerksAndUpdateThresholds();
        });

        input.focus();
    }

    async function validateApiKey(key) {
        try {
            const res  = await fetch(`https://api.torn.com/user/?selections=education,perks&key=${encodeURIComponent(key)}`);
            const data = await res.json();
            if (data.error) return { success: false, code: data.error.code };
            return { success: true };
        } catch { return { success: false }; }
    }

    async function fetchPerksAndUpdateThresholds() {
        const key = getApiKey().trim();
        if (key.length !== 16) return;

        try {
            const res  = await fetch(`https://api.torn.com/user/?selections=education,perks&key=${encodeURIComponent(key)}`);
            if (!res.ok) throw new Error(`HTTP ${res.status}`);
            const data = await res.json();
            if (data.error) return console.warn('[SmartFAK] API error:', data.error);

            let bonus = 0;
            [...(data.education_perks || []), ...(data.faction_perks || [])].forEach(p => {
                if (typeof p === 'string' && p.includes('medical item effectiveness')) {
                    const m = p.match(/(\d+)%/);
                    if (m) bonus += parseInt(m[1], 10);
                }
            });

            totalMedicalBonus = Math.min(bonus, 50);
            const multiplier  = 1 + totalMedicalBonus / 100;

            Object.keys(ITEMS).forEach(name => {
                ITEMS[name].removes = Math.round(ITEMS[name].baseRemoves * multiplier);
            });

            console.log(`[SmartFAK] Medical Item Effectiveness: +${totalMedicalBonus}%`);
            updateButtonDisplay();
            updatePerkDisplay();
        } catch (err) {
            console.warn('[SmartFAK] Perk fetch failed:', err.message);
        }
    }

    function updatePerkDisplay() {
        const perkEl = document.getElementById('smart-fak-perk-display');
        if (!perkEl) return;
        if (getApiKey().trim().length === 0) {
            perkEl.style.color = '#f44';
            perkEl.textContent = 'API Key needed for effectiveness bonus';
        } else {
            perkEl.style.color = '#0f0';
            perkEl.textContent = `Medical Item Effectiveness: +${totalMedicalBonus}%`;
        }
    }

    // ─── Item Selection ──────────────────────────────────────────────────────

    function selectBestItem(timer) {
        if (timer <= 0) return null;
        const available = [
            { name: 'Small First Aid Kit', ...ITEMS['Small First Aid Kit'] },
            { name: 'First Aid Kit',       ...ITEMS['First Aid Kit'] }
        ];
        if (morphineEnabled()) available.push({ name: 'Morphine', ...ITEMS['Morphine'] });
        if (bloodEnabled())    available.push({
            name: `Blood Bag: ${getBloodType()}`,
            id:   BLOOD_BAG_IDS[getBloodType()],
            removes: ITEMS['Blood Bag'].removes,
            cd:      ITEMS['Blood Bag'].cd
        });
        available.sort((a, b) => a.removes - b.removes);
        for (const item of available) if (item.removes >= timer) return item;
        return available.reduce((best, item) =>
            (item.removes / item.cd) > (best.removes / best.cd) ? item : best
        );
    }

    function getButtonColor(timer) {
        if (timer <= 0) return '#666';
        const item = selectBestItem(timer);
        if (item.name.startsWith('Blood Bag')) return '#a020f0';
        if (item.name === 'Morphine')          return '#e87722';
        if (item.name === 'First Aid Kit')     return '#1a6fc4';
        return '#c82333';
    }

    // ─── Display Updates ─────────────────────────────────────────────────────

    function updateButtonDisplay() {
        const container = document.getElementById('smart-fak-container');
        if (!container) return;
        const timerEl = container.querySelector('#smart-fak-timer');
        const btnEl   = container.querySelector('#smart-fak-btn');

        timerEl.style.display = 'block';
        if (cachedTimer <= 0) {
            timerEl.textContent    = 'No Hosp';
            btnEl.style.background = '#666';
        } else {
            timerEl.textContent    = formatTime(cachedTimer);
            btnEl.style.background = getButtonColor(cachedTimer);
        }
        updateCooldownDisplay();
    }

    function updateCooldownDisplay() {
        const el = document.getElementById('smart-fak-cd-text');
        if (!el) return;
        const med = getMedicalCooldownInfo();
        el.textContent = (med && med.isActive) ? `CD ${formatCDTime(med.remainingSec)}` : 'No Med CD';
    }

    // ─── Position / Visibility ───────────────────────────────────────────────

    const savePosition = (x, y) => localStorage.setItem(POS_KEY, JSON.stringify({
        xPct: x / window.innerWidth, yPct: y / window.innerHeight
    }));

    const loadPosition = () => {
        try {
            const pos = JSON.parse(localStorage.getItem(POS_KEY));
            if (!pos) return null;
            return pos.xPct !== undefined
                ? { x: Math.round(pos.xPct * window.innerWidth), y: Math.round(pos.yPct * window.innerHeight) }
                : pos;
        } catch { return null; }
    };

    function clampPosition(x, y, w, h) {
        const margin = 4;
        return {
            x: Math.min(Math.max(margin, x), window.innerWidth  - w - margin),
            y: Math.min(Math.max(margin, y), window.innerHeight - h - margin)
        };
    }

    function restorePosition() {
        const container = document.getElementById('smart-fak-container');
        if (!container) return;
        const pos = loadPosition();
        if (pos) {
            const r       = container.getBoundingClientRect();
            const clamped = clampPosition(pos.x, pos.y, r.width, r.height);
            container.style.left = clamped.x + 'px';
            container.style.top  = clamped.y + 'px';
        }
    }

    function updateVisibility() {
        const container = document.getElementById('smart-fak-container');
        if (!container) return;

        if (visibilityLockedUntilReload) {
            container.style.display = 'flex';
            return;
        }

        const vis     = getVisibility();
        const visible =
            vis === 'All Pages' ||
            (vis === 'Personal Items'     && isOnPersonalPage()) ||
            (vis === 'Faction Armory'     && isOnFactionPage())  ||
            (vis === 'Personal & Faction' && (isOnPersonalPage() || isOnFactionPage()));

        container.style.display = visible ? 'flex' : 'none';
    }

    // ─── Settings Panel ──────────────────────────────────────────────────────

    function openSettings() {
        const panel = document.getElementById('smart-fak-settings');
        if (!panel) return;
        // Load current saved values into the panel controls before showing
        panel.querySelector('#smart-fak-vis').value   = getVisibility();
        panel.querySelector('#smart-fak-mode').value  = getMode();
        panel.querySelector('#smart-fak-morph').value = getMorphine();
        panel.querySelector('#smart-fak-blood').value = getBloodType();
        updatePerkDisplay();
        panel.style.display = 'flex';
    }

    function closeSettings(save) {
        const panel = document.getElementById('smart-fak-settings');
        if (!panel) return;

        if (save) {
            const newVis  = panel.querySelector('#smart-fak-vis').value;
            const prevVis = getVisibility();
            saveMode(panel.querySelector('#smart-fak-mode').value);
            saveBloodType(panel.querySelector('#smart-fak-blood').value);
            saveMorphine(panel.querySelector('#smart-fak-morph').value);
            saveVisibility(newVis);
            if (newVis !== prevVis) visibilityLockedUntilReload = true;
            updateButtonDisplay();
        }

        panel.style.display = 'none';
        markSetupDone();
    }

    function toggleSettings() {
        const panel   = document.getElementById('smart-fak-settings');
        if (!panel) return;
        const isHidden = panel.style.display === 'none' || panel.style.display === '';
        isHidden ? openSettings() : closeSettings(false);
    }

    function syncSettingsUI() {
        updatePerkDisplay();
    }

    // ─── Item Use ────────────────────────────────────────────────────────────

    function isOnCorrectPage() {
        const mode = getMode();
        return (mode === 'Personal Items' && isOnPersonalPage()) ||
               (mode === 'Faction Armory' && isOnFactionPage());
    }

    async function useItemDirect(item) {
        if (!item?.id) return false;
        const isFaction = getMode() === 'Faction Armory';
        try {
            const body = new URLSearchParams({ step: 'useItem', itemID: item.id.toString() });
            if (isFaction) body.set('fac', '1');
            const res = await fetch('https://www.torn.com/item.php', {
                method: 'POST', body, credentials: 'include',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Requested-With': 'XMLHttpRequest' }
            });
            return res.ok;
        } catch { return false; }
    }

    async function onButtonClick() {
        if (isDragging) return;
        if (cachedTimer <= 0) return;
        const item = selectBestItem(cachedTimer);
        if (isOnCorrectPage()) {
            const success = await useItemDirect(item);
            if (success) setTimeout(updateButtonDisplay, 800);
        } else {
            window.location.href = getMode() === 'Faction Armory' ? FACTION_URL : PERSONAL_URL;
        }
    }

    // ─── Dragging ────────────────────────────────────────────────────────────

    function enableDragging(el, handle) {
        let drag = false, startX, startY, initX, initY;
        const s = e => {
            drag = true; isDragging = false;
            const evt = e.touches ? e.touches[0] : e;
            startX = evt.clientX; startY = evt.clientY;
            initX = el.offsetLeft; initY = el.offsetTop;
            e.preventDefault();
        };
        const m = e => {
            if (!drag) return;
            const evt = e.touches ? e.touches[0] : e;
            const dx = evt.clientX - startX, dy = evt.clientY - startY;
            if (Math.abs(dx) > 8 || Math.abs(dy) > 8) isDragging = true;
            const r       = el.getBoundingClientRect();
            const clamped = clampPosition(initX + dx, initY + dy, r.width, r.height);
            el.style.left = clamped.x + 'px';
            el.style.top  = clamped.y + 'px';
        };
        const end = () => {
            if (!drag) return;
            drag = false;
            const r       = el.getBoundingClientRect();
            const clamped = clampPosition(r.left, r.top, r.width, r.height);
            el.style.left = clamped.x + 'px';
            el.style.top  = clamped.y + 'px';
            savePosition(clamped.x, clamped.y);
            setTimeout(() => isDragging = false, 50);
        };
        handle.addEventListener('mousedown', s);
        document.addEventListener('mousemove', m);
        document.addEventListener('mouseup', end);
        handle.addEventListener('touchstart', s, { passive: false });
        document.addEventListener('touchmove', m, { passive: false });
        document.addEventListener('touchend', end);
    }

    // ─── Hospital Polling ────────────────────────────────────────────────────

    async function fetchHospitalTime() {
        try {
            const res  = await fetch('/page.php?sid=UserApiData', { credentials: 'include', headers: { 'X-Requested-With': 'XMLHttpRequest' } });
            const data = await res.json();
            const until = Number(data?.hospitalstamp) || 0;
            const now   = Math.floor(Date.now() / 1000);
            return until > now ? Math.max(0, until - now) : 0;
        } catch {
            return inHospitalNow() ? cachedTimer || 60 : 0;
        }
    }

    function startHospitalPolling() {
        setInterval(async () => {
            updateVisibility();
            const hosp = inHospitalNow();
            lastHospitalStatus = hosp;
            const t = hosp ? await fetchHospitalTime() : 0;
            if (t !== cachedTimer) { cachedTimer = t; updateButtonDisplay(); }
        }, 1000);
    }

    // ─── UI Injection ────────────────────────────────────────────────────────

    function injectUI() {
        if (document.getElementById('smart-fak-container')) return;
        const bloodOptions = BLOOD_TYPES.map(t => `<option value="${t}">${t}</option>`).join('');

        const container = document.createElement('div');
        container.id = 'smart-fak-container';
        container.innerHTML = `
            <div id="smart-fak-btn-wrap">
                <div id="smart-fak-btn" title="Click to use best item • Drag to move">
                    <div id="smart-fak-icon">✚</div>
                    <div id="smart-fak-timer"></div>
                </div>
                <div id="smart-fak-cog" title="Settings">⚙</div>
                <div id="smart-fak-cd-box">
                    <div id="smart-fak-cd-text">CD ---</div>
                </div>
            </div>
        `;

        // Settings panel — full-screen overlay, box centred
        const panel = document.createElement('div');
        panel.id = 'smart-fak-settings';
        panel.innerHTML = `
            <div id="smart-fak-settings-box">
                <div id="smart-fak-settings-title">Smart FAK: Settings</div>
                <div id="smart-fak-settings-body">
                    <label>Visibility</label>
                    <select id="smart-fak-vis">
                        <option value="All Pages">All Pages</option>
                        <option value="Personal Items">Personal Items</option>
                        <option value="Faction Armory">Faction Armory</option>
                        <option value="Personal &amp; Faction">Personal &amp; Faction</option>
                    </select>
                    <label>Item Source</label>
                    <select id="smart-fak-mode">
                        <option value="Personal Items">Personal Items</option>
                        <option value="Faction Armory">Faction Armory</option>
                    </select>
                    <label>Morphine</label>
                    <select id="smart-fak-morph">
                        <option value="Disabled">Disabled</option>
                        <option value="Enabled">Enabled</option>
                    </select>
                    <label>Blood Bag Type</label>
                    <select id="smart-fak-blood">${bloodOptions}</select>
                    <label>API Key</label>
                    <button id="smart-fak-api-btn">Add / Edit API Key</button>
                    <div id="smart-fak-perk-display"></div>
                </div>
                <div id="smart-fak-settings-footer">
                    <button id="smart-fak-settings-cancel">Cancel</button>
                    <button id="smart-fak-settings-save">Save</button>
                </div>
            </div>
        `;

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

        // Position FAK button — centre of screen on first run, else restore saved
        const pos = loadPosition();
        if (pos) {
            const r       = container.getBoundingClientRect();
            const clamped = clampPosition(pos.x, pos.y, r.width, r.height);
            container.style.left = clamped.x + 'px';
            container.style.top  = clamped.y + 'px';
        } else {
            container.style.left      = '50%';
            container.style.top       = '50%';
            container.style.transform = 'translate(-50%, -50%)';
        }

        // Settings wire-up
        panel.querySelector('#smart-fak-settings-cancel').addEventListener('click', () => closeSettings(false));
        panel.querySelector('#smart-fak-settings-save').addEventListener('click',   () => closeSettings(true));
        panel.querySelector('#smart-fak-api-btn').addEventListener('click', e => { e.stopPropagation(); openApiKeyDialog(); });

        const cog = container.querySelector('#smart-fak-cog');
        cog.addEventListener('click',    e => { e.stopPropagation(); toggleSettings(); });
        cog.addEventListener('touchend', e => { e.preventDefault(); e.stopPropagation(); toggleSettings(); }, { passive: false });

        const btn = container.querySelector('#smart-fak-btn');
        btn.addEventListener('click',    onButtonClick);
        btn.addEventListener('touchend', e => { if (!isDragging) { e.preventDefault(); onButtonClick(); } }, { passive: false });

        enableDragging(container, btn);

        window.addEventListener('resize', restorePosition);
        window.addEventListener('pageshow', () => {
            restorePosition();
            syncSettingsUI();
            updateVisibility();
            updateButtonDisplay();
        });
    }

    // ─── Styles ──────────────────────────────────────────────────────────────

    const style = document.createElement('style');
    style.textContent = `
        #smart-fak-container { position:fixed; z-index:999999; display:flex; flex-direction:column; align-items:center; user-select:none; }
        #smart-fak-btn-wrap  { position:relative; width:64px; height:74px; }
        #smart-fak-btn       { position:absolute; top:0; width:64px; height:64px; border-radius:50%; background:#666; border:3px solid #fff4; display:flex; justify-content:center; align-items:center; overflow:hidden; cursor:pointer; transition:all .15s; }
        #smart-fak-btn:hover { transform:scale(1.12); }
        #smart-fak-icon      { font-size:40px; position:absolute; top:calc(50% - 6px); left:50%; transform:translate(-50%,-50%); pointer-events:none; }
        #smart-fak-timer     { position:absolute; bottom:10px; left:50%; transform:translateX(-50%); font-size:11px; color:white; font-weight:bold; text-shadow:0 0 3px black; pointer-events:none; white-space:nowrap; }

        #smart-fak-cd-box  { position:absolute; top:60px; left:0; width:69px; height:22px; background:#8b0000; border:2px solid #fff4; border-radius:6px; display:flex; align-items:center; box-sizing:border-box; }
        #smart-fak-cd-text { font-size:11px; color:#fff; text-align:center; flex-grow:1; pointer-events:none; }

        #smart-fak-cog { width:18px; height:18px; background:#333; border:1px solid #fff4; border-radius:50%; font-size:11px; display:flex; align-items:center; justify-content:center; cursor:pointer; position:absolute; top:0; right:-5px; }

        /* Settings — full-screen dimmed overlay, box centred */
        #smart-fak-settings {
            position: fixed;
            inset: 0;
            z-index: 1000000;
            display: none;
            align-items: center;
            justify-content: center;
            background: rgba(0,0,0,0.6);
        }
        #smart-fak-settings-box {
            background: #1a1a1a;
            border: 1px solid #fff3;
            border-radius: 10px;
            width: 220px;
            display: flex;
            flex-direction: column;
            overflow: hidden;
        }
        #smart-fak-settings-title {
            background: #111;
            color: #fff;
            font-size: 13px;
            font-weight: bold;
            padding: 10px 14px;
            border-bottom: 1px solid #fff2;
            user-select: none;
        }
        #smart-fak-settings-body {
            display: flex;
            flex-direction: column;
            gap: 8px;
            padding: 12px 14px;
        }
        #smart-fak-settings-body label  { font-size:10px; color:#aaa; text-transform:uppercase; letter-spacing:0.5px; }
        #smart-fak-settings-body select { width:100%; font-size:12px; padding:4px 6px; border-radius:4px; border:1px solid #fff3; background:#2a2a2a; color:#fff; box-sizing:border-box; cursor:pointer; }
        #smart-fak-api-btn              { width:100%; font-size:12px; padding:5px 6px; border-radius:4px; border:1px solid #fff3; background:#2a2a2a; color:#fff; cursor:pointer; text-align:center; }
        #smart-fak-api-btn:hover        { background:#3a3a3a; }
        #smart-fak-perk-display         { font-size:11px; text-align:center; }
        #smart-fak-settings-footer {
            display: flex;
            gap: 8px;
            padding: 10px 14px;
            border-top: 1px solid #fff2;
            background: #111;
        }
        #smart-fak-settings-cancel,
        #smart-fak-settings-save {
            flex: 1;
            padding: 7px;
            border-radius: 4px;
            border: 1px solid #fff3;
            background: #2a2a2a;
            color: #fff;
            cursor: pointer;
            font-size: 12px;
        }
        #smart-fak-settings-cancel:hover { background:#3a3a3a; }
        #smart-fak-settings-save         { background:#1a4a1a; }
        #smart-fak-settings-save:hover   { background:#2a6a2a; }

        /* API key dialog */
        #smart-fak-api-dialog            { position:fixed; inset:0; z-index:2000000; background:rgba(0,0,0,0.8); display:flex; align-items:center; justify-content:center; }
        #smart-fak-api-dialog-box        { background:#1a1a1a; border:1px solid #fff3; border-radius:10px; width:320px; display:flex; flex-direction:column; overflow:hidden; }
        #smart-fak-api-dialog-title      { background:#111; color:#fff; font-size:13px; font-weight:bold; padding:10px 14px; border-bottom:1px solid #fff2; }
        #smart-fak-api-dialog-desc       { font-size:11px; color:#aaa; line-height:1.5; padding:14px 14px 0; }
        #smart-fak-api-input             { font-size:12px; padding:8px; border-radius:4px; border:1px solid #fff3; background:#2a2a2a; color:#fff; margin:12px 14px 0; box-sizing:border-box; }
        #smart-fak-api-dialog-buttons    { display:flex; gap:10px; padding:12px 14px; }
        #smart-fak-api-cancel,
        #smart-fak-api-save              { flex:1; padding:8px; border-radius:4px; border:1px solid #fff3; background:#2a2a2a; color:#fff; cursor:pointer; font-size:12px; }
        #smart-fak-api-cancel:hover      { background:#3a3a3a; }
        #smart-fak-api-save              { background:#1a4a1a; }
        #smart-fak-api-save:hover        { background:#2a6a2a; }
    `;
    document.head.appendChild(style);

    // ─── Init ────────────────────────────────────────────────────────────────

    async function init() {
        injectUI();
        updateVisibility();
        cachedTimer        = inHospitalNow() ? await fetchHospitalTime() : 0;
        lastHospitalStatus = inHospitalNow();
        updateButtonDisplay();
        syncSettingsUI();

        // First-run: auto-open settings so the user can configure before use
        if (!isSetupDone()) openSettings();

        startHospitalPolling();

        // Countdown tick — reads live from sessionStorage each second
        setInterval(updateCooldownDisplay, 1000);

        setInterval(() => {
            if (getApiKey().trim().length === 16) fetchPerksAndUpdateThresholds();
        }, 15000);

        setTimeout(() => {
            if (getApiKey().trim().length === 16) fetchPerksAndUpdateThresholds();
        }, 2000);
    }

    init();
})();