Torn War Stuff Enhanced Beta

Show travel status and hospital time and sort by hospital time on war page.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Torn War Stuff Enhanced Beta
// @namespace    namespace-beta
// @version      2.0-beta8
// @author       xentac
// @description  Show travel status and hospital time and sort by hospital time on war page.
// @license      MIT
// @match        https://www.torn.com/factions.php*
// @connect      api.torn.com
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// @run-at       document-end
// ==/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));};

  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() {
      if (typeof window !== "undefined") {
        if (window.flutter_inappwebview) {
          this.isPDA = true;
        }
        window.addEventListener("flutterInAppWebViewPlatformReady", () => {
          window.flutter_inappwebview.callHandler("isTornPDA").then((response) => {
            if (response?.isTornPDA) {
              this.isPDA = true;
            }
          }).catch(() => {
          });
        });
      }
    }
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 (typeof arg === "object" && arg !== null) {
          try {
            return JSON.stringify(arg, null, 2);
          } catch {
            return String(arg);
          }
        }
        return arg;
      });
    }
  }
  const logger = new Logger(
    "TWSE",
    1
);
  const log$7 = logger.child("storage");
  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$7.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$7.warn(`Key '${key}' has invalid JSON in it.`);
          this.remove(key);
          return null;
        }
        if (item.expiration && Date.now() > item.expiration) {
          this.remove(key);
          log$7.debug(`Key '${key}' has expired.`);
          return null;
        }
        return item.value;
      } catch (error) {
        log$7.error(`Error retrieving item '${key}':`, error);
        return null;
      }
    }
