Navbar panic button — deposit all cash into your vault target stock. Works with Smart Stock Vault or standalone.
// ==UserScript==
// @name Smart Panic
// @namespace Noobler.torn.panicNavbar
// @version 2.1.2
// @description Navbar panic button — deposit all cash into your vault target stock. Works with Smart Stock Vault or standalone.
// @author Noobler
// @match https://www.torn.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com
// @connect api.torn.com
// @license MIT
// ==/UserScript==
(() => {
// src/shared/theme.js
var tokens = {
accent: "#caa14a",
accentMuted: "rgba(202, 161, 74, 0.35)",
accentSubtle: "rgba(202, 161, 74, 0.12)",
accentText: "#f0e3c0",
panelBg: "linear-gradient(180deg, #23252b, #1b1d22)",
panelHeaderBg: "linear-gradient(180deg, #2c2f37, #23252b)",
panelBorder: "#34373f",
surface: "#15161a",
text: "#d7d9de",
textMuted: "#8a8d96",
textDim: "#71747d",
success: "#6fcf86",
successBg: "#16341f",
successBorder: "#2c6b3c",
warning: "#ffd966",
warningBg: "#332b13",
warningBorder: "#6b5a1f",
danger: "#ff6b6b",
dangerBg: "#341818",
dangerBorder: "#6b2c2c",
info: "#8dbdf0",
infoBg: "#15263d",
infoBorder: "#2c4f7b",
hover: "#d8b25c",
font: "'Trebuchet MS', Verdana, sans-serif",
radius: "8px",
shadow: "0 2px 10px rgba(0, 0, 0, 0.35)",
statDex: "#8a7ff0",
statDef: "#e07a4f",
statStr: "#3fae84",
statSpd: "#4a97e6"
};
var STYLE_ID = "hf-torn-theme";
function injectStyles(scopeClass = "hf-torn") {
if (document.getElementById(STYLE_ID)) {
return;
}
const css = `
.${scopeClass} {
--hf-accent: ${tokens.accent};
--hf-accent-muted: ${tokens.accentMuted};
--hf-accent-subtle: ${tokens.accentSubtle};
--hf-accent-text: ${tokens.accentText};
--hf-panel-bg: ${tokens.panelBg};
--hf-panel-header-bg: ${tokens.panelHeaderBg};
--hf-panel-border: ${tokens.panelBorder};
--hf-surface: ${tokens.surface};
--hf-text: ${tokens.text};
--hf-text-muted: ${tokens.textMuted};
--hf-text-dim: ${tokens.textDim};
--hf-success: ${tokens.success};
--hf-success-bg: ${tokens.successBg};
--hf-success-border: ${tokens.successBorder};
--hf-warning: ${tokens.warning};
--hf-warning-bg: ${tokens.warningBg};
--hf-warning-border: ${tokens.warningBorder};
--hf-danger: ${tokens.danger};
--hf-danger-bg: ${tokens.dangerBg};
--hf-danger-border: ${tokens.dangerBorder};
--hf-info: ${tokens.info};
--hf-info-bg: ${tokens.infoBg};
--hf-info-border: ${tokens.infoBorder};
--hf-hover: ${tokens.hover};
--hf-radius: ${tokens.radius};
--hf-shadow: ${tokens.shadow};
--hf-font: ${tokens.font};
--hf-stat-dex: ${tokens.statDex};
--hf-stat-def: ${tokens.statDef};
--hf-stat-str: ${tokens.statStr};
--hf-stat-spd: ${tokens.statSpd};
color: var(--hf-text);
font-family: var(--hf-font);
font-size: 12px;
line-height: 1.4;
box-sizing: border-box;
}
.${scopeClass} *, .${scopeClass} *::before, .${scopeClass} *::after {
box-sizing: border-box;
}
.${scopeClass} .hf-btn {
appearance: none;
border: 1px solid var(--hf-panel-border);
border-radius: 5px;
background: var(--hf-surface);
color: var(--hf-accent-text);
cursor: pointer;
padding: 6px 10px;
font: inherit;
transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.${scopeClass} .hf-btn:hover {
background: var(--hf-accent-subtle);
border-color: var(--hf-accent);
color: #fff;
}
.${scopeClass} .hf-btn.is-active,
.${scopeClass} .hf-btn--primary {
background: var(--hf-accent);
border-color: var(--hf-accent);
color: #1b1d22;
font-weight: 700;
}
.${scopeClass} .hf-btn.is-active:hover,
.${scopeClass} .hf-btn--primary:hover {
background: var(--hf-hover);
border-color: var(--hf-hover);
}
.${scopeClass} .hf-muted {
color: var(--hf-text-muted);
}
.${scopeClass} .hf-dim {
color: var(--hf-text-dim);
}
.${scopeClass} .hf-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
background: var(--hf-accent);
color: #1b1d22;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.${scopeClass} .hf-input,
.${scopeClass} .hf-select {
width: 100%;
box-sizing: border-box;
padding: 6px 8px;
background: var(--hf-surface);
border: 1px solid var(--hf-panel-border);
color: var(--hf-accent-text);
border-radius: 5px;
font: inherit;
font-size: 12px;
}
.${scopeClass} .hf-label {
display: block;
font-size: 11px;
color: var(--hf-text-muted);
margin: 12px 0 4px;
}
.${scopeClass} .hf-table {
width: 100%;
border-collapse: collapse;
}
.${scopeClass} .hf-table th,
.${scopeClass} .hf-table td {
padding: 4px 6px;
text-align: left;
border-bottom: 1px solid var(--hf-panel-border);
}
.${scopeClass} .hf-table th {
font-weight: bold;
color: var(--hf-accent-text);
}
.${scopeClass}.hf-panel {
margin: 0 0 12px;
border-radius: var(--hf-radius);
background: var(--hf-panel-bg);
border: 1px solid var(--hf-panel-border);
box-shadow: var(--hf-shadow);
overflow: hidden;
}
.${scopeClass}.hf-panel--page {
width: 100%;
}
.${scopeClass}.hf-panel--fixed {
position: fixed;
z-index: 9999;
}
.${scopeClass} .hf-panel-header {
background: var(--hf-panel-header-bg);
border-bottom: 1px solid var(--hf-panel-border);
border-radius: var(--hf-radius) var(--hf-radius) 0 0;
}
.${scopeClass} .hf-panel-tabs {
border-bottom: 1px solid var(--hf-panel-border);
}
.${scopeClass} .hf-panel-body {
padding: 12px 13px;
}
.${scopeClass}.hf-panel--page .hf-panel-body {
max-height: min(70vh, 640px);
overflow-y: auto;
}
.${scopeClass}.hf-panel--page.hf-panel--grow .hf-panel-body {
max-height: none;
overflow: visible;
}
.${scopeClass}.hf-overlay,
.${scopeClass} .hf-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
z-index: 99999;
display: flex;
align-items: center;
justify-content: center;
}
.${scopeClass}.hf-modal,
.${scopeClass} .hf-modal {
background: #1f2127;
border: 1px solid var(--hf-panel-border);
border-radius: var(--hf-radius);
width: min(440px, 92vw);
max-height: 84vh;
overflow-y: auto;
padding: 18px;
color: var(--hf-text);
}
.${scopeClass}.hf-modal h2,
.${scopeClass} .hf-modal h2 {
margin: 0 0 14px;
font-size: 15px;
color: var(--hf-accent-text);
display: flex;
justify-content: space-between;
align-items: center;
}
.${scopeClass} .hf-mini {
font-size: 10px;
color: var(--hf-text-dim);
margin-top: 3px;
line-height: 1.4;
}
`;
const style = document.createElement("style");
style.id = STYLE_ID;
style.textContent = css;
document.head.appendChild(style);
}
var theme = {
tokens,
injectStyles
};
// src/shared/storage.js
var PREFIX = "hf.torn";
function namespaced(key) {
return `${PREFIX}.${key}`;
}
function getLocal(key, fallback = null) {
try {
const raw = localStorage.getItem(namespaced(key));
if (raw === null) {
return fallback;
}
return JSON.parse(raw);
} catch {
return fallback;
}
}
function setLocal(key, value) {
localStorage.setItem(namespaced(key), JSON.stringify(value));
}
function removeLocal(key) {
localStorage.removeItem(namespaced(key));
}
var DB_NAME = "heartflower-torn";
var DB_VERSION = 1;
var dbPromise = null;
function openDb() {
if (dbPromise) {
return dbPromise;
}
dbPromise = new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onupgradeneeded = () => {
const db = request.result;
if (!db.objectStoreNames.contains("races")) {
const store = db.createObjectStore("races", { keyPath: "id" });
store.createIndex("timestamp", "timestamp", { unique: false });
store.createIndex("track", "track", { unique: false });
store.createIndex("carKey", "carKey", { unique: false });
}
};
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error ?? new Error("IndexedDB open failed"));
});
return dbPromise;
}
async function withStore(storeName, mode, run) {
const db = await openDb();
return new Promise((resolve, reject) => {
const tx = db.transaction(storeName, mode);
const store = tx.objectStore(storeName);
const request = run(store);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error ?? new Error("IndexedDB request failed"));
});
}
async function putRecord(storeName, record) {
await withStore(storeName, "readwrite", (store) => store.put(record));
}
async function deleteRecord(storeName, key) {
await withStore(storeName, "readwrite", (store) => store.delete(key));
}
async function getRecord(storeName, key) {
return withStore(storeName, "readonly", (store) => store.get(key));
}
async function getAllRecords(storeName, query, limit) {
const db = await openDb();
return new Promise((resolve, reject) => {
const tx = db.transaction(storeName, "readonly");
const store = tx.objectStore(storeName);
const request = store.getAll(query, limit);
request.onsuccess = () => resolve(
/** @type {Record<string, unknown>[]} */
request.result ?? []
);
request.onerror = () => reject(request.error ?? new Error("IndexedDB getAll failed"));
});
}
async function getByIndex(storeName, indexName, key) {
return withStore(storeName, "readonly", (store) => store.index(indexName).getAll(key));
}
var storage = {
PREFIX,
getLocal,
setLocal,
removeLocal,
putRecord,
deleteRecord,
getRecord,
getAllRecords,
getByIndex
};
// src/shared/dom.js
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function waitFor(selector, options = {}) {
const { timeout = 1e4, interval = 100, root = document } = options;
const start = Date.now();
while (Date.now() - start < timeout) {
const el = root.querySelector(selector);
if (el) {
return el;
}
await sleep(interval);
}
throw new Error(`waitFor timeout: ${selector}`);
}
async function waitForOptional(selector, options = {}) {
try {
return await waitFor(selector, options);
} catch {
return null;
}
}
function observeMutations(root, callback, options = {}) {
const { debounce = 50 } = options;
let timer = null;
const observer = new MutationObserver(() => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(callback, debounce);
});
observer.observe(root, { childList: true, subtree: true });
callback();
return () => {
if (timer) {
clearTimeout(timer);
}
observer.disconnect();
};
}
function onAnchorClick(callback) {
const handler = (event) => {
const target = (
/** @type {Element} */
event.target
);
if (target.tagName === "A" || target.closest("a")) {
setTimeout(callback, 150);
}
};
document.body.addEventListener("click", handler);
return () => document.body.removeEventListener("click", handler);
}
function normalizeText(text) {
return text.replace(/\s+/g, " ").trim();
}
var dom = {
sleep,
waitFor,
waitForOptional,
observeMutations,
onAnchorClick,
normalizeText
};
// src/shared/mount.js
var MOUNT_PRESETS = {
racing: {
waitFor: ["#racingMainContainer"],
target: "#racingMainContainer",
position: "before"
},
gym: {
waitFor: ["#gymroot"],
target: "#gymroot",
position: "prepend"
},
gymBar: {
waitFor: ["#gymroot"],
target: "#gymroot",
position: "before"
},
stocksBar: {
waitFor: ["#stockmarketroot"],
target: "#stockmarketroot",
position: "before"
},
disposalBar: {
waitFor: ['[class*="disposal-root"]'],
target: '[class*="disposal-root"]',
position: "before"
},
contentTitle: {
waitFor: [".content-title", ".body > .content-title", "div.content-title"],
target: ".content-title",
position: "after"
}
};
function resolveMountConfig(mount2) {
if (!mount2) {
return MOUNT_PRESETS.contentTitle;
}
if (typeof mount2 === "string") {
if (mount2 === "page") {
return MOUNT_PRESETS.contentTitle;
}
return MOUNT_PRESETS[mount2] ?? MOUNT_PRESETS.contentTitle;
}
return mount2;
}
function findMountTarget(config) {
const pageReady = config.waitFor.some((selector) => document.querySelector(selector));
if (!pageReady) {
return null;
}
return document.querySelector(config.target);
}
function insertAt(target, element, position) {
switch (position) {
case "before":
target.insertAdjacentElement("beforebegin", element);
break;
case "after":
target.insertAdjacentElement("afterend", element);
break;
case "prepend":
target.prepend(element);
break;
case "append":
target.append(element);
break;
}
}
function isMountedAt(element, target, position) {
if (!element.isConnected) {
return false;
}
switch (position) {
case "before":
return element.nextElementSibling === target;
case "after":
return element.previousElementSibling === target;
case "prepend":
return target.firstElementChild === element;
case "append":
return target.lastElementChild === element;
default:
return false;
}
}
function mountElement(config, element) {
const target = findMountTarget(config);
if (!target) {
return false;
}
if (isMountedAt(element, target, config.position)) {
return true;
}
insertAt(target, element, config.position);
return true;
}
function watchMount(config, getElement) {
let stopped = false;
const start = Date.now();
const timeoutMs = 15e3;
const tryMount = () => {
if (stopped) {
return;
}
const element = getElement();
mountElement(config, element);
};
const poll = window.setInterval(() => {
tryMount();
if (findMountTarget(config) || Date.now() - start > timeoutMs) {
window.clearInterval(poll);
}
}, 250);
const observer = new MutationObserver((mutations) => {
const onlyOwnChanges = mutations.every((mutation) => {
const node = mutation.target;
return node instanceof HTMLElement && node.closest(".hf-torn");
});
if (!onlyOwnChanges) {
tryMount();
}
});
observer.observe(document.body, { childList: true, subtree: true });
tryMount();
return () => {
stopped = true;
window.clearInterval(poll);
observer.disconnect();
};
}
var mount = {
MOUNT_PRESETS,
resolveMountConfig,
findMountTarget,
insertAt,
isMountedAt,
mountElement,
watchMount
};
// src/shared/api.js
var API_BASE = "https://api.torn.com/v2";
var SETTINGS_KEY = "hf.torn.settings";
function loadSettings() {
try {
const raw = GM_getValue(SETTINGS_KEY, "{}");
const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
return {
apiKey: String(parsed?.apiKey ?? "").trim()
};
} catch {
return { apiKey: "" };
}
}
function saveSettings(patch) {
const current = loadSettings();
GM_setValue(SETTINGS_KEY, JSON.stringify({ ...current, ...patch }));
}
function parseTornError(json) {
if (!json || typeof json !== "object" || !("error" in json)) {
return null;
}
const err = (
/** @type {{ error?: unknown }} */
json.error
);
if (typeof err === "string") {
return new Error(err);
}
if (err && typeof err === "object") {
const detail = (
/** @type {{ error?: string, message?: string, code?: number }} */
err
);
return new Error(detail.error || detail.message || `Torn API error (${detail.code ?? "unknown"})`);
}
return new Error("Torn API error");
}
function tornGet(path, params, apiKey) {
const url = new URL(`${API_BASE}/${path.replace(/^\//, "")}`);
url.searchParams.set("key", apiKey);
for (const [key, value] of Object.entries(params ?? {})) {
if (value !== void 0 && value !== "") {
url.searchParams.set(key, String(value));
}
}
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: url.toString(),
timeout: 2e4,
onload: (response) => {
try {
const json = JSON.parse(response.responseText || "{}");
const apiError = parseTornError(json);
if (apiError) {
reject(apiError);
return;
}
resolve(json);
} catch (error) {
reject(error instanceof Error ? error : new Error("Invalid API response"));
}
},
onerror: () => reject(new Error("Network error")),
ontimeout: () => reject(new Error("API request timed out"))
});
});
}
var api = {
loadSettings,
saveSettings,
parseTornError,
tornGet,
SETTINGS_KEY
};
// src/shared/ui.js
function panelCollapsedKey(panelId) {
return `panel.${panelId}.collapsed`;
}
function readPanelCollapsed(panelId, legacyKeys = []) {
const stored = getLocal(panelCollapsedKey(panelId), void 0);
if (stored === true || stored === "true") {
return true;
}
if (stored === false || stored === "false") {
return false;
}
for (const key of legacyKeys) {
const raw = localStorage.getItem(key);
if (raw === "true") {
setLocal(panelCollapsedKey(panelId), true);
return true;
}
if (raw === "false") {
setLocal(panelCollapsedKey(panelId), false);
return false;
}
}
return false;
}
var PANELS = /* @__PURE__ */ new Map();
function createPanel(options) {
const existing = PANELS.get(options.id);
if (existing?.element.isConnected) {
return existing;
}
if (existing) {
PANELS.delete(options.id);
}
injectStyles();
const mountOption = options.mount ?? "racing";
const isFixed = mountOption === "fixed";
const mountConfig = isFixed ? null : resolveMountConfig(mountOption);
const pageBody = options.pageBody ?? "cap";
const growPageBody = !isFixed && pageBody === "grow";
const root = document.createElement("section");
root.id = options.id;
root.className = isFixed ? "hf-torn hf-panel hf-panel--fixed" : `hf-torn hf-panel hf-panel--page${growPageBody ? " hf-panel--grow" : ""}`;
applyPanelLayout(root, isFixed, options.position ?? "bottom-right");
const header = document.createElement("div");
header.className = "hf-panel-header";
header.style.cssText = `
display: flex;
align-items: center;
gap: 10px;
padding: 9px 13px;
cursor: pointer;
user-select: none;
`;
const titleWrap = document.createElement("div");
titleWrap.style.display = "flex";
titleWrap.style.alignItems = "center";
titleWrap.style.gap = "10px";
titleWrap.style.flex = "1";
titleWrap.style.minWidth = "0";
if (options.badge) {
const badge = document.createElement("span");
badge.className = "hf-badge";
badge.textContent = options.badge;
titleWrap.appendChild(badge);
} else if (options.title) {
const title = document.createElement("div");
title.textContent = options.title;
title.style.fontWeight = "700";
title.style.fontSize = "13px";
title.style.color = "var(--hf-accent-text)";
titleWrap.appendChild(title);
}
const right = document.createElement("div");
right.className = "hf-panel-right";
right.style.cssText = `
display: flex;
align-items: center;
gap: 8px;
margin-left: auto;
flex-shrink: 0;
`;
const subtitle = document.createElement("div");
subtitle.className = "hf-muted hf-panel-subtitle";
subtitle.style.cssText = `
font-size: 11px;
max-width: min(280px, 40vw);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
pointer-events: none;
`;
const gear = document.createElement("button");
gear.type = "button";
gear.className = "hf-panel-gear";
gear.title = "Settings";
gear.innerHTML = "⚙";
gear.style.cssText = `
font-size: 15px;
color: var(--hf-text-muted);
background: none;
border: none;
cursor: pointer;
padding: 0 4px;
line-height: 1;
flex-shrink: 0;
position: relative;
z-index: 2;
`;
const openSettings2 = (event) => {
event.preventDefault();
event.stopPropagation();
options.onSettings?.();
};
gear.addEventListener("mousedown", openSettings2);
gear.addEventListener("click", openSettings2);
const toggle = document.createElement("span");
toggle.className = "hf-panel-chev";
toggle.textContent = "\u25BC";
toggle.style.cssText = "font-size:12px;color:var(--hf-text-muted);transition:transform .2s;flex-shrink:0;";
right.appendChild(subtitle);
if (options.onSettings) {
right.appendChild(gear);
}
right.appendChild(toggle);
header.appendChild(titleWrap);
header.appendChild(right);
const tabBar = document.createElement("div");
tabBar.className = "hf-panel-tabs";
tabBar.style.cssText = `
display: flex;
flex-wrap: wrap;
gap: 4px;
padding: 6px 8px;
`;
const body = document.createElement("div");
body.className = "hf-panel-body";
if (growPageBody) {
body.style.maxHeight = "none";
body.style.overflow = "visible";
}
root.appendChild(header);
if (options.tabs.length > 1) {
root.appendChild(tabBar);
}
root.appendChild(body);
let collapsed = readPanelCollapsed(options.id, options.collapsedLegacyKeys);
let activeTabId = options.tabs[0]?.id ?? "";
const tabButtons = /* @__PURE__ */ new Map();
const setCollapsed = (value) => {
collapsed = value;
const hideBody = collapsed;
if (options.tabs.length > 1) {
tabBar.style.display = collapsed ? "none" : "flex";
}
body.style.display = hideBody ? "none" : "block";
toggle.style.transform = collapsed ? "rotate(-90deg)" : "";
root.classList.toggle("is-collapsed", collapsed);
setLocal(panelCollapsedKey(options.id), value);
};
const renderActiveTab = async () => {
const tab = options.tabs.find((item) => item.id === activeTabId);
body.replaceChildren();
if (!tab) {
body.textContent = "No tab selected.";
return;
}
const content = await tab.render();
body.appendChild(content);
};
const setTab = (id) => {
activeTabId = id;
tabButtons.forEach((btn, tabId) => {
btn.classList.toggle("is-active", tabId === id);
});
renderActiveTab();
};
if (options.tabs.length > 1) {
for (const tab of options.tabs) {
const btn = document.createElement("button");
btn.type = "button";
btn.className = "hf-btn";
btn.textContent = tab.label;
btn.addEventListener("click", (event) => {
event.stopPropagation();
setTab(tab.id);
});
tabButtons.set(tab.id, btn);
tabBar.appendChild(btn);
}
}
header.addEventListener("click", (event) => {
if (
/** @type {Element} */
event.target.closest(".hf-panel-gear")
) {
return;
}
if (
/** @type {Element} */
event.target.closest("button")
) {
return;
}
setCollapsed(!collapsed);
});
gear.addEventListener("mouseenter", () => {
gear.style.color = "#fff";
});
gear.addEventListener("mouseleave", () => {
gear.style.color = "var(--hf-text-muted)";
});
setTab(activeTabId);
setCollapsed(collapsed);
let stopWatching = null;
const ensureMounted = () => {
if (isFixed) {
if (root.parentElement !== document.body) {
document.body.appendChild(root);
}
return;
}
if (mountConfig) {
mountElement(mountConfig, root);
}
};
if (isFixed) {
ensureMounted();
} else if (mountConfig) {
stopWatching = watchMount(mountConfig, () => root);
}
const panel = {
element: root,
setTab,
refresh: () => {
if (options.getSubtitle) {
subtitle.textContent = options.getSubtitle() ?? "";
}
renderActiveTab();
},
setSubtitle: (text) => {
subtitle.textContent = text;
},
ensureMounted,
destroy: () => {
stopWatching?.();
root.remove();
PANELS.delete(options.id);
}
};
if (options.getSubtitle) {
subtitle.textContent = options.getSubtitle() ?? "";
}
PANELS.set(options.id, panel);
return panel;
}
function applyPanelLayout(el, isFixed, position) {
if (!isFixed) {
el.style.cssText = `
position: static;
width: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
`;
return;
}
el.style.cssText = `
position: fixed;
z-index: 9999;
width: min(420px, calc(100vw - 24px));
max-height: min(70vh, 560px);
display: flex;
flex-direction: column;
overflow: hidden;
`;
applyFixedPosition(el, position);
}
function applyFixedPosition(el, position) {
el.style.top = "";
el.style.right = "";
el.style.bottom = "";
el.style.left = "";
switch (position) {
case "top-right":
el.style.top = "12px";
el.style.right = "12px";
break;
case "bottom-left":
el.style.bottom = "12px";
el.style.left = "12px";
break;
case "bottom-right":
default:
el.style.bottom = "12px";
el.style.right = "12px";
break;
}
}
function emptyState(message) {
const div = document.createElement("div");
div.className = "hf-muted";
div.textContent = message;
return div;
}
var ui = {
createPanel,
emptyState
};
// src/shared/percent.js
function parsePercent(raw, fallback = 0) {
if (raw === null || raw === void 0 || raw === "") {
return fallback;
}
if (typeof raw === "number") {
return Number.isFinite(raw) ? raw : fallback;
}
const cleaned = String(raw).trim().replace("%", "").replace(",", ".");
const value = parseFloat(cleaned);
return Number.isFinite(value) ? value : fallback;
}
function weightToPercent(weight) {
return Math.round(weight * 1e3) / 10;
}
function weightSharePercent(weight, weights) {
const sum = Object.values(weights).reduce((total, w) => total + (w > 0 ? w : 0), 0);
if (sum <= 0 || weight <= 0) {
return 0;
}
return weight / sum * 100;
}
function percentToWeight(raw, fallback = 0) {
const pct = parsePercent(raw, fallback * 100);
return pct / 100;
}
function weightsToShares(weights) {
const sum = Object.values(weights).reduce((total, w) => total + (w > 0 ? w : 0), 0);
if (sum <= 0) {
return {};
}
const out = {};
for (const [stat, w] of Object.entries(weights)) {
out[stat] = w > 0 ? w / sum * 100 : 0;
}
return out;
}
function normalizeWeightsToShares(weights) {
const sum = Object.values(weights).reduce((total, w) => total + (w > 0 ? w : 0), 0);
if (sum <= 0) {
return weights;
}
const out = {};
for (const [stat, w] of Object.entries(weights)) {
out[stat] = w > 0 ? w / sum * 100 : 0;
}
return out;
}
function parseShareInputs(inputsByStat) {
const raw = {};
let sum = 0;
for (const [stat, value] of Object.entries(inputsByStat)) {
const pct = Math.max(0, parsePercent(value, 0));
raw[stat] = pct;
sum += pct;
}
if (sum <= 0) {
return raw;
}
const out = {};
for (const [stat, pct] of Object.entries(raw)) {
out[stat] = pct > 0 ? pct / sum * 100 : 0;
}
return out;
}
function bonusesToMultiplier(bonuses) {
const f = parsePercent(bonuses?.faction, 0);
const e = parsePercent(bonuses?.education, 0);
const p = parsePercent(bonuses?.property, 0);
return (1 + f / 100) * (1 + e / 100) * (1 + p / 100);
}
function multiplierToTotalPercent(multiplier) {
if (!Number.isFinite(multiplier) || multiplier <= 1) {
return 0;
}
return Math.round((multiplier - 1) * 1e3) / 10;
}
function formatPercent(pct, digits = 1) {
const rounded = Math.round(pct * Math.pow(10, digits)) / Math.pow(10, digits);
const text = Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(digits);
return `${text}%`;
}
function formatWeightSummary(weights, shortLabels) {
const parts = Object.entries(weights).filter(([, w]) => w > 0).sort((a, b) => b[1] - a[1]).map(([stat, w]) => `${shortLabels[stat] ?? stat} ${formatPercent(weightSharePercent(w, weights), 0)}`);
return parts.join(" \xB7 ");
}
var percent = {
parsePercent,
weightToPercent,
weightSharePercent,
percentToWeight,
weightsToShares,
normalizeWeightsToShares,
parseShareInputs,
bonusesToMultiplier,
multiplierToTotalPercent,
formatPercent,
formatWeightSummary
};
// src/shared/index.js
var HF = {
version: "0.4.0",
theme,
storage,
dom,
mount,
api,
ui,
percent
};
// src/panic/config.js
var LS_PANIC = "alfa_panic_navbar_config";
var SS_PENDING = "alfa_panic_nav_pending";
var STOCKS_PATH = "/page.php?sid=stocks";
var DEFAULT_CONFIG = {
preferSmartStockVault: true,
targetStock: "",
vaultStocks: [],
lockedStocks: []
};
var DEFAULT_VAULT_FALLBACK = { stocks: [], lockedStocks: [] };
function loadJson(key, fallback) {
try {
const raw = localStorage.getItem(key);
if (!raw) return { ...fallback };
const o = JSON.parse(raw);
return { ...fallback, ...o };
} catch {
return { ...fallback };
}
}
function savePanicConfig(cfg) {
try {
localStorage.setItem(LS_PANIC, JSON.stringify(cfg));
} catch (e) {
console.error("[Smart Panic] save config failed", e);
}
}
function getPanicConfig() {
return loadJson(LS_PANIC, DEFAULT_CONFIG);
}
function getSsvVault() {
return loadJson("alfa_vault_config", DEFAULT_VAULT_FALLBACK);
}
function getSsvGamble() {
return loadJson("alfa_gamble_config", { targetStock: "" });
}
function resolveTargetSymbol(gambleTarget, vaultStocks) {
const list = (Array.isArray(vaultStocks) ? vaultStocks : []).map((s) => String(s).toUpperCase());
const gt = String(gambleTarget || "").toUpperCase();
if (gt && list.includes(gt)) return gt;
return String(list[0] || "").toUpperCase();
}
function getEffectiveVaultAndTarget() {
const panic = getPanicConfig();
if (panic.preferSmartStockVault) {
const v = getSsvVault();
const g = getSsvGamble();
const stocks2 = (Array.isArray(v.stocks) ? v.stocks : []).map((s) => String(s).toUpperCase());
const lockedStocks2 = (Array.isArray(v.lockedStocks) ? v.lockedStocks : []).map((s) => String(s).toUpperCase());
const target2 = resolveTargetSymbol(g.targetStock || "", stocks2);
return { vaultStocks: stocks2, lockedStocks: lockedStocks2, targetStock: target2, source: "ssv" };
}
const stocks = (Array.isArray(panic.vaultStocks) ? panic.vaultStocks : []).map((s) => String(s).toUpperCase());
const lockedStocks = (Array.isArray(panic.lockedStocks) ? panic.lockedStocks : []).map((s) => String(s).toUpperCase());
const target = resolveTargetSymbol(panic.targetStock || "", stocks);
return { vaultStocks: stocks, lockedStocks, targetStock: target, source: "local" };
}
function isStocksPage() {
return /[?&]sid=stocks\b/.test(location.search || "");
}
function stocksUrl() {
return `https://www.torn.com${STOCKS_PATH}`;
}
function parseSymbolsInput(str) {
if (!str || typeof str !== "string") return [];
return str.split(/[\s,]+/).map((s) => s.trim().toUpperCase()).filter(Boolean);
}
// src/panic/money.js
function parseTornNumber(val) {
if (typeof val !== "string") return 0;
val = val.trim().toLowerCase();
if (!val) return 0;
if (val.endsWith("k")) return parseFloat(val.replace("k", "")) * 1e3;
if (val.endsWith("m")) return parseFloat(val.replace("m", "")) * 1e6;
if (val.endsWith("b")) return parseFloat(val.replace("b", "")) * 1e9;
return parseFloat(val.replace(/,/g, "")) || 0;
}
function getMoneyFromNavbar() {
const el = document.getElementById("user-money");
if (!el) return null;
const dm = el.getAttribute("data-money");
if (dm !== null && dm !== "") {
const n = parseFloat(dm);
if (!Number.isNaN(n)) return n;
}
const t = el.textContent || "";
if (t) {
const n = parseTornNumber(t.replace(/^\$/, ""));
if (!Number.isNaN(n) && n >= 0) return n;
}
return null;
}
async function getMoneyApiFallback() {
const key = localStorage.getItem("alfa_vault_apikey");
if (!key) return null;
try {
const res = await fetch(`https://api.torn.com/v2/user/money?key=${encodeURIComponent(key)}&ts=${Date.now()}`);
const data = await res.json();
if (data.error) return null;
if (data.money && typeof data.money.wallet === "number") return data.money.wallet;
} catch (e) {
console.warn("[Smart Panic] API money fallback failed", e);
}
return null;
}
async function getCashForPanic() {
const dom2 = getMoneyFromNavbar();
if (dom2 !== null && dom2 !== void 0 && !Number.isNaN(dom2)) return dom2;
const api2 = await getMoneyApiFallback();
return api2 !== null && api2 !== void 0 ? api2 : 0;
}
function formatMoneyWhole(amount) {
return `$${Math.round(Number(amount) || 0).toLocaleString("en-US", {
maximumFractionDigits: 0,
minimumFractionDigits: 0
})}`;
}
function tryUpdateGambleCashFromNavbar() {
const el = document.getElementById("gamble-cash-value");
if (!el || !document.getElementById("user-money")) return false;
const domCash = getMoneyFromNavbar();
if (domCash === null || domCash === void 0 || Number.isNaN(domCash)) return false;
el.textContent = formatMoneyWhole(domCash);
return true;
}
async function tryUpdateGambleCashWithApiFallback() {
const el = document.getElementById("gamble-cash-value");
if (!el) return;
const w = await getMoneyApiFallback();
if (w !== null && w !== void 0 && !Number.isNaN(w)) el.textContent = formatMoneyWhole(w);
}
function refreshGambleCashAfterPanicTrade() {
tryUpdateGambleCashFromNavbar();
setTimeout(() => tryUpdateGambleCashFromNavbar(), 400);
setTimeout(() => tryUpdateGambleCashFromNavbar(), 800);
setTimeout(() => {
if (!tryUpdateGambleCashFromNavbar()) void tryUpdateGambleCashWithApiFallback();
}, 1400);
}
// src/panic/stocks-page.js
function scanStocksFromDom() {
const map = /* @__PURE__ */ new Map();
document.querySelectorAll('ul[class^="stock_"]').forEach((ul) => {
const img = ul.querySelector('img[src*="logos/"]');
if (!img) return;
const src = img.getAttribute("src") || "";
const m = src.match(/logos\/([^/.]+)\.svg/i);
if (!m) return;
const sym = m[1].toUpperCase();
const stockId = ul.getAttribute("id");
const priceEl = ul.querySelector('div[class^="price_"]');
const priceTxt = priceEl ? priceEl.textContent : "";
const price = parseFloat(String(priceTxt).replace(/,/g, "")) || 0;
if (stockId && sym) map.set(sym, { stockId, price });
});
return map;
}
function waitForStockRow(symbol, maxMs = 12e3) {
const sym = String(symbol || "").toUpperCase();
return new Promise((resolve, reject) => {
const t0 = Date.now();
const tick = () => {
const row = scanStocksFromDom().get(sym);
if (row && row.price > 0 && row.stockId) {
resolve(row);
return;
}
if (Date.now() - t0 > maxMs) {
reject(new Error("Stock list not ready or symbol not found"));
return;
}
setTimeout(tick, 200);
};
tick();
});
}
// src/panic/trade.js
function getRFC() {
const c = document.cookie.match(/rfc_v=([^;]+)/);
return c ? c[1] : "";
}
async function postBuyShares(stockId, amount) {
const rfc = getRFC();
const url = `https://www.torn.com/page.php?sid=StockMarket&step=buyShares&rfcv=${encodeURIComponent(rfc)}`;
const body = new URLSearchParams();
body.set("stockId", stockId);
body.set("amount", String(amount));
const res = await fetch(url, {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"X-Requested-With": "XMLHttpRequest"
},
body: body.toString()
});
const text = await res.text();
let data;
try {
data = JSON.parse(text);
} catch {
throw new Error("Invalid response from Torn");
}
if (!data.success) throw new Error(data.message || "Trade failed");
return data;
}
async function runPanic(btn, setStatus, openSettings2) {
const { vaultStocks, lockedStocks, targetStock: sym } = getEffectiveVaultAndTarget();
if (!sym || !vaultStocks.includes(sym)) {
setStatus("Configure target stock (Settings)", true);
openSettings2();
return;
}
if (lockedStocks.includes(sym)) {
setStatus("Target stock is locked", true);
return;
}
if (!isStocksPage()) {
try {
sessionStorage.setItem(SS_PENDING, "1");
} catch {
}
setStatus("Opening stocks\u2026 tap Panic again", false);
window.location.href = stocksUrl();
return;
}
if (btn) {
btn.disabled = true;
btn.classList.add("is-busy");
}
setStatus("Working\u2026", false);
try {
const cash = await getCashForPanic();
const row = await waitForStockRow(sym);
const shares = Math.floor(cash / row.price);
if (shares <= 0) {
setStatus("No cash to deposit", true);
return;
}
await postBuyShares(row.stockId, shares);
setStatus(`Deposited ${shares.toLocaleString()} shares`, false);
refreshGambleCashAfterPanicTrade();
try {
sessionStorage.removeItem(SS_PENDING);
} catch {
}
} catch (e) {
console.error("[Smart Panic]", e);
setStatus(e instanceof Error ? e.message : "Failed", true);
} finally {
if (btn) {
btn.disabled = false;
btn.classList.remove("is-busy");
}
}
}
// src/panic/settings.js
function openSettings(setStatus) {
const existing = document.querySelector(".hf-panic-settings-overlay");
if (existing?.isConnected) return;
existing?.remove();
HF.theme.injectStyles();
const panic = getPanicConfig();
const overlay = document.createElement("div");
overlay.className = "hf-torn hf-overlay hf-panic-settings-overlay";
overlay.style.cssText = `
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
background: rgba(0, 0, 0, 0.62);
backdrop-filter: blur(2px);
`;
const modal = document.createElement("div");
modal.className = "hf-modal hf-torn hf-panic-settings-modal";
modal.style.cssText = "width: min(420px, 94vw); max-height: 90vh; overflow-y: auto; padding: 18px;";
modal.innerHTML = `
<h2>
Smart Panic settings
<button type="button" class="hf-btn hf-panic-close" title="Close">\xD7</button>
</h2>
<div class="hf-panic-field hf-panic-check">
<input type="checkbox" id="hf-panic-prefer-ssv" ${panic.preferSmartStockVault ? "checked" : ""}>
<label for="hf-panic-prefer-ssv">
Use Smart Stock Vault settings when available (vault list, locks, gamble target)
</label>
</div>
<div class="hf-panic-field" id="hf-panic-standalone-fields">
<label class="hf-label" for="hf-panic-vault-symbols">Vault symbols (standalone)</label>
<input class="hf-input" type="text" id="hf-panic-vault-symbols" placeholder="e.g. MUN, SYM, TCI" autocomplete="off">
<label class="hf-label" for="hf-panic-target" style="margin-top:12px">Target stock</label>
<select class="hf-select" id="hf-panic-target"></select>
</div>
<div class="hf-panic-field hf-panic-hint" id="hf-panic-ssv-hint" hidden>
Target and vault come from Smart Stock Vault. Configure there, or turn off the checkbox above.
</div>
<div class="hf-panic-actions">
<button type="button" class="hf-btn hf-btn--save" id="hf-panic-save">Save</button>
<button type="button" class="hf-btn" id="hf-panic-cancel">Cancel</button>
</div>
`;
overlay.appendChild(modal);
document.body.appendChild(overlay);
overlay.addEventListener("click", (e) => {
if (e.target === overlay) overlay.remove();
});
modal.addEventListener("click", (e) => e.stopPropagation());
const preferEl = modal.querySelector("#hf-panic-prefer-ssv");
const standaloneFields = modal.querySelector("#hf-panic-standalone-fields");
const ssvHint = modal.querySelector("#hf-panic-ssv-hint");
const vaultInput = modal.querySelector("#hf-panic-vault-symbols");
const targetSel = modal.querySelector("#hf-panic-target");
vaultInput.value = (panic.vaultStocks || []).join(", ");
function refreshStandaloneVisibility() {
const useSsv = preferEl.checked;
standaloneFields.hidden = useSsv;
ssvHint.hidden = !useSsv;
}
function fillTargetOptions() {
const useSsv = preferEl.checked;
const stocks = useSsv ? (getSsvVault().stocks || []).map((s) => String(s).toUpperCase()) : parseSymbolsInput(vaultInput.value);
targetSel.innerHTML = "";
const cur = useSsv ? resolveTargetSymbol(getSsvGamble().targetStock || "", stocks) : String(panic.targetStock || "").toUpperCase();
stocks.forEach((sym) => {
const opt = document.createElement("option");
opt.value = sym;
opt.textContent = sym;
if (sym === cur) opt.selected = true;
targetSel.appendChild(opt);
});
if (stocks.length === 0) {
const opt = document.createElement("option");
opt.value = "";
opt.textContent = "Add symbols first";
targetSel.appendChild(opt);
}
}
preferEl.addEventListener("change", () => {
refreshStandaloneVisibility();
fillTargetOptions();
});
vaultInput.addEventListener("input", () => {
if (!preferEl.checked) fillTargetOptions();
});
refreshStandaloneVisibility();
fillTargetOptions();
modal.querySelector(".hf-panic-close")?.addEventListener("click", () => overlay.remove());
modal.querySelector("#hf-panic-cancel")?.addEventListener("click", () => overlay.remove());
modal.querySelector("#hf-panic-save")?.addEventListener("click", () => {
const vaultParsed = parseSymbolsInput(vaultInput.value);
const next = {
preferSmartStockVault: preferEl.checked,
vaultStocks: preferEl.checked ? panic.vaultStocks || [] : vaultParsed,
lockedStocks: panic.lockedStocks || []
};
if (preferEl.checked) {
next.targetStock = panic.targetStock || "";
} else {
if (!vaultParsed.length) {
alert("Add at least one vault symbol for standalone mode.");
return;
}
const t = targetSel.value || "";
next.targetStock = t && vaultParsed.includes(t) ? t : resolveTargetSymbol("", vaultParsed);
next.vaultStocks = vaultParsed;
}
savePanicConfig({ ...DEFAULT_CONFIG, ...panic, ...next });
overlay.remove();
setStatus("Settings saved", false);
});
}
// src/panic/navbar.js
var ROOT_ID = "hf-panic-root";
function isMobileLayout() {
return window.matchMedia("(max-width: 784px)").matches;
}
function findEnergyBarLink() {
const selectors = [
'a.bar-link[class*="energy"]',
'a[class*="bar-link"][class*="energy"]',
'a[href*="sid=energy"]',
'a[href*="/energy"]'
];
for (const sel of selectors) {
const el = document.querySelector(sel);
if (el?.parentNode) return el;
}
return null;
}
function findInsertBeforeEnergy() {
const energy = findEnergyBarLink();
if (energy?.parentNode) return { parent: energy.parentNode, before: energy };
const money = document.getElementById("user-money");
if (money) {
let p = money.parentElement;
for (let i = 0; i < 6 && p; i++) {
const g = p.querySelector('a.bar-link[class*="energy"], a[class*="energy"][class*="bar-link"]');
if (g?.parentNode) return { parent: g.parentNode, before: g };
p = p.parentElement;
}
if (money.parentNode) return { parent: money.parentNode, before: money.nextElementSibling };
}
return null;
}
function findMobileHeaderRow() {
const money = document.getElementById("user-money");
if (!money?.parentNode) return null;
let p = money;
for (let i = 0; i < 8 && p; i++) {
const rect = p.getBoundingClientRect();
if (p.tagName === "UL" || p.tagName === "DIV" || p.tagName === "NAV") {
if (rect.width > 200) return p;
}
p = p.parentElement;
}
return money.parentElement;
}
function mountNavbar(setStatus, openSettingsFn) {
if (document.getElementById(ROOT_ID)) return true;
const root = document.createElement("div");
root.id = ROOT_ID;
root.className = "hf-torn hf-panic-root";
root.dataset.hfPanic = "1";
const stack = document.createElement("div");
stack.className = "hf-panic-stack";
const wrap = document.createElement("div");
wrap.className = "hf-panic-btn-wrap";
const btn = document.createElement("button");
btn.type = "button";
btn.className = "hf-panic-btn";
btn.textContent = "Panic";
const gear = document.createElement("button");
gear.type = "button";
gear.className = "hf-panic-gear";
gear.setAttribute("aria-label", "Smart Panic settings");
gear.innerHTML = "⚙";
const status = document.createElement("div");
status.className = "hf-panic-status";
status.id = "hf-panic-status";
btn.addEventListener("click", () => runPanic(btn, setStatus, openSettingsFn));
gear.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
openSettingsFn();
});
wrap.appendChild(btn);
wrap.appendChild(gear);
stack.appendChild(wrap);
stack.appendChild(status);
root.appendChild(stack);
if (isMobileLayout()) root.classList.add("hf-panic-root--mobile");
const anchor = findInsertBeforeEnergy();
if (anchor) {
anchor.parent.insertBefore(root, anchor.before);
return true;
}
if (isMobileLayout()) {
const rowHost = findMobileHeaderRow();
if (rowHost) {
if (getComputedStyle(rowHost).display === "flex" || rowHost.querySelector(".bar-link")) {
rowHost.appendChild(root);
} else {
const outer = document.createElement("div");
outer.style.cssText = "display:flex;width:100%;justify-content:center;padding:4px 0;";
outer.appendChild(root);
rowHost.parentNode?.insertBefore(outer, rowHost.nextSibling);
}
return true;
}
}
return false;
}
function createStatusSetter() {
let timer = null;
return function setStatus(msg, isError = false) {
const el = document.getElementById("hf-panic-status");
if (!el) return;
if (timer) {
clearTimeout(timer);
timer = null;
}
el.textContent = msg || "";
el.classList.toggle("is-error", !!isError);
el.classList.toggle("is-ok", !!msg && !isError);
if (msg && !isError) {
timer = window.setTimeout(() => {
el.textContent = "";
el.classList.remove("is-error", "is-ok");
timer = null;
}, 4e3);
}
};
}
// src/panic/styles.js
var STYLE_ID2 = "hf-panic-styles";
function injectPanicStyles() {
if (document.getElementById(STYLE_ID2)) return;
const style = document.createElement("style");
style.id = STYLE_ID2;
style.textContent = `
.hf-panic-root {
display: flex;
flex-direction: column;
align-items: stretch;
vertical-align: middle;
box-sizing: border-box;
flex: 1 1 0%;
min-width: 0;
width: 100%;
max-width: none;
margin: 0 4px;
font-family: var(--hf-font);
}
.hf-panic-root.hf-panic-root--mobile {
margin: 2px 6px;
}
.hf-panic-stack {
display: flex;
flex-direction: column;
align-items: stretch;
width: 100%;
box-sizing: border-box;
}
.hf-panic-btn-wrap {
position: relative;
width: 100%;
box-sizing: border-box;
flex-shrink: 0;
}
.hf-panic-btn {
display: block;
width: 100%;
box-sizing: border-box;
height: 33px;
min-height: 33px;
max-height: 33px;
padding: 0 24px 0 10px;
margin: 0;
font: inherit;
font-size: 12px;
font-weight: 700;
line-height: 31px;
border-radius: 5px;
border: 1px solid var(--hf-danger-border);
background: var(--hf-danger-bg);
color: var(--hf-danger);
cursor: pointer;
text-align: center;
transition: transform 0.1s ease, background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
transform: scale(1);
transform-origin: center center;
}
.hf-panic-btn:hover,
.hf-panic-btn:focus-visible {
background: var(--hf-danger);
border-color: var(--hf-danger);
color: #fff;
outline: none;
}
.hf-panic-btn:active:not(:disabled),
.hf-panic-btn.is-busy {
transform: scale(0.96);
}
.hf-panic-btn:disabled {
opacity: 0.85;
cursor: wait;
}
.hf-panic-gear {
appearance: none;
position: absolute;
top: 50%;
right: 7px;
width: 16px;
height: 16px;
min-width: 16px;
min-height: 16px;
padding: 0;
margin: 0;
border: 0;
border-radius: 50%;
background: transparent;
color: var(--hf-text-muted);
cursor: pointer;
font: inherit;
font-size: 11px;
line-height: 16px;
text-align: center;
z-index: 2;
opacity: 0.8;
transform: translateY(-50%);
transition: color 0.15s ease, opacity 0.15s ease, transform 0.1s ease;
}
.hf-panic-gear:hover,
.hf-panic-gear:focus-visible {
color: var(--hf-accent-text);
opacity: 1;
outline: none;
}
.hf-panic-gear:active {
transform: translateY(-50%) scale(0.9);
}
.hf-panic-status {
font-size: 10px;
line-height: 1.25;
margin-top: 3px;
height: 2.5em;
min-height: 2.5em;
max-height: 2.5em;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
word-break: break-word;
box-sizing: border-box;
color: var(--hf-text-muted);
}
.hf-panic-status.is-error {
color: var(--hf-danger);
}
.hf-panic-status.is-ok {
color: var(--hf-success);
}
.hf-panic-settings-overlay {
z-index: 2147483640;
}
.hf-panic-settings-modal h2 {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin: 0 0 14px;
font-size: 15px;
color: var(--hf-accent-text);
}
.hf-panic-settings-modal .hf-panic-close {
padding: 2px 8px;
line-height: 1.2;
}
.hf-panic-field {
margin-bottom: 14px;
}
.hf-panic-check {
display: flex;
align-items: flex-start;
gap: 10px;
min-height: 44px;
font-size: 12px;
line-height: 1.45;
color: var(--hf-text);
}
.hf-panic-check input {
width: 18px;
height: 18px;
margin-top: 2px;
flex-shrink: 0;
accent-color: var(--hf-accent);
}
.hf-panic-hint {
font-size: 11px;
color: var(--hf-text-muted);
line-height: 1.45;
}
.hf-panic-actions {
display: flex;
gap: 8px;
margin-top: 16px;
}
.hf-panic-actions .hf-btn {
flex: 1;
min-height: 40px;
font-weight: 700;
}
.hf-panic-actions .hf-btn--save {
background: var(--hf-success-bg);
border-color: var(--hf-success-border);
color: var(--hf-success);
}
.hf-panic-actions .hf-btn--save:hover {
filter: brightness(1.12);
color: var(--hf-success);
}
`;
document.head.appendChild(style);
}
// src/panic/main.js
function maybeShowPendingToast(setStatus) {
if (!isStocksPage()) return;
try {
if (sessionStorage.getItem(SS_PENDING) === "1") {
setStatus("Tap Panic to deposit all cash", false);
}
} catch {
}
}
function init() {
HF.theme.injectStyles();
injectPanicStyles();
const setStatus = createStatusSetter();
const openSettingsModal = () => openSettings(setStatus);
let tries = 0;
const maxTries = 80;
const timer = window.setInterval(() => {
tries += 1;
if (mountNavbar(setStatus, openSettingsModal) || tries >= maxTries) {
clearInterval(timer);
if (tries >= maxTries && !document.getElementById("hf-panic-root")) {
console.warn("[Smart Panic] Could not find header anchor; retrying on DOM mutations");
const obs = new MutationObserver(() => {
if (mountNavbar(setStatus, openSettingsModal)) obs.disconnect();
});
obs.observe(document.body, { childList: true, subtree: true });
}
}
}, 250);
maybeShowPendingToast(setStatus);
window.addEventListener("pageshow", () => {
setTimeout(() => maybeShowPendingToast(setStatus), 300);
});
let resizeT;
window.addEventListener("resize", () => {
clearTimeout(resizeT);
resizeT = window.setTimeout(() => {
if (!document.getElementById("hf-panic-root")) {
mountNavbar(setStatus, openSettingsModal);
}
}, 300);
});
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();