FF Scouter V2

Shows the expected Fair Fight score against targets and faction war status

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

// ==UserScript==
// @name         FF Scouter V2
// @namespace    Violentmonkey Scripts
// @version      3.0.1
// @author       xentac [3354782], MAVRI [2402357], rDacted [2670953], Weav3r [1853324], Glasnost [1844049]
// @description  Shows the expected Fair Fight score against targets and faction war status
// @license      GPLv3
// @copyright    2026, xentac
// @match        https://www.torn.com/*
// @connect      ffscouter.com
// @grant        GM_xmlhttpRequest
// @grant        unsafeWindow
// @run-at       document-start
// ==/UserScript==

(function () {
  'use strict';

  const n=new Set;const importCSS = async e=>{n.has(e)||(n.add(e),(d=>{const t=document.createElement("style");t.textContent=d,(document.head||document.documentElement).appendChild(t);})(e));};

  importCSS(" ._ffscouter-info-line__label_xi5zk_1{font-weight:700;margin-right:6px}._ffscouter-info-line__badge_xi5zk_8{font-weight:700;padding:2px 6px;border-radius:4px;display:inline-block}._ffscouter-info-line__premium-upgrade_xi5zk_15{display:block;margin-top:4px;line-height:1.3;white-space:nowrap;font-size:12px;font-style:normal}@media(max-width:768px){._ffscouter-info-line__premium-upgrade_xi5zk_15{margin-top:6px;line-height:1.35;white-space:normal;overflow-wrap:anywhere}}._ff-filter-box_ursux_1,._ff-filter-box_ursux_1 *,._ff-filter-box_ursux_1 *:before,._ff-filter-box_ursux_1 *:after{box-sizing:border-box!important}._ff-filter-box_ursux_1{background-color:var(--ffscouter-bg-color);border:1px solid var(--ffscouter-border-color);border-radius:8px;padding:12px 16px;margin-bottom:16px;color:var(--ffscouter-text-color);font-family:Arial,sans-serif;box-shadow:0 2px 5px #0000000d}._ff-filter-box_ursux_1._ff-filter-box--no-borders_ursux_19{background-color:var(--default-bg-panel-color);border-top:1px solid var(--ffscouter-border-color);border-bottom:1px solid var(--ffscouter-border-color);border-left:none;border-right:none;border-radius:0;box-shadow:none;padding:12px 10px;margin:0}._ff-filter-box_ursux_1 summary{cursor:pointer;font-size:14px;font-weight:700;outline:none;-webkit-user-select:none;user-select:none}._ff-filter-box_ursux_1[open] summary{border-bottom:1px solid var(--ffscouter-border-color);padding-bottom:6px;margin-bottom:12px}._ff-filter-box_ursux_1 summary:focus-visible{outline:2px solid var(--ffscouter-glow-color);outline-offset:2px}._ff-filter-box__header_ursux_52{display:inline-flex;justify-content:space-between;align-items:center;width:calc(100% - 24px);vertical-align:middle}._ff-filter-box__header-actions_ursux_60{display:flex;gap:6px;align-items:center}._ff-filter-box_ursux_1 ._ff-filter-box__action-btn_ursux_66{background:var(--ffscouter-alt-bg-color);border:1px solid var(--ffscouter-border-color);border-radius:4px;color:var(--ffscouter-text-color);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;transition:background-color .2s,color .2s,opacity .2s}._ff-filter-box_ursux_1 ._ff-filter-box__action-btn_ursux_66:hover{background-color:var(--ffscouter-hover-color)}._ff-filter-box_ursux_1 ._ff-filter-box__action-btn_ursux_66._ff-filter-box__action-btn--active_ursux_88{color:var(--ffscouter-text-color);opacity:1}._ff-filter-box_ursux_1 ._ff-filter-box__action-btn_ursux_66._ff-filter-box__action-btn--inactive_ursux_93{color:var(--ffscouter-text-color);opacity:.4}._ff-filter-box_ursux_1 ._ff-filter-box__action-btn_ursux_66 svg{width:14px;height:14px;fill:currentColor}._ff-filter-box_ursux_1 ._ff-filter-box__action-btn_ursux_66._ff-filter-box__action-btn--reset_ursux_104 svg{transition:transform .25s ease-in-out}._ff-filter-box_ursux_1 ._ff-filter-box__action-btn_ursux_66._ff-filter-box__action-btn--reset_ursux_104:hover svg{transform:rotate(-180deg)}._ff-filter-box__grid_ursux_114{display:grid;grid-template-columns:repeat(2,1fr);gap:12px;margin-top:12px}._ff-filter-box__group--sort_ursux_121{order:1}._ff-filter-box__group--level_ursux_125{order:2}._ff-filter-box__group--activity_ursux_129{order:3}._ff-filter-box__group--status_ursux_133{order:4}._ff-filter-box__group--ff_ursux_137{order:5}._ff-filter-box__group--stats_ursux_141{order:6}._ff-filter-box__group--last-action_ursux_145{order:7}._ff-filter-box__group--columns_ursux_149{order:8}@media(min-width:784px){._ff-filter-box__grid_ursux_114{grid-template-columns:repeat(3,1fr)}._ff-filter-box__grid_ursux_114>*{order:0}}._ff-filter-box__group_ursux_121{display:flex;flex-direction:column;gap:2px}._ff-filter-box__sort-controls_ursux_171{display:flex;flex-direction:column;gap:8px}._ff-filter-box__sort-controls_ursux_171 ._ff-filter-box__sort-btn_ursux_177{width:100%}._ff-filter-box__sort-controls_ursux_171 ._ff-filter-box__compare-btn_ursux_181{width:100%;height:32px}._ff-filter-box__display-select_ursux_186{padding:4px;border:1px solid var(--ffscouter-border-color);border-radius:4px;background:var(--ffscouter-alt-bg-color);color:var(--ffscouter-text-color);font-size:11px;cursor:pointer;height:32px}._ff-filter-box__options_ursux_197{display:flex;flex-direction:column}._ff-filter-box__options_ursux_197 label{display:flex;align-items:center;gap:6px;font-size:12px;cursor:pointer}._ff-filter-box__range-inputs_ursux_210{display:flex;align-items:center;gap:4px}._ff-filter-box__range-inputs_ursux_210 input{flex:1;width:0;min-width:30px;max-width:80px;padding:4px;border:1px solid var(--ffscouter-border-color);border-radius:4px;background:var(--ffscouter-alt-bg-color);color:var(--ffscouter-text-color);font-size:11px;text-align:center}._ff-filter-box_ursux_1 button{padding:6px 10px;border:1px solid var(--ffscouter-border-color);border-radius:4px;background:var(--ffscouter-alt-bg-color);color:var(--ffscouter-text-color);font-size:12px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:6px;transition:background-color .2s}._ff-filter-box_ursux_1 button:hover{background-color:var(--ffscouter-hover-color)}._ff-settings-panel__accordion_6bhvd_1{margin:10px 0;padding:15px;background-color:var(--ffscouter-bg-color);border:1px solid var(--ffscouter-border-color);border-radius:5px;color:var(--ffscouter-text-color)}._ff-settings-panel__accordion_6bhvd_1._ff-settings-panel__accordion--glow_6bhvd_10{border-color:var(--ffscouter-glow-color);box-shadow:0 0 8px #4caf5080}._ff-settings-panel__accordion_6bhvd_1 summary{cursor:pointer;font-weight:700}._ff-settings-panel__body_6bhvd_20{margin-top:15px}._ff-settings-panel__input-row_6bhvd_24{display:flex;flex-direction:column;gap:5px;margin-bottom:15px}._ff-settings-panel__range-row_6bhvd_32{display:flex;gap:10px;align-items:center}._ff-settings-panel__blur_6bhvd_38{filter:blur(4px);transition:filter .2s ease}._ff-settings-panel__blur_6bhvd_38:hover,._ff-settings-panel__blur_6bhvd_38:focus{filter:blur(0)}._ff-settings-panel__error-msg_6bhvd_48{color:#f33;font-size:13px;margin-top:5px}._ff-settings-panel__accordion_6bhvd_1 input[type=text],._ff-settings-panel__accordion_6bhvd_1 input[type=number]{box-sizing:border-box!important;text-align:left;vertical-align:top;width:178px;height:34px!important;margin-right:8px;padding:9px 10px;line-height:14px;display:inline-block}._ff-settings-panel__accordion_6bhvd_1 input[type=number]._ff-settings-panel__number_6bhvd_67{width:80px}._ff-settings-panel__accordion_6bhvd_1 select{box-sizing:border-box;text-align:left;vertical-align:top;width:178px;height:34px;margin-right:8px;padding:8px 10px;line-height:14px;display:inline-block;border:var(--input-border-color, 1px solid var(--ffscouter-border-color));border-radius:5px;font-family:Arial,serif;color:var(--input-color, var(--ffscouter-text-color));background:var(--input-background-color, var(--ffscouter-alt-bg-color))}.dark-mode ._ff-settings-panel__accordion_6bhvd_1 select option{background-color:#000;color:var(--input-color)}._ff-settings-panel__api-explanation_6bhvd_94{color:var(--ffscouter-text-color);margin-bottom:20px;font-size:13px;line-height:1.5}._ff-settings-panel__accordion_6bhvd_1 a{color:var(--ffscouter-success-color);text-decoration:underline}._ff-settings-panel__premium-badge_6bhvd_107{display:inline-block;color:#fff;font-size:11px;font-weight:700;padding:2px 8px;border-radius:4px;vertical-align:middle}._ff-settings-panel__premium-badge--enabled_6bhvd_117{background:#4caf50}._ff-settings-panel__premium-badge--disabled_6bhvd_121{background:#c62828}._ff-settings-panel__premium-badge--unknown_6bhvd_125{background:#f39c12}._ff-settings-panel__section_6bhvd_138{display:grid;grid-template-columns:1fr;gap:12px;margin-bottom:15px}@media(min-width:784px){._ff-settings-panel__section_6bhvd_138{grid-template-columns:repeat(3,1fr)}}._ff-settings-panel__span_6bhvd_152{grid-column:1 / -1;margin-bottom:0}._ff-settings-panel__cell_6bhvd_160{display:flex;flex-direction:column;gap:5px;min-width:0;margin-bottom:0}._ff-settings-panel__cell_6bhvd_160._ff-settings-panel__cell--checkbox_6bhvd_169{flex-direction:row;align-items:flex-start;gap:10px}._ff-settings-panel__cell_6bhvd_160 input[type=text]{width:100%;margin-right:0}._ff-settings-panel__cell_6bhvd_160 select{width:auto;max-width:100%;margin-right:0}._ff-settings-panel__api-block_6bhvd_193{display:flex;flex-direction:column;gap:10px}._ff-settings-panel__api-block_6bhvd_193 ._ff-settings-panel__cell_6bhvd_160 input[type=text]{max-width:360px}._ff-settings-panel__api-status-row_6bhvd_203{display:flex;flex-wrap:wrap;align-items:center;gap:10px}._ff-settings-panel__chain-suboptions_6bhvd_213{border-left:2px solid var(--ffscouter-border-color);padding-left:8px;margin-top:10px;grid-template-columns:repeat(2,1fr)}._ff-settings-panel__chain-suboptions_6bhvd_213 ._ff-settings-panel__chain-wide_6bhvd_218{grid-column:1 / -1}@media(min-width:784px){._ff-settings-panel__chain-suboptions_6bhvd_213{padding-left:16px;grid-template-columns:repeat(3,1fr)}._ff-settings-panel__chain-suboptions_6bhvd_213 ._ff-settings-panel__chain-wide_6bhvd_218{grid-column:auto}}._ff-settings-panel__group_6bhvd_242{background-color:var(--ffscouter-alt-bg-color);border:1px solid var(--ffscouter-border-color);border-radius:5px;padding:12px;margin-bottom:15px}._ff-settings-panel__group_6bhvd_242 h4{margin:0 0 12px}._ff-settings-panel__marker-size_6bhvd_256,._ff-settings-panel__marker-border-width_6bhvd_257{display:flex;flex-direction:column;gap:5px}._ff-settings-panel__marker-size-controls_6bhvd_263{display:flex;flex-wrap:wrap;align-items:center;gap:10px}._ff-settings-panel__marker-size-controls_6bhvd_263 input[type=range]{flex:1 1 120px;min-width:120px}._ff-settings-panel__color-scheme_6bhvd_276{display:flex;flex-direction:column;gap:5px}._ff-settings-panel__color-scheme-controls_6bhvd_282{display:flex;flex-wrap:wrap;align-items:center;gap:10px}._ff-settings-panel__accordion_6bhvd_1 .ffscouter-swatch-row{flex-wrap:wrap}._ff-settings-panel__actions_6bhvd_295{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:10px;margin-top:20px}._ff-settings-panel__saved-msg_6bhvd_304{color:#4caf50} ");

  var StartTime = ((StartTime2) => {
    StartTime2[StartTime2["DocumentStart"] = 0] = "DocumentStart";
    StartTime2[StartTime2["DocumentBody"] = 1] = "DocumentBody";
    StartTime2[StartTime2["DocumentEnd"] = 2] = "DocumentEnd";
    return StartTime2;
  })(StartTime || {});
  let _react$1;
  function getReact$1() {
    return _react$1 ??= unsafeWindow.React;
  }
  const FRAGMENT_SENTINEL = Symbol("ReactFragment");
  const Fragment = FRAGMENT_SENTINEL;
  function jsx(type, { children, ...props }, key) {
    const R = getReact$1();
    const realType = type === FRAGMENT_SENTINEL ? R.Fragment : type;
    if (key !== void 0) props.key = key;
    if (children === void 0) {
      return R.createElement(realType, props);
    }
    return Array.isArray(children) ? R.createElement(realType, props, ...children) : R.createElement(realType, props, children);
  }
  const jsxs = jsx;
  function isInPDA() {
    return typeof window !== "undefined" && typeof window.PDA_httpGet === "function";
  }
  var LogLevel = ((LogLevel2) => {
    LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
    LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
    LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
    LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
    LogLevel2[LogLevel2["NONE"] = 4] = "NONE";
    return LogLevel2;
  })(LogLevel || {});
  class Logger {
constructor(prefix = "", defaultLevel = 1, state = {}) {
      this.isPDA = false;
      this.colors = {
        debug: "#7f8c8d",
        info: "#3498db",
        warn: "#f39c12",
        error: "#e74c3c"
      };
      this.prefix = prefix;
      this.defaultLevel = defaultLevel;
      this.state = state;
      this.detectPDA();
    }
    detectPDA() {
      this.isPDA = isInPDA();
    }
setLevel(level) {
      this.state.explicitLevel = level;
    }
getLevel() {
      return this.state.explicitLevel !== void 0 ? this.state.explicitLevel : this.defaultLevel;
    }
debug(...args) {
      if (this.getLevel() <= 0) {
        if (this.isPDA) {
          console.log(`${this.formatPrefix("DEBUG")}`, ...this.formatArgs(args));
        } else {
          console.log(
            `%c${this.formatPrefix("DEBUG")}`,
            `color: ${this.colors.debug}; font-weight: bold`,
            ...args
          );
        }
      }
    }
info(...args) {
      if (this.getLevel() <= 1) {
        if (this.isPDA) {
          console.info(`${this.formatPrefix("INFO")}`, ...this.formatArgs(args));
        } else {
          console.info(
            `%c${this.formatPrefix("INFO")}`,
            `color: ${this.colors.info}; font-weight: bold`,
            ...args
          );
        }
      }
    }
warn(...args) {
      if (this.getLevel() <= 2) {
        if (this.isPDA) {
          console.warn(`${this.formatPrefix("WARN")}`, ...this.formatArgs(args));
        } else {
          console.warn(
            `%c${this.formatPrefix("WARN")}`,
            `color: ${this.colors.warn}; font-weight: bold`,
            ...args
          );
        }
      }
    }
error(...args) {
      if (this.getLevel() <= 3) {
        if (this.isPDA) {
          console.error(
            `${this.formatPrefix("ERROR")}`,
            ...this.formatArgs(args)
          );
        } else {
          console.error(
            `%c${this.formatPrefix("ERROR")}`,
            `color: ${this.colors.error}; font-weight: bold`,
            ...args
          );
        }
      }
    }
group(label, collapsed = false) {
      if (this.getLevel() < 4) {
        if (collapsed) {
          console.groupCollapsed(this.formatPrefix(""), label);
        } else {
          console.group(this.formatPrefix(""), label);
        }
      }
    }
groupEnd() {
      if (this.getLevel() < 4) {
        console.groupEnd();
      }
    }
child(subPrefix) {
      const childPrefix = this.prefix ? `${this.prefix}:${subPrefix}` : subPrefix;
      return new Logger(childPrefix, this.defaultLevel, this.state);
    }
formatPrefix(level) {
      const prefix = this.prefix ? `[${this.prefix}]` : "";
      return level ? `${prefix} - [${level}]: ` : `${prefix}: `;
    }
formatArgs(args) {
      return args.map((arg) => {
        if (isDOMNode(arg)) {
          return formatDOMNode(arg);
        }
        if (typeof arg === "object" && arg !== null) {
          try {
            const seen = new WeakSet();
            return JSON.stringify(
              arg,
              (_key, val) => {
                if (typeof val === "object" && val !== null) {
                  if (seen.has(val)) {
                    return "[Circular]";
                  }
                  seen.add(val);
                }
                if (isDOMNode(val)) {
                  return formatDOMNode(val);
                }
                if (val instanceof Error) {
                  return {
                    message: val.message,
                    stack: val.stack,
                    name: val.name
                  };
                }
                return val;
              },
              2
            );
          } catch {
            return String(arg);
          }
        }
        return arg;
      });
    }
  }
  function isDOMNode(val) {
    return typeof val === "object" && val !== null && "nodeType" in val && typeof val.nodeType === "number" && "nodeName" in val && typeof val.nodeName === "string";
  }
  function formatDOMNode(node) {
    if (node.nodeType === 1) {
      const tagName = node.tagName.toLowerCase();
      const id = node.id ? `#${node.id}` : "";
      const classes = node.className && typeof node.className === "string" && node.className.trim() ? `.${node.className.trim().split(/\s+/).join(".")}` : "";
      return `<${tagName}${id}${classes}>`;
    }
    return `[Node: ${node.nodeName}]`;
  }
  const logger = new Logger(
    "FFSV2",
    0
);
  const log$i = logger.child("storage");
  var Time = ((Time2) => {
    Time2[Time2["Seconds"] = 1e3] = "Seconds";
    Time2[Time2["Minutes"] = 6e4] = "Minutes";
    Time2[Time2["Hours"] = 36e5] = "Hours";
    Time2[Time2["Days"] = 864e5] = "Days";
    Time2[Time2["Weeks"] = 6048e5] = "Weeks";
    Time2[Time2["Years"] = 31536e6] = "Years";
    return Time2;
  })(Time || {});
  class Storage {
constructor(prefix) {
      this.prefix = prefix;
    }
set(key, value, expireConfig) {
      try {
        const item = {
          value,
          expiration: expireConfig ? Date.now() + expireConfig.amount * (expireConfig.unit || 6e4) : null
        };
        localStorage.setItem(this.prefix + key, JSON.stringify(item));
      } catch (error) {
        log$i.error(`Error storing item '${key}':`, error);
      }
    }
get(key) {
      try {
        const itemStr = localStorage.getItem(this.prefix + key);
        if (!itemStr) {
          return null;
        }
        let item = null;
        try {
          item = JSON.parse(itemStr);
        } catch {
          item = null;
        }
        if (!item) {
          log$i.warn(`Key '${key}' has invalid JSON in it.`);
          this.remove(key);
          return null;
        }
        if (item.expiration && Date.now() > item.expiration) {
          this.remove(key);
          log$i.debug(`Key ${key} has expired.`);
          return null;
        }
        return item.value;
      } catch (error) {
        log$i.error(`Error retrieving item '${key}':`, error);
        return null;
      }
    }
remove(key) {
      try {
        localStorage.removeItem(this.prefix + key);
      } catch (error) {
        log$i.error(`Error removing item [${key}]:`, error);
      }
    }
has(key) {
      return this.get(key) !== null;
    }
clearAll() {
      try {
        Object.keys(localStorage).filter((key) => key.startsWith(this.prefix)).forEach((key) => {
          localStorage.removeItem(key);
        });
      } catch (error) {
        log$i.error("Error clearing storage:", error);
      }
    }
  }
  var TornApiError;
  (function(TornApiError2) {
    TornApiError2[TornApiError2["UNKNOWN_ERROR"] = 0] = "UNKNOWN_ERROR";
    TornApiError2[TornApiError2["KEY_EMPTY"] = 1] = "KEY_EMPTY";
    TornApiError2[TornApiError2["INCORRECT_KEY"] = 2] = "INCORRECT_KEY";
    TornApiError2[TornApiError2["WRONG_TYPE"] = 3] = "WRONG_TYPE";
    TornApiError2[TornApiError2["WRONG_FIELDS"] = 4] = "WRONG_FIELDS";
    TornApiError2[TornApiError2["TOO_MANY_REQUESTS"] = 5] = "TOO_MANY_REQUESTS";
    TornApiError2[TornApiError2["INCORRECT_ID"] = 6] = "INCORRECT_ID";
    TornApiError2[TornApiError2["INCORRECT_RELATION"] = 7] = "INCORRECT_RELATION";
    TornApiError2[TornApiError2["IP_BLOCK"] = 8] = "IP_BLOCK";
    TornApiError2[TornApiError2["API_DISABLED"] = 9] = "API_DISABLED";
    TornApiError2[TornApiError2["KEY_FEDERAL_JAIL"] = 10] = "KEY_FEDERAL_JAIL";
    TornApiError2[TornApiError2["KEY_CHANGE_ERROR"] = 11] = "KEY_CHANGE_ERROR";
    TornApiError2[TornApiError2["KEY_READ_ERROR"] = 12] = "KEY_READ_ERROR";
    TornApiError2[TornApiError2["KEY_TEMPORARILY_DISABLED_TO_INACTIVITY"] = 13] = "KEY_TEMPORARILY_DISABLED_TO_INACTIVITY";
    TornApiError2[TornApiError2["DAILY_READ_LIMIT_REACHED"] = 14] = "DAILY_READ_LIMIT_REACHED";
    TornApiError2[TornApiError2["TEMPORARY_ERROR"] = 15] = "TEMPORARY_ERROR";
    TornApiError2[TornApiError2["ACCESS_LEVEL_KEY_NOT_HIGH"] = 16] = "ACCESS_LEVEL_KEY_NOT_HIGH";
    TornApiError2[TornApiError2["BACKEND_ERROR_OCCURRED"] = 17] = "BACKEND_ERROR_OCCURRED";
    TornApiError2[TornApiError2["API_KEY_HAS_BEEN_PAUSED"] = 18] = "API_KEY_HAS_BEEN_PAUSED";
    TornApiError2[TornApiError2["MUST_BE_MIGRATED_TO_CRIMES"] = 19] = "MUST_BE_MIGRATED_TO_CRIMES";
    TornApiError2[TornApiError2["RACE_NOT_YET_FINISHED"] = 20] = "RACE_NOT_YET_FINISHED";
    TornApiError2[TornApiError2["INCORRECT_CATEGORY"] = 21] = "INCORRECT_CATEGORY";
    TornApiError2[TornApiError2["SELECTION_ONLY_AVAILABLE_API_V1"] = 22] = "SELECTION_ONLY_AVAILABLE_API_V1";
    TornApiError2[TornApiError2["SELECTION_ONLY_AVAILABLE_API_V2"] = 23] = "SELECTION_ONLY_AVAILABLE_API_V2";
    TornApiError2[TornApiError2["CLOSED_TEMPORARILY"] = 24] = "CLOSED_TEMPORARILY";
    TornApiError2[TornApiError2["INVALID_STAT_REQUESTED"] = 25] = "INVALID_STAT_REQUESTED";
    TornApiError2[TornApiError2["ONLY_CATEGORY_OR_STATS_CAN"] = 26] = "ONLY_CATEGORY_OR_STATS_CAN";
    TornApiError2[TornApiError2["MUST_BE_MIGRATED_TO_ORGANIZED"] = 27] = "MUST_BE_MIGRATED_TO_ORGANIZED";
    TornApiError2[TornApiError2["INCORRECT_LOG_ID"] = 28] = "INCORRECT_LOG_ID";
    TornApiError2[TornApiError2["CATEGORY_SELECTION_NOT_AVAILABLE_FOR"] = 29] = "CATEGORY_SELECTION_NOT_AVAILABLE_FOR";
  })(TornApiError || (TornApiError = {}));
  class HTTPClient {
    canAbort() {
      return false;
    }
  }
  class AbortableHTTPClient extends HTTPClient {
    canAbort() {
      return true;
    }
  }
  class FetchHTTPClient extends AbortableHTTPClient {
    async getJson(url, timeout = void 0) {
      let response;
      if (timeout !== void 0) {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), timeout);
        response = await fetch(url, { signal: controller.signal });
        clearTimeout(timeoutId);
      } else {
        response = await fetch(url);
      }
      return await response.json();
    }
  }
  class TornApiClient {
    httpClient;
    defaultComment;
    defaultTimeout;
    constructor(options = {}) {
      this.httpClient = options.httpClient ?? new FetchHTTPClient();
      this.defaultComment = options.defaultComment;
      this.defaultTimeout = options.defaultTimeout;
    }
    async getV1({ section, selections, id, params = {}, key, comment, cache, expiry, timeout }) {
      const cached = await cache?.get({
        section,
        selections,
        id,
        params,
        key
      });
      if (cached)
        return cached;
      let url = `https://api.torn.com/${section}/${id ?? ""}`;
      url = this.populateUrl(url, key, selections ?? [], comment, params ?? {});
      if (this.httpClient.canAbort() && typeof timeout === "number") {
        return this.httpClient.getJson(url, timeout).then(addToCache).catch(this.handleError);
      } else {
        return this.httpClient.getJson(url).then(addToCache).catch(this.handleError);
      }
      function addToCache(response) {
        if ("error" in response)
          return response;
        cache?.set({
          section,
          selections,
          id,
          params,
          key
        }, response, expiry ?? Date.now() + 3e4);
        return response;
      }
    }
    async getV2({ section, selections, id, params = {}, key, comment, cache, expiry, timeout }) {
      const cached = await cache?.get({
        section,
        selections,
        id,
        params,
        key
      });
      if (cached)
        return cached;
      let url = `https://api.torn.com/v2/${section}/${id ?? ""}`;
      url = this.populateUrl(url, key, selections ?? [], comment, params ?? {});
      if (this.httpClient.canAbort() && typeof timeout === "number") {
        return this.httpClient.getJson(url, timeout).then(addToCache).catch(this.handleError);
      } else {
        return this.httpClient.getJson(url).then(addToCache).catch(this.handleError);
      }
      function addToCache(response) {
        if ("error" in response)
          return response;
        cache?.set({
          section,
          selections,
          id,
          params,
          key
        }, response, expiry ?? Date.now() + 3e4);
        return response;
      }
    }
    handleError(error) {
      console.error(error);
      return { error: { code: -1, error: generateErrorString(error) } };
      function generateErrorString(e) {
        switch (typeof e) {
          case "string":
            return e;
          case "object": {
            if (e instanceof Error)
              return e.message;
            return JSON.stringify(e);
          }
          default:
            return e.toString();
        }
      }
    }
    populateUrl(url, key, selections, comment, params) {
      const allParams = {
        key,
        comment: comment ?? this.defaultComment,
        selections: selections.length ? selections.join(",") : void 0,
        ...params
      };
      const query = Object.entries(allParams).filter((entry) => !!entry[1]).map(([key2, value]) => `${key2}=${value}`).join("&");
      return `${url}?${query}`;
    }
  }
  var FactionsColDisplay = ((FactionsColDisplay2) => {
    FactionsColDisplay2["FAIR_FIGHT"] = "fair_fight";
    FactionsColDisplay2["BATTLE_STATS"] = "battle_stats";
    FactionsColDisplay2["NONE"] = "none";
    return FactionsColDisplay2;
  })(FactionsColDisplay || {});
  var GaugeMarkerType = ((GaugeMarkerType2) => {
    GaugeMarkerType2["ARROW"] = "arrow";
    GaugeMarkerType2["BUBBLE_FF"] = "bubble_ff";
    GaugeMarkerType2["BUBBLE_ESTIMATE"] = "bubble_estimate";
    return GaugeMarkerType2;
  })(GaugeMarkerType || {});
  var ColorScheme = ((ColorScheme2) => {
    ColorScheme2["CLASSIC"] = "classic";
    ColorScheme2["COOL_DIVERGING"] = "cool_diverging";
    ColorScheme2["NEON"] = "neon";
    ColorScheme2["COLORBLIND_SAFE"] = "colorblind_safe";
    ColorScheme2["GRAYSCALE"] = "grayscale";
    ColorScheme2["GREEN_YELLOW_RED"] = "green_yellow_red";
    ColorScheme2["BLUE_YELLOW_RED"] = "blue_yellow_red";
    ColorScheme2["PLASMA"] = "plasma";
    ColorScheme2["CUSTOM"] = "custom";
    return ColorScheme2;
  })(ColorScheme || {});
  const CONFIG_DEFAULTS = {
    low_ff_range: 2,
    high_ff_range: 4,
    max_ff_range: 8,
    chain_button_enabled: true,
    chain_link_type: "attack",
    chain_tab_type: "newtab",
    chain_ff_target: 2.5,
    ff_history_enabled: true,
    factions_col_display: "battle_stats",
    war_col_display: "battle_stats",
    debug_logs: false,
    analytics_enabled: false,
    chain_min_level: null,
    chain_max_level: null,
    chain_inactive: true,
    chain_min_ff: null,
    chain_max_ff: 2.5,
    chain_factionless: false,
    gauge_marker_type: "arrow",
    gauge_marker_scale: 100,
    gauge_marker_border_width: 1,
    war_quick_attack_action: "new_tab",
    network_interception_enabled: false,
    status_attack_links_enabled: true,
    debug_disable_pda_http: false,
    color_scheme: "classic",
    custom_colors: null
  };
  class FFConfig {
    constructor(name) {
      this.name = name;
      this.storage = new Storage(this.name);
      logger.setLevel(this.debug_logs ? LogLevel.DEBUG : LogLevel.INFO);
    }
    get key() {
      return this.storage.get(
        "key"
) ?? "";
    }
    set key(key) {
      this.storage.set("key", key);
    }
    get low_ff_range() {
      return this.storage.get(
        "low_ff_range"
) ?? CONFIG_DEFAULTS.low_ff_range;
    }
    set low_ff_range(val) {
      this.storage.set("low_ff_range", val);
    }
    get high_ff_range() {
      return this.storage.get(
        "high_ff_range"
) ?? CONFIG_DEFAULTS.high_ff_range;
    }
    set high_ff_range(val) {
      this.storage.set("high_ff_range", val);
    }
    get max_ff_range() {
      return this.storage.get(
        "max_ff_range"
) ?? CONFIG_DEFAULTS.max_ff_range;
    }
    set max_ff_range(val) {
      this.storage.set("max_ff_range", val);
    }
    get chain_button_enabled() {
      return this.storage.get(
        "chain_button_enabled"
) ?? CONFIG_DEFAULTS.chain_button_enabled;
    }
    set chain_button_enabled(val) {
      this.storage.set("chain_button_enabled", val);
    }
    get chain_link_type() {
      return this.storage.get(
        "chain_link_type"
) ?? CONFIG_DEFAULTS.chain_link_type;
    }
    set chain_link_type(val) {
      this.storage.set("chain_link_type", val);
    }
    get chain_tab_type() {
      return this.storage.get(
        "chain_tab_type"
) ?? CONFIG_DEFAULTS.chain_tab_type;
    }
    set chain_tab_type(val) {
      this.storage.set("chain_tab_type", val);
    }
    get war_quick_attack_action() {
      return this.storage.get(
        "war_quick_attack_action"
) ?? CONFIG_DEFAULTS.war_quick_attack_action;
    }
    set war_quick_attack_action(val) {
      this.storage.set("war_quick_attack_action", val);
    }
    get chain_ff_target() {
      return this.storage.get(
        "chain_ff_target"
) ?? CONFIG_DEFAULTS.chain_ff_target;
    }
    set chain_ff_target(val) {
      this.storage.set("chain_ff_target", val);
    }
    get chain_min_level() {
      return this.storage.get(
        "chain_min_level"
) ?? CONFIG_DEFAULTS.chain_min_level;
    }
    set chain_min_level(val) {
      if (val === null) {
        this.storage.remove(
          "chain_min_level"
);
      } else {
        this.storage.set("chain_min_level", val);
      }
    }
    get chain_max_level() {
      return this.storage.get(
        "chain_max_level"
) ?? CONFIG_DEFAULTS.chain_max_level;
    }
    set chain_max_level(val) {
      if (val === null) {
        this.storage.remove(
          "chain_max_level"
);
      } else {
        this.storage.set("chain_max_level", val);
      }
    }
    get chain_inactive() {
      return this.storage.get(
        "chain_inactive"
) ?? CONFIG_DEFAULTS.chain_inactive;
    }
    set chain_inactive(val) {
      this.storage.set("chain_inactive", val);
    }
    get chain_min_ff() {
      return this.storage.get(
        "chain_min_ff"
) ?? CONFIG_DEFAULTS.chain_min_ff;
    }
    set chain_min_ff(val) {
      if (val === null) {
        this.storage.remove(
          "chain_min_ff"
);
      } else {
        this.storage.set("chain_min_ff", val);
      }
    }
    get chain_max_ff() {
      return this.storage.get(
        "chain_max_ff"
) ?? this.storage.get(
        "chain_ff_target"
) ?? CONFIG_DEFAULTS.chain_max_ff;
    }
    set chain_max_ff(val) {
      this.storage.set("chain_max_ff", val);
      this.storage.set("chain_ff_target", val);
    }
    get chain_factionless() {
      return this.storage.get(
        "chain_factionless"
) ?? CONFIG_DEFAULTS.chain_factionless;
    }
    set chain_factionless(val) {
      this.storage.set("chain_factionless", val);
    }
    get ff_history_enabled() {
      return this.storage.get(
        "ff_history_enabled"
) ?? CONFIG_DEFAULTS.ff_history_enabled;
    }
    set ff_history_enabled(val) {
      this.storage.set("ff_history_enabled", val);
    }
    get factions_col_display() {
      return this.storage.get(
        "factions_col_display"
) ?? CONFIG_DEFAULTS.factions_col_display;
    }
    set factions_col_display(val) {
      this.storage.set("factions_col_display", val);
    }
    get war_col_display() {
      return this.storage.get(
        "war_col_display"
) ?? CONFIG_DEFAULTS.war_col_display;
    }
    set war_col_display(val) {
      this.storage.set("war_col_display", val);
    }
    get debug_logs() {
      return this.storage.get(
        "debug_logs"
) ?? CONFIG_DEFAULTS.debug_logs;
    }
    set debug_logs(val) {
      this.storage.set("debug_logs", val);
      logger.setLevel(val ? LogLevel.DEBUG : LogLevel.INFO);
    }
    get analytics_enabled() {
      return this.storage.get(
        "analytics_enabled"
) ?? CONFIG_DEFAULTS.analytics_enabled;
    }
    set analytics_enabled(val) {
      this.storage.set("analytics_enabled", val);
    }
    get network_interception_enabled() {
      return this.storage.get(
        "network_interception_enabled"
) ?? CONFIG_DEFAULTS.network_interception_enabled;
    }
    set network_interception_enabled(val) {
      this.storage.set("network_interception_enabled", val);
    }
    get status_attack_links_enabled() {
      return this.storage.get(
        "status_attack_links_enabled"
) ?? CONFIG_DEFAULTS.status_attack_links_enabled;
    }
    set status_attack_links_enabled(val) {
      this.storage.set("status_attack_links_enabled", val);
    }
    get debug_disable_pda_http() {
      return this.storage.get(
        "debug_disable_pda_http"
) ?? CONFIG_DEFAULTS.debug_disable_pda_http;
    }
    set debug_disable_pda_http(val) {
      this.storage.set("debug_disable_pda_http", val);
    }
    get gauge_marker_type() {
      return this.storage.get(
        "gauge_marker_type"
) ?? CONFIG_DEFAULTS.gauge_marker_type;
    }
    set gauge_marker_type(val) {
      this.storage.set("gauge_marker_type", val);
    }
    get gauge_marker_scale() {
      return this.storage.get(
        "gauge_marker_scale"
) ?? CONFIG_DEFAULTS.gauge_marker_scale;
    }
    set gauge_marker_scale(val) {
      this.storage.set("gauge_marker_scale", val);
    }
    get gauge_marker_border_width() {
      return this.storage.get(
        "gauge_marker_border_width"
) ?? CONFIG_DEFAULTS.gauge_marker_border_width;
    }
    set gauge_marker_border_width(val) {
      this.storage.set("gauge_marker_border_width", val);
    }
    get color_scheme() {
      return this.storage.get(
        "color_scheme"
) ?? CONFIG_DEFAULTS.color_scheme;
    }
    set color_scheme(val) {
      this.storage.set("color_scheme", val);
    }
    get custom_colors() {
      return this.storage.get(
        "custom_colors"
) ?? CONFIG_DEFAULTS.custom_colors;
    }
    set custom_colors(val) {
      if (val === null) {
        this.storage.remove(
          "custom_colors"
);
      } else {
        this.storage.set("custom_colors", val);
      }
    }
    get faction_filter_state() {
      return this.storage.get(
        "faction_filter_state"
) ?? null;
    }
    set faction_filter_state(val) {
      this.storage.set("faction_filter_state", val);
    }
    get faction_filter_collapsed() {
      return this.storage.get(
        "faction_filter_collapsed"
) ?? false;
    }
    set faction_filter_collapsed(val) {
      this.storage.set("faction_filter_collapsed", val);
    }
    get war_filter_state() {
      return this.storage.get(
        "war_filter_state"
) ?? null;
    }
    set war_filter_state(val) {
      this.storage.set("war_filter_state", val);
    }
    get war_filter_collapsed() {
      return this.storage.get(
        "war_filter_collapsed"
) ?? false;
    }
    set war_filter_collapsed(val) {
      this.storage.set("war_filter_collapsed", val);
    }
    get chain_targets() {
      return this.storage.get(
        "chain_targets"
);
    }
    set chain_targets(val) {
      if (val === null) {
        this.storage.remove(
          "chain_targets"
);
      } else {
        this.storage.set("chain_targets", val);
      }
    }
    get chain_target_index() {
      return this.storage.get(
        "chain_target_index"
) ?? 0;
    }
    set chain_target_index(val) {
      this.storage.set("chain_target_index", val);
    }
    reset() {
      this.storage.remove(
        "low_ff_range"
);
      this.storage.remove(
        "high_ff_range"
);
      this.storage.remove(
        "max_ff_range"
);
      this.storage.remove(
        "chain_button_enabled"
);
      this.storage.remove(
        "chain_link_type"
);
      this.storage.remove(
        "chain_tab_type"
);
      this.storage.remove(
        "chain_ff_target"
);
      this.storage.remove(
        "ff_history_enabled"
);
      this.storage.remove(
        "factions_col_display"
);
      this.storage.remove(
        "war_col_display"
);
      this.storage.remove(
        "debug_logs"
);
      this.storage.remove(
        "analytics_enabled"
);
      this.storage.remove(
        "network_interception_enabled"
);
      this.storage.remove(
        "faction_filter_state"
);
      this.storage.remove(
        "faction_filter_collapsed"
);
      this.storage.remove(
        "war_filter_state"
);
      this.storage.remove(
        "war_filter_collapsed"
);
      this.storage.remove(
        "chain_min_level"
);
      this.storage.remove(
        "chain_max_level"
);
      this.storage.remove(
        "chain_inactive"
);
      this.storage.remove(
        "chain_min_ff"
);
      this.storage.remove(
        "chain_max_ff"
);
      this.storage.remove(
        "chain_factionless"
);
      this.storage.remove(
        "chain_targets"
);
      this.storage.remove(
        "chain_target_index"
);
      this.storage.remove(
        "gauge_marker_type"
);
      this.storage.remove(
        "gauge_marker_scale"
);
      this.storage.remove(
        "gauge_marker_border_width"
);
      this.storage.remove(
        "war_quick_attack_action"
);
      this.storage.remove(
        "status_attack_links_enabled"
);
      this.storage.remove(
        "debug_disable_pda_http"
);
      this.storage.remove(
        "color_scheme"
);
      this.storage.remove(
        "custom_colors"
);
    }
  }
  const ffconfig = new FFConfig("ffsv3-config");
  const FF_SCOUTER_BASE_URL = "https://ffscouter.com/api/v1";
  new TornApiClient({
    defaultComment: `FFScouterV2-${"3.0.1"}`,
    defaultTimeout: 30
});
  async function gmRequest(options) {
    if (isInPDA() && !ffconfig.debug_disable_pda_http) {
      const url = options.url;
      const headers = options.headers ?? {};
      const method = (options.method ?? "GET").toUpperCase();
      const pdaResp = method === "POST" ? await window.PDA_httpPost(url, headers, options.data) : await window.PDA_httpGet(url, headers);
      return pdaResp;
    }
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        ...options,
        onload: (response) => resolve(response),
        onerror: (err) => reject(err),
        ontimeout: () => reject(new Error("Timeout making GM_xmlhttpRequest"))
      });
    });
  }
  const make_stats_url = (key, player_ids) => {
    const query = new URLSearchParams([
      ["key", key],
      ["targets", player_ids.toString()]
    ]);
    return `${FF_SCOUTER_BASE_URL}/get-stats?${query.toString()}`;
  };
  function is_ff_success(resp) {
    return resp.code === void 0;
  }
  function is_ff_check_success(resp) {
    return resp.code === void 0;
  }
  class FFApiError extends Error {
    constructor(message, options) {
      super(message, options);
      this.ff_api_limits = options?.ff_api_limits;
      this.ff_api_error = options?.ff_api_error;
    }
  }
  const query_stats = async (key, player_ids, requester = gmRequest) => {
    logger.debug("Calling query_stats with arguments", { key, player_ids });
    const url = make_stats_url(key, player_ids);
    const resp = await requester({
      method: "GET",
      url
    });
    if (!resp) {
      return { result: new Map(), blank: true };
    }
    const limits = parse_limit_headers(resp.responseHeaders);
    let ff_response = null;
    try {
      ff_response = JSON.parse(resp.responseText);
    } catch {
      logger.warn(
        `query_stats: unparseable response. status=${resp.status}, body=${resp.responseText?.substring(0, 200)}, url=${url}`
      );
      throw new FFApiError(
        `API request failed. Couldn't parse response. HTTP status code: ${resp.status}`,
        { ff_api_limits: limits }
      );
    }
    if (ff_response == null) {
      logger.warn(
        `query_stats: null response after parse. status=${resp.status}, url=${url}`
      );
      throw new FFApiError(
        `API request failed. Response not set. HTTP status code: ${resp.status}`,
        { ff_api_limits: limits }
      );
    }
    if (!is_ff_success(ff_response)) {
      throw new FFApiError(
        `API request failed. Error: ${ff_response.error}; Code: ${ff_response.code}`,
        { ff_api_error: ff_response, ff_api_limits: limits }
      );
    }
    if (resp.status !== 200) {
      logger.warn(
        `query_stats: unexpected HTTP status. status=${resp.status}, body=${resp.responseText?.substring(0, 200)}, url=${url}`
      );
      throw new FFApiError(
        `API request failed. HTTP status code: ${resp.status}`,
        { ff_api_limits: limits }
      );
    }
    const results = new Map();
    ff_response.forEach((result) => {
      if (result?.player_id) {
        if (!result.fair_fight || !result.last_updated || !result.bs_estimate || !result.bs_estimate_human || !result.bss_public || !result.source) {
          results.set(result.player_id, {
            no_data: true,
            player_id: result.player_id
          });
        } else {
          let distribution;
          if (result.distribution) {
            distribution = {
              last_updated: result.distribution.last_updated,
              distribution_human: result.distribution.distribution_human,
              stats_percentage: {
                strength: result.distribution.stats_percentage?.strength,
                speed: result.distribution.stats_percentage?.speed,
                defense: result.distribution.stats_percentage?.defense,
                dexterity: result.distribution.stats_percentage?.dexterity
              }
            };
          }
          results.set(result.player_id, {
            no_data: false,
            fair_fight: result.fair_fight,
            last_updated: result.last_updated,
            bs_estimate: result.bs_estimate,
            bs_estimate_human: result.bs_estimate_human,
            bss_public: result.bss_public,
            source: result.source ?? "bss",
            premium_insights_available: result.premium_insights_available ?? false,
            distribution,
            player_id: result.player_id
          });
        }
      }
    });
    for (const id of player_ids) {
      if (!results.get(id)) {
        results.set(id, {
          no_data: true,
          player_id: id
        });
      }
    }
    return { result: results, blank: false, limits };
  };
  const parse_limit_headers = (responseHeaders) => {
    if (typeof responseHeaders !== "string") {
      return void 0;
    }
    const headerLines = responseHeaders.split("\n");
    const headers = new Map();
    for (const line of headerLines) {
      const [key, value] = line.split(":", 2);
      if (!key || !value) {
        continue;
      }
      headers.set(key, value.trim());
    }
    const reset_time_str = headers.get("x-ratelimit-reset-timestamp");
    const remaining_str = headers.get("x-ratelimit-remaining");
    const rate_limit_str = headers.get("x-ratelimit-limit");
    if (reset_time_str && remaining_str && rate_limit_str) {
      const remaining = parseInt(remaining_str, 10);
      const rate_limit = parseInt(rate_limit_str, 10);
      const this_minute = rate_limit - remaining;
      return {
        reset_time: new Date(parseInt(reset_time_str, 10) * 1e3),
        remaining,
        rate_limit,
        this_minute
      };
    }
  };
  const check_key = async (key, requester = gmRequest) => {
    if (!key) {
      return { blank: true };
    }
    const query = new URLSearchParams([["key", key]]);
    const url = `${FF_SCOUTER_BASE_URL}/check-key?${query.toString()}`;
    const resp = await requester({
      method: "GET",
      url
    });
    if (!resp) {
      return { blank: true };
    }
    const limits = parse_limit_headers(resp.responseHeaders);
    let ff_response = null;
    try {
      ff_response = JSON.parse(resp.responseText);
    } catch {
      logger.warn(
        `check_key: unparseable response. status=${resp.status}, body=${resp.responseText?.substring(0, 200)}, url=${url}`
      );
      throw new FFApiError(
        `API request failed. Couldn't parse response. HTTP status code: ${resp.status}`,
        { ff_api_limits: limits }
      );
    }
    if (ff_response == null) {
      logger.warn(
        `check_key: null response after parse. status=${resp.status}, url=${url}`
      );
      throw new FFApiError(
        `API request failed. Response not set. HTTP status code: ${resp.status}`,
        { ff_api_limits: limits }
      );
    }
    if (!is_ff_check_success(ff_response)) {
      throw new FFApiError(
        `API request failed. Error: ${ff_response.error}; Code: ${ff_response.code}`,
        { ff_api_error: ff_response, ff_api_limits: limits }
      );
    }
    if (resp.status !== 200) {
      logger.warn(
        `check_key: unexpected HTTP status. status=${resp.status}, body=${resp.responseText?.substring(0, 200)}, url=${url}`
      );
      throw new FFApiError(
        `API request failed. HTTP status code: ${resp.status}`,
        { ff_api_limits: limits }
      );
    }
    return { result: ff_response, blank: false, limits };
  };
  const make_flights_url = (key, target) => {
    const query = new URLSearchParams([
      ["key", key],
      ["target", target.toString()]
    ]);
    return `${FF_SCOUTER_BASE_URL}/player-flights?${query.toString()}`;
  };
  function is_flight_success(resp) {
    return resp.code === void 0;
  }
  const query_flights = async (key, target, requester = gmRequest) => {
    logger.debug("Calling query_flights with arguments", { key, target });
    const url = make_flights_url(key, target);
    const resp = await requester({
      method: "GET",
      url
    });
    if (!resp) {
      return { blank: true };
    }
    const limits = parse_limit_headers(resp.responseHeaders);
    let ff_response = null;
    try {
      ff_response = JSON.parse(resp.responseText);
    } catch {
      logger.warn(
        `query_flights: unparseable response. status=${resp.status}, body=${resp.responseText?.substring(0, 200)}, url=${url}`
      );
      throw new FFApiError(
        `API request failed. Couldn't parse response. HTTP status code: ${resp.status}`,
        { ff_api_limits: limits }
      );
    }
    if (ff_response == null) {
      logger.warn(
        `query_flights: null response after parse. status=${resp.status}, url=${url}`
      );
      throw new FFApiError(
        `API request failed. Response not set. HTTP status code: ${resp.status}`,
        { ff_api_limits: limits }
      );
    }
    if (!is_flight_success(ff_response)) {
      throw new FFApiError(
        `API request failed. Error: ${ff_response.error}; Code: ${ff_response.code}`,
        { ff_api_error: ff_response, ff_api_limits: limits }
      );
    }
    if (resp.status !== 200) {
      logger.warn(
        `query_flights: unexpected HTTP status. status=${resp.status}, body=${resp.responseText?.substring(0, 200)}, url=${url}`
      );
      throw new FFApiError(
        `API request failed. HTTP status code: ${resp.status}`,
        { ff_api_limits: limits }
      );
    }
    return { result: ff_response, blank: false, limits };
  };
  const query_targets = async (key, params, requester = gmRequest) => {
    logger.debug("Calling query_targets with arguments", { key, params });
    const query = new URLSearchParams([["key", key]]);
    if (params.minlevel !== void 0 && params.minlevel !== null) {
      query.append("minlevel", params.minlevel.toString());
    }
    if (params.maxlevel !== void 0 && params.maxlevel !== null) {
      query.append("maxlevel", params.maxlevel.toString());
    }
    if (params.minff !== void 0 && params.minff !== null) {
      query.append("minff", params.minff.toString());
    }
    if (params.maxff !== void 0 && params.maxff !== null) {
      query.append("maxff", params.maxff.toString());
    }
    if (params.inactiveonly !== void 0 && params.inactiveonly !== null) {
      query.append("inactiveonly", params.inactiveonly.toString());
    }
    if (params.factionless !== void 0 && params.factionless !== null) {
      query.append("factionless", params.factionless.toString());
    }
    if (params.limit !== void 0 && params.limit !== null) {
      query.append("limit", params.limit.toString());
    }
    const url = `${FF_SCOUTER_BASE_URL}/get-targets?${query.toString()}`;
    const resp = await requester({
      method: "GET",
      url
    });
    if (!resp) {
      throw new Error("Empty response from get-targets");
    }
    if (resp.status !== 200) {
      let errMessage = `API request failed with HTTP ${resp.status}`;
      try {
        const errJson = JSON.parse(resp.responseText);
        if (errJson?.error) {
          errMessage = errJson.error;
        }
      } catch {
      }
      throw new Error(errMessage);
    }
    const parsed = JSON.parse(resp.responseText);
    if (parsed.error) {
      throw new Error(parsed.error);
    }
    return parsed;
  };
  const log$h = logger.child("api");
  const CHECK_KEY = "check-key-status";
  class CheckKeyStatus {
    constructor(config, storage) {
      this.check_key_status = async (force = false) => {
        if (!force) {
          const cached = this.storage.get(CHECK_KEY);
          if (cached) {
            return cached;
          }
        }
        let result;
        try {
          result = await check_key(this.config.key);
        } catch (err) {
          log$h.error(
            "Received error response querying ffscouter check-key api:",
            err
          );
          throw err;
        }
        if (result.blank) {
          return null;
        }
        this.storage.set(CHECK_KEY, result.result, {
          amount: 5,
          unit: Time.Minutes
        });
        return result.result;
      };
      this.is_premium = async (force = false) => {
        try {
          const status = await this.check_key_status(force);
          if (!status) return null;
          return status.is_premium;
        } catch (err) {
          log$h.warn("Failed to check premium status:", err);
          return null;
        }
      };
      this.clear = () => {
        this.storage.remove(CHECK_KEY);
      };
      this.config = config;
      this.storage = storage;
    }
  }
  const check_key_status = new CheckKeyStatus(
    ffconfig,
    new Storage("ffsv3-check")
  );
  const instanceOfAny = (object, constructors) => constructors.some((c) => object instanceof c);
  let idbProxyableTypes;
  let cursorAdvanceMethods;
  function getIdbProxyableTypes() {
    return idbProxyableTypes || (idbProxyableTypes = [
      IDBDatabase,
      IDBObjectStore,
      IDBIndex,
      IDBCursor,
      IDBTransaction
    ]);
  }
  function getCursorAdvanceMethods() {
    return cursorAdvanceMethods || (cursorAdvanceMethods = [
      IDBCursor.prototype.advance,
      IDBCursor.prototype.continue,
      IDBCursor.prototype.continuePrimaryKey
    ]);
  }
  const transactionDoneMap = new WeakMap();
  const transformCache = new WeakMap();
  const reverseTransformCache = new WeakMap();
  function promisifyRequest(request) {
    const promise = new Promise((resolve, reject) => {
      const unlisten = () => {
        request.removeEventListener("success", success);
        request.removeEventListener("error", error);
      };
      const success = () => {
        resolve(wrap(request.result));
        unlisten();
      };
      const error = () => {
        reject(request.error);
        unlisten();
      };
      request.addEventListener("success", success);
      request.addEventListener("error", error);
    });
    reverseTransformCache.set(promise, request);
    return promise;
  }
  function cacheDonePromiseForTransaction(tx) {
    if (transactionDoneMap.has(tx))
      return;
    const done = new Promise((resolve, reject) => {
      const unlisten = () => {
        tx.removeEventListener("complete", complete);
        tx.removeEventListener("error", error);
        tx.removeEventListener("abort", error);
      };
      const complete = () => {
        resolve();
        unlisten();
      };
      const error = () => {
        reject(tx.error || new DOMException("AbortError", "AbortError"));
        unlisten();
      };
      tx.addEventListener("complete", complete);
      tx.addEventListener("error", error);
      tx.addEventListener("abort", error);
    });
    transactionDoneMap.set(tx, done);
  }
  let idbProxyTraps = {
    get(target, prop, receiver) {
      if (target instanceof IDBTransaction) {
        if (prop === "done")
          return transactionDoneMap.get(target);
        if (prop === "store") {
          return receiver.objectStoreNames[1] ? void 0 : receiver.objectStore(receiver.objectStoreNames[0]);
        }
      }
      return wrap(target[prop]);
    },
    set(target, prop, value) {
      target[prop] = value;
      return true;
    },
    has(target, prop) {
      if (target instanceof IDBTransaction && (prop === "done" || prop === "store")) {
        return true;
      }
      return prop in target;
    }
  };
  function replaceTraps(callback) {
    idbProxyTraps = callback(idbProxyTraps);
  }
  function wrapFunction(func) {
    if (getCursorAdvanceMethods().includes(func)) {
      return function(...args) {
        func.apply(unwrap(this), args);
        return wrap(this.request);
      };
    }
    return function(...args) {
      return wrap(func.apply(unwrap(this), args));
    };
  }
  function transformCachableValue(value) {
    if (typeof value === "function")
      return wrapFunction(value);
    if (value instanceof IDBTransaction)
      cacheDonePromiseForTransaction(value);
    if (instanceOfAny(value, getIdbProxyableTypes()))
      return new Proxy(value, idbProxyTraps);
    return value;
  }
  function wrap(value) {
    if (value instanceof IDBRequest)
      return promisifyRequest(value);
    if (transformCache.has(value))
      return transformCache.get(value);
    const newValue = transformCachableValue(value);
    if (newValue !== value) {
      transformCache.set(value, newValue);
      reverseTransformCache.set(newValue, value);
    }
    return newValue;
  }
  const unwrap = (value) => reverseTransformCache.get(value);
  function openDB(name, version, { blocked, upgrade, blocking, terminated } = {}) {
    const request = indexedDB.open(name, version);
    const openPromise = wrap(request);
    if (upgrade) {
      request.addEventListener("upgradeneeded", (event) => {
        upgrade(wrap(request.result), event.oldVersion, event.newVersion, wrap(request.transaction), event);
      });
    }
    if (blocked) {
      request.addEventListener("blocked", (event) => blocked(
event.oldVersion,
        event.newVersion,
        event
      ));
    }
    openPromise.then((db) => {
      if (terminated)
        db.addEventListener("close", () => terminated());
      if (blocking) {
        db.addEventListener("versionchange", (event) => blocking(event.oldVersion, event.newVersion, event));
      }
    }).catch(() => {
    });
    return openPromise;
  }
  function deleteDB(name, { blocked } = {}) {
    const request = indexedDB.deleteDatabase(name);
    if (blocked) {
      request.addEventListener("blocked", (event) => blocked(
event.oldVersion,
        event
      ));
    }
    return wrap(request).then(() => void 0);
  }
  const readMethods = ["get", "getKey", "getAll", "getAllKeys", "count"];
  const writeMethods = ["put", "add", "delete", "clear"];
  const cachedMethods = new Map();
  function getMethod(target, prop) {
    if (!(target instanceof IDBDatabase && !(prop in target) && typeof prop === "string")) {
      return;
    }
    if (cachedMethods.get(prop))
      return cachedMethods.get(prop);
    const targetFuncName = prop.replace(/FromIndex$/, "");
    const useIndex = prop !== targetFuncName;
    const isWrite = writeMethods.includes(targetFuncName);
    if (
!(targetFuncName in (useIndex ? IDBIndex : IDBObjectStore).prototype) || !(isWrite || readMethods.includes(targetFuncName))
    ) {
      return;
    }
    const method = async function(storeName, ...args) {
      const tx = this.transaction(storeName, isWrite ? "readwrite" : "readonly");
      let target2 = tx.store;
      if (useIndex)
        target2 = target2.index(args.shift());
      return (await Promise.all([
        target2[targetFuncName](...args),
        isWrite && tx.done
      ]))[0];
    };
    cachedMethods.set(prop, method);
    return method;
  }
  replaceTraps((oldTraps) => ({
    ...oldTraps,
    get: (target, prop, receiver) => getMethod(target, prop) || oldTraps.get(target, prop, receiver),
    has: (target, prop) => !!getMethod(target, prop) || oldTraps.has(target, prop)
  }));
  const advanceMethodProps = ["continue", "continuePrimaryKey", "advance"];
  const methodMap = {};
  const advanceResults = new WeakMap();
  const ittrProxiedCursorToOriginalProxy = new WeakMap();
  const cursorIteratorTraps = {
    get(target, prop) {
      if (!advanceMethodProps.includes(prop))
        return target[prop];
      let cachedFunc = methodMap[prop];
      if (!cachedFunc) {
        cachedFunc = methodMap[prop] = function(...args) {
          advanceResults.set(this, ittrProxiedCursorToOriginalProxy.get(this)[prop](...args));
        };
      }
      return cachedFunc;
    }
  };
  async function* iterate(...args) {
    let cursor = this;
    if (!(cursor instanceof IDBCursor)) {
      cursor = await cursor.openCursor(...args);
    }
    if (!cursor)
      return;
    cursor = cursor;
    const proxiedCursor = new Proxy(cursor, cursorIteratorTraps);
    ittrProxiedCursorToOriginalProxy.set(proxiedCursor, cursor);
    reverseTransformCache.set(proxiedCursor, unwrap(cursor));
    while (cursor) {
      yield proxiedCursor;
      cursor = await (advanceResults.get(proxiedCursor) || cursor.continue());
      advanceResults.delete(proxiedCursor);
    }
  }
  function isIteratorProp(target, prop) {
    return prop === Symbol.asyncIterator && instanceOfAny(target, [IDBIndex, IDBObjectStore, IDBCursor]) || prop === "iterate" && instanceOfAny(target, [IDBIndex, IDBObjectStore]);
  }
  replaceTraps((oldTraps) => ({
    ...oldTraps,
    get(target, prop, receiver) {
      if (isIteratorProp(target, prop))
        return iterate;
      return oldTraps.get(target, prop, receiver);
    },
    has(target, prop) {
      return isIteratorProp(target, prop) || oldTraps.has(target, prop);
    }
  }));
  const log$g = logger.child("storage");
  const STORES = {
    CACHE: "cache",
    FLIGHTS: "flights",
    ANALYTICS: "analytics"
  };
  class FFCache {
    constructor(db_name) {
      this.db = null;
      this.db_version = 3;
      this.cache_interval = 60 * 60 * 1e3;
      this.last_clean = 0;
      this.active_operations = 0;
      this.close_timer = null;
      this.open_promise = null;
      this.channel = null;
      this.state = "CLOSED";
      this.deletion_promise = null;
      this.resolve_deletion = null;
      this.migrations = new Map([
        [
          1,
          (db, _) => {
            const store = db.createObjectStore(STORES.CACHE, {
              keyPath: "player_id"
            });
            store.createIndex("expiry", "expiry", {
              unique: false
            });
          }
        ],
        [
          2,
          (db, _) => {
            const store = db.createObjectStore(STORES.FLIGHTS, {
              keyPath: "player_id"
            });
            store.createIndex("expiry", "expiry", {
              unique: false
            });
          }
        ],
        [
          3,
          (db, _) => {
            const store = db.createObjectStore(STORES.ANALYTICS, {
              keyPath: "id",
              autoIncrement: true
            });
            store.createIndex("timestamp", "timestamp", {
              unique: false
            });
          }
        ]
      ]);
      this.open = async () => {
        if (this.db) {
          return this.db;
        }
        if (this.open_promise) {
          return this.open_promise;
        }
        if (typeof BroadcastChannel !== "undefined" && !this.channel) {
          this.channel = new BroadcastChannel(`ffcache-channel-${this.db_name}`);
          this.channel.onmessage = (event) => {
            this.handle_broadcast(event.data);
          };
          if (typeof this.channel.unref === "function") {
            this.channel.unref();
          }
        }
        const cache = this;
        this.open_promise = (async () => {
          try {
            const db = await openDB(this.db_name, this.db_version, {
              upgrade(db2, oldVersion, newVersion, transaction, _event) {
                log$g.info("Need to upgrade from", oldVersion, "to", newVersion);
                for (let i = (oldVersion ?? 0) + 1; i <= cache.db_version; i++) {
                  log$g.debug(`Migration: ${i}`);
                  const m2 = cache.migrations.get(i);
                  if (m2) {
                    m2(db2, transaction);
                  } else {
                    log$g.debug(`Migration not found: ${i}`);
                  }
                  log$g.debug(`Migration complete: ${i}`);
                }
              },
              blocking(currentVersion, blockedVersion, event) {
                log$g.debug(
                  `Can't open ${blockedVersion} because ${currentVersion} is open. Closing.`
                );
                cache.close();
                if (event?.target && typeof event.target.close === "function") {
                  event.target.close();
                }
              }
});
            cache.db = db;
            cache.state = "OPEN";
            return db;
          } finally {
            cache.open_promise = null;
          }
        })();
        return this.open_promise;
      };
      this.close = () => {
        if (this.db) {
          this.db.close();
          this.db = null;
        }
        this.state = "CLOSED";
      };
      this.start_op = async () => {
        if (this.state === "DELETING_LOCAL" || this.state === "DELETING_REMOTE") {
          await this.wait_for_deletion_complete();
        }
        this.active_operations++;
        if (this.close_timer) {
          clearTimeout(this.close_timer);
          this.close_timer = null;
        }
        return await this.open();
      };
      this.end_op = () => {
        this.active_operations = Math.max(0, this.active_operations - 1);
        if (this.active_operations === 0) {
          if (this.close_timer) {
            clearTimeout(this.close_timer);
          }
          this.close_timer = setTimeout(() => {
            this.close();
            this.close_timer = null;
          }, 1e3);
        }
      };
      this.delete_db = async () => {
        if (this.close_timer) {
          clearTimeout(this.close_timer);
          this.close_timer = null;
        }
        this.state = "DELETING_LOCAL";
        this.channel?.postMessage({ type: "deleting" });
        await this.wait_for_active_ops();
        this.close();
        try {
          await deleteDB(this.db_name, {
            blocked: () => {
              log$g.debug("deleteDB blocked callback called!");
            }
          });
          log$g.info(`Successfully deleted ${this.db_name} IndexedDB.`);
        } finally {
          this.channel?.postMessage({ type: "deleted" });
          this.state = "CLOSED";
          if (this.resolve_deletion) {
            this.resolve_deletion();
            this.resolve_deletion = null;
            this.deletion_promise = null;
          }
          if (this.channel) {
            this.channel.close();
            this.channel = null;
          }
        }
      };
      this.handle_broadcast = (data) => {
        if (data && typeof data === "object") {
          if (data.type === "deleting") {
            this.state = "DELETING_REMOTE";
            if (!this.deletion_promise) {
              this.deletion_promise = new Promise((resolve) => {
                this.resolve_deletion = resolve;
              });
            }
            this.close();
          } else if (data.type === "deleted") {
            this.state = "CLOSED";
            if (this.resolve_deletion) {
              this.resolve_deletion();
              this.resolve_deletion = null;
              this.deletion_promise = null;
            }
          }
        }
      };
      this.wait_for_deletion_complete = async () => {
        while (this.state === "DELETING_LOCAL" || this.state === "DELETING_REMOTE") {
          if (this.deletion_promise) {
            await this.deletion_promise;
          } else {
            await new Promise((resolve) => setTimeout(resolve, 10));
          }
        }
      };
      this.wait_for_active_ops = async () => {
        if (this.open_promise) {
          try {
            await this.open_promise;
          } catch {
          }
        }
        while (this.active_operations > 0) {
          await new Promise((resolve) => setTimeout(resolve, 10));
        }
      };
      this.get = async (player_ids) => {
        const db = await this.start_op();
        try {
          const tx = db.transaction(STORES.CACHE, "readonly");
          const requests = player_ids.map((id) => tx.store.get(id));
          const entries = await Promise.all(requests);
          await tx.done;
          const result = new Map();
          player_ids.forEach((id, idx) => {
            const value = entries[idx];
            result.set(id, !value || value.expiry <= Date.now() ? null : value);
          });
          return result;
        } finally {
          this.end_op();
        }
      };
      this.update = async (values) => {
        const db = await this.start_op();
        try {
          const tx = db.transaction(STORES.CACHE, "readwrite");
          const values_expiry = values.map((value) => {
            return {
              ...value,
              expiry: Date.now() + this.cache_interval
            };
          });
          const requests = values_expiry.map((value) => {
            return tx.store.put(value);
          });
          await Promise.all(requests);
          await tx.done;
        } finally {
          this.end_op();
        }
      };
      this.clean_expired = (force = false) => {
        const now = Date.now();
        if (!force && now - this.last_clean < 60 * 60 * 1e3) {
          return Promise.resolve();
        }
        this.last_clean = now;
        const runClean = async () => {
          const db = await this.start_op();
          try {
            {
              const tx = db.transaction(STORES.CACHE, "readwrite");
              const index2 = tx.store.index("expiry");
              const range = IDBKeyRange.upperBound(Date.now());
              const r = await index2.getAllKeys(range);
              log$g.info(`Found ${r.length} expired values to delete from cache.`);
              await Promise.all(r.map((id) => tx.store.delete(id)));
              await tx.done;
            }
            {
              const tx = db.transaction(STORES.FLIGHTS, "readwrite");
              const index2 = tx.store.index("expiry");
              const range = IDBKeyRange.upperBound(Date.now());
              const r = await index2.getAllKeys(range);
              log$g.info(`Found ${r.length} expired values to delete from flights.`);
              await Promise.all(r.map((id) => tx.store.delete(id)));
              await tx.done;
            }
            {
              const tx = db.transaction(STORES.ANALYTICS, "readwrite");
              const index2 = tx.store.index("timestamp");
              const thirty_days_ago = Date.now() - 30 * 24 * 60 * 60 * 1e3;
              const range = IDBKeyRange.upperBound(thirty_days_ago);
              const r = await index2.getAllKeys(range);
              log$g.info(
                `Found ${r.length} expired values to delete from analytics.`
              );
              await Promise.all(r.map((id) => tx.store.delete(id)));
              await tx.done;
            }
          } finally {
            this.end_op();
          }
        };
        if (typeof window !== "undefined" && "requestIdleCallback" in window) {
          return new Promise((resolve, reject) => {
            window.requestIdleCallback(() => {
              runClean().then(resolve, reject);
            });
          });
        }
        return runClean();
      };
      this.get_flight = async (player_id) => {
        const db = await this.start_op();
        try {
          const tx = db.transaction(STORES.FLIGHTS, "readonly");
          const entry = await tx.store.get(player_id);
          await tx.done;
          if (!entry || entry.expiry <= Date.now()) {
            return null;
          }
          return entry;
        } finally {
          this.end_op();
        }
      };
      this.update_flight = async (value, cache_interval = 60 * 1e3) => {
        const db = await this.start_op();
        try {
          const tx = db.transaction(STORES.FLIGHTS, "readwrite");
          const value_expiry = {
            ...value,
            expiry: Date.now() + cache_interval
          };
          await tx.store.put(value_expiry);
          await tx.done;
        } finally {
          this.end_op();
        }
      };
      this.delete_flight = async (player_id) => {
        const db = await this.start_op();
        try {
          const tx = db.transaction(STORES.FLIGHTS, "readwrite");
          await tx.store.delete(player_id);
          await tx.done;
        } finally {
          this.end_op();
        }
      };
      this.add_analytics = async (entry) => {
        const db = await this.start_op();
        try {
          const tx = db.transaction(STORES.ANALYTICS, "readwrite");
          const value = {
            ...entry,
            timestamp: Date.now()
          };
          await tx.store.add(value);
          await tx.done;
        } finally {
          this.end_op();
        }
      };
      this.get_analytics = async () => {
        const db = await this.start_op();
        try {
          const tx = db.transaction(STORES.ANALYTICS, "readonly");
          const res = await tx.store.getAll();
          await tx.done;
          return res;
        } finally {
          this.end_op();
        }
      };
      this.clear_analytics = async () => {
        const db = await this.start_op();
        try {
          const tx = db.transaction(STORES.ANALYTICS, "readwrite");
          await tx.store.clear();
          await tx.done;
        } finally {
          this.end_op();
        }
      };
      this.dump = async () => {
        const db = await this.start_op();
        try {
          const tx = db.transaction(STORES.CACHE, "readonly");
          const res = await tx.store.getAll();
          await tx.done;
          return res;
        } finally {
          this.end_op();
        }
      };
      this.dump_flights = async () => {
        const db = await this.start_op();
        try {
          const tx = db.transaction(STORES.FLIGHTS, "readonly");
          const res = await tx.store.getAll();
          await tx.done;
          return res;
        } finally {
          this.end_op();
        }
      };
      this.db_name = db_name;
      if (typeof BroadcastChannel !== "undefined") {
        this.channel = new BroadcastChannel(`ffcache-channel-${db_name}`);
        this.channel.onmessage = (event) => {
          this.handle_broadcast(event.data);
        };
        if (typeof this.channel.unref === "function") {
          this.channel.unref();
        }
      }
    }
  }
  const log$f = logger.child("api");
  const DB_NAME = "FFSV3-cache";
  const RECHECK_RETRY_DELAY = 60 * 1e3;
  const RECHECK_WINDOW_DURATION = 3 * 60 * 1e3;
  const FINALIZED_NO_FLIGHT_TTL = 30 * 60 * 1e3;
  const FLIGHT_PACING_DELAY = 1e3;
  const GLOBAL_BUDGET_RESERVE = 50;
  function getParamFast(queryString, paramName) {
    if (!queryString) return null;
    const target = `${paramName}=`;
    if (!queryString.includes(target)) return null;
    let startIdx = 0;
    if (queryString.charCodeAt(0) === 63) {
      startIdx = 1;
    }
    let pos = queryString.indexOf(target, startIdx);
    while (pos !== -1) {
      if (pos === startIdx || queryString.charCodeAt(pos - 1) === 38 ||
queryString.charCodeAt(pos - 1) === 63) {
        const valStart = pos + target.length;
        let valEnd = queryString.indexOf("&", valStart);
        if (valEnd === -1) {
          valEnd = queryString.length;
        }
        const rawVal = queryString.substring(valStart, valEnd);
        if (rawVal.indexOf("%") === -1 && rawVal.indexOf("+") === -1) {
          return rawVal;
        }
        try {
          return decodeURIComponent(rawVal.replace(/\+/g, " "));
        } catch {
          return rawVal;
        }
      }
      pos = queryString.indexOf(target, pos + 1);
    }
    return null;
  }
  class FFScouter {
    constructor(config, cache) {
      this.cache = new FFCache(DB_NAME);
      this.pending = new Map();
      this.flight_queue = [];
      this.flight_timer = null;
      this.flight_recheck_until = new Map();
      this.pending_flights = new Map();
      this.cache_queue = new Set();
      this.cache_delay = 10;
      this.cache_timer = null;
      this.api_queue = new Set();
      this.api_max_batch_size = 200;
      this.api_initial_delay = 100;
      this.api_default_delay = 1e3;
      this.api_timer = null;
      this.api_attempts = 5;
      this.schedule = (fn, delay) => {
        return setTimeout(fn, delay);
      };
      this.clear = (timer) => {
        if (timer) {
          clearTimeout(timer);
        }
      };
      this.get = (player_id) => {
        const p = this.pending.get(player_id);
        if (p) {
          return p.promise;
        }
        let resolve;
        let reject;
        const promise = new Promise((res, rej) => {
          resolve = res;
          reject = rej;
        });
        this.pending.set(player_id, { promise, resolve, reject, api_attempts: 0 });
        if (!this.config.key) {
          this.resolve(player_id, { player_id, no_data: true });
          return promise;
        }
        this.enqueue_cache(player_id);
        return promise;
      };
      this.clear_flight_cache = async (player_id) => {
        try {
          await this.cache.delete_flight(player_id);
        } catch (err) {
          log$f.error("Failed to delete flight from cache", err);
        }
      };
      this.calculate_flight_cache_ttl = (result) => {
        if (result.current) {
          const now = Date.now();
          const latest_arrival_time_ms = result.current.latest_arrival_time * 1e3;
          const time_remaining = latest_arrival_time_ms - now;
          if (time_remaining > 0) {
            const segment = Math.floor(time_remaining / 2);
            const min_ttl = 60 * 1e3;
            return Math.max(min_ttl, segment);
          }
        }
        return FINALIZED_NO_FLIGHT_TTL;
      };
      this.enqueue_flight_api = (player_id, recheck_until) => {
        let resolve;
        let reject;
        const promise = new Promise((res, rej) => {
          resolve = res;
          reject = rej;
        });
        if (recheck_until !== void 0) {
          this.flight_recheck_until.set(player_id, recheck_until);
        }
        const pending = this.pending_flights.get(player_id);
        if (pending) {
          pending.push({ resolve, reject });
          return promise;
        }
        this.pending_flights.set(player_id, [{ resolve, reject }]);
        this.flight_queue.push(player_id);
        this.schedule_flight_processor();
        return promise;
      };
      this.schedule_flight_processor = (delay = 0) => {
        if (this.flight_timer) {
          return;
        }
        this.flight_timer = this.schedule(this.process_flight_queue, delay);
      };
      this.process_flight_queue = async () => {
        this.flight_timer = null;
        if (this.flight_queue.length === 0) {
          return;
        }
        if (this.last_limits && this.last_limits.reset_time > new Date() && this.last_limits.remaining <= GLOBAL_BUDGET_RESERVE) {
          log$f.warn(
            `Total API quota <= ${GLOBAL_BUDGET_RESERVE}. Deferring flight status checks to prioritize stats.`
          );
          this.schedule_flight_processor(5e3);
          return;
        }
        const player_id = this.flight_queue.shift();
        if (player_id === void 0) {
          return;
        }
        const pending = this.pending_flights.get(player_id);
        if (!pending) {
          this.schedule_flight_processor(0);
          return;
        }
        log$f.debug(`Querying paced flight API for player ${player_id}`);
        try {
          const response = await query_flights(this.config.key, player_id);
          if (response.blank) {
            throw new Error(
              `Empty flight response returned for player ${player_id}`
            );
          }
          if (response.limits) {
            this.last_limits = response.limits;
          }
          let finalResult = response.result;
          if (response.result.current) {
            try {
              const ttl = this.calculate_flight_cache_ttl(response.result);
              await this.cache.update_flight(response.result, ttl);
            } catch (err) {
              log$f.error("Failed to update flight cache", err);
            }
          } else {
            log$f.debug(`Start rechecking cycle for player ${player_id}`);
            const now = Date.now();
            const next_retry_at = now + RECHECK_RETRY_DELAY;
            const existing_recheck_until = this.flight_recheck_until.get(player_id);
            const recheck_until = existing_recheck_until ?? now + RECHECK_WINDOW_DURATION;
            const rechecking_response = {
              player_id: response.result.player_id,
              current: null,
              recent_flights: response.result.recent_flights,
              rechecking: true,
              next_retry_at,
              recheck_until
            };
            try {
              const remaining_ttl = Math.max(0, recheck_until - now);
              await this.cache.update_flight(rechecking_response, remaining_ttl);
            } catch (err) {
              log$f.error("Failed to update flight cache during recheck", err);
            }
            finalResult = rechecking_response;
          }
          for (const job of pending) {
            job.resolve(finalResult);
          }
        } catch (err) {
          log$f.error(`Paced flight API query failed for ${player_id}:`, err);
          const apiErr = err;
          if (apiErr?.ff_api_limits) {
            this.last_limits = apiErr.ff_api_limits;
          }
          for (const job of pending) {
            job.reject(err);
          }
        } finally {
          this.pending_flights.delete(player_id);
          this.flight_recheck_until.delete(player_id);
          try {
            await this.cache.clean_expired();
          } catch (err) {
            log$f.error("Failed to clean expired cache entries", err);
          }
          if (this.flight_queue.length > 0) {
            this.schedule_flight_processor(FLIGHT_PACING_DELAY);
          }
        }
      };
      this.get_flights = async (player_id) => {
        log$f.debug(`get_flights called for ${player_id}`);
        if (!this.config.key) {
          return {
            player_id,
            current: null,
            recent_flights: []
          };
        }
        let cached = null;
        try {
          cached = await this.cache.get_flight(player_id);
        } catch (err) {
          log$f.error("Failed to query flight cache", err);
        }
        if (cached) {
          log$f.debug(`Flight cache hit for player ${player_id}`);
          if (cached.rechecking) {
            const now = Date.now();
            if (cached.recheck_until && now >= cached.recheck_until) {
              log$f.debug(
                `Rechecking window expired for player ${player_id}. Finalizing no data.`
              );
              const final_response = {
                player_id: cached.player_id,
                current: null,
                recent_flights: cached.recent_flights,
                rechecking: false
              };
              try {
                await this.cache.update_flight(
                  final_response,
                  FINALIZED_NO_FLIGHT_TTL
                );
              } catch (err) {
                log$f.error("Failed to finalize flight cache", err);
              }
              return final_response;
            }
            if (cached.next_retry_at && now >= cached.next_retry_at) {
              log$f.debug(
                `Retrying API call for player ${player_id} during recheck window`
              );
              const result2 = await this.enqueue_flight_api(
                player_id,
                cached.recheck_until
              );
              return result2;
            }
            return {
              player_id: cached.player_id,
              current: cached.current,
              recent_flights: cached.recent_flights,
              rechecking: true,
              next_retry_at: cached.next_retry_at,
              recheck_until: cached.recheck_until
            };
          }
          return {
            player_id: cached.player_id,
            current: cached.current,
            recent_flights: cached.recent_flights
          };
        }
        log$f.debug(`Flight cache miss for player ${player_id}. Querying API paced.`);
        const result = await this.enqueue_flight_api(player_id);
        return result;
      };
      this.complete = () => {
        this.process_cache();
      };
      this.enqueue_cache = (player_id) => {
        log$f.debug(`Enqueuing cache ${player_id}`);
        this.cache_queue.add(player_id);
        this.schedule_cache();
      };
      this.schedule_cache = () => {
        if (this.cache_timer) {
          log$f.debug(`schedule_cache called but job already scheduled`);
          return;
        }
        log$f.debug(
          `schedule_cache called and job scheduled for ${this.cache_delay} ms`
        );
        this.cache_timer = this.schedule(this.process_cache, this.cache_delay);
      };
      this.process_cache = async () => {
        if (this.cache_timer) {
          this.clear(this.cache_timer);
          this.cache_timer = null;
        }
        const ids = Array.from(this.cache_queue);
        this.cache_queue.clear();
        if (ids.length <= 0) {
          return;
        }
        let results;
        try {
          results = await this.cache.get(ids);
        } catch (_) {
          results = new Map();
        }
        log$f.debug(`Received ${results.size} cache results`);
        for (const id of ids) {
          const v = results.get(id);
          if (v) {
            log$f.debug("Id", id, "found in cache. Resolving value.");
            this.resolve(id, v);
          } else {
            log$f.debug("Id", id, "not found in cache. Scheduling api call.");
            this.enqueue_api(id);
          }
        }
      };
      this.clear_cache = () => {
        this.cache.delete_db();
        check_key_status.clear();
      };
      this.enqueue_api = (player_id) => {
        log$f.debug(`Enqueuing api ${player_id}`);
        this.api_queue.add(player_id);
        this.schedule_api();
      };
      this.schedule_api = (delay = this.api_initial_delay) => {
        if (this.api_timer) {
          log$f.debug(`schedule_api called but job already scheduled`);
          return;
        }
        log$f.debug(`schedule_api called and job scheduled for ${delay} ms`);
        this.api_timer = this.schedule(this.process_api, delay);
      };
      this.process_api = async () => {
        log$f.debug("process_api called");
        if (this.api_timer) {
          this.clear(this.api_timer);
          this.api_timer = null;
        }
        let ids = Array.from(this.api_queue);
        if (ids.length > this.api_max_batch_size) {
          ids = ids.slice(0, this.api_max_batch_size);
        }
        for (const id of ids) {
          this.api_queue.delete(id);
        }
        log$f.debug(`Processing ${ids} api requests`);
        if (ids.length <= 0) {
          log$f.debug("No ids found to query");
          return;
        }
        let next_run = this.api_default_delay;
        let results;
        try {
          log$f.debug(`Calling query_stats with key=*** ids=[${ids}]`);
          results = await query_stats(this.config.key, ids);
        } catch (err) {
          log$f.error("Received error response querying ffscouter api:", err);
          for (const id of ids) {
            this.reject(id, err);
          }
          const ff_error = err;
          results = {
            result: new Map(),
            blank: true,
            limits: ff_error.ff_api_limits
          };
        }
        log$f.debug(
          `Received api results: blank=${results.blank}, count=${results.result.size}`
        );
        if (results.blank) {
          for (const id of ids) {
            this.requeue_api(id);
          }
        } else {
          try {
            await this.cache.update(Array.from(results.result.values()));
          } catch (err) {
            log$f.error("Failed to update cache", err);
          }
          for (const id of ids) {
            const v = results.result.get(id);
            if (v) {
              log$f.debug("Id", id, "found in results. Resolving value.");
              this.resolve(id, v);
            } else {
              log$f.debug("Id", id, "not found in results. Resolving no_data.");
              this.resolve(id, { player_id: id, no_data: true });
            }
          }
        }
        if (results.limits) {
          this.last_limits = results.limits;
          next_run = this.calculate_next_api_run(results.limits);
        }
        this.schedule_api(next_run);
        await this.cache.clean_expired();
      };
      this.calculate_next_api_run = (limits) => {
        if (limits.remaining <= 0) {
          return limits.reset_time.getTime() - Date.now();
        } else if (limits.reset_time < new Date()) {
          return this.api_initial_delay;
        } else if (limits.rate_limit * 0.75 < limits.remaining) {
          return this.api_default_delay;
        } else {
          const ms_left = limits.reset_time.getTime() - Date.now();
          return ms_left / limits.remaining;
        }
      };
      this.resolve = (id, value) => {
        const entry = this.pending.get(id);
        if (!entry) return;
        entry.resolve(value);
        this.pending.delete(id);
      };
      this.reject = (id, err) => {
        const entry = this.pending.get(id);
        if (!entry) return;
        entry.reject(err);
        this.pending.delete(id);
      };
      this.requeue_api = (id) => {
        const entry = this.pending.get(id);
        if (!entry) return;
        entry.api_attempts++;
        if (entry.api_attempts > this.api_attempts) {
          this.reject(
            id,
            new Error(`Too many failed attempts to get stats for ${id}.`)
          );
          return false;
        }
        this.enqueue_api(id);
        return true;
      };
      this.add_analytics_entry = async (feature, player_id, status) => {
        if (!this.config.analytics_enabled) {
          return;
        }
        try {
          const url = window.location.origin + window.location.pathname;
          const params = window.location.search;
          const hash = window.location.hash;
          await this.cache.add_analytics({
            feature,
            player_id,
            status,
            url,
            params,
            hash
          });
        } catch (err) {
          log$f.error("Failed to add analytics entry", err);
        }
      };
      this.get_analytics_entries = async () => {
        try {
          return await this.cache.get_analytics();
        } catch (err) {
          log$f.error("Failed to get analytics entries", err);
          return [];
        }
      };
      this.get_aggregated_analytics = async () => {
        const entries = await this.get_analytics_entries();
        const aggregationMap = new Map();
        for (const entry of entries) {
          let param = "";
          if (entry.params) {
            param = getParamFast(entry.params, "sid") || getParamFast(entry.params, "step") || "";
          }
          if (!param && entry.hash) {
            let hashClean = entry.hash;
            if (hashClean.startsWith("#/")) {
              hashClean = hashClean.substring(2);
            } else if (hashClean.startsWith("#") || hashClean.startsWith("/")) {
              hashClean = hashClean.substring(1);
            }
            if (!hashClean.startsWith("!") && !hashClean.startsWith("?")) {
              hashClean = `?${hashClean}`;
            }
            param = getParamFast(hashClean, "sid") || getParamFast(hashClean, "step") || "";
          }
          const key = `${entry.url}|${param}|${entry.feature}|${entry.status}`;
          const existing = aggregationMap.get(key);
          if (existing) {
            existing.count++;
          } else {
            aggregationMap.set(key, {
              url: entry.url,
              param: param || "-",
              feature: entry.feature,
              status: entry.status,
              count: 1
            });
          }
        }
        return Array.from(aggregationMap.values());
      };
      this.clear_analytics = async () => {
        try {
          await this.cache.clear_analytics();
        } catch (err) {
          log$f.error("Failed to clear analytics entries", err);
        }
      };
      this.config = config;
      if (cache) {
        this.cache = cache;
      }
    }
    get analytics_enabled() {
      return this.config.analytics_enabled;
    }
  }
  const ffscouter = new FFScouter(ffconfig);
  const unit = Object.create(null);
  const m = 6e4, h = m * 60, d = h * 24, y = d * 365.25;
  unit.year = unit.yr = unit.y = y;
  unit.month = unit.mo = unit.mth = y / 12;
  unit.week = unit.wk = unit.w = d * 7;
  unit.day = unit.d = d;
  unit.hour = unit.hr = unit.h = h;
  unit.minute = unit.min = unit.m = m;
  unit.second = unit.sec = unit.s = 1e3;
  unit.millisecond = unit.millisec = unit.ms = 1;
  unit.microsecond = unit.microsec = unit.us = unit.µs = 1e-3;
  unit.nanosecond = unit.nanosec = unit.ns = 1e-6;
  unit.group = ",";
  unit.decimal = ".";
  unit.placeholder = " _";
  const durationRE = /((?:\d{1,16}(?:\.\d{1,16})?|\.\d{1,16})(?:[eE][-+]?\d{1,4})?)\s?([\p{L}]{0,14})/gu;
  parse.unit = unit;
  function parse(str = "", format = "ms") {
    let result = null, prevUnits;
    String(str).replace(new RegExp(`(\\d)[${parse.unit.placeholder}${parse.unit.group}](\\d)`, "g"), "$1$2").replace(parse.unit.decimal, ".").replace(durationRE, (_, n, units) => {
      if (!units) {
        if (prevUnits) {
          for (const u in parse.unit) if (parse.unit[u] < prevUnits) {
            units = u;
            break;
          }
        } else units = format;
      } else units = units.toLowerCase();
      prevUnits = units = parse.unit[units] || parse.unit[units.replace(/s$/, "")];
      if (units) result = (result || 0) + n * units;
    });
    return result && result / (parse.unit[format] || 1) * (str[0] === "-" ? -1 : 1);
  }
  const HOUR = 60 * 60;
  const DAY = HOUR * 24;
  const OLD_ESTIMATE_INTERVAL = 14 * DAY;
  function format_ff_score(d2) {
    const ff = d2.fair_fight.toFixed(2);
    const now = Date.now() / 1e3;
    const age = now - d2.last_updated;
    var suffix = "";
    if (age > OLD_ESTIMATE_INTERVAL) {
      suffix = "?";
    }
    return `${ff}${suffix}`;
  }
  function format_difficulty_text(d2) {
    if (d2.fair_fight <= 1) {
      return "Extremely easy";
    } else if (d2.fair_fight <= 2) {
      return "Easy";
    } else if (d2.fair_fight <= 3.5) {
      return "Moderately difficult";
    } else if (d2.fair_fight <= 4.5) {
      return "Difficult";
    } else {
      return "May be impossible";
    }
  }
  function format_relative_time(timestamp_sec) {
    const age = Date.now() / 1e3 - timestamp_sec;
    if (age < DAY) {
      return "";
    } else if (age < 31 * DAY) {
      const days = Math.round(age / DAY);
      if (days === 1) {
        return "(1 day old)";
      } else {
        return `(${days} days old)`;
      }
    } else if (age < 365 * DAY) {
      const months = Math.round(age / (31 * DAY));
      if (months === 1) {
        return "(1 month old)";
      } else {
        return `(${months} months old)`;
      }
    } else {
      const years = Math.round(age / (365 * DAY));
      if (years === 1) {
        return "(1 year old)";
      } else {
        return `(${years} years old)`;
      }
    }
  }
  function get_ff_colour(d2) {
    return get_ff_arrow_colour(d2);
  }
  const FF_ARROW_VIEWBOX = "0 0 20 13";
  const FF_ARROW_PATH_D = "M 0,0 H 13 20 L 10,12 Z";
  const NO_DATA_COLOR = "#000000";
  const BUILTIN_PALETTES = {

[ColorScheme.CLASSIC]: [
      "#1734e8",
      "#1788e8",
      "#17dbe8",
      "#17e8a1",
      "#17e84e",
      "#34e817",
      "#88e817",
      "#dbe817",
      "#e8a117",
      "#e84e17",
      "#e81734"
    ],

[ColorScheme.COOL_DIVERGING]: [
      "#2166ac",
      "#2080a2",
      "#1f9497",
      "#1e8d75",
      "#1c8254",
      "#1b7837",
      "#2e8b1e",
      "#6c9e21",
      "#b1aa23",
      "#c47525",
      "#d73027"
    ],
[ColorScheme.NEON]: [
      "#0c50ff",
      "#0cb1ff",
      "#0cffec",
      "#0cff8a",
      "#0cff29",
      "#50ff0c",
      "#b1ff0c",
      "#ffec0c",
      "#ff8a0c",
      "#ff290c",
      "#ff0c50"
    ],

[ColorScheme.COLORBLIND_SAFE]: [
      "#440154",
      "#481a6c",
      "#472f7d",
      "#414487",
      "#39568c",
      "#2a788e",
      "#21908d",
      "#22a884",
      "#42be71",
      "#a8db34",
      "#fde725"
    ],
[ColorScheme.GRAYSCALE]: [
      "#f0f0f0",
      "#e0e0e0",
      "#cccccc",
      "#b3b3b3",
      "#999999",
      "#808080",
      "#666666",
      "#4d4d4d",
      "#333333",
      "#1a1a1a",
      "#000000"
    ],



[ColorScheme.GREEN_YELLOW_RED]: [
      "#73bf69",
      "#8ec55c",
      "#a9cb50",
      "#c4d243",
      "#dfd837",
      "#fade2a",
      "#f8c034",
      "#f7a23e",
      "#f58548",
      "#f46752",
      "#f2495c"
    ],



[ColorScheme.BLUE_YELLOW_RED]: [
      "#1f60c4",
      "#4c7ebb",
      "#799db3",
      "#a5bbaa",
      "#d2daa2",
      "#fff899",
      "#f3cb83",
      "#e79e6d",
      "#dc7056",
      "#d04340",
      "#c4162a"
    ],

[ColorScheme.PLASMA]: [
      "#0d0887",
      "#41049d",
      "#6a00a8",
      "#8f0da4",
      "#b12a90",
      "#cc4778",
      "#e16462",
      "#f2844b",
      "#fca636",
      "#fcce25",
      "#f0f921"
    ]
  };
  function is_valid_custom_palette(colors) {
    return colors !== null && colors.length === 11 && colors.every((c) => typeof c === "string");
  }
  function get_palette_for_scheme(scheme, customColors = null) {
    if (scheme === ColorScheme.CUSTOM) {
      if (is_valid_custom_palette(customColors)) {
        return customColors;
      }
      return BUILTIN_PALETTES[ColorScheme.CLASSIC];
    }
    return BUILTIN_PALETTES[scheme];
  }
  function get_active_palette() {
    return get_palette_for_scheme(ffconfig.color_scheme, ffconfig.custom_colors);
  }
  function get_ff_arrow_colour(d2) {
    if (d2.no_data) {
      return NO_DATA_COLOR;
    }
    let ff = d2.fair_fight;
    if (ff < 1) {
      ff = 1;
    } else if (ff > 5) {
      ff = 5;
    }
    const ratio = Math.floor((ff - 1) / 4 * 10);
    const r = get_active_palette()[ratio];
    return r ?? NO_DATA_COLOR;
  }
  function get_contrast_color(hex) {
    const r = parseInt(hex.slice(1, 3), 16);
    const g = parseInt(hex.slice(3, 5), 16);
    const b = parseInt(hex.slice(5, 7), 16);
    const brightness = r * 0.299 + g * 0.587 + b * 0.114;
    return brightness > 126 ? "black" : "white";
  }
  function ff_to_percent(d2) {
    const low_ff = ffconfig.low_ff_range;
    const high_ff = ffconfig.high_ff_range;
    const max_ff = ffconfig.max_ff_range;
    const low_mid_percent = 33;
    const mid_high_percent = 66;
    const ff_lower = Math.min(d2.fair_fight, max_ff);
    let percent;
    if (ff_lower < low_ff) {
      percent = (ff_lower - 1) / (low_ff - 1) * low_mid_percent;
    } else if (ff_lower < high_ff) {
      percent = (ff_lower - low_ff) / (high_ff - low_ff) * (mid_high_percent - low_mid_percent) + low_mid_percent;
    } else {
      percent = (ff_lower - high_ff) / (max_ff - high_ff) * (100 - mid_high_percent) + mid_high_percent;
    }
    return percent;
  }
  function format_timestamp(ts) {
    const d2 = new Date(ts * 1e3);
    return `${d2.getHours() < 10 ? "0" : ""}${d2.getHours()}:${d2.getMinutes() < 10 ? "0" : ""}${d2.getMinutes()}:${d2.getSeconds() < 10 ? "0" : ""}${d2.getSeconds()} - ${d2.getDate() < 10 ? "0" : ""}${d2.getDate()}/${d2.getMonth() + 1 < 10 ? "0" : ""}${d2.getMonth() + 1}/${d2.getFullYear() - 2e3}`;
  }
  function parse_suffix_number(valStr) {
    const trimmed = valStr.trim().toLowerCase();
    if (!trimmed) return null;
    const match = trimmed.match(/^([\d.,]+)\s*([kmbt])?$/);
    if (!match) return null;
    const matchStr = match[1];
    if (!matchStr) return null;
    const num = Number(matchStr.replace(/,/g, ""));
    if (Number.isNaN(num)) return null;
    const suffix = match[2];
    if (!suffix) return num;
    const multiplier = {
      k: 1e3,
      m: 1e6,
      b: 1e9,
      t: 1e12
    };
    return num * (multiplier[suffix] ?? 1);
  }
  function parse_duration_to_seconds(valStr) {
    const trimmed = valStr.trim();
    if (!trimmed) return null;
    return parse(trimmed, "s");
  }
  let _react;
  function getReact() {
    return _react ??= unsafeWindow.React;
  }
  new Proxy({}, {
    get(_, prop) {
      return getReact()[prop];
    }
  });
  const useState = ((...args) => getReact().useState(
    ...args
  ));
  const useEffect = ((...args) => getReact().useEffect(
    ...args
  ));
  const useRef = ((...args) => getReact().useRef(
    ...args
  ));
  const useImperativeHandle = ((...args) => getReact().useImperativeHandle(
    ...args
  ));
  const createElement = ((...args) => getReact().createElement(
    ...args
  ));
  new Proxy(
    {},
    {
      get: (_, prop) => getReact().Fragment[prop]
    }
  );
  new Proxy(
    {},
    {
      get: (_, prop) => getReact().StrictMode[prop]
    }
  );
  new Proxy(
    {},
    {
      get: (_, prop) => getReact().Suspense[prop]
    }
  );
  new Proxy(
    {},
    {
      get: (_, prop) => getReact().Children[prop]
    }
  );
  const styles$2 = {
    "ffscouter-info-line__label": "_ffscouter-info-line__label_xi5zk_1",
    "ffscouter-info-line__badge": "_ffscouter-info-line__badge_xi5zk_8",
    "ffscouter-info-line__premium-upgrade": "_ffscouter-info-line__premium-upgrade_xi5zk_15"
  };
  const log$e = logger.child("ui");
  const PREMIUM_UPGRADE_URL$1 = "https://ffscouter.com/premium";
  function FFHeaderLine({ playerId }) {
    const [data, setData] = useState(null);
    const [isPremium, setIsPremium] = useState(null);
    const [premiumLoading, setPremiumLoading] = useState(false);
    useEffect(() => {
      let cancelled = false;
      ffscouter.get(playerId).then((result) => {
        if (!cancelled) setData(result);
      }).catch((err) => {
        log$e.error(err);
      });
      return () => {
        cancelled = true;
      };
    }, [playerId]);
    useEffect(() => {
      if (!data) return;
      setPremiumLoading(true);
      let cancelled = false;
      check_key_status.is_premium().then((premium) => {
        if (!cancelled) setIsPremium(premium);
      }).catch((err) => {
        log$e.error(err);
      }).finally(() => {
        if (!cancelled) setPremiumLoading(false);
      });
      return () => {
        cancelled = true;
      };
    }, [data]);
    if (data === null) {
      return jsxs(Fragment, { children: [
jsx("span", { className: styles$2["ffscouter-info-line__label"], children: "FairFight:" }),
jsx("span", { style: { fontStyle: "italic" }, children: "Loading..." })
      ] });
    }
    if (data.no_data) {
      return jsxs(Fragment, { children: [
jsx("span", { className: styles$2["ffscouter-info-line__label"], children: "FairFight:" }),
jsx(
          "span",
          {
            className: styles$2["ffscouter-info-line__badge"],
            style: { background: "#444", color: "#fff" },
            children: "No data"
          }
        )
      ] });
    }
    const ffString = format_ff_score(data);
    const difficulty = format_difficulty_text(data);
    const fresh = format_relative_time(data.last_updated);
    const backgroundColor = get_ff_colour(data);
    const textColor = get_contrast_color(backgroundColor);
    let extraDetailsLine = null;
    if (data.distribution?.distribution_human) {
      const ageStr = format_relative_time(data.distribution.last_updated);
      extraDetailsLine = jsxs(
        "span",
        {
          style: {
            display: "block",
            marginTop: "2px",
            fontSize: "12px",
            fontStyle: "normal"
          },
          children: [
jsx("span", { className: styles$2["ffscouter-info-line__label"], children: "Top Stats:" }),
jsxs("span", { style: { fontWeight: "normal" }, children: [
              data.distribution.distribution_human,
              " ",
              ageStr
            ] })
          ]
        }
      );
    } else if (premiumLoading) {
      extraDetailsLine = null;
    } else if (isPremium === false && data.premium_insights_available) {
      extraDetailsLine = jsx("span", { className: styles$2["ffscouter-info-line__premium-upgrade"], children: jsx(
        "a",
        {
          href: PREMIUM_UPGRADE_URL$1,
          target: "_blank",
          rel: "noopener noreferrer",
          style: { fontWeight: "bold", textDecoration: "underline" },
          children: "Premium Data Available - Upgrade To View"
        }
      ) });
    }
    return jsxs(Fragment, { children: [
jsx("span", { className: styles$2["ffscouter-info-line__label"], children: "FairFight:" }),
jsxs(
        "span",
        {
          className: styles$2["ffscouter-info-line__badge"],
          style: { background: backgroundColor, color: textColor },
          children: [
            ffString,
            " (",
            difficulty,
            ") ",
            fresh
          ]
        }
      ),
jsxs(
        "span",
        {
          style: {
            fontSize: "11px",
            fontWeight: "normal",
            marginLeft: "6px",
            verticalAlign: "middle",
            fontStyle: "italic"
          },
          children: [
            "Est. Stats: ",
jsx("span", { children: data.bs_estimate_human })
          ]
        }
      ),
      extraDetailsLine
    ] });
  }
  const log$d = logger.child("dom");
  const ID_PARAMS = ["XID", "user2ID"];
  function extract_id_from_url(url) {
    const parsed = new URL(url);
    const search = new URLSearchParams(parsed.search);
    for (const param of ID_PARAMS) {
      const v = search.get(param);
      if (v) {
        return parseInt(v, 10);
      }
    }
    return null;
  }
  function torn_page(page, params = {}, match_hash = []) {
    const url_match = window.location.href.startsWith(
      `https://www.torn.com/${page}.php`
    );
    if (!url_match) {
      return false;
    }
    const search = new URLSearchParams(window.location.search);
    let sid_match = true;
    let step_match = true;
    let page_match = true;
    if (params.sid) {
      const page_sid = search.get("sid");
      sid_match = page_sid !== null && params.sid === page_sid;
    }
    if (params.step) {
      const page_step = search.get("step");
      step_match = page_step !== null && params.step === page_step;
    }
    if (params.page) {
      const page_page = search.get("page");
      page_match = page_page !== null && params.page === page_page;
    }
    if (!sid_match || !step_match || !page_match) {
      return false;
    }
    let hash_match = false;
    if (match_hash.length === 0) {
      hash_match = true;
    } else {
      const hash = window.location.hash;
      for (const h2 of match_hash) {
        if (h2.endsWith("*")) {
          const stripped = h2.substring(0, h2.length - 1);
          if (hash.startsWith(stripped)) {
            hash_match = true;
            break;
          }
        }
        if (hash === h2) {
          hash_match = true;
          break;
        }
      }
    }
    return sid_match && step_match && page_match && hash_match;
  }
  function make_arrow(d2) {
    const fill = get_ff_arrow_colour(d2);
    const div = document.createElement("div");
    div.innerHTML = `<svg version="1.2" id="Layer_1" x="0px" y="0px" width="20" height="13" viewBox="${FF_ARROW_VIEWBOX}" xml:space="preserve" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
	<path fill-rule="evenodd" fill="${fill}" stroke="#000000" d="${FF_ARROW_PATH_D}" id="path1" style="display:inline;stroke-width:${ffconfig.gauge_marker_border_width};"/>
</svg>`;
    if (!div.firstChild || !(div.firstChild instanceof SVGElement)) {
      throw new Error(
        "Wasn't able to extract just created SVG out of div element"
      );
    }
    const svg = div.firstChild;
    svg.classList.add("ffscouter-arrow");
    return svg;
  }
  function make_marker(d2) {
    const markerType = ffconfig.gauge_marker_type;
    if (markerType === GaugeMarkerType.BUBBLE_FF || markerType === GaugeMarkerType.BUBBLE_ESTIMATE) {
      const fill = get_ff_arrow_colour(d2);
      const contrastColor = get_contrast_color(fill);
      const bubble = document.createElement("div");
      bubble.classList.add("ffscouter-bubble");
      bubble.style.backgroundColor = fill;
      bubble.style.color = contrastColor;
      bubble.style.borderWidth = `${ffconfig.gauge_marker_border_width * (ffconfig.gauge_marker_scale / 100)}px`;
      if (markerType === GaugeMarkerType.BUBBLE_FF) {
        bubble.textContent = d2.fair_fight.toFixed(2);
      } else {
        bubble.textContent = d2.bs_estimate_human || "N/A";
      }
      return bubble;
    }
    return make_arrow(d2);
  }
  function add_ff_arrow(element, featureName = "Unknown") {
    const player_id = get_player_id_in_element(element);
    if (!player_id) {
      return;
    }
    if (element.querySelector(".ffscouter-gauge") || element.classList.contains("ffscouter-gauge")) {
      ffscouter.add_analytics_entry(featureName, player_id, "ignored");
      return;
    }
    ffscouter.get(player_id).then((d2) => {
      if (d2.no_data) {
        return;
      }
      if (element.querySelector(".ffscouter-gauge") || element.classList.contains("ffscouter-gauge")) {
        ffscouter.add_analytics_entry(featureName, player_id, "ignored");
        return;
      }
      const percent = ff_to_percent(d2);
      element.classList.add("ffscouter-gauge");
      element.style.setProperty("--band-percent", `${percent}`);
      document.body.style.setProperty(
        "--ffscouter-marker-scale",
        `${ffconfig.gauge_marker_scale / 100}`
      );
      const a = element.querySelector(".ffscouter-arrow, .ffscouter-bubble");
      if (a) {
        a.remove();
      }
      element.appendChild(make_marker(d2));
      ffscouter.add_analytics_entry(featureName, player_id, "applied");
    });
  }
  function has_href(el) {
    return el instanceof HTMLAnchorElement;
  }
  function extract_target_id(href, r) {
    const match = href.match(r);
    const groups = match?.groups;
    if (groups?.target_id) {
      return parseInt(groups.target_id, 10);
    }
    return null;
  }
  function get_player_id_in_element(element) {
    const parent = element.parentElement;
    if (has_href(parent)) {
      const xid = extract_target_id(parent.href, /.*XID=(?<target_id>\d+)/);
      if (xid) {
        return xid;
      }
    }
    const anchors = element.getElementsByTagName("a");
    for (const anchor of anchors) {
      const xid = extract_target_id(anchor.href, /.*XID=(?<target_id>\d+)/);
      if (xid) {
        return xid;
      }
      const userid = extract_target_id(anchor.href, /.*userId=(?<target_id>\d+)/);
      if (userid) {
        return userid;
      }
    }
    if (has_href(element)) {
      const xid = extract_target_id(element.href, /.*XID=(?<target_id>\d+)/);
      if (xid) {
        return xid;
      }
      const userid = extract_target_id(
        element.href,
        /.*userId=(?<target_id>\d+)/
      );
      if (userid) {
        return userid;
      }
    }
    const parent_anchor = element.closest("a");
    if (parent_anchor) {
      const xid = extract_target_id(
        parent_anchor.href,
        /.*XID=(?<target_id>\d+)/
      );
      if (xid) {
        return xid;
      }
      const userid = extract_target_id(
        parent_anchor.href,
        /.*userId=(?<target_id>\d+)/
      );
      if (userid) {
        return userid;
      }
    }
    return null;
  }
  const seenUnknownActivityLabels = new Set();
  function get_activity_status(row) {
    const wrap2 = row.querySelector('[class*="userStatusWrap"]');
    const label = wrap2?.getAttribute("aria-label") || "";
    const match = label.match(/ is (online|offline|idle)$/i);
    const status = match?.[1];
    if (!status) {
      if (!seenUnknownActivityLabels.has(label)) {
        seenUnknownActivityLabels.add(label);
        log$d.warn(`Unrecognized activity aria-label: "${label}"`);
      }
      return "unknown";
    }
    return status.toLowerCase();
  }
  function apply_ff_gauge_selector(node_list, featureName = "Unknown") {
    for (const node of node_list) {
      add_ff_arrow(node, featureName);
    }
  }
  function apply_ff_gauge(element, featureName = "Unknown") {
    if (!(element instanceof HTMLElement)) {
      return;
    }
    add_ff_arrow(element, featureName);
  }
  async function wait_for_element(querySelector, timeout, root) {
    const existingElement = document.querySelector(querySelector);
    if (existingElement) return existingElement;
    return new Promise((resolve) => {
      let timer;
      const observer = new MutationObserver(() => {
        const element = document.querySelector(querySelector);
        if (element) {
          cleanup();
          resolve(element);
        }
      });
      if (!root) {
        root = document.body;
      }
      observer.observe(root, {
        childList: true,
        subtree: true
      });
      if (timeout) {
        timer = setTimeout(() => {
          cleanup();
          resolve(null);
        }, timeout);
      }
      function cleanup() {
        observer.disconnect();
        if (timer) clearTimeout(timer);
      }
    });
  }
  async function wait_for_body(timeout) {
    const body = await wait_for_element(
      "body",
      timeout,
      document.documentElement
    );
    return body !== null;
  }
  const CREATE_ELEMENT_RETRY_DELAYS_MS = [0, 50, 150];
  async function create_ff_element(tagName) {
    const ctor = customElements.get(tagName);
    for (const delay of CREATE_ELEMENT_RETRY_DELAYS_MS) {
      if (delay > 0) {
        await new Promise((resolve) => setTimeout(resolve, delay));
      }
      const element = document.createElement(tagName);
      if (!ctor || element instanceof ctor) {
        return element;
      }
      log$d.warn(`<${tagName}> construction produced a fallback element; retrying`);
    }
    log$d.error(
      `Failed to construct a working <${tagName}> after multiple attempts`
    );
    return null;
  }
  class MonitorElements {
    constructor(node_matcher, handler, root, continuous, options, _timeout) {
      this.options = {
        target: false,
        added: false,
        removed: false
      };
      this.started = false;
      this.timer = null;
      this.start = () => {
        if (this.started) {
          return;
        }
        let initial = false;
        for (const child of this.root.childNodes) {
          if (child instanceof HTMLElement && this.node_matcher(child)) {
            this.handler({ added: child });
            initial = true;
          }
        }
        if (!this.continuous && initial) {
          this.started = false;
          return;
        }
        this.observer.observe(this.root, { childList: true });
        this.timer = setInterval(() => {
          if (!this.root.isConnected) {
            this.cleanup();
          }
        }, 1e4);
        this.started = true;
      };
      this.cleanup = () => {
        this.observer.disconnect();
        if (this.timer) {
          clearInterval(this.timer);
        }
        this.timer = null;
      };
      this.node_matcher = node_matcher;
      this.handler = handler;
      this.root = root;
      this.continuous = continuous;
      this.options = options;
      this.observer = new MutationObserver(async (mutations) => {
        for (const mutation of mutations) {
          if (this.options.target && mutation.target instanceof HTMLElement && this.node_matcher(mutation.target)) {
            this.handler({ target: mutation.target });
          }
          if (this.options.added) {
            for (const node of mutation.addedNodes) {
              if (node instanceof HTMLElement && this.node_matcher(node)) {
                this.handler({ added: node });
              }
            }
          }
          if (this.options.removed) {
            for (const node of mutation.removedNodes) {
              if (node instanceof HTMLElement && this.node_matcher(node)) {
                this.handler({ removed: node });
              }
            }
          }
        }
      });
    }
  }
  async function getLocalUserId() {
    const name = await wait_for_element(
      ".settings-menu > .link > a:first-child",
      15e3
    );
    if (!name || !name.href) {
      log$d.debug("Failed to find the XID element.");
      return null;
    }
    try {
      const params = new URL(name.href).searchParams;
      return params.get("XID");
    } catch {
      log$d.debug("User XID is malformed");
      return null;
    }
  }
  function create_info_line() {
    const info_line = document.createElement("div");
    info_line.className = "ffscouter-info-line";
    info_line.style.display = "block";
    info_line.style.clear = "both";
    info_line.style.margin = "5px 0";
    return info_line;
  }
  function on_navigation(callback) {
    const nav = window.navigation;
    if (nav) {
      nav.addEventListener("currententrychange", callback);
      return () => {
        nav.removeEventListener("currententrychange", callback);
      };
    }
    const delayedCallback = () => {
      setTimeout(callback, 0);
    };
    window.addEventListener("popstate", delayedCallback);
    window.addEventListener("hashchange", delayedCallback);
    return () => {
      window.removeEventListener("popstate", delayedCallback);
      window.removeEventListener("hashchange", delayedCallback);
    };
  }
  function get_attack_url(playerId) {
    return `https://www.torn.com/page.php?sid=attack&user2ID=${playerId}`;
  }
  function open_attack_link(playerId, options) {
    const url = get_attack_url(playerId);
    const shouldOpenInNewTab = options?.openInNewTab !== void 0 ? options.openInNewTab : ffconfig.war_quick_attack_action === "new_tab";
    if (shouldOpenInNewTab) {
      window.open(url, "_blank");
    } else {
      window.location.href = url;
    }
  }
  const SORT_ICON_CLASS_SETS = {
    sortIcon___wbOOi: {
      sortIcon: "sortIcon___wbOOi",
      activeIcon: "activeIcon___wmLLe",
      desc: "desc___wkA0A",
      asc: "asc___y_atw"
    },
    sortIcon___LNQ9D: {
      sortIcon: "sortIcon___LNQ9D",
      activeIcon: "activeIcon___SwNJj",
      desc: "desc___ZvHWf",
      asc: "asc___YAXFZ"
    },
    sortIcon___pMyNX: {
      sortIcon: "sortIcon___pMyNX",
      activeIcon: "activeIcon___dw8TK",
      desc: "desc___TLpPc",
      asc: "asc___Q3bz5"
    }
  };
  function detect_sort_icon_classes(root) {
    const existing = root.querySelector(
      "[class*='sortIcon___']:not(.ffscouter-sort-icon)"
    );
    if (!existing) return null;
    const sortIcon = Array.from(existing.classList).find((c) => c.startsWith("sortIcon___")) ?? "";
    const classes = SORT_ICON_CLASS_SETS[sortIcon];
    if (!classes) return null;
    const tab = Array.from(existing.parentElement?.classList ?? []).find(
      (c) => c.startsWith("tab___")
    ) ?? "";
    return { ...classes, tab };
  }
  let _reactDOM;
  function getReactDOM() {
    return _reactDOM ??= unsafeWindow.ReactDOM;
  }
  new Proxy({}, {
    get(_, prop) {
      return getReactDOM()[prop];
    }
  });
  const createRoot = ((...args) => getReactDOM().createRoot(...args));
  function mountComponent(element, parent) {
    const container = document.createElement("div");
    container.style.display = "contents";
    parent.appendChild(container);
    const root = createRoot(container);
    root.render(element);
    return root;
  }
  const log$c = logger.child("feature:attack");
  async function inject_info_line$1(info_line) {
    const h4 = await wait_for_element("h4", 1e4);
    if (!h4) {
      return;
    }
    h4.parentNode?.parentNode?.parentNode?.insertBefore(
      info_line,
      h4.parentNode?.parentNode?.nextSibling
    );
  }
  const index$d = {
    name: "Attack FF display",
    description: "Shows FF on top left of any attack page",
    executionTime: StartTime.DocumentBody,
    async shouldRun() {
      return torn_page("page", { sid: "attack" });
    },
    async run() {
      const player_id = extract_id_from_url(window.location.href);
      if (!player_id) {
        return;
      }
      log$c.debug("On the attack page, found player_id", player_id);
      const info_line = create_info_line();
      mountComponent(
        createElement(FFHeaderLine, { playerId: player_id }),
        info_line
      );
      inject_info_line$1(info_line);
    },
    httpIntercept: {
      before(_url, _init) {
        return void 0;
      },
      after(_bodyText, _response, _ctx) {
        return void 0;
      }
    }
  };
  const __vite_glob_0_0 = Object.freeze( Object.defineProperty({
    __proto__: null,
    default: index$d
  }, Symbol.toStringTag, { value: "Module" }));
  const _deprecatedStub = null;
  const __vite_glob_0_1 = Object.freeze( Object.defineProperty({
    __proto__: null,
    default: _deprecatedStub
  }, Symbol.toStringTag, { value: "Module" }));
  const styles$1 = {
    "ff-filter-box": "_ff-filter-box_ursux_1",
    "ff-filter-box--no-borders": "_ff-filter-box--no-borders_ursux_19",
    "ff-filter-box__header": "_ff-filter-box__header_ursux_52",
    "ff-filter-box__header-actions": "_ff-filter-box__header-actions_ursux_60",
    "ff-filter-box__action-btn": "_ff-filter-box__action-btn_ursux_66",
    "ff-filter-box__action-btn--active": "_ff-filter-box__action-btn--active_ursux_88",
    "ff-filter-box__action-btn--inactive": "_ff-filter-box__action-btn--inactive_ursux_93",
    "ff-filter-box__action-btn--reset": "_ff-filter-box__action-btn--reset_ursux_104",
    "ff-filter-box__grid": "_ff-filter-box__grid_ursux_114",
    "ff-filter-box__group--sort": "_ff-filter-box__group--sort_ursux_121",
    "ff-filter-box__group--level": "_ff-filter-box__group--level_ursux_125",
    "ff-filter-box__group--activity": "_ff-filter-box__group--activity_ursux_129",
    "ff-filter-box__group--status": "_ff-filter-box__group--status_ursux_133",
    "ff-filter-box__group--ff": "_ff-filter-box__group--ff_ursux_137",
    "ff-filter-box__group--stats": "_ff-filter-box__group--stats_ursux_141",
    "ff-filter-box__group--last-action": "_ff-filter-box__group--last-action_ursux_145",
    "ff-filter-box__group--columns": "_ff-filter-box__group--columns_ursux_149",
    "ff-filter-box__group": "_ff-filter-box__group_ursux_121",
    "ff-filter-box__sort-controls": "_ff-filter-box__sort-controls_ursux_171",
    "ff-filter-box__sort-btn": "_ff-filter-box__sort-btn_ursux_177",
    "ff-filter-box__compare-btn": "_ff-filter-box__compare-btn_ursux_181",
    "ff-filter-box__display-select": "_ff-filter-box__display-select_ursux_186",
    "ff-filter-box__options": "_ff-filter-box__options_ursux_197",
    "ff-filter-box__range-inputs": "_ff-filter-box__range-inputs_ursux_210"
  };
  const cls$1 = {
    box: styles$1["ff-filter-box"],
    boxNoBorders: styles$1["ff-filter-box--no-borders"],
    header: styles$1["ff-filter-box__header"],
    headerActions: styles$1["ff-filter-box__header-actions"],
    actionBtn: styles$1["ff-filter-box__action-btn"],
    actionBtnActive: styles$1["ff-filter-box__action-btn--active"],
    actionBtnInactive: styles$1["ff-filter-box__action-btn--inactive"],
    actionBtnReset: styles$1["ff-filter-box__action-btn--reset"],
    grid: styles$1["ff-filter-box__grid"],
    group: styles$1["ff-filter-box__group"],
    groupSort: styles$1["ff-filter-box__group--sort"],
    groupLevel: styles$1["ff-filter-box__group--level"],
    groupActivity: styles$1["ff-filter-box__group--activity"],
    groupStatus: styles$1["ff-filter-box__group--status"],
    groupFf: styles$1["ff-filter-box__group--ff"],
    groupStats: styles$1["ff-filter-box__group--stats"],
    groupLastAction: styles$1["ff-filter-box__group--last-action"],
    groupColumns: styles$1["ff-filter-box__group--columns"],
    sortControls: styles$1["ff-filter-box__sort-controls"],
    sortBtn: styles$1["ff-filter-box__sort-btn"],
    compareBtn: styles$1["ff-filter-box__compare-btn"],
    displaySelect: styles$1["ff-filter-box__display-select"],
    options: styles$1["ff-filter-box__options"],
    rangeInputs: styles$1["ff-filter-box__range-inputs"]
  };
  const DEFAULT_HIDDEN_COLUMNS = { level: false, status: false, score: false };
  const DEFAULT_STATE = {
    sortBy: "none",
    filterEnabled: true,
    activity: { online: true, idle: true, offline: true },
    status: {
      okay: true,
      traveling: true,
      hospital: true,
      jail: true,
      abroad: true,
      federal: true,
      fallen: true
    },
    levelMin: null,
    levelMax: null,
    ffMin: null,
    ffMax: null,
    statsMin: null,
    statsMax: null,
    lastActionMin: null,
    lastActionMax: null,
    hiddenColumns: DEFAULT_HIDDEN_COLUMNS
  };
  function getFilterBoxHandle(el) {
    if (!el) return null;
    return el.__ffHandle ?? null;
  }
  function isMobileView() {
    return typeof window !== "undefined" && window.innerWidth < 784;
  }
  function FFFactionFilterBox({
    mode,
    onFilterChange,
    ref,
    initialHasLastActionData = false,
    onReady
  }) {
    const [filterState, setFilterState] = useState(
      () => DEFAULT_STATE
    );
    const [collapsed, setCollapsed] = useState(
      () => mode === "war" ? ffconfig.war_filter_collapsed : ffconfig.faction_filter_collapsed
    );
    const [colDisplay, setColDisplay] = useState(
      FactionsColDisplay.FAIR_FIGHT
    );
    const [hasLastActionData, setHasLastActionData] = useState(
      initialHasLastActionData
    );
    const filterStateRef = useRef(filterState);
    filterStateRef.current = filterState;
    const collapsedRef = useRef(collapsed);
    collapsedRef.current = collapsed;
    const hasLastActionDataRef = useRef(hasLastActionData);
    hasLastActionDataRef.current = hasLastActionData;
    const modeRef = useRef(mode);
    modeRef.current = mode;
    const onFilterChangeRef = useRef(onFilterChange);
    onFilterChangeRef.current = onFilterChange;
    const debounceTimerRef = useRef(null);
    const wasMobileRef = useRef(isMobileView());
    const rootRef = useRef(null);
    const applyStatePatch = (patch) => {
      const next = { ...filterStateRef.current, ...patch };
      filterStateRef.current = next;
      setFilterState(next);
    };
    const getFilterSnapshot = () => {
      const s2 = filterStateRef.current;
      return {
        sortBy: s2.sortBy,
        filterEnabled: s2.filterEnabled,
        activity: s2.activity,



status: modeRef.current === "war" ? { ...s2.status, fallen: false } : s2.status,
        levelMin: s2.levelMin,
        levelMax: s2.levelMax,
        ffMin: s2.ffMin,
        ffMax: s2.ffMax,
        statsMin: s2.statsMin ? parse_suffix_number(s2.statsMin) : null,
        statsMax: s2.statsMax ? parse_suffix_number(s2.statsMax) : null,
        lastActionMinSec: s2.lastActionMin ? parse_duration_to_seconds(s2.lastActionMin) : null,
        lastActionMaxSec: s2.lastActionMax ? parse_duration_to_seconds(s2.lastActionMax) : null
      };
    };
    const dispatchChange = () => {
      onFilterChangeRef.current(getFilterSnapshot());
    };
    const saveState = (state) => {
      const isWar = modeRef.current === "war";
      const isMobile = isMobileView();
      const existing = isWar ? ffconfig.war_filter_state : ffconfig.faction_filter_state;
      const savedHiddenColumns = existing?.hiddenColumns;
      const savedHiddenColumnsMobile = existing?.hiddenColumnsMobile;
      const hiddenColumnsToSave = isMobile ? savedHiddenColumns ?? DEFAULT_HIDDEN_COLUMNS : state.hiddenColumns ?? DEFAULT_HIDDEN_COLUMNS;
      const hiddenColumnsMobileToSave = isMobile ? state.hiddenColumns ?? DEFAULT_HIDDEN_COLUMNS : savedHiddenColumnsMobile ?? {
        level: true,
        status: false,
        score: false
      };
      const toSave = {
        ...state,
        hiddenColumns: hiddenColumnsToSave,
        hiddenColumnsMobile: hiddenColumnsMobileToSave
      };
      if (isWar) {
        ffconfig.war_filter_state = toSave;
      } else {
        ffconfig.faction_filter_state = toSave;
      }
    };
    const executeChangeImmediately = () => {
      if (debounceTimerRef.current) {
        clearTimeout(debounceTimerRef.current);
        debounceTimerRef.current = null;
      }
      saveState(filterStateRef.current);
      dispatchChange();
    };
    const queueChange = () => {
      if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
      debounceTimerRef.current = setTimeout(() => {
        saveState(filterStateRef.current);
        dispatchChange();
        debounceTimerRef.current = null;
      }, 250);
    };
    const loadState = () => {
      const isWar = modeRef.current === "war";
      const isMobile = isMobileView();
      const newCollapsed = isWar ? ffconfig.war_filter_collapsed : ffconfig.faction_filter_collapsed;
      setCollapsed(newCollapsed);
      setColDisplay(
        isWar ? ffconfig.war_col_display : ffconfig.factions_col_display
      );
      const parsed = isWar ? ffconfig.war_filter_state : ffconfig.faction_filter_state;
      let next;
      if (parsed) {
        const savedSortBy = parsed.sortBy ?? "none";
        const hiddenColumns2 = isMobile ? {
          level: parsed.hiddenColumnsMobile?.level ?? true,
          status: parsed.hiddenColumnsMobile?.status ?? DEFAULT_HIDDEN_COLUMNS.status,
          score: parsed.hiddenColumnsMobile?.score ?? DEFAULT_HIDDEN_COLUMNS.score
        } : {
          level: parsed.hiddenColumns?.level ?? DEFAULT_HIDDEN_COLUMNS.level,
          status: parsed.hiddenColumns?.status ?? DEFAULT_HIDDEN_COLUMNS.status,
          score: parsed.hiddenColumns?.score ?? DEFAULT_HIDDEN_COLUMNS.score
        };
        next = {
          sortBy: savedSortBy === "ff-asc" || savedSortBy === "ff-desc" ? savedSortBy : "none",
          filterEnabled: parsed.filterEnabled ?? true,
          activity: { ...DEFAULT_STATE.activity, ...parsed.activity },
          status: { ...DEFAULT_STATE.status, ...parsed.status },
          levelMin: parsed.levelMin ?? null,
          levelMax: parsed.levelMax ?? null,
          ffMin: parsed.ffMin ?? null,
          ffMax: parsed.ffMax ?? null,
          statsMin: parsed.statsMin ?? null,
          statsMax: parsed.statsMax ?? null,
          lastActionMin: parsed.lastActionMin ?? null,
          lastActionMax: parsed.lastActionMax ?? null,
          hiddenColumns: hiddenColumns2
        };
      } else {
        next = {
          ...DEFAULT_STATE,
          hiddenColumns: {
            level: isMobile,
            status: DEFAULT_HIDDEN_COLUMNS.status,
            score: DEFAULT_HIDDEN_COLUMNS.score
          }
        };
      }
      filterStateRef.current = next;
      setFilterState(next);
      onFilterChangeRef.current(getFilterSnapshot());
    };
    useEffect(() => {
      loadState();
      const onConfigUpdated = () => {
        setColDisplay(
          modeRef.current === "war" ? ffconfig.war_col_display : ffconfig.factions_col_display
        );
      };
      const onResize = () => {
        const isMobile = isMobileView();
        if (isMobile !== wasMobileRef.current) {
          wasMobileRef.current = isMobile;
          loadState();
        }
      };
      window.addEventListener("ff-config-updated", onConfigUpdated);
      window.addEventListener("resize", onResize);
      onReady?.();
      return () => {
        if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
        window.removeEventListener("ff-config-updated", onConfigUpdated);
        window.removeEventListener("resize", onResize);
      };
    }, []);
    useEffect(() => {
      if (mode !== "war") return;
      const target = rootRef.current?.closest(".faction-war");
      if (!(target instanceof HTMLElement)) return;
      const cols = filterState.hiddenColumns ?? DEFAULT_HIDDEN_COLUMNS;
      for (const [col, isHidden] of Object.entries(cols)) {
        if (isHidden) {
          target.setAttribute(`data-ffscouter-hide-${col}`, "true");
        } else {
          target.removeAttribute(`data-ffscouter-hide-${col}`);
        }
      }
    }, [filterState.hiddenColumns, mode]);
    useImperativeHandle(
      ref,
      () => ({
        setSortBy(val) {
          applyStatePatch({ sortBy: val });
          executeChangeImmediately();
        },
        getFilterSnapshot,
        get sortBy() {
          return filterStateRef.current.sortBy;
        },
        get activity() {
          return filterStateRef.current.activity;
        },
        get hasLastActionData() {
          return hasLastActionDataRef.current;
        },
        setHasLastActionData(val) {
          hasLastActionDataRef.current = val;
          setHasLastActionData(val);
        },
        setFilterState(patch) {
          applyStatePatch(patch);
          dispatchChange();
        },
        dispatchChange,
        get ready() {
          return true;
        }
      }),
      []
    );
    const onToggle = (e) => {
      const newCollapsed = !e.currentTarget.open;
      if (newCollapsed === collapsedRef.current) return;
      setCollapsed(newCollapsed);
      if (mode === "war") {
        ffconfig.war_filter_collapsed = newCollapsed;
      } else {
        ffconfig.faction_filter_collapsed = newCollapsed;
      }
    };
    const onSortToggle = () => {
      const next = filterStateRef.current.sortBy === "none" ? "ff-desc" : filterStateRef.current.sortBy === "ff-desc" ? "ff-asc" : "none";
      applyStatePatch({ sortBy: next });
      executeChangeImmediately();
    };
    const onDisplayChange = (e) => {
      const val = e.target.value;
      setColDisplay(val);
      if (mode === "war") {
        ffconfig.war_col_display = val;
      } else {
        ffconfig.factions_col_display = val;
      }
      window.dispatchEvent(new CustomEvent("ff-config-updated"));
      executeChangeImmediately();
    };
    const onToggleFilter = (e) => {
      e.preventDefault();
      e.stopPropagation();
      applyStatePatch({ filterEnabled: !filterStateRef.current.filterEnabled });
      executeChangeImmediately();
    };
    const onResetFilters = (e) => {
      e.preventDefault();
      e.stopPropagation();
      applyStatePatch({
        activity: { online: true, idle: true, offline: true },
        status: {
          okay: true,
          traveling: true,
          hospital: true,
          jail: true,
          abroad: true,
          federal: true,
          fallen: true
        },
        levelMin: null,
        levelMax: null,
        ffMin: null,
        ffMax: null,
        statsMin: null,
        statsMax: null,
        lastActionMin: null,
        lastActionMax: null
      });
      executeChangeImmediately();
    };
    const onActivityChange = (key, val) => {
      applyStatePatch({
        activity: { ...filterStateRef.current.activity, [key]: val }
      });
      executeChangeImmediately();
    };
    const onStatusChange = (key, val) => {
      applyStatePatch({
        status: { ...filterStateRef.current.status, [key]: val }
      });
      executeChangeImmediately();
    };
    const onLevelChange = (type, valStr) => {
      const val = valStr === "" ? null : Number.parseInt(valStr, 10);
      applyStatePatch(type === "min" ? { levelMin: val } : { levelMax: val });
      queueChange();
    };
    const onFFChange = (type, valStr) => {
      const val = valStr === "" ? null : Number.parseFloat(valStr);
      applyStatePatch(type === "min" ? { ffMin: val } : { ffMax: val });
      queueChange();
    };
    const onStatsChange = (type, valStr) => {
      const val = valStr.trim() === "" ? null : valStr;
      applyStatePatch(type === "min" ? { statsMin: val } : { statsMax: val });
      queueChange();
    };
    const onLastActionChange = (type, valStr) => {
      const val = valStr.trim() === "" ? null : valStr;
      applyStatePatch(
        type === "min" ? { lastActionMin: val } : { lastActionMax: val }
      );
      queueChange();
    };
    const onColumnVisibilityChange = (key, val) => {
      applyStatePatch({
        hiddenColumns: {
          ...filterStateRef.current.hiddenColumns ?? DEFAULT_HIDDEN_COLUMNS,
          [key]: val
        }
      });
      executeChangeImmediately();
    };
    const onCompareActivity = () => {
      const container = rootRef.current?.closest(".faction-war");
      const links = container ? Array.from(
        container.querySelectorAll('a[href*="step=profile"]')
      ) : [];
      const seen = new Set();
      const ids = [];
      const tryExtract = (link) => {
        try {
          const url = new URL(link.href, window.location.origin);
          const id = url.searchParams.get("ID");
          if (id && !seen.has(id)) {
            seen.add(id);
            ids.push(id);
          }
        } catch {
          const match = (link.getAttribute("href") || "").match(/[?&]ID=(\d+)/i);
          if (match?.[1]) {
            const id = match[1];
            if (!seen.has(id)) {
              seen.add(id);
              ids.push(id);
            }
          }
        }
      };
      for (const link of links) tryExtract(link);
      if (ids.length < 2) {
        const docLinks = Array.from(
          document.querySelectorAll('a[href*="step=profile"]')
        );
        for (const link of docLinks) tryExtract(link);
      }
      if (ids.length < 2) {
        console.warn("Could not find faction IDs to compare activity.");
        return;
      }
      const now = new Date();
      const start = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1e3);
      const formatUTC = (d2) => {
        const y2 = d2.getUTCFullYear();
        const m2 = String(d2.getUTCMonth() + 1).padStart(2, "0");
        const day = String(d2.getUTCDate()).padStart(2, "0");
        const h2 = String(d2.getUTCHours()).padStart(2, "0");
        const min = String(d2.getUTCMinutes()).padStart(2, "0");
        return `${y2}-${m2}-${day}T${h2}:${min}`;
      };
      const factionId1 = ids[0];
      const factionId2 = ids[1];
      const scouterUrl = `https://ffscouter.com/faction-activity-comparison?faction_id_1=${factionId1}&faction_id_2=${factionId2}&start_at=${encodeURIComponent(formatUTC(start))}&end_at=${encodeURIComponent(formatUTC(now))}&bucket_minutes=5`;
      window.open(scouterUrl, "_blank");
    };
    const s = filterState;
    const hiddenColumns = s.hiddenColumns ?? DEFAULT_HIDDEN_COLUMNS;
    const isEst = colDisplay === FactionsColDisplay.BATTLE_STATS;
    const sortText = isEst ? "Stats" : "FF";
    return jsxs(
      "details",
      {
        ref: rootRef,
        className: `${cls$1.box}${mode === "war" ? ` ${cls$1.boxNoBorders}` : ""}`,
        open: !collapsed,
        onToggle,
        children: [
jsx("summary", { children: jsxs("div", { className: cls$1.header, children: [
jsx("span", { children: "FFScouter Filter & Sort Controls" }),
jsxs(
              "div",
              {
                className: cls$1.headerActions,
                onClick: (e) => {
                  e.preventDefault();
                  e.stopPropagation();
                },
                children: [
jsx(
                    "button",
                    {
                      type: "button",
                      className: `${cls$1.actionBtn} ${s.filterEnabled ? cls$1.actionBtnActive : cls$1.actionBtnInactive}`,
                      title: s.filterEnabled ? "Turn off filtering" : "Turn on filtering",
                      onClick: onToggleFilter,
                      children: s.filterEnabled ? jsx("svg", { viewBox: "0 0 16 16", "aria-hidden": "true", children: jsx("path", { d: "M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.124.318l-4.5 5.5v4.682a.5.5 0 0 1-.168.373l-2.5 2a.5.5 0 0 1-.832-.373v-6.682l-4.5-5.5A.5.5 0 0 1 1.5 3.5v-2z" }) }) : jsxs("svg", { viewBox: "0 0 16 16", "aria-hidden": "true", children: [
jsx("path", { d: "M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.124.318l-4.5 5.5v4.682a.5.5 0 0 1-.168.373l-2.5 2a.5.5 0 0 1-.832-.373v-6.682l-4.5-5.5A.5.5 0 0 1 1.5 3.5v-2z" }),
jsx(
                          "line",
                          {
                            x1: "1.5",
                            y1: "14.5",
                            x2: "14.5",
                            y2: "1.5",
                            stroke: "currentColor",
                            strokeWidth: "1.5"
                          }
                        )
                      ] })
                    }
                  ),
jsx(
                    "button",
                    {
                      type: "button",
                      className: `${cls$1.actionBtn} ${cls$1.actionBtnReset}`,
                      title: "Reset filters to default",
                      onClick: onResetFilters,
                      children: jsxs("svg", { viewBox: "0 0 16 16", "aria-hidden": "true", children: [
jsx(
                          "path",
                          {
                            fillRule: "evenodd",
                            d: "M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"
                          }
                        ),
jsx("path", { d: "M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z" })
                      ] })
                    }
                  )
                ]
              }
            )
          ] }) }),
jsxs("div", { className: cls$1.grid, children: [
jsxs("div", { className: `${cls$1.group} ${cls$1.groupSort}`, children: [
jsx("strong", { children: "Sort & Display" }),
jsxs("div", { className: cls$1.sortControls, children: [
jsx(
                  "button",
                  {
                    type: "button",
                    id: "sort-toggle-btn",
                    className: cls$1.sortBtn,
                    onClick: onSortToggle,
                    children: s.sortBy === "none" ? "Sort: Default" : s.sortBy === "ff-asc" ? `Sort: ${sortText} ▲` : `Sort: ${sortText} ▼`
                  }
                ),
jsxs(
                  "select",
                  {
                    id: mode === "war" ? "war-col-display-filter" : "factions-col-display-filter",
                    value: colDisplay,
                    onChange: onDisplayChange,
                    className: cls$1.displaySelect,
                    children: [
jsx("option", { value: "fair_fight", children: "Show: FF Score" }),
jsx("option", { value: "battle_stats", children: "Show: BS Estimate" }),
jsx("option", { value: "none", children: "Show: None (Hide)" })
                    ]
                  }
                ),
                mode === "war" && jsx(
                  "button",
                  {
                    type: "button",
                    id: "compare-faction-activity-btn",
                    className: cls$1.compareBtn,
                    onClick: onCompareActivity,
                    children: "Compare Activity"
                  }
                )
              ] })
            ] }),
jsxs("div", { className: `${cls$1.group} ${cls$1.groupActivity}`, children: [
jsx("strong", { children: "Activity" }),
jsx("div", { className: cls$1.options, children: [
                ["online", "Online"],
                ["idle", "Idle"],
                ["offline", "Offline"]
              ].map(([key, label]) => jsxs("label", { children: [
jsx(
                  "input",
                  {
                    type: "checkbox",
                    checked: s.activity[key],
                    onChange: (e) => onActivityChange(key, e.target.checked)
                  }
                ),
                label
              ] }, key)) })
            ] }),
jsxs("div", { className: `${cls$1.group} ${cls$1.groupStatus}`, children: [
jsx("strong", { children: "Status" }),
jsx("div", { className: cls$1.options, children: [
                ["okay", "Okay"],
                ["hospital", "Hospital"],
                ["jail", "Jail"],
                ["abroad", "Abroad"],
                ["traveling", "Traveling"],
                ["federal", "Fedded"],
                ["fallen", "Fallen"]
              ].filter(([key]) => mode !== "war" || key !== "fallen").map(([key, label]) => jsxs("label", { children: [
jsx(
                  "input",
                  {
                    type: "checkbox",
                    checked: s.status[key],
                    onChange: (e) => onStatusChange(key, e.target.checked)
                  }
                ),
                label
              ] }, key)) })
            ] }),
jsxs("div", { className: `${cls$1.group} ${cls$1.groupLevel}`, children: [
jsx("strong", { children: "Level Range" }),
jsxs("div", { className: cls$1.rangeInputs, children: [
jsx(
                  "input",
                  {
                    type: "number",
                    placeholder: "Min",
                    value: s.levelMin !== null ? String(s.levelMin) : "",
                    onChange: (e) => onLevelChange("min", e.target.value)
                  }
                ),
jsx("span", { children: "to" }),
jsx(
                  "input",
                  {
                    type: "number",
                    placeholder: "Max",
                    value: s.levelMax !== null ? String(s.levelMax) : "",
                    onChange: (e) => onLevelChange("max", e.target.value)
                  }
                )
              ] })
            ] }),
jsxs("div", { className: `${cls$1.group} ${cls$1.groupFf}`, children: [
jsx("strong", { children: "FF Range" }),
jsxs("div", { className: cls$1.rangeInputs, children: [
jsx(
                  "input",
                  {
                    type: "number",
                    step: "0.1",
                    placeholder: "Min",
                    value: s.ffMin !== null ? String(s.ffMin) : "",
                    onChange: (e) => onFFChange("min", e.target.value)
                  }
                ),
jsx("span", { children: "to" }),
jsx(
                  "input",
                  {
                    type: "number",
                    step: "0.1",
                    placeholder: "Max",
                    value: s.ffMax !== null ? String(s.ffMax) : "",
                    onChange: (e) => onFFChange("max", e.target.value)
                  }
                )
              ] })
            ] }),
jsxs("div", { className: `${cls$1.group} ${cls$1.groupStats}`, children: [
jsx("strong", { children: "Stats Range" }),
jsxs("div", { className: cls$1.rangeInputs, children: [
jsx(
                  "input",
                  {
                    type: "text",
                    placeholder: "Min",
                    value: s.statsMin !== null ? s.statsMin : "",
                    onChange: (e) => onStatsChange("min", e.target.value)
                  }
                ),
jsx("span", { children: "to" }),
jsx(
                  "input",
                  {
                    type: "text",
                    placeholder: "Max",
                    value: s.statsMax !== null ? s.statsMax : "",
                    onChange: (e) => onStatsChange("max", e.target.value)
                  }
                )
              ] })
            ] }),
            mode === "war" && hasLastActionData && jsxs("div", { className: `${cls$1.group} ${cls$1.groupLastAction}`, children: [
jsx("strong", { children: "Last Action Range" }),
jsxs("div", { className: cls$1.rangeInputs, children: [
jsx(
                  "input",
                  {
                    type: "text",
                    placeholder: "Min",
                    title: 'e.g. "10m", "1h", "4h2m15s"',
                    value: s.lastActionMin !== null ? s.lastActionMin : "",
                    onChange: (e) => onLastActionChange("min", e.target.value)
                  }
                ),
jsx("span", { children: "to" }),
jsx(
                  "input",
                  {
                    type: "text",
                    placeholder: "Max",
                    title: 'e.g. "10m", "1h", "4h2m15s"',
                    value: s.lastActionMax !== null ? s.lastActionMax : "",
                    onChange: (e) => onLastActionChange("max", e.target.value)
                  }
                )
              ] })
            ] }),
            mode === "war" && jsxs("div", { className: `${cls$1.group} ${cls$1.groupColumns}`, children: [
jsx("strong", { children: "Visible Columns" }),
jsx("div", { className: cls$1.options, children: [
                ["level", "Level"],
                ["status", "Status"],
                ["score", "Score"]
              ].map(([key, label]) => jsxs("label", { children: [
jsx(
                  "input",
                  {
                    type: "checkbox",
                    checked: !hiddenColumns[key],
                    onChange: (e) => onColumnVisibilityChange(key, !e.target.checked)
                  }
                ),
                label
              ] }, key)) })
            ] })
          ] })
        ]
      }
    );
  }
  function get_current_time_seconds() {
    if (typeof window.getCurrentTimestamp === "function") {
      return window.getCurrentTimestamp() / 1e3;
    }
    return Date.now() / 1e3;
  }
  const isApplying = new WeakMap();
  function is_applying(list) {
    return isApplying.get(list) ?? false;
  }
  function apply_filters_and_sort(membersList, filters) {
    if (isApplying.get(membersList)) return;
    isApplying.set(membersList, true);
    try {
      const tbody = membersList.querySelector(".table-body") || membersList.querySelector(".members-list") || membersList;
      const rows = Array.from(
        tbody.querySelectorAll(":scope > .table-row, .enemy, .your")
      );
      for (const row of rows) {
        if (filters.filterEnabled === false) {
          show_row(row);
          continue;
        }
        const activity = get_activity_status(row);
        const allActivityUnchecked = !filters.activity.online && !filters.activity.idle && !filters.activity.offline;
        const matchesActivity = allActivityUnchecked || activity === "unknown" || activity === "online" && filters.activity.online || activity === "idle" && filters.activity.idle || activity === "offline" && filters.activity.offline;
        if (!matchesActivity) {
          hide_row(row);
          continue;
        }
        let status = "okay";
        const statusCell = row.querySelector(".status");
        if (statusCell) {
          if (statusCell.classList.contains("traveling") || statusCell.querySelector(".traveling")) {
            status = "traveling";
          } else if (statusCell.classList.contains("hospital") || statusCell.querySelector(".hospital")) {
            status = "hospital";
          } else if (statusCell.classList.contains("jail") || statusCell.querySelector(".jail")) {
            status = "jail";
          } else if (statusCell.classList.contains("abroad") || statusCell.querySelector(".abroad")) {
            status = "abroad";
          } else if (statusCell.classList.contains("federal") || statusCell.querySelector(".federal")) {
            status = "federal";
          } else if (statusCell.classList.contains("fallen") || statusCell.querySelector(".fallen")) {
            status = "fallen";
          } else {
            status = "okay";
          }
        }
        const allStatusUnchecked = !filters.status.okay && !filters.status.traveling && !filters.status.hospital && !filters.status.jail && !filters.status.abroad && !filters.status.federal && !filters.status.fallen;
        const matchesStatus = allStatusUnchecked || status === "okay" && filters.status.okay || status === "traveling" && filters.status.traveling || status === "hospital" && filters.status.hospital || status === "jail" && filters.status.jail || status === "abroad" && filters.status.abroad || status === "federal" && filters.status.federal || status === "fallen" && filters.status.fallen;
        if (!matchesStatus) {
          hide_row(row);
          continue;
        }
        const levelCell = row.querySelector(".lvl:not(.ffscouter-cell), .level");
        const level = levelCell ? Number.parseInt(levelCell.textContent || "0", 10) : 0;
        const matchesLevel = (filters.levelMin === null || level >= filters.levelMin) && (filters.levelMax === null || level <= filters.levelMax);
        if (!matchesLevel) {
          hide_row(row);
          continue;
        }
        const ffVal = row.dataset["ffValue"] ? (
Number.parseFloat(row.dataset["ffValue"])
        ) : null;
        const matchesFF = ffVal === null || (filters.ffMin === null || ffVal >= filters.ffMin) && (filters.ffMax === null || ffVal <= filters.ffMax);
        if (!matchesFF) {
          hide_row(row);
          continue;
        }
        const estVal = row.dataset["estValue"] ? (
Number.parseInt(row.dataset["estValue"], 10)
        ) : null;
        const matchesStats = estVal === null || (filters.statsMin === void 0 || filters.statsMin === null || estVal >= filters.statsMin) && (filters.statsMax === void 0 || filters.statsMax === null || estVal <= filters.statsMax);
        if (!matchesStats) {
          hide_row(row);
          continue;
        }
        const lastActionRaw = row.dataset["twseLastActionTimestamp"];
        const lastActionTs = lastActionRaw ? Number.parseInt(lastActionRaw, 10) : null;
        const hasLastActionData = lastActionTs !== null && !Number.isNaN(lastActionTs) && lastActionTs !== 0;
        const lastActionAge = hasLastActionData ? get_current_time_seconds() - lastActionTs : null;
        const matchesLastAction = lastActionAge === null || (filters.lastActionMinSec === void 0 || filters.lastActionMinSec === null || lastActionAge >= filters.lastActionMinSec) && (filters.lastActionMaxSec === void 0 || filters.lastActionMaxSec === null || lastActionAge <= filters.lastActionMaxSec);
        if (!matchesLastAction) {
          hide_row(row);
          continue;
        }
        if (matchesActivity && matchesStatus && matchesLevel && matchesFF && matchesStats && matchesLastAction) {
          show_row(row);
        } else {
          hide_row(row);
        }
      }
      if (filters.sortBy !== "none") {
        const isEst = filters.colDisplay === FactionsColDisplay.BATTLE_STATS;
        const valKey = isEst ? "estValue" : "ffValue";
        rows.sort((a, b) => {
          const getVal = (row) => {
            const dataVal = row.dataset[valKey];
            return dataVal ? Number.parseFloat(dataVal) : -1;
          };
          const valA = getVal(a);
          const valB = getVal(b);
          if (filters.sortBy.endsWith("asc")) {
            return valA - valB;
          }
          return valB - valA;
        });
        for (const row of rows) {
          const extra_tt_rows = [];
          let next_sibling = row.nextElementSibling;
          while (next_sibling && !next_sibling?.classList.contains("table-row") && !next_sibling?.classList.contains("enemy") && !next_sibling?.classList.contains("your")) {
            if (next_sibling.classList.contains("tt-last-action") || next_sibling.classList.contains("tt-stats-estimate")) {
              extra_tt_rows.push(next_sibling);
            }
            next_sibling = next_sibling.nextElementSibling;
          }
          tbody.appendChild(row);
          for (const r of extra_tt_rows) {
            tbody.appendChild(r);
          }
        }
      }
      if (is_filter_active(filters)) {
        tbody.setAttribute("data-ffscouter-active-filter", "true");
      } else {
        tbody.removeAttribute("data-ffscouter-active-filter");
      }
    } finally {
      isApplying.set(membersList, false);
    }
  }
  function update_header_sort_indicator(list, sortBy) {
    const header = list.querySelector(".ffscouter-header");
    if (!header) return;
    if (sortBy === "none") {
      header.removeAttribute("data-ffscouter-sort");
      header.querySelector(".ffscouter-sort-icon")?.remove();
      return;
    }
    header.setAttribute(
      "data-ffscouter-sort",
      sortBy === "ff-asc" ? "asc" : "desc"
    );
    const classes = detect_sort_icon_classes(list);
    if (!classes) return;
    if (classes.tab) header.classList.add(classes.tab);
    let icon = header.querySelector(".ffscouter-sort-icon");
    if (!icon) {
      icon = document.createElement("div");
      icon.classList.add("ffscouter-sort-icon", classes.sortIcon);
      if (classes.activeIcon) icon.classList.add(classes.activeIcon);
      header.appendChild(icon);
    }
    icon.classList.remove(classes.asc, classes.desc);
    icon.classList.add(sortBy === "ff-asc" ? classes.asc : classes.desc);
  }
  function is_tt_extra_row(el) {
    return el.classList.contains("tt-last-action") || el.classList.contains("tt-stats-estimate");
  }
  function toggle_tt_siblings(row, hidden) {
    let next = row.nextElementSibling;
    while (next && !next.classList.contains("table-row") && !next.classList.contains("enemy") && !next.classList.contains("your")) {
      if (is_tt_extra_row(next)) {
        if (hidden) {
          next.setAttribute("data-ffscouter-hidden", "");
        } else {
          next.removeAttribute("data-ffscouter-hidden");
        }
      }
      next = next.nextElementSibling;
    }
  }
  function hide_row(row) {
    row.setAttribute("data-ffscouter-hidden", "");
    toggle_tt_siblings(row, true);
  }
  function show_row(row) {
    row.removeAttribute("data-ffscouter-hidden");
    toggle_tt_siblings(row, false);
  }
  function is_filter_active(filters) {
    if (filters.sortBy !== "none") return true;
    return false;
  }
  const log$b = logger.child("feature:faction");
  async function poll_traveling_flights(membersList) {
    const rows = Array.from(
      membersList.querySelectorAll(".enemy, .your")
    );
    const travelingPlayers = rows.map((row) => {
      const player_id = get_player_id_in_element(row);
      if (!player_id) return null;
      const statusCell = row.querySelector(".status");
      const statusText = statusCell?.textContent?.trim() || "";
      const isTraveling = statusText === "Traveling";
      return { row, player_id, isTraveling };
    }).filter((item) => item !== null);
    for (const p of travelingPlayers) {
      if (!p.isTraveling) {
        p.row.removeAttribute("data-earliest-arrival");
        p.row.removeAttribute("data-latest-arrival");
        ffscouter.clear_flight_cache(p.player_id);
      }
    }
    const traveling = travelingPlayers.filter((p) => p.isTraveling);
    if (traveling.length === 0) return;
    const isPremium = await check_key_status.is_premium();
    if (!isPremium) {
      for (const p of traveling) {
        p.row.removeAttribute("data-earliest-arrival");
        p.row.removeAttribute("data-latest-arrival");
      }
      return;
    }
    await Promise.all(
      traveling.map(async (p) => {
        try {
          const flights = await ffscouter.get_flights(p.player_id);
          const current = flights?.current;
          if (current) {
            const earliest = current.earliest_arrival_time;
            const latest = current.latest_arrival_time;
            p.row.setAttribute("data-earliest-arrival", String(earliest));
            p.row.setAttribute("data-latest-arrival", String(latest));
          } else {
            p.row.removeAttribute("data-earliest-arrival");
            p.row.removeAttribute("data-latest-arrival");
          }
        } catch (err) {
          log$b.error(`Failed to fetch flights for player ${p.player_id}`, err);
        }
      })
    );
  }
  async function apply_ff_columns(membersList) {
    const headerLvl = membersList.querySelector(".table-header > .lvl") || membersList.querySelector(".white-grad");
    if (!headerLvl) return;
    const isWar = membersList.closest(".faction-war") !== null;
    const colDisplay = isWar ? ffconfig.war_col_display : ffconfig.factions_col_display;
    const isEst = colDisplay === FactionsColDisplay.BATTLE_STATS;
    const isNone = colDisplay === FactionsColDisplay.NONE;
    const expectedText = isEst ? "Est" : "FF";
    const factionWar = membersList.closest(".faction-war");
    if (factionWar) {
      factionWar.setAttribute("data-ffscouter-col-display", colDisplay);
    }
    let headerLi = membersList.querySelector(
      ".ffscouter-header"
    );
    if (isNone) {
      if (headerLi) {
        headerLi.remove();
      }
    } else {
      if (headerLi && (isWar && headerLi.tagName !== "DIV" || !isWar && headerLi.tagName !== "LI")) {
        headerLi.remove();
        headerLi = null;
      }
      if (!headerLi) {
        if (isWar) {
          const headerLvlEl = membersList.querySelector(
            ".white-grad > .level"
          );
          if (headerLvlEl) {
            headerLi = document.createElement("div");
            headerLi.classList.add("left", "level", "ffscouter-header");
            headerLvlEl.after(headerLi);
          }
        } else {
          headerLi = document.createElement("li");
          headerLi.tabIndex = 0;
          headerLi.classList.add(
            "table-cell",
            "lvl",
            "torn-divider",
            "divider-vertical",
            "ffscouter-header"
          );
          headerLvl.after(headerLi);
        }
      }
      if (headerLi && headerLi.textContent !== expectedText) {
        headerLi.textContent = expectedText;
      }
    }
    const rows = Array.from(
      membersList.querySelectorAll(".table-body > .table-row, .enemy, .your")
    );
    const rowPlayers = rows.map((row) => {
      const memberDiv = row.querySelector(".member");
      if (!memberDiv) return null;
      const profileLink = memberDiv.querySelector('a[href^="/profiles"]');
      if (!profileLink || !(profileLink instanceof HTMLAnchorElement))
        return null;
      const url = profileLink.href;
      const match = url.match(/.*XID=(?<player_id>\d+)/);
      if (!match?.groups?.["player_id"]) return null;
      return {
        row,
player_id: Number.parseInt(match.groups["player_id"], 10),
        memberDiv
      };
    }).filter((item) => item !== null);
    if (rowPlayers.length === 0) return;
    const playerIds = rowPlayers.map((p) => p.player_id);
    const dataPromises = playerIds.map((id) => ffscouter.get(id));
    ffscouter.complete();
    const dataList = await Promise.all(dataPromises);
    const dataMap = new Map(dataList.map((d2) => [d2.player_id, d2]));
    for (const rp of rowPlayers) {
      let cell = rp.row.querySelector(".ffscouter-cell");
      if (isNone) {
        if (cell) {
          cell.remove();
        }
      } else {
        if (!cell) {
          cell = document.createElement("div");
          if (isWar) {
            cell.classList.add("left", "level", "ffscouter-cell");
            const levelEl = rp.row.querySelector(".level, [class*='level__']");
            if (levelEl) {
              levelEl.after(cell);
            } else {
              rp.memberDiv.after(cell);
            }
          } else {
            cell.classList.add("table-cell", "lvl", "ffscouter-cell");
            const rowLvl = rp.row.querySelector(".lvl");
            if (rowLvl) {
              rowLvl.after(cell);
            } else {
              rp.memberDiv.after(cell);
            }
          }
        }
        cell.style.cursor = "pointer";
        cell.onclick = (e) => {
          e.preventDefault();
          e.stopPropagation();
          const forceNewTab = e.ctrlKey || e.metaKey || e.button === 1;
          open_attack_link(rp.player_id, {
            openInNewTab: forceNewTab ? true : void 0
          });
        };
      }
      const data = dataMap.get(rp.player_id);
      if (data && !data.no_data) {
        rp.row.dataset["ffValue"] = String(data.fair_fight);
        rp.row.dataset["estValue"] = String(data.bs_estimate);
        if (cell) {
          const text = isEst ? data.bs_estimate_human : format_ff_score(data);
          const bg_color = get_ff_colour(data);
          const text_color = get_contrast_color(bg_color);
          cell.style.backgroundColor = bg_color;
          cell.style.color = text_color;
          cell.style.fontWeight = "bold";
          cell.textContent = text;
          if (isEst && data.distribution) {
            const ageStr = format_relative_time(data.distribution.last_updated);
            const agePart = ageStr ? ` ${ageStr}` : "";
            cell.title = `Top Stats: ${data.distribution.distribution_human}${agePart}`;
          } else {
            cell.title = "";
          }
        }
      } else {
        rp.row.dataset["ffValue"] = "";
        rp.row.dataset["estValue"] = "";
        if (cell) {
          cell.textContent = "-";
          cell.style.backgroundColor = "";
          cell.style.color = "";
          cell.style.fontWeight = "";
          cell.title = "";
        }
      }
    }
    const boxEl = (membersList.closest(".faction-war") || membersList.parentNode)?.querySelector("[data-ff-filter-box]");
    const handle = getFilterBoxHandle(boxEl);
    if (handle?.activity) {
      apply_filters_and_sort(membersList, {
        ...handle.getFilterSnapshot(),
        colDisplay
      });
    }
    update_header_sort_indicator(membersList, handle?.sortBy ?? "none");
    poll_traveling_flights(membersList);
  }
  const log$a = logger.child("feature:faction");
  const FEATURE_NAME$4 = "faction";
  function cleanup_when_detached(el, dispose) {
    const cleanupInterval = setInterval(() => {
      if (!el.isConnected) {
        clearInterval(cleanupInterval);
        dispose();
      }
    }, 1e4);
  }
  function run_when_ready(root, isReady, onReady) {
    if (isReady()) {
      onReady();
      return;
    }
    const loadObserver = new MutationObserver((_mutations, obs) => {
      if (isReady()) {
        obs.disconnect();
        onReady();
      }
    });
    loadObserver.observe(root, { childList: true, subtree: true });
    cleanup_when_detached(root, () => loadObserver.disconnect());
  }
  function mountFilterBox(mode, onFilterChange) {
    const container = document.createElement("div");
    container.style.display = "contents";
    container.setAttribute("data-ff-filter-box", "true");
    container.setAttribute("data-mode", mode);
    const ref = { current: null };
    let ready = false;
    let hasLastActionDataProp = false;
    const pending = [];
    const runOrBuffer = (fn) => {
      if (ready && ref.current) {
        fn(ref.current);
      } else {
        pending.push(fn);
      }
    };
    const onReady = () => {
      ready = true;
      if (ref.current && pending.length > 0) {
        for (const fn of pending) fn(ref.current);
        pending.length = 0;
      }
    };
    const handle = {
      get ready() {
        return ready;
      },
      get sortBy() {
        return ready ? ref.current?.sortBy ?? "none" : "none";
      },
      get activity() {
        return ready && ref.current ? ref.current.activity : { online: true, idle: true, offline: true };
      },
      get hasLastActionData() {
        return ready && ref.current ? ref.current.hasLastActionData : hasLastActionDataProp;
      },
      setSortBy: (val) => runOrBuffer((h2) => h2.setSortBy(val)),
      getFilterSnapshot: () => ready && ref.current ? ref.current.getFilterSnapshot() : {
        sortBy: "none",
        filterEnabled: true,
        activity: { online: true, idle: true, offline: true },
        status: {
          okay: true,
          traveling: true,
          hospital: true,
          jail: true,
          abroad: true,
          federal: true,
          fallen: true
        },
        levelMin: null,
        levelMax: null,
        ffMin: null,
        ffMax: null,
        statsMin: null,
        statsMax: null,
        lastActionMinSec: null,
        lastActionMaxSec: null
      },
      setHasLastActionData: (val) => {
        hasLastActionDataProp = val;
        runOrBuffer((h2) => h2.setHasLastActionData(val));
      },
      setFilterState: (patch) => runOrBuffer((h2) => h2.setFilterState(patch)),
      dispatchChange: () => runOrBuffer((h2) => h2.dispatchChange())
    };
    container.__ffHandle = handle;
    createRoot(container).render(
      createElement(FFFactionFilterBox, {
        mode,
        onFilterChange,
        ref,
        initialHasLastActionData: hasLastActionDataProp,
        onReady
      })
    );
    return container;
  }
  function update_last_action_visibility(list) {
    const scope = list.closest(".faction-war") || list;
    const boxEl = (list.closest(".faction-war") || list.parentNode)?.querySelector("[data-ff-filter-box]");
    const handle = getFilterBoxHandle(boxEl);
    if (!handle) return;
    handle.setHasLastActionData(
      !!scope.querySelector("[data-twse-last-action-timestamp]")
    );
  }
  function setup_reapply_watcher(list, observeTarget, getColDisplay) {
    update_last_action_visibility(list);
    let rafPending = false;
    const attributeObserver = new MutationObserver((mutations) => {
      if (is_applying(list)) return;
      let shouldReapply = false;
      for (const m2 of mutations) {
        if (m2.type === "attributes") {
          if (m2.attributeName === "aria-label" && m2.target instanceof HTMLElement && m2.target.closest('[class*="userStatusWrap"]')) {
            shouldReapply = true;
            break;
          }
          if (m2.attributeName === "class" && m2.target instanceof HTMLElement && m2.target.closest(".status")) {
            shouldReapply = true;
            break;
          }
          if (m2.attributeName === "data-twse-last-action-timestamp") {
            shouldReapply = true;
            break;
          }
        }
      }
      if (shouldReapply && !rafPending) {
        rafPending = true;
        requestAnimationFrame(() => {
          rafPending = false;
          update_last_action_visibility(list);
          const boxEl = (list.closest(".faction-war") || list.parentNode)?.querySelector("[data-ff-filter-box]");
          const handle = getFilterBoxHandle(boxEl);
          if (handle?.activity) {
            apply_filters_and_sort(list, {
              ...handle.getFilterSnapshot(),
              colDisplay: getColDisplay()
            });
          }
        });
      }
    });
    attributeObserver.observe(observeTarget, {
      attributes: true,
      attributeFilter: ["class", "aria-label", "data-twse-last-action-timestamp"],
      subtree: true
    });
    const flightInterval = setInterval(() => {
      poll_traveling_flights(list);
      update_last_action_visibility(list);
    }, 3e4);
    cleanup_when_detached(list, () => {
      clearInterval(flightInterval);
      attributeObserver.disconnect();
    });
  }
  function inject_filter_box(membersList) {
    const parent = membersList.parentNode;
    if (!parent) return;
    if (parent.querySelector("[data-ff-filter-box]")) return;
    const container = mountFilterBox("faction", (snapshot) => {
      apply_filters_and_sort(membersList, {
        ...snapshot,
        colDisplay: ffconfig.factions_col_display
      });
      update_header_sort_indicator(membersList, snapshot.sortBy);
    });
    parent.insertBefore(container, membersList);
  }
  function initialize_features(membersList) {
    if (membersList.hasAttribute("data-ffscouter-initialized")) return;
    membersList.setAttribute("data-ffscouter-initialized", "true");
    inject_filter_box(membersList);
    setup_header_click(membersList, ".table-header", "[role='button']");
    apply_ff_columns(membersList);
    const target = membersList.querySelector(".table-body") || membersList;
    setup_reapply_watcher(
      membersList,
      target,
      () => ffconfig.factions_col_display
    );
  }
  function setup_faction_features(membersList) {
    run_when_ready(
      membersList,
      () => !!membersList.querySelector(".table-body")?.querySelector(".table-row"),
      () => initialize_features(membersList)
    );
  }
  const monitor_member_list = (root = document.body, _dynamic = false) => {
    const membersList = root.classList.contains("members-list") ? root : root.querySelector(".members-list");
    if (membersList) {
      setup_faction_features(membersList);
    } else {
      apply_ff_members_list(root);
      const loadObserver = new MutationObserver((mutations, obs) => {
        const foundList = root.classList.contains("members-list") ? root : root.querySelector(".members-list");
        if (foundList) {
          obs.disconnect();
          setup_faction_features(foundList);
        } else {
          let shouldUpdate = false;
          for (const m2 of mutations) {
            for (const node of m2.addedNodes) {
              if (node instanceof HTMLElement && (node.querySelector(".honor-text-wrap") || node.querySelector(".member"))) {
                shouldUpdate = true;
                break;
              }
            }
            if (shouldUpdate) break;
          }
          if (shouldUpdate) {
            apply_ff_members_list(root);
          }
        }
      });
      loadObserver.observe(root, { childList: true, subtree: true });
      cleanup_when_detached(root, () => loadObserver.disconnect());
    }
  };
  const apply_ff_members_list = (root = document.body) => {
    const membersList = root.classList.contains("members-list") ? root : root.querySelector(".members-list");
    if (membersList) {
      setup_faction_features(membersList);
      return;
    }
    apply_ff_gauge_selector(
      root.querySelectorAll(".honor-text-wrap"),
      FEATURE_NAME$4
    );
    apply_ff_gauge_selector(root.querySelectorAll(".member"), FEATURE_NAME$4);
    for (const l of root.querySelectorAll(".members-list, .chain-attacks-list")) {
      if (l instanceof HTMLElement) {
        apply_ff_members_list(l);
      }
    }
  };
  function setup_header_click(list, headerAreaSelector, nativeTabSelector) {
    if (list.hasAttribute("data-ffscouter-header-click")) return;
    list.setAttribute("data-ffscouter-header-click", "true");
    list.addEventListener(
      "click",
      (e) => {
        const target = e.target;
        if (!target.closest(headerAreaSelector)) return;
        const scope = list.closest(".faction-war") ?? list.parentElement;
        const handle = getFilterBoxHandle(
          scope?.querySelector("[data-ff-filter-box]")
        );
        if (!handle) return;
        if (target.closest(".ffscouter-header")) {
          e.preventDefault();
          e.stopPropagation();
          const newSort = handle.sortBy === "ff-desc" ? "ff-asc" : "ff-desc";
          handle.setSortBy(newSort);
        } else if (target.closest(nativeTabSelector)) {
          if (handle.sortBy !== "none") {
            handle.setSortBy("none");
          }
        }
      },
      { capture: true }
    );
  }
  function setup_war_features(factionWar) {
    run_when_ready(
      factionWar,
      () => factionWar.querySelectorAll(".enemy-faction, .your-faction").length > 0,
      () => initialize_war_features(
        factionWar,
        Array.from(
          factionWar.querySelectorAll(".enemy-faction, .your-faction")
        )
      )
    );
  }
  function initialize_war_features(factionWar, lists) {
    if (factionWar.hasAttribute("data-ffscouter-initialized")) return;
    factionWar.setAttribute("data-ffscouter-initialized", "true");
    if (!factionWar.querySelector("[data-ff-filter-box][data-mode='war']")) {
      const container = mountFilterBox("war", (snapshot) => {
        const currentLists = Array.from(
          factionWar.querySelectorAll(".enemy-faction, .your-faction")
        );
        const colDisplay = ffconfig.war_col_display;
        for (const list of currentLists) {
          apply_filters_and_sort(list, { ...snapshot, colDisplay });
          update_header_sort_indicator(list, snapshot.sortBy);
        }
      });
      factionWar.insertBefore(container, factionWar.firstChild);
    }
    for (const list of lists) {
      setup_war_list(list);
    }
  }
  function setup_war_list(list) {
    run_when_ready(
      list,
      () => !!list.querySelector(".enemy, .your"),
      () => initialize_war_list(list)
    );
  }
  function initialize_war_list(list) {
    setup_header_click(list, ".white-grad", "[class*='tab___']");
    apply_ff_columns(list);
    setup_reapply_watcher(list, list, () => ffconfig.war_col_display);
  }
  const process_page = () => {
    wait_for_element(".members-list", 1e4).then((node) => {
      if (node instanceof HTMLElement) {
        log$a.debug("Found members-list!");
        monitor_member_list(node);
      }
    });
    wait_for_element(".chain-attacks-list", 1e4).then((node) => {
      if (node instanceof HTMLElement) {
        log$a.debug("Found chain-attacks-list!");
        monitor_member_list(node, true);
      }
    });
    wait_for_element("#faction_war_list_id", 1e4).then(async (node) => {
      if (!node) {
        return;
      }
      log$a.debug("Found faction_war_list_id");
      const descriptions_observer = new MutationObserver(async (mutations) => {
        for (const mutation of mutations) {
          for (const node2 of mutation.addedNodes) {
            if (node2 instanceof HTMLElement && node2.classList.contains("descriptions")) {
              log$a.debug(
                "Observed mutation that included adding descriptions",
                node2
              );
              const faction_war = await wait_for_element(".faction-war", 1e4);
              if (faction_war instanceof HTMLElement) {
                setup_war_features(faction_war);
              }
            }
          }
        }
      });
      descriptions_observer.observe(node, { childList: true });
      log$a.debug(
        `Set up descriptions observer on <${node.tagName.toLowerCase()}> .${[...node.classList].join(".")}`
      );
      const existing_descriptions = node.querySelector(".descriptions");
      if (existing_descriptions) {
        const faction_war = await wait_for_element(
          " .faction-war",
          1e4,
          existing_descriptions
        );
        if (faction_war instanceof HTMLElement) {
          setup_war_features(faction_war);
        }
      }
    });
  };
  function should_run_faction() {
    if (torn_page("factions", { step: "profile" })) {
      return true;
    }
    if (torn_page("factions", { step: "your" })) {
      if (window.location.hash === "" || window.location.hash === "#" || window.location.hash === "#/" || window.location.hash.startsWith("#/war/") || window.location.hash === "#/tab=info") {
        return true;
      }
    }
    return false;
  }
  const index$c = {
    name: "Faction page FF display",
    description: "Shows FF arrows on both your faction and other faction pages.",
    executionTime: StartTime.DocumentBody,
    async shouldRun() {
      return torn_page("factions");
    },
    async run() {
      on_navigation(() => {
        if (should_run_faction()) {
          process_page();
        }
      });
      window.addEventListener("ff-config-updated", () => {
        if (should_run_faction()) {
          const lists = document.querySelectorAll(
            ".members-list, .chain-attacks-list, .enemy-faction, .your-faction"
          );
          for (const list of lists) {
            if (list instanceof HTMLElement) {
              apply_ff_columns(list);
            }
          }
        }
      });
      if (should_run_faction()) {
        process_page();
      }
    }
  };
  const __vite_glob_0_2 = Object.freeze( Object.defineProperty({
    __proto__: null,
    default: index$c,
    getFilterBoxHandle,
    initialize_features,
    setup_war_features,
    should_run_faction
  }, Symbol.toStringTag, { value: "Module" }));
  const log$9 = logger.child("feature:fallback");
  const FEATURE_NAME_HONOR_BAR = "fallback-honor-bar";
  const FEATURE_NAME_USER_NAME = "fallback-user-name";
  const FEATURE_NAME$3 = "fallback";
  function is_excluded_page() {
    switch (true) {
      case torn_page("gym"):
      case torn_page("item"):
      case torn_page("city"):
      case torn_page("casino"):
      case torn_page("calendar"):
      case torn_page("preferences"):
      case torn_page("estateagents"):
      case torn_page("profiles"):
      case torn_page("pc"):
      case torn_page("citystats"):
      case torn_page("usersonline"):
      case torn_page("displaycase"):
      case torn_page("bank"):
      case torn_page("loan"):
      case torn_page("donator"):
      case torn_page("token_shop"):
      case torn_page("freebies"):
      case torn_page("bigalgunshop"):
      case torn_page("shops"):
      case torn_page("joblisting"):
      case torn_page("messageinc"):
      case torn_page("comics"):
      case torn_page("archives"):
      case torn_page("rules"):
      case torn_page("credits"):
      case torn_page("committee"):
      case torn_page("church"):
      case torn_page("christmas_town"):
      case torn_page("index", { page: "hunting" }):
      case torn_page("index", { page: "bank" }):
      case torn_page("page", { sid: "slotsLastRolls" }):
      case torn_page("page", { sid: "rouletteLastSpins" }):
      case torn_page("page", { sid: "highlowLastGames" }):
      case torn_page("page", { sid: "kenoLastGames" }):
      case torn_page("page", { sid: "crapsLastRolls" }):
      case torn_page("page", { sid: "blackjackLastGames" }):
      case torn_page("page", { sid: "spinTheWheelLastSpins" }):
      case torn_page("page", { sid: "bunker" }):
      case torn_page("page", { sid: "points" }):
      case torn_page("page", { sid: "itemsMods" }):
      case torn_page("page", { sid: "keepsakes" }):
      case torn_page("page", { sid: "ammo" }):
      case torn_page("page", { sid: "awards" }):
      case torn_page("page", { sid: "log" }):
      case torn_page("page", { sid: "events" }):
      case torn_page("page", { sid: "crimes" }):
      case torn_page("page", { sid: "crimesRecord" }):
      case torn_page("page", { sid: "factionWarfare" }):
      case torn_page("page", { sid: "travel" }):
      case torn_page("page", { sid: "missions" }):
      case torn_page("page", { sid: "stocks" }):
      case torn_page("page", { sid: "slots" }):
      case torn_page("page", { sid: "roulette" }):
      case torn_page("page", { sid: "highlow" }):
      case torn_page("page", { sid: "keno" }):
      case torn_page("page", { sid: "craps" }):
      case torn_page("page", { sid: "bookie" }):
      case torn_page("page", { sid: "blackjack" }):
      case torn_page("page", { sid: "spinTheWheel" }):
      case torn_page("page", { sid: "education" }):
      case torn_page("page", { sid: "itemMarket" }):
        return true;
      default:
        if (torn_page("factions", { step: "your" })) {
          const hash = window.location.hash;
          if (!(hash.startsWith("#/war/") || hash === "#/tab=info" || hash.startsWith("#/tab=controls"))) {
            return true;
          }
        }
        return false;
    }
  }
  async function find_mutation_target() {
    const content_wrapper = await wait_for_element(".content-wrapper", 1e4);
    if (content_wrapper) {
      return content_wrapper;
    }
    await wait_for_body(1e4);
    return document.body;
  }
  const index$b = {
    name: "Fallback mutation observer",
    description: "Catch all mutations and see if we can apply FF data",
    executionTime: StartTime.DocumentBody,
    async shouldRun() {
      return true;
    },
    async run() {
      const IGNORED_TAGS = new Set([
        "SCRIPT",
        "STYLE",
        "LINK",
        "META",
        "SVG",
        "PATH",
        "BR",
        "HR",
        "HEAD",
        "TITLE"
      ]);
      const get_page_selectors = () => {
        const href = window.location.href;
        let page_specific = [];
        if (href.startsWith("https://www.torn.com/companies.php")) {
          page_specific = [".employee", ".director"];
        } else if (href.startsWith("https://www.torn.com/page.php?sid=competition#/team")) {
          page_specific = ['[class*="name__"]'];
        } else if (href.startsWith("https://www.torn.com/joblist.php")) {
          page_specific = [".employee", ".director"];
        } else if (torn_page("messages") || torn_page("index") || torn_page("hospitalview") || torn_page("page", { sid: "UserList" })) {
          page_specific = [".name"];
        } else if (href.startsWith("https://www.torn.com/bounties.php")) {
          page_specific = [".target, .listed"];
        } else if (href.startsWith("https://www.torn.com/page.php?sid=attackLog")) {
          page_specific = ["ul.participants-list li"];
        } else if (href.startsWith("https://www.torn.com/forums.php")) {
          page_specific = [".last-poster, .starter, .last-post, .poster"];
        } else if (href.includes("page.php?sid=hof") || torn_page("factions", { step: "profile" }) || torn_page("factions", { step: "your" }, [
          "",
          "#",
          "#/",
          "#/tab=info",
          "#/war/*"
        ])) {
          page_specific = ['[class*="userInfoBox__"]'];
        }
        if (page_specific.length > 0) {
          const combined = [".honor-text-wrap", ...page_specific].join(", ");
          return {
            has_page_specific: true,
            page_specific_selectors: page_specific,
            combined_selector: combined
          };
        }
        return {
          has_page_specific: false,
          page_specific_selectors: [],
          combined_selector: ".honor-text-wrap, .user.name"
        };
      };
      let current_config = get_page_selectors();
      let is_observing = false;
      const check_mutation = async (node) => {
        if (!node.querySelectorAll || IGNORED_TAGS.has(node.tagName)) {
          return;
        }
        if (!node.matches(current_config.combined_selector) && !node.querySelector(current_config.combined_selector)) {
          return;
        }
        const honor_bars = node.querySelectorAll(
          ".honor-text-wrap"
        );
        if (honor_bars.length > 0) {
          apply_ff_gauge_selector(honor_bars, FEATURE_NAME_HONOR_BAR);
        } else if (current_config.has_page_specific) {
          for (const selector of current_config.page_specific_selectors) {
            apply_ff_gauge_selector(
              node.querySelectorAll(selector),
              FEATURE_NAME$3
            );
          }
        } else {
          const name_elems = node.querySelectorAll(
            ".user.name"
          );
          if (name_elems.length > 0) {
            apply_ff_gauge_selector(name_elems, FEATURE_NAME_USER_NAME);
          }
        }
        ffscouter.complete();
      };
      const ff_gauge_observer = new MutationObserver(async (mutations) => {
        for (const mutation of mutations) {
          for (const node of mutation.addedNodes) {
            if (node instanceof Element) {
              check_mutation(node);
            }
          }
        }
      });
      const update_observer_state = async () => {
        const excluded = is_excluded_page();
        if (excluded) {
          if (is_observing) {
            ff_gauge_observer.disconnect();
            is_observing = false;
            log$9.debug("Disconnected fallback MutationObserver (excluded page)");
          }
        } else {
          current_config = get_page_selectors();
          if (!is_observing) {
            const target = await find_mutation_target();
            ff_gauge_observer.observe(target, {
              attributes: false,
              childList: true,
              characterData: false,
              subtree: true
            });
            is_observing = true;
            log$9.debug("Connected fallback MutationObserver (included page)");
            if (target) {
              check_mutation(target);
            }
          }
        }
      };
      on_navigation(() => {
        log$9.debug("Navigation detected, re-evaluating fallback observer state");
        update_observer_state();
      });
      update_observer_state();
    },
    httpIntercept: {
      before(_url, _init) {
        return void 0;
      },
      after(_bodyText, _response, _ctx) {
        return void 0;
      }
    }
  };
  const __vite_glob_0_3 = Object.freeze( Object.defineProperty({
    __proto__: null,
    default: index$b
  }, Symbol.toStringTag, { value: "Module" }));
  const log$8 = logger.child("ui");
  var TOAST_LEVEL = ((TOAST_LEVEL2) => {
    TOAST_LEVEL2[TOAST_LEVEL2["DEBUG"] = 0] = "DEBUG";
    TOAST_LEVEL2[TOAST_LEVEL2["INFO"] = 1] = "INFO";
    TOAST_LEVEL2[TOAST_LEVEL2["WARNING"] = 2] = "WARNING";
    TOAST_LEVEL2[TOAST_LEVEL2["ERROR"] = 3] = "ERROR";
    return TOAST_LEVEL2;
  })(TOAST_LEVEL || {});
  const TOAST_COLOURS = {
    [
      0
]: "blue",
    [
      1
]: "green",
    [
      2
]: "orange",
    [
      3
]: "#c62828"
  };
  function get_toast_colour(level) {
    return TOAST_COLOURS[level];
  }
  function toast(message, level = 1) {
    const existing = document.getElementById("ffscouter-toast");
    if (existing) existing.remove();
    const toast2 = document.createElement("div");
    toast2.id = "ffscouter-toast";
    toast2.style.position = "fixed";
    toast2.style.bottom = "30px";
    toast2.style.left = "50%";
    toast2.style.transform = "translateX(-50%)";
    toast2.style.color = "#fff";
    toast2.style.padding = "8px 16px";
    toast2.style.borderRadius = "8px";
    toast2.style.fontSize = "14px";
    toast2.style.boxShadow = "0 2px 12px rgba(0,0,0,0.2)";
    toast2.style.zIndex = "2147483647";
    toast2.style.opacity = "1";
    toast2.style.transition = "opacity 0.5s";
    toast2.style.display = "flex";
    toast2.style.alignItems = "center";
    toast2.style.gap = "10px";
    const closeBtn = document.createElement("button");
    closeBtn.textContent = "×";
    closeBtn.style.cursor = "pointer";
    closeBtn.style.marginLeft = "8px";
    closeBtn.style.fontWeight = "bold";
    closeBtn.style.fontSize = "18px";
    closeBtn.style.background = "none";
    closeBtn.style.border = "none";
    closeBtn.style.color = "inherit";
    closeBtn.style.padding = "0";
    closeBtn.style.lineHeight = "1";
    closeBtn.setAttribute("aria-label", "Close");
    closeBtn.onclick = () => toast2.remove();
    toast2.style.background = get_toast_colour(level);
    const msg = document.createElement("span");
    if (message === "Invalid API key. Please sign up at ffscouter.com to use this service") {
      msg.innerHTML = 'FairFight Scouter V2: Invalid API key. Please sign up at <a href="https://ffscouter.com" target="_blank" style="color: #fff; text-decoration: underline; font-weight: bold;">ffscouter.com</a> to use this service. Register the API key with the script.';
    } else {
      msg.textContent = `FairFight Scouter V2: ${message}`;
    }
    log$8.info("[FF Scouter V2] Toast: ", message);
    toast2.appendChild(msg);
    toast2.appendChild(closeBtn);
    document.body.appendChild(toast2);
    setTimeout(() => {
      if (toast2.parentNode) {
        toast2.style.opacity = "0";
        setTimeout(() => toast2.remove(), 500);
      }
    }, 4e3);
  }
  const log$7 = logger.child("feature:ff-button");
  const CACHE_LIFETIME_MS = 7 * 24 * 60 * 60 * 1e3;
  const POLL_INTERVAL_MS = 24 * 60 * 60 * 1e3;
  function get_active_filters() {
    return {
      minlevel: ffconfig.chain_min_level,
      maxlevel: ffconfig.chain_max_level,
      minff: ffconfig.chain_min_ff,
      maxff: ffconfig.chain_max_ff,
      inactive: ffconfig.chain_inactive,
      factionless: ffconfig.chain_factionless
    };
  }
  function filters_changed(a, b) {
    return a.minlevel !== b.minlevel || a.maxlevel !== b.maxlevel || a.minff !== b.minff || a.maxff !== b.maxff || a.inactive !== b.inactive || a.factionless !== b.factionless;
  }
  async function update_ff_targets(force = false) {
    const key = ffconfig.key;
    if (!key) {
      log$7.debug("API key not set, skipping target fetch");
      return;
    }
    const currentFilters = get_active_filters();
    const cached = ffconfig.chain_targets;
    const hasNoCacheOrExpired = !cached || Date.now() > cached.expiry;
    const filtersChanged = cached && filters_changed(cached.filters, currentFilters);
    const timeToRefresh = cached && (!cached.last_updated || Date.now() - cached.last_updated > POLL_INTERVAL_MS);
    if (!force && !hasNoCacheOrExpired && !filtersChanged && !timeToRefresh) {
      log$7.debug(
        "Using cached targets, not expired, filters match, and not time to poll yet"
      );
      return;
    }
    try {
      const response = await query_targets(key, {
        minlevel: currentFilters.minlevel,
        maxlevel: currentFilters.maxlevel,
        minff: currentFilters.minff,
        maxff: currentFilters.maxff,
        inactiveonly: currentFilters.inactive ? 1 : 0,
        factionless: currentFilters.factionless ? 1 : 0,
        limit: 50
      });
      if (response?.targets) {
        ffconfig.chain_targets = {
          targets: response.targets,
          expiry: Date.now() + CACHE_LIFETIME_MS,
          last_updated: Date.now(),
          filters: currentFilters
        };
        ffconfig.chain_target_index = 0;
        log$7.info(
          `Chain targets updated successfully: ${response.targets.length} targets found`
        );
      }
    } catch (err) {
      log$7.error("Failed to update chain targets:", err);
    }
  }
  function get_next_target_index(maxLen) {
    const val = ffconfig.chain_target_index;
    let nextVal = val + 1;
    if (nextVal >= maxLen) {
      nextVal = 0;
    }
    ffconfig.chain_target_index = nextVal;
    return val < maxLen ? val : 0;
  }
  function get_random_chain_target() {
    const cached = ffconfig.chain_targets;
    if (!cached || !cached.targets || cached.targets.length === 0) {
      return null;
    }
    const index2 = get_next_target_index(cached.targets.length);
    return cached.targets[index2] ?? null;
  }
  function remove_chain_button() {
    const button = document.getElementById("ff-scouter-chain-btn");
    if (button) {
      button.remove();
    }
  }
  function update_anchor_attributes(anchor) {
    const cached = ffconfig.chain_targets;
    if (!cached || !cached.targets || cached.targets.length === 0) {
      anchor.href = "#";
      anchor.removeAttribute("target");
      return;
    }
    const idx = ffconfig.chain_target_index;
    const currentTarget = cached.targets[idx < cached.targets.length ? idx : 0];
    if (!currentTarget) {
      anchor.href = "#";
      anchor.removeAttribute("target");
      return;
    }
    const linkType = ffconfig.chain_link_type;
    anchor.href = linkType === "profile" ? `https://www.torn.com/profiles.php?XID=${currentTarget.player_id}` : get_attack_url(currentTarget.player_id);
    anchor.target = ffconfig.chain_tab_type === "sametab" ? "_self" : "_blank";
  }
  function create_chain_button() {
    if (!ffconfig.chain_button_enabled || !ffconfig.key) {
      remove_chain_button();
      return;
    }
    const existing = document.getElementById(
      "ff-scouter-chain-btn"
    );
    if (existing) {
      update_anchor_attributes(existing);
      return;
    }
    const anchor = document.createElement("a");
    anchor.id = "ff-scouter-chain-btn";
    anchor.innerHTML = "FF";
    anchor.style.position = "fixed";
    anchor.style.top = "32%";
    anchor.style.right = "0%";
    anchor.style.zIndex = "9999";
    anchor.style.backgroundColor = "green";
    anchor.style.color = "white";
    anchor.style.border = "none";
    anchor.style.padding = "6px";
    anchor.style.borderRadius = "6px";
    anchor.style.cursor = "pointer";
    anchor.style.display = "block";
    anchor.style.textDecoration = "none";
    update_anchor_attributes(anchor);
    const handler = async (e) => {
      if (e instanceof KeyboardEvent) {
        if (e.key !== "Enter") {
          return;
        }
      } else if (e instanceof MouseEvent) {
        if (e.button !== 0 && e.button !== 1 && e.button !== 2) {
          return;
        }
      }
      const cached = ffconfig.chain_targets;
      if (!cached || !cached.targets || cached.targets.length === 0) {
        e.preventDefault();
        const isPrimary = e instanceof MouseEvent && e.button === 0 || e instanceof KeyboardEvent;
        if (isPrimary) {
          toast("No cached targets found. Fetching...", TOAST_LEVEL.WARNING);
          update_ff_targets(true).then(() => {
            const newCached = ffconfig.chain_targets;
            if (!newCached || !newCached.targets || newCached.targets.length === 0) {
              toast(
                "No targets available matching your criteria.",
                TOAST_LEVEL.ERROR
              );
              return;
            }
            update_anchor_attributes(anchor);
            toast("Targets loaded. Click to navigate!", TOAST_LEVEL.INFO);
          });
        }
        return;
      }
      get_next_target_index(cached.targets.length);
      update_anchor_attributes(anchor);
    };
    anchor.addEventListener("mousedown", handler);
    anchor.addEventListener("keydown", handler);
    document.body.appendChild(anchor);
  }
  const index$a = {
    name: "FF Target Finder Button",
    description: "Renders the floating green FF button to cycle through potential targets",
    executionTime: StartTime.DocumentBody,
    async shouldRun() {
      return true;
    },
    async run() {
      if (!ffconfig.chain_button_enabled || !ffconfig.key) {
        remove_chain_button();
        return;
      }
      update_ff_targets().then(() => {
        const button = document.getElementById(
          "ff-scouter-chain-btn"
        );
        if (button) {
          update_anchor_attributes(button);
        }
      });
      create_chain_button();
      window.addEventListener("ff-config-updated", async () => {
        if (!ffconfig.chain_button_enabled || !ffconfig.key) {
          remove_chain_button();
          return;
        }
        const cached = ffconfig.chain_targets;
        const currentFilters = get_active_filters();
        if (!cached || filters_changed(cached.filters, currentFilters)) {
          log$7.info("Target filters changed, refetching targets immediately");
          await update_ff_targets(true);
        }
        create_chain_button();
      });
    }
  };
  const __vite_glob_0_4 = Object.freeze( Object.defineProperty({
    __proto__: null,
    CACHE_LIFETIME_MS,
    POLL_INTERVAL_MS,
    create_chain_button,
    default: index$a,
    filters_changed,
    get_active_filters,
    get_next_target_index,
    get_random_chain_target,
    remove_chain_button,
    update_anchor_attributes,
    update_ff_targets
  }, Symbol.toStringTag, { value: "Module" }));
  const FEATURE_NAME$2 = "item_market";
  const log$6 = logger.child(`feature:${FEATURE_NAME$2}`);
  const index$9 = {
    name: "Item market FF display",
    description: "Shows FF on the item market page",
    executionTime: StartTime.DocumentBody,
    async shouldRun() {
      return torn_page("page", { sid: "ItemMarket" });
    },
    async run() {
      const root = await wait_for_element('[class*="marketWrapper__"', 1e4);
      if (!root) {
        return;
      }
      log$6.info("Found item list wrapper!");
      log$6.debug("Root element:", root);
      const process = () => {
        apply_ff_gauge_selector(
          root.querySelectorAll(
            "div.bazaar-listing-card div:first-child div:first-child > a"
          ),
          FEATURE_NAME$2
        );
        apply_ff_gauge_selector(
          root.querySelectorAll(".bazaar-card a"),
          FEATURE_NAME$2
        );
        apply_ff_gauge_selector(
          root.querySelectorAll(".bazaar-card .bazaar-card-name"),
          FEATURE_NAME$2
        );
        apply_ff_gauge_selector(
          root.querySelectorAll(".honor-text-wrap"),
          FEATURE_NAME$2
        );
        apply_ff_gauge_selector(
          root.querySelectorAll('[class*="userInfoWrapper__"]'),
          FEATURE_NAME$2
        );
      };
      const observer = new MutationObserver(process);
      observer.observe(root, {
        childList: true,
        subtree: true
      });
    },
    httpIntercept: {
      before(_url, _init) {
        return void 0;
      },
      after(_bodyText, _response, _ctx) {
        return void 0;
      }
    }
  };
  const __vite_glob_0_5 = Object.freeze( Object.defineProperty({
    __proto__: null,
    default: index$9
  }, Symbol.toStringTag, { value: "Module" }));
  const log$5 = logger.child("ui");
  const PREMIUM_UPGRADE_URL = "https://ffscouter.com/premium";
  function format_duration_human(totalSeconds, compact) {
    const clampedSeconds = Math.max(0, Math.floor(totalSeconds));
    const hours = Math.floor(clampedSeconds / 3600);
    const minutes = Math.floor(clampedSeconds % 3600 / 60);
    const seconds = clampedSeconds % 60;
    const parts = [];
    if (hours > 0) {
      parts.push(`${hours}h`);
    }
    if (hours > 0 || minutes > 0) {
      parts.push(`${minutes}m`);
    }
    parts.push(`${seconds}s`);
    if (compact) {
      return parts.join("");
    }
    return parts.join(" ");
  }
  function format_tct_time(unixSeconds) {
    if (!Number.isFinite(unixSeconds)) return null;
    const d2 = new Date(unixSeconds * 1e3);
    const hours = String(d2.getUTCHours()).padStart(2, "0");
    const minutes = String(d2.getUTCMinutes()).padStart(2, "0");
    return `${hours}:${minutes}`;
  }
  function FFFlightProfileStatus({ playerId, compact = false }) {
    const [data, setData] = useState(null);
    const [isPremium, setIsPremium] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);
    const [currentTimeSeconds, setCurrentTimeSeconds] = useState(
      get_current_time_seconds
    );
    const [prevPlayerId, setPrevPlayerId] = useState(playerId);
    if (playerId !== prevPlayerId) {
      setPrevPlayerId(playerId);
      setData(null);
      setError(null);
      setLoading(true);
      setIsPremium(null);
    }
    const dataRef = useRef(null);
    dataRef.current = data;
    useEffect(() => {
      if (!playerId) return;
      let active = true;
      const fetchData = async () => {
        if (!active) return;
        let isPremiumResult;
        try {
          isPremiumResult = await check_key_status.is_premium();
        } catch (err) {
          log$5.error("Failed to check premium status", err);
          if (active) setLoading(false);
          return;
        }
        if (!active) return;
        setIsPremium(isPremiumResult);
        if (!isPremiumResult) {
          setLoading(false);
          return;
        }
        try {
          const result = await ffscouter.get_flights(playerId);
          if (!active) return;
          setData(result);
          setError(null);
        } catch (err) {
          if (!active) return;
          log$5.error("Failed to fetch flight data", err);
          if (err instanceof FFApiError) {
            const code = err.ff_api_error?.code;
            if (code === 19) {
              setIsPremium(false);
            } else if (code === 2 || code === 10 || code === 12) {
              setError("Invalid API key");
            } else if (code === 20) {
              setError("Rate limit exceeded. Retrying...");
            } else {
              setError(
                err.ff_api_error?.error ?? err.message ?? "Flight tracking unavailable"
              );
            }
          } else {
            setError(
              err instanceof Error ? err.message : "Flight tracking unavailable"
            );
          }
        } finally {
          if (active) setLoading(false);
        }
      };
      fetchData();
      const tick = setInterval(() => {
        setCurrentTimeSeconds(get_current_time_seconds());
        const d2 = dataRef.current;
        if (d2?.rechecking && d2.next_retry_at && Date.now() >= d2.next_retry_at) {
          fetchData();
        }
      }, 1e3);
      const fetchInterval = setInterval(() => {
        if (!dataRef.current?.rechecking) {
          fetchData();
        }
      }, 3e4);
      return () => {
        active = false;
        clearInterval(tick);
        clearInterval(fetchInterval);
      };
    }, [playerId]);
    if (!playerId) return null;
    if (isPremium === false) {
      return jsx("div", { className: "ff-scouter-profile-flight-info", children: jsx(
        "a",
        {
          href: PREMIUM_UPGRADE_URL,
          target: "_blank",
          rel: "noopener noreferrer",
          style: { fontWeight: "bold", textDecoration: "underline" },
          children: "Upgrade to FFScouter Flight Tracking"
        }
      ) });
    }
    let content;
    if (error) {
      content = jsxs("span", { style: { color: "#ff6b6b" }, children: [
        "Error: ",
        error
      ] });
    } else if (loading && !data) {
      content = compact ? "Estimating..." : "Landing: estimating...";
    } else {
      const current = data?.current;
      if (data?.rechecking) {
        const next = data.next_retry_at ?? 0;
        const seconds = Math.max(0, Math.ceil((next - Date.now()) / 1e3));
        content = compact ? jsxs(Fragment, { children: [
          "No data.",
jsx("br", {}),
          "Rechecking..."
        ] }) : `No data. Rechecking in ${seconds} seconds.`;
      } else if (!current || !current.earliest_arrival_time && !current.latest_arrival_time) {
        content = compact ? "Unavailable" : "Landing: unavailable for current route";
      } else {
        const earliest = Number(current.earliest_arrival_time);
        const latest = Number(current.latest_arrival_time);
        if (!Number.isFinite(earliest) || !Number.isFinite(latest)) {
          content = compact ? "Unavailable" : "Landing: unavailable for current route";
        } else {
          const earliestRemaining = earliest - currentTimeSeconds;
          const latestRemaining = latest - currentTimeSeconds;
          const earliestTct = format_tct_time(earliest);
          const latestTct = format_tct_time(latest);
          if (latestRemaining <= -5 * 60) {
            content = compact ? "Late" : jsxs(Fragment, { children: [
              "Landing: Late, probably flight delayed.",
jsx("br", {}),
              "(",
              latestTct,
              " TCT latest)"
            ] });
          } else if (latestRemaining <= 0) {
            content = compact ? jsxs(Fragment, { children: [
              "Just landed",
jsx("br", {}),
              "(Latest: ",
              latestTct,
              " TCT)"
            ] }) : jsxs(Fragment, { children: [
              "Landing: just landed",
jsx("br", {}),
              "(",
              latestTct,
              " TCT latest)"
            ] });
          } else if (earliestRemaining <= 0) {
            content = compact ? jsxs(Fragment, { children: [
              "Imminent",
jsx("br", {}),
              format_duration_human(latestRemaining, compact),
jsx("br", {})
            ] }) : jsxs(Fragment, { children: [
              "Landing: imminent -",
              " ",
              format_duration_human(latestRemaining, compact),
jsx("br", {}),
              "(Latest: ",
              latestTct,
              " TCT)"
            ] });
          } else {
            content = compact ? jsxs(Fragment, { children: [
              format_duration_human(earliestRemaining, compact),
jsx("br", {}),
              format_duration_human(latestRemaining, compact)
            ] }) : jsxs(Fragment, { children: [
              "Landing: ",
              format_duration_human(earliestRemaining, compact),
              " -",
              " ",
              format_duration_human(latestRemaining, compact),
jsx("br", {}),
              "(",
              earliestTct,
              " - ",
              latestTct,
              " TCT)"
            ] });
          }
        }
      }
    }
    return jsx("div", { className: "ff-scouter-profile-flight-info", children: content });
  }
  const log$4 = logger.child("feature:mini-profile-flights");
  const FLIGHT_CONTAINER_CLASS = "ff-flight-element";
  function is_flying$1(status) {
    return status.classList.contains("travelling");
  }
  const monitor_mini_profile_root$1 = () => {
    const miniprofile = document.querySelector("#profile-mini-root");
    if (miniprofile) {
      log$4.debug("profile-mini-root already exists.");
      setup_mini_flight_observer();
      return;
    }
    const mini_body_observer = new MutationObserver((mutations) => {
      for (const mutation of mutations) {
        for (const node of mutation.addedNodes) {
          if (node instanceof HTMLElement && node.id === "profile-mini-root") {
            setup_mini_flight_observer();
            mini_body_observer.disconnect();
          }
        }
      }
    });
    mini_body_observer.observe(document.body, { childList: true });
  };
  const setup_mini_flight_observer = async () => {
    const miniroot = document.querySelector("#profile-mini-root");
    if (!miniroot) {
      return;
    }
    const container = document.createElement("div");
    container.classList.add(FLIGHT_CONTAINER_CLASS);
    let root = null;
    let lastPlayerId = null;
    const renderForPlayer = (player_id2) => {
      if (!root) {
        root = createRoot(container);
      }
      root.render(
        createElement(FFFlightProfileStatus, {
          playerId: player_id2,
          compact: true
        })
      );
    };
    const mp_observer = new MutationObserver((mutations) => {
      for (const mutation of mutations) {
        if (Array.from(mutation.addedNodes).some(
          (node) => node instanceof HTMLElement && (node.classList.contains(FLIGHT_CONTAINER_CLASS) || node.classList.contains("ff-scouter-profile-flight-info"))
        )) {
          return;
        }
      }
      const player_id2 = get_player_id_in_element(miniroot);
      if (!player_id2) {
        lastPlayerId = null;
        return;
      }
      if (player_id2 === lastPlayerId) {
        return;
      }
      lastPlayerId = player_id2;
      const status2 = miniroot.querySelector(".profile-container");
      if (!status2) {
        return;
      }
      renderForPlayer(player_id2);
      if (is_flying$1(status2)) {
        const description = status2.querySelector(".description");
        if (description && !description.contains(container)) {
          log$4.debug(
            `Player ${player_id2} is flying, adding flight tracker to mini-profile`
          );
          description.appendChild(container);
        }
      } else {
        container.remove();
        ffscouter.clear_flight_cache(player_id2);
      }
    });
    mp_observer.observe(miniroot, { childList: true, subtree: true });
    const player_id = get_player_id_in_element(miniroot);
    const status = miniroot.querySelector(".profile-status");
    if (player_id && status) {
      renderForPlayer(player_id);
      lastPlayerId = player_id;
      if (is_flying$1(status)) {
        const description = status.querySelector(".description");
        if (description && !description.contains(container)) {
          description.appendChild(container);
        }
      }
    }
  };
  const index$8 = {
    name: "Mini profile flight tracking",
    description: "Display flight estimates on player mini-profiles if they're flying",
    executionTime: StartTime.DocumentBody,
    async shouldRun() {
      return true;
    },
    async run() {
      monitor_mini_profile_root$1();
      log$4.debug("mini-profile-flights installed");
    },
    httpIntercept: {
      before(_url, _init) {
        return void 0;
      },
      after(_bodyText, _response, _ctx) {
        return void 0;
      }
    }
  };
  const __vite_glob_0_6 = Object.freeze( Object.defineProperty({
    __proto__: null,
    default: index$8
  }, Symbol.toStringTag, { value: "Module" }));
  const log$3 = logger.child("feature:mini-profile");
  const FEATURE_NAME$1 = "mini-profile";
  const monitor_mini_profile_root = () => {
    const miniprofile = document.querySelector("#profile-mini-root");
    if (miniprofile) {
      log$3.debug("profile-mini-root already exists.");
      setup_mini_observer();
      return;
    }
    const mini_body_observer = new MutationObserver((mutations) => {
      for (const mutation of mutations) {
        for (const node of mutation.addedNodes) {
          if (node instanceof HTMLElement && node.id === "profile-mini-root") {
            setup_mini_observer();
            mini_body_observer.disconnect();
          }
        }
      }
    });
    mini_body_observer.observe(document.body, { childList: true });
  };
  const setup_mini_observer = () => {
    const miniroot = document.querySelector("#profile-mini-root");
    if (!miniroot) {
      return;
    }
    let lastPlayerId = null;
    const mp_observer = new MutationObserver((mutations) => {
      for (const mutation of mutations) {
        if (mutation.target instanceof HTMLElement && mutation.target.classList.contains("ffscouter-gauge")) {
          return;
        }
        if (Array.from(mutation.addedNodes).some(
          (node) => node instanceof HTMLElement && node.classList.contains("ffscouter-mini-desc")
        )) {
          return;
        }
      }
      const player_id = get_player_id_in_element(miniroot);
      if (!player_id) {
        lastPlayerId = null;
        return;
      }
      if (player_id === lastPlayerId) {
        return;
      }
      lastPlayerId = player_id;
      ffscouter.get(player_id).then(async (d2) => {
        const current_player_id = get_player_id_in_element(miniroot);
        if (current_player_id !== player_id) {
          return;
        }
        if (d2.no_data) {
          return;
        }
        log$3.debug(`Found mini profile update for ${player_id}, adding ff data`);
        for (const bar of miniroot.querySelectorAll(".honor-text-wrap")) {
          apply_ff_gauge(bar, FEATURE_NAME$1);
        }
        miniroot.querySelector(".ffscouter-mini-desc")?.remove();
        const ff_string = format_ff_score(d2);
        const fresh = format_relative_time(d2.last_updated);
        const message = `FF ${ff_string} ${fresh}`;
        const lastaction = miniroot.querySelector(".last-action");
        const desc = document.createElement("span");
        desc.classList.add("ffscouter-mini-desc");
        desc.innerText = message;
        lastaction?.appendChild(document.createElement("br"));
        lastaction?.appendChild(desc);
      });
      ffscouter.complete();
    });
    mp_observer.observe(miniroot, { childList: true, subtree: true });
  };
  const index$7 = {
    name: "Fill mini profile",
    description: "Add FF data to mini profile",
    executionTime: StartTime.DocumentBody,
    async shouldRun() {
      return true;
    },
    async run() {
      monitor_mini_profile_root();
      log$3.debug("mini-profile installed");
    },
    httpIntercept: {
      before(_url, _init) {
        return void 0;
      },
      after(_bodyText, _response, _ctx) {
        return void 0;
      }
    }
  };
  const __vite_glob_0_7 = Object.freeze( Object.defineProperty({
    __proto__: null,
    default: index$7
  }, Symbol.toStringTag, { value: "Module" }));
  function is_flying(status) {
    return status.classList.contains("travelling");
  }
  const index$6 = {
    name: "Profile flight tracking",
    description: "Display flight estimates on player profiles if they're flying",
    executionTime: StartTime.DocumentBody,
    async shouldRun() {
      return torn_page("profiles");
    },
    async run() {
      const player_id = extract_id_from_url(window.location.href);
      if (!player_id) {
        return;
      }
      const status = await wait_for_element(".profile-status", 1e4);
      if (!status) {
        return;
      }
      const container = document.createElement("div");
      container.classList.add("ff-flight-element");
      createRoot(container).render(
        createElement(FFFlightProfileStatus, { playerId: player_id })
      );
      const check_and_update = () => {
        if (is_flying(status)) {
          const description = status.querySelector(".description");
          if (description === null) {
            return;
          }
          if (!description.contains(container)) {
            description.appendChild(container);
          }
        } else {
          container.remove();
          ffscouter.clear_flight_cache(player_id);
        }
      };
      const status_observer = new MutationObserver(check_and_update);
      status_observer.observe(status, {
        attributes: true,
        attributeFilter: ["class"]
      });
      check_and_update();
    },
    httpIntercept: {
      before(_url, _init) {
        return void 0;
      },
      after(_bodyText, _response, _ctx) {
        return void 0;
      }
    }
  };
  const __vite_glob_0_8 = Object.freeze( Object.defineProperty({
    __proto__: null,
    default: index$6
  }, Symbol.toStringTag, { value: "Module" }));
  const index$5 = {
    name: "Profile FF history",
    description: "Add a button to all user's profiles to link to ffscouter.com",
    executionTime: StartTime.DocumentBody,
    async shouldRun() {
      return torn_page("profiles");
    },
    async run() {
      const player_id = extract_id_from_url(window.location.href);
      if (!player_id) {
        return;
      }
      if (!ffconfig.ff_history_enabled) return;
      if (document.querySelector(".ff-scouter-history-btn")) return;
      const buttonsList = await wait_for_element(
        ".profile-buttons.profile-action .buttons-list",
        1e4
      );
      if (!buttonsList) return;
      const btn = document.createElement("a");
      btn.href = `https://ffscouter.com/player-view?player_id=${player_id}`;
      btn.target = "_blank";
      btn.rel = "noopener noreferrer";
      btn.className = "profile-button ff-scouter-history-btn";
      btn.title = "View Stats History on FFScouter";
      const container = document.createElement("div");
      container.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="46" height="46" viewBox="640 178 46 46" class="icon___GP196">
  <g fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
    <!-- Clock face outline representing history/time -->
    <circle cx="663" cy="201" r="17" />
    <!-- Clock hands pointing to 10:10 -->
    <path d="M663,201 L663,191 M663,201 L671,197" />
  </g>
  <g fill="none" stroke-width="1">
    <text x="657" y="212">FF</text>
  </g>
</svg>`;
      for (const node of container.children) {
        btn.appendChild(node);
      }
      const label = document.createElement("span");
      label.className = "ff-history-label";
      label.textContent = "FF\nHistory";
      buttonsList.appendChild(btn);
    },
    httpIntercept: {
      before(_url, _init) {
        return void 0;
      },
      after(_bodyText, _response, _ctx) {
        return void 0;
      }
    }
  };
  const __vite_glob_0_9 = Object.freeze( Object.defineProperty({
    __proto__: null,
    default: index$5
  }, Symbol.toStringTag, { value: "Module" }));
  async function inject_info_line(info_line) {
    const h4 = await wait_for_element("h4", 1e4);
    if (!h4) {
      return;
    }
    const links_top_wrap = h4.parentNode?.querySelector(".links-top-wrap");
    if (links_top_wrap?.parentNode) {
      links_top_wrap.parentNode.insertBefore(
        info_line,
        links_top_wrap.nextSibling
      );
    } else {
      h4.after(info_line);
    }
  }
  const index$4 = {
    name: "Profile FF display",
    description: "Shows FF on top left of any profile page",
    executionTime: StartTime.DocumentBody,
    async shouldRun() {
      return torn_page("profiles");
    },
    async run() {
      const info_line = create_info_line();
      if (!ffconfig.key) {
        info_line.innerHTML = "[FF Scouter V2]: Limited API key needed - enter in FF Scouter Settings below";
        inject_info_line(info_line);
        return;
      }
      const player_id = extract_id_from_url(window.location.href);
      if (!player_id) {
        return;
      }
      mountComponent(
        createElement(FFHeaderLine, { playerId: player_id }),
        info_line
      );
      inject_info_line(info_line);
    },
    httpIntercept: {
      before(_url, _init) {
        return void 0;
      },
      after(_bodyText, _response, _ctx) {
        return void 0;
      }
    }
  };
  const __vite_glob_0_10 = Object.freeze( Object.defineProperty({
    __proto__: null,
    default: index$4
  }, Symbol.toStringTag, { value: "Module" }));
  const FEATURE_NAME = "rr";
  const index$3 = {
    name: "Russian Roulette FF display",
    description: "Shows FF on the Russian Roulette page",
    executionTime: StartTime.DocumentBody,
    async shouldRun() {
      return torn_page("page", { sid: "russianRoulette" });
    },
    async run() {
      const rows_wrapper = await wait_for_element(
        '[class*="rowsWrap__"]',
        2e4
      );
      if (!rows_wrapper || !(rows_wrapper instanceof HTMLElement)) {
        return;
      }
      const row_matcher = (node) => {
        const user_info_wrapper = node.querySelector(
          '.honor-text-wrap, [class*="userInfoBlock__"]'
        );
        return user_info_wrapper !== null;
      };
      const row_handler = (options) => {
        if (!options.added) {
          return;
        }
        const honor_bar = options.added.querySelector(".honor-text-wrap");
        if (honor_bar) {
          apply_ff_gauge(honor_bar, FEATURE_NAME);
          return;
        }
        const user_info_wrapper = options.added.querySelector(
          '[class*="userInfoBlock__"]'
        );
        if (user_info_wrapper) {
          apply_ff_gauge(user_info_wrapper, FEATURE_NAME);
          return;
        }
      };
      const rows_monitor = new MonitorElements(
        row_matcher,
        row_handler,
        rows_wrapper,
        true,
        { added: true },
        0
      );
      rows_monitor.start();
    }
  };
  const __vite_glob_0_11 = Object.freeze( Object.defineProperty({
    __proto__: null,
    default: index$3
  }, Symbol.toStringTag, { value: "Module" }));
  const styles = {
    "ff-settings-panel__accordion": "_ff-settings-panel__accordion_6bhvd_1",
    "ff-settings-panel__accordion--glow": "_ff-settings-panel__accordion--glow_6bhvd_10",
    "ff-settings-panel__body": "_ff-settings-panel__body_6bhvd_20",
    "ff-settings-panel__input-row": "_ff-settings-panel__input-row_6bhvd_24",
    "ff-settings-panel__range-row": "_ff-settings-panel__range-row_6bhvd_32",
    "ff-settings-panel__blur": "_ff-settings-panel__blur_6bhvd_38",
    "ff-settings-panel__error-msg": "_ff-settings-panel__error-msg_6bhvd_48",
    "ff-settings-panel__number": "_ff-settings-panel__number_6bhvd_67",
    "ff-settings-panel__api-explanation": "_ff-settings-panel__api-explanation_6bhvd_94",
    "ff-settings-panel__premium-badge": "_ff-settings-panel__premium-badge_6bhvd_107",
    "ff-settings-panel__premium-badge--enabled": "_ff-settings-panel__premium-badge--enabled_6bhvd_117",
    "ff-settings-panel__premium-badge--disabled": "_ff-settings-panel__premium-badge--disabled_6bhvd_121",
    "ff-settings-panel__premium-badge--unknown": "_ff-settings-panel__premium-badge--unknown_6bhvd_125",
    "ff-settings-panel__section": "_ff-settings-panel__section_6bhvd_138",
    "ff-settings-panel__span": "_ff-settings-panel__span_6bhvd_152",
    "ff-settings-panel__cell": "_ff-settings-panel__cell_6bhvd_160",
    "ff-settings-panel__cell--checkbox": "_ff-settings-panel__cell--checkbox_6bhvd_169",
    "ff-settings-panel__api-block": "_ff-settings-panel__api-block_6bhvd_193",
    "ff-settings-panel__api-status-row": "_ff-settings-panel__api-status-row_6bhvd_203",
    "ff-settings-panel__chain-suboptions": "_ff-settings-panel__chain-suboptions_6bhvd_213",
    "ff-settings-panel__chain-wide": "_ff-settings-panel__chain-wide_6bhvd_218",
    "ff-settings-panel__group": "_ff-settings-panel__group_6bhvd_242",
    "ff-settings-panel__marker-size": "_ff-settings-panel__marker-size_6bhvd_256",
    "ff-settings-panel__marker-border-width": "_ff-settings-panel__marker-border-width_6bhvd_257",
    "ff-settings-panel__marker-size-controls": "_ff-settings-panel__marker-size-controls_6bhvd_263",
    "ff-settings-panel__color-scheme": "_ff-settings-panel__color-scheme_6bhvd_276",
    "ff-settings-panel__color-scheme-controls": "_ff-settings-panel__color-scheme-controls_6bhvd_282",
    "ff-settings-panel__actions": "_ff-settings-panel__actions_6bhvd_295",
    "ff-settings-panel__saved-msg": "_ff-settings-panel__saved-msg_6bhvd_304"
  };
  const cls = {
    accordion: styles["ff-settings-panel__accordion"],
    accordionGlow: styles["ff-settings-panel__accordion--glow"],
    body: styles["ff-settings-panel__body"],
    group: styles["ff-settings-panel__group"],
    section: styles["ff-settings-panel__section"],
    span: styles["ff-settings-panel__span"],
    cell: styles["ff-settings-panel__cell"],
    cellCheckbox: styles["ff-settings-panel__cell--checkbox"],
    apiExplanation: styles["ff-settings-panel__api-explanation"],
    apiBlock: styles["ff-settings-panel__api-block"],
    apiStatusRow: styles["ff-settings-panel__api-status-row"],
    premiumBadge: styles["ff-settings-panel__premium-badge"],
    premiumBadgeEnabled: styles["ff-settings-panel__premium-badge--enabled"],
    premiumBadgeDisabled: styles["ff-settings-panel__premium-badge--disabled"],
    premiumBadgeUnknown: styles["ff-settings-panel__premium-badge--unknown"],
    blur: styles["ff-settings-panel__blur"],
    number: styles["ff-settings-panel__number"],
    markerSize: styles["ff-settings-panel__marker-size"],
    markerBorderWidth: styles["ff-settings-panel__marker-border-width"],
    markerSizeControls: styles["ff-settings-panel__marker-size-controls"],
    colorScheme: styles["ff-settings-panel__color-scheme"],
    colorSchemeControls: styles["ff-settings-panel__color-scheme-controls"],
    inputRow: styles["ff-settings-panel__input-row"],
    rangeRow: styles["ff-settings-panel__range-row"],
    errorMsg: styles["ff-settings-panel__error-msg"],
    chainSuboptions: styles["ff-settings-panel__chain-suboptions"],
    chainWide: styles["ff-settings-panel__chain-wide"],
    actions: styles["ff-settings-panel__actions"],
    savedMsg: styles["ff-settings-panel__saved-msg"]
  };
  const DEFAULT_VALUES = {
    apiKey: "",
    lowRange: CONFIG_DEFAULTS.low_ff_range,
    highRange: CONFIG_DEFAULTS.high_ff_range,
    maxRange: CONFIG_DEFAULTS.max_ff_range,
    chainButtonEnabled: CONFIG_DEFAULTS.chain_button_enabled,
    chainLinkType: CONFIG_DEFAULTS.chain_link_type,
    chainTabType: CONFIG_DEFAULTS.chain_tab_type,
    chainFFTarget: CONFIG_DEFAULTS.chain_ff_target,
    chainMinLevel: CONFIG_DEFAULTS.chain_min_level,
    chainMaxLevel: CONFIG_DEFAULTS.chain_max_level,
    chainInactive: CONFIG_DEFAULTS.chain_inactive,
    chainMinFF: CONFIG_DEFAULTS.chain_min_ff,
    chainMaxFF: CONFIG_DEFAULTS.chain_max_ff,
    chainFactionless: CONFIG_DEFAULTS.chain_factionless,
    ffHistoryEnabled: CONFIG_DEFAULTS.ff_history_enabled,
    factionsColDisplay: CONFIG_DEFAULTS.factions_col_display,
    warColDisplay: CONFIG_DEFAULTS.war_col_display,
    debugLogs: CONFIG_DEFAULTS.debug_logs,
    analyticsEnabled: CONFIG_DEFAULTS.analytics_enabled,
    networkInterceptionEnabled: CONFIG_DEFAULTS.network_interception_enabled,
    gaugeMarkerType: CONFIG_DEFAULTS.gauge_marker_type,
    gaugeMarkerScale: CONFIG_DEFAULTS.gauge_marker_scale,
    gaugeMarkerBorderWidth: CONFIG_DEFAULTS.gauge_marker_border_width,
    colorScheme: CONFIG_DEFAULTS.color_scheme,
    warQuickAttackAction: CONFIG_DEFAULTS.war_quick_attack_action,
    statusAttackLinksEnabled: CONFIG_DEFAULTS.status_attack_links_enabled,
    debugDisablePdaHttp: CONFIG_DEFAULTS.debug_disable_pda_http,
    isPremium: null
  };
  function SettingsPanelComponent({
    props,
    drafts,
    isPremium,
    rangeError,
    showSavedMessage,
    onChange,
    onApiKeyBlur,
    onVerify,
    onSave,
    onReset,
    onClearCache,
    onRendered
  }) {
    useEffect(() => {
      onRendered();
    });
    const previewColor = get_palette_for_scheme(drafts.colorScheme)[5] ?? "#888888";
    return jsxs(
      "details",
      {
        className: `${cls.accordion}${!props.apiKey ? ` ${cls.accordionGlow}` : ""} cont-gray border-round`,
        children: [
jsx("summary", { children: "FF Scouter Settings" }),
jsxs("div", { className: cls.body, children: [
jsxs("div", { className: cls.group, children: [
jsx("h4", { children: "API Key & Premium" }),
jsxs("div", { className: cls.section, children: [
jsxs("div", { className: `${cls.apiExplanation} ${cls.span}`, children: [
jsx("strong", { children: "Important:" }),
                  " You must use the SAME exact API key that you use on",
                  " ",
jsx("a", { href: "https://ffscouter.com/", target: "_blank", rel: "noreferrer", children: "ffscouter.com" }),
                  ". ",
jsx("br", {}),
jsx("br", {}),
                  "If you're not sure which API key you used, go to",
                  " ",
jsx(
                    "a",
                    {
                      href: "https://www.torn.com/preferences.php#tab=api",
                      target: "_blank",
                      rel: "noreferrer",
                      children: "your API preferences"
                    }
                  ),
                  " ",
                  'and look for "FFScouter3" in your API key history comments.'
                ] }),
jsxs("div", { className: `${cls.span} ${cls.apiBlock}`, children: [
jsxs("div", { className: cls.cell, children: [
jsx("label", { htmlFor: "api-key", children: "API Key:" }),
jsx(
                      "input",
                      {
                        id: "api-key",
                        type: "text",
                        className: props.apiKey ? cls.blur : "",
                        placeholder: "Paste your key here...",
                        value: drafts.apiKey,
                        onChange,
                        onBlur: onApiKeyBlur
                      }
                    )
                  ] }),
jsxs("div", { className: cls.apiStatusRow, children: [
jsx("label", { htmlFor: "ff-premium-badge", children: "FF Scouter Premium:" }),
jsx(
                      "span",
                      {
                        id: "ff-premium-badge",
                        className: `${cls.premiumBadge} ${isPremium === null ? cls.premiumBadgeUnknown : isPremium ? cls.premiumBadgeEnabled : cls.premiumBadgeDisabled}`,
                        children: isPremium === null ? "Unknown" : isPremium ? "Enabled" : "Disabled"
                      }
                    ),
jsx(
                      "button",
                      {
                        type: "button",
                        className: "torn-btn btn-save",
                        onClick: onVerify,
                        children: "Verify"
                      }
                    )
                  ] })
                ] })
              ] })
            ] }),
jsxs("div", { className: cls.group, children: [
jsx("h4", { children: "Gauge Marker Settings" }),
jsxs("div", { className: cls.section, children: [
jsxs("div", { className: cls.cell, children: [
jsx("label", { htmlFor: "gauge-marker-type", children: "Gauge Marker Style:" }),
jsxs(
                    "select",
                    {
                      id: "gauge-marker-type",
                      value: drafts.gaugeMarkerType,
                      onChange,
                      children: [
jsx("option", { value: "arrow", children: "Arrow (Default)" }),
jsx("option", { value: "bubble_ff", children: "Bubble (FF Score)" }),
jsx("option", { value: "bubble_estimate", children: "Bubble (BS Estimate)" })
                      ]
                    }
                  )
                ] }),
jsxs("div", { className: `${cls.span} ${cls.markerSize}`, children: [
jsx("label", { htmlFor: "gauge-marker-scale", children: "Marker Size:" }),
jsxs("div", { className: cls.markerSizeControls, children: [
jsx(
                      "input",
                      {
                        id: "gauge-marker-scale",
                        type: "range",
                        min: "50",
                        max: "200",
                        step: "5",
                        value: drafts.gaugeMarkerScale,
                        onChange
                      }
                    ),
jsx(
                      "input",
                      {
                        id: "gauge-marker-scale-number",
                        type: "number",
                        min: "50",
                        max: "200",
                        step: "5",
                        className: cls.number,
                        value: drafts.gaugeMarkerScale,
                        onChange
                      }
                    ),
jsx("span", { children: "%" })
                  ] })
                ] }),
jsxs("div", { className: `${cls.span} ${cls.markerBorderWidth}`, children: [
jsx("label", { htmlFor: "gauge-marker-border-width", children: "Border Thickness:" }),
jsxs("div", { className: cls.markerSizeControls, children: [
jsx(
                      "input",
                      {
                        id: "gauge-marker-border-width",
                        type: "range",
                        min: "0",
                        max: "3",
                        step: "0.5",
                        value: drafts.gaugeMarkerBorderWidth,
                        onChange
                      }
                    ),
jsx(
                      "input",
                      {
                        id: "gauge-marker-border-width-number",
                        type: "number",
                        min: "0",
                        max: "3",
                        step: "0.5",
                        className: cls.number,
                        value: drafts.gaugeMarkerBorderWidth,
                        onChange
                      }
                    ),
jsx("span", { children: "px" }),
jsxs(
                      "div",
                      {
                        className: "ffscouter-marker-preview",
                        style: {
                          "--ffscouter-marker-scale": drafts.gaugeMarkerScale / 100
                        },
                        children: [
jsxs(
                            "svg",
                            {
                              className: "ffscouter-preview-arrow",
                              viewBox: FF_ARROW_VIEWBOX,
                              children: [
jsx("title", { children: "Preview Gauge Marker" }),
jsx(
                                  "path",
                                  {
                                    fillRule: "evenodd",
                                    fill: previewColor,
                                    stroke: "#000000",
                                    strokeWidth: drafts.gaugeMarkerBorderWidth,
                                    d: FF_ARROW_PATH_D
                                  }
                                )
                              ]
                            }
                          ),
jsx(
                            "div",
                            {
                              className: "ffscouter-preview-bubble",
                              style: {
                                backgroundColor: previewColor,
                                color: get_contrast_color(previewColor),
                                borderWidth: `${drafts.gaugeMarkerBorderWidth * (drafts.gaugeMarkerScale / 100)}px`
                              },
                              children: "2.34"
                            }
                          )
                        ]
                      }
                    )
                  ] })
                ] }),
jsxs("div", { className: `${cls.span} ${cls.colorScheme}`, children: [
jsx("label", { htmlFor: "color-scheme", children: "Color Scheme:" }),
jsxs("div", { className: cls.colorSchemeControls, children: [
jsxs(
                      "select",
                      {
                        id: "color-scheme",
                        value: drafts.colorScheme,
                        onChange,
                        children: [
jsx("option", { value: "classic", children: "Classic (Default)" }),
jsx("option", { value: "cool_diverging", children: "Cool Diverging" }),
jsx("option", { value: "neon", children: "Neon" }),
jsx("option", { value: "colorblind_safe", children: "Colorblind-Safe" }),
jsx("option", { value: "grayscale", children: "Grayscale" }),
jsx("option", { value: "green_yellow_red", children: "Green-Yellow-Red" }),
jsx("option", { value: "blue_yellow_red", children: "Blue-Yellow-Red" }),
jsx("option", { value: "plasma", children: "Plasma" })
                        ]
                      }
                    ),
jsx("div", { className: "ffscouter-swatch-row", children: get_palette_for_scheme(drafts.colorScheme).map((color) => jsxs(
                      "svg",
                      {
                        className: "ffscouter-swatch",
                        viewBox: FF_ARROW_VIEWBOX,
                        children: [
jsxs("title", { children: [
                            color,
                            " swatch"
                          ] }),
jsx(
                            "path",
                            {
                              fillRule: "evenodd",
                              fill: color,
                              stroke: "#000000",
                              strokeWidth: "1.5",
                              d: FF_ARROW_PATH_D
                            }
                          )
                        ]
                      },
                      color
                    )) })
                  ] })
                ] }),
jsxs("div", { className: `${cls.inputRow} ${cls.span}`, children: [
jsx("label", { htmlFor: "ff-range-low", children: "FF Ranges (Low, High, Max):" }),
jsxs("div", { className: cls.rangeRow, children: [
jsx(
                      "input",
                      {
                        id: "ff-range-low",
                        type: "number",
                        step: "0.1",
                        className: cls.number,
                        value: drafts.lowRange,
                        onChange
                      }
                    ),
jsx("span", { children: "<" }),
jsx(
                      "input",
                      {
                        id: "ff-range-high",
                        type: "number",
                        step: "0.1",
                        className: cls.number,
                        value: drafts.highRange,
                        onChange
                      }
                    ),
jsx("span", { children: "<" }),
jsx(
                      "input",
                      {
                        id: "ff-range-max",
                        type: "number",
                        step: "0.1",
                        className: cls.number,
                        value: drafts.maxRange,
                        onChange
                      }
                    )
                  ] }),
                  rangeError && jsx("div", { className: cls.errorMsg, children: rangeError })
                ] })
              ] })
            ] }),
jsxs("div", { className: cls.group, children: [
jsx("h4", { children: "Feature Toggles" }),
jsxs("div", { className: cls.section, children: [
jsxs("div", { className: `${cls.span} ff-chain-block`, children: [
jsxs("div", { className: `${cls.cell} ${cls.cellCheckbox}`, children: [
jsx(
                      "input",
                      {
                        id: "chain-button-toggle",
                        type: "checkbox",
                        checked: drafts.chainButtonEnabled,
                        onChange
                      }
                    ),
jsx("label", { htmlFor: "chain-button-toggle", children: "Enable Chain Button (Green FF Button)" })
                  ] }),
                  drafts.chainButtonEnabled && jsxs("div", { className: `${cls.section} ${cls.chainSuboptions}`, children: [
jsxs("div", { className: `${cls.cell} ${cls.chainWide}`, children: [
jsx("label", { htmlFor: "chain-link-type", children: "Chain button opens:" }),
jsxs(
                        "select",
                        {
                          id: "chain-link-type",
                          value: drafts.chainLinkType,
                          onChange,
                          children: [
jsx("option", { value: "attack", children: "Attack page" }),
jsx("option", { value: "profile", children: "Profile page" })
                          ]
                        }
                      )
                    ] }),
jsxs("div", { className: `${cls.cell} ${cls.chainWide}`, children: [
jsx("label", { htmlFor: "chain-tab-type", children: "Open in:" }),
jsxs(
                        "select",
                        {
                          id: "chain-tab-type",
                          value: drafts.chainTabType,
                          onChange,
                          children: [
jsx("option", { value: "newtab", children: "New tab" }),
jsx("option", { value: "sametab", children: "Same tab" })
                          ]
                        }
                      )
                    ] }),
jsxs("div", { className: cls.cell, children: [
jsx("label", { htmlFor: "chain-min-level", children: "Min Level:" }),
jsx(
                        "input",
                        {
                          id: "chain-min-level",
                          type: "number",
                          className: cls.number,
                          placeholder: "No min",
                          value: drafts.chainMinLevel === null ? "" : drafts.chainMinLevel,
                          onChange
                        }
                      )
                    ] }),
jsxs("div", { className: cls.cell, children: [
jsx("label", { htmlFor: "chain-max-level", children: "Max Level:" }),
jsx(
                        "input",
                        {
                          id: "chain-max-level",
                          type: "number",
                          className: cls.number,
                          placeholder: "No max",
                          value: drafts.chainMaxLevel === null ? "" : drafts.chainMaxLevel,
                          onChange
                        }
                      )
                    ] }),
jsxs("div", { className: cls.cell, children: [
jsx("label", { htmlFor: "chain-min-ff", children: "Min FF:" }),
jsx(
                        "input",
                        {
                          id: "chain-min-ff",
                          type: "number",
                          step: "0.1",
                          className: cls.number,
                          placeholder: "No min",
                          value: drafts.chainMinFF === null ? "" : drafts.chainMinFF,
                          onChange
                        }
                      )
                    ] }),
jsxs("div", { className: cls.cell, children: [
jsx("label", { htmlFor: "chain-max-ff", children: "Max FF:" }),
jsx(
                        "input",
                        {
                          id: "chain-max-ff",
                          type: "number",
                          step: "0.1",
                          className: cls.number,
                          placeholder: "No max",
                          value: drafts.chainMaxFF,
                          onChange
                        }
                      )
                    ] }),
jsxs(
                      "div",
                      {
                        className: `${cls.cell} ${cls.cellCheckbox} ${cls.chainWide}`,
                        children: [
jsx(
                            "input",
                            {
                              id: "chain-inactive",
                              type: "checkbox",
                              checked: drafts.chainInactive,
                              onChange
                            }
                          ),
jsx("label", { htmlFor: "chain-inactive", children: "Inactive Only (14+ days offline)" })
                        ]
                      }
                    ),
jsxs(
                      "div",
                      {
                        className: `${cls.cell} ${cls.cellCheckbox} ${cls.chainWide}`,
                        children: [
jsx(
                            "input",
                            {
                              id: "chain-factionless",
                              type: "checkbox",
                              checked: drafts.chainFactionless,
                              onChange
                            }
                          ),
jsx("label", { htmlFor: "chain-factionless", children: "Factionless Only" })
                        ]
                      }
                    )
                  ] })
                ] }),
jsxs("div", { className: cls.cell, children: [
jsx("label", { htmlFor: "factions-col-display", children: "Faction Page Shows:" }),
jsxs(
                    "select",
                    {
                      id: "factions-col-display",
                      value: drafts.factionsColDisplay,
                      onChange,
                      children: [
jsx("option", { value: "fair_fight", children: "FF Score" }),
jsx("option", { value: "battle_stats", children: "BS Estimate" }),
jsx("option", { value: "none", children: "None (Hide Column)" })
                      ]
                    }
                  )
                ] }),
jsxs("div", { className: cls.cell, children: [
jsx("label", { htmlFor: "war-col-display", children: "War Page Shows:" }),
jsxs(
                    "select",
                    {
                      id: "war-col-display",
                      value: drafts.warColDisplay,
                      onChange,
                      children: [
jsx("option", { value: "fair_fight", children: "FF Score" }),
jsx("option", { value: "battle_stats", children: "BS Estimate" }),
jsx("option", { value: "none", children: "None (Hide Column)" })
                      ]
                    }
                  )
                ] }),
jsxs("div", { className: `${cls.cell} ${cls.cellCheckbox}`, children: [
jsx(
                    "input",
                    {
                      id: "status-attack-links-toggle",
                      type: "checkbox",
                      checked: drafts.statusAttackLinksEnabled,
                      onChange
                    }
                  ),
jsx("label", { htmlFor: "status-attack-links-toggle", children: "Enable online status indicator quick attack links" })
                ] }),
jsxs("div", { className: cls.cell, children: [
jsx("label", { htmlFor: "war-quick-attack-action", children: "Quick Attack Action:" }),
jsxs(
                    "select",
                    {
                      id: "war-quick-attack-action",
                      value: drafts.warQuickAttackAction,
                      onChange,
                      children: [
jsx("option", { value: "new_tab", children: "New Tab" }),
jsx("option", { value: "current", children: "Same Tab" })
                      ]
                    }
                  )
                ] }),
jsxs("div", { className: `${cls.cell} ${cls.cellCheckbox}`, children: [
jsx(
                    "input",
                    {
                      id: "ff-history-toggle",
                      type: "checkbox",
                      checked: drafts.ffHistoryEnabled,
                      onChange
                    }
                  ),
jsx("label", { htmlFor: "ff-history-toggle", children: "Enable FF History button on profile pages" })
                ] }),
jsx("div", { className: `${cls.span} ff-deprecation-note`, children: jsxs("span", { children: [
                  "War Monitor is no longer supported. Use",
                  " ",
jsx(
                    "a",
                    {
                      target: "_blank",
                      href: "https://greasyfork.org/en/scripts/529238-torn-war-stuff-enhanced",
                      rel: "noreferrer",
                      children: "Torn War Stuff Enhanced"
                    }
                  ),
                  " ",
                  "instead."
                ] }) })
              ] })
            ] }),
jsxs("div", { className: cls.group, children: [
jsx("h4", { children: "Debug Settings" }),
jsxs("div", { className: cls.section, children: [
jsxs("div", { className: `${cls.cell} ${cls.cellCheckbox}`, children: [
jsx(
                    "input",
                    {
                      id: "debug-logs",
                      type: "checkbox",
                      checked: drafts.debugLogs,
                      onChange
                    }
                  ),
jsx("label", { htmlFor: "debug-logs", children: "Enable debug logging" })
                ] }),
jsxs("div", { className: `${cls.cell} ${cls.cellCheckbox}`, children: [
jsx(
                    "input",
                    {
                      id: "analytics-toggle",
                      type: "checkbox",
                      checked: drafts.analyticsEnabled,
                      onChange
                    }
                  ),
jsx("label", { htmlFor: "analytics-toggle", children: "Enable local analytics logging (last 30 days)" })
                ] }),
jsxs("div", { className: `${cls.cell} ${cls.cellCheckbox}`, children: [
jsx(
                    "input",
                    {
                      id: "network-interception-toggle",
                      type: "checkbox",
                      checked: drafts.networkInterceptionEnabled,
                      onChange
                    }
                  ),
jsx("label", { htmlFor: "network-interception-toggle", children: "Enable network request interception (Fetch/XHR/WS)" })
                ] }),
jsxs("div", { className: `${cls.cell} ${cls.cellCheckbox}`, children: [
jsx(
                    "input",
                    {
                      id: "debug-disable-pda-http",
                      type: "checkbox",
                      checked: drafts.debugDisablePdaHttp,
                      onChange
                    }
                  ),
jsx("label", { htmlFor: "debug-disable-pda-http", children: "Disable PDA native HTTP (use GM_xmlhttpRequest instead)" })
                ] })
              ] })
            ] }),
jsxs("div", { className: cls.actions, children: [
jsx("button", { type: "button", className: "torn-btn btn-save", onClick: onSave, children: "Save Settings" }),
jsx(
                "button",
                {
                  type: "button",
                  className: "torn-btn btn-secondary",
                  onClick: onReset,
                  children: "Reset to Defaults"
                }
              ),
jsx(
                "button",
                {
                  type: "button",
                  className: "torn-btn btn-secondary",
                  onClick: onClearCache,
                  children: "Clear FF Cache"
                }
              ),
              showSavedMessage && jsx("span", { className: cls.savedMsg, children: "✓ Saved!" })
            ] })
          ] })
        ]
      }
    );
  }
  class FFSettingsPanel extends HTMLElement {
    constructor() {
      super();
      this._props = { ...DEFAULT_VALUES };
      this._drafts = { ...DEFAULT_VALUES };
      this._rangeError = "";
      this._showSavedMessage = false;
      this._root = null;
      this._updatePromise = Promise.resolve();
      this._resolveUpdate = null;
      this.handleChange = (e) => {
        const target = e.target;
        if (!target) return;
        this._showSavedMessage = false;
        const id = target.id;
        if (id === "api-key") {
          this._drafts.apiKey = target.value;
        } else if (id === "gauge-marker-scale" || id === "gauge-marker-scale-number") {
          const raw = Number(target.value);
          if (!Number.isNaN(raw)) {
            this._drafts.gaugeMarkerScale = Math.min(200, Math.max(50, raw));
          }
        } else if (id === "gauge-marker-border-width" || id === "gauge-marker-border-width-number") {
          const raw = Number(target.value);
          if (!Number.isNaN(raw)) {
            this._drafts.gaugeMarkerBorderWidth = Math.min(3, Math.max(0, raw));
          }
        } else if (id === "ff-range-low") {
          this._drafts.lowRange = Number(target.value);
        } else if (id === "ff-range-high") {
          this._drafts.highRange = Number(target.value);
        } else if (id === "ff-range-max") {
          this._drafts.maxRange = Number(target.value);
        } else if (id === "chain-min-level") {
          this._drafts.chainMinLevel = target.value === "" ? null : Number(target.value);
        } else if (id === "chain-max-level") {
          this._drafts.chainMaxLevel = target.value === "" ? null : Number(target.value);
        } else if (id === "chain-min-ff") {
          this._drafts.chainMinFF = target.value === "" ? null : Number(target.value);
        } else if (id === "chain-max-ff") {
          const num = Number(target.value);
          this._drafts.chainMaxFF = num;
          this._drafts.chainFFTarget = num;
        } else if (id === "gauge-marker-type") {
          this._drafts.gaugeMarkerType = target.value;
        } else if (id === "color-scheme") {
          this._drafts.colorScheme = target.value;
        } else if (id === "chain-link-type") {
          this._drafts.chainLinkType = target.value;
        } else if (id === "chain-tab-type") {
          this._drafts.chainTabType = target.value;
        } else if (id === "war-quick-attack-action") {
          this._drafts.warQuickAttackAction = target.value;
        } else if (id === "factions-col-display") {
          this._drafts.factionsColDisplay = target.value;
        } else if (id === "war-col-display") {
          this._drafts.warColDisplay = target.value;
        } else if (id === "chain-button-toggle") {
          this._drafts.chainButtonEnabled = target.checked;
        } else if (id === "chain-inactive") {
          this._drafts.chainInactive = target.checked;
        } else if (id === "chain-factionless") {
          this._drafts.chainFactionless = target.checked;
        } else if (id === "status-attack-links-toggle") {
          this._drafts.statusAttackLinksEnabled = target.checked;
        } else if (id === "ff-history-toggle") {
          this._drafts.ffHistoryEnabled = target.checked;
        } else if (id === "debug-logs") {
          this._drafts.debugLogs = target.checked;
        } else if (id === "analytics-toggle") {
          this._drafts.analyticsEnabled = target.checked;
        } else if (id === "network-interception-toggle") {
          this._drafts.networkInterceptionEnabled = target.checked;
        } else if (id === "debug-disable-pda-http") {
          this._drafts.debugDisablePdaHttp = target.checked;
        }
        this.render();
      };
      this.handleApiKeyBlur = (e) => {
        this._showSavedMessage = false;
        const val = e.target.value.trim();
        this._drafts.apiKey = val;
        this.dispatchEvent(
          new CustomEvent("ff-save-key", {
            detail: { apiKey: val },
            bubbles: true,
            composed: true
          })
        );
        this.render();
      };
      this.resetDrafts();
    }
    connectedCallback() {
      this._root = createRoot(this);
      this.render();
    }
    disconnectedCallback() {
      this._root?.unmount();
      this._root = null;
    }
    get updateComplete() {
      return this._updatePromise;
    }
    resetDrafts() {
      this._drafts = {
        apiKey: this._props.apiKey,
        lowRange: this._props.lowRange,
        highRange: this._props.highRange,
        maxRange: this._props.maxRange,
        chainButtonEnabled: this._props.chainButtonEnabled,
        chainLinkType: this._props.chainLinkType,
        chainTabType: this._props.chainTabType,
        chainFFTarget: this._props.chainFFTarget,
        chainMinLevel: this._props.chainMinLevel,
        chainMaxLevel: this._props.chainMaxLevel,
        chainInactive: this._props.chainInactive,
        chainMinFF: this._props.chainMinFF,
        chainMaxFF: this._props.chainMaxFF,
        chainFactionless: this._props.chainFactionless,
        ffHistoryEnabled: this._props.ffHistoryEnabled,
        factionsColDisplay: this._props.factionsColDisplay,
        warColDisplay: this._props.warColDisplay,
        debugLogs: this._props.debugLogs,
        analyticsEnabled: this._props.analyticsEnabled,
        networkInterceptionEnabled: this._props.networkInterceptionEnabled,
        gaugeMarkerType: this._props.gaugeMarkerType,
        gaugeMarkerScale: this._props.gaugeMarkerScale,
        gaugeMarkerBorderWidth: this._props.gaugeMarkerBorderWidth,
        colorScheme: this._props.colorScheme,
        warQuickAttackAction: this._props.warQuickAttackAction,
        statusAttackLinksEnabled: this._props.statusAttackLinksEnabled,
        debugDisablePdaHttp: this._props.debugDisablePdaHttp
      };
    }
    render() {
      if (!this._root) return;
      if (!this._resolveUpdate) {
        this._updatePromise = new Promise((resolve) => {
          this._resolveUpdate = resolve;
        });
      }
      this._root.render(
        createElement(SettingsPanelComponent, {
          props: this._props,
          drafts: this._drafts,
          isPremium: this._props.isPremium,
          rangeError: this._rangeError,
          showSavedMessage: this._showSavedMessage,
          onChange: this.handleChange,
          onApiKeyBlur: this.handleApiKeyBlur,
          onVerify: () => {
            this.dispatchEvent(
              new CustomEvent("ff-verify", {
                detail: { apiKey: this._drafts.apiKey },
                bubbles: true,
                composed: true
              })
            );
          },
          onSave: () => {
            this.handleSave();
          },
          onReset: () => {
            if (confirm("Are you sure you want to reset all settings to defaults?")) {
              this.dispatchEvent(
                new CustomEvent("ff-reset", {
                  bubbles: true,
                  composed: true
                })
              );
            }
          },
          onClearCache: () => {
            if (confirm("Are you sure you want to clear all FF Scouter cache?")) {
              this.dispatchEvent(
                new CustomEvent("ff-clear-cache", {
                  bubbles: true,
                  composed: true
                })
              );
            }
          },
          onRendered: () => {
            if (this._resolveUpdate) {
              this._resolveUpdate();
              this._resolveUpdate = null;
            }
          }
        })
      );
    }
    handleSave() {
      const low = this._drafts.lowRange;
      const high = this._drafts.highRange;
      const max = this._drafts.maxRange;
      if (Number.isNaN(low) || Number.isNaN(high) || Number.isNaN(max)) {
        this._rangeError = "FF ranges must be valid numbers";
        this.render();
        return;
      }
      if (low <= 0 || high <= 0 || max <= 0) {
        this._rangeError = "FF ranges must be positive numbers";
        this.render();
        return;
      }
      if (low >= high || high >= max) {
        this._rangeError = "FF ranges must be in ascending order: low < high < max";
        this.render();
        return;
      }
      this._rangeError = "";
      this._showSavedMessage = true;
      this.render();
      setTimeout(() => {
        this._showSavedMessage = false;
        this.render();
      }, 3e3);
      this.dispatchEvent(
        new CustomEvent("ff-save", {
          detail: {
            apiKey: this._drafts.apiKey,
            lowRange: low,
            highRange: high,
            maxRange: max,
            chainButtonEnabled: this._drafts.chainButtonEnabled,
            chainLinkType: this._drafts.chainLinkType,
            chainTabType: this._drafts.chainTabType,
            chainFFTarget: this._drafts.chainFFTarget,
            chainMinLevel: this._drafts.chainMinLevel,
            chainMaxLevel: this._drafts.chainMaxLevel,
            chainInactive: this._drafts.chainInactive,
            chainMinFF: this._drafts.chainMinFF,
            chainMaxFF: this._drafts.chainMaxFF,
            chainFactionless: this._drafts.chainFactionless,
            ffHistoryEnabled: this._drafts.ffHistoryEnabled,
            factionsColDisplay: this._drafts.factionsColDisplay,
            warColDisplay: this._drafts.warColDisplay,
            debugLogs: this._drafts.debugLogs,
            analyticsEnabled: this._drafts.analyticsEnabled,
            networkInterceptionEnabled: this._drafts.networkInterceptionEnabled,
            gaugeMarkerType: this._drafts.gaugeMarkerType,
            gaugeMarkerScale: this._drafts.gaugeMarkerScale,
            gaugeMarkerBorderWidth: this._drafts.gaugeMarkerBorderWidth,
            colorScheme: this._drafts.colorScheme,
            warQuickAttackAction: this._drafts.warQuickAttackAction,
            statusAttackLinksEnabled: this._drafts.statusAttackLinksEnabled,
            debugDisablePdaHttp: this._drafts.debugDisablePdaHttp
          },
          bubbles: true,
          composed: true
        })
      );
    }
get apiKey() {
      return this._props.apiKey;
    }
    set apiKey(val) {
      this._props.apiKey = val;
      this._drafts.apiKey = val;
      this.render();
    }
    get lowRange() {
      return this._props.lowRange;
    }
    set lowRange(val) {
      this._props.lowRange = val;
      this._drafts.lowRange = val;
      this.render();
    }
    get highRange() {
      return this._props.highRange;
    }
    set highRange(val) {
      this._props.highRange = val;
      this._drafts.highRange = val;
      this.render();
    }
    get maxRange() {
      return this._props.maxRange;
    }
    set maxRange(val) {
      this._props.maxRange = val;
      this._drafts.maxRange = val;
      this.render();
    }
    get chainButtonEnabled() {
      return this._props.chainButtonEnabled;
    }
    set chainButtonEnabled(val) {
      this._props.chainButtonEnabled = val;
      this._drafts.chainButtonEnabled = val;
      this.render();
    }
    get chainLinkType() {
      return this._props.chainLinkType;
    }
    set chainLinkType(val) {
      this._props.chainLinkType = val;
      this._drafts.chainLinkType = val;
      this.render();
    }
    get chainTabType() {
      return this._props.chainTabType;
    }
    set chainTabType(val) {
      this._props.chainTabType = val;
      this._drafts.chainTabType = val;
      this.render();
    }
    get chainFFTarget() {
      return this._props.chainFFTarget;
    }
    set chainFFTarget(val) {
      this._props.chainFFTarget = val;
      this._drafts.chainFFTarget = val;
      this.render();
    }
    get chainMinLevel() {
      return this._props.chainMinLevel;
    }
    set chainMinLevel(val) {
      this._props.chainMinLevel = val;
      this._drafts.chainMinLevel = val;
      this.render();
    }
    get chainMaxLevel() {
      return this._props.chainMaxLevel;
    }
    set chainMaxLevel(val) {
      this._props.chainMaxLevel = val;
      this._drafts.chainMaxLevel = val;
      this.render();
    }
    get chainInactive() {
      return this._props.chainInactive;
    }
    set chainInactive(val) {
      this._props.chainInactive = val;
      this._drafts.chainInactive = val;
      this.render();
    }
    get chainMinFF() {
      return this._props.chainMinFF;
    }
    set chainMinFF(val) {
      this._props.chainMinFF = val;
      this._drafts.chainMinFF = val;
      this.render();
    }
    get chainMaxFF() {
      return this._props.chainMaxFF;
    }
    set chainMaxFF(val) {
      this._props.chainMaxFF = val;
      this._drafts.chainMaxFF = val;
      this.render();
    }
    get chainFactionless() {
      return this._props.chainFactionless;
    }
    set chainFactionless(val) {
      this._props.chainFactionless = val;
      this._drafts.chainFactionless = val;
      this.render();
    }
    get ffHistoryEnabled() {
      return this._props.ffHistoryEnabled;
    }
    set ffHistoryEnabled(val) {
      this._props.ffHistoryEnabled = val;
      this._drafts.ffHistoryEnabled = val;
      this.render();
    }
    get factionsColDisplay() {
      return this._props.factionsColDisplay;
    }
    set factionsColDisplay(val) {
      this._props.factionsColDisplay = val;
      this._drafts.factionsColDisplay = val;
      this.render();
    }
    get warColDisplay() {
      return this._props.warColDisplay;
    }
    set warColDisplay(val) {
      this._props.warColDisplay = val;
      this._drafts.warColDisplay = val;
      this.render();
    }
    get debugLogs() {
      return this._props.debugLogs;
    }
    set debugLogs(val) {
      this._props.debugLogs = val;
      this._drafts.debugLogs = val;
      this.render();
    }
    get analyticsEnabled() {
      return this._props.analyticsEnabled;
    }
    set analyticsEnabled(val) {
      this._props.analyticsEnabled = val;
      this._drafts.analyticsEnabled = val;
      this.render();
    }
    get networkInterceptionEnabled() {
      return this._props.networkInterceptionEnabled;
    }
    set networkInterceptionEnabled(val) {
      this._props.networkInterceptionEnabled = val;
      this._drafts.networkInterceptionEnabled = val;
      this.render();
    }
    get gaugeMarkerType() {
      return this._props.gaugeMarkerType;
    }
    set gaugeMarkerType(val) {
      this._props.gaugeMarkerType = val;
      this._drafts.gaugeMarkerType = val;
      this.render();
    }
    get gaugeMarkerScale() {
      return this._props.gaugeMarkerScale;
    }
    set gaugeMarkerScale(val) {
      this._props.gaugeMarkerScale = val;
      this._drafts.gaugeMarkerScale = val;
      this.render();
    }
    get gaugeMarkerBorderWidth() {
      return this._props.gaugeMarkerBorderWidth;
    }
    set gaugeMarkerBorderWidth(val) {
      this._props.gaugeMarkerBorderWidth = val;
      this._drafts.gaugeMarkerBorderWidth = val;
      this.render();
    }
    get colorScheme() {
      return this._props.colorScheme;
    }
    set colorScheme(val) {
      this._props.colorScheme = val;
      this._drafts.colorScheme = val;
      this.render();
    }
    get warQuickAttackAction() {
      return this._props.warQuickAttackAction;
    }
    set warQuickAttackAction(val) {
      this._props.warQuickAttackAction = val;
      this._drafts.warQuickAttackAction = val;
      this.render();
    }
    get statusAttackLinksEnabled() {
      return this._props.statusAttackLinksEnabled;
    }
    set statusAttackLinksEnabled(val) {
      this._props.statusAttackLinksEnabled = val;
      this._drafts.statusAttackLinksEnabled = val;
      this.render();
    }
    get debugDisablePdaHttp() {
      return this._props.debugDisablePdaHttp;
    }
    set debugDisablePdaHttp(val) {
      this._props.debugDisablePdaHttp = val;
      this._drafts.debugDisablePdaHttp = val;
      this.render();
    }
    get isPremium() {
      return this._props.isPremium;
    }
    set isPremium(val) {
      this._props.isPremium = val;
      this.render();
    }
get draftApiKey() {
      return this._drafts.apiKey;
    }
    set draftApiKey(val) {
      this._drafts.apiKey = val;
      this.render();
    }
    get draftLowRange() {
      return this._drafts.lowRange;
    }
    set draftLowRange(val) {
      this._drafts.lowRange = val;
      this.render();
    }
    get draftHighRange() {
      return this._drafts.highRange;
    }
    set draftHighRange(val) {
      this._drafts.highRange = val;
      this.render();
    }
    get draftMaxRange() {
      return this._drafts.maxRange;
    }
    set draftMaxRange(val) {
      this._drafts.maxRange = val;
      this.render();
    }
    get draftChainButtonEnabled() {
      return this._drafts.chainButtonEnabled;
    }
    set draftChainButtonEnabled(val) {
      this._drafts.chainButtonEnabled = val;
      this.render();
    }
    get draftChainLinkType() {
      return this._drafts.chainLinkType;
    }
    set draftChainLinkType(val) {
      this._drafts.chainLinkType = val;
      this.render();
    }
    get draftChainTabType() {
      return this._drafts.chainTabType;
    }
    set draftChainTabType(val) {
      this._drafts.chainTabType = val;
      this.render();
    }
    get draftChainFFTarget() {
      return this._drafts.chainFFTarget;
    }
    set draftChainFFTarget(val) {
      this._drafts.chainFFTarget = val;
      this.render();
    }
    get draftChainMinLevel() {
      return this._drafts.chainMinLevel;
    }
    set draftChainMinLevel(val) {
      this._drafts.chainMinLevel = val;
      this.render();
    }
    get draftChainMaxLevel() {
      return this._drafts.chainMaxLevel;
    }
    set draftChainMaxLevel(val) {
      this._drafts.chainMaxLevel = val;
      this.render();
    }
    get draftChainInactive() {
      return this._drafts.chainInactive;
    }
    set draftChainInactive(val) {
      this._drafts.chainInactive = val;
      this.render();
    }
    get draftChainMinFF() {
      return this._drafts.chainMinFF;
    }
    set draftChainMinFF(val) {
      this._drafts.chainMinFF = val;
      this.render();
    }
    get draftChainMaxFF() {
      return this._drafts.chainMaxFF;
    }
    set draftChainMaxFF(val) {
      this._drafts.chainMaxFF = val;
      this.render();
    }
    get draftChainFactionless() {
      return this._drafts.chainFactionless;
    }
    set draftChainFactionless(val) {
      this._drafts.chainFactionless = val;
      this.render();
    }
    get draftFFHistoryEnabled() {
      return this._drafts.ffHistoryEnabled;
    }
    set draftFFHistoryEnabled(val) {
      this._drafts.ffHistoryEnabled = val;
      this.render();
    }
    get draftFactionsColDisplay() {
      return this._drafts.factionsColDisplay;
    }
    set draftFactionsColDisplay(val) {
      this._drafts.factionsColDisplay = val;
      this.render();
    }
    get draftWarColDisplay() {
      return this._drafts.warColDisplay;
    }
    set draftWarColDisplay(val) {
      this._drafts.warColDisplay = val;
      this.render();
    }
    get draftDebugLogs() {
      return this._drafts.debugLogs;
    }
    set draftDebugLogs(val) {
      this._drafts.debugLogs = val;
      this.render();
    }
    get draftAnalyticsEnabled() {
      return this._drafts.analyticsEnabled;
    }
    set draftAnalyticsEnabled(val) {
      this._drafts.analyticsEnabled = val;
      this.render();
    }
    get draftNetworkInterceptionEnabled() {
      return this._drafts.networkInterceptionEnabled;
    }
    set draftNetworkInterceptionEnabled(val) {
      this._drafts.networkInterceptionEnabled = val;
      this.render();
    }
    get draftGaugeMarkerType() {
      return this._drafts.gaugeMarkerType;
    }
    set draftGaugeMarkerType(val) {
      this._drafts.gaugeMarkerType = val;
      this.render();
    }
    get draftGaugeMarkerScale() {
      return this._drafts.gaugeMarkerScale;
    }
    set draftGaugeMarkerScale(val) {
      this._drafts.gaugeMarkerScale = val;
      this.render();
    }
    get draftGaugeMarkerBorderWidth() {
      return this._drafts.gaugeMarkerBorderWidth;
    }
    set draftGaugeMarkerBorderWidth(val) {
      this._drafts.gaugeMarkerBorderWidth = val;
      this.render();
    }
    get draftColorScheme() {
      return this._drafts.colorScheme;
    }
    set draftColorScheme(val) {
      this._drafts.colorScheme = val;
      this.render();
    }
    get draftWarQuickAttackAction() {
      return this._drafts.warQuickAttackAction;
    }
    set draftWarQuickAttackAction(val) {
      this._drafts.warQuickAttackAction = val;
      this.render();
    }
    get draftStatusAttackLinksEnabled() {
      return this._drafts.statusAttackLinksEnabled;
    }
    set draftStatusAttackLinksEnabled(val) {
      this._drafts.statusAttackLinksEnabled = val;
      this.render();
    }
    get draftDebugDisablePdaHttp() {
      return this._drafts.debugDisablePdaHttp;
    }
    set draftDebugDisablePdaHttp(val) {
      this._drafts.debugDisablePdaHttp = val;
      this.render();
    }
  }
  customElements.define("ff-settings-panel", FFSettingsPanel);
  const V2_PREFIX = "ffscouterv2-";
  const V3_PREFIX = "ffsv3-config";
  const V2_IDB_NAME = "ffscouter-cache";
  function v2_get(key) {
    return localStorage.getItem(V2_PREFIX + key);
  }
  function v3_has(key) {
    return localStorage.getItem(V3_PREFIX + key) !== null;
  }
  function v3_set(key, value) {
    localStorage.setItem(
      V3_PREFIX + key,
      JSON.stringify({ value, expiration: null })
    );
  }
  function migrate_bool(old_key, new_key) {
    if (v3_has(new_key)) return;
    const v = v2_get(old_key);
    if (v !== null) v3_set(new_key, v === "true");
  }
  function migrate_string(old_key, new_key, valid) {
    if (v3_has(new_key)) return;
    const v = v2_get(old_key);
    if (v !== null && valid.includes(v)) v3_set(new_key, v);
  }
  function migrate_float(old_key, new_key) {
    if (v3_has(new_key)) return;
    const v = v2_get(old_key);
    if (v !== null) {
      const n = parseFloat(v);
      if (!Number.isNaN(n)) v3_set(new_key, n);
    }
  }
  function run_migration() {
    if (!v3_has("key")) {
      const old_key = localStorage.getItem("limited_key");
      if (old_key) v3_set("key", old_key);
    }
    migrate_bool("debug-logs", "debug_logs");
    migrate_bool("ff-history-enabled", "ff_history_enabled");
    migrate_bool("chain-button-enabled", "chain_button_enabled");
    migrate_string("factions-col-display", "factions_col_display", [
      "fair_fight",
      "battle_stats",
      "none"
    ]);
    migrate_string("chain-link-type", "chain_link_type", ["attack", "profile"]);
    migrate_string("chain-tab-type", "chain_tab_type", ["newtab", "sametab"]);
    migrate_float("chain-ff-target", "chain_ff_target");
    if (!v3_has("low_ff_range") && !v3_has("high_ff_range") && !v3_has("max_ff_range")) {
      const raw = localStorage.getItem("ffscouterv2-ranges");
      if (raw) {
        try {
          const { low, high, max } = JSON.parse(raw);
          if (typeof low === "number") v3_set("low_ff_range", low);
          if (typeof high === "number") v3_set("high_ff_range", high);
          if (typeof max === "number") v3_set("max_ff_range", max);
        } catch {
        }
      }
    }
    if (!v3_has("faction_filter_state")) {
      const col_display = v2_get("factions-col-display") ?? "battle_stats";
      const sort_key = col_display === "fair_fight" ? "factions-ff-sort-order" : "factions-est-sort-order";
      const sort_order = v2_get(sort_key);
      if (sort_order === "asc" || sort_order === "desc") {
        v3_set("faction_filter_state", {
          sortBy: sort_order === "asc" ? "ff-asc" : "ff-desc"
        });
      }
    }
  }
  function clear_v2_data() {
    localStorage.removeItem("limited_key");
    const to_delete = Object.keys(localStorage).filter(
      (k) => k.startsWith(V2_PREFIX)
    );
    for (const key of to_delete) {
      localStorage.removeItem(key);
    }
    try {
      indexedDB.deleteDatabase(V2_IDB_NAME);
    } catch {
    }
  }
  const index$2 = {
    name: "FF Scouter settings panel",
    description: "Give users a FF Scouter settings box injected on the profile page",
    executionTime: StartTime.DocumentBody,
    async shouldRun() {
      return torn_page("profiles");
    },
    async run() {
      const panel = await create_ff_element("ff-settings-panel");
      if (!panel) {
        toast(
          "FF Scouter settings failed to load. This may be caused by a conflicting browser extension (e.g. AdBlocker Ultimate).",
          TOAST_LEVEL.ERROR
        );
        return;
      }
      panel.apiKey = ffconfig.key || "";
      panel.lowRange = ffconfig.low_ff_range;
      panel.highRange = ffconfig.high_ff_range;
      panel.maxRange = ffconfig.max_ff_range;
      panel.chainButtonEnabled = ffconfig.chain_button_enabled;
      panel.chainLinkType = ffconfig.chain_link_type;
      panel.chainTabType = ffconfig.chain_tab_type;
      panel.chainFFTarget = ffconfig.chain_ff_target;
      panel.chainMinLevel = ffconfig.chain_min_level;
      panel.chainMaxLevel = ffconfig.chain_max_level;
      panel.chainInactive = ffconfig.chain_inactive;
      panel.chainMinFF = ffconfig.chain_min_ff;
      panel.chainMaxFF = ffconfig.chain_max_ff;
      panel.chainFactionless = ffconfig.chain_factionless;
      panel.ffHistoryEnabled = ffconfig.ff_history_enabled;
      panel.factionsColDisplay = ffconfig.factions_col_display;
      panel.warColDisplay = ffconfig.war_col_display;
      panel.debugLogs = ffconfig.debug_logs;
      panel.analyticsEnabled = ffconfig.analytics_enabled;
      panel.networkInterceptionEnabled = ffconfig.network_interception_enabled;
      panel.gaugeMarkerType = ffconfig.gauge_marker_type;
      panel.gaugeMarkerScale = ffconfig.gauge_marker_scale;
      panel.gaugeMarkerBorderWidth = ffconfig.gauge_marker_border_width;
      panel.colorScheme = ffconfig.color_scheme;
      panel.warQuickAttackAction = ffconfig.war_quick_attack_action;
      panel.statusAttackLinksEnabled = ffconfig.status_attack_links_enabled;
      panel.debugDisablePdaHttp = ffconfig.debug_disable_pda_http;
      panel.addEventListener("ff-save", async (e) => {
        const detail = e.detail;
        ffconfig.key = detail.apiKey;
        ffconfig.low_ff_range = detail.lowRange;
        ffconfig.high_ff_range = detail.highRange;
        ffconfig.max_ff_range = detail.maxRange;
        ffconfig.chain_button_enabled = detail.chainButtonEnabled;
        ffconfig.chain_link_type = detail.chainLinkType;
        ffconfig.chain_tab_type = detail.chainTabType;
        ffconfig.chain_ff_target = detail.chainFFTarget;
        ffconfig.chain_min_level = detail.chainMinLevel;
        ffconfig.chain_max_level = detail.chainMaxLevel;
        ffconfig.chain_inactive = detail.chainInactive;
        ffconfig.chain_min_ff = detail.chainMinFF;
        ffconfig.chain_max_ff = detail.chainMaxFF;
        ffconfig.chain_factionless = detail.chainFactionless;
        ffconfig.ff_history_enabled = detail.ffHistoryEnabled;
        ffconfig.factions_col_display = detail.factionsColDisplay;
        ffconfig.war_col_display = detail.warColDisplay;
        ffconfig.debug_logs = detail.debugLogs;
        if (detail.debugLogs) {
          logger.setLevel(LogLevel.DEBUG);
        } else {
          logger.setLevel(LogLevel.INFO);
        }
        ffconfig.analytics_enabled = detail.analyticsEnabled;
        ffconfig.network_interception_enabled = detail.networkInterceptionEnabled;
        ffconfig.gauge_marker_type = detail.gaugeMarkerType;
        ffconfig.gauge_marker_scale = detail.gaugeMarkerScale;
        ffconfig.gauge_marker_border_width = detail.gaugeMarkerBorderWidth;
        document.body.style.setProperty(
          "--ffscouter-marker-scale",
          `${detail.gaugeMarkerScale / 100}`
        );
        ffconfig.color_scheme = detail.colorScheme;
        ffconfig.war_quick_attack_action = detail.warQuickAttackAction;
        ffconfig.status_attack_links_enabled = detail.statusAttackLinksEnabled;
        ffconfig.debug_disable_pda_http = detail.debugDisablePdaHttp;
        panel.isPremium = await check_key_status.is_premium(true);
        toast("Settings saved successfully!");
        window.dispatchEvent(new CustomEvent("ff-config-updated"));
      });
      panel.addEventListener("ff-reset", () => {
        ffconfig.reset();
        panel.apiKey = ffconfig.key || "";
        panel.lowRange = ffconfig.low_ff_range;
        panel.highRange = ffconfig.high_ff_range;
        panel.maxRange = ffconfig.max_ff_range;
        panel.chainButtonEnabled = ffconfig.chain_button_enabled;
        panel.chainLinkType = ffconfig.chain_link_type;
        panel.chainTabType = ffconfig.chain_tab_type;
        panel.chainFFTarget = ffconfig.chain_ff_target;
        panel.chainMinLevel = ffconfig.chain_min_level;
        panel.chainMaxLevel = ffconfig.chain_max_level;
        panel.chainInactive = ffconfig.chain_inactive;
        panel.chainMinFF = ffconfig.chain_min_ff;
        panel.chainMaxFF = ffconfig.chain_max_ff;
        panel.chainFactionless = ffconfig.chain_factionless;
        panel.ffHistoryEnabled = ffconfig.ff_history_enabled;
        panel.factionsColDisplay = ffconfig.factions_col_display;
        panel.warColDisplay = ffconfig.war_col_display;
        panel.debugLogs = ffconfig.debug_logs;
        panel.analyticsEnabled = ffconfig.analytics_enabled;
        panel.networkInterceptionEnabled = ffconfig.network_interception_enabled;
        panel.gaugeMarkerType = ffconfig.gauge_marker_type;
        panel.gaugeMarkerScale = ffconfig.gauge_marker_scale;
        panel.gaugeMarkerBorderWidth = ffconfig.gauge_marker_border_width;
        document.body.style.setProperty(
          "--ffscouter-marker-scale",
          `${ffconfig.gauge_marker_scale / 100}`
        );
        panel.colorScheme = ffconfig.color_scheme;
        panel.warQuickAttackAction = ffconfig.war_quick_attack_action;
        panel.statusAttackLinksEnabled = ffconfig.status_attack_links_enabled;
        panel.debugDisablePdaHttp = ffconfig.debug_disable_pda_http;
        toast("Settings reset to defaults!");
        window.dispatchEvent(new CustomEvent("ff-config-updated"));
      });
      panel.addEventListener("ff-clear-cache", async () => {
        try {
          ffscouter.clear_cache();
          clear_v2_data();
          toast("FF Scouter cache cleared successfully!");
        } catch (err) {
          logger.error("Failed to delete IndexedDB cache", err);
          toast("Failed to clear cache database", TOAST_LEVEL.ERROR);
        }
      });
      panel.addEventListener("ff-save-key", async (e) => {
        const detail = e.detail;
        ffconfig.key = detail.apiKey;
        panel.apiKey = detail.apiKey;
        panel.isPremium = await check_key_status.is_premium(true);
        toast("API key saved successfully!");
        window.dispatchEvent(new CustomEvent("ff-config-updated"));
      });
      panel.addEventListener("ff-verify", async (e) => {
        const detail = e.detail;
        if (!detail.apiKey) {
          toast("Please enter an API key.", TOAST_LEVEL.ERROR);
          return;
        }
        panel.isPremium = null;
        let result = null;
        try {
          result = await check_key(detail.apiKey);
        } catch (err) {
          panel.isPremium = null;
          toast(`${err}`, TOAST_LEVEL.ERROR);
          return;
        }
        if (result == null || result.blank) {
          panel.isPremium = null;
          toast(
            "Problem querying ffscouter.com API. Please wait a few seconds and try again.",
            TOAST_LEVEL.WARNING
          );
          return;
        }
        let message = `FF Scouter not configured. API key (${result.result.key}) not registered.`;
        let level = TOAST_LEVEL.ERROR;
        if (result.result.is_registered) {
          message = `FF Scouter successfully configured. API key (${result.result.key}) was registered on ${format_timestamp(result.result.registered_at)} and last used ${format_timestamp(result.result.last_used)}.`;
          level = TOAST_LEVEL.INFO;
          ffconfig.key = detail.apiKey;
          panel.apiKey = detail.apiKey;
          panel.isPremium = result.result.is_premium;
          check_key_status.clear();
        } else {
          panel.isPremium = false;
        }
        toast(message, level);
      });
      const profileWrapper = await wait_for_element(".profile-wrapper", 15e3);
      if (!profileWrapper) {
        logger.error("Could not find profile wrapper for settings panel");
        return;
      }
      profileWrapper.parentNode?.insertBefore(panel, profileWrapper.nextSibling);
      check_key_status.is_premium(true).then((result) => {
        panel.isPremium = result;
      });
    },
    httpIntercept: {
      before(_url, _init) {
        return void 0;
      },
      after(_bodyText, _response, _ctx) {
        return void 0;
      }
    }
  };
  const __vite_glob_0_12 = Object.freeze( Object.defineProperty({
    __proto__: null,
    default: index$2
  }, Symbol.toStringTag, { value: "Module" }));
  const log$2 = logger.child("feature:status-attack");
  function updateBodyClass() {
    const isEnabled = ffconfig.status_attack_links_enabled;
    if (isEnabled) {
      document.body.setAttribute("data-ff-status-attack-enabled", "true");
    } else {
      document.body.removeAttribute("data-ff-status-attack-enabled");
    }
  }
  function isForumStatusIcon(el) {
    const title = (el.getAttribute("title") || "").toLowerCase();
    const ariaLabel = (el.querySelector("a")?.getAttribute("aria-label") || "").toLowerCase();
    return title.includes("online") || title.includes("offline") || title.includes("away") || title.includes("idle") || ariaLabel.includes("online") || ariaLabel.includes("offline") || ariaLabel.includes("away") || ariaLabel.includes("idle");
  }
  function labelForumStatuses(root = document.body) {
    const elements = root.querySelectorAll(
      'li[id^="icon"][id*="___"].iconShow:not(.ffscouter-forum-status)'
    );
    for (const el of elements) {
      if (el instanceof HTMLElement && isForumStatusIcon(el)) {
        el.classList.add("ffscouter-forum-status");
      }
    }
  }
  let forumObserver = null;
  function setupForumObserver() {
    if (forumObserver) {
      forumObserver.disconnect();
      forumObserver = null;
    }
    if (!torn_page("forums")) return;
    labelForumStatuses();
    forumObserver = new MutationObserver((mutations) => {
      for (const mutation of mutations) {
        for (const node of mutation.addedNodes) {
          if (node instanceof Element) {
            labelForumStatuses(node);
          }
        }
      }
    });
    const content = document.querySelector(".content-wrapper") || document.body;
    forumObserver.observe(content, {
      childList: true,
      subtree: true
    });
    log$2.debug("Forum status observer connected");
  }
  let localUserId = null;
  async function fetchLocalUserId() {
    try {
      const idStr = await getLocalUserId();
      if (idStr) {
        localUserId = parseInt(idStr, 10);
        log$2.debug("Logged-in user ID cached:", localUserId);
      }
    } catch (err) {
      log$2.error("Failed to retrieve logged-in user ID", err);
    }
  }
  function handleStatusClick(e) {
    if (!ffconfig.status_attack_links_enabled) return;
    const target = e.target;
    const statusEl = target.closest(`
    [class*="userStatusWrap__"],
    li[id^="icon"][id*="-profile-"].user-status-16-Online,
    li[id^="icon"][id*="-profile-"].user-status-16-Away,
    li[id^="icon"][id*="-profile-"].user-status-16-Offline,
    #profile-mini-root li[id^="icon"][id*="-mini-profile-"].user-status-16-Online,
    #profile-mini-root li[id^="icon"][id*="-mini-profile-"].user-status-16-Away,
    #profile-mini-root li[id^="icon"][id*="-mini-profile-"].user-status-16-Offline,
    li[id^="icon"][id*="___"].iconShow.ffscouter-forum-status
  `);
    if (!statusEl) return;
    let playerId = null;
    const idAttr = statusEl.id || "";
    if (idAttr?.includes("-profile-")) {
      const match = idAttr.match(/profile-(\d+)$/);
      if (match?.[1]) {
        playerId = parseInt(match[1], 10);
      }
    }
    if (!playerId && idAttr && idAttr.includes("-mini-profile-")) {
      const match = idAttr.match(/mini-profile-(\d+)$/);
      if (match?.[1]) {
        playerId = parseInt(match[1], 10);
      }
    }
    if (!playerId && torn_page("factions")) {
      const row = statusEl.closest(".table-row, .enemy, .your, .member");
      if (row) {
        playerId = get_player_id_in_element(row);
      }
    }
    if (!playerId && torn_page("forums")) {
      const container = statusEl.closest(`
      [class*="poster"], .poster,
      [class*="lastPost"], .lastPost,
      [class*="last-poster"], .last-poster,
      [class*="last-post"], .last-post,
      [class*="starter"], .starter
    `);
      if (container) {
        playerId = get_player_id_in_element(container);
      }
    }
    if (!playerId && torn_page("profiles")) {
      const match = window.location.href.match(/XID=(\d+)/);
      if (match?.[1]) {
        playerId = parseInt(match[1], 10);
      }
    }
    if (!playerId) {
      const miniRoot = document.getElementById("profile-mini-root");
      if (miniRoot?.contains(statusEl)) {
        playerId = get_player_id_in_element(miniRoot);
      }
    }
    if (!playerId) {
      log$2.debug("Failed to extract playerId from status icon click");
      return;
    }
    if (localUserId && playerId === localUserId) {
      log$2.debug("Bypassing click-to-attack: clicked own status icon.");
      return;
    }
    e.preventDefault();
    e.stopPropagation();
    log$2.debug("Initiating attack on user:", playerId);
    const forceNewTab = e.ctrlKey || e.metaKey || e.button === 1;
    open_attack_link(playerId, {
      openInNewTab: forceNewTab ? true : void 0
    });
  }
  const index$1 = {
    name: "Online Status Attack Links",
    description: "Converts online/idle/offline status indicators into clickable quick-attack links.",
    executionTime: StartTime.DocumentBody,
    async shouldRun() {
      return true;
    },
    async run() {
      updateBodyClass();
      fetchLocalUserId();
      const initPage = () => {
        setupForumObserver();
      };
      document.body.addEventListener("click", handleStatusClick, true);
      window.addEventListener("ff-config-updated", () => {
        updateBodyClass();
      });
      if (typeof window !== "undefined") {
        const originalPushState = window.history.pushState;
        const originalReplaceState = window.history.replaceState;
        window.history.pushState = function(...args) {
          originalPushState.apply(this, args);
          setTimeout(initPage, 100);
        };
        window.history.replaceState = function(...args) {
          originalReplaceState.apply(this, args);
          setTimeout(initPage, 100);
        };
        window.addEventListener("popstate", () => {
          setTimeout(initPage, 100);
        });
      }
      initPage();
      log$2.debug("Online Status Attack Links feature installed successfully.");
    }
  };
  const __vite_glob_0_13 = Object.freeze( Object.defineProperty({
    __proto__: null,
    default: index$1
  }, Symbol.toStringTag, { value: "Module" }));
  const log$1 = logger.child("feature:test-feature");
  const index = {
    name: "Test Feature!",
    description: "It's literally a test feature :P",
    executionTime: StartTime.DocumentStart,
    async shouldRun() {
      return true;
    },
    async run() {
      log$1.info("hello world but from feature");
    },
    httpIntercept: {
      before(_url, _init) {
        return void 0;
      },
      after(_bodyText, _response, _ctx) {
        return void 0;
      }
    }
  };
  const __vite_glob_0_14 = Object.freeze( Object.defineProperty({
    __proto__: null,
    default: index
  }, Symbol.toStringTag, { value: "Module" }));
  const modules = Object.assign({
    "./attack/index.ts": __vite_glob_0_0,
    "./deprecation-notice/index.ts": __vite_glob_0_1,
    "./faction/index.ts": __vite_glob_0_2,
    "./fallback/index.ts": __vite_glob_0_3,
    "./ff-button/index.ts": __vite_glob_0_4,
    "./item_market/index.ts": __vite_glob_0_5,
    "./mini-profile-flights/index.ts": __vite_glob_0_6,
    "./mini-profile/index.ts": __vite_glob_0_7,
    "./profile-flights/index.ts": __vite_glob_0_8,
    "./profile-history/index.ts": __vite_glob_0_9,
    "./profile/index.ts": __vite_glob_0_10,
    "./rr/index.ts": __vite_glob_0_11,
    "./settings/index.ts": __vite_glob_0_12,
    "./status-attack/index.ts": __vite_glob_0_13,
    "./test-feature/index.ts": __vite_glob_0_14
  });
  const Features = Object.values(modules).map((mod) => mod.default).filter(
    (feat) => !!feat && "name" in feat && feat.name !== "Test Feature!"
  );
  const httpInterceptors = [];
  function registerHttpInterceptor(interceptor) {
    httpInterceptors.push(interceptor);
    httpInterceptors.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
  }
  const stylesCss = ".ffscouter-gauge{position:relative;display:block;padding:0}.ffscouter-arrow,.ffscouter-preview-arrow{width:var(--ffscouter-arrow-width);object-fit:cover;pointer-events:none}.ffscouter-arrow{position:absolute;transform:translate(-50%,-30%);padding:0;top:0;left:calc(var(--ffscouter-arrow-width) / 2 + var(--band-percent) * (100% - var(--ffscouter-arrow-width)) / 100)}.ffscouter-preview-arrow{display:inline-block;vertical-align:middle}.ffscouter-bubble,.ffscouter-preview-bubble{min-width:2.5882em;height:1.6471em;line-height:1.4118;border:1px solid rgba(0,0,0,.4);border-radius:999px;font-size:var(--ffscouter-bubble-font-size);font-weight:700;font-family:Geneva,Arial,sans-serif;text-align:center;padding:0 .4706em;box-sizing:border-box;white-space:nowrap;display:inline-flex;align-items:center;justify-content:center;text-shadow:0 1px 1px rgba(0,0,0,.5);box-shadow:0 1px 2px #0000004d}.ffscouter-bubble{position:absolute;transform:translate(-50%,-30%);top:0;left:calc(var(--ffscouter-arrow-width) / 2 + var(--band-percent) * (100% - var(--ffscouter-arrow-width)) / 100);pointer-events:none;z-index:10}.ffscouter-preview-bubble{vertical-align:middle}.ffscouter-marker-preview{display:inline-flex;align-items:center;gap:10px;--ffscouter-arrow-width: calc(20px * var(--ffscouter-marker-scale));--ffscouter-bubble-font-size: calc(8.5px * var(--ffscouter-marker-scale))}.ffscouter-mini-desc{padding:0 5px}.ffscouter-swatch-row{display:inline-flex;gap:3px}.ffscouter-swatch{display:inline-block;width:20px;height:13px}body{--ffscouter-bg-color: #f0f0f0;--ffscouter-alt-bg-color: #fff;--ffscouter-border-color: #ccc;--ffscouter-input-color: #ccc;--ffscouter-text-color: #000;--ffscouter-hover-color: #ddd;--ffscouter-glow-color: #4caf50;--ffscouter-success-color: #4caf50;--ffscouter-marker-scale: 1;--ffscouter-arrow-width: calc(20px * var(--ffscouter-marker-scale));--ffscouter-bubble-font-size: calc(8.5px * var(--ffscouter-marker-scale))}body.dark-mode{--ffscouter-bg-color: #333;--ffscouter-alt-bg-color: #383838;--ffscouter-border-color: #444;--ffscouter-input-color: #504f4f;--ffscouter-text-color: #ccc;--ffscouter-hover-color: #555;--ffscouter-glow-color: #4caf50;--ffscouter-success-color: #4caf50}ff-settings-panel{display:block}.profile-status{position:relative}.ff-flight-element{position:absolute;right:10px;bottom:2px;z-index:2}.ff-scouter-profile-flight-info{display:inline-block;text-align:right;font-size:11px;line-height:1.25;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.85)}.profile-status .ff-scouter-profile-flight-info a{color:#fff;text-decoration:underline}.faction-war .ffscouter-cell{float:left!important;width:32px!important;height:20px!important;font-size:11px!important;font-weight:700!important;border-radius:3px!important;box-sizing:border-box!important;margin:7px 4px!important;padding:0!important;text-align:center!important;line-height:20px!important;z-index:10!important}.ffscouter-cell{cursor:pointer!important}.faction-war .ffscouter-header,.table-header .ffscouter-header{float:left!important;width:38px!important;font-size:12px!important;font-weight:700!important;padding:0!important;text-align:center!important;background-color:transparent!important;cursor:pointer!important}.faction-war:has(.ffscouter-header[data-ffscouter-sort]) [class*=sortIcon___]:not(.ffscouter-sort-icon),.members-list:has(.ffscouter-header[data-ffscouter-sort]) [class*=sortIcon___]:not(.ffscouter-sort-icon){visibility:hidden!important}[data-ffscouter-hidden]{display:none!important}.faction-war[data-ffscouter-hide-level=true] .level:not(.ffscouter-cell):not(.ffscouter-header){display:none!important}.faction-war[data-ffscouter-hide-status=true] .status,.faction-war[data-ffscouter-hide-score=true] .points{display:none!important}.faction-war[data-ffscouter-col-display=fair_fight]:not([data-ffscouter-hide-level=true]) .level:not(.ffscouter-cell):not(.ffscouter-header),.faction-war[data-ffscouter-col-display=battle_stats]:not([data-ffscouter-hide-level=true]) .level:not(.ffscouter-cell):not(.ffscouter-header){width:29px!important}.faction-war[data-ffscouter-col-display=fair_fight]:not([data-ffscouter-hide-level=true]) .status,.faction-war[data-ffscouter-col-display=battle_stats]:not([data-ffscouter-hide-level=true]) .status{width:50px!important}.faction-war[data-ffscouter-col-display=fair_fight]:not([data-ffscouter-hide-level=true]) .points,.faction-war[data-ffscouter-col-display=battle_stats]:not([data-ffscouter-hide-level=true]) .points{width:38px!important}.members-list li.enemy:has(>.tt-stats-estimate),.members-list li.your:has(>.tt-stats-estimate),.members-list li.enemy:has(>div.clear~*),.members-list li.your:has(>div.clear~*){padding-bottom:22px!important;position:relative!important}.members-list li.enemy>.tt-stats-estimate,.members-list li.your>.tt-stats-estimate,.members-list li.enemy>div.clear~*,.members-list li.your>div.clear~*{position:absolute!important;bottom:2px!important;left:10px!important;height:18px!important;line-height:18px!important;font-size:11px!important;width:calc(100% - 20px)!important;display:block!important;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}body[data-ff-status-attack-enabled=true] [class*=userStatusWrap__],body[data-ff-status-attack-enabled=true] li[id^=icon][id*=-profile-].user-status-16-Online,body[data-ff-status-attack-enabled=true] li[id^=icon][id*=-profile-].user-status-16-Away,body[data-ff-status-attack-enabled=true] li[id^=icon][id*=-profile-].user-status-16-Offline,body[data-ff-status-attack-enabled=true] #profile-mini-root li[id^=icon][id*=-mini-profile-].user-status-16-Online,body[data-ff-status-attack-enabled=true] #profile-mini-root li[id^=icon][id*=-mini-profile-].user-status-16-Away,body[data-ff-status-attack-enabled=true] #profile-mini-root li[id^=icon][id*=-mini-profile-].user-status-16-Offline,body[data-ff-status-attack-enabled=true] li[id^=icon][id*=___].iconShow.ffscouter-forum-status{cursor:crosshair!important}.d .job-lists-wrap .item>li.company,.d .job-lists-wrap .item>li.director,.d .job-lists-wrap .item>li.salary,.d .job-lists-wrap .item>li.ranks{margin-bottom:0!important;padding-bottom:0!important}";
  importCSS(stylesCss);
  const log = logger.child("boot");
  const INJECTION_KEY = "__FF_SCOUTER_V2_INJECTED__";
  async function safeShouldRun(feat) {
    try {
      return await feat.shouldRun();
    } catch (err) {
      log.error(`shouldRun() threw for feature "${feat.name}"`, err);
      return false;
    }
  }
  function safeRun(feat) {
    feat.run().catch((err) => {
      log.error(`run() threw for feature "${feat.name}"`, err);
    });
  }
  async function main() {
    if (document.documentElement.hasAttribute(INJECTION_KEY)) {
      log.info("Script already injected");
      return;
    }
    document.documentElement.setAttribute(INJECTION_KEY, "1");
    log.info("Initializing", "3.0.1");
    run_migration();
    if (ffscouter.analytics_enabled) {
      if (typeof unsafeWindow !== "undefined") {
        unsafeWindow.ffscouter = ffscouter;
      }
      window.ffscouter = ffscouter;
    }
    for (const feat of Features) {
      if (feat.executionTime === StartTime.DocumentStart && await safeShouldRun(feat)) {
        if (feat.httpIntercept) {
          feat.httpIntercept.name = feat.name;
          registerHttpInterceptor(feat.httpIntercept);
        }
        safeRun(feat);
      }
    }
    await wait_for_body(1e4);
    for (const feat of Features) {
      if (feat.executionTime === StartTime.DocumentBody && await safeShouldRun(feat)) {
        if (feat.httpIntercept) {
          feat.httpIntercept.name = feat.name;
          registerHttpInterceptor(feat.httpIntercept);
        }
        safeRun(feat);
      }
    }
  }
  main();

})();