remove(key) {
      try {
        localStorage.removeItem(this.prefix + key);
      } catch (error) {
        log$7.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$7.error("Error clearing storage:", error);
      }
    }
  }
  class Config {
    constructor(prefix = "twse-config-") {
      this.legacyPrefix = "xentac-torn_war_stuff_enhanced-";
      this.storage = new Storage(prefix);
      logger.setLevel(this.debug_logs ? LogLevel.DEBUG : LogLevel.INFO);
    }
get apiKey() {
      const key = this.storage.get(
        "apikey"
);
      if (key) {
        return key;
      }
      const legacyKey = localStorage.getItem(`${this.legacyPrefix}apikey`);
      if (legacyKey) {
        return legacyKey;
      }
      return "";
    }
set apiKey(val) {
      this.storage.set("apikey", val);
      localStorage.setItem(`${this.legacyPrefix}apikey`, val);
    }
get debug_logs() {
      return this.storage.get(
        "debug_logs"
) ?? false;
    }
    set debug_logs(val) {
      this.storage.set("debug_logs", val);
      logger.setLevel(val ? LogLevel.DEBUG : LogLevel.INFO);
    }
get war_sorting() {
      return this.storage.get(
        "war_sorting"
) ?? true;
    }
    set war_sorting(val) {
      this.storage.set("war_sorting", val);
    }
get bubble_position() {
      return this.storage.get(
        "bubble_position"
) ?? null;
    }
    set bubble_position(val) {
      if (val === null) {
        this.storage.remove(
          "bubble_position"
);
      } else {
        this.storage.set("bubble_position", val);
      }
    }
get bubble_minimized() {
      return this.storage.get(
        "bubble_minimized"
) ?? false;
    }
    set bubble_minimized(val) {
      this.storage.set("bubble_minimized", val);
    }
get bubble_enabled() {
      return this.storage.get(
        "bubble_enabled"
) ?? true;
    }
    set bubble_enabled(val) {
      this.storage.set("bubble_enabled", val);
    }
get copy_button_enabled() {
      return this.storage.get(
        "copy_button_enabled"
) ?? true;
    }
    set copy_button_enabled(val) {
      this.storage.set("copy_button_enabled", val);
    }
reset() {
      this.storage.remove(
        "debug_logs"
);
      this.storage.remove(
        "war_sorting"
);
      this.storage.remove(
        "bubble_position"
);
      this.storage.remove(
        "bubble_minimized"
);
      this.storage.remove(
        "bubble_enabled"
);
      this.storage.remove(
        "copy_button_enabled"
);
    }
  }
  const twseconfig = new Config();
  var StartTime = ((StartTime2) => {
    StartTime2[StartTime2["DocumentStart"] = 0] = "DocumentStart";
    StartTime2[StartTime2["DocumentBody"] = 1] = "DocumentBody";
    StartTime2[StartTime2["DocumentEnd"] = 2] = "DocumentEnd";
    return StartTime2;
  })(StartTime || {});
  const log$6 = logger.child("feature:key-manager");
  const KeyManagerFeature = {
    name: "Key Manager",
    description: "Allows the user to register their Torn API key via a Tampermonkey menu command",
    executionTime: StartTime.DocumentEnd,
    shouldRun() {
      return true;
    },
    run() {
      if (typeof GM_registerMenuCommand !== "undefined") {
        GM_registerMenuCommand("Torn War Stuff: Register Key", () => {
          const defaultPrompt = twseconfig.apiKey;
          const key = prompt("Please enter a Torn API Key:", defaultPrompt);
          if (key !== null) {
            const trimmedKey = key.trim();
            if (trimmedKey.length === 16 || trimmedKey === "") {
              twseconfig.apiKey = trimmedKey;
              log$6.info("Successfully updated API Key registration");
              alert("Torn API key registered successfully!");
            } else {
              alert("Invalid key! A Torn API key must be exactly 16 characters.");
            }
          }
        });
        log$6.debug("Tampermonkey menu command 'Register Key' initialized");
      } else {
        log$6.warn("GM_registerMenuCommand is not available in this context.");
      }
    }
  };
  const __vite_glob_0_0 = Object.freeze( Object.defineProperty({
    __proto__: null,
    default: KeyManagerFeature
  }, Symbol.toStringTag, { value: "Module" }));
  const log$5 = logger.child("dom");
  function waitForElement(selector, timeoutMs = 15e3) {
    return new Promise((resolve) => {
      const existing = document.querySelector(selector);
      if (existing) {
        return resolve(existing);
      }
      const observer = new MutationObserver((_2, obs) => {
        const el = document.querySelector(selector);
        if (el) {
          obs.disconnect();
          resolve(el);
        }
      });
      observer.observe(document.documentElement, {
        childList: true,
        subtree: true
      });
      if (timeoutMs > 0) {
        setTimeout(() => {
          observer.disconnect();
          log$5.debug(`Timeout waiting for element selector: '${selector}'`);
          resolve(null);
        }, timeoutMs);
      }
    });
  }
  function observeElement(target, callback, options = { childList: true, subtree: true }) {
    const observer = new MutationObserver((mutations, obs) => {
      if (!target.isConnected) {
        cleanup();
        return;
      }
      callback(mutations, obs);
    });
    const intervalId = setInterval(() => {
      if (!target.isConnected) {
        cleanup();
      }
    }, 1e4);
    function cleanup() {
      clearInterval(intervalId);
      observer.disconnect();
    }
    const originalDisconnect = observer.disconnect.bind(observer);
    observer.disconnect = () => {
      clearInterval(intervalId);
      originalDisconnect();
    };
    observer.observe(target, options);
    return observer;
  }
  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);
    };
  }
  const t$2 = globalThis, e$2 = t$2.ShadowRoot && (void 0 === t$2.ShadyCSS || t$2.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype, s$2 = Symbol(), o$4 = new WeakMap();
  let n$3 = class n {
    constructor(t2, e2, o2) {
      if (this._$cssResult$ = true, o2 !== s$2) throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");
      this.cssText = t2, this.t = e2;
    }
    get styleSheet() {
      let t2 = this.o;
      const s2 = this.t;
      if (e$2 && void 0 === t2) {
        const e2 = void 0 !== s2 && 1 === s2.length;
        e2 && (t2 = o$4.get(s2)), void 0 === t2 && ((this.o = t2 = new CSSStyleSheet()).replaceSync(this.cssText), e2 && o$4.set(s2, t2));
      }
      return t2;
    }
    toString() {
      return this.cssText;
    }
  };
  const r$4 = (t2) => new n$3("string" == typeof t2 ? t2 : t2 + "", void 0, s$2), S$1 = (s2, o2) => {
    if (e$2) s2.adoptedStyleSheets = o2.map((t2) => t2 instanceof CSSStyleSheet ? t2 : t2.styleSheet);
    else for (const e2 of o2) {
      const o3 = document.createElement("style"), n3 = t$2.litNonce;
      void 0 !== n3 && o3.setAttribute("nonce", n3), o3.textContent = e2.cssText, s2.appendChild(o3);
    }
  }, c$2 = e$2 ? (t2) => t2 : (t2) => t2 instanceof CSSStyleSheet ? ((t3) => {
    let e2 = "";
    for (const s2 of t3.cssRules) e2 += s2.cssText;
    return r$4(e2);
  })(t2) : t2;
  const { is: i$2, defineProperty: e$1, getOwnPropertyDescriptor: h$1, getOwnPropertyNames: r$3, getOwnPropertySymbols: o$3, getPrototypeOf: n$2 } = Object, a$1 = globalThis, c$1 = a$1.trustedTypes, l$1 = c$1 ? c$1.emptyScript : "", p$1 = a$1.reactiveElementPolyfillSupport, d$1 = (t2, s2) => t2, u$1 = { toAttribute(t2, s2) {
    switch (s2) {
      case Boolean:
        t2 = t2 ? l$1 : null;
        break;
      case Object:
      case Array:
        t2 = null == t2 ? t2 : JSON.stringify(t2);
    }
    return t2;
  }, fromAttribute(t2, s2) {
    let i2 = t2;
    switch (s2) {
      case Boolean:
        i2 = null !== t2;
        break;
      case Number:
        i2 = null === t2 ? null : Number(t2);
        break;
      case Object:
      case Array:
        try {
          i2 = JSON.parse(t2);
        } catch (t3) {
          i2 = null;
        }
    }
    return i2;
  } }, f$1 = (t2, s2) => !i$2(t2, s2), b$1 = { attribute: true, type: String, converter: u$1, reflect: false, useDefault: false, hasChanged: f$1 };
  Symbol.metadata ??= Symbol("metadata"), a$1.litPropertyMetadata ??= new WeakMap();
  let y$1 = class y extends HTMLElement {
    static addInitializer(t2) {
      this._$Ei(), (this.l ??= []).push(t2);
    }
    static get observedAttributes() {
      return this.finalize(), this._$Eh && [...this._$Eh.keys()];
    }
    static createProperty(t2, s2 = b$1) {
      if (s2.state && (s2.attribute = false), this._$Ei(), this.prototype.hasOwnProperty(t2) && ((s2 = Object.create(s2)).wrapped = true), this.elementProperties.set(t2, s2), !s2.noAccessor) {
        const i2 = Symbol(), h2 = this.getPropertyDescriptor(t2, i2, s2);
        void 0 !== h2 && e$1(this.prototype, t2, h2);
      }
    }
    static getPropertyDescriptor(t2, s2, i2) {
      const { get: e2, set: r2 } = h$1(this.prototype, t2) ?? { get() {
        return this[s2];
      }, set(t3) {
        this[s2] = t3;
      } };
      return { get: e2, set(s3) {
        const h2 = e2?.call(this);
        r2?.call(this, s3), this.requestUpdate(t2, h2, i2);
      }, configurable: true, enumerable: true };
    }
    static getPropertyOptions(t2) {
      return this.elementProperties.get(t2) ?? b$1;
    }
    static _$Ei() {
      if (this.hasOwnProperty(d$1("elementProperties"))) return;
      const t2 = n$2(this);
      t2.finalize(), void 0 !== t2.l && (this.l = [...t2.l]), this.elementProperties = new Map(t2.elementProperties);
    }
    static finalize() {
      if (this.hasOwnProperty(d$1("finalized"))) return;
      if (this.finalized = true, this._$Ei(), this.hasOwnProperty(d$1("properties"))) {
        const t3 = this.properties, s2 = [...r$3(t3), ...o$3(t3)];
        for (const i2 of s2) this.createProperty(i2, t3[i2]);
      }
      const t2 = this[Symbol.metadata];
      if (null !== t2) {
        const s2 = litPropertyMetadata.get(t2);
        if (void 0 !== s2) for (const [t3, i2] of s2) this.elementProperties.set(t3, i2);
      }
      this._$Eh = new Map();
      for (const [t3, s2] of this.elementProperties) {
        const i2 = this._$Eu(t3, s2);
        void 0 !== i2 && this._$Eh.set(i2, t3);
      }
      this.elementStyles = this.finalizeStyles(this.styles);
    }
    static finalizeStyles(s2) {
      const i2 = [];
      if (Array.isArray(s2)) {
        const e2 = new Set(s2.flat(1 / 0).reverse());
        for (const s3 of e2) i2.unshift(c$2(s3));
      } else void 0 !== s2 && i2.push(c$2(s2));
      return i2;
    }
    static _$Eu(t2, s2) {
      const i2 = s2.attribute;
      return false === i2 ? void 0 : "string" == typeof i2 ? i2 : "string" == typeof t2 ? t2.toLowerCase() : void 0;
    }
    constructor() {
      super(), this._$Ep = void 0, this.isUpdatePending = false, this.hasUpdated = false, this._$Em = null, this._$Ev();
    }
    _$Ev() {
      this._$ES = new Promise((t2) => this.enableUpdating = t2), this._$AL = new Map(), this._$E_(), this.requestUpdate(), this.constructor.l?.forEach((t2) => t2(this));
    }
    addController(t2) {
      (this._$EO ??= new Set()).add(t2), void 0 !== this.renderRoot && this.isConnected && t2.hostConnected?.();
    }
    removeController(t2) {
      this._$EO?.delete(t2);
    }
    _$E_() {
      const t2 = new Map(), s2 = this.constructor.elementProperties;
      for (const i2 of s2.keys()) this.hasOwnProperty(i2) && (t2.set(i2, this[i2]), delete this[i2]);
      t2.size > 0 && (this._$Ep = t2);
    }
    createRenderRoot() {
      const t2 = this.shadowRoot ?? this.attachShadow(this.constructor.shadowRootOptions);
      return S$1(t2, this.constructor.elementStyles), t2;
    }
    connectedCallback() {
      this.renderRoot ??= this.createRenderRoot(), this.enableUpdating(true), this._$EO?.forEach((t2) => t2.hostConnected?.());
    }
    enableUpdating(t2) {
    }
    disconnectedCallback() {
      this._$EO?.forEach((t2) => t2.hostDisconnected?.());
    }
    attributeChangedCallback(t2, s2, i2) {
      this._$AK(t2, i2);
    }
    _$ET(t2, s2) {
      const i2 = this.constructor.elementProperties.get(t2), e2 = this.constructor._$Eu(t2, i2);
      if (void 0 !== e2 && true === i2.reflect) {
        const h2 = (void 0 !== i2.converter?.toAttribute ? i2.converter : u$1).toAttribute(s2, i2.type);
        this._$Em = t2, null == h2 ? this.removeAttribute(e2) : this.setAttribute(e2, h2), this._$Em = null;
      }
    }
    _$AK(t2, s2) {
      const i2 = this.constructor, e2 = i2._$Eh.get(t2);
      if (void 0 !== e2 && this._$Em !== e2) {
        const t3 = i2.getPropertyOptions(e2), h2 = "function" == typeof t3.converter ? { fromAttribute: t3.converter } : void 0 !== t3.converter?.fromAttribute ? t3.converter : u$1;
        this._$Em = e2;
        const r2 = h2.fromAttribute(s2, t3.type);
        this[e2] = r2 ?? this._$Ej?.get(e2) ?? r2, this._$Em = null;
      }
    }
    requestUpdate(t2, s2, i2, e2 = false, h2) {
      if (void 0 !== t2) {
        const r2 = this.constructor;
        if (false === e2 && (h2 = this[t2]), i2 ??= r2.getPropertyOptions(t2), !((i2.hasChanged ?? f$1)(h2, s2) || i2.useDefault && i2.reflect && h2 === this._$Ej?.get(t2) && !this.hasAttribute(r2._$Eu(t2, i2)))) return;
        this.C(t2, s2, i2);
      }
      false === this.isUpdatePending && (this._$ES = this._$EP());
    }
    C(t2, s2, { useDefault: i2, reflect: e2, wrapped: h2 }, r2) {
      i2 && !(this._$Ej ??= new Map()).has(t2) && (this._$Ej.set(t2, r2 ?? s2 ?? this[t2]), true !== h2 || void 0 !== r2) || (this._$AL.has(t2) || (this.hasUpdated || i2 || (s2 = void 0), this._$AL.set(t2, s2)), true === e2 && this._$Em !== t2 && (this._$Eq ??= new Set()).add(t2));
    }
    async _$EP() {
      this.isUpdatePending = true;
      try {
        await this._$ES;
      } catch (t3) {
        Promise.reject(t3);
      }
      const t2 = this.scheduleUpdate();
      return null != t2 && await t2, !this.isUpdatePending;
    }
    scheduleUpdate() {
      return this.performUpdate();
    }
    performUpdate() {
      if (!this.isUpdatePending) return;
      if (!this.hasUpdated) {
        if (this.renderRoot ??= this.createRenderRoot(), this._$Ep) {
          for (const [t4, s3] of this._$Ep) this[t4] = s3;
          this._$Ep = void 0;
        }
        const t3 = this.constructor.elementProperties;
        if (t3.size > 0) for (const [s3, i2] of t3) {
          const { wrapped: t4 } = i2, e2 = this[s3];
          true !== t4 || this._$AL.has(s3) || void 0 === e2 || this.C(s3, void 0, i2, e2);
        }
      }
      let t2 = false;
      const s2 = this._$AL;
      try {
        t2 = this.shouldUpdate(s2), t2 ? (this.willUpdate(s2), this._$EO?.forEach((t3) => t3.hostUpdate?.()), this.update(s2)) : this._$EM();
      } catch (s3) {
        throw t2 = false, this._$EM(), s3;
      }
      t2 && this._$AE(s2);
    }
    willUpdate(t2) {
    }
    _$AE(t2) {
      this._$EO?.forEach((t3) => t3.hostUpdated?.()), this.hasUpdated || (this.hasUpdated = true, this.firstUpdated(t2)), this.updated(t2);
    }
    _$EM() {
      this._$AL = new Map(), this.isUpdatePending = false;
    }
    get updateComplete() {
      return this.getUpdateComplete();
    }
    getUpdateComplete() {
      return this._$ES;
    }
    shouldUpdate(t2) {
      return true;
    }
    update(t2) {
      this._$Eq &&= this._$Eq.forEach((t3) => this._$ET(t3, this[t3])), this._$EM();
    }
    updated(t2) {
    }
    firstUpdated(t2) {
    }
  };
  y$1.elementStyles = [], y$1.shadowRootOptions = { mode: "open" }, y$1[d$1("elementProperties")] = new Map(), y$1[d$1("finalized")] = new Map(), p$1?.({ ReactiveElement: y$1 }), (a$1.reactiveElementVersions ??= []).push("2.1.2");
  const t$1 = globalThis, i$1 = (t2) => t2, s$1 = t$1.trustedTypes, e = s$1 ? s$1.createPolicy("lit-html", { createHTML: (t2) => t2 }) : void 0, h = "$lit$", o$2 = `lit$${Math.random().toFixed(9).slice(2)}$`, n$1 = "?" + o$2, r$2 = `<${n$1}>`, l = document, c = () => l.createComment(""), a = (t2) => null === t2 || "object" != typeof t2 && "function" != typeof t2, u = Array.isArray, d = (t2) => u(t2) || "function" == typeof t2?.[Symbol.iterator], f = "[ 	\n\f\r]", v = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g, _ = /-->/g, m = />/g, p = RegExp(`>|${f}(?:([^\\s"'>=/]+)(${f}*=${f}*(?:[^ 	
\f\r"'\`<>=]|("|')|))|$)`, "g"), g = /'/g, $ = /"/g, y2 = /^(?:script|style|textarea|title)$/i, x = (t2) => (i2, ...s2) => ({ _$litType$: t2, strings: i2, values: s2 }), b = x(1), E = Symbol.for("lit-noChange"), A = Symbol.for("lit-nothing"), C = new WeakMap(), P = l.createTreeWalker(l, 129);
  function V(t2, i2) {
    if (!u(t2) || !t2.hasOwnProperty("raw")) throw Error("invalid template strings array");
    return void 0 !== e ? e.createHTML(i2) : i2;
  }
  const N = (t2, i2) => {
    const s2 = t2.length - 1, e2 = [];
    let n3, l2 = 2 === i2 ? "<svg>" : 3 === i2 ? "<math>" : "", c2 = v;
    for (let i3 = 0; i3 < s2; i3++) {
      const s3 = t2[i3];
      let a2, u2, d2 = -1, f2 = 0;
      for (; f2 < s3.length && (c2.lastIndex = f2, u2 = c2.exec(s3), null !== u2); ) f2 = c2.lastIndex, c2 === v ? "!--" === u2[1] ? c2 = _ : void 0 !== u2[1] ? c2 = m : void 0 !== u2[2] ? (y2.test(u2[2]) && (n3 = RegExp("</" + u2[2], "g")), c2 = p) : void 0 !== u2[3] && (c2 = p) : c2 === p ? ">" === u2[0] ? (c2 = n3 ?? v, d2 = -1) : void 0 === u2[1] ? d2 = -2 : (d2 = c2.lastIndex - u2[2].length, a2 = u2[1], c2 = void 0 === u2[3] ? p : '"' === u2[3] ? $ : g) : c2 === $ || c2 === g ? c2 = p : c2 === _ || c2 === m ? c2 = v : (c2 = p, n3 = void 0);
      const x2 = c2 === p && t2[i3 + 1].startsWith("/>") ? " " : "";
      l2 += c2 === v ? s3 + r$2 : d2 >= 0 ? (e2.push(a2), s3.slice(0, d2) + h + s3.slice(d2) + o$2 + x2) : s3 + o$2 + (-2 === d2 ? i3 : x2);
    }
    return [V(t2, l2 + (t2[s2] || "<?>") + (2 === i2 ? "</svg>" : 3 === i2 ? "</math>" : "")), e2];
  };
  class S {
    constructor({ strings: t2, _$litType$: i2 }, e2) {
      let r2;
      this.parts = [];
      let l2 = 0, a2 = 0;
      const u2 = t2.length - 1, d2 = this.parts, [f2, v2] = N(t2, i2);
      if (this.el = S.createElement(f2, e2), P.currentNode = this.el.content, 2 === i2 || 3 === i2) {
        const t3 = this.el.content.firstChild;
        t3.replaceWith(...t3.childNodes);
      }
      for (; null !== (r2 = P.nextNode()) && d2.length < u2; ) {
        if (1 === r2.nodeType) {
          if (r2.hasAttributes()) for (const t3 of r2.getAttributeNames()) if (t3.endsWith(h)) {
            const i3 = v2[a2++], s2 = r2.getAttribute(t3).split(o$2), e3 = /([.?@])?(.*)/.exec(i3);
            d2.push({ type: 1, index: l2, name: e3[2], strings: s2, ctor: "." === e3[1] ? I : "?" === e3[1] ? L : "@" === e3[1] ? z : H }), r2.removeAttribute(t3);
          } else t3.startsWith(o$2) && (d2.push({ type: 6, index: l2 }), r2.removeAttribute(t3));
          if (y2.test(r2.tagName)) {
            const t3 = r2.textContent.split(o$2), i3 = t3.length - 1;
            if (i3 > 0) {
              r2.textContent = s$1 ? s$1.emptyScript : "";
              for (let s2 = 0; s2 < i3; s2++) r2.append(t3[s2], c()), P.nextNode(), d2.push({ type: 2, index: ++l2 });
              r2.append(t3[i3], c());
            }
          }
        } else if (8 === r2.nodeType) if (r2.data === n$1) d2.push({ type: 2, index: l2 });
        else {
          let t3 = -1;
          for (; -1 !== (t3 = r2.data.indexOf(o$2, t3 + 1)); ) d2.push({ type: 7, index: l2 }), t3 += o$2.length - 1;
        }
        l2++;
      }
    }
    static createElement(t2, i2) {
      const s2 = l.createElement("template");
      return s2.innerHTML = t2, s2;
    }
  }
  function M(t2, i2, s2 = t2, e2) {
    if (i2 === E) return i2;
    let h2 = void 0 !== e2 ? s2._$Co?.[e2] : s2._$Cl;
    const o2 = a(i2) ? void 0 : i2._$litDirective$;
    return h2?.constructor !== o2 && (h2?._$AO?.(false), void 0 === o2 ? h2 = void 0 : (h2 = new o2(t2), h2._$AT(t2, s2, e2)), void 0 !== e2 ? (s2._$Co ??= [])[e2] = h2 : s2._$Cl = h2), void 0 !== h2 && (i2 = M(t2, h2._$AS(t2, i2.values), h2, e2)), i2;
  }
  class R {
    constructor(t2, i2) {
      this._$AV = [], this._$AN = void 0, this._$AD = t2, this._$AM = i2;
    }
    get parentNode() {
      return this._$AM.parentNode;
    }
    get _$AU() {
      return this._$AM._$AU;
    }
    u(t2) {
      const { el: { content: i2 }, parts: s2 } = this._$AD, e2 = (t2?.creationScope ?? l).importNode(i2, true);
      P.currentNode = e2;
      let h2 = P.nextNode(), o2 = 0, n3 = 0, r2 = s2[0];
      for (; void 0 !== r2; ) {
        if (o2 === r2.index) {
          let i3;
          2 === r2.type ? i3 = new k(h2, h2.nextSibling, this, t2) : 1 === r2.type ? i3 = new r2.ctor(h2, r2.name, r2.strings, this, t2) : 6 === r2.type && (i3 = new Z(h2, this, t2)), this._$AV.push(i3), r2 = s2[++n3];
        }
        o2 !== r2?.index && (h2 = P.nextNode(), o2++);
      }
      return P.currentNode = l, e2;
    }
    p(t2) {
      let i2 = 0;
      for (const s2 of this._$AV) void 0 !== s2 && (void 0 !== s2.strings ? (s2._$AI(t2, s2, i2), i2 += s2.strings.length - 2) : s2._$AI(t2[i2])), i2++;
    }
  }
  class k {
    get _$AU() {
      return this._$AM?._$AU ?? this._$Cv;
    }
    constructor(t2, i2, s2, e2) {
      this.type = 2, this._$AH = A, this._$AN = void 0, this._$AA = t2, this._$AB = i2, this._$AM = s2, this.options = e2, this._$Cv = e2?.isConnected ?? true;
    }
    get parentNode() {
      let t2 = this._$AA.parentNode;
      const i2 = this._$AM;
      return void 0 !== i2 && 11 === t2?.nodeType && (t2 = i2.parentNode), t2;
    }
    get startNode() {
      return this._$AA;
    }
    get endNode() {
      return this._$AB;
    }
    _$AI(t2, i2 = this) {
      t2 = M(this, t2, i2), a(t2) ? t2 === A || null == t2 || "" === t2 ? (this._$AH !== A && this._$AR(), this._$AH = A) : t2 !== this._$AH && t2 !== E && this._(t2) : void 0 !== t2._$litType$ ? this.$(t2) : void 0 !== t2.nodeType ? this.T(t2) : d(t2) ? this.k(t2) : this._(t2);
    }
    O(t2) {
      return this._$AA.parentNode.insertBefore(t2, this._$AB);
    }
    T(t2) {
      this._$AH !== t2 && (this._$AR(), this._$AH = this.O(t2));
    }
    _(t2) {
      this._$AH !== A && a(this._$AH) ? this._$AA.nextSibling.data = t2 : this.T(l.createTextNode(t2)), this._$AH = t2;
    }
    $(t2) {
      const { values: i2, _$litType$: s2 } = t2, e2 = "number" == typeof s2 ? this._$AC(t2) : (void 0 === s2.el && (s2.el = S.createElement(V(s2.h, s2.h[0]), this.options)), s2);
      if (this._$AH?._$AD === e2) this._$AH.p(i2);
      else {
        const t3 = new R(e2, this), s3 = t3.u(this.options);
        t3.p(i2), this.T(s3), this._$AH = t3;
      }
    }
    _$AC(t2) {
      let i2 = C.get(t2.strings);
      return void 0 === i2 && C.set(t2.strings, i2 = new S(t2)), i2;
    }
    k(t2) {
      u(this._$AH) || (this._$AH = [], this._$AR());
      const i2 = this._$AH;
      let s2, e2 = 0;
      for (const h2 of t2) e2 === i2.length ? i2.push(s2 = new k(this.O(c()), this.O(c()), this, this.options)) : s2 = i2[e2], s2._$AI(h2), e2++;
      e2 < i2.length && (this._$AR(s2 && s2._$AB.nextSibling, e2), i2.length = e2);
    }
    _$AR(t2 = this._$AA.nextSibling, s2) {
      for (this._$AP?.(false, true, s2); t2 !== this._$AB; ) {
        const s3 = i$1(t2).nextSibling;
        i$1(t2).remove(), t2 = s3;
      }
    }
    setConnected(t2) {
      void 0 === this._$AM && (this._$Cv = t2, this._$AP?.(t2));
    }
  }
  class H {
    get tagName() {
      return this.element.tagName;
    }
    get _$AU() {
      return this._$AM._$AU;
    }
    constructor(t2, i2, s2, e2, h2) {
      this.type = 1, this._$AH = A, this._$AN = void 0, this.element = t2, this.name = i2, this._$AM = e2, this.options = h2, s2.length > 2 || "" !== s2[0] || "" !== s2[1] ? (this._$AH = Array(s2.length - 1).fill(new String()), this.strings = s2) : this._$AH = A;
    }
    _$AI(t2, i2 = this, s2, e2) {
      const h2 = this.strings;
      let o2 = false;
      if (void 0 === h2) t2 = M(this, t2, i2, 0), o2 = !a(t2) || t2 !== this._$AH && t2 !== E, o2 && (this._$AH = t2);
      else {
        const e3 = t2;
        let n3, r2;
        for (t2 = h2[0], n3 = 0; n3 < h2.length - 1; n3++) r2 = M(this, e3[s2 + n3], i2, n3), r2 === E && (r2 = this._$AH[n3]), o2 ||= !a(r2) || r2 !== this._$AH[n3], r2 === A ? t2 = A : t2 !== A && (t2 += (r2 ?? "") + h2[n3 + 1]), this._$AH[n3] = r2;
      }
      o2 && !e2 && this.j(t2);
    }
    j(t2) {
      t2 === A ? this.element.removeAttribute(this.name) : this.element.setAttribute(this.name, t2 ?? "");
    }
  }
  class I extends H {
    constructor() {
      super(...arguments), this.type = 3;
    }
    j(t2) {
      this.element[this.name] = t2 === A ? void 0 : t2;
    }
  }
  class L extends H {
    constructor() {
      super(...arguments), this.type = 4;
    }
    j(t2) {
      this.element.toggleAttribute(this.name, !!t2 && t2 !== A);
    }
  }
  class z extends H {
    constructor(t2, i2, s2, e2, h2) {
      super(t2, i2, s2, e2, h2), this.type = 5;
    }
    _$AI(t2, i2 = this) {
      if ((t2 = M(this, t2, i2, 0) ?? A) === E) return;
      const s2 = this._$AH, e2 = t2 === A && s2 !== A || t2.capture !== s2.capture || t2.once !== s2.once || t2.passive !== s2.passive, h2 = t2 !== A && (s2 === A || e2);
      e2 && this.element.removeEventListener(this.name, this, s2), h2 && this.element.addEventListener(this.name, this, t2), this._$AH = t2;
    }
    handleEvent(t2) {
      "function" == typeof this._$AH ? this._$AH.call(this.options?.host ?? this.element, t2) : this._$AH.handleEvent(t2);
    }
  }
  class Z {
    constructor(t2, i2, s2) {
      this.element = t2, this.type = 6, this._$AN = void 0, this._$AM = i2, this.options = s2;
    }
    get _$AU() {
      return this._$AM._$AU;
    }
    _$AI(t2) {
      M(this, t2);
    }
  }
  const B = t$1.litHtmlPolyfillSupport;
  B?.(S, k), (t$1.litHtmlVersions ??= []).push("3.3.3");
  const D = (t2, i2, s2) => {
    const e2 = s2?.renderBefore ?? i2;
    let h2 = e2._$litPart$;
    if (void 0 === h2) {
      const t3 = s2?.renderBefore ?? null;
      e2._$litPart$ = h2 = new k(i2.insertBefore(c(), t3), t3, void 0, s2 ?? {});
    }
    return h2._$AI(t2), h2;
  };
  const s = globalThis;
  class i extends y$1 {
    constructor() {
      super(...arguments), this.renderOptions = { host: this }, this._$Do = void 0;
    }
    createRenderRoot() {
      const t2 = super.createRenderRoot();
      return this.renderOptions.renderBefore ??= t2.firstChild, t2;
    }
    update(t2) {
      const r2 = this.render();
      this.hasUpdated || (this.renderOptions.isConnected = this.isConnected), super.update(t2), this._$Do = D(r2, this.renderRoot, this.renderOptions);
    }
    connectedCallback() {
      super.connectedCallback(), this._$Do?.setConnected(true);
    }
    disconnectedCallback() {
      super.disconnectedCallback(), this._$Do?.setConnected(false);
    }
    render() {
      return E;
    }
  }
  i._$litElement$ = true, i["finalized"] = true, s.litElementHydrateSupport?.({ LitElement: i });
  const o$1 = s.litElementPolyfillSupport;
  o$1?.({ LitElement: i });
  (s.litElementVersions ??= []).push("4.2.2");
  const t = (t2) => (e2, o2) => {
    void 0 !== o2 ? o2.addInitializer(() => {
      customElements.define(t2, e2);
    }) : customElements.define(t2, e2);
  };
  const o = { attribute: true, type: String, converter: u$1, reflect: false, hasChanged: f$1 }, r$1 = (t2 = o, e2, r2) => {
    const { kind: n3, metadata: i2 } = r2;
    let s2 = globalThis.litPropertyMetadata.get(i2);
    if (void 0 === s2 && globalThis.litPropertyMetadata.set(i2, s2 = new Map()), "setter" === n3 && ((t2 = Object.create(t2)).wrapped = true), s2.set(r2.name, t2), "accessor" === n3) {
      const { name: o2 } = r2;
      return { set(r3) {
        const n4 = e2.get.call(this);
        e2.set.call(this, r3), this.requestUpdate(o2, n4, t2, true, r3);
      }, init(e3) {
        return void 0 !== e3 && this.C(o2, void 0, t2, e3), e3;
      } };
    }
    if ("setter" === n3) {
      const { name: o2 } = r2;
      return function(r3) {
        const n4 = this[o2];
        e2.call(this, r3), this.requestUpdate(o2, n4, t2, true, r3);
      };
    }
    throw Error("Unsupported decorator location: " + n3);
  };
  function n2(t2) {
    return (e2, o2) => "object" == typeof o2 ? r$1(t2, e2, o2) : ((t3, e3, o3) => {
      const r2 = e3.hasOwnProperty(o3);
      return e3.constructor.createProperty(o3, t3), r2 ? Object.getOwnPropertyDescriptor(e3, o3) : void 0;
    })(t2, e2, o2);
  }
  function r(r2) {
    return n2({ ...r2, state: true, attribute: false });
  }
  var __defProp = Object.defineProperty;
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
  var __decorateClass = (decorators, target, key, kind) => {
    var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
    for (var i2 = decorators.length - 1, decorator; i2 >= 0; i2--)
      if (decorator = decorators[i2])
        result = (kind ? decorator(target, key, result) : decorator(result)) || result;
    if (kind && result) __defProp(target, key, result);
    return result;
  };
  let TWSESettingsPanel = class extends i {
    constructor() {
      super(...arguments);
      this.apiKey = "";
      this.warSorting = true;
      this.bubbleEnabled = true;
      this.copyButtonEnabled = true;
      this.debugLogs = false;
      this.draftApiKey = "";
      this.draftWarSorting = true;
      this.draftBubbleEnabled = true;
      this.draftCopyButtonEnabled = true;
      this.draftDebugLogs = false;
      this.showSavedMessage = false;
    }
    createRenderRoot() {
      return this;
    }
    connectedCallback() {
      super.connectedCallback();
      this.resetDrafts();
    }
    willUpdate(changedProperties) {
      if (changedProperties.has("apiKey") || changedProperties.has("warSorting") || changedProperties.has("bubbleEnabled") || changedProperties.has("copyButtonEnabled") || changedProperties.has("debugLogs")) {
        this.resetDrafts();
      }
    }
    resetDrafts() {
      this.draftApiKey = this.apiKey;
      this.draftWarSorting = this.warSorting;
      this.draftBubbleEnabled = this.bubbleEnabled;
      this.draftCopyButtonEnabled = this.copyButtonEnabled;
      this.draftDebugLogs = this.debugLogs;
    }
    handleSave() {
      this.showSavedMessage = true;
      setTimeout(() => {
        this.showSavedMessage = false;
      }, 3e3);
      this.dispatchEvent(
        new CustomEvent("twse-save", {
          detail: {
            apiKey: this.draftApiKey,
            warSorting: this.draftWarSorting,
            bubbleEnabled: this.draftBubbleEnabled,
            copyButtonEnabled: this.draftCopyButtonEnabled,
            debugLogs: this.draftDebugLogs
          },
          bubbles: true,
          composed: true
        })
      );
    }
    handleReset() {
      if (confirm("Are you sure you want to reset all settings to defaults?")) {
        this.dispatchEvent(
          new CustomEvent("twse-reset", {
            bubbles: true,
            composed: true
          })
        );
      }
    }
    handleClearCache() {
      if (confirm("Are you sure you want to clear all TWSE war monitoring cache?")) {
        this.dispatchEvent(
          new CustomEvent("twse-clear-cache", {
            bubbles: true,
            composed: true
          })
        );
      }
    }
    onKeyInput(e2) {
      this.draftApiKey = e2.target.value.trim();
      this.showSavedMessage = false;
    }
    onWarSortingChange(e2) {
      this.draftWarSorting = e2.target.checked;
      this.showSavedMessage = false;
    }
    onBubbleEnabledChange(e2) {
      this.draftBubbleEnabled = e2.target.checked;
      this.showSavedMessage = false;
    }
    onCopyButtonEnabledChange(e2) {
      this.draftCopyButtonEnabled = e2.target.checked;
      this.showSavedMessage = false;
    }
    onDebugLogsChange(e2) {
      this.draftDebugLogs = e2.target.checked;
      this.showSavedMessage = false;
    }
    render() {
      return b`
      <details class="accordion cont-gray border-round twse-settings-details">
        <summary style="cursor: pointer; font-weight: bold; user-select: none;">
          Torn War Stuff Enhanced Settings
        </summary>

        <div style="margin-top: 15px;">
          <!-- API Key Section -->
          <div class="input-row">
            <label for="twse-api-key">Torn API Key:</label>
            <input
              id="twse-api-key"
              type="text"
              class="${this.apiKey ? "blur-mode" : ""}"
              placeholder="Paste 16-char API key here..."
              maxlength="16"
              .value=${this.draftApiKey}
              @input=${this.onKeyInput}
            />
            <div class="twse-api-explanation">
              <strong>Info:</strong> Provide a valid 16-character public API key to pull faction war information and real-time member statuses.
            </div>
          </div>

          <!-- Feature Toggles -->
          <h3>Feature Toggles:</h3>

          <!-- War sorting toggle -->
          <div class="input-row-inline">
            <input
              id="twse-war-sorting"
              type="checkbox"
              .checked=${this.draftWarSorting}
              @change=${this.onWarSortingChange}
            />
            <label for="twse-war-sorting">Enable War Page Sorting (automatically sorts okay/traveling/hospitalized members)</label>
          </div>

          <!-- Chain bubble toggle -->
          <div class="input-row-inline">
            <input
              id="twse-chain-bubble-toggle"
              type="checkbox"
              .checked=${this.draftBubbleEnabled}
              @change=${this.onBubbleEnabledChange}
            />
            <label for="twse-chain-bubble-toggle">Show Floating Chain Bubble (displays real-time countdown of your faction's chain)</label>
          </div>

          <!-- Copy button toggle -->
          <div class="input-row-inline">
            <input
              id="twse-copy-btn-toggle"
              type="checkbox"
              .checked=${this.draftCopyButtonEnabled}
              @change=${this.onCopyButtonEnabledChange}
            />
            <label for="twse-copy-btn-toggle">Enable "Copy Name [ID]" Button next to members</label>
          </div>

          <!-- Debug logs toggle -->
          <div class="input-row-inline">
            <input
              id="twse-debug-logs"
              type="checkbox"
              .checked=${this.draftDebugLogs}
              @change=${this.onDebugLogsChange}
            />
            <label for="twse-debug-logs">Enable Developer/Debug Logging</label>
          </div>

          <!-- Action Buttons -->
          <div style="display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 10px; margin-top: 20px;">
            <button class="torn-btn btn-save" @click=${this.handleSave}>
              Save Settings
            </button>
            <button class="torn-btn btn-secondary" @click=${this.handleReset}>
              Reset to Defaults
            </button>
            <button class="torn-btn btn-secondary" @click=${this.handleClearCache}>
              Clear Cache
            </button>
            ${this.showSavedMessage ? b`<span style="color: #4CAF50; font-weight: bold; margin-left: 10px;">✓ Saved!</span>` : ""}
          </div>
        </div>
      </details>
    `;
    }
  };
  __decorateClass([
    n2({ type: String })
  ], TWSESettingsPanel.prototype, "apiKey", 2);
  __decorateClass([
    n2({ type: Boolean })
  ], TWSESettingsPanel.prototype, "warSorting", 2);
  __decorateClass([
    n2({ type: Boolean })
  ], TWSESettingsPanel.prototype, "bubbleEnabled", 2);
  __decorateClass([
    n2({ type: Boolean })
  ], TWSESettingsPanel.prototype, "copyButtonEnabled", 2);
  __decorateClass([
    n2({ type: Boolean })
  ], TWSESettingsPanel.prototype, "debugLogs", 2);
  __decorateClass([
    r()
  ], TWSESettingsPanel.prototype, "draftApiKey", 2);
  __decorateClass([
    r()
  ], TWSESettingsPanel.prototype, "draftWarSorting", 2);
  __decorateClass([
    r()
  ], TWSESettingsPanel.prototype, "draftBubbleEnabled", 2);
  __decorateClass([
    r()
  ], TWSESettingsPanel.prototype, "draftCopyButtonEnabled", 2);
  __decorateClass([
    r()
  ], TWSESettingsPanel.prototype, "draftDebugLogs", 2);
  __decorateClass([
    r()
  ], TWSESettingsPanel.prototype, "showSavedMessage", 2);
  TWSESettingsPanel = __decorateClass([
    t("twse-settings-panel")
  ], TWSESettingsPanel);
  const log$4 = logger.child("feature:settings");
  const SettingsFeature = {
    name: "Settings",
    description: "Renders and handles the settings panel at the bottom of the faction page",
    executionTime: StartTime.DocumentEnd,
    shouldRun() {
      return window.location.href.includes("factions.php");
    },
    async run() {
      const factionsContainer = await waitForElement("#factions");
      if (!factionsContainer) {
        log$4.warn("Failed to find #factions element to append settings panel");
        return;
      }
      const panel = document.createElement("twse-settings-panel");
      panel.apiKey = twseconfig.apiKey;
      panel.warSorting = twseconfig.war_sorting;
      panel.bubbleEnabled = twseconfig.bubble_enabled;
      panel.copyButtonEnabled = twseconfig.copy_button_enabled;
      panel.debugLogs = twseconfig.debug_logs;
      panel.addEventListener("twse-save", (e2) => {
        const detail = e2.detail;
        twseconfig.apiKey = detail.apiKey;
        twseconfig.war_sorting = detail.warSorting;
        twseconfig.bubble_enabled = detail.bubbleEnabled;
        twseconfig.copy_button_enabled = detail.copyButtonEnabled;
        twseconfig.debug_logs = detail.debugLogs;
        log$4.info("Settings saved successfully");
        window.dispatchEvent(new CustomEvent("twse-config-updated"));
      });
      panel.addEventListener("twse-reset", () => {
        twseconfig.reset();
        panel.apiKey = twseconfig.apiKey;
        panel.warSorting = twseconfig.war_sorting;
        panel.bubbleEnabled = twseconfig.bubble_enabled;
        panel.copyButtonEnabled = twseconfig.copy_button_enabled;
        panel.debugLogs = twseconfig.debug_logs;
        log$4.info("Settings reset to defaults");
        window.dispatchEvent(new CustomEvent("twse-config-updated"));
      });
      panel.addEventListener("twse-clear-cache", () => {
        log$4.info("Settings cleared caching successfully");
        window.dispatchEvent(new CustomEvent("twse-clear-cache"));
      });
      factionsContainer.appendChild(panel);
      log$4.debug("Settings panel successfully appended to #factions container");
    }
  };
  const __vite_glob_0_1 = Object.freeze( Object.defineProperty({
    __proto__: null,
    default: SettingsFeature
  }, Symbol.toStringTag, { value: "Module" }));
  const log$3 = logger.child("api");
  class TornApiClient {
    constructor() {
      this.baseUrl = "https://api.torn.com/faction/";
    }
async fetchFactionData(factionId) {
      const tornpdakey = "###PDA-APIKEY###";
      let key = twseconfig.apiKey;
      if (!tornpdakey.startsWith("###PDA")) {
        key = tornpdakey;
      }
      if (!key || key.length !== 16) {
        log$3.warn("Torn API key is invalid or not set. Skipping API request.");
        return null;
      }
      const url = `${this.baseUrl}${factionId}?selections=basic,chain&key=${key}&comment=TornWarStuffEnhanced`;
      try {
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error(`HTTP Error status: ${response.status}`);
        }
        const data = await response.json();
        if (data.error) {
          log$3.error(
            `Torn API returned error code ${data.error.code}: ${data.error.error}`
          );
          return data;
        }
        return data;
      } catch (e2) {
        log$3.error(
          `Network or parse error fetching faction ${factionId} data:`,
          e2
        );
        return null;
      }
    }
isUnrecoverableError(errorCode) {
      const unrecoverable = [0, 1, 2, 3, 4, 6, 7, 10, 12, 13, 14, 16, 18, 21];
      return unrecoverable.includes(errorCode);
    }
isRateLimitError(errorCode) {
      const rateLimits = [5, 8, 9];
      return rateLimits.includes(errorCode);
    }
  }
  const tornApi = new TornApiClient();
  const log$2 = logger.child("cache");
  class FactionCache {
    constructor() {
      this.prefix = "xentac-torn_war_stuff_enhanced-status-";
      this.ttlMs = 1e4;
    }

get(factionId) {
      try {
        const key = `${this.prefix}${factionId}`;
        const cacheStr = localStorage.getItem(key);
        if (!cacheStr) {
          return null;
        }
        const parsed = JSON.parse(cacheStr);
        if (!parsed || typeof parsed.timestamp !== "number" || !parsed.status) {
          this.remove(factionId);
          return null;
        }
        const now = Date.now();
        if (now - parsed.timestamp > this.ttlMs) {
          this.remove(factionId);
          return null;
        }
        return parsed.status;
      } catch (e2) {
        log$2.error(`Error reading cached status for faction ${factionId}:`, e2);
        this.remove(factionId);
        return null;
      }
    }
set(factionId, status) {
      try {
        const key = `${this.prefix}${factionId}`;
        const cacheItem = {
          timestamp: Date.now(),
          status
        };
        localStorage.setItem(key, JSON.stringify(cacheItem));
      } catch (e2) {
        log$2.error(`Error caching status for faction ${factionId}:`, e2);
      }
    }
remove(factionId) {
      try {
        const key = `${this.prefix}${factionId}`;
        localStorage.removeItem(key);
      } catch (e2) {
        log$2.error(`Error removing cached status for faction ${factionId}:`, e2);
      }
    }
cleanExpired() {
      try {
        const now = Date.now();
        let cleanedCount = 0;
        for (let i2 = 0; i2 < localStorage.length; i2++) {
          const key = localStorage.key(i2);
          if (!key || !key.startsWith(this.prefix)) {
            continue;
          }
          const value = localStorage.getItem(key);
          if (!value) {
            continue;
          }
          try {
            const parsed = JSON.parse(value);
            if (!parsed || now - parsed.timestamp > this.ttlMs) {
              localStorage.removeItem(key);
              cleanedCount++;
              i2--;
            }
          } catch {
            localStorage.removeItem(key);
            cleanedCount++;
            i2--;
          }
        }
        if (cleanedCount > 0) {
          log$2.info(`Cleaned ${cleanedCount} expired cached statuses`);
        }
      } catch (e2) {
        log$2.error("Error sweeping expired cached statuses:", e2);
      }
    }
clearAll() {
      try {
        const keysToRemove = [];
        for (let i2 = 0; i2 < localStorage.length; i2++) {
          const key = localStorage.key(i2);
          if (key?.startsWith(this.prefix)) {
            keysToRemove.push(key);
          }
        }
        keysToRemove.forEach((key) => {
          localStorage.removeItem(key);
        });
        log$2.info(`Cleared all cached faction statuses`);
      } catch (e2) {
        log$2.error("Error clearing cached statuses:", e2);
      }
    }
  }
  const factionCache = new FactionCache();
  function getCurrentTimeSec() {
    const w = window;
    if (typeof w.getCurrentTimestamp === "function") {
      try {
        return w.getCurrentTimestamp() / 1e3;
      } catch (_e) {
      }
    }
    return Date.now() / 1e3;
  }
  function pad_with_zeros(n3) {
    if (n3 < 10) {
      return `0${n3}`;
    }
    return String(n3);
  }
  function calc_delta(delta, include_seconds = true, pad_hour = true) {
    const s2 = Math.floor(delta % 60);
    const m2 = Math.floor(delta / 60 % 60);
    const h2 = Math.floor(delta / 60 / 60);
    const hour_minute = `${pad_hour ? pad_with_zeros(h2) : h2}:${pad_with_zeros(m2)}`;
    return hour_minute + (include_seconds ? `:${pad_with_zeros(s2)}` : "");
  }
  function formatChainTimeout(seconds) {
    const isNegative = seconds < 0;
    const absSeconds = Math.abs(seconds);
    const m2 = Math.floor(absSeconds / 60);
    const s2 = Math.floor(absSeconds % 60);
    return `${isNegative ? "-" : ""}${m2}:${pad_with_zeros(s2)}`;
  }
  function formatChainCooldown(seconds) {
    if (seconds <= 0) return "0:00";
    const s2 = Math.floor(seconds % 60);
    const m2 = Math.floor(seconds / 60 % 60);
    const h2 = Math.floor(seconds / 3600 % 24);
    const d2 = Math.floor(seconds / 86400);
    if (d2 > 0) return `${d2}d${h2}h`;
    if (h2 > 0) return `${h2}h${m2}m`;
    if (m2 >= 10) return `${m2}m`;
    return `${m2}:${pad_with_zeros(s2)}`;
  }
  const DEST_TABLE = new Map([
    ["mexico", "MX"],
    ["cayman islands", "CI"],
    ["canada", "CA"],
    ["hawaii", "HI"],
    ["united kingdom", "UK"],
    ["argentina", "AR"],
    ["switzerland", "SW"],
    ["japan", "JP"],
    ["china", "CN"],
    ["uae", "UAE"],
    ["south africa", "SA"],
    ["torn", "TC"]
  ]);
  function shorten_destination(dest) {
    return DEST_TABLE.get(dest.toLowerCase().trim()) ?? dest;
  }
  const TRAVELING_REGEX = /Traveling from ([\S ]+) to ([\S ]+)/;
  function extract_destinations_from_description(description) {
    if (!description.startsWith("Traveling from")) {
      return null;
    }
    const match = TRAVELING_REGEX.exec(description);
    if (!match) {
      return null;
    }
    return {
      from: shorten_destination(match[1]),
      to: shorten_destination(match[2])
    };
  }
  const stylesCss = ".members-list li:has(div.status[data-twse-highlight=true]){background-color:#99eb99!important}.members-list li:has(div.status[data-twse-status-differs=true]){background-color:#c4974c!important}.members-list div.status[data-twse-traveling=true]:after{color:#696026!important}:root .dark-mode .members-list li:has(div.status[data-twse-highlight=true]){background-color:#446944!important}:root .dark-mode .members-list li:has(div.status[data-twse-status-differs=true]){background-color:#795315!important}:root .dark-mode .members-list div.status[data-twse-traveling=true]:after{color:#ffed76!important}.members-list div.status{position:relative!important;color:transparent!important}.members-list div.status:after{content:var(--twse-content);position:absolute;top:0;left:0;width:calc(100% - 10px);height:100%;background:inherit;display:flex;right:10px;justify-content:flex-end;align-items:center;white-space:nowrap!important}.members-list .ok.status:after{color:var(--user-status-green-color)}.members-list .not-ok.status:after{color:var(--user-status-red-color)}.members-list .abroad.status:after,.members-list .traveling.status:after{color:var(--user-status-blue-color)}.twse-sort-toggle-container{position:absolute;left:10px;display:inline-flex;align-items:center}.twse-sort-toggle-label{display:inline-flex;align-items:center;gap:6px;cursor:pointer;color:#999;font-size:13px;-webkit-user-select:none;user-select:none}.twse-sort-toggle-checkbox{cursor:pointer;margin:0;width:13px;height:13px}.members-list li .member{position:relative!important;display:flex!important;align-items:center}.twse-copy-btn{position:absolute;right:8px;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;padding:4px;color:#888;transition:color .15s,background-color .15s,transform .1s;border-radius:4px;z-index:10}.twse-copy-btn:hover{color:#333;background-color:#0000000d}:root .dark-mode .twse-copy-btn:hover{color:#fff;background-color:#ffffff26}.twse-copy-btn:active{transform:translateY(-50%) scale(.9)}.twse-copy-btn.success{color:#494!important}:root .dark-mode .twse-copy-btn.success{color:#69eb69!important}.twse-chain-bubble{position:fixed;bottom:100px;right:20px;z-index:9999;background:#1e1e1ed9;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border:1px solid rgba(255,255,255,.1);border-radius:12px;padding:6px 10px;box-shadow:0 8px 32px #0000005e;color:#e0e0e0;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:11px;line-height:1.5;display:flex;flex-direction:column;transition:opacity .3s ease,transform .3s ease;min-width:100px;pointer-events:auto;cursor:grab;user-select:none;-webkit-user-select:none}.twse-chain-bubble.hidden{opacity:0;transform:translateY(10px);pointer-events:none}.twse-chain-body{display:flex;flex-direction:column;gap:4px;width:100%}.twse-chain-tag,.twse-chain-mult{display:none}.twse-chain-row{display:flex;justify-content:space-between;align-items:center;gap:12px}.twse-chain-stats{display:flex;align-items:center;gap:6px;width:100%}.twse-chain-count{font-weight:600;color:#fff}.twse-chain-timer{margin-left:auto;font-family:monospace;font-weight:700;padding:2px 6px;border-radius:4px;background:#0000004d}.twse-chain-timer.okay{color:#69eb69}.twse-chain-timer.cooldown{color:#64b5f6;background:#64b5f626}.twse-chain-count.cooldown{color:#64b5f6}.twse-chain-timer.negative{color:#ff5252}.twse-chain-timer.urgent{color:#ff5252;background:#ff525226;animation:twse-pulse 1s infinite alternate}@keyframes twse-pulse{0%{box-shadow:0 0 2px #ff525266}to{box-shadow:0 0 8px #ff5252cc}}body.twse-copy-disabled .twse-copy-btn,body.twse-bubble-disabled #twse-chain-bubble{display:none!important}body{--twse-bg-color: #f0f0f0;--twse-alt-bg-color: #fff;--twse-border-color: #ccc;--twse-input-color: #333;--twse-text-color: #000;--twse-hover-color: #ddd;--twse-glow-color: #4caf50;--twse-success-color: #4caf50}:root .dark-mode{--twse-bg-color: #333;--twse-alt-bg-color: #383838;--twse-border-color: #444;--twse-input-color: #ccc;--twse-text-color: #ccc;--twse-hover-color: #555;--twse-glow-color: #4caf50;--twse-success-color: #4caf50}twse-settings-panel{display:block;margin-top:20px;clear:both}twse-settings-panel .accordion{margin:10px 0;padding:15px;background-color:var(--twse-bg-color);border:1px solid var(--twse-border-color);border-radius:5px;color:var(--twse-text-color);font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}twse-settings-panel .accordion.glow{border-color:var(--twse-glow-color);box-shadow:0 0 8px #4caf5080}twse-settings-panel .input-row{display:flex;flex-direction:column;gap:5px;margin-bottom:15px}twse-settings-panel .input-row-inline{display:flex;align-items:center;gap:10px;margin-bottom:15px;font-size:13px;cursor:pointer;-webkit-user-select:none;user-select:none}twse-settings-panel .input-row-inline input[type=checkbox]{cursor:pointer;width:14px;height:14px;margin:0}twse-settings-panel .input-row-inline label{cursor:pointer;line-height:1.4}twse-settings-panel .blur-mode{filter:blur(4px);transition:filter .2s ease}twse-settings-panel .blur-mode:hover,twse-settings-panel .blur-mode:focus{filter:blur(0)}twse-settings-panel input[type=text]{box-sizing:border-box;text-align:left;vertical-align:top;width:250px;height:34px;margin-right:8px;padding:8px 10px;line-height:14px;display:inline-block;border:1px solid var(--twse-border-color);border-radius:5px;background-color:var(--twse-alt-bg-color);color:var(--twse-text-color);outline:none}twse-settings-panel input[type=text]:focus{border-color:var(--twse-glow-color)}twse-settings-panel .twse-api-explanation{background-color:var(--twse-alt-bg-color);border:1px solid var(--twse-border-color);border-radius:8px;color:var(--twse-text-color);margin-top:5px;margin-bottom:5px;padding:10px 14px;font-size:12px;line-height:1.4;max-width:600px}twse-settings-panel h3{margin:20px 0 12px;font-size:14px;font-weight:700;border-bottom:1px solid var(--twse-border-color);padding-bottom:6px}";
  importCSS(stylesCss);
  const log$1 = logger.child("feature:war-monitor");
  const TRAVELING = "data-twse-traveling";
  const HIGHLIGHT = "data-twse-highlight";
  const STATUS_DIFFERS = "data-twse-status-differs";
  function shouldRunMonitor() {
    if (!window.location.href.includes("factions.php")) {
      return false;
    }
    const hash = window.location.hash || "";
    if (!hash.startsWith("#/war/")) {
      return false;
    }
    return true;
  }
  const WarMonitorFeature = {
    name: "War Monitor",
    description: "Monitors active Faction wars, retrieves real-time member statuses, and decorates rows",
    executionTime: StartTime.DocumentEnd,
    shouldRun() {
      return window.location.href.includes("factions.php");
    },
    async run() {
      let active = false;
      let stopMonitor = null;
      const startMonitor = async () => {
        if (active) return;
        active = true;
        factionCache.cleanExpired();
        const syncBodyClasses = () => {
          document.body.classList.toggle(
            "twse-copy-disabled",
            !twseconfig.copy_button_enabled
          );
          document.body.classList.toggle(
            "twse-bubble-disabled",
            !twseconfig.bubble_enabled
          );
        };
        syncBodyClasses();
        let running = true;
        let foundWar = false;
        let pageVisible = !document.hidden;
        let everSorted = false;
        let ffscouterSortingDeferred = false;
        const memberStatus = new Map();
        const memberLis = new Map();
        const deferredWrites = [];
        const deferredStyles = [];
        let lastRequestTime = 0;
        const minTimeBetweenRequestsMs = 1e4;
        const activeChains = new Map();
        const onConfigUpdated = () => {
          syncBodyClasses();
          const checkbox = document.querySelector(
            "#twse-war-sort-checkbox"
          );
          if (checkbox) {
            checkbox.checked = twseconfig.war_sorting;
          }
        };
        window.addEventListener("twse-config-updated", onConfigUpdated);
        const onClearCache = () => {
          log$1.info("Received twse-clear-cache event. Purging all caches.");
          memberStatus.clear();
          factionCache.clearAll();
          activeChains.clear();
          updateStatuses();
        };
        window.addEventListener("twse-clear-cache", onClearCache);
        let bubbleContainer = document.getElementById(
          "twse-chain-bubble"
        );
        if (!bubbleContainer) {
          bubbleContainer = document.createElement("div");
          bubbleContainer.id = "twse-chain-bubble";
          bubbleContainer.className = "twse-chain-bubble hidden";
          document.body.appendChild(bubbleContainer);
        }
        if (bubbleContainer && !bubbleContainer.querySelector(".twse-chain-body")) {
          bubbleContainer.innerHTML = `<div class="twse-chain-body"></div>`;
        }
        const getBubbleRect = () => {
          if (bubbleContainer && typeof bubbleContainer.getBoundingClientRect === "function") {
            const r2 = bubbleContainer.getBoundingClientRect();
            return {
              left: r2.left ?? 0,
              top: r2.top ?? 0,
              width: r2.width || 170,
              height: r2.height || 60
            };
          }
          return { left: 0, top: 0, width: 170, height: 60 };
        };
        const clampToScreen = () => {
          if (!bubbleContainer) return;
          const rect = getBubbleRect();
          const w = rect.width;
          const h2 = rect.height;
          const currentLeft = parseFloat(bubbleContainer.style.left);
          const currentTop = parseFloat(bubbleContainer.style.top);
          if (!Number.isNaN(currentLeft) && !Number.isNaN(currentTop)) {
            const maxLeft = window.innerWidth - w;
            const maxTop = window.innerHeight - h2;
            bubbleContainer.style.left = `${Math.max(0, Math.min(currentLeft, maxLeft))}px`;
            bubbleContainer.style.top = `${Math.max(0, Math.min(currentTop, maxTop))}px`;
          }
        };
        window.addEventListener("resize", clampToScreen, { passive: true });
        if (bubbleContainer) {
          const savedPos = twseconfig.bubble_position;
          if (savedPos) {
            bubbleContainer.style.bottom = "auto";
            bubbleContainer.style.right = "auto";
            bubbleContainer.style.left = `${savedPos.left}px`;
            bubbleContainer.style.top = `${savedPos.top}px`;
            setTimeout(clampToScreen, 0);
          }
          let isDragging = false;
          let startX = 0;
          let startY = 0;
          let initialX = 0;
          let initialY = 0;
          const dragStart = (e2) => {
            isDragging = true;
            const isTouch = e2.type === "touchstart";
            const touchEvent = e2;
            const mouseEvent = e2;
            const clientX = isTouch && touchEvent.touches && touchEvent.touches.length > 0 ? touchEvent.touches[0].clientX : mouseEvent.clientX;
            const clientY = isTouch && touchEvent.touches && touchEvent.touches.length > 0 ? touchEvent.touches[0].clientY : mouseEvent.clientY;
            startX = clientX;
            startY = clientY;
            if (bubbleContainer) {
              const rect = getBubbleRect();
              initialX = rect.left;
              initialY = rect.top;
              bubbleContainer.style.transition = "none";
              bubbleContainer.style.cursor = "grabbing";
            }
            if (!isTouch && e2.cancelable) {
              e2.preventDefault();
            }
            window.getSelection()?.removeAllRanges();
            document.addEventListener("mousemove", dragMove);
            document.addEventListener("touchmove", dragMove, { passive: false });
            document.addEventListener("mouseup", dragEnd);
            document.addEventListener("touchend", dragEnd);
          };
          const dragMove = (e2) => {
            if (!isDragging || !bubbleContainer) return;
            if (e2.cancelable) {
              e2.preventDefault();
            }
            const isTouch = e2.type === "touchmove";
            const touchEvent = e2;
            const mouseEvent = e2;
            const clientX = isTouch && touchEvent.touches && touchEvent.touches.length > 0 ? touchEvent.touches[0].clientX : mouseEvent.clientX;
            const clientY = isTouch && touchEvent.touches && touchEvent.touches.length > 0 ? touchEvent.touches[0].clientY : mouseEvent.clientY;
            const dx = clientX - startX;
            const dy = clientY - startY;
            const rect = getBubbleRect();
            const w = rect.width;
            const h2 = rect.height;
            let newLeft = initialX + dx;
            let newTop = initialY + dy;
            const maxLeft = window.innerWidth - w;
            const maxTop = window.innerHeight - h2;
            newLeft = Math.max(0, Math.min(newLeft, maxLeft));
            newTop = Math.max(0, Math.min(newTop, maxTop));
            bubbleContainer.style.bottom = "auto";
            bubbleContainer.style.right = "auto";
            bubbleContainer.style.left = `${newLeft}px`;
            bubbleContainer.style.top = `${newTop}px`;
          };
          const dragEnd = () => {
            isDragging = false;
            if (bubbleContainer) {
              bubbleContainer.style.cursor = "grab";
              const left = parseFloat(bubbleContainer.style.left) || 0;
              const top = parseFloat(bubbleContainer.style.top) || 0;
              twseconfig.bubble_position = { left, top };
            }
            document.removeEventListener("mousemove", dragMove);
            document.removeEventListener("touchmove", dragMove);
            document.removeEventListener("mouseup", dragEnd);
            document.removeEventListener("touchend", dragEnd);
          };
          bubbleContainer.addEventListener("mousedown", dragStart);
          bubbleContainer.addEventListener("touchstart", dragStart, {
            passive: false
          });
        }
        const onVisibilityChange = () => {
          pageVisible = !document.hidden;
        };
        document.addEventListener("visibilitychange", onVisibilityChange);
        async function copyToClipboard(text) {
          if (typeof window !== "undefined" && window.flutter_inappwebview && typeof window.flutter_inappwebview.callHandler === "function") {
            try {
              await window.flutter_inappwebview.callHandler(
                "copyToClipboard",
                text
              );
              return true;
            } catch (err) {
              log$1.error("Failed to copy using Torn PDA callHandler", err);
            }
          }
          try {
            if (navigator.clipboard?.writeText) {
              await navigator.clipboard.writeText(text);
              return true;
            }
          } catch (err) {
            log$1.error("Failed to copy using clipboard API", err);
          }
          try {
            const textarea = document.createElement("textarea");
            textarea.value = text;
            textarea.style.position = "fixed";
            textarea.style.opacity = "0";
            document.body.appendChild(textarea);
            textarea.select();
            const success = document.execCommand("copy");
            document.body.removeChild(textarea);
            return success;
          } catch (err) {
            log$1.error("Failed to copy using fallback", err);
            return false;
          }
        }
        function injectCopyButton(id, li) {
          if (li.querySelector(".twse-copy-btn")) return;
          const atag = li.querySelector(
            "a[href^='/profiles.php']"
          );
          if (!atag) return;
          const parent = li.querySelector(".member");
          if (!parent) return;
          const copyBtn = document.createElement("button");
          copyBtn.className = "twse-copy-btn";
          copyBtn.type = "button";
          copyBtn.title = "Copy Name [ID]";
          copyBtn.innerHTML = `
          <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="twse-copy-icon"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
        `;
          copyBtn.addEventListener("click", async (e2) => {
            e2.preventDefault();
            e2.stopPropagation();
            const name = atag.textContent?.trim() || "";
            const copyText = `${name} [${id}]`;
            const success = await copyToClipboard(copyText);
            if (success) {
              copyBtn.classList.add("success");
              copyBtn.innerHTML = `
              <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" class="twse-copy-icon-success"><polyline points="20 6 9 17 4 12"></polyline></svg>
            `;
              setTimeout(() => {
                copyBtn.classList.remove("success");
                copyBtn.innerHTML = `
                <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="twse-copy-icon"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
              `;
              }, 1e3);
            }
          });
          parent.appendChild(copyBtn);
        }
        function extractAllMemberLis() {
          memberLis.clear();
          const memberLists = document.querySelectorAll("ul.members-list");
          memberLists.forEach((ul) => {
            const lis = ul.querySelectorAll("li.enemy, li.your");
            lis.forEach((li) => {
              const atag = li.querySelector(
                "a[href^='/profiles.php']"
              );
              if (!atag) return;
              const parts = atag.href.split("ID=");
              if (parts.length <= 1) return;
              const id = parts[1];
              memberLis.set(id, {
                li,
                statusDiv: li.querySelector("div.status")
              });
              injectCopyButton(id, li);
            });
          });
        }
        function getFactionIds() {
          const memberLists = document.querySelectorAll("ul.members-list");
          const ids = [];
          memberLists.forEach((elem) => {
            const q = elem.querySelector(
              "a[href^='/factions.php']"
            );
            if (!q) return;
            const s2 = q.href.split("ID=");
            if (s2.length <= 1) return;
            const id = s2[1];
            if (id) {
              ids.push(id);
            }
          });
          return ids;
        }
        function getSortedColumn(memberList) {
          const parent = memberList.parentNode;
          if (!parent) return { column: null, order: null };
          const memberDiv = parent.querySelector("div.member div");
          const levelDiv = parent.querySelector("div.level div");
          const pointsDiv = parent.querySelector("div.points div");
          const statusDiv = parent.querySelector("div.status div");
          let column = null;
          let classname = "";
          if (memberDiv?.className.includes("activeIcon__")) {
            column = "member";
            classname = memberDiv.className;
          } else if (levelDiv?.className.includes("activeIcon__")) {
            column = "level";
            classname = levelDiv.className;
          } else if (pointsDiv?.className.includes("activeIcon__")) {
            column = "points";
            classname = pointsDiv.className;
          } else if (statusDiv?.className.includes("activeIcon__")) {
            column = "status";
            classname = statusDiv.className;
          }
          const order = classname.includes("asc__") ? "asc" : "desc";
          if (column && (column !== "points" || order !== "desc")) {
            everSorted = true;
          }
          return { column, order };
        }
        function populateCachedStatus(factionId) {
          const cached = factionCache.get(factionId);
          if (!cached) return;
          for (const [id, status] of Object.entries(cached)) {
            memberStatus.set(id, status);
          }
          log$1.info(
            `Populated war monitor cache with stored statuses for faction: ${factionId}`
          );
        }
        function queueAttrWrite(elem, attr, value) {
          if (elem.getAttribute(attr) !== value) {
            deferredWrites.push([elem, attr, value]);
            return true;
          }
          return false;
        }
        function queueStyleWrite(elem, prop, value) {
          if (elem.style.getPropertyValue(prop) !== value) {
            deferredStyles.push([elem, prop, value]);
          }
        }
        function calculateFlightTimeRemaining(li) {
          const earliestArrivalAttr = li.getAttribute("data-earliest-arrival");
          const latestArrivalAttr = li.getAttribute("data-latest-arrival");
          if (!earliestArrivalAttr && !latestArrivalAttr) return "";
          const earliestArrival = parseInt(earliestArrivalAttr || "", 10);
          const latestArrival = parseInt(latestArrivalAttr || "", 10);
          if (Number.isNaN(earliestArrival) && Number.isNaN(latestArrival))
            return "";
          const now = getCurrentTimeSec();
          if (!Number.isNaN(earliestArrival) && earliestArrival > now) {
            const remaining = Math.round(earliestArrival - now);
            return ` ${calc_delta(remaining, false, false)}`;
          }
          if (!Number.isNaN(latestArrival) && latestArrival > now) {
            const remaining = Math.round(latestArrival - now);
            return ` <${calc_delta(remaining, false, false)}`;
          }
          return " LATE";
        }
        async function updateStatuses() {
          if (!running) return;
          const factionIds = getFactionIds();
          if (factionIds.length === 0) return;
          const now = Date.now();
          if (now - lastRequestTime < minTimeBetweenRequestsMs) return;
          lastRequestTime = now;
          for (const factionId of factionIds) {
            log$1.debug(`Fetching API status update for faction: ${factionId}`);
            const data = await tornApi.fetchFactionData(factionId);
            if (!data) continue;
            if (data.error) {
              if (tornApi.isUnrecoverableError(data.error.code)) {
                log$1.error(
                  "Torn API returned unrecoverable error. Halting war monitor polling."
                );
                running = false;
                break;
              }
              continue;
            }
            if (!data.members) continue;
            const reqTime = Date.now();
            const factionStatus = {};
            for (const [id, memberData] of Object.entries(data.members)) {
              const status = memberData.status;
              status.last_req_time = reqTime;
              const prev = memberStatus.get(id);
              const prev_state = prev?.state ?? "Unknown";
              const prev_since = prev?.since ?? reqTime;
              if (prev_state === status.state) {
                status.since = prev_since;
              } else {
                status.since = reqTime;
              }
              memberStatus.set(id, status);
              factionStatus[id] = status;
            }
            factionCache.set(factionId, factionStatus);
            if (data.chain) {
              activeChains.set(factionId, {
                current: data.chain.current,
                max: data.chain.max,
                timeout: data.chain.timeout,
                modifier: data.chain.modifier,
                tag: data.tag || "",
                apiReceivedAt: getCurrentTimeSec(),
                cooldown: data.chain.cooldown || 0,
                end: data.chain.end
              });
            }
          }
        }
        function watch() {
          deferredWrites.length = 0;
          deferredStyles.length = 0;
          let dirtySort = false;
          memberLis.forEach((elem, id) => {
            const li = elem.li;
            const statusDiv = elem.statusDiv;
            if (!li || !statusDiv) return;
            const status = memberStatus.get(id);
            if (!status || !running) {
              queueStyleWrite(
                statusDiv,
                "--twse-content",
                `"${statusDiv.textContent || ""}"`
              );
              return;
            }
            if (queueAttrWrite(li, "data-until", String(status.until))) {
              dirtySort = true;
            }
            if (queueAttrWrite(li, "data-since", String(status.since))) {
              dirtySort = true;
            }
            let dataLocation = "";
            switch (status.state) {
              case "Abroad":
              case "Traveling": {
                const hasTravelingClass = statusDiv.classList.contains("traveling") || statusDiv.classList.contains("abroad");
                if (!hasTravelingClass) {
                  if (statusDiv.textContent === "Okay") {
                    queueAttrWrite(statusDiv, STATUS_DIFFERS, "true");
                    if (queueAttrWrite(li, "data-sortA", "0")) {
                      dirtySort = true;
                    }
                  }
                  queueStyleWrite(
                    statusDiv,
                    "--twse-content",
                    `"${statusDiv.textContent || ""}"`
                  );
                  break;
                }
                queueAttrWrite(statusDiv, STATUS_DIFFERS, "false");
                if (status.description.includes("In ")) {
                  if (queueAttrWrite(li, "data-sortA", "4")) {
                    dirtySort = true;
                  }
                  const content = shorten_destination(
                    status.description.split("In ")[1]
                  );
                  dataLocation = content;
                  queueStyleWrite(statusDiv, "--twse-content", `"${content}"`);
                  break;
                }
                const route = extract_destinations_from_description(
                  status.description
                );
                if (route?.from === "TC") {
                  if (queueAttrWrite(li, "data-sortA", "5")) {
                    dirtySort = true;
                  }
                  const dest = route.to;
                  dataLocation = `► ${dest}`;
                  const remaining = calculateFlightTimeRemaining(li);
                  queueStyleWrite(
                    statusDiv,
                    "--twse-content",
                    `"${dataLocation}${remaining}"`
                  );
                } else if (route?.to === "TC") {
                  if (queueAttrWrite(li, "data-sortA", "3")) {
                    dirtySort = true;
                  }
                  const dest = route.from;
                  dataLocation = `◄ ${dest}`;
                  const remaining = calculateFlightTimeRemaining(li);
                  queueStyleWrite(
                    statusDiv,
                    "--twse-content",
                    `"${dataLocation}${remaining}"`
                  );
                } else {
                  if (queueAttrWrite(li, "data-sortA", "6")) {
                    dirtySort = true;
                  }
                  dataLocation = "Traveling";
                  queueStyleWrite(
                    statusDiv,
                    "--twse-content",
                    `"${dataLocation}"`
                  );
                }
                break;
              }
              case "Hospital":
              case "Jail": {
                const now = getCurrentTimeSec();
                const timeRemaining = Math.round(status.until - now);
                const hasHospitalClass = statusDiv.classList.contains("hospital") || statusDiv.classList.contains("jail");
                if (!hasHospitalClass) {
                  if (timeRemaining >= 0) {
                    if (queueAttrWrite(li, "data-sortA", "0")) {
                      dirtySort = true;
                    }
                    queueAttrWrite(statusDiv, STATUS_DIFFERS, "true");
                  }
                  queueStyleWrite(
                    statusDiv,
                    "--twse-content",
                    `"${statusDiv.textContent || ""}"`
                  );
                  queueAttrWrite(statusDiv, TRAVELING, "false");
                  queueAttrWrite(statusDiv, HIGHLIGHT, "false");
                  break;
                }
                queueAttrWrite(statusDiv, STATUS_DIFFERS, "false");
                if (queueAttrWrite(li, "data-sortA", "2")) {
                  dirtySort = true;
                }
                if (status.description.includes("In a")) {
                  queueAttrWrite(statusDiv, TRAVELING, "true");
                } else {
                  queueAttrWrite(statusDiv, TRAVELING, "false");
                }
                if (timeRemaining <= 0) {
                  queueAttrWrite(statusDiv, HIGHLIGHT, "false");
                  break;
                }
                const timeStr = calc_delta(timeRemaining);
                queueStyleWrite(statusDiv, "--twse-content", `"${timeStr}"`);
                if (timeRemaining < 300) {
                  queueAttrWrite(statusDiv, HIGHLIGHT, "true");
                } else {
                  queueAttrWrite(statusDiv, HIGHLIGHT, "false");
                }
                break;
              }
              default:
                queueStyleWrite(
                  statusDiv,
                  "--twse-content",
                  `"${statusDiv.textContent || ""}"`
                );
                if (queueAttrWrite(li, "data-sortA", "1")) {
                  dirtySort = true;
                }
                queueAttrWrite(statusDiv, TRAVELING, "false");
                queueAttrWrite(statusDiv, HIGHLIGHT, "false");
                queueAttrWrite(statusDiv, STATUS_DIFFERS, "false");
                break;
            }
            if (li.getAttribute("data-location") !== dataLocation) {
              queueAttrWrite(li, "data-location", dataLocation);
              dirtySort = true;
            }
          });
          if (deferredWrites.length > 0) {
            for (const [elem, attr, val] of deferredWrites) {
              elem.setAttribute(attr, val);
            }
            deferredWrites.length = 0;
          }
          if (deferredStyles.length > 0) {
            for (const [elem, prop, val] of deferredStyles) {
              elem.style.setProperty(prop, val);
            }
            deferredStyles.length = 0;
          }
          if (twseconfig.war_sorting && dirtySort) {
            const memberLists = document.querySelectorAll("ul.members-list");
            for (let i2 = 0; i2 < memberLists.length; i2++) {
              const listElem = memberLists[i2];
              let sortedColumn = getSortedColumn(listElem);
              if (!everSorted) {
                sortedColumn = { column: "status", order: "asc" };
              }
              if (listElem.getAttribute("data-ffscouter-active-filter") === "true") {
                ffscouterSortingDeferred = true;
                continue;
              }
              if (sortedColumn.column !== "status") {
                continue;
              }
              const lis = Array.from(listElem.childNodes);
              const validLis = lis.filter(
                (node) => node.nodeType === Node.ELEMENT_NODE
              );
              const sortedLis = validLis.sort((a2, b2) => {
                let left = a2;
                let right = b2;
                if (sortedColumn.order === "desc") {
                  left = b2;
                  right = a2;
                }
                const sortA_a = parseInt(
                  left.getAttribute("data-sortA") || "1",
                  10
                );
                const sortA_b = parseInt(
                  right.getAttribute("data-sortA") || "1",
                  10
                );
                const sorta = sortA_a - sortA_b;
                if (sorta !== 0) {
                  return sorta;
                }
                const leftLocation = left.getAttribute("data-location") || "";
                const rightLocation = right.getAttribute("data-location") || "";
                if (leftLocation && rightLocation) {
                  if (leftLocation < rightLocation) return -1;
                  if (leftLocation > rightLocation) return 1;
                  return 0;
                }
                if (sortA_a === 0 || sortA_a === 1) {
                  const since_a = parseInt(
                    left.getAttribute("data-since") || "0",
                    10
                  );
                  const since_b = parseInt(
                    right.getAttribute("data-since") || "0",
                    10
                  );
                  return since_b - since_a;
                }
                const until_a = parseInt(
                  left.getAttribute("data-until") || "0",
                  10
                );
                const until_b = parseInt(
                  right.getAttribute("data-until") || "0",
                  10
                );
                return until_a - until_b;
              });
              let sorted = true;
              for (let j = 0; j < sortedLis.length; j++) {
                if (listElem.children[j] !== sortedLis[j]) {
                  sorted = false;
                  break;
                }
              }
              if (!sorted) {
                const fragment = document.createDocumentFragment();
                sortedLis.forEach((li) => {
                  fragment.appendChild(li);
                });
                listElem.appendChild(fragment);
              }
            }
          }
          if (ffscouterSortingDeferred) {
            const memberLists = document.querySelectorAll("ul.members-list");
            let activeFilterFound = false;
            for (let i2 = 0; i2 < memberLists.length; i2++) {
              if (memberLists[i2].getAttribute("data-ffscouter-active-filter") === "true") {
                activeFilterFound = true;
                break;
              }
            }
            if (!activeFilterFound) {
              ffscouterSortingDeferred = false;
              dirtySort = true;
            }
          }
          for (const [id, ref] of memberLis) {
            if (!ref.li.isConnected) {
              memberLis.delete(id);
            }
          }
          updateChainBubble();
        }
        function updateChainBubble() {
          if (!bubbleContainer) return;
          if (!foundWar || activeChains.size === 0) {
            bubbleContainer.classList.add("hidden");
            return;
          }
          const bodyContainer = bubbleContainer.querySelector(".twse-chain-body");
          if (!bodyContainer) return;
          let html = "";
          const now = getCurrentTimeSec();
          activeChains.forEach((chain) => {
            let formattedTime = "";
            let timerClass = "okay";
            let countClass = "";
            if (chain.cooldown > 0) {
              const elapsed = now - chain.apiReceivedAt;
              const remainingCooldown = Math.max(0, chain.cooldown - elapsed);
              formattedTime = formatChainCooldown(remainingCooldown);
              timerClass = "cooldown";
              countClass = "cooldown";
            } else if (chain.timeout === 0) {
              formattedTime = "-:--";
              timerClass = "okay";
            } else {
              const elapsed = now - chain.apiReceivedAt;
              const remaining = chain.end && chain.end > 0 ? chain.end - now : chain.timeout - elapsed;
              formattedTime = formatChainTimeout(remaining);
              if (remaining < 0) {
                timerClass = "negative";
              } else if (remaining < 60) {
                timerClass = "urgent";
              }
            }
            html += `
            <div class="twse-chain-row">
              <span class="twse-chain-tag">[${chain.tag || "Faction"}]</span>
              <div class="twse-chain-stats">
                <span class="twse-chain-count ${countClass}">${chain.current}/${chain.max}</span>
                <span class="twse-chain-mult">${chain.modifier.toFixed(2)}x</span>
                <span class="twse-chain-timer ${timerClass}">${formattedTime}</span>
              </div>
            </div>
          `;
          });
          bodyContainer.innerHTML = html;
          bubbleContainer.classList.remove("hidden");
        }
        let descriptionsObserver = null;
        let innerDescriptionsObserver = null;
        const initWarMonitoring = (descriptions) => {
          foundWar = false;
          log$1.info("Descriptions container detected. Starting observation.");
          let injectedToggle = false;
          const injectSortingToggle = (descEl) => {
            if (injectedToggle) return;
            if (descEl.querySelector("#twse-war-sort-checkbox")) {
              injectedToggle = true;
              return;
            }
            const graphContainer = descEl.querySelector('[class*="graphIcon"]');
            if (!graphContainer || !graphContainer.parentNode) return;
            const parent = graphContainer.parentNode;
            parent.style.position = "relative";
            const computedStyle = window.getComputedStyle(graphContainer);
            const toggleContainer = document.createElement("div");
            toggleContainer.className = "twse-sort-toggle-container";
            toggleContainer.style.top = computedStyle.top && computedStyle.top !== "auto" ? computedStyle.top : "10px";
            toggleContainer.innerHTML = `
            <label class="twse-sort-toggle-label">
              <input type="checkbox" id="twse-war-sort-checkbox" class="twse-sort-toggle-checkbox" ${twseconfig.war_sorting ? "checked" : ""} />
              TWSE Sort
            </label>
          `;
            graphContainer.parentNode.insertBefore(
              toggleContainer,
              graphContainer
            );
            log$1.info(
              "Successfully injected war sorting toggle checkbox before Graph link."
            );
            injectedToggle = true;
            const checkbox = toggleContainer.querySelector(
              "#twse-war-sort-checkbox"
            );
            if (checkbox) {
              checkbox.addEventListener("change", (e2) => {
                const isChecked = e2.target.checked;
                log$1.info(`War sorting configuration changed: ${isChecked}`);
                twseconfig.war_sorting = isChecked;
              });
            }
          };
          injectSortingToggle(descriptions);
          innerDescriptionsObserver = observeElement(descriptions, () => {
            if (!injectedToggle) {
              injectSortingToggle(descriptions);
            }
            if (!foundWar && descriptions.querySelector(".faction-war")) {
              foundWar = true;
              extractAllMemberLis();
              const ids = getFactionIds();
              ids.forEach(populateCachedStatus);
              updateStatuses();
            }
          });
          if (descriptions.querySelector(".faction-war")) {
            foundWar = true;
            extractAllMemberLis();
            const ids = getFactionIds();
            ids.forEach(populateCachedStatus);
            updateStatuses();
          }
        };
        const factWarList = await waitForElement("#faction_war_list_id");
        if (!active) return;
        if (factWarList) {
          descriptionsObserver = new MutationObserver((mutations) => {
            for (const mutation of mutations) {
              for (const node of mutation.addedNodes) {
                if (node instanceof HTMLElement && node.classList.contains("descriptions")) {
                  log$1.info("Observed descriptions container added to DOM");
                  initWarMonitoring(node);
                }
              }
            }
          });
          descriptionsObserver.observe(factWarList, { childList: true });
          const existingDescriptions = factWarList.querySelector(".descriptions");
          if (existingDescriptions) {
            log$1.info("Found existing descriptions container");
            initWarMonitoring(existingDescriptions);
          }
        }
        const pollingInterval = setInterval(() => {
          if (running && foundWar) {
            updateStatuses();
          }
        }, 1e4);
        const watchInterval = setInterval(() => {
          if (foundWar && running && pageVisible) {
            watch();
          }
        }, 500);
        stopMonitor = () => {
          active = false;
          running = false;
          clearInterval(pollingInterval);
          clearInterval(watchInterval);
          if (descriptionsObserver) {
            descriptionsObserver.disconnect();
          }
          if (innerDescriptionsObserver) {
            innerDescriptionsObserver.disconnect();
          }
          window.removeEventListener("twse-config-updated", onConfigUpdated);
          window.removeEventListener("twse-clear-cache", onClearCache);
          window.removeEventListener("resize", clampToScreen);
          document.removeEventListener("visibilitychange", onVisibilityChange);
          if (bubbleContainer) {
            bubbleContainer.remove();
            bubbleContainer = null;
          }
          document.querySelector(".twse-sort-toggle-container")?.remove();
        };
      };
      const handleNavigation = () => {
        const shouldRun = shouldRunMonitor();
        if (shouldRun) {
          if (stopMonitor) {
            stopMonitor();
            stopMonitor = null;
          }
          startMonitor();
        } else if (!shouldRun && active) {
          if (stopMonitor) {
            stopMonitor();
            stopMonitor = null;
          }
        }
      };
      on_navigation(handleNavigation);
      if (shouldRunMonitor()) {
        startMonitor();
      }
      window.dispatchEvent(new Event("FFScouterV2DisableWarMonitor"));
    }
  };
  const __vite_glob_0_2 = Object.freeze( Object.defineProperty({
    __proto__: null,
    default: WarMonitorFeature
  }, Symbol.toStringTag, { value: "Module" }));
  const modules = Object.assign({
    "./key-manager/index.ts": __vite_glob_0_0,
    "./settings/index.ts": __vite_glob_0_1,
    "./war-monitor/index.ts": __vite_glob_0_2
  });
  const Features = Object.values(modules).map((mod) => mod.default).filter((feat) => !!feat && "name" in feat);
  const log = logger.child("boot");
  async function boot() {
    log.info("Initializing Torn War Stuff Enhanced...");
    for (const feature of Features) {
      try {
        const shouldRun = await feature.shouldRun();
        if (!shouldRun) {
          continue;
        }
        log.debug(`Booting feature: '${feature.name}'`);
        if (feature.executionTime === StartTime.DocumentStart) {
          feature.run();
        } else if (feature.executionTime === StartTime.DocumentBody) {
          if (document.body) {
            feature.run();
          } else {
            let booted = false;
            const trigger = () => {
              if (booted) return;
              booted = true;
              bodyObserver.disconnect();
              feature.run();
            };
            const bodyObserver = new MutationObserver(() => {
              if (document.body) {
                trigger();
              }
            });
            bodyObserver.observe(document.documentElement, {
              childList: true
            });
            document.addEventListener("DOMContentLoaded", trigger);
          }
        } else {
          if (document.readyState === "complete" || document.readyState === "interactive") {
            feature.run();
          } else {
            document.addEventListener("DOMContentLoaded", () => {
              feature.run();
            });
          }
        }
      } catch (e2) {
        log.error(`Error running feature '${feature.name}':`, e2);
      }
    }
  }
  boot();

})();