TORN: TornTools - Properties Filter

Filter the properties you and your spouse own.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name             TORN: TornTools - Properties Filter
// @namespace        torntools.properties-filter
// @version          1.0.4
// @author           DeKleineKobini [2114440] and the TornTools team
// @description      Filter the properties you and your spouse own.
// @license          GPL-3.0-or-later
// @icon             https://www.google.com/s2/favicons?sz=64&domain=torn.com
// @supportURL       https://github.com/Mephiles/torntools_extension/issues
// @match            https://*.torn.com/properties.php*
// @grant            GM.getValue
// @grant            GM.info
// @grant            GM.setValue
// @grant            GM_addStyle
// @grant            unsafeWindow
// @run-at           document-end
// @contributionURL  https://buymeacoffee.com/dekleinekobini
// ==/UserScript==

(function() {
	"use strict";
	var s = new Set();
	var _css = async (t) => {
		if (s.has(t)) return;
		s.add(t);
		((c) => {
			if (typeof GM_addStyle === "function") GM_addStyle(c);
			else (document.head || document.documentElement).appendChild(document.createElement("style")).append(c);
		})(t);
	};
	_css(".tt-loading-placeholder{content:var(--default-preloader-url,url(https://www.torn.com/images/v2/main/ajax-loader.gif));margin:0 auto;padding:10px;display:none}.tt-loading-placeholder.active{display:block}");
	(() => {
		if (typeof window === "undefined" || window.location.href.endsWith("/_generated_background_page.html")) return "BACKGROUND";
		else if (typeof browser === "object" && browser.action) return "POPUP";
		else if (typeof location !== "undefined" && location.protocol?.includes("extension")) return "INTERNAL_CONTENT";
		else return "CONTENT";
	})();
	var TO_MILLIS = {
		SECONDS: 1e3,
		MINUTES: 6e4,
		HOURS: 36e5,
		DAYS: 864e5
	};
	function getUUID() {
		return `_${Math.random().toString(36).slice(2, 11)}`;
	}
	function toNumericVersion(version) {
		return parseInt(version.split(".").map((part) => part.padStart(3, "0")).join("").padEnd(9, "9"));
	}
	function isTabFocused() {
		return document.hasFocus();
	}
	function checkListener(listener, entry) {
		const element = listener.parent.querySelector(listener.selector);
		if (!(listener.invert ? !element : !!element)) return false;
		if (listener.timeoutId) clearTimeout(listener.timeoutId);
		entry.listeners.delete(listener);
		listener.resolve(listener.invert ? true : element);
		cleanupEntryIfEmpty(entry);
		return true;
	}
	function cleanupEntryIfEmpty(entry) {
		if (entry.listeners.size > 0) return;
		entry.observer.disconnect();
		observerRegistry.delete(entry.parent);
	}
	function removeListenerFromRegistry(listener) {
		const entry = observerRegistry.get(listener.parent);
		if (!entry) return;
		entry.listeners.delete(listener);
		cleanupEntryIfEmpty(entry);
	}
	function requireElement(selector, attributes = {}) {
		const options = {
			invert: false,
			parent: document,
			timeout: TO_MILLIS.SECONDS * 5,
			observerOptions: {
				childList: true,
				subtree: true
			},
			...attributes
		};
		const error = new Error("Maximum cycles reached.");
		return new Promise((resolve, reject) => {
			const element = options.parent.querySelector(selector);
			if (options.invert && !element) {
				resolve(true);
				return;
			} else if (!options.invert && element) {
				resolve(element);
				return;
			}
			const timeoutId = options.timeout > 0 ? window.setTimeout(() => {
				removeListenerFromRegistry(listener);
				reject(error);
			}, options.timeout) : null;
			const listener = {
				selector,
				invert: options.invert,
				parent: options.parent,
				resolve,
				reject,
				timeoutId
			};
			getOrCreateObserverEntry(options.parent).listeners.add(listener);
		});
	}
	var observerRegistry = new Map();
	function getOrCreateObserverEntry(parent) {
		const existing = observerRegistry.get(parent);
		if (existing) return existing;
		const observer = new MutationObserver(() => {
			const entry = observerRegistry.get(parent);
			if (!entry) return;
			entry.listeners.forEach((listener) => checkListener(listener, entry));
		});
		const entry = {
			parent,
			observer,
			listeners: new Set()
		};
		observerRegistry.set(parent, entry);
		observer.observe(parent, {
			childList: true,
			subtree: true
		});
		return entry;
	}
	function requireDOMInteractive() {
		return new Promise((resolve) => {
			if (document.readyState === "loading") document.addEventListener("readystatechange", () => resolve(), { once: true });
			else resolve();
		});
	}
	function svgImport(svgImport) {
		if (typeof svgImport !== "string") return (attributes = {}) => createFallbackElement(attributes);
		if (svgImport.startsWith("data:image/svg+xml")) {
			const encodedData = svgImport.substring(19);
			let svgContent;
			try {
				svgContent = decodeURIComponent(encodedData);
			} catch (error) {
				console.error("Failed to decode SVG data URL", error);
				return (attributes = {}) => createFallbackElement(attributes);
			}
			return (attributes = {}) => createSvgElement(svgContent, attributes);
		}
		return (attributes = {}) => createSvgElement(svgImport, attributes);
	}
	function createFallbackElement(attributes) {
		const svgNS = "http://www.w3.org/2000/svg";
		const svg = document.createElementNS(svgNS, "svg");
		svg.setAttribute("width", "24");
		svg.setAttribute("height", "24");
		svg.setAttribute("viewBox", "0 0 24 24");
		Object.entries(attributes).filter(([, value]) => value !== false && value !== null && value !== void 0).map(([key, value]) => svg.setAttribute(key, String(value)));
		const rect = document.createElementNS(svgNS, "rect");
		rect.setAttribute("x", "0");
		rect.setAttribute("y", "0");
		rect.setAttribute("width", "24");
		rect.setAttribute("height", "24");
		rect.setAttribute("fill", "red");
		svg.appendChild(rect);
		return svg;
	}
	function createSvgElement(svgContent, attributes = {}) {
		const fullAttributes = {
			width: "size" in attributes ? attributes.size : "1em",
			height: "size" in attributes ? attributes.size : "1em",
			...attributes
		};
		const svg = elementBuilder({
			type: "template",
			html: svgContent.trim()
		}).content.firstChild;
		if (!isSVGElement(svg)) return createFallbackElement(fullAttributes);
		Object.entries(fullAttributes).filter(([, value]) => value !== false && value !== null && value !== void 0).forEach(([key, value]) => svg.setAttribute(key, String(value)));
		return svg;
	}
	var caret_down_fill_default = "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20256%20256'%20fill='currentColor'%3e%3cpath%20d='M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,48,88H208a8,8,0,0,1,5.66,13.66Z'/%3e%3c/svg%3e";
	var funnel_fill_default = "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20256%20256'%20fill='currentColor'%3e%3cpath%20d='M227.81,66.76l-.08.09L160,139.17v55.49A16,16,0,0,1,152.87,208l-32,21.34A16,16,0,0,1,96,216V139.17L28.27,66.85l-.08-.09A16,16,0,0,1,40,40H216a16,16,0,0,1,11.84,26.76Z'/%3e%3c/svg%3e";
	var funnel_x_fill_default = "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20256%20256'%20fill='currentColor'%3e%3cpath%20d='M227.73,66.85,160,139.17v55.49A16,16,0,0,1,152.87,208l-32,21.34A16,16,0,0,1,96,216V139.17L28.27,66.85l-.08-.09A16,16,0,0,1,40,40H216a16,16,0,0,1,11.84,26.76ZM227.31,192l18.35-18.34a8,8,0,0,0-11.32-11.32L216,180.69l-18.34-18.35a8,8,0,0,0-11.32,11.32L204.69,192l-18.35,18.34a8,8,0,0,0,11.32,11.32L216,203.31l18.34,18.35a8,8,0,0,0,11.32-11.32Z'/%3e%3c/svg%3e";
	var lazyImport = (svgData) => {
		let factory;
		return (attributes) => {
			factory ??= svgImport(svgData);
			return factory(attributes);
		};
	};
	var PHFillCaretDown = lazyImport(caret_down_fill_default);
	var PHFillFunnel = lazyImport(funnel_fill_default);
	var PHFillFunnelX = lazyImport(funnel_x_fill_default);
	var mobile;
	var tablet;
	var hasSidebar;
	var tabletHorizontal;
	var tabletVertical;
	function elementBuilder(options) {
		if (typeof options === "string") return document.createElement(options);
		else if (typeof options === "object") {
			options = {
				id: void 0,
				class: void 0,
				text: void 0,
				html: void 0,
				value: void 0,
				href: void 0,
				children: [],
				attributes: {},
				events: {},
				style: {},
				dataset: {},
				...options
			};
			const newElement = document.createElement(options.type);
			if (options.id) newElement.id = options.id;
			if (options.class) newElement.className = Array.isArray(options.class) ? options.class.filter((name) => !!name).join(" ") : options.class.trim();
			if (options.text !== void 0) newElement.textContent = options.text.toString();
			if (options.html) newElement.innerHTML = options.html;
			if (options.value && "value" in newElement) {
				if (typeof options.value === "function") newElement.value = options.value();
				else newElement.value = options.value;
			}
			if (options.href && "href" in newElement) newElement.href = options.href;
			for (const child of options.children?.filter((child) => !!child) || []) if (typeof child === "string") newElement.appendChild(document.createTextNode(child));
			else newElement.appendChild(child);
			if (options.attributes) {
				let attributes = options.attributes;
				if (typeof attributes === "function") attributes = attributes();
				for (const attribute in attributes) newElement.setAttribute(attribute, attributes[attribute].toString());
			}
			for (const event in options.events) newElement.addEventListener(event, options.events[event]);
			for (const key in options.style) newElement.style[key] = options.style[key];
			for (const key in options.dataset) if (typeof options.dataset[key] === "object") newElement.dataset[key] = JSON.stringify(options.dataset[key]);
			else newElement.dataset[key] = options.dataset[key].toString();
			return newElement;
		} else throw new Error("Invalid options provided to newElement.");
	}
	function findAllElements(selector, parent = document) {
		return Array.from(parent.querySelectorAll(selector));
	}
	async function checkDevice() {
		await requireDOMInteractive();
		const innerWidth = window.innerWidth;
		mobile = innerWidth <= 600;
		tablet = innerWidth <= 1e3 && innerWidth >= 600;
		hasSidebar = innerWidth > 1e3;
		tabletHorizontal = tablet && innerWidth >= 784;
		tabletVertical = tablet && !tabletHorizontal;
		return {
			mobile,
			tablet,
			tabletHorizontal,
			tabletVertical,
			hasSidebar
		};
	}
	function getHashParameters(hash) {
		if (!hash) hash = location.hash;
		if (hash.startsWith("#/") || hash.startsWith("#!")) hash = hash.substring(2);
		else if (hash.startsWith("#") || hash.startsWith("/")) hash = hash.substring(1);
		if (!hash.startsWith("!")) hash = `?${hash}`;
		return new URLSearchParams(hash);
	}
	function findParent(element, partialOptions = {}) {
		const options = {
			tag: void 0,
			class: void 0,
			partialClass: void 0,
			id: void 0,
			hasAttribute: void 0,
			maxAttempts: -1,
			currentAttempt: 1,
			...partialOptions
		};
		if (!element?.parentElement) return void 0;
		if (options.maxAttempts !== -1 && options.currentAttempt > options.maxAttempts) return void 0;
		if (options.tag && element.parentElement.tagName === options.tag) return element.parentElement;
		if (options.id && element.parentElement.id === options.id) return element.parentElement;
		if (options.class && element.parentElement && (Array.isArray(options.class) && options.class.some((c) => element.parentElement.classList.contains(c)) || !Array.isArray(options.class) && element.parentElement.classList.contains(options.class))) return element.parentElement;
		if (options.partialClass && Array.from(element.parentElement.classList).some((c) => c.startsWith(options.partialClass))) return element.parentElement;
		if (options.hasAttribute && element.parentElement.getAttribute(options.hasAttribute) !== null) return element.parentElement;
		return findParent(element.parentElement, {
			...options,
			currentAttempt: (options.currentAttempt ?? 0) + 1
		});
	}
	function isSVGElement(node) {
		return !!node && node instanceof SVGElement;
	}
	function isCustomEvent(event) {
		return event instanceof CustomEvent;
	}
	var FEATURE_MANAGER;
	var ttStorage;
	var RUNTIME_INFORMATION;
	var RUNTIME_STORAGE;
	var OFFLOAD_SERVICE;
	var EVENT_HANDLER;
	function setFeatureManager(featureManager) {
		FEATURE_MANAGER = featureManager;
	}
	function setTTStorage(storage) {
		ttStorage = storage;
	}
	function setRuntimeInformation(runtimeInformation) {
		RUNTIME_INFORMATION = runtimeInformation;
	}
	function setRuntimeStorage(runtimeStorage) {
		RUNTIME_STORAGE = runtimeStorage;
	}
	function setEventHandler(eventHandler) {
		EVENT_HANDLER = eventHandler;
	}
	var EVENT_CHANNELS = function(EVENT_CHANNELS) {
		EVENT_CHANNELS["CHAT_MESSAGE"] = "chat-message";
		EVENT_CHANNELS["CHAT_NEW"] = "chat-box-new";
		EVENT_CHANNELS["CHAT_OPENED"] = "chat-box-opened";
		EVENT_CHANNELS["CHAT_PEOPLE_MENU_OPENED"] = "chat-people-menu-opened";
		EVENT_CHANNELS["CHAT_SETTINGS_MENU_OPENED"] = "chat-settings-menu-opened";
		EVENT_CHANNELS["CHAT_REFRESHED"] = "chat-refreshed";
		EVENT_CHANNELS["CHAT_RECONNECTED"] = "chat-reconnected";
		EVENT_CHANNELS["CHAT_CLOSED"] = "chat-closed";
		EVENT_CHANNELS["COMPANY_EMPLOYEES_PAGE"] = "company-employees-page";
		EVENT_CHANNELS["COMPANY_STOCK_PAGE"] = "company-stock-page";
		EVENT_CHANNELS["FACTION_ARMORY_TAB"] = "faction-armory-tab";
		EVENT_CHANNELS["FACTION_CRIMES"] = "faction-crimes";
		EVENT_CHANNELS["FACTION_CRIMES2"] = "faction-crimes2";
		EVENT_CHANNELS["FACTION_CRIMES2_TAB"] = "faction-crimes2-tab";
		EVENT_CHANNELS["FACTION_CRIMES2_REFRESH"] = "faction-crimes2-refresh";
		EVENT_CHANNELS["FACTION_GIVE_TO_USER"] = "faction-give-to-user";
		EVENT_CHANNELS["FACTION_UPGRADE_INFO"] = "faction-upgrade-info";
		EVENT_CHANNELS["FACTION_INFO"] = "faction-info";
		EVENT_CHANNELS["FACTION_MAIN"] = "faction-main";
		EVENT_CHANNELS["FACTION_NATIVE_FILTER"] = "faction-filter_native";
		EVENT_CHANNELS["FACTION_NATIVE_SORT"] = "faction-sort_native";
		EVENT_CHANNELS["FACTION_NATIVE_ICON_UPDATE"] = "faction-icon_update_native";
		EVENT_CHANNELS["FF_SCOUTER_GAUGE"] = "ff-scouter-gauge";
		EVENT_CHANNELS["ITEM_AMOUNT"] = "item-amount";
		EVENT_CHANNELS["ITEM_EQUIPPED"] = "item-equipped";
		EVENT_CHANNELS["ITEM_ITEMS_LOADED"] = "item-items-loaded";
		EVENT_CHANNELS["ITEM_SWITCH_TAB"] = "item-switch-tab";
		EVENT_CHANNELS["HOSPITAL_SWITCH_PAGE"] = "hospital-switch-page";
		EVENT_CHANNELS["JAIL_SWITCH_PAGE"] = "jail-switch-page";
		EVENT_CHANNELS["USERLIST_SWITCH_PAGE"] = "userlist-switch-page";
		EVENT_CHANNELS["TRAVEL_SELECT_TYPE"] = "travel-select-type";
		EVENT_CHANNELS["TRAVEL_SELECT_COUNTRY"] = "travel-select-country";
		EVENT_CHANNELS["TRAVEL_DESTINATION_UPDATE"] = "travel-destination-update";
		EVENT_CHANNELS["TRAVEL_ABROAD__SHOP_LOAD"] = "TRAVEL_ABROAD__SHOP_LOAD";
		EVENT_CHANNELS["TRAVEL_ABROAD__SHOP_REFRESH"] = "TRAVEL_ABROAD__SHOP_REFRESH";
		EVENT_CHANNELS["FEATURE_ENABLED"] = "feature-enabled";
		EVENT_CHANNELS["FEATURE_DISABLED"] = "feature-disabled";
		EVENT_CHANNELS["STATE_CHANGED"] = "state-changed";
		EVENT_CHANNELS["SHOP__LOAD"] = "SHOP__LOAD";
		EVENT_CHANNELS["GYM_LOAD"] = "gym-load";
		EVENT_CHANNELS["GYM_TRAIN"] = "gym-train";
		EVENT_CHANNELS["CRIMES_LOADED"] = "crimes-loaded";
		EVENT_CHANNELS["CRIMES_CRIME"] = "crimes-crime";
		EVENT_CHANNELS["CRIMES2_HOME_LOADED"] = "crimes2-home-loaded";
		EVENT_CHANNELS["CRIMES2_BURGLARY_LOADED"] = "crimes2-burglary-loaded";
		EVENT_CHANNELS["CRIMES2_CRIME_LOADED"] = "crimes2-crime-loaded";
		EVENT_CHANNELS["MISSION_LOAD"] = "mission-load";
		EVENT_CHANNELS["MISSION_REWARDS"] = "mission-rewards";
		EVENT_CHANNELS["TRADE"] = "trade";
		EVENT_CHANNELS["PROFILE_FETCHED"] = "profile-fetched";
		EVENT_CHANNELS["FILTER_APPLIED"] = "filter-applied";
		EVENT_CHANNELS["STATS_ESTIMATED"] = "stats-estimated";
		EVENT_CHANNELS["SWITCH_PAGE"] = "switch-page";
		EVENT_CHANNELS["AUCTION_SWITCH_TYPE"] = "auction-switch-type";
		EVENT_CHANNELS["ITEMMARKET_CATEGORY_ITEMS"] = "itemmarket-category-items";
		EVENT_CHANNELS["ITEMMARKET_CATEGORY_ITEMS_UPDATE"] = "itemmarket-category-items-update";
		EVENT_CHANNELS["ITEMMARKET_ITEMS"] = "itemmarket-items";
		EVENT_CHANNELS["ITEMMARKET_ITEMS_UPDATE"] = "itemmarket-items-update";
		EVENT_CHANNELS["ITEMMARKET_ITEM_DETAILS"] = "itemmarket-item-details";
		EVENT_CHANNELS["WINDOW__FOCUS"] = "WINDOW__FOCUS";
		EVENT_CHANNELS["PROPERTIES__ROUTE"] = "PROPERTIES__ROUTE";
		EVENT_CHANNELS["PROPERTIES__ROUTE_PAGE"] = "PROPERTIES__ROUTE_PAGE";
		EVENT_CHANNELS["EFFICIENT_REHAB"] = "EFFICIENT_REHAB";
		EVENT_CHANNELS["EFFICIENT_REHAB__INJECTED"] = "EFFICIENT_REHAB__INJECTED";
		EVENT_CHANNELS["CITY_ITEMS_MAP__SET_ITEMS"] = "CITY_ITEMS_MAP__SET_ITEMS";
		EVENT_CHANNELS["CITY_ITEMS_MAP__REQUEST_MODEL_ITEMS"] = "CITY_ITEMS_MAP__REQUEST_MODEL_ITEMS";
		EVENT_CHANNELS["CITY_ITEMS_MAP__MODEL_ITEMS"] = "CITY_ITEMS_MAP__MODEL_ITEMS";
		EVENT_CHANNELS["CITY_ITEMS_MAP__CLEAR"] = "CITY_ITEMS_MAP__CLEAR";
		return EVENT_CHANNELS;
	}({});
	var ANTI_SCRAPE_EVENTS = [
		"TRAVEL_ABROAD__SHOP_LOAD",
		"chat-message",
		"chat-box-opened",
		"chat-closed",
		"chat-refreshed",
		"chat-reconnected",
		"itemmarket-category-items",
		"itemmarket-category-items-update",
		"itemmarket-items",
		"itemmarket-items-update"
	];
	function triggerCustomListener(channel, payload) {
		if (ANTI_SCRAPE_EVENTS.includes(channel) && !isTabFocused()) return;
		EVENT_HANDLER.triggerEvent(channel, payload);
	}
	function addCustomListener(channel, listener) {
		EVENT_HANDLER.registerListener(channel, listener);
	}
	function setupPropertiesPage() {
		let route = decidePropertiesRoute();
		requireElement("#properties-page-wrap").then((wrapper) => {
			requireElement(".ajax-preloader", {
				invert: true,
				parent: wrapper
			}).then(() => {
				triggerCustomListener(EVENT_CHANNELS.PROPERTIES__ROUTE, { route });
			});
			window.addEventListener("hashchange", async () => {
				const oldRoute = route;
				const newRoute = decidePropertiesRoute();
				if (!hasRouteChanged(oldRoute, newRoute)) return;
				route = newRoute;
				await requireElement(ROUTE_ROOT_MAP[newRoute.page]);
				await requireElement(".ajax-preloader", {
					invert: true,
					parent: wrapper
				});
				const tabName = getTab(newRoute.page);
				if (tabName) await requireElement(`.ui-state-active[aria-controls='${tabName}']`);
				const pagination = getPaginationPage(newRoute.page);
				if (pagination !== null) {
					await requireElement(`.page-number.active[page='${pagination}']`);
					await requireElement(".properties-list > li");
				}
				if (oldRoute.page !== newRoute.page) triggerCustomListener(EVENT_CHANNELS.PROPERTIES__ROUTE, { route: newRoute });
				else if (oldRoute.paramStart !== newRoute.paramStart || oldRoute.paramTab !== newRoute.paramTab) triggerCustomListener(EVENT_CHANNELS.PROPERTIES__ROUTE_PAGE, { route: newRoute });
			});
		});
	}
	function decidePropertiesRoute() {
		let page;
		let paramStart = null;
		let paramTab = null;
		const params = getHashParameters();
		const step = params.get("p");
		if (!step || step === "properties") {
			page = "all-properties";
			paramStart = params.has("start") ? parseInt(params.get("start")) : null;
		} else if (step === "yourProperties") {
			page = "your-properties";
			paramStart = params.has("start") ? parseInt(params.get("start")) : null;
		} else if (step === "spousesProperties") {
			page = "spouse-properties";
			paramStart = params.has("start") ? parseInt(params.get("start")) : null;
		} else if (step === "options") {
			page = "options";
			paramTab = params.get("tab");
		} else throw new Error(`Unknown properties route: ${step}`);
		return {
			page,
			paramStart,
			paramTab
		};
	}
	function hasRouteChanged(oldRoute, newRoute) {
		return oldRoute.page !== newRoute.page || oldRoute.paramStart !== newRoute.paramStart || oldRoute.paramTab !== newRoute.paramTab;
	}
	var ROUTE_ROOT_MAP = {
		"all-properties": ".properties-list",
		"your-properties": ".properties-list",
		"spouse-properties": ".properties-list",
		options: ".property-option"
	};
	function getTab(page) {
		switch (page) {
			case "all-properties": return "all";
			case "your-properties": return "your";
			case "spouse-properties": return "spouses";
			case "options": return null;
		}
	}
	function getPaginationPage(page) {
		if (page === "options") return null;
		const params = getHashParameters();
		if (!params.has("start")) return 1;
		return parseInt(params.get("start")) / 12 + 1;
	}
	var TornToolsCache = class {
		_cache;
		persistTimer = null;
		constructor() {
			this._cache = {};
		}
		set cache(value) {
			this._cache = value || {};
		}
		get cache() {
			return this._cache;
		}
		get(section, key) {
			return this.getCacheValue(section, key)?.value;
		}
		remove(section, key) {
			const actualKey = key ?? section;
			const actualSection = key ? section : null;
			if (actualSection && !this.hasValue(actualSection, actualKey) || !actualSection && !this.hasValue(actualKey.toString())) return;
			if (actualSection) delete this.cache[actualSection][actualKey];
			else delete this.cache[actualKey];
			this.schedulePersist();
		}
		hasValue(section, key) {
			return this.getCacheValue(section, key) !== null;
		}
		getCacheValue(section, key) {
			const actualKey = key ?? section;
			const actualSection = key ? section : null;
			let value = null;
			if (actualSection) {
				if (section in this.cache && actualKey in this.cache[actualSection]) value = this.cache[actualSection][actualKey];
			} else if (actualKey in this.cache) value = this.cache[actualKey];
			if (value === null || !("value" in value)) return null;
			if ("indefinite" in value) return value;
			else return value.timeout > Date.now() ? value : null;
		}
		set(object, ttl, section) {
			return this._set(object, ttl, section);
		}
		setIndefinite(object, section) {
			return this._set(object, null, section);
		}
		_set(object, ttl, section) {
			const timeout = ttl === null ? null : Date.now() + ttl;
			if (section) {
				if (!(section in this.cache)) this.cache[section] = {};
				for (const [key, value] of Object.entries(object)) this.cache[section][key] = this.createCacheValue(value, timeout);
			} else for (const [key, value] of Object.entries(object)) this.cache[key] = this.createCacheValue(value, timeout);
			this.schedulePersist();
		}
		createCacheValue(value, timeout) {
			if (timeout === null) return {
				value,
				indefinite: true
			};
			else return {
				value,
				timeout
			};
		}
		async clear(section) {
			if (section) {
				delete this.cache[section];
				this.schedulePersist();
			} else {
				this.cache = {};
				if (this.persistTimer) clearTimeout(this.persistTimer);
				this.persistTimer = null;
				await ttStorage.set({ cache: {} });
			}
		}
		async refresh() {
			let hasChanged = false;
			const now = Date.now();
			refreshObject(this.cache);
			for (const section in this.cache) if (!Object.keys(this.cache[section]).length) delete this.cache[section];
			if (hasChanged) this.schedulePersist();
			function refreshObject(object) {
				for (const key in object) {
					const value = object[key];
					if ("value" in value) {
						const cacheValue = value;
						if ("indefinite" in cacheValue || cacheValue.timeout > now) continue;
						hasChanged = true;
						delete object[key];
					} else refreshObject(value);
				}
			}
		}
		schedulePersist() {
			if (this.persistTimer) clearTimeout(this.persistTimer);
			this.persistTimer = setTimeout(() => {
				this.persistTimer = null;
				ttStorage.set({ cache: this.cache }).catch((err) => console.error("Failed to persist cache.", err));
			}, 500);
		}
	};
	var ttCache = new TornToolsCache();
	var DefaultSetting = class {
		type;
		defaultValue;
		constructor(type, defaultValue) {
			this.type = type;
			this.defaultValue = defaultValue ?? null;
		}
	};
	var DEFAULT_STORAGE = {
		version: {
			current: new DefaultSetting("string", () => RUNTIME_INFORMATION.getVersion()),
			initial: new DefaultSetting("string", () => RUNTIME_INFORMATION.getVersion()),
			oldVersion: new DefaultSetting("string"),
			showNotice: new DefaultSetting("boolean", true)
		},
		api: {
			torn: {
				key: new DefaultSetting("string"),
				online: new DefaultSetting("boolean", true),
				error: new DefaultSetting("string"),
				owner: new DefaultSetting("number")
			},
			tornstats: { key: new DefaultSetting("string") },
			yata: { key: new DefaultSetting("string") },
			ffScouter: { key: new DefaultSetting("string") }
		},
		settings: {
			updateNotice: new DefaultSetting("boolean", true),
			featureDisplay: new DefaultSetting("boolean", true),
			featureDisplayPosition: new DefaultSetting("string", "bottom-left"),
			featureDisplayOnlyFailed: new DefaultSetting("boolean", false),
			featureDisplayHideDisabled: new DefaultSetting("boolean", false),
			featureDisplayHideEmpty: new DefaultSetting("boolean", true),
			developer: new DefaultSetting("boolean", false),
			formatting: {
				tct: new DefaultSetting("boolean", false),
				date: new DefaultSetting("string", "eu"),
				time: new DefaultSetting("string", "eu")
			},
			sorting: { abroad: {
				column: new DefaultSetting("string", ""),
				order: new DefaultSetting("string", "none")
			} },
			notifications: {
				sound: new DefaultSetting("string", "default"),
				soundCustom: new DefaultSetting("string", ""),
				tts: new DefaultSetting("boolean", false),
				ttsVoice: new DefaultSetting("string", "default"),
				ttsRate: new DefaultSetting("number", 1),
				link: new DefaultSetting("boolean", true),
				volume: new DefaultSetting("number", 100),
				requireInteraction: new DefaultSetting("boolean", false),
				types: {
					global: new DefaultSetting("boolean", () => typeof Notification !== "undefined" && Notification.permission === "granted"),
					events: new DefaultSetting("boolean", true),
					messages: new DefaultSetting("boolean", true),
					status: new DefaultSetting("boolean", true),
					traveling: new DefaultSetting("boolean", true),
					cooldowns: new DefaultSetting("boolean", true),
					education: new DefaultSetting("boolean", true),
					newDay: new DefaultSetting("boolean", true),
					energy: new DefaultSetting("array", ["100%"]),
					nerve: new DefaultSetting("array", ["100%"]),
					happy: new DefaultSetting("array", ["100%"]),
					life: new DefaultSetting("array", ["100%"]),
					offline: new DefaultSetting("array", []),
					chainTimerEnabled: new DefaultSetting("boolean", true),
					chainBonusEnabled: new DefaultSetting("boolean", true),
					leavingHospitalEnabled: new DefaultSetting("boolean", true),
					landingEnabled: new DefaultSetting("boolean", true),
					cooldownDrugEnabled: new DefaultSetting("boolean", true),
					cooldownBoosterEnabled: new DefaultSetting("boolean", true),
					cooldownMedicalEnabled: new DefaultSetting("boolean", true),
					chainTimer: new DefaultSetting("array", []),
					chainBonus: new DefaultSetting("array", []),
					leavingHospital: new DefaultSetting("array", []),
					landing: new DefaultSetting("array", []),
					cooldownDrug: new DefaultSetting("array", []),
					cooldownBooster: new DefaultSetting("array", []),
					cooldownMedical: new DefaultSetting("array", []),
					stocks: new DefaultSetting("object", {}),
					missionsLimitEnabled: new DefaultSetting("boolean", false),
					missionsLimit: new DefaultSetting("string", ""),
					missionsExpireEnabled: new DefaultSetting("boolean", false),
					missionsExpire: new DefaultSetting("array", []),
					npcsGlobal: new DefaultSetting("boolean", true),
					npcs: new DefaultSetting("array", []),
					npcPlannedEnabled: new DefaultSetting("boolean", true),
					npcPlanned: new DefaultSetting("array", []),
					refillEnergyEnabled: new DefaultSetting("boolean", true),
					refillEnergy: new DefaultSetting("string", ""),
					refillNerveEnabled: new DefaultSetting("boolean", true),
					refillNerve: new DefaultSetting("string", "")
				}
			},
			apiUsage: {
				comment: new DefaultSetting("string", "TornTools"),
				delayEssential: new DefaultSetting("number", 30),
				delayBasic: new DefaultSetting("number", 120),
				delayPassive: new DefaultSetting("number", 3600),
				delayStakeouts: new DefaultSetting("number", 30),
				user: {
					bars: new DefaultSetting("boolean", true),
					cooldowns: new DefaultSetting("boolean", true),
					travel: new DefaultSetting("boolean", true),
					newevents: new DefaultSetting("boolean", true),
					newmessages: new DefaultSetting("boolean", true),
					refills: new DefaultSetting("boolean", true),
					stocks: new DefaultSetting("boolean", true),
					education: new DefaultSetting("boolean", true),
					networth: new DefaultSetting("boolean", true),
					inventory: new DefaultSetting("boolean", true),
					jobpoints: new DefaultSetting("boolean", true),
					merits: new DefaultSetting("boolean", true),
					perks: new DefaultSetting("boolean", true),
					icons: new DefaultSetting("boolean", true),
					ammo: new DefaultSetting("boolean", true),
					battlestats: new DefaultSetting("boolean", true),
					crimes: new DefaultSetting("boolean", true),
					workstats: new DefaultSetting("boolean", true),
					skills: new DefaultSetting("boolean", true),
					weaponexp: new DefaultSetting("boolean", true),
					properties: new DefaultSetting("boolean", true),
					calendar: new DefaultSetting("boolean", true),
					organizedcrime: new DefaultSetting("boolean", true),
					missions: new DefaultSetting("boolean", true),
					personalstats: new DefaultSetting("boolean", true),
					attacks: new DefaultSetting("boolean", true),
					money: new DefaultSetting("boolean", true),
					honors: new DefaultSetting("boolean", true),
					medals: new DefaultSetting("boolean", true),
					virus: new DefaultSetting("boolean", true)
				}
			},
			themes: {
				pages: new DefaultSetting("string", "default"),
				containers: new DefaultSetting("string", "default")
			},
			hideIcons: new DefaultSetting("array", []),
			hideCasinoGames: new DefaultSetting("array", []),
			hideStocks: new DefaultSetting("array", []),
			alliedFactions: new DefaultSetting("array", []),
			customLinks: new DefaultSetting("array", []),
			employeeInactivityWarning: new DefaultSetting("array", []),
			factionInactivityWarning: new DefaultSetting("array", []),
			userAlias: new DefaultSetting("array", []),
			csvDelimiter: new DefaultSetting("string", ";"),
			pages: {
				global: {
					alignLeft: new DefaultSetting("boolean", false),
					hideLevelUpgrade: new DefaultSetting("boolean", false),
					hideQuitButtons: new DefaultSetting("boolean", false),
					hideTutorials: new DefaultSetting("boolean", false),
					keepAttackHistory: new DefaultSetting("boolean", true),
					miniProfileLastAction: new DefaultSetting("boolean", true),
					reviveProvider: new DefaultSetting("string", ""),
					pageTitles: new DefaultSetting("boolean", true),
					stackingMode: new DefaultSetting("boolean", false),
					noOutsideLinkAlert: new DefaultSetting("boolean", false),
					urlFill: new DefaultSetting("boolean", true)
				},
				profile: {
					avgpersonalstats: new DefaultSetting("boolean", false),
					statusIndicator: new DefaultSetting("boolean", true),
					idBesideProfileName: new DefaultSetting("boolean", true),
					notes: new DefaultSetting("boolean", true),
					showAllyWarning: new DefaultSetting("boolean", true),
					ageToWords: new DefaultSetting("boolean", true),
					disableAllyAttacks: new DefaultSetting("boolean", true),
					box: new DefaultSetting("boolean", true),
					boxStats: new DefaultSetting("boolean", true),
					boxSpy: new DefaultSetting("boolean", true),
					boxStakeout: new DefaultSetting("boolean", true),
					boxAttackHistory: new DefaultSetting("boolean", true),
					boxFetch: new DefaultSetting("boolean", true)
				},
				chat: {
					fontSize: new DefaultSetting("number", 12),
					searchChat: new DefaultSetting("boolean", true),
					completeUsernames: new DefaultSetting("boolean", true),
					highlights: new DefaultSetting("array", [{
						name: "$player",
						color: "#7ca900"
					}]),
					titleHighlights: new DefaultSetting("array", []),
					tradeTimer: new DefaultSetting("boolean", true),
					resizable: new DefaultSetting("boolean", true),
					hideChatButton: new DefaultSetting("boolean", true),
					hideChat: new DefaultSetting("boolean", false)
				},
				sidebar: {
					notes: new DefaultSetting("boolean", true),
					highlightEnergy: new DefaultSetting("boolean", true),
					highlightNerve: new DefaultSetting("boolean", false),
					ocTimer: new DefaultSetting("boolean", true),
					oc2Timer: new DefaultSetting("boolean", true),
					oc2TimerPosition: new DefaultSetting("boolean", false),
					oc2TimerLevel: new DefaultSetting("boolean", true),
					factionOCTimer: new DefaultSetting("boolean", false),
					collapseAreas: new DefaultSetting("boolean", true),
					settingsLink: new DefaultSetting("boolean", true),
					hideGymHighlight: new DefaultSetting("boolean", false),
					hideNewspaperHighlight: new DefaultSetting("boolean", false),
					upkeepPropHighlight: new DefaultSetting("number", 0),
					barLinks: new DefaultSetting("boolean", true),
					pointsValue: new DefaultSetting("boolean", true),
					npcLootTimes: new DefaultSetting("boolean", true),
					npcLootTimesService: new DefaultSetting("string", "tornstats"),
					cooldownEndTimes: new DefaultSetting("boolean", true),
					companyAddictionLevel: new DefaultSetting("boolean", true),
					showJobPointsToolTip: new DefaultSetting("boolean", true),
					rwTimer: new DefaultSetting("boolean", true),
					virusTimer: new DefaultSetting("boolean", false)
				},
				popup: {
					dashboard: new DefaultSetting("boolean", true),
					marketSearch: new DefaultSetting("boolean", true),
					bazaarUsingExternal: new DefaultSetting("boolean", true),
					calculator: new DefaultSetting("boolean", true),
					stocksOverview: new DefaultSetting("boolean", true),
					notifications: new DefaultSetting("boolean", true),
					defaultTab: new DefaultSetting("string", "dashboard"),
					showStakeouts: new DefaultSetting("boolean", true),
					showIcons: new DefaultSetting("boolean", true),
					fullBarTime: new DefaultSetting("boolean", false)
				},
				icon: {
					global: new DefaultSetting("boolean", true),
					energy: new DefaultSetting("boolean", true),
					nerve: new DefaultSetting("boolean", true),
					happy: new DefaultSetting("boolean", true),
					life: new DefaultSetting("boolean", true),
					chain: new DefaultSetting("boolean", true),
					travel: new DefaultSetting("boolean", true)
				},
				education: {
					greyOut: new DefaultSetting("boolean", true),
					finishTime: new DefaultSetting("boolean", true)
				},
				jail: { filter: new DefaultSetting("boolean", true) },
				bank: {
					investmentInfo: new DefaultSetting("boolean", true),
					investmentDueTime: new DefaultSetting("boolean", true)
				},
				home: {
					networthDetails: new DefaultSetting("boolean", true),
					effectiveStats: new DefaultSetting("boolean", true)
				},
				items: {
					quickItems: new DefaultSetting("boolean", true),
					values: new DefaultSetting("boolean", true),
					drugDetails: new DefaultSetting("boolean", true),
					marketLinks: new DefaultSetting("boolean", false),
					highlightBloodBags: new DefaultSetting("string", "none"),
					missingFlowers: new DefaultSetting("boolean", false),
					missingPlushies: new DefaultSetting("boolean", false),
					missingBooks: new DefaultSetting("boolean", false),
					bookEffects: new DefaultSetting("boolean", true),
					canGains: new DefaultSetting("boolean", true),
					nerveGains: new DefaultSetting("boolean", true),
					candyHappyGains: new DefaultSetting("boolean", true),
					energyWarning: new DefaultSetting("boolean", true),
					medicalLife: new DefaultSetting("boolean", true),
					openedSupplyPackValue: new DefaultSetting("boolean", true),
					hideRecycleMessage: new DefaultSetting("boolean", false),
					hideTooManyItemsWarning: new DefaultSetting("boolean", false)
				},
				crimes: { quickCrimes: new DefaultSetting("boolean", true) },
				companies: {
					idBesideCompanyName: new DefaultSetting("boolean", false),
					specials: new DefaultSetting("boolean", true),
					autoStockFill: new DefaultSetting("boolean", true),
					employeeEffectiveness: new DefaultSetting("number", 18)
				},
				travel: {
					computer: new DefaultSetting("boolean", true),
					table: new DefaultSetting("boolean", true),
					cleanFlight: new DefaultSetting("boolean", false),
					tabTitleTimer: new DefaultSetting("boolean", false),
					travelProfits: new DefaultSetting("boolean", true),
					fillMax: new DefaultSetting("boolean", true),
					peopleFilter: new DefaultSetting("boolean", true),
					landingTime: new DefaultSetting("boolean", true),
					flyingTime: new DefaultSetting("boolean", true),
					itemFilter: new DefaultSetting("boolean", true),
					energyWarning: new DefaultSetting("boolean", true),
					cooldownWarnings: new DefaultSetting("boolean", true),
					autoTravelTableCountry: new DefaultSetting("boolean", false),
					autoFillMax: new DefaultSetting("boolean", true),
					efficientRehab: new DefaultSetting("boolean", true),
					efficientRehabSelect: new DefaultSetting("boolean", false),
					hideInventoryButton: new DefaultSetting("boolean", true),
					fastHunting: new DefaultSetting("boolean", true)
				},
				stocks: {
					filter: new DefaultSetting("boolean", true),
					acronyms: new DefaultSetting("boolean", true),
					valueAndProfit: new DefaultSetting("boolean", true),
					moneyInput: new DefaultSetting("boolean", true)
				},
				competitions: {
					easterEggs: new DefaultSetting("boolean", false),
					easterEggsAlert: new DefaultSetting("boolean", true)
				},
				events: { worth: new DefaultSetting("boolean", true) },
				hospital: { filter: new DefaultSetting("boolean", true) },
				auction: {
					filter: new DefaultSetting("boolean", true),
					movePagination: new DefaultSetting("boolean", false)
				},
				api: {
					autoFillKey: new DefaultSetting("boolean", true),
					autoDemo: new DefaultSetting("boolean", false),
					autoPretty: new DefaultSetting("boolean", true),
					clickableSelections: new DefaultSetting("boolean", true)
				},
				forums: {
					menu: new DefaultSetting("boolean", true),
					hidePosts: new DefaultSetting("object", {}),
					hideThreads: new DefaultSetting("object", {}),
					highlightPosts: new DefaultSetting("object", {}),
					highlightThreads: new DefaultSetting("object", {}),
					ignoredThreads: new DefaultSetting("object", {}),
					debugInfoBtn: new DefaultSetting("boolean", true),
					onlyNewFeedButton: new DefaultSetting("boolean", true)
				},
				bazaar: {
					itemsCost: new DefaultSetting("boolean", true),
					worth: new DefaultSetting("boolean", true),
					fillMax: new DefaultSetting("boolean", true),
					maxBuyIgnoreCash: new DefaultSetting("boolean", false),
					highlightSubVendorItems: new DefaultSetting("boolean", false)
				},
				trade: {
					itemValues: new DefaultSetting("boolean", true),
					openChat: new DefaultSetting("boolean", true)
				},
				displayCase: { worth: new DefaultSetting("boolean", true) },
				shops: {
					fillMax: new DefaultSetting("boolean", true),
					maxBuyIgnoreCash: new DefaultSetting("boolean", false),
					profit: new DefaultSetting("boolean", true),
					filters: new DefaultSetting("boolean", true),
					values: new DefaultSetting("boolean", true)
				},
				casino: {
					netTotal: new DefaultSetting("boolean", true),
					blackjack: new DefaultSetting("boolean", true),
					highlow: new DefaultSetting("boolean", false),
					highlowMovement: new DefaultSetting("boolean", true)
				},
				racing: {
					winPercentage: new DefaultSetting("boolean", true),
					upgrades: new DefaultSetting("boolean", true),
					filter: new DefaultSetting("boolean", true)
				},
				faction: {
					idBesideFactionName: new DefaultSetting("boolean", false),
					csvRaidReport: new DefaultSetting("boolean", true),
					csvRankedWarReport: new DefaultSetting("boolean", true),
					csvWarReport: new DefaultSetting("boolean", true),
					csvChainReport: new DefaultSetting("boolean", true),
					csvChallengeContributions: new DefaultSetting("boolean", true),
					openOc: new DefaultSetting("boolean", true),
					highlightOwn: new DefaultSetting("boolean", true),
					availablePlayers: new DefaultSetting("boolean", true),
					recommendedNnb: new DefaultSetting("boolean", true),
					ocNnb: new DefaultSetting("boolean", true),
					ocTimes: new DefaultSetting("boolean", true),
					ocLastAction: new DefaultSetting("boolean", true),
					banker: new DefaultSetting("boolean", true),
					showFullInfobox: new DefaultSetting("boolean", true),
					foldableInfobox: new DefaultSetting("boolean", true),
					numberMembers: new DefaultSetting("boolean", true),
					warFinishTimes: new DefaultSetting("boolean", false),
					memberFilter: new DefaultSetting("boolean", true),
					memberFilterRevivable: new DefaultSetting("boolean", false),
					armoryFilter: new DefaultSetting("boolean", true),
					armoryWorth: new DefaultSetting("boolean", true),
					upgradeRequiredRespect: new DefaultSetting("boolean", true),
					memberInfo: new DefaultSetting("boolean", false),
					rankedWarFilter: new DefaultSetting("boolean", true),
					quickItems: new DefaultSetting("boolean", true),
					stakeout: new DefaultSetting("boolean", true),
					showFactionSpy: new DefaultSetting("boolean", true),
					oc2Filter: new DefaultSetting("boolean", true),
					warnCrime: new DefaultSetting("boolean", false),
					rankedWarValue: new DefaultSetting("boolean", true),
					totalChallengeContributions: new DefaultSetting("boolean", true),
					memberRevives: new DefaultSetting("boolean", true),
					warReportHighlight: new DefaultSetting("boolean", true)
				},
				property: {
					filter: new DefaultSetting("boolean", true),
					value: new DefaultSetting("boolean", true),
					happy: new DefaultSetting("boolean", true)
				},
				gym: {
					specialist: new DefaultSetting("boolean", true),
					disableStats: new DefaultSetting("boolean", true),
					graph: new DefaultSetting("boolean", true),
					steadfast: new DefaultSetting("boolean", true),
					progress: new DefaultSetting("boolean", true)
				},
				missions: {
					hints: new DefaultSetting("boolean", true),
					rewards: new DefaultSetting("boolean", true)
				},
				attack: {
					bonusInformation: new DefaultSetting("boolean", true),
					timeoutWarning: new DefaultSetting("boolean", true),
					fairAttack: new DefaultSetting("boolean", true),
					weaponExperience: new DefaultSetting("boolean", true),
					hideAttackButtons: new DefaultSetting("array", [])
				},
				city: {
					items: new DefaultSetting("boolean", true),
					combineDuplicates: new DefaultSetting("boolean", true),
					groupByPeriod: new DefaultSetting("boolean", false),
					groupByPeriodUnit: new DefaultSetting("string", "day")
				},
				joblist: { specials: new DefaultSetting("boolean", true) },
				bounties: { filter: new DefaultSetting("boolean", true) },
				userlist: { filter: new DefaultSetting("boolean", true) },
				itemmarket: {
					highlightCheapItems: new DefaultSetting("number|empty", ""),
					highlightCheapItemsSound: new DefaultSetting("boolean", false),
					leftBar: new DefaultSetting("boolean", false),
					fillMax: new DefaultSetting("boolean", true),
					bazaars: new DefaultSetting("boolean", true)
				},
				competition: { filter: new DefaultSetting("boolean", true) },
				museum: { autoFill: new DefaultSetting("boolean", true) },
				enemies: { filter: new DefaultSetting("boolean", true) },
				friends: { filter: new DefaultSetting("boolean", true) },
				targets: { filter: new DefaultSetting("boolean", true) },
				crimes2: { value: new DefaultSetting("boolean", true) }
			},
			scripts: {
				noConfirm: {
					itemEquip: new DefaultSetting("boolean", true),
					tradeAccept: new DefaultSetting("boolean", false),
					pointsMarketRemove: new DefaultSetting("boolean", false),
					pointsMarketBuy: new DefaultSetting("boolean", false),
					abroadItemBuy: new DefaultSetting("boolean", true),
					propertiesSell: new DefaultSetting("boolean", false)
				},
				achievements: {
					show: new DefaultSetting("boolean", true),
					completed: new DefaultSetting("boolean", false)
				},
				reminders: {
					finished: new DefaultSetting("boolean", false),
					show: new DefaultSetting("boolean", true),
					types: {
						energyRefill: new DefaultSetting("boolean", true),
						nerveRefill: new DefaultSetting("boolean", true),
						medicalCooldown: new DefaultSetting("boolean", true),
						boosterCooldown: new DefaultSetting("boolean", true),
						drugCooldown: new DefaultSetting("boolean", true),
						bankInvestment: new DefaultSetting("boolean", true),
						virusCoding: new DefaultSetting("boolean", true),
						missionReward: new DefaultSetting("boolean", true),
						oc: new DefaultSetting("boolean", true),
						ocItem: new DefaultSetting("boolean", true),
						race: new DefaultSetting("boolean", true),
						education: new DefaultSetting("boolean", true)
					}
				},
				lastAction: {
					factionMember: new DefaultSetting("boolean", false),
					companyOwn: new DefaultSetting("boolean", false),
					companyOther: new DefaultSetting("boolean", false)
				},
				statsEstimate: {
					global: new DefaultSetting("boolean", true),
					delay: new DefaultSetting("number", 1500),
					cachedOnly: new DefaultSetting("boolean", true),
					displayNoResult: new DefaultSetting("boolean", false),
					maxLevel: new DefaultSetting("number", 100),
					profiles: new DefaultSetting("boolean", true),
					enemies: new DefaultSetting("boolean", true),
					hof: new DefaultSetting("boolean", true),
					attacks: new DefaultSetting("boolean", true),
					userlist: new DefaultSetting("boolean", true),
					bounties: new DefaultSetting("boolean", true),
					factions: new DefaultSetting("boolean", true),
					wars: new DefaultSetting("boolean", true),
					abroad: new DefaultSetting("boolean", true),
					competition: new DefaultSetting("boolean", true),
					rankedWars: new DefaultSetting("boolean", true),
					targets: new DefaultSetting("boolean", true)
				},
				ffScouter: {
					miniProfile: new DefaultSetting("boolean", true),
					profile: new DefaultSetting("boolean", true),
					attack: new DefaultSetting("boolean", true),
					factionList: new DefaultSetting("boolean", true),
					gauge: new DefaultSetting("boolean", true)
				}
			},
			external: {
				tornstats: new DefaultSetting("boolean", false),
				yata: new DefaultSetting("boolean", false),
				prometheus: new DefaultSetting("boolean", false),
				lzpt: new DefaultSetting("boolean", false),
				tornw3b: new DefaultSetting("boolean", false),
				ffScouter: new DefaultSetting("boolean", false),
				tornintel: new DefaultSetting("boolean", false),
				playgroundTorntools: new DefaultSetting("boolean", false)
			},
			servicePreferences: {
				spies: {
					tornstats: {
						enabled: new DefaultSetting("boolean", true),
						priority: new DefaultSetting("number", 1)
					},
					yata: {
						enabled: new DefaultSetting("boolean", true),
						priority: new DefaultSetting("number", 2)
					}
				},
				factionSpies: {
					tornstats: {
						enabled: new DefaultSetting("boolean", true),
						priority: new DefaultSetting("number", 1)
					},
					yata: {
						enabled: new DefaultSetting("boolean", true),
						priority: new DefaultSetting("number", 2)
					}
				},
				travelData: {
					prometheus: {
						enabled: new DefaultSetting("boolean", true),
						priority: new DefaultSetting("number", 1)
					},
					tornintel: {
						enabled: new DefaultSetting("boolean", true),
						priority: new DefaultSetting("number", 1)
					},
					yata: {
						enabled: new DefaultSetting("boolean", true),
						priority: new DefaultSetting("number", 2)
					}
				}
			},
			reporting: {}
		},
		filters: {
			hospital: {
				enabled: new DefaultSetting("boolean", true),
				timeStart: new DefaultSetting("number", 0),
				timeEnd: new DefaultSetting("number", 100),
				levelStart: new DefaultSetting("number", 0),
				levelEnd: new DefaultSetting("number", 100),
				faction: new DefaultSetting("string", ""),
				activity: new DefaultSetting("array", []),
				revivesOn: new DefaultSetting("boolean", false)
			},
			jail: {
				enabled: new DefaultSetting("boolean", true),
				activity: new DefaultSetting("array", []),
				faction: new DefaultSetting("string", "All"),
				timeStart: new DefaultSetting("number", 0),
				timeEnd: new DefaultSetting("number", 100),
				levelStart: new DefaultSetting("number", 1),
				levelEnd: new DefaultSetting("number", 100),
				scoreStart: new DefaultSetting("number", 0),
				scoreEnd: new DefaultSetting("number", 5e3),
				bailCost: new DefaultSetting("number", -1)
			},
			racing: {
				enabled: new DefaultSetting("boolean", true),
				hideRaces: new DefaultSetting("array", []),
				timeStart: new DefaultSetting("number", 0),
				timeEnd: new DefaultSetting("number", 48),
				driversMin: new DefaultSetting("number", 2),
				driversMax: new DefaultSetting("number", 100),
				lapsMin: new DefaultSetting("number", 1),
				lapsMax: new DefaultSetting("number", 100),
				track: new DefaultSetting("array", []),
				name: new DefaultSetting("string", ""),
				exemptions: new DefaultSetting("array", [])
			},
			containers: new DefaultSetting("object", {}),
			travel: {
				open: new DefaultSetting("boolean", false),
				type: new DefaultSetting("string", "basic"),
				categories: new DefaultSetting("array", []),
				countries: new DefaultSetting("array", []),
				hideOutOfStock: new DefaultSetting("boolean", false),
				applySalesTax: new DefaultSetting("boolean", false),
				sellAnonymously: new DefaultSetting("boolean", false)
			},
			abroadPeople: {
				enabled: new DefaultSetting("boolean", true),
				activity: new DefaultSetting("array", []),
				status: new DefaultSetting("array", []),
				levelStart: new DefaultSetting("number", 0),
				levelEnd: new DefaultSetting("number", 100),
				faction: new DefaultSetting("string", ""),
				special: {
					newPlayer: new DefaultSetting("string", "both"),
					inCompany: new DefaultSetting("string", "both"),
					inFaction: new DefaultSetting("string", "both"),
					isDonator: new DefaultSetting("string", "both"),
					hasBounties: new DefaultSetting("string", "both"),
					bazaarOpen: new DefaultSetting("string", "both")
				},
				estimates: new DefaultSetting("array", []),
				ffScoreMax: new DefaultSetting("number", null),
				ffScoreMin: new DefaultSetting("number", null)
			},
			abroadItems: {
				enabled: new DefaultSetting("boolean", true),
				profitOnly: new DefaultSetting("boolean", false),
				outOfStock: new DefaultSetting("boolean", false),
				categories: new DefaultSetting("array", []),
				taxes: new DefaultSetting("array", [])
			},
			trade: { hideValues: new DefaultSetting("boolean", false) },
			gym: {
				specialist1: new DefaultSetting("string", "none"),
				specialist2: new DefaultSetting("string", "none"),
				strength: new DefaultSetting("boolean", false),
				speed: new DefaultSetting("boolean", false),
				defense: new DefaultSetting("boolean", false),
				dexterity: new DefaultSetting("boolean", false)
			},
			city: { highlightItems: new DefaultSetting("boolean", true) },
			bounties: {
				maxLevel: new DefaultSetting("number", 100),
				hideUnavailable: new DefaultSetting("boolean", false)
			},
			properties: {
				enabled: new DefaultSetting("boolean", true),
				daysOnLeaseLow: new DefaultSetting("number", 1),
				daysOnLeaseHigh: new DefaultSetting("number", 100),
				status: new DefaultSetting("string", "all"),
				types: new DefaultSetting("array", [])
			},
			userlist: {
				enabled: new DefaultSetting("boolean", true),
				activity: new DefaultSetting("array", []),
				levelStart: new DefaultSetting("number", 0),
				levelEnd: new DefaultSetting("number", 100),
				special: {
					fedded: new DefaultSetting("string", "both"),
					fallen: new DefaultSetting("string", "both"),
					traveling: new DefaultSetting("string", "both"),
					newPlayer: new DefaultSetting("string", "both"),
					onWall: new DefaultSetting("string", "both"),
					inCompany: new DefaultSetting("string", "both"),
					inFaction: new DefaultSetting("string", "both"),
					isDonator: new DefaultSetting("string", "both"),
					inHospital: new DefaultSetting("string", "both"),
					inJail: new DefaultSetting("string", "both"),
					earlyDischarge: new DefaultSetting("string", "both"),
					hasBounties: new DefaultSetting("string", "both"),
					bazaarOpen: new DefaultSetting("string", "both")
				},
				hospReason: {
					attackedBy: new DefaultSetting("string", "both"),
					muggedBy: new DefaultSetting("string", "both"),
					hospitalizedBy: new DefaultSetting("string", "both"),
					other: new DefaultSetting("string", "both")
				},
				estimates: new DefaultSetting("array", []),
				ffScoreMax: new DefaultSetting("number", null),
				ffScoreMin: new DefaultSetting("number", null)
			},
			stocks: {
				enabled: new DefaultSetting("boolean", true),
				name: new DefaultSetting("string", ""),
				investment: {
					owned: new DefaultSetting("string", "both"),
					benefit: new DefaultSetting("string", "both"),
					passive: new DefaultSetting("string", "both"),
					collectionReady: new DefaultSetting("string", "both")
				},
				price: {
					price: new DefaultSetting("string", "both"),
					profit: new DefaultSetting("string", "both")
				}
			},
			faction: {
				enabled: new DefaultSetting("boolean", true),
				activity: new DefaultSetting("array", []),
				levelStart: new DefaultSetting("number", 1),
				levelEnd: new DefaultSetting("number", 100),
				lastActionStart: new DefaultSetting("number", 0),
				lastActionEnd: new DefaultSetting("number", -1),
				status: new DefaultSetting("array", []),
				position: new DefaultSetting("string", ""),
				special: {
					fedded: new DefaultSetting("string", "both"),
					fallen: new DefaultSetting("string", "both"),
					newPlayer: new DefaultSetting("string", "both"),
					inCompany: new DefaultSetting("string", "both"),
					isDonator: new DefaultSetting("string", "both"),
					isRecruit: new DefaultSetting("string", "both")
				},
				ffScoreMax: new DefaultSetting("number", null),
				ffScoreMin: new DefaultSetting("number", null),
				revivable: new DefaultSetting("array", [])
			},
			factionArmory: {
				enabled: new DefaultSetting("boolean", true),
				hideUnavailable: new DefaultSetting("boolean", false),
				weapons: {
					name: new DefaultSetting("string", ""),
					category: new DefaultSetting("string", ""),
					rarity: new DefaultSetting("string", ""),
					weaponType: new DefaultSetting("string", ""),
					damage: new DefaultSetting("string", ""),
					accuracy: new DefaultSetting("string", ""),
					weaponBonus: new DefaultSetting("array", [])
				},
				armor: {
					name: new DefaultSetting("string", ""),
					rarity: new DefaultSetting("string", ""),
					defence: new DefaultSetting("string", ""),
					set: new DefaultSetting("string", ""),
					armorBonus: new DefaultSetting("string", "")
				},
				temporary: { name: new DefaultSetting("string", "") }
			},
			factionRankedWar: {
				enabled: new DefaultSetting("boolean", true),
				activity: new DefaultSetting("array", []),
				status: new DefaultSetting("array", []),
				levelStart: new DefaultSetting("number", 1),
				levelEnd: new DefaultSetting("number", 100),
				estimates: new DefaultSetting("array", []),
				ffScoreMax: new DefaultSetting("number", null),
				ffScoreMin: new DefaultSetting("number", null)
			},
			profile: {
				relative: new DefaultSetting("boolean", false),
				stats: new DefaultSetting("array", [])
			},
			competition: {
				levelStart: new DefaultSetting("number", 1),
				levelEnd: new DefaultSetting("number", 100),
				estimates: new DefaultSetting("array", [])
			},
			shops: {
				hideLoss: new DefaultSetting("boolean", false),
				hideUnder100: new DefaultSetting("boolean", false)
			},
			auction: {
				enabled: new DefaultSetting("boolean", true),
				weapons: {
					name: new DefaultSetting("string", ""),
					category: new DefaultSetting("string", ""),
					rarity: new DefaultSetting("string", ""),
					weaponType: new DefaultSetting("string", ""),
					damage: new DefaultSetting("string", ""),
					accuracy: new DefaultSetting("string", ""),
					weaponBonus: new DefaultSetting("array", []),
					quality: new DefaultSetting("string", "")
				},
				armor: {
					name: new DefaultSetting("string", ""),
					rarity: new DefaultSetting("string", ""),
					defence: new DefaultSetting("string", ""),
					set: new DefaultSetting("string", ""),
					armorBonus: new DefaultSetting("string", "")
				},
				items: {
					name: new DefaultSetting("string", ""),
					category: new DefaultSetting("string", ""),
					rarity: new DefaultSetting("string", "")
				}
			},
			enemies: {
				enabled: new DefaultSetting("boolean", true),
				activity: new DefaultSetting("array", []),
				levelStart: new DefaultSetting("number", 0),
				levelEnd: new DefaultSetting("number", 100),
				estimates: new DefaultSetting("array", []),
				ffScoreMax: new DefaultSetting("number", null),
				ffScoreMin: new DefaultSetting("number", null)
			},
			friends: {
				enabled: new DefaultSetting("boolean", true),
				activity: new DefaultSetting("array", []),
				levelStart: new DefaultSetting("number", 0),
				levelEnd: new DefaultSetting("number", 100)
			},
			targets: {
				enabled: new DefaultSetting("boolean", true),
				activity: new DefaultSetting("array", []),
				levelStart: new DefaultSetting("number", 0),
				levelEnd: new DefaultSetting("number", 100),
				estimates: new DefaultSetting("array", []),
				ffScoreMax: new DefaultSetting("number", null),
				ffScoreMin: new DefaultSetting("number", null)
			},
			oc2: {
				enabled: new DefaultSetting("boolean", true),
				difficulty: new DefaultSetting("array", []),
				status: new DefaultSetting("array", [])
			}
		},
		userdata: new DefaultSetting("object", { date: -1 }),
		torndata: new DefaultSetting("object", { date: -2 }),
		stockdata: {
			date: new DefaultSetting("number", 0),
			stocks: new DefaultSetting("array", [])
		},
		factiondata: new DefaultSetting("object", {}),
		localdata: {
			tradeMessage: new DefaultSetting("number", 0),
			popup: { calculatorItems: new DefaultSetting("array", []) },
			vault: {
				initialized: new DefaultSetting("boolean", false),
				lastTransaction: new DefaultSetting("string", ""),
				total: new DefaultSetting("number", 0),
				user: {
					initial: new DefaultSetting("number", 0),
					current: new DefaultSetting("number", 0)
				},
				partner: {
					initial: new DefaultSetting("number", 0),
					current: new DefaultSetting("number", 0)
				}
			},
			chatResize: new DefaultSetting("object", {}),
			feedHidden: new DefaultSetting("object", {}),
			threadsHiddenInFeed: new DefaultSetting("array", []),
			hiddenTravelInventory: new DefaultSetting("boolean", false)
		},
		stakeouts: new DefaultSetting("object", {
			list: [],
			date: 0
		}),
		factionStakeouts: new DefaultSetting("object", {
			list: [],
			date: 0
		}),
		attackHistory: {
			fetchData: new DefaultSetting("boolean", true),
			lastAttack: new DefaultSetting("number", 0),
			history: new DefaultSetting("object", {})
		},
		notes: {
			sidebar: {
				text: new DefaultSetting("string", ""),
				height: new DefaultSetting("string", "22px")
			},
			profile: new DefaultSetting("object", {})
		},
		quick: {
			items: new DefaultSetting("array", []),
			factionItems: new DefaultSetting("array", []),
			crimes: new DefaultSetting("array", []),
			jail: new DefaultSetting("array", [])
		},
		cache: new DefaultSetting("object", {}),
		npcs: new DefaultSetting("object", {}),
		notificationHistory: new DefaultSetting("array", []),
		notifications: {
			events: new DefaultSetting("object", {}),
			messages: new DefaultSetting("object", {}),
			newDay: new DefaultSetting("object", {}),
			energy: new DefaultSetting("object", {}),
			happy: new DefaultSetting("object", {}),
			nerve: new DefaultSetting("object", {}),
			life: new DefaultSetting("object", {}),
			travel: new DefaultSetting("object", {}),
			drugs: new DefaultSetting("object", {}),
			boosters: new DefaultSetting("object", {}),
			medical: new DefaultSetting("object", {}),
			hospital: new DefaultSetting("object", {}),
			chain: new DefaultSetting("object", {}),
			chainCount: new DefaultSetting("object", {}),
			stakeouts: new DefaultSetting("object", {}),
			npcs: new DefaultSetting("object", {}),
			offline: new DefaultSetting("object", {}),
			missionsLimit: new DefaultSetting("object", {}),
			missionsExpire: new DefaultSetting("object", {}),
			refillEnergy: new DefaultSetting("object", {}),
			refillNerve: new DefaultSetting("object", {})
		},
		migrations: new DefaultSetting("array", [])
	};
	function getDefaultStorage(defaultStorage) {
		const newStorage = {};
		for (const key in defaultStorage) if (typeof defaultStorage[key] === "object") {
			const setting = defaultStorage[key];
			if (setting instanceof DefaultSetting && "defaultValue" in setting) switch (typeof setting.defaultValue) {
				case "function":
					newStorage[key] = setting.defaultValue();
					break;
				case "boolean":
				case "number":
				case "string":
				case "object":
					newStorage[key] = setting.defaultValue;
					break;
				default: newStorage[key] = setting.defaultValue;
			}
			else newStorage[key] = getDefaultStorage(defaultStorage[key]);
		} else newStorage[key] = defaultStorage[key];
		return newStorage;
	}
	var MIGRATIONS = [
		{
			id: "43fae1f2-5568-4ae5-b12f-f3625e1e58c6",
			version: "9.0.0",
			execute(database, _flags, _oldStorage) {
				database.cache["personal-stats"] = {};
			}
		},
		{
			id: "b194a6d5-4230-4b03-8a8b-bebd7c431cc9",
			version: "9.0.0",
			execute(database, _flags, _oldStorage) {
				database.settings.pages.api.autoDemo = false;
			}
		},
		{
			id: "b0f539ba-41f8-4eed-93e2-e8523f7c49a5",
			version: "9.0.1",
			execute(database, _flags, oldStorage) {
				const oldCustomLinks = oldStorage?.settings?.customLinks ?? [];
				database.settings.customLinks = oldCustomLinks.map((link) => {
					return link.preset && link.preset !== "custom" ? {
						newTab: link.newTab,
						location: link.location,
						name: link.name,
						preset: link.preset
					} : {
						newTab: link.newTab,
						location: link.location,
						name: link.name,
						href: link.href
					};
				});
			}
		},
		{
			id: "360b1f70-c78b-44c1-b217-24bd6b398bac",
			version: "9.0.5",
			execute(database, _flags, oldStorage) {
				if (!oldStorage?.settings?.userAlias || Array.isArray(oldStorage.settings.userAlias)) return;
				const oldUserAliases = oldStorage.settings.userAlias;
				database.settings.userAlias = Object.entries(oldUserAliases).map(([id, { alias, name }]) => {
					const idMatch = id.match(/^(\d+)$/);
					return idMatch ? {
						userId: parseInt(idMatch[0]),
						userName: name,
						alias
					} : {
						userId: -1,
						userName: name,
						alias,
						incorrectId: id
					};
				});
			}
		},
		{
			id: "95c020eb-2c75-4bbe-8fe9-64f96f108f48",
			version: "9.0.5",
			execute(database, _flags, oldStorage) {
				if (!oldStorage?.settings?.pages?.popup?.defaultTab) return;
				if (oldStorage.settings.pages.popup.defaultTab === "stocks") database.settings.pages.popup.defaultTab = "stocksOverview";
				else if (oldStorage.settings.pages.popup.defaultTab === "market") database.settings.pages.popup.defaultTab = "marketSearch";
			}
		},
		{
			id: "96356911-fecd-4b79-9825-ee5ad422c8fe",
			version: "9.0.5",
			execute(database, _flags, oldStorage) {
				if (typeof oldStorage?.settings?.pages?.popup.hoverBarTime !== "boolean") return;
				database.settings.pages.popup.fullBarTime = oldStorage.settings.pages.popup.hoverBarTime;
			}
		},
		{
			id: "7396191c-35a9-4d92-905a-0e411f9a6823",
			version: "9.0.5",
			execute(_database, _flags, _oldStorage) {
				ttStorage.remove("usage");
			}
		},
		{
			id: "d3e6e03a-698d-4df4-9062-4d3c9ce9d479",
			version: "9.0.5",
			execute(database, _flags, oldStorage) {
				if (!oldStorage?.filters?.travel?.categories?.includes("other")) return;
				database.filters.travel.categories = [...oldStorage.filters.travel.categories, "defensive"];
			}
		},
		{
			id: "700848e9-ee48-42ce-b8b1-893cb471cfe4",
			version: "9.0.6",
			execute(_database, flags, _oldStorage) {
				flags.clearCache = true;
			}
		},
		{
			id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
			version: "9.0.6",
			execute(database, _flags, oldStorage) {
				const oldStakeouts = oldStorage?.stakeouts;
				if (!oldStakeouts || typeof oldStakeouts !== "object") return;
				const reservedKeys = new Set([
					"order",
					"date",
					"list"
				]);
				const oldOrder = oldStakeouts.order ?? [];
				const list = [];
				Object.entries(oldStakeouts).filter((entry) => !reservedKeys.has(entry[0])).forEach(([id, data]) => {
					const orderIndex = oldOrder.indexOf(id);
					list.push({
						...data,
						id: parseInt(id),
						order: orderIndex !== -1 ? orderIndex : Date.now()
					});
				});
				database.stakeouts.list = list;
			}
		},
		{
			id: "b2c3d4e5-f6a7-8901-bcde-f12345678901",
			version: "9.0.6",
			execute(database, _flags, oldStorage) {
				const oldFactionStakeouts = oldStorage?.factionStakeouts;
				if (!oldFactionStakeouts || typeof oldFactionStakeouts !== "object") return;
				const reservedKeys = new Set(["date", "list"]);
				const list = [];
				Object.entries(oldFactionStakeouts).filter((entry) => !reservedKeys.has(entry[0])).forEach(([id, data]) => {
					list.push({
						...data,
						id: parseInt(id),
						order: Date.now()
					});
				});
				database.factionStakeouts.list = list;
			}
		},
		{
			id: "16d7de5c-e9ad-4060-966e-49b4252301c5",
			version: "9.0.7",
			execute(_database, _flags, _oldStorage) {
				OFFLOAD_SERVICE.reinitializeTimers().catch(() => {});
			}
		},
		{
			id: "a1b8db49-f255-43fc-b3b8-dc82b8c072b1",
			version: "9.0.9",
			execute(database, _flags, oldStorage) {
				const owner = oldStorage.userdata?.profile?.id;
				if (!owner) return;
				database.api.torn.owner = owner;
			}
		},
		{
			id: "8a88db28-d02c-4b08-a672-bb73394b5ae4",
			version: "9.0.12",
			execute(_database, _flags, _oldStorage) {
				OFFLOAD_SERVICE.reinitializeTimers().catch(() => {});
			}
		},
		{
			id: "19384047-faaa-4894-a0bb-1695b964a125",
			version: "9.0.14",
			execute(_database, flags, _oldStorage) {
				flags.updateStockdata = true;
			}
		},
		{
			id: "b2102994-0920-4586-8259-0e5beedc7f13",
			version: "9.1.0",
			execute(database, _flags, oldStorage) {
				if (oldStorage.api.torn.owner) return;
				const owner = oldStorage.userdata?.profile?.id;
				if (!owner) return;
				database.api.torn.owner = owner;
			}
		},
		{
			id: "8f883a44-fa45-407b-bdc7-18c6982ab108",
			version: "9.1.0",
			execute(database, _flags, _oldStorage) {
				database.cache["stats-estimate"] = {};
			}
		},
		{
			id: "4b9a96e4-ce68-46c1-b5ab-b7dfbc699db0",
			version: "9.1.2",
			execute(_database, flags, _oldStorage) {
				flags.updateUserdata = true;
			}
		}
	];
	async function executeMigrationScripts(storage, oldStorage) {
		if (RUNTIME_INFORMATION.isUserscript()) return;
		const migrations = MIGRATIONS.filter(({ version }) => toNumericVersion(version) >= toNumericVersion(storage.version.initial)).filter(({ id }) => !storage.migrations.map(({ id }) => id).includes(id));
		const flags = {
			updateUserdata: false,
			updateFactiondata: false,
			updateStockdata: false,
			updateTorndata: false,
			clearCache: false
		};
		migrations.reverse().forEach((migration) => {
			migration.execute(storage, flags, oldStorage);
			storage.migrations.push({ id: migration.id });
		});
		if (flags.updateUserdata) storage.userdata.date = 0;
		if (flags.updateFactiondata) storage.factiondata.date = 0;
		if (flags.updateStockdata) storage.stockdata.date = 0;
		if (flags.updateTorndata) storage.torndata.date = 0;
		if (flags.clearCache) storage.cache = {};
	}
	var settings;
	var filters;
	var version;
	var api;
	var userdata;
	var torndata;
	var stakeouts;
	var attackHistory;
	var notes;
	var factiondata;
	var quick;
	var localdata;
	var npcs;
	var notificationHistory;
	var stockdata;
	var factionStakeouts;
	var notifications;
	var migrations;
	var storageListeners = {
		settings: [],
		filters: [],
		version: [],
		userdata: [],
		torndata: [],
		attackHistory: [],
		stakeouts: [],
		factionStakeouts: [],
		notes: [],
		factiondata: [],
		localdata: [],
		cache: [],
		api: [],
		npcs: [],
		stockdata: [],
		notificationHistory: [],
		notifications: [],
		quick: [],
		migrations: []
	};
	var databaseLoaded = false;
	var databaseLoadPromise = null;
	async function loadDatabase(force = false) {
		if (databaseLoaded && !force) return {
			settings,
			filters,
			version,
			userdata,
			stakeouts,
			factionStakeouts,
			notes,
			factiondata,
			localdata,
			cache: ttCache.cache,
			api,
			npcs,
			torndata,
			notificationHistory,
			attackHistory,
			quick,
			stockdata,
			notifications,
			migrations
		};
		if (databaseLoadPromise) return await databaseLoadPromise;
		databaseLoadPromise = (async () => {
			const database = await ttStorage.get();
			populateDatabaseVariables(database);
			console.debug("TT - Database loaded.");
			return database;
		})();
		try {
			const result = await databaseLoadPromise;
			databaseLoaded = true;
			databaseLoadPromise = null;
			return result;
		} catch (error) {
			databaseLoadPromise = null;
			throw error;
		}
	}
	async function migrateDatabase(force = false) {
		try {
			const loadedStorage = await ttStorage.get();
			if (!loadedStorage || !Object.keys(loadedStorage).length) {
				console.log("TT - Fresh installation detected, setting up default storage.");
				await ttStorage.reset();
				await loadDatabase();
				return;
			}
			const storedVersion = loadedStorage?.version?.current || "5.0.0";
			const currentVersion = RUNTIME_INFORMATION.getVersion();
			console.log(`TT - Migration check: ${storedVersion} -> ${currentVersion}`);
			const migratedStorage = convertStorage(loadedStorage, DEFAULT_STORAGE);
			await executeMigrationScripts(migratedStorage, loadedStorage);
			migratedStorage.version.current = currentVersion;
			await ttStorage.set(migratedStorage);
			populateDatabaseVariables(migratedStorage);
			console.debug("TT - Database migration completed successfully.");
		} catch (error) {
			console.error("TT - Database migration failed:", error);
			await loadDatabase();
		}
	}
	function convertStorage(oldStorage, defaultStorage) {
		const newStorage = {};
		for (const key in defaultStorage) {
			if (!oldStorage) oldStorage = {};
			const defaultValue = defaultStorage[key];
			const oldValue = key in oldStorage ? oldStorage[key] : void 0;
			if (typeof defaultValue === "object" && defaultValue !== null) {
				if (defaultValue instanceof DefaultSetting) newStorage[key] = migrateDefaultSetting(oldValue ?? {}, defaultValue);
				else newStorage[key] = convertStorage(oldValue ?? {}, defaultValue);
			} else newStorage[key] = oldValue ?? defaultValue;
		}
		return newStorage;
	}
	function migrateDefaultSetting(oldValue, setting) {
		if (isValidSettingValue(oldValue, setting)) return oldValue;
		if (setting.defaultValue !== null) return typeof setting.defaultValue === "function" ? setting.defaultValue() : setting.defaultValue;
		return null;
	}
	function isValidSettingValue(value, setting) {
		if (setting.type === "array") return Array.isArray(value);
		return setting.type.split("|").some((type) => type === "empty" && value === "" || typeof value === type);
	}
	function populateDatabaseVariables(database) {
		settings = database.settings;
		filters = database.filters;
		version = database.version;
		api = database.api;
		userdata = database.userdata;
		torndata = database.torndata;
		localdata = database.localdata;
		stakeouts = database.stakeouts;
		attackHistory = database.attackHistory;
		notes = database.notes;
		factiondata = database.factiondata;
		quick = database.quick;
		npcs = database.npcs;
		stockdata = database.stockdata;
		factionStakeouts = database.factionStakeouts;
		notificationHistory = database.notificationHistory;
		notifications = database.notifications;
		migrations = database.migrations;
		ttCache.cache = database.cache;
	}
	var initializedDatabaseListeners = false;
	function initializeDatabaseListener() {
		if (initializedDatabaseListeners) return;
		RUNTIME_STORAGE.addChangeListener((changes, area) => {
			if (area === "local") for (const key in changes) {
				switch (key) {
					case "settings":
						settings = changes.settings.newValue;
						break;
					case "filters":
						filters = changes.filters.newValue;
						break;
					case "version":
						version = changes.version.newValue;
						break;
					case "userdata":
						userdata = changes.userdata.newValue;
						break;
					case "api":
						api = changes.api.newValue;
						break;
					case "torndata":
						torndata = changes.torndata.newValue;
						break;
					case "stakeouts":
						stakeouts = changes.stakeouts.newValue;
						break;
					case "attackHistory":
						attackHistory = changes.attackHistory.newValue;
						break;
					case "notes":
						notes = changes.notes.newValue;
						break;
					case "factiondata":
						factiondata = changes.factiondata.newValue;
						break;
					case "quick":
						quick = changes.quick.newValue;
						break;
					case "localdata":
						localdata = changes.localdata.newValue;
						break;
					case "cache":
						ttCache.cache = changes.cache.newValue;
						break;
					case "npcs":
						npcs = changes.npcs.newValue;
						break;
					case "stockdata":
						stockdata = changes.stockdata.newValue;
						break;
					case "notificationHistory":
						notificationHistory = changes.notificationHistory.newValue;
						break;
					case "notifications":
						notifications = changes.notifications.newValue;
						break;
					case "factionStakeouts": factionStakeouts = changes.factionStakeouts.newValue;
				}
				if (storageListeners[key]) storageListeners[key].forEach((listener) => listener(changes[key].oldValue, changes[key].newValue));
			}
		});
		initializedDatabaseListeners = true;
	}
	function setLocaldata(data) {
		localdata = data;
	}
	function setFilters(data) {
		filters = data;
	}
	_css(".tt-filter{font-family:Arial}.tt-filter .statistics{text-align:right;width:100%;padding-top:5px;transform:translate(-10px)}.tt-filter .content{flex-wrap:wrap;justify-content:space-evenly;gap:10px;width:100%;padding:10px 0;display:flex}.tt-filter .content .tt-yn-checkboxes>div{align-items:center;display:flex}.tt-filter .content .tt-yn-checkboxes label{margin-left:4px}.tt-filter .content>div{text-align:center}.tt-filter .content strong{margin-bottom:2px;display:block}.tt-filter .content .tt-slider{padding:0 10px}.tt-filter .content .slider-counter{margin-top:5px}.weaponBonus__section-class{grid-template:\"title title\"\"b1 v1\"\"b2 v2\"/1fr .5fr;gap:4px;display:grid}.weaponBonus__section-class strong{grid-area:title}.tt-filter-enabled-funnel{cursor:pointer;font-size:16px;margin-right:-4px!important}");
	_css(".tt-checkbox-duo{display:flex}.tt-checkbox-duo>:not(:first-child){margin-left:4px}.tt-checkbox-duo .duo-yes .icon{color:var(--tt-color-green)}.tt-checkbox-duo .duo-no .icon{color:var(--tt-color-red)}");
	_css(".tt-checkbox-wrapper,.tt-checkbox-wrapper label{align-items:center;display:inline-flex}.tt-checkbox-wrapper>*{cursor:pointer}.tt-checkbox-wrapper.reverse-label label{flex-flow:row-reverse}.tt-checkbox-wrapper:not(.reverse-label) input{margin-right:2px}.tt-checkbox-wrapper.reverse-label input{margin-left:2px}");
	_css(".tt-checkbox-list-wrapper{display:flex}.tt-checkbox-list-wrapper.tt-checkbox-list-column{flex-direction:column}.tt-checkbox-list-wrapper.tt-checkbox-list-row{flex-direction:row}.tt-checkbox-list-wrapper.tt-checkbox-list-row>.tt-checkbox-wrapper:not(:first-child){margin-left:10px}");
	_css(".tt-radio-list-wrapper{display:flex}.tt-radio-list-wrapper.tt-radio-list-column{flex-direction:column}.tt-radio-list-wrapper.tt-radio-list-row{flex-direction:row}.tt-radio-list-wrapper.tt-radio-list-row>.tt-radio-wrapper:not(:first-child){margin-left:10px}");
	_css(".tt-radio-wrapper,.tt-radio-wrapper label{align-items:center;display:inline-flex}.tt-radio-wrapper>*{cursor:pointer}.tt-radio-wrapper.reverse-label label{flex-flow:row-reverse}.tt-radio-wrapper:not(.reverse-label) input{margin-right:2px}.tt-radio-wrapper.reverse-label input{margin-left:2px}");
	function createRadio(name, value, partialOptions = {}) {
		const options = {
			description: "",
			isHTML: false,
			reverseLabel: false,
			class: "",
			...partialOptions
		};
		const radio = elementBuilder({
			type: "input",
			attributes: {
				type: "radio",
				name,
				value
			}
		});
		let label;
		if (typeof options.description === "object") label = elementBuilder({
			type: "label",
			children: [options.description]
		});
		else label = elementBuilder({
			type: "label",
			[options.isHTML ? "html" : "text"]: options.description
		});
		label.insertAdjacentElement("afterbegin", radio);
		const checkboxWrapper = elementBuilder({
			type: "div",
			class: `tt-radio-wrapper ${options.reverseLabel ? "reverse-label" : ""} ${options.class}`,
			children: [label],
			events: { click(event) {
				event.stopPropagation();
			} }
		});
		let onChangeCallback;
		function setChecked(isChecked) {
			radio.checked = isChecked;
		}
		function isChecked() {
			return radio.checked;
		}
		function onChange(callback) {
			onChangeCallback = callback;
			radio.addEventListener("change", _onChangeListener);
		}
		function dispose() {
			if (onChangeCallback) {
				radio.removeEventListener("change", _onChangeListener);
				onChangeCallback = void 0;
			}
		}
		function _onChangeListener() {
			onChangeCallback?.();
		}
		return {
			element: checkboxWrapper,
			setChecked,
			isChecked,
			getValue: () => value,
			onChange,
			dispose
		};
	}
	function createRadioList(name, partialOptions) {
		const options = {
			items: [],
			orientation: "column",
			reverseLabel: false,
			...partialOptions
		};
		const radios = {};
		let selectionChangeCallback;
		for (const item of options.items) {
			const radio = createRadio(name, item.value, {
				description: item.description,
				reverseLabel: options.reverseLabel
			});
			radio.onChange(() => {
				if (selectionChangeCallback) selectionChangeCallback();
			});
			radios[item.value] = radio;
		}
		const radioWrapper = elementBuilder({
			type: "div",
			class: ["tt-radio-list-wrapper", options.orientation === "row" ? "tt-radio-list-row" : "tt-radio-list-column"].join(" "),
			children: Object.values(radios).map((radio) => radio.element)
		});
		function setValue(value) {
			radios[value].setChecked(true);
		}
		function getValue() {
			return Object.values(radios).find((radio) => radio.isChecked()).getValue();
		}
		function onSelectionChange(callback) {
			selectionChangeCallback = callback;
		}
		function dispose() {
			Object.values(radios).forEach((radio) => radio.dispose());
			selectionChangeCallback = void 0;
		}
		return {
			element: radioWrapper,
			setValue,
			getValue,
			onSelectionChange,
			dispose
		};
	}
	_css(".tt-multi-select{border-radius:4px;flex-direction:column;max-height:100px;padding:5px;display:flex;overflow:hidden auto}body:not(.dark-mode) .tt-multi-select{background-color:#ddd}body.dark-mode .tt-multi-select{background-color:#444}.tt-multi-select label{cursor:pointer;align-items:center;margin-bottom:4px;display:flex}.tt-multi-select label[disabled]{cursor:not-allowed}.tt-multi-select input{cursor:pointer}.tt-multi-select span{margin-left:6px}");
	function createMultiSelect(options) {
		let selectedValues = Array.isArray(options.defaults) ? options.defaults : [];
		let shownOptions = options.select;
		let onChangeCallback;
		const container = elementBuilder({
			type: "div",
			class: "tt-multi-select"
		});
		function renderOptions() {
			container.innerHTML = "";
			shownOptions.forEach((opt) => {
				const wrapper = elementBuilder({ type: "label" });
				if (opt.disabled) wrapper.setAttribute("disabled", "true");
				const checkbox = elementBuilder({
					type: "input",
					value: opt.value,
					attributes: { type: "checkbox" },
					events: { change: () => {
						if (checkbox.checked) {
							if (!selectedValues.includes(opt.value)) selectedValues.push(opt.value);
						} else selectedValues = selectedValues.filter((v) => v !== opt.value);
						if (onChangeCallback) onChangeCallback();
					} }
				});
				checkbox.checked = selectedValues.includes(opt.value);
				checkbox.disabled = !!opt.disabled;
				const text = elementBuilder({
					type: "span",
					text: opt.description
				});
				wrapper.appendChild(checkbox);
				wrapper.appendChild(text);
				container.appendChild(wrapper);
			});
		}
		function setSelected(values) {
			selectedValues = Array.isArray(values) ? values : [values];
			renderOptions();
		}
		function getSelected() {
			return Array.from(selectedValues);
		}
		function onChange(callback) {
			onChangeCallback = callback;
		}
		function updateOptionsList(newList) {
			shownOptions = newList;
			renderOptions();
		}
		renderOptions();
		return {
			element: container,
			getSelected,
			setSelected,
			onChange,
			updateOptionsList
		};
	}
	_css(".tt-dual-range{user-select:none;background:#ddd;border-radius:50px;width:150px;height:12px;margin:auto;position:relative}.tt-dual-range .highlight{height:12px;width:calc(calc(var(--right) - var(--left)) + 21px);left:var(--left);z-index:1;background-color:#627e0d;border-radius:50px;position:absolute}.tt-dual-range .handle{z-index:2;cursor:grab;float:none;border-radius:50%;width:21px;height:21px;position:absolute;top:50%;transform:translateY(-50%);box-shadow:0 3px 6px #0000001a}.tt-dual-range .handle.focus{border:3px solid #888;width:15px;height:15px}body:not(.dark-mode) .tt-dual-range .handle{background-color:#fff}body.dark-mode .tt-dual-range .handle{background-color:#e8e8e8}.tt-dual-range .handle.left{left:var(--left)}.tt-dual-range .handle.right{left:var(--right)}.tt-dual-range .dump{width:0;overflow:hidden}.tt-dual-range .dump *{opacity:0}.tt-slider-wrapper{flex-direction:column;align-items:center;display:flex}.tt-slider-label{margin-top:10px}");
	function roundNearest(number, multiple) {
		return Math.round(number / multiple) * multiple;
	}
	function camelCase(text, lowerCamelCase = true) {
		return (text.trim().charAt(0)[lowerCamelCase ? "toLowerCase" : "toUpperCase"]() + text.slice(1)).trim().replaceAll(" ", "");
	}
	var DualRangeSlider = class {
		options;
		uuid;
		startPos;
		slider;
		activeHandle;
		handles;
		moveTouchListener = this.moveTouch.bind(this);
		moveListener = this.move.bind(this);
		constructor(options = {}) {
			this.options = {
				min: 0,
				max: 100,
				step: 1,
				valueLow: options.min || 0,
				valueHigh: options.max || 100,
				...options
			};
			this.uuid = getUUID();
			this.startPos = 0;
			this.handles = [];
			this._createElement();
		}
		_createElement() {
			this.slider = elementBuilder({
				type: "div",
				class: "tt-dual-range",
				html: `	
				<label for="handle-left_${this.uuid}" class="handle left"></label>
				<span class="highlight"></span>
				<label for="handle-right_${this.uuid}" class="handle right"></label>
				<div class="dump">
					<input id="handle-left_${this.uuid}"/>
					<input id="handle-right_${this.uuid}"/>
				</div>
			`
			});
			this.handles = findAllElements(".handle", this.slider);
			this.handles.forEach((handle) => {
				const input = this.slider.querySelector(`#${handle.getAttribute("for")}`);
				handle.addEventListener("mousedown", this.startMove.bind(this));
				handle.addEventListener("touchstart", this.startMoveTouch.bind(this));
				handle.addEventListener("click", () => input.focus());
				input.addEventListener("focus", () => handle.classList.add("focus"));
				input.addEventListener("blur", () => handle.classList.remove("focus"));
				input.addEventListener("keydown", this.moveKeyboard.bind(this));
			});
			this.updateValue(this.handles[0], this.options.valueLow);
			this.updateValue(this.handles[1], this.options.valueHigh);
			window.addEventListener("mouseup", this.stopMove.bind(this));
			window.addEventListener("touchend", this.stopMove.bind(this));
			window.addEventListener("touchcancel", this.stopMove.bind(this));
			window.addEventListener("touchleave", this.stopMove.bind(this));
		}
		startMoveTouch(event) {
			const handleRect = event.target.getBoundingClientRect();
			this.startPos = event.touches[0].clientX - handleRect.x;
			this.activeHandle = event.target;
			window.addEventListener("touchmove", this.moveTouchListener);
		}
		startMove(event) {
			this.startPos = event.offsetX;
			this.activeHandle = event.target;
			window.addEventListener("mousemove", this.moveListener);
		}
		moveKeyboard(event) {
			if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
			const handle = this.slider.querySelector(`.handle[for="${event.target.id}"]`);
			if (!handle) return;
			let value = parseInt(handle.dataset.value);
			if (event.key === "ArrowLeft") value -= this.options.step;
			else if (event.key === "ArrowRight") value += this.options.step;
			this.updateValue(handle, value);
		}
		moveTouch(event) {
			this.move({ clientX: event.touches[0].clientX });
		}
		move(event) {
			const parentRect = this.slider.getBoundingClientRect();
			const handleRect = this.activeHandle.getBoundingClientRect();
			const position = Math.max(Math.min(event.clientX - parentRect.x - this.startPos, parentRect.width - handleRect.width / 2), 0 - handleRect.width / 2);
			this.updateValue(this.activeHandle, this.calculateValue((position + handleRect.width / 2) / parentRect.width));
		}
		calculateValue(percentage) {
			return Math.round(percentage * (this.options.max - this.options.min) + this.options.min);
		}
		updateValue(handle, value) {
			value = roundNearest(Math.max(Math.min(value, this.options.max), this.options.min), this.options.step);
			handle.dataset.value = value.toString();
			this.updateValues();
		}
		stopMove() {
			window.removeEventListener("mousemove", this.moveListener);
			window.removeEventListener("touchmove", this.moveTouchListener);
		}
		updateValues() {
			const valueLeft = parseInt(this.handles[0].dataset.value);
			const valueRight = parseInt(this.handles[1].dataset.value);
			const low = Math.min(valueLeft, valueRight);
			const high = Math.max(valueLeft, valueRight);
			this.updateHighlight("left", low);
			this.updateHighlight("right", high);
			this.slider.dataset.low = low.toString();
			this.slider.dataset.high = high.toString();
		}
		updateHighlight(side, value) {
			const rangeWidth = this.slider.getBoundingClientRect().width || 150;
			const handleWidth = this.handles[0].getBoundingClientRect().width || 21;
			const percentage = (value - this.options.min) / (this.options.max - this.options.min);
			this.slider.style.setProperty(`--${side}`, `${percentage * rangeWidth - handleWidth / 2}px`);
		}
	};
	_css(".tt-textbox{border:1px solid var(--input-border-color);background:var(--input-background-color);width:75%;color:var(--input-color);border-radius:5px;padding:0 3px}");
	function hasAPIData() {
		const hasKey = !!api?.torn?.key;
		const hasError = !!api?.torn?.error && !api.torn.error.includes("Backend error") && api.torn.error !== "Network issues";
		const hasUserdata = !!(userdata && Object.keys(userdata).length);
		return hasKey && !hasError && hasUserdata;
	}
	_css(".tt-container.spacer{margin-bottom:10px}.tt-container .title{text-shadow:1px 1px 2px #000000a6;letter-spacing:1px;white-space:nowrap;height:30px;margin:initial;align-items:center;padding-left:10px;font-size:13px;display:flex}.tt-theme,.tt-container.tt-theme-background .title{color:var(--tt-theme-color);background:var(--tt-theme-background)}.tt-container.collapsible .title{cursor:pointer}.tt-container.spacer .title{margin-top:10px}.tt-container .title .text{width:-webkit-fill-available;width:-moz-available}.tt-container .title .icon{text-align:center;min-width:30px;margin:auto;font-size:16px;position:static!important}.tt-container.rounding.always-content .title,.tt-container.rounding .title:not(.collapsed){border-radius:5px 5px 0 0}.tt-container.rounding:not(.always-content) .title.collapsed{border-radius:5px}.tt-container .title.collapsed .icon{transform:rotate(-90deg)}.tt-container:not(.always-content) .title.collapsed+main,.tt-container.always-content .title.collapsed+main .hide-collapse{display:none!important}.tt-container .title .options{flex-direction:row-reverse;align-items:center;width:100%;margin-right:4px;display:flex}.tt-container .title .options>*{align-items:center;margin-right:4px;display:flex}.tt-container .title .options i{font-size:1rem}#sidebarroot .tt-container .title{border-top-right-radius:5px;border-bottom-right-radius:5px;align-items:center;height:22px}body.tt-tablet #sidebarroot .tt-container .title{height:34px}.tt-container>main{margin-top:initial!important}.tt-container:not(.compact)>main{padding:4px 4px 3px}.tt-container.rounding>main{border-radius:0 0 5px 5px}.tt-container>main.background{background-color:var(--default-bg-panel-color)}.tt-container.reset-styles{letter-spacing:0;color:var(--default-color)!important;font-family:Arial,serif!important}.tt-container input[type=checkbox]{accent-color:#6e8820}");
	function createContainer(title, partialOptions) {
		const options = {
			id: camelCase(title),
			class: [],
			showHeader: true,
			onlyHeader: false,
			collapsible: true,
			applyRounding: true,
			spacer: false,
			contentBackground: true,
			allowDragging: false,
			flexContainer: false,
			compact: false,
			alwaysContent: false,
			filter: false,
			resetStyles: false,
			...partialOptions
		};
		if (options.onlyHeader) options.collapsible = false;
		const { container, collapsed } = _createContainer(title, options);
		let parentElement;
		if ("parentElement" in options) parentElement = options.parentElement;
		else if ("nextElement" in options) parentElement = options.nextElement.parentElement;
		else if ("previousElement" in options) parentElement = options.previousElement.parentElement;
		else parentElement = document.querySelector(".content-wrapper");
		if ("nextElement" in options) parentElement.insertBefore(container, options.nextElement);
		else if ("previousElement" in options) parentElement.insertBefore(container, options.previousElement.nextSibling);
		else parentElement.appendChild(container);
		return {
			container,
			content: container.querySelector(":scope > main"),
			options: container.querySelector(".options"),
			collapsed
		};
		function _createContainer(title, options) {
			document.querySelector(`#${options.id}`)?.remove();
			const containerClasses = ["tt-container"];
			if (options.collapsible) containerClasses.push("collapsible");
			if (options.applyRounding) containerClasses.push("rounding");
			if (options.spacer) containerClasses.push("spacer");
			if (options.compact) containerClasses.push("compact");
			if (options.alwaysContent) containerClasses.push("always-content");
			if (options.class) {
				let classes;
				if (typeof options.class === "string") classes = options.class.split(" ").filter((c) => !!c);
				else classes = options.class.filter((c) => !!c);
				containerClasses.push(...classes);
			}
			if (options.filter) containerClasses.push("tt-filter");
			if (options.resetStyles) containerClasses.push("reset-styles");
			const mainClasses = [];
			if (options.contentBackground) mainClasses.push("background");
			if (options.flexContainer) mainClasses.push("t-flex");
			containerClasses.push("tt-theme-background");
			const container = elementBuilder({
				type: "div",
				class: containerClasses.join(" "),
				id: options.id
			});
			const collapsed = options.onlyHeader || options.collapsible && (options.id in filters.containers ? filters.containers[options.id] : false);
			if (options.showHeader) container.appendChild(elementBuilder({
				type: "div",
				class: ["title", collapsed ? "collapsed" : null],
				children: [
					elementBuilder({
						type: "div",
						class: "text",
						text: title
					}),
					elementBuilder({
						type: "div",
						class: "options"
					}),
					options.collapsible ? PHFillCaretDown({ class: "icon" }) : null
				]
			}));
			if (!options.onlyHeader) container.appendChild(elementBuilder({
				type: "main",
				class: mainClasses
			}));
			if (options.collapsible) container.querySelector(".title").addEventListener("click", async () => {
				container.querySelector(".title").classList.toggle("collapsed");
				await ttStorage.change({ filters: { containers: { [options.id]: container.querySelector(".title").classList.contains("collapsed") } } });
			});
			if (options.allowDragging) {
				const content = container.querySelector(":scope > main");
				content.addEventListener("dragover", (event) => event.preventDefault());
				content.addEventListener("drop", (event) => {
					if (content.querySelector(".temp.item, .temp.quick-item")) content.querySelector(".temp.item, .temp.quick-item").classList.remove("temp");
					event.preventDefault();
					event.dataTransfer?.clearData();
				});
			}
			return {
				container,
				collapsed
			};
		}
	}
	function findContainer(title, partialOptions = {}) {
		const options = {
			id: camelCase(title),
			selector: void 0,
			...partialOptions
		};
		if (!options.id) return null;
		const container = document.querySelector(`#${options.id}`);
		if (!container) return null;
		if (options.selector) return container.querySelector(options.selector);
		else return container;
	}
	function removeContainer(title, partialOptions = {}) {
		const container = findContainer(title, partialOptions);
		if (!container) return;
		container.remove();
	}
	var LINKS = {
		auction: "https://www.torn.com/amarket.php",
		bank: "https://www.torn.com/bank.php",
		bazaar: "https://www.torn.com/bazaar.php",
		bounties: "https://www.torn.com/bounties.php#!p=main",
		chain: "https://www.torn.com/factions.php?step=your#/war/chain",
		church: "https://www.torn.com/church.php",
		committee: "https://www.torn.com/committee.php",
		companies: "https://www.torn.com/companies.php",
		companyEmployees: "https://www.torn.com/companies.php#/option=employees",
		crimes: "https://www.torn.com/crimes.php",
		donator: "https://www.torn.com/donator.php",
		education: "https://www.torn.com/page.php?sid=education",
		events: "https://www.torn.com/events.php#/step=all",
		faction: "https://www.torn.com/factions.php",
		faction__ranked_war: "https://www.torn.com/factions.php?step=your&type=1#/war/rank",
		faction_oc: "https://www.torn.com/factions.php?step=your#/tab=crimes",
		gym: "https://www.torn.com/gym.php",
		home: "https://www.torn.com/index.php",
		homepage: "https://www.torn.com/index.php",
		hospital: "https://www.torn.com/hospitalview.php",
		itemmarket: "https://www.torn.com/page.php?sid=ItemMarket",
		items: "https://www.torn.com/item.php",
		items_booster: "https://www.torn.com/item.php#boosters-items",
		items_candy: "https://www.torn.com/item.php#candy-items",
		items_drug: "https://www.torn.com/item.php#drugs-items",
		items_medical: "https://www.torn.com/item.php#medical-items",
		jailview: "https://www.torn.com/jailview.php",
		jobs: "https://www.torn.com/companies.php",
		loan: "https://www.torn.com/loan.php",
		messages: "https://www.torn.com/messages.php",
		missions: "https://www.torn.com/page.php?sid=missions",
		organizedCrimes: "https://www.torn.com/factions.php?step=your#/tab=crimes",
		pc: "https://www.torn.com/pc.php",
		points: "https://www.torn.com/page.php?sid=points",
		pointsmarket: "https://www.torn.com/pmarket.php",
		properties: "https://www.torn.com/properties.php",
		property_upkeep: "https://www.torn.com/properties.php#/p=options&tab=upkeep",
		property_vault: "https://www.torn.com/properties.php#/p=options&tab=vault",
		raceway: "https://www.torn.com/page.php?sid=racing",
		staff: "https://www.torn.com/staff.php",
		stocks: "https://www.torn.com/page.php?sid=stocks",
		trade: "https://www.torn.com/trade.php",
		travelagency: "https://www.torn.com/page.php?sid=travel"
	};
	LINKS.donator, LINKS.donator, LINKS.staff, LINKS.committee, LINKS.church, LINKS.jobs, LINKS.jobs, LINKS.jobs, LINKS.jobs, LINKS.jobs, LINKS.jobs, LINKS.companies, LINKS.companies, LINKS.companies, LINKS.faction, LINKS.faction, LINKS.faction, LINKS.faction, LINKS.faction, LINKS.education, LINKS.education, LINKS.bank, LINKS.bank, LINKS.travelagency, LINKS.property_vault, LINKS.loan, LINKS.auction, LINKS.bazaar, LINKS.itemmarket, LINKS.pointsmarket, LINKS.stocks, LINKS.stocks, LINKS.trade, LINKS.homepage, LINKS.raceway, LINKS.raceway, LINKS.faction_oc, LINKS.faction_oc, LINKS.faction_oc, LINKS.faction_oc, LINKS.bounties, LINKS.bank, LINKS.auction, LINKS.auction, LINKS.hospital, LINKS.hospital, LINKS.hospital, LINKS.jailview, LINKS.hospital, LINKS.items_booster, LINKS.items_booster, LINKS.items_booster, LINKS.items_booster, LINKS.items_booster, LINKS.items_medical, LINKS.items_medical, LINKS.items_medical, LINKS.items_medical, LINKS.items_medical, LINKS.items_drug, LINKS.items_drug, LINKS.items_drug, LINKS.items_drug, LINKS.items_drug, LINKS.travelagency, LINKS.travelagency, LINKS.travelagency, LINKS.travelagency, LINKS.travelagency, LINKS.items_medical, LINKS.items_medical, LINKS.items_medical, LINKS.items_medical, LINKS.items_medical, LINKS.property_upkeep, LINKS.property_upkeep, LINKS.property_upkeep;
	[
		{
			id: 1,
			reason: "Admin"
		},
		{
			id: 4,
			reason: "NPC"
		},
		{
			id: 7,
			reason: "NPC"
		},
		{
			id: 9,
			reason: "NPC"
		},
		{
			id: 10,
			reason: "NPC"
		},
		{
			id: 15,
			reason: "NPC"
		},
		{
			id: 17,
			reason: "NPC"
		},
		{
			id: 19,
			reason: "NPC"
		},
		{
			id: 20,
			reason: "NPC"
		},
		{
			id: 21,
			reason: "NPC"
		}
	].map(({ id }) => id);
	function is2FACheckPage() {
		return !!document.querySelector(".content-wrapper.logged-out .two-factor-auth-container");
	}
	function getPageStatus() {
		const infoMessage = document.querySelector(".content-wrapper .info-msg-cont");
		if (infoMessage?.classList.contains("red")) {
			if (infoMessage.textContent.includes("items in your inventory")) return { access: true };
			else if (findParent(infoMessage, { class: "no-parcel-wrap" })?.style?.display === "none") return { access: true };
			return {
				access: false,
				message: infoMessage.textContent
			};
		}
		if (document.querySelector(".captcha")) return {
			access: false,
			message: "Captcha required"
		};
		else if (document.querySelector(".dirty-bomb")) return {
			access: false,
			message: "Dirty bomb screen"
		};
		else if (is2FACheckPage()) return {
			access: false,
			message: "2 Factor Authentication"
		};
		return { access: true };
	}
	[...hasAPIData() && userdata.faction ? [{
		value: userdata.faction.tag,
		description: userdata.faction.tag
	}] : []];
	function createStatistics(name = "entries", addBrackets = false, lowercase = false) {
		const statistics = elementBuilder({
			type: "div",
			class: "statistics",
			children: [
				`${addBrackets ? "(" : ""}${lowercase ? "s" : "S"}howing `,
				elementBuilder({
					type: "strong",
					class: "stat-count",
					text: "X"
				}),
				" of ",
				elementBuilder({
					type: "strong",
					class: "stat-total",
					text: "Y"
				}),
				` ${name}${addBrackets ? ")" : "."}`
			]
		});
		function updateStatistics(count, total, content) {
			content.querySelector(".statistics .stat-count").textContent = count.toString();
			content.querySelector(".statistics .stat-total").textContent = total.toString();
		}
		return {
			element: statistics,
			updateStatistics
		};
	}
	function createFilterEnabledFunnel(partialOptions = {}) {
		const options = {
			id: getUUID(),
			class: "",
			...partialOptions
		};
		const iconWrapper = elementBuilder({
			type: "div",
			class: ["tt-filter-enabled-funnel", options.class],
			attributes: {
				id: options.id,
				title: "Disable this filter."
			}
		});
		let onChangeCallback;
		let enabled = false;
		function updateIcon() {
			iconWrapper.innerHTML = "";
			iconWrapper.appendChild(enabled ? PHFillFunnel() : PHFillFunnelX());
			iconWrapper.setAttribute("title", enabled ? "Disable this filter." : "Enable this filter.");
		}
		function setEnabled(isEnabled) {
			enabled = isEnabled;
			updateIcon();
		}
		function isEnabled() {
			return enabled;
		}
		function onChange(callback) {
			onChangeCallback = callback;
			iconWrapper.addEventListener("click", _onClickListener);
		}
		function dispose() {
			if (onChangeCallback) {
				iconWrapper.removeEventListener("click", _onClickListener);
				onChangeCallback = void 0;
			}
		}
		function _onClickListener(event) {
			event.stopPropagation();
			enabled = !enabled;
			updateIcon();
			onChangeCallback?.(enabled);
		}
		updateIcon();
		return {
			element: iconWrapper,
			setEnabled,
			isEnabled,
			onChange,
			dispose
		};
	}
	function sliderSection(options) {
		const { key, title, priority, config, defaults, formatCounter, test, enabled } = options;
		return {
			key,
			title,
			priority,
			enabled,
			build(onChange) {
				const slider = new DualRangeSlider({
					min: config.min,
					max: config.max,
					step: config.step,
					valueLow: defaults.low,
					valueHigh: defaults.high
				});
				const counter = elementBuilder({
					type: "div",
					class: "slider-counter",
					text: ""
				});
				const section = elementBuilder({
					type: "div",
					class: "tt-slider",
					children: [slider.slider, counter]
				});
				function readRange() {
					const low = parseInt(slider.slider.dataset.low) ?? config.min;
					const high = parseInt(slider.slider.dataset.high) ?? config.max;
					return {
						start: Math.min(low, high),
						end: Math.max(low, high)
					};
				}
				function updateCounter() {
					if (!formatCounter) return;
					counter.textContent = formatCounter(readRange());
				}
				updateCounter();
				new MutationObserver(() => {
					updateCounter();
					onChange();
				}).observe(slider.slider, { attributes: true });
				return {
					element: section,
					getValue: readRange
				};
			},
			test
		};
	}
	function multiSelectSection(options) {
		const { key, title, priority, items, defaults, test, enabled } = options;
		return {
			key,
			title,
			priority,
			enabled,
			build(onChange) {
				const multi = createMultiSelect({
					select: items,
					defaults
				});
				multi.onChange(onChange);
				return {
					element: multi.element,
					getValue: () => multi.getSelected()
				};
			},
			test
		};
	}
	function radioSection(options) {
		const { key, title, priority, items, defaultValue, test, orientation, enabled } = options;
		let name = options.name ?? key;
		return {
			key,
			title,
			priority,
			enabled,
			build(onChange) {
				const list = createRadioList(name, {
					items,
					orientation: orientation ?? "column"
				});
				list.setValue(defaultValue);
				list.onSelectionChange(onChange);
				return {
					element: list.element,
					getValue: () => list.getValue()
				};
			},
			test
		};
	}
	var DEFAULT_PRIORITY = 50;
	function createFilter(options) {
		const sections = [];
		const sectionDefs = options.sections ?? [];
		const { rowSelector, container: containerOpts, statisticsLabel, enabled: initialEnabled, onStateChange, onAfterRun, preserveHeight } = options;
		const rowHeight = typeof preserveHeight === "number" ? preserveHeight : 36;
		const { container, content, options: headerOptions } = createContainer(containerOpts.title, {
			filter: true,
			compact: true,
			...containerOpts
		});
		const statistics = createStatistics(statisticsLabel ?? "entries", false, false);
		content.appendChild(statistics.element);
		const sectionWrapper = elementBuilder({
			type: "div",
			class: "content"
		});
		content.appendChild(sectionWrapper);
		const wrapperMap = new Map();
		function _toggleSiblings(row, hide) {
			const sibling = row.nextElementSibling;
			if (!sibling) return;
			const isClass = (element, className) => element.classList.contains(className);
			const nextNext = sibling.nextElementSibling;
			if (isClass(sibling, "tt-last-action") || isClass(sibling, "tt-stats-estimate")) {
				sibling.classList.toggle("tt-hidden", hide);
				if (nextNext && (isClass(nextNext, "tt-last-action") || isClass(nextNext, "tt-stats-estimate"))) nextNext.classList.toggle("tt-hidden", hide);
			}
		}
		function _compensateHeight(rows) {
			if (!preserveHeight || !rows.length) return;
			const list = rows[0]?.parentElement;
			if (!list) return;
			const hiddenCount = rows.filter((r) => r.classList.contains("tt-hidden")).length;
			if (hiddenCount === 0) {
				delete list.style.minHeight;
				return;
			}
			const listTop = list.getBoundingClientRect().top;
			const visibleHeight = (rows.length - hiddenCount) * rowHeight;
			const contentBottom = listTop + visibleHeight;
			const target = window.innerHeight + 200;
			if (contentBottom >= target) {
				delete list.style.minHeight;
				return;
			}
			const compensated = visibleHeight + (target - contentBottom);
			const naturalHeight = rows.length * rowHeight;
			list.style.minHeight = `${Math.min(compensated, naturalHeight)}px`;
		}
		function applyFilter(rows, activeSections, values) {
			const activeSectionReasons = activeSections.map((s) => s.key);
			rowLoop: for (const row of rows) {
				for (const section of activeSections) try {
					if (!section.test(row, values.get(section.key))) {
						if (section.isExemption) continue;
						row.classList.add("tt-hidden");
						row.dataset.hideReason = section.key;
						_toggleSiblings(row, true);
						continue rowLoop;
					}
					if (section.isExemption) break;
				} catch (e) {
					console.warn(`TT Filters: Something went wrong when filtering '${section?.key}' in the '${containerOpts?.title}'`, e);
				}
				if (activeSectionReasons.includes(row.dataset.hideReason)) {
					row.classList.remove("tt-hidden");
					_toggleSiblings(row, false);
					delete row.dataset.hideReason;
				}
			}
		}
		async function run() {
			await requireElement(rowSelector);
			sections.sort((a, b) => a.priority - b.priority);
			sections.forEach((section) => section.onBeforeFilter?.());
			const enabled = funnel.isEnabled();
			const values = new Map();
			for (const section of sections) values.set(section.key, section.getValue());
			const state = { enabled };
			for (const [key, value] of values) state[key] = value;
			if (onStateChange) await onStateChange(state);
			if (!enabled) {
				findAllElements(`${rowSelector}.tt-hidden`).forEach((row) => {
					row.classList.remove("tt-hidden");
					delete row.dataset.hideReason;
				});
				_compensateHeight(findAllElements(rowSelector));
				const allRows = findAllElements(rowSelector);
				statistics.updateStatistics(allRows.length, allRows.length, content);
				await onAfterRun?.();
				return;
			}
			const rows = findAllElements(rowSelector);
			applyFilter(rows, sections, values);
			_compensateHeight(rows);
			const visible = rows.filter((r) => !r.classList.contains("tt-hidden")).length;
			statistics.updateStatistics(visible, rows.length, content);
			await onAfterRun?.();
		}
		async function runScoped(options) {
			const scopedRows = options?.rows ?? findAllElements(rowSelector);
			const activeSections = options?.sections ? sections.filter((s) => options.sections.includes(s.key)) : sections;
			activeSections.sort((a, b) => a.priority - b.priority);
			activeSections.forEach((s) => s.onBeforeFilter?.());
			const enabled = funnel.isEnabled();
			const values = new Map();
			for (const section of activeSections) values.set(section.key, section.getValue());
			if (!enabled) scopedRows.forEach((row) => {
				row.classList.remove("tt-hidden");
				delete row.dataset.hideReason;
			});
			else applyFilter(scopedRows, activeSections, values);
			_compensateHeight(scopedRows);
			const allRows = findAllElements(rowSelector);
			const visible = allRows.filter((r) => !r.classList.contains("tt-hidden")).length;
			statistics.updateStatistics(visible, allRows.length, content);
			await onAfterRun?.();
		}
		const funnel = createFilterEnabledFunnel();
		funnel.setEnabled(initialEnabled ?? true);
		funnel.onChange(() => run());
		headerOptions.appendChild(funnel.element);
		function addSection(section) {
			const trigger = () => run();
			const built = section.build?.(trigger);
			const isHeader = section.placement === "header";
			if (built) {
				const wrapper = elementBuilder({
					type: "div",
					children: [!isHeader && section.title ? elementBuilder({
						type: "strong",
						text: section.title
					}) : null, built.element]
				});
				if (isHeader) headerOptions.appendChild(wrapper);
				else sectionWrapper.appendChild(wrapper);
				wrapperMap.set(section.key, wrapper);
			}
			sections.push({
				key: section.key,
				priority: section.priority ?? DEFAULT_PRIORITY,
				isExemption: section.isExemption,
				getValue: built?.getValue.bind(built) ?? (() => true),
				test: section.test,
				onBeforeFilter: built?.onBeforeFilter?.bind(built)
			});
		}
		function removeSection(key) {
			const wrapper = wrapperMap.get(key);
			if (wrapper) {
				wrapper.remove();
				wrapperMap.delete(key);
			}
			const idx = sections.findIndex((s) => s.key === key);
			if (idx !== -1) sections.splice(idx, 1);
		}
		function rerenderSections() {
			sections.slice().forEach((s) => removeSection(s.key));
			sectionDefs.filter((def) => !def.enabled || def.enabled()).forEach(addSection);
			run();
		}
		rerenderSections();
		return {
			rerenderSections,
			run,
			runScoped,
			getRows(visible) {
				const rows = findAllElements(rowSelector);
				if (visible === void 0) return rows;
				return rows.filter((r) => visible ? !r.classList.contains("tt-hidden") : r.classList.contains("tt-hidden"));
			},
			reattach(position) {
				let parentElement;
				if ("parentElement" in position) parentElement = position.parentElement;
				else if ("nextElement" in position) parentElement = position.nextElement.parentElement;
				else if ("previousElement" in position) parentElement = position.previousElement.parentElement;
				else parentElement = document.querySelector(".content-wrapper");
				if ("nextElement" in position) parentElement.insertBefore(container, position.nextElement);
				else if ("previousElement" in position) parentElement.insertBefore(container, position.previousElement.nextSibling);
				else parentElement.appendChild(container);
			},
			dispose() {
				removeContainer(containerOpts.title);
				funnel.dispose();
				findAllElements(`${rowSelector}.tt-hidden`).forEach((row) => {
					row.classList.remove("tt-hidden");
					delete row.dataset.hideReason;
				});
				_compensateHeight(findAllElements(rowSelector));
			}
		};
	}
	var Feature = class {
		name;
		scope;
		executionTiming;
		constructor(name, scope, executionTiming = "CONTENT_LOADED") {
			this.name = name;
			this.scope = scope;
			this.executionTiming = executionTiming;
		}
		precondition() {
			return true;
		}
		initialise() {}
		execute(liveReload) {}
		cleanup() {}
		storageKeys() {
			return [];
		}
		requirements() {
			return true;
		}
		shouldTriggerEvents() {
			return false;
		}
		shouldLiveReload() {
			return false;
		}
		requiresScreenInformation() {
			return true;
		}
	};
	var filter;
	function initialiseListeners() {
		addCustomListener(EVENT_CHANNELS.PROPERTIES__ROUTE, async () => {
			if (!FEATURE_MANAGER.isEnabled(PropertiesFilterFeature)) return;
			filter?.reattach({ previousElement: document.querySelector(".properties-tabs") });
			await filter?.run();
		});
		addCustomListener(EVENT_CHANNELS.PROPERTIES__ROUTE_PAGE, async () => {
			if (!FEATURE_MANAGER.isEnabled(PropertiesFilterFeature)) return;
			filter?.reattach({ previousElement: document.querySelector(".properties-tabs") });
			await filter?.run();
		});
	}
	var REGEX_LEASED = /Leased to (?<name>.*) \((?<left>\d+) \/ (?<total>\d+) days\)/;
	var PROPERTY_TYPES = [
		"Private Island",
		"Castle",
		"Palace",
		"Ranch",
		"Mansion",
		"Penthouse",
		"Villa",
		"Chalet",
		"Beach House",
		"Detached House",
		"Semi-Detached House",
		"Apartment",
		"Trailer"
	];
	async function addFilterContainer() {
		await requireElement(".properties-list > li");
		torndata?.properties;
		const sections = [
			sliderSection({
				key: "daysOnLease",
				title: "Days left on lease",
				config: {
					min: 1,
					max: 100,
					step: 1
				},
				defaults: {
					low: filters.properties.daysOnLeaseLow,
					high: filters.properties.daysOnLeaseHigh
				},
				formatCounter: ({ start, end }) => `${start} - ${end} days`,
				test: (row, range) => {
					const description = row.querySelector(".image-description > span");
					if (!description) return true;
					const leaseMatch = REGEX_LEASED.exec(description.textContent.trim());
					if (!leaseMatch) return true;
					const days = parseInt(leaseMatch.groups.left);
					if (isNaN(days)) return false;
					return days >= range.start && days <= range.end;
				}
			}),
			radioSection({
				key: "status",
				title: "Status",
				items: [
					{
						description: "All",
						value: "all"
					},
					{
						description: "Occupied",
						value: "occupied"
					},
					{
						description: "Leased",
						value: "leased"
					},
					{
						description: "Empty",
						value: "empty"
					}
				],
				defaultValue: filters.properties.status,
				test: (row, status) => {
					if (status === "all") return true;
					const description = row.querySelector(".image-description > span")?.textContent.toLowerCase();
					if (!description) return false;
					if (status === "occupied") return description?.includes("living");
					else if (status === "leased") return description?.includes("leased to");
					else if (status === "empty") return description?.includes("owned");
					else return true;
				}
			}),
			multiSelectSection({
				key: "types",
				title: "Types",
				items: PROPERTY_TYPES.map((property) => ({
					value: property,
					description: property
				})),
				defaults: filters.properties.types,
				test: (row, types) => {
					if (!types.length) return true;
					const type = row.querySelector(".image-place img[alt]").getAttribute("alt").replace("Spouse's ", "");
					return types.includes(type);
				}
			})
		];
		filter = createFilter({
			rowSelector: ".properties-list > li:not(.clear)",
			container: {
				title: "Properties Filter",
				class: "mt10 mb10",
				previousElement: document.querySelector(".properties-tabs")
			},
			statisticsLabel: "properties",
			enabled: filters.properties.enabled,
			sections,
			onStateChange: async (state) => {
				await ttStorage.change({ filters: { properties: {
					enabled: state.enabled,
					daysOnLeaseLow: state.daysOnLease.start,
					daysOnLeaseHigh: state.daysOnLease.end,
					status: state.status,
					types: state.types
				} } });
			},
			onAfterRun: () => {
				filter?.getRows().forEach((row) => row.classList.remove("t-m-clear", "m-clear"));
				filter?.getRows(true).forEach((row, index) => {
					if ((index + 1) % 2 === 0) row.classList.add("t-m-clear");
					if ((index + 1) % 3 === 0) row.classList.add("m-clear");
				});
			}
		});
	}
	var PropertiesFilterFeature = class extends Feature {
		constructor() {
			super("Properties Filter", "properties");
		}
		precondition() {
			return getPageStatus().access;
		}
		isEnabled() {
			return settings.pages.property.filter;
		}
		initialise() {
			initialiseListeners();
		}
		async execute() {
			await addFilterContainer();
		}
		cleanup() {
			filter?.getRows().forEach((row, index) => {
				row.classList.remove("t-m-clear", "m-clear");
				if ((index + 1) % 2 === 0) row.classList.add("t-m-clear");
				if ((index + 1) % 3 === 0) row.classList.add("m-clear");
			});
			filter?.dispose();
		}
		storageKeys() {
			return ["settings.pages.property.filter"];
		}
	};
	_css(".tt-hidden{display:none!important}.tt-black-overlay{z-index:100;background-color:#00000059;width:100%;height:100%;position:fixed;top:0;left:0}.no-margin{margin:0}.tt-delimiter{border-top:#ccc;border-left:none;border-right:none;border-top:1px solid var(--sidebar-horizontal-divider-bg-color);border-bottom:#fff;border-bottom:1px solid var(--sidebar-horizontal-divider-shadow-color);height:0;margin-bottom:5px;overflow:hidden}.tt-overlay{z-index:1000000;background-color:#00000059;width:100%;height:100%;position:fixed;top:0;left:0}.tt-overlay-item,.tt-overlay-item-notbroken{z-index:999999999;position:relative}.tt-overlay-item .tt-overlay-ignore{z-index:0;pointer-events:none}.tt-overlay-item .tt-overlay-ignore:before{content:\"\";z-index:1000000;background-color:#00000059;width:100%;height:100%;position:absolute;top:0;left:0}.relative{position:relative}.flex-break{border:0;height:0;margin:0;flex-basis:100%!important}.mt10{margin-top:10px}.mb10{margin-bottom:10px}.t-flex{display:flex}[class*=torn-icon-]{vertical-align:middle;background:url(https://www.torn.com/images/v2/city/location_icons_34x34px.svg) no-repeat;width:34px;height:34px;display:inline-block}.torn-icon-item-market{background-position:-68px -34px}.tt-sidebar-area{margin-top:2px;overflow:hidden}.tt-sidebar-area>div{cursor:pointer;vertical-align:top;background-color:var(--default-bg-panel-color);border-top-right-radius:5px;border-bottom-right-radius:5px;position:relative;overflow:hidden}.tt-sidebar-area a{color:var(--default-content-font-color);justify-content:flex-start;align-items:center;height:100%;text-decoration:none;display:flex;overflow:hidden}.tt-sidebar-area a span{float:none;vertical-align:middle;margin-left:10px;display:inline-block}.tt-button-link{cursor:pointer;color:var(--default-blue-color)}.tt-btn{background-color:var(--tt-color-light-green);color:#000;border-radius:6px;width:fit-content}.tt-btn:not([disabled]){cursor:pointer}.tt-btn[disabled]{cursor:not-allowed;opacity:.4}.tt-msg-box{background:var(--info-msg-grey-gradient);box-shadow:var(--info-msg-box-shadow);color:var(--info-msg-font-color);border-radius:5px;margin-top:10px;font-size:0;line-height:16px}.tt-msg-box .tt-msg-div{background:var(--info-msg-horizontal-gradient);border-radius:5px;justify-content:flex-start;display:flex}.tt-msg-box .tt-msg{vertical-align:middle;background-color:var(--default-bg-panel-active-color);background:var(--info-msg-delimiter-gradient);border-radius:0 5px 5px 0;flex-grow:1;width:1px;height:auto}.tt-msg-box .tt-content{vertical-align:middle;color:var(--info-msg-font-color);background-color:var(--default-bg-panel-active-color);background:var(--info-msg-bg-gradient);border-radius:0 5px 5px 0;padding:10px;font-size:13px;position:relative}.tt-message-box{color:var(--info-msg-font-color);box-shadow:var(--info-msg-box-shadow);border-radius:5px;margin-top:10px;font-size:13px;display:flex}.tt-message-box .tt-message-icon-wrap{background:var(--info-msg-grey-gradient);border-radius:5px 0 0 5px;width:34px}.tt-message-box .tt-message-icon{background:var(--info-msg-horizontal-gradient);border-radius:5px 0 0 5px;justify-content:center;width:34px;height:100%;display:flex}.tt-message-box .tt-svg{width:34px;height:34px}.tt-message-box .tt-message-wrap{background-color:var(--default-bg-panel-active-color);background:var(--info-msg-bg-gradient);border-radius:0 5px 5px 0;flex-grow:1;align-items:center;padding:10px;display:flex}.tt-message-box .tt-message{flex-grow:1}.tt-svg{width:128px;height:128px}.tt-svg .tt-svg-upper{stroke:#000;fill:#000}.tt-svg .tt-svg-lower{stroke:#568725;fill:#568725}#sidebarroot .pill{cursor:pointer;background-color:var(--default-bg-panel-color);min-height:22px;color:var(--default-font-color);border-top-right-radius:5px;border-bottom-right-radius:5px;align-items:center;margin-top:2px;text-decoration:none;display:flex;overflow:hidden}#sidebarroot .pill:not([icon]){box-sizing:border-box;padding-top:5px;padding-bottom:5px}#sidebarroot .pill:not([icon]),#sidebarroot .pill[icon] span{height:100%;color:var(--default-font-color);justify-content:flex-start;align-items:center;padding-left:8px;text-decoration:none;display:flex;overflow:hidden}body.tt-tablet #sidebarroot .pill{min-height:34px}body[data-layout=hospital] #sidebarroot .pill{margin-top:0;margin-bottom:1px}#sidebarroot .pill:hover{background-color:var(--default-bg-panel-active-color)!important}.tt-sidebar-information{flex-direction:column;display:flex}.tt-sidebar-information .title{color:inherit;margin:inherit;font-weight:700;text-decoration:none}.tt-sidebar-information .countdown.short{color:var(--tt-color-red)}.tt-sidebar-information .countdown.medium{color:var(--tt-color-orange)}.tt-top-icons{gap:10px;display:flex}");
	_css(":root{--tt-color-green:#00a500;--tt-color-light-green:#acea00;--tt-color-red:#d83500;--tt-color-green--20:#00a50033;--tt-color-green--30:#00a5004d;--tt-color-green--40:#00a50066;--tt-background-torn-gray:repeating-linear-gradient(90deg, #627e0d, #627e0d 2px, #6e8820 0, #6e8820 4px);--tt-background-green:repeating-linear-gradient(90deg, #627e0d, #627e0d 2px, #6e8820 0, #6e8820 4px);--tt-background-alternative:repeating-linear-gradient(90deg, #242424, #242424 2px, #2e2e2e 0, #2e2e2e 4px)}body:not(.dark-mode){--tt-color-blue:blue;--tt-color-orange:orange;--tt-color-item-text:#678c00;--tt-color-item-quantity:black;--tt-background-popup:#f1f1f1;--tt-shadow-popup:unset}body.dark-mode{--tt-color-blue:#058cff;--tt-color-orange:gold;--tt-color-item-text:#9c0;--tt-color-item-quantity:#ddd;--tt-background-popup:#444;--tt-shadow-popup:0 0 10px black}.tt-color-green{color:var(--tt-color-green)}.tt-color-red{color:var(--tt-color-red)}");
	function handleDeviceSizeClasses() {
		checkDevice().then(({ mobile, tablet }) => {
			if (mobile) document.body.classList.add("tt-mobile");
			else document.body.classList.remove("tt-mobile");
			if (tablet) document.body.classList.add("tt-tablet");
			else document.body.classList.remove("tt-tablet");
		});
	}
	var ScriptFeatureManager = class {
		constructor() {
			this.getScriptState();
		}
		createPopup() {}
		isEnabled(featureConstructor) {
			return this.getScriptState().enabled[new featureConstructor().name];
		}
		registerFeature(feature) {
			if (feature.requiresScreenInformation()) handleDeviceSizeClasses();
			feature.initialise();
			feature.execute();
			this.getScriptState().enabled[feature.name] = true;
			if (feature.shouldTriggerEvents()) triggerCustomListener(EVENT_CHANNELS.FEATURE_ENABLED, { name: feature.name });
		}
		getScriptState() {
			const win = RUNTIME_INFORMATION.getWindow();
			if (!win.ttScriptState) {
				const newState = { enabled: {} };
				win.ttScriptState = newState;
				return newState;
			}
			return win.ttScriptState;
		}
	};
	function isPDA() {
		return typeof PDA_evaluateJavascript === "function";
	}
	function registerCoreUserscriptContext() {
		setRuntimeInformation(UserscriptRuntimeInformation);
		setFeatureManager(new ScriptFeatureManager());
		setEventHandler(ScriptEventHandler);
		initializeScriptTheme();
	}
	function initializeScriptTheme() {
		document.documentElement.style.setProperty("--tt-theme-color", "#fff");
		document.documentElement.style.setProperty("--tt-theme-background", "var(--tt-background-green)");
	}
	var UserscriptRuntimeInformation = {
		getWindow() {
			return unsafeWindow;
		},
		getVersion() {
			return GM.info.version;
		},
		isUserscript() {
			return true;
		},
		reloadWindow(force) {
			if (isPDA()) window.flutter_inappwebview.callHandler("reloadPage");
			else location.reload(force);
		}
	};
	var ScriptEventHandler = {
		triggerEvent(channel, payload) {
			document.dispatchEvent(new CustomEvent(`TT_${channel}`, { detail: payload }));
		},
		registerListener(channel, listener) {
			document.addEventListener(`TT_${channel}`, (event) => {
				if (!isCustomEvent(event)) return;
				listener(event.detail);
			});
		},
		get eventRoot() {
			return document;
		},
		triggerEventCrossWorld(target, channel, payload) {
			target.dispatchEvent(new CustomEvent(`TT_${channel}`, { detail: payload }));
		},
		registerListenerCrossWorld(target, channel, listener) {
			target.addEventListener(`TT_${channel}`, (event) => {
				if (!isCustomEvent(event)) return;
				listener(event.detail);
			});
		}
	};
	var TornToolsStorage = class {
		async update(key, fn) {
			const database = await this.get(key);
			fn(database);
			await this.set({ [key]: database });
		}
		async change(object) {
			const keys = Object.keys(object);
			for (const key of keys) {
				const data = this.recursive(await this.get(key), object[key]);
				await this.set({ [key]: data });
			}
		}
		recursive(parent, toChange) {
			for (const key in toChange) if (parent && typeof parent === "object" && !Array.isArray(parent[key]) && key in parent && typeof toChange[key] === "object" && !Array.isArray(toChange[key]) && toChange[key] !== null) parent[key] = this.recursive(parent[key], toChange[key]);
			else if (parent && typeof parent === "object") {
				const value = toChange[key];
				parent[key] = Array.isArray(value) ? Array.from(value) : value;
			} else parent = { [key]: toChange[key] };
			return parent;
		}
	};
	var TTScriptStorage = class extends TornToolsStorage {
		prefix;
		constructor(prefix) {
			super();
			this.prefix = prefix;
		}
		storageKey(key) {
			return key === "cache" ? key : `${this.prefix}_${key}`;
		}
		async get(key) {
			if (Array.isArray(key)) return await Promise.all(key.map((k) => this.storageKey(k)).map((k) => GM.getValue(k)));
			else if (key) return await GM.getValue(this.storageKey(key));
			else {
				const storageKeys = Object.keys(DEFAULT_STORAGE);
				const storageValues = await this.get(storageKeys);
				return storageKeys.reduce((total, k, i) => {
					total[k] = storageValues[i];
					return total;
				}, {});
			}
		}
		async set(object) {
			await Promise.all(Object.entries(object).map(([key, value]) => {
				UserscriptRuntimeStorage.callback({ [key]: {
					newValue: value,
					oldValue: null
				} }, "local");
				return GM.setValue(this.storageKey(key), value);
			}));
		}
		remove(_key) {
			throw new Error("Method not implemented.");
		}
		clear() {
			throw new Error("Method not implemented.");
		}
		reset(_key) {
			throw new Error("Method not implemented.");
		}
		getSize() {
			throw new Error("Method not implemented.");
		}
	};
	var PDAScriptStorage = class extends TornToolsStorage {
		async get(key) {
			if (Array.isArray(key)) return await Promise.all(key.map(this.get.bind(this)));
			else if (key) {
				if (key === "cache") return await GM.getValue(key);
				else return await PDA_storage.get(key);
			} else {
				const storageKeys = Object.keys(DEFAULT_STORAGE);
				const storageValues = await this.get(storageKeys);
				return storageKeys.reduce((total, k, i) => {
					total[k] = storageValues[i];
					return total;
				}, {});
			}
		}
		async set(object) {
			await Promise.all(Object.entries(object).map(([key, value]) => {
				UserscriptRuntimeStorage.callback({ [key]: {
					newValue: value,
					oldValue: null
				} }, "local");
				if (key === "cache") return GM.setValue(key, value);
				else return PDA_storage.set(key, value);
			}));
		}
		async remove(key) {
			if (typeof key === "string") await PDA_storage.delete(key);
			else await Promise.all(key.map(PDA_storage.delete));
		}
		async clear() {
			await Promise.all((await PDA_storage.list()).map(PDA_storage.delete));
		}
		reset(_key) {
			throw new Error("Method not implemented.");
		}
		async getSize() {
			return (await PDA_storage.usage()).used;
		}
	};
	async function registerDatabaseUserscriptContext(storagePrefix) {
		setTTStorage(isPDA() ? new PDAScriptStorage() : new TTScriptStorage(storagePrefix));
		setRuntimeStorage(UserscriptRuntimeStorage);
		await migrateDatabase(true);
		initializeDatabaseListener();
		const [localdata, filters, cache] = await ttStorage.get([
			"localdata",
			"filters",
			"cache"
		]);
		setLocaldata(localdata ? localdata : getDefaultStorage(DEFAULT_STORAGE.localdata));
		setFilters(filters ? filters : getDefaultStorage(DEFAULT_STORAGE.filters));
		ttCache.cache = cache ? cache : getDefaultStorage(DEFAULT_STORAGE.cache);
	}
	var UserscriptRuntimeStorage = {
		callback: () => {},
		addChangeListener(callback) {
			this.callback = callback;
		}
	};
	(async () => {
		registerCoreUserscriptContext();
		await registerDatabaseUserscriptContext("tt_pf");
		setupPropertiesPage();
		FEATURE_MANAGER.registerFeature(new PropertiesFilterFeature());
	})();
})();