Smart hospital FAK button. Shows hospital time and auto-uses best med item. Applies education + faction medical effectiveness bonuses via API key.
// ==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 & Faction">Personal & 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();
})();