const CONSTANTS = {
  MODULE_NAME: "sequencer",
  EFFECTS_FLAG_NAME: "effects",
  SOUNDS_FLAG_NAME: "sounds",
  EFFECTS_LAYER: "sequencerEffects",
  DATABASE_NAME: "sequencerDatabase",
  INTERFACE_LAYER: "sequencerInterfaceLayer",
  IS_V14: false,
  COLOR: {
    PRIMARY: 15615023,
    SECONDARY: 6298186
  },
  SHAPES: {
    POLY: "polygon",
    RECT: "rectangle",
    CIRC: "circle",
    ELIP: "ellipse",
    RREC: "roundedRect"
  },
  FEET_REGEX: new RegExp(/\.[0-9]+ft\.*/g),
  ARRAY_REGEX: new RegExp(/\.[0-9]$/g),
  STATUS: {
    READY: 0,
    RUNNING: 1,
    COMPLETE: 2,
    SKIPPED: 3,
    ABORTED: 4
  },
  PLACEMENT_RESTRICTIONS: {
    ANYWHERE: "anywhere",
    LINE_OF_SIGHT: "lineOfSight",
    NO_COLLIDABLES: "noCollidables"
  },
  CALLBACKS: {
    SHOW: "show",
    MOUSE_MOVE: "mouseMove",
    MOVE: "move",
    COLLIDE: "collide",
    STOP_COLLIDING: "stopColliding",
    INVALID_PLACEMENT: "invalidPlacement",
    PLACED: "placed",
    CANCEL: "cancel"
  },
  /**
   * Map of user-friendly blend mode names to PIXI.BLEND_MODES numeric values.
   * Keys are lowercase, alphanumeric, and underscore-tolerant. Multiple aliases
   * (e.g. "soft_light", "soft-light", "softlight") are all accepted via the
   * normalization in the blendMode trait.
   */
  BLEND_MODES: {
    normal: 0,
    add: 1,
    multiply: 2,
    screen: 3,
    overlay: 4,
    darken: 5,
    lighten: 6,
    color_dodge: 7,
    color_burn: 8,
    hard_light: 9,
    soft_light: 10,
    difference: 11,
    exclusion: 12,
    hue: 13,
    saturation: 14,
    color: 15,
    luminosity: 16,
    normal_npm: 17,
    add_npm: 18,
    screen_npm: 19,
    none: 20,
    subtract: 28,
    erase: 29
  },
  /**
   * Blend modes that PIXI v7 exposes by name but does not actually implement.
   * Their entries in PIXI's WebGL state table (mapWebGLBlendModesToPixi) are
   * aliased to the same [gl.ONE, gl.ONE_MINUS_SRC_ALPHA] pair as NORMAL, so
   * the GPU composites them as normal blending. Listed here so the blendMode
   * trait can warn the user when one is requested.
   */
  UNSUPPORTED_BLEND_MODES: /* @__PURE__ */ new Set([
    4,
    // overlay
    5,
    // darken
    6,
    // lighten
    7,
    // color_dodge
    8,
    // color_burn
    9,
    // hard_light
    10,
    // soft_light
    11,
    // difference
    12,
    // exclusion
    13,
    // hue
    14,
    // saturation
    15,
    // color
    16
    // luminosity
  ])
};
CONSTANTS.TOOLS = {
  SELECT: `${CONSTANTS.MODULE_NAME}-select-effect`,
  PLAY: `${CONSTANTS.MODULE_NAME}-play-effect`,
  VIEWER: `${CONSTANTS.MODULE_NAME}-effectviewer`,
  DATABASE: `${CONSTANTS.MODULE_NAME}-effectdatabase`
};
CONSTANTS.EFFECTS_FLAG = `flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.EFFECTS_FLAG_NAME}`;
CONSTANTS.REMOVE_EFFECTS_FLAG = `flags.${CONSTANTS.MODULE_NAME}.-=${CONSTANTS.EFFECTS_FLAG_NAME}`;
CONSTANTS.SOUNDS_FLAG = `flags.${CONSTANTS.MODULE_NAME}.${CONSTANTS.SOUNDS_FLAG_NAME}`;
function registerEase(easeName, easeFunction, overwrite = false) {
  if (typeof easeName !== "string")
    throw custom_error("registerEase", "easeName must be of type string");
  if (!is_function(easeFunction))
    throw custom_error(
      "registerEase",
      "easeFunction must be of type function"
    );
  if (easeFunctions[easeName] !== void 0 && !overwrite) return;
  debug(`registerEase | Registered ease function: ${easeName}`);
  easeFunctions[easeName] = easeFunction;
}
const easeFunctions = {
  linear,
  easeInSine,
  easeOutSine,
  easeInOutSine,
  easeInQuad,
  easeOutQuad,
  easeInOutQuad,
  easeInCubic,
  easeOutCubic,
  easeInOutCubic,
  easeInQuart,
  easeOutQuart,
  easeInOutQuart,
  easeInQuint,
  easeOutQuint,
  easeInOutQuint,
  easeInExpo,
  easeOutExpo,
  easeInOutExpo,
  easeInCirc,
  easeOutCirc,
  easeInOutCirc,
  easeInBack,
  easeOutBack,
  easeInOutBack,
  easeInElastic,
  easeOutElastic,
  easeInOutElastic,
  easeInBounce,
  easeOutBounce,
  easeInOutBounce
};
const EASE = {
  LINEAR: "linear",
  SINE_IN: "easeInSine",
  SINE_OUT: "easeOutSine",
  SINE_IN_OUT: "easeInOutSine",
  QUAD_IN: "easeInQuad",
  QUAD_OUT: "easeOutQuad",
  QUAD_IN_OUT: "easeInOutQuad",
  CUBIC_IN: "easeInCubic",
  CUBIC_OUT: "easeOutCubic",
  CUBIC_IN_OUT: "easeInOutCubic",
  QUART_IN: "easeInQuart",
  QUART_OUT: "easeOutQuart",
  QUART_IN_OUT: "easeInOutQuart",
  QUINT_IN: "easeInQuint",
  QUINT_OUT: "easeOutQuint",
  QUINT_IN_OUT: "easeInOutQuint",
  EXPO_IN: "easeInExpo",
  EXPO_OUT: "easeOutExpo",
  EXPO_IN_OUT: "easeInOutExpo",
  CIRC_IN: "easeInCirc",
  CIRC_OUT: "easeOutCirc",
  CIRC_IN_OUT: "easeInOutCirc",
  BACK_IN: "easeInBack",
  BACK_OUT: "easeOutBack",
  BACK_IN_OUT: "easeInOutBack",
  ELASTIC_IN: "easeInElastic",
  ELASTIC_OUT: "easeOutElastic",
  ELASTIC_IN_OUT: "easeInOutElastic",
  BOUNCE_IN: "easeInBounce",
  BOUNCE_OUT: "easeOutBounce",
  BOUNCE_IN_OUT: "easeInOutBounce"
};
function linear(x) {
  return x;
}
function easeInSine(x) {
  return 1 - Math.cos(x * Math.PI / 2);
}
function easeOutSine(x) {
  return Math.sin(x * Math.PI / 2);
}
function easeInOutSine(x) {
  return -(Math.cos(Math.PI * x) - 1) / 2;
}
function easeInQuad(x) {
  return x * x;
}
function easeOutQuad(x) {
  return 1 - (1 - x) * (1 - x);
}
function easeInOutQuad(x) {
  return x < 0.5 ? 2 * x * x : 1 - Math.pow(-2 * x + 2, 2) / 2;
}
function easeInCubic(x) {
  return x * x * x;
}
function easeOutCubic(x) {
  return 1 - Math.pow(1 - x, 3);
}
function easeInOutCubic(x) {
  return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2;
}
function easeInQuart(x) {
  return x * x * x * x;
}
function easeOutQuart(x) {
  return 1 - Math.pow(1 - x, 4);
}
function easeInOutQuart(x) {
  return x < 0.5 ? 8 * x * x * x * x : 1 - Math.pow(-2 * x + 2, 4) / 2;
}
function easeInQuint(x) {
  return x * x * x * x * x;
}
function easeOutQuint(x) {
  return 1 - Math.pow(1 - x, 5);
}
function easeInOutQuint(x) {
  return x < 0.5 ? 16 * x * x * x * x * x : 1 - Math.pow(-2 * x + 2, 5) / 2;
}
function easeInExpo(x) {
  return x === 0 ? 0 : Math.pow(2, 10 * x - 10);
}
function easeOutExpo(x) {
  return x === 1 ? 1 : 1 - Math.pow(2, -10 * x);
}
function easeInOutExpo(x) {
  return x === 0 ? 0 : x === 1 ? 1 : x < 0.5 ? Math.pow(2, 20 * x - 10) / 2 : (2 - Math.pow(2, -20 * x + 10)) / 2;
}
function easeInCirc(x) {
  return 1 - Math.sqrt(1 - Math.pow(x, 2));
}
function easeOutCirc(x) {
  return Math.sqrt(1 - Math.pow(x - 1, 2));
}
function easeInOutCirc(x) {
  return x < 0.5 ? (1 - Math.sqrt(1 - Math.pow(2 * x, 2))) / 2 : (Math.sqrt(1 - Math.pow(-2 * x + 2, 2)) + 1) / 2;
}
function easeInBack(x) {
  const c1 = 1.70158;
  const c3 = c1 + 1;
  return c3 * x * x * x - c1 * x * x;
}
function easeOutBack(x) {
  const c1 = 1.70158;
  const c3 = c1 + 1;
  return 1 + c3 * Math.pow(x - 1, 3) + c1 * Math.pow(x - 1, 2);
}
function easeInOutBack(x) {
  const c1 = 1.70158;
  const c2 = c1 * 1.525;
  return x < 0.5 ? Math.pow(2 * x, 2) * ((c2 + 1) * 2 * x - c2) / 2 : (Math.pow(2 * x - 2, 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2;
}
function easeInElastic(x) {
  const c4 = 2 * Math.PI / 3;
  return x === 0 ? 0 : x === 1 ? 1 : -Math.pow(2, 10 * x - 10) * Math.sin((x * 10 - 10.75) * c4);
}
function easeOutElastic(x) {
  const c4 = 2 * Math.PI / 3;
  return x === 0 ? 0 : x === 1 ? 1 : Math.pow(2, -10 * x) * Math.sin((x * 10 - 0.75) * c4) + 1;
}
function easeInOutElastic(x) {
  const c5 = 2 * Math.PI / 4.5;
  return x === 0 ? 0 : x === 1 ? 1 : x < 0.5 ? -(Math.pow(2, 20 * x - 10) * Math.sin((20 * x - 11.125) * c5)) / 2 : Math.pow(2, -20 * x + 10) * Math.sin((20 * x - 11.125) * c5) / 2 + 1;
}
function easeInBounce(x) {
  return 1 - easeOutBounce(1 - x);
}
function easeOutBounce(x) {
  const n1 = 7.5625;
  const d1 = 2.75;
  if (x < 1 / d1) {
    return n1 * x * x;
  } else if (x < 2 / d1) {
    return n1 * (x -= 1.5 / d1) * x + 0.75;
  } else if (x < 2.5 / d1) {
    return n1 * (x -= 2.25 / d1) * x + 0.9375;
  } else {
    return n1 * (x -= 2.625 / d1) * x + 0.984375;
  }
}
function easeInOutBounce(x) {
  return x < 0.5 ? (1 - easeOutBounce(1 - 2 * x)) / 2 : (1 + easeOutBounce(2 * x - 1)) / 2;
}
async function getFiles(inFile, { applyWildCard = false, softFail = false } = {}) {
  let source2 = "data";
  const browseOptions = { wildcard: applyWildCard };
  if (/\.s3\./.test(inFile)) {
    source2 = "s3";
    const { bucket, keyPrefix } = foundry.utils.parseS3URL(inFile);
    if (bucket) {
      browseOptions.bucket = bucket;
      inFile = keyPrefix;
    }
  }
  try {
    return (await foundry.applications.apps.FilePicker.implementation.browse(source2, inFile, browseOptions)).files;
  } catch (err) {
    if (softFail) return false;
    throw custom_error("Sequencer", `getFiles | ${err}`);
  }
}
function interpolate(p1, p2, t, ease = "linear") {
  const easeFunction = is_function(ease) ? ease : easeFunctions[ease];
  return p1 + (p2 - p1) * easeFunction(t);
}
function random_float_between(min, max, twister = false) {
  const random = twister ? twister.random() : Math.random();
  const _max = Math.max(max, min);
  const _min = Math.min(max, min);
  return random * (_max - _min) + _min;
}
function random_int_between(min, max, twister = false) {
  return Math.floor(random_float_between(min, max, twister));
}
function flip_negate(num_1, num_2) {
  if (num_1 > 0) {
    num_1 -= num_2;
  } else {
    num_1 += num_2;
  }
  return num_1;
}
function shuffle_array(inArray, twister = false) {
  let shuffled = [...inArray];
  const randomMethod = twister?.random ?? Math.random;
  for (let i = shuffled.length - 1; i > 0; i--) {
    let j = Math.floor(randomMethod() * (i + 1));
    let temp = shuffled[i];
    shuffled[i] = shuffled[j];
    shuffled[j] = temp;
  }
  return shuffled;
}
function is_function(inFunc) {
  return inFunc && ({}.toString.call(inFunc) === "[object Function]" || {}.toString.call(inFunc) === "[object AsyncFunction]");
}
function random_array_element(inArray, { recurse = false, twister = false, index: index2 = false } = {}) {
  const chosenIndex = random_int_between(0, inArray.length, twister);
  let choice = inArray[chosenIndex];
  if (recurse && Array.isArray(choice)) {
    return random_array_element(choice, { recurse, twister, index: index2 });
  }
  if (index2) {
    return chosenIndex;
  }
  return choice;
}
function random_object_element(inObject, { recurse = false, twister = false } = {}) {
  let keys = Object.keys(inObject).filter((k) => !k.startsWith("_"));
  let choice = inObject[random_array_element(keys, { twister })];
  if (typeof choice === "object" && recurse) {
    return random_object_element(choice, { recurse: true });
  }
  return choice;
}
function is_real_number(inNumber) {
  return !isNaN(inNumber) && typeof inNumber === "number" && isFinite(inNumber);
}
function normalize_alpha_number(inNumber) {
  if (!is_real_number(inNumber)) return void 0;
  inNumber = Math.abs(inNumber);
  while (inNumber > 1) inNumber /= 10;
  return inNumber;
}
function deep_get(obj, path) {
  if (!Array.isArray(path)) path = path.split(".");
  try {
    let i;
    for (i = 0; i < path.length - 1; i++) {
      obj = obj[path[i]];
    }
    return obj[path[i]];
  } catch (err) {
  }
}
function deep_set(obj, path, value) {
  if (!Array.isArray(path)) path = path.split(".");
  try {
    let i;
    for (i = 0; i < path.length - 1; i++) {
      obj = obj[path[i]];
    }
    if (is_function(obj[path[i]])) {
      obj[path[i]](value);
    } else {
      obj[path[i]] = value;
    }
  } catch (err) {
  }
}
function flatten_object(obj) {
  let toReturn = [];
  for (let i in obj) {
    if (i.startsWith("_")) continue;
    if (!obj.hasOwnProperty(i)) continue;
    if (typeof obj[i] == "object") {
      let flatObject = flatten_object(obj[i]);
      for (let x in flatObject) {
        if (!flatObject.hasOwnProperty(x)) continue;
        toReturn[i + "." + x] = flatObject[x];
      }
    } else {
      toReturn[i] = obj[i];
    }
  }
  return toReturn;
}
function wait(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
function clamp(num, min, max) {
  const _max = Math.max(min, max);
  const _min = Math.min(min, max);
  return Math.max(_min, Math.min(_max, num));
}
function is_UUID(inId) {
  return typeof inId === "string" && (inId.startsWith("Scene") || inId.startsWith("Actor") || inId.startsWith("Item")) && (inId.match(/\./g) || []).length && !inId.endsWith(".");
}
function get_object_from_scene(inObjectId, inSceneId = game.user.viewedScene) {
  let tryUUID = is_UUID(inObjectId);
  if (tryUUID) {
    const obj = fromUuidSync(inObjectId);
    if (obj) return obj;
    tryUUID = false;
  }
  const allDocs = get_all_documents_from_scene(inSceneId);
  return allDocs.find((obj) => {
    return get_object_identifier(obj, tryUUID) === inObjectId;
  });
}
function get_all_documents_from_scene(inSceneId = false) {
  const scene = inSceneId ? game.scenes.get(inSceneId) : game.scenes.get(game.user?.viewedScene);
  if (!scene) return [];
  return [
    ...canvas.layers.map((layer) => layer?.children ?? [].concat(layer?.preview?.children ?? [])),
    ...Array.from(scene?.tokens ?? []),
    ...Array.from(scene?.lights ?? []),
    ...Array.from(scene?.sounds ?? []),
    ...Array.from(scene?.regions ?? []),
    ...Array.from(scene?.tiles ?? []),
    ...Array.from(scene?.walls ?? []),
    ...Array.from(scene?.drawings ?? [])
  ].deepFlatten().filter(Boolean);
}
function validate_document(inObject) {
  const document2 = inObject?.document ?? inObject;
  return is_UUID(document2?.uuid) ? document2 : inObject;
}
function get_object_identifier(inObject, tryUUID = true) {
  const uuid = tryUUID && is_UUID(inObject?.uuid) ? inObject?.uuid : void 0;
  return uuid ?? inObject?.id ?? inObject?.document?.name ?? inObject?.name ?? (inObject?.tag !== "" ? inObject?.tag : void 0) ?? (inObject?.label !== "" ? inObject?.label : void 0) ?? inObject?.sound_id;
}
function make_array_unique(inArray) {
  return Array.from(new Set(inArray));
}
function debug(msg, args = "") {
  if (game.settings.get(CONSTANTS.MODULE_NAME, "debug"))
    console.log(`DEBUG | Sequencer | ${msg}`, args);
}
function debug_error(msg, args) {
  if (game.settings.get(CONSTANTS.MODULE_NAME, "debug"))
    console.error(`DEBUG | Sequencer | ${msg}`, args);
}
function custom_warning(inClassName, warning, notify = false) {
  inClassName = inClassName !== "Sequencer" ? "Sequencer | Module: " + inClassName : inClassName;
  warning = `${inClassName} | ${warning}`;
  if (notify) ui.notifications.warn(warning, { console: false });
  console.warn(warning.replace("<br>", "\n"));
}
const throttledWarnings = {};
function throttled_custom_warning(inClassName, warning, delay = 1e4, notify = false) {
  inClassName = inClassName !== "Sequencer" ? "Sequencer | Module: " + inClassName : inClassName;
  warning = `${inClassName} | ${warning}`;
  if (throttledWarnings[warning]) return;
  throttledWarnings[warning] = true;
  if (notify) ui.notifications.warn(warning, { console: false });
  console.warn(warning.replace("<br>", "\n"));
  setTimeout(() => {
    delete throttledWarnings[warning];
  }, delay);
}
function custom_error(inClassName, error) {
  inClassName = inClassName !== "Sequencer" ? "Sequencer | Module: " + inClassName : inClassName;
  error = `${inClassName} | ${error}`;
  ui.notifications.error(error, { console: false });
  return new Error(error.replace("<br>", "\n"));
}
function user_can_do(inSetting) {
  return game.user.role > game.settings.get(CONSTANTS.MODULE_NAME, inSetting);
}
function group_by(xs, key) {
  return xs.reduce(function(acc, obj) {
    let property = foundry.utils.getProperty(obj, key);
    acc[property] = acc[property] || [];
    acc[property].push(obj);
    return acc;
  }, {});
}
function objHasProperty(obj, prop2) {
  return obj.constructor.prototype.hasOwnProperty(prop2);
}
function sequence_proxy_wrap(inSequence) {
  return new Proxy(inSequence, {
    get: function(target, prop2) {
      if (!objHasProperty(target, prop2)) {
        if (Sequencer.SectionManager.externalSections[prop2] === void 0) {
          const section = target.sections[target.sections.length - 1];
          if (section && objHasProperty(section, prop2)) {
            const targetProperty = Reflect.get(section, prop2);
            return is_function(targetProperty) ? targetProperty.bind(section) : targetProperty;
          }
          return Reflect.get(target, prop2);
        }
        target.sectionToCreate = Sequencer.SectionManager.externalSections[prop2];
        return Reflect.get(target, "_createCustomSection");
      }
      return Reflect.get(target, prop2);
    }
  });
}
function section_proxy_wrap(inClass) {
  return new Proxy(inClass, {
    get: function(target, prop2) {
      if (!objHasProperty(target, prop2) && objHasProperty(target.sequence, prop2)) {
        const targetProperty = Reflect.get(target.sequence, prop2);
        return is_function(targetProperty) ? targetProperty.bind(target.sequence) : targetProperty;
      }
      return Reflect.get(target, prop2);
    }
  });
}
function str_to_search_regex_str(str) {
  return str.trim().replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*+/g, ".*?");
}
function safe_str(str) {
  return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
function get_hash(input) {
  let hash = 0;
  const len = input.length;
  for (let i = 0; i < len; i++) {
    hash = (hash << 5) - hash + input.charCodeAt(i);
    hash |= 0;
  }
  return hash;
}
function parseColor(inColor) {
  return {
    hexadecimal: is_real_number(inColor) ? inColor.toString(16) : inColor,
    decimal: typeof inColor === "string" && inColor.startsWith("#") ? parseInt(inColor.slice(1), 16) : inColor
  };
}
function getCanvasMouse() {
  return canvas?.app?.renderer?.events?.pointer ?? canvas?.app?.renderer?.plugins?.interaction?.pointer;
}
function createMersenneTwister(seed) {
  const twister = foundry?.dice?.MersenneTwister ?? MersenneTwister;
  return new twister(seed);
}
async function createJournalDatabase() {
  let database = game.journal.getName(CONSTANTS.DATABASE_NAME);
  if (!database) {
    database = await JournalEntry.create({
      name: CONSTANTS.DATABASE_NAME,
      "ownership.default": CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER
    });
  }
  return database;
}
function forceDeletionKeyWrapper(updateObj, key) {
  if (CONSTANTS.IS_V14) {
    updateObj[key.replaceAll("-=", "")] = new foundry.data.operators.ForcedDeletion();
  } else {
    updateObj[key] = null;
  }
}
const PUBLIC_VERSION = "5";
if (typeof window !== "undefined") {
  ((window.__svelte ??= {}).v ??= /* @__PURE__ */ new Set()).add(PUBLIC_VERSION);
}
let legacy_mode_flag = false;
let tracing_mode_flag = false;
function enable_legacy_mode_flag() {
  legacy_mode_flag = true;
}
enable_legacy_mode_flag();
const EACH_ITEM_REACTIVE = 1;
const EACH_INDEX_REACTIVE = 1 << 1;
const EACH_IS_CONTROLLED = 1 << 2;
const EACH_IS_ANIMATED = 1 << 3;
const EACH_ITEM_IMMUTABLE = 1 << 4;
const PROPS_IS_IMMUTABLE = 1;
const PROPS_IS_RUNES = 1 << 1;
const PROPS_IS_UPDATED = 1 << 2;
const PROPS_IS_BINDABLE = 1 << 3;
const PROPS_IS_LAZY_INITIAL = 1 << 4;
const TEMPLATE_FRAGMENT = 1;
const TEMPLATE_USE_IMPORT_NODE = 1 << 1;
const UNINITIALIZED = Symbol();
const FILENAME = Symbol("filename");
const NAMESPACE_HTML = "http://www.w3.org/1999/xhtml";
const NAMESPACE_SVG = "http://www.w3.org/2000/svg";
const NAMESPACE_MATHML = "http://www.w3.org/1998/Math/MathML";
const node_env = globalThis.process?.env?.NODE_ENV;
const DEV = node_env && !node_env.toLowerCase().startsWith("prod");
var is_array = Array.isArray;
var index_of = Array.prototype.indexOf;
var includes = Array.prototype.includes;
var array_from = Array.from;
var define_property = Object.defineProperty;
var get_descriptor = Object.getOwnPropertyDescriptor;
var get_descriptors = Object.getOwnPropertyDescriptors;
var object_prototype = Object.prototype;
var array_prototype = Array.prototype;
var get_prototype_of = Object.getPrototypeOf;
var is_extensible = Object.isExtensible;
const noop = () => {
};
function run(fn) {
  return fn();
}
function run_all(arr) {
  for (var i = 0; i < arr.length; i++) {
    arr[i]();
  }
}
function deferred() {
  var resolve;
  var reject;
  var promise = new Promise((res, rej) => {
    resolve = res;
    reject = rej;
  });
  return { promise, resolve, reject };
}
function to_array(value, n) {
  if (Array.isArray(value)) {
    return value;
  }
  if (!(Symbol.iterator in value)) {
    return Array.from(value);
  }
  const array = [];
  for (const element of value) {
    array.push(element);
    if (array.length === n) break;
  }
  return array;
}
const DERIVED = 1 << 1;
const EFFECT = 1 << 2;
const RENDER_EFFECT = 1 << 3;
const MANAGED_EFFECT = 1 << 24;
const BLOCK_EFFECT = 1 << 4;
const BRANCH_EFFECT = 1 << 5;
const ROOT_EFFECT = 1 << 6;
const BOUNDARY_EFFECT = 1 << 7;
const CONNECTED = 1 << 9;
const CLEAN = 1 << 10;
const DIRTY = 1 << 11;
const MAYBE_DIRTY = 1 << 12;
const INERT = 1 << 13;
const DESTROYED = 1 << 14;
const REACTION_RAN = 1 << 15;
const DESTROYING = 1 << 25;
const EFFECT_TRANSPARENT = 1 << 16;
const EAGER_EFFECT = 1 << 17;
const HEAD_EFFECT = 1 << 18;
const EFFECT_PRESERVED = 1 << 19;
const USER_EFFECT = 1 << 20;
const EFFECT_OFFSCREEN = 1 << 25;
const WAS_MARKED = 1 << 16;
const REACTION_IS_UPDATING = 1 << 21;
const ASYNC = 1 << 22;
const ERROR_VALUE = 1 << 23;
const STATE_SYMBOL = Symbol("$state");
const LEGACY_PROPS = Symbol("legacy props");
const LOADING_ATTR_SYMBOL = Symbol("");
const PROXY_PATH_SYMBOL = Symbol("proxy path");
const HMR_ANCHOR = Symbol("hmr anchor");
const STALE_REACTION = new class StaleReactionError extends Error {
  name = "StaleReactionError";
  message = "The reaction that called `getAbortSignal()` was re-run or destroyed";
}();
function invalid_default_snippet() {
  if (DEV) {
    const error = new Error(`invalid_default_snippet
Cannot use \`{@render children(...)}\` if the parent component uses \`let:\` directives. Consider using a named snippet instead
https://svelte.dev/e/invalid_default_snippet`);
    error.name = "Svelte error";
    throw error;
  } else {
    throw new Error(`https://svelte.dev/e/invalid_default_snippet`);
  }
}
function invariant_violation(message) {
  if (DEV) {
    const error = new Error(`invariant_violation
An invariant violation occurred, meaning Svelte's internal assumptions were flawed. This is a bug in Svelte, not your app — please open an issue at https://github.com/sveltejs/svelte, citing the following message: "${message}"
https://svelte.dev/e/invariant_violation`);
    error.name = "Svelte error";
    throw error;
  } else {
    throw new Error(`https://svelte.dev/e/invariant_violation`);
  }
}
function lifecycle_outside_component(name2) {
  if (DEV) {
    const error = new Error(`lifecycle_outside_component
\`${name2}(...)\` can only be used during component initialisation
https://svelte.dev/e/lifecycle_outside_component`);
    error.name = "Svelte error";
    throw error;
  } else {
    throw new Error(`https://svelte.dev/e/lifecycle_outside_component`);
  }
}
function async_derived_orphan() {
  if (DEV) {
    const error = new Error(`async_derived_orphan
Cannot create a \`$derived(...)\` with an \`await\` expression outside of an effect tree
https://svelte.dev/e/async_derived_orphan`);
    error.name = "Svelte error";
    throw error;
  } else {
    throw new Error(`https://svelte.dev/e/async_derived_orphan`);
  }
}
function bind_invalid_checkbox_value() {
  if (DEV) {
    const error = new Error(`bind_invalid_checkbox_value
Using \`bind:value\` together with a checkbox input is not allowed. Use \`bind:checked\` instead
https://svelte.dev/e/bind_invalid_checkbox_value`);
    error.name = "Svelte error";
    throw error;
  } else {
    throw new Error(`https://svelte.dev/e/bind_invalid_checkbox_value`);
  }
}
function derived_references_self() {
  if (DEV) {
    const error = new Error(`derived_references_self
A derived value cannot reference itself recursively
https://svelte.dev/e/derived_references_self`);
    error.name = "Svelte error";
    throw error;
  } else {
    throw new Error(`https://svelte.dev/e/derived_references_self`);
  }
}
function each_key_duplicate(a, b, value) {
  if (DEV) {
    const error = new Error(`each_key_duplicate
${value ? `Keyed each block has duplicate key \`${value}\` at indexes ${a} and ${b}` : `Keyed each block has duplicate key at indexes ${a} and ${b}`}
https://svelte.dev/e/each_key_duplicate`);
    error.name = "Svelte error";
    throw error;
  } else {
    throw new Error(`https://svelte.dev/e/each_key_duplicate`);
  }
}
function each_key_volatile(index2, a, b) {
  if (DEV) {
    const error = new Error(`each_key_volatile
Keyed each block has key that is not idempotent — the key for item at index ${index2} was \`${a}\` but is now \`${b}\`. Keys must be the same each time for a given item
https://svelte.dev/e/each_key_volatile`);
    error.name = "Svelte error";
    throw error;
  } else {
    throw new Error(`https://svelte.dev/e/each_key_volatile`);
  }
}
function effect_in_teardown(rune) {
  if (DEV) {
    const error = new Error(`effect_in_teardown
\`${rune}\` cannot be used inside an effect cleanup function
https://svelte.dev/e/effect_in_teardown`);
    error.name = "Svelte error";
    throw error;
  } else {
    throw new Error(`https://svelte.dev/e/effect_in_teardown`);
  }
}
function effect_in_unowned_derived() {
  if (DEV) {
    const error = new Error(`effect_in_unowned_derived
Effect cannot be created inside a \`$derived\` value that was not itself created inside an effect
https://svelte.dev/e/effect_in_unowned_derived`);
    error.name = "Svelte error";
    throw error;
  } else {
    throw new Error(`https://svelte.dev/e/effect_in_unowned_derived`);
  }
}
function effect_orphan(rune) {
  if (DEV) {
    const error = new Error(`effect_orphan
\`${rune}\` can only be used inside an effect (e.g. during component initialisation)
https://svelte.dev/e/effect_orphan`);
    error.name = "Svelte error";
    throw error;
  } else {
    throw new Error(`https://svelte.dev/e/effect_orphan`);
  }
}
function effect_update_depth_exceeded() {
  if (DEV) {
    const error = new Error(`effect_update_depth_exceeded
Maximum update depth exceeded. This typically indicates that an effect reads and writes the same piece of state
https://svelte.dev/e/effect_update_depth_exceeded`);
    error.name = "Svelte error";
    throw error;
  } else {
    throw new Error(`https://svelte.dev/e/effect_update_depth_exceeded`);
  }
}
function lifecycle_legacy_only(name2) {
  if (DEV) {
    const error = new Error(`lifecycle_legacy_only
\`${name2}(...)\` cannot be used in runes mode
https://svelte.dev/e/lifecycle_legacy_only`);
    error.name = "Svelte error";
    throw error;
  } else {
    throw new Error(`https://svelte.dev/e/lifecycle_legacy_only`);
  }
}
function props_invalid_value(key) {
  if (DEV) {
    const error = new Error(`props_invalid_value
Cannot do \`bind:${key}={undefined}\` when \`${key}\` has a fallback value
https://svelte.dev/e/props_invalid_value`);
    error.name = "Svelte error";
    throw error;
  } else {
    throw new Error(`https://svelte.dev/e/props_invalid_value`);
  }
}
function rune_outside_svelte(rune) {
  if (DEV) {
    const error = new Error(`rune_outside_svelte
The \`${rune}\` rune is only available inside \`.svelte\` and \`.svelte.js/ts\` files
https://svelte.dev/e/rune_outside_svelte`);
    error.name = "Svelte error";
    throw error;
  } else {
    throw new Error(`https://svelte.dev/e/rune_outside_svelte`);
  }
}
function state_descriptors_fixed() {
  if (DEV) {
    const error = new Error(`state_descriptors_fixed
Property descriptors defined on \`$state\` objects must contain \`value\` and always be \`enumerable\`, \`configurable\` and \`writable\`.
https://svelte.dev/e/state_descriptors_fixed`);
    error.name = "Svelte error";
    throw error;
  } else {
    throw new Error(`https://svelte.dev/e/state_descriptors_fixed`);
  }
}
function state_prototype_fixed() {
  if (DEV) {
    const error = new Error(`state_prototype_fixed
Cannot set prototype of \`$state\` object
https://svelte.dev/e/state_prototype_fixed`);
    error.name = "Svelte error";
    throw error;
  } else {
    throw new Error(`https://svelte.dev/e/state_prototype_fixed`);
  }
}
function state_unsafe_mutation() {
  if (DEV) {
    const error = new Error(`state_unsafe_mutation
Updating state inside \`$derived(...)\`, \`$inspect(...)\` or a template expression is forbidden. If the value should not be reactive, declare it without \`$state\`
https://svelte.dev/e/state_unsafe_mutation`);
    error.name = "Svelte error";
    throw error;
  } else {
    throw new Error(`https://svelte.dev/e/state_unsafe_mutation`);
  }
}
function svelte_boundary_reset_onerror() {
  if (DEV) {
    const error = new Error(`svelte_boundary_reset_onerror
A \`<svelte:boundary>\` \`reset\` function cannot be called while an error is still being handled
https://svelte.dev/e/svelte_boundary_reset_onerror`);
    error.name = "Svelte error";
    throw error;
  } else {
    throw new Error(`https://svelte.dev/e/svelte_boundary_reset_onerror`);
  }
}
var bold = "font-weight: bold";
var normal = "font-weight: normal";
function await_reactivity_loss(name2) {
  if (DEV) {
    console.warn(`%c[svelte] await_reactivity_loss
%cDetected reactivity loss when reading \`${name2}\`. This happens when state is read in an async function after an earlier \`await\`
https://svelte.dev/e/await_reactivity_loss`, bold, normal);
  } else {
    console.warn(`https://svelte.dev/e/await_reactivity_loss`);
  }
}
function derived_inert() {
  if (DEV) {
    console.warn(`%c[svelte] derived_inert
%cReading a derived belonging to a now-destroyed effect may result in stale values
https://svelte.dev/e/derived_inert`, bold, normal);
  } else {
    console.warn(`https://svelte.dev/e/derived_inert`);
  }
}
function lifecycle_double_unmount() {
  if (DEV) {
    console.warn(`%c[svelte] lifecycle_double_unmount
%cTried to unmount a component that was not mounted
https://svelte.dev/e/lifecycle_double_unmount`, bold, normal);
  } else {
    console.warn(`https://svelte.dev/e/lifecycle_double_unmount`);
  }
}
function select_multiple_invalid_value() {
  if (DEV) {
    console.warn(`%c[svelte] select_multiple_invalid_value
%cThe \`value\` property of a \`<select multiple>\` element should be an array, but it received a non-array value. The selection will be kept as is.
https://svelte.dev/e/select_multiple_invalid_value`, bold, normal);
  } else {
    console.warn(`https://svelte.dev/e/select_multiple_invalid_value`);
  }
}
function state_proxy_equality_mismatch(operator) {
  if (DEV) {
    console.warn(`%c[svelte] state_proxy_equality_mismatch
%cReactive \`$state(...)\` proxies and the values they proxy have different identities. Because of this, comparisons with \`${operator}\` will produce unexpected results
https://svelte.dev/e/state_proxy_equality_mismatch`, bold, normal);
  } else {
    console.warn(`https://svelte.dev/e/state_proxy_equality_mismatch`);
  }
}
function state_proxy_unmount() {
  if (DEV) {
    console.warn(`%c[svelte] state_proxy_unmount
%cTried to unmount a state proxy, rather than a component
https://svelte.dev/e/state_proxy_unmount`, bold, normal);
  } else {
    console.warn(`https://svelte.dev/e/state_proxy_unmount`);
  }
}
function svelte_boundary_reset_noop() {
  if (DEV) {
    console.warn(`%c[svelte] svelte_boundary_reset_noop
%cA \`<svelte:boundary>\` \`reset\` function only resets the boundary the first time it is called
https://svelte.dev/e/svelte_boundary_reset_noop`, bold, normal);
  } else {
    console.warn(`https://svelte.dev/e/svelte_boundary_reset_noop`);
  }
}
function equals(value) {
  return value === this.v;
}
function safe_not_equal(a, b) {
  return a != a ? b == b : a !== b || a !== null && typeof a === "object" || typeof a === "function";
}
function safe_equals(value) {
  return !safe_not_equal(value, this.v);
}
function tag$1(source2, label) {
  source2.label = label;
  tag_proxy(source2.v, label);
  return source2;
}
function tag_proxy(value, label) {
  value?.[PROXY_PATH_SYMBOL]?.(label);
  return value;
}
function get_error(label) {
  const error = new Error();
  const stack2 = get_stack();
  if (stack2.length === 0) {
    return null;
  }
  stack2.unshift("\n");
  define_property(error, "stack", {
    value: stack2.join("\n")
  });
  define_property(error, "name", {
    value: label
  });
  return (
    /** @type {Error & { stack: string }} */
    error
  );
}
function get_stack() {
  const limit = Error.stackTraceLimit;
  Error.stackTraceLimit = Infinity;
  const stack2 = new Error().stack;
  Error.stackTraceLimit = limit;
  if (!stack2) return [];
  const lines = stack2.split("\n");
  const new_lines = [];
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    const posixified = line.replaceAll("\\", "/");
    if (line.trim() === "Error") {
      continue;
    }
    if (line.includes("validate_each_keys")) {
      return [];
    }
    if (posixified.includes("svelte/src/internal") || posixified.includes("node_modules/.vite")) {
      continue;
    }
    new_lines.push(line);
  }
  return new_lines;
}
function invariant(condition, message) {
  if (!DEV) {
    throw new Error("invariant(...) was not guarded by if (DEV)");
  }
  if (!condition) invariant_violation(message);
}
let component_context = null;
function set_component_context(context) {
  component_context = context;
}
let dev_stack = null;
function set_dev_stack(stack2) {
  dev_stack = stack2;
}
let dev_current_component_function = null;
function set_dev_current_component_function(fn) {
  dev_current_component_function = fn;
}
function push(props, runes = false, fn) {
  component_context = {
    p: component_context,
    i: false,
    c: null,
    e: null,
    s: props,
    x: null,
    r: (
      /** @type {Effect} */
      active_effect
    ),
    l: legacy_mode_flag && !runes ? { s: null, u: null, $: [] } : null
  };
  if (DEV) {
    component_context.function = fn;
    dev_current_component_function = fn;
  }
}
function pop(component2) {
  var context = (
    /** @type {ComponentContext} */
    component_context
  );
  var effects = context.e;
  if (effects !== null) {
    context.e = null;
    for (var fn of effects) {
      create_user_effect(fn);
    }
  }
  if (component2 !== void 0) {
    context.x = component2;
  }
  context.i = true;
  component_context = context.p;
  if (DEV) {
    dev_current_component_function = component_context?.function ?? null;
  }
  return component2 ?? /** @type {T} */
  {};
}
function is_runes() {
  return !legacy_mode_flag || component_context !== null && component_context.l === null;
}
let micro_tasks = [];
function run_micro_tasks() {
  var tasks = micro_tasks;
  micro_tasks = [];
  run_all(tasks);
}
function queue_micro_task(fn) {
  if (micro_tasks.length === 0 && !is_flushing_sync) {
    var tasks = micro_tasks;
    queueMicrotask(() => {
      if (tasks === micro_tasks) run_micro_tasks();
    });
  }
  micro_tasks.push(fn);
}
function flush_tasks() {
  while (micro_tasks.length > 0) {
    run_micro_tasks();
  }
}
const adjustments = /* @__PURE__ */ new WeakMap();
function handle_error(error) {
  var effect2 = active_effect;
  if (effect2 === null) {
    active_reaction.f |= ERROR_VALUE;
    return error;
  }
  if (DEV && error instanceof Error && !adjustments.has(error)) {
    adjustments.set(error, get_adjustments(error, effect2));
  }
  if ((effect2.f & REACTION_RAN) === 0 && (effect2.f & EFFECT) === 0) {
    if (DEV && !effect2.parent && error instanceof Error) {
      apply_adjustments(error);
    }
    throw error;
  }
  invoke_error_boundary(error, effect2);
}
function invoke_error_boundary(error, effect2) {
  while (effect2 !== null) {
    if ((effect2.f & BOUNDARY_EFFECT) !== 0) {
      if ((effect2.f & REACTION_RAN) === 0) {
        throw error;
      }
      try {
        effect2.b.error(error);
        return;
      } catch (e) {
        error = e;
      }
    }
    effect2 = effect2.parent;
  }
  if (DEV && error instanceof Error) {
    apply_adjustments(error);
  }
  throw error;
}
function get_adjustments(error, effect2) {
  const message_descriptor = get_descriptor(error, "message");
  if (message_descriptor && !message_descriptor.configurable) return;
  var indent = is_firefox ? "  " : "	";
  var component_stack = `
${indent}in ${effect2.fn?.name || "<unknown>"}`;
  var context = effect2.ctx;
  while (context !== null) {
    component_stack += `
${indent}in ${context.function?.[FILENAME].split("/").pop()}`;
    context = context.p;
  }
  return {
    message: error.message + `
${component_stack}
`,
    stack: error.stack?.split("\n").filter((line) => !line.includes("svelte/src/internal")).join("\n")
  };
}
function apply_adjustments(error) {
  const adjusted = adjustments.get(error);
  if (adjusted) {
    define_property(error, "message", {
      value: adjusted.message
    });
    define_property(error, "stack", {
      value: adjusted.stack
    });
  }
}
const STATUS_MASK = -7169;
function set_signal_status(signal, status) {
  signal.f = signal.f & STATUS_MASK | status;
}
function update_derived_status(derived2) {
  if ((derived2.f & CONNECTED) !== 0 || derived2.deps === null) {
    set_signal_status(derived2, CLEAN);
  } else {
    set_signal_status(derived2, MAYBE_DIRTY);
  }
}
function clear_marked(deps) {
  if (deps === null) return;
  for (const dep of deps) {
    if ((dep.f & DERIVED) === 0 || (dep.f & WAS_MARKED) === 0) {
      continue;
    }
    dep.f ^= WAS_MARKED;
    clear_marked(
      /** @type {Derived} */
      dep.deps
    );
  }
}
function defer_effect(effect2, dirty_effects, maybe_dirty_effects) {
  if ((effect2.f & DIRTY) !== 0) {
    dirty_effects.add(effect2);
  } else if ((effect2.f & MAYBE_DIRTY) !== 0) {
    maybe_dirty_effects.add(effect2);
  }
  clear_marked(effect2.deps);
  set_signal_status(effect2, CLEAN);
}
function subscribe_to_store(store, run2, invalidate) {
  if (store == null) {
    run2(void 0);
    return noop;
  }
  const unsub = untrack(
    () => store.subscribe(
      run2,
      // @ts-expect-error
      invalidate
    )
  );
  return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub;
}
const subscriber_queue = [];
function writable(value, start = noop) {
  let stop = null;
  const subscribers = /* @__PURE__ */ new Set();
  function set2(new_value) {
    if (safe_not_equal(value, new_value)) {
      value = new_value;
      if (stop) {
        const run_queue = !subscriber_queue.length;
        for (const subscriber of subscribers) {
          subscriber[1]();
          subscriber_queue.push(subscriber, value);
        }
        if (run_queue) {
          for (let i = 0; i < subscriber_queue.length; i += 2) {
            subscriber_queue[i][0](subscriber_queue[i + 1]);
          }
          subscriber_queue.length = 0;
        }
      }
    }
  }
  function update2(fn) {
    set2(fn(
      /** @type {T} */
      value
    ));
  }
  function subscribe(run2, invalidate = noop) {
    const subscriber = [run2, invalidate];
    subscribers.add(subscriber);
    if (subscribers.size === 1) {
      stop = start(set2, update2) || noop;
    }
    run2(
      /** @type {T} */
      value
    );
    return () => {
      subscribers.delete(subscriber);
      if (subscribers.size === 0 && stop) {
        stop();
        stop = null;
      }
    };
  }
  return { set: set2, update: update2, subscribe };
}
function get$1(store) {
  let value;
  subscribe_to_store(store, (_) => value = _)();
  return value;
}
let legacy_is_updating_store = false;
let is_store_binding = false;
let IS_UNMOUNTED = Symbol();
function store_get(store, store_name, stores) {
  const entry = stores[store_name] ??= {
    store: null,
    source: /* @__PURE__ */ mutable_source(void 0),
    unsubscribe: noop
  };
  if (DEV) {
    entry.source.label = store_name;
  }
  if (entry.store !== store && !(IS_UNMOUNTED in stores)) {
    entry.unsubscribe();
    entry.store = store ?? null;
    if (store == null) {
      entry.source.v = void 0;
      entry.unsubscribe = noop;
    } else {
      var is_synchronous_callback = true;
      entry.unsubscribe = subscribe_to_store(store, (v) => {
        if (is_synchronous_callback) {
          entry.source.v = v;
        } else {
          set(entry.source, v);
        }
      });
      is_synchronous_callback = false;
    }
  }
  if (store && IS_UNMOUNTED in stores) {
    return get$1(store);
  }
  return get(entry.source);
}
function store_set(store, value) {
  update_with_flag(store, value);
  return value;
}
function setup_stores() {
  const stores = {};
  function cleanup() {
    teardown(() => {
      for (var store_name in stores) {
        const ref = stores[store_name];
        ref.unsubscribe();
      }
      define_property(stores, IS_UNMOUNTED, {
        enumerable: false,
        value: true
      });
    });
  }
  return [stores, cleanup];
}
function update_with_flag(store, value) {
  legacy_is_updating_store = true;
  try {
    store.set(value);
  } finally {
    legacy_is_updating_store = false;
  }
}
function capture_store_binding(fn) {
  var previous_is_store_binding = is_store_binding;
  try {
    is_store_binding = false;
    return [fn(), is_store_binding];
  } finally {
    is_store_binding = previous_is_store_binding;
  }
}
const batches = /* @__PURE__ */ new Set();
let current_batch = null;
let batch_values = null;
let last_scheduled_effect = null;
let is_flushing_sync = false;
let is_processing = false;
let collected_effects = null;
let legacy_updates = null;
var flush_count = 0;
var source_stacks = DEV ? /* @__PURE__ */ new Set() : null;
let uid = 1;
class Batch {
  id = uid++;
  /**
   * The current values of any signals that are updated in this batch.
   * Tuple format: [value, is_derived] (note: is_derived is false for deriveds, too, if they were overridden via assignment)
   * They keys of this map are identical to `this.#previous`
   * @type {Map<Value, [any, boolean]>}
   */
  current = /* @__PURE__ */ new Map();
  /**
   * The values of any signals (sources and deriveds) that are updated in this batch _before_ those updates took place.
   * They keys of this map are identical to `this.#current`
   * @type {Map<Value, any>}
   */
  previous = /* @__PURE__ */ new Map();
  /**
   * When the batch is committed (and the DOM is updated), we need to remove old branches
   * and append new ones by calling the functions added inside (if/each/key/etc) blocks
   * @type {Set<(batch: Batch) => void>}
   */
  #commit_callbacks = /* @__PURE__ */ new Set();
  /**
   * If a fork is discarded, we need to destroy any effects that are no longer needed
   * @type {Set<(batch: Batch) => void>}
   */
  #discard_callbacks = /* @__PURE__ */ new Set();
  /**
   * Callbacks that should run only when a fork is committed.
   * @type {Set<(batch: Batch) => void>}
   */
  #fork_commit_callbacks = /* @__PURE__ */ new Set();
  /**
   * Async effects that are currently in flight
   * @type {Map<Effect, number>}
   */
  #pending = /* @__PURE__ */ new Map();
  /**
   * Async effects that are currently in flight, _not_ inside a pending boundary
   * @type {Map<Effect, number>}
   */
  #blocking_pending = /* @__PURE__ */ new Map();
  /**
   * A deferred that resolves when the batch is committed, used with `settled()`
   * TODO replace with Promise.withResolvers once supported widely enough
   * @type {{ promise: Promise<void>, resolve: (value?: any) => void, reject: (reason: unknown) => void } | null}
   */
  #deferred = null;
  /**
   * The root effects that need to be flushed
   * @type {Effect[]}
   */
  #roots = [];
  /**
   * Effects created while this batch was active.
   * @type {Effect[]}
   */
  #new_effects = [];
  /**
   * Deferred effects (which run after async work has completed) that are DIRTY
   * @type {Set<Effect>}
   */
  #dirty_effects = /* @__PURE__ */ new Set();
  /**
   * Deferred effects that are MAYBE_DIRTY
   * @type {Set<Effect>}
   */
  #maybe_dirty_effects = /* @__PURE__ */ new Set();
  /**
   * A map of branches that still exist, but will be destroyed when this batch
   * is committed — we skip over these during `process`.
   * The value contains child effects that were dirty/maybe_dirty before being reset,
   * so they can be rescheduled if the branch survives.
   * @type {Map<Effect, { d: Effect[], m: Effect[] }>}
   */
  #skipped_branches = /* @__PURE__ */ new Map();
  /**
   * Inverse of #skipped_branches which we need to tell prior batches to unskip them when committing
   * @type {Set<Effect>}
   */
  #unskipped_branches = /* @__PURE__ */ new Set();
  is_fork = false;
  #decrement_queued = false;
  /** @type {Set<Batch>} */
  #blockers = /* @__PURE__ */ new Set();
  #is_deferred() {
    return this.is_fork || this.#blocking_pending.size > 0;
  }
  #is_blocked() {
    for (const batch of this.#blockers) {
      for (const effect2 of batch.#blocking_pending.keys()) {
        var skipped = false;
        var e = effect2;
        while (e.parent !== null) {
          if (this.#skipped_branches.has(e)) {
            skipped = true;
            break;
          }
          e = e.parent;
        }
        if (!skipped) {
          return true;
        }
      }
    }
    return false;
  }
  /**
   * Add an effect to the #skipped_branches map and reset its children
   * @param {Effect} effect
   */
  skip_effect(effect2) {
    if (!this.#skipped_branches.has(effect2)) {
      this.#skipped_branches.set(effect2, { d: [], m: [] });
    }
    this.#unskipped_branches.delete(effect2);
  }
  /**
   * Remove an effect from the #skipped_branches map and reschedule
   * any tracked dirty/maybe_dirty child effects
   * @param {Effect} effect
   * @param {(e: Effect) => void} callback
   */
  unskip_effect(effect2, callback = (e) => this.schedule(e)) {
    var tracked = this.#skipped_branches.get(effect2);
    if (tracked) {
      this.#skipped_branches.delete(effect2);
      for (var e of tracked.d) {
        set_signal_status(e, DIRTY);
        callback(e);
      }
      for (e of tracked.m) {
        set_signal_status(e, MAYBE_DIRTY);
        callback(e);
      }
    }
    this.#unskipped_branches.add(effect2);
  }
  #process() {
    if (flush_count++ > 1e3) {
      batches.delete(this);
      infinite_loop_guard();
    }
    if (!this.#is_deferred()) {
      for (const e of this.#dirty_effects) {
        this.#maybe_dirty_effects.delete(e);
        set_signal_status(e, DIRTY);
        this.schedule(e);
      }
      for (const e of this.#maybe_dirty_effects) {
        set_signal_status(e, MAYBE_DIRTY);
        this.schedule(e);
      }
    }
    const roots = this.#roots;
    this.#roots = [];
    this.apply();
    var effects = collected_effects = [];
    var render_effects = [];
    var updates = legacy_updates = [];
    for (const root2 of roots) {
      try {
        this.#traverse(root2, effects, render_effects);
      } catch (e) {
        reset_all(root2);
        throw e;
      }
    }
    current_batch = null;
    if (updates.length > 0) {
      var batch = Batch.ensure();
      for (const e of updates) {
        batch.schedule(e);
      }
    }
    collected_effects = null;
    legacy_updates = null;
    if (this.#is_deferred() || this.#is_blocked()) {
      this.#defer_effects(render_effects);
      this.#defer_effects(effects);
      for (const [e, t] of this.#skipped_branches) {
        reset_branch(e, t);
      }
    } else {
      if (this.#pending.size === 0) {
        batches.delete(this);
      }
      this.#dirty_effects.clear();
      this.#maybe_dirty_effects.clear();
      for (const fn of this.#commit_callbacks) fn(this);
      this.#commit_callbacks.clear();
      flush_queued_effects(render_effects);
      flush_queued_effects(effects);
      this.#deferred?.resolve();
    }
    var next_batch = (
      /** @type {Batch | null} */
      /** @type {unknown} */
      current_batch
    );
    if (this.#roots.length > 0) {
      const batch2 = next_batch ??= this;
      batch2.#roots.push(...this.#roots.filter((r) => !batch2.#roots.includes(r)));
    }
    if (next_batch !== null) {
      batches.add(next_batch);
      if (DEV) {
        for (const source2 of this.current.keys()) {
          source_stacks.add(source2);
        }
      }
      next_batch.#process();
    }
  }
  /**
   * Traverse the effect tree, executing effects or stashing
   * them for later execution as appropriate
   * @param {Effect} root
   * @param {Effect[]} effects
   * @param {Effect[]} render_effects
   */
  #traverse(root2, effects, render_effects) {
    root2.f ^= CLEAN;
    var effect2 = root2.first;
    while (effect2 !== null) {
      var flags2 = effect2.f;
      var is_branch = (flags2 & (BRANCH_EFFECT | ROOT_EFFECT)) !== 0;
      var is_skippable_branch = is_branch && (flags2 & CLEAN) !== 0;
      var skip = is_skippable_branch || (flags2 & INERT) !== 0 || this.#skipped_branches.has(effect2);
      if (!skip && effect2.fn !== null) {
        if (is_branch) {
          effect2.f ^= CLEAN;
        } else if ((flags2 & EFFECT) !== 0) {
          effects.push(effect2);
        } else if (is_dirty(effect2)) {
          if ((flags2 & BLOCK_EFFECT) !== 0) this.#maybe_dirty_effects.add(effect2);
          update_effect(effect2);
        }
        var child2 = effect2.first;
        if (child2 !== null) {
          effect2 = child2;
          continue;
        }
      }
      while (effect2 !== null) {
        var next = effect2.next;
        if (next !== null) {
          effect2 = next;
          break;
        }
        effect2 = effect2.parent;
      }
    }
  }
  /**
   * @param {Effect[]} effects
   */
  #defer_effects(effects) {
    for (var i = 0; i < effects.length; i += 1) {
      defer_effect(effects[i], this.#dirty_effects, this.#maybe_dirty_effects);
    }
  }
  /**
   * Associate a change to a given source with the current
   * batch, noting its previous and current values
   * @param {Value} source
   * @param {any} value
   * @param {boolean} [is_derived]
   */
  capture(source2, value, is_derived = false) {
    if (source2.v !== UNINITIALIZED && !this.previous.has(source2)) {
      this.previous.set(source2, source2.v);
    }
    if ((source2.f & ERROR_VALUE) === 0) {
      this.current.set(source2, [value, is_derived]);
      batch_values?.set(source2, value);
    }
    if (!this.is_fork) {
      source2.v = value;
    }
  }
  activate() {
    current_batch = this;
  }
  deactivate() {
    current_batch = null;
    batch_values = null;
  }
  flush() {
    var source_stacks2 = DEV ? /* @__PURE__ */ new Set() : null;
    try {
      is_processing = true;
      current_batch = this;
      this.#process();
    } finally {
      flush_count = 0;
      last_scheduled_effect = null;
      collected_effects = null;
      legacy_updates = null;
      is_processing = false;
      current_batch = null;
      batch_values = null;
      old_values.clear();
      if (DEV) {
        for (
          const source2 of
          /** @type {Set<Source>} */
          source_stacks2
        ) {
          source2.updated = null;
        }
      }
    }
  }
  discard() {
    for (const fn of this.#discard_callbacks) fn(this);
    this.#discard_callbacks.clear();
    this.#fork_commit_callbacks.clear();
    batches.delete(this);
  }
  /**
   * @param {Effect} effect
   */
  register_created_effect(effect2) {
    this.#new_effects.push(effect2);
  }
  #commit() {
    for (const batch of batches) {
      var is_earlier = batch.id < this.id;
      var sources = [];
      for (const [source3, [value, is_derived]] of this.current) {
        if (batch.current.has(source3)) {
          var batch_value = (
            /** @type {[any, boolean]} */
            batch.current.get(source3)[0]
          );
          if (is_earlier && value !== batch_value) {
            batch.current.set(source3, [value, is_derived]);
          } else {
            continue;
          }
        }
        sources.push(source3);
      }
      var others = [...batch.current.keys()].filter((s) => !this.current.has(s));
      if (others.length === 0) {
        if (is_earlier) {
          batch.discard();
        }
      } else if (sources.length > 0) {
        if (DEV) {
          invariant(batch.#roots.length === 0, "Batch has scheduled roots");
        }
        if (is_earlier) {
          for (const unskipped of this.#unskipped_branches) {
            batch.unskip_effect(unskipped, (e) => {
              if ((e.f & (BLOCK_EFFECT | ASYNC)) !== 0) {
                batch.schedule(e);
              } else {
                batch.#defer_effects([e]);
              }
            });
          }
        }
        batch.activate();
        var marked2 = /* @__PURE__ */ new Set();
        var checked = /* @__PURE__ */ new Map();
        for (var source2 of sources) {
          mark_effects(source2, others, marked2, checked);
        }
        checked = /* @__PURE__ */ new Map();
        var current_unequal = [...batch.current.keys()].filter(
          (c) => this.current.has(c) ? (
            /** @type {[any, boolean]} */
            this.current.get(c)[0] !== c
          ) : true
        );
        for (const effect2 of this.#new_effects) {
          if ((effect2.f & (DESTROYED | INERT | EAGER_EFFECT)) === 0 && depends_on(effect2, current_unequal, checked)) {
            if ((effect2.f & (ASYNC | BLOCK_EFFECT)) !== 0) {
              set_signal_status(effect2, DIRTY);
              batch.schedule(effect2);
            } else {
              batch.#dirty_effects.add(effect2);
            }
          }
        }
        if (batch.#roots.length > 0) {
          batch.apply();
          for (var root2 of batch.#roots) {
            batch.#traverse(root2, [], []);
          }
          batch.#roots = [];
        }
        batch.deactivate();
      }
    }
    for (const batch of batches) {
      if (batch.#blockers.has(this)) {
        batch.#blockers.delete(this);
        if (batch.#blockers.size === 0 && !batch.#is_deferred()) {
          batch.activate();
          batch.#process();
        }
      }
    }
  }
  /**
   * @param {boolean} blocking
   * @param {Effect} effect
   */
  increment(blocking, effect2) {
    let pending_count = this.#pending.get(effect2) ?? 0;
    this.#pending.set(effect2, pending_count + 1);
    if (blocking) {
      let blocking_pending_count = this.#blocking_pending.get(effect2) ?? 0;
      this.#blocking_pending.set(effect2, blocking_pending_count + 1);
    }
  }
  /**
   * @param {boolean} blocking
   * @param {Effect} effect
   * @param {boolean} skip - whether to skip updates (because this is triggered by a stale reaction)
   */
  decrement(blocking, effect2, skip) {
    let pending_count = this.#pending.get(effect2) ?? 0;
    if (pending_count === 1) {
      this.#pending.delete(effect2);
    } else {
      this.#pending.set(effect2, pending_count - 1);
    }
    if (blocking) {
      let blocking_pending_count = this.#blocking_pending.get(effect2) ?? 0;
      if (blocking_pending_count === 1) {
        this.#blocking_pending.delete(effect2);
      } else {
        this.#blocking_pending.set(effect2, blocking_pending_count - 1);
      }
    }
    if (this.#decrement_queued || skip) return;
    this.#decrement_queued = true;
    queue_micro_task(() => {
      this.#decrement_queued = false;
      this.flush();
    });
  }
  /**
   * @param {Set<Effect>} dirty_effects
   * @param {Set<Effect>} maybe_dirty_effects
   */
  transfer_effects(dirty_effects, maybe_dirty_effects) {
    for (const e of dirty_effects) {
      this.#dirty_effects.add(e);
    }
    for (const e of maybe_dirty_effects) {
      this.#maybe_dirty_effects.add(e);
    }
    dirty_effects.clear();
    maybe_dirty_effects.clear();
  }
  /** @param {(batch: Batch) => void} fn */
  oncommit(fn) {
    this.#commit_callbacks.add(fn);
  }
  /** @param {(batch: Batch) => void} fn */
  ondiscard(fn) {
    this.#discard_callbacks.add(fn);
  }
  /** @param {(batch: Batch) => void} fn */
  on_fork_commit(fn) {
    this.#fork_commit_callbacks.add(fn);
  }
  run_fork_commit_callbacks() {
    for (const fn of this.#fork_commit_callbacks) fn(this);
    this.#fork_commit_callbacks.clear();
  }
  settled() {
    return (this.#deferred ??= deferred()).promise;
  }
  static ensure() {
    if (current_batch === null) {
      const batch = current_batch = new Batch();
      if (!is_processing) {
        batches.add(current_batch);
        if (!is_flushing_sync) {
          queue_micro_task(() => {
            if (current_batch !== batch) {
              return;
            }
            batch.flush();
          });
        }
      }
    }
    return current_batch;
  }
  apply() {
    {
      batch_values = null;
      return;
    }
  }
  /**
   *
   * @param {Effect} effect
   */
  schedule(effect2) {
    last_scheduled_effect = effect2;
    if (effect2.b?.is_pending && (effect2.f & (EFFECT | RENDER_EFFECT | MANAGED_EFFECT)) !== 0 && (effect2.f & REACTION_RAN) === 0) {
      effect2.b.defer_effect(effect2);
      return;
    }
    var e = effect2;
    while (e.parent !== null) {
      e = e.parent;
      var flags2 = e.f;
      if (collected_effects !== null && e === active_effect) {
        if ((active_reaction === null || (active_reaction.f & DERIVED) === 0) && !legacy_is_updating_store) {
          return;
        }
      }
      if ((flags2 & (ROOT_EFFECT | BRANCH_EFFECT)) !== 0) {
        if ((flags2 & CLEAN) === 0) {
          return;
        }
        e.f ^= CLEAN;
      }
    }
    this.#roots.push(e);
  }
}
function flushSync(fn) {
  var was_flushing_sync = is_flushing_sync;
  is_flushing_sync = true;
  try {
    var result;
    if (fn) ;
    while (true) {
      flush_tasks();
      if (current_batch === null) {
        return (
          /** @type {T} */
          result
        );
      }
      current_batch.flush();
    }
  } finally {
    is_flushing_sync = was_flushing_sync;
  }
}
function infinite_loop_guard() {
  if (DEV) {
    var updates = /* @__PURE__ */ new Map();
    for (
      const source2 of
      /** @type {Batch} */
      current_batch.current.keys()
    ) {
      for (const [stack2, update2] of source2.updated ?? []) {
        var entry = updates.get(stack2);
        if (!entry) {
          entry = { error: update2.error, count: 0 };
          updates.set(stack2, entry);
        }
        entry.count += update2.count;
      }
    }
    for (const update2 of updates.values()) {
      if (update2.error) {
        console.error(update2.error);
      }
    }
  }
  try {
    effect_update_depth_exceeded();
  } catch (error) {
    if (DEV) {
      define_property(error, "stack", { value: "" });
    }
    invoke_error_boundary(error, last_scheduled_effect);
  }
}
let eager_block_effects = null;
function flush_queued_effects(effects) {
  var length = effects.length;
  if (length === 0) return;
  var i = 0;
  while (i < length) {
    var effect2 = effects[i++];
    if ((effect2.f & (DESTROYED | INERT)) === 0 && is_dirty(effect2)) {
      eager_block_effects = /* @__PURE__ */ new Set();
      update_effect(effect2);
      if (effect2.deps === null && effect2.first === null && effect2.nodes === null && effect2.teardown === null && effect2.ac === null) {
        unlink_effect(effect2);
      }
      if (eager_block_effects?.size > 0) {
        old_values.clear();
        for (const e of eager_block_effects) {
          if ((e.f & (DESTROYED | INERT)) !== 0) continue;
          const ordered_effects = [e];
          let ancestor = e.parent;
          while (ancestor !== null) {
            if (eager_block_effects.has(ancestor)) {
              eager_block_effects.delete(ancestor);
              ordered_effects.push(ancestor);
            }
            ancestor = ancestor.parent;
          }
          for (let j = ordered_effects.length - 1; j >= 0; j--) {
            const e2 = ordered_effects[j];
            if ((e2.f & (DESTROYED | INERT)) !== 0) continue;
            update_effect(e2);
          }
        }
        eager_block_effects.clear();
      }
    }
  }
  eager_block_effects = null;
}
function mark_effects(value, sources, marked2, checked) {
  if (marked2.has(value)) return;
  marked2.add(value);
  if (value.reactions !== null) {
    for (const reaction of value.reactions) {
      const flags2 = reaction.f;
      if ((flags2 & DERIVED) !== 0) {
        mark_effects(
          /** @type {Derived} */
          reaction,
          sources,
          marked2,
          checked
        );
      } else if ((flags2 & (ASYNC | BLOCK_EFFECT)) !== 0 && (flags2 & DIRTY) === 0 && depends_on(reaction, sources, checked)) {
        set_signal_status(reaction, DIRTY);
        schedule_effect(
          /** @type {Effect} */
          reaction
        );
      }
    }
  }
}
function depends_on(reaction, sources, checked) {
  const depends = checked.get(reaction);
  if (depends !== void 0) return depends;
  if (reaction.deps !== null) {
    for (const dep of reaction.deps) {
      if (includes.call(sources, dep)) {
        return true;
      }
      if ((dep.f & DERIVED) !== 0 && depends_on(
        /** @type {Derived} */
        dep,
        sources,
        checked
      )) {
        checked.set(
          /** @type {Derived} */
          dep,
          true
        );
        return true;
      }
    }
  }
  checked.set(reaction, false);
  return false;
}
function schedule_effect(effect2) {
  current_batch.schedule(effect2);
}
function reset_branch(effect2, tracked) {
  if ((effect2.f & BRANCH_EFFECT) !== 0 && (effect2.f & CLEAN) !== 0) {
    return;
  }
  if ((effect2.f & DIRTY) !== 0) {
    tracked.d.push(effect2);
  } else if ((effect2.f & MAYBE_DIRTY) !== 0) {
    tracked.m.push(effect2);
  }
  set_signal_status(effect2, CLEAN);
  var e = effect2.first;
  while (e !== null) {
    reset_branch(e, tracked);
    e = e.next;
  }
}
function reset_all(effect2) {
  set_signal_status(effect2, CLEAN);
  var e = effect2.first;
  while (e !== null) {
    reset_all(e);
    e = e.next;
  }
}
function createSubscriber(start) {
  let subscribers = 0;
  let version = source(0);
  let stop;
  if (DEV) {
    tag$1(version, "createSubscriber version");
  }
  return () => {
    if (effect_tracking()) {
      get(version);
      render_effect(() => {
        if (subscribers === 0) {
          stop = untrack(() => start(() => increment(version)));
        }
        subscribers += 1;
        return () => {
          queue_micro_task(() => {
            subscribers -= 1;
            if (subscribers === 0) {
              stop?.();
              stop = void 0;
              increment(version);
            }
          });
        };
      });
    }
  };
}
var flags = EFFECT_TRANSPARENT | EFFECT_PRESERVED;
function boundary(node, props, children, transform_error) {
  new Boundary(node, props, children, transform_error);
}
class Boundary {
  /** @type {Boundary | null} */
  parent;
  is_pending = false;
  /**
   * API-level transformError transform function. Transforms errors before they reach the `failed` snippet.
   * Inherited from parent boundary, or defaults to identity.
   * @type {(error: unknown) => unknown}
   */
  transform_error;
  /** @type {TemplateNode} */
  #anchor;
  /** @type {TemplateNode | null} */
  #hydrate_open = null;
  /** @type {BoundaryProps} */
  #props;
  /** @type {((anchor: Node) => void)} */
  #children;
  /** @type {Effect} */
  #effect;
  /** @type {Effect | null} */
  #main_effect = null;
  /** @type {Effect | null} */
  #pending_effect = null;
  /** @type {Effect | null} */
  #failed_effect = null;
  /** @type {DocumentFragment | null} */
  #offscreen_fragment = null;
  #local_pending_count = 0;
  #pending_count = 0;
  #pending_count_update_queued = false;
  /** @type {Set<Effect>} */
  #dirty_effects = /* @__PURE__ */ new Set();
  /** @type {Set<Effect>} */
  #maybe_dirty_effects = /* @__PURE__ */ new Set();
  /**
   * A source containing the number of pending async deriveds/expressions.
   * Only created if `$effect.pending()` is used inside the boundary,
   * otherwise updating the source results in needless `Batch.ensure()`
   * calls followed by no-op flushes
   * @type {Source<number> | null}
   */
  #effect_pending = null;
  #effect_pending_subscriber = createSubscriber(() => {
    this.#effect_pending = source(this.#local_pending_count);
    if (DEV) {
      tag$1(this.#effect_pending, "$effect.pending()");
    }
    return () => {
      this.#effect_pending = null;
    };
  });
  /**
   * @param {TemplateNode} node
   * @param {BoundaryProps} props
   * @param {((anchor: Node) => void)} children
   * @param {((error: unknown) => unknown) | undefined} [transform_error]
   */
  constructor(node, props, children, transform_error) {
    this.#anchor = node;
    this.#props = props;
    this.#children = (anchor) => {
      var effect2 = (
        /** @type {Effect} */
        active_effect
      );
      effect2.b = this;
      effect2.f |= BOUNDARY_EFFECT;
      children(anchor);
    };
    this.parent = /** @type {Effect} */
    active_effect.b;
    this.transform_error = transform_error ?? this.parent?.transform_error ?? ((e) => e);
    this.#effect = block$1(() => {
      {
        this.#render();
      }
    }, flags);
  }
  #hydrate_resolved_content() {
    try {
      this.#main_effect = branch(() => this.#children(this.#anchor));
    } catch (error) {
      this.error(error);
    }
  }
  /**
   * @param {unknown} error The deserialized error from the server's hydration comment
   */
  #hydrate_failed_content(error) {
    const failed = this.#props.failed;
    if (!failed) return;
    this.#failed_effect = branch(() => {
      failed(
        this.#anchor,
        () => error,
        () => () => {
        }
      );
    });
  }
  #hydrate_pending_content() {
    const pending = this.#props.pending;
    if (!pending) return;
    this.is_pending = true;
    this.#pending_effect = branch(() => pending(this.#anchor));
    queue_micro_task(() => {
      var fragment = this.#offscreen_fragment = document.createDocumentFragment();
      var anchor = create_text();
      fragment.append(anchor);
      this.#main_effect = this.#run(() => {
        return branch(() => this.#children(anchor));
      });
      if (this.#pending_count === 0) {
        this.#anchor.before(fragment);
        this.#offscreen_fragment = null;
        pause_effect(
          /** @type {Effect} */
          this.#pending_effect,
          () => {
            this.#pending_effect = null;
          }
        );
        this.#resolve(
          /** @type {Batch} */
          current_batch
        );
      }
    });
  }
  #render() {
    try {
      this.is_pending = this.has_pending_snippet();
      this.#pending_count = 0;
      this.#local_pending_count = 0;
      this.#main_effect = branch(() => {
        this.#children(this.#anchor);
      });
      if (this.#pending_count > 0) {
        var fragment = this.#offscreen_fragment = document.createDocumentFragment();
        move_effect(this.#main_effect, fragment);
        const pending = (
          /** @type {(anchor: Node) => void} */
          this.#props.pending
        );
        this.#pending_effect = branch(() => pending(this.#anchor));
      } else {
        this.#resolve(
          /** @type {Batch} */
          current_batch
        );
      }
    } catch (error) {
      this.error(error);
    }
  }
  /**
   * @param {Batch} batch
   */
  #resolve(batch) {
    this.is_pending = false;
    batch.transfer_effects(this.#dirty_effects, this.#maybe_dirty_effects);
  }
  /**
   * Defer an effect inside a pending boundary until the boundary resolves
   * @param {Effect} effect
   */
  defer_effect(effect2) {
    defer_effect(effect2, this.#dirty_effects, this.#maybe_dirty_effects);
  }
  /**
   * Returns `false` if the effect exists inside a boundary whose pending snippet is shown
   * @returns {boolean}
   */
  is_rendered() {
    return !this.is_pending && (!this.parent || this.parent.is_rendered());
  }
  has_pending_snippet() {
    return !!this.#props.pending;
  }
  /**
   * @template T
   * @param {() => T} fn
   */
  #run(fn) {
    var previous_effect = active_effect;
    var previous_reaction = active_reaction;
    var previous_ctx = component_context;
    set_active_effect(this.#effect);
    set_active_reaction(this.#effect);
    set_component_context(this.#effect.ctx);
    try {
      Batch.ensure();
      return fn();
    } catch (e) {
      handle_error(e);
      return null;
    } finally {
      set_active_effect(previous_effect);
      set_active_reaction(previous_reaction);
      set_component_context(previous_ctx);
    }
  }
  /**
   * Updates the pending count associated with the currently visible pending snippet,
   * if any, such that we can replace the snippet with content once work is done
   * @param {1 | -1} d
   * @param {Batch} batch
   */
  #update_pending_count(d, batch) {
    if (!this.has_pending_snippet()) {
      if (this.parent) {
        this.parent.#update_pending_count(d, batch);
      }
      return;
    }
    this.#pending_count += d;
    if (this.#pending_count === 0) {
      this.#resolve(batch);
      if (this.#pending_effect) {
        pause_effect(this.#pending_effect, () => {
          this.#pending_effect = null;
        });
      }
      if (this.#offscreen_fragment) {
        this.#anchor.before(this.#offscreen_fragment);
        this.#offscreen_fragment = null;
      }
    }
  }
  /**
   * Update the source that powers `$effect.pending()` inside this boundary,
   * and controls when the current `pending` snippet (if any) is removed.
   * Do not call from inside the class
   * @param {1 | -1} d
   * @param {Batch} batch
   */
  update_pending_count(d, batch) {
    this.#update_pending_count(d, batch);
    this.#local_pending_count += d;
    if (!this.#effect_pending || this.#pending_count_update_queued) return;
    this.#pending_count_update_queued = true;
    queue_micro_task(() => {
      this.#pending_count_update_queued = false;
      if (this.#effect_pending) {
        internal_set(this.#effect_pending, this.#local_pending_count);
      }
    });
  }
  get_effect_pending() {
    this.#effect_pending_subscriber();
    return get(
      /** @type {Source<number>} */
      this.#effect_pending
    );
  }
  /** @param {unknown} error */
  error(error) {
    if (!this.#props.onerror && !this.#props.failed) {
      throw error;
    }
    if (current_batch?.is_fork) {
      if (this.#main_effect) current_batch.skip_effect(this.#main_effect);
      if (this.#pending_effect) current_batch.skip_effect(this.#pending_effect);
      if (this.#failed_effect) current_batch.skip_effect(this.#failed_effect);
      current_batch.on_fork_commit(() => {
        this.#handle_error(error);
      });
    } else {
      this.#handle_error(error);
    }
  }
  /**
   * @param {unknown} error
   */
  #handle_error(error) {
    if (this.#main_effect) {
      destroy_effect(this.#main_effect);
      this.#main_effect = null;
    }
    if (this.#pending_effect) {
      destroy_effect(this.#pending_effect);
      this.#pending_effect = null;
    }
    if (this.#failed_effect) {
      destroy_effect(this.#failed_effect);
      this.#failed_effect = null;
    }
    var onerror = this.#props.onerror;
    let failed = this.#props.failed;
    var did_reset = false;
    var calling_on_error = false;
    const reset = () => {
      if (did_reset) {
        svelte_boundary_reset_noop();
        return;
      }
      did_reset = true;
      if (calling_on_error) {
        svelte_boundary_reset_onerror();
      }
      if (this.#failed_effect !== null) {
        pause_effect(this.#failed_effect, () => {
          this.#failed_effect = null;
        });
      }
      this.#run(() => {
        this.#render();
      });
    };
    const handle_error_result = (transformed_error) => {
      try {
        calling_on_error = true;
        onerror?.(transformed_error, reset);
        calling_on_error = false;
      } catch (error2) {
        invoke_error_boundary(error2, this.#effect && this.#effect.parent);
      }
      if (failed) {
        this.#failed_effect = this.#run(() => {
          try {
            return branch(() => {
              var effect2 = (
                /** @type {Effect} */
                active_effect
              );
              effect2.b = this;
              effect2.f |= BOUNDARY_EFFECT;
              failed(
                this.#anchor,
                () => transformed_error,
                () => reset
              );
            });
          } catch (error2) {
            invoke_error_boundary(
              error2,
              /** @type {Effect} */
              this.#effect.parent
            );
            return null;
          }
        });
      }
    };
    queue_micro_task(() => {
      var result;
      try {
        result = this.transform_error(error);
      } catch (e) {
        invoke_error_boundary(e, this.#effect && this.#effect.parent);
        return;
      }
      if (result !== null && typeof result === "object" && typeof /** @type {any} */
      result.then === "function") {
        result.then(
          handle_error_result,
          /** @param {unknown} e */
          (e) => invoke_error_boundary(e, this.#effect && this.#effect.parent)
        );
      } else {
        handle_error_result(result);
      }
    });
  }
}
function flatten(blockers, sync, async, fn) {
  const d = is_runes() ? derived : derived_safe_equal;
  var pending = blockers.filter((b) => !b.settled);
  if (async.length === 0 && pending.length === 0) {
    fn(sync.map(d));
    return;
  }
  var parent = (
    /** @type {Effect} */
    active_effect
  );
  var restore = capture();
  var blocker_promise = pending.length === 1 ? pending[0].promise : pending.length > 1 ? Promise.all(pending.map((b) => b.promise)) : null;
  function finish(values) {
    restore();
    try {
      fn(values);
    } catch (error) {
      if ((parent.f & DESTROYED) === 0) {
        invoke_error_boundary(error, parent);
      }
    }
    unset_context();
  }
  if (async.length === 0) {
    blocker_promise.then(() => finish(sync.map(d)));
    return;
  }
  var decrement_pending = increment_pending();
  function run2() {
    Promise.all(async.map((expression) => /* @__PURE__ */ async_derived(expression))).then((result) => finish([...sync.map(d), ...result])).catch((error) => invoke_error_boundary(error, parent)).finally(() => decrement_pending());
  }
  if (blocker_promise) {
    blocker_promise.then(() => {
      restore();
      run2();
      unset_context();
    });
  } else {
    run2();
  }
}
function capture() {
  var previous_effect = (
    /** @type {Effect} */
    active_effect
  );
  var previous_reaction = active_reaction;
  var previous_component_context = component_context;
  var previous_batch = (
    /** @type {Batch} */
    current_batch
  );
  if (DEV) {
    var previous_dev_stack = dev_stack;
  }
  return function restore(activate_batch = true) {
    set_active_effect(previous_effect);
    set_active_reaction(previous_reaction);
    set_component_context(previous_component_context);
    if (activate_batch && (previous_effect.f & DESTROYED) === 0) {
      previous_batch?.activate();
      previous_batch?.apply();
    }
    if (DEV) {
      set_reactivity_loss_tracker(null);
      set_dev_stack(previous_dev_stack);
    }
  };
}
function unset_context(deactivate_batch = true) {
  set_active_effect(null);
  set_active_reaction(null);
  set_component_context(null);
  if (deactivate_batch) current_batch?.deactivate();
  if (DEV) {
    set_reactivity_loss_tracker(null);
    set_dev_stack(null);
  }
}
function increment_pending() {
  var effect2 = (
    /** @type {Effect} */
    active_effect
  );
  var boundary2 = (
    /** @type {Boundary} */
    effect2.b
  );
  var batch = (
    /** @type {Batch} */
    current_batch
  );
  var blocking = boundary2.is_rendered();
  boundary2.update_pending_count(1, batch);
  batch.increment(blocking, effect2);
  return (skip = false) => {
    boundary2.update_pending_count(-1, batch);
    batch.decrement(blocking, effect2, skip);
  };
}
let reactivity_loss_tracker = null;
function set_reactivity_loss_tracker(v) {
  reactivity_loss_tracker = v;
}
const recent_async_deriveds = /* @__PURE__ */ new Set();
// @__NO_SIDE_EFFECTS__
function derived(fn) {
  var flags2 = DERIVED | DIRTY;
  if (active_effect !== null) {
    active_effect.f |= EFFECT_PRESERVED;
  }
  const signal = {
    ctx: component_context,
    deps: null,
    effects: null,
    equals,
    f: flags2,
    fn,
    reactions: null,
    rv: 0,
    v: (
      /** @type {V} */
      UNINITIALIZED
    ),
    wv: 0,
    parent: active_effect,
    ac: null
  };
  return signal;
}
// @__NO_SIDE_EFFECTS__
function async_derived(fn, label, location2) {
  let parent = (
    /** @type {Effect | null} */
    active_effect
  );
  if (parent === null) {
    async_derived_orphan();
  }
  var promise = (
    /** @type {Promise<V>} */
    /** @type {unknown} */
    void 0
  );
  var signal = source(
    /** @type {V} */
    UNINITIALIZED
  );
  if (DEV) signal.label = label;
  var should_suspend = !active_reaction;
  var deferreds = /* @__PURE__ */ new Map();
  async_effect(() => {
    var effect2 = (
      /** @type {Effect} */
      active_effect
    );
    if (DEV) {
      reactivity_loss_tracker = { effect: effect2, effect_deps: /* @__PURE__ */ new Set(), warned: false };
    }
    var d = deferred();
    promise = d.promise;
    try {
      Promise.resolve(fn()).then(d.resolve, d.reject).finally(unset_context);
    } catch (error) {
      d.reject(error);
      unset_context();
    }
    if (DEV) {
      if (reactivity_loss_tracker) {
        if (effect2.deps !== null) {
          for (let i = 0; i < skipped_deps; i += 1) {
            reactivity_loss_tracker.effect_deps.add(effect2.deps[i]);
          }
        }
        if (new_deps !== null) {
          for (let i = 0; i < new_deps.length; i += 1) {
            reactivity_loss_tracker.effect_deps.add(new_deps[i]);
          }
        }
      }
      reactivity_loss_tracker = null;
    }
    var batch = (
      /** @type {Batch} */
      current_batch
    );
    if (should_suspend) {
      if ((effect2.f & REACTION_RAN) !== 0) {
        var decrement_pending = increment_pending();
      }
      if (
        /** @type {Boundary} */
        parent.b.is_rendered()
      ) {
        deferreds.get(batch)?.reject(STALE_REACTION);
        deferreds.delete(batch);
      } else {
        for (const d2 of deferreds.values()) {
          d2.reject(STALE_REACTION);
        }
        deferreds.clear();
      }
      deferreds.set(batch, d);
    }
    const handler = (value, error = void 0) => {
      if (DEV) {
        reactivity_loss_tracker = null;
      }
      if (decrement_pending) {
        var skip = error === STALE_REACTION;
        decrement_pending(skip);
      }
      if (error === STALE_REACTION || (effect2.f & DESTROYED) !== 0) {
        return;
      }
      batch.activate();
      if (error) {
        signal.f |= ERROR_VALUE;
        internal_set(signal, error);
      } else {
        if ((signal.f & ERROR_VALUE) !== 0) {
          signal.f ^= ERROR_VALUE;
        }
        internal_set(signal, value);
        for (const [b, d2] of deferreds) {
          deferreds.delete(b);
          if (b === batch) break;
          d2.reject(STALE_REACTION);
        }
      }
      batch.deactivate();
    };
    d.promise.then(handler, (e) => handler(null, e || "unknown"));
  });
  teardown(() => {
    for (const d of deferreds.values()) {
      d.reject(STALE_REACTION);
    }
  });
  if (DEV) {
    signal.f |= ASYNC;
  }
  return new Promise((fulfil) => {
    function next(p) {
      function go() {
        if (p === promise) {
          fulfil(signal);
        } else {
          next(promise);
        }
      }
      p.then(go, go);
    }
    next(promise);
  });
}
// @__NO_SIDE_EFFECTS__
function user_derived(fn) {
  const d = /* @__PURE__ */ derived(fn);
  push_reaction_value(d);
  return d;
}
// @__NO_SIDE_EFFECTS__
function derived_safe_equal(fn) {
  const signal = /* @__PURE__ */ derived(fn);
  signal.equals = safe_equals;
  return signal;
}
function destroy_derived_effects(derived2) {
  var effects = derived2.effects;
  if (effects !== null) {
    derived2.effects = null;
    for (var i = 0; i < effects.length; i += 1) {
      destroy_effect(
        /** @type {Effect} */
        effects[i]
      );
    }
  }
}
let stack = [];
function execute_derived(derived2) {
  var value;
  var prev_active_effect = active_effect;
  var parent = derived2.parent;
  if (!is_destroying_effect && parent !== null && (parent.f & (DESTROYED | INERT)) !== 0) {
    derived_inert();
    return derived2.v;
  }
  set_active_effect(parent);
  if (DEV) {
    let prev_eager_effects = eager_effects;
    set_eager_effects(/* @__PURE__ */ new Set());
    try {
      if (includes.call(stack, derived2)) {
        derived_references_self();
      }
      stack.push(derived2);
      derived2.f &= ~WAS_MARKED;
      destroy_derived_effects(derived2);
      value = update_reaction(derived2);
    } finally {
      set_active_effect(prev_active_effect);
      set_eager_effects(prev_eager_effects);
      stack.pop();
    }
  } else {
    try {
      derived2.f &= ~WAS_MARKED;
      destroy_derived_effects(derived2);
      value = update_reaction(derived2);
    } finally {
      set_active_effect(prev_active_effect);
    }
  }
  return value;
}
function update_derived(derived2) {
  var value = execute_derived(derived2);
  if (!derived2.equals(value)) {
    derived2.wv = increment_write_version();
    if (!current_batch?.is_fork || derived2.deps === null) {
      if (current_batch !== null) {
        current_batch.capture(derived2, value, true);
      } else {
        derived2.v = value;
      }
      if (derived2.deps === null) {
        set_signal_status(derived2, CLEAN);
        return;
      }
    }
  }
  if (is_destroying_effect) {
    return;
  }
  if (batch_values !== null) {
    if (effect_tracking() || current_batch?.is_fork) {
      batch_values.set(derived2, value);
    }
  } else {
    update_derived_status(derived2);
  }
}
function freeze_derived_effects(derived2) {
  if (derived2.effects === null) return;
  for (const e of derived2.effects) {
    if (e.teardown || e.ac) {
      e.teardown?.();
      e.ac?.abort(STALE_REACTION);
      e.teardown = noop;
      e.ac = null;
      remove_reactions(e, 0);
      destroy_effect_children(e);
    }
  }
}
function unfreeze_derived_effects(derived2) {
  if (derived2.effects === null) return;
  for (const e of derived2.effects) {
    if (e.teardown) {
      update_effect(e);
    }
  }
}
let eager_effects = /* @__PURE__ */ new Set();
const old_values = /* @__PURE__ */ new Map();
function set_eager_effects(v) {
  eager_effects = v;
}
let eager_effects_deferred = false;
function set_eager_effects_deferred() {
  eager_effects_deferred = true;
}
function source(v, stack2) {
  var signal = {
    f: 0,
    // TODO ideally we could skip this altogether, but it causes type errors
    v,
    reactions: null,
    equals,
    rv: 0,
    wv: 0
  };
  return signal;
}
// @__NO_SIDE_EFFECTS__
function state(v, stack2) {
  const s = source(v);
  push_reaction_value(s);
  return s;
}
// @__NO_SIDE_EFFECTS__
function mutable_source(initial_value, immutable = false, trackable = true) {
  const s = source(initial_value);
  if (!immutable) {
    s.equals = safe_equals;
  }
  if (legacy_mode_flag && trackable && component_context !== null && component_context.l !== null) {
    (component_context.l.s ??= []).push(s);
  }
  return s;
}
function mutate(source2, value) {
  set(
    source2,
    untrack(() => get(source2))
  );
  return value;
}
function set(source2, value, should_proxy = false) {
  if (active_reaction !== null && // since we are untracking the function inside `$inspect.with` we need to add this check
  // to ensure we error if state is set inside an inspect effect
  (!untracking || (active_reaction.f & EAGER_EFFECT) !== 0) && is_runes() && (active_reaction.f & (DERIVED | BLOCK_EFFECT | ASYNC | EAGER_EFFECT)) !== 0 && (current_sources === null || !includes.call(current_sources, source2))) {
    state_unsafe_mutation();
  }
  let new_value = should_proxy ? proxy(value) : value;
  if (DEV) {
    tag_proxy(
      new_value,
      /** @type {string} */
      source2.label
    );
  }
  return internal_set(source2, new_value, legacy_updates);
}
function internal_set(source2, value, updated_during_traversal = null) {
  if (!source2.equals(value)) {
    old_values.set(source2, is_destroying_effect ? value : source2.v);
    var batch = Batch.ensure();
    batch.capture(source2, value);
    if (DEV) {
      if (active_effect !== null) {
        source2.updated ??= /* @__PURE__ */ new Map();
        const count = (source2.updated.get("")?.count ?? 0) + 1;
        source2.updated.set("", { error: (
          /** @type {any} */
          null
        ), count });
        if (count > 5) {
          const error = get_error("updated at");
          if (error !== null) {
            let entry = source2.updated.get(error.stack);
            if (!entry) {
              entry = { error, count: 0 };
              source2.updated.set(error.stack, entry);
            }
            entry.count++;
          }
        }
      }
      if (active_effect !== null) {
        source2.set_during_effect = true;
      }
    }
    if ((source2.f & DERIVED) !== 0) {
      const derived2 = (
        /** @type {Derived} */
        source2
      );
      if ((source2.f & DIRTY) !== 0) {
        execute_derived(derived2);
      }
      if (batch_values === null) {
        update_derived_status(derived2);
      }
    }
    source2.wv = increment_write_version();
    mark_reactions(source2, DIRTY, updated_during_traversal);
    if (is_runes() && active_effect !== null && (active_effect.f & CLEAN) !== 0 && (active_effect.f & (BRANCH_EFFECT | ROOT_EFFECT)) === 0) {
      if (untracked_writes === null) {
        set_untracked_writes([source2]);
      } else {
        untracked_writes.push(source2);
      }
    }
    if (!batch.is_fork && eager_effects.size > 0 && !eager_effects_deferred) {
      flush_eager_effects();
    }
  }
  return value;
}
function flush_eager_effects() {
  eager_effects_deferred = false;
  for (const effect2 of eager_effects) {
    if ((effect2.f & CLEAN) !== 0) {
      set_signal_status(effect2, MAYBE_DIRTY);
    }
    if (is_dirty(effect2)) {
      update_effect(effect2);
    }
  }
  eager_effects.clear();
}
function update(source2, d = 1) {
  var value = get(source2);
  var result = d === 1 ? value++ : value--;
  set(source2, value);
  return result;
}
function increment(source2) {
  set(source2, source2.v + 1);
}
function mark_reactions(signal, status, updated_during_traversal) {
  var reactions = signal.reactions;
  if (reactions === null) return;
  var runes = is_runes();
  var length = reactions.length;
  for (var i = 0; i < length; i++) {
    var reaction = reactions[i];
    var flags2 = reaction.f;
    if (!runes && reaction === active_effect) continue;
    if (DEV && (flags2 & EAGER_EFFECT) !== 0) {
      eager_effects.add(reaction);
      continue;
    }
    var not_dirty = (flags2 & DIRTY) === 0;
    if (not_dirty) {
      set_signal_status(reaction, status);
    }
    if ((flags2 & DERIVED) !== 0) {
      var derived2 = (
        /** @type {Derived} */
        reaction
      );
      batch_values?.delete(derived2);
      if ((flags2 & WAS_MARKED) === 0) {
        if (flags2 & CONNECTED && (active_effect === null || (active_effect.f & REACTION_IS_UPDATING) === 0)) {
          reaction.f |= WAS_MARKED;
        }
        mark_reactions(derived2, MAYBE_DIRTY, updated_during_traversal);
      }
    } else if (not_dirty) {
      var effect2 = (
        /** @type {Effect} */
        reaction
      );
      if ((flags2 & BLOCK_EFFECT) !== 0 && eager_block_effects !== null) {
        eager_block_effects.add(effect2);
      }
      if (updated_during_traversal !== null) {
        updated_during_traversal.push(effect2);
      } else {
        schedule_effect(effect2);
      }
    }
  }
}
const regex_is_valid_identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/;
function proxy(value) {
  if (typeof value !== "object" || value === null || STATE_SYMBOL in value) {
    return value;
  }
  const prototype = get_prototype_of(value);
  if (prototype !== object_prototype && prototype !== array_prototype) {
    return value;
  }
  var sources = /* @__PURE__ */ new Map();
  var is_proxied_array = is_array(value);
  var version = /* @__PURE__ */ state(0);
  var parent_version = update_version;
  var with_parent = (fn) => {
    if (update_version === parent_version) {
      return fn();
    }
    var reaction = active_reaction;
    var version2 = update_version;
    set_active_reaction(null);
    set_update_version(parent_version);
    var result = fn();
    set_active_reaction(reaction);
    set_update_version(version2);
    return result;
  };
  if (is_proxied_array) {
    sources.set("length", /* @__PURE__ */ state(
      /** @type {any[]} */
      value.length
    ));
    if (DEV) {
      value = /** @type {any} */
      inspectable_array(
        /** @type {any[]} */
        value
      );
    }
  }
  var path = "";
  let updating = false;
  function update_path(new_path) {
    if (updating) return;
    updating = true;
    path = new_path;
    tag$1(version, `${path} version`);
    for (const [prop2, source2] of sources) {
      tag$1(source2, get_label(path, prop2));
    }
    updating = false;
  }
  return new Proxy(
    /** @type {any} */
    value,
    {
      defineProperty(_, prop2, descriptor) {
        if (!("value" in descriptor) || descriptor.configurable === false || descriptor.enumerable === false || descriptor.writable === false) {
          state_descriptors_fixed();
        }
        var s = sources.get(prop2);
        if (s === void 0) {
          with_parent(() => {
            var s2 = /* @__PURE__ */ state(descriptor.value);
            sources.set(prop2, s2);
            if (DEV && typeof prop2 === "string") {
              tag$1(s2, get_label(path, prop2));
            }
            return s2;
          });
        } else {
          set(s, descriptor.value, true);
        }
        return true;
      },
      deleteProperty(target, prop2) {
        var s = sources.get(prop2);
        if (s === void 0) {
          if (prop2 in target) {
            const s2 = with_parent(() => /* @__PURE__ */ state(UNINITIALIZED));
            sources.set(prop2, s2);
            increment(version);
            if (DEV) {
              tag$1(s2, get_label(path, prop2));
            }
          }
        } else {
          set(s, UNINITIALIZED);
          increment(version);
        }
        return true;
      },
      get(target, prop2, receiver) {
        if (prop2 === STATE_SYMBOL) {
          return value;
        }
        if (DEV && prop2 === PROXY_PATH_SYMBOL) {
          return update_path;
        }
        var s = sources.get(prop2);
        var exists = prop2 in target;
        if (s === void 0 && (!exists || get_descriptor(target, prop2)?.writable)) {
          s = with_parent(() => {
            var p = proxy(exists ? target[prop2] : UNINITIALIZED);
            var s2 = /* @__PURE__ */ state(p);
            if (DEV) {
              tag$1(s2, get_label(path, prop2));
            }
            return s2;
          });
          sources.set(prop2, s);
        }
        if (s !== void 0) {
          var v = get(s);
          return v === UNINITIALIZED ? void 0 : v;
        }
        return Reflect.get(target, prop2, receiver);
      },
      getOwnPropertyDescriptor(target, prop2) {
        var descriptor = Reflect.getOwnPropertyDescriptor(target, prop2);
        if (descriptor && "value" in descriptor) {
          var s = sources.get(prop2);
          if (s) descriptor.value = get(s);
        } else if (descriptor === void 0) {
          var source2 = sources.get(prop2);
          var value2 = source2?.v;
          if (source2 !== void 0 && value2 !== UNINITIALIZED) {
            return {
              enumerable: true,
              configurable: true,
              value: value2,
              writable: true
            };
          }
        }
        return descriptor;
      },
      has(target, prop2) {
        if (prop2 === STATE_SYMBOL) {
          return true;
        }
        var s = sources.get(prop2);
        var has = s !== void 0 && s.v !== UNINITIALIZED || Reflect.has(target, prop2);
        if (s !== void 0 || active_effect !== null && (!has || get_descriptor(target, prop2)?.writable)) {
          if (s === void 0) {
            s = with_parent(() => {
              var p = has ? proxy(target[prop2]) : UNINITIALIZED;
              var s2 = /* @__PURE__ */ state(p);
              if (DEV) {
                tag$1(s2, get_label(path, prop2));
              }
              return s2;
            });
            sources.set(prop2, s);
          }
          var value2 = get(s);
          if (value2 === UNINITIALIZED) {
            return false;
          }
        }
        return has;
      },
      set(target, prop2, value2, receiver) {
        var s = sources.get(prop2);
        var has = prop2 in target;
        if (is_proxied_array && prop2 === "length") {
          for (var i = value2; i < /** @type {Source<number>} */
          s.v; i += 1) {
            var other_s = sources.get(i + "");
            if (other_s !== void 0) {
              set(other_s, UNINITIALIZED);
            } else if (i in target) {
              other_s = with_parent(() => /* @__PURE__ */ state(UNINITIALIZED));
              sources.set(i + "", other_s);
              if (DEV) {
                tag$1(other_s, get_label(path, i));
              }
            }
          }
        }
        if (s === void 0) {
          if (!has || get_descriptor(target, prop2)?.writable) {
            s = with_parent(() => /* @__PURE__ */ state(void 0));
            if (DEV) {
              tag$1(s, get_label(path, prop2));
            }
            set(s, proxy(value2));
            sources.set(prop2, s);
          }
        } else {
          has = s.v !== UNINITIALIZED;
          var p = with_parent(() => proxy(value2));
          set(s, p);
        }
        var descriptor = Reflect.getOwnPropertyDescriptor(target, prop2);
        if (descriptor?.set) {
          descriptor.set.call(receiver, value2);
        }
        if (!has) {
          if (is_proxied_array && typeof prop2 === "string") {
            var ls = (
              /** @type {Source<number>} */
              sources.get("length")
            );
            var n = Number(prop2);
            if (Number.isInteger(n) && n >= ls.v) {
              set(ls, n + 1);
            }
          }
          increment(version);
        }
        return true;
      },
      ownKeys(target) {
        get(version);
        var own_keys = Reflect.ownKeys(target).filter((key2) => {
          var source3 = sources.get(key2);
          return source3 === void 0 || source3.v !== UNINITIALIZED;
        });
        for (var [key, source2] of sources) {
          if (source2.v !== UNINITIALIZED && !(key in target)) {
            own_keys.push(key);
          }
        }
        return own_keys;
      },
      setPrototypeOf() {
        state_prototype_fixed();
      }
    }
  );
}
function get_label(path, prop2) {
  if (typeof prop2 === "symbol") return `${path}[Symbol(${prop2.description ?? ""})]`;
  if (regex_is_valid_identifier.test(prop2)) return `${path}.${prop2}`;
  return /^\d+$/.test(prop2) ? `${path}[${prop2}]` : `${path}['${prop2}']`;
}
function get_proxied_value(value) {
  try {
    if (value !== null && typeof value === "object" && STATE_SYMBOL in value) {
      return value[STATE_SYMBOL];
    }
  } catch {
  }
  return value;
}
function is(a, b) {
  return Object.is(get_proxied_value(a), get_proxied_value(b));
}
const ARRAY_MUTATING_METHODS = /* @__PURE__ */ new Set([
  "copyWithin",
  "fill",
  "pop",
  "push",
  "reverse",
  "shift",
  "sort",
  "splice",
  "unshift"
]);
function inspectable_array(array) {
  return new Proxy(array, {
    get(target, prop2, receiver) {
      var value = Reflect.get(target, prop2, receiver);
      if (!ARRAY_MUTATING_METHODS.has(
        /** @type {string} */
        prop2
      )) {
        return value;
      }
      return function(...args) {
        set_eager_effects_deferred();
        var result = value.apply(this, args);
        flush_eager_effects();
        return result;
      };
    }
  });
}
function init_array_prototype_warnings() {
  const array_prototype2 = Array.prototype;
  const cleanup = Array.__svelte_cleanup;
  if (cleanup) {
    cleanup();
  }
  const { indexOf, lastIndexOf, includes: includes2 } = array_prototype2;
  array_prototype2.indexOf = function(item, from_index) {
    const index2 = indexOf.call(this, item, from_index);
    if (index2 === -1) {
      for (let i = from_index ?? 0; i < this.length; i += 1) {
        if (get_proxied_value(this[i]) === item) {
          state_proxy_equality_mismatch("array.indexOf(...)");
          break;
        }
      }
    }
    return index2;
  };
  array_prototype2.lastIndexOf = function(item, from_index) {
    const index2 = lastIndexOf.call(this, item, from_index ?? this.length - 1);
    if (index2 === -1) {
      for (let i = 0; i <= (from_index ?? this.length - 1); i += 1) {
        if (get_proxied_value(this[i]) === item) {
          state_proxy_equality_mismatch("array.lastIndexOf(...)");
          break;
        }
      }
    }
    return index2;
  };
  array_prototype2.includes = function(item, from_index) {
    const has = includes2.call(this, item, from_index);
    if (!has) {
      for (let i = 0; i < this.length; i += 1) {
        if (get_proxied_value(this[i]) === item) {
          state_proxy_equality_mismatch("array.includes(...)");
          break;
        }
      }
    }
    return has;
  };
  Array.__svelte_cleanup = () => {
    array_prototype2.indexOf = indexOf;
    array_prototype2.lastIndexOf = lastIndexOf;
    array_prototype2.includes = includes2;
  };
}
var $window;
var is_firefox;
var first_child_getter;
var next_sibling_getter;
function init_operations() {
  if ($window !== void 0) {
    return;
  }
  $window = window;
  is_firefox = /Firefox/.test(navigator.userAgent);
  var element_prototype = Element.prototype;
  var node_prototype = Node.prototype;
  var text_prototype = Text.prototype;
  first_child_getter = get_descriptor(node_prototype, "firstChild").get;
  next_sibling_getter = get_descriptor(node_prototype, "nextSibling").get;
  if (is_extensible(element_prototype)) {
    element_prototype.__click = void 0;
    element_prototype.__className = void 0;
    element_prototype.__attributes = null;
    element_prototype.__style = void 0;
    element_prototype.__e = void 0;
  }
  if (is_extensible(text_prototype)) {
    text_prototype.__t = void 0;
  }
  if (DEV) {
    element_prototype.__svelte_meta = null;
    init_array_prototype_warnings();
  }
}
function create_text(value = "") {
  return document.createTextNode(value);
}
// @__NO_SIDE_EFFECTS__
function get_first_child(node) {
  return (
    /** @type {TemplateNode | null} */
    first_child_getter.call(node)
  );
}
// @__NO_SIDE_EFFECTS__
function get_next_sibling(node) {
  return (
    /** @type {TemplateNode | null} */
    next_sibling_getter.call(node)
  );
}
function child(node, is_text) {
  {
    return /* @__PURE__ */ get_first_child(node);
  }
}
function first_child(node, is_text = false) {
  {
    var first = /* @__PURE__ */ get_first_child(node);
    if (first instanceof Comment && first.data === "") return /* @__PURE__ */ get_next_sibling(first);
    return first;
  }
}
function sibling(node, count = 1, is_text = false) {
  let next_sibling = node;
  while (count--) {
    next_sibling = /** @type {TemplateNode} */
    /* @__PURE__ */ get_next_sibling(next_sibling);
  }
  {
    return next_sibling;
  }
}
function clear_text_content(node) {
  node.textContent = "";
}
function should_defer_append() {
  return false;
}
function create_element(tag2, namespace, is2) {
  let options = void 0;
  return (
    /** @type {T extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[T] : Element} */
    document.createElementNS(namespace ?? NAMESPACE_HTML, tag2, options)
  );
}
let listening_to_form_reset = false;
function add_form_reset_listener() {
  if (!listening_to_form_reset) {
    listening_to_form_reset = true;
    document.addEventListener(
      "reset",
      (evt) => {
        Promise.resolve().then(() => {
          if (!evt.defaultPrevented) {
            for (
              const e of
              /**@type {HTMLFormElement} */
              evt.target.elements
            ) {
              e.__on_r?.();
            }
          }
        });
      },
      // In the capture phase to guarantee we get noticed of it (no possibility of stopPropagation)
      { capture: true }
    );
  }
}
function without_reactive_context(fn) {
  var previous_reaction = active_reaction;
  var previous_effect = active_effect;
  set_active_reaction(null);
  set_active_effect(null);
  try {
    return fn();
  } finally {
    set_active_reaction(previous_reaction);
    set_active_effect(previous_effect);
  }
}
function listen_to_event_and_reset_event(element, event2, handler, on_reset = handler) {
  element.addEventListener(event2, () => without_reactive_context(handler));
  const prev = element.__on_r;
  if (prev) {
    element.__on_r = () => {
      prev();
      on_reset(true);
    };
  } else {
    element.__on_r = () => on_reset(true);
  }
  add_form_reset_listener();
}
function validate_effect(rune) {
  if (active_effect === null) {
    if (active_reaction === null) {
      effect_orphan(rune);
    }
    effect_in_unowned_derived();
  }
  if (is_destroying_effect) {
    effect_in_teardown(rune);
  }
}
function push_effect(effect2, parent_effect) {
  var parent_last = parent_effect.last;
  if (parent_last === null) {
    parent_effect.last = parent_effect.first = effect2;
  } else {
    parent_last.next = effect2;
    effect2.prev = parent_last;
    parent_effect.last = effect2;
  }
}
function create_effect(type, fn) {
  var parent = active_effect;
  if (DEV) {
    while (parent !== null && (parent.f & EAGER_EFFECT) !== 0) {
      parent = parent.parent;
    }
  }
  if (parent !== null && (parent.f & INERT) !== 0) {
    type |= INERT;
  }
  var effect2 = {
    ctx: component_context,
    deps: null,
    nodes: null,
    f: type | DIRTY | CONNECTED,
    first: null,
    fn,
    last: null,
    next: null,
    parent,
    b: parent && parent.b,
    prev: null,
    teardown: null,
    wv: 0,
    ac: null
  };
  if (DEV) {
    effect2.component_function = dev_current_component_function;
  }
  current_batch?.register_created_effect(effect2);
  var e = effect2;
  if ((type & EFFECT) !== 0) {
    if (collected_effects !== null) {
      collected_effects.push(effect2);
    } else {
      Batch.ensure().schedule(effect2);
    }
  } else if (fn !== null) {
    try {
      update_effect(effect2);
    } catch (e2) {
      destroy_effect(effect2);
      throw e2;
    }
    if (e.deps === null && e.teardown === null && e.nodes === null && e.first === e.last && // either `null`, or a singular child
    (e.f & EFFECT_PRESERVED) === 0) {
      e = e.first;
      if ((type & BLOCK_EFFECT) !== 0 && (type & EFFECT_TRANSPARENT) !== 0 && e !== null) {
        e.f |= EFFECT_TRANSPARENT;
      }
    }
  }
  if (e !== null) {
    e.parent = parent;
    if (parent !== null) {
      push_effect(e, parent);
    }
    if (active_reaction !== null && (active_reaction.f & DERIVED) !== 0 && (type & ROOT_EFFECT) === 0) {
      var derived2 = (
        /** @type {Derived} */
        active_reaction
      );
      (derived2.effects ??= []).push(e);
    }
  }
  return effect2;
}
function effect_tracking() {
  return active_reaction !== null && !untracking;
}
function teardown(fn) {
  const effect2 = create_effect(RENDER_EFFECT, null);
  set_signal_status(effect2, CLEAN);
  effect2.teardown = fn;
  return effect2;
}
function user_effect(fn) {
  validate_effect("$effect");
  if (DEV) {
    define_property(fn, "name", {
      value: "$effect"
    });
  }
  var flags2 = (
    /** @type {Effect} */
    active_effect.f
  );
  var defer = !active_reaction && (flags2 & BRANCH_EFFECT) !== 0 && (flags2 & REACTION_RAN) === 0;
  if (defer) {
    var context = (
      /** @type {ComponentContext} */
      component_context
    );
    (context.e ??= []).push(fn);
  } else {
    return create_user_effect(fn);
  }
}
function create_user_effect(fn) {
  return create_effect(EFFECT | USER_EFFECT, fn);
}
function user_pre_effect(fn) {
  validate_effect("$effect.pre");
  if (DEV) {
    define_property(fn, "name", {
      value: "$effect.pre"
    });
  }
  return create_effect(RENDER_EFFECT | USER_EFFECT, fn);
}
function component_root(fn) {
  Batch.ensure();
  const effect2 = create_effect(ROOT_EFFECT | EFFECT_PRESERVED, fn);
  return (options = {}) => {
    return new Promise((fulfil) => {
      if (options.outro) {
        pause_effect(effect2, () => {
          destroy_effect(effect2);
          fulfil(void 0);
        });
      } else {
        destroy_effect(effect2);
        fulfil(void 0);
      }
    });
  };
}
function effect(fn) {
  return create_effect(EFFECT, fn);
}
function legacy_pre_effect(deps, fn) {
  var context = (
    /** @type {ComponentContextLegacy} */
    component_context
  );
  var token = { effect: null, ran: false, deps };
  context.l.$.push(token);
  token.effect = render_effect(() => {
    deps();
    if (token.ran) return;
    token.ran = true;
    var effect2 = (
      /** @type {Effect} */
      active_effect
    );
    try {
      set_active_effect(effect2.parent);
      untrack(fn);
    } finally {
      set_active_effect(effect2);
    }
  });
}
function legacy_pre_effect_reset() {
  var context = (
    /** @type {ComponentContextLegacy} */
    component_context
  );
  render_effect(() => {
    for (var token of context.l.$) {
      token.deps();
      var effect2 = token.effect;
      if ((effect2.f & CLEAN) !== 0 && effect2.deps !== null) {
        set_signal_status(effect2, MAYBE_DIRTY);
      }
      if (is_dirty(effect2)) {
        update_effect(effect2);
      }
      token.ran = false;
    }
  });
}
function async_effect(fn) {
  return create_effect(ASYNC | EFFECT_PRESERVED, fn);
}
function render_effect(fn, flags2 = 0) {
  return create_effect(RENDER_EFFECT | flags2, fn);
}
function template_effect(fn, sync = [], async = [], blockers = []) {
  flatten(blockers, sync, async, (values) => {
    create_effect(RENDER_EFFECT, () => fn(...values.map(get)));
  });
}
function block$1(fn, flags2 = 0) {
  var effect2 = create_effect(BLOCK_EFFECT | flags2, fn);
  if (DEV) {
    effect2.dev_stack = dev_stack;
  }
  return effect2;
}
function branch(fn) {
  return create_effect(BRANCH_EFFECT | EFFECT_PRESERVED, fn);
}
function execute_effect_teardown(effect2) {
  var teardown2 = effect2.teardown;
  if (teardown2 !== null) {
    const previously_destroying_effect = is_destroying_effect;
    const previous_reaction = active_reaction;
    set_is_destroying_effect(true);
    set_active_reaction(null);
    try {
      teardown2.call(null);
    } finally {
      set_is_destroying_effect(previously_destroying_effect);
      set_active_reaction(previous_reaction);
    }
  }
}
function destroy_effect_children(signal, remove_dom = false) {
  var effect2 = signal.first;
  signal.first = signal.last = null;
  while (effect2 !== null) {
    const controller = effect2.ac;
    if (controller !== null) {
      without_reactive_context(() => {
        controller.abort(STALE_REACTION);
      });
    }
    var next = effect2.next;
    if ((effect2.f & ROOT_EFFECT) !== 0) {
      effect2.parent = null;
    } else {
      destroy_effect(effect2, remove_dom);
    }
    effect2 = next;
  }
}
function destroy_block_effect_children(signal) {
  var effect2 = signal.first;
  while (effect2 !== null) {
    var next = effect2.next;
    if ((effect2.f & BRANCH_EFFECT) === 0) {
      destroy_effect(effect2);
    }
    effect2 = next;
  }
}
function destroy_effect(effect2, remove_dom = true) {
  var removed = false;
  if ((remove_dom || (effect2.f & HEAD_EFFECT) !== 0) && effect2.nodes !== null && effect2.nodes.end !== null) {
    remove_effect_dom(
      effect2.nodes.start,
      /** @type {TemplateNode} */
      effect2.nodes.end
    );
    removed = true;
  }
  set_signal_status(effect2, DESTROYING);
  destroy_effect_children(effect2, remove_dom && !removed);
  remove_reactions(effect2, 0);
  var transitions = effect2.nodes && effect2.nodes.t;
  if (transitions !== null) {
    for (const transition of transitions) {
      transition.stop();
    }
  }
  execute_effect_teardown(effect2);
  effect2.f ^= DESTROYING;
  effect2.f |= DESTROYED;
  var parent = effect2.parent;
  if (parent !== null && parent.first !== null) {
    unlink_effect(effect2);
  }
  if (DEV) {
    effect2.component_function = null;
  }
  effect2.next = effect2.prev = effect2.teardown = effect2.ctx = effect2.deps = effect2.fn = effect2.nodes = effect2.ac = effect2.b = null;
}
function remove_effect_dom(node, end) {
  while (node !== null) {
    var next = node === end ? null : /* @__PURE__ */ get_next_sibling(node);
    node.remove();
    node = next;
  }
}
function unlink_effect(effect2) {
  var parent = effect2.parent;
  var prev = effect2.prev;
  var next = effect2.next;
  if (prev !== null) prev.next = next;
  if (next !== null) next.prev = prev;
  if (parent !== null) {
    if (parent.first === effect2) parent.first = next;
    if (parent.last === effect2) parent.last = prev;
  }
}
function pause_effect(effect2, callback, destroy = true) {
  var transitions = [];
  pause_children(effect2, transitions, true);
  var fn = () => {
    if (destroy) destroy_effect(effect2);
    if (callback) callback();
  };
  var remaining = transitions.length;
  if (remaining > 0) {
    var check = () => --remaining || fn();
    for (var transition of transitions) {
      transition.out(check);
    }
  } else {
    fn();
  }
}
function pause_children(effect2, transitions, local) {
  if ((effect2.f & INERT) !== 0) return;
  effect2.f ^= INERT;
  var t = effect2.nodes && effect2.nodes.t;
  if (t !== null) {
    for (const transition of t) {
      if (transition.is_global || local) {
        transitions.push(transition);
      }
    }
  }
  var child2 = effect2.first;
  while (child2 !== null) {
    var sibling2 = child2.next;
    if ((child2.f & ROOT_EFFECT) === 0) {
      var transparent = (child2.f & EFFECT_TRANSPARENT) !== 0 || // If this is a branch effect without a block effect parent,
      // it means the parent block effect was pruned. In that case,
      // transparency information was transferred to the branch effect.
      (child2.f & BRANCH_EFFECT) !== 0 && (effect2.f & BLOCK_EFFECT) !== 0;
      pause_children(child2, transitions, transparent ? local : false);
    }
    child2 = sibling2;
  }
}
function resume_effect(effect2) {
  resume_children(effect2, true);
}
function resume_children(effect2, local) {
  if ((effect2.f & INERT) === 0) return;
  effect2.f ^= INERT;
  if ((effect2.f & CLEAN) === 0) {
    set_signal_status(effect2, DIRTY);
    Batch.ensure().schedule(effect2);
  }
  var child2 = effect2.first;
  while (child2 !== null) {
    var sibling2 = child2.next;
    var transparent = (child2.f & EFFECT_TRANSPARENT) !== 0 || (child2.f & BRANCH_EFFECT) !== 0;
    resume_children(child2, transparent ? local : false);
    child2 = sibling2;
  }
  var t = effect2.nodes && effect2.nodes.t;
  if (t !== null) {
    for (const transition of t) {
      if (transition.is_global || local) {
        transition.in();
      }
    }
  }
}
function move_effect(effect2, fragment) {
  if (!effect2.nodes) return;
  var node = effect2.nodes.start;
  var end = effect2.nodes.end;
  while (node !== null) {
    var next = node === end ? null : /* @__PURE__ */ get_next_sibling(node);
    fragment.append(node);
    node = next;
  }
}
let is_updating_effect = false;
let is_destroying_effect = false;
function set_is_destroying_effect(value) {
  is_destroying_effect = value;
}
let active_reaction = null;
let untracking = false;
function set_active_reaction(reaction) {
  active_reaction = reaction;
}
let active_effect = null;
function set_active_effect(effect2) {
  active_effect = effect2;
}
let current_sources = null;
function push_reaction_value(value) {
  if (active_reaction !== null && true) {
    if (current_sources === null) {
      current_sources = [value];
    } else {
      current_sources.push(value);
    }
  }
}
let new_deps = null;
let skipped_deps = 0;
let untracked_writes = null;
function set_untracked_writes(value) {
  untracked_writes = value;
}
let write_version = 1;
let read_version = 0;
let update_version = read_version;
function set_update_version(value) {
  update_version = value;
}
function increment_write_version() {
  return ++write_version;
}
function is_dirty(reaction) {
  var flags2 = reaction.f;
  if ((flags2 & DIRTY) !== 0) {
    return true;
  }
  if (flags2 & DERIVED) {
    reaction.f &= ~WAS_MARKED;
  }
  if ((flags2 & MAYBE_DIRTY) !== 0) {
    var dependencies = (
      /** @type {Value[]} */
      reaction.deps
    );
    var length = dependencies.length;
    for (var i = 0; i < length; i++) {
      var dependency = dependencies[i];
      if (is_dirty(
        /** @type {Derived} */
        dependency
      )) {
        update_derived(
          /** @type {Derived} */
          dependency
        );
      }
      if (dependency.wv > reaction.wv) {
        return true;
      }
    }
    if ((flags2 & CONNECTED) !== 0 && // During time traveling we don't want to reset the status so that
    // traversal of the graph in the other batches still happens
    batch_values === null) {
      set_signal_status(reaction, CLEAN);
    }
  }
  return false;
}
function schedule_possible_effect_self_invalidation(signal, effect2, root2 = true) {
  var reactions = signal.reactions;
  if (reactions === null) return;
  if (current_sources !== null && includes.call(current_sources, signal)) {
    return;
  }
  for (var i = 0; i < reactions.length; i++) {
    var reaction = reactions[i];
    if ((reaction.f & DERIVED) !== 0) {
      schedule_possible_effect_self_invalidation(
        /** @type {Derived} */
        reaction,
        effect2,
        false
      );
    } else if (effect2 === reaction) {
      if (root2) {
        set_signal_status(reaction, DIRTY);
      } else if ((reaction.f & CLEAN) !== 0) {
        set_signal_status(reaction, MAYBE_DIRTY);
      }
      schedule_effect(
        /** @type {Effect} */
        reaction
      );
    }
  }
}
function update_reaction(reaction) {
  var previous_deps = new_deps;
  var previous_skipped_deps = skipped_deps;
  var previous_untracked_writes = untracked_writes;
  var previous_reaction = active_reaction;
  var previous_sources = current_sources;
  var previous_component_context = component_context;
  var previous_untracking = untracking;
  var previous_update_version = update_version;
  var flags2 = reaction.f;
  new_deps = /** @type {null | Value[]} */
  null;
  skipped_deps = 0;
  untracked_writes = null;
  active_reaction = (flags2 & (BRANCH_EFFECT | ROOT_EFFECT)) === 0 ? reaction : null;
  current_sources = null;
  set_component_context(reaction.ctx);
  untracking = false;
  update_version = ++read_version;
  if (reaction.ac !== null) {
    without_reactive_context(() => {
      reaction.ac.abort(STALE_REACTION);
    });
    reaction.ac = null;
  }
  try {
    reaction.f |= REACTION_IS_UPDATING;
    var fn = (
      /** @type {Function} */
      reaction.fn
    );
    var result = fn();
    reaction.f |= REACTION_RAN;
    var deps = reaction.deps;
    var is_fork = current_batch?.is_fork;
    if (new_deps !== null) {
      var i;
      if (!is_fork) {
        remove_reactions(reaction, skipped_deps);
      }
      if (deps !== null && skipped_deps > 0) {
        deps.length = skipped_deps + new_deps.length;
        for (i = 0; i < new_deps.length; i++) {
          deps[skipped_deps + i] = new_deps[i];
        }
      } else {
        reaction.deps = deps = new_deps;
      }
      if (effect_tracking() && (reaction.f & CONNECTED) !== 0) {
        for (i = skipped_deps; i < deps.length; i++) {
          (deps[i].reactions ??= []).push(reaction);
        }
      }
    } else if (!is_fork && deps !== null && skipped_deps < deps.length) {
      remove_reactions(reaction, skipped_deps);
      deps.length = skipped_deps;
    }
    if (is_runes() && untracked_writes !== null && !untracking && deps !== null && (reaction.f & (DERIVED | MAYBE_DIRTY | DIRTY)) === 0) {
      for (i = 0; i < /** @type {Source[]} */
      untracked_writes.length; i++) {
        schedule_possible_effect_self_invalidation(
          untracked_writes[i],
          /** @type {Effect} */
          reaction
        );
      }
    }
    if (previous_reaction !== null && previous_reaction !== reaction) {
      read_version++;
      if (previous_reaction.deps !== null) {
        for (let i2 = 0; i2 < previous_skipped_deps; i2 += 1) {
          previous_reaction.deps[i2].rv = read_version;
        }
      }
      if (previous_deps !== null) {
        for (const dep of previous_deps) {
          dep.rv = read_version;
        }
      }
      if (untracked_writes !== null) {
        if (previous_untracked_writes === null) {
          previous_untracked_writes = untracked_writes;
        } else {
          previous_untracked_writes.push(.../** @type {Source[]} */
          untracked_writes);
        }
      }
    }
    if ((reaction.f & ERROR_VALUE) !== 0) {
      reaction.f ^= ERROR_VALUE;
    }
    return result;
  } catch (error) {
    return handle_error(error);
  } finally {
    reaction.f ^= REACTION_IS_UPDATING;
    new_deps = previous_deps;
    skipped_deps = previous_skipped_deps;
    untracked_writes = previous_untracked_writes;
    active_reaction = previous_reaction;
    current_sources = previous_sources;
    set_component_context(previous_component_context);
    untracking = previous_untracking;
    update_version = previous_update_version;
  }
}
function remove_reaction(signal, dependency) {
  let reactions = dependency.reactions;
  if (reactions !== null) {
    var index2 = index_of.call(reactions, signal);
    if (index2 !== -1) {
      var new_length = reactions.length - 1;
      if (new_length === 0) {
        reactions = dependency.reactions = null;
      } else {
        reactions[index2] = reactions[new_length];
        reactions.pop();
      }
    }
  }
  if (reactions === null && (dependency.f & DERIVED) !== 0 && // Destroying a child effect while updating a parent effect can cause a dependency to appear
  // to be unused, when in fact it is used by the currently-updating parent. Checking `new_deps`
  // allows us to skip the expensive work of disconnecting and immediately reconnecting it
  (new_deps === null || !includes.call(new_deps, dependency))) {
    var derived2 = (
      /** @type {Derived} */
      dependency
    );
    if ((derived2.f & CONNECTED) !== 0) {
      derived2.f ^= CONNECTED;
      derived2.f &= ~WAS_MARKED;
    }
    if (derived2.v !== UNINITIALIZED) {
      update_derived_status(derived2);
    }
    freeze_derived_effects(derived2);
    remove_reactions(derived2, 0);
  }
}
function remove_reactions(signal, start_index) {
  var dependencies = signal.deps;
  if (dependencies === null) return;
  for (var i = start_index; i < dependencies.length; i++) {
    remove_reaction(signal, dependencies[i]);
  }
}
function update_effect(effect2) {
  var flags2 = effect2.f;
  if ((flags2 & DESTROYED) !== 0) {
    return;
  }
  set_signal_status(effect2, CLEAN);
  var previous_effect = active_effect;
  var was_updating_effect = is_updating_effect;
  active_effect = effect2;
  is_updating_effect = true;
  if (DEV) {
    var previous_component_fn = dev_current_component_function;
    set_dev_current_component_function(effect2.component_function);
    var previous_stack = (
      /** @type {any} */
      dev_stack
    );
    set_dev_stack(effect2.dev_stack ?? dev_stack);
  }
  try {
    if ((flags2 & (BLOCK_EFFECT | MANAGED_EFFECT)) !== 0) {
      destroy_block_effect_children(effect2);
    } else {
      destroy_effect_children(effect2);
    }
    execute_effect_teardown(effect2);
    var teardown2 = update_reaction(effect2);
    effect2.teardown = typeof teardown2 === "function" ? teardown2 : null;
    effect2.wv = write_version;
    var dep;
    if (DEV && tracing_mode_flag && (effect2.f & DIRTY) !== 0 && effect2.deps !== null) ;
  } finally {
    is_updating_effect = was_updating_effect;
    active_effect = previous_effect;
    if (DEV) {
      set_dev_current_component_function(previous_component_fn);
      set_dev_stack(previous_stack);
    }
  }
}
async function tick() {
  await Promise.resolve();
  flushSync();
}
function get(signal) {
  var flags2 = signal.f;
  var is_derived = (flags2 & DERIVED) !== 0;
  if (active_reaction !== null && !untracking) {
    var destroyed = active_effect !== null && (active_effect.f & DESTROYED) !== 0;
    if (!destroyed && (current_sources === null || !includes.call(current_sources, signal))) {
      var deps = active_reaction.deps;
      if ((active_reaction.f & REACTION_IS_UPDATING) !== 0) {
        if (signal.rv < read_version) {
          signal.rv = read_version;
          if (new_deps === null && deps !== null && deps[skipped_deps] === signal) {
            skipped_deps++;
          } else if (new_deps === null) {
            new_deps = [signal];
          } else {
            new_deps.push(signal);
          }
        }
      } else {
        (active_reaction.deps ??= []).push(signal);
        var reactions = signal.reactions;
        if (reactions === null) {
          signal.reactions = [active_reaction];
        } else if (!includes.call(reactions, active_reaction)) {
          reactions.push(active_reaction);
        }
      }
    }
  }
  if (DEV) {
    if (!untracking && reactivity_loss_tracker && !reactivity_loss_tracker.warned && (reactivity_loss_tracker.effect.f & REACTION_IS_UPDATING) === 0 && !reactivity_loss_tracker.effect_deps.has(signal)) {
      reactivity_loss_tracker.warned = true;
      await_reactivity_loss(
        /** @type {string} */
        signal.label
      );
      var trace = get_error("traced at");
      if (trace) console.warn(trace);
    }
    recent_async_deriveds.delete(signal);
  }
  if (is_destroying_effect && old_values.has(signal)) {
    return old_values.get(signal);
  }
  if (is_derived) {
    var derived2 = (
      /** @type {Derived} */
      signal
    );
    if (is_destroying_effect) {
      var value = derived2.v;
      if ((derived2.f & CLEAN) === 0 && derived2.reactions !== null || depends_on_old_values(derived2)) {
        value = execute_derived(derived2);
      }
      old_values.set(derived2, value);
      return value;
    }
    var should_connect = (derived2.f & CONNECTED) === 0 && !untracking && active_reaction !== null && (is_updating_effect || (active_reaction.f & CONNECTED) !== 0);
    var is_new = (derived2.f & REACTION_RAN) === 0;
    if (is_dirty(derived2)) {
      if (should_connect) {
        derived2.f |= CONNECTED;
      }
      update_derived(derived2);
    }
    if (should_connect && !is_new) {
      unfreeze_derived_effects(derived2);
      reconnect(derived2);
    }
  }
  if (batch_values?.has(signal)) {
    return batch_values.get(signal);
  }
  if ((signal.f & ERROR_VALUE) !== 0) {
    throw signal.v;
  }
  return signal.v;
}
function reconnect(derived2) {
  derived2.f |= CONNECTED;
  if (derived2.deps === null) return;
  for (const dep of derived2.deps) {
    (dep.reactions ??= []).push(derived2);
    if ((dep.f & DERIVED) !== 0 && (dep.f & CONNECTED) === 0) {
      unfreeze_derived_effects(
        /** @type {Derived} */
        dep
      );
      reconnect(
        /** @type {Derived} */
        dep
      );
    }
  }
}
function depends_on_old_values(derived2) {
  if (derived2.v === UNINITIALIZED) return true;
  if (derived2.deps === null) return false;
  for (const dep of derived2.deps) {
    if (old_values.has(dep)) {
      return true;
    }
    if ((dep.f & DERIVED) !== 0 && depends_on_old_values(
      /** @type {Derived} */
      dep
    )) {
      return true;
    }
  }
  return false;
}
function untrack(fn) {
  var previous_untracking = untracking;
  try {
    untracking = true;
    return fn();
  } finally {
    untracking = previous_untracking;
  }
}
function deep_read_state(value) {
  if (typeof value !== "object" || !value || value instanceof EventTarget) {
    return;
  }
  if (STATE_SYMBOL in value) {
    deep_read(value);
  } else if (!Array.isArray(value)) {
    for (let key in value) {
      const prop2 = value[key];
      if (typeof prop2 === "object" && prop2 && STATE_SYMBOL in prop2) {
        deep_read(prop2);
      }
    }
  }
}
function deep_read(value, visited = /* @__PURE__ */ new Set()) {
  if (typeof value === "object" && value !== null && // We don't want to traverse DOM elements
  !(value instanceof EventTarget) && !visited.has(value)) {
    visited.add(value);
    if (value instanceof Date) {
      value.getTime();
    }
    for (let key in value) {
      try {
        deep_read(value[key], visited);
      } catch (e) {
      }
    }
    const proto = get_prototype_of(value);
    if (proto !== Object.prototype && proto !== Array.prototype && proto !== Map.prototype && proto !== Set.prototype && proto !== Date.prototype) {
      const descriptors = get_descriptors(proto);
      for (let key in descriptors) {
        const get2 = descriptors[key].get;
        if (get2) {
          try {
            get2.call(value);
          } catch (e) {
          }
        }
      }
    }
  }
}
const event_symbol = Symbol("events");
const all_registered_events = /* @__PURE__ */ new Set();
const root_event_handles = /* @__PURE__ */ new Set();
function create_event(event_name, dom, handler, options = {}) {
  function target_handler(event2) {
    if (!options.capture) {
      handle_event_propagation.call(dom, event2);
    }
    if (!event2.cancelBubble) {
      return without_reactive_context(() => {
        return handler?.call(this, event2);
      });
    }
  }
  if (event_name.startsWith("pointer") || event_name.startsWith("touch") || event_name === "wheel") {
    queue_micro_task(() => {
      dom.addEventListener(event_name, target_handler, options);
    });
  } else {
    dom.addEventListener(event_name, target_handler, options);
  }
  return target_handler;
}
function event(event_name, dom, handler, capture2, passive) {
  var options = { capture: capture2, passive };
  var target_handler = create_event(event_name, dom, handler, options);
  if (dom === document.body || // @ts-ignore
  dom === window || // @ts-ignore
  dom === document || // Firefox has quirky behavior, it can happen that we still get "canplay" events when the element is already removed
  dom instanceof HTMLMediaElement) {
    teardown(() => {
      dom.removeEventListener(event_name, target_handler, options);
    });
  }
}
function delegated(event_name, element, handler) {
  (element[event_symbol] ??= {})[event_name] = handler;
}
function delegate(events) {
  for (var i = 0; i < events.length; i++) {
    all_registered_events.add(events[i]);
  }
  for (var fn of root_event_handles) {
    fn(events);
  }
}
let last_propagated_event = null;
function handle_event_propagation(event2) {
  var handler_element = this;
  var owner_document = (
    /** @type {Node} */
    handler_element.ownerDocument
  );
  var event_name = event2.type;
  var path = event2.composedPath?.() || [];
  var current_target = (
    /** @type {null | Element} */
    path[0] || event2.target
  );
  last_propagated_event = event2;
  var path_idx = 0;
  var handled_at = last_propagated_event === event2 && event2[event_symbol];
  if (handled_at) {
    var at_idx = path.indexOf(handled_at);
    if (at_idx !== -1 && (handler_element === document || handler_element === /** @type {any} */
    window)) {
      event2[event_symbol] = handler_element;
      return;
    }
    var handler_idx = path.indexOf(handler_element);
    if (handler_idx === -1) {
      return;
    }
    if (at_idx <= handler_idx) {
      path_idx = at_idx;
    }
  }
  current_target = /** @type {Element} */
  path[path_idx] || event2.target;
  if (current_target === handler_element) return;
  define_property(event2, "currentTarget", {
    configurable: true,
    get() {
      return current_target || owner_document;
    }
  });
  var previous_reaction = active_reaction;
  var previous_effect = active_effect;
  set_active_reaction(null);
  set_active_effect(null);
  try {
    var throw_error;
    var other_errors = [];
    while (current_target !== null) {
      var parent_element = current_target.assignedSlot || current_target.parentNode || /** @type {any} */
      current_target.host || null;
      try {
        var delegated2 = current_target[event_symbol]?.[event_name];
        if (delegated2 != null && (!/** @type {any} */
        current_target.disabled || // DOM could've been updated already by the time this is reached, so we check this as well
        // -> the target could not have been disabled because it emits the event in the first place
        event2.target === current_target)) {
          delegated2.call(current_target, event2);
        }
      } catch (error) {
        if (throw_error) {
          other_errors.push(error);
        } else {
          throw_error = error;
        }
      }
      if (event2.cancelBubble || parent_element === handler_element || parent_element === null) {
        break;
      }
      current_target = parent_element;
    }
    if (throw_error) {
      for (let error of other_errors) {
        queueMicrotask(() => {
          throw error;
        });
      }
      throw throw_error;
    }
  } finally {
    event2[event_symbol] = handler_element;
    delete event2.currentTarget;
    set_active_reaction(previous_reaction);
    set_active_effect(previous_effect);
  }
}
const policy = (
  // We gotta write it like this because after downleveling the pure comment may end up in the wrong location
  globalThis?.window?.trustedTypes && /* @__PURE__ */ globalThis.window.trustedTypes.createPolicy("svelte-trusted-html", {
    /** @param {string} html */
    createHTML: (html2) => {
      return html2;
    }
  })
);
function create_trusted_html(html2) {
  return (
    /** @type {string} */
    policy?.createHTML(html2) ?? html2
  );
}
function create_fragment_from_html(html2) {
  var elem = create_element("template");
  elem.innerHTML = create_trusted_html(html2.replaceAll("<!>", "<!---->"));
  return elem.content;
}
function assign_nodes(start, end) {
  var effect2 = (
    /** @type {Effect} */
    active_effect
  );
  if (effect2.nodes === null) {
    effect2.nodes = { start, end, a: null, t: null };
  }
}
// @__NO_SIDE_EFFECTS__
function from_html(content, flags2) {
  var is_fragment = (flags2 & TEMPLATE_FRAGMENT) !== 0;
  var use_import_node = (flags2 & TEMPLATE_USE_IMPORT_NODE) !== 0;
  var node;
  var has_start = !content.startsWith("<!>");
  return () => {
    if (node === void 0) {
      node = create_fragment_from_html(has_start ? content : "<!>" + content);
      if (!is_fragment) node = /** @type {TemplateNode} */
      /* @__PURE__ */ get_first_child(node);
    }
    var clone = (
      /** @type {TemplateNode} */
      use_import_node || is_firefox ? document.importNode(node, true) : node.cloneNode(true)
    );
    if (is_fragment) {
      var start = (
        /** @type {TemplateNode} */
        /* @__PURE__ */ get_first_child(clone)
      );
      var end = (
        /** @type {TemplateNode} */
        clone.lastChild
      );
      assign_nodes(start, end);
    } else {
      assign_nodes(clone, clone);
    }
    return clone;
  };
}
function text$1(value = "") {
  {
    var t = create_text(value + "");
    assign_nodes(t, t);
    return t;
  }
}
function comment() {
  var frag2 = document.createDocumentFragment();
  var start = document.createComment("");
  var anchor = create_text();
  frag2.append(start, anchor);
  assign_nodes(start, anchor);
  return frag2;
}
function append(anchor, dom) {
  if (anchor === null) {
    return;
  }
  anchor.before(
    /** @type {Node} */
    dom
  );
}
const PASSIVE_EVENTS = ["touchstart", "touchmove"];
function is_passive_event(name2) {
  return PASSIVE_EVENTS.includes(name2);
}
function set_text(text2, value) {
  var str = value == null ? "" : typeof value === "object" ? `${value}` : value;
  if (str !== (text2.__t ??= text2.nodeValue)) {
    text2.__t = str;
    text2.nodeValue = `${str}`;
  }
}
function mount(component2, options) {
  return _mount(component2, options);
}
const listeners = /* @__PURE__ */ new Map();
function _mount(Component, { target, anchor, props = {}, events, context, intro = true, transformError }) {
  init_operations();
  var component2 = void 0;
  var unmount2 = component_root(() => {
    var anchor_node = anchor ?? target.appendChild(create_text());
    boundary(
      /** @type {TemplateNode} */
      anchor_node,
      {
        pending: () => {
        }
      },
      (anchor_node2) => {
        push({});
        var ctx = (
          /** @type {ComponentContext} */
          component_context
        );
        if (context) ctx.c = context;
        if (events) {
          props.$$events = events;
        }
        component2 = Component(anchor_node2, props) || {};
        pop();
      },
      transformError
    );
    var registered_events = /* @__PURE__ */ new Set();
    var event_handle = (events2) => {
      for (var i = 0; i < events2.length; i++) {
        var event_name = events2[i];
        if (registered_events.has(event_name)) continue;
        registered_events.add(event_name);
        var passive = is_passive_event(event_name);
        for (const node of [target, document]) {
          var counts = listeners.get(node);
          if (counts === void 0) {
            counts = /* @__PURE__ */ new Map();
            listeners.set(node, counts);
          }
          var count = counts.get(event_name);
          if (count === void 0) {
            node.addEventListener(event_name, handle_event_propagation, { passive });
            counts.set(event_name, 1);
          } else {
            counts.set(event_name, count + 1);
          }
        }
      }
    };
    event_handle(array_from(all_registered_events));
    root_event_handles.add(event_handle);
    return () => {
      for (var event_name of registered_events) {
        for (const node of [target, document]) {
          var counts = (
            /** @type {Map<string, number>} */
            listeners.get(node)
          );
          var count = (
            /** @type {number} */
            counts.get(event_name)
          );
          if (--count == 0) {
            node.removeEventListener(event_name, handle_event_propagation);
            counts.delete(event_name);
            if (counts.size === 0) {
              listeners.delete(node);
            }
          } else {
            counts.set(event_name, count);
          }
        }
      }
      root_event_handles.delete(event_handle);
      if (anchor_node !== anchor) {
        anchor_node.parentNode?.removeChild(anchor_node);
      }
    };
  });
  mounted_components.set(component2, unmount2);
  return component2;
}
let mounted_components = /* @__PURE__ */ new WeakMap();
function unmount(component2, options) {
  const fn = mounted_components.get(component2);
  if (fn) {
    mounted_components.delete(component2);
    return fn(options);
  }
  if (DEV) {
    if (STATE_SYMBOL in component2) {
      state_proxy_unmount();
    } else {
      lifecycle_double_unmount();
    }
  }
  return Promise.resolve();
}
class BranchManager {
  /** @type {TemplateNode} */
  anchor;
  /** @type {Map<Batch, Key>} */
  #batches = /* @__PURE__ */ new Map();
  /**
   * Map of keys to effects that are currently rendered in the DOM.
   * These effects are visible and actively part of the document tree.
   * Example:
   * ```
   * {#if condition}
   * 	foo
   * {:else}
   * 	bar
   * {/if}
   * ```
   * Can result in the entries `true->Effect` and `false->Effect`
   * @type {Map<Key, Effect>}
   */
  #onscreen = /* @__PURE__ */ new Map();
  /**
   * Similar to #onscreen with respect to the keys, but contains branches that are not yet
   * in the DOM, because their insertion is deferred.
   * @type {Map<Key, Branch>}
   */
  #offscreen = /* @__PURE__ */ new Map();
  /**
   * Keys of effects that are currently outroing
   * @type {Set<Key>}
   */
  #outroing = /* @__PURE__ */ new Set();
  /**
   * Whether to pause (i.e. outro) on change, or destroy immediately.
   * This is necessary for `<svelte:element>`
   */
  #transition = true;
  /**
   * @param {TemplateNode} anchor
   * @param {boolean} transition
   */
  constructor(anchor, transition = true) {
    this.anchor = anchor;
    this.#transition = transition;
  }
  /**
   * @param {Batch} batch
   */
  #commit = (batch) => {
    if (!this.#batches.has(batch)) return;
    var key = (
      /** @type {Key} */
      this.#batches.get(batch)
    );
    var onscreen = this.#onscreen.get(key);
    if (onscreen) {
      resume_effect(onscreen);
      this.#outroing.delete(key);
    } else {
      var offscreen = this.#offscreen.get(key);
      if (offscreen) {
        this.#onscreen.set(key, offscreen.effect);
        this.#offscreen.delete(key);
        if (DEV) {
          offscreen.fragment.lastChild[HMR_ANCHOR] = this.anchor;
        }
        offscreen.fragment.lastChild.remove();
        this.anchor.before(offscreen.fragment);
        onscreen = offscreen.effect;
      }
    }
    for (const [b, k] of this.#batches) {
      this.#batches.delete(b);
      if (b === batch) {
        break;
      }
      const offscreen2 = this.#offscreen.get(k);
      if (offscreen2) {
        destroy_effect(offscreen2.effect);
        this.#offscreen.delete(k);
      }
    }
    for (const [k, effect2] of this.#onscreen) {
      if (k === key || this.#outroing.has(k)) continue;
      const on_destroy = () => {
        const keys = Array.from(this.#batches.values());
        if (keys.includes(k)) {
          var fragment = document.createDocumentFragment();
          move_effect(effect2, fragment);
          fragment.append(create_text());
          this.#offscreen.set(k, { effect: effect2, fragment });
        } else {
          destroy_effect(effect2);
        }
        this.#outroing.delete(k);
        this.#onscreen.delete(k);
      };
      if (this.#transition || !onscreen) {
        this.#outroing.add(k);
        pause_effect(effect2, on_destroy, false);
      } else {
        on_destroy();
      }
    }
  };
  /**
   * @param {Batch} batch
   */
  #discard = (batch) => {
    this.#batches.delete(batch);
    const keys = Array.from(this.#batches.values());
    for (const [k, branch2] of this.#offscreen) {
      if (!keys.includes(k)) {
        destroy_effect(branch2.effect);
        this.#offscreen.delete(k);
      }
    }
  };
  /**
   *
   * @param {any} key
   * @param {null | ((target: TemplateNode) => void)} fn
   */
  ensure(key, fn) {
    var batch = (
      /** @type {Batch} */
      current_batch
    );
    var defer = should_defer_append();
    if (fn && !this.#onscreen.has(key) && !this.#offscreen.has(key)) {
      if (defer) {
        var fragment = document.createDocumentFragment();
        var target = create_text();
        fragment.append(target);
        this.#offscreen.set(key, {
          effect: branch(() => fn(target)),
          fragment
        });
      } else {
        this.#onscreen.set(
          key,
          branch(() => fn(this.anchor))
        );
      }
    }
    this.#batches.set(batch, key);
    if (defer) {
      for (const [k, effect2] of this.#onscreen) {
        if (k === key) {
          batch.unskip_effect(effect2);
        } else {
          batch.skip_effect(effect2);
        }
      }
      for (const [k, branch2] of this.#offscreen) {
        if (k === key) {
          batch.unskip_effect(branch2.effect);
        } else {
          batch.skip_effect(branch2.effect);
        }
      }
      batch.oncommit(this.#commit);
      batch.ondiscard(this.#discard);
    } else {
      this.#commit(batch);
    }
  }
}
if (DEV) {
  let throw_rune_error = function(rune) {
    if (!(rune in globalThis)) {
      let value;
      Object.defineProperty(globalThis, rune, {
        configurable: true,
        // eslint-disable-next-line getter-return
        get: () => {
          if (value !== void 0) {
            return value;
          }
          rune_outside_svelte(rune);
        },
        set: (v) => {
          value = v;
        }
      });
    }
  };
  var throw_rune_error2 = throw_rune_error;
  throw_rune_error("$state");
  throw_rune_error("$effect");
  throw_rune_error("$derived");
  throw_rune_error("$inspect");
  throw_rune_error("$props");
  throw_rune_error("$bindable");
}
function onMount(fn) {
  if (component_context === null) {
    lifecycle_outside_component("onMount");
  }
  if (legacy_mode_flag && component_context.l !== null) {
    init_update_callbacks(component_context).m.push(fn);
  } else {
    user_effect(() => {
      const cleanup = untrack(fn);
      if (typeof cleanup === "function") return (
        /** @type {() => void} */
        cleanup
      );
    });
  }
}
function onDestroy(fn) {
  if (component_context === null) {
    lifecycle_outside_component("onDestroy");
  }
  onMount(() => () => untrack(fn));
}
function create_custom_event(type, detail, { bubbles = false, cancelable = false } = {}) {
  return new CustomEvent(type, { detail, bubbles, cancelable });
}
function createEventDispatcher() {
  const active_component_context = component_context;
  if (active_component_context === null) {
    lifecycle_outside_component("createEventDispatcher");
  }
  return (type, detail, options) => {
    const events = (
      /** @type {Record<string, Function | Function[]>} */
      active_component_context.s.$$events?.[
        /** @type {string} */
        type
      ]
    );
    if (events) {
      const callbacks = is_array(events) ? events.slice() : [events];
      const event2 = create_custom_event(
        /** @type {string} */
        type,
        detail,
        options
      );
      for (const fn of callbacks) {
        fn.call(active_component_context.x, event2);
      }
      return !event2.defaultPrevented;
    }
    return true;
  };
}
function afterUpdate(fn) {
  if (component_context === null) {
    lifecycle_outside_component("afterUpdate");
  }
  if (component_context.l === null) {
    lifecycle_legacy_only("afterUpdate");
  }
  init_update_callbacks(component_context).a.push(fn);
}
function init_update_callbacks(context) {
  var l = (
    /** @type {ComponentContextLegacy} */
    context.l
  );
  return l.u ??= { a: [], b: [], m: [] };
}
function if_block(node, fn, elseif = false) {
  var branches = new BranchManager(node);
  var flags2 = elseif ? EFFECT_TRANSPARENT : 0;
  function update_branch(key, fn2) {
    branches.ensure(key, fn2);
  }
  block$1(() => {
    var has_branch = false;
    fn((fn2, key = 0) => {
      has_branch = true;
      update_branch(key, fn2);
    });
    if (!has_branch) {
      update_branch(-1, null);
    }
  }, flags2);
}
function index(_, i) {
  return i;
}
function pause_effects(state2, to_destroy, controlled_anchor) {
  var transitions = [];
  var length = to_destroy.length;
  var group;
  var remaining = to_destroy.length;
  for (var i = 0; i < length; i++) {
    let effect2 = to_destroy[i];
    pause_effect(
      effect2,
      () => {
        if (group) {
          group.pending.delete(effect2);
          group.done.add(effect2);
          if (group.pending.size === 0) {
            var groups = (
              /** @type {Set<EachOutroGroup>} */
              state2.outrogroups
            );
            destroy_effects(state2, array_from(group.done));
            groups.delete(group);
            if (groups.size === 0) {
              state2.outrogroups = null;
            }
          }
        } else {
          remaining -= 1;
        }
      },
      false
    );
  }
  if (remaining === 0) {
    var fast_path = transitions.length === 0 && controlled_anchor !== null;
    if (fast_path) {
      var anchor = (
        /** @type {Element} */
        controlled_anchor
      );
      var parent_node = (
        /** @type {Element} */
        anchor.parentNode
      );
      clear_text_content(parent_node);
      parent_node.append(anchor);
      state2.items.clear();
    }
    destroy_effects(state2, to_destroy, !fast_path);
  } else {
    group = {
      pending: new Set(to_destroy),
      done: /* @__PURE__ */ new Set()
    };
    (state2.outrogroups ??= /* @__PURE__ */ new Set()).add(group);
  }
}
function destroy_effects(state2, to_destroy, remove_dom = true) {
  var preserved_effects;
  if (state2.pending.size > 0) {
    preserved_effects = /* @__PURE__ */ new Set();
    for (const keys of state2.pending.values()) {
      for (const key of keys) {
        preserved_effects.add(
          /** @type {EachItem} */
          state2.items.get(key).e
        );
      }
    }
  }
  for (var i = 0; i < to_destroy.length; i++) {
    var e = to_destroy[i];
    if (preserved_effects?.has(e)) {
      e.f |= EFFECT_OFFSCREEN;
      const fragment = document.createDocumentFragment();
      move_effect(e, fragment);
    } else {
      destroy_effect(to_destroy[i], remove_dom);
    }
  }
}
var offscreen_anchor;
function each(node, flags2, get_collection, get_key, render_fn, fallback_fn = null) {
  var anchor = node;
  var items = /* @__PURE__ */ new Map();
  var is_controlled = (flags2 & EACH_IS_CONTROLLED) !== 0;
  if (is_controlled) {
    var parent_node = (
      /** @type {Element} */
      node
    );
    anchor = parent_node.appendChild(create_text());
  }
  var fallback = null;
  var each_array = /* @__PURE__ */ derived_safe_equal(() => {
    var collection = get_collection();
    return is_array(collection) ? collection : collection == null ? [] : array_from(collection);
  });
  if (DEV) {
    tag$1(each_array, "{#each ...}");
  }
  var array;
  var pending = /* @__PURE__ */ new Map();
  var first_run = true;
  function commit(batch) {
    if ((state2.effect.f & DESTROYED) !== 0) {
      return;
    }
    state2.pending.delete(batch);
    state2.fallback = fallback;
    reconcile(state2, array, anchor, flags2, get_key);
    if (fallback !== null) {
      if (array.length === 0) {
        if ((fallback.f & EFFECT_OFFSCREEN) === 0) {
          resume_effect(fallback);
        } else {
          fallback.f ^= EFFECT_OFFSCREEN;
          move(fallback, null, anchor);
        }
      } else {
        pause_effect(fallback, () => {
          fallback = null;
        });
      }
    }
  }
  function discard(batch) {
    state2.pending.delete(batch);
  }
  var effect2 = block$1(() => {
    array = /** @type {V[]} */
    get(each_array);
    var length = array.length;
    var keys = /* @__PURE__ */ new Set();
    var batch = (
      /** @type {Batch} */
      current_batch
    );
    var defer = should_defer_append();
    for (var index2 = 0; index2 < length; index2 += 1) {
      var value = array[index2];
      var key = get_key(value, index2);
      if (DEV) {
        var key_again = get_key(value, index2);
        if (key !== key_again) {
          each_key_volatile(String(index2), String(key), String(key_again));
        }
      }
      var item = first_run ? null : items.get(key);
      if (item) {
        if (item.v) internal_set(item.v, value);
        if (item.i) internal_set(item.i, index2);
        if (defer) {
          batch.unskip_effect(item.e);
        }
      } else {
        item = create_item(
          items,
          first_run ? anchor : offscreen_anchor ??= create_text(),
          value,
          key,
          index2,
          render_fn,
          flags2,
          get_collection
        );
        if (!first_run) {
          item.e.f |= EFFECT_OFFSCREEN;
        }
        items.set(key, item);
      }
      keys.add(key);
    }
    if (length === 0 && fallback_fn && !fallback) {
      if (first_run) {
        fallback = branch(() => fallback_fn(anchor));
      } else {
        fallback = branch(() => fallback_fn(offscreen_anchor ??= create_text()));
        fallback.f |= EFFECT_OFFSCREEN;
      }
    }
    if (length > keys.size) {
      if (DEV) {
        validate_each_keys(array, get_key);
      } else {
        each_key_duplicate("", "", "");
      }
    }
    if (!first_run) {
      pending.set(batch, keys);
      if (defer) {
        for (const [key2, item2] of items) {
          if (!keys.has(key2)) {
            batch.skip_effect(item2.e);
          }
        }
        batch.oncommit(commit);
        batch.ondiscard(discard);
      } else {
        commit(batch);
      }
    }
    get(each_array);
  });
  var state2 = { effect: effect2, items, pending, outrogroups: null, fallback };
  first_run = false;
}
function skip_to_branch(effect2) {
  while (effect2 !== null && (effect2.f & BRANCH_EFFECT) === 0) {
    effect2 = effect2.next;
  }
  return effect2;
}
function reconcile(state2, array, anchor, flags2, get_key) {
  var is_animated = (flags2 & EACH_IS_ANIMATED) !== 0;
  var length = array.length;
  var items = state2.items;
  var current = skip_to_branch(state2.effect.first);
  var seen;
  var prev = null;
  var to_animate;
  var matched = [];
  var stashed = [];
  var value;
  var key;
  var effect2;
  var i;
  if (is_animated) {
    for (i = 0; i < length; i += 1) {
      value = array[i];
      key = get_key(value, i);
      effect2 = /** @type {EachItem} */
      items.get(key).e;
      if ((effect2.f & EFFECT_OFFSCREEN) === 0) {
        effect2.nodes?.a?.measure();
        (to_animate ??= /* @__PURE__ */ new Set()).add(effect2);
      }
    }
  }
  for (i = 0; i < length; i += 1) {
    value = array[i];
    key = get_key(value, i);
    effect2 = /** @type {EachItem} */
    items.get(key).e;
    if (state2.outrogroups !== null) {
      for (const group of state2.outrogroups) {
        group.pending.delete(effect2);
        group.done.delete(effect2);
      }
    }
    if ((effect2.f & INERT) !== 0) {
      resume_effect(effect2);
      if (is_animated) {
        effect2.nodes?.a?.unfix();
        (to_animate ??= /* @__PURE__ */ new Set()).delete(effect2);
      }
    }
    if ((effect2.f & EFFECT_OFFSCREEN) !== 0) {
      effect2.f ^= EFFECT_OFFSCREEN;
      if (effect2 === current) {
        move(effect2, null, anchor);
      } else {
        var next = prev ? prev.next : current;
        if (effect2 === state2.effect.last) {
          state2.effect.last = effect2.prev;
        }
        if (effect2.prev) effect2.prev.next = effect2.next;
        if (effect2.next) effect2.next.prev = effect2.prev;
        link$1(state2, prev, effect2);
        link$1(state2, effect2, next);
        move(effect2, next, anchor);
        prev = effect2;
        matched = [];
        stashed = [];
        current = skip_to_branch(prev.next);
        continue;
      }
    }
    if (effect2 !== current) {
      if (seen !== void 0 && seen.has(effect2)) {
        if (matched.length < stashed.length) {
          var start = stashed[0];
          var j;
          prev = start.prev;
          var a = matched[0];
          var b = matched[matched.length - 1];
          for (j = 0; j < matched.length; j += 1) {
            move(matched[j], start, anchor);
          }
          for (j = 0; j < stashed.length; j += 1) {
            seen.delete(stashed[j]);
          }
          link$1(state2, a.prev, b.next);
          link$1(state2, prev, a);
          link$1(state2, b, start);
          current = start;
          prev = b;
          i -= 1;
          matched = [];
          stashed = [];
        } else {
          seen.delete(effect2);
          move(effect2, current, anchor);
          link$1(state2, effect2.prev, effect2.next);
          link$1(state2, effect2, prev === null ? state2.effect.first : prev.next);
          link$1(state2, prev, effect2);
          prev = effect2;
        }
        continue;
      }
      matched = [];
      stashed = [];
      while (current !== null && current !== effect2) {
        (seen ??= /* @__PURE__ */ new Set()).add(current);
        stashed.push(current);
        current = skip_to_branch(current.next);
      }
      if (current === null) {
        continue;
      }
    }
    if ((effect2.f & EFFECT_OFFSCREEN) === 0) {
      matched.push(effect2);
    }
    prev = effect2;
    current = skip_to_branch(effect2.next);
  }
  if (state2.outrogroups !== null) {
    for (const group of state2.outrogroups) {
      if (group.pending.size === 0) {
        destroy_effects(state2, array_from(group.done));
        state2.outrogroups?.delete(group);
      }
    }
    if (state2.outrogroups.size === 0) {
      state2.outrogroups = null;
    }
  }
  if (current !== null || seen !== void 0) {
    var to_destroy = [];
    if (seen !== void 0) {
      for (effect2 of seen) {
        if ((effect2.f & INERT) === 0) {
          to_destroy.push(effect2);
        }
      }
    }
    while (current !== null) {
      if ((current.f & INERT) === 0 && current !== state2.fallback) {
        to_destroy.push(current);
      }
      current = skip_to_branch(current.next);
    }
    var destroy_length = to_destroy.length;
    if (destroy_length > 0) {
      var controlled_anchor = (flags2 & EACH_IS_CONTROLLED) !== 0 && length === 0 ? anchor : null;
      if (is_animated) {
        for (i = 0; i < destroy_length; i += 1) {
          to_destroy[i].nodes?.a?.measure();
        }
        for (i = 0; i < destroy_length; i += 1) {
          to_destroy[i].nodes?.a?.fix();
        }
      }
      pause_effects(state2, to_destroy, controlled_anchor);
    }
  }
  if (is_animated) {
    queue_micro_task(() => {
      if (to_animate === void 0) return;
      for (effect2 of to_animate) {
        effect2.nodes?.a?.apply();
      }
    });
  }
}
function create_item(items, anchor, value, key, index2, render_fn, flags2, get_collection) {
  var v = (flags2 & EACH_ITEM_REACTIVE) !== 0 ? (flags2 & EACH_ITEM_IMMUTABLE) === 0 ? /* @__PURE__ */ mutable_source(value, false, false) : source(value) : null;
  var i = (flags2 & EACH_INDEX_REACTIVE) !== 0 ? source(index2) : null;
  if (DEV && v) {
    v.trace = () => {
      get_collection()[i?.v ?? index2];
    };
  }
  return {
    v,
    i,
    e: branch(() => {
      render_fn(anchor, v ?? value, i ?? index2, get_collection);
      return () => {
        items.delete(key);
      };
    })
  };
}
function move(effect2, next, anchor) {
  if (!effect2.nodes) return;
  var node = effect2.nodes.start;
  var end = effect2.nodes.end;
  var dest = next && (next.f & EFFECT_OFFSCREEN) === 0 ? (
    /** @type {EffectNodes} */
    next.nodes.start
  ) : anchor;
  while (node !== null) {
    var next_node = (
      /** @type {TemplateNode} */
      /* @__PURE__ */ get_next_sibling(node)
    );
    dest.before(node);
    if (node === end) {
      return;
    }
    node = next_node;
  }
}
function link$1(state2, prev, next) {
  if (prev === null) {
    state2.effect.first = next;
  } else {
    prev.next = next;
  }
  if (next === null) {
    state2.effect.last = prev;
  } else {
    next.prev = prev;
  }
}
function validate_each_keys(array, key_fn) {
  const keys = /* @__PURE__ */ new Map();
  const length = array.length;
  for (let i = 0; i < length; i++) {
    const key = key_fn(array[i], i);
    if (keys.has(key)) {
      const a = String(keys.get(key));
      const b = String(i);
      let k = String(key);
      if (k.startsWith("[object ")) k = null;
      each_key_duplicate(a, b, k);
    }
    keys.set(key, i);
  }
}
function html$1(node, get_value, is_controlled = false, svg = false, mathml = false, skip_warning = false) {
  var anchor = node;
  var value = "";
  if (is_controlled) {
    var parent_node = (
      /** @type {Element} */
      node
    );
  }
  template_effect(() => {
    var effect2 = (
      /** @type {Effect} */
      active_effect
    );
    if (value === (value = get_value() ?? "")) {
      return;
    }
    if (is_controlled && true) {
      effect2.nodes = null;
      parent_node.innerHTML = /** @type {string} */
      value;
      if (value !== "") {
        assign_nodes(
          /** @type {TemplateNode} */
          /* @__PURE__ */ get_first_child(parent_node),
          /** @type {TemplateNode} */
          parent_node.lastChild
        );
      }
      return;
    }
    if (effect2.nodes !== null) {
      remove_effect_dom(
        effect2.nodes.start,
        /** @type {TemplateNode} */
        effect2.nodes.end
      );
      effect2.nodes = null;
    }
    if (value === "") return;
    var ns = svg ? NAMESPACE_SVG : mathml ? NAMESPACE_MATHML : void 0;
    var wrapper = (
      /** @type {HTMLTemplateElement | SVGElement | MathMLElement} */
      create_element(svg ? "svg" : mathml ? "math" : "template", ns)
    );
    wrapper.innerHTML = /** @type {any} */
    value;
    var node2 = svg || mathml ? wrapper : (
      /** @type {HTMLTemplateElement} */
      wrapper.content
    );
    assign_nodes(
      /** @type {TemplateNode} */
      /* @__PURE__ */ get_first_child(node2),
      /** @type {TemplateNode} */
      node2.lastChild
    );
    if (svg || mathml) {
      while (/* @__PURE__ */ get_first_child(node2)) {
        anchor.before(
          /** @type {TemplateNode} */
          /* @__PURE__ */ get_first_child(node2)
        );
      }
    } else {
      anchor.before(node2);
    }
  });
}
function slot(anchor, $$props, name2, slot_props, fallback_fn) {
  var slot_fn = $$props.$$slots?.[name2];
  var is_interop = false;
  if (slot_fn === true) {
    slot_fn = $$props[name2 === "default" ? "children" : name2];
    is_interop = true;
  }
  if (slot_fn === void 0) ;
  else {
    slot_fn(anchor, is_interop ? () => slot_props : slot_props);
  }
}
function sanitize_slots(props) {
  const sanitized = {};
  if (props.children) sanitized.default = true;
  for (const key in props.$$slots) {
    sanitized[key] = true;
  }
  return sanitized;
}
function component(node, get_component, render_fn) {
  var branches = new BranchManager(node);
  block$1(() => {
    var component2 = get_component() ?? null;
    branches.ensure(component2, component2 && ((target) => render_fn(target, component2)));
  }, EFFECT_TRANSPARENT);
}
function action(dom, action2, get_value) {
  effect(() => {
    var payload = untrack(() => action2(dom, get_value?.()) || {});
    if (get_value && payload?.update) {
      var inited = false;
      var prev = (
        /** @type {any} */
        {}
      );
      render_effect(() => {
        var value = get_value();
        deep_read_state(value);
        if (inited && safe_not_equal(prev, value)) {
          prev = value;
          payload.update(value);
        }
      });
      inited = true;
    }
    if (payload?.destroy) {
      return () => (
        /** @type {Function} */
        payload.destroy()
      );
    }
  });
}
const whitespace = [..." 	\n\r\f \v\uFEFF"];
function to_class(value, hash, directives) {
  var classname = value == null ? "" : "" + value;
  if (hash) {
    classname = classname ? classname + " " + hash : hash;
  }
  if (directives) {
    for (var key of Object.keys(directives)) {
      if (directives[key]) {
        classname = classname ? classname + " " + key : key;
      } else if (classname.length) {
        var len = key.length;
        var a = 0;
        while ((a = classname.indexOf(key, a)) >= 0) {
          var b = a + len;
          if ((a === 0 || whitespace.includes(classname[a - 1])) && (b === classname.length || whitespace.includes(classname[b]))) {
            classname = (a === 0 ? "" : classname.substring(0, a)) + classname.substring(b + 1);
          } else {
            a = b;
          }
        }
      }
    }
  }
  return classname === "" ? null : classname;
}
function to_style(value, styles) {
  return value == null ? null : String(value);
}
function set_class(dom, is_html, value, hash, prev_classes, next_classes) {
  var prev = dom.__className;
  if (prev !== value || prev === void 0) {
    var next_class_name = to_class(value, hash, next_classes);
    {
      if (next_class_name == null) {
        dom.removeAttribute("class");
      } else {
        dom.className = next_class_name;
      }
    }
    dom.__className = value;
  } else if (next_classes && prev_classes !== next_classes) {
    for (var key in next_classes) {
      var is_present = !!next_classes[key];
      if (prev_classes == null || is_present !== !!prev_classes[key]) {
        dom.classList.toggle(key, is_present);
      }
    }
  }
  return next_classes;
}
function set_style(dom, value, prev_styles, next_styles) {
  var prev = dom.__style;
  if (prev !== value) {
    var next_style_attr = to_style(value);
    {
      if (next_style_attr == null) {
        dom.removeAttribute("style");
      } else {
        dom.style.cssText = next_style_attr;
      }
    }
    dom.__style = value;
  }
  return next_styles;
}
function select_option(select, value, mounting = false) {
  if (select.multiple) {
    if (value == void 0) {
      return;
    }
    if (!is_array(value)) {
      return select_multiple_invalid_value();
    }
    for (var option of select.options) {
      option.selected = value.includes(get_option_value(option));
    }
    return;
  }
  for (option of select.options) {
    var option_value = get_option_value(option);
    if (is(option_value, value)) {
      option.selected = true;
      return;
    }
  }
  if (!mounting || value !== void 0) {
    select.selectedIndex = -1;
  }
}
function init_select(select) {
  var observer = new MutationObserver(() => {
    select_option(select, select.__value);
  });
  observer.observe(select, {
    // Listen to option element changes
    childList: true,
    subtree: true,
    // because of <optgroup>
    // Listen to option element value attribute changes
    // (doesn't get notified of select value changes,
    // because that property is not reflected as an attribute)
    attributes: true,
    attributeFilter: ["value"]
  });
  teardown(() => {
    observer.disconnect();
  });
}
function bind_select_value(select, get2, set2 = get2) {
  var batches2 = /* @__PURE__ */ new WeakSet();
  var mounting = true;
  listen_to_event_and_reset_event(select, "change", (is_reset) => {
    var query = is_reset ? "[selected]" : ":checked";
    var value;
    if (select.multiple) {
      value = [].map.call(select.querySelectorAll(query), get_option_value);
    } else {
      var selected_option = select.querySelector(query) ?? // will fall back to first non-disabled option if no option is selected
      select.querySelector("option:not([disabled])");
      value = selected_option && get_option_value(selected_option);
    }
    set2(value);
    select.__value = value;
    if (current_batch !== null) {
      batches2.add(current_batch);
    }
  });
  effect(() => {
    var value = get2();
    if (select === document.activeElement) {
      var batch = (
        /** @type {Batch} */
        current_batch
      );
      if (batches2.has(batch)) {
        return;
      }
    }
    select_option(select, value, mounting);
    if (mounting && value === void 0) {
      var selected_option = select.querySelector(":checked");
      if (selected_option !== null) {
        value = get_option_value(selected_option);
        set2(value);
      }
    }
    select.__value = value;
    mounting = false;
  });
  init_select(select);
}
function get_option_value(option) {
  if ("__value" in option) {
    return option.__value;
  } else {
    return option.value;
  }
}
const IS_CUSTOM_ELEMENT = Symbol("is custom element");
const IS_HTML = Symbol("is html");
function set_attribute(element, attribute, value, skip_warning) {
  var attributes = get_attributes(element);
  if (attributes[attribute] === (attributes[attribute] = value)) return;
  if (attribute === "loading") {
    element[LOADING_ATTR_SYMBOL] = value;
  }
  if (value == null) {
    element.removeAttribute(attribute);
  } else if (typeof value !== "string" && get_setters(element).includes(attribute)) {
    element[attribute] = value;
  } else {
    element.setAttribute(attribute, value);
  }
}
function get_attributes(element) {
  return (
    /** @type {Record<string | symbol, unknown>} **/
    // @ts-expect-error
    element.__attributes ??= {
      [IS_CUSTOM_ELEMENT]: element.nodeName.includes("-"),
      [IS_HTML]: element.namespaceURI === NAMESPACE_HTML
    }
  );
}
var setters_cache = /* @__PURE__ */ new Map();
function get_setters(element) {
  var cache_key = element.getAttribute("is") || element.nodeName;
  var setters = setters_cache.get(cache_key);
  if (setters) return setters;
  setters_cache.set(cache_key, setters = []);
  var descriptors;
  var proto = element;
  var element_proto = Element.prototype;
  while (element_proto !== proto) {
    descriptors = get_descriptors(proto);
    for (var key in descriptors) {
      if (descriptors[key].set) {
        setters.push(key);
      }
    }
    proto = get_prototype_of(proto);
  }
  return setters;
}
function bind_value(input, get2, set2 = get2) {
  var batches2 = /* @__PURE__ */ new WeakSet();
  listen_to_event_and_reset_event(input, "input", async (is_reset) => {
    if (DEV && input.type === "checkbox") {
      bind_invalid_checkbox_value();
    }
    var value = is_reset ? input.defaultValue : input.value;
    value = is_numberlike_input(input) ? to_number(value) : value;
    set2(value);
    if (current_batch !== null) {
      batches2.add(current_batch);
    }
    await tick();
    if (value !== (value = get2())) {
      var start = input.selectionStart;
      var end = input.selectionEnd;
      var length = input.value.length;
      input.value = value ?? "";
      if (end !== null) {
        var new_length = input.value.length;
        if (start === end && end === length && new_length > length) {
          input.selectionStart = new_length;
          input.selectionEnd = new_length;
        } else {
          input.selectionStart = start;
          input.selectionEnd = Math.min(end, new_length);
        }
      }
    }
  });
  if (
    // If we are hydrating and the value has since changed,
    // then use the updated value from the input instead.
    // If defaultValue is set, then value == defaultValue
    // TODO Svelte 6: remove input.value check and set to empty string?
    untrack(get2) == null && input.value
  ) {
    set2(is_numberlike_input(input) ? to_number(input.value) : input.value);
    if (current_batch !== null) {
      batches2.add(current_batch);
    }
  }
  render_effect(() => {
    if (DEV && input.type === "checkbox") {
      bind_invalid_checkbox_value();
    }
    var value = get2();
    if (input === document.activeElement) {
      var batch = (
        /** @type {Batch} */
        current_batch
      );
      if (batches2.has(batch)) {
        return;
      }
    }
    if (is_numberlike_input(input) && value === to_number(input.value)) {
      return;
    }
    if (input.type === "date" && !value && !input.value) {
      return;
    }
    if (value !== input.value) {
      input.value = value ?? "";
    }
  });
}
function bind_checked(input, get2, set2 = get2) {
  listen_to_event_and_reset_event(input, "change", (is_reset) => {
    var value = is_reset ? input.defaultChecked : input.checked;
    set2(value);
  });
  if (
    // If we are hydrating and the value has since changed,
    // then use the update value from the input instead.
    // If defaultChecked is set, then checked == defaultChecked
    untrack(get2) == null
  ) {
    set2(input.checked);
  }
  render_effect(() => {
    var value = get2();
    input.checked = Boolean(value);
  });
}
function is_numberlike_input(input) {
  var type = input.type;
  return type === "number" || type === "range";
}
function to_number(value) {
  return value === "" ? null : +value;
}
function bind_prop(props, prop2, value) {
  var desc = get_descriptor(props, prop2);
  if (desc && desc.set) {
    props[prop2] = value;
    teardown(() => {
      props[prop2] = null;
    });
  }
}
function is_bound_this(bound_value, element_or_component) {
  return bound_value === element_or_component || bound_value?.[STATE_SYMBOL] === element_or_component;
}
function bind_this(element_or_component = {}, update2, get_value, get_parts) {
  var component_effect = (
    /** @type {ComponentContext} */
    component_context.r
  );
  var parent = (
    /** @type {Effect} */
    active_effect
  );
  effect(() => {
    var old_parts;
    var parts;
    render_effect(() => {
      old_parts = parts;
      parts = [];
      untrack(() => {
        if (element_or_component !== get_value(...parts)) {
          update2(element_or_component, ...parts);
          if (old_parts && is_bound_this(get_value(...old_parts), element_or_component)) {
            update2(null, ...old_parts);
          }
        }
      });
    });
    return () => {
      let p = parent;
      while (p !== component_effect && p.parent !== null && p.parent.f & DESTROYING) {
        p = p.parent;
      }
      const teardown2 = () => {
        if (parts && is_bound_this(get_value(...parts), element_or_component)) {
          update2(null, ...parts);
        }
      };
      const original_teardown = p.teardown;
      p.teardown = () => {
        teardown2();
        original_teardown?.();
      };
    };
  });
  return element_or_component;
}
function init(immutable = false) {
  const context = (
    /** @type {ComponentContextLegacy} */
    component_context
  );
  const callbacks = context.l.u;
  if (!callbacks) return;
  let props = () => deep_read_state(context.s);
  if (immutable) {
    let version = 0;
    let prev = (
      /** @type {Record<string, any>} */
      {}
    );
    const d = /* @__PURE__ */ derived(() => {
      let changed = false;
      const props2 = context.s;
      for (const key in props2) {
        if (props2[key] !== prev[key]) {
          prev[key] = props2[key];
          changed = true;
        }
      }
      if (changed) version++;
      return version;
    });
    props = () => get(d);
  }
  if (callbacks.b.length) {
    user_pre_effect(() => {
      observe_all(context, props);
      run_all(callbacks.b);
    });
  }
  user_effect(() => {
    const fns = untrack(() => callbacks.m.map(run));
    return () => {
      for (const fn of fns) {
        if (typeof fn === "function") {
          fn();
        }
      }
    };
  });
  if (callbacks.a.length) {
    user_effect(() => {
      observe_all(context, props);
      run_all(callbacks.a);
    });
  }
}
function observe_all(context, props) {
  if (context.l.s) {
    for (const signal of context.l.s) get(signal);
  }
  props();
}
function reactive_import(fn) {
  var s = source(0);
  return function() {
    if (arguments.length === 1) {
      set(s, get(s) + 1);
      return arguments[0];
    } else {
      get(s);
      return fn();
    }
  };
}
const legacy_rest_props_handler = {
  get(target, key) {
    if (target.exclude.includes(key)) return;
    get(target.version);
    return key in target.special ? target.special[key]() : target.props[key];
  },
  set(target, key, value) {
    if (!(key in target.special)) {
      var previous_effect = active_effect;
      try {
        set_active_effect(target.parent_effect);
        target.special[key] = prop(
          {
            get [key]() {
              return target.props[key];
            }
          },
          /** @type {string} */
          key,
          PROPS_IS_UPDATED
        );
      } finally {
        set_active_effect(previous_effect);
      }
    }
    target.special[key](value);
    update(target.version);
    return true;
  },
  getOwnPropertyDescriptor(target, key) {
    if (target.exclude.includes(key)) return;
    if (key in target.props) {
      return {
        enumerable: true,
        configurable: true,
        value: target.props[key]
      };
    }
  },
  deleteProperty(target, key) {
    if (target.exclude.includes(key)) return true;
    target.exclude.push(key);
    update(target.version);
    return true;
  },
  has(target, key) {
    if (target.exclude.includes(key)) return false;
    return key in target.props;
  },
  ownKeys(target) {
    return Reflect.ownKeys(target.props).filter((key) => !target.exclude.includes(key));
  }
};
function legacy_rest_props(props, exclude) {
  return new Proxy(
    {
      props,
      exclude,
      special: {},
      version: source(0),
      // TODO this is only necessary because we need to track component
      // destruction inside `prop`, because of `bind:this`, but it
      // seems likely that we can simplify `bind:this` instead
      parent_effect: (
        /** @type {Effect} */
        active_effect
      )
    },
    legacy_rest_props_handler
  );
}
function prop(props, key, flags2, fallback) {
  var runes = !legacy_mode_flag || (flags2 & PROPS_IS_RUNES) !== 0;
  var bindable = (flags2 & PROPS_IS_BINDABLE) !== 0;
  var lazy = (flags2 & PROPS_IS_LAZY_INITIAL) !== 0;
  var fallback_value = (
    /** @type {V} */
    fallback
  );
  var fallback_dirty = true;
  var get_fallback = () => {
    if (fallback_dirty) {
      fallback_dirty = false;
      fallback_value = lazy ? untrack(
        /** @type {() => V} */
        fallback
      ) : (
        /** @type {V} */
        fallback
      );
    }
    return fallback_value;
  };
  let setter;
  if (bindable) {
    var is_entry_props = STATE_SYMBOL in props || LEGACY_PROPS in props;
    setter = get_descriptor(props, key)?.set ?? (is_entry_props && key in props ? (v) => props[key] = v : void 0);
  }
  var initial_value;
  var is_store_sub = false;
  if (bindable) {
    [initial_value, is_store_sub] = capture_store_binding(() => (
      /** @type {V} */
      props[key]
    ));
  } else {
    initial_value = /** @type {V} */
    props[key];
  }
  if (initial_value === void 0 && fallback !== void 0) {
    initial_value = get_fallback();
    if (setter) {
      if (runes) props_invalid_value(key);
      setter(initial_value);
    }
  }
  var getter;
  if (runes) {
    getter = () => {
      var value = (
        /** @type {V} */
        props[key]
      );
      if (value === void 0) return get_fallback();
      fallback_dirty = true;
      return value;
    };
  } else {
    getter = () => {
      var value = (
        /** @type {V} */
        props[key]
      );
      if (value !== void 0) {
        fallback_value = /** @type {V} */
        void 0;
      }
      return value === void 0 ? fallback_value : value;
    };
  }
  if (runes && (flags2 & PROPS_IS_UPDATED) === 0) {
    return getter;
  }
  if (setter) {
    var legacy_parent = props.$$legacy;
    return (
      /** @type {() => V} */
      (function(value, mutation) {
        if (arguments.length > 0) {
          if (!runes || !mutation || legacy_parent || is_store_sub) {
            setter(mutation ? getter() : value);
          }
          return value;
        }
        return getter();
      })
    );
  }
  var overridden = false;
  var d = ((flags2 & PROPS_IS_IMMUTABLE) !== 0 ? derived : derived_safe_equal)(() => {
    overridden = false;
    return getter();
  });
  if (DEV) {
    d.label = key;
  }
  if (bindable) get(d);
  var parent_effect = (
    /** @type {Effect} */
    active_effect
  );
  return (
    /** @type {() => V} */
    (function(value, mutation) {
      if (arguments.length > 0) {
        const new_value = mutation ? get(d) : runes && bindable ? proxy(value) : value;
        set(d, new_value);
        overridden = true;
        if (fallback_value !== void 0) {
          fallback_value = new_value;
        }
        return value;
      }
      if (is_destroying_effect && overridden || (parent_effect.f & DESTROYED) !== 0) {
        return d.v;
      }
      return get(d);
    })
  );
}
class loading_bar {
  constructor() {
    this.total = 0;
    this.current = 0;
    this.lastPct = 0;
    this.bar = null;
  }
  init(context, total) {
    this.bar?.remove();
    this.context = context;
    this.total = total;
    this.current = 0;
    this.lastPct = 0;
    this.bar = ui.notifications.info(this.context, { progress: true });
    this.bar.update({ pct: 0.01 });
  }
  incrementProgress() {
    this.current += 1;
    const pct = Math.round(this.current / this.total * 100);
    if (pct !== this.lastPct) {
      debug(`${pct}% loaded...`);
      this.bar?.update({ message: this.context, pct: pct / 100 });
    }
    this.lastPct = pct;
    if (this.current >= this.total) {
      this.bar?.remove();
      this.bar = null;
    }
  }
}
const LoadingBar = new loading_bar();
const SequencerFileCache = {
  /** @type {Map<string, { blob: Blob }>} */
  _videos: /* @__PURE__ */ new Map(),
  _preloadedFiles: /* @__PURE__ */ new Set(),
  _totalCacheSize: 0,
  _validTypes: ["video/webm", "video/x-webm", "application/octet-stream", "binary/octet-stream"],
  /** @type {Map<string, PIXI.Spritesheet>} */
  _spritesheets: /* @__PURE__ */ new Map(),
  /** @type {Map<string, Promise<{spritesheet: PIXI.Spritesheet, scale: number}> | null>} */
  _generateSpritesheetJobs: /* @__PURE__ */ new Map(),
  /** @type {Promise<import("../lib/spritesheets/SpritesheetGenerator.js").SpritesheetGenerator> | null} */
  _spritesheetGenerator: null,
  /**
   *
   * @param {string} inSrc
   * @returns {Promise<Blob>} the video blob
   */
  async loadVideo(inSrc) {
    let entry = this._videos.get(inSrc);
    if (!entry) {
      const blob = await fetch(inSrc, {
        mode: "cors",
        credentials: "same-origin"
      }).then((r) => r.blob()).catch((err) => {
        console.error(err);
      });
      if (this._validTypes.indexOf(blob?.type) === -1) return false;
      while (this._totalCacheSize + blob.size > 524288e3 && this._videos.size > 0) {
        const oldSrc = this._videos.keys().next().value;
        this._totalCacheSize -= this._videos.get(oldSrc).blob.size;
        this._preloadedFiles.delete(oldSrc);
        this._videos.delete(oldSrc);
      }
      this._totalCacheSize += blob.size;
      this._preloadedFiles.add(inSrc);
      entry = { blob };
      this._videos.set(inSrc, entry);
    } else {
      this._videos.delete(inSrc);
      this._videos.set(inSrc, entry);
    }
    return entry.blob;
  },
  srcExists(inSrc) {
    if (this._preloadedFiles.has(inSrc)) {
      return true;
    }
    return foundry.utils.srcExists(inSrc);
  },
  async loadFile(inSrc, preload = false) {
    if (inSrc.toLowerCase().endsWith(".webm")) {
      let blob = await this.loadVideo(inSrc);
      if (!blob) return false;
      this._preloadedFiles.add(inSrc);
      if (preload) return true;
      return get_video_texture(blob);
    } else if (foundry.audio.AudioHelper.hasAudioExtension(inSrc)) {
      try {
        const audio2 = await foundry.audio.AudioHelper.preloadSound(inSrc);
        if (audio2) {
          this._preloadedFiles.add(inSrc);
        }
        return audio2;
      } catch (err) {
        console.error(`Failed to load audio: ${inSrc}`);
        return false;
      }
    }
    const texture = await foundry.canvas.loadTexture(inSrc);
    if (texture) {
      this._preloadedFiles.add(inSrc);
    }
    return texture;
  },
  /**
   * @param {string} inSrc
   * @param {Object} options
   * @param {number} options.minimumScale
   * @return {Promise<PIXI.Spritesheet | undefined>} the compiled spritesheet or
   * undefined if it cannot be generated
   */
  async requestCompiledSpritesheet(inSrc, { minimumScale }) {
    if (!inSrc.toLowerCase().endsWith(".webm")) {
      return;
    }
    if (this._spritesheetGenerator == null) {
      this._spritesheetGenerator = import("./SpritesheetGenerator-Dw7_9Yk1.js").then((m) => m.SpritesheetGenerator.create());
    }
    const generator = await this._spritesheetGenerator;
    if (!generator) {
      return;
    }
    let job = this._generateSpritesheetJobs.get(inSrc);
    if (!job) {
      job = new Promise(async (resolve) => {
        const timeStart = Date.now();
        debug(
          `Spritesheets | generating ${inSrc}. Required scale ${minimumScale}`
        );
        const blob = await this.loadVideo(inSrc);
        const buffer = await blob.arrayBuffer();
        let spritesheet = null;
        let scale2 = 1;
        try {
          const result2 = await generator.spritesheetFromBuffer({
            buffer,
            id: inSrc,
            minimumScale
          });
          spritesheet = result2.spritesheet;
          scale2 = result2.scale;
        } catch (error) {
          console.warn(error);
          resolve(null);
          return;
        }
        const w = spritesheet.baseTexture.width;
        const h = spritesheet.baseTexture.height;
        const megaBytes = Math.round(
          spritesheet.baseTexture.resource._levelBuffers.reduce(
            (acc, cur) => acc + cur.levelBuffer.byteLength,
            0
          ) / 1e5
        ) / 10;
        const time2 = Math.round((Date.now() - timeStart) / 100) / 10;
        debug(
          `Spritesheets | ${inSrc} generated in ${time2}s. ${w}x${h} ${megaBytes}mb`
        );
        resolve({ spritesheet, scale: scale2 });
      });
      this._generateSpritesheetJobs.set(inSrc, job);
    }
    const result = await job;
    if (result && result.scale < minimumScale) {
      debug(
        `Spritesheets | ${inSrc} minimum scale of ${minimumScale} requested but only scale ${result.scale} is available`
      );
      return void 0;
    }
    return result?.spritesheet;
  },
  registerSpritesheet(inSrc, inSpriteSheet) {
    const existingSheetRef = this._spritesheets.get(inSrc);
    if (existingSheetRef) {
      existingSheetRef[1] += 1;
    } else {
      this._spritesheets.set(inSrc, [inSpriteSheet, 1]);
    }
  },
  /**
   *
   * @param {string} inSrc
   * @returns {void}
   */
  async unloadSpritesheet(inSrc) {
    const existingSheetRef = this._spritesheets.get(inSrc);
    if (!existingSheetRef) {
      console.error("trying to unlaod spritesheet that was not loaded:", inSrc);
      return;
    }
    existingSheetRef[1] -= 1;
    if (existingSheetRef[1] > 0) {
      return;
    }
    this._generateSpritesheetJobs.delete(inSrc);
    this._spritesheets.delete(inSrc);
    const sheet = existingSheetRef[0];
    sheet.data?.meta?.related_multi_packs ?? [];
    sheet.linkedSheets;
    const cacheKeys = [
      get_sheet_image_url(inSrc, sheet),
      foundry.utils.getRoute(inSrc)
    ];
    await PIXI.Assets.unload(cacheKeys.filter((src) => !!src));
    if (sheet.textures) {
      Object.values(sheet.textures).forEach((t) => t.destroy());
    }
    sheet.baseTexture?.destroy();
  }
};
function get_sheet_image_url(inSrc, sheet) {
  const imageName = sheet?.data?.meta?.image;
  if (!imageName) {
    return;
  }
  const srcPrefix = inSrc.split("/").slice(0, -1).join("/");
  const sheetSrc = `${srcPrefix}/${imageName}`;
  return foundry.utils.getRoute(sheetSrc);
}
async function get_video_texture(inBlob) {
  return new Promise(async (resolve, reject) => {
    const video = document.createElement("video");
    video.preload = "auto";
    video.crossOrigin = "anonymous";
    video.controls = true;
    video.autoplay = false;
    video.autoload = true;
    video.muted = true;
    video.src = URL.createObjectURL(inBlob);
    let canplay = true;
    video.oncanplay = async () => {
      if (!canplay) return;
      canplay = false;
      video.height = video.videoHeight;
      video.width = video.videoWidth;
      const baseTexture = PIXI.BaseTexture.from(video, {
        resourceOptions: { autoPlay: false }
      });
      if (game.settings.get(CONSTANTS.MODULE_NAME, "enable-fix-pixi")) {
        baseTexture.alphaMode = PIXI.ALPHA_MODES.PREMULTIPLIED_ALPHA;
      }
      const texture = new PIXI.Texture(baseTexture);
      resolve(texture);
      video.oncanplay = null;
    };
    video.onerror = () => {
      URL.revokeObjectURL(video.src);
      reject();
    };
  });
}
class SequencerFileBase {
  static make(inData, inDBPath, inMetadata) {
    if (typeof inData === "string" && !inDBPath && !inMetadata) {
      return new SequencerFilePlain(inData);
    }
    const originalFile = inData?.file ?? inData;
    const file = foundry.utils.duplicate(originalFile);
    const isRangeFind = typeof file !== "string" && !Array.isArray(originalFile) ? Object.keys(originalFile).filter((key) => key.endsWith("ft")).length > 0 : false;
    return isRangeFind ? new SequencerFileRangeFind(inData, inDBPath, inMetadata) : new SequencerFile(inData, inDBPath, inMetadata);
  }
}
class SequencerFilePlain extends SequencerFileBase {
  #file;
  rangeFind = false;
  constructor(inData) {
    super();
    this.#file = inData;
  }
  getFileForDistance() {
    return this.getFile();
  }
  getFile() {
    return this.#file;
  }
  getAllFiles() {
    return [this.#file];
  }
  async validate() {
    return Promise.resolve(!!this.#file);
  }
  clone() {
    return new SequencerFilePlain(this.#file);
  }
  getTimestamps() {
    return void 0;
  }
}
class SequencerFile extends SequencerFileBase {
  rangeFind = false;
  get isFlipbook() {
    return this.originalMetadata?.flipbook;
  }
  constructor(inData, inDBPath, inMetadata) {
    super();
    inData = foundry.utils.duplicate(inData);
    inMetadata = foundry.utils.duplicate(inMetadata);
    this.originalData = inData;
    this.originalMetadata = inMetadata;
    for (let [key, value] of Object.entries(inMetadata)) {
      this[key] = value;
    }
    this.dbPath = inDBPath;
    this.moduleName = inDBPath.split(".")[0];
    this.originalFile = inData?.file ?? inData;
    this.file = foundry.utils.duplicate(this.originalFile);
    this.fileIndex = null;
    this.twister = false;
  }
  clone() {
    return SequencerFile.make(
      this.originalData,
      this.dbPath,
      this.originalMetadata
    );
  }
  async validate() {
    let isValid = true;
    const directories = {};
    const allFiles = this.getAllFiles();
    for (const file of allFiles) {
      let directory = file.split("/");
      directory.pop();
      directory = directory.join("/");
      if (directories[directory] === void 0) {
        directories[directory] = await getFiles(directory);
      }
    }
    for (const file of allFiles) {
      let directory = file.split("/");
      directory.pop();
      directory = directory.join("/");
      if (directories[directory].indexOf(file) === -1) {
        console.warn(
          `"${this.dbPath}" has an incorrect file path, could not find file. Points to:
${file}`
        );
        isValid = false;
      }
    }
    return isValid;
  }
  getAllFiles() {
    return [this.file].deepFlatten();
  }
  getFile() {
    if (Array.isArray(this.file)) {
      this.fileIndex = is_real_number(this.fileIndex) ? this.fileIndex : random_array_element(this.file, {
        twister: this.twister,
        index: true
      });
      return this.file[this.fileIndex];
    }
    return this.file;
  }
  getTimestamps() {
    if (Array.isArray(this.originalMetadata?.timestamps)) {
      return this.originalMetadata?.timestamps?.[this.fileIndex] ?? this.originalMetadata?.timestamps[0];
    }
    return this.originalMetadata?.timestamps;
  }
  getPreviewFile(entry) {
    let parts = entry.split(".");
    let files2 = this.getAllFiles();
    if (Array.isArray(files2)) {
      if (is_real_number(parts[parts.length - 1])) {
        files2 = files2[parts[parts.length - 1]];
      } else {
        const index2 = Math.floor(interpolate(0, files2.length - 1, 0.5));
        files2 = files2?.[index2 - 1] ?? files2[index2];
      }
    }
    return files2;
  }
  getFileForDistance() {
    return this.getFile();
  }
}
class SequencerFileRangeFind extends SequencerFile {
  rangeFind = true;
  constructor(...args) {
    super(...args);
    this._fileDistanceMap = false;
  }
  static get ftToDistanceMap() {
    return {
      "90ft": canvas.grid.size * 15,
      "60ft": canvas.grid.size * 9,
      "30ft": canvas.grid.size * 5,
      "15ft": canvas.grid.size * 2,
      "05ft": 0
    };
  }
  get _gridSizeDiff() {
    return canvas.grid.size / this.template[0];
  }
  getAllFiles() {
    return Object.values(this.file).deepFlatten();
  }
  getFile(inFt = "15ft") {
    if (inFt && this.file[inFt]) {
      if (Array.isArray(this.file[inFt])) {
        const fileIndex = is_real_number(this.fileIndex) ? Math.min(this.file[inFt].length - 1, this.fileIndex) : random_array_element(this.file[inFt], {
          twister: this.twister,
          index: true
        });
        return this.file[inFt][fileIndex];
      }
      return this.file[inFt];
    }
    return this.file;
  }
  getPreviewFile(entry) {
    let parts = entry.split(".");
    const ft = parts.find(
      (part) => Object.keys(SequencerFileRangeFind.ftToDistanceMap).indexOf(part) > -1
    );
    if (!ft) {
      return super.getPreviewFile(entry);
    }
    const fileIndex = parts.slice(parts.indexOf(ft) + 1)?.[0];
    if (is_real_number(Number(fileIndex))) {
      this.fileIndex = Number(fileIndex);
    }
    return this.getFile(ft);
  }
  _getMatchingDistance(inEntry) {
    return SequencerFileRangeFind.ftToDistanceMap[inEntry] / this._gridSizeDiff;
  }
  getFileForDistance(inDistance) {
    if (!this._fileDistanceMap) {
      let distances = Object.keys(this.file).filter(
        (entry) => Object.keys(SequencerFileRangeFind.ftToDistanceMap).indexOf(entry) > -1
      ).map((ft) => {
        return {
          file: this.getFile(ft),
          minDistance: this._getMatchingDistance(ft)
        };
      });
      let uniqueDistances = [
        ...new Set(distances.map((item) => item.minDistance))
      ];
      uniqueDistances.sort((a, b) => a - b);
      let max = Math.max(...uniqueDistances);
      let min = Math.min(...uniqueDistances);
      this._fileDistanceMap = distances.map((entry) => {
        entry.distances = {
          min: entry.minDistance === min ? 0 : entry.minDistance,
          max: entry.minDistance === max ? Infinity : uniqueDistances[uniqueDistances.indexOf(entry.minDistance) + 1]
        };
        return entry;
      });
    }
    const possibleFiles = this._fileDistanceMap.filter((entry) => {
      const relativeDistance = inDistance / this._gridSizeDiff;
      return relativeDistance >= entry.distances.min && relativeDistance < entry.distances.max;
    }).map((entry) => entry.file).flat();
    return possibleFiles.length > 1 ? random_array_element(possibleFiles, { twister: this.twister }) : possibleFiles[0];
  }
}
class Database {
  #entriesStore = writable({});
  privateModules = [];
  flattenedEntries = [];
  flattenedEntriesAdvanced = [];
  inverseFlattenedEntries = /* @__PURE__ */ new Map();
  get entries() {
    return get$1(this.#entriesStore);
  }
  set entries(entries) {
    this.#entriesStore.set(entries);
  }
  get entriesStore() {
    return this.#entriesStore;
  }
  get publicModules() {
    return Object.keys(this.entries).filter(
      (module) => !this.privateModules.includes(module)
    );
  }
  get publicFlattenedEntries() {
    return this.flattenedEntries.filter((entry) => {
      return this.privateModules.indexOf(entry.split(".")[0]) === -1;
    });
  }
  get publicFlattenedSimpleEntries() {
    return make_array_unique(
      this.publicFlattenedEntries.map((entry) => {
        return entry.split(CONSTANTS.FEET_REGEX)[0];
      })
    );
  }
  get publicFlattenedEntriesAdvanced() {
    return this.flattenedEntriesAdvanced.filter((entry) => {
      return this.privateModules.indexOf(entry.path.split(".")[0]) === -1;
    });
  }
  /**
   *  Retrieves an object of every public entry
   *
   * @return {object}
   */
  get filePathDatabasePaths() {
    const fileDatabaseObject = {};
    Object.entries(this.entries).map((entry) => entry[1]).deepFlatten().forEach((sequencerFile) => {
      if (sequencerFile?.rangeFind) {
        Object.entries(sequencerFile.file).forEach((entry) => {
          fileDatabaseObject[entry[1]] = sequencerFile.dbPath + "." + entry[0];
        });
      } else {
        fileDatabaseObject[sequencerFile.file] = sequencerFile.dbPath;
      }
    });
    return fileDatabaseObject;
  }
  /**
   *  Registers a set of entries to the database on the given module name
   *
   * @param  {string}      inModuleName  The namespace to assign to the inserted entries
   * @param  {object}      inEntries     The entries to merge into the database
   * @param  {boolean}     isPrivate     Whether to mark these entries as private and not show in Effect Player or Database Viewer
   * @param  {boolean}     override      Whether to ignore existing entries and completely override the module's previous entries
   * @return {boolean}
   */
  registerEntries(inModuleName, inEntries, isPrivate = false, override = false) {
    if (inModuleName.includes("."))
      return this._throwError(
        "registerEntries",
        "module name must not contain periods"
      );
    if (this.entries[inModuleName] && !override)
      custom_warning(
        "Sequencer",
        `registerEntries | module "${inModuleName}" has already been registered to the database! Do you have two similar modules active?`,
        true
      );
    this._flatten(inEntries, inModuleName);
    const processedEntries = this._processEntries(inModuleName, inEntries);
    if (override) {
      if (isPrivate) {
        this.privateModules.push(inModuleName);
      } else {
        this.privateModules = this.privateModules.filter((mod) => mod !== inModuleName);
      }
      this.entries[inModuleName] = processedEntries;
    } else {
      if (isPrivate) this.privateModules.push(inModuleName);
      this.entries = foundry.utils.mergeObject(this.entries, {
        [inModuleName]: processedEntries
      });
    }
    debug(
      `Sequencer | Database | Entries for "${inModuleName}" registered`
    );
    Hooks.callAll("registerSequencerDatabaseEntries", inModuleName);
    return true;
  }
  /**
   *  Validates the entries under a certain module name, checking whether paths to assets are correct or not
   *
   * @param  {string}     inModuleName    The namespace to assign to the inserted entries
   * @return {boolean}
   */
  async validateEntries(inModuleName) {
    let entries = this.getEntry(inModuleName);
    if (!Array.isArray(entries)) {
      entries = [entries];
    }
    ui.notifications.info(
      `Validating paths registered to "${inModuleName}"...`
    );
    let isValid = true;
    LoadingBar.init(
      `Validating paths registered to "${inModuleName}"...`,
      entries.length
    );
    for (let entry of entries) {
      const result = await entry.validate();
      LoadingBar.incrementProgress();
      isValid = !(!result || !isValid);
    }
    if (!isValid) {
      ui.notifications.error(
        `Validation of paths registered to "${inModuleName}" failed! Errors logged in console.`
      );
    } else {
      ui.notifications.info(
        `Validation of paths registered to "${inModuleName}" complete! No errors found!`
      );
    }
  }
  /**
   *  Quickly checks if the entry exists in the database
   *
   * @param  {string}      inString    The entry to find in the database
   * @return {boolean}                 If the entry exists in the database
   */
  entryExists(inString) {
    if (typeof inString !== "string")
      return this._throwError("entryExists", "inString must be of type string");
    inString = inString.trim();
    if (inString === "")
      return this._throwError("entryExists", "inString cannot be empty");
    inString = inString.replace(/\[[0-9]+]$/, "");
    const match = this.flattenedEntries.find((entry) => entry.startsWith(inString));
    if (match && match !== inString && !match.startsWith(inString + ".")) {
      custom_warning(
        "Sequencer",
        `entryExists | "${inString}" matched via partial segment prefix ("${match}"). This behavior will be removed in a future version. Use the full entry path or a dot-delimited prefix instead.`
      );
    }
    return !!match;
  }
  /**
   *  Gets the entry in the database by a dot-notated string
   *
   * @param  {string}                     inString        The entry to find in the database
   * @param  {boolean}                    softFail        Whether it should soft fail (no error) when no entry was found
   * @return {array|SequencerFile|boolean}                The found entry in the database, or false if not found (with warning)
   */
  getEntry(inString, { softFail = false } = {}) {
    if (typeof inString !== "string") {
      if (softFail) return false;
      return this._throwError("getEntry", "inString must be of type string");
    }
    inString = inString.trim();
    if (inString === "") {
      if (softFail) return false;
      return this._throwError("getEntry", "inString cannot be empty");
    }
    inString = inString.replace(/\[[0-9]+]$/, "");
    if (!this.entryExists(inString)) {
      if (softFail) return false;
      return this._throwError(
        "getEntry",
        `Could not find ${inString} in database`
      );
    }
    let ft = false;
    let index2 = false;
    if (CONSTANTS.FEET_REGEX.test(inString)) {
      ft = inString.match(CONSTANTS.FEET_REGEX)[0];
      const split = inString.split(ft).filter((str) => str !== "");
      if (split.length > 1) {
        index2 = split[1].split(".")[0];
      }
      inString = split[0];
    }
    const module = inString.split(".")[0];
    const exactEntries = this.entries[module].filter((entry) => {
      return entry.dbPath === inString;
    });
    let filteredEntries = (exactEntries.length ? exactEntries : this.entries[module].filter((entry) => {
      return entry.dbPath.startsWith(inString);
    })).map((entry) => {
      let foundFile = entry;
      if (ft) foundFile = entry.file?.[ft] ?? foundFile;
      if (index2) foundFile = foundFile?.[index2] ?? foundFile;
      return foundFile;
    });
    if (!filteredEntries.length)
      return this._throwError(
        "getEntry",
        `Could not find ${inString} in database`
      );
    const foundEntry = filteredEntries.length === 1 ? filteredEntries[0] : filteredEntries;
    if (index2 && filteredEntries.length === 1) {
      foundEntry.fileIndex = Number(index2);
    }
    return foundEntry;
  }
  /**
   *  Gets all files under a database path
   *
   * @param  {string}         inDBPath    The module to get all files from
   * @return {array|boolean}                  The found entries in the database under the module's name, or false if not found (with warning)
   */
  getAllFileEntries(inDBPath) {
    if (typeof inDBPath !== "string")
      return this._throwError(
        "getAllFileEntries",
        "inDBPath must be of type string"
      );
    inDBPath = inDBPath.trim();
    if (inDBPath === "")
      return this._throwError("getAllFileEntries", "inDBPath cannot be empty");
    if (!this.entryExists(inDBPath))
      return this._throwError(
        "getAllFileEntries",
        `Could not find ${inDBPath} in database`
      );
    const entries = this._recurseGetFilePaths(inDBPath);
    return make_array_unique(entries.flat());
  }
  /**
   *  Get all valid entries under a certain path
   *
   * @param  {string}             inPath      The database path to get entries under
   * @return {array|boolean}                  An array containing the next layer of valid paths
   */
  getPathsUnder(inPath, {
    ranges = false,
    arrays = false,
    match = false,
    fullyQualified = false
  } = {}) {
    if (typeof inPath !== "string")
      return this._throwError("getPathsUnder", "inPath must be of type string");
    inPath = inPath.trim();
    if (inPath === "")
      return this._throwError("getPathsUnder", "inPath cannot be empty");
    inPath = inPath.replace(/\[[0-9]+]$/, "");
    if (!this.entryExists(inPath))
      return this._throwError(
        "getPathsUnder",
        `Could not find ${inPath} in database`
      );
    const entries = this.flattenedEntries.filter((e) => {
      return (e.startsWith(inPath + ".") || e === inPath) && (!match || match && e.match(match));
    });
    if (entries.length === 0) return [];
    return make_array_unique(
      entries.map((e) => !arrays ? e.split(CONSTANTS.ARRAY_REGEX)[0] : e).map((e) => !ranges ? e.split(CONSTANTS.FEET_REGEX)[0] : e).map((e) => !fullyQualified ? e.split(inPath)[1] : e).map((e) => !fullyQualified ? e ? e.split(".")[1] : "" : e).filter(Boolean)
    );
  }
  /**
   *  Get all valid entries under a certain path
   *
   * @param  {string}             inPath          The database path to search for
   * @param  {boolean}            publicOnly      Whether to only search for public modules
   * @return {array|boolean}                      An array containing potential database paths
   */
  searchFor(inPath, publicOnly = true) {
    const modules = publicOnly ? this.publicModules : Object.keys(this.entries);
    const originalEntries = publicOnly ? this.publicFlattenedEntries : this.flattenedEntries;
    if ((!inPath || inPath === "") && !modules.includes(inPath)) return modules;
    if (typeof inPath !== "string") {
      return this._throwError("searchFor", "inString must be of type string");
    }
    inPath = inPath.trim();
    if (inPath === "")
      return this._throwError("searchFor", "inString cannot be empty");
    inPath = inPath.replace(/\[[0-9]+]$/, "");
    inPath = inPath.trim();
    let entries = originalEntries.filter(
      (e) => e.startsWith(inPath) && e !== inPath
    );
    if (inPath.endsWith(".")) inPath = inPath.substring(0, inPath.length - 1);
    let length = inPath.split(".").length + 1;
    let foundEntries = entries.map((e) => {
      let path = e.split(CONSTANTS.FEET_REGEX)[0];
      return path.split(".").slice(0, length).join(".");
    });
    if (foundEntries.length === 0) {
      const regexString = str_to_search_regex_str(inPath).replace(/\s+/g, "|");
      const searchParts = regexString.split("|").length;
      const regexSearch = new RegExp(regexString, "gu");
      foundEntries = originalEntries.filter((e) => {
        return e.match(regexSearch)?.length >= searchParts;
      }).map((e) => {
        return e.split(CONSTANTS.FEET_REGEX)[0];
      });
    }
    return make_array_unique(foundEntries);
  }
  /**
   * Throws an error without THROWING one. Duh.
   *
   * @param inFunctionName
   * @param inError
   * @returns {boolean}
   * @private
   */
  _throwError(inFunctionName, inError) {
    let error = `Sequencer | Database | ${inFunctionName} - ${inError}`;
    ui.notifications.error(error);
    return false;
  }
  /**
   * Gets all file paths from the entirety of
   *
   * @param inDBPath
   * @returns {Array}
   * @private
   */
  _recurseGetFilePaths(inDBPath) {
    const module = inDBPath.split(".")[0];
    return this.entries[module].filter((entry) => entry.dbPath.startsWith(inDBPath)).map((entry) => {
      return entry.getAllFiles();
    }).flat();
  }
  /**
   * Flattens a given object to just their db path and file path
   *
   * @param entries
   * @param inModule
   * @private
   */
  _flatten(entries, inModule) {
    let flattened = flatten_object(
      foundry.utils.duplicate({ [inModule]: entries })
    );
    this.flattenedEntries = make_array_unique(
      this.flattenedEntries.concat(
        Object.keys(flattened).map((file) => file.split(".file")[0])
      )
    );
    this.flattenedEntriesAdvanced = make_array_unique(
      this.flattenedEntriesAdvanced.concat(
        Object.entries(flattened).map(([path, file]) => ({
          path: path.split(".file")[0],
          file,
          isAudio: file.endsWith("ogg") || file.endsWith("mp3") || file.endsWith("wav")
        }))
      )
    );
    this.inverseFlattenedEntries = Object.keys(flattened).reduce(
      (acc, entry) => {
        return acc.set(flattened[entry], entry.split(".file")[0]);
      },
      this.inverseFlattenedEntries
    );
  }
  /**
   * Processes and recurse into a large object containing file paths at any given depth
   *
   * @param moduleName
   * @param entries
   * @returns {object}
   * @private
   */
  _processEntries(moduleName, entries) {
    const allPaths = new Set(
      this.flattenedEntries.filter((e) => e.split(".")[0] === moduleName).map((e) => e.split(CONSTANTS.FEET_REGEX)[0])
    );
    const allTemplates = foundry.utils.mergeObject(entries?._templates ?? {}, {
      default: [100, 0, 0]
    });
    if (entries?._templates) {
      delete entries?._templates;
    }
    const moduleEntries = [];
    const mediaFileExtensions = Object.keys(CONST.FILE_CATEGORIES.IMAGE).concat(Object.keys(CONST.FILE_CATEGORIES.VIDEO)).concat(Object.keys(CONST.FILE_CATEGORIES.AUDIO)).concat(["json"]);
    for (let wholeDBPath of allPaths) {
      let metadata = this._getCleanData(entries);
      let dbPath = wholeDBPath.split(".");
      dbPath.shift();
      let combinedPath = "";
      for (let part of dbPath) {
        combinedPath = combinedPath ? combinedPath + "." + part : part;
        const entry = foundry.utils.getProperty(entries, combinedPath);
        if (Array.isArray(entry) || typeof entry === "string" || entry?.file) {
          metadata = this._getCleanData(entry, { existingData: metadata });
          break;
        }
        metadata = this._getCleanData(entry, { existingData: metadata });
      }
      if (!metadata.template) metadata.template = "default";
      if (typeof metadata.template === "string") {
        metadata.template = allTemplates?.[metadata.template] ?? allTemplates?.["default"];
      }
      let data = foundry.utils.getProperty(entries, dbPath.join("."));
      if (!Array.isArray(data) && !(typeof data === "string")) {
        data = this._getCleanData(data, { metadata: false });
      }
      if (typeof data === "string") {
        const existingEntry = this.entryExists(data);
        const extension = data.split(".")[data.split(".").length - 1].toLowerCase();
        if (!existingEntry && extension && !mediaFileExtensions.includes(extension)) {
          console.warn(
            `Sequencer | Database | registerEntries - failed to register ${wholeDBPath} to ${data}!`
          );
          this.flattenedEntries.splice(
            this.flattenedEntries.indexOf(wholeDBPath),
            1
          );
          continue;
        } else if (existingEntry) {
          const sequencerFile = this.getEntry(data);
          const clone = sequencerFile.clone();
          clone.dbPath = wholeDBPath;
          clone.metadata = foundry.utils.mergeObject(
            clone.metadata ?? {},
            metadata ?? {}
          );
          moduleEntries.push(clone);
          continue;
        }
      }
      moduleEntries.push(SequencerFileBase.make(data, wholeDBPath, metadata));
    }
    return moduleEntries;
  }
  _getCleanData(data, { existingData = {}, metadata = true } = {}) {
    data = Object.entries(data).filter((entry) => {
      return metadata === entry[0].startsWith("_");
    });
    if (metadata) {
      data = data.map((entry) => [entry[0].slice(1), entry[1]]);
    }
    return foundry.utils.mergeObject(existingData, Object.fromEntries(data));
  }
}
const SequencerDatabase = new Database();
var root_1$8 = /* @__PURE__ */ from_html(`<a class="svelte-sequencer-uyryhb"><i></i></a>`);
var root_3$4 = /* @__PURE__ */ from_html(`<a class="database-entry-button svelte-sequencer-uyryhb"><i></i></a>`);
var root_2$6 = /* @__PURE__ */ from_html(`<!> <a class="database-entry-button svelte-sequencer-uyryhb"><i></i></a> <a class="database-entry-button svelte-sequencer-uyryhb"><i class="fas fa-play svelte-sequencer-uyryhb"></i></a>`, 1);
var root$l = /* @__PURE__ */ from_html(`<div class="database-entry svelte-sequencer-uyryhb"><div class="database-entry-text-container svelte-sequencer-uyryhb"><!> <!> <div class="database-entry-text svelte-sequencer-uyryhb"><div class="database-entry-text-highlight svelte-sequencer-uyryhb"></div> </div></div></div>`);
function TreeViewEntry($$anchor, $$props) {
  push($$props, false);
  const $searchRegex = () => store_get(searchRegex, "$searchRegex", $$stores);
  const [$$stores, $$cleanup] = setup_stores();
  const highlight = /* @__PURE__ */ mutable_source();
  let data = prop($$props, "data", 8);
  let localize = game.i18n.localize.bind(game.i18n);
  const searchRegex = databaseStore.searchRegex;
  let flashFilePath = /* @__PURE__ */ mutable_source(false);
  let flashDBPath = /* @__PURE__ */ mutable_source(false);
  legacy_pre_effect(() => (deep_read_state(data()), $searchRegex()), () => {
    set(highlight, data().path.replace($searchRegex(), "<mark>$&</mark>"));
  });
  legacy_pre_effect_reset();
  init();
  var div = root$l();
  var div_1 = child(div);
  var node = child(div_1);
  {
    var consequent = ($$anchor2) => {
      var a = root_1$8();
      var i = child(a);
      let classes;
      template_effect(() => classes = set_class(i, 1, "fas svelte-sequencer-uyryhb", null, classes, { "fa-angle-down": data().open, "fa-angle-right": !data().open }));
      event("click", a, (e) => {
        databaseStore.openTreePath(data().fullPath, !data().open, e.ctrlKey);
      });
      append($$anchor2, a);
    };
    if_block(node, ($$render) => {
      if (deep_read_state(data()), untrack(() => data().hasChildren)) $$render(consequent);
    });
  }
  var node_1 = sibling(node, 2);
  {
    var consequent_2 = ($$anchor2) => {
      var fragment = root_2$6();
      var node_2 = first_child(fragment);
      {
        var consequent_1 = ($$anchor3) => {
          var a_1 = root_3$4();
          var i_1 = child(a_1);
          let classes_1;
          template_effect(
            ($0) => {
              set_attribute(a_1, "title", $0);
              classes_1 = set_class(i_1, 1, "fas fa-file svelte-sequencer-uyryhb", null, classes_1, { "flash-it": get(flashFilePath) });
            },
            [
              () => untrack(() => localize("SEQUENCER.Database.CopyFilePath"))
            ]
          );
          event("click", a_1, (e) => {
            databaseStore.copyPath(data().fullPath, true, e.ctrlKey);
            set(flashFilePath, true);
            setTimeout(
              () => {
                set(flashFilePath, false);
              },
              400
            );
          });
          append($$anchor3, a_1);
        };
        if_block(node_2, ($$render) => {
          if (deep_read_state(data()), untrack(() => !data().hasChildren)) $$render(consequent_1);
        });
      }
      var a_2 = sibling(node_2, 2);
      var i_2 = child(a_2);
      let classes_2;
      var a_3 = sibling(a_2, 2);
      template_effect(
        ($0, $1) => {
          set_attribute(a_2, "title", $0);
          classes_2 = set_class(i_2, 1, "fas fa-database svelte-sequencer-uyryhb", null, classes_2, { "flash-it": get(flashDBPath) });
          set_attribute(a_3, "title", $1);
        },
        [
          () => untrack(() => localize("SEQUENCER.Database.CopyDatabasePath")),
          () => untrack(() => localize("SEQUENCER.Database.PlayPreview"))
        ]
      );
      event("click", a_2, (e) => {
        databaseStore.copyPath(data().fullPath, false, e.ctrlKey);
        set(flashDBPath, true);
        setTimeout(
          () => {
            set(flashDBPath, false);
          },
          400
        );
      });
      event("click", a_3, () => {
        databaseStore.playFile(data().fullPath);
      });
      append($$anchor2, fragment);
    };
    var d = /* @__PURE__ */ user_derived(() => (deep_read_state(data()), untrack(() => data().fullPath.includes("."))));
    if_block(node_1, ($$render) => {
      if (get(d)) $$render(consequent_2);
    });
  }
  var div_2 = sibling(node_1, 2);
  var div_3 = child(div_2);
  html$1(div_3, () => get(highlight), true);
  var text2 = sibling(div_3);
  template_effect(() => {
    set_style(div, `margin-left:${(deep_read_state(data()), untrack(() => data().depth * 15)) ?? ""}px;`);
    set_attribute(div_2, "title", (deep_read_state(data()), untrack(() => data().path)));
    set_text(text2, ` ${(deep_read_state(data()), untrack(() => data().path)) ?? ""}`);
  });
  append($$anchor, div);
  pop();
  $$cleanup();
}
var root$k = /* @__PURE__ */ from_html(`<div style="margin-bottom: 0.3rem;"></div>`);
function TreeViewSeparator($$anchor, $$props) {
  prop($$props, "data", 8);
  var div = root$k();
  append($$anchor, div);
}
const viewSize = 320;
let animatedSprite;
let offscreenApp;
let canvas$1;
let offscreenCanvas;
let oldImageSrcs = /* @__PURE__ */ new Map();
function initOffscreenApp(databaseStore2) {
  if (offscreenApp) {
    return;
  }
  canvas$1 = document.createElement("canvas");
  canvas$1.style.display = "none";
  offscreenCanvas = canvas$1.transferControlToOffscreen();
  offscreenApp = new PIXI.Application({ autoStart: false, view: offscreenCanvas, backgroundColor: 10066329, sharedTicker: false });
  offscreenApp.ticker.stop();
  document.body.appendChild(canvas$1);
  offscreenCanvas.style = {};
  offscreenCanvas.width = viewSize;
  offscreenCanvas.height = viewSize;
}
async function playSpritesheet(file, databaseStore2) {
  initOffscreenApp();
  const sheet = await SequencerFileCache.loadFile(file);
  if (!sheet) {
    return;
  }
  SequencerFileCache.registerSpritesheet(file, sheet);
  const frames = Object.values(sheet.animations)?.[0];
  if (!frames) {
    return;
  }
  const framerate = sheet.data?.meta?.framerate ?? 30;
  const frametime = 1 / framerate * 1e3;
  databaseStore2.metadata.set({
    type: "Spritesheet",
    duration: Math.round(frametime * frames.length) + "ms"
  });
  if (!animatedSprite) {
    animatedSprite = new PIXI.AnimatedSprite(frames, true);
    animatedSprite.anchor.set(0.5);
    animatedSprite.x = viewSize / 2;
    animatedSprite.y = viewSize / 2;
    offscreenApp.stage.addChild(animatedSprite);
  }
  let queuedFrame;
  let renderedFrame;
  animatedSprite.onFrameChange = async (frame) => {
    if (queuedFrame !== renderedFrame) {
      return;
    }
    let curerntImageSrc = oldImageSrcs.get(frame);
    if (!curerntImageSrc) {
      queuedFrame = frame;
      offscreenApp.render();
      const imageBlob = await offscreenCanvas.convertToBlob();
      curerntImageSrc = URL.createObjectURL(imageBlob);
      oldImageSrcs.set(frame, curerntImageSrc);
      renderedFrame = frame;
    }
    databaseStore2.elements.image.src = curerntImageSrc;
  };
  animatedSprite.textures = frames;
  animatedSprite.scale.set(viewSize / Math.max(frames[0].width, frames[0].height));
  animatedSprite.file = file;
  animatedSprite.play();
}
async function cleanupSpritesheet(destroy = false) {
  if (!animatedSprite) {
    return;
  }
  animatedSprite.stop();
  animatedSprite.onFrameChange = null;
  if (animatedSprite.file) {
    await SequencerFileCache.unloadSpritesheet(animatedSprite.file);
    animatedSprite.file = null;
  }
  for (const objectUrl of oldImageSrcs.values()) {
    URL.revokeObjectURL(objectUrl);
  }
  oldImageSrcs.clear();
  if (destroy) {
    animatedSprite.destroy();
    animatedSprite = null;
    offscreenApp.destroy();
    canvas$1.remove();
    offscreenCanvas = null;
    offscreenApp = void 0;
  }
}
let lastFile = false;
function getFileData(entryText) {
  let entry = SequencerDatabase.getEntry(entryText);
  if (Array.isArray(entry)) {
    if (entry.includes(lastFile) && entry.length > 1) {
      entry.splice(entry.indexOf(lastFile), 1);
    }
    entry = random_array_element(entry);
    lastFile = entry;
  }
  let previewFile = entry?.file ?? entry;
  if (entry instanceof SequencerFileBase) {
    previewFile = entry.clone().getPreviewFile(entryText);
  }
  let lowerCaseEntry = previewFile ? previewFile.toLowerCase() : "unknown.jpg";
  const isAudio = lowerCaseEntry.endsWith("ogg") || lowerCaseEntry.endsWith("mp3") || lowerCaseEntry.endsWith("wav");
  const isSpritesheet = lowerCaseEntry.endsWith("json");
  const isImage = !lowerCaseEntry.endsWith("webm") && !isAudio && !isSpritesheet;
  const isVideo = !isAudio && !isImage && !isSpritesheet;
  const icon = previewFile ? isVideo || isSpritesheet ? "fa-film" : isAudio ? "fa-volume-high" : "fa-image" : "fa-question-mark";
  const title = previewFile ? isVideo ? "Animated WebM" : isSpritesheet ? "Spritesheet" : isAudio ? "Audio" : "Image" : "Unknown";
  return {
    file: previewFile ?? "unknown.jpg",
    dbEntry: entry,
    icon,
    title,
    isAudio,
    isImage,
    isSpritesheet,
    isVideo
  };
}
function copyPath(dbPath, getFilepath, quotes = false) {
  const tempInput = document.createElement("input");
  tempInput.value = `${dbPath}`;
  let entry;
  if (getFilepath) {
    entry = Sequencer.Database.getEntry(dbPath);
    if (Array.isArray(entry)) {
      entry = random_array_element(entry);
    }
    if (entry instanceof SequencerFileBase) {
      const specificFt = dbPath.match(CONSTANTS.FEET_REGEX);
      if (specificFt) {
        const ft = specificFt[0].replaceAll(".", "");
        entry = entry.getFile(ft);
      } else {
        const files2 = entry.getAllFiles();
        if (Array.isArray(files2)) {
          const index2 = Math.floor(interpolate(0, files2.length - 1, 0.5));
          entry = files2[index2];
        }
      }
    }
    tempInput.value = `${entry?.file ?? entry}`;
  }
  if (quotes) {
    tempInput.value = `"${tempInput.value}"`;
  }
  document.body.appendChild(tempInput);
  tempInput.select();
  document.execCommand("copy");
  document.body.removeChild(tempInput);
  document.execCommand("copy");
  ui.notifications.info(getFilepath ? "Copied filepath to clipboard!" : "Copied database path to clipboard!");
}
async function playFile(entry) {
  await cleanupSpritesheet();
  const { file, isAudio, isImage, isVideo, isSpritesheet } = getFileData(entry);
  databaseStore.elements.audio.classList.toggle("hidden", !isAudio);
  databaseStore.elements.image.classList.toggle("hidden", !isImage && !isSpritesheet);
  databaseStore.elements.player.classList.toggle("hidden", !isVideo);
  if (isImage) {
    databaseStore.elements.image.src = file;
    databaseStore.metadata.set({
      type: "Image",
      duration: "n/a"
    });
    return;
  }
  if (isSpritesheet) {
    playSpritesheet(file, databaseStore);
    return;
  }
  const element = isAudio ? databaseStore.elements.audio : databaseStore.elements.player;
  element.onerror = () => {
    const error = `Sequencer Database Viewer | Could not play file: ${file}`;
    ui.notifications.error(error);
    console.error(error);
  };
  element.oncanplay = () => {
    element.play();
  };
  element.onloadedmetadata = () => {
    databaseStore.metadata.set({
      type: isVideo ? "Video" : isAudio ? "Audio" : "Image",
      duration: isImage ? "n/a" : element.duration * 1e3 + "ms"
    });
  };
  element.src = file;
}
const treeStore = writable({});
const visibleTreeStore = writable([]);
let flattenedEntries = [];
const entriesStore = SequencerDatabase.entriesStore;
const packStore = writable(SequencerDatabase.publicModules);
const selectedPackStore = writable("all");
const searchStore = writable("");
const cleanSearchStore = writable("");
const searchRegexStore = writable(new RegExp("", "gu"));
SequencerDatabase.entriesStore.subscribe(() => {
  packStore.set(SequencerDatabase.publicModules);
});
const databaseStore = {
  metadata: writable(false),
  allRanges: writable(false),
  fileTypes: writable("all"),
  subLists: writable(false),
  listView: writable(false),
  packStore,
  selectedPackStore,
  visibleTreeStore,
  search: searchStore,
  cleanSearchStore,
  searchRegex: searchRegexStore,
  elements: {},
  cleanupSpritesheet() {
    cleanupSpritesheet(true);
  },
  copyPath,
  playFile,
  openTreePath
};
entriesStore.subscribe(() => {
  filterFlattenedEntries();
});
databaseStore.allRanges.subscribe(() => {
  filterFlattenedEntries();
});
databaseStore.subLists.subscribe(() => {
  filterFlattenedEntries();
});
databaseStore.selectedPackStore.subscribe(() => {
  filterFlattenedEntries();
});
searchStore.subscribe((val) => {
  const cleanSearch = str_to_search_regex_str(val).replace(/\s+/g, "|");
  cleanSearchStore.set(cleanSearch);
  searchRegexStore.set(new RegExp(cleanSearch, "gu"));
  updateVisualTree();
});
function filterFlattenedEntries() {
  const selectedPack = get$1(selectedPackStore);
  const search = get$1(searchStore);
  const searchRegex = get$1(searchRegexStore);
  const subLists = get$1(databaseStore.subLists);
  const allRanges = get$1(databaseStore.allRanges);
  flattenedEntries = make_array_unique(
    SequencerDatabase.publicFlattenedEntries.filter((e) => {
      return (selectedPack === "all" || e.startsWith(selectedPack + ".")) && (!search || e.match(searchRegex));
    }).map((e) => !subLists ? e.split(CONSTANTS.ARRAY_REGEX)[0] : e).map((e) => !allRanges ? e.split(CONSTANTS.FEET_REGEX)[0] : e)
  );
  treeStore.set(
    flattenedEntries.reduce((acc, entry) => {
      let path = "";
      for (const part of entry.split(".")) {
        const fullPath = path ? path + "." + part : part;
        path = path ? path + ".children." + part : part;
        if (!foundry.utils.getProperty(acc, path)) {
          foundry.utils.setProperty(
            acc,
            path,
            foundry.utils.mergeObject(
              {
                path: part,
                fullPath,
                open: false,
                children: {}
              },
              foundry.utils.getProperty(acc, path)
            )
          );
        }
      }
      return acc;
    }, {})
  );
}
function openTreePath(fullPath, open, openAll = false) {
  treeStore.update((tree) => {
    const fullTreePath = fullPath.split(".").join(".children.");
    const node = foundry.utils.getProperty(tree, fullTreePath);
    foundry.utils.setProperty(tree, fullTreePath + ".open", open);
    if ((!open || openAll) && !foundry.utils.isEmpty(node.children)) {
      recurseOpenTree(node.children, open);
    }
    return tree;
  });
}
function recurseOpenTree(children, open) {
  for (const node of Object.values(children)) {
    node.open = open;
    if (!foundry.utils.isEmpty(node.children)) {
      recurseOpenTree(node.children, open);
    }
  }
}
treeStore.subscribe(() => {
  updateVisualTree();
});
function updateVisualTree() {
  const tree = get$1(treeStore);
  const visibleTree = recurseTree(tree).deepFlatten().filter((e) => e.visible);
  visibleTreeStore.set(visibleTree);
}
function recurseTree(tree, path = "", depth = 0) {
  const search = get$1(searchStore);
  const searchRegex = get$1(searchRegexStore);
  const searchParts = get$1(cleanSearchStore).split("|");
  return Object.entries(tree).map(([key, data]) => {
    const fullPath = path ? path + "." + key : key;
    const children = recurseTree(
      data.children,
      fullPath,
      depth + 1
    ).deepFlatten();
    const matchParts = make_array_unique(fullPath.match(searchRegex) || []);
    const open = data.open || search && (matchParts.length >= searchParts.length || children.filter((e) => e.visible).length);
    let visible = !search || matchParts.length >= searchParts.length;
    if (visible) {
      children.forEach((e) => e.visible = true);
    } else {
      visible = children.filter((e) => e.visible).length;
    }
    const entry = {
      class: TreeViewEntry,
      path: key,
      fullPath,
      open,
      visible,
      hasChildren: !foundry.utils.isEmpty(data.children),
      depth
    };
    const leaf = [entry];
    if ((data.open || entry.open) && entry.hasChildren) {
      leaf.push(...children, {
        fullPath: foundry.utils.randomID(),
        class: TreeViewSeparator
      });
    }
    return leaf;
  });
}
var root$j = /* @__PURE__ */ from_html(`<div class="database-entry svelte-sequencer-1yjg93f"><button type="button" class="btn_play svelte-sequencer-1yjg93f"><i class="fas fa-play svelte-sequencer-1yjg93f"></i></button> <button type="button" class="btn_copy_filepath svelte-sequencer-1yjg93f"><i></i></button> <button type="button" class="btn_copy_databasepath svelte-sequencer-1yjg93f"><i></i></button> <div class="database-entry-text svelte-sequencer-1yjg93f"><div class="database-entry-text-highlight svelte-sequencer-1yjg93f"></div> </div></div>`);
function DatabaseEntry($$anchor, $$props) {
  push($$props, false);
  const $searchRegex = () => store_get(searchRegex, "$searchRegex", $$stores);
  const [$$stores, $$cleanup] = setup_stores();
  const highlight = /* @__PURE__ */ mutable_source();
  let localize = game.i18n.localize.bind(game.i18n);
  createEventDispatcher();
  let entry = prop($$props, "entry", 8);
  const searchRegex = databaseStore.searchRegex;
  let flashFilePath = /* @__PURE__ */ mutable_source(false);
  let flashDBPath = /* @__PURE__ */ mutable_source(false);
  legacy_pre_effect(() => (deep_read_state(entry()), $searchRegex()), () => {
    set(highlight, entry().replace($searchRegex(), "<mark>$&</mark>"));
  });
  legacy_pre_effect_reset();
  init();
  var div = root$j();
  var button = child(div);
  var button_1 = sibling(button, 2);
  var i = child(button_1);
  let classes;
  var button_2 = sibling(button_1, 2);
  var i_1 = child(button_2);
  let classes_1;
  var div_1 = sibling(button_2, 2);
  var div_2 = child(div_1);
  html$1(div_2, () => get(highlight), true);
  var text2 = sibling(div_2);
  template_effect(
    ($0, $1, $2) => {
      set_attribute(div, "data-id", entry());
      set_attribute(button, "title", $0);
      set_attribute(button_1, "title", $1);
      classes = set_class(i, 1, "fas fa-file svelte-sequencer-1yjg93f", null, classes, { "flash-it": get(flashFilePath) });
      set_attribute(button_2, "title", $2);
      classes_1 = set_class(i_1, 1, "fas fa-database svelte-sequencer-1yjg93f", null, classes_1, { "flash-it": get(flashDBPath) });
      set_attribute(div_1, "title", entry());
      set_text(text2, ` ${entry() ?? ""}`);
    },
    [
      () => untrack(() => localize("SEQUENCER.Database.PlayPreview")),
      () => untrack(() => localize("SEQUENCER.Database.CopyFilePath")),
      () => untrack(() => localize("SEQUENCER.Database.CopyDatabasePath"))
    ]
  );
  event("click", button, () => {
    databaseStore.playFile(entry());
  });
  event("click", button_1, (e) => {
    databaseStore.copyPath(entry(), true, e.ctrlKey);
    set(flashFilePath, true);
    setTimeout(
      () => {
        set(flashFilePath, false);
      },
      400
    );
  });
  event("click", button_2, (e) => {
    databaseStore.copyPath(entry(), false, e.ctrlKey);
    set(flashDBPath, true);
    setTimeout(
      () => {
        set(flashDBPath, false);
      },
      400
    );
  });
  append($$anchor, div);
  pop();
  $$cleanup();
}
const DIRECTION_TYPE = {
  FRONT: "FRONT",
  // scroll up or left
  BEHIND: "BEHIND"
  // scroll down or right
};
const CALC_TYPE = {
  INIT: "INIT",
  FIXED: "FIXED",
  DYNAMIC: "DYNAMIC"
};
const LEADING_BUFFER = 2;
class Virtual {
  param;
  callUpdate;
  firstRangeTotalSize = 0;
  firstRangeAverageSize = 0;
  lastCalcIndex = 0;
  fixedSizeValue = 0;
  calcType = CALC_TYPE.INIT;
  offset = 0;
  direction = "";
  range;
  constructor(param, callUpdate) {
    this.init(param, callUpdate);
  }
  init(param, callUpdate) {
    this.param = param;
    this.callUpdate = callUpdate;
    this.sizes = /* @__PURE__ */ new Map();
    this.firstRangeTotalSize = 0;
    this.firstRangeAverageSize = 0;
    this.fixedSizeValue = 0;
    this.calcType = CALC_TYPE.INIT;
    this.offset = 0;
    this.direction = "";
    this.range = /* @__PURE__ */ Object.create(null);
    if (param) {
      this.checkRange(0, param.keeps - 1);
    }
  }
  destroy() {
    this.init(null, null);
  }
  // return current render range
  getRange() {
    const range = /* @__PURE__ */ Object.create(null);
    range.start = this.range.start;
    range.end = this.range.end;
    range.padFront = this.range.padFront;
    range.padBehind = this.range.padBehind;
    return range;
  }
  isBehind() {
    return this.direction === DIRECTION_TYPE.BEHIND;
  }
  isFront() {
    return this.direction === DIRECTION_TYPE.FRONT;
  }
  // return start index offset
  getOffset(start) {
    return (start < 1 ? 0 : this.getIndexOffset(start)) + this.param.slotHeaderSize;
  }
  updateParam(key, value) {
    if (this.param && key in this.param) {
      if (key === "uniqueIds") {
        this.sizes.forEach((v, key2) => {
          if (!value.includes(key2)) {
            this.sizes.delete(key2);
          }
        });
      }
      this.param[key] = value;
    }
  }
  // save each size map by id
  saveSize(id, size) {
    this.sizes.set(id, size);
    if (this.calcType === CALC_TYPE.INIT) {
      this.fixedSizeValue = size;
      this.calcType = CALC_TYPE.FIXED;
    } else if (this.calcType === CALC_TYPE.FIXED && this.fixedSizeValue !== size) {
      this.calcType = CALC_TYPE.DYNAMIC;
      delete this.fixedSizeValue;
    }
    if (this.calcType !== CALC_TYPE.FIXED && typeof this.firstRangeTotalSize !== "undefined") {
      if (this.sizes.size < Math.min(this.param.keeps, this.param.uniqueIds.length)) {
        this.firstRangeTotalSize = [...this.sizes.values()].reduce((acc, val) => acc + val, 0);
        this.firstRangeAverageSize = Math.round(this.firstRangeTotalSize / this.sizes.size);
      } else {
        delete this.firstRangeTotalSize;
      }
    }
  }
  // in some special situation (e.g. length change) we need to update in a row
  // try going to render next range by a leading buffer according to current direction
  handleDataSourcesChange() {
    let start = this.range.start;
    if (this.isFront()) {
      start = start - LEADING_BUFFER;
    } else if (this.isBehind()) {
      start = start + LEADING_BUFFER;
    }
    start = Math.max(start, 0);
    this.updateRange(this.range.start, this.getEndByStart(start));
  }
  // when slot size change, we also need force update
  handleSlotSizeChange() {
    this.handleDataSourcesChange();
  }
  // calculating range on scroll
  handleScroll(offset2) {
    this.direction = offset2 < this.offset ? DIRECTION_TYPE.FRONT : DIRECTION_TYPE.BEHIND;
    this.offset = offset2;
    if (!this.param) {
      return;
    }
    if (this.direction === DIRECTION_TYPE.FRONT) {
      this.handleFront();
    } else if (this.direction === DIRECTION_TYPE.BEHIND) {
      this.handleBehind();
    }
  }
  // ----------- public method end -----------
  handleFront() {
    const overs = this.getScrollOvers();
    if (overs > this.range.start) {
      return;
    }
    const start = Math.max(overs - this.param.buffer, 0);
    this.checkRange(start, this.getEndByStart(start));
  }
  handleBehind() {
    const overs = this.getScrollOvers();
    if (overs < this.range.start + this.param.buffer) {
      return;
    }
    this.checkRange(overs, this.getEndByStart(overs));
  }
  // return the pass overs according to current scroll offset
  getScrollOvers() {
    const offset2 = this.offset - this.param.slotHeaderSize;
    if (offset2 <= 0) {
      return 0;
    }
    if (this.isFixedType()) {
      return Math.floor(offset2 / this.fixedSizeValue);
    }
    let low = 0;
    let middle = 0;
    let middleOffset = 0;
    let high = this.param.uniqueIds.length;
    while (low <= high) {
      middle = low + Math.floor((high - low) / 2);
      middleOffset = this.getIndexOffset(middle);
      if (middleOffset === offset2) {
        return middle;
      } else if (middleOffset < offset2) {
        low = middle + 1;
      } else if (middleOffset > offset2) {
        high = middle - 1;
      }
    }
    return low > 0 ? --low : 0;
  }
  // return a scroll offset from given index, can efficiency be improved more here?
  // although the call frequency is very high, its only a superposition of numbers
  getIndexOffset(givenIndex) {
    if (!givenIndex) {
      return 0;
    }
    let offset2 = 0;
    let indexSize = 0;
    for (let index2 = 0; index2 < givenIndex; index2++) {
      indexSize = this.sizes.get(this.param.uniqueIds[index2]);
      offset2 = offset2 + (typeof indexSize === "number" ? indexSize : this.getEstimateSize());
    }
    this.lastCalcIndex = Math.max(this.lastCalcIndex, givenIndex - 1);
    this.lastCalcIndex = Math.min(this.lastCalcIndex, this.getLastIndex());
    return offset2;
  }
  // is fixed size type
  isFixedType() {
    return this.calcType === CALC_TYPE.FIXED;
  }
  // return the real last index
  getLastIndex() {
    return this.param.uniqueIds.length - 1;
  }
  // in some conditions range is broke, we need correct it
  // and then decide whether need update to next range
  checkRange(start, end) {
    const keeps = this.param.keeps;
    const total = this.param.uniqueIds.length;
    if (total <= keeps) {
      start = 0;
      end = this.getLastIndex();
    } else if (end - start < keeps - 1) {
      start = end - keeps + 1;
    }
    if (this.range.start !== start) {
      this.updateRange(start, end);
    }
  }
  // setting to a new range and rerender
  updateRange(start, end) {
    this.range.start = start;
    this.range.end = end;
    this.range.padFront = this.getPadFront();
    this.range.padBehind = this.getPadBehind();
    this.callUpdate(this.getRange());
  }
  // return end base on start
  getEndByStart(start) {
    const theoryEnd = start + this.param.keeps - 1;
    const truelyEnd = Math.min(theoryEnd, this.getLastIndex());
    return truelyEnd;
  }
  // return total front offset
  getPadFront() {
    if (this.isFixedType()) {
      return this.fixedSizeValue * this.range.start;
    } else {
      return this.getIndexOffset(this.range.start);
    }
  }
  // return total behind offset
  getPadBehind() {
    const end = this.range.end;
    const lastIndex = this.getLastIndex();
    if (this.isFixedType()) {
      return (lastIndex - end) * this.fixedSizeValue;
    }
    if (this.lastCalcIndex === lastIndex) {
      return this.getIndexOffset(lastIndex) - this.getIndexOffset(end);
    } else {
      return (lastIndex - end) * this.getEstimateSize();
    }
  }
  // get the item estimate size
  getEstimateSize() {
    return this.isFixedType() ? this.fixedSizeValue : this.firstRangeAverageSize || this.param.estimateSize;
  }
}
function isBrowser() {
  return typeof document !== "undefined";
}
var root$i = /* @__PURE__ */ from_html(`<div class="virtual-scroll-item"><!></div>`);
function Item($$anchor, $$props) {
  push($$props, false);
  let horizontal = prop($$props, "horizontal", 8, false);
  let uniqueKey = prop($$props, "uniqueKey", 8);
  let type = prop($$props, "type", 8, "item");
  let resizeObserver;
  let itemDiv = /* @__PURE__ */ mutable_source();
  let previousSize;
  const dispatch = createEventDispatcher();
  const shapeKey = horizontal() ? "offsetWidth" : "offsetHeight";
  onMount(() => {
    if (typeof ResizeObserver !== "undefined") {
      resizeObserver = new ResizeObserver(dispatchSizeChange);
      resizeObserver.observe(get(itemDiv));
    }
  });
  afterUpdate(dispatchSizeChange);
  onDestroy(() => {
    if (resizeObserver) {
      resizeObserver.disconnect();
      resizeObserver = null;
    }
  });
  function dispatchSizeChange() {
    const size = get(itemDiv) ? get(itemDiv)[shapeKey] : 0;
    if (size === previousSize) return;
    previousSize = size;
    dispatch("resize", { id: uniqueKey(), size, type: type() });
  }
  init();
  var div = root$i();
  var node = child(div);
  slot(node, $$props, "default", {});
  bind_this(div, ($$value) => set(itemDiv, $$value), () => get(itemDiv));
  append($$anchor, div);
  pop();
}
var root_1$7 = /* @__PURE__ */ from_html(`<div style="overflow-y: auto; height: inherit" class="virtual-scroll-root"><!> <div class="virtual-scroll-wrapper"></div> <!> <div class="shepherd"></div></div>`);
function VirtualScroll($$anchor, $$props) {
  const $$slots = sanitize_slots($$props);
  push($$props, false);
  let key = prop($$props, "key", 8, "id");
  let data = prop($$props, "data", 8);
  let keeps = prop($$props, "keeps", 8, 30);
  let estimateSize = prop($$props, "estimateSize", 8, 50);
  let isHorizontal = prop($$props, "isHorizontal", 8, false);
  let start = prop($$props, "start", 8, 0);
  let offset2 = prop($$props, "offset", 8, 0);
  let pageMode = prop($$props, "pageMode", 8, false);
  let topThreshold = prop($$props, "topThreshold", 8, 0);
  let bottomThreshold = prop($$props, "bottomThreshold", 8, 0);
  let displayItems = /* @__PURE__ */ mutable_source([]);
  let paddingStyle = /* @__PURE__ */ mutable_source();
  let directionKey = isHorizontal() ? "scrollLeft" : "scrollTop";
  let range = null;
  let virtual = new Virtual(
    {
      slotHeaderSize: 0,
      slotFooterSize: 0,
      keeps: keeps(),
      estimateSize: estimateSize(),
      buffer: Math.round(keeps() / 3),
      // recommend for a third of keeps
      uniqueIds: getUniqueIdFromDataSources()
    },
    onRangeChanged
  );
  let root2 = /* @__PURE__ */ mutable_source();
  let shepherd = /* @__PURE__ */ mutable_source();
  const dispatch = createEventDispatcher();
  function getSize(id) {
    return virtual.sizes.get(id);
  }
  function getSizes() {
    return virtual.sizes.size;
  }
  function getOffset() {
    if (pageMode() && isBrowser()) {
      return document.documentElement[directionKey] || document.body[directionKey];
    } else {
      return get(root2) ? Math.ceil(get(root2)[directionKey]) : 0;
    }
  }
  function getClientSize() {
    const key2 = isHorizontal() ? "clientWidth" : "clientHeight";
    if (pageMode() && isBrowser()) {
      return document.documentElement[key2] || document.body[key2];
    } else {
      return get(root2) ? Math.ceil(get(root2)[key2]) : 0;
    }
  }
  function getScrollSize() {
    const key2 = isHorizontal() ? "scrollWidth" : "scrollHeight";
    if (pageMode() && isBrowser()) {
      return document.documentElement[key2] || document.body[key2];
    } else {
      return get(root2) ? Math.ceil(get(root2)[key2]) : 0;
    }
  }
  function updatePageModeFront() {
    if (get(root2) && isBrowser()) {
      const rect = get(root2).getBoundingClientRect();
      const { defaultView } = get(root2).ownerDocument;
      const offsetFront = isHorizontal() ? rect.left + defaultView.pageXOffset : rect.top + defaultView.pageYOffset;
      virtual.updateParam("slotHeaderSize", offsetFront);
    }
  }
  function scrollToOffset(offset3) {
    if (!isBrowser()) return;
    if (pageMode()) {
      document.body[directionKey] = offset3;
      document.documentElement[directionKey] = offset3;
    } else if (get(root2)) {
      mutate(root2, get(root2)[directionKey] = offset3);
    }
  }
  function scrollToIndex(index2) {
    if (index2 >= data().length - 1) {
      scrollToBottom();
    } else {
      const offset3 = virtual.getOffset(index2);
      scrollToOffset(offset3);
    }
  }
  function scrollToBottom() {
    if (get(shepherd)) {
      const offset3 = get(shepherd)[isHorizontal() ? "offsetLeft" : "offsetTop"];
      scrollToOffset(offset3);
      setTimeout(
        () => {
          if (getOffset() + getClientSize() + 1 < getScrollSize()) {
            scrollToBottom();
          }
        },
        3
      );
    }
  }
  onMount(() => {
    if (start()) {
      scrollToIndex(start());
    } else if (offset2()) {
      scrollToOffset(offset2());
    }
    if (pageMode()) {
      updatePageModeFront();
      document.addEventListener("scroll", onScroll, { passive: false });
    }
  });
  onDestroy(() => {
    virtual.destroy();
    if (pageMode() && isBrowser()) {
      document.removeEventListener("scroll", onScroll);
    }
  });
  function getUniqueIdFromDataSources() {
    return data().map((dataSource) => dataSource[key()]);
  }
  function onItemResized(event2) {
    const { id, size, type } = event2.detail;
    if (type === "item") virtual.saveSize(id, size);
    else if (type === "slot") {
      if (id === "header") virtual.updateParam("slotHeaderSize", size);
      else if (id === "footer") virtual.updateParam("slotFooterSize", size);
    }
  }
  function onRangeChanged(range_) {
    range = range_;
    set(paddingStyle, set(paddingStyle, isHorizontal() ? `0px ${range.padBehind}px 0px ${range.padFront}px` : `${range.padFront}px 0px ${range.padBehind}px`));
    set(displayItems, data().slice(range.start, range.end + 1));
  }
  function onScroll(event2) {
    const offset3 = getOffset();
    const clientSize = getClientSize();
    const scrollSize = getScrollSize();
    if (offset3 < 0 || offset3 + clientSize > scrollSize || !scrollSize) {
      return;
    }
    virtual.handleScroll(offset3);
    emitEvent(offset3, clientSize, scrollSize, event2);
  }
  function emitEvent(offset3, clientSize, scrollSize, event2) {
    dispatch("scroll", { event: event2, range: virtual.getRange() });
    if (virtual.isFront() && !!data().length && offset3 - topThreshold() <= 0) {
      dispatch("top");
    } else if (virtual.isBehind() && offset3 + clientSize + bottomThreshold() >= scrollSize) {
      dispatch("bottom");
    }
  }
  function handleKeepsChange(keeps2) {
    virtual.updateParam("keeps", keeps2);
    virtual.handleSlotSizeChange();
  }
  async function handleDataSourcesChange(data2) {
    virtual.updateParam("uniqueIds", getUniqueIdFromDataSources());
    virtual.handleDataSourcesChange();
  }
  legacy_pre_effect(() => deep_read_state(offset2()), () => {
    scrollToOffset(offset2());
  });
  legacy_pre_effect(() => deep_read_state(start()), () => {
    scrollToIndex(start());
  });
  legacy_pre_effect(() => deep_read_state(keeps()), () => {
    handleKeepsChange(keeps());
  });
  legacy_pre_effect(() => deep_read_state(data()), () => {
    handleDataSourcesChange(data());
  });
  legacy_pre_effect_reset();
  var $$exports = {
    getSize,
    getSizes,
    getOffset,
    getClientSize,
    getScrollSize,
    updatePageModeFront,
    scrollToOffset,
    scrollToIndex,
    scrollToBottom
  };
  init();
  var div = root_1$7();
  var node = child(div);
  {
    var consequent = ($$anchor2) => {
      Item($$anchor2, {
        type: "slot",
        uniqueKey: "header",
        $$events: { resize: onItemResized },
        children: ($$anchor3, $$slotProps) => {
          var fragment_1 = comment();
          var node_1 = first_child(fragment_1);
          slot(node_1, $$props, "header", {});
          append($$anchor3, fragment_1);
        },
        $$slots: { default: true }
      });
    };
    if_block(node, ($$render) => {
      if (untrack(() => $$slots.header)) $$render(consequent);
    });
  }
  var div_1 = sibling(node, 2);
  each(div_1, 7, () => get(displayItems), (dataItem) => dataItem[key()], ($$anchor2, dataItem, dataIndex) => {
    Item($$anchor2, {
      get uniqueKey() {
        return get(dataItem), deep_read_state(key()), untrack(() => get(dataItem)[key()]);
      },
      get horizontal() {
        return isHorizontal();
      },
      type: "item",
      $$events: { resize: onItemResized },
      children: ($$anchor3, $$slotProps) => {
        var fragment_3 = comment();
        var node_2 = first_child(fragment_3);
        slot(
          node_2,
          $$props,
          "default",
          {
            get data() {
              return get(dataItem);
            },
            get index() {
              return get(dataIndex);
            }
          }
        );
        append($$anchor3, fragment_3);
      },
      $$slots: { default: true }
    });
  });
  var node_3 = sibling(div_1, 2);
  {
    var consequent_1 = ($$anchor2) => {
      Item($$anchor2, {
        type: "slot",
        uniqueKey: "footer",
        $$events: { resize: onItemResized },
        children: ($$anchor3, $$slotProps) => {
          var fragment_5 = comment();
          var node_4 = first_child(fragment_5);
          slot(node_4, $$props, "footer", {});
          append($$anchor3, fragment_5);
        },
        $$slots: { default: true }
      });
    };
    if_block(node_3, ($$render) => {
      if (untrack(() => $$slots.footer)) $$render(consequent_1);
    });
  }
  var div_2 = sibling(node_3, 2);
  bind_this(div_2, ($$value) => set(shepherd, $$value), () => get(shepherd));
  bind_this(div, ($$value) => set(root2, $$value), () => get(root2));
  template_effect(() => {
    set_style(div_1, `padding: ${get(paddingStyle) ?? ""}`);
    set_style(div_2, `width: ${isHorizontal() ? "0px" : "100%"};height: ${isHorizontal() ? "100%" : "0px"}`);
  });
  event("scroll", div, onScroll);
  append($$anchor, div);
  bind_prop($$props, "getSize", getSize);
  bind_prop($$props, "getSizes", getSizes);
  bind_prop($$props, "getOffset", getOffset);
  bind_prop($$props, "getClientSize", getClientSize);
  bind_prop($$props, "getScrollSize", getScrollSize);
  bind_prop($$props, "updatePageModeFront", updatePageModeFront);
  bind_prop($$props, "scrollToOffset", scrollToOffset);
  bind_prop($$props, "scrollToIndex", scrollToIndex);
  bind_prop($$props, "scrollToBottom", scrollToBottom);
  return pop($$exports);
}
var $$_import_databaseStore = reactive_import(() => databaseStore);
var root_1$6 = /* @__PURE__ */ from_html(`<option> </option>`);
var root_2$5 = /* @__PURE__ */ from_html(`<div class="sequencer-database-entries svelte-sequencer-19800lr"><!></div>`);
var root_4$1 = /* @__PURE__ */ from_html(`<div class="sequencer-database-entries-tree svelte-sequencer-19800lr"><!></div>`);
var root$h = /* @__PURE__ */ from_html(`<div class="sequencer-database-content svelte-sequencer-19800lr"><div class="sequencer-database-header svelte-sequencer-19800lr"><select name="pack-select" class="svelte-sequencer-19800lr"><option> </option><!></select> <select name="file-select" class="svelte-sequencer-19800lr"><option> </option><option> </option><option> </option></select> <input class="ml-2 svelte-sequencer-19800lr" type="text"/> <input class="ml-2" id="database-all-ranges" type="checkbox"/> <label class="all-ranges-label svelte-sequencer-19800lr" for="database-all-ranges"> </label> <input class="ml-2" id="include-sub-lists" type="checkbox"/> <label class="all-ranges-label svelte-sequencer-19800lr" for="include-sub-lists"> </label> <input class="ml-2" id="treeview" type="checkbox"/> <label class="all-ranges-label svelte-sequencer-19800lr" for="treeview"> </label></div> <div class="sequencer-database-entries-container svelte-sequencer-19800lr"><!> <div class="sequencer-database-player-container svelte-sequencer-19800lr"><div class="sequencer-database-player-content svelte-sequencer-19800lr"><video autoplay="" class="database-player svelte-sequencer-19800lr" crossorigin="anonymous" height="335" loop="" preload="" width="335"></video> <img class="database-image hidden svelte-sequencer-19800lr"/> <audio controls="" autoplay="" class="database-audio hidden svelte-sequencer-19800lr"><source type="audio"/></audio></div> <div class="sequencer-database-metadata-footer svelte-sequencer-19800lr"><!></div></div></div></div>`, 2);
function Database_shell($$anchor, $$props) {
  push($$props, false);
  const $listView = () => store_get(listView, "$listView", $$stores);
  const $entriesStore = () => store_get(entriesStore2, "$entriesStore", $$stores);
  const $allRanges = () => store_get(allRanges, "$allRanges", $$stores);
  const $fileTypes = () => store_get(fileTypes, "$fileTypes", $$stores);
  const $cleanSearchStore = () => store_get(cleanSearchStore2, "$cleanSearchStore", $$stores);
  const $searchRegex = () => store_get(searchRegex, "$searchRegex", $$stores);
  const $selectedPackStore = () => store_get(selectedPackStore2, "$selectedPackStore", $$stores);
  const $search = () => store_get(search, "$search", $$stores);
  const $packStore = () => store_get(packStore2, "$packStore", $$stores);
  const $subLists = () => store_get(subLists, "$subLists", $$stores);
  const $visibleTreeStore = () => store_get(visibleTreeStore2, "$visibleTreeStore", $$stores);
  const $metadata = () => store_get(metadata, "$metadata", $$stores);
  const [$$stores, $$cleanup] = setup_stores();
  let localize = game.i18n.localize.bind(game.i18n);
  let entries = /* @__PURE__ */ mutable_source([]);
  let filteredEntries = /* @__PURE__ */ mutable_source([]);
  const selectedPackStore2 = $$_import_databaseStore().selectedPackStore;
  const packStore2 = $$_import_databaseStore().packStore;
  const fileTypes = $$_import_databaseStore().fileTypes;
  const metadata = $$_import_databaseStore().metadata;
  const allRanges = $$_import_databaseStore().allRanges;
  const subLists = $$_import_databaseStore().subLists;
  const listView = $$_import_databaseStore().listView;
  const visibleTreeStore2 = $$_import_databaseStore().visibleTreeStore;
  const search = $$_import_databaseStore().search;
  const cleanSearchStore2 = $$_import_databaseStore().cleanSearchStore;
  const searchRegex = $$_import_databaseStore().searchRegex;
  const entriesStore2 = SequencerDatabase.entriesStore;
  listView.set(game.settings.get(CONSTANTS.MODULE_NAME, "db-list-view"));
  onDestroy(() => $$_import_databaseStore().cleanupSpritesheet());
  legacy_pre_effect(() => $listView(), () => {
    game.settings.set(CONSTANTS.MODULE_NAME, "db-list-view", $listView());
  });
  legacy_pre_effect(() => ($entriesStore(), $allRanges()), () => {
    $entriesStore();
    const specificRanges = $allRanges() ? Sequencer.Database.publicFlattenedEntriesAdvanced : Sequencer.Database.publicFlattenedSimpleEntries;
    set(entries, specificRanges.map((entry) => {
      if (typeof entry === "string") {
        return { pack: entry.split(".")[0], entry };
      } else {
        return {
          pack: entry.path.split(".")[0],
          entry: entry.path,
          isAudio: entry.isAudio
        };
      }
    }));
  });
  legacy_pre_effect(() => $allRanges(), () => {
    if (!$allRanges()) store_set(fileTypes, "all");
  });
  legacy_pre_effect(
    () => ($cleanSearchStore(), get(entries), $searchRegex(), $selectedPackStore(), $search(), $fileTypes()),
    () => {
      const searchParts = $cleanSearchStore().split("|");
      set(filteredEntries, get(entries).filter((part) => {
        const matchParts = make_array_unique(part.entry.match($searchRegex()) || []);
        return ($selectedPackStore() === "all" || $selectedPackStore() === part.pack) && ($search() === "" || matchParts.length >= searchParts.length) && ($fileTypes() === "all" || $fileTypes() === "video" && !part.isAudio || $fileTypes() === "sound" && part.isAudio);
      }));
    }
  );
  legacy_pre_effect_reset();
  init();
  var div = root$h();
  var div_1 = child(div);
  var select = child(div_1);
  var option = child(select);
  var text2 = child(option);
  option.value = option.__value = "all";
  var node = sibling(option);
  each(node, 1, $packStore, index, ($$anchor2, pack) => {
    var option_1 = root_1$6();
    var text_1 = child(option_1);
    var option_1_value = {};
    template_effect(() => {
      set_text(text_1, get(pack));
      if (option_1_value !== (option_1_value = get(pack))) {
        option_1.__value = get(pack);
      }
    });
    append($$anchor2, option_1);
  });
  var select_1 = sibling(select, 2);
  var option_2 = child(select_1);
  var text_2 = child(option_2);
  option_2.value = option_2.__value = "all";
  var option_3 = sibling(option_2);
  var text_3 = child(option_3);
  option_3.value = option_3.__value = "video";
  var option_4 = sibling(option_3);
  var text_4 = child(option_4);
  option_4.value = option_4.__value = "sound";
  var input = sibling(select_1, 2);
  var input_1 = sibling(input, 2);
  var label = sibling(input_1, 2);
  var text_5 = child(label);
  var input_2 = sibling(label, 2);
  var label_1 = sibling(input_2, 2);
  var text_6 = child(label_1);
  var input_3 = sibling(label_1, 2);
  var label_2 = sibling(input_3, 2);
  var text_7 = child(label_2);
  var div_2 = sibling(div_1, 2);
  var node_1 = child(div_2);
  {
    var consequent = ($$anchor2) => {
      var div_3 = root_2$5();
      var node_2 = child(div_3);
      VirtualScroll(node_2, {
        get data() {
          return get(filteredEntries);
        },
        key: "entry",
        children: invalid_default_snippet,
        $$slots: {
          default: ($$anchor3, $$slotProps) => {
            const data = /* @__PURE__ */ derived_safe_equal(() => $$slotProps.data);
            DatabaseEntry($$anchor3, {
              get entry() {
                return deep_read_state(get(data)), untrack(() => get(data).entry);
              }
            });
          }
        }
      });
      append($$anchor2, div_3);
    };
    var alternate = ($$anchor2) => {
      var div_4 = root_4$1();
      var node_3 = child(div_4);
      VirtualScroll(node_3, {
        get data() {
          return $visibleTreeStore();
        },
        key: "fullPath",
        children: invalid_default_snippet,
        $$slots: {
          default: ($$anchor3, $$slotProps) => {
            const data = /* @__PURE__ */ derived_safe_equal(() => $$slotProps.data);
            var fragment_1 = comment();
            var node_4 = first_child(fragment_1);
            component(node_4, () => get(data).class, ($$anchor4, $$component) => {
              $$component($$anchor4, {
                get data() {
                  return get(data);
                }
              });
            });
            append($$anchor3, fragment_1);
          }
        }
      });
      append($$anchor2, div_4);
    };
    if_block(node_1, ($$render) => {
      if ($listView()) $$render(consequent);
      else $$render(alternate, -1);
    });
  }
  var div_5 = sibling(node_1, 2);
  var div_6 = child(div_5);
  var video = child(div_6);
  bind_this(video, ($$value) => $$_import_databaseStore($$_import_databaseStore().elements.player = $$value), () => $$_import_databaseStore()?.elements?.player);
  var img = sibling(video, 2);
  bind_this(img, ($$value) => $$_import_databaseStore($$_import_databaseStore().elements.image = $$value), () => $$_import_databaseStore()?.elements?.image);
  var audio2 = sibling(img, 2);
  bind_this(audio2, ($$value) => $$_import_databaseStore($$_import_databaseStore().elements.audio = $$value), () => $$_import_databaseStore()?.elements?.audio);
  var div_7 = sibling(div_6, 2);
  var node_5 = child(div_7);
  {
    var consequent_1 = ($$anchor2) => {
      var text_8 = text$1();
      template_effect(() => set_text(text_8, `Type: ${($metadata(), untrack(() => $metadata().type)) ?? ""} | Duration: ${($metadata(), untrack(() => $metadata().duration)) ?? ""}`));
      append($$anchor2, text_8);
    };
    var alternate_1 = ($$anchor2) => {
      var text_9 = text$1("No file loaded...");
      append($$anchor2, text_9);
    };
    if_block(node_5, ($$render) => {
      if ($metadata()) $$render(consequent_1);
      else $$render(alternate_1, -1);
    });
  }
  template_effect(
    ($0, $1, $2, $3, $4, $5, $6, $7, $8) => {
      set_text(text2, $0);
      select_1.disabled = !$allRanges();
      set_attribute(select_1, "title", $1);
      set_text(text_2, $2);
      set_text(text_3, $3);
      set_text(text_4, $4);
      set_attribute(input, "placeholder", $5);
      set_text(text_5, $6);
      set_text(text_6, $7);
      set_text(text_7, $8);
    },
    [
      () => untrack(() => localize("SEQUENCER.Database.AllPacks")),
      () => ($allRanges(), untrack(() => $allRanges() ? "" : localize("SEQUENCER.Database.FileTypePredicate"))),
      () => untrack(() => localize("SEQUENCER.Database.All")),
      () => untrack(() => localize("SEQUENCER.Database.Video")),
      () => untrack(() => localize("SEQUENCER.Database.Sound")),
      () => untrack(() => localize("SEQUENCER.Database.Search")),
      () => untrack(() => localize("SEQUENCER.Database.ShowAllRanges")),
      () => untrack(() => localize("SEQUENCER.Database.ShowSubLists")),
      () => untrack(() => localize("SEQUENCER.Database.ListView"))
    ]
  );
  bind_select_value(select, $selectedPackStore, ($$value) => store_set(selectedPackStore2, $$value));
  bind_select_value(select_1, $fileTypes, ($$value) => store_set(fileTypes, $$value));
  bind_value(input, $search, ($$value) => store_set(search, $$value));
  bind_checked(input_1, $allRanges, ($$value) => store_set(allRanges, $$value));
  bind_checked(input_2, $subLists, ($$value) => store_set(subLists, $$value));
  bind_checked(input_3, $listView, ($$value) => store_set(listView, $$value));
  event("mouseenter", video, () => {
    $$_import_databaseStore($$_import_databaseStore().elements.player.controls = true);
  });
  event("mouseleave", video, () => {
    $$_import_databaseStore($$_import_databaseStore().elements.player.controls = false);
  });
  append($$anchor, div);
  pop();
  $$cleanup();
}
function SvelteApplicationMixin(Base) {
  class SvelteApplication extends Base {
    static DEFAULT_OPTIONS = {
      classes: []
    };
    /** Root Svelte component (set this in your subclass) */
    // root
    /** State data tracked by the root component */
    $state = writable({});
    /** The mounted root component, saved to be unmounted on application close */
    #mount = {};
    root = null;
    async _renderHTML(context) {
      return context;
    }
    async _prepareContext() {
      return {
        ...this.options,
        foundryApp: this
      };
    }
    _replaceHTML(result, content, options) {
      this.$state.update((s) => ({ ...s, ...result.state ?? {} }));
      if (options.isFirstRender) {
        this.#mount = mount(this.root, { target: content, props: { ...result, ...options, state: this.$state } });
      }
      this.updateElementProps(result, options);
    }
    updateElementProps(result, options) {
      if (!this.#mount) return;
      Object.assign(this.#mount, {
        ...result,
        ...options,
        state: this.$state,
        foundryApp: this
      });
    }
    _onClose(options) {
      super._onClose(options);
      unmount(this.#mount);
    }
    static getActiveApp() {
      const app_v1s = Object.values(ui.windows);
      let all_apps = app_v1s.concat(
        foundry?.applications?.instances ? Array.from(foundry?.applications?.instances.values()) : []
      );
      return all_apps.find((app) => {
        return app instanceof this && app.state > Application.RENDER_STATES.CLOSED;
      });
    }
    static async show(options = {}) {
      const existingApp = this.getActiveApp();
      if (existingApp) return existingApp.render({ ...options, force: true, focus: true });
      return new this(options).render({ force: true, focus: true });
    }
  }
  return SvelteApplication;
}
const PATREON_URL$2 = "https://patreon.com/cw/fantasycomputerworks";
class DatabaseViewerApp extends SvelteApplicationMixin(foundry.applications.api.ApplicationV2) {
  static DEFAULT_OPTIONS = {
    classes: ["dialog"],
    position: {
      width: 900,
      height: 450
    },
    actions: {
      openSequencerChangelog: () => {
        if (window.Sequencer?.showChangelog) window.Sequencer.showChangelog();
      },
      openSequencerPatreon: () => {
        window.open(PATREON_URL$2, "_blank", "noopener");
      }
    },
    window: {
      title: "SEQUENCER.Database.Title",
      icon: "fa-solid fa-archive",
      resizable: true,
      controls: [
        {
          action: "openSequencerChangelog",
          icon: "fa-solid fa-clipboard-list",
          label: "Changelog"
        },
        {
          action: "openSequencerPatreon",
          icon: "fa-brands fa-patreon",
          label: "Support Sequencer"
        }
      ]
    }
  };
  root = Database_shell;
}
var root_1$5 = /* @__PURE__ */ from_html(`<p></p> <button type="button" class="w-100 open-module-settings"><i class="fas fa-plug"></i> <!></button>`, 1);
var root$g = /* @__PURE__ */ from_html(`<div class="howto-container svelte-sequencer-1gfsmnk"><p></p> <p></p> <!> <p></p> <p></p> <ol><li class="svelte-sequencer-1gfsmnk"><strong></strong><br/> <!></li> <li class="svelte-sequencer-1gfsmnk"><strong></strong><br/> <!></li> <li class="svelte-sequencer-1gfsmnk"><strong></strong><br/> <!></li> <li class="svelte-sequencer-1gfsmnk"><strong></strong><br/> <!></li> <li class="svelte-sequencer-1gfsmnk"><strong></strong></li></ol></div>`);
function HowTo($$anchor, $$props) {
  push($$props, false);
  let localize = game.i18n.localize.bind(game.i18n);
  async function openSettings() {
    const settings = new foundry.applications.settings.SettingsConfig({ initialCategory: "sequencer" });
    await settings.render(true);
    await wait(75);
    const focusElement = settings.element.querySelector('select[name="sequencer.permissions-effect-create"]');
    const scrollable = settings.element.querySelector("section.tab.scrollable.active");
    scrollable.scrollTo({ top: focusElement.offsetTop - 100, behavior: "smooth" });
    await wait(250);
    focusElement.parentNode.parentNode.style.transition = "box-shadow 0.5s ease-in-out";
    focusElement.parentNode.parentNode.style.boxShadow = "rgba(200, 64, 67, 0.75) inset 0 0 10px, rgba(200, 64, 67, 0.75) 0 0 10px";
    await wait(5e3);
    focusElement.parentNode.parentNode.style.boxShadow = "none";
    await wait(500);
    focusElement.parentNode.parentNode.style.transition = "none";
  }
  init();
  var div = root$g();
  var p = child(div);
  html$1(p, () => localize("SEQUENCER.HowTo.Welcome"), true);
  var p_1 = sibling(p, 2);
  html$1(p_1, () => localize("SEQUENCER.HowTo.Explanation"), true);
  var node = sibling(p_1, 2);
  {
    var consequent = ($$anchor2) => {
      var fragment = root_1$5();
      var p_2 = first_child(fragment);
      html$1(p_2, () => localize("SEQUENCER.HowTo.PermissionsExplanation"), true);
      var button = sibling(p_2, 2);
      var node_1 = sibling(child(button), 2);
      html$1(node_1, () => localize("SEQUENCER.HowTo.OpenModuleSettings"));
      event("click", button, openSettings);
      append($$anchor2, fragment);
    };
    if_block(node, ($$render) => {
      if (game.user.isGM) $$render(consequent);
    });
  }
  var p_3 = sibling(node, 2);
  html$1(p_3, () => localize("SEQUENCER.HowTo.PlayerExplanation"), true);
  var p_4 = sibling(p_3, 2);
  html$1(p_4, () => localize("SEQUENCER.HowTo.LayerExplanation"), true);
  var ol = sibling(p_4, 2);
  var li = child(ol);
  var strong = child(li);
  html$1(strong, () => localize("SEQUENCER.HowTo.Click"), true);
  var node_2 = sibling(strong, 3);
  html$1(node_2, () => localize("SEQUENCER.HowTo.ClickLabel"));
  var li_1 = sibling(li, 2);
  var strong_1 = child(li_1);
  html$1(strong_1, () => localize("SEQUENCER.HowTo.ClickDrag"), true);
  var node_3 = sibling(strong_1, 3);
  html$1(node_3, () => localize("SEQUENCER.HowTo.ClickDragLabel"));
  var li_2 = sibling(li_1, 2);
  var strong_2 = child(li_2);
  html$1(strong_2, () => localize("SEQUENCER.HowTo.Shift"), true);
  var node_4 = sibling(strong_2, 3);
  html$1(node_4, () => localize("SEQUENCER.HowTo.ShiftLabel"));
  var li_3 = sibling(li_2, 2);
  var strong_3 = child(li_3);
  html$1(strong_3, () => localize("SEQUENCER.HowTo.ShiftControl"), true);
  var node_5 = sibling(strong_3, 3);
  html$1(node_5, () => localize("SEQUENCER.HowTo.ShiftControlLabel"));
  var li_4 = sibling(li_3, 2);
  var strong_4 = child(li_4);
  html$1(strong_4, () => localize("SEQUENCER.HowTo.MoreToCome"), true);
  append($$anchor, div);
  pop();
}
var root_2$4 = /* @__PURE__ */ from_html(`<div style="border-right: 1px solid rgba(0,0,0,0.5); margin: 0 10px;"></div>`);
var root_3$3 = /* @__PURE__ */ from_html(`<i></i>`);
var root_1$4 = /* @__PURE__ */ from_html(`<!> <div data-tab="rest"><!> </div>`, 1);
var root$f = /* @__PURE__ */ from_html(`<nav class="tabs svelte-sequencer-123fyp" data-group="primary"></nav>`);
function Tabs($$anchor, $$props) {
  const $$sanitized_props = legacy_rest_props($$props, ["children", "$$slots", "$$events", "$$legacy"]);
  push($$props, false);
  let localize = game.i18n.localize.bind(game.i18n);
  let activeTab = prop($$props, "activeTab", 12);
  let tabs = prop($$props, "tabs", 8);
  let underscore = prop($$props, "underscore", 8, false);
  let separateElements = prop($$props, "separateElements", 8, false);
  init();
  var nav = root$f();
  each(
    nav,
    7,
    () => (deep_read_state(tabs()), untrack(() => tabs().filter((tab) => !tab.hidden))),
    (tab) => tab.value,
    ($$anchor2, tab, index2) => {
      var fragment = root_1$4();
      var node = first_child(fragment);
      {
        var consequent = ($$anchor3) => {
          var div = root_2$4();
          append($$anchor3, div);
        };
        if_block(node, ($$render) => {
          if (separateElements() && get(index2) > 0) $$render(consequent);
        });
      }
      var div_1 = sibling(node, 2);
      let classes;
      var node_1 = child(div_1);
      {
        var consequent_1 = ($$anchor3) => {
          var i = root_3$3();
          template_effect(() => set_class(i, 1, `icon ${(get(tab), untrack(() => get(tab).icon)) ?? ""}`, "svelte-sequencer-123fyp"));
          append($$anchor3, i);
        };
        if_block(node_1, ($$render) => {
          if (get(tab), untrack(() => get(tab).icon)) $$render(consequent_1);
        });
      }
      var text2 = sibling(node_1);
      template_effect(
        ($0) => {
          classes = set_class(div_1, 1, "item item-piles-flexrow item-piles-clickable-link svelte-sequencer-123fyp", null, classes, {
            underscore: underscore(),
            active: activeTab() === get(tab).value
          });
          set_text(text2, ` ${$0 ?? ""}`);
        },
        [
          () => (get(tab), untrack(() => localize(get(tab).label)))
        ]
      );
      event("click", div_1, () => {
        activeTab(get(tab).value);
      });
      append($$anchor2, fragment);
    }
  );
  template_effect(() => set_style(nav, (deep_read_state($$sanitized_props), untrack(() => $$sanitized_props.style))));
  append($$anchor, nav);
  pop();
}
const SequenceManager = {
  VisibleEffects: writable({}),
  RunningSounds: writable({}),
  RunningSequences: writable({})
};
SequenceManager.VisibleEffects.get = (id) => {
  return get$1(SequenceManager.VisibleEffects)[id];
};
SequenceManager.VisibleEffects.add = (id, data) => {
  SequenceManager.VisibleEffects.update((effects) => {
    effects[id] = data;
    return effects;
  });
};
SequenceManager.VisibleEffects.delete = (id) => {
  SequenceManager.VisibleEffects.update((effects) => {
    delete effects[id];
    return effects;
  });
};
SequenceManager.VisibleEffects.values = () => {
  return Object.values(get$1(SequenceManager.VisibleEffects));
};
SequenceManager.RunningSounds.get = (id) => {
  return get$1(SequenceManager.RunningSounds)[id];
};
SequenceManager.RunningSounds.add = (id, data) => {
  SequenceManager.RunningSounds.update((sounds) => {
    sounds[id] = data;
    return sounds;
  });
};
SequenceManager.RunningSounds.delete = (id) => {
  SequenceManager.RunningSounds.update((sounds) => {
    delete sounds[id];
    return sounds;
  });
};
SequenceManager.RunningSounds.values = () => {
  return Object.values(get$1(SequenceManager.RunningSounds));
};
SequenceManager.RunningSounds.keys = () => {
  return Object.keys(get$1(SequenceManager.RunningSounds));
};
SequenceManager.RunningSequences.get = (id) => {
  return get$1(SequenceManager.RunningSequences)[id];
};
SequenceManager.RunningSequences.add = (id, sequence) => {
  SequenceManager.RunningSequences.update((sequences) => {
    sequences[id] = sequence;
    return sequences;
  });
};
SequenceManager.RunningSequences.delete = (id) => {
  SequenceManager.RunningSequences.update((sequences) => {
    delete sequences[id];
    return sequences;
  });
};
SequenceManager.RunningSequences.clearFinishedSequences = () => {
  SequenceManager.RunningSequences.update((sequences) => {
    for (const sequence of Object.values(sequences)) {
      if (sequence.status === CONSTANTS.STATUS.COMPLETE || sequence.status === CONSTANTS.STATUS.ABORTED) {
        delete sequences[sequence.id];
      }
    }
    return sequences;
  });
};
SequenceManager.RunningSequences.stopAll = () => {
  SequenceManager.RunningSequences.update((sequences) => {
    for (const sequence of Object.values(sequences)) {
      sequence._abort();
    }
    return sequences;
  });
};
SequenceManager.RunningSequences.values = () => {
  return Object.values(get$1(SequenceManager.RunningSequences));
};
class ColorMatrixFilter extends globalThis.PIXI.ColorMatrixFilter {
  /**
   * Properties & default values:
   *     - hue [false]
   *     - brightness [1]
   *     - contrast [1]
   *     - saturate [1]
   */
  constructor(inData) {
    super();
    this.isValid = true;
    this.values = /* @__PURE__ */ new Map();
    for (let [key, value] of Object.entries(inData)) {
      if (value === void 0 || value === null) continue;
      this.setValue(key, value);
      if (!this.isValid) break;
    }
  }
  setValue(key, value) {
    try {
      this.values[key] = value;
      super[key](value, true);
    } catch (err) {
      ui.notifications.warn(
        `Sequencer | ${this.constructor.name} | Could not set property ${key}`
      );
    }
  }
  get hue() {
    return this.values?.hue ?? 0;
  }
  set hue(value) {
    this.values.hue = value;
    super.hue(value);
    this.setOthers("hue");
  }
  get contrast() {
    return this.values?.contrast ?? 0;
  }
  set contrast(value) {
    this.values.contrast = value;
    super.contrast(value);
  }
  get brightness() {
    return this.values?.brightness ?? 0;
  }
  set brightness(value) {
    this.values.brightness = value;
    super.brightness(value);
  }
  get saturate() {
    return this.values?.saturate ?? 0;
  }
  set saturate(value) {
    this.values.saturate = value;
    super.saturate(value);
  }
  setOthers(ignoreKey) {
    for (let [key, value] of this.values.entries) {
      if (key === ignoreKey) continue;
      this.setValue(key, value);
    }
  }
}
class BlurFilter extends globalThis.PIXI.BlurFilter {
  /**
   * Properties & default values:
   *     - strength [8]
   *     - blur [2]
   *     - blurX [2]
   *     - blurY [2]
   *     - quality [4]
   *     - resolution [PIXI.settings.FILTER_RESOLUTION]
   *     - kernelSize [5]
   */
  constructor(inData = {}) {
    inData = foundry.utils.mergeObject(
      {
        strength: 1,
        quality: 4,
        resolution: PIXI.settings.FILTER_RESOLUTION,
        kernelSize: 5
      },
      inData
    );
    super(...Object.values(inData));
    this.isValid = true;
    for (let [key, value] of Object.entries(inData)) {
      try {
        this[key] = value;
      } catch (err) {
        ui.notifications.warn(
          `Sequencer | ${this.constructor.name} | Could not set property ${key}`
        );
        this.isValid = false;
      }
    }
  }
}
class NoiseFilter extends globalThis.PIXI.NoiseFilter {
  /**
   * Properties & default values:
   *     - noise [0.5]
   *     - seed [Math.random()]
   */
  constructor(inData = {}) {
    super();
    inData = foundry.utils.mergeObject(
      {
        noise: 0.5,
        seed: Math.random()
      },
      inData
    );
    this.isValid = true;
    for (let [key, value] of Object.entries(inData)) {
      try {
        this[key] = value;
      } catch (err) {
        ui.notifications.warn(
          `Sequencer | ${this.constructor.name} | Could not set property ${key}`
        );
        this.isValid = false;
      }
    }
  }
}
class GlowFilter extends globalThis.PIXI.Filter {
  static defaults = {
    distance: 10,
    outerStrength: 4,
    innerStrength: 0,
    color: 16777215,
    quality: 0.1,
    knockout: false,
    alpha: 1
  };
  constructor(options) {
    const opts = Object.assign({}, GlowFilter.defaults, options);
    const {
      outerStrength,
      innerStrength,
      color,
      knockout,
      quality,
      alpha
    } = opts;
    const distance = Math.round(opts.distance);
    super(vertex, frag.replace(/__ANGLE_STEP_SIZE__/gi, `${(1 / quality / distance).toFixed(7)}`).replace(/__DIST__/gi, `${distance.toFixed(0)}.0`));
    this.uniforms.glowColor = new Float32Array([0, 0, 0, 1]);
    this.uniforms.alpha = 1;
    Object.assign(this, {
      color,
      outerStrength,
      innerStrength,
      padding: distance,
      knockout,
      alpha
    });
    this.isValid = true;
  }
  /**
   * The color of the glow.
   * @default 0xFFFFFF
   */
  get color() {
    return PIXI.utils.rgb2hex(this.uniforms.glowColor);
  }
  set color(value) {
    PIXI.utils.hex2rgb(value, this.uniforms.glowColor);
  }
  /**
   * The strength of the glow outward from the edge of the sprite.
   * @default 4
   */
  get outerStrength() {
    return this.uniforms.outerStrength;
  }
  set outerStrength(value) {
    this.uniforms.outerStrength = value;
  }
  /**
   * The strength of the glow inward from the edge of the sprite.
   * @default 0
   */
  get innerStrength() {
    return this.uniforms.innerStrength;
  }
  set innerStrength(value) {
    this.uniforms.innerStrength = value;
  }
  /**
   * Only draw the glow, not the texture itself
   * @default false
   */
  get knockout() {
    return this.uniforms.knockout;
  }
  set knockout(value) {
    this.uniforms.knockout = value;
  }
  /**
   * The alpha value of the glow
   * @default 1
   */
  get alpha() {
    return this.uniforms.alpha;
  }
  set alpha(value) {
    this.uniforms.alpha = value;
  }
}
const frag = `varying vec2 vTextureCoord;
varying vec4 vColor;

uniform sampler2D uSampler;

uniform float outerStrength;
uniform float innerStrength;

uniform vec4 glowColor;

uniform vec4 filterArea;
uniform vec4 filterClamp;
uniform bool knockout;
uniform float alpha;

const float PI = 3.14159265358979323846264;

const float DIST = __DIST__;
const float ANGLE_STEP_SIZE = min(__ANGLE_STEP_SIZE__, PI * 2.0);
const float ANGLE_STEP_NUM = ceil(PI * 2.0 / ANGLE_STEP_SIZE);

const float MAX_TOTAL_ALPHA = ANGLE_STEP_NUM * DIST * (DIST + 1.0) / 2.0;

void main(void) {
    vec2 px = vec2(1.0 / filterArea.x, 1.0 / filterArea.y);

    float totalAlpha = 0.0;

    vec2 direction;
    vec2 displaced;
    vec4 curColor;

    for (float angle = 0.0; angle < PI * 2.0; angle += ANGLE_STEP_SIZE) {
       direction = vec2(cos(angle), sin(angle)) * px;

       for (float curDistance = 0.0; curDistance < DIST; curDistance++) {
           displaced = clamp(vTextureCoord + direction * 
                   (curDistance + 1.0), filterClamp.xy, filterClamp.zw);

           curColor = texture2D(uSampler, displaced);

           totalAlpha += (DIST - curDistance) * curColor.a;
       }
    }
    
    curColor = texture2D(uSampler, vTextureCoord);

    float alphaRatio = (totalAlpha / MAX_TOTAL_ALPHA);

    float innerGlowAlpha = (1.0 - alphaRatio) * innerStrength * curColor.a;
    float innerGlowStrength = min(1.0, innerGlowAlpha);
    
    vec4 innerColor = mix(curColor, glowColor, innerGlowStrength);

    float outerGlowAlpha = alphaRatio * outerStrength * (1. - curColor.a);
    float outerGlowStrength = min(1.0 - innerColor.a, outerGlowAlpha);

    if (knockout) {
      float resultAlpha = (outerGlowAlpha + innerGlowAlpha) * alpha;
      gl_FragColor = vec4(glowColor.rgb * resultAlpha, resultAlpha);
    }
    else {
      vec4 outerGlowColor = outerGlowStrength * glowColor.rgba * alpha;
      gl_FragColor = innerColor + outerGlowColor;
    }
}`;
const vertex = `attribute vec2 aVertexPosition;
attribute vec2 aTextureCoord;

uniform mat3 projectionMatrix;

varying vec2 vTextureCoord;

void main(void)
{
    gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
    vTextureCoord = aTextureCoord;
}`;
let shader = `
uniform sampler2D uSampler;
varying vec2 vTextureCoord;
float alpha;

void main() {
    vec4 pixel = texture2D(uSampler, vTextureCoord);
    alpha = smoothstep(0.6,1.0,pixel.a);
    gl_FragColor = vec4(alpha, alpha, alpha, pixel.a);
}
`;
class ClipFilter extends PIXI.Filter {
  constructor() {
    super(null, shader);
  }
}
const filters = {
  ColorMatrix: ColorMatrixFilter,
  Blur: BlurFilter,
  Noise: NoiseFilter,
  Glow: GlowFilter,
  Clip: ClipFilter
};
let fragmentShader = `        varying vec2 vTextureCoord;

        uniform sampler2D uSampler;
        uniform sampler2D uMaskSampler;

        void main(void) {
            gl_FragColor = texture2D(uSampler, vTextureCoord)
                * texture2D(uMaskSampler, vTextureCoord).a;
        }`;
class MaskFilter extends foundry.canvas.rendering.filters.AbstractBaseFilter {
  /** @override */
  static defaultUniforms = { uMaskSampler: null };
  /** @override */
  static fragmentShader = fragmentShader;
  /** @type {DisplayObject[]|null} */
  masks = [];
  static _createFragmentShader() {
    return fragmentShader;
  }
  /** @override */
  apply(filterManager, input, output, clearMode, currentState) {
    const maskFilterTexture = filterManager.getFilterTexture();
    const originalFilterTexture = this.#push(
      filterManager,
      currentState,
      maskFilterTexture
    );
    const renderer = filterManager.renderer;
    for (const mask of this.masks) {
      if (mask?.obj?.destroyed) continue;
      const renderable = mask.renderable;
      mask.renderable = true;
      mask.render(renderer);
      mask.renderable = renderable;
    }
    renderer.batch.flush();
    this.#pop(filterManager, currentState, originalFilterTexture);
    this.uniforms.uMaskSampler = maskFilterTexture;
    filterManager.applyFilter(this, input, output, clearMode);
    filterManager.returnFilterTexture(maskFilterTexture);
  }
  #push(filterManager, currentState, maskFilterTexture) {
    const originalFilterTexture = currentState.renderTexture;
    currentState.renderTexture = maskFilterTexture;
    filterManager.defaultFilterStack.push(currentState);
    filterManager.bindAndClear(maskFilterTexture);
    return originalFilterTexture;
  }
  #pop(filterManager, currentState, originalFilterTexture) {
    currentState.renderTexture = originalFilterTexture;
    filterManager.defaultFilterStack.pop();
    if (filterManager.activeState === currentState) {
      return;
    }
    filterManager.activeState = currentState;
    const globalUniforms = filterManager.globalUniforms.uniforms;
    globalUniforms.outputFrame = currentState.sourceFrame;
    globalUniforms.resolution = currentState.resolution;
    const inputSize = globalUniforms.inputSize;
    const inputPixel = globalUniforms.inputPixel;
    const inputClamp = globalUniforms.inputClamp;
    inputSize[0] = currentState.destinationFrame.width;
    inputSize[1] = currentState.destinationFrame.height;
    inputSize[2] = 1 / inputSize[0];
    inputSize[3] = 1 / inputSize[1];
    inputPixel[0] = Math.round(inputSize[0] * currentState.resolution);
    inputPixel[1] = Math.round(inputSize[1] * currentState.resolution);
    inputPixel[2] = 1 / inputPixel[0];
    inputPixel[3] = 1 / inputPixel[1];
    inputClamp[0] = 0.5 * inputPixel[2];
    inputClamp[1] = 0.5 * inputPixel[3];
    inputClamp[2] = currentState.sourceFrame.width * inputSize[2] - 0.5 * inputPixel[2];
    inputClamp[3] = currentState.sourceFrame.height * inputSize[3] - 0.5 * inputPixel[3];
    if (currentState.legacy) {
      const filterArea = globalUniforms.filterArea;
      filterArea[0] = currentState.destinationFrame.width;
      filterArea[1] = currentState.destinationFrame.height;
      filterArea[2] = currentState.sourceFrame.x;
      filterArea[3] = currentState.sourceFrame.y;
      globalUniforms.filterClamp = globalUniforms.inputClamp;
    }
    filterManager.globalUniforms.update();
  }
}
const SequencerAnimationEngine = {
  _animations: [],
  _debug: void 0,
  _deltas: [],
  ticker: false,
  dt: 0,
  isRunning: false,
  addAnimation(origin, attributes = [], timeDifference = 0) {
    if (!Array.isArray(attributes)) attributes = [attributes];
    return new Promise((resolve) => {
      this._animations.push({
        origin,
        attributes: attributes.map((attribute) => {
          attribute.targetId = get_object_identifier(attribute.target) + "-" + attribute.propertyName;
          attribute.id = foundry.utils.randomID();
          attribute.started = false;
          attribute.initialized = false;
          attribute.finishing = false;
          attribute.complete = false;
          attribute.previousValue = null;
          attribute.progress = 0;
          attribute.value = 0;
          attribute.coreValue = 0;
          attribute.isFunkyProperty = attribute.propertyName.startsWith("scale.");
          attribute.duration = attribute.duration ?? 0;
          attribute.durationDone = timeDifference ?? 0;
          if (attribute?.looping) {
            attribute.loopDuration = attribute.loopDuration ?? attribute.duration ?? 0;
            attribute.loopDurationDone = timeDifference % attribute.loopDuration;
            attribute.loops = attribute.loops ?? 0;
            attribute.loopsDone = Math.floor(
              attribute.durationDone / attribute.duration
            );
            attribute.index = attribute.loopsDone % attribute.values.length;
            attribute.nextIndex = (attribute.loopsDone + 1) % attribute.values.length;
            if (!attribute.pingPong && attribute.nextIndex === 0) {
              attribute.index = 0;
              attribute.nextIndex = 1;
            }
          }
          return attribute;
        }),
        complete: false,
        totalDt: timeDifference,
        resolve
      });
      this._animations.sort((a, b) => {
        const aNumAbsolute = a.attributes.filter((attr) => !attr.complete && attr.absolute).length;
        const bNumAbsolute = b.attributes.filter((attr) => !attr.complete && attr.absolute).length;
        return bNumAbsolute - aNumAbsolute;
      });
      if (!this.ticker) {
        this.start();
      }
      debug(`Added animations to Animation Engine`);
    });
  },
  endAnimations(target) {
    this._animations = this._animations.filter(
      (animation2) => animation2.origin !== target
    );
  },
  updateStartValues(target, propertyName) {
    const targetId = get_object_identifier(target) + "-" + propertyName;
    if (targetId in this._coreValues) {
      this._coreValues[targetId].value = deep_get(target, propertyName);
    }
  },
  start() {
    if (!this.ticker) {
      debug(`Animation Engine Started`);
      this.ticker = foundry.canvas.animation.CanvasAnimation.ticker;
      this.ticker.add(this.nextFrame.bind(this));
    }
  },
  nextFrame() {
    if (this._animations.length === 0) {
      this._coreValues = {};
      return;
    }
    this._storedValues = {};
    this._animations.forEach((animation2) => this._animate(animation2));
    for (let delta of Object.values(this._storedValues)) {
      try {
        deep_set(delta.target, delta.propertyName, delta.value);
      } catch (err) {
      }
    }
    this._animations = this._animations.filter((animation2) => !animation2.complete);
  },
  _coreValues: {},
  _storedValues: {},
  _animate(animation2) {
    animation2.totalDt += this.ticker.elapsedMS;
    animation2.attributes.filter((attribute) => {
      return !attribute.complete && animation2.totalDt >= attribute.delay;
    }).forEach((attr) => {
      this._animateAttribute(attr);
      if (attr.finishing) {
        attr.complete = true;
        if (this._coreValues[attr.targetId]) {
          delete this._coreValues[attr.targetId];
        }
      }
    });
    animation2.complete = animation2.attributes.filter((attribute) => !attribute.complete).length === 0;
    if (animation2.complete) {
      animation2.resolve();
    }
  },
  _animateAttribute(attribute) {
    if (this._coreValues[attribute.targetId] === void 0) {
      this._coreValues[attribute.targetId] = {
        id: attribute.id,
        value: deep_get(attribute.target, attribute.propertyName)
      };
    }
    if (this._storedValues[attribute.targetId] === void 0) {
      this._storedValues[attribute.targetId] = {
        value: this._coreValues[attribute.targetId].value,
        target: attribute.target,
        propertyName: attribute.propertyName,
        targetId: attribute.targetId
      };
    }
    if (attribute?.looping && attribute?.indefinite) {
      this._handleIndefiniteLoop(attribute);
    } else if (attribute?.looping) {
      this._handleLoops(attribute);
    } else {
      this._handleDefault(attribute);
    }
    if (attribute.absolute) {
      this._coreValues[attribute.targetId].value = attribute.value;
      const delta = this._coreValues[attribute.targetId].value - this._storedValues[attribute.targetId].value;
      this._storedValues[attribute.targetId].value += delta;
    } else {
      if (attribute.isFunkyProperty) {
        const coreValue = this._coreValues[attribute.targetId].value;
        attribute.delta = coreValue * attribute.value - coreValue;
        this._storedValues[attribute.targetId].value += attribute.delta;
      } else {
        this._storedValues[attribute.targetId].value += attribute.value;
      }
    }
  },
  _handleBaseLoop(attribute) {
    if (!attribute.initialized) {
      if (attribute.values.length === 1) {
        attribute.values.unshift(this._coreValues[attribute.targetId].value);
      }
      attribute.initialized = true;
    }
    attribute.loopDurationDone += this.ticker.deltaMS;
    attribute.progress = attribute.loopDurationDone / attribute.loopDuration;
    attribute.value = interpolate(
      attribute.values[attribute.index],
      attribute.values[attribute.nextIndex],
      attribute.progress,
      attribute.ease
    );
    if (attribute.progress >= 1) {
      attribute.loopDurationDone -= attribute.loopDuration;
      attribute.index = (attribute.index + 1) % attribute.values.length;
      attribute.nextIndex = (attribute.nextIndex + 1) % attribute.values.length;
      if (!attribute.pingPong && attribute.nextIndex === 0) {
        attribute.index = 0;
        attribute.nextIndex = 1;
      }
      attribute.loopsDone++;
      attribute.value = interpolate(
        attribute.values[attribute.index],
        attribute.values[attribute.nextIndex],
        attribute.progress % 1,
        attribute.ease
      );
    }
  },
  _handleIndefiniteLoop(attribute) {
    return this._handleBaseLoop(attribute);
  },
  _handleLoops(attribute) {
    this._handleBaseLoop(attribute);
    attribute.durationDone += this.ticker.deltaMS;
    attribute.overallProgress = attribute.durationDone / attribute.duration;
    if (attribute.progress >= 1 && attribute.loopsDone === attribute.loops * 2) {
      attribute.finishing = true;
      attribute.value = attribute.values[attribute.index];
    }
    if (attribute.overallProgress >= 1) {
      attribute.finishing = true;
    }
  },
  _handleDefault(attribute) {
    if (!attribute.initialized) {
      if (attribute.from === void 0) {
        attribute.from = this._coreValues[attribute.targetId].value;
      }
      attribute.initialized = true;
    }
    attribute.durationDone += this.ticker.deltaMS;
    attribute.progress = attribute.durationDone / attribute.duration;
    attribute.value = interpolate(
      attribute.from,
      attribute.to,
      clamp(attribute.progress, 0, 1),
      attribute.ease
    );
    if (attribute.progress >= 1) {
      attribute.value = attribute.to;
      attribute.finishing = true;
    }
  }
};
const flagManager = {
  flagAddBuffer: /* @__PURE__ */ new Map(),
  flagRemoveBuffer: /* @__PURE__ */ new Map(),
  _latestFlagVersion: false,
  _databaseId: null,
  get database() {
    if (this._databaseId) {
      const cached = game.journal.get(this._databaseId);
      if (cached) return cached;
      this._databaseId = null;
    }
    const database = game.journal.getName(CONSTANTS.DATABASE_NAME);
    if (database) {
      this._databaseId = database.id;
    }
    return database;
  },
  setup() {
    Hooks.on("renderJournalDirectory", async (app) => {
      let database = this.database;
      if (!database) return;
      let element = app.element.querySelector(`[data-entry-id="${database.id}"]`);
      if (element) {
        element.remove();
      }
    });
    Hooks.on("preDeleteJournalEntry", (doc) => {
      let database = this.database;
      if (!database) return;
      return doc !== database;
    });
  },
  _getDatabaseData(flag) {
    let database = this.database;
    if (!database) {
      return {};
    }
    let flagData = {};
    let flags2 = foundry.utils.getProperty(database, flag) ?? {};
    for (let [uuid, data] of Object.entries(flags2)) {
      let newUuid = uuid.replaceAll("-", ".");
      flagData[newUuid] ??= [];
      flagData[newUuid] = flagData[newUuid].concat(data);
    }
    return flagData;
  },
  getDatabaseFlags() {
    return {
      effects: this._getDatabaseData(CONSTANTS.EFFECTS_FLAG),
      sounds: this._getDatabaseData(CONSTANTS.SOUNDS_FLAG)
    };
  },
  get latestFlagVersion() {
    if (!this._latestFlagVersion) {
      const versions = Object.keys(this.effectMigrations);
      versions.sort((a, b) => {
        return foundry.utils.isNewerVersion(a, b) ? -1 : 1;
      });
      this._latestFlagVersion = versions[0];
    }
    return this._latestFlagVersion;
  },
  /**
   * Sanitizes the effect data, accounting for changes to the structure in previous versions
   *
   * @param inDocument
   * @returns {array}
   */
  getEffectFlags(inDocument) {
    if (!inDocument?.uuid) return [];
    let effects = this.getDatabaseFlags().effects[inDocument.uuid] ?? [];
    if (!effects?.length) return [];
    effects = foundry.utils.deepClone(effects);
    const changes = [];
    for (let [effectId, effectData] of effects) {
      let effectVersion = effectData?.flagVersion ?? "1.0.0";
      let changesAdded = false;
      if (effectData._id !== effectId) {
        changesAdded = true;
        effectData._id = effectId;
        changes.push(effectData);
        debug(
          `Fixed effect with broken ID ${effectId}`
        );
      }
      if (effectData.flagVersion === this.latestFlagVersion) continue;
      for (let [version, migration] of Object.entries(this.effectMigrations)) {
        if (!foundry.utils.isNewerVersion(version, effectVersion)) continue;
        effectData = migration(inDocument, effectData);
      }
      debug(
        `Migrated effect with ID ${effectId} from version ${effectVersion} to version ${this.latestFlagVersion}`
      );
      effectData.flagVersion = this.latestFlagVersion;
      if (!changesAdded) changes.push(effectData);
    }
    if (changes.length) {
      flagManager.addFlags(inDocument.uuid, { effects: changes });
    }
    return effects;
  },
  effectMigrations: {
    "2.0.0": (inDocument, effectData) => {
      effectData._id = effectData.id;
      effectData.creationTimestamp = effectData.timestamp;
      if (effectData.template) {
        effectData.template = {
          gridSize: effectData.template[0],
          startPoint: effectData.template[1],
          endPoint: effectData.template[2]
        };
      }
      if (effectData.attachTo) {
        effectData.attachTo = {
          active: true,
          align: "center",
          rotation: true,
          bindVisibility: true,
          bindAlpha: true
        };
        effectData.source = inDocument.uuid;
        const objectSize = get_object_dimensions(inDocument, true);
        effectData.offset = {
          x: effectData.position.x - objectSize.width,
          y: effectData.position.y - objectSize.height
        };
      } else if (effectData.position) {
        effectData.source = effectData.position;
      }
      if (effectData.reachTowards) {
        effectData.stretchTo = {
          attachTo: false,
          onlyX: false
        };
      }
      if (effectData.filters) {
        effectData.filters = Object.entries(effectData.filters).map((entry) => {
          return {
            className: entry[0],
            ...entry[1]
          };
        });
      }
      effectData.moveSpeed = effectData.speed;
      effectData.target = null;
      effectData.forcedIndex = null;
      effectData.flipX = false;
      effectData.flipY = false;
      effectData.nameOffsetMap = {};
      effectData.sequenceId = 0;
      delete effectData.id;
      delete effectData.timestamp;
      delete effectData.position;
      delete effectData.reachTowards;
      delete effectData.speed;
      delete effectData.audioVolume;
      delete effectData.gridSizeDifference;
      delete effectData.template;
      if (effectData.animatedProperties) {
        delete effectData.animatedProperties.fadeInAudio;
        delete effectData.animatedProperties.fadeOutAudio;
      }
      effectData = foundry.utils.mergeObject(
        effectData,
        effectData.animatedProperties
      );
      delete effectData.animatedProperties;
      return effectData;
    },
    "2.0.6": (inDocument, effectData) => {
      effectData.private = null;
      return effectData;
    },
    "2.0.8": (inDocument, effectData) => {
      if (effectData.stretchTo) {
        effectData.stretchTo.tiling = false;
      }
      return effectData;
    },
    "2.0.9": (inDocument, effectData) => {
      effectData.tilingTexture = false;
      if (effectData.stretchTo?.tiling !== void 0) {
        if (effectData.stretchTo.tiling) {
          effectData.tilingTexture = {
            scale: { x: 1, y: 1 },
            position: { x: 0, y: 0 }
          };
        }
        delete effectData.stretchTo.tiling;
      }
      return effectData;
    },
    "2.1.0": (inDocument, effectData) => {
      if (effectData.randomOffset) {
        effectData.randomOffset = {
          source: !effectData.target ? effectData.randomOffset : false,
          target: !!effectData.target ? effectData.randomOffset : false
        };
      }
      if (effectData.nameOffsetMap) {
        Object.values(effectData.nameOffsetMap).forEach((offsetMap) => {
          if (offsetMap.randomOffset) {
            offsetMap.randomOffset = {
              source: !offsetMap.target ? offsetMap.randomOffset : false,
              target: !!offsetMap.target ? offsetMap.randomOffset : false
            };
          }
        });
      }
      return effectData;
    },
    "3.2.0": (inDocument, effectData) => {
      if (effectData.attachTo?.followRotation !== void 0) {
        effectData.attachTo.bindRotation = effectData.attachTo.followRotation;
        delete effectData.attachTo.followRotation;
      }
      if (effectData.scaleToObject) {
        if (effectData.attachTo?.active) {
          effectData.attachTo.bindScale = true;
        } else if (!effectData.attachTo?.active && !effectData.attachTo?.bindScale) {
          effectData.scaleToObject = false;
          effectData.size = get_object_dimensions(inDocument);
        }
      }
      return effectData;
    },
    "3.2.1": (inDocument, effectData) => {
      if (effectData.noLoop !== void 0) {
        effectData.loopOptions = {
          loops: 1,
          loopDelay: 0,
          endOnLastLoop: false
        };
        delete effectData.noLoop;
      }
      return effectData;
    }
  },
  /**
   * Adds effects and/or sounds to a given document
   *
   * @param inObjectUUID
   * @param effects
   * @param sounds
   */
  addFlags: (inObjectUUID, { effects = [], sounds = [] } = {}) => {
    if (!Array.isArray(effects)) effects = [effects];
    if (!Array.isArray(sounds)) sounds = [sounds];
    sequencerSocket.executeAsMainUser(
      SOCKET_HANDLERS.ADD_FLAGS,
      inObjectUUID,
      effects,
      sounds
    );
  },
  /**
   * Removes effects and/or sounds from a given document
   *
   * @param inObjectUUID
   * @param effects
   * @param removeAllEffects
   * @param sounds
   * @param removeAllSounds
   */
  removeFlags: (inObjectUUID, { effects = [], removeAllEffects = false, sounds = [], removeAllSounds = false } = []) => {
    if (!Array.isArray(effects)) effects = [effects];
    if (!Array.isArray(sounds)) sounds = [sounds];
    sequencerSocket.executeAsMainUser(
      SOCKET_HANDLERS.REMOVE_FLAGS,
      inObjectUUID,
      { effects, removeAllEffects, sounds, removeAllSounds }
    );
  },
  _addFlags: (inObjectUUID, effects, sounds) => {
    if (!Array.isArray(effects)) effects = [effects];
    if (!Array.isArray(sounds)) sounds = [sounds];
    let flagsToSet = flagManager.flagAddBuffer.get(inObjectUUID) ?? {
      effects: [],
      sounds: []
    };
    flagsToSet.effects.push(...effects);
    flagsToSet.sounds.push(...sounds);
    flagManager.flagAddBuffer.set(inObjectUUID, flagsToSet);
    flagManager.updateFlags();
  },
  _removeFlags: (inObjectUUID, { effects = [], removeAllEffects = false, sounds = [], removeAllSounds = false }) => {
    if (effects && !Array.isArray(effects)) effects = [effects];
    if (sounds && !Array.isArray(sounds)) sounds = [sounds];
    let flagsToSet = flagManager.flagRemoveBuffer.get(inObjectUUID) ?? {
      effects: [],
      sounds: [],
      removeAllEffects,
      removeAllSounds
    };
    if (effects) flagsToSet.effects.push(...effects);
    if (sounds) flagsToSet.sounds.push(...sounds);
    flagManager.flagRemoveBuffer.set(inObjectUUID, flagsToSet);
    flagManager.updateFlags();
  },
  updateFlags: foundry.utils.debounce(async () => {
    let database = flagManager.database;
    if (!database) return;
    let flagsToAdd = Array.from(flagManager.flagAddBuffer);
    let flagsToRemove = Array.from(flagManager.flagRemoveBuffer);
    if (flagsToAdd.length === 0 && flagsToRemove.length === 0) return;
    flagManager.flagAddBuffer.clear();
    flagManager.flagRemoveBuffer.clear();
    flagsToAdd.forEach((entry) => entry[1].original = true);
    flagsToRemove.forEach((entry) => entry[1].original = true);
    const objects = /* @__PURE__ */ new Set([
      ...flagsToAdd.map((effect2) => effect2[0]).filter(Boolean),
      ...flagsToRemove.map((effect2) => effect2[0]).filter(Boolean)
    ]);
    flagsToAdd = new Map(flagsToAdd);
    flagsToRemove = new Map(flagsToRemove);
    const effectUpdate = {};
    const soundUpdate = {};
    const allEffectFlags = flagManager.getDatabaseFlags().effects;
    const allSoundFlags = flagManager.getDatabaseFlags().sounds;
    for (let objectUUID of objects) {
      let object = fromUuidSync(objectUUID);
      if (!object) {
        forceDeletionKeyWrapper(effectUpdate, "-=" + objectUUID.replaceAll(".", "-"));
        forceDeletionKeyWrapper(soundUpdate, "-=" + objectUUID.replaceAll(".", "-"));
        continue;
      }
      const isLinkedToken = object instanceof TokenDocument && object.actorLink;
      const isLinkedActor = object instanceof Actor && object.prototypeToken.actorLink;
      let toAdd = flagsToAdd.get(objectUUID) ?? { effects: [], sounds: [] };
      let toRemove = flagsToRemove.get(objectUUID) ?? {
        effects: [],
        removeAllEffects: false,
        sounds: [],
        removeAllSounds: false
      };
      let origExistingEffectFlags = allEffectFlags[object.uuid] ?? [];
      if (isLinkedToken) {
        origExistingEffectFlags = origExistingEffectFlags.concat(allEffectFlags[object?.actor?.uuid] ?? []);
      }
      const existingEffectFlags = new Map(origExistingEffectFlags);
      let origExistingSoundFlags = allSoundFlags[object.uuid] ?? [];
      if (isLinkedToken) {
        origExistingSoundFlags = origExistingSoundFlags.concat(allSoundFlags[object?.actor?.uuid] ?? []);
      }
      const existingSoundFlags = new Map(origExistingSoundFlags);
      if (toRemove?.removeAllEffects) {
        toRemove.effects = Array.from(existingEffectFlags).map((entry) => entry[0]);
      }
      if (toRemove?.removeAllSounds) {
        toRemove.sounds = Array.from(existingSoundFlags).map((entry) => entry[0]);
      }
      for (let effect2 of toAdd.effects) {
        if (typeof effect2 === "string") {
          effect2 = existingEffectFlags.get(effect2);
          if (!effect2) continue;
        }
        existingEffectFlags.set(effect2._id, effect2);
      }
      for (let sound of toAdd.sounds) {
        if (typeof sound === "string") {
          sound = existingSoundFlags.get(sound);
          if (!sound) continue;
        }
        existingSoundFlags.set(sound._id, sound);
      }
      for (let effect2 of toRemove.effects) {
        if (typeof effect2 === "string") {
          effect2 = existingEffectFlags.get(effect2);
          if (!effect2) continue;
        }
        existingEffectFlags.delete(effect2._id);
      }
      for (let sound of toRemove.sounds) {
        if (typeof sound === "string") {
          sound = existingSoundFlags.get(sound);
          if (!sound) continue;
        }
        existingSoundFlags.delete(sound._id);
      }
      let effectFlagsToSet = Array.from(existingEffectFlags);
      let soundFlagsToSet = Array.from(existingSoundFlags);
      let actorEffectFlags = [];
      let actorSoundFlags = [];
      let actor = object?.actor ?? object;
      if (actor instanceof foundry.documents.Actor && (isLinkedToken || isLinkedActor) && (toAdd.original || toRemove.original)) {
        actorEffectFlags = effectFlagsToSet.filter(
          (effect2) => is_UUID(effect2[1]?.source) && effect2[1]?.attachTo?.active && effect2[1]?.persistOptions?.persistTokenPrototype
        );
        effectFlagsToSet = effectFlagsToSet.filter(
          (effect2) => !effect2[1]?.persistOptions?.persistTokenPrototype || !is_UUID(effect2[1]?.source) || !effect2[1]?.attachTo?.active
        );
        actorSoundFlags = soundFlagsToSet.filter(
          (sound) => is_UUID(sound[1]?.source) && sound[1]?.attachTo?.active && sound[1]?.persistOptions?.persistTokenPrototype
        );
        soundFlagsToSet = soundFlagsToSet.filter(
          (sound) => !sound[1]?.persistOptions?.persistTokenPrototype || !is_UUID(sound[1]?.source) || !sound[1]?.attachTo?.active
        );
      }
      let objectSanitizedUuid = object.uuid.replaceAll(".", "-");
      if (effectFlagsToSet.length) {
        effectUpdate[objectSanitizedUuid] = effectFlagsToSet;
      } else {
        forceDeletionKeyWrapper(effectUpdate, "-=" + objectSanitizedUuid);
      }
      if (soundFlagsToSet.length) {
        soundUpdate[objectSanitizedUuid] = soundFlagsToSet;
      } else {
        forceDeletionKeyWrapper(soundUpdate, "-=" + objectSanitizedUuid);
      }
      if (actor instanceof foundry.documents.Actor) {
        let actorSanitizedUuid = actor.uuid.replaceAll(".", "-");
        if (actorEffectFlags.length) {
          effectUpdate[actorSanitizedUuid] = actorEffectFlags;
        } else {
          forceDeletionKeyWrapper(effectUpdate, "-=" + actorSanitizedUuid);
        }
        if (actorSoundFlags.length) {
          soundUpdate[actorSanitizedUuid] = actorSoundFlags;
        } else {
          forceDeletionKeyWrapper(soundUpdate, "-=" + actorSanitizedUuid);
        }
      }
    }
    await database.update({
      [CONSTANTS.EFFECTS_FLAG]: effectUpdate,
      [CONSTANTS.SOUNDS_FLAG]: soundUpdate
    });
  }, 250),
  soundMigrations: {},
  /**
   * Sanitizes the sound data, accounting for changes to the structure in previous versions
   *
   * @param inDocument
   * @returns {array}
   */
  getSoundFlags(inDocument) {
    if (!inDocument?.uuid) return [];
    let sounds = this.getDatabaseFlags().sounds[inDocument.uuid] ?? [];
    if (!sounds?.length) return [];
    sounds = foundry.utils.deepClone(sounds);
    const changes = [];
    for (let [soundId, soundData] of sounds) {
      let soundVersion = soundData?.flagVersion ?? "1.0.0";
      if (soundData.flagVersion === this.latestFlagVersion) continue;
      for (let [version, migration] of Object.entries(this.soundMigrations)) {
        if (!foundry.utils.isNewerVersion(version, soundVersion)) continue;
        soundData = migration(inDocument, soundData);
      }
      debug(
        `Migrated sound with ID ${soundId} from version ${soundVersion} to version ${this.latestFlagVersion}`
      );
      soundData.flagVersion = this.latestFlagVersion;
      changes.push(soundData);
    }
    if (changes.length) {
      flagManager.addFlags(inDocument.uuid, { sounds: changes });
    }
    return sounds;
  }
};
function smoothstep(edge_0, edge_1, value) {
  const t = clamp((value - edge_0) / (edge_1 - edge_0), 0, 1);
  return t * t * (3 - 2 * t);
}
function ease_pan_value(pan_value, power) {
  const sign = Math.sign(pan_value);
  const absolute_value = Math.min(1, Math.abs(pan_value));
  return sign * Math.pow(absolute_value, power);
}
function calculate_pan_factor(ray_distance_pixels, inner_radius_pixels, outer_radius_pixels) {
  if (inner_radius_pixels > 0 && ray_distance_pixels <= inner_radius_pixels || outer_radius_pixels <= inner_radius_pixels) {
    return 0;
  }
  const t = (ray_distance_pixels - inner_radius_pixels) / (outer_radius_pixels - inner_radius_pixels);
  return smoothstep(0, 1, t);
}
const PASTE_BATCH_STALE_MS = 5e3;
const PasteManager = {
  userId: null,
  documentName: null,
  sourceUuids: null,
  cursor: 0,
  capturedAt: 0,
  consumed: /* @__PURE__ */ new WeakMap(),
  setup() {
    for (const documentName of ["Token", "Drawing", "Tile", "MeasuredTemplate", "Region"]) {
      Hooks.on(`paste${documentName}`, (objects, data, opts) => {
        if (opts?.cut) return;
        this.capture(documentName, objects.map((o) => o.document.uuid));
      });
    }
  },
  /**
   * Records the source UUIDs of a paste operation on the originating client, keyed by document type and
   * cursor position. Consumed in lockstep order by the matching createDocumentName hook handlers.
   *
   * @param {string} documentName
   * @param {string[]} sourceUuids
   */
  capture(documentName, sourceUuids) {
    this.userId = game.user.id;
    this.documentName = documentName;
    this.sourceUuids = sourceUuids;
    this.cursor = 0;
    this.capturedAt = Date.now();
  },
  /**
   * Returns the source UUID corresponding to the given newly-created document, or null if no paste batch is
   * available for this client and document type. The result is memoized per document instance, so multiple
   * managers (effect and sound) hooking the same createDocumentName event resolve to the same source UUID.
   *
   * @param {foundry.abstract.Document} inDocument
   * @returns {string|null}
   */
  consume(inDocument) {
    if (this.consumed.has(inDocument)) return this.consumed.get(inDocument);
    if (!this.sourceUuids) return null;
    if (this.userId !== game.user.id) return null;
    if (this.documentName !== inDocument.documentName) return null;
    if (Date.now() - this.capturedAt > PASTE_BATCH_STALE_MS) {
      this.sourceUuids = null;
      return null;
    }
    if (this.cursor >= this.sourceUuids.length) {
      this.sourceUuids = null;
      return null;
    }
    const sourceUuid = this.sourceUuids[this.cursor++];
    this.consumed.set(inDocument, sourceUuid);
    return sourceUuid;
  }
};
const SOUND_STATES = {
  STARTING: 0,
  PLAYING: 1,
  ENDING: 2,
  ENDED: 3
};
const global_sound_mixin = (base_class) => class extends base_class {
  get playData() {
    return {
      ...this.data.locationOptions,
      ...this.playbackOptions,
      volume: this.data.fadeIn ? 0 : this.data.volume
    };
  }
  async playSound() {
    this.sound.play(this.playData);
  }
};
const placed_sound_mixin = (base_class) => class extends base_class {
  movementDuration = 0;
  constructor(...args) {
    super(...args);
    if (this.isSourceDestroyed) {
      this.stop();
    }
  }
  get volume_property() {
    return "volume_multiplier";
  }
  async load() {
    await super.load();
    this.movementDuration = this.totalDuration;
    if (this.moveTowards) {
      if (this.data.moveSpeed) {
        const distance = distance_between(
          this.sourcePosition,
          this.targetPosition
        );
        this.movementDuration = distance / this.data.moveSpeed * 1e3;
      }
      if (this.creationTimeDelta > this.movementDuration) {
        this.position = this.targetPosition;
      }
    }
  }
  async playSound() {
    await this.sound.playAtPosition(this.position, this.data.locationOptions?.radius || 1, this.playData);
  }
  update() {
    if (this.state !== SOUND_STATES.PLAYING) return;
    let volume = this.data.volume ?? 0.8;
    let {
      easing = true,
      walls = false,
      gmAlways = false,
      sourceData,
      radius = 1,
      muffledEffect = { type: "lowpass", intensity: 5 }
    } = this.data.locationOptions ?? {};
    let sourcePosition = this.sourcePosition;
    sourceData ??= this._emptySourceData ??= {};
    const resolvedSourceData = this._resolveLevelForSourceData(sourceData, sourcePosition);
    const source2 = new CONFIG.Canvas.soundSourceClass({ object: null });
    source2.initialize({
      x: sourcePosition.x,
      y: sourcePosition.y,
      elevation: sourcePosition.elevation ?? 0,
      radius: canvas.dimensions.distancePixels * radius,
      walls,
      ...resolvedSourceData
    });
    const config = { sound: this.sound, source: source2, listener: void 0, volume: 0, walls, muffled: false, pan: 0 };
    const listeners2 = gmAlways && game.user.isGM ? [sourcePosition] : canvas.sounds.getListenerPositions();
    for (const l of listeners2) {
      const v = volume * source2.getVolumeMultiplier(l, { easing });
      Object.assign(config, { listener: l, volume: v });
      let ray = new foundry.canvas.geometry.Ray(l, sourcePosition);
      if (this.data.panSound?.active && ray.distance > 0) {
        const normalized_x = ray.dx / ray.distance;
        const panned_value = ease_pan_value(normalized_x, 1.6);
        let pan_ease_factor = 1;
        if (this.data.panSound?.innerEaseDistance > 0) {
          const inner_ease_distance = this.data.panSound?.innerEaseDistance || 0;
          const outer_ease_distance = this.data.panSound?.outerEaseDistance || Math.min(inner_ease_distance * 2, this.data.locationOptions?.radius ?? 1);
          const inner_ease_distance_pixels = inner_ease_distance / canvas.grid.distance * canvas.grid.size;
          const outer_ease_distance_pixels = outer_ease_distance / canvas.grid.distance * canvas.grid.size;
          pan_ease_factor = calculate_pan_factor(ray.distance, inner_ease_distance_pixels, outer_ease_distance_pixels);
        }
        config.pan = panned_value * pan_ease_factor;
      }
    }
    canvas.sounds._configurePlayback(config);
    let applyEffect = false;
    let hasMuffledEffect = this.sound.effects.findIndex((e) => e.type === muffledEffect.type);
    if (hasMuffledEffect > -1 && !config.muffled) {
      this.sound.effects[hasMuffledEffect].disconnect();
      this.sound.effects.splice(hasMuffledEffect, 1);
      applyEffect = true;
    } else if (hasMuffledEffect === -1 && config.muffled) {
      const sfx = CONFIG.soundEffects;
      let effect2;
      if (config.muffled && muffledEffect?.type in sfx) {
        const muffledCfg = sfx[muffledEffect.type];
        effect2 = new muffledCfg.effectClass(this.sound.context, muffledEffect);
        this.sound.effects.push(effect2);
      }
      applyEffect = true;
    }
    let hasPanner = this.sound.effects.findIndex((e) => e.type === "panner");
    if (this.data.panSound) {
      this.panner ||= this.sound.context.createStereoPanner();
      this.panner.type = "panner";
      if (hasPanner === -1) {
        this.sound.effects.push(this.panner);
        applyEffect = true;
      }
    }
    if (applyEffect) {
      this.sound.applyEffects(this.sound.effects);
    }
    if (this.data.attachTo?.active) {
      if (this.sourceDocument.hidden) {
        config.volume = 0;
      }
    }
    this.sound.volume = config.volume * this.sound.volume_multiplier;
    if (this.data.panSound) {
      const t = this.sound.context.currentTime;
      this.panner.pan.cancelScheduledValues(t);
      this.panner.pan.setValueAtTime(this.panner.pan.value, t);
      this.panner.pan.linearRampToValueAtTime(config.pan, t + 0.03);
    }
  }
  get playData() {
    let volume = this.data.volume ?? 0.8;
    if (this.data.attachTo?.active) {
      if (this.sourceDocument.hidden) {
        volume = 0;
      }
    }
    const baseSourceData = this.data.locationOptions?.sourceData ?? (this._emptySourceData ??= {});
    const sourceData = this._resolveLevelForSourceData(baseSourceData, this.sourcePosition);
    return {
      easing: true,
      walls: false,
      gmAlways: false,
      radius: 1,
      muffledEffect: { type: "lowpass", intensity: 5 },
      ...this.data.locationOptions,
      sourceData,
      playbackOptions: this.playbackOptions,
      volume
    };
  }
  /**
   * Merge a scene level id into the PointSoundSource init payload
   * so Foundry's cross-level attenuation works. Honors `.onLevels()`.
   *
   * Called per frame for moving sounds via the update ticker, so
   * the result is memoized on `(baseSourceData ref, elevation)`.
   * The `.onLevels()` restriction Set is built once and resolves
   * names against the scene the sound is playing in.
   *
   * @param {object} baseSourceData
   * @param {{elevation?: number}|undefined} position
   * @returns {object}
   * @private
   */
  _resolveLevelForSourceData(baseSourceData, position) {
    if (!CONSTANTS.IS_V14) return baseSourceData;
    if (baseSourceData?.level !== void 0) return baseSourceData;
    if (typeof canvas?.inferLevelFromElevation !== "function") return baseSourceData;
    const elevation2 = position?.elevation ?? 0;
    if (this._levelResolvedSourceData && this._levelResolvedBase === baseSourceData && this._levelResolvedElevation === elevation2) {
      return this._levelResolvedSourceData;
    }
    if (this._levelRestrictionSet === void 0) {
      this._levelRestrictionSet = this._buildLevelRestrictionSet();
    }
    const level = canvas.inferLevelFromElevation(elevation2, {
      levels: this._levelRestrictionSet || void 0
    });
    const resolved = level ? { ...baseSourceData, level: level.id } : baseSourceData;
    this._levelResolvedBase = baseSourceData;
    this._levelResolvedElevation = elevation2;
    this._levelResolvedSourceData = resolved;
    return resolved;
  }
  /**
   * Resolve `.onLevels()` entries (ids or names) to a Set of ids
   * against the scene the sound is playing in. Returns null when
   * no restriction is set, an empty Set when entries were given
   * but none resolved (so Foundry sees no allowed levels).
   *
   * @returns {Set<string>|null}
   * @private
   */
  _buildLevelRestrictionSet() {
    const entries = this.data.levels;
    if (!entries?.length) return null;
    const sceneLevels = canvas?.scene?.levels;
    const ids = /* @__PURE__ */ new Set();
    for (const entry of entries) {
      if (sceneLevels?.get(entry)) {
        ids.add(entry);
        continue;
      }
      const byName = sceneLevels?.getName?.(entry);
      if (byName) ids.add(byName.id);
    }
    return ids;
  }
  animate() {
    super.animate();
    canvas.app.ticker.add(this.update, this);
    if (this.data.moveTowards && this.creationTimeDelta <= this.movementDuration) {
      const sourcePos = this.sourcePosition;
      const targetPos = this.targetPosition;
      SequencerAnimationEngine.addAnimation(this.data._id, {
        target: this,
        propertyName: "position.x",
        from: sourcePos.x,
        to: targetPos.x,
        duration: this.movementDuration,
        ease: this.data.moveTowards.ease ?? "linear",
        delay: 0,
        absolute: true
      });
      SequencerAnimationEngine.addAnimation(this.data._id, {
        target: this,
        propertyName: "position.y",
        from: sourcePos.y,
        to: targetPos.y,
        duration: this.movementDuration,
        ease: this.data.moveTowards.ease ?? "linear",
        delay: 0,
        absolute: true
      });
    }
  }
  stop() {
    canvas.app.ticker.remove(this.update, this);
    if (this.panner) {
      this.panner.disconnect();
    }
    super.stop();
  }
};
const persistent_sound_mixin = (base_class) => class extends base_class {
  stop() {
    flagManager.removeFlags(this.context.uuid, {
      sounds: this.data
    });
    let extraEndDuration = this.data.extraEndDuration ?? 0;
    let endDuration = extraEndDuration + this.fadeOut();
    setTimeout(() => {
      SequenceManager.RunningSounds.delete(this.id);
      super.stop();
    }, endDuration);
  }
  fadeIn() {
    let fadeInDuration = Math.min(this.data.fadeIn.duration, this.totalDuration);
    if (this.creationTimeDelta <= fadeInDuration) {
      super.fadeIn();
    }
  }
  get startOffset() {
    let offset2 = this.startTime ?? 0;
    if (this.creationTimeDelta > this.soundDuration && this.state === SOUND_STATES.STARTING) {
      offset2 += this.creationTimeDelta % this.duration;
    }
    return offset2;
  }
  get loopDuration() {
    if (this.state === SOUND_STATES.STARTING) {
      return super.loopDuration - this.startOffset;
    }
    return super.loopDuration;
  }
  get isLoopingSound() {
    return true;
  }
};
class SequencerSound {
  /**
   * @property {string} id - Unique identifier for the sound.
   */
  id;
  /**
   * @property {Object} data - The data associated with the sound.
   */
  data;
  /**
   * @property {Object} twister - Mersenne Twister instance for random number generation.
   */
  twister;
  /**
   * @property {number} actualCreationTime - The actual creation time of the sound.
   */
  actualCreationTime;
  /**
   * @property {number} creationTimeDelta - The difference in time since the sound was created and when it started playing.
   */
  creationTimeDelta;
  /**
   * @property {number} state - The current state of the sound.
   * Possible values are:
   * - 0: STARTING - The sound is in the process of starting.
   * - 1: PLAYING - The sound is currently playing.
   * - 2: ENDING - The sound is in the process of ending.
   * - 3: ENDED - The sound has finished playing.
   */
  state;
  /**
   * @property {Object|null} sound - The sound object instance.
   */
  sound;
  /**
   * @property {number|null} totalDuration - The total playback duration of the sound.
   */
  totalDuration;
  /**
   * @property {number|null} soundDuration - The duration of the actual sound file.
   */
  soundDuration;
  /**
   * @property {number|null} startTime - The start time of the sound in milliseconds.
   */
  startTime;
  /**
   * @property {number|null} endTime - The end time of the sound in milliseconds.
   */
  endTime;
  /**
   * @private
   * @property {Object|null} #file - The file metadata associated with the sound.
   */
  #file;
  /**
   * @property {boolean} shouldPlaySound - Whether the sound should play based on user settings.
   */
  shouldPlaySound;
  /**
   * @property {Object|null} panner - Stereo panner node for sound effects.
   */
  panner;
  /**
   * @property {number} loops - Number of loops the sound is configured to play.
   */
  loops;
  /**
   * @property {number} loopDelay - Delay between loops in milliseconds.
   */
  loopDelay;
  /**
   * @property {number} currentLoop - The current loop count.
   */
  currentLoop = 1;
  /**
   * @property {Object|null} _sourcePosition - The source position of the sound on canvas.
   */
  _sourcePosition;
  /**
   * @property {Object|null} _targetPosition - The target position of the sound on canvas.
   */
  _targetPosition;
  /**
   * @property {Object|null} loopTimes - Information about the loop's start and end times.
   */
  loopTimes;
  /**
   * @property {Object|null} position - The current playback position of the sound.
   */
  position;
  /**
   * The resolve method for the promise of the sound finishing;
   */
  #resolve;
  constructor(data) {
    this.id = data._id;
    this.data = data;
    this.twister = createMersenneTwister(data.seed);
    this.actualCreationTime = +/* @__PURE__ */ new Date();
    this.creationTimeDelta = this.actualCreationTime - this.data.creationTimestamp;
    this.state = SOUND_STATES.STARTING;
    this.shouldPlaySound = game.settings.get("sequencer", "soundsEnabled") && game.user.viewedScene === data.sceneId && (!data?.users?.length || data?.users?.includes(game.userId));
    this.panner = null;
    this.totalLoops = this.data.loopOptions.loops ?? 0;
    this.delayBetweenLoops = this.data.loopOptions.loopDelay ?? 0;
    this.position = this.sourcePosition;
  }
  static make(data) {
    if (data.persist && data.source && !data.global) {
      return new SequencerPersistentPlacedSound(data);
    } else if (data.source && !data.global) {
      return new SequencerPlacedSound(data);
    } else if (data.persist) {
      return new SequencerPersistentGlobalSound(data);
    }
    return new SequencerGlobalSound(data);
  }
  get sourcePosition() {
    if (this.data.attachTo?.active) {
      if (!this.isSourceDestroyed) {
        let position = get_object_position(this.source);
        let offset2 = getOffsetFromData(this.data, { type: "source", twister: this.twister });
        let elevation2 = this.data.attachTo.bindElevation ? position.elevation ?? 0 : 0;
        this._sourcePosition = {
          x: position.x - offset2.x,
          y: position.y - offset2.y,
          elevation: this._applyElevationOverride(elevation2)
        };
      }
    } else if (!this._sourcePosition) {
      const fromData = this.data.source ? getPositionFromData(this.data, "source", this.twister) : false;
      if (fromData) {
        fromData.elevation = this._applyElevationOverride(fromData.elevation ?? 0);
      }
      this._sourcePosition = fromData;
    }
    return this._sourcePosition;
  }
  _applyElevationOverride(baseElevation) {
    const override = this.data.elevation;
    if (!override) return baseElevation;
    if (override.absolute) return override.elevation ?? 0;
    return baseElevation + (override.elevation ?? 0);
  }
  get targetPosition() {
    if (!this._targetPosition) {
      this._targetPosition = this.data.target ? getPositionFromData(this.data, "target", this.twister) : false;
    }
    return this._targetPosition;
  }
  _validateObject(inObject) {
    if (is_UUID(inObject) || !is_object_canvas_data(inObject)) {
      inObject = get_object_from_scene(inObject, this.data.sceneId);
      inObject = inObject?._object ?? inObject;
    }
    return inObject;
  }
  _getObjectByID(inIdentifier, specific = false, returnSource = false) {
    let source2 = inIdentifier;
    let offsetMap = this._nameOffsetMap?.[inIdentifier];
    if (offsetMap) {
      if (specific) {
        source2 = (returnSource ? offsetMap?.sourceObj || offsetMap?.targetObj : offsetMap?.targetObj || offsetMap?.sourceObj) || source2;
      } else {
        source2 = offsetMap?.targetObj || offsetMap?.sourceObj || source2;
      }
    } else {
      source2 = this._validateObject(source2);
    }
    return source2;
  }
  /**
   * Whether the source of this sound has been destroyed
   *
   * @returns {boolean}
   */
  get isSourceDestroyed() {
    return this.source && this.source?.destroyed && (!this.sourceDocument?.object || this.sourceDocument?.object?.destroyed || this.source.constructor.name === "Crosshairs");
  }
  get source() {
    if (!this._source && this.data.source) {
      const getDifferentTarget = this.data.source === this.data.target;
      this._source = this._getObjectByID(this.data.source?.uuid ?? this.data.source, getDifferentTarget, true) ?? this.data.source;
      this._source = this._source?._object ?? this._source;
    }
    return this._source;
  }
  get sourceDocument() {
    return this.source?.document ?? this.source;
  }
  get target() {
    if (!this._target && this.data.target) {
      const getDifferentTarget = this.data.source === this.data.target;
      this._target = this._getObjectByID(this.data.target?.uuid ?? this.data.target, getDifferentTarget, false) ?? this.data.target;
      this._target = this._target?._object ?? this._target;
    }
    return this._target;
  }
  get targetDocument() {
    return this.target?.document ?? this.target;
  }
  get context() {
    return this.data.attachTo?.active && this.sourceDocument ? this.sourceDocument : game.scenes.get(this.data.sceneId);
  }
  static checkShouldPlay(data) {
    return !data.local || data.creatorUserId === game.user.id;
  }
  static checkValid(data) {
    if (data.delete) {
      return false;
    }
    let sourceExists = true;
    let targetExists = true;
    if (data.source && is_UUID(data.source)) {
      sourceExists = fromUuidSync(data.source);
    }
    if (data.target && is_UUID(data.target)) {
      targetExists = fromUuidSync(data.target);
    }
    for (let tiedDocumentUuid of data?.tiedDocuments ?? []) {
      if (tiedDocumentUuid && is_UUID(tiedDocumentUuid)) {
        let tiedDocumentExists = fromUuidSync(tiedDocumentUuid);
        if (!tiedDocumentExists) return false;
      }
    }
    if (data.source && is_UUID(data.source) && data.target && is_UUID(data.target)) {
      const sourceScene = data.source.split(".")[1];
      const targetScene = data.target.split(".")[1];
      if (sourceScene !== targetScene || sourceScene !== data.sceneId)
        return false;
    }
    return sourceExists && targetExists;
  }
  get startOffset() {
    return this.startTime ?? 0;
  }
  get isLoopingSound() {
    return this.loopTimes !== void 0 || this.totalLoops && this.currentLoop < this.totalLoops;
  }
  get looping() {
    return this.isLoopingSound && this.state === SOUND_STATES.PLAYING;
  }
  get playbackOptions() {
    let loopStart = this.startTime ?? 0;
    let loopEnd = this.endTime ?? 0;
    let offset2 = this.startOffset / 1e3;
    debug("SOUND DEBUG", [this, this.currentLoop, Object.keys(SOUND_STATES)[this.state]]);
    if (this.loopTimes && this.isLoopingSound) {
      if (this.state === SOUND_STATES.PLAYING) {
        loopStart = this.loopTimes.loopStart;
        offset2 = this.loopTimes.loopStart;
      }
      if (this.state === SOUND_STATES.STARTING || this.state === SOUND_STATES.PLAYING) {
        loopEnd = this.loopTimes.loopEnd;
      }
      if (this.state === SOUND_STATES.ENDING) {
        loopStart = this.loopTimes.forcedEnd || this.loopTimes.loopEnd;
      }
    }
    return {
      offset: offset2,
      loop: this.looping,
      loopStart: loopStart / 1e3,
      loopEnd: loopEnd / 1e3,
      channel: this.data.channel || "interface",
      delay: 0
    };
  }
  async load() {
    if (!this.sound) {
      let placedOnCanvas = this.data.source && !this.data.global;
      let sound = new foundry.audio.Sound(this.getFile(), {
        context: placedOnCanvas ? game.audio.environment : game.audio.interface
      });
      await sound.load();
      this.sound = sound;
    }
    this.soundDuration = this.sound.duration * 1e3;
    this.startTime = 0;
    this.endTime = this.soundDuration;
    if (this.data.time?.start) {
      if (this.data.time.start.isPerc) {
        this.startTime = this.data.time.start.value * this.soundDuration;
      } else {
        this.startTime = this.data.time.start.value;
      }
    }
    if (this.data.time?.end) {
      if (this.data.time.end.isPerc) {
        this.endTime = this.soundDuration - this.soundDuration * this.data.time.end.value;
      } else {
        this.endTime = this.data.time.isRange ? this.data.time.end.value : this.soundDuration - this.data.time.end.value;
      }
    }
    this.duration = this.data.duration === false ? this.endTime - this.startTime : this.data.duration;
    this.totalDuration = this.duration;
    if (this.totalLoops && !this.data.duration) {
      this.totalDuration += this.delayBetweenLoops;
      this.totalDuration *= this.totalLoops + 1;
    }
    if (this.file?.markers && this.startTime === 0 && this.endTime === this.duration) {
      this.loopTimes = {
        loopStart: this.file.markers.loop.start,
        loopEnd: this.file.markers.loop.end,
        forcedEnd: this.file.markers.forcedEnd
      };
    }
    this.sound.data = this.data;
    this.sound.sound_id = this.data._id;
    this.sound.loop = this.totalDuration > this.soundDuration && !(this.totalLoops && this.delayBetweenLoops);
    this.sound.volume_multiplier = 1;
    this.sound.volume = this.data.volume * this.sound.volume_multiplier;
  }
  get volume_property() {
    return "volume";
  }
  get file() {
    if (this.#file) {
      return this.#file;
    }
    let file;
    if (this.data.customRange) {
      const template = this.data.template ? [this.data.template.gridSize, this.data.template.startPoint, this.data.template.endPoint] : [100, 0, 0];
      file = SequencerFileBase.make(
        this.data.file,
        "temporary.range.file",
        { template }
      );
    } else if (Sequencer.Database.entryExists(this.data.file)) {
      file = Sequencer.Database.getEntry(this.data.file).clone();
    } else {
      file = SequencerFileBase.make(this.data.file);
    }
    if (is_real_number(this.data.forcedIndex)) {
      file.fileIndex = this.data.forcedIndex;
    }
    this.#file = file;
    return file;
  }
  getFile() {
    if (this.file.rangeFind && this.source && this.target) {
      let distance = new foundry.canvas.geometry.Ray(this.sourcePosition, this.targetPosition).distance;
      return this.file.getFileForDistance(distance);
    }
    return this.file.getFile();
  }
  async playSound() {
    throw new Error("NotImplementedError: playSound() must be implemented by a subclass of SequencerSound.");
  }
  startPlay() {
    let { promise, resolve } = Promise.withResolvers();
    this.#resolve = resolve;
    this.play();
    return promise;
  }
  async play() {
    if (this.state === SOUND_STATES.ENDED) {
      return;
    }
    if (!this.shouldPlaySound) {
      this.state = SOUND_STATES.ENDED;
      this.#resolve();
      return;
    }
    if (this.totalLoops && this.currentLoop > this.totalLoops) {
      return this.stop();
    }
    if (this.currentLoop === this.totalLoops) {
      this.state = SOUND_STATES.ENDING;
    }
    if (game.audio.locked) {
      setTimeout(() => this.play(), 100);
      return;
    }
    await this.sound.stop();
    await this.playSound();
    if (!this.sound.gain) {
      setTimeout(() => this.play(), 100);
      return;
    }
    if (this.state === SOUND_STATES.STARTING) {
      this.animate();
    }
    if (this.isLoopingSound) {
      if (this.totalLoops) {
        this.currentLoop += 1;
      }
      setTimeout(this.play.bind(this), this.loopDuration);
    } else {
      setTimeout(() => {
        this.stop();
      }, this.duration);
    }
    if (this.state === SOUND_STATES.STARTING) {
      this.state = SOUND_STATES.PLAYING;
    }
  }
  get loopDuration() {
    let duration = this.duration;
    if (this.loopTimes) {
      if (this.state === SOUND_STATES.STARTING) {
        duration = this.loopTimes.loopEnd;
      } else if (this.state === SOUND_STATES.PLAYING) {
        duration = this.loopTimes.loopEnd - this.loopTimes.loopStart;
      } else if (this.state === SOUND_STATES.ENDING) {
        duration = this.soundDuration - this.loopTimes.loopEnd;
      }
    }
    return duration;
  }
  animate() {
    if (!this.shouldPlaySound) return;
    if (this.data.fadeIn) {
      this.fadeIn();
    }
    if (this.data.fadeOut) {
      this.fadeOut();
    }
  }
  stop() {
    if (this.loopTimes && this.state !== SOUND_STATES.ENDED) {
      this.state = SOUND_STATES.ENDING;
      let currentTime = this.sound.currentTime * 1e3;
      let lastLoopWaitDuration = currentTime > this.loopTimes.loopStart ? this.loopTimes.loopEnd - currentTime : this.loopTimes.loopStart - currentTime;
      if (this.loopTimes.forcedEnd) {
        this.play();
      } else {
        setTimeout(() => {
          this.play();
        }, lastLoopWaitDuration);
      }
      let stopWaitDuration = this.loopTimes.forcedEnd ? this.duration - this.loopTimes.forcedEnd : lastLoopWaitDuration + (this.duration - this.loopTimes.loopEnd);
      return setTimeout(() => {
        this.state = SOUND_STATES.ENDED;
        this.sound.stop();
        this.#resolve();
      }, stopWaitDuration);
    }
    this.state = SOUND_STATES.ENDED;
    this.sound.stop();
    this.#resolve();
  }
  fadeIn() {
    this.sound.volume = 0;
    SequencerAnimationEngine.addAnimation(this.data._id, {
      target: this.sound,
      propertyName: this.volume_property,
      from: 0,
      to: 1,
      duration: Math.min(this.data.fadeIn.duration, this.duration),
      ease: this.data.fadeIn.ease,
      delay: Math.min(this.data.fadeIn.delay, this.duration),
      absolute: true
    });
  }
  fadeOut(immediate = false) {
    if (!this.data.fadeOut) return 0;
    let duration = Math.min(this.data.fadeOut.duration, this.duration);
    let delay = is_real_number(immediate) ? Math.max(immediate - this.data.fadeOut.duration + this.data.fadeOut.delay, 0) : Math.max(this.totalDuration - this.data.fadeOut.duration + this.data.fadeOut.delay, 0);
    SequencerAnimationEngine.addAnimation(this.data._id, {
      target: this.sound,
      propertyName: this.volume_property,
      from: 1,
      to: 0,
      duration,
      ease: this.data.fadeOut.ease,
      delay,
      absolute: true
    });
    return duration + delay;
  }
  addFlags() {
    let uuid = this.context?.uuid;
    if (uuid) {
      flagManager.addFlags(uuid, { sounds: this.data });
    }
  }
}
class SequencerGlobalSound extends global_sound_mixin(SequencerSound) {
}
class SequencerPersistentGlobalSound extends global_sound_mixin(persistent_sound_mixin(SequencerSound)) {
}
class SequencerPlacedSound extends placed_sound_mixin(SequencerSound) {
}
class SequencerPersistentPlacedSound extends placed_sound_mixin(persistent_sound_mixin(SequencerSound)) {
}
class SequencerSoundManager {
  static states = SOUND_STATES;
  static setup() {
    Hooks.on("createToken", this._documentCreated.bind(this));
    Hooks.on("createDrawing", this._documentCreated.bind(this));
    Hooks.on("createTile", this._documentCreated.bind(this));
    Hooks.on("createMeasuredTemplate", this._documentCreated.bind(this));
    Hooks.on("createRegion", this._documentCreated.bind(this));
  }
  static _patchSoundDataForDocument(inDocumentUuid, sounds) {
    return sounds.map((sound) => {
      sound[0] = foundry.utils.randomID();
      const soundData = sound[1];
      soundData._id = sound[0];
      if (is_UUID(soundData.source)) {
        soundData.source = inDocumentUuid;
      }
      soundData.sceneId = inDocumentUuid.split(".")[1];
      return sound;
    });
  }
  /**
   * Plays the sounds of a given document on creation
   *
   * @param inDocument
   * @returns {*}
   */
  static async _documentCreated(inDocument) {
    let sounds = flagManager.getSoundFlags(inDocument);
    if (inDocument instanceof TokenDocument && inDocument?.actorLink) {
      let actorSounds = flagManager.getSoundFlags(inDocument?.actor);
      if (actorSounds.length) {
        actorSounds = this._patchSoundDataForDocument(
          inDocument.uuid,
          actorSounds
        );
      }
      sounds = sounds.concat(actorSounds);
    }
    const sourceUuid = PasteManager.consume(inDocument);
    if (sourceUuid) {
      const sourceSounds = flagManager.getSoundFlags({ uuid: sourceUuid });
      if (sourceSounds.length) {
        const rekeyed = this._patchSoundDataForDocument(inDocument.uuid, sourceSounds);
        const soundDatas = rekeyed.map(([, data]) => data);
        flagManager.addFlags(inDocument.uuid, { sounds: soundDatas });
        for (const soundData of soundDatas) {
          sequencerSocket.executeForOthers(SOCKET_HANDLERS.PLAY_SOUND, soundData, false);
        }
        sounds = sounds.concat(rekeyed);
      }
    }
    if (!sounds?.length) return;
    return this._playSoundsMap(sounds, inDocument);
  }
  /**
   * Sets up persisting sounds when the scene is first loaded
   *
   * @returns {promise}
   */
  static async initializePersistentSounds() {
    await this.tearDownPersistentSounds();
    let docSoundsMap = {};
    let soundsToRemove = {};
    let databaseSounds = foundry.utils.deepClone(flagManager.getDatabaseFlags().sounds);
    for (let [uuid, sounds] of Object.entries(databaseSounds)) {
      let doc = fromUuidSync(uuid);
      if (doc instanceof foundry.documents.Actor && doc.prototypeToken.actorLink) {
        for (let token of doc.getActiveTokens()) {
          let tokenSounds = foundry.utils.deepClone(sounds);
          docSoundsMap[token.document.uuid] = tokenSounds.map(([id, sound]) => {
            if (is_UUID(sound.source)) {
              sound.source = token.document.uuid;
            }
            sound.sceneId = canvas.scene.id;
            return [id, sound];
          });
        }
      } else if (doc) {
        docSoundsMap[uuid] = sounds;
      } else {
        soundsToRemove[uuid] = sounds;
      }
    }
    const promises = Object.entries(docSoundsMap).map(([uuid, sounds]) => {
      return this._playSoundsMap(sounds, fromUuidSync(uuid));
    }).flat();
    for (let [uuid, sounds] of Object.entries(soundsToRemove)) {
      flagManager.removeFlags(uuid, {
        sounds,
        removeAllSounds: true
      });
    }
    return Promise.all(promises).then(() => {
      Hooks.callAll("sequencerSoundManagerReady");
    });
  }
  /**
   * Tears down persisting sounds when the scene is unloaded
   */
  static tearDownPersistentSounds() {
    return Promise.allSettled(
      this.sounds.map((sound) => {
        SequenceManager.RunningSounds.delete(sound._id);
        return sound.stop();
      })
    );
  }
  /**
   * Plays multiple sounds at the same time
   *
   * @param inSounds
   * @param inDocument
   * @returns {Promise<{duration: Promise<number>, promise: Promise<void>}[]>}
   * @private
   */
  static _playSoundsMap(inSounds, inDocument) {
    if (inSounds instanceof Map) inSounds = Array.from(inSounds);
    return Promise.all(
      inSounds.map((sound) => {
        if (!SequencerSound.checkShouldPlay(sound[1])) return;
        if (!SequencerSound.checkValid(sound[1])) {
          if (!game.user.isGM) return;
          custom_warning(
            `Sequencer`,
            `Removed sound from ${inDocument.uuid} as it no longer had a valid source or target`
          );
          return flagManager.removeFlags(inDocument.uuid, { sounds: sound });
        }
        return this._playSound(sound[1], false).then((result) => {
          if (!result) {
            debug("Error playing sound");
          }
        }).catch((err) => {
          debug("Error playing sound:", err);
        });
      })
    );
  }
  /**
   * Returns all the currently running sounds
   *
   * @returns {Array}
   */
  static get sounds() {
    return Array.from(SequenceManager.RunningSounds.values());
  }
  /**
   * Opens the Sequencer Manager with the sounds tab open
   */
  static show() {
    return EffectsUIApp.show({ tab: "manager" });
  }
  /**
   * Play an audio file.
   *
   * @param {Object} data The data that describes the audio to play.
   * @param {boolean} [push=false] A flag indicating whether to make other clients play the audio, too.
   * @returns {Number} A promise that resolves when the audio file has finished playing.
   */
  static async play(data, push2 = true) {
    if (push2)
      sequencerSocket.executeForOthers(SOCKET_HANDLERS.PLAY_SOUND, data);
    return this._playSound(data);
  }
  /**
   * @param {Object} data
   * @param {Boolean} setFlags
   * @returns {Number}
   * @private
   */
  static async _playSound(data, setFlags = true) {
    if (data.delete) return false;
    Hooks.callAll("createSequencerSound", data);
    debug(`Playing sound:`, data);
    let sequencerSound = SequencerSound.make(data);
    await sequencerSound.load();
    SequenceManager.RunningSounds.add(sequencerSound.id, sequencerSound);
    let promise = sequencerSound.startPlay();
    promise.then(() => {
      SequenceManager.RunningSounds.delete(sequencerSound.id);
      Hooks.callAll("endedSequencerSound", data);
    });
    if (data.persist) {
      let responsibleUser = game.users.getDesignatedUser((user) => user.active);
      if (responsibleUser === game.user && setFlags) {
        sequencerSound.addFlags();
      }
    }
    return sequencerSound.duration;
  }
  static _validateFilters(inFilter) {
    if (inFilter?.sounds) {
      if (!Array.isArray(inFilter.sounds)) {
        inFilter.sounds = [inFilter.sounds];
      }
      inFilter.sounds = inFilter.sounds.map((sound) => {
        if (!(typeof sound === "string" || sound instanceof SequencerSound))
          throw custom_error(
            "Sequencer",
            "SoundManager | collections in inFilter.sounds must be of type string or SequencerSound"
          );
        if (sound instanceof SequencerSound) return sound.data._id;
        return sound;
      });
    }
    if (inFilter?.name && typeof inFilter?.name !== "string")
      throw custom_error(
        "Sequencer",
        "SoundManager | inFilter.name must be of type string"
      );
    if (inFilter?.sceneId) {
      if (typeof inFilter.sceneId !== "string")
        throw custom_error(
          "Sequencer",
          "SoundManager | inFilter.sceneId must be of type string"
        );
      if (!game.scenes.get(inFilter.sceneId))
        throw custom_error(
          "Sequencer",
          "SoundManager | inFilter.sceneId must be a valid scene id (could not find scene)"
        );
    } else {
      inFilter.sceneId = game.user.viewedScene;
    }
    if (inFilter?.origin && typeof inFilter?.origin !== "string")
      throw custom_error(
        "Sequencer",
        "SoundManager | inFilter.origin must be of type string"
      );
    if (!inFilter.sounds && !inFilter.name && !inFilter.sceneId && !inFilter.origin) {
      return false;
    }
    return foundry.utils.mergeObject(
      {
        sounds: null,
        name: null,
        sceneId: null,
        origin: null
      },
      inFilter
    );
  }
  static _filterSounds(inFilter) {
    if (inFilter.name) {
      inFilter.name = new RegExp(
        "^" + str_to_search_regex_str(safe_str(inFilter.name)) + "$",
        "gu"
      );
    }
    return this.sounds.filter((sound) => {
      return (inFilter.sounds === null || inFilter.sounds.includes(sound.data._id)) && (inFilter.name === null || sound.data.name && inFilter.name && sound.data.name.match(inFilter.name)?.length) && (inFilter.sceneId === null || sound.data.sceneId === inFilter.sceneId) && (inFilter.origin === null || inFilter.origin === sound.data.origin);
    });
  }
  static getSounds(inFilter = {}) {
    const filters2 = this._validateFilters(inFilter);
    if (!inFilter)
      throw custom_error(
        "Sequencer",
        "SoundManager | getSounds | Incorrect or incomplete parameters provided"
      );
    return this._filterSounds(filters2);
  }
  static endSounds(inFilter, push2 = true) {
    const filters2 = this._validateFilters(inFilter);
    const sounds = this._filterSounds(filters2);
    if (!sounds?.length) return;
    const ids = sounds.map((sound) => sound.data._id);
    if (push2) {
      sequencerSocket.executeForOthers(SOCKET_HANDLERS.END_SOUNDS, ids);
    }
    return this._endSounds(ids);
  }
  /**
   * @param inSoundIds
   * @private
   */
  static _endSounds(inSoundIds) {
    const actorSoundsToEnd = this.sounds.filter((sound) => {
      return sound.context?.actorLink && inSoundIds.includes(sound.data?.persistOptions?.id);
    });
    const soundsByActorUuid = Object.values(
      group_by(actorSoundsToEnd, "context.actor.uuid")
    );
    const regularSoundsToEnd = this.sounds.filter((sound) => {
      return inSoundIds.includes(sound.id) || !sound.context?.actorLink && inSoundIds.includes(sound.data?.persistOptions?.id);
    });
    const soundsByContextUuid = Object.values(
      group_by(regularSoundsToEnd, "context.uuid")
    );
    soundsByContextUuid.forEach((sounds) => {
      sounds = sounds.filter(
        (sound) => sound.data.persist && !sound.data.temporary
      );
      if (!sounds.length) return;
      const soundData = sounds.map((sound) => sound.data);
      flagManager.removeFlags(
        sounds[0].context.uuid,
        { sounds: soundData, removeAllSounds: !inSoundIds }
      );
    });
    soundsByActorUuid.forEach((sounds) => {
      sounds = sounds.filter(
        (sound) => sound.data.persist && !sound.data.temporary
      );
      if (!sounds.length) return;
      const soundContext = sounds[0].context;
      const soundData = sounds.map((sound) => sound.data);
      if (!(soundContext instanceof TokenDocument && soundContext.actorLink && soundContext.actor?.prototypeToken?.actorLink)) {
        return;
      }
      const persistentSoundData = soundData.filter(
        (data) => is_UUID(data?.source) && data?.persistOptions?.persistTokenPrototype
      );
      if (!persistentSoundData.length) return;
      const actorSounds = flagManager.getSoundFlags(soundContext.actor);
      const applicableActorSounds = actorSounds.filter((sound) => {
        return is_UUID(sound[1]?.source) && sound[1]?.persistOptions?.persistTokenPrototype && persistentSoundData.some(
          (persistentSound) => persistentSound.persistOptions.id === sound[1]?.persistOptions?.id
        );
      }).map((e) => e[0]);
      flagManager.removeFlags(
        soundContext.actor.uuid,
        { sounds: applicableActorSounds, removeAllSounds: !inSoundIds }
      );
    });
    const soundsToEnd = soundsByContextUuid.concat(soundsByActorUuid).deepFlatten();
    return Promise.allSettled(
      soundsToEnd.map((sound) => this._removeSound(sound))
    );
  }
  static _removeSound(sound) {
    SequenceManager.RunningSounds.delete(sound.id);
    return sound.stop();
  }
  static endAllSounds(push2 = true) {
    const ids = SequenceManager.RunningSounds.keys();
    if (push2) {
      sequencerSocket.executeForOthers(SOCKET_HANDLERS.END_SOUNDS, ids);
    }
    return this._endSounds(ids);
  }
}
let lockedView = false;
class SequencerFoundryReplicator {
  static registerHooks() {
    Hooks.on("canvasPan", () => {
      if (!lockedView) return;
      canvas.stage.pivot.set(lockedView.x, lockedView.y);
      canvas.stage.scale.set(lockedView.scale, lockedView.scale);
      canvas.updateBlur(lockedView.scale);
      canvas.controls._onCanvasPan();
      canvas.hud.align();
    });
  }
  static playScrollingText(data, push2 = true) {
    if (push2) {
      sequencerSocket.executeForOthers(
        SOCKET_HANDLERS.CREATE_SCROLLING_TEXT,
        data
      );
    }
    return this._playScrollingText(data);
  }
  static _playScrollingText(data) {
    if (game.user.viewedScene !== data.sceneId) return;
    if (Array.isArray(data.users) && data.users.length && !data.users.includes(game.userId)) return;
    canvas.interface.createScrollingText(
      getPositionFromData(data),
      data.content,
      data.options
    );
    return data.options?.duration ?? 2e3;
  }
  static panCanvas(data, push2 = true) {
    if (push2) {
      sequencerSocket.executeForOthers(SOCKET_HANDLERS.PAN_CANVAS, data);
    }
    return this._panCanvas(data);
  }
  static _panCanvas(data) {
    if (game.user.viewedScene !== data.sceneId) return;
    if (Array.isArray(data.users) && data.users.length && !data.users.includes(game.userId)) return;
    if (data.source) {
      const position = getPositionFromData(data);
      if (data.speed) {
        const ray = new foundry.canvas.geometry.Ray(canvas.stage.pivot, {
          x: position.x,
          y: position.y
        });
        data.duration = Math.round(ray.distance * 1e3 / data.speed);
      }
      canvas.animatePan({
        x: position.x,
        y: position.y,
        scale: data.scale,
        duration: data.duration
      });
      if (data.lockView > 0) {
        setTimeout(() => {
          lockedView = {
            x: position.x,
            y: position.y,
            scale: data.scale
          };
        }, data.duration);
        setTimeout(() => {
          lockedView = false;
        }, data.lockView + data.duration);
      }
    } else {
      data.duration = 0;
    }
    if (data.shake) {
      setTimeout(() => {
        this._shake(data.shake);
      }, data.duration);
    }
    return data.duration + Math.max(data.lockView ?? 0, data.shake?.duration ?? 0);
  }
  static _shake(shakeData) {
    if (!shakeData.duration || !shakeData.frequency) return;
    let x = random_float_between(-1, 1);
    let y = random_float_between(-1, 1);
    let rot = shakeData.rotation ? random_float_between(-1, 1) : 0;
    let positions = [{ x, y, rot }];
    for (let index2 = 0; index2 < Math.floor(shakeData.duration / shakeData.frequency); index2++) {
      x = flip_negate(x, Math.random());
      y = flip_negate(y, Math.random());
      rot = shakeData.rotation ? flip_negate(rot, Math.random()) : 0;
      positions.push({ x, y, rot });
    }
    let currentDuration = 0;
    positions = positions.map((pos) => {
      let fadeStrength = 1;
      if (shakeData.fadeInDuration && currentDuration <= shakeData.fadeInDuration) {
        fadeStrength = Math.max(
          0,
          Math.min(1, currentDuration / shakeData.fadeInDuration)
        );
      }
      if (shakeData.fadeOutDuration && currentDuration >= shakeData.duration - shakeData.fadeOutDuration) {
        fadeStrength = Math.max(
          0,
          Math.min(
            1,
            (shakeData.duration - currentDuration) / shakeData.fadeOutDuration
          )
        );
      }
      pos.x *= shakeData.strength * fadeStrength;
      pos.y *= shakeData.strength * fadeStrength;
      if (shakeData.rotation) {
        pos.rot *= shakeData.strength / 7.5 * fadeStrength;
      } else {
        pos.rot = 0;
      }
      currentDuration += shakeData.frequency;
      return {
        transform: `translate(${pos.x}px, ${pos.y}px) rotate(${pos.rot}deg)`
      };
    });
    document.getElementById("board").animate(positions, {
      duration: shakeData.duration
    });
  }
}
const PING_INTERVAL_MS = 5e3;
const PING_TIMEOUT_MS = 3e3;
const SequencerPreloader = {
  _activeRequests: /* @__PURE__ */ new Map(),
  _disconnectHookId: null,
  /**
   *  Caches provided file(s) locally, vastly improving loading speed of those files.
   *
   * @param {Array|String}        inSrcs
   * @param {Boolean}             showProgressBar
   * @returns {Promise<void>}
   */
  preload(inSrcs, showProgressBar = false) {
    if (Array.isArray(inSrcs)) {
      inSrcs.forEach((str) => {
        if (typeof str !== "string") {
          throw custom_error(
            "Sequencer",
            "preload | each entry in inSrcs must be of type string"
          );
        }
      });
    } else if (typeof inSrcs !== "string") {
      throw custom_error(
        "Sequencer",
        "preload | inSrcs must be of type string or array of strings"
      );
    }
    if (typeof showProgressBar !== "boolean") {
      throw custom_error(
        "Sequencer",
        "preload | showProgressBar must be of type of boolean"
      );
    }
    const srcs = this._cleanSrcs(inSrcs);
    if (srcs.length === 0) return;
    return this._preloadLocal(srcs, showProgressBar);
  },
  /**
   *  Causes each connected client (including the caller) to fetch and cache the provided file(s) locally, vastly
   *  improving loading speed of those files.
   *
   * @param {Array|String}        inSrcs
   * @param {Boolean}             showProgressBar
   * @returns {Promise<void>}
   */
  preloadForClients(inSrcs, showProgressBar = false) {
    if (Array.isArray(inSrcs)) {
      inSrcs.forEach((str) => {
        if (typeof str !== "string") {
          throw custom_error(
            "Sequencer",
            "preloadForClients | each entry in inSrcs must be of type string"
          );
        }
      });
    } else if (typeof inSrcs !== "string") {
      throw custom_error(
        "Sequencer",
        "preloadForClients | inSrcs must be of type string or array of strings"
      );
    }
    if (typeof showProgressBar !== "boolean") {
      throw custom_error(
        "Sequencer",
        "preloadForClients | showProgressBar must be of type of boolean"
      );
    }
    const srcs = this._cleanSrcs(inSrcs);
    if (srcs.length === 0) return;
    if (!user_can_do("permissions-preload")) {
      custom_warning(
        "Sequencer",
        "preloadForClients - You do not have permission to force other clients to preload. Preloading locally instead."
      );
      return this._preloadLocal(srcs, showProgressBar);
    }
    const requestId = foundry.utils.randomID();
    const request = {
      id: requestId,
      resolve: null,
      usersToRespond: new Set(
        game.users.filter((user) => user.active).map((user) => user.id)
      ),
      clientsDone: [],
      pingInterval: null
    };
    const promise = new Promise((resolve) => {
      request.resolve = resolve;
    });
    this._activeRequests.set(requestId, request);
    this._ensureDisconnectHook();
    sequencerSocket.executeForEveryone(
      SOCKET_HANDLERS.PRELOAD,
      requestId,
      game.user.id,
      srcs,
      showProgressBar
    );
    request.pingInterval = setInterval(() => {
      this._pingUnresponsiveClients(requestId);
    }, PING_INTERVAL_MS);
    return promise;
  },
  /**
   * Pings clients that haven't responded yet. If they don't respond to the
   * ping within PING_TIMEOUT_MS, assume they are unreachable and remove them.
   *
   * @private
   */
  _pingUnresponsiveClients(requestId) {
    const request = this._activeRequests.get(requestId);
    if (!request || request.usersToRespond.size === 0) return;
    for (const userId of request.usersToRespond) {
      if (userId === game.user.id) continue;
      const user = game.users.get(userId);
      if (!user?.active) {
        this._removeUserFromRequest(requestId, userId, "disconnected");
        continue;
      }
      const pingId = foundry.utils.randomID();
      if (!request.pendingPings) request.pendingPings = /* @__PURE__ */ new Map();
      const pingTimeout = setTimeout(() => {
        request.pendingPings?.delete(pingId);
        if (request.usersToRespond.has(userId)) {
          this._removeUserFromRequest(requestId, userId, "unresponsive to ping");
        }
      }, PING_TIMEOUT_MS);
      request.pendingPings.set(pingId, { userId, timeout: pingTimeout });
      sequencerSocket.executeAsUser(
        SOCKET_HANDLERS.PING,
        userId,
        requestId,
        pingId,
        game.user.id
      );
    }
  },
  /**
   * Handles an incoming ping request by immediately responding with a pong.
   */
  respondToPing(requestId, pingId, senderId) {
    sequencerSocket.executeAsUser(
      SOCKET_HANDLERS.PONG,
      senderId,
      requestId,
      pingId
    );
  },
  /**
   * Handles a pong response, confirming the client is still alive.
   */
  handlePong(requestId, pingId) {
    const request = this._activeRequests.get(requestId);
    if (!request?.pendingPings) return;
    const pingData = request.pendingPings.get(pingId);
    if (pingData) {
      clearTimeout(pingData.timeout);
      request.pendingPings.delete(pingId);
    }
  },
  /**
   * Removes a user from a request's wait set and checks if the request is complete.
   *
   * @private
   */
  _removeUserFromRequest(requestId, userId, reason) {
    const request = this._activeRequests.get(requestId);
    if (!request) return;
    if (!request.usersToRespond.has(userId)) return;
    request.usersToRespond.delete(userId);
    const userName = game.users.get(userId)?.name ?? userId;
    debug(`Client "${userName}" removed from preload request (${reason})`);
    if (request.usersToRespond.size === 0) {
      this._resolveRequest(requestId);
    }
  },
  /**
   * Ensures the disconnect hook is registered.
   *
   * @private
   */
  _ensureDisconnectHook() {
    if (this._disconnectHookId !== null) return;
    this._disconnectHookId = Hooks.on("userConnected", (user, connected) => {
      if (connected) return;
      for (const [requestId] of this._activeRequests) {
        this._removeUserFromRequest(requestId, user.id, "disconnected");
      }
    });
  },
  async respond(requestId, inSenderId, inSrcs, showProgressBar) {
    const numFilesFailedToLoad = await this._preloadLocal(
      inSrcs,
      showProgressBar
    );
    return sequencerSocket.executeAsUser(
      SOCKET_HANDLERS.PRELOAD_RESPONSE,
      inSenderId,
      requestId,
      game.user.id,
      numFilesFailedToLoad
    );
  },
  handleResponse(requestId, inUserId, numFilesFailedToLoad) {
    const request = this._activeRequests.get(requestId);
    if (!request) return;
    request.usersToRespond.delete(inUserId);
    request.clientsDone.push({
      userId: inUserId,
      numFilesFailedToLoad
    });
    if (request.usersToRespond.size > 0) return;
    this._resolveRequest(requestId);
  },
  /**
   * Resolves a preload request, cleans up state, and logs results.
   *
   * @private
   */
  _resolveRequest(requestId) {
    const request = this._activeRequests.get(requestId);
    if (!request) return;
    if (request.pingInterval) {
      clearInterval(request.pingInterval);
    }
    if (request.pendingPings) {
      for (const [, pingData] of request.pendingPings) {
        clearTimeout(pingData.timeout);
      }
      request.pendingPings.clear();
    }
    request.clientsDone.forEach((user) => {
      if (user.numFilesFailedToLoad > 0) {
        debug(
          `${game.users.get(user.userId).name} preloaded files, failed to preload ${user.numFilesFailedToLoad} files`
        );
      } else {
        debug(
          `${game.users.get(user.userId).name} preloaded files successfully`
        );
      }
    });
    debug(`All clients responded to file preloads`);
    request.resolve();
    this._activeRequests.delete(requestId);
    if (this._activeRequests.size === 0 && this._disconnectHookId !== null) {
      Hooks.off("userConnected", this._disconnectHookId);
      this._disconnectHookId = null;
    }
  },
  /**
   * Filters and cleans up file paths given to the preload methods
   *
   * @private
   */
  _cleanSrcs(inSrcs) {
    if (!Array.isArray(inSrcs)) {
      inSrcs = [inSrcs];
    }
    if (inSrcs.length === 0) {
      custom_warning("Sequencer", "You need to provide files to preload");
      return [];
    }
    inSrcs = make_array_unique(
      inSrcs.filter(Boolean).map((src) => {
        if (Sequencer.Database.entryExists(src)) {
          return Sequencer.Database.getAllFileEntries(src);
        }
        return src;
      }).deepFlatten()
    );
    if (inSrcs.length >= 750) {
      custom_warning(
        "Sequencer",
        "You are preloading over 750 files, you are most likely preloading more files than the system can cache.",
        true
      );
    }
    return inSrcs;
  },
  /**
   * The method that actually preloads files locally, with an optional progress bar
   *
   * @private
   */
  _preloadLocal(inSrcs, showProgressBar) {
    let startTime = performance.now();
    let numFilesToLoad = inSrcs.length;
    debug(`Preloading ${numFilesToLoad} files...`);
    if (showProgressBar) {
      LoadingBar.init(
        `Sequencer - Preloading ${numFilesToLoad} files`,
        numFilesToLoad
      );
    }
    return new Promise(async (resolve) => {
      let numFilesFailedToLoad = 0;
      const loadingPromises = inSrcs.map(async (src) => {
        const blob = await SequencerFileCache.loadFile(src, true);
        if (showProgressBar) LoadingBar.incrementProgress();
        if (!blob) {
          numFilesFailedToLoad++;
        }
      });
      await Promise.allSettled(loadingPromises);
      const timeTaken = (performance.now() - startTime) / 1e3;
      let failedToLoad = ` (${numFilesFailedToLoad} failed to load)`;
      debug(
        `Preloading ${numFilesToLoad} files took ${timeTaken}s` + failedToLoad
      );
      resolve(numFilesFailedToLoad);
    });
  }
};
const RECIPIENT_TYPES = {
  GM: 0,
  SUPERUSER: 1,
  ALL: 2,
  USER: 3
};
const SOCKET_HANDLERS = {
  PLAY_EFFECT: "playEffect",
  END_EFFECTS: "endEffects",
  UPDATE_EFFECT: "updateEffects",
  ADD_EFFECT_ANIMATIONS: "addEffectAnimations",
  PLAY_SOUND: "playSound",
  END_SOUNDS: "endSounds",
  PRELOAD: "preload",
  PRELOAD_RESPONSE: "preloadResponse",
  PING: "ping",
  PONG: "pong",
  UPDATE_DOCUMENT: "updateDocument",
  ADD_FLAGS: "addFlags",
  REMOVE_FLAGS: "removeFlags",
  UPDATE_EFFECT_POSITION: "updateEffectPosition",
  ADD_SOUND_FLAGS: "addSoundFlags",
  REMOVE_SOUND_FLAGS: "removeSoundFlags",
  CREATE_SCROLLING_TEXT: "createScrollingText",
  PAN_CANVAS: "panCanvas",
  RUN_SEQUENCE_LOCALLY: "runSequenceLocally"
};
const SOCKET_CALLBACKS = {
  [SOCKET_HANDLERS.PLAY_EFFECT]: (data) => {
    return SequencerEffectManager._playEffect(...data.payload);
  },
  [SOCKET_HANDLERS.END_EFFECTS]: (data) => {
    return SequencerEffectManager._endManyEffects(...data.payload);
  },
  [SOCKET_HANDLERS.UPDATE_EFFECT]: (data) => {
    return SequencerEffectManager._updateEffect(...data.payload);
  },
  [SOCKET_HANDLERS.ADD_EFFECT_ANIMATIONS]: (data) => {
    return SequencerEffectManager._addEffectAnimations(...data.payload);
  },
  [SOCKET_HANDLERS.PLAY_SOUND]: (data) => {
    return SequencerSoundManager._playSound(...data.payload);
  },
  [SOCKET_HANDLERS.END_SOUNDS]: (data) => {
    return SequencerSoundManager._endSounds(...data.payload);
  },
  [SOCKET_HANDLERS.PRELOAD]: (data) => {
    return SequencerPreloader.respond(...data.payload);
  },
  [SOCKET_HANDLERS.PRELOAD_RESPONSE]: (data) => {
    return SequencerPreloader.handleResponse(...data.payload);
  },
  [SOCKET_HANDLERS.PING]: (data) => {
    return SequencerPreloader.respondToPing(...data.payload);
  },
  [SOCKET_HANDLERS.PONG]: (data) => {
    return SequencerPreloader.handlePong(...data.payload);
  },
  [SOCKET_HANDLERS.UPDATE_DOCUMENT]: async (data) => {
    let [documentUuid, updates, animate] = data.payload;
    const document2 = await fromUuid(documentUuid);
    if (!document2) {
      return;
    }
    return document2.update(updates, animate);
  },
  [SOCKET_HANDLERS.ADD_FLAGS]: (data) => {
    return flagManager._addFlags(...data.payload);
  },
  [SOCKET_HANDLERS.REMOVE_FLAGS]: (data) => {
    return flagManager._removeFlags(...data.payload);
  },
  [SOCKET_HANDLERS.UPDATE_EFFECT_POSITION]: (data) => {
    return SequencerEffectManager._updatePosition(...data.payload);
  },
  [SOCKET_HANDLERS.ADD_SOUND_FLAGS]: (data) => {
    return flagManager._addFlags(...data.payload);
  },
  [SOCKET_HANDLERS.REMOVE_SOUND_FLAGS]: (data) => {
    return flagManager._removeFlags(...data.payload);
  },
  [SOCKET_HANDLERS.CREATE_SCROLLING_TEXT]: (data) => {
    return SequencerFoundryReplicator._playScrollingText(...data.payload);
  },
  [SOCKET_HANDLERS.PAN_CANVAS]: (data) => {
    return SequencerFoundryReplicator._panCanvas(...data.payload);
  },
  [SOCKET_HANDLERS.RUN_SEQUENCE_LOCALLY]: (data) => {
    debug("Playing remote Sequence");
    new Sequence().fromJSON(data.payload).play();
  }
};
class SequencerSocket {
  constructor() {
    game.socket.on(`module.${CONSTANTS.MODULE_NAME}`, this._executeSocket.bind(this));
  }
  executeAsGM(type, ...args) {
    let data = { senderId: game.user.id, sendType: RECIPIENT_TYPES.GM, type, payload: args };
    if (game.user.isGM) {
      return this._executeSocket(data);
    }
    if (!game.users.activeGM) {
      custom_warning("Sequencer", "executeAsGM | No active GM is connected to handle this request");
      return data;
    }
    game.socket.emit(`module.${CONSTANTS.MODULE_NAME}`, data);
    return data;
  }
  executeForOthers(type, ...args) {
    let data = { senderId: game.user.id, sendType: RECIPIENT_TYPES.ALL, type, payload: args };
    game.socket.emit(`module.${CONSTANTS.MODULE_NAME}`, data);
    return data;
  }
  executeForEveryone(type, ...args) {
    let data = { senderId: game.user.id, sendType: RECIPIENT_TYPES.ALL, type, payload: args };
    game.socket.emit(`module.${CONSTANTS.MODULE_NAME}`, data);
    return this._executeSocket(data);
  }
  executeAsUser(type, receiverId, ...args) {
    let data = { senderId: game.user.id, receiverId, sendType: RECIPIENT_TYPES.USER, type, payload: args };
    if (receiverId === game.user.id) {
      return this._executeSocket(data);
    }
    game.socket.emit(`module.${CONSTANTS.MODULE_NAME}`, data);
    return data;
  }
  executeAsMainUser(type, ...args) {
    let data = { senderId: game.user.id, sendType: RECIPIENT_TYPES.SUPERUSER, type, payload: args };
    let targetUser = game.users.getDesignatedUser((user) => user.active);
    if (!targetUser) {
      custom_warning("Sequencer", "executeAsMainUser | No active user is eligible to handle this request");
      return data;
    }
    if (game.user.id === targetUser.id) {
      return this._executeSocket(data);
    }
    game.socket.emit(`module.${CONSTANTS.MODULE_NAME}`, data);
    return data;
  }
  _executeSocket(data) {
    const handler = SOCKET_CALLBACKS[data.type];
    if (!handler) {
      return false;
    }
    switch (data.sendType) {
      case RECIPIENT_TYPES.SUPERUSER:
        let user = game.users.getDesignatedUser((user2) => user2.active);
        if (user !== game.user) {
          return;
        }
        break;
      case RECIPIENT_TYPES.GM:
        if (data.sendType === RECIPIENT_TYPES.GM && !game.user.isActiveGM) {
          return;
        }
        break;
      case RECIPIENT_TYPES.USER:
        if (data.sendType === RECIPIENT_TYPES.USER && game.user.id !== data.receiverId) {
          return;
        }
        break;
    }
    return handler(data);
  }
}
let sequencerSocket = false;
function registerSocket() {
  sequencerSocket = new SequencerSocket();
}
const PositionContainer = /* @__PURE__ */ new Map();
const TemporaryPositionsContainer = /* @__PURE__ */ new Map();
class SequencerEffectManager {
  /**
   * Returns all the currently running effects on the canvas
   *
   * @returns {Array}
   */
  static get effects() {
    return Array.from(SequenceManager.VisibleEffects.values());
  }
  static _updatePosition(uuid, position) {
    TemporaryPositionsContainer.set(uuid, position);
  }
  static getPositionForUUID(uuid) {
    return TemporaryPositionsContainer.get(uuid);
  }
  /**
   * Opens the Sequencer Manager UI with the effects tab open
   */
  static show() {
    return EffectsUIApp.show({ tab: "manager" });
  }
  /**
   * Play an effect on the canvas.
   *
   * @param {object} data The data that describes the audio to play
   * @param {boolean} [push=true] A flag indicating whether or not to make other clients play the effect
   * @returns {CanvasEffect} A CanvasEffect object
   */
  static async play(data, push2 = true) {
    if (!user_can_do("permissions-effect-create")) {
      custom_warning(
        "Sequencer",
        "EffectManager | play | Players do not have permissions to play effects. This can be configured in Sequencer's module settings."
      );
      return { duration: Promise.resolve(0), promise: Promise.resolve() };
    }
    if (push2) sequencerSocket.executeForOthers(SOCKET_HANDLERS.PLAY_EFFECT, data);
    return this._playEffect(data);
  }
  /**
   * Get effects that are playing on the canvas based on a set of filters
   *
   * @param {object} inFilter An object containing filters that determine which effects to return
   *                             - object: An ID or a PlaceableObject
   *                             - name: The name of the effect
   *                             - sceneId: the ID of the scene to search within
   * @returns {Array} An array containing effects that match the given filter
   */
  static getEffects(inFilter = {}) {
    const filters2 = this._validateFilters(inFilter);
    if (!inFilter)
      throw custom_error(
        "Sequencer",
        "EffectManager | getEffects | Incorrect or incomplete parameters provided"
      );
    return this._filterEffects(filters2);
  }
  /**
   * Updates effects based on a set of filters
   *
   * @param {object} inFilter An object containing filters that determine which effects to return
   *                             - object: An ID or a PlaceableObject
   *                             - name: The name of the effect
   *                             - sceneId: the ID of the scene to search within
   *                             - effects: a single CanvasEffect or its ID, or an array of such
   * @param {object} inUpdates
   * @returns {promise}
   */
  static updateEffects(inFilter, inUpdates) {
    inFilter = this._validateFilters(inFilter);
    if (!inFilter)
      throw custom_error(
        "Sequencer",
        "EffectManager | updateEffects | Incorrect or incomplete parameters provided"
      );
    CanvasEffect.validateUpdate(inUpdates);
    const effectsToUpdate = this._filterEffects(inFilter).filter(
      (effect2) => effect2.userCanUpdate
    );
    return Promise.allSettled(
      effectsToUpdate.map((effect2) => effect2.update(inUpdates))
    );
  }
  /**
   * End effects that are playing on the canvas based on a set of filters
   *
   * @param {object} inFilter An object containing filters that determine which effects to end
   *                             - object: An ID or a PlaceableObject
   *                             - name: The name of the effect
   *                             - sceneId: the ID of the scene to search within
   *                             - effects: a single CanvasEffect or its ID, or an array of such
   * @param {boolean} [push=true] A flag indicating whether or not to make other clients end the effects
   * @returns {promise} A promise that resolves when the effects have ended
   */
  static async endEffects(inFilter = {}, push2 = true) {
    inFilter = this._validateFilters(inFilter);
    if (!inFilter)
      throw custom_error(
        "Sequencer",
        "EffectManager | endEffects | Incorrect or incomplete parameters provided"
      );
    const effectsToEnd = this._getEffectsByFilter(inFilter);
    if (!effectsToEnd.length) return;
    if (push2)
      sequencerSocket.executeForOthers(
        SOCKET_HANDLERS.END_EFFECTS,
        effectsToEnd
      );
    return this._endManyEffects(effectsToEnd);
  }
  /**
   * End all effects that are playing on the canvas
   *
   * @param {string} [inSceneId] A parameter which determines which scene to end all effects on, defaults to current viewed scene
   * @param {boolean} [push=true] A flag indicating whether or not to make other clients end all effects
   * @returns {promise} A promise that resolves when all of the effects have _ended
   */
  static async endAllEffects(inSceneId = game.user.viewedScene, push2 = true) {
    const inFilter = this._validateFilters({ sceneId: inSceneId });
    if (!inFilter)
      throw custom_error(
        "Sequencer",
        "EffectManager | endAllEffects | Incorrect or incomplete parameters provided"
      );
    const effectsToEnd = this._getEffectsByFilter(inFilter);
    if (!effectsToEnd.length) return;
    if (push2)
      sequencerSocket.executeForOthers(
        SOCKET_HANDLERS.END_EFFECTS,
        effectsToEnd
      );
    return this._endManyEffects(effectsToEnd);
  }
  static _getEffectsByFilter(inFilter) {
    return make_array_unique(
      this._filterEffects(inFilter).filter((effect2) => effect2.userCanDelete).map((effect2) => {
        return effect2.data?.persistOptions?.persistTokenPrototype && is_UUID(effect2?.data.source) && effect2.data?.attachTo?.active ? effect2.data?.persistOptions?.id ?? effect2.id : effect2.id;
      })
    );
  }
  /**
   * If an effect has been named its position will be cached, which can be retrieved with this method
   *
   * @param {string} inName
   * @returns {object|boolean}
   * @private
   */
  static getEffectPositionByName(inName) {
    if (!(typeof inName === "string"))
      throw custom_error(
        "Sequencer",
        "EffectManager | getEffectPositionByName | inName must be of type string"
      );
    return PositionContainer.get(inName) ?? false;
  }
  /**
   * Filters the existing effects based on the given filter
   *
   * @param inFilter
   * @returns {array}
   * @private
   */
  static _filterEffects(inFilter) {
    if (inFilter.name) {
      inFilter.name = new RegExp(
        "^" + str_to_search_regex_str(safe_str(inFilter.name)) + "$",
        "gu"
      );
    }
    return this.effects.filter((effect2) => {
      return (!inFilter.effects || inFilter.effects.includes(effect2.id)) && (!inFilter.name || effect2.data.name && effect2.data.name.match(inFilter.name)?.length) && (!inFilter.source || inFilter.source === effect2.data.source) && (!inFilter.target || inFilter.target === effect2.data.target) && (!inFilter.origin || inFilter.origin === effect2.data.origin);
    });
  }
  /**
   * Validates an object actually exists, and gets its UUID
   *
   * @param object
   * @param sceneId
   * @returns {string}
   * @private
   */
  static _validateObject(object, sceneId) {
    if (!(object instanceof foundry.abstract.Document || object instanceof foundry.canvas.placeables.PlaceableObject || typeof object === "string")) {
      throw custom_error(
        "Sequencer",
        "EffectManager | object must be instance of PlaceableObject or of type string"
      );
    } else if (object instanceof foundry.canvas.placeables.PlaceableObject || object instanceof foundry.abstract.Document) {
      object = get_object_identifier(object?.document ?? object);
    } else if (typeof object === "string") {
      const actualObject = get_object_from_scene(object, sceneId);
      if (!actualObject) {
        throw custom_error(
          "Sequencer",
          `EffectManager | could not find object with ID: ${object}`
        );
      }
      const uuid = get_object_identifier(actualObject);
      if (!uuid) {
        throw custom_error(
          "Sequencer",
          `EffectManager | could could not establish identifier of object with ID: ${object}`
        );
      }
      object = uuid;
    }
    return object;
  }
  /**
   * Validates the filter given to any of the above public methods
   *
   * @param inFilter
   * @returns {boolean}
   * @private
   */
  static _validateFilters(inFilter) {
    if (inFilter?.sceneId) {
      if (typeof inFilter.sceneId !== "string")
        throw custom_error(
          "Sequencer",
          "EffectManager | inFilter.sceneId must be of type string"
        );
      if (!game.scenes.get(inFilter.sceneId))
        throw custom_error(
          "Sequencer",
          "EffectManager | inFilter.sceneId must be a valid scene id (could not find scene)"
        );
    } else {
      inFilter.sceneId = game.user.viewedScene;
    }
    if (inFilter?.object) {
      inFilter.source = this._validateObject(inFilter.object, inFilter.sceneId);
      delete inFilter.object;
    }
    if (inFilter?.source) {
      inFilter.source = this._validateObject(inFilter.source, inFilter.sceneId);
    }
    if (inFilter?.target) {
      inFilter.target = this._validateObject(inFilter.target, inFilter.sceneId);
    }
    if (inFilter?.name && typeof inFilter?.name !== "string")
      throw custom_error(
        "Sequencer",
        "EffectManager | inFilter.name must be of type string"
      );
    if (inFilter?.origin && typeof inFilter?.origin !== "string")
      throw custom_error(
        "Sequencer",
        "EffectManager | inFilter.origin must be of type string"
      );
    if (inFilter?.effects) {
      if (!Array.isArray(inFilter.effects))
        inFilter.effects = [inFilter.effects];
      inFilter.effects = inFilter.effects.map((effect2) => {
        if (!(typeof effect2 === "string" || effect2 instanceof CanvasEffect))
          throw custom_error(
            "Sequencer",
            "EffectManager | collections in inFilter.effects must be of type string or CanvasEffect"
          );
        if (effect2 instanceof CanvasEffect) return effect2.id;
        return effect2;
      });
    }
    if (!inFilter.name && !inFilter.origin && !inFilter.target && !inFilter.sceneId && !inFilter.effects && !inFilter.origin)
      return false;
    return foundry.utils.mergeObject(
      {
        effects: false,
        name: false,
        source: false,
        target: false,
        sceneId: false,
        origin: false
      },
      inFilter
    );
  }
  /**
   * Actually plays the effect on the canvas
   *
   * @param data
   * @param setFlags
   * @returns {Promise<{duration: Promise<number>, promise: Promise<void>}>}
   * @private
   */
  static async _playEffect(data, setFlags = true) {
    const effect2 = CanvasEffect.make(data);
    if (data.persist && setFlags && effect2.context && effect2.owner && !effect2.isSourceTemporary && !data.temporary && !data.remote) {
      flagManager.addFlags(effect2.context.uuid, { effects: effect2.data });
    }
    if (!effect2.shouldPlay) {
      return { duration: Promise.resolve(0), promise: Promise.resolve() };
    }
    const playData = effect2.play();
    SequenceManager.VisibleEffects.add(effect2.id, effect2);
    if (effect2.data.name) {
      effect2._addToTicker(() => {
        if (effect2.isDestroyed) return;
        PositionContainer.set(effect2.data.name, {
          start: effect2.sourcePosition,
          end: effect2.targetPosition
        });
      });
    }
    if (data.temporary && effect2.owner) {
      let lastSource = null;
      let lastTarget = null;
      effect2._addToTicker(() => {
        if (effect2.source && !effect2.isSourceDestroyed) {
          const sourceData = effect2.getSourceData();
          const pos = sourceData.position;
          if (!lastSource || lastSource.x !== pos?.x || lastSource.y !== pos?.y || lastSource.elevation !== pos?.elevation || lastSource.width !== sourceData.width || lastSource.height !== sourceData.height || lastSource.rotation !== sourceData.rotation || lastSource.alpha !== sourceData.alpha) {
            sequencerSocket.executeForOthers(
              SOCKET_HANDLERS.UPDATE_EFFECT_POSITION,
              data.source,
              sourceData
            );
            lastSource = {
              x: pos?.x,
              y: pos?.y,
              elevation: pos?.elevation,
              width: sourceData.width,
              height: sourceData.height,
              rotation: sourceData.rotation,
              alpha: sourceData.alpha
            };
          }
        }
        if (effect2.target && !effect2.isTargetDestroyed) {
          const targetData = effect2.getTargetData();
          const pos = targetData.position;
          if (!lastTarget || lastTarget.x !== pos?.x || lastTarget.y !== pos?.y || lastTarget.elevation !== pos?.elevation || lastTarget.width !== targetData.width || lastTarget.height !== targetData.height || lastTarget.rotation !== targetData.rotation || lastTarget.alpha !== targetData.alpha) {
            sequencerSocket.executeForOthers(
              SOCKET_HANDLERS.UPDATE_EFFECT_POSITION,
              data.target,
              targetData
            );
            lastTarget = {
              x: pos?.x,
              y: pos?.y,
              elevation: pos?.elevation,
              width: targetData.width,
              height: targetData.height,
              rotation: targetData.rotation,
              alpha: targetData.alpha
            };
          }
        }
      });
    }
    if (!data.persist) {
      playData.promise.then(() => this._removeEffect(effect2));
    }
    return playData;
  }
  /**
   * Updates a single effect with the given data
   *
   * @param inEffectId
   * @param inUpdates
   * @returns {promise|boolean}
   * @private
   */
  static _updateEffect(inEffectId, inUpdates) {
    const effect2 = SequenceManager.VisibleEffects.get(inEffectId);
    if (!effect2) return false;
    return effect2._update(inUpdates);
  }
  /**
   * Updates a single effect with new animations
   *
   * @param inEffectId
   * @param inAnimations
   * @param inLoopingAnimations
   * @returns {promise|boolean}
   * @private
   */
  static _addEffectAnimations(inEffectId, inAnimations, inLoopingAnimations) {
    const effect2 = SequenceManager.VisibleEffects.get(inEffectId);
    if (!effect2) return false;
    return effect2._addAnimations(inAnimations, inLoopingAnimations);
  }
  /**
   * Sets up persisting effects when the scene is first loaded
   *
   * @returns {promise}
   */
  static async initializePersistentEffects() {
    await this.tearDownPersistentEffects();
    let docEffectsMap = {};
    let databaseEffects = foundry.utils.deepClone(flagManager.getDatabaseFlags().effects);
    let effectsToRemove = {};
    for (let [uuid, effects] of Object.entries(databaseEffects)) {
      let doc = fromUuidSync(uuid);
      if (doc instanceof foundry.documents.Actor && doc.prototypeToken.actorLink) {
        for (let token of doc.getActiveTokens()) {
          let tokenEffects = foundry.utils.deepClone(effects);
          docEffectsMap[token.document.uuid] = tokenEffects.map(([id, effect2]) => {
            if (is_UUID(effect2.source)) {
              effect2.source = token.document.uuid;
            }
            effect2.sceneId = canvas.scene.id;
            return [id, effect2];
          });
        }
      } else if (doc) {
        docEffectsMap[uuid] = effects;
      } else {
        effectsToRemove[uuid] = effects;
      }
    }
    const promises = Object.entries(docEffectsMap).map(([uuid, effects]) => {
      return this._playEffectMap(effects, fromUuidSync(uuid));
    }).flat();
    for (let [uuid, effects] of Object.entries(effectsToRemove)) {
      flagManager.removeFlags(uuid, {
        effects,
        removeAllEffects: true
      });
    }
    return Promise.all(promises).then(() => {
      Hooks.callAll("sequencerEffectManagerReady");
    });
  }
  /**
   * Tears down persisting effects when the scene is unloaded
   */
  static tearDownPersistentEffects() {
    return Promise.allSettled(
      this.effects.map((effect2) => {
        SequenceManager.VisibleEffects.delete(effect2.id);
        return effect2.destroy();
      })
    );
  }
  static setup() {
    Hooks.on("createToken", this._documentCreated.bind(this));
    Hooks.on("createDrawing", this._documentCreated.bind(this));
    Hooks.on("createTile", this._documentCreated.bind(this));
    Hooks.on("createMeasuredTemplate", this._documentCreated.bind(this));
    Hooks.on("createRegion", this._documentCreated.bind(this));
  }
  static _patchEffectDataForDocument(inDocumentUuid, effects) {
    return effects.map((effect2) => {
      effect2[0] = foundry.utils.randomID();
      const effectData = effect2[1];
      effectData._id = effect2[0];
      if (is_UUID(effectData.source)) {
        if (effectData.masks.includes(effectData.source)) {
          const index2 = effectData.masks.indexOf(effectData.source);
          effectData.masks[index2] = inDocumentUuid;
        }
        effectData.source = inDocumentUuid;
      }
      effectData.sceneId = inDocumentUuid.split(".")[1];
      return effect2;
    });
  }
  /**
   * Plays the effects of a given document on creation
   *
   * @param inDocument
   * @returns {*}
   */
  static async _documentCreated(inDocument) {
    let effects = flagManager.getEffectFlags(inDocument);
    if (inDocument instanceof TokenDocument && inDocument?.actorLink) {
      let actorEffects = flagManager.getEffectFlags(inDocument?.actor);
      if (actorEffects.length) {
        actorEffects = this._patchEffectDataForDocument(
          inDocument.uuid,
          actorEffects
        );
      }
      effects = effects.concat(actorEffects);
    }
    const sourceUuid = PasteManager.consume(inDocument);
    if (sourceUuid) {
      const sourceEffects = flagManager.getEffectFlags({ uuid: sourceUuid });
      if (sourceEffects.length) {
        const rekeyed = this._patchEffectDataForDocument(inDocument.uuid, sourceEffects);
        const effectDatas = rekeyed.map(([, data]) => data);
        flagManager.addFlags(inDocument.uuid, { effects: effectDatas });
        for (const effectData of effectDatas) {
          sequencerSocket.executeForOthers(SOCKET_HANDLERS.PLAY_EFFECT, effectData, false);
        }
        effects = effects.concat(rekeyed);
      }
    }
    if (!effects?.length) return;
    return this._playEffectMap(effects, inDocument);
  }
  /**
   * Plays multiple effects at the same time
   *
   * @param inEffects
   * @param inDocument
   * @returns {Promise<{duration: Promise<number>, promise: Promise<void>}[]>}
   * @private
   */
  static _playEffectMap(inEffects, inDocument) {
    if (inEffects instanceof Map) inEffects = Array.from(inEffects);
    return Promise.all(
      inEffects.map((effect2) => {
        if (!CanvasEffect.checkShouldPlay(effect2[1])) return;
        if (!CanvasEffect.checkValid(effect2[1])) {
          if (!game.user.isGM) return;
          custom_warning(
            `Sequencer`,
            `Removed effect from ${inDocument.uuid} as it no longer had a valid source or target`
          );
          return flagManager.removeFlags(inDocument.uuid, { effects: effect2 });
        }
        return this._playEffect(effect2[1], false).then((result) => {
          if (!result) {
            debug("Error playing effect");
          }
        }).catch((err) => {
          debug("Error playing effect:", err);
        });
      })
    );
  }
  /**
   * Ends one or many effects at the same time, returning a promise that resolves once every effect has fully ended
   *
   * @param inEffectIds
   * @returns {Promise}
   * @private
   */
  static async _endManyEffects(inEffectIds = false) {
    const actorEffectsToEnd = this.effects.filter((effect2) => {
      return effect2.context?.actorLink && effect2.context?.actor && inEffectIds.includes(effect2.data?.persistOptions?.id);
    });
    const effectsByActorUuid = Object.values(
      group_by(actorEffectsToEnd, "context.actor.uuid")
    );
    const regularEffectsToEnd = this.effects.filter((effect2) => {
      return inEffectIds.includes(effect2.id) || !(effect2.context?.actorLink && effect2.context?.actor) && inEffectIds.includes(effect2.data?.persistOptions?.id);
    });
    const effectsByContextUuid = Object.values(
      group_by(regularEffectsToEnd, "context.uuid")
    );
    effectsByContextUuid.forEach((effects) => {
      effects = effects.filter(
        (effect2) => effect2.data.persist && !effect2.data.temporary
      );
      if (!effects.length) return;
      const effectData = effects.map((effect2) => effect2.data);
      flagManager.removeFlags(
        effects[0].context.uuid,
        { effects: effectData, removeAllEffects: !inEffectIds }
      );
    });
    effectsByActorUuid.forEach((effects) => {
      effects = effects.filter(
        (effect2) => effect2.data.persist && !effect2.data.temporary
      );
      if (!effects.length) return;
      const effectContext = effects[0].context;
      const effectData = effects.map((effect2) => effect2.data);
      if (!(effectContext instanceof TokenDocument && effectContext.actorLink && effectContext.actor && effectContext.actor?.prototypeToken?.actorLink)) {
        return;
      }
      const persistentEffectData = effectData.filter(
        (data) => is_UUID(data?.source) && data?.persistOptions?.persistTokenPrototype
      );
      if (!persistentEffectData.length) return;
      const actorEffects = flagManager.getEffectFlags(effectContext.actor);
      const applicableActorEffects = actorEffects.filter((effect2) => {
        return is_UUID(effect2[1]?.source) && effect2[1]?.persistOptions?.persistTokenPrototype && persistentEffectData.some(
          (persistentEffect) => persistentEffect.persistOptions.id === effect2[1]?.persistOptions?.id
        );
      }).map((e) => e[0]);
      flagManager.removeFlags(
        effectContext.actor.uuid,
        { effects: applicableActorEffects, removeAllEffects: !inEffectIds }
      );
    });
    const effectsToEnd = effectsByContextUuid.concat(effectsByActorUuid).deepFlatten();
    return Promise.allSettled(
      effectsToEnd.map((effect2) => this._removeEffect(effect2))
    );
  }
  static _effectContextFilter(inUUID, effectData) {
    return effectData?.source === inUUID || effectData?.target === inUUID || (effectData?.tiedDocuments ?? []).indexOf(inUUID) > -1;
  }
  /**
   * Handles the deletion of objects that effects are attached to
   *
   * @param inUUID
   * @returns {Promise}
   */
  static objectDeleted(inUUID) {
    const databaseEffects = flagManager.getDatabaseFlags().effects;
    const flagRemovals = [];
    for (const [ownerUuid, effects] of Object.entries(databaseEffects)) {
      const idsToEnd = effects.filter(([, effectData]) => this._effectContextFilter(inUUID, effectData)).map(([effectId]) => effectId);
      if (idsToEnd.length) {
        flagRemovals.push(flagManager.removeFlags(ownerUuid, { effects: idsToEnd }));
      }
    }
    const visibleEffectsToEnd = this.effects.filter((effect2) => this._effectContextFilter(inUUID, effect2.data)).map((e) => e.id);
    return Promise.allSettled([
      this._endManyEffects(visibleEffectsToEnd),
      ...flagRemovals
    ]);
  }
  /**
   * Removes the effect from the manager and ends it, returning a promise that resolves once the effect has fully _ended
   *
   * @param effect
   * @returns {Promise}
   * @private
   */
  static _removeEffect(effect2) {
    SequenceManager.VisibleEffects.delete(effect2.id);
    TemporaryPositionsContainer.delete(effect2.data.source);
    TemporaryPositionsContainer.delete(effect2.data.target);
    return effect2.endEffect();
  }
}
class BaseEffectsLayer extends foundry.canvas.layers.InteractionLayer {
  static get layerOptions() {
    return foundry.utils.mergeObject(super.layerOptions, {
      name: CONSTANTS.EFFECTS_LAYER
    });
  }
}
class SequencerInterfaceLayer extends foundry.canvas.layers.InteractionLayer {
  constructor(...args) {
    super(...args);
  }
  isActive = false;
  static get layerOptions() {
    return foundry.utils.mergeObject(super.layerOptions, {
      elevation: 1e8,
      name: CONSTANTS.INTERFACE_LAYER
    });
  }
  activate(...args) {
    const result = super.activate(...args);
    this.isActive = true;
    return result;
  }
  deactivate() {
    super.deactivate();
    if (!this.isActive) return;
    this._clearChildren();
    this.isActive = false;
    InteractionManager.tearDown();
  }
  _setup() {
    if (!this.UIContainer || this.UIContainer._destroyed) {
      this.UIContainer = new PIXI.Container();
      this.UIContainer.sortableChildren = true;
      this.UIContainer.parentName = "sequencerUIContainer";
      this.UIContainer.zIndex = 1e13;
      this.addChild(this.UIContainer);
      this.visualizedSound = this.UIContainer.addChild(new PIXI.Graphics());
      this.linePoint = this.UIContainer.addChild(new PIXI.Graphics());
      this.line = this.UIContainer.addChild(new PIXI.Graphics());
      this.lineHead = this.UIContainer.addChild(new PIXI.Graphics());
      this.suggestionPoint = this.UIContainer.addChild(new PIXI.Graphics());
      this.effectHoverBoxes = this.UIContainer.addChild(new PIXI.Graphics());
      this.effectSelectionBorder = this.UIContainer.addChild(
        new PIXI.Graphics()
      );
      this.effectSourcePosition = this.UIContainer.addChild(
        new PIXI.Graphics()
      );
      this.effectTargetPosition = this.UIContainer.addChild(
        new PIXI.Graphics()
      );
      this.suggestionPoint.filters = [new PIXI.AlphaFilter(0.75)];
      this.effectSourcePosition.filters = [new PIXI.AlphaFilter(0.75)];
      this.effectTargetPosition.filters = [new PIXI.AlphaFilter(0.75)];
      this.effectSelectionBorder.zIndex = 1;
      this.effectSourcePosition.interactive = true;
      this.effectSourcePosition.on("mousedown", () => {
        SelectionManager.sourcePointSelected();
      });
      this.effectTargetPosition.interactive = true;
      this.effectTargetPosition.on("mousedown", () => {
        SelectionManager.targetPointSelected();
      });
    }
  }
  async _draw(...args) {
  }
  render(...args) {
    super.render(...args);
    this._setup();
    this._clearChildren();
    this._drawHoveredEffectElements();
    this._drawVisualizedSounds();
    if (!this.isActive) return;
    this._drawLine();
    this._drawPoints();
    this._drawSelectedEffectElements();
    this._drawSuggestionPoint();
  }
  _drawVisualizedSounds() {
    let sound = SelectionManager.hoveredSoundUI;
    if (!sound || sound.status === SequencerSoundManager.states.ENDED || !sound.data.source) return;
    const baseRadius = sound.data.locationOptions?.radius;
    if (!is_real_number(baseRadius)) return;
    const sourcePos = sound.sourcePosition;
    let radius = baseRadius / canvas.grid.distance * canvas.grid.size;
    let data = {
      position: {
        x: sourcePos.x,
        y: sourcePos.y
      },
      shape: foundry.canvas.geometry.ClockwiseSweepPolygon.create(sourcePos, {
        type: "sound",
        radius
      })
    };
    for (let constraint of data.shape.config.boundaryShapes) {
      this.visualizedSound.lineStyle(2, 16729156, 1);
      this.visualizedSound.beginFill(16729156, 0.1);
      this.visualizedSound.drawShape(constraint);
      this.visualizedSound.endFill();
    }
    this.visualizedSound.beginFill(CONSTANTS.COLOR.PRIMARY, 0.5);
    this.visualizedSound.lineStyle(2, CONSTANTS.COLOR.PRIMARY, 1);
    this.visualizedSound.drawCircle(data.position.x, data.position.y, canvas.grid.size / 4);
    this.visualizedSound.endFill();
    this.visualizedSound.beginFill(43775, 0.25);
    this.visualizedSound.drawShape(data.shape);
    this.visualizedSound.endFill();
  }
  _clearChildren() {
    if (!this.UIContainer) return;
    this.UIContainer.children.forEach((child2) => {
      child2.clear();
    });
  }
  _drawLine() {
    if (!EffectPlayer.startPos || !EffectPlayer.endPos || game?.activeTool !== CONSTANTS.TOOLS.PLAY)
      return;
    this.line.lineStyle(3, CONSTANTS.COLOR.PRIMARY, 1);
    this.line.moveTo(EffectPlayer.startPos.x, EffectPlayer.startPos.y);
    this.line.lineTo(EffectPlayer.endPos.x, EffectPlayer.endPos.y);
  }
  _drawPoints() {
    if (game?.activeTool !== CONSTANTS.TOOLS.PLAY) return;
    const startPos = EffectPlayer.startPos || EffectPlayer.cursorPos;
    this.linePoint.beginFill(CONSTANTS.COLOR.PRIMARY);
    this.linePoint.drawCircle(startPos.x, startPos.y, 5);
    if (EffectPlayer.sourceAttachFound) {
      this._drawCrossAtPosition(this.linePoint, startPos);
    }
    if (!EffectPlayer.endPos) return;
    const angle = new foundry.canvas.geometry.Ray(startPos, EffectPlayer.endPos).angle;
    this.lineHead.beginFill(CONSTANTS.COLOR.PRIMARY);
    this.lineHead.moveTo(0, -5);
    this.lineHead.lineTo(-15, 30);
    this.lineHead.lineTo(15, 30);
    this.lineHead.endFill();
    this.lineHead.rotation = angle + Math.PI / 2;
    this.lineHead.position.set(EffectPlayer.endPos.x, EffectPlayer.endPos.y);
    if (EffectPlayer.targetAttachFound) {
      this.linePoint.beginFill(CONSTANTS.COLOR.SECONDARY);
      this._drawCrossAtPosition(this.linePoint, EffectPlayer.endPos);
    }
  }
  _drawHoveredEffectElements() {
    const effects = new Set(SelectionManager.hoveredEffects);
    if (SelectionManager.hoveredEffectUI)
      effects.add(SelectionManager.hoveredEffectUI);
    for (const effect2 of effects) {
      if (!effect2 || effect2 === SelectionManager.selectedEffect || effect2.data.screenSpace || effect2._isEnding)
        continue;
      this._drawBoxAroundEffect(this.effectHoverBoxes, effect2);
    }
  }
  _drawSelectedEffectElements() {
    if (!SelectionManager.selectedEffect) return;
    this._drawBoxAroundEffect(
      this.effectSelectionBorder,
      SelectionManager.selectedEffect,
      true
    );
    this._drawEffectStartEndPoints(SelectionManager.selectedEffect);
  }
  _drawBoxAroundEffect(graphic, effect2, selected = false) {
    if (!effect2 || effect2._destroyed || !effect2.spriteContainer || !effect2.ready)
      return;
    graphic.lineStyle(3, selected ? CONSTANTS.COLOR.PRIMARY : 16777215, 0.9);
    let boundingBox = effect2.sprite.getLocalBounds();
    let dimensions = {
      x: effect2.position.x + boundingBox.x,
      y: effect2.position.y + boundingBox.y,
      width: boundingBox.width,
      height: boundingBox.height
    };
    if (effect2.data.shapes.length) {
      for (const shape of Object.values(effect2.shapes)) {
        boundingBox = shape.getLocalBounds();
        dimensions = {
          x: Math.min(
            dimensions.x,
            effect2.position.x + boundingBox.x * shape.scale.x
          ),
          y: Math.min(
            dimensions.y,
            effect2.position.y + boundingBox.y * shape.scale.y
          ),
          width: Math.max(dimensions.width, boundingBox.width * shape.scale.x),
          height: Math.max(
            dimensions.height,
            boundingBox.height * shape.scale.y
          )
        };
      }
    }
    const rotation2 = Math.normalizeRadians(
      effect2.rotationContainer.rotation + effect2.spriteContainer.rotation + effect2.sprite.rotation
    );
    this._drawRectangle(graphic, effect2.position, rotation2, dimensions);
  }
  _drawRectangle(graphic, position, rotation2, dimensions) {
    graphic.moveTo(
      ...rotate_coordinate(
        position,
        {
          x: dimensions.x,
          y: dimensions.y
        },
        -rotation2
      )
    );
    graphic.lineTo(
      ...rotate_coordinate(
        position,
        {
          x: dimensions.x + dimensions.width,
          y: dimensions.y
        },
        -rotation2
      )
    );
    graphic.lineTo(
      ...rotate_coordinate(
        position,
        {
          x: dimensions.x + dimensions.width,
          y: dimensions.y + dimensions.height
        },
        -rotation2
      )
    );
    graphic.lineTo(
      ...rotate_coordinate(
        position,
        {
          x: dimensions.x,
          y: dimensions.y + dimensions.height
        },
        -rotation2
      )
    );
    graphic.lineTo(
      ...rotate_coordinate(
        position,
        {
          x: dimensions.x,
          y: dimensions.y
        },
        -rotation2
      )
    );
    graphic.lineTo(
      ...rotate_coordinate(
        position,
        {
          x: dimensions.x + dimensions.width,
          y: dimensions.y
        },
        -rotation2
      )
    );
  }
  /**
   * Draws the start/end point circles
   * @private
   */
  _drawEffectStartEndPoints(effect2) {
    if (!effect2 || effect2._destroyed || !effect2.spriteContainer) return;
    if (!effect2.data.stretchTo) return;
    const sourcePos = effect2.sourcePosition;
    const targetPos = effect2.targetPosition;
    if (!sourcePos || !targetPos) return;
    this.effectSourcePosition.beginFill(CONSTANTS.COLOR.PRIMARY);
    this.effectSourcePosition.drawCircle(
      sourcePos.x,
      sourcePos.y,
      canvas.grid.size * 0.25
    );
    if (typeof effect2.data.source === "string") {
      this._drawCrossAtPosition(
        this.effectSourcePosition,
        sourcePos
      );
    }
    this.effectTargetPosition.beginFill(CONSTANTS.COLOR.SECONDARY);
    this.effectTargetPosition.drawCircle(
      targetPos.x,
      targetPos.y,
      canvas.grid.size * 0.25
    );
    this.effectTargetPosition.alpha = 0.75;
    if (typeof effect2.data.target === "string") {
      this._drawCrossAtPosition(
        this.effectTargetPosition,
        targetPos
      );
    }
  }
  _drawSuggestionPoint() {
    if (!SelectionManager.suggestedProperties || !SelectionManager.selectedEffect)
      return;
    const effect2 = SelectionManager.selectedEffect;
    const suggestion = SelectionManager.suggestedProperties;
    this.suggestionPoint.position.set(0, 0);
    this.suggestionPoint.rotation = 0;
    if (effect2.data.stretchTo) {
      this.suggestionPoint.beginFill(suggestion.color);
      this.suggestionPoint.drawCircle(
        suggestion.position.x,
        suggestion.position.y,
        canvas.grid.size * 0.25
      );
      if (suggestion.showCursor) {
        this._drawCrossAtPosition(this.suggestionPoint, suggestion.position);
      }
      return;
    }
    const boundingBox = effect2.spriteContainer.getLocalBounds();
    const dimensions = {
      x: boundingBox.x * effect2.scale.x,
      y: boundingBox.y * effect2.scale.y,
      width: boundingBox.width * effect2.scale.x,
      height: boundingBox.height * effect2.scale.y
    };
    this.suggestionPoint.lineStyle(3, CONSTANTS.COLOR.PRIMARY, 0.9);
    this.suggestionPoint.position.set(
      suggestion.position.x,
      suggestion.position.y
    );
    this._drawRectangle(
      this.suggestionPoint,
      suggestion.position,
      effect2.rotation,
      dimensions,
      true
    );
    if (suggestion.showCursor) {
      this.suggestionPoint.beginFill(CONSTANTS.COLOR.SECONDARY);
      this._drawCrossAtPosition(this.suggestionPoint);
    }
    if (suggestion.showPoint) {
      this.suggestionPoint.drawCircle(0, 0, canvas.grid.size * 0.2);
    }
  }
  _drawCrossAtPosition(inElement, inPosition = { x: 0, y: 0 }) {
    inElement.drawRect(
      inPosition.x + canvas.grid.size * -0.05,
      inPosition.y + canvas.grid.size * -0.5,
      canvas.grid.size * 0.1,
      canvas.grid.size
    );
    inElement.drawRect(
      inPosition.x + canvas.grid.size * -0.5,
      inPosition.y + canvas.grid.size * -0.05,
      canvas.grid.size,
      canvas.grid.size * 0.1
    );
  }
}
class UIEffectsLayer extends foundry.canvas.layers.InteractionLayer {
  static get layerOptions() {
    return foundry.utils.mergeObject(super.layerOptions, {
      zIndex: 999999999999999,
      name: "sequencerEffectsAboveEverything"
    });
  }
  async _draw(options) {
    await super._draw(options);
    this.sortableChildren = true;
  }
  updateTransform() {
    if (this.sortableChildren && this.sortDirty) {
      this.sortChildren();
    }
    this._boundsID++;
    this.transform.updateTransform(PIXI.Transform.IDENTITY);
    this.worldAlpha = this.alpha;
    for (let child2 of this.children) {
      if (child2.visible) {
        child2.updateTransform();
      }
    }
  }
}
let ABOVE_UI_LAYER = false;
class SequencerAboveUILayer {
  constructor(name2, zIndex = 0.1) {
    this.canvas = document.createElement("canvas");
    this.canvas.id = name2;
    this.canvas.style.cssText = `
            position:absolute;
            touch-action: none;
            pointer-events: none;
            width:100%;
            height:100%;
            z-index:${zIndex};
            padding: 0;
            margin: 0;
        `;
    document.body.appendChild(this.canvas);
    this.app = new PIXI.Application({
      width: window.innerWidth,
      height: window.innerHeight,
      view: this.canvas,
      antialias: true,
      backgroundAlpha: 0,
      sharedTicker: true
    });
    this.app.resizeTo = window;
    this.app.stage.renderable = false;
    this.app.stage.sortableChildren = true;
    PIXI.Ticker.shared.remove(this.app.render, this.app);
    this._renderTickerActive = false;
  }
  static getLayer() {
    return ABOVE_UI_LAYER ? ABOVE_UI_LAYER.app.stage : canvas.sequencerEffectsUILayer;
  }
  static addChild(...args) {
    if (!ABOVE_UI_LAYER && game.settings.get("sequencer", "enable-above-ui-screenspace")) {
      ABOVE_UI_LAYER = new this("sequencerUILayerAbove", 1e4);
    }
    const targetLayer = this.getLayer();
    const result = targetLayer.addChild(...args);
    if (ABOVE_UI_LAYER && targetLayer === ABOVE_UI_LAYER.app.stage) {
      targetLayer.renderable = targetLayer.children.length > 0;
      if (targetLayer.renderable && !ABOVE_UI_LAYER._renderTickerActive) {
        PIXI.Ticker.shared.add(
          ABOVE_UI_LAYER.app.render,
          ABOVE_UI_LAYER.app,
          PIXI.UPDATE_PRIORITY.LOW
        );
        ABOVE_UI_LAYER._renderTickerActive = true;
      }
    }
    return result;
  }
  static sortChildren() {
    this.getLayer().sortDirty = true;
  }
  static removeContainerByEffect(inEffect) {
    const targetLayer = this.getLayer();
    if (!targetLayer) return;
    targetLayer.removeChild(inEffect);
    if (ABOVE_UI_LAYER && targetLayer === ABOVE_UI_LAYER.app.stage) {
      targetLayer.renderable = targetLayer.children.length > 0;
      if (!targetLayer.renderable && ABOVE_UI_LAYER._renderTickerActive) {
        try {
          ABOVE_UI_LAYER.app.render();
        } finally {
          PIXI.Ticker.shared.remove(
            ABOVE_UI_LAYER.app.render,
            ABOVE_UI_LAYER.app
          );
          ABOVE_UI_LAYER._renderTickerActive = false;
        }
      }
    }
  }
}
const tempMat = new PIXI.Matrix();
const uvPoint = new PIXI.Point();
class VisionSamplerShaderGenerator extends foundry.canvas.rendering.batching.BatchShaderGenerator {
  generateSampleSrc(maxTextures) {
    let src = "\n\n";
    for (let i = 0; i < maxTextures; i++) {
      if (i > 0) {
        src += "\nelse ";
      }
      if (i < maxTextures - 1) {
        src += `if(vTextureId < ${i}.5)`;
      }
      src += "\n{";
      src += `
	color = texture(uSamplers[${i}], coord, unclamped == coord ? 0.0f : -32.0f);`;
      src += "\n}";
    }
    src += "\n";
    src += "\n";
    return src;
  }
}
const emptyColorMatrix = new Uint8Array(20);
class VisionSamplerShader extends foundry.canvas.rendering.shaders.BaseSamplerShader {
  static classPluginName = "sequencerVisionBatch";
  static batchShaderGeneratorClass = VisionSamplerShaderGenerator;
  static batchGeometry = [
    { id: "aVertexPosition", size: 2, normalized: false, type: PIXI.TYPES.FLOAT },
    { id: "aTextureCoord", size: 2, normalized: false, type: PIXI.TYPES.FLOAT },
    { id: "aClampOffset", size: 2, normalized: false, type: PIXI.TYPES.FLOAT },
    { id: "aClampFrame", size: 4, normalized: false, type: PIXI.TYPES.FLOAT },
    { id: "aMapCoord1", size: 3, normalized: false, type: PIXI.TYPES.FLOAT },
    { id: "aMapCoord2", size: 3, normalized: false, type: PIXI.TYPES.FLOAT },
    { id: "aMapCoord3", size: 3, normalized: false, type: PIXI.TYPES.FLOAT },
    { id: "aColor", size: 4, normalized: true, type: PIXI.TYPES.UNSIGNED_BYTE },
    { id: "aColorMatrixR", size: 4, normalized: false, type: PIXI.TYPES.FLOAT },
    { id: "aColorMatrixG", size: 4, normalized: false, type: PIXI.TYPES.FLOAT },
    { id: "aColorMatrixB", size: 4, normalized: false, type: PIXI.TYPES.FLOAT },
    { id: "aColorMatrixA", size: 4, normalized: false, type: PIXI.TYPES.FLOAT },
    { id: "aColorMatrixOffset", size: 4, normalized: false, type: PIXI.TYPES.FLOAT },
    { id: "aColorMatrixAlpha", size: 1, normalized: false, type: PIXI.TYPES.FLOAT },
    { id: "aShaderFlags", size: 1, normalized: false, type: PIXI.TYPES.UNSIGNED_SHORT },
    { id: "aTextureId", size: 1, normalized: false, type: PIXI.TYPES.UNSIGNED_SHORT }
  ];
  static #SHADER_FLAGS = foundry.utils.BitMask.generateShaderBitMaskConstants([
    "IS_TILING",
    "IS_VISION_MASKING_ENABLED",
    "IS_COLOR_MATRIX_ENABLED"
  ]);
  /** @override */
  static batchVertexSize = 42;
  /** @override */
  static reservedTextureUnits = 1;
  // We need a texture unit for the occlusion texture
  /**
   * The batch vertex shader source.
   * @type {string}
   */
  static batchVertexShader = (
    /* glsl */
    `#version 300 es
	precision highp float;
	
	uniform vec2 screenDimensions;
	uniform mat3 projectionMatrix;
	uniform mat3 translationMatrix;
	uniform vec4 tint;
	
	in vec2 aVertexPosition;
	in vec2 aTextureCoord;
	in vec2 aClampOffset;
	in vec4 aClampFrame;
	in vec3 aMapCoord1;
	in vec3 aMapCoord2;
	in vec3 aMapCoord3;
	in vec4 aColor;
	in vec4 aColorMatrixR;
	in vec4 aColorMatrixG;
	in vec4 aColorMatrixB;
	in vec4 aColorMatrixA;
	in vec4 aColorMatrixOffset;
	in float aColorMatrixAlpha;
	in float aShaderFlags;
	in float aTextureId;
	
	out vec2 vTextureCoord;
	out vec2 vVisionCoord;
	flat out vec2 vClampOffset;
	flat out vec4 vClampFrame;
	flat out mat3 vMapCoord;
	flat out vec4 vColor;
	flat out mat4 vColorMatrix;
	flat out vec4 vColorMatrixOffset;
	flat out float vColorMatrixAlpha;
	flat out uint vShaderFlags;
	flat out float vTextureId;
	
	void main(void) {
		gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0f)).xy, 0.0f, 1.0f);
		vTextureCoord = aTextureCoord;
		vVisionCoord = aVertexPosition / screenDimensions;
		vColor = aColor * tint;
		vClampOffset = aClampOffset;
		vClampFrame = aClampFrame;
		vMapCoord = mat3(aMapCoord1, aMapCoord2, aMapCoord3);
		vColorMatrix = mat4(aColorMatrixR, aColorMatrixG, aColorMatrixB, aColorMatrixA);
		vColorMatrixOffset = aColorMatrixOffset;
		vColorMatrixAlpha = aColorMatrixAlpha;
		vShaderFlags = uint(aShaderFlags);
		vTextureId = aTextureId;
	}
	`
  );
  /**
   * The batch fragment shader source.
   * @type {string}
   */
  static batchFragmentShader = (
    /* glsl */
    `#version 300 es
	precision mediump float;

	in vec2 vTextureCoord;
	in vec2 vVisionCoord;
	flat in float vTextureId;
	flat in vec4 vColor;
	flat in mat3 vMapCoord;
	flat in uint vShaderFlags;
	flat in vec2 vClampOffset;
	flat in vec4 vClampFrame;
	flat in mat4 vColorMatrix;
	flat in vec4 vColorMatrixOffset;
	flat in float vColorMatrixAlpha;

	uniform bool enableVisionMasking;
	uniform sampler2D visionMaskTexture;
	uniform sampler2D uSamplers[%count%];
	out vec4 fragColor;

	${VisionSamplerShader.#SHADER_FLAGS}

	void main(void){
		bool isTilingEnabled = ((vShaderFlags & IS_TILING) == IS_TILING);
		bool isVisionMaskingEnabled = ((vShaderFlags & IS_VISION_MASKING_ENABLED) == IS_VISION_MASKING_ENABLED);
		bool isColorMatrixEnabled = ((vShaderFlags & IS_COLOR_MATRIX_ENABLED) == IS_COLOR_MATRIX_ENABLED);

		vec2 coord = vTextureCoord;
		vec2 unclamped;
		if (isTilingEnabled) {
			coord = coord + ceil(vClampOffset - coord);
			coord = (vMapCoord * vec3(coord, 1.0)).xy;
			unclamped = coord;
			coord = clamp(coord, vClampFrame.xy, vClampFrame.zw);
		} else {
			unclamped = coord;
	  }
	  float mask = isVisionMaskingEnabled && enableVisionMasking ? texture(visionMaskTexture, vVisionCoord).r : 1.0;

	  vec4 color;
	  %forloop%

		if (isColorMatrixEnabled && vColorMatrixAlpha > 0.0) {
			if (color.a > 0.0) {
	      color.rgb /= color.a;
	    }
			vec4 result = color * vColorMatrix + vColorMatrixOffset;

			vec3 rgb = mix(color.rgb, result.rgb, vColorMatrixAlpha);
			rgb *= result.a;
			color = vec4(rgb, result.a);
	  }
	  fragColor = color * mask * vColor;
	}	
	`
  );
  /** @override */
  static batchDefaultUniforms(maxTex) {
    return {
      screenDimensions: [1, 1],
      visionMaskTexture: maxTex
    };
  }
  /** @override */
  static _preRenderBatch(batchRenderer) {
    const uniforms = batchRenderer._shader.uniforms;
    uniforms.screenDimensions = canvas.screenDimensions;
    uniforms.enableVisionMasking = canvas?.visibility?.visible ?? canvas?.effects?.visibility?.visible ?? true;
    batchRenderer.renderer.texture.bind(canvas.masks.vision.renderTexture, uniforms.visionMaskTexture);
  }
  /** @override */
  static _packInterleavedGeometry(element, attributeBuffer, indexBuffer, aIndex, iIndex) {
    const { float32View, uint32View, uint16View } = attributeBuffer;
    const mesh = element.object;
    const packedVertices = aIndex / this.vertexSize;
    const indices = element.indices;
    for (let i = 0; i < indices.length; i++) {
      indexBuffer[iIndex++] = packedVertices + indices[i];
    }
    const vertexData = element.vertexData;
    let uvs = element.uvs;
    const alpha = Math.min(element.worldAlpha, 1);
    const baseTexture = element._texture.baseTexture;
    const argb = PIXI.Color.shared.setValue(element._tintRGB).toPremultiplied(alpha, baseTexture.alphaMode > 0);
    const textureId = element._texture.baseTexture._batchLocation;
    const mapCoord = mesh.uvMatrix.mapCoord.toArray(true);
    let clampFrame = mesh.uvMatrix.uClampFrame;
    let clampOffset = mesh.uvMatrix.uClampOffset;
    if (mesh.tiling) {
      const tex = element._texture;
      const w = tex.width;
      const h = tex.height;
      const W = mesh.width;
      const H = mesh.height;
      const lt = mesh.tileTransform.localTransform;
      tempMat.set(lt.a * w / W, lt.b * w / H, lt.c * h / W, lt.d * h / H, lt.tx / W, lt.ty / H);
      const anchorX = mesh.uvRespectAnchor ? mesh.anchor.x : 0;
      const anchorY = mesh.uvRespectAnchor ? mesh.anchor.y : 0;
      uvs[0] = uvs[6] = -anchorX;
      uvs[1] = uvs[3] = -anchorY;
      uvs[2] = uvs[4] = 1 - anchorX;
      uvs[5] = uvs[7] = 1 - anchorY;
    }
    const vertexSize = this.vertexSize;
    const colorMatrixArray = mesh.colorMatrixFilter?.matrix ?? emptyColorMatrix;
    const colorMatrixAlpha = mesh.colorMatrixFilter?.alpha ?? 1;
    for (let i = 0, j = 0; i < vertexData.length; i += 2, j += vertexSize) {
      uvPoint.set(uvs[i], uvs[i + 1]);
      if (mesh.tiling) {
        tempMat.applyInverse(uvPoint, uvPoint);
      }
      let k = aIndex + j;
      float32View[k++] = vertexData[i];
      float32View[k++] = vertexData[i + 1];
      float32View[k++] = uvPoint.x;
      float32View[k++] = uvPoint.y;
      float32View[k++] = clampOffset[0];
      float32View[k++] = clampOffset[1];
      float32View[k++] = clampFrame[0];
      float32View[k++] = clampFrame[1];
      float32View[k++] = clampFrame[2];
      float32View[k++] = clampFrame[3];
      float32View[k++] = mapCoord[0];
      float32View[k++] = mapCoord[1];
      float32View[k++] = mapCoord[2];
      float32View[k++] = mapCoord[3];
      float32View[k++] = mapCoord[4];
      float32View[k++] = mapCoord[5];
      float32View[k++] = mapCoord[6];
      float32View[k++] = mapCoord[7];
      float32View[k++] = mapCoord[8];
      uint32View[k++] = argb;
      float32View[k++] = colorMatrixArray[0];
      float32View[k++] = colorMatrixArray[1];
      float32View[k++] = colorMatrixArray[2];
      float32View[k++] = colorMatrixArray[3];
      float32View[k++] = colorMatrixArray[5];
      float32View[k++] = colorMatrixArray[6];
      float32View[k++] = colorMatrixArray[7];
      float32View[k++] = colorMatrixArray[8];
      float32View[k++] = colorMatrixArray[10];
      float32View[k++] = colorMatrixArray[11];
      float32View[k++] = colorMatrixArray[12];
      float32View[k++] = colorMatrixArray[13];
      float32View[k++] = colorMatrixArray[15];
      float32View[k++] = colorMatrixArray[16];
      float32View[k++] = colorMatrixArray[17];
      float32View[k++] = colorMatrixArray[18];
      float32View[k++] = colorMatrixArray[4];
      float32View[k++] = colorMatrixArray[9];
      float32View[k++] = colorMatrixArray[14];
      float32View[k++] = colorMatrixArray[19];
      float32View[k++] = colorMatrixAlpha;
      k <<= 1;
      uint16View[k++] = mesh.shaderFlags.valueOf();
      uint16View[k++] = textureId;
    }
  }
  // need to override createPlugin as canvas.performance is not defined in foundry when
  // we need it to be
  static createPlugin() {
    const shaderClass = this;
    const geometryClass = Array.isArray(shaderClass.batchGeometry) ? class BatchGeometry extends PIXI.Geometry {
      constructor(_static = false) {
        super();
        this._buffer = new PIXI.Buffer(null, _static, false);
        this._indexBuffer = new PIXI.Buffer(null, _static, true);
        for (const { id, size, normalized, type } of shaderClass.batchGeometry) {
          this.addAttribute(id, this._buffer, size, normalized, type);
        }
        this.addIndex(this._indexBuffer);
      }
    } : shaderClass.batchGeometry;
    return class BatchPlugin extends shaderClass.batchRendererClass {
      /** @override */
      static get shaderGeneratorClass() {
        return shaderClass.batchShaderGeneratorClass;
      }
      /* ---------------------------------------- */
      /** @override */
      static get defaultVertexSrc() {
        return shaderClass.batchVertexShader;
      }
      /* ---------------------------------------- */
      /** @override */
      static get defaultFragmentTemplate() {
        return shaderClass.batchFragmentShader;
      }
      /* ---------------------------------------- */
      /** @override */
      static get defaultUniforms() {
        return shaderClass.batchDefaultUniforms;
      }
      /* ---------------------------------------- */
      /**
       * The batch plugin constructor.
       * @param {PIXI.Renderer} renderer    The renderer
       */
      constructor(renderer) {
        super(renderer);
        this.geometryClass = geometryClass;
        this.vertexSize = shaderClass.batchVertexSize;
        this.reservedTextureUnits = shaderClass.reservedTextureUnits;
        this._packInterleavedGeometry = shaderClass._packInterleavedGeometry;
        this._preRenderBatch = shaderClass._preRenderBatch;
      }
      /* ---------------------------------------- */
      /** @inheritdoc */
      setShaderGenerator(options) {
        super.setShaderGenerator(options);
      }
      /* ---------------------------------------- */
      /** @inheritdoc */
      contextChange() {
        this.shaderGenerator = null;
        super.contextChange();
      }
    };
  }
}
class TilingSpriteMesh extends foundry.canvas.containers.SpriteMesh {
  /** @type {PIXI.Transform} */
  tileTransform;
  /** @type {PIXI.TextureMatrix} */
  uvMatrix;
  /** @type {PIXI.ColorMatrixFilter} */
  #colorMatrixFilter;
  shaderFlags = new foundry.utils.BitMask({
    isTiling: false,
    isVisionMaskingEnabled: true,
    isColorMatrixEnabled: false
  });
  /**
   * @param {PIXI.Texture} texture - The {@link PIXI.Texture} bound to this mesh
   * @param {object} [options = {}]
   * @property {typeof BaseSamplerShader} options.shaderClassader Shader class used by this sprite mesh.
   * @property {boolean} options.tiling Shader class used by this sprite mesh.
   * @property {boolean} options.isVisionMaskingEnabled Shader class used by this sprite mesh.
   */
  constructor(texture, { shaderClass = VisionSamplerShader, tiling = false, isVisionMaskingEnabled = true } = {}) {
    super(texture, shaderClass);
    this.tileTransform = new PIXI.Transform();
    this.tiling = tiling;
    this.isVisionMaskingEnabled = isVisionMaskingEnabled;
    this.uvMatrix = this.texture.uvMatrix || new PIXI.TextureMatrix(texture);
  }
  get tiling() {
    return this.shaderFlags.hasState("isTiling");
  }
  /**
   * @param {boolean} value
   */
  set tiling(value) {
    this.shaderFlags.toggleState("isTiling", value);
  }
  get isVisionMaskingEnabled() {
    return this.shaderFlags.hasState("isVisionMaskingEnabled");
  }
  /**
   * @param {boolean} value
   */
  set isVisionMaskingEnabled(value) {
    this.shaderFlags.toggleState("isVisionMaskingEnabled", value);
  }
  /**
   * @param {PIXI.ColorMatrixFilter | null} value
   */
  set colorMatrixFilter(value) {
    this.shaderFlags.toggleState("isColorMatrixEnabled", value != null);
    this.#colorMatrixFilter = value;
  }
  get colorMatrixFilter() {
    return this.#colorMatrixFilter;
  }
  /** The scaling of the image that is being tiled. */
  get tileScale() {
    return this.tileTransform.scale;
  }
  set tileScale(value) {
    this.tileTransform.scale.copyFrom(value);
  }
  /** The offset of the image that is being tiled. */
  get tilePosition() {
    return this.tileTransform.position;
  }
  set tilePosition(value) {
    this.tileTransform.position.copyFrom(value);
  }
  _onTextureUpdate() {
    super._onTextureUpdate();
    if (this.uvMatrix) {
      this.uvMatrix.texture = this._texture;
    }
  }
  /**
   * Renders the object using the WebGL renderer
   * @param renderer - The renderer
   */
  _render(renderer) {
    this.uvMatrix.update();
    this.tileTransform.updateLocalTransform();
    super._render(renderer);
  }
  destroy() {
    super.destroy();
    this.tileTransform = null;
    this.uvMatrix = null;
  }
}
class AnimatedSpriteMesh extends TilingSpriteMesh {
  /**
   * @param textures - An array of {@link PIXI.Texture} or frame
   *  objects that make up the animation.
   * @param {object} [options = {}]
   * @property {typeof BaseSamplerShader} options.shaderClassader Shader class used by this sprite mesh.
   * @property {boolean} options.tiling Shader class used by this sprite mesh.
   * @property {boolean} options.isVisionMaskingEnabled Shader class used by this sprite mesh.
   */
  constructor(textures, { autoUpdate = true, shaderClass = VisionSamplerShader, isVisionMaskingEnabled = true, tiling } = {}) {
    const texture = textures[0] instanceof PIXI.Texture ? textures[0] : textures[0].texture;
    super(texture, { shaderClass, tiling, isVisionMaskingEnabled });
    this._textures = [];
    this._durations = null;
    this._autoUpdate = autoUpdate;
    this._isConnectedToTicker = false;
    this.animationSpeed = 1;
    this.loop = true;
    this.updateAnchor = false;
    this.onComplete = null;
    this.onFrameChange = null;
    this.onLoop = null;
    this._currentTime = 0;
    this._playing = false;
    this._previousFrame = null;
    this.textures = textures;
  }
  /** Stops the AnimatedSprite. */
  stop() {
    if (!this._playing) {
      return;
    }
    this._playing = false;
    if (this._autoUpdate && this._isConnectedToTicker) {
      PIXI.Ticker.shared.remove(this.update, this);
      this._isConnectedToTicker = false;
    }
  }
  /** Plays the AnimatedSprite. */
  play() {
    if (this._playing) {
      return;
    }
    this._playing = true;
    if (this._autoUpdate && !this._isConnectedToTicker) {
      PIXI.Ticker.shared.add(this.update, this, PIXI.UPDATE_PRIORITY.HIGH);
      this._isConnectedToTicker = true;
    }
  }
  /**
   * Stops the AnimatedSprite and goes to a specific frame.
   * @param frameNumber - Frame index to stop at.
   */
  gotoAndStop(frameNumber) {
    this.stop();
    this.currentFrame = frameNumber;
  }
  /**
   * Goes to a specific frame and begins playing the AnimatedSprite.
   * @param frameNumber - Frame index to start at.
   */
  gotoAndPlay(frameNumber) {
    this.currentFrame = frameNumber;
    this.play();
  }
  /**
   * Updates the object transform for rendering.
   * @param deltaTime - Time since last tick.
   */
  update(deltaTime) {
    if (!this._playing) {
      return;
    }
    const elapsed = this.animationSpeed * deltaTime;
    const previousFrame = this.currentFrame;
    if (this._durations !== null) {
      let lag = this._currentTime % 1 * this._durations[this.currentFrame];
      lag += elapsed / 60 * 1e3;
      while (lag < 0) {
        this._currentTime--;
        lag += this._durations[this.currentFrame];
      }
      const sign = Math.sign(this.animationSpeed * deltaTime);
      this._currentTime = Math.floor(this._currentTime);
      while (lag >= this._durations[this.currentFrame]) {
        lag -= this._durations[this.currentFrame] * sign;
        this._currentTime += sign;
      }
      this._currentTime += lag / this._durations[this.currentFrame];
    } else {
      this._currentTime += elapsed;
    }
    if (this._currentTime < 0 && !this.loop) {
      this.gotoAndStop(0);
      if (this.onComplete) {
        this.onComplete();
      }
    } else if (this._currentTime >= this._textures.length && !this.loop) {
      this.gotoAndStop(this._textures.length - 1);
      if (this.onComplete) {
        this.onComplete();
      }
    } else if (previousFrame !== this.currentFrame) {
      if (this.loop && this.onLoop) {
        if (this.animationSpeed > 0 && this.currentFrame < previousFrame || this.animationSpeed < 0 && this.currentFrame > previousFrame) {
          this.onLoop();
        }
      }
      this.updateTexture();
    }
  }
  /** Updates the displayed texture to match the current frame index. */
  updateTexture() {
    const currentFrame = this.currentFrame;
    if (this._previousFrame === currentFrame) {
      return;
    }
    this._previousFrame = currentFrame;
    this._texture = this._textures[currentFrame];
    this._textureID = -1;
    this._textureTrimmedID = -1;
    this._cachedTint = [1, 1, 1, 1];
    this.uvs.set(this._texture._uvs.uvsFloat32);
    if (this.updateAnchor) {
      this._anchor.copyFrom(this._texture.defaultAnchor);
    }
    if (this.onFrameChange) {
      this.onFrameChange(this.currentFrame);
    }
    if (this.uvMatrix) {
      this.uvMatrix.texture = this._texture;
    }
  }
  destroy(options) {
    this.stop();
    super.destroy(options);
    this.onComplete = null;
    this.onFrameChange = null;
    this.onLoop = null;
  }
  /**
   * A short hand way of creating an AnimatedSprite from an array of frame ids.
   * @param frames - The array of frames ids the AnimatedSprite will use as its texture frames.
   * @returns - The new animated sprite with the specified frames.
   */
  static fromFrames(frames) {
    const textures = [];
    for (let i = 0; i < frames.length; ++i) {
      textures.push(PIXI.Texture.from(frames[i]));
    }
    return new AnimatedSprite(textures);
  }
  /**
   * A short hand way of creating an AnimatedSprite from an array of image ids.
   * @param images - The array of image urls the AnimatedSprite will use as its texture frames.
   * @returns The new animate sprite with the specified images as frames.
   */
  static fromImages(images) {
    const textures = [];
    for (let i = 0; i < images.length; ++i) {
      textures.push(PIXI.Texture.from(images[i]));
    }
    return new AnimatedSprite(textures);
  }
  /**
   * The total number of frames in the AnimatedSprite. This is the same as number of textures
   * assigned to the AnimatedSprite.
   * @readonly
   * @default
   */
  get totalFrames() {
    return this._textures.length;
  }
  /** The array of textures used for this AnimatedSprite. */
  get textures() {
    return this._textures;
  }
  set textures(value) {
    if (value[0] instanceof PIXI.Texture) {
      this._textures = value;
      this._durations = null;
    } else {
      this._textures = [];
      this._durations = [];
      for (let i = 0; i < value.length; i++) {
        this._textures.push(value[i].texture);
        this._durations.push(value[i].time);
      }
    }
    this._previousFrame = null;
    this.gotoAndStop(0);
    this.updateTexture();
  }
  /** The AnimatedSprite's current frame index. */
  get currentFrame() {
    let currentFrame = Math.floor(this._currentTime) % this._textures.length;
    if (currentFrame < 0) {
      currentFrame += this._textures.length;
    }
    return currentFrame;
  }
  set currentFrame(value) {
    if (value < 0 || value > this.totalFrames - 1) {
      throw new Error(
        `[AnimatedSprite]: Invalid frame index value ${value}, expected to be between 0 and totalFrames ${this.totalFrames}.`
      );
    }
    const previousFrame = this.currentFrame;
    this._currentTime = value;
    if (previousFrame !== this.currentFrame) {
      this.updateTexture();
    }
  }
  /**
   * Indicates if the AnimatedSprite is currently playing.
   * @readonly
   */
  get playing() {
    return this._playing;
  }
  /** Whether to use Ticker.shared to auto update animation time. */
  get autoUpdate() {
    return this._autoUpdate;
  }
  set autoUpdate(value) {
    if (value !== this._autoUpdate) {
      this._autoUpdate = value;
      if (!this._autoUpdate && this._isConnectedToTicker) {
        PIXI.Ticker.shared.remove(this.update, this);
        this._isConnectedToTicker = false;
      } else if (this._autoUpdate && !this._isConnectedToTicker && this._playing) {
        PIXI.Ticker.shared.add(this.update, this, PIXI.UPDATE_PRIORITY.HIGH);
        this._isConnectedToTicker = true;
      }
    }
  }
}
class Asset {
  destroy() {
  }
}
class TextureAsset extends Asset {
  filepath;
  texture;
  constructor({ filepath, texture }) {
    super();
    this.filepath = filepath;
    this.texture = texture;
  }
}
class VideoAsset extends Asset {
  /** @type {string} */
  filepath;
  /** @type {PIXI.Texture} */
  texture;
  /** @type {HTMLVideoElement  } */
  video;
  /**
   *
   * @param {object} params
   * @param {string} params.filepath
   * @param {PIXI.Texture} params.texture
   * @param {HTMLVideoElement} params.video
   */
  constructor({ filepath, texture, video }) {
    super();
    this.filepath = filepath;
    this.texture = texture;
    this.video = video;
  }
  destroy() {
    try {
      this.video.pause();
      URL.revokeObjectURL(this.video.src);
      this.video.removeAttribute("src");
      this.video.onerror = null;
      this.video.oncanplay = null;
      this.video.load();
      this.video = null;
    } catch (err) {
    }
    this.texture.destroy(true);
  }
}
class VideoSpritesheetAsset extends Asset {
  /** @type {string} */
  filepath;
  /** @type {PIXI.Spritesheet} */
  spritesheet;
  /** @type {PIXI.FrameObject[]} */
  frameObjects;
  /** @type {number} */
  framerate;
  /**
   * @param {object} params
   * @param {string} params.filepath
   * @param {PIXI.Spritesheet} params.spritesheet
   */
  constructor({ filepath, spritesheet }) {
    super();
    this.filepath = filepath;
    this.spritesheet = spritesheet;
    this.framerate = this.spritesheet.data?.meta?.frameRate ?? 30;
    const frametime = 1 / this.framerate * 1e3;
    this.frameObjects = (Object.values(spritesheet.animations)[0] ?? []).map((texture) => ({
      texture,
      time: frametime
    }));
    this.#register();
  }
  destroy() {
    return SequencerFileCache.unloadSpritesheet(this.filepath);
  }
  #register() {
    SequencerFileCache.registerSpritesheet(this.filepath, this.spritesheet);
  }
}
class FlipbookAsset extends Asset {
  /** @type {string[]} */
  filepaths;
  /** @type {PIXI.FrameObject[]} */
  frameObjects;
  /** @type {number} */
  framerate;
  /**
   * @param {object} params
   * @param {string[]} params.filepaths
   * @param {PIXI.Texture[]} params.textures
   * @param {number} params.framerate
   */
  constructor({ filepaths, textures, framerate = 24 }) {
    super();
    this.filepaths = filepaths;
    this.framerate = framerate;
    const frametime = 1 / framerate * 1e3;
    this.frameObjects = textures.map((texture) => ({ texture, time: frametime }));
  }
  destroy() {
  }
}
class PlaybackControls {
  destroy() {
  }
}
class VideoPlaybackControls extends PlaybackControls {
  /** @type {HTMLVideoElement} */
  #video;
  /** @type {PIXI.Texture} */
  #texture;
  /**
   * @param {HTMLVideoElement} video
   * @param {PIXI.Texture} texture
   */
  constructor(video, texture) {
    super();
    this.#video = video;
    this.#texture = texture;
  }
  async play() {
    try {
      await this.#video.play();
      this.#texture.update();
    } catch (error) {
    }
  }
  stop() {
    this.#video.pause();
  }
  get duration() {
    return this.#video.duration;
  }
  get isPlaying() {
    return !this.#video.paused;
  }
  get loop() {
    return this.#video.loop;
  }
  set loop(value) {
    this.#video.loop = value;
  }
  get volume() {
    return this.#video.volume;
  }
  set volume(value) {
    this.#video.muted = !value;
    this.#video.volume = value;
  }
  get currentTime() {
    return this.#video.currentTime;
  }
  set currentTime(value) {
    this.#video.currentTime = value;
  }
  get playbackRate() {
    return this.#video.playbackRate;
  }
  set playbackRate(value) {
    this.#video.playbackRate = value;
  }
  destroy() {
    this.stop();
    this.#video = null;
    this.#texture = null;
  }
}
class SpritePlaybackControls extends PlaybackControls {
  /** @type {AnimatedSpriteMesh} */
  #sprite;
  /** @type {number} */
  #framerate;
  /** @type {number} */
  #framecount;
  /**
   * @param {AnimatedSpriteMesh} sprite
   * @param {number} framerate
   * @param {number} framecount
   */
  constructor(sprite, framerate, framecount) {
    super();
    this.#sprite = sprite;
    this.#framerate = framerate;
    this.#framecount = framecount;
  }
  async play() {
    this.#sprite.play();
  }
  stop() {
    this.#sprite.stop();
  }
  get duration() {
    return this.#framecount / this.#framerate;
  }
  get isPlaying() {
    return this.#sprite.playing;
  }
  get loop() {
    return this.#sprite.loop;
  }
  set loop(value) {
    this.#sprite.loop = value;
  }
  get volume() {
    return 0;
  }
  set volume(_value) {
  }
  get currentTime() {
    const accurateTime = (this.#sprite.currentFrame + 1) / this.#framerate;
    return Math.round(accurateTime * 1e3) / 1e3;
  }
  set currentTime(value) {
    const newFrame = Math.floor(value * this.#framerate);
    const newFrameIndex = Math.clamp(newFrame, 0, this.#framecount - 1);
    if (this.#sprite.playing) {
      this.#sprite.gotoAndPlay(newFrameIndex);
    } else {
      this.#sprite.gotoAndStop(newFrameIndex);
    }
  }
  get playbackRate() {
    return this.#sprite.animationSpeed;
  }
  set playbackRate(value) {
    this.#sprite.animationSpeed = value;
  }
  destroy() {
    this.stop();
    this.#sprite = null;
  }
}
class SequencerSpriteManager extends PIXI.Container {
  // @ts-ignore
  #id = foundry.utils.randomID();
  /** @type {import("../modules/sequencer-file.js").SequencerFile} */
  #file;
  /** @type {{ antialiasing: PIXI.SCALE_MODES; tiling?: boolean; xray?: boolean; isPersisted: boolean }} */
  #sharedSpriteConfig;
  /** @type {string | undefined} */
  #activeAssetPath;
  /** @type {Map<string | undefined, VideoAsset | VideoSpritesheetAsset | TextureAsset | FlipbookAsset>} */
  #relatedAssets = /* @__PURE__ */ new Map();
  /** @type {VideoPlaybackControls | SpritePlaybackControls | null} */
  #playbackControls;
  /** @type {Promise<void> | undefined} */
  #preloadingPromise;
  /** @type {TilingSpriteMesh | null} */
  #managedSprite;
  /** @type {PIXI.Text | null} */
  #textSprite;
  /** @type {{width: number, height: number, scaleX: number, scaleY: number}} */
  #defaultScaling = {
    width: 0,
    height: 0,
    scaleX: 1,
    scaleY: 1
  };
  get preloadingPromise() {
    if (this.#preloadingPromise) {
      return this.#preloadingPromise;
    }
    return Promise.resolve();
  }
  /**
   * @param {import("../modules/sequencer-file.js").SequencerFile} file
   * @param {{ texture?: null, antialiasing: PIXI.SCALE_MODES; tiling?: boolean; xray?: boolean; isPersisted: boolean }} options
   */
  constructor(file, options) {
    super();
    this.#sharedSpriteConfig = options ?? {
      texture: null,
      antialiasing: PIXI.SCALE_MODES.LINEAR,
      tiling: false,
      xray: false,
      isPersisted: false
    };
    this.#file = file;
  }
  //#region public api
  get activePath() {
    return this.#activeAssetPath;
  }
  get activeAsset() {
    return this.#relatedAssets.get(this.activePath);
  }
  get texture() {
    return this.managedSprite?.texture;
  }
  get managedSprite() {
    return this.#managedSprite;
  }
  /** @return {foundry.canvas.containers.PreciseText | null} */
  get textSprite() {
    return this.#textSprite;
  }
  /**
   * caches current scaling values to be used for spritesheet scaling if needed
   */
  updateDefaultScaling() {
    this.#defaultScaling = {
      scaleX: this.scale.x,
      scaleY: this.scale.y,
      width: this.width,
      height: this.height
    };
  }
  updateVideoTextures() {
    this.#managedSprite?.texture?.update();
  }
  /**
   * @param {string | undefined} filePath
   */
  async activate(filePath) {
    if (!filePath || this.#activeAssetPath === filePath) {
      return;
    }
    let nextAsset = this.#relatedAssets.get(filePath);
    if (this.#relatedAssets.has(filePath) && nextAsset == null) {
      return;
    }
    if (!nextAsset) {
      this.#relatedAssets.set(filePath, null);
      nextAsset = await this.#loadAsset(filePath);
      if (this.destroyed) {
        return;
      }
      this.#relatedAssets.set(filePath, nextAsset);
    }
    if (this.#activateAsset(nextAsset)) {
      this.#activeAssetPath = filePath;
    }
    if (this.#sharedSpriteConfig.isPersisted && !this.#sharedSpriteConfig.tiling) {
      requestAnimationFrame(async () => {
        const minimumScale = this.#calculateMinimumSpritesheetScale();
        const spritesheet = await SequencerFileCache.requestCompiledSpritesheet(filePath, {
          minimumScale
        });
        if (!spritesheet) {
          return;
        }
        const previousAsset = this.activeAsset;
        const asset = new VideoSpritesheetAsset({ filepath: filePath, spritesheet });
        this.#relatedAssets.set(filePath, asset);
        if (this.destroyed || this.#activeAssetPath !== filePath) {
          return;
        }
        this.#activateAsset(asset);
        previousAsset?.destroy();
      });
    }
  }
  /**
   * @param {{ text: string | null; textStyle: Partial<PIXI.ITextStyle> | PIXI.TextStyle }} textData
   */
  addText(textData) {
    if (this.#textSprite) {
      this.#textSprite.text = textData.text ?? "";
      return this.#textSprite;
    }
    const textSprite = new foundry.canvas.containers.PreciseText(textData.text ?? "", textData.textStyle);
    textSprite.resolution = 5;
    textSprite.zIndex = 1;
    textSprite.anchor.set(0.5, 0.5);
    this.#textSprite = textSprite;
    return this.addChild(textSprite);
  }
  removeText() {
    this.#textSprite?.destroy();
    this.#textSprite = null;
  }
  /**
   * @param {string | number} filepath
   */
  removeSprite(filepath) {
    this.#relatedAssets.get(filepath)?.destroy();
    this.#relatedAssets.delete(filepath);
  }
  async preloadVariants() {
    if (!this.#preloadingPromise) {
      return this.#preloadingPromise = this.#preloadVariants();
    }
    return this.#preloadingPromise;
  }
  destroy() {
    this.#playbackControls?.destroy();
    for (const asset of this.#relatedAssets.values()) {
      asset?.destroy();
    }
    this.#relatedAssets.clear();
    this.managedSprite?.removeFromParent();
    this.managedSprite?.destroy();
    super.destroy({ children: true });
  }
  //#endregion
  //#region Managed Sprite proxies
  async play() {
    this.#playbackControls?.play();
  }
  stop() {
    this.#playbackControls?.stop();
  }
  get tileScale() {
    return this.managedSprite?.tileScale;
  }
  set tileScale(point) {
    if (point) {
      this.managedSprite?.tileScale.copyFrom(point);
    }
  }
  get tilePosition() {
    return this.managedSprite?.tilePosition;
  }
  set tilePosition(point) {
    if (point) {
      this.managedSprite?.tilePosition.copyFrom(point);
    }
  }
  get anchor() {
    return this.managedSprite?.anchor;
  }
  set anchor(point) {
    if (point) {
      this.managedSprite?.anchor.copyFrom(point);
    }
  }
  get tint() {
    return this.managedSprite?.tint ?? 16777215;
  }
  set tint(value) {
    if (this.managedSprite) {
      this.managedSprite.tint = typeof value === "number" ? Math.floor(value) : value;
    }
  }
  get blendMode() {
    return this.managedSprite?.blendMode ?? PIXI.BLEND_MODES.NORMAL;
  }
  set blendMode(value) {
    if (this.managedSprite) {
      this.managedSprite.blendMode = value;
    }
  }
  get scale() {
    return this.managedSprite?.scale || super.scale;
  }
  set scale(point) {
    this.managedSprite?.scale.copyFrom(point);
  }
  get width() {
    return this.managedSprite?.width ?? 0;
  }
  set width(value) {
    if (!this.managedSprite) {
      return;
    }
    this.managedSprite.width = value;
  }
  get height() {
    return this.managedSprite?.height ?? 0;
  }
  set height(value) {
    if (!this.managedSprite) {
      return;
    }
    this.managedSprite.height = value;
  }
  get resolution() {
    return this.#textSprite?.resolution;
  }
  set resolution(value) {
    if (!this.#textSprite) return;
    this.#textSprite.resolution = value ?? 5;
  }
  /** @type {} */
  get hasAnimatedMedia() {
    return this.activeAsset instanceof VideoAsset || this.activeAsset instanceof VideoSpritesheetAsset || this.activeAsset instanceof FlipbookAsset;
  }
  get playing() {
    return this.#playbackControls?.isPlaying ?? false;
  }
  get duration() {
    return this.#playbackControls?.duration ?? 0;
  }
  get volume() {
    return this.#playbackControls?.volume ?? 0;
  }
  set volume(value) {
    if (!this.#playbackControls) return;
    this.#playbackControls.volume = value;
  }
  get loop() {
    return this.#playbackControls?.loop ?? false;
  }
  set loop(value) {
    if (!this.#playbackControls) return;
    this.#playbackControls.loop = value;
  }
  get currentTime() {
    return this.#playbackControls?.currentTime ?? 0;
  }
  set currentTime(value) {
    if (!this.#playbackControls) return;
    this.#playbackControls.currentTime = value;
  }
  get playbackRate() {
    return this.#playbackControls?.playbackRate ?? 1;
  }
  set playbackRate(value) {
    if (!this.#playbackControls) return;
    this.#playbackControls.playbackRate = value;
  }
  /** @return {PIXI.ColorMatrixFilter | null} */
  get colorMatrixFilter() {
    return this.managedSprite?.colorMatrixFilter ?? null;
  }
  set colorMatrixFilter(value) {
    if (!this.managedSprite) return;
    this.managedSprite.colorMatrixFilter = value;
    if (this.#textSprite) {
      if (value) {
        this.#textSprite.filters?.push(value);
      } else if (this.#textSprite.filters) {
        this.#textSprite.filters = this.#textSprite.filters.filter(
          (filter2) => !(filter2 instanceof PIXI.ColorMatrixFilter)
        );
      }
    }
  }
  async #preloadVariants() {
    if (this.#activeAssetPath === "TEXT") {
      return;
    }
    if (!this.#file || this.#file.isFlipbook) {
      return;
    }
    const allFiles = this.#file.getAllFiles();
    for (const filePath of allFiles) {
      if (this.#relatedAssets.has(filePath)) {
        continue;
      }
      const asset = await this.#loadAsset(filePath);
      this.#relatedAssets.set(filePath, asset);
    }
  }
  /**
   * @param {string} filepath
   */
  async #loadAsset(filepath) {
    if (this.#file && this.#file.isFlipbook) {
      return this.#loadFlipbook(this.#file.getAllFiles(), this.#file.originalMetadata);
    }
    const texture = this.#sharedSpriteConfig.texture ?? await SequencerFileCache.loadFile(filepath);
    if (texture?.baseTexture?.resource instanceof PIXI.CompressedTextureResource && texture?.baseTexture?.resource.levels === 1) {
      texture?.baseTexture?.setStyle(0, 0);
    } else if (this.#sharedSpriteConfig.antialiasing && this.#sharedSpriteConfig.antialiasing !== PIXI.SCALE_MODES.LINEAR) {
      texture?.baseTexture.setStyle(0, this.#sharedSpriteConfig.antialiasing);
    }
    if (texture instanceof PIXI.Spritesheet) {
      return new VideoSpritesheetAsset({ filepath, spritesheet: texture });
    }
    if (texture.baseTexture?.resource?.source instanceof HTMLVideoElement) {
      return new VideoAsset({ filepath, texture, video: texture.baseTexture.resource.source });
    }
    return new TextureAsset({ filepath, texture });
  }
  /**
   * @param {string[]} filepaths
   * @param {{ fps: number; }} metadata
   */
  async #loadFlipbook(filepaths, metadata) {
    const textures = (await Promise.all(filepaths.map(async (filepath) => foundry.canvas.loadTexture(filepath)))).filter(
      (t) => t instanceof PIXI.Texture
    );
    return new FlipbookAsset({ filepaths, textures, framerate: metadata?.fps ?? 24 });
  }
  /**
   * @param {VideoAsset | VideoSpritesheetAsset | TextureAsset | FlipbookAsset} nextAsset
   * @returns
   */
  #activateAsset(nextAsset) {
    let view;
    let controls;
    const tiling = this.#sharedSpriteConfig.tiling;
    const isVisionMaskingEnabled = !this.#sharedSpriteConfig.xray;
    if (nextAsset instanceof VideoAsset) {
      view = new TilingSpriteMesh(nextAsset.texture, { isVisionMaskingEnabled, tiling });
      controls = new VideoPlaybackControls(nextAsset.video, nextAsset.texture);
    } else if (nextAsset instanceof VideoSpritesheetAsset || nextAsset instanceof FlipbookAsset) {
      view = new AnimatedSpriteMesh(nextAsset.frameObjects, {
        autoUpdate: true,
        isVisionMaskingEnabled,
        tiling: !!tiling
      });
      controls = new SpritePlaybackControls(view, nextAsset.framerate, nextAsset.frameObjects.length);
    } else if (nextAsset instanceof TextureAsset) {
      view = new TilingSpriteMesh(nextAsset.texture, { isVisionMaskingEnabled, tiling });
      controls = null;
    } else {
      return false;
    }
    this.#applyPreviousValues(view, controls);
    if (this.#playbackControls) {
      this.#playbackControls.destroy();
    } else {
      controls?.play();
    }
    this.#playbackControls = controls;
    if (this.managedSprite) {
      this.managedSprite.removeFromParent();
      this.managedSprite.destroy();
    }
    this.#managedSprite = view;
    this.addChildAt(view, 0);
    return true;
  }
  /**
   *
   * @param {TilingSpriteMesh} view
   * @param {VideoPlaybackControls | SpritePlaybackControls | null} controls
   */
  #applyPreviousValues(view, controls) {
    if (this.managedSprite) {
      const prev = this.managedSprite;
      view.tint = prev.tint;
      view.anchor.copyFrom(prev.anchor);
      view.scale.copyFrom(prev.scale);
      view.width = prev.width;
      view.height = prev.height;
      view.tileScale.copyFrom(prev.tileScale);
      view.tilePosition.copyFrom(prev.tilePosition);
      view.pivot.copyFrom(prev.pivot);
      view.skew.copyFrom(prev.skew);
      view.alpha = prev.alpha;
      view.colorMatrixFilter = prev.colorMatrixFilter;
      view.position.copyFrom(prev.position);
      view.alphaMode = prev.alphaMode;
      view.blendMode = prev.blendMode;
      view.cullable = prev.cullable;
      view.renderable = prev.renderable;
      view.visible = prev.visible;
      view.tiling = prev.tiling;
    }
    if (controls && this.#playbackControls) {
      const prev = this.#playbackControls;
      controls.volume = prev.volume;
      controls.loop = prev.loop;
      controls.playbackRate = prev.playbackRate;
      controls.currentTime = prev.currentTime;
      if (prev.isPlaying) {
        controls.play();
      } else {
        controls.stop();
      }
    }
  }
  /**
   * calculates the minimum scale required for generated spritesheets
   * @returns {number}
   */
  #calculateMinimumSpritesheetScale() {
    const defaultScale = this.#defaultScaling;
    const maxScale = Math.max(defaultScale.scaleX || 1, defaultScale.scaleY || 1);
    return Math.min(maxScale, 1);
  }
}
class CrosshairsPlaceable extends foundry.canvas.placeables.MeasuredTemplate {
  constructor(...args) {
    super(...args);
    this.tag = "sequencer-crosshair-" + foundry.utils.randomID();
    this.cachedLocation = false;
  }
  #handlers = {
    mouseup: null,
    move: null,
    wheel: null
  };
  #promise = {
    resolve: null
  };
  #isDrag = false;
  #isPanning = false;
  #customText = false;
  #distanceText = false;
  #rangeHighlight = false;
  isValid = true;
  #lastPositions = false;
  get crosshair() {
    return this.document.crosshair;
  }
  get callbacks() {
    return this.document.callbacks;
  }
  get range() {
    const objLocation = this.crosshair.location.obj?.center ?? this.crosshair.location.obj;
    return canvas.grid.measurePath([objLocation, this.position]).distance;
  }
  updateCrosshair(data) {
    this.document.crosshair = foundry.utils.mergeObject(this.document.crosshair, data);
    if (this.#customText) {
      this.#customText.destroy();
      this.#customText = null;
    }
    if (this.#distanceText) {
      this.#distanceText.destroy();
      this.#distanceText = null;
    }
    if (this.#rangeHighlight) {
      this.#rangeHighlight.destroy();
      this.#rangeHighlight = null;
    }
    return this.draw();
  }
  async #refreshIcon() {
    this.controlIcon.renderable = !!this.crosshair.icon.texture;
    if (this.crosshair.icon.texture) {
      this.controlIcon.iconSrc = this.crosshair.icon.texture;
      this.controlIcon.texture = await foundry.canvas.loadTexture(this.controlIcon.iconSrc);
      this.controlIcon.icon.texture = this.controlIcon.texture;
    }
    if (!this.crosshair.icon.borderVisible) {
      this.controlIcon.bg.clear();
    }
  }
  _refreshRulerText() {
    const shapeWidth = this.shape.width ?? 0;
    const shapeHeight = this.shape.height ?? 0;
    if (this.crosshair.location.showRange && this.crosshair.location.obj) {
      if (!this.#distanceText && this.crosshair.location.obj) {
        const style = CONFIG.canvasTextStyle.clone();
        style.align = "center";
        this.#distanceText = this.template.addChild(new foundry.canvas.containers.PreciseText("", style));
      }
      const actualHeight = (this.shapeHeight || this.shape.radius) + canvas.grid.size / 2;
      this.#distanceText.anchor.set(0.5, 0.5);
      this.#distanceText.position.set(shapeWidth / 2, actualHeight);
      const { units } = this.document.parent.grid;
      this.#distanceText.text = this.range.toString() + " " + units;
    }
    this.ruler.renderable = !!this.crosshair.label?.text;
    if (!this.ruler.renderable) return;
    if (this.crosshair.label?.text) {
      if (!this.#customText) {
        const style = CONFIG.canvasTextStyle.clone();
        style.align = "center";
        this.#customText = this.template.addChild(new foundry.canvas.containers.PreciseText("", style));
      }
      if (this.#customText.text !== this.crosshair.label.text) this.#customText.text = this.crosshair.label.text;
      this.#customText.anchor.set(0.5);
      this.#customText.position.set(
        shapeWidth / 2 + (this.crosshair.label.dx ?? 0),
        shapeHeight / 2 + (this.crosshair.label.dy ?? 0)
      );
    } else {
      if (this.#customText) {
        this.#customText.text = "";
        this.#customText.destroy();
        this.#customText = null;
      }
      return super._refreshRulerText();
    }
  }
  _refreshTemplate() {
    const t = this.template.clear();
    t.lineStyle(this._borderThickness, this.document.borderColor, this.crosshair.borderAlpha).beginFill(0, 0);
    if (this.texture) {
      const d = canvas.dimensions;
      let { direction, distance } = this.document;
      distance *= d.size / d.distance;
      const textureAlpha = this.crosshair.textureAlpha || 0.5;
      let textureScale = this.crosshair.textureScale || 1;
      let textureSize = distance * textureScale;
      let xScale = 1;
      let yScale = 1;
      let xOffset = 0;
      let yOffset = 0;
      switch (this.document.t) {
        case "circle":
          xOffset = yOffset = textureSize;
          xScale = yScale = textureSize * 2 / this.texture.width;
          break;
        case "cone":
          textureSize /= 2;
          yOffset = -textureSize;
          xScale = yScale = textureSize * 2 / this.texture.width;
          break;
        case "rect":
          xScale = textureSize * Math.cos(Math.toRadians(direction)) / this.texture.width;
          yScale = textureSize * Math.sin(Math.toRadians(direction)) / this.texture.height;
          textureSize /= 2;
          direction = 0;
          break;
        case "ray":
          yOffset = this.document.width / d.distance * d.size / 2;
          xScale = textureSize / this.texture.width;
          yScale = textureSize / this.texture.height;
          yScale *= this.document.width / this.document.distance;
          break;
      }
      t.beginTextureFill({
        texture: this.texture,
        matrix: new PIXI.Matrix().scale(xScale, yScale).translate(xOffset, yOffset).rotate(Math.toRadians(direction)),
        alpha: textureAlpha
      });
    } else {
      t.beginFill(0, 0);
    }
    t.drawShape(this.shape);
    if (!this.crosshair.lockDrag) {
      t.lineStyle(this._borderThickness, 0).beginFill(0, 0.5).drawCircle(0, 0, 6).drawCircle(this.ray.dx, this.ray.dy, 6).endFill();
    }
  }
  #refreshRangeHighlight() {
    const loc = this.crosshair.location;
    if (!loc.displayRangePoly || !loc.obj || loc.limitMaxRange === null) return;
    if (this.#rangeHighlight) {
      this.#rangeHighlight.clear();
    } else {
      this.#rangeHighlight = new PIXI.Graphics();
      canvas.tokens.addChild(this.#rangeHighlight);
      this.#rangeHighlight.zIndex = -1;
      this.#rangeHighlight.interactive = false;
      canvas.tokens.sortChildren();
    }
    const object = loc.obj;
    const position = {
      x: object?.center?.x ?? object?.position?.x ?? object?.x,
      y: object?.center?.y ?? object?.position?.y ?? object?.y
    };
    const defaultSize = canvas.dimensions.distancePixels;
    const { w, h } = object;
    const distance = loc.limitMaxRange + Math.max(w ?? defaultSize, h ?? defaultSize) / 2 / canvas.dimensions.distancePixels;
    this.#rangeHighlight.beginFill(loc.rangePolyFillColor ?? 16777215, clamp(loc.rangePolyFillAlpha ?? 0.25, 0, 1)).lineStyle(2, loc.rangePolyLineColor ?? 16711680, clamp(loc.rangePolyLineAlpha ?? 0.5, 0, 1)).drawPolygon(canvas.grid.getCircle(position, distance));
  }
  async show() {
    await this.draw();
    this.layer.addChild(this);
    this.oldInteractiveChildren = this.layer.interactiveChildren;
    this.layer.interactiveChildren = false;
    this.#updateLocation();
    this.#refreshRangeHighlight();
    this.#runCallback(CONSTANTS.CALLBACKS.SHOW, this);
    return this.#activateShowListeners();
  }
  async #activateShowListeners() {
    return new Promise((resolve) => {
      this.#promise.resolve = resolve;
      this.#handlers.move = this.#onMove.bind(this);
      this.#handlers.mouseup = this.#onMouseUp.bind(this);
      this.#handlers.wheel = this.#onWheel.bind(this);
      canvas.stage.on("mousemove", this.#handlers.move);
      canvas.stage.on("pointerup", this.#handlers.mouseup);
      canvas.app.view.addEventListener("wheel", this.#handlers.wheel, { passive: true });
    });
  }
  #getSnappedPoint(point, mode = this.crosshair.snap.position, resolution = this.crosshair.snap.resolution) {
    resolution = !resolution || resolution <= 0 ? canvas.grid.size : resolution;
    return canvas.grid.getSnappedPoint(point, { mode, resolution });
  }
  #onMove(evt) {
    const now = Date.now();
    const leftDown = (evt.buttons & 1) > 0;
    const rightDown = (evt.buttons & 2) > 0;
    this.#isDrag = !!(leftDown && canvas.mouseInteractionManager.isDragging);
    this.#isPanning = this.#isPanning || !!(rightDown && canvas.mouseInteractionManager.isDragging);
    if (this.#isPanning) return;
    if (this.#isDrag) {
      canvas.mouseInteractionManager.cancel(evt);
    }
    evt.preventDefault();
    if (now - this.moveTime <= 20) return;
    const moved = this.#updateLocation();
    this.#runCallback(CONSTANTS.CALLBACKS.MOUSE_MOVE, this);
    if (moved) this.#runCallback(CONSTANTS.CALLBACKS.MOVE, this);
    this.#updateLineOfSight();
    this.refresh();
    this.moveTime = now;
  }
  #updateLineOfSight() {
    if (!this.crosshair.location?.obj || this.crosshair.location?.wallBehavior === CONSTANTS.PLACEMENT_RESTRICTIONS.ANYWHERE) return;
    const object = this.crosshair.location.obj;
    const objLocation = {
      x: object?.center?.x ?? object?.position?.x ?? object?.x,
      y: object?.center?.y ?? object?.position?.y ?? object?.y
    };
    const location2 = { x: this.document.x, y: this.document.y };
    const exitEarly = this.#lastPositions && (this.#lastPositions.obj.x === objLocation.x && this.#lastPositions.obj.y === objLocation.y && this.#lastPositions.tgt.x === location2.x && this.#lastPositions.tgt.y === location2.y);
    this.#lastPositions = {
      obj: objLocation,
      tgt: location2
    };
    if (exitEarly) return;
    const type = this.crosshair.location?.wallBehavior === CONSTANTS.PLACEMENT_RESTRICTIONS.LINE_OF_SIGHT ? "sight" : "move";
    const collisions = CONFIG.Canvas.polygonBackends.sight.testCollision(objLocation, location2, {
      type,
      useThreshold: true
    });
    const wasValid = this.isValid;
    this.isValid = !collisions.length;
    if (!this.isValid && wasValid) {
      this.#runCallback(CONSTANTS.CALLBACKS.COLLIDE, this, collisions);
    } else if (this.isValid && !wasValid) {
      this.#runCallback(CONSTANTS.CALLBACKS.STOP_COLLIDING, this);
    }
  }
  #handleLockedEdge(mouseLocation) {
    let snappedMouseLocation = this.#getSnappedPoint(mouseLocation, CONST.GRID_SNAPPING_MODES.CENTER);
    const { lockToEdgeDirection } = this.crosshair.location;
    const placeable = this.crosshair.location.obj;
    const shape = placeable.getShape();
    const { w, h } = this.crosshair.location.obj;
    const shapePoints = shape?.points ?? [0, 0, w, 0, w, h, 0, h];
    const points = shapePoints.map((point, i) => {
      const worldCoord = i % 2 === 0 ? placeable.document.x : placeable.document.y;
      return point + worldCoord;
    });
    const centerPoint = placeable.getCenterPoint();
    const ray = new foundry.canvas.geometry.Ray(centerPoint, mouseLocation);
    let intersection;
    for (let i = 0; i < points.length; i += 2) {
      intersection = ray.intersectSegment([points[i], points[i + 1], points[i + 2 >= points.length ? 0 : i + 2], points[i + 3 >= points.length ? 1 : i + 3]]);
      if (intersection) break;
    }
    if (!intersection) return {};
    let snappedIntersection = this.#getSnappedPoint(intersection, CONST.GRID_SNAPPING_MODES.EDGE_MIDPOINT);
    if (canvas.scene.grid.type === CONST.GRID_TYPES.SQUARE) {
      const size = this.document.parent.grid.size;
      const left = snappedMouseLocation.x < points[0];
      const above = snappedMouseLocation.y < points[1];
      const right = snappedMouseLocation.x > points[2];
      const below = snappedMouseLocation.y > points[5];
      if ((left || right) && (below || above)) {
        snappedIntersection.x = left ? points[0] - size : right ? points[2] + size : snappedIntersection.x;
        snappedIntersection.y = above ? points[1] - size : right ? points[5] + size : snappedIntersection.y;
        if (above && left) {
          snappedIntersection.x = points[0];
          snappedIntersection.y = points[1];
        } else if (above && right) {
          snappedIntersection.x = points[2];
          snappedIntersection.y = points[3];
        } else if (below && right) {
          snappedIntersection.x = points[4];
          snappedIntersection.y = points[5];
        } else if (below && left) {
          snappedIntersection.x = points[6];
          snappedIntersection.y = points[7];
        }
      }
    }
    if (lockToEdgeDirection) {
      const lockedRay = foundry.canvas.geometry.Ray.towardsPoint(snappedIntersection, snappedMouseLocation, 2);
      const snappedDirection = this.#getSnappedPoint(lockedRay.B, CONST.GRID_SNAPPING_MODES.CENTER);
      snappedMouseLocation.x = snappedDirection.x;
      snappedMouseLocation.y = snappedDirection.y;
    }
    const { direction, distance } = this.#getDraggedMatrix(snappedIntersection, snappedMouseLocation);
    return {
      ...snappedIntersection,
      direction,
      distance
    };
  }
  #handleLimit(mouseLocation, targetLocation) {
    const ray = new foundry.canvas.geometry.Ray(targetLocation, mouseLocation);
    const gridPath = canvas.grid.measurePath([targetLocation, mouseLocation]);
    const limitMinRange = is_real_number(this.crosshair.location.limitMinRange) ? this.crosshair.location.limitMinRange : 0;
    const limitMaxRange = is_real_number(this.crosshair.location.limitMaxRange) ? this.crosshair.location.limitMaxRange : Infinity;
    let finalLocation = mouseLocation;
    if (gridPath.cost < limitMinRange) {
      finalLocation = canvas.grid.getTranslatedPoint(targetLocation, Math.toDegrees(ray.angle), limitMinRange);
    } else if (gridPath.cost > limitMaxRange) {
      finalLocation = canvas.grid.getTranslatedPoint(targetLocation, Math.toDegrees(ray.angle), limitMaxRange);
    }
    const snappedPosition = this.#getSnappedPoint(finalLocation);
    const { direction, distance } = this.#getDraggedMatrix(targetLocation, snappedPosition);
    return {
      ...snappedPosition,
      direction,
      distance
    };
  }
  #updateLocation() {
    let mouseLocation = get_mouse_position();
    mouseLocation.x += this.crosshair.location.offset.x ?? 0;
    mouseLocation.y += this.crosshair.location.offset.y ?? 0;
    let update2;
    if (this.crosshair.location.obj && (this.crosshair.location.lockToEdge || this.crosshair.location.limitMinRange || this.crosshair.location.limitMaxRange)) {
      const location2 = this.crosshair.location.obj;
      const targetLocation = {
        x: location2?.center?.x ?? location2?.position?.x ?? location2?.x,
        y: location2?.center?.y ?? location2?.position?.y ?? location2?.y
      };
      if (this.crosshair.location.lockToEdge) {
        update2 = this.#handleLockedEdge(mouseLocation);
      } else if (this.crosshair.location.limitMinRange || this.crosshair.location.limitMaxRange) {
        update2 = this.#handleLimit(mouseLocation, targetLocation);
      }
    } else if (this.#isDrag) {
      const { direction, distance } = this.#getDraggedMatrix(this.document, mouseLocation);
      update2 = { distance, direction };
    } else {
      const snappedPosition = this.#getSnappedPoint(mouseLocation);
      update2 = {
        x: snappedPosition.x,
        y: snappedPosition.y
      };
    }
    const isChanged = update2?.x !== void 0 && this.document.x !== update2.x || update2?.y !== void 0 && this.document.y !== update2.y || update2?.direction !== void 0 && this.document.direction !== update2.direction || update2?.distance !== void 0 && this.document.distance !== update2.distance;
    this.document.updateSource(update2);
    return isChanged;
  }
  #getDistance(dragDistance) {
    if (this.crosshair.distanceMin === null && this.crosshair.distanceMax === null) {
      return this.document.distance;
    }
    const min = this.crosshair.distanceMin ?? this.document.originalConfig.distance;
    const max = this.crosshair.distanceMax ?? this.document.originalConfig.distance;
    return Math.min(Math.max(0.5, dragDistance, min), max);
  }
  #getDraggedMatrix(source2, target) {
    const dragAngle = new foundry.canvas.geometry.Ray(source2, target).angle;
    const dragDistance = canvas.grid.measurePath([source2, target]);
    const direction = this.crosshair.snap.direction ? Math.round(Math.toDegrees(dragAngle) / this.crosshair.snap.direction) * this.crosshair.snap.direction : Math.toDegrees(dragAngle);
    return {
      direction: direction || 0,
      distance: this.#getDistance(dragDistance.distance)
    };
  }
  #onMouseUp(evt) {
    const event2 = evt?.nativeEvent ?? evt;
    if (!(event2.which === 1 || event2.which === 3)) return;
    if (this.#isDrag) {
      this.#isDrag = false;
      return;
    }
    if (this.#isPanning) {
      this.#isPanning = false;
      return;
    }
    return event2.which === 1 ? this.#onConfirm() : this.#onCancel();
  }
  #onConfirm() {
    const position = this.document.getOrientation();
    if (!this.isValid) {
      this.#runCallback(CONSTANTS.CALLBACKS.INVALID_PLACEMENT, position);
      return;
    }
    const placedCallback = this.#runCallback(CONSTANTS.CALLBACKS.PLACED, position);
    if (placedCallback === false) {
      return;
    }
    this.cachedLocation = position;
    this.#promise.resolve(this.document);
    this.destroy();
  }
  #onCancel() {
    this.#runCallback(CONSTANTS.CALLBACKS.CANCEL);
    this.#promise.resolve(false);
    this.destroy();
  }
  #runCallback(name2, ...params) {
    if (!this.callbacks[name2]) return;
    return this.callbacks[name2](...params);
  }
  #clearHandlers() {
    this.layer.interactiveChildren = this.oldInteractiveChildren;
    canvas.stage.off("mousemove", this.#handlers.move);
    canvas.stage.off("pointerup", this.#handlers.mouseup);
    canvas.app.view.removeEventListener("wheel", this.#handlers.wheel);
  }
  #onWheel(evt) {
    if (!evt.altKey && !evt.ctrlKey && !evt.shiftKey) return;
    evt.stopPropagation();
    if (evt.shiftKey) this.#updateDirection(evt);
    if (evt.altKey) this.#updateDistance(evt);
    if (evt.ctrlKey) ;
    this.refresh();
  }
  #updateDistance(evt) {
    const step = this.document.parent.grid.distance / 2;
    const delta = step * Math.sign(-evt.deltaY);
    let distance = this.document.distance + delta;
    distance = Math.max(0.5, distance.toNearest(step));
    distance = this.#getDistance(distance);
    this.document.updateSource({ distance });
  }
  #updateDirection(evt) {
    if (this.crosshair.lockManualRotation) return;
    const scrollDelta = Math.sign(evt.deltaY);
    let delta = this.crosshair.snap.direction ? this.crosshair.snap.direction * scrollDelta : scrollDelta * 5;
    if (delta < 0) delta += 360;
    if (delta > 360) delta -= 360;
    const direction = Math.max(1, this.document.direction + delta);
    this.document.updateSource({ direction });
  }
  /** @override */
  _getGridHighlightPositions() {
    if (!this.crosshair.gridHighlight) return [];
    return super._getGridHighlightPositions();
  }
  /** @override */
  _destroy(options = {}) {
    super._destroy(options);
    if (this.#rangeHighlight) this.#rangeHighlight.destroy();
    this.#clearHandlers();
  }
  /** @override */
  async draw() {
    await super.draw();
    this.#refreshIcon();
    return this;
  }
  /**
   * The only difference between here and `super` is that this passes along an alpha
   * @override
   */
  highlightGrid() {
    canvas.interface.grid.clearHighlightLayer(this.highlightId);
    const border = this.document.borderColor;
    const color = this.document.fillColor;
    const alpha = this.crosshair.fillAlpha;
    if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
      const shape = this._getGridHighlightShape();
      canvas.interface.grid.highlightPosition(this.highlightId, { border, color, alpha, shape });
    } else {
      const positions = this._getGridHighlightPositions();
      for (const { x, y } of positions) {
        canvas.interface.grid.highlightPosition(this.highlightId, { x, y, border, color, alpha });
      }
    }
  }
}
class Plugin {
  constructor(name2) {
    this.name = name2;
  }
  /**
   * The effect's source location, eg the target of `.atLocation()` or `.attachTo()`
   *
   * @param effect
   * @param position
   * @returns {*}
   */
  sourcePosition({ effect: effect2, position } = {}) {
    return position;
  }
  /**
   * The effect's source location, eg the target of `.stretchTo()` or `.rotateTowards()`
   *
   * @param effect
   * @param position
   * @returns {*}
   */
  targetPosition({ effect: effect2, position } = {}) {
    return position;
  }
  /**
   * The effect's rotation calculation
   *
   * @param effect
   */
  rotation({ effect: effect2 } = {}) {
  }
  /**
   * The effect's elevation calculation
   *
   * @param effect
   * @param sort
   * @returns Number
   */
  elevation({ effect: effect2, sort } = {}) {
    return sort;
  }
  createSpriteContainers({ effect: effect2, container } = {}) {
    return container;
  }
  /**
   * makes changes to the effect upon creation
   *
   * @param effect
   */
  createSprite({ effect: effect2 } = {}) {
  }
  /**
   * Allows you to make changes to the effect's mask, if used
   *
   * @param effect
   * @param doc
   * @param obj
   * @param shape
   * @returns {*}
   */
  masking({ effect: effect2, doc, obj, shape } = {}) {
    return shape;
  }
  /**
   * Which hooks should refresh the masking of the effect
   *
   * @returns {*[]}
   */
  get maskingHooks() {
    return [];
  }
}
class GrapeJuiceIsometrics extends Plugin {
  isIsometricActive(effect2) {
    return !(effect2.data.screenSpace || effect2.data.screenSpaceAboveUI) && foundry.utils.getProperty(
      game.scenes.get(effect2.data.sceneId),
      `flags.${this.name}.is_isometric`
    );
  }
  sourcePosition({ effect: effect2, position, height } = {}) {
    if (this.isIsometricActive(effect2) && effect2.source instanceof PlaceableObject) {
      const elevation2 = (effect2.sourceDocument.elevation ?? 0) / canvas.scene.grid.distance * canvas.grid.size;
      position.x += elevation2;
      position.y -= elevation2;
      if (effect2.data.isometric?.overlay || effect2.target instanceof PlaceableObject) {
        position.x += height / 2;
        position.y -= height / 2;
      }
    }
    return position;
  }
  targetPosition({ effect: effect2, position, height } = {}) {
    if (this.isIsometricActive(effect2) && effect2.target instanceof PlaceableObject) {
      const targetHeight = height / 2;
      const elevation2 = (effect2.targetDocument.elevation ?? 0) / canvas.scene.grid.distance * canvas.grid.size;
      position.x += elevation2 + targetHeight;
      position.y -= elevation2 + targetHeight;
    }
    return position;
  }
  rotation({ effect: effect2 } = {}) {
    if (effect2.data.stretchTo) {
      let skew = Math.normalizeRadians(
        effect2.rotationContainer.rotation - Math.PI / 4
      );
      if (Math.abs(skew) >= Math.PI / 2 - 0.5 && Math.abs(skew) <= Math.PI / 2 + 0.5) {
        skew -= Math.PI / 2;
      }
      effect2.isometricContainer.skew.set(Math.normalizeRadians(skew), 0);
      effect2.isometricContainer.rotation = 0;
    } else if (effect2.data?.isometric?.overlay) {
      effect2.rotationContainer.rotation = 0;
      let skew = Math.PI / 4 + effect2.rotationContainer.rotation;
      effect2.isometricContainer.skew.set(
        Math.normalizeRadians(skew - Math.PI / 4),
        0
      );
      effect2.isometricContainer.scale.set(
        1,
        window.scale ?? Math.sqrt(3)
      );
    }
  }
  elevation({ effect: effect2, sort } = {}) {
    if (this.isIsometricActive(effect2)) {
      const sourceSort = effect2.source ? (effect2.sourceMesh?.sort ?? 0) + (effect2.data.isometric?.overlay ? 1 : -1) : 0;
      const targetSort = effect2.target ? (effect2.targetMesh?.sort ?? 0) + (effect2.data.isometric?.overlay ? 1 : -1) : 0;
      sort = Math.max(sourceSort, targetSort);
    }
    return sort;
  }
  createSpriteContainers({ effect: effect2, container } = {}) {
    effect2.isometricContainer = container.addChild(
      new PIXI.Container()
    );
    effect2.isometricContainer.id = effect2.id + "-isometricContainer";
    return effect2.isometricContainer;
  }
  createSprite({ effect: effect2 } = {}) {
    if (this.isIsometricActive(effect2)) {
      effect2.isometricContainer.rotation = Math.PI / 4;
    }
  }
}
class IsometricPerspective extends GrapeJuiceIsometrics {
  isIsometricActive(effect2) {
    return !(effect2.data.screenSpace || effect2.data.screenSpaceAboveUI) && foundry.utils.getProperty(
      game.scenes.get(effect2.data.sceneId),
      `flags.${this.name}.isometricEnabled`
    );
  }
}
class WalledTemplates extends Plugin {
  masking({ effect: effect2, doc, obj, shape } = {}) {
    if (obj.walledtemplates?.walledTemplate) {
      let wt = obj.walledtemplates.walledTemplate;
      wt.options.padding = 3 * canvas.dimensions.distancePixels;
      shape = wt.computeShape();
      wt.options.padding = 0;
    }
    return shape;
  }
  get maskingHooks() {
    return ["createWall", "updateWall", "deleteWall"];
  }
}
class PluginsManager {
  static modules = {
    "grape_juice-isometrics": GrapeJuiceIsometrics,
    "isometric-perspective": IsometricPerspective,
    "walledtemplates": WalledTemplates
  };
  static plugins = [];
  static initialize() {
    for (const [module, pluginClass] of Object.entries(this.modules)) {
      if (!game.modules.get(module)?.active) continue;
      this.plugins.push(new pluginClass(module));
    }
  }
  static sourcePosition({ effect: effect2, position, height } = {}) {
    for (const plugin of this.plugins) {
      position = plugin.sourcePosition({ effect: effect2, position, height });
    }
    return position;
  }
  static targetPosition({ effect: effect2, position, height } = {}) {
    for (const plugin of this.plugins) {
      position = plugin.targetPosition({ effect: effect2, position, height });
    }
    return position;
  }
  static rotation({ effect: effect2 } = {}) {
    for (const plugin of this.plugins) {
      plugin.rotation({ effect: effect2 });
    }
  }
  static elevation({ effect: effect2, sort } = {}) {
    for (const plugin of this.plugins) {
      sort = plugin.elevation({ effect: effect2, sort });
    }
    return sort;
  }
  static createSpriteContainers({ effect: effect2, container } = {}) {
    for (const plugin of this.plugins) {
      container = plugin.createSpriteContainers({ effect: effect2, container });
    }
    return container;
  }
  static createSprite({ effect: effect2 } = {}) {
    for (const plugin of this.plugins) {
      plugin.createSprite({ effect: effect2 });
    }
  }
  static masking({ effect: effect2, doc, obj, shape } = {}) {
    for (const plugin of this.plugins) {
      shape = plugin.masking({ effect: effect2, doc, obj, shape });
    }
    return shape;
  }
  static get maskingHooks() {
    let hooks = [];
    for (const plugin of this.plugins) {
      hooks = hooks.concat(plugin.maskingHooks);
    }
    return hooks;
  }
}
const hooksManager = {
  _byHook: /* @__PURE__ */ new Map(),
  _hooksRegistered: /* @__PURE__ */ new Set(),
  addHook(effectUuid, hookName, callable, { effect: effect2 = null, callNow = false } = {}) {
    if (!this._hooksRegistered.has(hookName)) {
      debug("registering hook for: " + hookName);
      this._hooksRegistered.add(hookName);
      Hooks.on(hookName, (...args) => {
        this._hookCalled(hookName, ...args);
      });
    }
    let perHook = this._byHook.get(hookName);
    if (!perHook) {
      perHook = /* @__PURE__ */ new Map();
      this._byHook.set(hookName, perHook);
    }
    let callbacks = perHook.get(effectUuid);
    if (!callbacks) {
      callbacks = [];
      perHook.set(effectUuid, callbacks);
    }
    callbacks.push(callable);
    if (callNow) {
      if (effect2?._setTimeout) effect2._setTimeout(callable, 20);
      else setTimeout(callable, 20);
    }
  },
  _hookCalled(hookName, ...args) {
    const perHook = this._byHook.get(hookName);
    if (!perHook) return;
    for (const callbacks of perHook.values()) {
      for (let i = 0; i < callbacks.length; i++) {
        try {
          callbacks[i](...args);
        } catch (err) {
          console.error(`Sequencer | hook handler for "${hookName}" threw:`, err);
        }
      }
    }
  },
  removeHooks(effectUuid) {
    for (const perHook of this._byHook.values()) {
      perHook.delete(effectUuid);
    }
  }
};
const SyncGroups = {
  times: /* @__PURE__ */ new Map(),
  effectIds: /* @__PURE__ */ new Map(),
  get(effect2) {
    const fullName = effect2.data.sceneId + "-" + effect2.data.syncGroup;
    const effectIds = new Set(this.effectIds.get(fullName));
    if (effectIds && !effectIds.has(effect2.id)) {
      effectIds.add(effect2.id);
      this.effectIds.set(fullName, Array.from(effectIds));
    }
    return this.times.get(fullName);
  },
  set(effect2) {
    const fullName = effect2.data.sceneId + "-" + effect2.data.syncGroup;
    this.times.set(fullName, effect2.data.creationTimestamp);
    this.effectIds.set(fullName, [effect2.id]);
  },
  remove(effect2) {
    const fullName = effect2.data.sceneId + "-" + effect2.data.syncGroup;
    const effectIds = new Set(this.effectIds.get(fullName));
    effectIds.delete(effect2.id);
    if (effectIds.size) {
      this.effectIds.set(fullName, Array.from(effectIds));
    } else {
      this.effectIds.delete(fullName);
      this.times.delete(fullName);
    }
  }
};
function isDocOnViewedLevel(doc, viewedLevel) {
  if (!doc || !viewedLevel) return false;
  if (doc.includedInLevel?.(viewedLevel.id) === true) return true;
  const cross = viewedLevel.visibility?.levels;
  if (!cross?.size) return false;
  if (doc.levels?.size) {
    for (const id of doc.levels) {
      if (cross.has(id)) return true;
    }
  } else if (typeof doc.level === "string") {
    if (cross.has(doc.level)) return true;
  }
  return false;
}
function isAttachedPlaceableVisible(placeable) {
  if (!placeable) return false;
  const Token = foundry.canvas.placeables.Token;
  if (!(placeable instanceof Token)) return true;
  return placeable.isVisible !== false;
}
function isPositionCulled(scene, viewedLevel, x, y, zMin, zMax) {
  const { bottom, top } = viewedLevel.elevation;
  let lo, hi;
  if (zMax < bottom) {
    lo = Math.nextUp(zMax);
    hi = bottom;
  } else if (zMin >= top) {
    lo = top;
    hi = zMin;
  } else {
    return false;
  }
  const probe = { x, y };
  for (const surface of scene.getSurfaces({ culling: true, level: viewedLevel })) {
    if (surface.elevation > hi) break;
    if (surface.elevation < lo) continue;
    if (surface.region.polygonTree.testPoint(probe)) return true;
  }
  return false;
}
function intervalsOverlap(a0, a1, b0, b1, aTopInclusive = false) {
  const aIsRange = a0 < a1;
  const bIsRange = b0 < b1;
  const lo = Math.max(a0, b0);
  const hi = Math.min(a1, b1);
  if (lo > hi) return false;
  if (lo < hi) return true;
  if (bIsRange && hi === b1) return false;
  if (aIsRange && hi === a1) return aTopInclusive;
  return true;
}
class CanvasEffect extends PIXI.Container {
  #elevation = 0;
  #sort = 0;
  #sortLayer = 800;
  constructor(inData) {
    super();
    this.sortableChildren = true;
    this.interactiveChildren = false;
    this.actualCreationTime = +/* @__PURE__ */ new Date();
    this.data = inData;
    this._resolve = null;
    this._durationResolve = null;
    this.ready = false;
    this._ended = false;
    this._isEnding = false;
    this._cachedSourceData = {};
    this._cachedTargetData = {};
    this.uuid = false;
  }
  static get protectedValues() {
    return [
      "_id",
      "sequenceId",
      "creationTimestamp",
      "creatorUserId",
      "moduleName",
      "index",
      "repetition",
      "moves",
      "fadeIn",
      "fadeOut",
      "scaleIn",
      "scaleOut",
      "rotateIn",
      "rotateOut",
      "fadeInAudio",
      "fadeOutAudio",
      "animations",
      "nameOffsetMap",
      "persist"
    ];
  }
  /** @type {number} */
  get elevation() {
    return this.#elevation;
  }
  set elevation(value) {
    this.#elevation = value;
  }
  /** @type {number} */
  get sort() {
    return this.#sort;
  }
  set sort(value) {
    this.#sort = value;
  }
  /** @type {number} */
  get sortLayer() {
    return this.#sortLayer;
  }
  set sortLayer(value) {
    this.#sortLayer = value;
  }
  get context() {
    return this.data.attachTo?.active && this.sourceDocument ? this.sourceDocument : game.scenes.get(this.data.sceneId);
  }
  get creationTimestamp() {
    if (this.data.syncGroup) {
      const time2 = SyncGroups.get(this);
      if (time2) return time2;
      SyncGroups.set(this);
    }
    return this.data.creationTimestamp;
  }
  /**
   * The ID of the effect
   *
   * @returns {string}
   */
  get id() {
    return this.data._id;
  }
  /**
   * Whether this effect is destroyed or is in the process of being destroyed
   */
  get isDestroyed() {
    return this.destroyed || this.source && this.isSourceDestroyed || this.target && this.isTargetDestroyed;
  }
  /**
   * Whether the source of this effect is temporary
   *
   * @returns {boolean}
   */
  get isSourceTemporary() {
    return this.data.attachTo?.active && this.sourceDocument && !is_UUID(this.sourceDocument?.uuid);
  }
  /**
   * Whether the source of this effect has been destroyed
   *
   * @returns {boolean}
   */
  get isSourceDestroyed() {
    return this.source && this.source?.destroyed && (!this.sourceDocument?.object || this.sourceDocument?.object?.destroyed || this.source.constructor.name === "Crosshairs");
  }
  /**
   * Whether the target of this effect is temporary
   *
   * @returns {boolean}
   */
  get isTargetTemporary() {
    return (this.data.stretchTo?.attachTo || this.data.rotateTowards?.attachTo) && this.targetDocument && !is_UUID(this.targetDocument.uuid);
  }
  /**
   * Whether the target of this effect has been destroyed
   *
   * @returns {boolean}
   */
  get isTargetDestroyed() {
    return this.target && this.target?.destroyed && (!this.targetDocument?.object || this.targetDocument?.object?.destroyed || this.target.constructor.name === "Crosshairs");
  }
  /**
   * The source object (or source location) of the effect
   *
   * @returns {boolean|object}
   */
  get source() {
    if (!this._source && this.data.source) {
      const getDifferentTarget = this.data.source === this.data.target;
      this._source = this._getObjectByID(this.data.source?.uuid ?? this.data.source, getDifferentTarget, true) ?? this.data.source;
      this._source = this._source?._object ?? this._source;
    }
    return this._source;
  }
  /**
   * Retrieves the source document
   *
   * @returns {Document|foundry.canvas.placeables.PlaceableObject}
   */
  get sourceDocument() {
    return this.source?.document ?? this.source;
  }
  /**
   * Retrieves the PIXI object for the source object
   *
   * @returns {*|PIXI.Sprite|TileHUD<Application.Options>}
   */
  get sourceMesh() {
    return this.source?.mesh ?? this.source?.template;
  }
  /**
   * The source position with the relevant offsets calculated
   *
   * @returns {{x: number, y: number}}
   */
  get sourcePosition() {
    return this._resolveSourcePosition(this.getSourceData());
  }
  _resolveSourcePosition(sourceData) {
    const position = sourceData.position;
    const offset2 = this._getOffset(this.data.source, true);
    if (this.data.attachTo?.active && this.data.attachTo?.align && this.data.attachTo?.align !== "center") {
      const additionalOffset = align({
        context: this.source,
        spriteWidth: this.sprite.width,
        spriteHeight: this.sprite.height,
        align: this.data.attachTo?.align,
        edge: this.data.attachTo?.edge
      });
      offset2.x += additionalOffset.x;
      offset2.y += additionalOffset.y;
    }
    return {
      x: position.x - offset2.x,
      y: position.y - offset2.y
    };
  }
  /**
   * The target object (or target location) of the effect
   *
   * @returns {boolean|object}
   */
  get target() {
    if (!this._target && this.data.target) {
      const getDifferentTarget = this.data.source === this.data.target;
      this._target = this._getObjectByID(this.data.target?.uuid ?? this.data.target, getDifferentTarget, false) ?? this.data.target;
      this._target = this._target?._object ?? this._target;
    }
    return this._target;
  }
  /**
   * Retrieves the document of the target
   *
   * @returns {Document|foundry.canvas.placeables.PlaceableObject}
   */
  get targetDocument() {
    return this.target?.document ?? this.target;
  }
  /**
   * Retrieves the PIXI object for the target object
   *
   * @returns {*|PIXI.Sprite|TileHUD<Application.Options>}
   */
  get targetMesh() {
    return this.target?.mesh ?? this.target?.template;
  }
  /**
   * The target position with the relevant offsets calculated
   *
   * @returns {{x: number, y: number}}
   */
  get targetPosition() {
    const position = this.getTargetData().position;
    const offset2 = this._getOffset(this.data.target);
    return {
      x: position.x - offset2.x,
      y: position.y - offset2.y
    };
  }
  /**
   * Returns this effect's world position
   *
   * @returns {{x: number, y: number}}
   */
  get worldPosition() {
    const t = canvas.stage.worldTransform;
    return {
      x: (this.sprite.worldTransform.tx - t.tx) / canvas.stage.scale.x,
      y: (this.sprite.worldTransform.ty - t.ty) / canvas.stage.scale.y
    };
  }
  /**
   * Whether the current user is the owner of this effect
   *
   * @returns {boolean}
   */
  get owner() {
    return this.data.creatorUserId === game.user.id;
  }
  get loopDelay() {
    return this.data.loopOptions?.loopDelay ?? 0;
  }
  get loops() {
    return this.data.loopOptions?.loops ?? 0;
  }
  /**
   * Whether the current user can update this effect
   *
   * @returns {boolean}
   */
  get userCanUpdate() {
    return game.user.isGM || this.owner || this.data.attachTo?.active && !!this.sourceDocument?.canUserModify?.(game.user, "update");
  }
  /**
   * Whether the current user can delete this effect
   *
   * @returns {boolean}
   */
  get userCanDelete() {
    return this.userCanUpdate || user_can_do("permissions-effect-delete");
  }
  /**
   * Whether this effect is on the current scene
   *
   * @returns {boolean}
   */
  get onCurrentScene() {
    return this.data.sceneId === game.user.viewedScene;
  }
  /**
   * Whether this effect should be shown as faded or not - effects created by users for other users should be shown
   * for all
   *
   * @returns {boolean}
   */
  get shouldShowFadedVersion() {
    return this.data.users && this.data.users.length && !(this.data.users.length === 1 && this.data.users.includes(this.data.creatorUserId)) && !this.data.users.includes(game.userId);
  }
  set effectAlpha(value) {
    if (this.sprite) {
      this.sprite.alpha = value;
    }
    if (this.shapes) {
      Object.values(this.shapes).forEach((shape) => {
        shape.alpha = value;
      });
    }
  }
  get effectAlpha() {
    return this.sprite?.alpha;
  }
  async playMedia() {
    if (this.destroyed || this._ended || this._isEnding || !this.sprite) {
      return;
    }
    await this.sprite.play();
    this._setupTimestampHook(this.mediaCurrentTime * 1e3);
  }
  updateTexture() {
    this.sprite?.updateVideoTextures();
  }
  async pauseMedia() {
    this.sprite?.stop();
  }
  get mediaLooping() {
    return !!this.sprite?.loop;
  }
  set mediaLooping(looping) {
    if (!this.sprite) return;
    this.sprite.loop = looping;
  }
  get mediaIsPlaying() {
    return !!this.sprite?.playing;
  }
  get mediaCurrentTime() {
    return this.sprite?.currentTime ?? 0;
  }
  get mediaPlaybackRate() {
    return this.sprite?.playbackRate ?? 1;
  }
  set mediaPlaybackRate(inPlaybackRate) {
    if (!this.sprite) return;
    this.sprite.playbackRate = inPlaybackRate;
  }
  set mediaCurrentTime(newTime) {
    if (!this.sprite) return;
    this.sprite.currentTime = newTime;
  }
  get mediaDuration() {
    return this.sprite?.duration ?? 0;
  }
  get mediaDurationMs() {
    return this.mediaDuration * 1e3;
  }
  get hasAnimatedMedia() {
    return !!this.sprite?.hasAnimatedMedia;
  }
  /**
   * The template of the effect, determining the effect's internal grid size, and start/end padding
   *
   * @returns {object}
   */
  get template() {
    return this._template;
  }
  /**
   * The grid size difference between the internal effect's grid vs the grid on the canvas. If the effect is in screen space, we ignore this.
   *
   * @returns {number}
   */
  get gridSizeDifference() {
    return canvas.grid.size / (this.template?.gridSize ?? this.defaultGridSize);
  }
  get defaultGridSize() {
    return 100;
  }
  /**
   * Whether the effect should be flipped on any given axis
   *
   * @returns {number}
   */
  get flipX() {
    const offsetMap = this._nameOffsetMap?.[this.data.source];
    let flip = this.data.flipX ? -1 : 1;
    if (offsetMap && offsetMap.mirrorX !== void 0) {
      flip *= offsetMap.mirrorX ? -1 : 1;
    }
    return flip;
  }
  get flipY() {
    const offsetMap = this._nameOffsetMap?.[this.data.source];
    let flip = this.data.flipY ? -1 : 1;
    if (offsetMap && offsetMap.mirrorY !== void 0) {
      flip *= offsetMap.mirrorY ? -1 : 1;
    }
    return flip;
  }
  /**
   * Whether this effect should play at all, depending on a multitude of factors
   *
   * @returns {boolean}
   */
  get shouldPlay() {
    return (game.user.viewedScene === this.data.sceneId || this.data.creatorUserId === game.userId) && (game.user.isGM || !this.data.users || this.data.users.length === 0 || this.data.users.includes(game.userId)) && (!this.data.local || this.data.creatorUserId === game.user.id);
  }
  get shouldPlayVisible() {
    let playVisible = this.shouldPlay && game.settings.get("sequencer", "effectsEnabled") && game.user.viewedScene === this.data.sceneId;
    if (game.settings.get("core", "photosensitiveMode")) {
      playVisible = false;
      throttled_custom_warning(
        this.data.moduleName,
        "Photosensitive Mode is turned on, so Sequencer's visual effects aren't being rendered"
      );
    }
    return playVisible;
  }
  static make(inData) {
    return !inData.persist ? new CanvasEffect(inData) : new PersistentCanvasEffect(inData);
  }
  static checkShouldPlay(effectData) {
    return !effectData.local || effectData.creatorUserId === game.user.id;
  }
  static checkValid(effectData) {
    if (effectData.delete) {
      return false;
    }
    let sourceExists = true;
    let targetExists = true;
    if (effectData.source && is_UUID(effectData.source)) {
      sourceExists = fromUuidSync(effectData.source);
    }
    if (effectData.target && is_UUID(effectData.target)) {
      targetExists = fromUuidSync(effectData.target);
    }
    for (let tiedDocumentUuid of effectData?.tiedDocuments ?? []) {
      if (tiedDocumentUuid && is_UUID(tiedDocumentUuid)) {
        let tiedDocumentExists = fromUuidSync(tiedDocumentUuid);
        if (!tiedDocumentExists) return false;
      }
    }
    if (effectData.source && is_UUID(effectData.source) && effectData.target && is_UUID(effectData.target)) {
      const sourceScene = effectData.source.split(".")[1];
      const targetScene = effectData.target.split(".")[1];
      if (sourceScene !== targetScene || sourceScene !== effectData.sceneId)
        return false;
    }
    return sourceExists && targetExists;
  }
  /**
   * Validates that the update contains the appropriate data
   *
   * @param inUpdates
   */
  static validateUpdate(inUpdates) {
    const updateKeys = Object.keys(inUpdates);
    const protectedValues = updateKeys.filter(
      (key) => CanvasEffect.protectedValues.includes(key)
    );
    if (protectedValues.length) {
      throw custom_error(
        "Sequencer",
        `CanvasEffect | update | You cannot update the following keys of an effect's data: ${protectedValues.join(
          "\n - "
        )}`
      );
    }
    if (updateKeys.includes("source")) {
      if (!(is_UUID(inUpdates.source) || is_object_canvas_data(inUpdates.source))) {
        throw custom_error(
          "Sequencer",
          `CanvasEffect | update | source must be of type document UUID or object with X and Y coordinates`
        );
      }
    }
    if (updateKeys.includes("target")) {
      if (!(is_UUID(inUpdates.target) || is_object_canvas_data(inUpdates.target))) {
        throw custom_error(
          "Sequencer",
          `CanvasEffect | update | target must be of type document UUID or object with X and Y coordinates`
        );
      }
    }
  }
  getHook(type, uuid) {
    if (!is_UUID(uuid)) return false;
    const parts = uuid.split(".");
    return type + parts[parts.length - 2];
  }
  /**
   * Gets the source hook name
   *
   * @param {string} type
   * @returns {string|boolean}
   */
  getSourceHook(type = "") {
    return this.getHook(type, this.data.source);
  }
  /**
   * The source object's current position, or its current position
   *
   * @returns {boolean|object}
   */
  getSourceData() {
    if (this.data.temporary && !this.owner) {
      return SequencerEffectManager.getPositionForUUID(this.data.source);
    }
    if (this.source instanceof foundry.canvas.placeables.PlaceableObject && this.isSourceDestroyed) {
      return {
        ...this._cachedSourceData
      };
    }
    let crosshairPos = this.source instanceof CrosshairsPlaceable ? this.sourceDocument.getOrientation() : false;
    crosshairPos = crosshairPos?.source;
    const positionSource = this.source instanceof foundry.canvas.placeables.PlaceableObject && !this.isSourceTemporary ? this.source : this.sourceDocument ?? this.source;
    let position = crosshairPos || (positionSource ? get_object_position(positionSource) : null) || this.source?.worldPosition || this.source?.center || this.source;
    const { width, height } = crosshairPos || get_object_dimensions(positionSource);
    position = PluginsManager.sourcePosition({ effect: this, position, height });
    if (position !== void 0) {
      this._cachedSourceData.position = position;
    }
    if (width !== void 0 && height !== void 0) {
      this._cachedSourceData.width = width;
      this._cachedSourceData.height = height;
    }
    let rotation2 = 0;
    if (this.source instanceof foundry.canvas.placeables.MeasuredTemplate && this.sourceDocument?.t !== "rect") {
      rotation2 = Math.normalizeRadians(
        Math.toRadians(this.sourceDocument?.direction)
      );
    } else if (!(this.source instanceof foundry.canvas.placeables.MeasuredTemplate)) {
      rotation2 = this.sourceDocument?.rotation ? Math.normalizeRadians(Math.toRadians(this.sourceDocument?.rotation)) : 0;
    }
    if (rotation2 !== void 0) {
      this._cachedSourceData.rotation = rotation2;
    }
    const alpha = this.sourceDocument instanceof TokenDocument || this.sourceDocument instanceof TileDocument ? this.sourceDocument?._source?.alpha ?? 1 : 1;
    if (alpha !== void 0) {
      this._cachedSourceData.alpha = alpha;
    }
    return {
      ...this._cachedSourceData
    };
  }
  /**
   * Gets the target hook name
   *
   * @param {string} type
   * @returns {string|boolean}
   */
  getTargetHook(type = "") {
    return this.getHook(type, this.data.target);
  }
  /**
   * The target object's current position, or its current position
   *
   * @returns {boolean|object}
   */
  getTargetData() {
    if (this.data.temporary && !this.owner) {
      return SequencerEffectManager.getPositionForUUID(this.data.target) ?? this.getSourceData();
    }
    if (this.target instanceof foundry.canvas.placeables.PlaceableObject && this.isTargetDestroyed) {
      return {
        ...this._cachedTargetData
      };
    }
    let crosshairPos = this.target instanceof CrosshairsPlaceable ? this.targetDocument.getOrientation() : false;
    crosshairPos = crosshairPos?.target ?? crosshairPos?.source;
    const positionTarget = this.target instanceof foundry.canvas.placeables.PlaceableObject && !this.isTargetTemporary && !this.isTargetDestroyed ? this.target : this.targetDocument ?? this.target;
    let position = crosshairPos || (positionTarget ? get_object_position(positionTarget, { measure: true }) : null) || this.target?.worldPosition || this.target?.center || this.target;
    const { width, height } = crosshairPos || get_object_dimensions(positionTarget);
    position = PluginsManager.targetPosition({ effect: this, position, height });
    if (width !== void 0 && height !== void 0) {
      this._cachedTargetData.width = width;
      this._cachedTargetData.height = height;
    }
    if (position !== void 0) {
      this._cachedTargetData.position = position;
    }
    let rotation2 = 0;
    if (this.target instanceof foundry.canvas.placeables.MeasuredTemplate && this.targetDocument?.t !== "rect") {
      rotation2 = Math.normalizeRadians(
        Math.toRadians(this.targetDocument?.direction)
      );
    } else if (!(this.target instanceof foundry.canvas.placeables.MeasuredTemplate)) {
      rotation2 = this.targetDocument?.rotation ? Math.normalizeRadians(Math.toRadians(this.targetDocument?.rotation)) : 0;
    }
    if (rotation2 !== void 0) {
      this._cachedTargetData.rotation = rotation2;
    }
    const alpha = this.targetDocument instanceof TokenDocument || this.targetDocument instanceof TileDocument ? this.targetDocument?.alpha ?? 1 : 1;
    if (alpha !== void 0) {
      this._cachedTargetData.alpha = alpha;
    }
    return {
      ...this._cachedTargetData
    };
  }
  /**
   * Calculates the offset for a given offset property and name mapping
   *
   * @param {string} offsetMapName
   * @param {boolean} source
   * @returns {{x: number, y: number}|*}
   * @private
   */
  _getOffset(offsetMapName, source2 = false) {
    const key = source2 ? "source" : "target";
    const offset2 = {
      x: 0,
      y: 0
    };
    let twister = this._twister;
    let nameOffsetMap = this._nameOffsetMap?.[this.data.name];
    if (nameOffsetMap) {
      twister = nameOffsetMap.twister;
    }
    if (this.data.missed && (!source2 || !this.data.target)) {
      let missedOffset = this._offsetCache[key]?.missedOffset || calculate_missed_position(this.source, this.target, twister);
      this._offsetCache[key].missedOffset = missedOffset;
      offset2.x -= missedOffset.x;
      offset2.y -= missedOffset.y;
    }
    const obj = source2 ? this.source : this.target;
    const multiplier = source2 ? this.data.randomOffset?.source : this.data.randomOffset?.target;
    if (obj && multiplier) {
      let randomOffset = this._offsetCache[key]?.randomOffset || get_random_offset(obj, multiplier, twister);
      this._offsetCache[key].randomOffset = randomOffset;
      offset2.x -= randomOffset.x;
      offset2.y -= randomOffset.y;
    }
    let extraOffset = this.data?.offset?.[key];
    if (extraOffset) {
      let newOffset = {
        x: extraOffset.x,
        y: extraOffset.y
      };
      if (extraOffset.gridUnits) {
        newOffset.x *= canvas.grid.size;
        newOffset.y *= canvas.grid.size;
      }
      if (extraOffset.local) {
        if (!this._cachedSourceData?.position || !this._cachedTargetData?.position) {
          this.getSourceData();
          this.getTargetData();
        }
        const startPos = this._cachedSourceData.position;
        const endPos = this._cachedTargetData.position;
        const angle = this.target ? new foundry.canvas.geometry.Ray(startPos, endPos).angle : foundry.canvas.geometry.Ray.fromAngle(
          startPos.x,
          startPos.y,
          this._cachedSourceData.rotation,
          1
        ).angle;
        newOffset = rotateAroundPoint(
          0,
          0,
          newOffset.x,
          newOffset.y,
          -angle
        );
      }
      offset2.x -= newOffset.x;
      offset2.y -= newOffset.y;
    }
    let offsetMap = this._nameOffsetMap?.[offsetMapName];
    if (!this._offsetCache[key]["nameCache"][offsetMapName]) {
      this._offsetCache[key]["nameCache"][offsetMapName] = {};
    }
    if (offsetMap) {
      if (offsetMap.missed) {
        const missedOffset = this._offsetCache[key]["nameCache"][offsetMapName]?.missedOffset || calculate_missed_position(
          offsetMap.sourceObj,
          offsetMap.targetObj,
          offsetMap.twister
        );
        this._offsetCache[key]["nameCache"][offsetMapName].missedOffset = missedOffset;
        offset2.x -= missedOffset.x;
        offset2.y -= missedOffset.y;
      }
      const obj2 = offsetMap.targetObj || offsetMap.sourceObj;
      const multiplier2 = offsetMap.randomOffset?.source || offsetMap.randomOffset?.target;
      if (obj2 && multiplier2) {
        let randomOffset = this._offsetCache[key]["nameCache"][offsetMapName]?.randomOffset || get_random_offset(obj2, multiplier2, offsetMap.twister);
        this._offsetCache[key]["nameCache"][offsetMapName].randomOffset = randomOffset;
        offset2.x -= randomOffset.x;
        offset2.y -= randomOffset.y;
      }
      if (offsetMap.offset) {
        offset2.x += offsetMap.offset.x;
        offset2.y += offsetMap.offset.y;
      }
    }
    return offset2;
  }
  /**
   * Initializes the name offset map by establishing targets
   *
   * @param inOffsetMap
   * @returns {{setup}|*}
   * @private
   */
  _setupOffsetMap(inOffsetMap) {
    if (!inOffsetMap.setup) {
      inOffsetMap.setup = true;
      inOffsetMap.sourceObj = inOffsetMap.source ? this._validateObject(inOffsetMap.source) : false;
      inOffsetMap.targetObj = inOffsetMap.target ? this._validateObject(inOffsetMap.target) : false;
      const repetition = this.data.repetition % inOffsetMap.repetitions;
      const seed = get_hash(`${inOffsetMap.seed}-${repetition}`);
      inOffsetMap.twister = createMersenneTwister(seed);
    }
    return inOffsetMap;
  }
  /**
   * Plays the effect, returning two promises; one that resolves once the duration has been established, and another
   * when the effect has finished playing
   *
   * @returns {Object}
   */
  play() {
    const durationPromise = new Promise((resolve, reject) => {
      this._durationResolve = resolve;
    });
    const finishPromise = new Promise(async (resolve, reject) => {
      this._resolve = resolve;
      Hooks.callAll("createSequencerEffect", this);
      debug(`Playing effect:`, this.data);
      this._initialize();
    });
    return {
      duration: durationPromise,
      promise: finishPromise
    };
  }
  /**
   *  Ends the effect
   */
  endEffect() {
    if (this._ended) return;
    this._durationResolve?.(0);
    this._durationResolve = null;
    this._resolve?.(this.data);
    Hooks.callAll("endedSequencerEffect", this);
    this.destroy();
  }
  destroy(...args) {
    this._destroyDependencies();
    return super.destroy(...args);
  }
  /**
   * Updates this effect with the given parameters
   * @param inUpdates
   * @returns {Promise}
   */
  async update(inUpdates) {
    if (!this.userCanUpdate)
      throw custom_error(
        "Sequencer",
        "CanvasEffect | Update | You do not have permission to update this effect"
      );
    CanvasEffect.validateUpdate(inUpdates);
    const newData = foundry.utils.deepClone(this.data);
    const updateKeys = Object.keys(inUpdates);
    updateKeys.forEach((key) => {
      foundry.utils.setProperty(newData, key, inUpdates[key]);
    });
    if (Object.keys(foundry.utils.diffObject(newData, this.data)).length === 0) {
      debug(
        `Skipped updating effect with ID ${this.id} - no changes needed`
      );
      return;
    }
    if (this.data.persist) {
      const originalSourceUUID = is_UUID(this.data.source) && this.data.attachTo ? this.data.source : "Scene." + this.data.sceneId;
      const newSourceUUID = is_UUID(newData.source) && newData.attachTo ? newData.source : "Scene." + newData.sceneId;
      if (originalSourceUUID !== newSourceUUID) {
        flagManager.removeFlags(originalSourceUUID, { effects: newData });
      }
      flagManager.addFlags(newSourceUUID, { effects: newData });
    }
    debug(`Updated effect with ID ${this.id}`);
    return sequencerSocket.executeForEveryone(
      SOCKET_HANDLERS.UPDATE_EFFECT,
      this.id,
      newData
    );
  }
  async addAnimatedProperties({ animations = [], loopingAnimation = [] } = {}) {
    const animationsToAdd = [];
    if (!Array.isArray(animations)) {
      throw custom_error(
        this.data.moduleName,
        `animations must be an array of arrays`
      );
    }
    for (const animationData of animations) {
      if (!Array.isArray(animationData)) {
        throw custom_error(
          this.data.moduleName,
          `each entry in animations must be an array, each with target, property name, and animation options`
        );
      }
      const result = validateAnimation(...animationData);
      if (typeof result === "string") {
        throw custom_error(this.data.moduleName, result);
      }
      result.creationTimestamp = +/* @__PURE__ */ new Date();
      animationsToAdd.push(result);
    }
    if (!Array.isArray(loopingAnimation)) {
      throw custom_error(
        this.data.moduleName,
        `loopingAnimation must be an array of arrays`
      );
    }
    for (const animationData of loopingAnimation) {
      if (!Array.isArray(animationData)) {
        throw custom_error(
          this.data.moduleName,
          `each entry in loopingAnimation must be an array, each with target, property name, and animation options`
        );
      }
      const result = validateLoopingAnimation(...animationData);
      if (typeof result === "string") {
        throw custom_error(this.data.moduleName, result);
      }
      result.creationTimestamp = +/* @__PURE__ */ new Date();
      animationsToAdd.push(result);
    }
    if (this.data.persist) {
      const originalSourceUUID = is_UUID(this.data.source) && this.data.attachTo ? this.data.source : "Scene." + this.data.sceneId;
      const newData = foundry.utils.deepClone(this.data);
      newData.animations = (newData.animations ?? []).concat(
        foundry.utils.deepClone(animationsToAdd)
      );
      flagManager.addFlags(originalSourceUUID, { effects: newData });
    }
    return sequencerSocket.executeForEveryone(
      SOCKET_HANDLERS.ADD_EFFECT_ANIMATIONS,
      this.id,
      animationsToAdd
    );
  }
  async _addAnimations(inAnimations) {
    this._playAnimations(foundry.utils.deepClone(inAnimations));
    this.data.animations = (this.data.animations ?? []).concat(inAnimations);
  }
  /**
   * Updates the effect
   *
   * @param inUpdates
   * @returns {Promise}
   * @private
   */
  _update(inUpdates) {
    this.data = inUpdates;
    Hooks.callAll("updateSequencerEffect", this);
    this._destroyDependencies();
    return this._reinitialize();
  }
  /**
   * Determines whether a position is within the bounds of this effect
   *
   * @param inPosition
   * @returns {boolean}
   */
  isPositionWithinBounds(inPosition) {
    if (!this.spriteContainer) return false;
    return is_position_within_bounds(
      inPosition,
      this.spriteContainer,
      this.parent
    );
  }
  /**
   * Initializes the effect and places it on the canvas
   *
   * @returns {Promise}
   * @private
   */
  async _initialize() {
    try {
      this.ready = false;
      this._initializeVariables();
      this._addToContainer();
      await this._createFile();
      this._updateCurrentFilePath(false, true);
      await this._createSprite();
      this._calculateDuration();
      this._createShapes();
      await this._setupMasks();
      await this._transformSprite();
      this._playPresetAnimations();
      this._playCustomAnimations();
      this._setEndTimeout();
      this._registerTickers();
      this._timeoutVisibility();
      await this._startEffect();
      this.ready = true;
    } catch (err) {
      this._durationResolve?.(0);
      this._durationResolve = null;
      this._resolve?.(this.data);
      this._resolve = null;
      throw err;
    }
  }
  /**
   * Reinitializes the effect after it has been updated
   *
   * @param play
   * @returns {Promise}
   * @private
   */
  async _reinitialize() {
    this._initializing = true;
    this._recomputeRenderable();
    if (!this.shouldPlay) {
      return Sequencer.EffectManager._removeEffect(this);
    }
    this.actualCreationTime = +/* @__PURE__ */ new Date();
    return this._initialize();
  }
  /**
   * Initializes variables core to the function of the effect
   * This is run as a part of the construction of the effect
   *
   * @private
   */
  _initializeVariables() {
    this.rotationContainer = this.addChild(new PIXI.Container());
    this.rotationContainer.id = this.id + "-rotationContainer";
    this.pluginContainer = PluginsManager.createSpriteContainers({ effect: this, container: this.rotationContainer });
    this.spriteContainer = this.pluginContainer.addChild(
      new PIXI.Container()
    );
    this.spriteContainer.id = this.id + "-spriteContainer";
    this._template = this.data.template;
    this._ended = null;
    this._maskContainer = null;
    this._maskSprite = null;
    this._stageMasks = [];
    this._wallMaskGraphics = null;
    this._wallMaskDirty = false;
    this._verticalExtent = [0, 0, false];
    this._timeouts = /* @__PURE__ */ new Set();
    this._file = null;
    this._loopOffset = 0;
    this.effectFilters = {};
    this._animationDuration = 0;
    this._animationTimes = {};
    this._twister = createMersenneTwister(this.creationTimestamp);
    this._distanceCache = null;
    this._isRangeFind = false;
    this._customAngle = 0;
    this._currentFilePath = this.data.file;
    this._hooks = [];
    this._lastDimensions = {};
    this._lastScreenDimensions = {};
    this._renderTexture = null;
    if (this._resetTimeout) {
      clearTimeout(this._resetTimeout);
    }
    this._resetTimeout = null;
    this._source = false;
    this._target = false;
    this._offsetCache = {
      source: { nameCache: {} },
      target: { nameCache: {} }
    };
    this._nameOffsetMap = Object.fromEntries(
      Object.entries(
        foundry.utils.deepClone(this.data.nameOffsetMap ?? {})
      ).map((entry) => {
        return [entry[0], this._setupOffsetMap(entry[1])];
      })
    );
    this.uuid = !is_UUID(this.context.uuid) ? this.id : this.context.uuid + ".data.flags.sequencer.effects." + this.id;
    this._ticker = foundry.canvas.animation.CanvasAnimation.ticker;
    this._tickerMethods = [];
  }
  _addToTicker(func) {
    this._tickerMethods.push(func);
    this._ticker.add(func, this);
  }
  _setTimeout(fn, ms) {
    const id = setTimeout(() => {
      this._timeouts?.delete(id);
      fn();
    }, ms);
    this._timeouts.add(id);
    return id;
  }
  /**
   * Destroys all dependencies to this element, such as tickers, animations, textures, and child elements
   *
   * @private
   */
  _destroyDependencies() {
    if (this._ended) return;
    this._ended = true;
    this._durationResolve?.(0);
    this._durationResolve = null;
    this.mask = null;
    this._teardownVoidProxy();
    if (this._timeouts?.size) {
      for (const id of this._timeouts) clearTimeout(id);
      this._timeouts.clear();
    }
    this.restartLoopHandler = null;
    hooksManager.removeHooks(this.uuid);
    this._tickerMethods.forEach((func) => this._ticker.remove(func, this));
    this._ticker = null;
    this._perEffectTickHandlers = null;
    SequencerAnimationEngine.endAnimations(this.id);
    if (this._maskContainer) this._maskContainer.destroy({ children: true });
    this._maskContainer = null;
    if (this._maskSprite) {
      try {
        this._maskSprite.texture.destroy(true);
        this._maskSprite.destroy();
      } catch (err) {
      }
      this._maskSprite = null;
    }
    if (this._stageMasks?.length) {
      for (const shape of this._stageMasks) {
        try {
          if (shape?.parent) shape.parent.removeChild(shape);
          shape?.destroy?.({ children: true });
        } catch (err) {
        }
      }
      this._stageMasks.length = 0;
    }
    this._wallMaskGraphics = null;
    this.sprite?.destroy();
    this.sprite = null;
    if (this.data.screenSpaceAboveUI) {
      SequencerAboveUILayer.removeContainerByEffect(this);
    }
    if (this.data.syncGroup) {
      SyncGroups.remove(this);
    }
    this.removeChildren().forEach((child2) => child2.destroy({ children: true }));
    this.rotationContainer = null;
    this.pluginContainer = null;
    this.spriteContainer = null;
  }
  /**
   * Plays preset animations
   *
   * @private
   */
  _playPresetAnimations() {
    this._moveTowards();
    this._fadeOut();
    this._fadeIn();
    this._rotateOut();
    this._rotateIn();
    this._scaleOut();
    this._scaleIn();
    this._fadeOutAudio();
    this._fadeInAudio();
  }
  /**
   * Gets an object based on an identifier, checking if it exists within the named offset map, whether it's a
   * coordinate object, or if it's an UUID that needs to be fetched from the scene
   *
   * @param inIdentifier
   * @param specific
   * @param returnSource
   * @returns {*}
   * @private
   */
  _getObjectByID(inIdentifier, specific = false, returnSource = false) {
    let source2 = inIdentifier;
    let offsetMap = this._nameOffsetMap?.[inIdentifier];
    if (offsetMap) {
      if (specific) {
        source2 = (returnSource ? offsetMap?.sourceObj || offsetMap?.targetObj : offsetMap?.targetObj || offsetMap?.sourceObj) || source2;
      } else {
        source2 = offsetMap?.targetObj || offsetMap?.sourceObj || source2;
      }
    } else {
      source2 = this._validateObject(source2);
    }
    return source2;
  }
  /**
   * Validates the given parameter, whether it's a UUID or a coordinate object, and returns the proper one
   *
   * @param inObject
   * @returns {*}
   * @private
   */
  _validateObject(inObject) {
    if (is_UUID(inObject) || !is_object_canvas_data(inObject)) {
      inObject = get_object_from_scene(inObject, this.data.sceneId);
      inObject = inObject?._object ?? inObject;
    }
    return inObject;
  }
  /**
   * Adds this effect to the appropriate container on the right layer
   *
   * @private
   */
  _addToContainer() {
    let layer;
    let registerVoidProxy = false;
    if (this.data.screenSpaceAboveUI) {
      layer = SequencerAboveUILayer;
    } else if (this.data.screenSpace) {
      layer = canvas.sequencerEffectsUILayer;
    } else if (this.data.aboveInterface) {
      layer = canvas.controls;
    } else if (this.data.aboveLighting) {
      layer = canvas.interface;
    } else {
      layer = canvas.primary;
      registerVoidProxy = true;
    }
    if (!layer || typeof layer.addChild !== "function") return;
    layer.addChild(this);
    layer.sortChildren?.();
    if (registerVoidProxy) this._registerVoidProxy();
  }
  /* -------------------------------------------------------------- */
  /*  Region "punch-through" (voidMesh proxy)                       */
  /* -------------------------------------------------------------- */
  /**
   * Register an ERASE-blend proxy container in the interface group so
   * this effect visually punches through region highlights that draw
   * above the primary canvas group.
   *
   * The proxy itself has no geometry. Its `render` callback reaches back
   * into this effect's sub-tree and re-renders every visible mesh/graphic/
   * text element with BLEND_MODES.ERASE. InterfaceCanvasGroup has a
   * VoidFilter that isolates its framebuffer, so the erase is constrained
   * to interface-group pixels only.
   *
   * @private
   */
  _registerVoidProxy() {
    if (this._voidProxy || !canvas?.interface) return;
    const proxy2 = new PIXI.Container();
    proxy2.updateTransform = () => {
    };
    proxy2.zIndex = 1e3;
    proxy2.__sequencerOwner = this;
    proxy2.render = (renderer) => this._renderAsVoid(renderer);
    canvas.interface.addChild(proxy2);
    this._voidProxy = proxy2;
  }
  /**
   * Root render hook for the voidMesh proxy. Guards and dispatches
   * to the subtree-walking renderer. Silent failure on error (the
   * effect still renders in its primary layer).
   *
   * @param {PIXI.Renderer} renderer
   * @private
   */
  _renderAsVoid(renderer) {
    if (this._destroyed || this._isEnding) return;
    if (!this.renderable || !this.visible || this.worldAlpha <= 0) return;
    if (!CanvasEffect._sceneHasActiveRegionHighlights()) return;
    try {
      this._renderSubtreeAsVoid(renderer, this, null);
    } catch (err) {
      if (CONFIG.debug?.sequencer) {
        console.warn("Sequencer | voidProxy render error", err);
      }
    }
  }
  static _sceneHasActiveRegionHighlights() {
    const layer = canvas?.regions;
    if (!layer) return false;
    const placeables = layer.placeables;
    if (!placeables?.length) return false;
    for (let i = 0; i < placeables.length; i++) {
      const region = placeables[i];
      if (region?.visible && region?.renderable) return true;
    }
    return false;
  }
  _renderSubtreeAsVoid(renderer, displayObject, outerFilter) {
    if (!displayObject) return;
    if (!displayObject.renderable || !displayObject.visible) return;
    if ((displayObject.worldAlpha ?? 1) <= 0) return;
    const filters2 = displayObject.filters;
    const mask = displayObject._mask;
    let enabledFilters = null;
    if (filters2?.length) {
      let anyDisabled = false;
      for (let i = 0; i < filters2.length; i++) {
        if (!filters2[i].enabled) {
          anyDisabled = true;
          break;
        }
      }
      if (anyDisabled) {
        const scratch = this._voidEnabledFilters ??= [];
        scratch.length = 0;
        for (let i = 0; i < filters2.length; i++) {
          if (filters2[i].enabled) scratch.push(filters2[i]);
        }
        if (scratch.length) enabledFilters = scratch.slice();
      } else {
        enabledFilters = filters2;
      }
    }
    const hasFilter = !!enabledFilters && enabledFilters.length > 0;
    const hasMask = !!mask && (!mask.isMaskData || mask.enabled && (mask.autoDetect || mask.type !== PIXI.MASK_TYPES.NONE));
    const flush = hasFilter || hasMask;
    let pushedFilter = null;
    let priorFilterBlend;
    let promoted = false;
    let nextOuterFilter = outerFilter;
    if (flush) renderer.batch.flush();
    if (hasFilter) {
      renderer.filter.push(displayObject, enabledFilters);
      const installed = renderer.filter.defaultFilterStack?.at?.(-1);
      const installedFilters = installed?.filters ?? enabledFilters;
      pushedFilter = installedFilters[installedFilters.length - 1];
      if (!outerFilter && pushedFilter) {
        priorFilterBlend = pushedFilter.blendMode;
        pushedFilter.blendMode = PIXI.BLEND_MODES.ERASE;
        promoted = true;
        nextOuterFilter = pushedFilter;
      }
    }
    if (hasMask) renderer.mask.push(displayObject, mask);
    const isMeshLike = typeof displayObject._render === "function" && (displayObject.isSprite === true || displayObject instanceof PIXI.Mesh || displayObject instanceof PIXI.Graphics || displayObject instanceof PIXI.Text);
    const shouldBlendLeaf = isMeshLike && !nextOuterFilter;
    let originalLeafBlend;
    if (shouldBlendLeaf) {
      originalLeafBlend = displayObject.blendMode;
      displayObject.blendMode = PIXI.BLEND_MODES.ERASE;
    }
    try {
      if (isMeshLike) {
        if (displayObject.cullable && typeof displayObject._renderWithCulling === "function") {
          displayObject._renderWithCulling(renderer);
        } else {
          displayObject._render(renderer);
        }
      }
      const children = displayObject.children;
      if (children?.length) {
        for (let i = 0, n = children.length; i < n; i++) {
          this._renderSubtreeAsVoid(renderer, children[i], nextOuterFilter);
        }
      }
    } finally {
      if (shouldBlendLeaf) displayObject.blendMode = originalLeafBlend;
      if (promoted && pushedFilter) pushedFilter.blendMode = priorFilterBlend;
      if (flush) {
        try {
          renderer.batch.flush();
        } catch (_) {
        }
      }
      if (hasMask) {
        try {
          renderer.mask.pop(displayObject);
        } catch (_) {
        }
      }
      if (hasFilter) {
        try {
          renderer.filter.pop();
        } catch (_) {
        }
      }
    }
  }
  /**
   * Remove and destroy the voidMesh proxy if one was registered.
   *
   * @private
   */
  _teardownVoidProxy() {
    const proxy2 = this._voidProxy;
    if (!proxy2) return;
    this._voidProxy = null;
    try {
      if (proxy2.parent) proxy2.parent.removeChild(proxy2);
      proxy2.destroy({ children: false });
    } catch (err) {
      if (CONFIG.debug?.sequencer) {
        console.warn("Sequencer | voidProxy teardown error", err);
      }
    }
  }
  get startTimeMs() {
    return this._startTime * 1e3;
  }
  get endTimeMs() {
    return this._endTime * 1e3;
  }
  /**
   * Calculates the duration of this effect, based on animation durations, the video source duration, end/start times, etc
   *
   * @private
   */
  _calculateDuration() {
    let playbackRate = this.data.playbackRate || 1;
    this.mediaPlaybackRate = playbackRate;
    this._animationDuration = this.data.duration || this.mediaDurationMs;
    if (this.data.moveSpeed && this.data.moves) {
      let distance = distance_between(
        this.sourcePosition,
        this.targetPosition
      );
      let durationFromSpeed = distance / this.data.moveSpeed * 1e3;
      this._animationDuration = Math.max(durationFromSpeed, this.data.duration);
    } else if (!this.data.duration && !this.hasAnimatedMedia) {
      let fadeDuration = (this.data.fadeIn?.duration ?? 0) + (this.data.fadeOut?.duration ?? 0);
      let scaleDuration = (this.data.scaleIn?.duration ?? 0) + (this.data.scaleOut?.duration ?? 0);
      let rotateDuration = (this.data.rotateIn?.duration ?? 0) + (this.data.rotateOut?.duration ?? 0);
      let moveDuration = 0;
      if (this.data.moves) {
        let distance = distance_between(
          this.sourcePosition,
          this.targetPosition
        );
        moveDuration = (this.data.moveSpeed ? distance / this.data.moveSpeed * 1e3 : 1e3) + this.data.moves.delay;
      }
      let animationDurations = this.data.animations?.length ? Math.max(
        ...this.data.animations.map((animation2) => {
          if (animation2.looping) {
            if (animation2.loops === 0) return 0;
            return (animation2?.duration ?? 0) * (animation2?.loops ?? 0) + (animation2?.delay ?? 0);
          } else {
            return (animation2?.duration ?? 0) + (animation2?.delay ?? 0);
          }
        })
      ) : 0;
      this._animationDuration = Math.max(
        fadeDuration,
        scaleDuration,
        rotateDuration,
        moveDuration,
        animationDurations
      );
      this._animationDuration = this._animationDuration || 1e3;
    }
    this._startTime = 0;
    if (this.data.time?.start && this.mediaCurrentTime !== null) {
      let currentTime = !this.data.time.start.isPerc ? this.data.time.start.value ?? 0 : this._animationDuration * this.data.time.start.value;
      this.mediaCurrentTime = currentTime / 1e3;
      this._startTime = currentTime / 1e3;
    }
    this._endTime = this._animationDuration;
    if (this.data.time?.end) {
      if (this.data.time.end.isPerc) {
        this._endTime = this._animationDuration - this._animationDuration * this.data.time.end.value;
      } else {
        this._endTime = this.data.time.isRange ? this.data.time.end.value : this._animationDuration - this.data.time.end.value;
      }
    }
    this._endTime /= 1e3;
    this._animationDuration = clamp(this.endTimeMs - this.startTimeMs, 0, this._animationDuration);
    if (this._file?.markers && this._startTime === 0 && this._endTime === this.mediaDuration) {
      this._animationTimes.loopStart = this._file.markers.loop.start / playbackRate / 1e3;
      this._animationTimes.loopEnd = this._file.markers.loop.end / playbackRate / 1e3;
      this._animationTimes.forcedEnd = this._file.markers.forcedEnd / playbackRate / 1e3;
    }
    this._totalDuration = this.loops ? this._animationDuration * this.loops + this.loopDelay * (this.loops - 1) : this._animationDuration;
    this._totalDuration /= playbackRate;
    if (this.data.persist) {
      this.mediaLooping = (!this.data.time || this._startTime === 0 && this._endTime === this.mediaDuration) && this._animationTimes.loopStart === void 0 && this._animationTimes.loopEnd === void 0 && !this.loops && !this.loopDelay;
    } else {
      this.mediaLooping = this._startTime === 0 && this._endTime > this.mediaDuration && !this.loops;
    }
    this._durationResolve?.(this._totalDuration);
    this._durationResolve = null;
  }
  /**
   * If this effect is animatable, hold off on rendering it for a bit so that the animations have time to initialize to
   * prevent it from spawning and then jumping to the right place
   *
   * @private
   */
  _timeoutVisibility() {
    if (!this.data.animations) {
      return this._setupHooks();
    }
    this._setTimeout(() => {
      this._setupHooks();
    }, 50);
  }
  /**
   * Add Ticker handler to check for updates to attached objects
   *
   * @private
   */
  _registerTickers() {
    const handlers = [];
    if (this.data.stretchTo && this.data.stretchTo?.attachTo) {
      handlers.push(this._transformStretchToAttachedSprite);
    }
    if (this.data.attachTo?.active && !this.data.stretchTo?.attachTo) {
      handlers.push(this._transformAttachedNoStretchSprite);
    }
    if (this.data.rotateTowards && this.data.rotateTowards?.attachTo) {
      handlers.push(this._transformRotateTowardsAttachedSprite);
    }
    if (this.data.scaleToObject && this.data?.attachTo?.active && this.data?.attachTo?.bindScale) {
      const { heightWidthRatio, widthHeightRatio, baseScaleX, baseScaleY } = this._getBaseScale();
      handlers.push(function scaleTick() {
        this._applyScaleToObject(heightWidthRatio, widthHeightRatio, baseScaleX, baseScaleY);
        this._setAnchors();
      });
    }
    if (this.isSourceTemporary) handlers.push(this._checkSourceDestroyed);
    if (this.isTargetTemporary) handlers.push(this._checkTargetDestroyed);
    if (!handlers.length) return;
    this._perEffectTickHandlers = handlers;
    this._addToTicker(this._perEffectTick);
  }
  _perEffectTick() {
    const handlers = this._perEffectTickHandlers;
    if (!handlers) return;
    for (let i = 0; i < handlers.length; i++) {
      try {
        handlers[i].call(this);
      } catch (err) {
        if (CONFIG.debug?.sequencer) {
          console.warn("Sequencer | per-effect ticker handler threw", err);
        }
      }
    }
  }
  _checkSourceDestroyed() {
    if (this.isSourceDestroyed) {
      this._source = this.sourcePosition;
      this._source.uuid = this.uuid;
      SequencerEffectManager.endEffects({ effects: this });
    }
  }
  _checkTargetDestroyed() {
    if (this.isTargetDestroyed) {
      this._target = this.targetPosition;
      SequencerEffectManager.endEffects({ effects: this });
    }
  }
  async _createFile() {
    if (this.data.copySprite) {
      let targetDocument = fromUuidSync(this.data.copySprite.uuid);
      if (targetDocument?.ring?.enabled && targetDocument.object) {
        let clonedObject = targetDocument.object.clone();
        await clonedObject.draw();
        let clonedMesh = clonedObject.mesh;
        clonedMesh.position.set(0, 0);
        clonedMesh.setShaderClass(CONFIG.Token.ring.shaderClass);
        this._renderTexture = canvas.app.renderer.generateTexture(clonedMesh, {
          resolution: clonedMesh.texture.resolution
        });
        clonedObject.destroy();
        return;
      }
    }
    if (this.data.file === "") {
      return;
    }
    let file;
    if (this.data.customRange) {
      const template = this.template ? [this.template.gridSize, this.template.startPoint, this.template.endPoint] : [100, 0, 0];
      file = SequencerFileBase.make(
        this.data.file,
        "temporary.range.file",
        { template }
      );
    } else if (Sequencer.Database.entryExists(this.data.file)) {
      file = Sequencer.Database.getEntry(this.data.file).clone();
    } else {
      file = SequencerFileBase.make(this.data.file);
      this._currentFilePath = this.data.file;
    }
    if (file.template) {
      this._template = foundry.utils.mergeObject(
        { gridSize: file.template[0], startPoint: file.template[1], endPoint: file.template[2] },
        this.data.template ?? {}
      );
    }
    file.fileIndex = this.data.forcedIndex;
    file.twister = this._twister;
    this._file = file;
    this._isRangeFind = file?.rangeFind;
  }
  _updateCurrentFilePath(distance, showDistanceWarning = false) {
    if (this.data.copySprite && this._renderTexture) {
      this._currentFilePath = this.data.copySprite.uuid;
      return;
    }
    if (!this._file) {
      return;
    }
    if (!this.data.stretchTo) {
      this._currentFilePath = this._file.getFile();
      return;
    }
    distance = distance || new foundry.canvas.geometry.Ray(this.sourcePosition, this.targetPosition).distance;
    if (distance === 0 && showDistanceWarning) {
      custom_error(
        "effect",
        `stretchTo - You are stretching over a distance of "0", you may be attempting to stretch between two of the same coordinates!`
      );
    }
    this._currentFilePath = this._file.getFileForDistance(distance);
  }
  /**
   * Creates the sprite, and the relevant containers that manage the position and offsets of the overall visual look of the sprite
   *
   * @private
   */
  async _createSprite() {
    if (this.data.copySprite?.offsetX !== void 0 && this.data.copySprite?.offsetY !== void 0) {
      this.rotationContainer.position.set(this.data.copySprite.offsetX, this.data.copySprite.offsetY);
    }
    this._initializing = true;
    this._recomputeRenderable();
    const spriteData = {
      texture: this._renderTexture,
      antialiasing: this.data?.fileOptions?.antialiasing,
      tiling: this.data.tilingTexture,
      xray: this.data.xray || this.data.screenSpace || this.data.screenSpaceAboveUI,
      isPersisted: this.data.persist && !this.data.loopOptions?.endOnLastLoop
    };
    this.sprite = new SequencerSpriteManager(this._file, spriteData);
    this.spriteContainer.addChild(this.sprite);
    this.sprite.id = this.id + "-sprite";
    this.sprite.loopDelay = this.loopDelay;
    this.sprite.currentTime = this._startTime;
    this.sprite.loop = this.loops;
    await this.sprite.activate(this._currentFilePath);
    if (this.data.copySprite && !this._renderTexture && !this.data.time?.start) {
      const sourcePlaceable = fromUuidSync(this.data.copySprite.uuid)?.object ?? null;
      const sourceTime = sourcePlaceable?.sourceElement?.currentTime;
      if (Number.isFinite(sourceTime) && sourceTime > 0) {
        this.sprite.currentTime = sourceTime;
      }
    }
    this.sprite.volume = (this.data.volume ?? 0) * game.settings.get("core", "globalInterfaceVolume");
    if (this._isRangeFind && this.data.stretchTo && (this.data.attachTo?.active || this.data.stretchTo?.attachTo?.active)) {
      this.sprite.preloadVariants();
    }
    if (this.data.text) {
      const text2 = this.data.text.text;
      const fontSettings = foundry.utils.deepClone(this.data.text);
      fontSettings.fontSize = (fontSettings?.fontSize ?? 26) * (150 / canvas.grid.size);
      const textSprite = this.sprite.addText({ text: text2, textStyle: fontSettings });
      textSprite.zIndex = 1;
      const textAnchor = this.data.text.anchor;
      textSprite.anchor.set(textAnchor?.x ?? 0.5, textAnchor?.y ?? 0.5);
    }
    this.sprite.filters = [];
    if (this.data.filters) {
      for (let index2 = 0; index2 < this.data.filters.length; index2++) {
        const filterData = this.data.filters[index2];
        const filter2 = new filters[filterData.className](filterData.data);
        filter2.id = this.id + "-" + filterData.className + "-" + index2.toString();
        if (filter2 instanceof PIXI.ColorMatrixFilter) {
          this.sprite.colorMatrixFilter = filter2;
        } else {
          this.sprite.filters.push(filter2);
        }
        const filterKeyName = filterData.name || filterData.className;
        this.effectFilters[filterKeyName] = filter2;
      }
    }
    this.effectAlpha = this.data.opacity;
    let spriteOffsetX = this.data.spriteOffset?.x ?? 0;
    let spriteOffsetY = this.data.spriteOffset?.y ?? 0;
    if (this.data.spriteOffset?.gridUnits) {
      spriteOffsetX *= canvas.grid.size;
      spriteOffsetY *= canvas.grid.size;
    }
    this.sprite.position.set(spriteOffsetX, spriteOffsetY);
    this.sprite.anchor?.set(
      this.data.spriteAnchor?.x ?? 0.5,
      this.data.spriteAnchor?.y ?? 0.5
    );
    let spriteRotation = this.data.spriteRotation ?? 0;
    if (this.data.randomSpriteRotation) {
      spriteRotation += random_float_between(-360, 360, this._twister);
    }
    this.sprite.rotation = Math.normalizeRadians(
      Math.toRadians(spriteRotation)
    );
    this._customAngle = this.data.angle ?? 0;
    if (this.data.randomRotation) {
      this._customAngle += random_float_between(-360, 360, this._twister);
    }
    const offsetMap = this._nameOffsetMap?.[this.data.source];
    if (offsetMap?.angle !== void 0) {
      this._customAngle += offsetMap?.angle;
    }
    if (offsetMap?.randomRotation) {
      this._customAngle += random_float_between(-360, 360, offsetMap.twister);
    }
    this.spriteContainer.rotation = -Math.normalizeRadians(
      Math.toRadians(this._customAngle)
    );
    PluginsManager.createSprite({ effect: this });
    if (this.data.tint) {
      this.sprite.tint = this.data.tint;
    }
    if (this.data.blendMode != null) {
      this.sprite.blendMode = this.data.blendMode;
    }
    if (this.shouldShowFadedVersion) {
      this.alpha = game.settings.get(CONSTANTS.MODULE_NAME, "user-effect-opacity") / 100;
      this.filters = [
        new PIXI.ColorMatrixFilter({
          saturation: -1
        })
      ];
    }
    this.updateElevation();
  }
  _createShapes() {
    const nonMaskShapes = (this.data?.shapes ?? []).filter(
      (shape) => !shape.isMask
    );
    this.shapes = {};
    for (const shape of nonMaskShapes) {
      const graphic = createShape(shape);
      graphic.filters = this.sprite.filters;
      this.spriteContainer.addChild(graphic);
      this.shapes[shape?.name ?? "shape-" + foundry.utils.randomID()] = graphic;
    }
  }
  /**
   * Single place to write to this.renderable. Other code paths set their
   * gate slot (e.g. _baseRenderable) and call this; don't bypass.
   *
   * @private
   */
  _recomputeRenderable() {
    if (this._initializing) {
      this.renderable = false;
      return;
    }
    const base = this._baseRenderable ?? this.shouldPlayVisible;
    const paused = this._pauseRenderable !== false;
    this.renderable = base && paused && this._isOnViewedLevel();
  }
  /**
   * Whether this effect is visible on the currently viewed scene level.
   * Always true on Foundry v13, and on `.screenSpace()`,
   * `.screenSpaceAboveUI()`, and `.aboveInterface()` effects.
   *
   * @returns {boolean}
   * @private
   */
  _isOnViewedLevel() {
    if (!CONSTANTS.IS_V14) return true;
    const currentLevel = canvas?.level;
    if (!currentLevel) return true;
    if (this.data.screenSpace || this.data.screenSpaceAboveUI || this.data.aboveInterface) {
      return true;
    }
    const crossLevels = currentLevel.visibility?.levels;
    const sceneLevels = canvas.scene?.levels;
    if (this.data.levels?.length) {
      for (const entry of this.data.levels) {
        let id = entry;
        if (sceneLevels && !sceneLevels.get(id)) {
          id = sceneLevels.getName?.(entry)?.id ?? entry;
        }
        if (id === currentLevel.id) return true;
        if (crossLevels?.has(id)) return true;
      }
      return false;
    }
    const hasExplicitSpatial = !!this.data.elevation && (typeof this.data.elevation.top === "number" || this.data.elevation.absolute === true);
    const sourceAttached = !hasExplicitSpatial && this.data.attachTo?.active && typeof this.sourceDocument?.viewed === "boolean";
    const targetAttached = !hasExplicitSpatial && (this.data.stretchTo?.attachTo || this.data.rotateTowards?.attachTo) && typeof this.targetDocument?.viewed === "boolean";
    if (sourceAttached || targetAttached) {
      if (sourceAttached && isDocOnViewedLevel(this.sourceDocument, currentLevel)) return true;
      if (targetAttached && isDocOnViewedLevel(this.targetDocument, currentLevel)) return true;
      return false;
    }
    if (sceneLevels?.size === 1 && sceneLevels.has(foundry.documents.BaseScene.metadata.defaultLevelId)) {
      return true;
    }
    const [zMin, zMax, topInclusive] = this._getEffectiveVerticalExtent();
    const viewed = currentLevel.elevation;
    let levelMatch = intervalsOverlap(zMin, zMax, viewed.bottom, viewed.top, topInclusive);
    if (!levelMatch && crossLevels?.size) {
      const allLevels = canvas.scene?.levels;
      for (const otherId of crossLevels) {
        const other = allLevels?.get(otherId)?.elevation;
        if (!other) continue;
        if (intervalsOverlap(zMin, zMax, other.bottom, other.top, topInclusive)) {
          levelMatch = true;
          break;
        }
      }
    }
    if (!levelMatch) return false;
    const pos = this.sourcePosition;
    if (!pos || !canvas.scene) return true;
    return !isPositionCulled(canvas.scene, currentLevel, pos.x, pos.y, zMin, zMax);
  }
  /**
   * The effect's vertical `[zMin, zMax, topInclusive]` in scene elevation
   * units. Pinned by an explicit `.elevation()`; otherwise inherits the
   * source/target's extent (Token depth, Region elevation range and its
   * `topInclusive` flag).
   *
   * @returns {[number, number, boolean]}
   * @private
   */
  _getEffectiveVerticalExtent() {
    const out = this._verticalExtent;
    if (this.data.elevation) {
      out[0] = typeof this.elevationBottom === "number" ? this.elevationBottom : this.elevation;
      out[1] = typeof this.elevationTop === "number" ? this.elevationTop : out[0];
      out[2] = !!this.data.elevation.topInclusive;
      return out;
    }
    const sourceExtent = get_object_vertical_extent(this.sourceDocument);
    const targetExtent = get_object_vertical_extent(this.targetDocument);
    if (sourceExtent && targetExtent) {
      out[0] = Math.min(sourceExtent[0], targetExtent[0]);
      out[1] = Math.max(sourceExtent[1], targetExtent[1]);
      out[2] = !!(sourceExtent[1] >= targetExtent[1] ? sourceExtent[2] : false) || !!(targetExtent[1] >= sourceExtent[1] ? targetExtent[2] : false);
      return out;
    }
    if (sourceExtent) {
      out[0] = sourceExtent[0];
      out[1] = sourceExtent[1];
      out[2] = !!sourceExtent[2];
      return out;
    }
    if (targetExtent) {
      out[0] = targetExtent[0];
      out[1] = targetExtent[1];
      out[2] = !!targetExtent[2];
      return out;
    }
    out[0] = this.elevation;
    out[1] = this.elevation;
    out[2] = false;
    return out;
  }
  updateElevation() {
    let targetElevation = Math.max(
      get_object_elevation(this.source ?? {}),
      get_object_elevation(this.target ?? {})
    );
    const offset2 = this.data.elevation?.absolute ? 0 : targetElevation;
    const bottom = (this.data.elevation?.elevation ?? 0) + offset2;
    const top = typeof this.data.elevation?.top === "number" ? this.data.elevation.top + offset2 : null;
    this.elevationBottom = bottom;
    this.elevationTop = top;
    let renderElevation = bottom;
    const onDefaultRoute = !this.data.aboveLighting && !this.data.aboveInterface && !this.data.screenSpace && !this.data.screenSpaceAboveUI;
    if (onDefaultRoute) {
      const [, extentTop, extentTopInclusive] = this._getEffectiveVerticalExtent();
      const effectiveTop = typeof extentTop === "number" ? extentTop : bottom;
      const sceneLevels = canvas?.scene?.levels;
      if (sceneLevels) {
        for (const level of sceneLevels) {
          const lvlBottom = level.elevation?.bottom;
          if (!Number.isFinite(lvlBottom)) continue;
          const crosses = extentTopInclusive ? effectiveTop >= lvlBottom : effectiveTop > lvlBottom;
          if (crosses && lvlBottom > renderElevation) renderElevation = lvlBottom;
        }
      }
    }
    this.elevation = renderElevation;
    let sort = !is_real_number(this.data.zIndex) ? this?.parent?.children?.length ?? 0 : 1e5;
    sort = PluginsManager.elevation({ effect: this, sort });
    sort += 100;
    this.sort = sort;
    this.sortLayer = this.data.sortLayer ?? 800;
    if (this.data.aboveLighting) {
      this.zIndex = 750 + (is_real_number(this.data.zIndex) ? this.data.zIndex : 0);
    } else {
      this.zIndex = sort + (is_real_number(this.data.zIndex) ? this.data.zIndex : 0);
    }
    if (this.parent) {
      this.parent.sortDirty = true;
    }
    this._recomputeRenderable();
  }
  updateTransform() {
    super.updateTransform();
    if (this.data.screenSpace || this.data.screenSpaceAboveUI) {
      if (!canvas?.screenDimensions) return;
      const [screenWidth, screenHeight] = canvas.screenDimensions;
      if (this._lastScreenDimensions?.screenWidth !== screenWidth || this._lastScreenDimensions?.screenHeight !== screenHeight) {
        this._lastScreenDimensions.screenWidth = screenWidth;
        this._lastScreenDimensions.screenHeight = screenHeight;
      }
      this.position.set(
        (this.data.screenSpacePosition?.x ?? 0) + screenWidth * (this.data.screenSpaceAnchor?.x ?? this.data.anchor?.x ?? 0.5),
        (this.data.screenSpacePosition?.y ?? 0) + screenHeight * (this.data.screenSpaceAnchor?.y ?? this.data.anchor?.y ?? 0.5)
      );
      if (this.data.screenSpaceScale) {
        const scaleData = this.data.screenSpaceScale ?? { x: 1, y: 1 };
        let scaleX = scaleData.x;
        let scaleY = scaleData.y;
        this._lastScreenDimensions.width = this.sprite.texture?.width || this._lastScreenDimensions.width || this.sprite.width || this.spriteContainer.children[this.spriteContainer.children.length - 1].width;
        this._lastScreenDimensions.height = this.sprite.texture?.height || this._lastScreenDimensions.height || this.sprite.height || this.spriteContainer.children[this.spriteContainer.children.length - 1].height;
        if (scaleData.fitX) {
          scaleX = scaleX * (screenWidth / this._lastScreenDimensions.width);
        }
        if (scaleData.fitY) {
          scaleY = scaleY * (screenHeight / this._lastScreenDimensions.height);
        }
        scaleX = scaleData.ratioX ? scaleY : scaleX;
        scaleY = scaleData.ratioY ? scaleX : scaleY;
        this.scale.set(scaleX, scaleY);
      }
    }
  }
  async _setupMasks() {
    const maskShapes = this.data.shapes.filter((shape) => shape.isMask);
    if (!this.data?.masks?.length && !maskShapes.length && !this.data?.constrainedByWalls) return;
    const maskFilter = MaskFilter.create();
    if (this.data.constrainedByWalls) {
      this._setupWallMask(maskFilter);
    }
    for (const entry of this.data.masks) {
      if (entry && typeof entry === "object" && entry.__shape) {
        const rawShape = deserializeShape(entry.__shape);
        if (!rawShape) continue;
        const graphics = new PIXI.LegacyGraphics().beginFill().drawShape(rawShape).endFill();
        graphics.cullable = true;
        graphics.custom = true;
        graphics.renderable = false;
        graphics.uuid = "__rawShape-" + foundry.utils.randomID();
        canvas.stage.addChild(graphics);
        this._stageMasks.push(graphics);
        maskFilter.masks.push(graphics);
        continue;
      }
      const uuid = entry;
      const documentObj = fromUuidSync(uuid);
      if (!documentObj || documentObj.parent.id !== this.data.sceneId) continue;
      const obj = documentObj.object;
      const docName = documentObj.documentName;
      let shape = obj?.mesh;
      let shapeToAdd = shape;
      if (docName === "Region") {
        shape = new PIXI.LegacyGraphics();
        for (let polygon of documentObj.polygons) {
          if (polygon.isPositive) {
            shape.beginFill();
          } else {
            shape.beginHole();
          }
          shape.drawShape(polygon);
          if (polygon.isPositive) {
            shape.endFill();
          } else {
            shape.endHole();
          }
        }
        shapeToAdd = shape;
        shapeToAdd.cullable = true;
        shapeToAdd.custom = true;
        shapeToAdd.renderable = false;
        shapeToAdd.uuid = uuid;
        canvas.stage.addChild(shapeToAdd);
        this._stageMasks.push(shapeToAdd);
      } else if (docName === "Token" || docName === "Tile") {
        if (!obj?.mesh) continue;
        maskFilter.masks.push(obj.mesh);
        continue;
      } else if (docName === "MeasuredTemplate" || docName === "Drawing") {
        if (obj) {
          shape = obj?.shape?.geometry?.graphicsData?.[0]?.shape ?? obj?.shape;
        } else if (docName === "Drawing") {
          shape = create_drawing_mask_shape(documentObj);
        } else {
          continue;
        }
        if (!shape) continue;
        shape = PluginsManager.masking({
          effect: this,
          doc: documentObj,
          obj,
          shape
        });
        shapeToAdd = new PIXI.LegacyGraphics().beginFill().drawShape(shape).endFill();
        if (docName === "MeasuredTemplate") {
          shapeToAdd.position.set(documentObj.x, documentObj.y);
        } else {
          const {
            x,
            y,
            shape: { width, height },
            rotation: rotation2
          } = documentObj;
          shapeToAdd.pivot.set(width / 2, height / 2);
          shapeToAdd.position.set(x + width / 2, y + height / 2);
          shapeToAdd.angle = rotation2;
        }
        shapeToAdd.cullable = true;
        shapeToAdd.custom = true;
        shapeToAdd.renderable = false;
        shapeToAdd.uuid = uuid;
        canvas.stage.addChild(shapeToAdd);
        this._stageMasks.push(shapeToAdd);
      } else {
        continue;
      }
      shapeToAdd.obj = obj;
      const updateMethod = (doc) => {
        if (doc !== documentObj) return;
        const mask = maskFilter.masks.find((shape2) => shape2.uuid === uuid);
        if (!mask) return;
        if (!mask.custom) return;
        mask.clear();
        if (docName === "Region") {
          for (let polygon of documentObj.polygons) {
            if (polygon.isPositive) {
              mask.beginFill();
            } else {
              mask.beginHole();
            }
            mask.drawShape(polygon);
            if (polygon.isPositive) {
              mask.endFill();
            } else {
              mask.endHole();
            }
          }
        } else if (docName === "MeasuredTemplate") {
          mask.position.set(documentObj.x, documentObj.y);
          let maskObj = documentObj.object;
          shape = maskObj?.shape?.geometry?.graphicsData?.[0]?.shape ?? maskObj?.shape;
          shape = PluginsManager.masking({
            effect: this,
            doc: documentObj,
            obj: maskObj,
            shape
          });
          mask.beginFill().drawShape(shape).endFill();
        } else if (docName === "Drawing") {
          const {
            x,
            y,
            shape: { width, height },
            rotation: rotation2
          } = documentObj;
          mask.pivot.set(width / 2, height / 2);
          mask.position.set(x + width / 2, y + height / 2);
          mask.angle = rotation2;
          let updatedShape;
          if (documentObj.object) {
            updatedShape = documentObj.object?.shape?.geometry?.graphicsData?.[0]?.shape ?? documentObj.object?.shape;
          } else {
            updatedShape = create_drawing_mask_shape(documentObj);
          }
          if (updatedShape) mask.beginFill().drawShape(updatedShape).endFill();
        }
      };
      PluginsManager.maskingHooks.forEach((hook) => {
        hooksManager.addHook(this.uuid, hook, (doc) => {
          setTimeout(() => {
            updateMethod(doc);
          }, 100);
        });
      });
      hooksManager.addHook(this.uuid, this.getHook("update", uuid), (doc) => {
        setTimeout(() => {
          updateMethod(doc);
        }, 100);
      });
      maskFilter.masks.push(shapeToAdd);
    }
    for (const shapeData of maskShapes) {
      const shape = createShape(shapeData);
      shape.cullable = true;
      shape.custom = true;
      shape.renderable = false;
      this.spriteContainer.addChild(shape);
      this.shapes[shapeData?.name ?? "shape-" + foundry.utils.randomID()] = shape;
      maskFilter.masks.push(shape);
    }
    this.sprite.filters.push(maskFilter);
  }
  /**
   * Builds the initial wall-constrained mask graphic and wires hooks that recompute
   * the sweep when walls change or when the attached source moves.
   *
   * @param {MaskFilter} maskFilter
   * @private
   */
  _setupWallMask(maskFilter) {
    const shape = this._computeWallPolygon();
    if (!shape) return;
    const graphics = new PIXI.LegacyGraphics().beginFill().drawShape(shape).endFill();
    graphics.cullable = true;
    graphics.custom = true;
    graphics.renderable = false;
    graphics.uuid = "__constrainedByWalls";
    canvas.stage.addChild(graphics);
    this._stageMasks.push(graphics);
    maskFilter.masks.push(graphics);
    this._wallMaskGraphics = graphics;
    const recompute = () => {
      if (!this._wallMaskGraphics || this._ended) return;
      const next = this._computeWallPolygon();
      if (!next) return;
      this._wallMaskGraphics.clear().beginFill().drawShape(next).endFill();
    };
    const debouncedRecompute = () => {
      if (this._wallMaskDirty) return;
      this._wallMaskDirty = true;
      this._setTimeout(() => {
        this._wallMaskDirty = false;
        recompute();
      }, 100);
    };
    for (const hook of ["createWall", "updateWall", "deleteWall"]) {
      hooksManager.addHook(this.uuid, hook, debouncedRecompute);
    }
    if (CONSTANTS.IS_V14) {
      for (const hook of ["createLevel", "updateLevel", "deleteLevel"]) {
        hooksManager.addHook(this.uuid, hook, (level) => {
          if (level?.parent?.id !== this.data.sceneId) return;
          debouncedRecompute();
        });
      }
    }
    const attachedToSource = this.data.attachTo?.active && is_UUID(this.data.source);
    const sourceRefreshHook = attachedToSource && this.getSourceHook("refresh");
    if (sourceRefreshHook) {
      hooksManager.addHook(this.uuid, sourceRefreshHook, (placeable) => {
        if (placeable?.document?.uuid !== this.data.source) return;
        recompute();
      });
    }
  }
  /**
   * Computes the wall-constrained polygon for this effect from the source position.
   *
   * @returns {PIXI.Polygon|null}
   * @private
   */
  _computeWallPolygon() {
    const cfg = this.data.constrainedByWalls;
    if (!cfg) return null;
    const origin = cfg.origin ?? this.sourcePosition;
    if (!origin || !is_real_number(origin.x) || !is_real_number(origin.y)) return null;
    const elevation2 = is_real_number(origin.elevation) ? origin.elevation : get_object_elevation(this.source ?? this.sourceDocument ?? {});
    return computeWallPolygon({ ...origin, elevation: elevation2 }, {
      type: cfg.type,
      radius: cfg.radius,
      level: cfg.level ?? this._resolveWallSweepLevel()
    });
  }
  /**
   * Resolves which scene Level the wall sweep should consult on Foundry v14.
   * Returns null on v13 (where Levels do not exist) and when no clear
   * affinity is available, in which case the polygon backend falls back to
   * `canvas.level` (the currently viewed level).
   *
   * Priority: an attached source's `document.level`, then a single-entry
   * `.onLevels()` selection, then the lowest-elevation Level whose vertical
   * range overlaps the effect.
   *
   * @returns {foundry.documents.Level|null}
   * @private
   */
  _resolveWallSweepLevel() {
    if (!CONSTANTS.IS_V14) return null;
    const sceneLevels = canvas.scene?.levels;
    if (!sceneLevels?.size) return null;
    const sourceDoc = this.sourceDocument;
    const attachedLevelId = this.data.attachTo?.active ? sourceDoc?.level : null;
    if (attachedLevelId) {
      const fromSource = sceneLevels.get(attachedLevelId);
      if (fromSource) return fromSource;
    }
    if (this.data.levels?.length === 1) {
      const entry = this.data.levels[0];
      const fromSection = sceneLevels.get(entry) ?? sceneLevels.getName?.(entry);
      if (fromSection) return fromSection;
    }
    const [zMin, zMax, topInclusive] = this._getEffectiveVerticalExtent();
    let best = null;
    for (const level of sceneLevels) {
      const ext = level.elevation;
      if (!ext) continue;
      if (intervalsOverlap(zMin, zMax, ext.bottom, ext.top, topInclusive)) {
        if (!best || (ext.bottom ?? -Infinity) < (best.elevation.bottom ?? -Infinity)) {
          best = level;
        }
      }
    }
    return best;
  }
  /**
   * Sets up the hooks relating to this effect's source and target
   *
   * @private
   */
  _setupHooks() {
    const attachedToSource = this.data.attachTo?.active && is_UUID(this.data.source);
    const attachedToTarget = (this.data.stretchTo?.attachTo || this.data.rotateTowards?.attachTo) && is_UUID(this.data.target);
    const baseRenderable = this.shouldPlayVisible;
    let alpha = null;
    if (attachedToSource) {
      hooksManager.addHook(this.uuid, this.getSourceHook("delete"), (doc) => {
        const uuid = doc.uuid;
        if (doc !== this.sourceDocument) return;
        this._source = this._cachedSourceData.position;
        this._source.uuid = uuid;
        SequencerEffectManager.objectDeleted(uuid);
      });
      if (this.isSourceDestroyed) {
        SequencerEffectManager.objectDeleted(this.sourceDocument.uuid);
      }
      if (this.data.attachTo?.bindVisibility) {
        hooksManager.addHook(
          this.uuid,
          "sightRefresh",
          () => {
            if (this._ended) return;
            const sourceVisible = this.source && isAttachedPlaceableVisible(this.source) && !this.sourceMesh?.occluded;
            const sourceHidden = this.sourceDocument && (this.sourceDocument?.hidden ?? false);
            const targetVisible = this.target && isAttachedPlaceableVisible(this.target) && (!attachedToTarget || (this.targetMesh?.occluded ?? true));
            this._baseRenderable = baseRenderable && (!sourceHidden || game.user.isGM) && (sourceVisible || targetVisible) && this._checkWallCollisions();
            this.alpha = sourceVisible && sourceHidden ? 0.5 : 1;
            this._recomputeRenderable();
          },
          { effect: this, callNow: true }
        );
      }
      if (this.data.attachTo?.bindAlpha || this.data.attachTo?.bindElevation) {
        hooksManager.addHook(this.uuid, this.getSourceHook("update"), (doc) => {
          if (doc !== this.sourceDocument) return;
          if (this.data.attachTo?.bindAlpha) {
            this.spriteContainer.alpha = this.getSourceData().alpha;
          }
          if (this.data.attachTo?.bindElevation) {
            this.updateElevation();
          }
        });
      }
      if (CONSTANTS.IS_V14) {
        hooksManager.addHook(this.uuid, this.getSourceHook("update"), (doc, changed) => {
          if (doc !== this.sourceDocument) return;
          if (changed?.level === void 0) return;
          this._recomputeRenderable();
        });
      }
      if (this.data.attachTo?.bindAlpha) {
        alpha = this.getSourceData().alpha;
      }
    }
    if (attachedToTarget) {
      hooksManager.addHook(this.uuid, this.getTargetHook("delete"), (doc) => {
        if (doc !== this.target) return;
        this._target = this._cachedTargetData.position;
        const uuid = doc.uuid;
        SequencerEffectManager.objectDeleted(uuid);
      });
      if (this.isTargetDestroyed) {
        SequencerEffectManager.objectDeleted(this.targetDocument.uuid);
      }
      hooksManager.addHook(this.uuid, this.getTargetHook("update"), (doc) => {
        if (doc !== this.target) return;
        this.updateElevation();
      });
      if (CONSTANTS.IS_V14) {
        hooksManager.addHook(this.uuid, this.getTargetHook("update"), (doc, changed) => {
          if (doc !== this.targetDocument) return;
          if (changed?.level === void 0) return;
          this._recomputeRenderable();
        });
      }
    }
    if (CONSTANTS.IS_V14) {
      hooksManager.addHook(this.uuid, "updateLevel", (level) => {
        if (level.parent?.id !== this.data.sceneId) return;
        this._recomputeRenderable();
      });
    }
    for (let uuid of this.data?.tiedDocuments ?? []) {
      const tiedDocument = fromUuidSync(uuid);
      if (tiedDocument) {
        hooksManager.addHook(
          this.uuid,
          this.getHook("delete", tiedDocument.uuid),
          (doc) => {
            if (tiedDocument !== doc) return;
            SequencerEffectManager.objectDeleted(doc.uuid);
          }
        );
      }
    }
    this._setTimeout(() => {
      if (this._ended) return;
      if (this._baseRenderable === void 0) {
        this._baseRenderable = baseRenderable;
      }
      this._pauseRenderable = true;
      this._initializing = false;
      this._recomputeRenderable();
      if (this.spriteContainer) this.spriteContainer.alpha = alpha ?? 1;
    }, 25);
  }
  /**
   * Calculates the padding and scale to stretch an effect across the given distance
   *
   * If the file is a SequencerFileBase instance, it will also pick the appropriate file for the right distance
   *
   * @param {number} distance
   * @param {number} textureWidth
   * @returns {Object}
   * @private
   */
  async _getDistanceScaling(distance, textureWidth) {
    if (!this._distanceCache || this._distanceCache?.distance !== distance) {
      let scaleX = 1;
      let scaleY = 1;
      if (this._file instanceof SequencerFileBase) {
        const startPoint = this.template?.startPoint ?? 0;
        const endPoint = this.template?.endPoint ?? 0;
        const widthWithPadding = textureWidth - (startPoint + endPoint);
        const spriteScale = distance / widthWithPadding;
        scaleX = spriteScale;
        scaleY = this.data.stretchTo?.onlyX ? widthWithPadding / textureWidth : spriteScale;
      }
      this._distanceCache = {
        scaleX,
        scaleY,
        distance
      };
    }
    return this._distanceCache;
  }
  /**
   * Applies the distance scaling to the sprite based on the previous method
   *
   * @returns {Promise<void>}
   * @private
   */
  async _applyDistanceScaling() {
    const ray = new foundry.canvas.geometry.Ray(this.sourcePosition, this.targetPosition);
    this._rotateTowards(ray);
    const distance = ray.distance / (this.data.scale.x ?? 1);
    this._updateCurrentFilePath(distance);
    await this.sprite.activate(this._currentFilePath);
    const texture = this.sprite.texture;
    let { scaleX, scaleY } = await this._getDistanceScaling(distance, texture.width);
    if (this.data.attachTo?.active) {
      const { x, y } = this.sourcePosition;
      this.position.set(x, y);
    }
    if (this.data.tilingTexture) {
      const scaleX2 = this.data.scale.x ?? 1;
      const scaleY2 = this.data.scale.y ?? 1;
      this.sprite.scale.set(scaleX2 * this.flipX, scaleY2 * this.flipY);
      this.sprite.width = distance * scaleX2;
      this.sprite.height = texture.height * scaleY2;
      this.sprite.tileScale.x = this.data.tilingTexture.scale.x * scaleX2;
      this.sprite.tileScale.y = this.data.tilingTexture.scale.y * scaleY2;
      this.sprite.tilePosition = this.data.tilingTexture.position;
    } else {
      this.sprite.scale.set(
        scaleX * (this.data.scale.x ?? 1) * this.flipX,
        scaleY * (this.data.scale.y ?? 1) * this.flipY
      );
    }
  }
  _setAnchors() {
    let anchor = { x: 0.5, y: 0.5, ...this.data.spriteAnchor ?? null };
    if (this.data.rotateTowards && this.data.rotateTowards.template || this.data.stretchTo) {
      const textureWidth = this.sprite.texture?.width ?? this.sprite.width;
      const templateAnchorX = this.template ? this.template.startPoint / textureWidth : void 0;
      anchor = { x: templateAnchorX, y: 0.5 };
    }
    if (this.data.rotateTowards && !this.data.rotateTowards.template && !this.data.anchor) {
      this.spriteContainer.pivot.set(this.sprite.width * -0.5, 0);
    } else {
      this.spriteContainer.pivot.set(
        interpolate(
          this.sprite.width * -0.5,
          this.sprite.width * 0.5,
          this.data.anchor?.x ?? 0.5
        ),
        interpolate(
          this.sprite.height * -0.5,
          this.sprite.height * 0.5,
          this.data.anchor?.y ?? 0.5
        )
      );
    }
    this.sprite.anchor?.set(
      this.flipX === 1 ? anchor.x : 1 - anchor.x,
      anchor.y
    );
  }
  _checkWallCollisions() {
    if (!this.data.stretchTo?.attachTo || !this.data.stretchTo?.requiresLineOfSight)
      return true;
    const ray = new foundry.canvas.geometry.Ray(this.sourcePosition, this.targetPosition);
    const blockingObjects = canvas.walls.checkCollision(ray, { type: "sight" });
    if (!blockingObjects.length && !this.data.stretchTo?.hideLineOfSight) {
      SequencerEffectManager.endEffects({ effects: this });
    }
    return !blockingObjects.length;
  }
  /**
   * Rotates the effect towards the target
   *
   * @param ray
   * @private
   */
  _rotateTowards(ray) {
    if (!ray) {
      const sourcePosition = this.flipX === 1 ? this.sourcePosition : this.targetPosition;
      const targetPosition = this.flipX === 1 ? this.targetPosition : this.sourcePosition;
      ray = new foundry.canvas.geometry.Ray(sourcePosition, targetPosition);
    }
    this.rotationContainer.rotation = Math.normalizeRadians(
      ray.angle + Math.toRadians(this.data.rotateTowards?.rotationOffset ?? 0)
    );
    PluginsManager.rotation({ effect: this });
  }
  /**
   * Transforms the sprite, rotating it, stretching it, scaling it, sizing it according its data
   *
   * @private
   */
  async _transformSprite() {
    if (this.data.stretchTo) {
      await this._applyDistanceScaling();
    }
    if (!this.data.stretchTo) {
      this._transformNoStretchSprite();
    }
    if (!this.data.screenSpace && (!this.data.attachTo?.active || this.data.stretchTo?.attachTo)) {
      const { x, y } = this.sourcePosition;
      this.position.set(x, y);
    }
    if (this.data.rotateTowards) {
      this._rotateTowards();
    }
    this._setAnchors();
    PluginsManager.rotation({ effect: this });
    this.sprite.updateDefaultScaling();
  }
  async _transformStretchToAttachedSprite() {
    try {
      await this._applyDistanceScaling();
      this._setAnchors();
    } catch (err) {
    }
  }
  _transformNoStretchSprite() {
    if (this.data.tilingTexture) {
      this.sprite.tileScale = {
        x: this.data.tilingTexture.scale.x * this.gridSizeDifference,
        y: this.data.tilingTexture.scale.y * this.gridSizeDifference
      };
      this.sprite.tilePosition = this.data.tilingTexture.position;
    }
    const { heightWidthRatio, widthHeightRatio, baseScaleX, baseScaleY } = this._getBaseScale();
    if (this.data.scaleToObject) {
      this._applyScaleToObject(heightWidthRatio, widthHeightRatio, baseScaleX, baseScaleY);
    } else if (this.data.size) {
      let { height, width } = this.data.size;
      if (this.data.size.width === "auto" || this.data.size.height === "auto") {
        height = this.sprite.height;
        width = this.sprite.width;
        if (this.data.size.width === "auto") {
          height = this.data.size.height;
          if (this.data.size.gridUnits) {
            height *= canvas.grid.size;
          }
          width = height * widthHeightRatio;
        } else if (this.data.size.height === "auto") {
          width = this.data.size.width;
          if (this.data.size.gridUnits) {
            width *= canvas.grid.size;
          }
          height = width * heightWidthRatio;
        }
      } else if (this.data.size.gridUnits) {
        height *= canvas.grid.size;
        width *= canvas.grid.size;
      }
      this.sprite.width = width * baseScaleX;
      this.sprite.height = height * baseScaleY;
    } else if (this.data.screenSpace) {
      this.sprite.scale.set(
        baseScaleX,
        baseScaleY
      );
    } else {
      this.sprite.scale.set(
        baseScaleX * this.gridSizeDifference,
        baseScaleY * this.gridSizeDifference
      );
    }
  }
  /**
   * Calculate the base scale and aspect ratios of the sprite
   *
   * @returns {{heightWidthRatio: number, widthHeightRatio: number, baseScaleX: number, baseScaleY: number}}
   *
   * @private
   */
  _getBaseScale() {
    const heightWidthRatio = this.sprite.height / this.sprite.width;
    const widthHeightRatio = this.sprite.width / this.sprite.height;
    const baseScaleX = (this.data.scale?.x ?? 1) * (this.data.spriteScale?.x ?? 1) * this.flipX;
    const baseScaleY = (this.data.scale?.y ?? 1) * (this.data.spriteScale?.y ?? 1) * this.flipY;
    return {
      heightWidthRatio,
      widthHeightRatio,
      baseScaleX,
      baseScaleY
    };
  }
  _applyScaleToObject(heightWidthRatio, widthHeightRatio, baseScaleX, baseScaleY) {
    try {
      let { width, height } = this.getSourceData();
      if (this.sourceDocument instanceof TokenDocument) {
        width *= this.data.scaleToObject?.considerTokenScale ? this.sourceDocument.texture.scaleX : 1;
        height *= this.data.scaleToObject?.considerTokenScale ? this.sourceDocument.texture.scaleY : 1;
      }
      if (width === this._lastDimensions.width && height === this._lastDimensions.height) return;
      this._lastDimensions = { width, height };
      const ratioToUse = heightWidthRatio > widthHeightRatio;
      if (this.data.scaleToObject?.uniform) {
        let newWidth = Math.max(width, height);
        height = Math.max(width, height);
        width = newWidth;
      } else {
        width = width * (ratioToUse ? widthHeightRatio : 1);
        height = height * (!ratioToUse ? heightWidthRatio : 1);
      }
      this.sprite.width = width * (this.data.scaleToObject?.scale ?? 1) * baseScaleX;
      this.sprite.height = height * (this.data.scaleToObject?.scale ?? 1) * baseScaleY;
      SequencerAnimationEngine.updateStartValues(this.sprite, "width");
      SequencerAnimationEngine.updateStartValues(this.sprite, "height");
      SequencerAnimationEngine.updateStartValues(this.sprite, "scale.x");
      SequencerAnimationEngine.updateStartValues(this.sprite, "scale.y");
    } catch (err) {
    }
  }
  _transformAttachedNoStretchSprite() {
    if (this.isDestroyed) return;
    const applyRotation = this.data.attachTo?.bindRotation && !(this.sourceDocument instanceof TokenDocument && this.sourceDocument.lockRotation) && (this.sourceDocument?.rotation !== void 0 || this.sourceDocument?.direction !== void 0) && !this.data.rotateTowards && !this.data.stretchTo;
    const sourceData = this.getSourceData();
    if (applyRotation) {
      this.rotationContainer.rotation = sourceData.rotation;
    }
    PluginsManager.rotation({ effect: this });
    const { x, y } = this._resolveSourcePosition(sourceData);
    this.position.set(x, y);
  }
  _transformRotateTowardsAttachedSprite() {
    if (this.isDestroyed) return;
    try {
      this._rotateTowards();
      this._setAnchors();
    } catch (err) {
      debug_error(err);
    }
  }
  /**
   * Provided an animation targeting the rotation of the sprite's primary container, this method will counter-rotate
   * the sprite in an equal fashion so that the sprite's rotation remains static relative to this animation
   *
   * @param animation
   * @returns {*[]}
   * @private
   */
  _counterAnimateRotation(animation2) {
    if (animation2.target === this.spriteContainer && this.data.zeroSpriteRotation) {
      delete animation2.target;
      let counterAnimation = foundry.utils.deepClone(animation2);
      animation2.target = this.spriteContainer;
      counterAnimation.target = this.sprite;
      if (counterAnimation.values) {
        counterAnimation.values = counterAnimation.values.map(
          (value) => value * -1
        );
      } else {
        counterAnimation.from *= -1;
        counterAnimation.to *= -1;
      }
      if (!Array.isArray(animation2)) {
        animation2 = [animation2, counterAnimation];
      } else {
        animation2.push(counterAnimation);
      }
    }
    return animation2;
  }
  /**
   * Plays the custom animations of this effect
   *
   * @returns {number}
   * @private
   */
  _playCustomAnimations() {
    if (!this.data.animations) return 0;
    this._playAnimations(
      foundry.utils.deepClone(this.data.animations) ?? [],
      this.actualCreationTime - this.creationTimestamp
    );
  }
  _playAnimations(animations, timeDifference = 0) {
    let animationsToSend = [];
    const oneShotAnimations = animations.filter(
      (animation2) => !animation2.looping && !animation2.fromEnd
    );
    for (let animation2 of oneShotAnimations) {
      if (animation2.target === "alphaFilter") {
        animation2.target = this;
        animation2.propertyName = "effectAlpha";
      } else if (animation2.target === "effect") {
        animation2.target = this;
      } else {
        animation2.target = foundry.utils.getProperty(this, animation2.target);
      }
      if (!animation2.target) continue;
      if (animation2.propertyName.indexOf("rotation") > -1) {
        animation2.from = animation2.from * (Math.PI / 180);
        animation2.to = animation2.to * (Math.PI / 180);
      }
      if (["position.x", "position.y", "height", "width"].includes(
        animation2.propertyName
      ) && animation2.gridUnits) {
        animation2.from *= canvas.grid.size;
        animation2.to *= canvas.grid.size;
      }
      if (["position.x", "position.y", "height", "width"].includes(
        animation2.propertyName
      ) && animation2.screenSpace) {
        const [screenWidth, screenHeight] = canvas.screenDimensions;
        const dimension = animation2.propertyName === "position.x" || animation2.propertyName === "width" ? screenWidth : screenHeight;
        animation2.from *= dimension;
        animation2.to *= dimension;
      }
      if (["hue"].includes(animation2.propertyName)) {
        animation2.getPropertyName = "values." + animation2.propertyName;
      }
      animationsToSend = animationsToSend.concat(
        this._counterAnimateRotation(animation2)
      );
    }
    const loopingAnimations = animations.filter(
      (animation2) => animation2.looping
    );
    for (let animation2 of loopingAnimations) {
      if (animation2.target === "alphaFilter") {
        animation2.target = this;
        animation2.propertyName = "effectAlpha";
      } else if (animation2.target === "effect") {
        animation2.target = this;
      } else {
        animation2.target = foundry.utils.getProperty(this, animation2.target);
      }
      if (!animation2.target) continue;
      if (animation2.propertyName.indexOf("rotation") > -1) {
        animation2.values = animation2.values.map((angle) => {
          return angle * (Math.PI / 180);
        });
      }
      if (["position.x", "position.y", "height", "width"].includes(
        animation2.propertyName
      ) && animation2.gridUnits) {
        animation2.values = animation2.values.map((value) => {
          return value * canvas.grid.size;
        });
      }
      if (["hue"].includes(animation2.propertyName)) {
        animation2.getPropertyName = "values." + animation2.propertyName;
      }
      animationsToSend = animationsToSend.concat(
        this._counterAnimateRotation(animation2)
      );
    }
    if (!(this instanceof PersistentCanvasEffect)) {
      animationsToSend = animationsToSend.concat(
        this._getFromEndCustomAnimations()
      );
    }
    setTimeout(() => {
      SequencerAnimationEngine.addAnimation(
        this.id,
        animationsToSend,
        timeDifference
      );
    }, 20);
  }
  _getFromEndCustomAnimations(immediate = false) {
    let fromEndAnimations = [];
    const animations = foundry.utils.deepClone(this.data.animations) ?? [];
    const oneShotEndingAnimations = animations.filter(
      (animation2) => !animation2.looping && animation2.fromEnd
    );
    for (let animation2 of oneShotEndingAnimations) {
      animation2.target = foundry.utils.getProperty(this, animation2.target);
      if (!animation2.target) continue;
      animation2.delay = is_real_number(immediate) ? Math.max(immediate - animation2.duration + animation2.delay, 0) : Math.max(
        this._totalDuration - animation2.duration + animation2.delay,
        0
      );
      if (animation2.propertyName.indexOf("rotation") > -1) {
        animation2.from = animation2.from * (Math.PI / 180);
        animation2.to = animation2.to * (Math.PI / 180);
      }
      if (["position.x", "position.y", "height", "width"].includes(
        animation2.propertyName
      ) && animation2.gridUnits) {
        animation2.from *= canvas.grid.size;
        animation2.to *= canvas.grid.size;
      }
      fromEndAnimations = fromEndAnimations.concat(
        this._counterAnimateRotation(animation2)
      );
    }
    return fromEndAnimations;
  }
  /**
   * Fades in the effect at the start of the effect
   *
   * @returns {number|*}
   * @private
   */
  _fadeIn() {
    if (!this.data.fadeIn || !this.sprite) return 0;
    let fadeIn = this.data.fadeIn;
    if (this.actualCreationTime - (this.creationTimestamp + fadeIn.duration + fadeIn.delay) > 0) {
      return;
    }
    this.effectAlpha = 0;
    SequencerAnimationEngine.addAnimation(this.id, {
      target: this,
      propertyName: "effectAlpha",
      to: this.data.opacity,
      duration: fadeIn.duration,
      ease: fadeIn.ease,
      delay: fadeIn.delay,
      absolute: true
    });
    return fadeIn.duration + fadeIn.delay;
  }
  /**
   * Fades in the effect's audio at the start of the effect
   *
   * @returns {number|*}
   * @private
   */
  _fadeInAudio() {
    if (!this.data.fadeInAudio || !this.sprite || !this.sprite.hasAnimatedMedia) return 0;
    let fadeInAudio = this.data.fadeInAudio;
    if (this.actualCreationTime - (this.creationTimestamp + fadeInAudio.duration + fadeInAudio.delay) > 0)
      return;
    this.sprite.volume = 0;
    SequencerAnimationEngine.addAnimation(this.id, {
      target: this.sprite,
      propertyName: "volume",
      to: (this.data.volume ?? 0) * game.settings.get("core", "globalInterfaceVolume"),
      duration: fadeInAudio.duration,
      ease: fadeInAudio.ease,
      delay: fadeInAudio.delay,
      absolute: true
    });
    return fadeInAudio.duration + fadeInAudio.delay;
  }
  /**
   * Fades out the effect at the end of the effect's duration
   *
   * @returns {number|*}
   * @private
   */
  _fadeOut(immediate = false) {
    if (!this.data.fadeOut || !this.sprite) return 0;
    const fadeOut = this.data.fadeOut;
    const delay = is_real_number(immediate) ? Math.max(immediate - fadeOut.duration + fadeOut.delay, 0) : Math.max(this._totalDuration - fadeOut.duration + fadeOut.delay, 0);
    SequencerAnimationEngine.addAnimation(this.id, {
      target: this,
      propertyName: "effectAlpha",
      to: 0,
      duration: fadeOut.duration,
      ease: fadeOut.ease,
      delay,
      absolute: true
    });
    return fadeOut.duration + delay;
  }
  /**
   * Fades out the effect at the end of the effect's duration
   *
   * @returns {number|*}
   * @private
   */
  _fadeOutAudio(immediate = false) {
    if (!this.data.fadeOutAudio || !this.sprite || !this.sprite.hasAnimatedMedia) return 0;
    const fadeOutAudio = this.data.fadeOutAudio;
    const delay = is_real_number(immediate) ? Math.max(immediate - fadeOutAudio.duration + fadeOutAudio.delay, 0) : Math.max(
      this._totalDuration - fadeOutAudio.duration + fadeOutAudio.delay,
      0
    );
    setTimeout(() => {
      SequencerAnimationEngine.addAnimation(this.id, {
        target: this.sprite,
        propertyName: "volume",
        to: 0,
        duration: fadeOutAudio.duration,
        ease: fadeOutAudio.ease,
        delay,
        absolute: true
      });
    });
    return fadeOutAudio.duration + delay;
  }
  /**
   * Determines the scale to animate from or to
   * @param property
   * @returns {{x: number, y: number}}
   * @private
   */
  _determineScale(property) {
    let scale2 = {
      x: this.sprite.scale.x,
      y: this.sprite.scale.y
    };
    if (is_real_number(property.value)) {
      scale2.x *= property.value * this.gridSizeDifference * this.flipX;
      scale2.y *= property.value * this.gridSizeDifference * this.flipY;
    } else {
      scale2.x *= property.value.x * this.gridSizeDifference * this.flipX;
      scale2.y *= property.value.y * this.gridSizeDifference * this.flipY;
    }
    return scale2;
  }
  /**
   * Scales the effect in at the start of the effect
   *
   * @returns {number|*}
   * @private
   */
  _scaleIn() {
    if (!this.data.scaleIn || !this.sprite) return 0;
    let scaleIn = this.data.scaleIn;
    let fromScale = this._determineScale(scaleIn);
    if (this.actualCreationTime - (this.creationTimestamp + scaleIn.duration + scaleIn.delay) > 0)
      return;
    let toScale = {
      x: this.sprite.scale.x,
      y: this.sprite.scale.y
    };
    this.sprite.scale.set(fromScale.x, fromScale.y);
    SequencerAnimationEngine.addAnimation(this.id, [
      {
        target: this.sprite,
        propertyName: "scale.x",
        from: fromScale.x,
        to: toScale.x,
        duration: scaleIn.duration,
        ease: scaleIn.ease,
        delay: scaleIn.delay,
        absolute: true
      },
      {
        target: this.sprite,
        propertyName: "scale.y",
        from: fromScale.y,
        to: toScale.y,
        duration: scaleIn.duration,
        ease: scaleIn.ease,
        delay: scaleIn.delay,
        absolute: true
      }
    ]);
    return scaleIn.duration + scaleIn.delay;
  }
  /**
   * Scales the effect out at the end of the effect's duration
   *
   * @returns {number|*}
   * @private
   */
  _scaleOut(immediate = false) {
    if (!this.data.scaleOut || !this.sprite) return 0;
    const scaleOut = this.data.scaleOut;
    const scale2 = this._determineScale(scaleOut);
    const delay = is_real_number(immediate) ? Math.max(immediate - scaleOut.duration + scaleOut.delay, 0) : Math.max(
      this._totalDuration - scaleOut.duration + scaleOut.delay,
      0
    );
    SequencerAnimationEngine.addAnimation(this.id, [
      {
        target: this.sprite,
        propertyName: "scale.x",
        to: scale2.x,
        duration: scaleOut.duration,
        ease: scaleOut.ease,
        delay,
        absolute: true
      },
      {
        target: this.sprite,
        propertyName: "scale.y",
        to: scale2.y,
        duration: scaleOut.duration,
        ease: scaleOut.ease,
        delay,
        absolute: true
      }
    ]);
    return scaleOut.duration + delay;
  }
  /**
   * Rotates the effect in at the start of the effect
   *
   * @returns {number|*}
   * @private
   */
  _rotateIn() {
    if (!this.data.rotateIn || !this.sprite) return 0;
    let rotateIn = this.data.rotateIn;
    if (this.actualCreationTime - (this.creationTimestamp + rotateIn.duration + rotateIn.delay) > 0)
      return;
    let original_radians = this.spriteContainer.rotation;
    this.spriteContainer.rotation = rotateIn.value * (Math.PI / 180);
    SequencerAnimationEngine.addAnimation(
      this.id,
      this._counterAnimateRotation({
        target: this.spriteContainer,
        propertyName: "rotation",
        to: original_radians,
        duration: rotateIn.duration,
        ease: rotateIn.ease,
        delay: rotateIn.delay,
        absolute: true
      })
    );
    return rotateIn.duration + rotateIn.delay;
  }
  /**
   * Rotates the effect out at the end of the effect's duration
   *
   * @returns {number|*}
   * @private
   */
  _rotateOut(immediate = false) {
    if (!this.data.rotateOut || !this.sprite) return 0;
    const rotateOut = this.data.rotateOut;
    const delay = is_real_number(immediate) ? Math.max(immediate - rotateOut.duration + rotateOut.delay, 0) : Math.max(
      this._totalDuration - rotateOut.duration + rotateOut.delay,
      0
    );
    SequencerAnimationEngine.addAnimation(
      this.id,
      this._counterAnimateRotation({
        target: this.spriteContainer,
        propertyName: "rotation",
        to: rotateOut.value * (Math.PI / 180),
        duration: rotateOut.duration,
        ease: rotateOut.ease,
        delay,
        absolute: true
      })
    );
    return rotateOut.duration + delay;
  }
  /**
   * Causes the effect to move towards the given location
   *
   * @returns {number|*}
   * @private
   */
  _moveTowards() {
    if (!this.data.moves || !this.sprite) return 0;
    let moves2 = this.data.moves;
    const targetPos = this.targetPosition;
    let movementDuration = this._totalDuration;
    if (this.data.moveSpeed) {
      const distance = distance_between(
        this.sourcePosition,
        targetPos
      );
      movementDuration = distance / this.data.moveSpeed * 1e3;
    }
    if (this.data.moves.rotate) this._rotateTowards();
    const duration = movementDuration - moves2.delay;
    if (this.actualCreationTime - (this.creationTimestamp + duration + moves2.delay) > 0)
      return;
    SequencerAnimationEngine.addAnimation(this.id, [
      {
        target: this,
        propertyName: "position.x",
        to: targetPos.x,
        duration,
        ease: moves2.ease,
        delay: moves2.delay,
        absolute: true
      },
      {
        target: this,
        propertyName: "position.y",
        to: targetPos.y,
        duration,
        ease: moves2.ease,
        delay: moves2.delay,
        absolute: true
      }
    ]);
    return duration + moves2.delay;
  }
  /**
   * If this effect is temporary, this sets the timeout for when the effect should resolve and get removed;
   *
   * @private
   */
  _setEndTimeout() {
    this._setTimeout(() => {
      this._resolve(this.data);
      this.endEffect();
    }, this._totalDuration);
  }
  _setupTimestampHook(offset2) {
    if (!this._file?.originalMetadata?.timestamps || this._ended) return;
    const timestamps = this._file.getTimestamps();
    const timestampArray = Array.isArray(timestamps) ? timestamps : [timestamps];
    for (const timestamp of timestampArray) {
      if (!is_real_number(timestamp)) continue;
      let realTimestamp = timestamp - offset2 / this.mediaPlaybackRate;
      if (realTimestamp < 0) {
        realTimestamp += this._endTime;
      }
      this._setTimeout(() => {
        if (this._ended) return;
        Hooks.callAll("sequencerEffectTimestamp", this, this._file);
        if (this.mediaLooping) {
          const offsets = (this._endTime - this.mediaCurrentTime) * -1e3;
          this._setupTimestampHook(offsets);
        }
      }, realTimestamp);
    }
  }
  /**
   * Starts the loop of this effect, calculating the difference between the effect's creation time, and the actual
   * creation time on the client
   *
   * @returns {Promise<void>}
   * @private
   */
  async _startEffect() {
    if (!this.hasAnimatedMedia) return;
    let creationTimeDifference = this.data.persist ? this.actualCreationTime - this.creationTimestamp : 0;
    creationTimeDifference *= this.mediaPlaybackRate;
    this._currentLoops = Math.floor(creationTimeDifference / this._totalDuration) + 1;
    if (this.loops && this._currentLoops > this.loops) {
      if (this.data.loopOptions?.endOnLastLoop || !this.data.persist) {
        return this.endEffect();
      }
      await this.pauseMedia();
      this.mediaCurrentTime = this._endTime;
      if (this.sprite.texture) {
        this._pauseRenderable = false;
        this._recomputeRenderable();
        this._setTimeout(() => {
          this.updateTexture();
          this._setTimeout(() => {
            this._pauseRenderable = true;
            this._recomputeRenderable();
          }, 150);
        }, 150);
      }
      return;
    }
    return this._startLoop(creationTimeDifference);
  }
  /**
   * Kicks off the loop, or just sets the video to loop
   *
   * @param creationTimeDifference
   * @returns {Promise<void>}
   * @private
   */
  async _startLoop(creationTimeDifference) {
    if (!this._animationTimes.loopStart) {
      this._loopOffset = creationTimeDifference % this._animationDuration / 1e3;
    } else if (creationTimeDifference / 1e3 > this._animationTimes.loopStart) {
      const loopDuration = this._animationTimes.loopEnd - this._animationTimes.loopStart;
      this._loopOffset = creationTimeDifference % (loopDuration * 1e3) / 1e3;
    }
    if (this._loopOffset) {
      this.mediaCurrentTime = this._loopOffset;
    }
    await this.playMedia();
    this._addToTicker(this.loopHandler);
  }
  async loopHandler() {
    if (this._ended || this._isEnding) {
      return;
    }
    const endTime = this.data.persist ? this._animationTimes.loopEnd ?? this._endTime : this.loops ? Math.min(this._endTime, this.mediaDuration) : this._endTime;
    if (this.mediaCurrentTime < endTime) {
      return;
    }
    if (this.restartLoopHandler != null) {
      return;
    }
    this.pauseMedia();
    this.mediaCurrentTime = this._endTime;
    if ((this.loops || !this.data.persist) && this._currentLoops >= this.loops) {
      const holdsLastFrame = !this.data.persist && this._endTime > this.mediaDuration;
      if (!holdsLastFrame && (!this.data.persist || this.data.loopOptions?.endOnLastLoop)) {
        this.endEffect();
      }
      this._ticker?.remove(this.loopHandler, this);
      return;
    }
    const restartTime = this._startTime === 0 && this._animationTimes.loopStart ? this._animationTimes.loopStart : this._startTime;
    if (!this.loopDelay) {
      this._currentLoops++;
      this.mediaCurrentTime = restartTime;
      this.playMedia();
      return;
    }
    this._currentLoops++;
    this.restartLoopHandler = this._setTimeout(() => {
      this.restartLoopHandler = null;
      this.mediaCurrentTime = restartTime;
      this.playMedia();
    }, this.loopDelay);
  }
}
class PersistentCanvasEffect extends CanvasEffect {
  /** @OVERRIDE */
  _playPresetAnimations() {
    this._moveTowards();
    this._fadeIn();
    this._scaleIn();
    this._rotateIn();
  }
  /** @OVERRIDE */
  _timeoutVisibility() {
    let creationTimeDifference = this.actualCreationTime - this.creationTimestamp;
    let timeout = creationTimeDifference === 0 && !this.data.animations ? 0 : 50;
    this._setTimeout(() => {
      this._setupHooks();
    }, timeout);
  }
  /** @OVERRIDE */
  _setEndTimeout() {
    let creationTimeDifference = this.actualCreationTime - this.creationTimestamp;
    if (this.loops && creationTimeDifference >= this._totalDuration && this.hasAnimatedMedia) {
      this._setTimeout(() => {
        this.pauseMedia();
      }, this._totalDuration);
    }
  }
  /** @OVERRIDE */
  async endEffect() {
    if (this._isEnding) return;
    this._isEnding = true;
    let extraEndDuration = this.data.extraEndDuration ?? 0;
    this.mediaLooping = false;
    if (this._animationTimes?.forcedEnd) {
      this.mediaCurrentTime = this._animationTimes.forcedEnd;
      extraEndDuration += (this.mediaDuration - (this._animationTimes?.forcedEnd ?? 0)) * 1e3;
    } else if (this._animationTimes?.loopEnd) {
      extraEndDuration += (this.mediaDuration - this.mediaCurrentTime) * 1e3;
    }
    const fromEndCustomAnimations = this._getFromEndCustomAnimations(extraEndDuration);
    const durations = [
      this._fadeOut(extraEndDuration),
      this._fadeOutAudio(extraEndDuration),
      this._scaleOut(extraEndDuration),
      this._rotateOut(extraEndDuration),
      extraEndDuration,
      ...fromEndCustomAnimations.map(
        (animation2) => animation2.duration + animation2.delay
      )
    ].filter(Boolean);
    SequencerAnimationEngine.addAnimation(this.id, fromEndCustomAnimations);
    const waitDuration = Math.max(...durations, 0);
    this._resolve(waitDuration);
    return new Promise(
      (resolve) => this._setTimeout(() => {
        super.endEffect();
        resolve(this.data);
      }, waitDuration)
    );
  }
}
function createShape(shape) {
  const graphic = new PIXI.LegacyGraphics();
  graphic.id = "sequencer-graphics-" + foundry.utils.randomID();
  graphic.beginFill(
    shape?.isMask || !is_real_number(shape?.fillColor) ? 16777215 : shape?.fillColor,
    shape?.isMask ? 1 : shape?.fillAlpha ?? 0
  );
  graphic.lineStyle(
    shape.lineSize ?? (shape?.isMask ? 1 : 0),
    shape?.lineColor ?? 16777215
  );
  const offsetX = (shape.offset?.x ?? 0) * (shape.offset?.gridUnits ? canvas.grid.size : 1);
  const offsetY = (shape.offset?.y ?? 0) * (shape.offset?.gridUnits ? canvas.grid.size : 1);
  const sizeMultiplier = shape.gridUnits ? canvas.grid.size : 1;
  graphic.offset = {
    x: offsetX,
    y: offsetY
  };
  switch (shape.type) {
    case CONSTANTS.SHAPES.CIRC:
      graphic.drawCircle(
        graphic.offset.x,
        graphic.offset.y,
        shape.radius * sizeMultiplier
      );
      break;
    case CONSTANTS.SHAPES.RECT:
      graphic.drawRect(
        graphic.offset.x,
        graphic.offset.y,
        shape.width * sizeMultiplier,
        shape.height * sizeMultiplier
      );
      break;
    case CONSTANTS.SHAPES.ELIP:
      graphic.drawEllipse(
        graphic.offset.x,
        graphic.offset.y,
        shape.width * sizeMultiplier,
        shape.height * sizeMultiplier
      );
      break;
    case CONSTANTS.SHAPES.RREC:
      graphic.drawRoundedRect(
        graphic.offset.x,
        graphic.offset.y,
        shape.width * sizeMultiplier,
        shape.height * sizeMultiplier,
        shape.radius * sizeMultiplier
      );
      break;
    case CONSTANTS.SHAPES.POLY:
      graphic.drawPolygon(
        shape.points.map((point) => {
          return new PIXI.Point(
            point[0] * sizeMultiplier,
            point[1] * sizeMultiplier
          );
        })
      );
      break;
  }
  graphic.alpha = shape.alpha ?? 1;
  graphic.endFill();
  if (shape.anchor) {
    graphic.pivot.set(
      shape.width * (shape.anchor.x ?? 0),
      shape.height * (shape.anchor.y ?? 0)
    );
  }
  return graphic;
}
function serializeShape(shape) {
  if (shape instanceof PIXI.Polygon) {
    return { type: "Polygon", points: Array.from(shape.points) };
  }
  if (shape instanceof PIXI.Circle) {
    return { type: "Circle", x: shape.x, y: shape.y, radius: shape.radius };
  }
  if (shape instanceof PIXI.Rectangle) {
    return { type: "Rectangle", x: shape.x, y: shape.y, width: shape.width, height: shape.height };
  }
  throw new Error("serializeShape: unsupported shape type");
}
function deserializeShape(data) {
  if (!data || typeof data !== "object") return null;
  switch (data.type) {
    case "Polygon":
      return new PIXI.Polygon(data.points ?? []);
    case "Circle":
      return new PIXI.Circle(data.x ?? 0, data.y ?? 0, data.radius ?? 0);
    case "Rectangle":
      return new PIXI.Rectangle(data.x ?? 0, data.y ?? 0, data.width ?? 0, data.height ?? 0);
  }
  return null;
}
function computeWallPolygon(origin, options = {}) {
  if (!origin || typeof origin.x !== "number" || typeof origin.y !== "number") {
    throw new Error("computeWallPolygon: origin must be { x: number, y: number }");
  }
  const {
    type = "sight",
    radius = null,
    level = null
  } = options;
  const validTypes = ["sight", "sound", "move", "light"];
  if (!validTypes.includes(type)) {
    throw new Error(`computeWallPolygon: type must be one of ${validTypes.join(", ")}`);
  }
  const config = { type };
  if (typeof radius === "number") {
    config.radius = radius;
  }
  if (level) {
    let resolved = level;
    if (typeof level === "string") {
      const sceneLevels = canvas.scene?.levels;
      resolved = sceneLevels?.get(level) ?? sceneLevels?.getName?.(level) ?? null;
    }
    if (resolved) config.level = resolved;
  }
  const PolygonBackend = CONFIG.Canvas.polygonBackends[type];
  if (!PolygonBackend) {
    throw new Error(`computeWallPolygon: no polygon backend registered for type "${type}"`);
  }
  return PolygonBackend.create(
    { x: origin.x, y: origin.y, elevation: origin.elevation ?? 0 },
    config
  );
}
function calculate_missed_position(source2, target, twister) {
  const sourcePosition = get_object_position(source2);
  const sourceDimensions = get_object_dimensions(source2, true);
  if (!target) {
    const angle2 = twister.random() * Math.PI * 2;
    let x2 = Math.cos(angle2) * sourceDimensions.width;
    let y2 = Math.sin(angle2) * sourceDimensions.height;
    return {
      x: random_float_between(x2 * 1.5, x2 * 2.5, twister),
      y: random_float_between(y2 * 1.5, y2 * 2.5, twister)
    };
  }
  const targetDimensions = get_object_dimensions(target, true);
  const targetPosition = get_object_position(target);
  const ray = new foundry.canvas.geometry.Ray(targetPosition, sourcePosition);
  let startRadians = ray.angle + Math.PI / 2;
  let endRadians = ray.angle - Math.PI / 2;
  const sizeCompensation = Math.max(
    1,
    Math.abs(sourceDimensions.width - targetDimensions.height)
  );
  let distance = ray.distance / canvas.grid.size - sizeCompensation;
  if (distance <= 1) {
    const angle2 = twister.random() > 0.5 ? ray.angle + Math.PI / 4 : ray.angle - Math.PI / 4;
    const x2 = Math.cos(angle2) * targetDimensions.width;
    const y2 = Math.sin(angle2) * targetDimensions.height;
    return { x: x2, y: y2 };
  }
  distance = Math.max(Math.abs(distance - 15), 6);
  endRadians -= Math.PI / distance;
  startRadians += Math.PI / distance;
  const angle = interpolate(startRadians, endRadians, twister.random());
  const x = Math.cos(angle) * targetDimensions.width;
  const y = Math.sin(angle) * targetDimensions.height;
  return {
    x: random_float_between(x * 1.5, x * 2.5, twister),
    y: random_float_between(y * 1.5, y * 2.5, twister)
  };
}
function get_object_position(obj, { measure = false, exact = false } = {}) {
  if (obj instanceof CanvasEffect) {
    return obj.worldPosition;
  }
  obj = obj?._object ?? obj.object ?? obj;
  let pos = {};
  if (obj instanceof foundry.canvas.placeables.Region) {
    if (obj.document.shapes.length > 1) {
      pos.x = obj.bounds.x + obj.bounds.width / 2;
      pos.y = obj.bounds.y + obj.bounds.height / 2;
    } else {
      let firstShape = obj.document.shapes[0];
      switch (firstShape.type) {
        case "cone":
        case "line":
          if (!measure) {
            pos.x = firstShape.measuredSegments[0].ray.A.x;
            pos.y = firstShape.measuredSegments[0].ray.A.y;
          } else {
            pos.x = firstShape.measuredSegments[0].ray.B.x;
            pos.y = firstShape.measuredSegments[0].ray.B.y;
          }
          break;
        default:
          pos.x = firstShape.center.x;
          pos.y = firstShape.center.y;
      }
    }
  } else if (obj instanceof foundry.canvas.placeables.MeasuredTemplate) {
    if (measure) {
      if (obj.document.t === "cone" || obj.document.t === "ray") {
        pos.x = obj.ray.B.x;
        pos.y = obj.ray.B.y;
      }
    }
    if (obj.document.t === "rect") {
      pos.x = obj.x;
      pos.y = obj.y;
      if (!exact) {
        pos.x += Math.abs(obj.shape.width / 2) + obj.shape.x;
        pos.y += Math.abs(obj.shape.height / 2) + obj.shape.y;
      }
    }
  } else if (obj instanceof foundry.canvas.placeables.Tile) {
    pos = {
      x: obj.document.x,
      y: obj.document.y
    };
    if (!foundry.utils.isNewerVersion(game.version, "14")) {
      if (!exact) {
        pos.x += Math.abs(obj.document.width / 2);
        pos.y += Math.abs(obj.document.height / 2);
      }
    }
  } else if (obj instanceof foundry.canvas.placeables.Token) {
    const halfSize = get_object_dimensions(obj, true);
    pos = {
      x: obj.x + halfSize.width,
      y: obj.y + halfSize.height
    };
    if (exact) {
      pos.x -= halfSize.width;
      pos.y -= halfSize.height;
    }
  } else if (obj instanceof foundry.canvas.placeables.Drawing) {
    pos = {
      x: obj.document.x,
      y: obj.document.y
    };
    if (!exact) {
      const halfSize = get_object_dimensions(obj, true);
      pos.x += halfSize.width;
      pos.y += halfSize.height;
    }
  } else {
    const docBounds = compute_document_bounds(obj, { measure });
    if (docBounds) {
      if (exact) {
        pos.x = docBounds.x;
        pos.y = docBounds.y;
      } else {
        pos.x = docBounds.x + docBounds.width / 2;
        pos.y = docBounds.y + docBounds.height / 2;
      }
      const directional = compute_region_directional_endpoint(obj, { measure });
      if (directional) {
        pos.x = directional.x;
        pos.y = directional.y;
      }
    }
  }
  pos = {
    x: pos.x ?? obj?.x ?? obj?.position?.x ?? obj?.position?._x ?? obj?.document?.x ?? obj?.document?.position?.x ?? null,
    y: pos.y ?? obj?.y ?? obj?.position?.y ?? obj?.position?._y ?? obj?.document?.y ?? obj?.document?.position?.y ?? null,
    elevation: (obj?.elevation ?? obj?.document?.elevation) != null ? get_object_elevation(obj) : null
  };
  if (pos.x === null) delete pos["x"];
  if (pos.y === null) delete pos["y"];
  if (pos.elevation === null) delete pos["elevation"];
  return pos;
}
function get_random_offset(target, randomOffset, twister = false) {
  let { width, height } = get_object_dimensions(target, true);
  width *= randomOffset;
  height *= randomOffset;
  return {
    x: random_float_between(width * -1, width, twister),
    y: random_float_between(height * -1, height, twister)
  };
}
function get_object_dimensions(inObj, half = false) {
  inObj = inObj?.object ?? inObj?._object ?? inObj;
  if (inObj instanceof foundry.canvas.placeables.Region) {
    if (inObj.document.shapes.length > 1) {
      return {
        width: inObj.bounds.width / (half ? 2 : 1),
        height: inObj.bounds.height / (half ? 2 : 1)
      };
    } else {
      let firstShape = inObj.document.shapes[0];
      switch (firstShape.type) {
        case "cone":
        case "circle":
          return {
            width: firstShape.radius / (half ? 1 : 0.5),
            height: firstShape.radius / (half ? 1 : 0.5)
          };
        case "ring":
          return {
            width: (firstShape.radius + firstShape.outerWidth - firstShape.innerWidth / 2) / (half ? 1 : 0.5),
            height: (firstShape.radius + firstShape.outerWidth - firstShape.innerWidth / 2) / (half ? 1 : 0.5)
          };
        default:
          return "radius" in firstShape ? {
            width: firstShape.radius / (half ? 1 : 0.5),
            height: firstShape.radius / (half ? 1 : 0.5)
          } : {
            width: firstShape.width / (half ? 2 : 1),
            height: firstShape.height / (half ? 2 : 1)
          };
      }
    }
  } else if (inObj instanceof foundry.canvas.placeables.Tile) {
    return {
      width: inObj.document.width / (half ? 2 : 1),
      height: inObj.document.height / (half ? 2 : 1)
    };
  }
  const docBounds = compute_document_bounds(inObj);
  if (docBounds) {
    return {
      width: docBounds.width / (half ? 2 : 1),
      height: docBounds.height / (half ? 2 : 1)
    };
  }
  let width = inObj?.hitArea?.width ?? inObj?.w ?? inObj?.shape?.width ?? (inObj?.shape?.radius ? inObj?.shape?.radius * 2 : void 0) ?? inObj?.bounds?.width ?? inObj?.width ?? canvas.grid.size;
  let height = inObj?.hitArea?.height ?? inObj?.h ?? inObj?.shape?.height ?? (inObj?.shape?.radius ? inObj?.shape?.radius * 2 : void 0) ?? inObj?.bounds?.height ?? inObj?.height ?? canvas.grid.size;
  return {
    width: width / (half ? 2 : 1),
    height: height / (half ? 2 : 1)
  };
}
const _warnedUnknownTypes = /* @__PURE__ */ new Set();
function _warnUnknownType(category, type) {
  const key = category + ":" + type;
  if (_warnedUnknownTypes.has(key)) return;
  _warnedUnknownTypes.add(key);
  debug(`Sequencer | unhandled ${category} type "${type}"`);
}
function compute_document_bounds(doc, { measure = false } = {}) {
  if (!doc || typeof doc !== "object") return null;
  const docName = doc.documentName;
  if (!docName) return null;
  const scene = doc.parent ?? canvas?.scene;
  const gridSize = scene?.grid?.size ?? canvas?.grid?.size ?? 100;
  const gridDistance = scene?.grid?.distance ?? canvas?.grid?.distance ?? 5;
  const distancePixels = scene?.dimensions?.distancePixels ?? gridSize / gridDistance;
  switch (docName) {
    case "Token": {
      const w = (doc.width ?? 1) * gridSize;
      const h = (doc.height ?? 1) * gridSize;
      return { x: doc.x ?? 0, y: doc.y ?? 0, width: w, height: h };
    }
    case "Tile": {
      return {
        x: doc.x ?? 0,
        y: doc.y ?? 0,
        width: doc.width ?? 0,
        height: doc.height ?? 0
      };
    }
    case "Drawing": {
      const shape = doc.shape ?? {};
      const baseX = doc.x ?? 0;
      const baseY = doc.y ?? 0;
      switch (shape.type) {
        case "r":
        // rectangle
        case "e":
          return { x: baseX, y: baseY, width: shape.width ?? 0, height: shape.height ?? 0 };
        case "c": {
          const r = shape.radius ?? 0;
          return { x: baseX, y: baseY, width: r * 2, height: r * 2 };
        }
        case "p":
        // polygon
        case "f": {
          const pts = shape.points;
          if (!pts?.length) return { x: baseX, y: baseY, width: 0, height: 0 };
          let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
          for (let i = 0; i < pts.length; i += 2) {
            const px = pts[i], py = pts[i + 1];
            if (px < minX) minX = px;
            if (py < minY) minY = py;
            if (px > maxX) maxX = px;
            if (py > maxY) maxY = py;
          }
          return { x: baseX + minX, y: baseY + minY, width: maxX - minX, height: maxY - minY };
        }
        default:
          _warnUnknownType("drawing shape", shape.type);
          return { x: baseX, y: baseY, width: shape.width ?? 0, height: shape.height ?? 0 };
      }
    }
    case "Region": {
      let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
      let any = false;
      for (const shape of doc.shapes ?? []) {
        const sb = compute_region_shape_bounds(shape);
        if (!sb) continue;
        any = true;
        if (sb.x < minX) minX = sb.x;
        if (sb.y < minY) minY = sb.y;
        if (sb.x + sb.width > maxX) maxX = sb.x + sb.width;
        if (sb.y + sb.height > maxY) maxY = sb.y + sb.height;
      }
      if (!any) return null;
      return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
    }
    case "MeasuredTemplate": {
      const baseX = doc.x ?? 0;
      const baseY = doc.y ?? 0;
      const distance = doc.distance ?? 0;
      const r = distance * distancePixels;
      switch (doc.t) {
        case "circle":
        case "cone":
          return { x: baseX - r, y: baseY - r, width: r * 2, height: r * 2 };
        case "rect":
          return { x: baseX, y: baseY, width: r, height: r };
        case "ray": {
          const w = (doc.width ?? 1) * distancePixels;
          return { x: baseX, y: baseY - w / 2, width: r, height: w };
        }
        default:
          return { x: baseX - r, y: baseY - r, width: r * 2, height: r * 2 };
      }
    }
    case "AmbientLight": {
      const radius = Math.max(doc.config?.dim ?? 0, doc.config?.bright ?? 0) * distancePixels;
      if (radius > 0) {
        return { x: (doc.x ?? 0) - radius, y: (doc.y ?? 0) - radius, width: radius * 2, height: radius * 2 };
      }
      return { x: doc.x ?? 0, y: doc.y ?? 0, width: gridSize, height: gridSize };
    }
    case "AmbientSound": {
      const radius = (doc.radius ?? 0) * distancePixels;
      if (radius > 0) {
        return { x: (doc.x ?? 0) - radius, y: (doc.y ?? 0) - radius, width: radius * 2, height: radius * 2 };
      }
      return { x: doc.x ?? 0, y: doc.y ?? 0, width: gridSize, height: gridSize };
    }
    case "Wall": {
      const c = doc.c ?? [0, 0, 0, 0];
      const [x1, y1, x2, y2] = c;
      return {
        x: Math.min(x1, x2),
        y: Math.min(y1, y2),
        width: Math.abs(x2 - x1),
        height: Math.abs(y2 - y1)
      };
    }
    case "Note": {
      const s = doc.iconSize ?? gridSize;
      return { x: (doc.x ?? 0) - s / 2, y: (doc.y ?? 0) - s / 2, width: s, height: s };
    }
    default:
      _warnUnknownType("document", docName);
      return null;
  }
}
function create_drawing_mask_shape(doc) {
  const shape = doc?.shape;
  if (!shape) return null;
  switch (shape.type) {
    case "r":
      return new PIXI.Rectangle(0, 0, shape.width ?? 0, shape.height ?? 0);
    case "e": {
      const w = shape.width ?? 0;
      const h = shape.height ?? 0;
      return new PIXI.Ellipse(w / 2, h / 2, w / 2, h / 2);
    }
    case "c": {
      const r = shape.radius ?? 0;
      return new PIXI.Circle(r, r, r);
    }
    case "p":
    case "f": {
      const pts = shape.points;
      if (!pts?.length) return null;
      return new PIXI.Polygon(pts.slice());
    }
    default:
      _warnUnknownType("drawing shape", shape.type);
      return null;
  }
}
function compute_region_directional_endpoint(doc, { measure = false } = {}) {
  if (doc?.documentName !== "Region") return null;
  const shapes = doc.shapes;
  if (!shapes || shapes.length !== 1) return null;
  const shape = shapes[0];
  if (shape?.type !== "line" && shape?.type !== "cone") return null;
  const ox = shape.x ?? 0;
  const oy = shape.y ?? 0;
  if (!measure) return { x: ox, y: oy };
  const extent = shape.type === "line" ? shape.length ?? 0 : shape.radius ?? 0;
  const rot = (shape.rotation ?? 0) * Math.PI / 180;
  return {
    x: ox + extent * Math.cos(rot),
    y: oy + extent * Math.sin(rot)
  };
}
function compute_region_shape_bounds(shape) {
  if (!shape) return null;
  switch (shape.type) {
    case "rectangle": {
      return {
        x: shape.x ?? 0,
        y: shape.y ?? 0,
        width: shape.width ?? 0,
        height: shape.height ?? 0
      };
    }
    case "circle": {
      const r = shape.radius ?? 0;
      return {
        x: (shape.x ?? 0) - r,
        y: (shape.y ?? 0) - r,
        width: r * 2,
        height: r * 2
      };
    }
    case "ellipse": {
      const rx = shape.radiusX ?? 0;
      const ry = shape.radiusY ?? 0;
      return {
        x: (shape.x ?? 0) - rx,
        y: (shape.y ?? 0) - ry,
        width: rx * 2,
        height: ry * 2
      };
    }
    case "emanation": {
      const base = compute_region_shape_bounds(shape.base);
      if (!base) return null;
      const r = shape.radius ?? 0;
      return {
        x: base.x - r,
        y: base.y - r,
        width: base.width + r * 2,
        height: base.height + r * 2
      };
    }
    case "cone": {
      const r = shape.radius ?? 0;
      return {
        x: (shape.x ?? 0) - r,
        y: (shape.y ?? 0) - r,
        width: r * 2,
        height: r * 2
      };
    }
    case "ring": {
      const outer = (shape.radius ?? 0) + (shape.outerWidth ?? 0);
      return {
        x: (shape.x ?? 0) - outer,
        y: (shape.y ?? 0) - outer,
        width: outer * 2,
        height: outer * 2
      };
    }
    case "line": {
      const ox = shape.x ?? 0;
      const oy = shape.y ?? 0;
      const len = shape.length ?? 0;
      const w = shape.width ?? 0;
      const rot = (shape.rotation ?? 0) * Math.PI / 180;
      const ex = ox + len * Math.cos(rot);
      const ey = oy + len * Math.sin(rot);
      const halfW = w / 2;
      const minX = Math.min(ox, ex) - halfW;
      const maxX = Math.max(ox, ex) + halfW;
      const minY = Math.min(oy, ey) - halfW;
      const maxY = Math.max(oy, ey) + halfW;
      return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
    }
    case "polygon": {
      const pts = shape.points;
      if (!pts?.length) return null;
      let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
      for (let i = 0; i < pts.length; i += 2) {
        const px = pts[i], py = pts[i + 1];
        if (px < minX) minX = px;
        if (py < minY) minY = py;
        if (px > maxX) maxX = px;
        if (py > maxY) maxY = py;
      }
      return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
    }
    case "token": {
      const grid = canvas?.scene?.grid ?? canvas?.grid;
      const gridSize = grid?.size ?? 100;
      return {
        x: shape.x ?? 0,
        y: shape.y ?? 0,
        width: (shape.width ?? 1) * gridSize,
        height: (shape.height ?? 1) * gridSize
      };
    }
    case "grid": {
      const offsets = shape.offsets;
      if (!offsets?.length) return null;
      const grid = canvas?.scene?.grid ?? canvas?.grid;
      const gridSize = grid?.size ?? 100;
      let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
      for (const off of offsets) {
        let cellX, cellY;
        if (grid && typeof grid.getTopLeftPoint === "function") {
          const tl = grid.getTopLeftPoint(off);
          cellX = tl.x;
          cellY = tl.y;
        } else {
          cellX = (off.j ?? 0) * gridSize;
          cellY = (off.i ?? 0) * gridSize;
        }
        if (cellX < minX) minX = cellX;
        if (cellY < minY) minY = cellY;
        if (cellX + gridSize > maxX) maxX = cellX + gridSize;
        if (cellY + gridSize > maxY) maxY = cellY + gridSize;
      }
      return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
    }
    default:
      _warnUnknownType("region shape", shape.type);
      return null;
  }
}
const alignments = {
  "top-left": { x: 0.5, y: 0.5 },
  top: { x: 0, y: 0.5 },
  "top-right": { x: -0.5, y: 0.5 },
  left: { x: 0.5, y: 0 },
  center: { x: 0, y: 0 },
  right: { x: -0.5, y: 0 },
  "bottom-left": { x: 0.5, y: -0.5 },
  bottom: { x: 0, y: -0.5 },
  "bottom-right": { x: -0.5, y: -0.5 }
};
function align({
  context,
  spriteWidth,
  spriteHeight,
  align: align2,
  edge
} = {}) {
  let { width, height } = get_object_dimensions(context);
  const alignRatio = alignments[align2];
  const offset2 = {
    x: interpolate(width * -0.5, width * 0.5, alignRatio.x + 0.5),
    y: interpolate(height * -0.5, height * 0.5, alignRatio.y + 0.5)
  };
  return {
    x: offset2.x + (edge && edge !== "on" ? spriteWidth * alignRatio.x * (edge === "outer" ? 1 : -1) : 0),
    y: offset2.y + (edge && edge !== "on" ? spriteHeight * alignRatio.y * (edge === "outer" ? 1 : -1) : 0)
  };
}
function is_object_canvas_data(inObj) {
  if (typeof inObj !== "object") return false;
  const keys = Object.keys(inObj);
  keys.sort();
  return keys.includes("x") && keys.includes("y") || keys.includes("height") && keys.includes("width") && keys.includes("x") && keys.includes("y");
}
function get_object_canvas_data(inObject, { measure = false, uuid = true } = {}) {
  inObject = inObject?.object ?? inObject;
  const data = {
    ...get_object_position(inObject, { measure }),
    ...get_object_dimensions(inObject?.mesh ?? inObject?.tile ?? inObject),
    elevation: get_object_elevation(inObject),
    cachedLocation: true
  };
  if (uuid) {
    data["uuid"] = inObject?.document?.uuid ?? inObject?.uuid;
  }
  return data;
}
function get_object_elevation(inObject) {
  const e = inObject?.document?.elevation ?? inObject?.elevation ?? 0;
  return typeof e === "object" ? e?.bottom ?? e?.top ?? 0 : e;
}
function get_object_vertical_extent(inObject) {
  const doc = inObject?.document ?? inObject;
  if (!doc) return null;
  const elevation2 = doc.elevation;
  if (elevation2 && typeof elevation2 === "object") {
    const bottom = elevation2.bottom ?? -Infinity;
    const top = elevation2.top ?? Infinity;
    return [bottom, top, !!elevation2.topInclusive];
  }
  if (typeof elevation2 === "number" && typeof doc.depth === "number") {
    const gridDistance = doc.parent?.grid?.distance ?? canvas?.scene?.grid?.distance ?? 1;
    return [elevation2, elevation2 + doc.depth * gridDistance, false];
  }
  if (typeof elevation2 === "number") {
    return [elevation2, elevation2, false];
  }
  return null;
}
function get_mouse_position(snapToGrid = false) {
  const pos = getCanvasMouse().getLocalPosition(canvas.app.stage);
  const snappingOptions = {
    mode: CONST.GRID_SNAPPING_MODES.CENTER | CONST.GRID_SNAPPING_MODES.VERTEX | CONST.GRID_SNAPPING_MODES.EDGE_MIDPOINT
  };
  return !snapToGrid ? new PIXI.Point(pos.x, pos.y) : canvas.grid.getSnappedPoint({ x: pos.x, y: pos.y }, snappingOptions);
}
function distance_between(p1, p2) {
  return new foundry.canvas.geometry.Ray(p1, p2).distance;
}
function validateObject(inObject, sceneId) {
  if (is_UUID(inObject) || !is_object_canvas_data(inObject)) {
    inObject = get_object_from_scene(inObject, sceneId);
  }
  return inObject?._object ?? inObject;
}
function getPositionFromData(data, type = "source", twister = false) {
  const source2 = data.nameOffsetMap[data[type]] ? data.nameOffsetMap[data[type]][type] : validateObject(data[type], data.sceneId);
  const position = source2 instanceof foundry.canvas.placeables.PlaceableObject ? get_object_position(source2) : source2?.worldPosition || source2?.center || source2;
  let offset2 = getOffsetFromData(data, { source: source2, type, twister });
  const result = {
    x: position.x - offset2.x,
    y: position.y - offset2.y
  };
  if (typeof position?.elevation === "number") {
    result.elevation = position.elevation;
  }
  return result;
}
function getOffsetFromData(data, { source: source2 = false, type = "source", twister = false } = {}) {
  if (!source2) {
    source2 = data.nameOffsetMap[data[type]] ? data.nameOffsetMap[data[type]][type] : validateObject(data[type], data.sceneId);
  }
  const multiplier = data.randomOffset?.[type];
  twister = twister || createMersenneTwister(data.seed);
  let offset2 = { x: 0, y: 0 };
  if (source2 && multiplier) {
    let randomOffset = get_random_offset(
      source2,
      multiplier,
      twister
    );
    offset2.x += randomOffset.x;
    offset2.y += randomOffset.y;
  }
  let extraOffset = data.offset;
  if (extraOffset) {
    let newOffset = {
      x: extraOffset.x,
      y: extraOffset.y
    };
    if (extraOffset.gridUnits) {
      newOffset.x *= canvas.grid.size;
      newOffset.y *= canvas.grid.size;
    }
    if (extraOffset.local) {
      newOffset = rotateAroundPoint(
        0,
        0,
        newOffset.x,
        newOffset.y,
        source2?.rotation ?? 0
      );
    }
    offset2.x += newOffset.x;
    offset2.y += newOffset.y;
  }
  return offset2;
}
function is_position_within_bounds(inPosition, inElement, relativeTo) {
  const localPosition = inElement.toLocal(inPosition, relativeTo);
  return inElement.getLocalBounds().contains(localPosition.x, localPosition.y);
}
function rotate_coordinate(p1, p2, radians) {
  let cos = Math.cos(radians);
  let sin = Math.sin(radians);
  let nx = cos * (p2.x - p1.x) + sin * (p2.y - p1.y) + p1.x;
  let ny = cos * (p2.y - p1.y) - sin * (p2.x - p1.x) + p1.y;
  return [nx, ny];
}
function get_closest_token(inPosition, { minimumDistance = false } = {}) {
  let tokens = Array.from(canvas.scene.tokens);
  if (minimumDistance) {
    tokens = tokens.filter(
      (token) => distance_between(get_object_position(token), inPosition) <= minimumDistance
    );
  }
  tokens.sort((a, b) => {
    return distance_between(get_object_position(a), inPosition) - distance_between(get_object_position(b), inPosition);
  });
  return tokens?.[0] ?? false;
}
function rotateAroundPoint(cx, cy, x, y, radians) {
  const cos = Math.cos(radians);
  const sin = Math.sin(radians);
  const nx = cos * (x - cx) + sin * (y - cy) + cx;
  const ny = cos * (y - cy) - sin * (x - cx) + cy;
  return { x: nx, y: ny };
}
function validateAnimation(inTarget, inPropertyName, inOptions) {
  if (typeof inPropertyName !== "string") {
    return `inPropertyName must be of type string`;
  }
  if (typeof inTarget !== "string") {
    return `inTarget must be of type string`;
  }
  if (!is_real_number(inOptions.from)) {
    return `inOptions.from must be of type number`;
  }
  if (!is_real_number(inOptions.to)) {
    return `inOptions.to must be of type number`;
  }
  if (!is_real_number(inOptions.duration)) {
    return `inOptions.duration must be of type number`;
  }
  if (inOptions?.delay !== void 0 && !is_real_number(inOptions.delay)) {
    return `inOptions.delay must be of type number`;
  }
  if (inOptions?.ease !== void 0 && typeof inOptions.ease !== "string") {
    return `inOptions.ease must be of type string`;
  }
  if (inOptions?.fromEnd !== void 0 && typeof inOptions.fromEnd !== "boolean") {
    return `inOptions.fromEnd must be of type boolean`;
  }
  if (inOptions?.screenSpace !== void 0 && typeof inOptions.screenSpace !== "boolean") {
    return `inOptions.screenSpace must be of type boolean`;
  }
  if (inOptions?.gridUnits !== void 0) {
    if (typeof inOptions.gridUnits !== "boolean") {
      return `inOptions.gridUnits must be of type boolean`;
    }
    if (inOptions.gridUnits && ![
      "position.x",
      "position.y",
      "scale.x",
      "scale.y",
      "height",
      "width"
    ].includes(inPropertyName)) {
      return `if inOptions.gridUnits is true, inPropertyName must be position.x, position.y, scale.x, scale.y, width, or height`;
    }
  }
  if (inOptions?.absolute !== void 0 && typeof inOptions.absolute !== "boolean") {
    return `inOptions.absolute must be of type boolean`;
  }
  return {
    target: inTarget,
    propertyName: inPropertyName,
    from: inOptions?.from,
    to: inOptions?.to,
    duration: inOptions?.duration ?? 0,
    delay: inOptions?.delay ?? 0,
    ease: inOptions?.ease ?? "linear",
    looping: false,
    fromEnd: inOptions?.fromEnd ?? false,
    gridUnits: inOptions?.gridUnits ?? false,
    absolute: inOptions?.absolute ?? false,
    screenSpace: inOptions?.screenSpace ?? false
  };
}
function validateLoopingAnimation(inTarget, inPropertyName, inOptions) {
  if (typeof inPropertyName !== "string") {
    return `inPropertyName must be of type string`;
  }
  if (typeof inTarget !== "string") {
    return `inTarget must be of type string`;
  }
  if (!inOptions?.values) {
    if (!inOptions?.from === void 0 || !inOptions?.to === void 0) {
      return `if inOptions.values is not set, you must provide inOptions.from and inOptions.to`;
    }
    if (!is_real_number(inOptions.from)) {
      return `inOptions.from must be of type number`;
    }
    if (!is_real_number(inOptions.to)) {
      return `inOptions.to must be of type number`;
    }
    inOptions.values = [inOptions?.from, inOptions?.to];
    delete inOptions.from;
    delete inOptions.to;
  } else {
    if (!Array.isArray(inOptions.values)) {
      return `inOptions.values must be of type array`;
    }
    inOptions.values.forEach((value) => {
      if (!is_real_number(value)) {
        return `values in inOptions.keys must be of type number`;
      }
    });
  }
  if (!is_real_number(inOptions.duration)) {
    return `inOptions.duration must be of type number`;
  }
  if (inOptions?.delay !== void 0 && !is_real_number(inOptions.delay)) {
    return `inOptions.delay must be of type number`;
  }
  if (inOptions?.ease !== void 0 && typeof inOptions.ease !== "string") {
    return `inOptions.ease must be of type string`;
  }
  if (inOptions?.loops !== void 0 && !is_real_number(inOptions.loops)) {
    return `inOptions.loops must be of type number`;
  }
  if (inOptions?.pingPong !== void 0 && typeof inOptions.pingPong !== "boolean") {
    return `inOptions.pingPong must be of type boolean`;
  }
  if (inOptions?.screenSpace !== void 0 && typeof inOptions.screenSpace !== "boolean") {
    return `inOptions.screenSpace must be of type boolean`;
  }
  if (inOptions?.gridUnits !== void 0) {
    if (typeof inOptions.gridUnits !== "boolean") {
      return `inOptions.gridUnits must be of type boolean`;
    }
    if (inOptions.gridUnits && ![
      "position.x",
      "position.y",
      "scale.x",
      "scale.y",
      "height",
      "width"
    ].includes(inPropertyName)) {
      return `if inOptions.gridUnits is true, inPropertyName must be position.x, position.y, scale.x, scale.y, width, or height`;
    }
  }
  return {
    target: inTarget,
    propertyName: inPropertyName,
    values: inOptions?.values,
    duration: inOptions?.duration ?? 0,
    delay: inOptions?.delay ?? 0,
    ease: inOptions?.ease ?? "linear",
    looping: true,
    loops: inOptions?.loops,
    indefinite: inOptions?.loops === void 0 || !is_real_number(inOptions?.loops),
    pingPong: inOptions?.pingPong ?? false,
    gridUnits: inOptions?.gridUnits ?? false,
    screenSpace: inOptions?.screenSpace ?? false
  };
}
const PlayerSettings = {
  file: {
    label: "SEQUENCER.Player.Option.File",
    store: writable(""),
    default: ""
  },
  scale: {
    label: "SEQUENCER.Player.Option.Scale",
    store: writable(1),
    default: 1
  },
  users: {
    label: "SEQUENCER.Player.Option.ForUsers",
    store: writable([]),
    default: []
  },
  belowTokens: {
    label: "SEQUENCER.Player.Option.BelowTokens",
    store: writable(false),
    default: false
  },
  snapToGrid: {
    label: "SEQUENCER.Player.Option.SnapToGrid",
    store: writable(false),
    default: false
  },
  rotation: {
    label: "SEQUENCER.Player.Option.Rotation",
    store: writable(0),
    default: 0
  },
  randomRotation: {
    label: "SEQUENCER.Player.Option.Randomize",
    store: writable(false),
    default: false
  },
  fadeIn: {
    label: "SEQUENCER.Player.Option.FadeIn",
    store: writable(0),
    default: 0
  },
  fadeOut: {
    label: "SEQUENCER.Player.Option.FadeOut",
    store: writable(0),
    default: 0
  },
  scaleIn: {
    label: "SEQUENCER.Player.Option.ScaleIn",
    store: writable(0),
    default: 0
  },
  scaleOut: {
    label: "SEQUENCER.Player.Option.ScaleOut",
    store: writable(0),
    default: 0
  },
  mirrorX: {
    label: "SEQUENCER.Player.Option.MirrorX",
    store: writable(false),
    default: false
  },
  mirrorY: {
    label: "SEQUENCER.Player.Option.MirrorY",
    store: writable(false),
    default: false
  },
  randomMirrorX: {
    label: "SEQUENCER.Player.Option.Randomize",
    store: writable(false),
    default: false
  },
  randomMirrorY: {
    label: "SEQUENCER.Player.Option.Randomize",
    store: writable(false),
    default: false
  },
  offsetX: {
    label: "SEQUENCER.Player.Option.OffsetX",
    store: writable(0),
    default: 0
  },
  offsetY: {
    label: "SEQUENCER.Player.Option.OffsetY",
    store: writable(0),
    default: 0
  },
  offsetGridUnits: {
    label: "SEQUENCER.Player.Option.GridUnits",
    store: writable(false),
    default: false
  },
  randomOffsetAmount: {
    label: "SEQUENCER.Player.Option.RandomOffset",
    store: writable(0),
    default: 0
  },
  randomOffset: {
    label: "SEQUENCER.Player.Option.Randomize",
    store: writable(false),
    default: false
  },
  preload: {
    label: "SEQUENCER.Player.Option.Preload",
    store: writable(false),
    default: false
  },
  moveTowards: {
    label: "SEQUENCER.Player.Option.DragBehavior",
    label_off: "SEQUENCER.Player.Option.DragStretch",
    label_on: "SEQUENCER.Player.Option.DragMove",
    store: writable(false),
    default: false
  },
  moveSpeed: {
    label: "SEQUENCER.Player.Option.MoveSpeed",
    store: writable(0),
    default: 0
  },
  attachTo: {
    label: "SEQUENCER.Player.Option.AttachTo",
    store: writable(false),
    default: false,
    callback: (e) => {
      EffectPlayer.sourceAttach = e.target.checked;
    }
  },
  stretchToAttach: {
    label: "SEQUENCER.Player.Option.StretchToAttach",
    store: writable(false),
    default: false,
    callback: (e) => {
      EffectPlayer.targetAttach = e.target.checked;
    }
  },
  persist: {
    label: "SEQUENCER.Player.Option.Persist",
    store: writable(false),
    default: false
  },
  repeat: {
    label: "SEQUENCER.Player.Option.Repeat",
    store: writable(false),
    default: false
  },
  repetitions: {
    label: "SEQUENCER.Player.Option.Repetitions",
    store: writable(1),
    default: 1
  },
  repeatDelayMin: {
    label: "SEQUENCER.Player.Option.DelayMin",
    store: writable(200),
    default: 200
  },
  repeatDelayMax: {
    label: "SEQUENCER.Player.Option.DelayMax",
    store: writable(400),
    default: 400
  }
};
PlayerSettings.export = () => {
  return Object.fromEntries(
    Object.entries(PlayerSettings).map((entry) => {
      return [entry[0], get$1(entry[1].store)];
    })
  );
};
PlayerSettings.import = (settings) => {
  Object.entries(PlayerSettings).forEach((entry) => {
    if (settings?.[entry[0]] !== void 0) {
      entry[1].store.set(settings?.[entry[0]]);
    } else if (entry[1]?.default !== void 0) {
      entry[1].store.set(entry[1].default);
    }
  });
};
PlayerSettings.getPresets = () => {
  return Object.keys(game.settings.get(CONSTANTS.MODULE_NAME, "effectPresets"));
};
PlayerSettings.loadPreset = (name2) => {
  const effectPresets = game.settings.get(
    CONSTANTS.MODULE_NAME,
    "effectPresets"
  );
  return PlayerSettings.import(effectPresets[name2]);
};
PlayerSettings.savePreset = async (name2) => {
  const newName = await promptNewPresetName(name2);
  if (!newName) return;
  const effectPresets = game.settings.get(
    CONSTANTS.MODULE_NAME,
    "effectPresets"
  );
  effectPresets[newName] = PlayerSettings.export();
  return game.settings.set(
    CONSTANTS.MODULE_NAME,
    "effectPresets",
    effectPresets
  );
};
PlayerSettings.copyPreset = async (name2) => {
  const newName = await promptNewPresetName(name2, true);
  if (!newName) return;
  const effectPresets = game.settings.get(
    CONSTANTS.MODULE_NAME,
    "effectPresets"
  );
  effectPresets[newName] = PlayerSettings.export();
  return game.settings.set(
    CONSTANTS.MODULE_NAME,
    "effectPresets",
    effectPresets
  );
};
PlayerSettings.deletePreset = (name2) => {
  const effectPresets = game.settings.get(
    CONSTANTS.MODULE_NAME,
    "effectPresets"
  );
  delete effectPresets[name2];
  return game.settings.set(
    CONSTANTS.MODULE_NAME,
    "effectPresets",
    effectPresets
  );
};
async function promptNewPresetName(inName, copy = false) {
  const effectPresets = game.settings.get(
    CONSTANTS.MODULE_NAME,
    "effectPresets"
  );
  let title = copy ? game.i18n.localize("SEQUENCER.Player.CopyPresetTitle") : game.i18n.localize("SEQUENCER.Player.CreateNewPresetTitle");
  const placeholder = game.i18n.localize(
    "SEQUENCER.Player.CreateNewPresetInputLabel"
  );
  const result = await foundry.applications.api.DialogV2.input({
    window: { title },
    content: `<p><input type="text" name="presetName" placeholder="${placeholder}" value="${foundry.utils.escapeHTML(inName ?? "")}" style="width:100%;" autofocus></p>`,
    ok: {
      icon: "fas fa-check",
      label: game.i18n.localize("SEQUENCER.OK")
    },
    rejectClose: false
  });
  let presetName = result?.presetName || false;
  if (presetName) {
    if (presetName.toLowerCase() === "default") {
      foundry.applications.api.DialogV2.prompt({
        window: {
          title: game.i18n.localize("SEQUENCER.Player.DefaultErrorTitle")
        },
        content: `<p>${game.i18n.localize(
          "SEQUENCER.Player.DefaultErrorContent"
        )}</p>`,
        ok: { label: game.i18n.localize("SEQUENCER.OK") }
      });
      return false;
    }
    if (effectPresets[presetName]) {
      const overwrite = await foundry.applications.api.DialogV2.confirm({
        window: {
          title: game.i18n.localize("SEQUENCER.Player.OverwritePresetTitle")
        },
        content: `<p>${game.i18n.format(
          "SEQUENCER.Player.OverwritePresetContent",
          { preset_name: presetName }
        )}</p>`
      });
      if (!overwrite) {
        presetName = await promptNewPresetName(presetName);
      }
    }
  }
  return presetName;
}
PlayerSettings.migrateOldPresets = () => {
  if (!game.user.isGM) return;
  const effectPresets = game.settings.get(
    CONSTANTS.MODULE_NAME,
    "effectPresets"
  );
  const presetsToUpdate = Object.values(effectPresets).filter((preset) => {
    return !preset?.version;
  });
  if (!presetsToUpdate.length) return;
  const newEffectPresets = Object.fromEntries(
    Object.entries(effectPresets).map(([name2, preset]) => {
      if (preset?.version) return [name2, preset];
      preset.version = game.modules.get(CONSTANTS.MODULE_NAME).version;
      if (preset.repetitions > 1) {
        preset.repeat = true;
      }
      if (preset.randomMirrorY) {
        preset.mirrorY = true;
      }
      if (preset.randomOffset) {
        preset.randomOffsetAmount = 1;
        preset.offsetGridUnits = true;
      }
      return [name2, preset];
    })
  );
  return game.settings.set(
    CONSTANTS.MODULE_NAME,
    "effectPresets",
    newEffectPresets
  );
};
const InteractionManager = {
  startDragPosition: false,
  state: {
    LeftMouseDown: false,
    RightMouseDown: false,
    Dragging: false
  },
  get isLayerActive() {
    return canvas.sequencerInterfaceLayer.active;
  },
  initialize() {
    window.addEventListener("mousedown", (event2) => {
      if (!canvas.ready) return;
      if (!this.isLayerActive) return;
      const hover = document.elementFromPoint(event2.clientX, event2.clientY);
      if (!hover || hover.id !== "board") return;
      const button = event2.button;
      if (!(button === 0 || button === 2)) return;
      if (button === 0) {
        this.state.LeftMouseDown = true;
        this._propagateEvent("mouseLeftDown");
      }
      if (button === 2) {
        this.state.RightMouseDown = true;
        this._propagateEvent("mouseRightDown");
      }
    });
    window.addEventListener("mouseup", (event2) => {
      if (!canvas.ready) return;
      if (!this.isLayerActive) return;
      const hover = document.elementFromPoint(event2.clientX, event2.clientY);
      if (!hover || hover.id !== "board") return;
      if (document.activeElement.tagName !== "BODY") return;
      const button = event2.button;
      if (!(button === 0 || button === 2)) return;
      if (button === 0) {
        this.state.LeftMouseDown = false;
        this._propagateEvent("mouseLeftUp");
        this.state.Dragging = false;
        this.startDragPosition = false;
      }
      if (button === 2) {
        this.state.RightMouseDown = false;
        this._propagateEvent("mouseRightUp");
        this.state.Dragging = false;
        this.startDragPosition = false;
      }
    });
    window.addEventListener("mousemove", (event2) => {
      if (!canvas.ready) return;
      if (!this.isLayerActive) return;
      const hover = document.elementFromPoint(event2.clientX, event2.clientY);
      if (!hover || hover.id !== "board") return;
      this._propagateEvent("mouseMove");
      if (this.state.LeftMouseDown && !this.startDragPosition) {
        this.startDragPosition = get_mouse_position();
      }
      if (this.state.LeftMouseDown && !this.state.Dragging) {
        const distance = distance_between(
          this.startDragPosition,
          get_mouse_position()
        );
        this.state.Dragging = distance > 20;
      }
    });
    EffectPlayer.initialize();
    SelectionManager.initialize();
  },
  tearDown() {
    EffectPlayer.tearDown();
    SelectionManager.tearDown();
  },
  _propagateEvent(eventName) {
    if (EffectPlayer.isActive && EffectPlayer[eventName]) {
      EffectPlayer[eventName]();
    }
    if (SelectionManager.isActive && SelectionManager[eventName]) {
      SelectionManager[eventName]();
    }
  }
};
const EffectPlayer = {
  sequenceBuffer: [],
  playMany: false,
  playManySequenced: false,
  cursorPos: false,
  startPos: false,
  endPos: false,
  get snapLocationToGrid() {
    return get$1(PlayerSettings.snapToGrid.store);
  },
  get sourceAttach() {
    return get$1(PlayerSettings.attachTo.store);
  },
  get targetAttach() {
    return get$1(PlayerSettings.stretchToAttach.store);
  },
  sourceAttachFound: false,
  targetAttachFound: false,
  get isActive() {
    return InteractionManager.isLayerActive && game?.activeTool === CONSTANTS.TOOLS.PLAY;
  },
  /**
   * Opens the Sequencer Effects UI with the player tab open
   */
  show() {
    return EffectsUIApp.show({ tab: "player" });
  },
  initialize() {
    this.layer = canvas.sequencerInterfaceLayer;
  },
  tearDown() {
    this._reset();
  },
  /**
   * Mouse events
   */
  mouseLeftDown() {
    this._evaluateStartPosition();
  },
  mouseLeftUp() {
    if (!this.startPos) return;
    this._playEffect();
    this.startPos = false;
    this.endPos = false;
    this.sourceAttachFound = false;
    this.targetAttachFound = false;
  },
  mouseRightUp() {
    this._reset();
  },
  mouseMove() {
    this._evaluateCursorPosition();
    if (InteractionManager.state.Dragging) {
      this._evaluateEndPosition();
    }
  },
  /**
   * Hotkeys
   */
  playManyUp() {
    this._playEffects();
    this._reset();
  },
  /**
   * Private methods
   */
  _evaluatePosition(attach = false) {
    let position = get_mouse_position(this.snapLocationToGrid);
    const attachToObject = attach ? get_closest_token(position, {
      minimumDistance: canvas.grid.size
    }) : false;
    let attachFound = false;
    if (attachToObject) {
      attachFound = true;
      position = get_object_position(attachToObject);
    }
    return [position, attachFound];
  },
  _evaluateCursorPosition() {
    const attach = InteractionManager.state.Dragging ? this.targetAttach : this.sourceAttach;
    [this.cursorPos] = this._evaluatePosition(attach);
  },
  _evaluateStartPosition() {
    if (this.startPos) return;
    [this.startPos, this.sourceAttachFound] = this._evaluatePosition(
      this.sourceAttach
    );
  },
  _evaluateEndPosition() {
    [this.endPos, this.targetAttachFound] = this._evaluatePosition(
      this.targetAttach
    );
  },
  _reset() {
    if (!this.layer) return;
    this.startPos = false;
    this.endPos = false;
    this.sourceAttachFound = false;
    this.targetAttachFound = false;
    this.sequenceBuffer = [];
    this._evaluateCursorPosition();
  },
  async _playEffect() {
    const settings = foundry.utils.mergeObject(PlayerSettings.export(), {
      ...InteractionManager.state,
      startPos: this.startPos,
      endPos: this.endPos
    });
    if (!settings.users.length || settings.users?.[0] === "all")
      settings.users = [];
    if (settings.file === "") return;
    if (!(Sequencer.Database.entryExists(settings.file) || await SequencerFileCache.srcExists(settings.file))) {
      throw custom_error(
        "Sequencer",
        `Sequencer Player | Could not find file or database entry: ${settings.file}`
      );
    }
    if (settings.preload) {
      await Sequencer.Preloader.preloadForClients(settings.file);
    }
    const sequence = this.sequenceBuffer.length > 0 && this.playManySequenced ? this.sequenceBuffer[this.sequenceBuffer.length - 1] : new Sequence();
    const effect2 = sequence.effect().file(settings.file).forUsers(settings.users).mirrorX(settings.mirrorX && !settings.randomMirrorX).mirrorY(settings.mirrorY && !settings.randomMirrorY).randomizeMirrorX(settings.randomMirrorX).randomizeMirrorY(settings.randomMirrorY).persist(settings.persist).belowTokens(settings.belowTokens);
    if (settings.repeat) {
      effect2.repeats(
        settings.repetitions,
        settings.repeatDelayMin,
        settings.repeatDelayMax
      );
    }
    if (settings.fadeIn > 0) effect2.fadeIn(settings.fadeIn);
    if (settings.fadeOut > 0) effect2.fadeOut(settings.fadeOut);
    const offsetData = settings.randomOffset ? {
      offset: { x: settings.offsetX, y: settings.offsetY },
      gridUnits: settings.offsetGridUnits
    } : {};
    const offsetSource = !settings.Dragging ? offsetData : {};
    const attachToObject = settings.attachTo ? get_closest_token(settings.startPos, {
      minimumDistance: canvas.grid.size
    }) : false;
    if (attachToObject) {
      effect2.attachTo(attachToObject, {
        randomOffset: settings.randomOffset && !settings.Dragging ? settings.randomOffsetAmount : false,
        ...offsetSource
      });
    } else {
      effect2.atLocation(settings.startPos, {
        randomOffset: settings.randomOffset && !settings.Dragging ? settings.randomOffsetAmount : false,
        ...offsetSource
      });
    }
    if (settings.persist && settings.name && settings.name !== "" && settings.name !== "default" && settings.name !== "new") {
      effect2.name("Preset: " + settings.name);
    }
    if (settings.Dragging) {
      if (settings.moveTowards) {
        effect2.moveTowards(settings.endPos, {
          randomOffset: settings.randomOffset ? settings.randomOffsetAmount : false,
          ...offsetData
        });
        if (settings.moveSpeed) {
          effect2.moveSpeed(settings.moveSpeed);
        }
      } else {
        let target = settings.stretchToAttach ? get_closest_token(settings.endPos, {
          minimumDistance: canvas.grid.size
        }) : settings.endPos;
        effect2.stretchTo(target, {
          attachTo: settings.stretchToAttach,
          randomOffset: settings.randomOffset ? settings.randomOffsetAmount : false,
          ...offsetData
        });
      }
    }
    if (!settings.Dragging || settings.Dragging && settings.moveTowards) {
      effect2.scale(settings.scale);
      if (settings.scaleIn > 0)
        effect2.scaleIn(0, settings.scaleIn, { ease: "easeInOutSine" });
      if (settings.scaleOut > 0)
        effect2.scaleOut(0, settings.scaleOut, { ease: "easeInOutSine" });
      effect2.randomRotation(settings.randomRotation);
    }
    if (this.playManySequenced) {
      effect2.waitUntilFinished();
    }
    if (!this.playManySequenced || this.sequenceBuffer.length === 0) {
      this.sequenceBuffer.push(sequence);
    }
    if (!this.playMany && !this.playManySequenced) this._playEffects();
  },
  _playEffects() {
    this.sequenceBuffer.forEach((sequence) => sequence.play());
    this.sequenceBuffer = [];
  }
};
const SelectionManager = {
  selectedEffect: false,
  hoveredEffects: /* @__PURE__ */ new Set(),
  suggestedProperties: false,
  sourceOrTarget: false,
  dragOffset: false,
  hoveredEffectUI: false,
  hoveredSoundUI: false,
  get snapToGrid() {
    return get$1(PlayerSettings.snapToGrid.store);
  },
  set snapToGrid(bool) {
    PlayerSettings.snapToGrid.store.set(bool);
  },
  set attachToTarget(bool) {
    PlayerSettings.stretchToAttach.store.set(bool);
  },
  get attachToTarget() {
    return get$1(PlayerSettings.stretchToAttach.store);
  },
  get isActive() {
    return InteractionManager.isLayerActive && game?.activeTool === CONSTANTS.TOOLS.SELECT;
  },
  get effects() {
    return SequencerEffectManager.effects.filter(
      (effect2) => effect2.userCanDelete
    );
  },
  initialize() {
    this.layer = canvas.sequencerInterfaceLayer;
  },
  tearDown() {
    this._reset();
    this.hoveredEffects = /* @__PURE__ */ new Set();
  },
  sourcePointSelected() {
    this.sourceOrTarget = "source";
  },
  targetPointSelected() {
    this.sourceOrTarget = "target";
  },
  /**
   * Mouse Events
   */
  mouseLeftDown() {
    if (!this.selectedEffect) {
      return this._selectEffects();
    }
    if (!this.hoveredEffects.size) {
      this._reset();
    }
  },
  mouseRightDown() {
  },
  mouseLeftUp() {
    if (!InteractionManager.state.Dragging) {
      return this._selectEffects();
    }
    if (!InteractionManager.state.Dragging || !this.selectedEffect || !this.suggestedProperties)
      return;
    this._updateEffect();
  },
  mouseRightUp() {
    InteractionManager.state.LeftMouseDown = false;
    this.suggestedProperties = false;
    this.sourceOrTarget = false;
    this.dragOffset = false;
  },
  mouseMove() {
    this._evaluateHoveredEffects();
    if (InteractionManager.state.LeftMouseDown && !InteractionManager.state.RightMouseDown) {
      this._evaluateEffectPositionUpdate();
    }
  },
  /**
   * Hotkeys
   */
  async delete() {
    if (!this.selectedEffect) return;
    await SequencerEffectManager.endEffects({ effects: this.selectedEffect });
    this.selectedEffect = false;
  },
  attachToTargetDown() {
    if (InteractionManager.state.LeftMouseDown && !InteractionManager.state.RightMouseDown) {
      this._evaluateEffectPositionUpdate();
    }
  },
  /**
   * Private methods
   */
  _selectEffects() {
    this._reset();
    if (!this.hoveredEffects.size) return;
    const firstElement = Array.from(this.hoveredEffects)[0];
    this.selectedEffect = !firstElement.selected ? firstElement : false;
  },
  _evaluateHoveredEffects() {
    const position = get_mouse_position();
    this.hoveredEffects = this.effects.filter(
      (effect2) => effect2.isPositionWithinBounds(position)
    );
    this.hoveredEffects.sort((a, b) => {
      const layerDiff = (b.data.sortLayer ?? 800) - (a.data.sortLayer ?? 800);
      if (layerDiff !== 0) return layerDiff;
      return (b.data.zIndex ?? 0) - (a.data.zIndex ?? 0);
    });
    this.hoveredEffects = new Set(this.hoveredEffects);
  },
  _evaluateEffectPositionUpdate() {
    if (!this.selectedEffect) return;
    if (this.selectedEffect.data.stretchTo && !this.sourceOrTarget) {
      return;
    }
    let showCursor = false;
    let showPoint = this.snapToGrid;
    let position = get_mouse_position(this.snapToGrid);
    if (!this.selectedEffect.data.stretchTo && !this.dragOffset) {
      this.dragOffset = {
        x: position.x - this.selectedEffect.position.x,
        y: position.y - this.selectedEffect.position.y
      };
    }
    if (this.attachToTarget) {
      const obj = get_closest_token(position, {
        minimumDistance: canvas.grid.size
      });
      if (obj) {
        position = get_object_position(obj);
        showCursor = true;
        showPoint = false;
      }
    }
    if (this.dragOffset && !showCursor && !this.snapToGrid) {
      position.x -= this.dragOffset.x;
      position.y -= this.dragOffset.y;
    }
    const color = (this.sourceOrTarget || "source") === "source" ? CONSTANTS.COLOR.PRIMARY : CONSTANTS.COLOR.SECONDARY;
    this.suggestedProperties = {
      position,
      showCursor,
      showPoint,
      color
    };
  },
  _updateEffect() {
    if (!this.selectedEffect) return;
    const updates = {
      attachTo: this.selectedEffect.data.attachTo,
      stretchTo: this.selectedEffect.data.stretchTo
    };
    const obj = this.attachToTarget ? get_closest_token(this.suggestedProperties.position, {
      minimumDistance: canvas.grid.size,
      type: TokenDocument
    }) : false;
    let objUuid = obj ? get_object_identifier(obj) : false;
    if (this.sourceOrTarget === "source") {
      if (!updates.attachTo) {
        updates.attachTo = {
          active: false,
          align: "center",
          rotation: true,
          bindVisibility: true,
          bindAlpha: true
        };
      }
      updates.attachTo = foundry.utils.mergeObject(updates.attachTo || {}, {
        active: this.attachToTarget && !!objUuid
      });
      if (this.attachToTarget && objUuid) {
        updates.source = objUuid;
      } else {
        updates["source"] = this.suggestedProperties.position;
      }
    } else if (this.sourceOrTarget === "target") {
      updates.stretchTo.attachTo = this.attachToTarget && !!objUuid;
      if (this.attachToTarget && objUuid) {
        updates.target = objUuid;
      } else {
        updates["target"] = this.suggestedProperties.position;
      }
    } else {
      updates["source"] = this.suggestedProperties.position;
    }
    this.selectedEffect.update(updates);
    this.suggestedProperties = false;
    this.sourceOrTarget = false;
    this.dragOffset = false;
  },
  _reset() {
    this.selectedEffect = false;
    this.suggestedProperties = false;
    this.sourceOrTarget = false;
    this.dragOffset = false;
  }
};
var $$_import_SelectionManager$1 = reactive_import(() => SelectionManager);
var root$e = /* @__PURE__ */ from_html(`<div class="effect hover-highlight svelte-sequencer-1fyjvdk"><button class="btn_end svelte-sequencer-1fyjvdk" type="button"><i class="fas fa-times"></i></button> <div class="effect-text hover-text svelte-sequencer-1fyjvdk"> </div></div>`);
function EffectEntry($$anchor, $$props) {
  push($$props, false);
  let localize = game.i18n.localize.bind(game.i18n);
  let effect2 = prop($$props, "effect", 8);
  function getEffectName(effect3) {
    let effectName = "";
    if (effect3.data.file && typeof effect3.data.file === "string") {
      effectName = effect3.data.file.split("\\").pop().split("/").pop();
    } else if (effect3.data.file && typeof effect3.data.file === "object") {
      effectName = Object.values(effect3.data.file)[0].split("\\").pop().split("/").pop();
    } else if (effect3.data.text) {
      effectName = "Text: " + effect3.data.text.text;
    } else {
      effectName = effect3.data?.shapes?.[0]?.type ? "Shape: " + effect3.data.shapes[0]?.type : "Unknown effect";
    }
    effectName = effect3.data.name ? `${effect3.data.name} (${effectName})` : effectName;
    if (effect3.data.creatorUserId !== game.userId) {
      let user_name = game.users.get(effect3.data.creatorUserId)?.name;
      let formattedUsername = user_name ? localize("SEQUENCER.ManagerPlayersEffect", { user_name }) : localize("SEQUENCER.ManagerUnknownEffect");
      effectName += ` (${formattedUsername})`;
    }
    return effectName;
  }
  function mouseOver() {
    $$_import_SelectionManager$1($$_import_SelectionManager$1().hoveredEffectUI = effect2());
  }
  function mouseLeave() {
    $$_import_SelectionManager$1($$_import_SelectionManager$1().hoveredEffectUI = false);
  }
  function endEffect() {
    SequencerEffectManager.endEffects({ effects: [effect2()] });
  }
  init();
  var div = root$e();
  var button = child(div);
  var div_1 = sibling(button, 2);
  var text2 = child(div_1);
  template_effect(($0) => set_text(text2, $0), [
    () => (deep_read_state(effect2()), untrack(() => getEffectName(effect2())))
  ]);
  event("click", button, endEffect);
  event("mouseleave", div, mouseLeave);
  event("mouseover", div, mouseOver);
  append($$anchor, div);
  pop();
}
var $$_import_SelectionManager = reactive_import(() => SelectionManager);
var root$d = /* @__PURE__ */ from_html(`<div class="sound hover-highlight svelte-sequencer-1tnr10c"><button class="btn_end svelte-sequencer-1tnr10c" type="button"><i class="fas fa-times"></i></button> <div class="sound-text hover-text svelte-sequencer-1tnr10c"> </div></div>`);
function SoundEntry($$anchor, $$props) {
  push($$props, false);
  let localize = game.i18n.localize.bind(game.i18n);
  let sound = prop($$props, "sound", 8);
  function getSoundName(sound2) {
    let soundName = "";
    if (sound2.data.file && typeof sound2.data.file === "string") {
      soundName = sound2.data.file.split("\\").pop().split("/").pop();
    } else if (sound2.data.file && typeof sound2.data.file === "object") {
      soundName = Object.values(sound2.data.file)[0].split("\\").pop().split("/").pop();
    } else if (sound2.data.text) {
      soundName = "Text: " + sound2.data.text.text;
    } else {
      soundName = sound2.data?.shapes?.[0]?.type ? "Shape: " + sound2.data.shapes[0]?.type : "Unknown sound";
    }
    soundName = sound2.data.name ? `${sound2.data.name} (${soundName})` : soundName;
    if (sound2.data.creatorUserId !== game.userId) {
      let user_name = game.users.get(sound2.data.creatorUserId)?.name;
      let formattedUsername = user_name ? localize("SEQUENCER.ManagerPlayersSound", { user_name }) : localize("SEQUENCER.ManagerUnknownSound");
      soundName += ` (${formattedUsername})`;
    }
    return soundName;
  }
  function mouseOver() {
    $$_import_SelectionManager($$_import_SelectionManager().hoveredSoundUI = sound());
  }
  function mouseLeave() {
    $$_import_SelectionManager($$_import_SelectionManager().hoveredSoundUI = false);
  }
  function endSound() {
    $$_import_SelectionManager($$_import_SelectionManager().hoveredSoundUI = false);
    SequencerSoundManager.endSounds({ sounds: [sound().id] }, true);
  }
  init();
  var div = root$d();
  var button = child(div);
  var div_1 = sibling(button, 2);
  var text2 = child(div_1);
  template_effect(($0) => set_text(text2, $0), [
    () => (deep_read_state(sound()), untrack(() => getSoundName(sound())))
  ]);
  event("click", button, endSound);
  event("mouseleave", div, mouseLeave);
  event("mouseover", div, mouseOver);
  append($$anchor, div);
  pop();
}
var root_1$3 = /* @__PURE__ */ from_html(`<div class="no-effects"><h2> </h2></div>`);
var root_3$2 = /* @__PURE__ */ from_html(`<h2> </h2> <div></div>`, 1);
var root_5$1 = /* @__PURE__ */ from_html(`<hr/>`);
var root_6$1 = /* @__PURE__ */ from_html(`<h2> </h2> <div></div>`, 1);
var root_2$3 = /* @__PURE__ */ from_html(`<button class="w-100 end-all-effects mb-2 svelte-sequencer-1mx9u4v" type="button"> </button> <div class="effects svelte-sequencer-1mx9u4v"><!> <!> <!></div>`, 1);
var root_9 = /* @__PURE__ */ from_html(`<h2> </h2> <div></div>`, 1);
var root_11 = /* @__PURE__ */ from_html(`<hr/>`);
var root_12 = /* @__PURE__ */ from_html(`<h2> </h2> <div></div>`, 1);
var root_8 = /* @__PURE__ */ from_html(`<button class="w-100 end-all-effects mb-2 svelte-sequencer-1mx9u4v" type="button"> </button> <div><!> <!> <!></div>`, 1);
var root$c = /* @__PURE__ */ from_html(`<div class="effects-container svelte-sequencer-1mx9u4v"><!> <!> <!></div>`);
function Manager($$anchor, $$props) {
  push($$props, false);
  const $VisibleEffects = () => store_get(VisibleEffects, "$VisibleEffects", $$stores);
  const $RunningSounds = () => store_get(RunningSounds, "$RunningSounds", $$stores);
  const [$$stores, $$cleanup] = setup_stores();
  const effects = /* @__PURE__ */ mutable_source();
  const persistentEffects = /* @__PURE__ */ mutable_source();
  const temporaryEffects = /* @__PURE__ */ mutable_source();
  const persistentSounds = /* @__PURE__ */ mutable_source();
  const temporarySounds = /* @__PURE__ */ mutable_source();
  const VisibleEffects = SequenceManager.VisibleEffects;
  const RunningSounds = SequenceManager.RunningSounds;
  let localize = game.i18n.localize.bind(game.i18n);
  let sounds = /* @__PURE__ */ mutable_source([]);
  function endAllEffects() {
    Sequencer.EffectManager.endEffects({
      effects: get(effects).filter((effect2) => effect2.userCanDelete && !effect2.data.private)
    });
  }
  function endAllSounds() {
    SequencerSoundManager.endSounds({ sounds: Object.entries($RunningSounds()).map((e) => e[0]) });
  }
  legacy_pre_effect(() => $VisibleEffects(), () => {
    set(effects, Object.values($VisibleEffects()));
  });
  legacy_pre_effect(() => $RunningSounds(), () => {
    set(sounds, Object.values($RunningSounds()));
  });
  legacy_pre_effect(() => get(effects), () => {
    set(persistentEffects, get(effects).filter((effect2) => effect2.data.persist));
  });
  legacy_pre_effect(() => get(effects), () => {
    set(temporaryEffects, get(effects).filter((effect2) => !effect2.data.persist));
  });
  legacy_pre_effect(() => get(sounds), () => {
    set(persistentSounds, get(sounds).filter((effect2) => effect2.data.persist));
  });
  legacy_pre_effect(() => get(sounds), () => {
    set(temporarySounds, get(sounds).filter((effect2) => !effect2.data.persist));
  });
  legacy_pre_effect_reset();
  init();
  var div = root$c();
  var node = child(div);
  {
    var consequent = ($$anchor2) => {
      var div_1 = root_1$3();
      var h2 = child(div_1);
      var text2 = child(h2);
      template_effect(($0) => set_text(text2, $0), [
        () => untrack(() => localize("SEQUENCER.Manager.NothingPlaying"))
      ]);
      append($$anchor2, div_1);
    };
    if_block(node, ($$render) => {
      if (get(effects), get(sounds), untrack(() => !get(effects).length && !get(sounds).length)) $$render(consequent);
    });
  }
  var node_1 = sibling(node, 2);
  {
    var consequent_4 = ($$anchor2) => {
      var fragment = root_2$3();
      var button = first_child(fragment);
      var text_1 = child(button);
      var div_2 = sibling(button, 2);
      var node_2 = child(div_2);
      {
        var consequent_1 = ($$anchor3) => {
          var fragment_1 = root_3$2();
          var h2_1 = first_child(fragment_1);
          var text_2 = child(h2_1);
          var div_3 = sibling(h2_1, 2);
          each(div_3, 5, () => get(persistentEffects), (effect2) => effect2.id, ($$anchor4, effect2) => {
            EffectEntry($$anchor4, {
              get effect() {
                return get(effect2);
              }
            });
          });
          template_effect(($0) => set_text(text_2, $0), [
            () => untrack(() => localize("SEQUENCER.Manager.PersistentEffects"))
          ]);
          append($$anchor3, fragment_1);
        };
        if_block(node_2, ($$render) => {
          if (get(persistentEffects), untrack(() => get(persistentEffects).length)) $$render(consequent_1);
        });
      }
      var node_3 = sibling(node_2, 2);
      {
        var consequent_2 = ($$anchor3) => {
          var hr2 = root_5$1();
          append($$anchor3, hr2);
        };
        if_block(node_3, ($$render) => {
          if (get(temporaryEffects), get(persistentEffects), untrack(() => get(temporaryEffects).length && get(persistentEffects).length)) $$render(consequent_2);
        });
      }
      var node_4 = sibling(node_3, 2);
      {
        var consequent_3 = ($$anchor3) => {
          var fragment_3 = root_6$1();
          var h2_2 = first_child(fragment_3);
          var text_3 = child(h2_2);
          var div_4 = sibling(h2_2, 2);
          each(div_4, 5, () => get(temporaryEffects), (effect2) => effect2.id, ($$anchor4, effect2) => {
            EffectEntry($$anchor4, {
              get effect() {
                return get(effect2);
              }
            });
          });
          template_effect(($0) => set_text(text_3, $0), [
            () => untrack(() => localize("SEQUENCER.Manager.TemporaryEffects"))
          ]);
          append($$anchor3, fragment_3);
        };
        if_block(node_4, ($$render) => {
          if (get(temporaryEffects), untrack(() => get(temporaryEffects).length)) $$render(consequent_3);
        });
      }
      template_effect(($0) => set_text(text_1, $0), [
        () => untrack(() => localize("SEQUENCER.Manager.EndAllEffects"))
      ]);
      event("click", button, endAllEffects);
      append($$anchor2, fragment);
    };
    if_block(node_1, ($$render) => {
      if (get(effects), untrack(() => get(effects).length)) $$render(consequent_4);
    });
  }
  var node_5 = sibling(node_1, 2);
  {
    var consequent_8 = ($$anchor2) => {
      var fragment_5 = root_8();
      var button_1 = first_child(fragment_5);
      var text_4 = child(button_1);
      var div_5 = sibling(button_1, 2);
      var node_6 = child(div_5);
      {
        var consequent_5 = ($$anchor3) => {
          var fragment_6 = root_9();
          var h2_3 = first_child(fragment_6);
          var text_5 = child(h2_3);
          var div_6 = sibling(h2_3, 2);
          each(div_6, 5, () => get(persistentSounds), (sound) => sound.id, ($$anchor4, sound) => {
            SoundEntry($$anchor4, {
              get sound() {
                return get(sound);
              }
            });
          });
          template_effect(($0) => set_text(text_5, $0), [
            () => untrack(() => localize("SEQUENCER.Manager.PersistentSounds"))
          ]);
          append($$anchor3, fragment_6);
        };
        if_block(node_6, ($$render) => {
          if (get(persistentSounds), untrack(() => get(persistentSounds).length)) $$render(consequent_5);
        });
      }
      var node_7 = sibling(node_6, 2);
      {
        var consequent_6 = ($$anchor3) => {
          var hr_1 = root_11();
          append($$anchor3, hr_1);
        };
        if_block(node_7, ($$render) => {
          if (get(temporarySounds), get(persistentSounds), untrack(() => get(temporarySounds).length && get(persistentSounds).length)) $$render(consequent_6);
        });
      }
      var node_8 = sibling(node_7, 2);
      {
        var consequent_7 = ($$anchor3) => {
          var fragment_8 = root_12();
          var h2_4 = first_child(fragment_8);
          var text_6 = child(h2_4);
          var div_7 = sibling(h2_4, 2);
          each(div_7, 5, () => get(temporarySounds), (sound) => sound.id, ($$anchor4, sound) => {
            SoundEntry($$anchor4, {
              get sound() {
                return get(sound);
              }
            });
          });
          template_effect(($0) => set_text(text_6, $0), [
            () => untrack(() => localize("SEQUENCER.Manager.TemporarySounds"))
          ]);
          append($$anchor3, fragment_8);
        };
        if_block(node_8, ($$render) => {
          if (get(temporarySounds), untrack(() => get(temporarySounds).length)) $$render(consequent_7);
        });
      }
      template_effect(($0) => set_text(text_4, $0), [
        () => untrack(() => localize("SEQUENCER.Manager.EndAllSounds"))
      ]);
      event("click", button_1, endAllSounds);
      append($$anchor2, fragment_5);
    };
    if_block(node_5, ($$render) => {
      if (get(sounds), untrack(() => get(sounds).length)) $$render(consequent_8);
    });
  }
  append($$anchor, div);
  pop();
  $$cleanup();
}
function applyStyles(node, data) {
  user_effect(() => {
    let existingStyle = node.getAttribute("style") || "";
    let existingStyleObj = {};
    if (existingStyle) {
      existingStyleObj = Object.fromEntries(existingStyle.split(";").filter((style) => style.trim()).map((style) => {
        const [key, value] = style.split(":");
        return [key.trim(), value.trim()];
      }));
    }
    const mergedStyles = { ...existingStyleObj, ...data };
    let styles = Object.entries(mergedStyles).map(([key, value]) => `${key}: ${value};`).join("");
    node.setAttribute("style", styles);
    return () => {
      const currentStyle = node.getAttribute("style") || "";
      const currentStyleObj = Object.fromEntries(currentStyle.split(";").filter((style) => style.trim()).map((style) => {
        const [key, value] = style.split(":");
        return [key.trim(), value.trim()];
      }));
      Object.keys(data).forEach((key) => delete currentStyleObj[key]);
      const cleanedStyles = Object.entries(currentStyleObj).map(([key, value]) => `${key}: ${value};`).join("");
      node.setAttribute("style", cleanedStyles);
    };
  });
}
var root$b = /* @__PURE__ */ from_html(`<div style="display: flex; align-items: center;"><label class="svelte-sequencer-x3192w"> </label> <input type="range" class="svelte-sequencer-x3192w"/> <input required="" type="number" class="svelte-sequencer-x3192w"/></div>`);
function SliderInput($$anchor, $$props) {
  push($$props, false);
  const $store = () => store_get(store, "$store", $$stores);
  const $isLocked = () => store_get(isLocked, "$isLocked", $$stores);
  const [$$stores, $$cleanup] = setup_stores();
  let localize = game.i18n.localize.bind(game.i18n);
  let setting = prop($$props, "setting", 8);
  let lock = prop($$props, "lock", 8, false);
  let min = prop($$props, "min", 8, 0.1);
  let max = prop($$props, "max", 8, 2);
  let maxInput = prop($$props, "maxInput", 8, Infinity);
  let step = prop($$props, "step", 8, 0.01);
  let styles = prop($$props, "styles", 24, () => ({}));
  const store = setting().store;
  const isLocked = lock() ? lock().store : writable(false);
  init();
  var div = root$b();
  var label = child(div);
  var text2 = child(label);
  var input = sibling(label, 2);
  var input_1 = sibling(input, 2);
  action(div, ($$node, $$action_arg) => applyStyles?.($$node, $$action_arg), styles);
  template_effect(
    ($0) => {
      set_text(text2, $0);
      input.disabled = $isLocked();
      set_attribute(input, "max", max());
      set_attribute(input, "min", min());
      set_attribute(input, "step", step());
      input_1.disabled = $isLocked();
      set_attribute(input_1, "max", maxInput());
      set_attribute(input_1, "min", min());
      set_attribute(input_1, "step", step());
    },
    [
      () => (deep_read_state(setting()), untrack(() => localize(setting().label)))
    ]
  );
  bind_value(input, $store, ($$value) => store_set(store, $$value));
  bind_value(input_1, $store, ($$value) => store_set(store, $$value));
  append($$anchor, div);
  pop();
  $$cleanup();
}
var root$a = /* @__PURE__ */ from_html(`<div style="display: flex; align-items: center;"><input style="height:15px;" type="checkbox"/> <label> </label></div>`);
function Checkbox($$anchor, $$props) {
  push($$props, false);
  const $isLocked = () => store_get(isLocked, "$isLocked", $$stores);
  const $store = () => store_get(store, "$store", $$stores);
  const [$$stores, $$cleanup] = setup_stores();
  let localize = game.i18n.localize.bind(game.i18n);
  let setting = prop($$props, "setting", 8);
  prop($$props, "label", 8, false);
  let lock = prop($$props, "lock", 8, false);
  let inverse = prop($$props, "inverse", 8, false);
  let styles = prop($$props, "styles", 24, () => ({}));
  const id = "sequencer-input-" + foundry.utils.randomID();
  const store = setting().store;
  const isLocked = lock() ? lock().store : writable(inverse());
  legacy_pre_effect(() => ($isLocked(), deep_read_state(inverse())), () => {
    if ($isLocked() !== inverse()) {
      store_set(store, false);
    }
  });
  legacy_pre_effect_reset();
  init();
  var div = root$a();
  var input = child(div);
  var label_1 = sibling(input, 2);
  var text2 = child(label_1);
  action(div, ($$node, $$action_arg) => applyStyles?.($$node, $$action_arg), styles);
  template_effect(
    ($0) => {
      set_attribute(input, "id", id);
      input.disabled = $isLocked() !== inverse();
      set_attribute(label_1, "for", id);
      set_text(text2, $0);
    },
    [
      () => (deep_read_state(setting()), untrack(() => localize(setting().label)))
    ]
  );
  bind_checked(input, $store, ($$value) => store_set(store, $$value));
  append($$anchor, div);
  pop();
  $$cleanup();
}
var root_1$2 = /* @__PURE__ */ from_html(`<label class="svelte-sequencer-ywsxq0"> </label>`);
var root$9 = /* @__PURE__ */ from_html(`<div style="display: flex; align-items: center;"><!> <input type="number" class="svelte-sequencer-ywsxq0"/></div>`);
function NumberInput($$anchor, $$props) {
  push($$props, false);
  const $isLocked = () => store_get(isLocked, "$isLocked", $$stores);
  const $store = () => store_get(store, "$store", $$stores);
  const [$$stores, $$cleanup] = setup_stores();
  let localize = game.i18n.localize.bind(game.i18n);
  let setting = prop($$props, "setting", 8);
  let lock = prop($$props, "lock", 8, false);
  let inverse = prop($$props, "inverse", 8, false);
  let styles = prop($$props, "styles", 24, () => ({}));
  const id = "sequencer-input-" + foundry.utils.randomID();
  const store = setting().store;
  const isLocked = lock() ? lock().store : writable(inverse());
  init();
  var div = root$9();
  var node = child(div);
  {
    var consequent = ($$anchor2) => {
      var label = root_1$2();
      var text2 = child(label);
      template_effect(
        ($0) => {
          set_attribute(label, "for", id);
          set_text(text2, $0);
        },
        [
          () => (deep_read_state(setting()), untrack(() => localize(setting().label)))
        ]
      );
      append($$anchor2, label);
    };
    var d = /* @__PURE__ */ user_derived(() => (deep_read_state(setting()), untrack(() => localize(setting().label))));
    if_block(node, ($$render) => {
      if (get(d)) $$render(consequent);
    });
  }
  var input = sibling(node, 2);
  action(div, ($$node, $$action_arg) => applyStyles?.($$node, $$action_arg), styles);
  template_effect(() => {
    set_attribute(input, "id", id);
    input.disabled = $isLocked() !== inverse();
  });
  bind_value(input, $store, ($$value) => store_set(store, $$value));
  event("change", input, () => {
    if ($store() === null) store_set(store, 0);
  });
  append($$anchor, div);
  pop();
  $$cleanup();
}
var root$8 = /* @__PURE__ */ from_html(`<div style="display: flex; align-items: center;" class="svelte-sequencer-o0yoxs"><div><span> </span></div> <div><span> </span></div></div>`);
function SwitchToggle($$anchor, $$props) {
  push($$props, false);
  const $store = () => store_get(store, "$store", $$stores);
  const [$$stores, $$cleanup] = setup_stores();
  const finalStyles = /* @__PURE__ */ mutable_source();
  let localize = game.i18n.localize.bind(game.i18n);
  let setting = prop($$props, "setting", 8);
  let styles = prop($$props, "styles", 24, () => ({}));
  const store = setting().store;
  const width = 5.75;
  legacy_pre_effect(() => deep_read_state(styles()), () => {
    set(finalStyles, {
      ...styles(),
      "--switch-width": `${width}em`,
      "--half-switch-width": `${width - 1.5}em`,
      "--half-switch-width-on": `${width - 1.7}em`
    });
  });
  legacy_pre_effect_reset();
  init();
  var div = root$8();
  var div_1 = child(div);
  let classes;
  var span = child(div_1);
  var text2 = child(span);
  var div_2 = sibling(div_1, 2);
  let classes_1;
  var span_1 = child(div_2);
  var text_1 = child(span_1);
  action(div, ($$node, $$action_arg) => applyStyles?.($$node, $$action_arg), () => get(finalStyles));
  template_effect(
    ($0, $1) => {
      classes = set_class(div_1, 1, "first svelte-sequencer-o0yoxs", null, classes, { active: !$store() });
      set_text(text2, $0);
      classes_1 = set_class(div_2, 1, "second svelte-sequencer-o0yoxs", null, classes_1, { active: $store() });
      set_text(text_1, $1);
    },
    [
      () => (deep_read_state(setting()), untrack(() => localize(setting().label_off))),
      () => (deep_read_state(setting()), untrack(() => localize(setting().label_on)))
    ]
  );
  event("click", div_1, () => {
    store_set(store, false);
  });
  event("click", div_2, () => {
    store_set(store, true);
  });
  append($$anchor, div);
  pop();
  $$cleanup();
}
var root_1$1 = /* @__PURE__ */ from_html(`<option> </option>`);
var root_2$2 = /* @__PURE__ */ from_html(`<option> </option>`);
var root_3$1 = /* @__PURE__ */ from_html(`<option> </option>`);
var root$7 = /* @__PURE__ */ from_html(`<div class="effect-player-container"><fieldset><legend>Effect</legend> <button class="activate-layer svelte-sequencer-1ipnpu1" type="button"> </button> <div class="file-settings svelte-sequencer-1ipnpu1"><input class="file-input flex3" list="dblist" type="text"/> <button class="custom-file-picker small-button svelte-sequencer-1ipnpu1" type="button"><i class="fas fa-file-import svelte-sequencer-1ipnpu1"></i></button> <datalist id="dblist"></datalist></div> <div class="user-settings flexcol svelte-sequencer-1ipnpu1"><label for="user-select">Play for users:</label> <select class="user-select" id="user-select" multiple=""><option selected=""> </option><!></select></div> <div class="flexcol"><div class="row w-100"> </div> <div class="preset-container svelte-sequencer-1ipnpu1"><select class="preset-select svelte-sequencer-1ipnpu1"><option> </option><!></select> <button class="save-preset small-button svelte-sequencer-1ipnpu1" data-tooltip="Save Preset" type="button"><i class="fas fa-download svelte-sequencer-1ipnpu1"></i></button> <button class="copy-preset small-button svelte-sequencer-1ipnpu1" data-tooltip="Copy Preset" type="button"><i class="fas fa-copy svelte-sequencer-1ipnpu1"></i></button> <button class="delete-preset small-button svelte-sequencer-1ipnpu1" data-tooltip="Delete Preset" type="button"><i class="fas fa-times svelte-sequencer-1ipnpu1"></i></button></div></div></fieldset> <fieldset><legend>Transform</legend> <div class="effect-transform-container"><!> <!> <!> <div class="divider"></div> <!> <div></div> <!> <div></div> <div class="divider"></div> <!> <!> <!> <!> <div class="divider"></div> <!> <!> <!> <!> <!> <div></div></div></fieldset> <fieldset><legend>Behavior</legend> <div class="effect-transform-container"><!> <!> <div class="divider"></div> <!> <!> <!> <!> <div class="divider"></div> <!> <!> <div class="divider"></div> <!> <!> <!> <!> <!></div></fieldset></div>`);
function Player($$anchor, $$props) {
  push($$props, false);
  const $fileStore = () => store_get(fileStore, "$fileStore", $$stores);
  const $userStore = () => store_get(userStore, "$userStore", $$stores);
  const [$$stores, $$cleanup] = setup_stores();
  let localize = game.i18n.localize.bind(game.i18n);
  const fileStore = PlayerSettings.file.store;
  const users2 = game.users.filter((user) => user.active);
  const userStore = PlayerSettings.users.store;
  let lastInput = "";
  let lastResults = [];
  let suggestions = /* @__PURE__ */ mutable_source([]);
  const searchDebounce = foundry.utils.debounce(
    () => {
      const fileInput = get$1(fileStore).toLowerCase();
      if (!fileInput) {
        set(suggestions, SequencerDatabase.publicModules);
        return;
      }
      if (lastInput === fileInput) return;
      let results = SequencerDatabase.searchFor(fileInput);
      if (lastResults.equals(results)) return;
      lastResults = foundry.utils.duplicate(results);
      if (results.length === 1 && results[0].startsWith(fileInput)) return;
      if (results.length > 100) {
        results = results.slice(0, 100);
      }
      set(suggestions, foundry.utils.duplicate(results));
    },
    200
  );
  async function activateLayer() {
    ui.controls.activate({ control: "sequencer", tool: CONSTANTS.TOOLS.PLAY });
    canvas.sequencerInterfaceLayer.activate({ tool: CONSTANTS.TOOLS.PLAY });
  }
  let filePicker = false;
  function handleClick() {
    if (!filePicker) {
      const file = get$1(fileStore);
      const current = SequencerDatabase.getEntry(file, { softFail: true }) ? file : "";
      filePicker = new foundry.applications.apps.FilePicker.implementation({
        type: "imageVideo",
        current,
        callback: (path) => {
          fileStore.set(path);
          filePicker = false;
        }
      });
    }
    filePicker.render(true, { focus: true });
  }
  const presets = PlayerSettings.getPresets();
  let selectedPreset = /* @__PURE__ */ mutable_source("default");
  legacy_pre_effect(() => $fileStore(), () => {
    searchDebounce($fileStore());
  });
  legacy_pre_effect_reset();
  init();
  var div = root$7();
  var fieldset = child(div);
  var button = sibling(child(fieldset), 2);
  var text2 = child(button);
  var div_1 = sibling(button, 2);
  var input = child(div_1);
  var button_1 = sibling(input, 2);
  var datalist = sibling(button_1, 2);
  each(datalist, 5, () => get(suggestions), index, ($$anchor2, suggestion) => {
    var option = root_1$1();
    var text_1 = child(option);
    var option_value = {};
    template_effect(() => {
      set_text(text_1, get(suggestion));
      if (option_value !== (option_value = get(suggestion))) {
        option.__value = get(suggestion);
      }
    });
    append($$anchor2, option);
  });
  var div_2 = sibling(div_1, 2);
  var select = sibling(child(div_2), 2);
  var option_1 = child(select);
  var text_2 = child(option_1);
  option_1.value = option_1.__value = "all";
  var node = sibling(option_1);
  each(node, 1, () => users2, (user) => user.id, ($$anchor2, user) => {
    var option_2 = root_2$2();
    var text_3 = child(option_2);
    var option_2_value = {};
    template_effect(() => {
      set_text(text_3, (get(user), untrack(() => get(user).name)));
      if (option_2_value !== (option_2_value = (get(user), untrack(() => get(user).id)))) {
        option_2.value = (option_2.__value = (get(user), untrack(() => get(user).id))) ?? "";
      }
    });
    append($$anchor2, option_2);
  });
  var div_3 = sibling(div_2, 2);
  var div_4 = child(div_3);
  var text_4 = child(div_4);
  var div_5 = sibling(div_4, 2);
  var select_1 = child(div_5);
  var option_3 = child(select_1);
  var text_5 = child(option_3);
  option_3.value = option_3.__value = "default";
  var node_1 = sibling(option_3);
  each(node_1, 1, () => presets, index, ($$anchor2, preset) => {
    var option_4 = root_3$1();
    var text_6 = child(option_4);
    var option_4_value = {};
    template_effect(() => {
      set_text(text_6, get(preset));
      if (option_4_value !== (option_4_value = get(preset))) {
        option_4.__value = get(preset);
      }
    });
    append($$anchor2, option_4);
  });
  var button_2 = sibling(select_1, 2);
  var button_3 = sibling(button_2, 2);
  var button_4 = sibling(button_3, 2);
  var fieldset_1 = sibling(fieldset, 2);
  var div_6 = sibling(child(fieldset_1), 2);
  var node_2 = child(div_6);
  SliderInput(node_2, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.scale);
    },
    styles: { "grid-column": `1 / 5` }
  });
  var node_3 = sibling(node_2, 2);
  NumberInput(node_3, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.scaleIn);
    },
    styles: { "grid-column": `1 / 3` }
  });
  var node_4 = sibling(node_3, 2);
  NumberInput(node_4, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.scaleOut);
    },
    styles: { "grid-column": `3 / 5` }
  });
  var node_5 = sibling(node_4, 4);
  SliderInput(node_5, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.rotation);
    },
    get lock() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.randomRotation);
    },
    min: "-180",
    max: "180",
    styles: { "grid-column": `1 / 5` }
  });
  var node_6 = sibling(node_5, 4);
  Checkbox(node_6, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.randomRotation);
    },
    styles: { "grid-column": `2 / 4` }
  });
  var node_7 = sibling(node_6, 6);
  Checkbox(node_7, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.mirrorX);
    },
    styles: { "grid-column": `1 / 3` }
  });
  var node_8 = sibling(node_7, 2);
  Checkbox(node_8, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.mirrorY);
    },
    styles: { "grid-column": `3 / 5` }
  });
  var node_9 = sibling(node_8, 2);
  Checkbox(node_9, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.randomMirrorX);
    },
    styles: { "grid-column": `1 / 3` },
    get lock() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.mirrorX);
    },
    inverse: true
  });
  var node_10 = sibling(node_9, 2);
  Checkbox(node_10, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.randomMirrorY);
    },
    styles: { "grid-column": `3 / 5` },
    get lock() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.mirrorY);
    },
    inverse: true
  });
  var node_11 = sibling(node_10, 4);
  NumberInput(node_11, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.offsetX);
    },
    get lock() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.randomOffset);
    },
    styles: { "grid-column": `1 / 3` }
  });
  var node_12 = sibling(node_11, 2);
  NumberInput(node_12, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.offsetY);
    },
    get lock() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.randomOffset);
    },
    styles: { "grid-column": `3 / 5` }
  });
  var node_13 = sibling(node_12, 2);
  Checkbox(node_13, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.randomOffset);
    },
    styles: { "grid-column": `1 / 3` }
  });
  var node_14 = sibling(node_13, 2);
  NumberInput(node_14, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.randomOffsetAmount);
    },
    get lock() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.randomOffset);
    },
    inverse: true,
    styles: { "grid-column": `3 / 5` }
  });
  var node_15 = sibling(node_14, 2);
  Checkbox(node_15, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.offsetGridUnits);
    },
    styles: { "grid-column": `2 / 5` }
  });
  var fieldset_2 = sibling(fieldset_1, 2);
  var div_7 = sibling(child(fieldset_2), 2);
  var node_16 = child(div_7);
  NumberInput(node_16, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.fadeIn);
    },
    styles: { "grid-column": `1 / 3` }
  });
  var node_17 = sibling(node_16, 2);
  NumberInput(node_17, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.fadeOut);
    },
    styles: { "grid-column": `3 / 5` }
  });
  var node_18 = sibling(node_17, 4);
  Checkbox(node_18, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.repeat);
    },
    styles: { "grid-column": `1 / 3` }
  });
  var node_19 = sibling(node_18, 2);
  NumberInput(node_19, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.repetitions);
    },
    get lock() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.repeat);
    },
    inverse: true,
    styles: { "grid-column": `3 / 5` }
  });
  var node_20 = sibling(node_19, 2);
  NumberInput(node_20, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.repeatDelayMin);
    },
    get lock() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.repeat);
    },
    inverse: true,
    styles: { "grid-column": `3 / 4` }
  });
  var node_21 = sibling(node_20, 2);
  NumberInput(node_21, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.repeatDelayMax);
    },
    get lock() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.repeat);
    },
    inverse: true,
    styles: { "grid-column": `4 / 5` }
  });
  var node_22 = sibling(node_21, 4);
  SwitchToggle(node_22, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.moveTowards);
    },
    styles: { "grid-column": `1 / 3` }
  });
  var node_23 = sibling(node_22, 2);
  NumberInput(node_23, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.moveSpeed);
    },
    get lock() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.moveTowards);
    },
    inverse: true,
    styles: { "grid-column": `3 / 5` }
  });
  var node_24 = sibling(node_23, 4);
  Checkbox(node_24, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.attachTo);
    },
    styles: { "grid-column": `1 / 5` }
  });
  var node_25 = sibling(node_24, 2);
  Checkbox(node_25, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.stretchToAttach);
    },
    styles: { "grid-column": `1 / 5` }
  });
  var node_26 = sibling(node_25, 2);
  Checkbox(node_26, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.snapToGrid);
    },
    styles: { "grid-column": `1 / 5` }
  });
  var node_27 = sibling(node_26, 2);
  Checkbox(node_27, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.persist);
    },
    styles: { "grid-column": `1 / 5` }
  });
  var node_28 = sibling(node_27, 2);
  Checkbox(node_28, {
    get setting() {
      return deep_read_state(PlayerSettings), untrack(() => PlayerSettings.belowTokens);
    },
    styles: { "grid-column": `1 / 5` }
  });
  template_effect(
    ($0, $1, $2, $3, $4) => {
      set_text(text2, $0);
      set_attribute(input, "placeholder", $1);
      set_text(text_2, $2);
      set_text(text_4, $3);
      set_text(text_5, $4);
      button_3.disabled = get(selectedPreset) === "default";
      button_4.disabled = get(selectedPreset) === "default";
    },
    [
      () => untrack(() => localize("SEQUENCER.Player.SwitchToLayer")),
      () => untrack(() => localize("SEQUENCER.Player.PathInput")),
      () => untrack(() => localize("SEQUENCER.Player.AllUsers")),
      () => untrack(() => localize("SEQUENCER.Player.Presets")),
      () => untrack(() => localize("SEQUENCER.Player.PresetsDefault"))
    ]
  );
  event("click", button, () => activateLayer());
  bind_value(input, $fileStore, ($$value) => store_set(fileStore, $$value));
  event("click", button_1, handleClick);
  bind_select_value(select, $userStore, ($$value) => store_set(userStore, $$value));
  bind_select_value(select_1, () => get(selectedPreset), ($$value) => set(selectedPreset, $$value));
  event("change", select_1, () => PlayerSettings.loadPreset(get(selectedPreset)));
  event("click", button_2, () => PlayerSettings.savePreset(get(selectedPreset)));
  event("click", button_3, () => PlayerSettings.copyPreset(get(selectedPreset)));
  event("click", button_4, () => PlayerSettings.deletePreset(get(selectedPreset)));
  append($$anchor, div);
  pop();
  $$cleanup();
}
var root$6 = /* @__PURE__ */ from_html(`<i></i> <i class="fa-solid "></i>`, 1);
function SequenceStatus($$anchor, $$props) {
  push($$props, false);
  const $status = () => store_get(status(), "$status", $$stores);
  const [$$stores, $$cleanup] = setup_stores();
  let status = prop($$props, "status", 8);
  init();
  var fragment = root$6();
  var i = first_child(fragment);
  let classes;
  template_effect(() => classes = set_class(i, 1, "fa-solid background-circle svelte-sequencer-1wkm0y3", null, classes, {
    invisible: $status() === CONSTANTS.STATUS.READY,
    "fa-arrow-right": $status() === CONSTANTS.STATUS.RUNNING || $status() === CONSTANTS.STATUS.READY,
    "fa-check": $status() === CONSTANTS.STATUS.COMPLETE,
    "fa-arrow-down": $status() === CONSTANTS.STATUS.SKIPPED,
    "fa-times": $status() === CONSTANTS.STATUS.ABORTED
  }));
  append($$anchor, fragment);
  pop();
  $$cleanup();
}
var root$5 = /* @__PURE__ */ from_html(`<div class="svelte-sequencer-1ee9h3"><span><!></span> <span class="section-name svelte-sequencer-1ee9h3"> </span> <span class="sequence-actions svelte-sequencer-1ee9h3"><a><i class="fas fa-stop"></i></a></span></div>`);
function SequenceSection($$anchor, $$props) {
  push($$props, false);
  const $status = () => store_get(status, "$status", $$stores);
  const [$$stores, $$cleanup] = setup_stores();
  let localize = game.i18n.localize.bind(game.i18n);
  let section = prop($$props, "section", 8);
  const status = section().sectionStatus;
  const sectionName = (section()?.constructor?.niceName ? section()?.constructor?.niceName : section().constructor.name) + (section()?._name ? " - " + section()._name : "");
  init();
  var div = root$5();
  var span = child(div);
  var node = child(span);
  SequenceStatus(node, {
    get status() {
      return status;
    }
  });
  var span_1 = sibling(span, 2);
  var text2 = child(span_1);
  var span_2 = sibling(span_1, 2);
  var a = child(span_2);
  let classes;
  template_effect(
    ($0) => {
      set_text(text2, sectionName);
      set_attribute(span_2, "data-tooltip", $0);
      classes = set_class(a, 1, "svelte-sequencer-1ee9h3", null, classes, { "section-done": $status() > CONSTANTS.STATUS.READY });
    },
    [
      () => untrack(() => localize("SEQUENCER.Sequences.AbortSection"))
    ]
  );
  event("click", a, () => {
    section()._abortSection();
  });
  append($$anchor, div);
  pop();
  $$cleanup();
}
var root$4 = /* @__PURE__ */ from_html(`<div class="sequence-name-container svelte-sequencer-1dcwqos"><span><!></span> <span class="sequence-name svelte-sequencer-1dcwqos"> </span> <span class="sequence-actions svelte-sequencer-1dcwqos"><a><i class="fas fa-trash-can"></i></a> <a><i class="fas fa-stop"></i></a></span></div> <!>`, 1);
function Sequence$2($$anchor, $$props) {
  push($$props, false);
  const $status = () => store_get(status, "$status", $$stores);
  const [$$stores, $$cleanup] = setup_stores();
  let localize = game.i18n.localize.bind(game.i18n);
  let sequence = prop($$props, "sequence", 8);
  let index$1 = prop($$props, "index", 8);
  const status = sequence().statusStore;
  init();
  var fragment = root$4();
  var div = first_child(fragment);
  var span = child(div);
  var node = child(span);
  SequenceStatus(node, {
    get status() {
      return status;
    }
  });
  var span_1 = sibling(span, 2);
  var text2 = child(span_1);
  var span_2 = sibling(span_1, 2);
  var a = child(span_2);
  let classes;
  var a_1 = sibling(a, 2);
  let classes_1;
  var node_1 = sibling(div, 2);
  each(
    node_1,
    1,
    () => (deep_read_state(sequence()), untrack(() => sequence().sections)),
    index,
    ($$anchor2, section) => {
      SequenceSection($$anchor2, {
        get section() {
          return get(section);
        }
      });
    }
  );
  template_effect(
    ($0, $1) => {
      set_text(text2, `Sequence ${index$1() ?? ""}${(deep_read_state(sequence()), untrack(() => sequence().moduleName ? ` (${sequence().moduleName})` : "")) ?? ""}`);
      classes = set_class(a, 1, "clear-sequence svelte-sequencer-1dcwqos", null, classes, { "sequence-done-show": $status() > CONSTANTS.STATUS.RUNNING });
      set_attribute(a, "data-tooltip", $0);
      set_attribute(a_1, "data-tooltip", $1);
      classes_1 = set_class(a_1, 1, "svelte-sequencer-1dcwqos", null, classes_1, { "sequence-done-hide": $status() > CONSTANTS.STATUS.RUNNING });
    },
    [
      () => untrack(() => localize("SEQUENCER.Sequences.Clear")),
      () => untrack(() => localize("SEQUENCER.Sequences.AbortSequence"))
    ]
  );
  event("click", a, () => {
    SequenceManager.RunningSequences.delete(sequence().id);
  });
  event("click", a_1, () => {
    sequence()._abort();
  });
  append($$anchor, fragment);
  pop();
  $$cleanup();
}
var root_1 = /* @__PURE__ */ from_html(`<div class="no-sequences"><h2> </h2></div>`);
var root_2$1 = /* @__PURE__ */ from_html(`<div class="sequence-button-header svelte-sequencer-1ismzan"><button type="button" class="svelte-sequencer-1ismzan"> </button> <button type="button" class="svelte-sequencer-1ismzan"> </button></div> <div class="running-sequences svelte-sequencer-1ismzan"></div>`, 1);
var root$3 = /* @__PURE__ */ from_html(`<div class="sequence-container svelte-sequencer-1ismzan"><!></div>`);
function Sequences($$anchor, $$props) {
  push($$props, false);
  const $RunningSequences = () => store_get(RunningSequences, "$RunningSequences", $$stores);
  const [$$stores, $$cleanup] = setup_stores();
  const runningSequences = /* @__PURE__ */ mutable_source();
  let localize = game.i18n.localize.bind(game.i18n);
  const RunningSequences = SequenceManager.RunningSequences;
  onMount(() => {
    SequenceManager.RunningSequences.clearFinishedSequences();
  });
  onDestroy(() => {
    SequenceManager.RunningSequences.clearFinishedSequences();
  });
  legacy_pre_effect(() => $RunningSequences(), () => {
    set(runningSequences, Object.entries($RunningSequences()));
  });
  legacy_pre_effect_reset();
  init();
  var div = root$3();
  var node = child(div);
  {
    var consequent = ($$anchor2) => {
      var div_1 = root_1();
      var h2 = child(div_1);
      var text2 = child(h2);
      template_effect(($0) => set_text(text2, $0), [
        () => untrack(() => localize("SEQUENCER.Sequences.NoSequences"))
      ]);
      append($$anchor2, div_1);
    };
    var alternate = ($$anchor2) => {
      var fragment = root_2$1();
      var div_2 = first_child(fragment);
      var button = child(div_2);
      var text_1 = child(button);
      var button_1 = sibling(button, 2);
      var text_2 = child(button_1);
      var div_3 = sibling(div_2, 2);
      each(div_3, 7, () => get(runningSequences), ([id, sequence]) => id, ($$anchor3, $$item, index2) => {
        var $$array = /* @__PURE__ */ user_derived(() => to_array(get($$item), 2));
        let sequence = () => get($$array)[1];
        {
          let $0 = /* @__PURE__ */ derived_safe_equal(() => get(index2) + 1);
          Sequence$2($$anchor3, {
            get sequence() {
              return sequence();
            },
            get index() {
              return get($0);
            }
          });
        }
      });
      template_effect(
        ($0, $1) => {
          set_text(text_1, $0);
          set_text(text_2, $1);
        },
        [
          () => untrack(() => localize("SEQUENCER.Sequences.ClearFinished")),
          () => untrack(() => localize("SEQUENCER.Sequences.StopAll"))
        ]
      );
      event("click", button, () => {
        SequenceManager.RunningSequences.clearFinishedSequences();
      });
      event("click", button_1, () => {
        SequenceManager.RunningSequences.stopAll();
      });
      append($$anchor2, fragment);
    };
    if_block(node, ($$render) => {
      if (get(runningSequences), untrack(() => !get(runningSequences).length)) $$render(consequent);
      else $$render(alternate, -1);
    });
  }
  append($$anchor, div);
  pop();
  $$cleanup();
}
var root$2 = /* @__PURE__ */ from_html(`<div><!> <!></div>`);
function Effects_ui_shell($$anchor, $$props) {
  push($$props, true);
  let localize = game.i18n.localize.bind(game.i18n);
  let tabs = [
    {
      value: "player",
      label: localize("SEQUENCER.Player.Title"),
      icon: "fas fa-play-circle",
      component: Player
    },
    {
      value: "manager",
      label: localize("SEQUENCER.Manager.Title"),
      icon: "fas fa-film",
      component: Manager
    },
    {
      value: "sequences",
      label: localize("SEQUENCER.Sequences.Title"),
      icon: "fas fa-play",
      component: Sequences
    },
    {
      value: "howto",
      label: localize("SEQUENCER.HowTo.Title"),
      icon: "fas fa-chalkboard-teacher",
      component: HowTo
    }
  ];
  let activeTab = /* @__PURE__ */ state(proxy($$props.tab ?? "manager"));
  let component$1 = /* @__PURE__ */ state(proxy(tabs[0].component));
  user_effect(() => {
    set(component$1, tabs.find((t) => t.value === get(activeTab))?.component, true);
  });
  var div = root$2();
  var node = child(div);
  Tabs(node, {
    get tabs() {
      return tabs;
    },
    get activeTab() {
      return get(activeTab);
    },
    set activeTab($$value) {
      set(activeTab, $$value, true);
    }
  });
  var node_1 = sibling(node, 2);
  component(node_1, () => get(component$1), ($$anchor2, $$component) => {
    $$component($$anchor2, {});
  });
  append($$anchor, div);
  pop();
}
const PATREON_URL$1 = "https://patreon.com/cw/fantasycomputerworks";
class EffectsUIApp extends SvelteApplicationMixin(foundry.applications.api.ApplicationV2) {
  static DEFAULT_OPTIONS = {
    classes: ["dialog"],
    tab: "manager",
    position: {
      width: "auto",
      height: "auto",
      top: 65,
      left: 120
    },
    actions: {
      openSequencerChangelog: () => {
        if (window.Sequencer?.showChangelog) window.Sequencer.showChangelog();
      },
      openSequencerPatreon: () => {
        window.open(PATREON_URL$1, "_blank", "noopener");
      }
    },
    window: {
      title: "SEQUENCER.ManagerUI",
      icon: "fa-solid fa-archive",
      resizable: false,
      controls: [
        {
          action: "openSequencerChangelog",
          icon: "fa-solid fa-clipboard-list",
          label: "Changelog"
        },
        {
          action: "openSequencerPatreon",
          icon: "fa-brands fa-patreon",
          label: "Support Sequencer"
        }
      ]
    }
  };
  root = Effects_ui_shell;
}
function registerSettings() {
  game.settings.register(CONSTANTS.MODULE_NAME, "enable-fix-pixi", {
    name: "SEQUENCER.Setting.EnablePixiFix.Title",
    hint: "SEQUENCER.Setting.EnablePixiFix.Label",
    scope: "client",
    config: true,
    default: false,
    requiresReload: true,
    type: Boolean
  });
  game.settings.register(CONSTANTS.MODULE_NAME, "enable-global-fix-pixi", {
    name: "SEQUENCER.Setting.EnableGlobalPixiFix.Title",
    hint: "SEQUENCER.Setting.EnableGlobalPixiFix.Label",
    scope: "client",
    config: true,
    default: false,
    requiresReload: true,
    type: Boolean
  });
  game.settings.register(CONSTANTS.MODULE_NAME, "enable-above-ui-screenspace", {
    name: "SEQUENCER.Setting.EnableAboveUIScreenspace.Title",
    hint: "SEQUENCER.Setting.EnableAboveUIScreenspace.Label",
    scope: "client",
    config: true,
    default: true,
    requiresReload: true,
    type: Boolean
  });
  game.settings.register(CONSTANTS.MODULE_NAME, "debug", {
    name: "SEQUENCER.Setting.Debug.Title",
    hint: "SEQUENCER.Setting.Debug.Label",
    scope: "client",
    config: true,
    default: false,
    type: Boolean
  });
  game.settings.register(CONSTANTS.MODULE_NAME, "showSidebarTools", {
    name: "SEQUENCER.Setting.ShowTools.Title",
    hint: "SEQUENCER.Setting.ShowTools.Label",
    scope: "client",
    config: true,
    default: true,
    requiresReload: true,
    type: Boolean
  });
  game.settings.register(CONSTANTS.MODULE_NAME, "showTokenSidebarTools", {
    name: "SEQUENCER.Setting.ShowTokenTools.Title",
    hint: "SEQUENCER.Setting.ShowTokenTools.Label",
    scope: "client",
    config: true,
    default: true,
    requiresReload: true,
    type: Boolean
  });
  game.settings.register(CONSTANTS.MODULE_NAME, "effectsEnabled", {
    name: "SEQUENCER.Setting.EnableEffects.Title",
    hint: "SEQUENCER.Setting.EnableEffects.Label",
    scope: "client",
    config: true,
    default: true,
    requiresReload: true,
    type: Boolean
  });
  game.settings.register(CONSTANTS.MODULE_NAME, "soundsEnabled", {
    name: "SEQUENCER.Setting.EnableSounds.Title",
    hint: "SEQUENCER.Setting.EnableSounds.Label",
    scope: "client",
    config: true,
    default: true,
    requiresReload: true,
    type: Boolean
  });
  game.settings.register(CONSTANTS.MODULE_NAME, "user-effect-opacity", {
    name: "SEQUENCER.Setting.ExternalEffectOpacity.Title",
    hint: "SEQUENCER.Setting.ExternalEffectOpacity.Label",
    scope: "client",
    config: true,
    default: 50,
    type: Number,
    range: {
      min: 0,
      max: 100,
      step: 1
    }
  });
  game.settings.register(CONSTANTS.MODULE_NAME, "db-list-view", {
    scope: "client",
    config: false,
    default: false,
    type: Boolean
  });
  game.settings.register(CONSTANTS.MODULE_NAME, "update-notice-shown-version", {
    scope: "world",
    config: false,
    default: "0.0.0",
    type: String
  });
  const permissionLevels = [
    game.i18n.localize("SEQUENCER.Permission.Player"),
    game.i18n.localize("SEQUENCER.Permission.Trusted"),
    game.i18n.localize("SEQUENCER.Permission.Assistant"),
    game.i18n.localize("SEQUENCER.Permission.GM")
  ];
  game.settings.register(CONSTANTS.MODULE_NAME, "permissions-effect-create", {
    name: "SEQUENCER.Setting.Permission.EffectCreate.Title",
    hint: "SEQUENCER.Setting.Permission.EffectCreate.Label",
    scope: "world",
    config: true,
    default: 0,
    type: Number,
    choices: permissionLevels,
    requiresReload: true
  });
  game.settings.register(CONSTANTS.MODULE_NAME, "permissions-effect-delete", {
    name: "SEQUENCER.Setting.Permission.EffectDelete.Title",
    hint: "SEQUENCER.Setting.Permission.EffectDelete.Label",
    scope: "world",
    config: true,
    default: 2,
    type: Number,
    choices: permissionLevels,
    requiresReload: true
  });
  game.settings.register(CONSTANTS.MODULE_NAME, "permissions-sound-create", {
    name: "SEQUENCER.Setting.Permission.SoundCreate.Title",
    hint: "SEQUENCER.Setting.Permission.SoundCreate.Label",
    scope: "world",
    config: true,
    default: 0,
    type: Number,
    choices: permissionLevels,
    requiresReload: true
  });
  game.settings.register(CONSTANTS.MODULE_NAME, "permissions-preload", {
    name: "SEQUENCER.Setting.Permission.PreloadClients.Title",
    hint: "SEQUENCER.Setting.Permission.PreloadClients.Label",
    scope: "world",
    config: true,
    default: 1,
    type: Number,
    choices: permissionLevels,
    requiresReload: true
  });
  game.settings.register(CONSTANTS.MODULE_NAME, "permissions-sidebar-tools", {
    name: "SEQUENCER.Setting.Permission.UseSidebarTools.Title",
    hint: "SEQUENCER.Setting.Permission.UseSidebarTools.Label",
    scope: "world",
    config: true,
    default: 0,
    type: Number,
    choices: permissionLevels,
    requiresReload: true
  });
  game.settings.register(CONSTANTS.MODULE_NAME, "effectPresets", {
    scope: "client",
    default: {},
    type: Object
  });
  Hooks.on("getSceneControlButtons", (controls) => {
    if (!game.settings.get(CONSTANTS.MODULE_NAME, "showSidebarTools")) return;
    const sidebarTools = {
      [CONSTANTS.TOOLS.SELECT]: {
        name: CONSTANTS.TOOLS.SELECT,
        title: "SEQUENCER.SidebarButtons.Select",
        icon: "fas fa-expand",
        visible: user_can_do("permissions-effect-create") && user_can_do("permissions-sidebar-tools"),
        onChange: () => {
        }
      },
      [CONSTANTS.TOOLS.PLAY]: {
        name: CONSTANTS.TOOLS.PLAY,
        title: "SEQUENCER.SidebarButtons.Play",
        icon: "fas fa-play",
        visible: user_can_do("permissions-effect-create") && user_can_do("permissions-sidebar-tools"),
        onChange: (event2, active) => {
          if (active) {
            EffectsUIApp.show({ inFocus: true, tab: "player" });
          }
        }
      },
      [CONSTANTS.TOOLS.VIEWER]: {
        name: CONSTANTS.TOOLS.VIEWER,
        title: "SEQUENCER.SidebarButtons.Manager",
        icon: "fas fa-film",
        button: true,
        visible: user_can_do("permissions-effect-create") && user_can_do("permissions-sidebar-tools"),
        onChange: (event2, active) => {
          if (active) {
            EffectsUIApp.show({ inFocus: true, tab: "manager" });
          }
        }
      },
      [CONSTANTS.TOOLS.DATABASE]: {
        name: CONSTANTS.TOOLS.DATABASE,
        title: "SEQUENCER.SidebarButtons.Database",
        icon: "fas fa-database",
        button: true,
        visible: user_can_do("permissions-sidebar-tools"),
        onChange: (event2, active) => {
          if (active) {
            DatabaseViewerApp.show();
          }
        }
      }
    };
    return setupSidebarTools(controls, sidebarTools);
  });
  debug("Sequencer | Registered settings");
}
function setupSidebarTools(controls, sidebarTools) {
  controls[CONSTANTS.MODULE_NAME] = {
    name: CONSTANTS.MODULE_NAME,
    title: "Sequencer Layer",
    icon: "fas fa-list-ol",
    layer: CONSTANTS.INTERFACE_LAYER,
    visible: user_can_do("permissions-effect-create") && user_can_do("permissions-sidebar-tools"),
    activeTool: CONSTANTS.TOOLS.PLAY,
    onChange: (event2, active) => {
      if (active) canvas.sequencerInterfaceLayer.activate({ tool: CONSTANTS.TOOLS.PLAY });
    },
    tools: sidebarTools
  };
  if (!game.settings.get(CONSTANTS.MODULE_NAME, "showTokenSidebarTools")) {
    return;
  }
  controls["tokens"].tools[CONSTANTS.TOOLS.DATABASE] = sidebarTools[CONSTANTS.TOOLS.DATABASE];
  controls["tokens"].tools[CONSTANTS.TOOLS.VIEWER] = sidebarTools[CONSTANTS.TOOLS.VIEWER];
}
async function migrateSettings() {
  const oldScreenspaceSetting = game.settings.storage.get("client").getItem("sequencer.disable-above-ui-screenspace");
  if (oldScreenspaceSetting) {
    const value = oldScreenspaceSetting === "true";
    game.settings.storage.get("client").removeItem("sequencer.disable-above-ui-screenspace");
    await game.settings.set(
      CONSTANTS.MODULE_NAME,
      "enable-above-ui-screenspace",
      !value
    );
  }
}
function registerLayers() {
  CONFIG.Canvas.layers = foundry.utils.mergeObject(foundry.canvas.Canvas.layers, {
    sequencerEffects: {
      layerClass: BaseEffectsLayer,
      group: "interface"
    },
    sequencerInterfaceLayer: {
      layerClass: SequencerInterfaceLayer,
      group: "interface"
    },
    sequencerEffectsUILayer: {
      layerClass: UIEffectsLayer,
      group: "interface"
    }
  });
  debug("Registered Layers");
}
function registerBatchShader() {
  VisionSamplerShader.registerPlugin();
  debug("Registered VisionBatchShader");
}
const hotkeys = {
  get _ready() {
    return canvas.ready && canvas.sequencerInterfaceLayer.active;
  },
  playTool: {
    playManySequencedDown: () => {
      if (!hotkeys._ready) return;
      EffectPlayer.playManySequenced = true;
    },
    playManySequencedUp: () => {
      if (!hotkeys._ready) return;
      EffectPlayer.playManySequenced = false;
      if (!EffectPlayer.isActive) return;
      EffectPlayer.playManyUp();
    },
    playManyDown: () => {
      if (!hotkeys._ready) return;
      EffectPlayer.playMany = true;
    },
    playManyUp: () => {
      if (!hotkeys._ready) return;
      EffectPlayer.playMany = false;
      if (!EffectPlayer.isActive) return;
      EffectPlayer.playManyUp();
    },
    attachToDown: () => {
      if (!hotkeys._ready) return;
      PlayerSettings.attachTo.store.set(true);
      PlayerSettings.stretchToAttach.store.set(true);
    },
    attachToUp: () => {
      if (!hotkeys._ready) return;
      PlayerSettings.attachTo.store.set(false);
      PlayerSettings.stretchToAttach.store.set(false);
    }
  },
  selectTool: {
    snapToGridDown: () => {
      if (!hotkeys._ready) return;
      SelectionManager.snapToGrid = true;
    },
    snapToGridUp: () => {
      if (!hotkeys._ready) return;
      SelectionManager.snapToGrid = false;
    },
    attachToTargetDown: () => {
      if (!hotkeys._ready) return;
      if (!SelectionManager.isActive) return;
      PlayerSettings.attachTo.store.set(true);
      PlayerSettings.stretchToAttach.store.set(true);
    },
    attachToTargetUp: () => {
      if (!hotkeys._ready) return;
      PlayerSettings.attachTo.store.set(false);
      PlayerSettings.stretchToAttach.store.set(false);
    },
    deleteDown: () => {
      if (!hotkeys._ready) return;
      SelectionManager.delete();
    }
  }
};
function registerHotkeys() {
  game.keybindings.register(CONSTANTS.MODULE_NAME, "play-tool-hotkey-control", {
    name: "SEQUENCER.Hotkeys.PlayTool.Control",
    editable: [{ key: "ControlLeft" }],
    onDown: hotkeys.playTool.playManySequencedDown,
    onUp: hotkeys.playTool.playManySequencedUp
  });
  game.keybindings.register(CONSTANTS.MODULE_NAME, "play-tool-hotkey-shift", {
    name: "SEQUENCER.Hotkeys.PlayTool.Shift",
    editable: [{ key: "ShiftLeft" }],
    onDown: hotkeys.playTool.playManyDown,
    onUp: hotkeys.playTool.playManyUp
  });
  game.keybindings.register(CONSTANTS.MODULE_NAME, "play-tool-hotkey-alt", {
    name: "SEQUENCER.Hotkeys.PlayTool.Alt",
    editable: [{ key: "AltLeft" }],
    onDown: hotkeys.playTool.attachToDown,
    onUp: hotkeys.playTool.attachToUp
  });
  game.keybindings.register(
    CONSTANTS.MODULE_NAME,
    "select-tool-hotkey-control",
    {
      name: "SEQUENCER.Hotkeys.SelectTool.Control",
      editable: [{ key: "ControlLeft" }],
      onDown: hotkeys.selectTool.snapToGridDown,
      onUp: hotkeys.selectTool.snapToGridUp
    }
  );
  game.keybindings.register(CONSTANTS.MODULE_NAME, "select-tool-hotkey-alt", {
    name: "SEQUENCER.Hotkeys.SelectTool.Alt",
    editable: [{ key: "AltLeft" }],
    onDown: hotkeys.selectTool.attachToTargetDown,
    onUp: hotkeys.selectTool.attachToTargetUp
  });
  game.keybindings.register(
    CONSTANTS.MODULE_NAME,
    "select-tool-hotkey-delete",
    {
      name: "SEQUENCER.Hotkeys.SelectTool.Delete",
      editable: [{ key: "Delete" }],
      onDown: hotkeys.selectTool.deleteDown
    }
  );
}
async function registerTypes(register) {
  fetch("modules/sequencer/typings/types.d.ts").then((response) => response.text()).then((content) => register("sequencer/types.d.ts", content));
}
const presetMap = /* @__PURE__ */ new Map();
class SequencerPresets {
  /**
   * Adds a preset that can then be used in sequences
   *
   * @param {string} inName
   * @param {Function} inFunction
   * @param {boolean} [overwrite=false] overwrite
   * @returns {Map<string, Function>}
   */
  static add(inName, inFunction, overwrite = false) {
    if (typeof inName !== "string") {
      throw custom_error(
        "Sequencer",
        `SequencerPresets | inName must be of type string`
      );
    }
    if (!is_function(inFunction)) {
      throw custom_error(
        "Sequencer",
        `SequencerPresets | inFunction must be of type function`
      );
    }
    if (presetMap.get(inName) && !overwrite) {
      throw custom_error(
        "Sequencer",
        `SequencerPresets | Preset "${inName}" already exists`
      );
    }
    presetMap.set(inName, inFunction);
    debug(`Sequencer | Presets | Added "${inName}" preset`);
    return presetMap;
  }
  /**
   * Retrieves all presets
   *
   * @returns {Map<string, Function>}
   */
  static getAll() {
    return presetMap;
  }
  /**
   * Retrieves preset based on its name
   *
   * @param {string} name
   * @param {boolean} exact
   * @returns {Function}
   */
  static get(name2, exact = false) {
    if (presetMap.has(name2) || exact) {
      return presetMap.get(name2);
    }
    const parts = name2.split(".");
    parts.pop();
    while (parts.length) {
      const newName = parts.join(".");
      if (presetMap.has(newName)) {
        return presetMap.get(newName);
      }
      parts.pop();
    }
    return false;
  }
}
class Section {
  constructor(inSequence) {
    this.sequence = inSequence;
    this._applyTraits();
    this._sectionStatus = writable(CONSTANTS.STATUS.READY);
    this._playIf = true;
    this._waitUntilFinished = false;
    this._async = false;
    this._waitUntilFinishedDelay = [0, 0];
    this._repetitions = 1;
    this._currentRepetition = 0;
    this._repeatDelayMin = 0;
    this._repeatDelayMax = 0;
    this._repeatDelay = 0;
    this._delayMin = 0;
    this._delayMax = 0;
    this._basicDelay = 0;
    this._duration = false;
  }
  static niceName = "Section";
  /**
   * @protected
   */
  get _shouldAsync() {
    return this._async || this._waitAnyway;
  }
  /**
   * @protected
   */
  get shouldWaitUntilFinished() {
    return this._waitUntilFinished || this._waitAnyway;
  }
  /**
   * @protected
   */
  get _waitAnyway() {
    return (this._async || this._waitUntilFinished) && this._isLastRepetition || this._isLastRepetition && this._isLastSection;
  }
  /**
   * @protected
   */
  get _isLastSection() {
    return this.sequence.sections.length - 1 === this.sequence.sections.indexOf(this);
  }
  /** ------------------------------------------------------------------------------------------------------------------------------ *
   * Methods below this point should NOT be overridden by child instances of the class, they are integral to the sequence functioning
   * ------------------------------------------------------------------------------------------------------------------------------- */
  /**
   * @protected
   */
  get _isLastRepetition() {
    return this._repetitions === 1 || this._repetitions === this._currentRepetition + 1;
  }
  /**
   * @protected
   */
  get _currentWaitTime() {
    let waitUntilFinishedDelay = this._waitAnyway ? random_int_between(...this._waitUntilFinishedDelay) : 0;
    return waitUntilFinishedDelay + this._repeatDelay;
  }
  /**
   * Method overwritten by inheriting classes, which is called just before the "run" method is called (see below)
   *
   * @returns {Promise<void>}
   * @protected
   */
  async preRun() {
  }
  /**
   * Method overwritten by inheriting classes, which is called when this section is executed by the Sequence
   *
   * @returns {Promise<void>}
   * @protected
   */
  async run() {
  }
  /**
   * Method overwritten by inheriting classes, which is called when this section is serialized by the Sequence
   *
   * @returns {object}
   * @private
   */
  async _serialize() {
    return {
      async: this._async,
      delay: [this._delayMin, this._delayMax],
      waitUntilFinished: this._waitUntilFinished,
      waitUntilFinishedDelay: this._waitUntilFinishedDelay,
      repetitions: this._repetitions,
      repetitionsDelay: [this._repeatDelayMin, this._repeatDelayMax]
    };
  }
  _deserialize(data) {
    this._async = data.async;
    this._waitUntilFinished = data.waitUntilFinished;
    this._waitUntilFinishedDelay = data.waitUntilFinishedDelay;
    this._repetitions = data.repetitions;
    this._repeatDelayMin = data.repetitionsDelay[0];
    this._repeatDelayMax = data.repetitionsDelay[1];
    return this;
  }
  /**
   * Method overwritten by inheriting classes, which stores data or prepares data before the Sequence executes it (see EffectsSection)
   *
   * @protected
   */
  async _initialize() {
  }
  /**
   * Method overwritten by inheriting classes. Inheriting classes uses the following to apply traits to themselves:
   * - Object.assign(this.constructor.prototype, trait)
   *
   * @protected
   */
  _applyTraits() {
  }
  /**
   * Causes the section to be repeated n amount of times, with an optional delay. If given inRepeatDelayMin
   * and inRepeatDelayMax, a random repetition delay will be picked for every repetition
   *
   * @param {number} inRepetitions
   * @param {number} inRepeatDelayMin
   * @param {number} inRepeatDelayMax
   * @returns {Section} this
   */
  repeats(inRepetitions, inRepeatDelayMin = 0, inRepeatDelayMax) {
    if (!is_real_number(inRepetitions))
      throw this.sequence._customError(
        this,
        "repeats",
        "inRepetitions must be of type number"
      );
    if (!is_real_number(inRepeatDelayMin))
      throw this.sequence._customError(
        this,
        "repeats",
        "repeatDelayMin must be of type number"
      );
    if (inRepeatDelayMax && !is_real_number(inRepeatDelayMax)) {
      throw this.sequence._customError(
        this,
        "repeats",
        "repeatDelayMax must be of type number"
      );
    }
    this._repetitions = inRepetitions;
    this._repeatDelayMin = Math.min(
      inRepeatDelayMin,
      inRepeatDelayMax ?? inRepeatDelayMin
    );
    this._repeatDelayMax = Math.max(
      inRepeatDelayMin,
      inRepeatDelayMax ?? inRepeatDelayMin
    );
    return this;
  }
  /**
   * Causes the effect or sound to not play, and skip all delays, repetitions, waits, etc. If you pass a function,
   * the function should return something false-y if you do not want the effect or sound to play.
   *
   * @param {boolean|function} inCondition
   * @returns {Section} this
   */
  playIf(inCondition) {
    this._playIf = inCondition;
    return this;
  }
  /**
   * Causes the section to finish running before starting the next section.
   *
   * @param {number|boolean} [minDelay=0] minDelay
   * @param {number/null} [maxDelay=null] maxDelay
   * @returns {Section} this
   */
  waitUntilFinished(minDelay = 0, maxDelay = null) {
    if (minDelay === false) return this;
    if (!is_real_number(minDelay))
      throw this.sequence._customError(
        this,
        "waitUntilFinished",
        "minDelay must be of type number"
      );
    if (maxDelay !== null && !is_real_number(maxDelay))
      throw this.sequence._customError(
        this,
        "waitUntilFinished",
        "maxDelay must be of type number"
      );
    this._waitUntilFinished = true;
    this._waitUntilFinishedDelay = [
      Math.min(minDelay, maxDelay ?? minDelay),
      Math.max(minDelay, maxDelay ?? minDelay)
    ];
    return this;
  }
  /**
   * Causes each effect or sound to finish playing before the next one starts playing. This differs from
   * .waitUntilFinished() in the sense that this is for each repetition, whilst .waitUntilFinished() is
   * for the entire section.
   *
   * @returns {Section} this
   */
  async() {
    this._async = true;
    return this;
  }
  /**
   * Delays the effect or sound from being played for a set amount of milliseconds. If given a second number, a
   * random delay between the two numbers will be generated.
   *
   * @param {number} [msMin=1] minMs
   * @param {number} [msMax=1] maxMs
   * @returns {Section} this
   */
  delay(msMin, msMax) {
    if (!is_real_number(msMin))
      throw this.sequence._customError(
        this,
        "delay",
        "msMin must be of type number"
      );
    if (msMax && !is_real_number(msMax))
      throw this.sequence._customError(
        this,
        "delay",
        "msMax must be of type number"
      );
    this._delayMin = Math.min(msMin, msMax ?? msMin);
    this._delayMax = Math.max(msMin, msMax ?? msMin);
    return this;
  }
  /**
   * Overrides the duration of this section
   *
   * @param {number} inDuration
   * @returns {Section} this
   */
  duration(inDuration) {
    if (!is_real_number(inDuration))
      throw this.sequence._customError(
        this,
        "duration",
        "inDuration must be of type number"
      );
    this._duration = inDuration;
    return this;
  }
  /**
   * Applies a preset to the current section
   *
   * @param {string} presetName
   * @param {*} args
   * @returns {Section|FunctionSection|EffectSection|AnimationSection|SoundSection}
   */
  preset(presetName, ...args) {
    if (typeof presetName !== "string") {
      throw this.sequence._customError(
        this,
        "name",
        `inName must be of type string`
      );
    }
    const preset = SequencerPresets.get(presetName);
    if (!preset) {
      custom_warning(
        "Sequencer",
        `preset | Could not find preset with name "${presetName}"`
      );
      return this;
    }
    return preset(this, ...args);
  }
  /**
   * @protected
   */
  async _shouldPlay() {
    return is_function(this._playIf) ? await this._playIf() : this._playIf;
  }
  /**
   * @protected
   */
  _validateLocation(inLocation) {
    inLocation = validate_document(inLocation);
    if (typeof inLocation === "string") {
      inLocation = get_object_from_scene(inLocation) ?? inLocation;
    }
    if (typeof inLocation === "string") {
      inLocation = safe_str(inLocation);
    }
    return inLocation;
  }
  /**
   * @protected
   */
  async _execute() {
    if (!await this._shouldPlay()) {
      this.sectionStatus = CONSTANTS.STATUS.SKIPPED;
      return;
    }
    this._basicDelay = random_float_between(this._delayMin, this._delayMax);
    return new Promise(async (resolve) => {
      setTimeout(async () => {
        this.sectionStatus = CONSTANTS.STATUS.RUNNING;
        for (let i = 0; i < this._repetitions; i++) {
          if (get$1(this.sectionStatus) === CONSTANTS.STATUS.ABORTED) {
            resolve();
            return;
          }
          this._currentRepetition = i;
          this._repeatDelay = i !== this._repetitions - 1 ? random_float_between(
            this._repeatDelayMin,
            this._repeatDelayMax
          ) : 0;
          await this.preRun();
          if (this._shouldAsync) {
            await this.run();
          } else {
            this.run();
          }
          if (this._repetitions > 1 && i !== this._repetitions - 1) {
            await this._delayBetweenRepetitions();
          }
        }
        resolve();
      }, this._basicDelay);
    }).then(() => {
      this.sectionStatus = CONSTANTS.STATUS.COMPLETE;
    });
  }
  set sectionStatus(inStatus) {
    this._sectionStatus.update((currentStatus) => {
      if (currentStatus === CONSTANTS.STATUS.READY || currentStatus === CONSTANTS.STATUS.RUNNING && inStatus !== CONSTANTS.STATUS.ABORTED) {
        return inStatus;
      }
      return currentStatus;
    });
  }
  get sectionStatus() {
    return this._sectionStatus;
  }
  _abortSection() {
    this.sectionStatus = CONSTANTS.STATUS.ABORTED;
  }
  /**
   * @protected
   */
  async _delayBetweenRepetitions() {
    let self = this;
    return new Promise((resolve) => {
      setTimeout(resolve, self._repeatDelay);
    });
  }
}
class FunctionSection extends Section {
  constructor(inSequence, inFunc) {
    super(inSequence);
    if (!is_function(inFunc))
      throw this.sequence._customError(
        this,
        "create",
        "The given function needs to be an actual function"
      );
    this._func = inFunc;
    this._waitUntilFinished = inFunc.constructor.name === "AsyncFunction";
  }
  static niceName = "Function";
  /**
   * @returns {Promise<void>}
   */
  async run() {
    debug("Running function");
    await this._func();
  }
  /**
   * @returns {Promise}
   * @private
   */
  async _execute() {
    await this.run();
  }
}
const animation = {
  /**
   * Base properties
   */
  _animations: null,
  /**
   * Animates a property on the target of the animation.
   *
   * @param {string} inTarget
   * @param {string} inPropertyName
   * @param {object} inOptions
   *      @param {Number} inOptions.from       - a single number from which to animate
   *      @param {Number} inOptions.to         - a single number to which to animate
   *      @param {Number} inOptions.duration   - how long in ms the animation should take
   *      @param {Number} inOptions.delay      - inserts a delay in ms before the animation starts
   *      @param {String} inOptions.ease       - what type of easing the animation should use
   *      @param {Boolean} inOptions.gridUnits - if animating width or height, this will set it to work in the scene's grid units
   *      @param {Boolean} inOptions.screenSpace - if animating position, width, or height, this will set it to work in the screen's dimensions
   *      @param {Boolean} inOptions.fromEnd   - makes this animation play from the end, like fadeOut, scaleOut, etc
   *      @param {Boolean} inOptions.absolute  - makes this animation's values override the current value
   *
   * @returns this
   */
  animateProperty(inTarget, inPropertyName, inOptions = {}) {
    if (!this._animations) this._animations = [];
    const result = validateAnimation(
      inTarget,
      inPropertyName,
      inOptions
    );
    if (typeof result === "string") {
      throw this.sequence._customError(this, "animateProperty", result);
    }
    this._animations.push(result);
    return this;
  },
  /**
   * Loops a property between a set of values on the target
   *
   * @param {string} inTarget
   * @param {string} inPropertyName
   * @param {object} inOptions
   *      @param {Number} inOptions.from         - a single number from which to loop
   *      @param {Number} inOptions.to           - a single number to which to loop
   *      @param {Number} inOptions.values       - an array of values to loop between
   *      @param {Number} inOptions.duration     - how long in ms the loop should take
   *      @param {Number} inOptions.loops        - how many loops in total this animation should go through - if none are specified, the loop is indefinite
   *      @param {Number} inOptions.delay        - inserts a delay in ms before the animation starts
   *      @param {String} inOptions.ease         - what type of easing the animation should use
   *      @param {Boolean} inOptions.pingPong    - sets whether loop should interpolate to the first value after it reaches the first value, or simply set it to the first value
   *      @param {Boolean} inOptions.gridUnits   - if animating width or height, this will set it to work in the scene's grid units
   *      @param {Boolean} inOptions.screenSpace - if animating position, width, or height, this will set it to work in the screen's dimensions
   *      @param {Boolean} inOptions.absolute    - makes this animation's values override the current value
   *
   * @returns this
   */
  loopProperty(inTarget, inPropertyName, inOptions = {}) {
    if (!this._animations) this._animations = [];
    const result = validateLoopingAnimation(
      inTarget,
      inPropertyName,
      inOptions
    );
    if (typeof result === "string") {
      throw this.sequence._customError(this, "loopProperty", result);
    }
    this._animations.push(result);
    return this;
  }
};
const audio = {
  /**
   * Base properties
   */
  _volume: null,
  _channel: "interface",
  _fadeInAudio: null,
  _fadeOutAudio: null,
  /**
   * Sets the volume of the sound.
   *
   * @param {number} inVolume
   * @returns this
   */
  volume(inVolume) {
    if (!is_real_number(inVolume))
      throw this.sequence._customError(
        this,
        "volume",
        "inVolume must be of type number"
      );
    this._volume = Math.max(0, Math.min(1, inVolume));
    return this;
  },
  /**
   * Causes the animated section to fade in its audio (if any) when played
   *
   * @param {number} duration     How long the fade should be
   * @param {object} [options]    Additional options, such as easing and delay
   * @returns this
   */
  fadeInAudio(duration, options = {}) {
    if (typeof options !== "object")
      throw this.sequence._customError(
        this,
        "fadeInAudio",
        "options must be of type object"
      );
    options = foundry.utils.mergeObject(
      {
        ease: "linear",
        delay: 0
      },
      options
    );
    if (!is_real_number(duration))
      throw this.sequence._customError(
        this,
        "fadeInAudio",
        "duration must be of type number"
      );
    if (typeof options.ease !== "string")
      throw this.sequence._customError(
        this,
        "fadeInAudio",
        "options.ease must be of type string"
      );
    if (!is_real_number(options.delay))
      throw this.sequence._customError(
        this,
        "fadeInAudio",
        "options.delay must be of type number"
      );
    this._fadeInAudio = {
      duration,
      ease: options.ease,
      delay: options.delay
    };
    return this;
  },
  /**
   * Causes the audio to fade out at the end of the animated section's duration
   *
   * @param {number} duration     How long the fade should be
   * @param {object} [options]    Additional options, such as easing and delay
   * @returns this
   */
  fadeOutAudio(duration, options = {}) {
    if (typeof options !== "object")
      throw this.sequence._customError(
        this,
        "fadeOutAudio",
        "options must be of type object"
      );
    options = foundry.utils.mergeObject(
      {
        ease: "linear",
        delay: 0
      },
      options
    );
    if (!is_real_number(duration))
      throw this.sequence._customError(
        this,
        "fadeOutAudio",
        "duration must be of type number"
      );
    if (typeof options.ease !== "string")
      throw this.sequence._customError(
        this,
        "fadeOutAudio",
        "ease must be of type string"
      );
    if (!is_real_number(options.delay))
      throw this.sequence._customError(
        this,
        "fadeOutAudio",
        "delay must be of type number"
      );
    this._fadeOutAudio = {
      duration,
      ease: options.ease,
      delay: options.delay
    };
    return this;
  }
};
const elevation = {
  _elevation: null,
  /**
   * Sets the section's elevation, either as a single Z value or as an
   * elevation range `[bottom, top]`. The range form makes the section
   * visible on every scene level it reaches on Foundry v14+.
   *
   * @param {number|[number, number]} inElevation
   * @param {Object} inOptions
   * @param {boolean} [inOptions.absolute=false]
   * @param {boolean} [inOptions.topInclusive=false] Range form only.
   *   When true, the top value is part of the range, matching Foundry
   *   Region `elevation.topInclusive`. By default an effect whose top
   *   equals the next level's bottom sits just below that level rather
   *   than reaching into it.
   * @returns {this}
   */
  elevation(inElevation, inOptions = {}) {
    let bottom;
    let top;
    if (typeof inElevation === "number") {
      bottom = inElevation;
    } else if (Array.isArray(inElevation) && inElevation.length === 2 && inElevation.every((n) => typeof n === "number")) {
      bottom = Math.min(inElevation[0], inElevation[1]);
      top = Math.max(inElevation[0], inElevation[1]);
    } else {
      throw this.sequence._customError(
        this,
        "elevation",
        "inElevation must be a number or a [bottom, top] array of two numbers"
      );
    }
    if (typeof inOptions !== "object")
      throw this.sequence._customError(
        this,
        "elevation",
        `inOptions must be of type object`
      );
    inOptions = foundry.utils.mergeObject(
      {
        absolute: false,
        topInclusive: false
      },
      inOptions
    );
    if (typeof inOptions.absolute !== "boolean")
      throw this.sequence._customError(
        this,
        "elevation",
        "inOptions.absolute must be of type boolean"
      );
    if (typeof inOptions.topInclusive !== "boolean")
      throw this.sequence._customError(
        this,
        "elevation",
        "inOptions.topInclusive must be of type boolean"
      );
    this._elevation = {
      elevation: bottom,
      top,
      absolute: inOptions.absolute,
      topInclusive: inOptions.topInclusive
    };
    return this;
  }
};
const files = {
  /**
   * Base properties
   */
  _file: "",
  _fileOptions: false,
  _baseFolder: "",
  _mustache: null,
  /**
   * Defines the base folder that will prepend to the file path. This is mainly just useful to make the file
   * path easier to manage.
   *
   * @param {string} inBaseFolder
   * @returns this
   */
  baseFolder(inBaseFolder) {
    if (typeof inBaseFolder !== "string")
      throw this.sequence._customError(
        this,
        "baseFolder",
        "inBaseFolder must be of type string"
      );
    this._baseFolder = inBaseFolder + (inBaseFolder.endsWith("/") ? "" : "/");
    return this;
  },
  /**
   * Sets the Mustache of the filepath. This is applied after the randomization of the filepath, if available.
   *
   * @param {object} inMustache
   * @returns this
   */
  setMustache(inMustache) {
    if (typeof inMustache !== "object")
      throw this.sequence._customError(
        this,
        "setMustache",
        "inMustache must be of type object"
      );
    this._mustache = inMustache;
    return this;
  },
  async _determineFile(inFile) {
    if (!Array.isArray(inFile) && typeof inFile === "object") {
      return this._validateCustomRange(inFile);
    }
    if (Array.isArray(inFile))
      inFile = random_array_element(inFile, { recurse: true });
    inFile = this._applyMustache(inFile);
    if (Sequencer.Database.entryExists(inFile)) {
      return this._determineDatabaseFile(inFile);
    }
    const determinedFile = await this._processFile(inFile);
    return { file: determinedFile, forcedIndex: false, customRange: false };
  },
  async _processFile(inFile) {
    inFile = this._applyMustache(inFile);
    inFile = this._applyBaseFolder(inFile);
    inFile = await this._applyWildcard(inFile);
    if (Array.isArray(inFile))
      inFile = random_array_element(inFile, { recurse: true });
    return inFile;
  },
  async _validateCustomRange(inFile) {
    const finalFiles = {};
    const validRanges = Object.keys(SequencerFileRangeFind.ftToDistanceMap);
    for (const [range, rangeFile] of Object.entries(inFile)) {
      if (!validRanges.includes(range)) {
        throw this.sequence._customError(
          this,
          "file",
          `a file-distance key map must only contain the following keys: ${validRanges.join(
            ", "
          )}`
        );
      }
      finalFiles[range] = await this._processFile(rangeFile);
    }
    return { file: finalFiles, forcedIndex: false, customRange: true };
  },
  _determineDatabaseFile(inFile) {
    const entries = Sequencer.Database.getEntry(inFile);
    const entry = Array.isArray(entries) ? random_array_element(entries) : entries;
    const match = inFile.match(/(\d+)$/);
    return {
      file: entry,
      forcedIndex: match ? Number(match[1]) : false,
      customRange: false
    };
  },
  _applyBaseFolder(inFile) {
    if (Array.isArray(inFile))
      return inFile.map((file) => this._applyBaseFolder(file));
    return inFile.startsWith(this._baseFolder) ? inFile : this._baseFolder + inFile;
  },
  _applyMustache(inFile) {
    if (!this._mustache) return inFile;
    let template = Handlebars.compile(inFile);
    return template(this._mustache);
  },
  async _applyWildcard(inFile) {
    if (!inFile.includes("*")) return inFile;
    if (Array.isArray(inFile))
      return inFile.map(async (file) => await this._applyWildcard(file));
    inFile = this._applyBaseFolder(inFile);
    return getFiles(inFile, {
      applyWildCard: true,
      softFail: this.sequence.softFail
    });
  }
};
const levels = {
  _levels: null,
  /**
   * Restricts this section to one or more scene levels on Foundry v14+.
   * Accepts a level id, level name, Level document, or an array mixing
   * any of those. Pass null to clear. Doesn't do anything on older
   * Foundry versions.
   *
   * Strings (ids or names) are resolved against the scene the section
   * is playing in, not the scene that was viewed when the section was
   * built. Level documents resolve to their id at build time.
   *
   * @param {string|foundry.documents.Level|Array<string|foundry.documents.Level>|null} inLevels
   * @returns {this}
   */
  onLevels(inLevels) {
    if (!CONSTANTS.IS_V14) return this;
    if (inLevels === null || inLevels === void 0) {
      this._levels = null;
      return this;
    }
    const arr = Array.isArray(inLevels) ? inLevels : [inLevels];
    const entries = arr.map((item) => {
      if (item && typeof item === "object" && item.documentName === "Level" && typeof item.id === "string") return item.id;
      if (typeof item === "string") return item;
      throw this.sequence._customError(
        this,
        "onLevels",
        "expected a level id, level name, Level document, or an array of any combination"
      );
    });
    this._levels = entries.length ? entries : null;
    return this;
  }
};
const moves = {
  /**
   * Base properties
   */
  _moveTowards: null,
  _moveSpeed: null,
  /**
   * Sets the location to move the target object to
   *
   * @param {object|string} inTarget
   * @param {object} options
   * @returns this
   */
  moveTowards(inTarget, options = {}) {
    options = foundry.utils.mergeObject(
      {
        ease: "linear",
        delay: 0,
        rotate: true,
        cacheLocation: false
      },
      options
    );
    if (typeof options.ease !== "string")
      throw this.sequence._customError(
        this,
        "moveTowards",
        "options.ease must be of type string"
      );
    if (!is_real_number(options.delay))
      throw this.sequence._customError(
        this,
        "moveTowards",
        "options.delay must be of type number"
      );
    if (typeof options.rotate !== "boolean")
      throw this.sequence._customError(
        this,
        "moveTowards",
        "options.rotate must be of type boolean"
      );
    if (typeof options.cacheLocation !== "boolean")
      throw this.sequence._customError(
        this,
        "moveTowards",
        "options.cacheLocation must be of type boolean"
      );
    options.target = this._validateLocation(inTarget);
    if (!options.target)
      throw this.sequence._customError(
        this,
        "moveTowards",
        "could not find position of given object"
      );
    options.target = options.cacheLocation ? get_object_position(options.target, { measure: true }) : options.target;
    this._moveTowards = options;
    return this;
  },
  /**
   * Sets the speed (pixels per frame) to move the target object
   *
   * @param {number} inSpeed
   * @returns this
   */
  moveSpeed(inSpeed) {
    if (!is_real_number(inSpeed))
      throw this.sequence._customError(
        this,
        "moveSpeed",
        "inSpeed must be of type number"
      );
    this._moveSpeed = inSpeed;
    return this;
  }
};
const opacity = {
  /**
   * Base properties
   */
  _opacity: null,
  _fadeIn: null,
  _fadeOut: null,
  /**
   * Sets the opacity of the effect. If used with ._fadeIn() and/or ._fadeOut(), this defines what the effect will fade to/from
   *
   * @param {number} inOpacity
   * @returns this
   */
  opacity(inOpacity) {
    if (!is_real_number(inOpacity))
      throw this.sequence._customError(
        this,
        "opacity",
        "inOpacity must be of type number"
      );
    this._opacity = inOpacity;
    return this;
  },
  /**
   * Causes the effect to fade in when played
   *
   * @param {number} duration     How long the fade should be
   * @param {object} [options]    Additional options, such as easing and delay
   * @returns this
   */
  fadeIn(duration, options = {}) {
    if (typeof options !== "object")
      throw this.sequence._customError(
        this,
        "fadeIn",
        "options must be of type object"
      );
    options = foundry.utils.mergeObject(
      {
        ease: "linear",
        delay: 0
      },
      options
    );
    if (!is_real_number(duration))
      throw this.sequence._customError(
        this,
        "fadeIn",
        "duration must be of type number"
      );
    if (typeof options.ease !== "string")
      throw this.sequence._customError(
        this,
        "fadeIn",
        "options.ease must be of type string"
      );
    if (!is_real_number(options.delay))
      throw this.sequence._customError(
        this,
        "fadeIn",
        "options.delay must be of type number"
      );
    this._fadeIn = {
      duration,
      ease: options.ease,
      delay: options.delay
    };
    return this;
  },
  /**
   * Causes the effect to fade out at the end of the effect's duration
   *
   * @param {number} duration     How long the fade should be
   * @param {object} [options]    Additional options, such as easing and delay
   * @returns this
   */
  fadeOut(duration, options = {}) {
    if (typeof options !== "object")
      throw this.sequence._customError(
        this,
        "fadeOut",
        "options must be of type object"
      );
    options = foundry.utils.mergeObject(
      {
        ease: "linear",
        delay: 0
      },
      options
    );
    if (!is_real_number(duration))
      throw this.sequence._customError(
        this,
        "fadeOut",
        "duration must be of type number"
      );
    if (typeof options.ease !== "string")
      throw this.sequence._customError(
        this,
        "fadeOut",
        "ease must be of type string"
      );
    if (!is_real_number(options.delay))
      throw this.sequence._customError(
        this,
        "fadeOut",
        "delay must be of type number"
      );
    this._fadeOut = {
      duration,
      ease: options.ease,
      delay: options.delay
    };
    return this;
  }
};
const rotation = {
  /**
   * Base properties
   */
  _angle: null,
  _rotateIn: null,
  _rotateOut: null,
  _randomRotation: null,
  _rotateTowards: null,
  /**
   * The object gets a random rotation, which means it should not be used with .stretchTo()
   *
   * @param {boolean} [inBool=true] inBool
   * @returns this
   */
  randomRotation(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "randomRotation",
        "inBool must be of type boolean"
      );
    this._randomRotation = inBool;
    return this;
  },
  /**
   * Sets the rotation of the object, which is added on top of the calculated rotation after .rotateTowards() or .randomRotation()
   *
   * @param {number} inRotation
   * @returns this
   */
  rotate(inRotation) {
    if (!is_real_number(inRotation))
      throw this.sequence._customError(
        this,
        "opacity",
        "inRotation must be of type number"
      );
    this._angle = inRotation;
    return this;
  },
  /**
   *  Causes the object to rotate when it starts playing
   *
   * @param {number} degrees
   * @param {number} duration
   * @param {object} [options] options
   * @returns this
   */
  rotateIn(degrees, duration, options = {}) {
    if (typeof options !== "object")
      throw this.sequence._customError(
        this,
        "rotateIn",
        "options must be of type object"
      );
    options = foundry.utils.mergeObject(
      {
        ease: "linear",
        delay: 0
      },
      options
    );
    if (!is_real_number(degrees))
      throw this.sequence._customError(
        this,
        "rotateOut",
        "degrees must be of type number"
      );
    if (!is_real_number(duration))
      throw this.sequence._customError(
        this,
        "rotateOut",
        "duration must be of type number"
      );
    if (typeof options.ease !== "string")
      throw this.sequence._customError(
        this,
        "rotateIn",
        "options.ease must be of type string"
      );
    if (!is_real_number(options.delay))
      throw this.sequence._customError(
        this,
        "rotateIn",
        "options.delay must be of type number"
      );
    this._rotateIn = {
      value: degrees,
      duration,
      ease: options.ease,
      delay: options.delay
    };
    return this;
  },
  /**
   *  Causes the object to rotate at the end of the effect's duration
   *
   * @param {number} degrees
   * @param {number} duration
   * @param {object} [options] options
   * @returns this
   */
  rotateOut(degrees, duration, options = {}) {
    if (typeof options !== "object")
      throw this.sequence._customError(
        this,
        "rotateOut",
        "options must be of type object"
      );
    options = foundry.utils.mergeObject(
      {
        ease: "linear",
        delay: 0
      },
      options
    );
    if (!is_real_number(degrees))
      throw this.sequence._customError(
        this,
        "rotateOut",
        "degrees must be of type number"
      );
    if (!is_real_number(duration))
      throw this.sequence._customError(
        this,
        "rotateOut",
        "duration must be of type number"
      );
    if (typeof options.ease !== "string")
      throw this.sequence._customError(
        this,
        "rotateOut",
        "options.ease must be of type string"
      );
    if (!is_real_number(options.delay))
      throw this.sequence._customError(
        this,
        "rotateOut",
        "options.delay must be of type number"
      );
    this._rotateOut = {
      value: degrees,
      duration,
      ease: options.ease,
      delay: options.delay
    };
    return this;
  }
};
const scale = {
  _scaleMin: null,
  _scaleMax: null,
  _scaleIn: null,
  _scaleOut: null,
  /**
   *  A method that can take the following:
   *  - A number to set the scale uniformly
   *  - An object with x and y for non-uniform scaling
   *  - Two numbers which the Sequencer will randomly pick a uniform scale between
   *
   * @param {number|object} inScaleMin
   * @param {number} [inScaleMax] inScaleMax
   * @returns this
   */
  scale(inScaleMin, inScaleMax) {
    if (!is_real_number(inScaleMin) && typeof inScaleMin !== "object")
      throw this.sequence._customError(
        this,
        "scale",
        "inScale must be of type number or object"
      );
    if (is_real_number(inScaleMin)) {
      if (inScaleMax && !is_real_number(inScaleMax)) {
        throw this.sequence._customError(
          this,
          "scale",
          "if inScaleMin is a number, inScaleMax must also be of type number"
        );
      }
    }
    this._scaleMin = inScaleMin;
    this._scaleMax = inScaleMax ?? false;
    return this;
  },
  /**
   *  Causes the effect to scale when it starts playing
   *
   * @param {number|object} scale
   * @param {number} duration
   * @param {object} [options] options
   * @returns this
   */
  scaleIn(scale2, duration, options = {}) {
    if (typeof options !== "object")
      throw this.sequence._customError(
        this,
        "scaleIn",
        "options must be of type object"
      );
    options = foundry.utils.mergeObject(
      {
        ease: "linear",
        delay: 0
      },
      options
    );
    if (!is_real_number(duration))
      throw this.sequence._customError(
        this,
        "scaleIn",
        "duration must be of type number"
      );
    if (!is_real_number(scale2) && typeof scale2 !== "object")
      throw this.sequence._customError(
        this,
        "scaleIn",
        "scale must be of type number or object"
      );
    if (typeof options.ease !== "string")
      throw this.sequence._customError(
        this,
        "scaleIn",
        "options.ease must be of type string"
      );
    if (!is_real_number(options.delay))
      throw this.sequence._customError(
        this,
        "scaleIn",
        "options.delay must be of type number"
      );
    this._scaleIn = {
      value: scale2,
      duration,
      ease: options.ease,
      delay: options.delay
    };
    return this;
  },
  /**
   *  Causes the effect to scale at the end of the effect's duration
   *
   * @param {number|object} scale
   * @param {number} duration
   * @param {object} [options] options
   * @returns this
   */
  scaleOut(scale2, duration, options = {}) {
    if (typeof options !== "object")
      throw this.sequence._customError(
        this,
        "scaleOut",
        "options must be of type object"
      );
    options = foundry.utils.mergeObject(
      {
        ease: "linear",
        delay: 0
      },
      options
    );
    if (!is_real_number(duration))
      throw this.sequence._customError(
        this,
        "scaleOut",
        "duration must be of type number"
      );
    if (!is_real_number(scale2) && typeof scale2 !== "object")
      throw this.sequence._customError(
        this,
        "scaleOut",
        "scale must be of type number or object"
      );
    if (typeof options.ease !== "string")
      throw this.sequence._customError(
        this,
        "scaleOut",
        "options.ease must be of type string"
      );
    if (!is_real_number(options.delay))
      throw this.sequence._customError(
        this,
        "scaleOut",
        "options.delay must be of type number"
      );
    this._scaleOut = {
      value: scale2,
      duration,
      ease: options.ease,
      delay: options.delay
    };
    return this;
  }
};
const time = {
  _hasTime: true,
  _isRange: false,
  _startTime: null,
  _startPerc: null,
  _endTime: null,
  _endPerc: null,
  /**
   * Sets the start and end time of the section, playing only that range
   *
   * @param {number} inMsStart
   * @param {number} inMsEnd
   * @returns this
   */
  timeRange(inMsStart, inMsEnd) {
    if (!is_real_number(inMsStart))
      throw this.sequence._customError(
        this,
        "timeRange",
        "inMsStart must be of type number"
      );
    if (!is_real_number(inMsEnd))
      throw this.sequence._customError(
        this,
        "timeRange",
        "inMsEnd must be of type number"
      );
    this._startTime = inMsStart;
    this._endTime = inMsEnd;
    this._isRange = true;
    return this;
  },
  /**
   * Sets the start time of the section.
   *
   * @param {number} inMs
   * @returns this
   */
  startTime(inMs) {
    if (!is_real_number(inMs))
      throw this.sequence._customError(
        this,
        "startTime",
        "inMs must be of type number"
      );
    this._startTime = inMs;
    this._startPerc = false;
    this._isRange = false;
    return this;
  },
  /**
   * Sets the start time of the section based on a percentage from its total duration.
   *
   * @param {number} inPercentage
   * @returns this
   */
  startTimePerc(inPercentage) {
    if (!is_real_number(inPercentage))
      throw this.sequence._customError(
        this,
        "startTimePerc",
        "inPercentage must be of type number"
      );
    this._startTime = inPercentage;
    this._startPerc = true;
    this._isRange = false;
    return this;
  },
  /**
   * Sets the ending time of the section (from the end).
   *
   * @param {number} inMs
   * @returns this
   */
  endTime(inMs) {
    if (!is_real_number(inMs))
      throw this.sequence._customError(
        this,
        "endTime",
        "inMs must be of type number"
      );
    this._endTime = inMs;
    this._endPerc = false;
    this._isRange = false;
    return this;
  },
  /**
   * Sets the ending time of the section based on a percentage from the total duration.
   *
   * @param {number} inPercentage
   * @returns this
   */
  endTimePerc(inPercentage) {
    if (!is_real_number(inPercentage))
      throw this.sequence._customError(
        this,
        "endTimePerc",
        "inPercentage must be of type number"
      );
    this._endTime = inPercentage;
    this._endPerc = true;
    this._isRange = false;
    return this;
  }
};
const users = {
  _users: null,
  _addUser(inUser) {
    if (!this._users) this._users = [];
    if (inUser instanceof User) inUser = inUser.id;
    if (typeof inUser !== "string")
      throw this.sequence._customError(
        this,
        "_addUser",
        "inUser must be a User, a user ID, or a user name"
      );
    if (!game.users.has(inUser)) {
      if (game.users.getName(inUser)) {
        inUser = game.users.getName(inUser).id;
      } else {
        throw this.sequence._customError(
          this,
          "_addUser",
          `user with id or name "${inUser}" does not exist!`
        );
      }
    }
    if (!this._users.includes(inUser)) this._users.push(inUser);
  },
  _deleteUser(inUser) {
    if (!this._users) this._users = [];
    if (this._users.includes(inUser)) {
      let index2 = this._users.indexOf(inUser);
      this._users.splice(index2, 1);
    }
  },
  /**
   * Causes section to be executed only locally, and not push to other connected clients.
   *
   * @param {boolean} inLocally
   * @returns this
   */
  locally(inLocally = true) {
    if (inLocally) this._addUser(game.userId);
    else this._deleteUser(game.userId);
    return this;
  },
  /**
   * Causes the section to be executed for only a set of users.
   *
   * @param {string|User|array<string|User>} inUsers
   * @returns this
   */
  forUsers(inUsers) {
    if (!Array.isArray(inUsers)) inUsers = [inUsers];
    inUsers.forEach((u) => this._addUser(u));
    return this;
  }
};
const filter = {
  _filters: null,
  _addFilter(inFilterName, inData, inName = false) {
    if (!this._filters) this._filters = [];
    this._filters.push({
      className: inFilterName,
      name: inName,
      data: inData
    });
  },
  _testFilter(inFilterName, inData) {
    let filter2 = new filters[inFilterName](inData);
    if (!filter2.isValid)
      throw this.sequence._customError(
        this,
        "filter",
        `Could not create ${inFilterName} filter - data is malformed!`
      );
  },
  filter(inFilterName, inData = {}, inName = "") {
    if (typeof inFilterName !== "string")
      throw this.sequence._customError(
        this,
        "filter",
        `inFilterName must be of type string`
      );
    if (!Object.keys(filters).includes(inFilterName))
      throw this.sequence._customError(
        this,
        "filter",
        `"${inFilterName}" does not exist`
      );
    this._testFilter(inFilterName, inData);
    this._addFilter(inFilterName, inData, inName);
    return this;
  }
};
const tint = {
  _tint: null,
  /**
   * Tints the target of this section by the color given to the
   *
   * @param {number|string} inColor
   * @returns this
   */
  tint(inColor) {
    if (!is_real_number(inColor) && typeof inColor !== "string")
      throw this.sequence._customError(
        this,
        "tint",
        `inColor must be of type string (hexadecimal) or number (decimal)!`
      );
    this._tint = parseColor(inColor);
    return this;
  }
};
const BLEND_MODE_NAMES = new Map(
  Object.entries(CONSTANTS.BLEND_MODES).map(([name2, value]) => [value, name2])
);
function normalizeBlendModeName(name2) {
  return String(name2).toLowerCase().replace(/[\s\-]+/g, "_").replace(/_+/g, "_").trim();
}
function resolveBlendMode(value) {
  if (is_real_number(value)) {
    const knownValues = Object.values(CONSTANTS.BLEND_MODES);
    return knownValues.includes(value) ? value : null;
  }
  if (typeof value !== "string") return null;
  let key = normalizeBlendModeName(value);
  const aliases = {
    none: "none",
    dodge: "color_dodge",
    burn: "color_burn",
    hardlight: "hard_light",
    softlight: "soft_light",
    colordodge: "color_dodge",
    colorburn: "color_burn",
    normalnpm: "normal_npm",
    addnpm: "add_npm",
    screennpm: "screen_npm"
  };
  const compact = key.replace(/_/g, "");
  if (aliases[compact]) key = aliases[compact];
  const resolved = CONSTANTS.BLEND_MODES[key];
  return is_real_number(resolved) ? resolved : null;
}
const blendMode = {
  _blendMode: null,
  /**
   * Sets the blend mode used when compositing this effect onto the canvas.
   *
   * Accepts either a string name (case-insensitive, hyphens/underscores/spaces
   * tolerated) or a numeric PIXI.BLEND_MODES constant.
   *
   * "normal", "add", "multiply", "screen", "subtract", and "erase" render
   * correctly on the GPU's blend unit. The "advanced" modes (overlay,
   * soft-light, hard-light, color-dodge, color-burn, darken, lighten,
   * difference, exclusion, hue, saturation, color, luminosity) are exposed
   * by name in PIXI v7 but are not implemented by the renderer; requesting
   * them logs a one-time warning and falls back to normal blending.
   *
   * @example
   *   .blendMode("multiply")
   *   .blendMode("add")
   *   .blendMode(PIXI.BLEND_MODES.SCREEN)
   *
   * @param {number|string} mode
   * @returns this
   */
  blendMode(mode) {
    if (!is_real_number(mode) && typeof mode !== "string") {
      throw this.sequence._customError(
        this,
        "blendMode",
        'mode must be a string (e.g. "multiply") or a number (PIXI.BLEND_MODES constant)'
      );
    }
    const resolved = resolveBlendMode(mode);
    if (resolved === null) {
      const valid = Object.keys(CONSTANTS.BLEND_MODES).join(", ");
      throw this.sequence._customError(
        this,
        "blendMode",
        `unknown blend mode "${mode}". Valid modes are: ${valid}`
      );
    }
    if (CONSTANTS.UNSUPPORTED_BLEND_MODES.has(resolved)) {
      const name2 = BLEND_MODE_NAMES.get(resolved) ?? String(mode);
      throttled_custom_warning(
        "Sequencer",
        `Effect | .blendMode("${name2}") is not supported by PIXI v7 and will render as normal blending. Supported modes: normal, add, multiply, screen, subtract, erase.`
      );
    }
    this._blendMode = resolved;
    return this;
  }
};
const location = {
  /**
   * Base properties
   */
  _source: null,
  _offset: null,
  _randomOffset: null,
  /**
   *  A smart method that can take a reference to an object, or a direct on the canvas to play the effect at,
   *  or a string reference (see .name())
   *
   * @param {Object|String} inLocation
   * @param {Object} inOptions
   * @returns {EffectSection}
   */
  atLocation(inLocation, inOptions = {}) {
    if (!inLocation || !(typeof inLocation === "object" || typeof inLocation === "string")) {
      throw this.sequence._customError(
        this,
        "atLocation",
        `inLocation is invalid, and must be of type of object, string, placeable object, or document`
      );
    }
    if (typeof inOptions !== "object")
      throw this.sequence._customError(
        this,
        "atLocation",
        `inOptions must be of type object`
      );
    inOptions = foundry.utils.mergeObject(
      {
        cacheLocation: false,
        offset: false,
        randomOffset: false,
        gridUnits: false,
        local: false
      },
      inOptions
    );
    inLocation = this._validateLocation(inLocation);
    if (inLocation === void 0)
      throw this.sequence._customError(
        this,
        "atLocation",
        "could not find position of given object"
      );
    if (typeof inOptions.cacheLocation !== "boolean")
      throw this.sequence._customError(
        this,
        "atLocation",
        "inOptions.cacheLocation must be of type boolean"
      );
    if (!(typeof inOptions.randomOffset === "boolean" || is_real_number(inOptions.randomOffset)))
      throw this.sequence._customError(
        this,
        "atLocation",
        "inOptions.randomOffset must be of type boolean or number"
      );
    if (inOptions.offset) {
      const offsetData = this._validateOffset(
        "atLocation",
        inOptions.offset,
        inOptions
      );
      this._offset = {
        source: offsetData,
        target: this._offset?.target ?? false
      };
    }
    this._randomOffset = {
      source: inOptions.randomOffset,
      target: this._randomOffset?.target ?? false
    };
    this._source = inOptions.cacheLocation && typeof inLocation !== "string" ? get_object_canvas_data(inLocation, { uuid: false }) : inLocation;
    return this;
  }
};
const offset = {
  _offset: null,
  _randomOffset: null,
  _validateOffset(functionName, inOffset, inOptions = {}) {
    inOffset = foundry.utils.mergeObject(
      {
        x: 0,
        y: 0
      },
      inOffset
    );
    inOptions = foundry.utils.mergeObject(
      {
        gridUnits: false,
        local: false
      },
      inOptions
    );
    if (typeof inOptions.gridUnits !== "boolean")
      throw this.sequence._customError(
        this,
        functionName,
        "inOptions.gridUnits must be of type boolean"
      );
    if (typeof inOptions.local !== "boolean")
      throw this.sequence._customError(
        this,
        functionName,
        "inOptions.local must be of type boolean"
      );
    if (!is_real_number(inOffset.x))
      throw this.sequence._customError(
        this,
        functionName,
        `inOffset.x must be of type number!`
      );
    if (!is_real_number(inOffset.y))
      throw this.sequence._customError(
        this,
        functionName,
        `inOffset.y must be of type number!`
      );
    return {
      ...inOffset,
      ...inOptions
    };
  }
};
const text = {
  _text: null,
  /**
   *  Creates a text element, attached to the sprite. The options for the text are available here:
   *  https://pixijs.io/pixi-text-style/
   *
   * @param {String} inText
   * @param {Object} inOptions
   * @returns {EffectSection}
   */
  text(inText, inOptions = {}) {
    if (typeof inText !== "string")
      throw this.sequence._customError(
        this,
        "text",
        "inText must be of type string"
      );
    this._text = foundry.utils.mergeObject(
      {
        text: inText
      },
      inOptions
    );
    return this;
  }
};
const name = {
  _name: null,
  _origin: null,
  /**
   * Causes the effect or sound to be named, which affects them differently. In the case of effects, names can be used
   * in the place of 
   *
   * @param {String} inName
   * @returns {EffectSection}
   */
  name(inName) {
    if (typeof inName !== "string")
      throw this.sequence._customError(
        this,
        "name",
        "inName must be of type string"
      );
    this._name = safe_str(inName);
    return this;
  },
  /**
   *  This is for adding extra information to an sound, like the origin of the sound in the form of the item's uuid.
   *  The method accepts a string or a Document that has an UUID.
   *
   * @param {string|document} inOrigin
   * @returns {Section}
   */
  origin(inOrigin) {
    inOrigin = validate_document(inOrigin);
    if (inOrigin instanceof foundry.abstract.Document) {
      inOrigin = inOrigin?.uuid;
      if (!inOrigin)
        throw this.sequence._customError(
          this,
          "origin",
          "could not find the UUID for the given Document"
        );
    }
    if (typeof inOrigin !== "string")
      throw this.sequence._customError(
        this,
        "origin",
        "inOrigin must be of type string"
      );
    this._origin = inOrigin;
    return this;
  }
};
const traits = {
  animation,
  audio,
  elevation,
  files,
  levels,
  moves,
  opacity,
  rotation,
  scale,
  time,
  users,
  filter,
  tint,
  blendMode,
  location,
  offset,
  text,
  name
};
class CrosshairsDocument extends MeasuredTemplateDocument {
  constructor(config = {}, context = {}, crosshair = {}, callbacks = {}) {
    if (config.t === CONST.MEASURED_TEMPLATE_TYPES.RECTANGLE && !(config.direction % 90)) {
      config.direction = 45;
    }
    super(config, context);
    this.crosshair = foundry.utils.mergeObject(CrosshairsDocument.defaultConfig, crosshair);
    this.callbacks = callbacks;
    this.originalConfig = foundry.utils.deepClone(config);
  }
  crosshair = {};
  #layer = null;
  cachedPosition = null;
  originalConfig = {};
  static get placeableClass() {
    return CrosshairsPlaceable;
  }
  static get defaultConfig() {
    return {
      borderAlpha: 0.75,
      fillAlpha: 0.25,
      textureAlpha: 0.5,
      textureScale: 1,
      gridHighlight: true,
      icon: {
        texture: "",
        borderVisible: false
      },
      snap: {
        position: CONST.GRID_SNAPPING_MODES.CENTER,
        size: CONST.GRID_SNAPPING_MODES.CENTER,
        resolution: 1,
        direction: 0
      },
      lockDrag: true,
      distanceMin: null,
      distanceMax: null,
      label: {
        text: "",
        dx: 0,
        dy: 0
      },
      location: {
        obj: null,
        limitMinRange: null,
        limitMaxRange: null,
        showRange: false,
        displayRangePoly: false,
        rangePolyFillColor: null,
        rangePolyLineColor: null,
        rangePolyFillAlpha: null,
        rangePolyLineAlpha: null,
        lockToEdge: false,
        lockToEdgeDirection: false,
        offset: { x: 0, y: 0 },
        wallBehavior: CONSTANTS.PLACEMENT_RESTRICTIONS.ANYWHERE
      },
      lockManualRotation: false
    };
  }
  getOrientation() {
    this.cachedPosition = !this.object || this.object?.destroyed ? this.cachedPosition : {
      source: get_object_canvas_data(this.object, { uuid: false }),
      target: this.t === CONST.MEASURED_TEMPLATE_TYPES.CONE || this.t === CONST.MEASURED_TEMPLATE_TYPES.RAY ? get_object_canvas_data(this.object, { measure: true, uuid: false }) : null
    };
    return this.cachedPosition;
  }
  get documentName() {
    return "CrosshairsDocument";
  }
  get layer() {
    if (this.#layer) return this.#layer;
    const create = (doc) => new this.constructor.placeableClass(doc);
    const sink = {
      get(target, prop2) {
        switch (prop2) {
          case "createObject":
            return create;
          default:
            return target[prop2];
        }
      }
    };
    this.#layer = new Proxy(canvas.activeLayer, sink);
    return this.#layer;
  }
  get isEmbedded() {
    return !!this.parent;
  }
  getCrosshairShape() {
    const { t, distance, direction, angle, width } = this;
    switch (t) {
      case "circle":
        return foundry.canvas.placeables.MeasuredTemplate.getCircleShape(distance);
      case "cone":
        return foundry.canvas.placeables.MeasuredTemplate.getConeShape(distance, direction, angle);
      case "rect":
        return foundry.canvas.placeables.MeasuredTemplate.getRectShape(distance, direction);
      case "ray":
        return foundry.canvas.placeables.MeasuredTemplate.getRayShape(distance, direction, width);
    }
  }
  token = {};
  prepareDerivedData() {
    super.prepareDerivedData();
    const gridUnits = this.distance / this.parent.grid.distance;
    this.radius = gridUnits * this.parent.grid.size;
    this.token = {};
    this.token.width = gridUnits * 2;
    this.token.height = gridUnits * 2;
    switch (this.t) {
      case "rect":
        this.token.x = this.x;
        this.token.y = this.y;
        break;
      case "circle":
      default:
        this.token.x = this.x - this.radius;
        this.token.y = this.y - this.radius;
    }
  }
  show() {
    this._destroyed = false;
    this.#layer = null;
    return this.object.show();
  }
}
class EffectSection extends Section {
  constructor(inSequence, inFile = "") {
    super(inSequence);
    this._deserializedData = null;
    this._file = inFile;
    this._text = null;
    this._source = null;
    this._stretchTo = null;
    this._attachTo = null;
    this._copySprite = null;
    this._anchor = null;
    this._spriteAnchor = null;
    this._randomOffset = null;
    this._missed = null;
    this._private = null;
    this._randomMirrorX = null;
    this._randomMirrorY = null;
    this._mirrorX = null;
    this._mirrorY = null;
    this._playbackRate = null;
    this._template = null;
    this._overrides = [];
    this._zIndex = null;
    this._offset = null;
    this._spriteOffset = null;
    this._size = null;
    this._syncGroup = null;
    this._persist = null;
    this._persistOptions = null;
    this._zeroSpriteRotation = null;
    this._extraEndDuration = null;
    this._loopOptions = null;
    this._tilingTexture = null;
    this._snapToGrid = null;
    this._scaleToObject = null;
    this._screenSpace = null;
    this._screenSpaceAboveUI = null;
    this._screenSpaceAnchor = null;
    this._screenSpacePosition = null;
    this._screenSpaceScale = null;
    this._elevation = null;
    this._sortLayer = 800;
    this._levels = null;
    this._masks = [];
    this._tiedDocuments = [];
    this._selfMask = false;
    this._temporaryEffect = false;
    this._spriteRotation = 0;
    this._randomSpriteRotation = false;
    this._isRangedEffect = null;
    this._offsetLegacy = null;
    this._randomOffsetLegacy = null;
    this._aboveLighting = null;
    this._aboveInterface = null;
    this._spriteScaleMin = 1;
    this._spriteScaleMax = null;
    this._isometric = null;
    this._shapes = [];
    this._xray = null;
    this._constrainedByWalls = null;
    this._playEffect = true;
  }
  static niceName = "Effect";
  /**
   * @private
   */
  get _target() {
    return this._stretchTo || this._rotateTowards || this._moveTowards || false;
  }
  static debounceWarning() {
    custom_warning(
      "Sequencer",
      "Effect | This user does not have permissions to play effects. This can be configured in Sequencer's module settings."
    );
  }
  /**
   * Causes the effect to persist indefinitely on the canvas until _ended via SequencerEffectManager.endAllEffects() or
   * name the effect with .name() and then end it through SequencerEffectManager.endEffect()
   *
   * @param {Boolean} [inBool=true] inBool
   * @param {Object} [inOptions={}] inOptions
   * @returns {EffectSection}
   */
  persist(inBool = true, inOptions = {}) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "persist",
        "inBool must be of type boolean"
      );
    if (typeof inOptions !== "object")
      throw this.sequence._customError(
        this,
        "persist",
        `inOptions must be of type object`
      );
    inOptions = foundry.utils.mergeObject(
      {
        id: foundry.utils.randomID(),
        persistTokenPrototype: false
      },
      inOptions
    );
    if (typeof inOptions.persistTokenPrototype !== "boolean")
      throw this.sequence._customError(
        this,
        "persist",
        "inOptions.persistTokenPrototype must be of type boolean"
      );
    this._persist = inBool;
    this._persistOptions = inOptions;
    return this;
  }
  syncGroup(inString) {
    if (typeof inString !== "string")
      throw this.sequence._customError(
        this,
        "syncGroup",
        "inString must be of type string"
      );
    this._syncGroup = inString;
    return this;
  }
  /**
   * Causes the effect to become temporary, which means it will not be stored in the flags of any object,
   * even if it .persist() is called
   *
   * @param {Boolean} inBool
   * @returns {EffectSection}
   */
  temporary(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "temporary",
        "inBool must be of type boolean"
      );
    this._temporaryEffect = inBool || this._temporaryEffect;
    return this;
  }
  /**
   * Sets the effect's playback rate. A playback rate of 2.0 would make it play 2x as fast, 0.5 would make
   * it play half as fast.
   *
   * @param {Number} inNumber
   * @returns {EffectSection}
   */
  playbackRate(inNumber = 1) {
    if (!is_real_number(inNumber))
      throw this.sequence._customError(
        this,
        "playbackRate",
        "inNumber must be of type number"
      );
    this._playbackRate = inNumber;
    return this;
  }
  /**
   * Causes the effect to target a location close to the .stretchTowards() location, but not on it.
   *
   * @param {Boolean} [inBool=true] inBool
   * @returns {EffectSection}
   */
  missed(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "missed",
        "inBool must be of type boolean"
      );
    this._missed = inBool;
    return this;
  }
  /**
   * Adds a function that will run at the end of the effect serialization step, but before it is played. Allows direct
   * modifications of effect's data. For example, it could be manipulated to change which file will be used based
   * on the distance to the target.
   *
   * @param {Function} inFunc
   * @returns {EffectSection}
   */
  addOverride(inFunc) {
    if (!is_function(inFunc))
      throw this.sequence._customError(
        this,
        "addOverride",
        "The given function needs to be an actual function."
      );
    this._overrides.push(inFunc);
    return this;
  }
  /**
   *  A smart method that can take a reference to an object, or a direct on the canvas to attach an effect to,
   *  or a string reference (see .name())
   *
   * @param {Object|String} inObject
   * @param {Object} inOptions
   * @returns {EffectSection}
   */
  attachTo(inObject, inOptions = {}) {
    if (!inObject || !(typeof inObject === "object" || typeof inObject === "string")) {
      throw this.sequence._customError(
        this,
        "attachTo",
        `inObject is invalid, and must be of type of object, string, placeable object, or document`
      );
    }
    if (typeof inOptions !== "object")
      throw this.sequence._customError(
        this,
        "attachTo",
        `inOptions must be of type object`
      );
    inOptions = foundry.utils.mergeObject(
      {
        align: "center",
        edge: "on",
        bindVisibility: true,
        bindAlpha: true,
        bindElevation: true,
        bindScale: true,
        bindRotation: true,
        offset: false,
        randomOffset: false,
        gridUnits: false,
        local: false
      },
      inOptions
    );
    const validatedObject = this._validateLocation(inObject);
    if (validatedObject === void 0)
      throw this.sequence._customError(
        this,
        "attachTo",
        "could not find given object"
      );
    const aligns = Object.keys(alignments);
    if (typeof inOptions.align !== "string" || !aligns.includes(inOptions.align)) {
      throw this.sequence._customError(
        this,
        "attachTo",
        `inOptions.align must be of type string, one of: ${aligns.join(", ")}`
      );
    }
    if (typeof inOptions.edge !== "string" || !(inOptions.edge === "on" || inOptions.edge === "inner" || inOptions.edge === "outer")) {
      throw this.sequence._customError(
        this,
        "attachTo",
        `inOptions.edge must of type string with the value of either "on", "inner", or "outer"`
      );
    }
    if (typeof inOptions.bindVisibility !== "boolean")
      throw this.sequence._customError(
        this,
        "attachTo",
        `inOptions.bindVisibility must be of type boolean`
      );
    if (typeof inOptions.bindRotation !== "boolean")
      throw this.sequence._customError(
        this,
        "attachTo",
        `inOptions.bindRotation must be of type boolean`
      );
    if (typeof inOptions.bindAlpha !== "boolean")
      throw this.sequence._customError(
        this,
        "attachTo",
        "inOptions.bindAlpha must be of type boolean"
      );
    if (typeof inOptions.bindElevation !== "boolean")
      throw this.sequence._customError(
        this,
        "attachTo",
        "inOptions.bindElevation must be of type boolean"
      );
    if (typeof inOptions.bindScale !== "boolean")
      throw this.sequence._customError(
        this,
        "attachTo",
        "inOptions.bindScale must be of type boolean"
      );
    if (!(typeof inOptions.randomOffset === "boolean" || is_real_number(inOptions.randomOffset)))
      throw this.sequence._customError(
        this,
        "attachTo",
        "inOptions.randomOffset must be of type boolean or number"
      );
    this._source = validatedObject;
    if (inOptions.offset) {
      const offsetData = this._validateOffset(
        "attachTo",
        inOptions.offset,
        inOptions
      );
      this._offset = {
        source: offsetData,
        target: this._offset?.target ?? false
      };
    }
    this._randomOffset = {
      source: inOptions.randomOffset,
      target: this._randomOffset?.target ?? false
    };
    this._attachTo = {
      active: !!validatedObject,
      align: inOptions.align,
      edge: inOptions.edge,
      bindVisibility: inOptions.bindVisibility,
      bindAlpha: inOptions.bindAlpha,
      bindScale: inOptions.bindScale,
      bindElevation: inOptions.bindElevation,
      bindRotation: inOptions.bindRotation
    };
    return this;
  }
  /**
   *  Causes the effect to be rotated and stretched towards an object, or a direct on the canvas to play the effect at, or a string reference (see .name())
   *  This effectively calculates the proper X scale for the effect to reach the target
   *
   * @param {Object|String} inLocation
   * @param {Object} inOptions
   * @returns {EffectSection}
   */
  stretchTo(inLocation, inOptions = {}) {
    if (!inLocation || !(typeof inLocation === "object" || typeof inLocation === "string")) {
      throw this.sequence._customError(
        this,
        "stretchTo",
        `inLocation is invalid, and must be of type of object, string, placeable object, or document`
      );
    }
    if (typeof inOptions !== "object")
      throw this.sequence._customError(
        this,
        "stretchTo",
        `inOptions must be of type object`
      );
    inOptions = foundry.utils.mergeObject(
      {
        cacheLocation: false,
        attachTo: false,
        onlyX: false,
        tiling: false,
        offset: false,
        randomOffset: false,
        gridUnits: false,
        local: false,
        requiresLineOfSight: false,
        hideLineOfSight: false
      },
      inOptions
    );
    const validatedObject = this._validateLocation(inLocation);
    if (validatedObject === void 0)
      throw this.sequence._customError(
        this,
        "stretchTo",
        "could not find position of given object"
      );
    if (typeof inOptions.cacheLocation !== "boolean")
      throw this.sequence._customError(
        this,
        "stretchTo",
        "inOptions.cacheLocation must be of type boolean"
      );
    if (typeof inOptions.attachTo !== "boolean")
      throw this.sequence._customError(
        this,
        "stretchTo",
        "inOptions.attachTo must be of type boolean"
      );
    if (typeof inOptions.onlyX !== "boolean")
      throw this.sequence._customError(
        this,
        "stretchTo",
        "inOptions.onlyX must be of type boolean"
      );
    if (typeof inOptions.tiling !== "boolean")
      throw this.sequence._customError(
        this,
        "stretchTo",
        "inOptions.tiling must be of type boolean"
      );
    if (!(typeof inOptions.randomOffset === "boolean" || is_real_number(inOptions.randomOffset)))
      throw this.sequence._customError(
        this,
        "stretchTo",
        "inOptions.randomOffset must be of type boolean or number"
      );
    if (inOptions.cacheLocation && inOptions.attachTo) {
      throw this.sequence._customError(
        this,
        "stretchTo",
        "cacheLocation and attachTo cannot both be true - pick one or the other"
      );
    }
    if (typeof inOptions.requiresLineOfSight !== "boolean") {
      throw this.sequence._customError(
        this,
        "stretchTo",
        "requiresLineOfSight must be of type boolean"
      );
    }
    if (!inOptions.attachTo && inOptions.requiresLineOfSight) {
      throw this.sequence._customError(
        this,
        "stretchTo",
        "requiresLineOfSight requires that attachTo is true"
      );
    }
    if (typeof inOptions.hideLineOfSight !== "boolean") {
      throw this.sequence._customError(
        this,
        "stretchTo",
        "hideLineOfSight must be of type boolean"
      );
    }
    if (!inOptions.requiresLineOfSight && inOptions.hideLineOfSight) {
      throw this.sequence._customError(
        this,
        "stretchTo",
        "hideLineOfSight requires that requiresLineOfSight is true"
      );
    }
    if (inOptions.tiling) this.tilingTexture();
    if (inOptions.offset) {
      const offsetData = this._validateOffset(
        "stretchTo",
        inOptions.offset,
        inOptions
      );
      this._offset = {
        source: this._offset?.source ?? false,
        target: offsetData
      };
    }
    this._randomOffset = {
      source: this._randomOffset?.source ?? false,
      target: inOptions.randomOffset
    };
    this._stretchTo = {
      target: inOptions.cacheLocation ? get_object_canvas_data(validatedObject, { measure: true, uuid: false }) : validatedObject,
      attachTo: inOptions.attachTo,
      onlyX: inOptions.onlyX,
      requiresLineOfSight: inOptions.requiresLineOfSight,
      hideLineOfSight: inOptions.hideLineOfSight
    };
    return this;
  }
  /**
   * Sets the location to rotate the object to
   *
   * @param {object|string} inLocation
   * @param {object} inOptions
   * @returns this
   */
  rotateTowards(inLocation, inOptions = {}) {
    if (!inLocation || !(typeof inLocation === "object" || typeof inLocation === "string")) {
      throw this.sequence._customError(
        this,
        "inLocation",
        `inLocation is invalid, and must be of type of object, string, placeable object, or document`
      );
    }
    inOptions = foundry.utils.mergeObject(
      {
        rotationOffset: 0,
        cacheLocation: false,
        attachTo: false,
        offset: false,
        randomOffset: false,
        local: false,
        gridUnits: false,
        template: false
      },
      inOptions
    );
    if (!is_real_number(inOptions.rotationOffset))
      throw this.sequence._customError(
        this,
        "rotateTowards",
        "inOptions.rotationOffset must be of type number"
      );
    if (typeof inOptions.attachTo !== "boolean")
      throw this.sequence._customError(
        this,
        "rotateTowards",
        "inOptions.attachTo must be of type boolean"
      );
    if (typeof inOptions.cacheLocation !== "boolean")
      throw this.sequence._customError(
        this,
        "rotateTowards",
        "inOptions.cacheLocation must be of type boolean"
      );
    const validatedObject = this._validateLocation(inLocation);
    if (!validatedObject)
      throw this.sequence._customError(
        this,
        "rotateTowards",
        "could not find position of given object"
      );
    if (inOptions.offset) {
      const offsetData = this._validateOffset(
        "attachTo",
        inOptions.offset,
        inOptions
      );
      this._offset = {
        target: offsetData ?? this._offset?.target ?? false
      };
    }
    if (typeof inOptions.template !== "boolean")
      throw this.sequence._customError(
        this,
        "rotateTowards",
        "inOptions.template must be of type boolean"
      );
    this._randomOffset = {
      target: inOptions.randomOffset ?? this._randomOffset?.target ?? false
    };
    this._rotateTowards = {
      target: inOptions.cacheLocation ? get_object_canvas_data(validatedObject, { measure: true, uuid: false }) : validatedObject,
      rotationOffset: inOptions.rotationOffset,
      cacheLocation: inOptions.cacheLocation,
      attachTo: inOptions.attachTo,
      template: inOptions.template
    };
    return this;
  }
  /**
   *  Create an effect based on the given object, effectively copying the object as an effect. Useful when you want to do some effect magic on tokens or tiles.
   *
   * @param {Object} inObject
   * @param {Object} inOptions
   * @returns {EffectSection}
   */
  copySprite(inObject, inOptions = {}) {
    if (!(inObject instanceof foundry.canvas.placeables.Token || inObject instanceof foundry.canvas.placeables.Tile || inObject instanceof TokenDocument || inObject instanceof TileDocument)) {
      throw this.sequence._customError(
        this,
        "copySprite",
        "inObject must be of type Token, Tile, TokenDocument, or TileDocument"
      );
    }
    if (typeof inOptions !== "object")
      throw this.sequence._customError(
        this,
        "copySprite",
        `inOptions must be of type object`
      );
    inObject = inObject.document ?? inObject;
    if (!inObject?.texture?.src)
      throw this.sequence._customError(
        this,
        "copySprite",
        "could not find the image for the given object"
      );
    inOptions = foundry.utils.mergeObject(
      {
        cacheLocation: false,
        offset: false,
        randomOffset: false,
        local: false,
        gridUnits: false
      },
      inOptions
    );
    if (typeof inOptions.cacheLocation !== "boolean")
      throw this.sequence._customError(
        this,
        "copySprite",
        "inOptions.cacheLocation must be of type boolean"
      );
    if (!(typeof inOptions.randomOffset === "boolean" || is_real_number(inOptions.randomOffset)))
      throw this.sequence._customError(
        this,
        "copySprite",
        "inOptions.randomOffset must be of type boolean or number"
      );
    if (inOptions.offset) {
      const offsetData = this._validateOffset(
        "attachTo",
        inOptions.offset,
        inOptions
      );
      this._offset = {
        source: offsetData,
        target: this._offset?.target ?? false
      };
    }
    this._randomOffset = {
      source: inOptions.randomOffset,
      target: this._randomOffset?.target ?? false
    };
    this._copySprite = {
      object: inObject,
      options: inOptions
    };
    return this;
  }
  shape(inType, inOptions = {}) {
    if (typeof inType !== "string")
      throw this.sequence._customError(
        this,
        "shape",
        "type must be of type string"
      );
    if (!Object.values(CONSTANTS.SHAPES).includes(inType)) {
      throw this.sequence._customError(
        this,
        "shape",
        "type must be one of: " + Object.values(CONSTANTS.SHAPES).join(", ")
      );
    }
    if (inType === CONSTANTS.SHAPES.POLY) {
      if (!Array.isArray(inOptions.points)) {
        throw this.sequence._customError(
          this,
          "shape",
          "if creating polygon, inOptions.points must be of type array"
        );
      }
      inOptions.points = inOptions.points.map((point) => {
        if (Array.isArray(point)) {
          if (!is_real_number(point[0]) || !is_real_number(point[1])) {
            throw this.sequence._customError(
              this,
              "shape",
              "inOptions.points must be an array, containing an array of two numbers or objects with x and y number properties"
            );
          }
          return point;
        }
        if (typeof point === "object") {
          if (!is_real_number(point?.x) || !is_real_number(point?.y)) {
            throw this.sequence._customError(
              this,
              "shape",
              "inOptions.points must be an array, containing an array of two numbers or objects with x and y number properties"
            );
          }
          return [point.x, point.y];
        }
      });
    } else if (inType === CONSTANTS.SHAPES.CIRC) {
      if (typeof inOptions.radius !== "number") {
        throw this.sequence._customError(
          this,
          "shape",
          "if creating circle, inOptions.radius must be of type number"
        );
      }
    } else if (inType === CONSTANTS.SHAPES.RECT || inType === CONSTANTS.SHAPES.RREC || inType === CONSTANTS.SHAPES.ELIP) {
      if (inOptions.width ^ inOptions.height) {
        inOptions.width = inOptions.width ?? inOptions.height;
        inOptions.height = inOptions.height ?? inOptions.width;
      }
      if (typeof inOptions.width !== "number") {
        throw this.sequence._customError(
          this,
          "shape",
          `if creating rectangle, rounded rectangle, or an ellipse, inOptions.width must be of type number`
        );
      }
      if (typeof inOptions.height !== "number") {
        throw this.sequence._customError(
          this,
          "shape",
          "if creating rectangle, rounded rectangle, or an ellipse, inOptions.height must be of type number"
        );
      }
      if (inType === CONSTANTS.SHAPES.RREC && typeof inOptions.radius !== "number") {
        throw this.sequence._customError(
          this,
          "shape",
          "if creating rounded border rectangle, inOptions.radius must be of type number"
        );
      }
    }
    if (inOptions.gridUnits !== void 0 && typeof inOptions.gridUnits !== "boolean") {
      throw this.sequence._customError(
        this,
        "shape",
        "inOptions.gridUnits must be of type boolean"
      );
    }
    if (inOptions.name && typeof inOptions.name !== "string") {
      throw this.sequence._customError(
        this,
        "shape",
        "inOptions.name must be of type string"
      );
    }
    if (inOptions.fillColor && !is_real_number(inOptions.fillColor) && typeof inOptions.fillColor !== "string") {
      throw this.sequence._customError(
        this,
        "shape",
        "inOptions.fillColor must be of type string (hexadecimal) or number (decimal)"
      );
    } else {
      inOptions.fillColor = parseColor(inOptions.fillColor).decimal;
    }
    if (inOptions.fillAlpha && !is_real_number(inOptions.fillAlpha)) {
      throw this.sequence._customError(
        this,
        "shape",
        "inOptions.fillAlpha must be of type number"
      );
    }
    if (inOptions.alpha && !is_real_number(inOptions.alpha)) {
      throw this.sequence._customError(
        this,
        "shape",
        "inOptions.alpha must be of type number"
      );
    }
    if (inOptions.lineSize && !is_real_number(inOptions.lineSize)) {
      throw this.sequence._customError(
        this,
        "shape",
        "inOptions.lineSize must be of type number"
      );
    }
    if (inOptions.lineColor && !is_real_number(inOptions.lineColor) && typeof inOptions.lineColor !== "string") {
      throw this.sequence._customError(
        this,
        "shape",
        "inOptions.lineColor must be of type string (hexadecimal) or number (decimal)"
      );
    } else {
      inOptions.lineColor = parseColor(inOptions.lineColor).decimal;
    }
    if (inOptions.offset) {
      inOptions.offset = this._validateOffset(
        "shape",
        inOptions.offset,
        inOptions.offset
      );
    }
    if (inOptions.texture !== void 0 && typeof inOptions.texture !== "string") {
      throw this.sequence._customError(
        this,
        "shape",
        "inOptions.texture must be of type string"
      );
    }
    if (inOptions.isMask !== void 0 && typeof inOptions.isMask !== "boolean") {
      throw this.sequence._customError(
        this,
        "shape",
        "inOptions.isMask must be of type boolean"
      );
    }
    this._shapes.push({
      ...inOptions,
      type: inType
    });
    return this;
  }
  /**
   *  Causes the effect's sprite to be offset relative to its location based on a given vector
   *
   * @param {Object} inOffset
   * @param {Object} inOptions
   * @returns {EffectSection}
   */
  spriteOffset(inOffset, inOptions = {}) {
    if (inOffset === void 0)
      throw this.sequence._customError(
        this,
        "spriteOffset",
        "inOffset must not be undefined"
      );
    if (typeof inOptions !== "object")
      throw this.sequence._customError(
        this,
        "spriteOffset",
        "options must be of type object"
      );
    this._spriteOffset = this._validateOffset(
      "spriteOffset",
      inOffset,
      inOptions
    );
    return this;
  }
  /**
   * Causes the final effect location to be snapped to the grid
   *
   * @param {Boolean} inBool
   * @returns {EffectSection}
   */
  snapToGrid(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "snapToGrid",
        "inBool must be of type boolean"
      );
    this._snapToGrid = inBool;
    return this;
  }
  /**
   * Causes the effect to be scaled to the target object's width
   *
   * @param {Number} inScale
   * @param {Object} inOptions
   * @returns {EffectSection}
   */
  scaleToObject(inScale = 1, inOptions = {}) {
    if (!is_real_number(inScale))
      throw this.sequence._customError(
        this,
        "scaleToObject",
        `inScale must be of type number!`
      );
    if (typeof inOptions !== "object")
      throw this.sequence._customError(
        this,
        "scaleToObject",
        "inOptions must be of type object"
      );
    inOptions = foundry.utils.mergeObject(
      {
        scale: inScale,
        considerTokenScale: false,
        uniform: false
      },
      inOptions
    );
    if (typeof inOptions.considerTokenScale !== "boolean")
      throw this.sequence._customError(
        this,
        "scaleToObject",
        "inOptions.considerTokenScale must be of type boolean"
      );
    if (typeof inOptions.uniform !== "boolean")
      throw this.sequence._customError(
        this,
        "scaleToObject",
        "inOptions.uniform must be of type boolean"
      );
    this._scaleToObject = inOptions;
    return this;
  }
  /**
   * Sets the width and the height of the effect in pixels, this size is set before any scaling
   *
   * @param {Number|Object<{width: {Number}, height: {Number}}>} inSize
   * @param {Object} inOptions
   * @returns {EffectSection}
   */
  size(inSize, inOptions = {}) {
    if (!is_real_number(inSize) && typeof inSize !== "object")
      throw this.sequence._customError(
        this,
        "size",
        "inSize must be of type number or object"
      );
    if (typeof inOptions !== "object")
      throw this.sequence._customError(
        this,
        "size",
        "inOptions must be of type object"
      );
    if (is_real_number(inSize)) {
      inSize = {
        width: inSize,
        height: inSize
      };
    }
    if (inSize.width === void 0 ^ inSize.height === void 0) {
      if (inSize.width) {
        if (!is_real_number(inSize.width))
          throw this.sequence._customError(
            this,
            "size",
            "inSize.width must be of type number or string 'auto'"
          );
        inSize["height"] = "auto";
      } else {
        if (!is_real_number(inSize.height))
          throw this.sequence._customError(
            this,
            "size",
            "inSize.height must be of type number or string 'auto'"
          );
        inSize["width"] = "auto";
      }
    }
    inOptions = foundry.utils.mergeObject(
      {
        gridUnits: false
      },
      inOptions
    );
    if (!is_real_number(inSize.width) && inSize.width !== "auto")
      throw this.sequence._customError(
        this,
        "size",
        "inSize.width must be of type number or string 'auto'"
      );
    if (!is_real_number(inSize.height) && inSize.height !== "auto")
      throw this.sequence._customError(
        this,
        "size",
        "inSize.height must be of type number or string 'auto'"
      );
    if (typeof inOptions.gridUnits !== "boolean")
      throw this.sequence._customError(
        this,
        "size",
        "inOptions.gridUnits must be of type boolean"
      );
    this._size = {
      width: inSize.width ?? canvas.grid.size,
      height: inSize.height ?? canvas.grid.size,
      ...inOptions
    };
    return this;
  }
  /**
   *  This scales the sprite of the effect, and this method can take the following:
   *  - A number to set the scale uniformly
   *  - An object with x and y for non-uniform scaling
   *  - Two numbers which the Sequencer will randomly pick a uniform scale between
   *
   * @param {number|object} inScaleMin
   * @param {number} [inScaleMax] inScaleMax
   * @returns this
   */
  spriteScale(inScaleMin, inScaleMax) {
    if (!is_real_number(inScaleMin) && typeof inScaleMin !== "object")
      throw this.sequence._customError(
        this,
        "spriteScale",
        "inScale must be of type number or object"
      );
    if (is_real_number(inScaleMin)) {
      if (inScaleMax && !is_real_number(inScaleMax)) {
        throw this.sequence._customError(
          this,
          "spriteScale",
          "if inScaleMin is a number, inScaleMax must also be of type number"
        );
      }
    }
    this._spriteScaleMin = inScaleMin;
    this._spriteScaleMax = inScaleMax ?? false;
    return this;
  }
  /**
   * This defines the internal padding of this effect. Gridsize determines the internal grid size of this effect which will determine how big it is on the canvas
   * relative to the canvas's grid size. Start and end point defines padding at the left and right of the effect
   *
   * @param {Number} gridSize
   * @param {Number} startPoint
   * @param {Number} endPoint
   * @returns {EffectSection}
   */
  template({ gridSize, startPoint, endPoint } = {}) {
    if (gridSize && !is_real_number(gridSize))
      throw this.sequence._customError(
        this,
        "template",
        "gridSize must be of type number"
      );
    if (startPoint && !is_real_number(startPoint))
      throw this.sequence._customError(
        this,
        "template",
        "startPoint must be of type number"
      );
    if (endPoint && !is_real_number(endPoint))
      throw this.sequence._customError(
        this,
        "template",
        "endPoint must be of type number"
      );
    if (!gridSize && !startPoint && !endPoint)
      throw this.sequence._customError(
        this,
        "template",
        "You need to define at least one parameter!"
      );
    if (!this._template) this._template = {};
    if (gridSize) this._template["gridSize"] = gridSize;
    if (startPoint) this._template["startPoint"] = startPoint;
    if (endPoint) this._template["endPoint"] = endPoint;
    return this;
  }
  /**
   * This makes the texture of the effect tile, effectively repeat itself within the sprite's dimensions
   *
   * @param {Object|Number} scale
   * @param {Object} position
   * @returns {EffectSection}
   */
  tilingTexture(scale2 = { x: 1, y: 1 }, position = { x: 0, y: 0 }) {
    if (is_real_number(scale2)) {
      scale2 = { x: scale2, y: scale2 };
    }
    scale2 = { x: scale2?.x ?? 1, y: scale2?.y ?? 1 };
    if (!is_real_number(scale2.x))
      throw this.sequence._customError(
        this,
        "tilingTexture",
        `scale.x must be of type number!`
      );
    if (!is_real_number(scale2.y))
      throw this.sequence._customError(
        this,
        "tilingTexture",
        `scale.y must be of type number!`
      );
    position = { x: position?.x ?? 0, y: position?.y ?? 0 };
    if (!is_real_number(position.x))
      throw this.sequence._customError(
        this,
        "tilingTexture",
        `position.x must be of type number!`
      );
    if (!is_real_number(position.y))
      throw this.sequence._customError(
        this,
        "tilingTexture",
        `position.y must be of type number!`
      );
    this._tilingTexture = {
      scale: scale2,
      position
    };
    return this;
  }
  /**
   *  Anchors the sprite's container according to the given x and y coordinates, or uniformly based on a single number
   *
   * @param {Number|Object} inAnchor
   * @returns {EffectSection}
   */
  anchor(inAnchor) {
    if (is_real_number(inAnchor)) {
      inAnchor = {
        x: inAnchor,
        y: inAnchor
      };
    }
    inAnchor = {
      x: inAnchor?.x ?? 0.5,
      y: inAnchor?.y ?? 0.5
    };
    if (!is_real_number(inAnchor.x))
      throw this.sequence._customError(
        this,
        "anchor",
        `inAnchor.x must be of type number!`
      );
    if (!is_real_number(inAnchor.y))
      throw this.sequence._customError(
        this,
        "anchor",
        `inAnchor.y must be of type number!`
      );
    this._anchor = inAnchor;
    return this;
  }
  /**
   *  Anchors the sprite according to the given x and y coordinates, or uniformly based on a single number
   *
   * @param {Number|Object} inAnchor
   * @returns {EffectSection}
   */
  spriteAnchor(inAnchor) {
    if (is_real_number(inAnchor)) {
      inAnchor = {
        x: inAnchor,
        y: inAnchor
      };
    }
    inAnchor = {
      x: inAnchor?.x ?? 0.5,
      y: inAnchor?.y ?? 0.5
    };
    if (!is_real_number(inAnchor.x))
      throw this.sequence._customError(
        this,
        "anchor",
        `inAnchor.x must be of type number!`
      );
    if (!is_real_number(inAnchor.y))
      throw this.sequence._customError(
        this,
        "anchor",
        `inAnchor.y must be of type number!`
      );
    this._spriteAnchor = inAnchor;
    return this;
  }
  /**
   *  Centers the sprite, effectively giving it an anchor of {x: 0.5, y: 0.5}
   *
   *  Note: If this is used, it will override the anchor set by Aim Towards, which sets the sprite's anchor to the
   *  outermost edge of the location the sprite is played at
   *
   * @returns {EffectSection}
   */
  center() {
    this.anchor(0.5);
    return this;
  }
  /**
   * The sprite gets a randomized flipped X scale. If the scale on that axis was 1, it can
   * become 1 or -1, effectively mirroring the sprite on its horizontal axis
   *
   * @param {Boolean} inBool
   * @returns {EffectSection}
   */
  randomizeMirrorX(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "randomizeMirrorX",
        "inBool must be of type boolean"
      );
    this._randomMirrorX = inBool;
    return this;
  }
  /**
   * The sprite gets a randomized flipped Y scale. If the scale on that axis was 1, it can
   * become 1 or -1, effectively mirroring the sprite on its vertical axis
   *
   * @param {Boolean} inBool
   * @returns {EffectSection}
   */
  randomizeMirrorY(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "randomizeMirrorY",
        "inBool must be of type boolean"
      );
    this._randomMirrorY = inBool;
    return this;
  }
  /**
   * The sprite gets a flipped X scale. If the scale on that axis was 1, it will become 1 or -1, effectively
   * mirroring the sprite on its horizontal axis
   *
   * @param {Boolean} inBool
   * @returns {EffectSection}
   */
  mirrorX(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "mirrorX",
        "inBool must be of type boolean"
      );
    this._mirrorX = inBool;
    return this;
  }
  /**
   * The sprite gets a flipped Y scale. If the scale on that axis was 1, it will become 1 or -1, effectively
   * mirroring the sprite on its vertical axis
   *
   * @param {Boolean} inBool
   * @returns {EffectSection}
   */
  mirrorY(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "mirrorY",
        "inBool must be of type boolean"
      );
    this._mirrorY = inBool;
    return this;
  }
  /**
   * Causes the effect to play beneath most tokens
   *
   * @param {Boolean} inBool
   * @returns {EffectSection}
   */
  belowTokens(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "belowTokens",
        "inBool must be of type boolean"
      );
    return this.sortLayer(inBool ? 600 : 800);
  }
  /**
   * Causes the effect to play beneath most tiles
   *
   * @param {Boolean} inBool
   * @returns {EffectSection}
   */
  belowTiles(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "belowTiles",
        "inBool must be of type boolean"
      );
    return this.sortLayer(inBool ? 300 : 500);
  }
  /**
   * Causes the effect to be played on top of the vision mask
   *
   * @param {Boolean} inBool
   * @returns {EffectSection}
   */
  aboveLighting(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "aboveLighting",
        "inBool must be of type boolean"
      );
    this._aboveLighting = inBool;
    return this;
  }
  /**
   * Causes the effect to be played on top of interface
   *
   * @param {Boolean} inBool
   * @returns {EffectSection}
   */
  aboveInterface(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "aboveInterface",
        "inBool must be of type boolean"
      );
    this._aboveInterface = inBool;
    return this;
  }
  /**
   * Changes the effect's sortLayer, potentially displaying effects below tiles, above tokens or even weather effects
   * in case of identical elevations
   *
   * @param {Number} inSortLayer
   * @param {Object} inOptions
   * @returns {EffectSection}
   */
  sortLayer(inSortLayer, inOptions = {}) {
    if (!is_real_number(inSortLayer))
      throw this.sequence._customError(
        this,
        "sortLayer",
        "inSortLayer must be of type number"
      );
    this._sortLayer = inSortLayer;
    return this;
  }
  /**
   * Sets the zIndex of the effect, potentially displaying it on top of other effects the same elevation and sort
   *
   * @param {Number} inZIndex
   * @returns {EffectSection}
   */
  zIndex(inZIndex) {
    if (!is_real_number(inZIndex))
      throw this.sequence._customError(
        this,
        "zIndex",
        "inZIndex must be of type number"
      );
    this._zIndex = inZIndex;
    return this;
  }
  /**
   * This method only modifies .persist()ed effects and causes them to not immediately end, but stick around for the given duration passed to this method.
   *
   * @param {Number} inExtraDuration
   * @returns {EffectSection}
   */
  extraEndDuration(inExtraDuration) {
    if (!is_real_number(inExtraDuration))
      throw this.sequence._customError(
        this,
        "extraEndDuration",
        "inExtraDuration must be of type number"
      );
    this._extraEndDuration = inExtraDuration;
    return this;
  }
  /**
   * Rotates the sprite
   *
   * @param {Number} inAngle
   * @returns {EffectSection}
   */
  spriteRotation(inAngle) {
    if (!is_real_number(inAngle))
      throw this.sequence._customError(
        this,
        "spriteRotation",
        "inAngle must be of type number"
      );
    this._spriteRotation = inAngle;
    return this;
  }
  /**
   * Rotates the sprite
   *
   * @param {Boolean} inBool
   * @returns {EffectSection}
   */
  randomSpriteRotation(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "randomSpriteRotation",
        "inBool must be of type boolean"
      );
    this._randomSpriteRotation = inBool;
    return this;
  }
  /**
   * Causes the effect to not rotate should its container rotate
   *
   * @param {Boolean} [inBool=true] inBool
   * @returns {EffectSection}
   */
  zeroSpriteRotation(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "zeroSpriteRotation",
        "inBool must be of type boolean"
      );
    this._zeroSpriteRotation = inBool;
    return this;
  }
  /**
   * Allows you to control the number of loops and the delays between each loop
   *
   * @param {Object} inOptions
   * @returns {EffectSection}
   */
  loopOptions(inOptions = {}) {
    if (typeof inOptions !== "object")
      throw this.sequence._customError(
        this,
        "loopOptions",
        `inOptions must be of type object`
      );
    if (inOptions.maxLoops !== void 0) {
      custom_warning(
        "Sequencer",
        "Effect | loopOptions' maxLoops option is deprecated, please use loops instead"
      );
      inOptions = { ...inOptions, loops: inOptions.loops ?? inOptions.maxLoops };
      delete inOptions.maxLoops;
    }
    inOptions = foundry.utils.mergeObject(
      {
        loopDelay: 0,
        loops: 0,
        endOnLastLoop: false
      },
      inOptions
    );
    if (typeof inOptions.endOnLastLoop !== "boolean")
      throw this.sequence._customError(
        this,
        "loopOptions",
        "inOptions.endOnLastLoop must be of type boolean"
      );
    if (typeof inOptions.loopDelay !== "number" || inOptions.loopDelay < 0)
      throw this.sequence._customError(
        this,
        "loopOptions",
        "inOptions.loopDelay must be of type number that is not below 0"
      );
    if (typeof inOptions.loops !== "number" || inOptions.loops < 0)
      throw this.sequence._customError(
        this,
        "loopOptions",
        "inOptions.loops must be of type number that is not below 0"
      );
    this._loopOptions = inOptions;
    return this;
  }
  /**
   * Causes the effect to not show up in the Effect Manager UI - DO NOT USE UNLESS YOU KNOW WHAT YOU ARE DOING
   *
   * @param inBool
   * @returns {EffectSection}
   */
  private(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "private",
        "inBool must be of type boolean"
      );
    this._private = inBool;
    return this;
  }
  /**
   * Causes the effect to be played in screen space instead of world space (where tokens are)
   *
   * @param {Boolean} [inBool=true] inBool
   * @returns {EffectSection}
   */
  screenSpace(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "screenSpace",
        "inBool must be of type boolean"
      );
    this._screenSpace = inBool;
    this._screenSpaceAnchor = this._screenSpaceAnchor ?? { x: 0.5, y: 0.5 };
    return this;
  }
  /**
   * Causes the effect to be played above all of the UI elements
   *
   * @param {Boolean} [inBool=true] inBool
   * @returns {EffectSection}
   */
  screenSpaceAboveUI(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "screenSpaceAboveUI",
        "inBool must be of type boolean"
      );
    this._screenSpaceAboveUI = inBool;
    return this;
  }
  /**
   *  Positions the effect in a screen space position, offset from its .screenSpaceAnchor()
   *
   * @param {Object} inPosition
   * @returns {EffectSection}
   */
  screenSpacePosition(inPosition) {
    inPosition = {
      x: inPosition?.x ?? 0,
      y: inPosition?.y ?? 0
    };
    if (!is_real_number(inPosition.x))
      throw this.sequence._customError(
        this,
        "screenSpacePosition",
        `inPosition.x must be of type number!`
      );
    if (!is_real_number(inPosition.y))
      throw this.sequence._customError(
        this,
        "screenSpacePosition",
        `inPosition.y must be of type number!`
      );
    this._screenSpacePosition = inPosition;
    return this;
  }
  /**
   *  Anchors the sprite according to the given x and y coordinates, or uniformly based on a single number in screen space
   *
   * @param {Number|Object} inAnchor
   * @returns {EffectSection}
   */
  screenSpaceAnchor(inAnchor) {
    if (is_real_number(inAnchor)) {
      inAnchor = {
        x: inAnchor,
        y: inAnchor
      };
    }
    inAnchor = {
      x: inAnchor?.x ?? 0.5,
      y: inAnchor?.y ?? 0.5
    };
    if (!is_real_number(inAnchor.x))
      throw this.sequence._customError(
        this,
        "screenSpaceAnchor",
        `inAnchor.x must be of type number!`
      );
    if (!is_real_number(inAnchor.y))
      throw this.sequence._customError(
        this,
        "screenSpaceAnchor",
        `inAnchor.y must be of type number!`
      );
    this._screenSpaceAnchor = inAnchor;
    return this;
  }
  /**
   *  Sets up various properties relating to scale of the effect on the screen
   *
   * @param {Object} inOptions
   * @returns {EffectSection}
   */
  screenSpaceScale(inOptions) {
    if (typeof inOptions !== "object")
      throw this.sequence._customError(
        this,
        "screenSpaceScale",
        `inOptions must be of type object`
      );
    inOptions = foundry.utils.mergeObject(
      {
        x: 1,
        y: 1,
        fitX: false,
        fitY: false,
        ratioX: false,
        ratioY: false
      },
      inOptions
    );
    if (!is_real_number(inOptions.x))
      throw this.sequence._customError(
        this,
        "screenSpaceScale",
        `inOptions.x must be of type number!`
      );
    if (!is_real_number(inOptions.y))
      throw this.sequence._customError(
        this,
        "screenSpaceScale",
        `inOptions.y must be of type number!`
      );
    if (typeof inOptions.fitX !== "boolean")
      throw this.sequence._customError(
        this,
        "screenSpaceScale",
        "inOptions.fitX must be of type boolean"
      );
    if (typeof inOptions.fitY !== "boolean")
      throw this.sequence._customError(
        this,
        "screenSpaceScale",
        "inOptions.fitY must be of type boolean"
      );
    if (typeof inOptions.ratioX !== "boolean")
      throw this.sequence._customError(
        this,
        "screenSpaceScale",
        "inOptions.ratioX must be of type boolean"
      );
    if (typeof inOptions.ratioY !== "boolean")
      throw this.sequence._customError(
        this,
        "screenSpaceScale",
        "inOptions.ratioY must be of type boolean"
      );
    if (inOptions.ratioX && inOptions.ratioY)
      throw this.sequence._customError(
        this,
        "screenSpaceScale",
        "both ratioX and ratioY cannot be true, one axis must fit or be set directly"
      );
    this._screenSpaceScale = inOptions;
    return this;
  }
  /**
   * Ties the effect to any number of documents in Foundry - if those get deleted, the effect is ended.
   *
   * @param {String|PlaceableObject|foundry.abstract.Document|Array<String|PlaceableObject|foundry.abstract.Document>} inDocuments
   * @returns {EffectSection}
   */
  tieToDocuments(inDocuments) {
    if (!Array.isArray(inDocuments)) {
      inDocuments = [inDocuments];
    }
    for (let doc of inDocuments) {
      if (typeof doc !== "string" && !(doc instanceof foundry.canvas.placeables.PlaceableObject) && !(doc instanceof foundry.abstract.Document)) {
        throw this.sequence._customError(
          this,
          "tieToDocument",
          "inOrigin must be of type string, PlaceableObject, or Document, or an array thereof"
        );
      }
      if (typeof doc === "string") {
        const obj = fromUuidSync(doc);
        if (!obj)
          throw this.sequence._customError(
            this,
            "tieToDocument",
            `could not find document with UUID "${doc}"`
          );
      } else {
        doc = validate_document(doc);
        if (doc instanceof foundry.abstract.Document) {
          doc = doc?.uuid;
          if (!doc)
            throw this.sequence._customError(
              this,
              "tieToDocument",
              "could not find the UUID for the given object"
            );
        }
      }
      this._tiedDocuments.push(doc);
    }
    return this;
  }
  /**
   *  Masks the effect to the given object, objects, or raw PIXI shape. If no object is given, the effect will be
   *  masked to the source of the effect. Raw shapes (PIXI.Polygon, PIXI.Circle, PIXI.Rectangle) are masked in
   *  scene coordinates; combine with `Sequencer.Helpers.computeWallPolygon` for wall-aware clipping.
   *
   * @param {Token/TokenDocument/Tile/TileDocument/Drawing/DrawingDocument/MeasuredTemplate/MeasuredTemplateDocument/Region/RegionDocument/PIXI.Polygon/PIXI.Circle/PIXI.Rectangle/Array} inObject
   * @returns {Section}
   */
  mask(inObject) {
    if (!inObject) {
      this._selfMask = true;
      return this;
    }
    if (Array.isArray(inObject)) {
      for (let obj of inObject) {
        this.mask(obj);
      }
      return this;
    }
    if (inObject instanceof PIXI.Polygon || inObject instanceof PIXI.Circle || inObject instanceof PIXI.Rectangle) {
      this._masks.push({ __shape: serializeShape(inObject) });
      return this;
    }
    const validatedObject = this._validateLocation(inObject);
    const isValidObject = validatedObject instanceof TokenDocument || validatedObject instanceof TileDocument || validatedObject instanceof DrawingDocument || validatedObject instanceof MeasuredTemplateDocument || validatedObject instanceof RegionDocument;
    if (!isValidObject) {
      throw this.sequence._customError(
        this,
        "mask",
        "A foundry object was provided, but only Tokens, Tiles, Drawings, MeasuredTemplates, Regions, or raw PIXI shapes may be used to create effect masks"
      );
    }
    this._masks.push(get_object_identifier(validatedObject));
    return this;
  }
  /**
   * Causes the effect to be visible through walls
   *
   * @param inBool
   * @returns {EffectSection}
   */
  xray(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "xray",
        "inBool must be of type boolean"
      );
    this._xray = inBool;
    return this;
  }
  /**
   * Clips the effect to a wall-bounded sweep from its source position, independent of the
   * Walled Templates module. The sweep is a full 360° polygon; pass `radius` to bound it.
   * Pass `false` to clear.
   *
   * @param {boolean|object} [inOptions=true]
   * @param {"sight"|"sound"|"move"|"light"} [inOptions.type="sight"]   Collision type
   * @param {number|null} [inOptions.radius=null]                      Bounding circle radius in pixels
   * @param {{x: number, y: number}|null} [inOptions.origin=null]      Override origin; defaults to source position
   * @param {string|foundry.documents.Level|null} [inOptions.level=null]
   *        Foundry v14+. The scene level whose walls the sweep should consult. Accepts a
   *        level id, level name, or Level document. When omitted, the level is inferred
   *        from the attached source, then from `.onLevels()`, then from the effect's
   *        elevation extent. Ignored on Foundry v13.
   * @returns {EffectSection}
   */
  constrainedByWalls(inOptions = true) {
    if (inOptions === false || inOptions === null) {
      this._constrainedByWalls = null;
      return this;
    }
    if (inOptions === true) inOptions = {};
    if (typeof inOptions !== "object") {
      throw this.sequence._customError(
        this,
        "constrainedByWalls",
        "inOptions must be of type object or boolean"
      );
    }
    inOptions = foundry.utils.mergeObject({
      type: "sight",
      radius: null,
      origin: null,
      level: null
    }, inOptions);
    const validTypes = ["sight", "sound", "move", "light"];
    if (!validTypes.includes(inOptions.type)) {
      throw this.sequence._customError(
        this,
        "constrainedByWalls",
        `inOptions.type must be one of ${validTypes.join(", ")}`
      );
    }
    if (inOptions.radius !== null && !is_real_number(inOptions.radius)) {
      throw this.sequence._customError(
        this,
        "constrainedByWalls",
        "inOptions.radius must be a number or null"
      );
    }
    if (inOptions.origin !== null) {
      if (typeof inOptions.origin !== "object" || !is_real_number(inOptions.origin.x) || !is_real_number(inOptions.origin.y)) {
        throw this.sequence._customError(
          this,
          "constrainedByWalls",
          "inOptions.origin must be null or { x: number, y: number }"
        );
      }
    }
    if (inOptions.level !== null) {
      if (typeof inOptions.level === "object" && typeof inOptions.level?.id === "string") {
        inOptions.level = inOptions.level.id;
      } else if (typeof inOptions.level !== "string") {
        throw this.sequence._customError(
          this,
          "constrainedByWalls",
          "inOptions.level must be null, a level id/name, or a Level document"
        );
      }
    }
    this._constrainedByWalls = inOptions;
    return this;
  }
  /**
   * Declares which file to be played. This may also be an array of paths, which will be randomly picked from each
   * time the section is played.
   *
   * @param {string|array} inFile
   * @param {object} inOptions
   * @returns this
   */
  file(inFile, inOptions = {}) {
    if (typeof inOptions !== "object")
      throw this.sequence._customError(
        this,
        "file",
        "inOptions must be of type object"
      );
    inOptions = foundry.utils.mergeObject({
      antialiasing: PIXI.SCALE_MODES.LINEAR
    }, inOptions);
    if (typeof inOptions.antialiasing !== "number" || !(inOptions.antialiasing >= 0 && inOptions.antialiasing <= 1)) {
      throw this.sequence._customError(
        this,
        "file",
        "inOptions.antialiasing must be of type number of 0 or 1 (see PIXI.SCALE_MODES)"
      );
    }
    this._file = inFile;
    this._fileOptions = inOptions;
    return this;
  }
  /**
   * Configures the isometric configuration of this effect
   *
   * @param inOptions
   * @returns {EffectSection}
   */
  isometric(inOptions = {}) {
    if (typeof inOptions !== "object")
      throw this.sequence._customError(
        this,
        "isometric",
        `inOptions must be of type object`
      );
    inOptions = foundry.utils.mergeObject(
      {
        overlay: false
      },
      inOptions
    );
    if (typeof inOptions?.overlay !== "boolean")
      throw this.sequence._customError(
        this,
        "isometric",
        "inOptions.overlay must be of type boolean"
      );
    this._isometric = inOptions;
    return this;
  }
  /**
   * @private
   */
  _expressWarnings() {
    if (this._stretchTo && this._anchor?.x) {
      this.sequence._showWarning(
        this,
        "stretchTo",
        "you have called .stretchTo() and .anchor() - stretchTo will manually set the X axis of the anchor and may not behave like you expect.",
        true
      );
    }
    if (this._stretchTo && this._scaleToObject) {
      throw this.sequence._customError(
        this,
        "stretchTo",
        "You're trying to stretch towards an object, while scaling to fit another??? Make up your mind!"
      );
    }
    if (this._stretchTo && this._randomRotation) {
      throw this.sequence._customError(
        this,
        "stretchTo",
        "You're trying to stretch towards an object, while trying to randomly rotate the effect? What?"
      );
    }
    if (this._stretchTo && this._moveTowards) {
      throw this.sequence._customError(
        this,
        "stretchTo",
        "You're trying to stretch towards an object, while moving towards it? You're insane."
      );
    }
    if (this._attachTo && this._stretchTo?.attachTo && (this._startTime || this._endTime) && this._isRangedEffect) {
      throw this.sequence._customError(
        this,
        "stretchTo",
        "Dual-attached range-finding effects combined while using any of the time methods is stable - modern web browsers cannot handle it and it may crash them, so this feature has been disabled."
      );
    }
    const source2 = this._getSourceObject();
    const target = this._getTargetObject();
    if (!this._screenSpace && this._persistOptions?.persistTokenPrototype && this._masks.filter((uuid) => uuid !== source2).length > 0) {
      this.sequence._showWarning(
        this,
        "persist",
        "You have applied persistTokenPrototype with multiple masks from objects in the scene - these will not be persisted across scenes!",
        true
      );
    }
    if (!source2 && !target && !this._screenSpace) {
      throw this.sequence._customError(
        this,
        "play",
        "Could not determine where to play the effect!"
      );
    }
  }
  /**
   * @OVERRIDE
   */
  async preRun() {
    const oldSource = this._source;
    const crosshairSource = this.sequence?.crosshairs?.[this._source];
    if (typeof this._source === "string" && crosshairSource) {
      this._source = crosshairSource.uuid;
    }
    const oldTarget = this._target?.target;
    const crosshairTarget = this.sequence?.crosshairs?.[this._target?.target];
    if (typeof this._target?.target === "string" && crosshairTarget) {
      this._target.target = crosshairTarget.uuid;
    }
    if (this._attachTo && !this._attachTo.active && typeof this._source === "string" && crosshairSource) {
      this._attachTo.active = !!crosshairSource;
    }
    if (this._name && (oldSource !== this._source || oldTarget !== this._target?.target)) {
      this.sequence.nameOffsetMap[this._name].source = this._getSourceObject();
      this.sequence.nameOffsetMap[this._name].target = this._getTargetObject();
    }
    const source2 = this._getSourceObject();
    if (this._persistOptions?.persistTokenPrototype && !(this._attachTo?.active || is_UUID(source2))) {
      this._persistOptions.persistTokenPrototype = false;
    }
  }
  /**
   * @OVERRIDE
   * @returns {Promise<void>}
   */
  async run() {
    if (!user_can_do("permissions-effect-create") || !this._playEffect) {
      if (!user_can_do("permissions-effect-create")) {
        foundry.utils.debounce(EffectSection.debounceWarning, 1e3);
      }
      return new Promise((resolve) => {
        resolve();
      });
    }
    if (!this._deserializedData) this._expressWarnings();
    const data = await this._sanitizeEffectData();
    if (Hooks.call("preCreateSequencerEffect", data) === false) return;
    let push2 = !(data?.users?.length === 1 && data?.users?.includes(game.userId)) && !this.sequence.local && !this.sequence.remote;
    let canvasEffectData = await Sequencer.EffectManager.play(data, push2);
    let totalDuration = this._currentWaitTime;
    if (this._persist) {
      totalDuration += await canvasEffectData.promise;
    } else {
      totalDuration += await canvasEffectData.duration;
    }
    await new Promise((resolve) => setTimeout(resolve, totalDuration));
  }
  /**
   * @private
   */
  _applyTraits() {
    Object.assign(this.constructor.prototype, traits.files);
    Object.assign(this.constructor.prototype, traits.audio);
    Object.assign(this.constructor.prototype, traits.elevation);
    Object.assign(this.constructor.prototype, traits.levels);
    Object.assign(this.constructor.prototype, traits.moves);
    Object.assign(this.constructor.prototype, traits.opacity);
    Object.assign(this.constructor.prototype, traits.rotation);
    Object.assign(this.constructor.prototype, traits.scale);
    Object.assign(this.constructor.prototype, traits.time);
    Object.assign(this.constructor.prototype, traits.users);
    Object.assign(this.constructor.prototype, traits.animation);
    Object.assign(this.constructor.prototype, traits.filter);
    Object.assign(this.constructor.prototype, traits.tint);
    Object.assign(this.constructor.prototype, traits.blendMode);
    Object.assign(this.constructor.prototype, traits.location);
    Object.assign(this.constructor.prototype, traits.offset);
    Object.assign(this.constructor.prototype, traits.text);
    Object.assign(this.constructor.prototype, traits.name);
  }
  /**
   * @private
   */
  async _initialize() {
    this._mirrorX = this._mirrorX || this._randomMirrorX && Math.random() < 0.5;
    this._mirrorY = this._mirrorY || this._randomMirrorY && Math.random() < 0.5;
    if (this._copySprite && !this._file) {
      const ringEnabled = this._copySprite.object instanceof TokenDocument && this._copySprite.object.ring?.enabled;
      if (!ringEnabled) {
        this._file = this._copySprite.object?.texture?.src ?? "";
      }
      if (this._source === null) {
        this._source = this._validateLocation(this._copySprite.object);
      }
      if (this._size === null) {
        const placeable = this._copySprite.object.object ?? null;
        const meshWidth = placeable?.mesh?.width;
        const meshHeight = placeable?.mesh?.height;
        const hasMeshSize = Number.isFinite(meshWidth) && meshWidth > 0 && Number.isFinite(meshHeight) && meshHeight > 0;
        const fallback = get_object_dimensions(this._copySprite.object);
        this._size = {
          width: hasMeshSize ? meshWidth : fallback?.width ?? canvas.grid.size,
          height: hasMeshSize ? meshHeight : fallback?.height ?? canvas.grid.size,
          gridUnits: false
        };
      }
      if (this._mirrorX === null && (this._copySprite.object.mirrorX || this._copySprite.object?.tile && this._copySprite.object?.tile.scale.x < 0)) {
        this._mirrorX = true;
      }
      if (this._mirrorY === null && (this._copySprite.object.mirrorY || this._copySprite.object?.tile && this._copySprite.object?.tile.scale.y < 0)) {
        this._mirrorY = true;
      }
      if (this._angle === null && this._copySprite.object?.rotation) {
        this._angle = -this._copySprite.object.rotation;
      }
      this._randomOffset = {
        source: this._randomOffset?.source ?? this._copySprite.options.randomOffset,
        target: this._randomOffset?.target ?? false
      };
    }
    if (this._name) {
      this.sequence.nameOffsetMap ||= {};
      if (!this.sequence.nameOffsetMap[this._name]) {
        const source2 = this._getSourceObject();
        const target = this._getTargetObject();
        if (this._offsetLegacy && !this._offset) {
          this._offset = {
            source: !target ? this._offsetLegacy : false,
            target: !!target ? this._offsetLegacy : false
          };
        }
        if (this._randomOffsetLegacy && !this._randomOffset) {
          this._randomOffset = {
            source: !target ? this._randomOffsetLegacy : false,
            target: !!target ? this._randomOffsetLegacy : false
          };
        }
        this.sequence.nameOffsetMap[this._name] = {
          seed: `${this._name}-${foundry.utils.randomID()}`,
          source: source2,
          target,
          randomOffset: this._randomOffset,
          angle: this._angle,
          randomRotation: this._randomRotation,
          mirrorX: this._mirrorX,
          mirrorY: this._mirrorY,
          missed: this._missed,
          offset: this._offset,
          repetitions: this._repetitions,
          twister: {}
        };
      }
    }
    if (!this._file && !this._copySprite && !this._text && !this._shapes.length && this.sequence.softFail) {
      this._playEffect = false;
      return;
    }
    if (this._copySprite && !this._file) {
      return;
    }
    let fileData = this._file ? await this._determineFile(this._file) : {
      file: this._file,
      forcedIndex: false,
      customRange: false
    };
    this._isRangedEffect = fileData?.file?.rangeFind;
    if (fileData.customRange || fileData.file?.dbPath) return;
    let exists = false;
    try {
      exists = await SequencerFileCache.srcExists(fileData.file);
    } catch (err) {
    }
    if (!exists) {
      if (this.sequence.softFail) {
        this._playEffect = false;
        return;
      }
      throw this.sequence._customError(
        this,
        "Play",
        `Could not find file:<br>${fileData.file}`
      );
    }
  }
  /**
   * @private
   */
  _getSourceObject() {
    if (!this._source || typeof this._source !== "object") {
      return this._source;
    }
    if (this._source instanceof CrosshairsPlaceable || this._source instanceof CrosshairsDocument) {
      const doc = this._source?.document ?? this._source;
      if (this._attachTo) {
        return get_object_identifier(doc.object);
      }
      return doc.getOrientation().source;
    }
    if (this._source?.cachedLocation || !this._attachTo) {
      return get_object_canvas_data(this._source, { uuid: true });
    }
    return get_object_identifier(this._source) ?? get_object_canvas_data(this._source, { uuid: true });
  }
  /**
   * @private
   */
  _getTargetObject() {
    if (!this._target) return this._target;
    if (typeof this._target.target !== "object") {
      return this._target.target;
    }
    if (this._target?.target instanceof CrosshairsPlaceable || this._target?.target instanceof CrosshairsDocument) {
      const doc = this._target?.target?.document ?? this._target?.target;
      if (this._stretchTo?.attachTo || this._rotateTowards?.attachTo) {
        return get_object_identifier(doc.object);
      }
      const orientation = doc.getOrientation();
      return orientation.target ?? orientation.source;
    }
    if (this._target?.target?.cachedLocation || !(this._stretchTo?.attachTo || this._rotateTowards?.attachTo)) {
      return get_object_canvas_data(this._target.target, { measure: true, uuid: true });
    }
    return get_object_identifier(this._target.target) ?? get_object_canvas_data(this._target.target, { measure: true, uuid: true });
  }
  /**
   * @private
   */
  async _sanitizeEffectData() {
    if (this._deserializedData) {
      this._deserializedData.creationTimestamp = +/* @__PURE__ */ new Date();
      this._deserializedData.remote = true;
      return this._deserializedData;
    }
    let file = "";
    let forcedIndex = null;
    let customRange = null;
    if (!this._copySprite || this._file) {
      const fileData = this._file && this._playEffect ? await this._determineFile(this._file) : {
        file: this._file,
        forcedIndex: false,
        customRange: false
      };
      file = fileData.file;
      forcedIndex = fileData.forcedIndex;
      customRange = fileData.customRange;
    }
    let source2 = this._getSourceObject();
    let target = this._getTargetObject();
    this._temporaryEffect = this._temporaryEffect || (source2 instanceof foundry.abstract.Document || source2 instanceof foundry.canvas.placeables.MeasuredTemplate ? !is_UUID(source2?.uuid) : this._temporaryEffect || false);
    if (this._offsetLegacy) {
      this._offset = {
        source: !target && this._offset?.source ? this._offsetLegacy : this._offset?.source,
        target: !!target && this._offset?.target ? this._offsetLegacy : this._offset?.target
      };
    }
    if (this._randomOffsetLegacy) {
      this._randomOffset = {
        source: !target && this._randomOffset?.source ? this._randomOffsetLegacy : this._randomOffset?.source,
        target: !!target && this._randomOffset?.target ? this._randomOffsetLegacy : this._randomOffset?.target
      };
    }
    if (this._selfMask) {
      this._masks.push(
        get_object_identifier(this._source) ?? get_object_canvas_data(this._source, { uuid: false })
      );
    }
    let sceneId = game.user.viewedScene;
    if (is_UUID(source2)) {
      const potentialSceneId = source2.split(".")[1];
      if (game.scenes.get(potentialSceneId)) {
        sceneId = potentialSceneId;
      }
    } else if (is_UUID(target)) {
      const potentialSceneId = target.split(".")[1];
      if (game.scenes.get(potentialSceneId)) {
        sceneId = potentialSceneId;
      }
    }
    let data = foundry.utils.duplicate({
      /**
       * Core properties
       */
      _id: foundry.utils.randomID(),
      flagVersion: flagManager.latestFlagVersion,
      sequenceId: this.sequence.id,
      creationTimestamp: Date.now(),
      sceneId,
      creatorUserId: game.userId,
      moduleName: this.sequence.moduleName,
      users: this._users ? Array.from(this._users) : false,
      name: this._name,
      origin: this._origin,
      index: this.sequence.effectIndex,
      repetition: this._currentRepetition,
      private: this._private,
      temporary: this._temporaryEffect,
      tiedDocuments: Array.from(new Set(this._tiedDocuments)),
      local: this.sequence.local,
      remote: this.sequence.remote,
      /**
       * Source/target properties
       */
      source: source2,
      target,
      rotateTowards: this._rotateTowards,
      stretchTo: this._stretchTo ? {
        attachTo: this._stretchTo.attachTo,
        onlyX: this._stretchTo.onlyX,
        requiresLineOfSight: this._stretchTo.requiresLineOfSight,
        hideLineOfSight: this._stretchTo.hideLineOfSight
      } : false,
      moveTowards: this._moveTowards ? {
        ease: this._moveTowards.ease,
        rotate: this._moveTowards.rotate
      } : false,
      attachTo: this._attachTo,
      missed: this._missed,
      copySprite: this._copySprite ? {
        uuid: this._copySprite.object?.uuid,
        ...this._copySprite.options
      } : false,
      /**
       * Sprite properties
      	 */
      file: file?.dbPath ?? file,
      customRange,
      forcedIndex,
      fileOptions: this._fileOptions,
      text: this._text,
      tilingTexture: this._tilingTexture,
      masks: Array.from(new Set(this._masks)),
      shapes: this._shapes,
      volume: this._volume,
      isometric: this._isometric,
      syncGroup: this._syncGroup,
      // Transforms
      scale: this._getCalculatedScale("scale"),
      spriteScale: this._getCalculatedScale("spriteScale"),
      angle: this._angle,
      size: this._size,
      offset: this._offset,
      anchor: this._anchor,
      spriteOffset: this._spriteOffset,
      spriteAnchor: this._spriteAnchor,
      template: this._template,
      zeroSpriteRotation: this._zeroSpriteRotation,
      randomOffset: this._randomOffset,
      randomRotation: this._randomRotation,
      scaleToObject: this._scaleToObject,
      elevation: this._elevation,
      sortLayer: this._sortLayer,
      levels: this._levels,
      aboveLighting: this._aboveLighting,
      aboveInterface: this._aboveInterface,
      xray: this._xray,
      constrainedByWalls: this._constrainedByWalls,
      // Appearance
      zIndex: this._zIndex,
      opacity: is_real_number(this._opacity) ? this._opacity : 1,
      filters: this._filters,
      loopOptions: this._loopOptions,
      spriteRotation: this._spriteRotation,
      randomSpriteRotation: this._randomSpriteRotation,
      tint: this._tint?.decimal,
      blendMode: this._blendMode,
      flipX: this._mirrorX,
      flipY: this._mirrorY,
      /**
       * Time properties
       */
      duration: this._duration,
      persist: this._persist,
      persistOptions: this._persistOptions,
      playbackRate: this._playbackRate,
      extraEndDuration: this._extraEndDuration,
      time: this._startTime || this._endTime ? {
        start: is_real_number(this._startTime) ? {
          value: this._startTime,
          isPerc: this._startPerc
        } : false,
        end: is_real_number(this._endTime) ? {
          value: this._endTime,
          isPerc: this._endPerc
        } : false,
        isRange: this._isRange
      } : false,
      /**
       * Animation properties
       */
      moves: this._moveTowards,
      moveSpeed: this._moveSpeed,
      fadeIn: this._fadeIn,
      fadeOut: this._fadeOut,
      scaleIn: this._scaleIn,
      scaleOut: this._scaleOut,
      rotateIn: this._rotateIn,
      rotateOut: this._rotateOut,
      fadeInAudio: this._fadeInAudio,
      fadeOutAudio: this._fadeOutAudio,
      animations: this._animations,
      /**
       * Screenspace properties
       */
      screenSpace: this._screenSpace,
      screenSpaceAboveUI: this._screenSpaceAboveUI,
      screenSpaceAnchor: this._screenSpaceAnchor,
      screenSpacePosition: this._screenSpacePosition,
      screenSpaceScale: this._screenSpaceScale,
      nameOffsetMap: this.sequence.nameOffsetMap
    });
    for (let override of this._overrides) {
      data = await override(this, data);
    }
    if ((typeof data.file !== "string" || data.file === "") && !data.text && !data.shapes && !data.customRange) {
      throw this.sequence._customError(
        this,
        "file",
        "an effect must have a file, text, or have a shape!"
      );
    }
    return data;
  }
  async _serialize() {
    const data = await super._serialize();
    await this.preRun();
    const sectionData = await this._sanitizeEffectData();
    return {
      ...data,
      type: "effect",
      sectionData
    };
  }
  async _deserialize(data) {
    this._deserializedData = data.sectionData;
    return super._deserialize(data);
  }
  /**
   * @private
   */
  _getCalculatedScale(type) {
    const min = this["_" + type + "Min"];
    const max = this["_" + type + "Max"];
    let scale2 = min;
    if (is_real_number(min)) {
      if (max && is_real_number(max)) {
        scale2 = random_float_between(min, max);
      }
      scale2 = {
        x: scale2,
        y: scale2
      };
    }
    return {
      x: scale2?.x ?? 1,
      y: scale2?.y ?? 1
    };
  }
}
class SoundSection extends Section {
  constructor(inSequence, inFile = "") {
    super(inSequence);
    this._file = inFile;
    this._volume = 0.8;
    this._channel = "interface";
    this._overrides = [];
    this._locationOptions = {};
    this._toLocation = false;
    this._moveTowards = false;
    this._global = false;
    this._persist = false;
    this._persistOptions = false;
    this._loopOptions = false;
    this._panSound = false;
    this._extraEndDuration = false;
    this._levels = null;
    this._elevation = null;
  }
  get _target() {
    return this._toLocation || this._moveTowards || false;
  }
  static niceName = "Sound";
  /**
   * Declares which file to be played. This may also be an array of paths, which will be randomly picked from each
   * time the section is played.
   *
   * @param {string|array} inFile
   * @returns this
   */
  file(inFile) {
    this._file = inFile;
    return this;
  }
  /**
   * Adds a function that will run at the end of the sound serialization step, but before it is played. Allows direct
   * modifications of sound's data.
   *
   * @param {function} inFunc
   * @returns {SoundSection}
   */
  addOverride(inFunc) {
    if (!is_function(inFunc))
      throw this.sequence._customError(
        this,
        "addOverride",
        "The given function needs to be an actual function."
      );
    this._overrides.push(inFunc);
    return this;
  }
  /**
   * Sets the radius (in scene units) within which the sound is audible. Requires `.atLocation()` to be set,
   * otherwise the sound remains global.
   *
   * @param {number} inNumber
   * @returns {SoundSection}
   */
  radius(inNumber) {
    if (!is_real_number(inNumber))
      throw this.sequence._customError(
        this,
        "radius",
        "inNumber must be of type number"
      );
    this._locationOptions["radius"] = inNumber;
    return this;
  }
  /**
   * Whether the sound is completely blocked by walls. Requires `.atLocation()` to be set. If `true`, the
   * `.muffledEffect()` will have no effect.
   *
   * @param {boolean} [inBool=true]
   * @returns {SoundSection}
   */
  constrainedByWalls(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "constrainedByWalls",
        `inBool must be of type boolean`
      );
    this._locationOptions["walls"] = inBool;
    return this;
  }
  /**
   * Whether the sound's volume is eased by the distance from its origin. Requires `.atLocation()` to be set.
   *
   * @param {boolean} [inBool=true]
   * @returns {SoundSection}
   */
  distanceEasing(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "distanceEasing",
        `inBool must be of type boolean`
      );
    this._locationOptions["easing"] = inBool;
    return this;
  }
  /**
   * Sets the audio channel the sound is played through. Accepts any channel name available on `game.audio` that
   * is an `AudioContext` (eg. `"music"`, `"environment"`, `"interface"`).
   *
   * @param {string} inString
   * @returns {SoundSection}
   */
  audioChannel(inString) {
    if (typeof inString !== "string")
      throw this.sequence._customError(
        this,
        "audioChannel",
        `inString must be of type string`
      );
    if (!(game.audio?.[inString] instanceof AudioContext)) {
      const validChannels = Object.entries(game.audio).filter((e) => e[1] instanceof AudioContext).map((e) => e[0]);
      throw this.sequence._customError(
        this,
        "audioChannel",
        `inString must be of type ${validChannels.slice(0, -1).join(", ")}, or ${validChannels.slice(-1)}`
      );
    }
    this._channel = inString;
    return this;
  }
  /**
   * Whether GMs always hear the sound as if standing at its origin, regardless of token position. Requires
   * `.atLocation()` to be set.
   *
   * @param {boolean} [inBool=true]
   * @returns {SoundSection}
   */
  alwaysForGMs(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "alwaysForGMs",
        `inBool must be of type boolean`
      );
    this._locationOptions["gmAlways"] = inBool;
    return this;
  }
  /**
   * Allows you to control the number of loops and the delays between each loop.
   *
   * @param {object} [inOptions]
   * @param {number} [inOptions.loopDelay=0] - delay (in ms) between each loop
   * @param {number} [inOptions.loops=0] - number of loops before ending, 0 for indefinite
   * @param {boolean} [inOptions.endOnLastLoop=false] - whether the sound should end when reaching the last loop
   * @returns {SoundSection}
   */
  loopOptions(inOptions = {}) {
    if (typeof inOptions !== "object")
      throw this.sequence._customError(
        this,
        "loopOptions",
        `inOptions must be of type object`
      );
    if (inOptions.maxLoops !== void 0) {
      custom_warning(
        "Sequencer",
        "Sound | loopOptions' maxLoops option is deprecated, please use loops instead"
      );
      inOptions = { ...inOptions, loops: inOptions.loops ?? inOptions.maxLoops };
      delete inOptions.maxLoops;
    }
    inOptions = foundry.utils.mergeObject(
      {
        loopDelay: 0,
        loops: 0,
        endOnLastLoop: false
      },
      inOptions
    );
    if (typeof inOptions.endOnLastLoop !== "boolean")
      throw this.sequence._customError(
        this,
        "loopOptions",
        "inOptions.endOnLastLoop must be of type boolean"
      );
    if (typeof inOptions.loopDelay !== "number" || inOptions.loopDelay < 0)
      throw this.sequence._customError(
        this,
        "loopOptions",
        "inOptions.loopDelay must be of type number that is not below 0"
      );
    if (typeof inOptions.loops !== "number" || inOptions.loops < 0)
      throw this.sequence._customError(
        this,
        "loopOptions",
        "inOptions.loops must be of type number that is not below 0"
      );
    this._loopOptions = inOptions;
    return this;
  }
  /**
   * Sets an audio effect to be applied while the sound is heard normally (no walls between source and listener).
   * Requires `.atLocation()` to be set.
   *
   * @param {object} [options]
   * @param {string} [options.type] - one of the keys in `CONFIG.soundEffects` (eg. `"lowpass"`, `"highpass"`, `"reverb"`)
   * @param {number} [options.intensity=0] - intensity of the effect
   * @returns {SoundSection}
   */
  baseEffect(options = {}) {
    options = foundry.utils.mergeObject({
      type: "",
      intensity: 0
    }, options);
    if (typeof options.type !== "string" || !CONFIG.soundEffects[options.type])
      throw this.sequence._customError(
        this,
        "baseEffect",
        `options.type must be of type string, and one of ${Object.keys(CONFIG.soundEffects).join(", ")}`
      );
    if (!is_real_number(options.intensity))
      throw this.sequence._customError(
        this,
        "baseEffect",
        "options.intensity must be of type number"
      );
    this._locationOptions["baseEffect"] = {
      type: options.type,
      intensity: options.intensity
    };
    return this;
  }
  /**
   * Sets an audio effect to be applied while the sound is heard through a wall. Requires `.atLocation()` to be
   * set and `.constrainedByWalls()` to be `false` (the default).
   *
   * @param {object} [options]
   * @param {string} [options.type] - one of the keys in `CONFIG.soundEffects` (eg. `"lowpass"`, `"highpass"`, `"reverb"`)
   * @param {number} [options.intensity=0] - intensity of the effect
   * @returns {SoundSection}
   */
  muffledEffect(options = {}) {
    options = foundry.utils.mergeObject({
      type: "",
      intensity: 0
    }, options);
    if (typeof options.type !== "string" || !CONFIG.soundEffects[options.type])
      throw this.sequence._customError(
        this,
        "baseEffect",
        `options.type must be of type string, and one of ${Object.keys(CONFIG.soundEffects).join(", ")}`
      );
    if (!is_real_number(options.intensity))
      throw this.sequence._customError(
        this,
        "baseEffect",
        "options.intensity must be of type number"
      );
    this._locationOptions["muffledEffect"] = {
      type: options.type,
      intensity: options.intensity
    };
    return this;
  }
  /**
   * Sets the target location of the sound, used together with `.atLocation()` so the sound has a direction (for
   * `.panSound()`, distance falloff, etc).
   *
   * @param {object|string} inLocation - reference to a placeable, document, canvas coordinate, or a string name (see `.name()`)
   * @param {object} [inOptions]
   * @param {boolean} [inOptions.cacheLocation=false] - cache the target's location at sequence build time instead of resolving it at runtime
   * @param {object|boolean} [inOptions.offset=false] - offset the target by `{ x, y }`
   * @param {number|boolean} [inOptions.randomOffset=false] - offset the target by a random amount; if a number, used as a multiplier on the object size
   * @param {boolean} [inOptions.gridUnits=false] - treat the offset's `x` and `y` as grid units
   * @param {boolean} [inOptions.local=false] - offset relative to the sound's rotation rather than world axes
   * @returns {SoundSection}
   */
  toLocation(inLocation, inOptions = {}) {
    if (!inLocation || !(typeof inLocation === "object" || typeof inLocation === "string")) {
      throw this.sequence._customError(
        this,
        "toLocation",
        `inLocation is invalid, and must be of type of object, string, placeable object, or document`
      );
    }
    if (typeof inOptions !== "object")
      throw this.sequence._customError(
        this,
        "toLocation",
        `inOptions must be of type object`
      );
    inOptions = foundry.utils.mergeObject(
      {
        cacheLocation: false,
        offset: false,
        randomOffset: false,
        gridUnits: false,
        local: false
      },
      inOptions
    );
    inLocation = this._validateLocation(inLocation);
    if (inLocation === void 0)
      throw this.sequence._customError(
        this,
        "toLocation",
        "could not find position of given object"
      );
    if (typeof inOptions.cacheLocation !== "boolean")
      throw this.sequence._customError(
        this,
        "toLocation",
        "inOptions.cacheLocation must be of type boolean"
      );
    if (!(typeof inOptions.randomOffset === "boolean" || is_real_number(inOptions.randomOffset)))
      throw this.sequence._customError(
        this,
        "toLocation",
        "inOptions.randomOffset must be of type boolean or number"
      );
    if (inOptions.offset) {
      const offsetData = this._validateOffset(
        "toLocation",
        inOptions.offset,
        inOptions
      );
      this._offset = {
        source: this._offset?.source ?? false,
        target: offsetData
      };
    }
    this._randomOffset = {
      source: this._randomOffset?.source ?? false,
      target: inOptions.randomOffset
    };
    this._toLocation = inOptions.cacheLocation && typeof inLocation !== "string" ? get_object_canvas_data(inLocation, { uuid: false }) : inLocation;
    return this;
  }
  /**
   * Forces the sound to be heard globally, ignoring `.atLocation()`, `.radius()`, walls, and distance easing.
   *
   * @param {boolean} [inBool=true]
   * @returns {SoundSection}
   */
  globalSound(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "globalSound",
        `inBool must be of type boolean`
      );
    this._global = inBool;
    return this;
  }
  /**
   * Causes the sound to pan stereo (left/right) based on the position of the sound relative to the listener.
   *
   * @param {boolean} [inBool=true]
   * @param {object} [inOptions]
   * @param {number} [inOptions.innerEaseDistance=0] - distance within which the pan is at full strength
   * @param {number} [inOptions.outerEaseDistance=0] - distance beyond which the pan stops easing; must be greater than `innerEaseDistance` when both are non-zero
   * @returns {SoundSection}
   */
  panSound(inBool = true, inOptions = {}) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "panSound",
        `inBool must be of type boolean`
      );
    if (typeof inOptions !== "object")
      throw this.sequence._customError(
        this,
        "panSound",
        `inOptions must be of type object`
      );
    inOptions = foundry.utils.mergeObject(
      {
        innerEaseDistance: 0,
        outerEaseDistance: 0
      },
      inOptions
    );
    if (typeof inOptions.innerEaseDistance !== "number")
      throw this.sequence._customError(
        this,
        "panSound",
        "inOptions.innerEaseDistance must be of type number"
      );
    if (typeof inOptions.outerEaseDistance !== "number")
      throw this.sequence._customError(
        this,
        "panSound",
        "inOptions.outerEaseDistance must be of type number"
      );
    if (inOptions.innerEaseDistance > 0 && inOptions.outerEaseDistance > 0 && inOptions.innerEaseDistance >= inOptions.outerEaseDistance) {
      throw this.sequence._customError(
        this,
        "panSound",
        "inOptions.innerEaseDistance must be less than inOptions.outerEaseDistance"
      );
    }
    this._panSound = {
      active: inBool,
      ...inOptions
    };
    return this;
  }
  /**
   * Adds extra time (in ms) before a persisted sound is considered ended. Only meaningful on `.persist()`ed sounds.
   *
   * @param {number} inExtraDuration
   * @returns {SoundSection}
   */
  extraEndDuration(inExtraDuration) {
    if (typeof inExtraDuration !== "number")
      throw this.sequence._customError(
        this,
        "extraEndDuration",
        "inExtraDuration must be of type number"
      );
    this._extraEndDuration = inExtraDuration;
    return this;
  }
  /**
   * Causes the sound to persist on the canvas. The sound can be ended later via the Sound Manager.
   *
   * @param {boolean} [inBool=true]
   * @param {object} [inOptions]
   * @param {boolean} [inOptions.persistTokenPrototype=false] - when attached to a token, persist the sound on the token's prototype data
   * @returns {SoundSection}
   */
  persist(inBool = true, inOptions = {}) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "persist",
        "inBool must be of type boolean"
      );
    if (typeof inOptions !== "object")
      throw this.sequence._customError(
        this,
        "persist",
        `inOptions must be of type object`
      );
    inOptions = foundry.utils.mergeObject(
      {
        id: foundry.utils.randomID(),
        persistTokenPrototype: false
      },
      inOptions
    );
    if (typeof inOptions.persistTokenPrototype !== "boolean")
      throw this.sequence._customError(
        this,
        "persist",
        "inOptions.persistTokenPrototype must be of type boolean"
      );
    this._persist = inBool;
    this._persistOptions = inOptions;
    return this;
  }
  /**
   *  A smart method that can take a reference to an object, or a direct on the canvas to attach an sound to,
   *  or a string reference (see .name())
   *
   * @param {Object|String} inObject
   * @param {Object} inOptions
   * @returns {SoundSection}
   */
  attachTo(inObject, inOptions = {}) {
    if (!inObject || !(typeof inObject === "object" || typeof inObject === "string")) {
      throw this.sequence._customError(
        this,
        "attachTo",
        `inObject is invalid, and must be of type of object, string, placeable object, or document`
      );
    }
    if (typeof inOptions !== "object")
      throw this.sequence._customError(
        this,
        "attachTo",
        `inOptions must be of type object`
      );
    inOptions = foundry.utils.mergeObject(
      {
        bindVisibility: true,
        bindElevation: true
      },
      inOptions
    );
    const validatedObject = this._validateLocation(inObject);
    if (validatedObject === void 0)
      throw this.sequence._customError(
        this,
        "attachTo",
        "could not find given object"
      );
    if (typeof inOptions.bindVisibility !== "boolean")
      throw this.sequence._customError(
        this,
        "attachTo",
        `inOptions.bindVisibility must be of type boolean`
      );
    if (typeof inOptions.bindElevation !== "boolean")
      throw this.sequence._customError(
        this,
        "attachTo",
        "inOptions.bindElevation must be of type boolean"
      );
    this._source = validatedObject;
    this._attachTo = {
      active: !!validatedObject,
      bindVisibility: inOptions.bindVisibility,
      bindElevation: inOptions.bindElevation
    };
    return this;
  }
  /**
   * @private
   */
  _applyTraits() {
    Object.assign(this.constructor.prototype, traits.files);
    Object.assign(this.constructor.prototype, traits.audio);
    Object.assign(this.constructor.prototype, traits.elevation);
    Object.assign(this.constructor.prototype, traits.levels);
    Object.assign(this.constructor.prototype, traits.time);
    Object.assign(this.constructor.prototype, traits.users);
    Object.assign(this.constructor.prototype, traits.name);
    Object.assign(this.constructor.prototype, traits.location);
    Object.assign(this.constructor.prototype, traits.offset);
    Object.assign(this.constructor.prototype, traits.moves);
  }
  /**
   * @private
   */
  async _initialize() {
    if (this._name) {
      this.sequence.nameOffsetMap ||= {};
      if (!this.sequence.nameOffsetMap[this._name]) {
        const source2 = this._getSourceObject();
        const target = this._getTargetObject();
        this.sequence.nameOffsetMap[this._name] = {
          seed: `${this._name}-${foundry.utils.randomID()}`,
          source: source2,
          target,
          twister: {}
        };
      }
    }
  }
  /**
   * @OVERRIDE
   */
  async preRun() {
    const oldSource = this._source;
    const crosshairSource = this.sequence?.crosshairs?.[this._source];
    if (typeof this._source === "string" && crosshairSource) {
      this._source = crosshairSource.uuid;
    }
    const oldTarget = this._target?.target;
    const crosshairTarget = this.sequence?.crosshairs?.[this._target?.target];
    if (typeof this._target?.target === "string" && crosshairTarget) {
      this._target.target = crosshairTarget.uuid;
    }
    if (this._attachTo && !this._attachTo.active && typeof this._source === "string" && crosshairSource) {
      this._attachTo.active = !!crosshairSource;
    }
    if (this._name && (oldSource !== this._source || oldTarget !== this._target?.target)) {
      this.sequence.nameOffsetMap[this._name].source = this._getSourceObject();
      this.sequence.nameOffsetMap[this._name].target = this._getTargetObject();
    }
    const source2 = this._getSourceObject();
    if (this._persistOptions?.persistTokenPrototype && !(this._attachTo?.active || is_UUID(source2))) {
      this._persistOptions.persistTokenPrototype = false;
    }
  }
  /**
   * @OVERRIDE
   * @returns {Promise}
   */
  async run() {
    const playData = await this._sanitizeSoundData();
    if (!playData.play && this.sequence.softFail) {
      return new Promise((reject) => {
        reject();
      });
    }
    if (!playData?.play) {
      this.sequence._customError(
        this,
        "Play",
        `File not found: ${playData.file}`
      );
      return new Promise((reject) => reject());
    }
    if (Hooks.call("preCreateSequencerSound", playData) === false) return;
    let push2 = !(playData?.users?.length === 1 && playData?.users?.includes(game.userId)) && !this.sequence.local && !this.sequence.remote;
    let duration = await SequencerSoundManager.play(playData, push2);
    await new Promise(
      (resolve) => setTimeout(resolve, this._currentWaitTime + duration)
    );
  }
  _getSourceObject() {
    if (!this._source || typeof this._source !== "object") {
      return this._source;
    }
    if (this._source instanceof CrosshairsPlaceable || this._source instanceof CrosshairsDocument) {
      const doc = this._source?.document ?? this._source;
      if (this._attachTo) {
        return get_object_identifier(doc.object);
      }
      return doc.getOrientation().source;
    }
    if (this._source?.cachedLocation || !this._attachTo) {
      return get_object_canvas_data(this._source, { uuid: true });
    }
    return get_object_identifier(this._source) ?? get_object_canvas_data(this._source, { uuid: true });
  }
  /**
   * @private
   */
  _getTargetObject() {
    let target = this._target.target ?? this._target;
    if (!target || typeof target !== "object") {
      return target;
    }
    if (target instanceof CrosshairsPlaceable || target instanceof CrosshairsDocument) {
      const doc = target?.document ?? target;
      return doc.getOrientation().source;
    }
    if (target?.cachedLocation) {
      return get_object_canvas_data(target, { uuid: true });
    }
    return get_object_identifier(target) ?? get_object_canvas_data(target, { uuid: true });
  }
  /**
   * @returns {Promise}
   * @private
   */
  async _sanitizeSoundData() {
    if (this._deserializedData) {
      return this._deserializedData;
    }
    if (!this._file) {
      return {
        play: false,
        file: false
      };
    }
    let { file, forcedIndex, customRange } = await this._determineFile(this._file);
    if (!file) {
      return {
        play: false,
        file: false,
        customRange: false
      };
    }
    let data = {
      _id: foundry.utils.randomID(),
      flagVersion: flagManager.latestFlagVersion,
      play: true,
      file: file.dbPath ?? file,
      forcedIndex,
      customRange,
      creationTimestamp: Date.now(),
      creatorUserId: game.user.id,
      source: this._getSourceObject(),
      target: this._getTargetObject(),
      attachTo: this._attachTo,
      moveTowards: this._moveTowards ? {
        ease: this._moveTowards.ease,
        rotate: this._moveTowards.rotate
      } : false,
      offset: this._offset,
      randomOffset: this._randomOffset,
      locationOptions: this._locationOptions,
      loopOptions: this._loopOptions,
      volume: this._volume,
      channel: this._channel,
      global: this._global,
      fadeIn: this._fadeInAudio,
      fadeOut: this._fadeOutAudio,
      time: this._startTime || this._endTime ? {
        start: is_real_number(this._startTime) ? {
          value: this._startTime,
          isPerc: this._startPerc
        } : false,
        end: is_real_number(this._endTime) ? {
          value: this._endTime,
          isPerc: this._endPerc
        } : false,
        isRange: this._isRange
      } : false,
      duration: this._duration,
      sceneId: this._source?.parent?.id || game.user.viewedScene,
      users: this._users ? Array.from(this._users) : null,
      name: this._name,
      origin: this._origin,
      seed: `${this._name ?? "sequencer-sound"}-${foundry.utils.randomID()}-${this._currentRepetition}`,
      nameOffsetMap: this.sequence.nameOffsetMap,
      persist: this._persist,
      persistOptions: this._persistOptions,
      panSound: this._panSound,
      extraEndDuration: this._extraEndDuration,
      levels: this._levels,
      elevation: this._elevation
    };
    for (let override of this._overrides) {
      data = await override(this, data);
    }
    return data;
  }
  async _serialize() {
    const data = await super._serialize();
    const sectionData = await this._sanitizeSoundData();
    return {
      ...data,
      type: "sound",
      sectionData
    };
  }
  async _deserialize(data) {
    this._deserializedData = data.sectionData;
    return super._deserialize(data);
  }
}
class AnimationSection extends Section {
  static niceName = "Animation";
  constructor(inSequence, inTarget) {
    super(inSequence);
    this._teleportTo = false;
    this._originObject = false;
    this._moveSpeed = 23;
    this._offset = { x: 0, y: 0 };
    this._closestSquare = false;
    this._snapToGrid = false;
    this._hide = void 0;
    if (inTarget) this.on(inTarget);
  }
  /**
   * Sets the target object to be animated
   *
   * @param {object|string} inTarget
   * @returns {AnimationSection}
   */
  on(inTarget) {
    inTarget = this._validateLocation(inTarget);
    if (!inTarget)
      throw this.sequence._customError(
        this,
        "on",
        "could not find position of given object"
      );
    this._originObject = this._validateLocation(inTarget);
    return this;
  }
  /**
   * Sets the location to teleport the target object to
   *
   * @param {object|string} inTarget
   * @param {object} options
   * @returns {AnimationSection}
   */
  teleportTo(inTarget, options = {}) {
    options = foundry.utils.mergeObject(
      {
        delay: 0,
        relativeToCenter: false
      },
      options
    );
    if (!is_real_number(options.delay))
      throw this.sequence._customError(
        this,
        "teleportTo",
        "options.delay must be of type number"
      );
    inTarget = this._validateLocation(inTarget);
    if (!inTarget)
      throw this.sequence._customError(
        this,
        "teleportTo",
        "could not find position of given object"
      );
    options.target = this._validateLocation(inTarget);
    this._teleportTo = options;
    return this;
  }
  /**
   * Sets the location to rotate the object to
   *
   * @param {object|string} inLocation
   * @param {object} options
   * @returns this
   */
  rotateTowards(inLocation, options = {}) {
    options = foundry.utils.mergeObject(
      {
        duration: 0,
        ease: "linear",
        delay: 0,
        rotationOffset: 0,
        towardsCenter: true,
        cacheLocation: false
      },
      options
    );
    if (!is_real_number(options.duration))
      throw this.sequence._customError(
        this,
        "rotateTowards",
        "options.duration must be of type number"
      );
    if (typeof options.ease !== "string")
      throw this.sequence._customError(
        this,
        "rotateTowards",
        "options.ease must be of type string"
      );
    if (!is_real_number(options.delay))
      throw this.sequence._customError(
        this,
        "rotateTowards",
        "options.delay must be of type number"
      );
    if (!is_real_number(options.rotationOffset))
      throw this.sequence._customError(
        this,
        "rotateTowards",
        "options.rotationOffset must be of type number"
      );
    if (typeof options.towardsCenter !== "boolean")
      throw this.sequence._customError(
        this,
        "rotateTowards",
        "options.towardsCenter must be of type boolean"
      );
    if (typeof options.cacheLocation !== "boolean")
      throw this.sequence._customError(
        this,
        "rotateTowards",
        "options.cacheLocation must be of type boolean"
      );
    options.target = this._validateLocation(inLocation);
    if (!options.target)
      throw this.sequence._customError(
        this,
        "rotateTowards",
        "could not find position of given object"
      );
    options.target = options.cacheLocation ? get_object_position(options.target, { measure: true }) : options.target;
    this._rotateTowards = options;
    return this;
  }
  /**
   * Causes the movement or teleportation to be offset in the X and/or Y axis
   *
   * @param {object} inOffset
   * @returns {AnimationSection}
   */
  offset(inOffset) {
    inOffset = foundry.utils.mergeObject({ x: 0, y: 0 }, inOffset);
    this._offset = this._validateLocation(inOffset);
    return this;
  }
  /**
   * Causes the movement or teleportation to pick the closest non-intersecting square, if the target is a token or tile
   *
   * @param {boolean} inBool
   * @returns {AnimationSection}
   */
  closestSquare(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "closestSquare",
        "inBool must be of type boolean"
      );
    this._closestSquare = inBool;
    return this;
  }
  /**
   * Causes the final location to be snapped to the grid
   *
   * @param {boolean} inBool
   * @returns {AnimationSection}
   */
  snapToGrid(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "snapToGrid",
        "inBool must be of type boolean"
      );
    this._snapToGrid = inBool;
    return this;
  }
  /**
   * Causes the object to become hidden
   *
   * @param {boolean} inBool
   * @returns {AnimationSection}
   */
  hide(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "hide",
        "inBool must be of type boolean"
      );
    this._hide = inBool;
    return this;
  }
  /**
   * Causes the object to become visible
   *
   * @param {boolean} inBool
   * @returns {AnimationSection}
   */
  show(inBool = true) {
    if (typeof inBool !== "boolean")
      throw this.sequence._customError(
        this,
        "show",
        "inBool must be of type boolean"
      );
    this._hide = !inBool;
    return this;
  }
  /**
   * @private
   */
  async run() {
    return this._runAnimate();
  }
  /**
   * @private
   */
  _applyTraits() {
    Object.assign(this.constructor.prototype, traits.moves);
    Object.assign(this.constructor.prototype, traits.opacity);
    Object.assign(this.constructor.prototype, traits.rotation);
    Object.assign(this.constructor.prototype, traits.audio);
    Object.assign(this.constructor.prototype, traits.tint);
  }
  /**
   * @private
   */
  async _updateObject(obj, updates, animate = false, animation2 = {}) {
    const uuid = obj?.uuid ?? obj?.document?.uuid;
    await sequencerSocket.executeAsGM(
      SOCKET_HANDLERS.UPDATE_DOCUMENT,
      uuid,
      updates,
      { animate, animation: animation2, teleport: !!this._teleportTo }
    );
  }
  /**
   * @private
   */
  async _waitForTokenRefresh(obj) {
    let token;
    if (obj instanceof foundry.canvas.placeables.Token) {
      token = obj;
    }
    if (obj instanceof TokenDocument) {
      token = obj.object;
    }
    if (token == null || token.mesh != null) {
      return;
    }
    let refreshTokenID;
    return await new Promise((resolve) => {
      refreshTokenID = Hooks.on("refreshToken", async (tokenDoc) => {
        if (tokenDoc.document.actorId !== token.document.actorId || !token.mesh)
          return;
        Hooks.off("refreshToken", refreshTokenID);
        resolve();
      });
    });
  }
  /**
   * @private
   */
  async preRun() {
    await this._waitForTokenRefresh(this._originObject);
  }
  /**
   * @private
   */
  _getClosestSquare(origin, target) {
    let originLoc = get_object_position(origin, { exact: true });
    let targetLoc = get_object_position(target, { exact: true });
    let originDimensions = get_object_dimensions(origin);
    let targetDimensions = get_object_dimensions(target);
    let originBottom = Math.max(
      originDimensions.width - canvas.grid.size,
      canvas.grid.size
    );
    let originRight = Math.max(
      originDimensions.height - canvas.grid.size,
      canvas.grid.size
    );
    let ray = new foundry.canvas.geometry.Ray(originLoc, targetLoc);
    let dx = ray.dx;
    let dy = ray.dy;
    if (dx > 0 && Math.abs(dx) > originRight) {
      dx -= originDimensions.width;
    } else if (dx < 0 && Math.abs(dx) > targetDimensions.width) {
      dx += targetDimensions.height;
    } else {
      dx = 0;
    }
    if (dy > 0 && Math.abs(dy) > originBottom) {
      dy -= originDimensions.height;
    } else if (dy < 0 && Math.abs(dy) > targetDimensions.height) {
      dy += targetDimensions.height;
    } else {
      dy = 0;
    }
    const pos = {
      x: originLoc.x + dx,
      y: originLoc.y + dy,
      elevation: targetLoc?.elevation
    };
    if (!pos.elevation) {
      delete pos["elevation"];
    }
    return pos;
  }
  /**
   * This needs a rewrite, jeesus.
   */
  async _runAnimate() {
    let animData = {
      attributes: [],
      maxFPS: 1e3 / game.settings.get("core", "maxFPS"),
      lastTimespan: performance.now(),
      totalDt: 0
    };
    if (typeof this._moveTowards?.target === "string" && this.sequence.nameOffsetMap[this._moveTowards?.target]) {
      this._moveTowards.target = this.sequence.nameOffsetMap[this._moveTowards?.target].target ?? this.sequence.nameOffsetMap[this._moveTowards?.target].source;
    }
    if (typeof this._teleportTo?.target === "string" && this.sequence.nameOffsetMap[this._teleportTo?.target]) {
      this._teleportTo.target = this.sequence.nameOffsetMap[this._teleportTo?.target].target ?? this.sequence.nameOffsetMap[this._teleportTo?.target].source;
    }
    if (typeof this._rotateTowards?.target === "string" && this.sequence.nameOffsetMap[this._rotateTowards?.target]) {
      this._rotateTowards.target = this.sequence.nameOffsetMap[this._rotateTowards?.target].target ?? this.sequence.nameOffsetMap[this._rotateTowards?.target].source;
    }
    let overallDuration = this._duration ? this._duration : 0;
    const originLocation = get_object_position(this._originObject, {
      exact: true
    });
    if (this._rotateTowards) {
      let offset2 = (this._angle ? this._angle : 0) + this._rotateTowards.rotationOffset;
      let targetLoc = this._moveTowards?.target || this._teleportTo?.target || this._originObject;
      targetLoc = this._closestSquare ? this._getClosestSquare(this._originObject, targetLoc) : get_object_position(targetLoc, { exact: true });
      targetLoc.x += this._offset.x;
      targetLoc.y += this._offset.y;
      if (this._snapToGrid) {
        targetLoc = canvas.grid.getSnappedPoint(targetLoc, { mode: CONST.GRID_SNAPPING_MODES.CENTER, resolution: 1 });
      }
      if (this._originObject instanceof TokenDocument) {
        setTimeout(async () => {
          let startLocation = this._originObject.object?.center ?? targetLoc;
          let targetLocation = this._rotateTowards.target?.object ?? this._rotateTowards.target;
          if (this._rotateTowards.towardsCenter) {
            targetLocation = targetLocation?.center ?? targetLocation;
          }
          let ray = new foundry.canvas.geometry.Ray(startLocation, targetLocation);
          let angle = Math.normalizeDegrees(ray.angle * 180 / Math.PI - 90);
          angle += offset2;
          await this._updateObject(
            this._originObject,
            { rotation: angle },
            this._rotateTowards.duration > 0,
            {
              duration: this._rotateTowards.duration
            }
          );
        }, this._rotateTowards.delay ?? 0);
      } else {
        animData.attributes.push({
          name: "rotationTowards",
          offset: offset2,
          origin: this._originObject,
          originLocation: targetLoc,
          target: this._rotateTowards.target?.object ?? this._rotateTowards.target,
          towardsCenter: this._rotateTowards.towardsCenter,
          from: false,
          to: false,
          progress: 0,
          done: false,
          duration: this._rotateTowards.duration,
          durationDone: 0,
          delay: this._rotateTowards.delay,
          ease: this._rotateTowards.ease
        });
      }
      let rotateDuration = this._rotateTowards.duration + this._rotateTowards.delay;
      overallDuration = overallDuration > rotateDuration ? overallDuration : rotateDuration;
    }
    if (this._fadeIn) {
      let to = is_real_number(this._opacity) ? this._opacity : 1;
      if (this._originObject instanceof TokenDocument) {
        setTimeout(async () => {
          await this._updateObject(this._originObject, { alpha: to }, true, {
            duration: this._fadeIn.duration,
            easing: this._fadeIn.ease
          });
        }, this._fadeIn.delay ?? 0);
      } else {
        animData.attributes.push({
          name: "alpha",
          from: 0,
          to,
          progress: 0,
          done: false,
          duration: this._fadeIn.duration,
          durationDone: 0,
          delay: this._fadeIn.delay,
          ease: this._fadeIn.ease
        });
      }
      let fadeDuration = this._fadeIn.duration + this._fadeIn.delay;
      overallDuration = overallDuration > fadeDuration ? overallDuration : fadeDuration;
    }
    if (this._fadeInAudio && this._originObject?.video?.volume !== void 0) {
      let to = is_real_number(this._volume) ? this._volume : 1;
      animData.attributes.push({
        name: "video.volume",
        from: 0,
        to,
        progress: 0,
        done: false,
        duration: this._fadeInAudio.duration,
        durationDone: 0,
        delay: this._fadeInAudio.delay,
        ease: this._fadeInAudio.ease
      });
      let fadeDuration = this._fadeInAudio.duration + this._fadeInAudio.delay;
      overallDuration = overallDuration > fadeDuration ? overallDuration : fadeDuration;
    }
    if (this._rotateIn) {
      let from = this._angle ? this._angle : this._originObject.rotation;
      let to = this._rotateIn.value;
      if (Math.abs(from - to) > 180) {
        if (to < 0) {
          to += 360;
        } else if (from > to) {
          from -= 360;
        }
      }
      if (this._originObject instanceof TokenDocument) {
        setTimeout(async () => {
          if (this._originObject.rotation !== from) {
            await this._updateObject(
              this._originObject,
              { rotation: from },
              false
            );
          }
          await this._updateObject(this._originObject, { rotation: to }, true, {
            duration: this._rotateIn.duration
          });
        }, this._rotateIn.delay ?? 0);
      } else {
        animData.attributes.push({
          name: "rotation",
          from,
          to,
          progress: 0,
          done: false,
          duration: this._rotateIn.duration,
          durationDone: 0,
          delay: this._rotateIn.delay,
          ease: this._rotateIn.ease
        });
      }
      let rotateDuration = this._rotateIn.duration + this._rotateIn.delay;
      overallDuration = overallDuration > rotateDuration ? overallDuration : rotateDuration;
    }
    if (this._moveTowards) {
      let originLocation2 = get_object_position(this._originObject, {
        exact: true
      });
      let targetLocation = this._closestSquare ? this._getClosestSquare(this._originObject, this._moveTowards.target) : get_object_position(this._moveTowards.target, {
        exact: true
      });
      targetLocation.x += this._offset.x;
      targetLocation.y += this._offset.y;
      targetLocation.elevation = targetLocation?.elevation ?? this._originObject?.elevation;
      if (this._moveTowards.relativeToCenter) {
        const dimensions = get_object_dimensions(this._originObject);
        targetLocation.x -= dimensions.width / 2;
        targetLocation.y -= dimensions.height / 2;
        if (this._snapToGrid) {
          targetLocation.x -= 0.01;
          targetLocation.y -= 0.01;
        }
      }
      if (this._snapToGrid) {
        targetLocation = canvas.grid.getSnappedPoint(targetLocation, {
          mode: CONST.GRID_SNAPPING_MODES.VERTEX,
          resolution: 1
        });
      }
      let originalDx = targetLocation.x - originLocation2.x;
      let originalDy = targetLocation.y - originLocation2.y;
      let originalDistance = Math.sqrt(
        originalDx * originalDx + originalDy * originalDy
      );
      let duration = this._duration ? this._duration : originalDistance / this._moveSpeed * animData.maxFPS;
      let moveDuration = duration + this._moveTowards.delay;
      overallDuration = overallDuration > moveDuration ? overallDuration : moveDuration;
      if (this._originObject instanceof TokenDocument) {
        setTimeout(async () => {
          await this._updateObject(this._originObject, targetLocation, true, {
            movementSpeed: this._moveSpeed,
            duration
          });
        }, this._moveTowards.delay ?? 0);
      } else {
        if (!(duration === 0 || originalDistance === 0)) {
          animData.attributes.push({
            name: "position",
            origin: originLocation2,
            target: targetLocation,
            originalDistance,
            currentDistance: 0,
            progress: 0,
            speed: 0,
            duration,
            done: false,
            ease: this._moveTowards.ease,
            delay: this._moveTowards.delay
          });
        }
      }
    }
    if (this._fadeOut) {
      let from = is_real_number(this._opacity) ? this._opacity : this._originObject.alpha ?? 1;
      if (this._originObject instanceof TokenDocument) {
        setTimeout(async () => {
          await this._updateObject(this._originObject, { alpha: 0 }, true, {
            duration: this._fadeOut.duration
          });
        }, this._fadeOut.delay ?? 0);
      } else {
        animData.attributes.push({
          name: "alpha",
          from,
          to: 0,
          progress: 0,
          done: false,
          duration: this._fadeOut.duration,
          durationDone: 0,
          delay: overallDuration - this._fadeOut.duration,
          ease: this._fadeOut.ease
        });
      }
      let fadeOutDuration = this._fadeOut.duration + this._fadeOut.delay;
      overallDuration = overallDuration > fadeOutDuration ? overallDuration : fadeOutDuration;
    }
    if (this._fadeOutAudio && this._originObject?.video?.volume !== void 0) {
      let from = is_real_number(this._volume) ? this._volume : this._originObject.video.volume;
      animData.attributes.push({
        name: "video.volume",
        from,
        to: 0,
        progress: 0,
        done: false,
        duration: this._fadeOutAudio.duration,
        durationDone: 0,
        delay: overallDuration - this._fadeOutAudio.duration,
        ease: this._fadeOutAudio.ease
      });
      let fadeOutAudioDuration = this._fadeOutAudio.duration + this._fadeOutAudio.delay;
      overallDuration = overallDuration > fadeOutAudioDuration ? overallDuration : fadeOutAudioDuration;
    }
    if (this._rotateOut) {
      let from = this._rotateOut.value;
      let to = this._angle ? this._angle : this._originObject.rotation;
      if (this._rotateIn) from += this._rotateIn.value;
      if (Math.abs(from - to) > 180) {
        if (to < 0) {
          to += 360;
        } else if (from > to) {
          from -= 360;
        }
      }
      if (this._originObject instanceof TokenDocument) {
        setTimeout(async () => {
          if (this._originObject.rotation !== from) {
            await this._updateObject(
              this._originObject,
              { rotation: from },
              false
            );
          }
          await this._updateObject(this._originObject, { rotation: to }, true, {
            duration: this._rotateOut.duration
          });
        }, this._rotateOut.delay ?? 0);
      } else {
        animData.attributes.push({
          name: "rotation",
          from,
          to,
          progress: 0,
          done: false,
          duration: this._rotateOut.duration,
          durationDone: 0,
          delay: overallDuration - this._rotateOut.duration,
          ease: this._rotateOut.ease
        });
      }
      let rotateOutDuration = this._rotateOut.duration + this._rotateOut.delay;
      overallDuration = overallDuration > rotateOutDuration ? overallDuration : rotateOutDuration;
    }
    if (this._teleportTo) {
      setTimeout(async () => {
        let targetLocation = this._closestSquare ? this._getClosestSquare(this._originObject, this._teleportTo.target) : get_object_position(this._teleportTo.target, {
          exact: true
        });
        if (targetLocation.x === void 0) targetLocation.x = originLocation.x;
        if (targetLocation.y === void 0) targetLocation.y = originLocation.y;
        if (targetLocation.elevation === void 0)
          targetLocation.elevation = originLocation.elevation;
        targetLocation.x += this._offset.x;
        targetLocation.y += this._offset.y;
        targetLocation.elevation = targetLocation?.elevation ?? this._originObject?.elevation;
        const dimensions = get_object_dimensions(this._originObject);
        if (this._teleportTo.relativeToCenter) {
          targetLocation.x -= dimensions.width / 2;
          targetLocation.y -= dimensions.height / 2;
          if (this._snapToGrid) {
            targetLocation.x -= 0.01;
            targetLocation.y -= 0.01;
          }
        }
        if (this._snapToGrid) {
          targetLocation.x -= dimensions.width / 2;
          targetLocation.y -= dimensions.height / 2;
          targetLocation = canvas.grid.getSnappedPoint(targetLocation, {
            mode: CONST.GRID_SNAPPING_MODES.VERTEX,
            resolution: 1
          });
        }
        await this._updateObject(this._originObject, targetLocation, false);
      }, this._teleportTo.delay);
      if (overallDuration <= this._teleportTo.delay) {
        this._waitUntilFinished = true;
      }
      overallDuration = overallDuration > this._teleportTo.delay ? overallDuration : this._teleportTo.delay;
    }
    let updateAttributes = {};
    if (is_real_number(this._angle) && !this._rotateIn && !this._rotateOut) {
      updateAttributes["rotation"] = this._angle;
    }
    if (is_real_number(this._opacity) && !this._fadeIn && !this._fadeOut) {
      updateAttributes["alpha"] = this._opacity;
    }
    if (is_real_number(this._volume) && !this._fadeInAudio && !this._fadeOutAudio && this._originObject?.video?.volume !== void 0) {
      updateAttributes["video.volume"] = this._volume;
    }
    if (this._tint) {
      updateAttributes["texture.tint"] = this._tint.hexadecimal;
    }
    if (this._hide !== void 0) {
      updateAttributes["hidden"] = this._hide;
    }
    if (Object.keys(updateAttributes).length) {
      await wait(1);
      await this._updateObject(this._originObject, updateAttributes);
      await wait(1);
    }
    return new Promise((resolve) => {
      let resolved = false;
      const safeResolve = () => {
        if (resolved) return;
        resolved = true;
        resolve();
      };
      this._animate(animData, safeResolve);
      setTimeout(
        safeResolve,
        Math.max(0, overallDuration + this._currentWaitTime + animData.maxFPS)
      );
    });
  }
  /**
   * @private
   */
  async _animate(animData, resolve, timespan) {
    if (timespan) {
      let animatedAttributes = {};
      let dt = timespan - animData.lastTimespan;
      if (dt >= animData.maxFPS) {
        animData.totalDt += dt;
        for (let attribute of animData.attributes) {
          if (attribute.done) continue;
          if (animData.totalDt < attribute.delay) continue;
          if (attribute.name === "position") {
            attribute.speed = attribute.originalDistance / (attribute.duration / dt);
            attribute.currentDistance += attribute.speed;
            attribute.progress = attribute.currentDistance / attribute.originalDistance;
            let x = interpolate(
              attribute.origin.x,
              attribute.target.x,
              attribute.progress,
              attribute.ease
            );
            let y = interpolate(
              attribute.origin.y,
              attribute.target.y,
              attribute.progress,
              attribute.ease
            );
            if (attribute.currentDistance >= attribute.originalDistance) {
              x = attribute.target.x;
              y = attribute.target.y;
              attribute.done = true;
            }
            animatedAttributes["x"] = x;
            animatedAttributes["y"] = y;
          } else {
            if (attribute.name === "rotationTowards" && !attribute.from && !attribute.to) {
              let target = attribute.target;
              if (this._rotateTowards.towardsCenter)
                target = target?.center ?? target;
              let ray = new foundry.canvas.geometry.Ray(attribute.originLocation, target);
              let angle = ray.angle * 180 / Math.PI - 90;
              angle += attribute.offset;
              attribute.from = attribute.origin.rotation;
              attribute.to = angle;
              if (Math.abs(attribute.from - attribute.to) > 180) {
                if (attribute.to < 0) {
                  attribute.to += 360;
                } else if (attribute.from > attribute.to) {
                  attribute.from -= 360;
                }
              }
              attribute.name = "rotation";
            }
            attribute.durationDone += dt;
            attribute.progress = attribute.durationDone / attribute.duration;
            let val = interpolate(
              attribute.from,
              attribute.to,
              attribute.progress,
              attribute.ease
            );
            if (attribute.progress >= 1) {
              val = attribute.to;
              attribute.done = true;
            }
            animatedAttributes[attribute.name] = val;
          }
        }
        if (Object.keys(animatedAttributes).length > 0) {
          await this._updateObject(this._originObject, animatedAttributes);
        }
        animData.attributes = animData.attributes.filter((a) => !a.done);
        if (animData.attributes.length === 0) {
          resolve();
          return;
        }
        animData.lastTimespan = timespan;
      }
    }
    let self = this;
    requestAnimationFrame(function(timespan2) {
      self._animate(animData, resolve, timespan2);
    });
  }
}
class ScrollingTextSection extends Section {
  constructor(inSequence, target, text2, textOptions) {
    super(inSequence);
    this._deserializedData = null;
    this._source = null;
    this._text = "";
    this._duration = 2e3;
    this._distance = null;
    this._jitter = 0;
    this._anchor = null;
    this._direction = null;
    this._seed = get_hash(foundry.utils.randomID());
    if (target) {
      this.atLocation(target);
    }
    if (text2) {
      this.text(text2, textOptions);
    }
  }
  static niceName = "Scrolling Text";
  /**
   * @private
   */
  _applyTraits() {
    Object.assign(this.constructor.prototype, traits.users);
    Object.assign(this.constructor.prototype, traits.location);
    Object.assign(this.constructor.prototype, traits.offset);
    Object.assign(this.constructor.prototype, traits.text);
  }
  direction(inDirection) {
    if (!(typeof inDirection === "string" || is_real_number(inDirection))) {
      throw this.sequence._customError(
        this,
        "direction",
        "inDirection must be of type string (CONST.TEXT_ANCHOR_POINTS) or number"
      );
    }
    if (typeof inDirection === "string" && !CONST.TEXT_ANCHOR_POINTS[inDirection]) {
      throw this.sequence._customError(
        this,
        "direction",
        `${inDirection} does not exist in CONST.TEXT_ANCHOR_POINTS!`
      );
    } else if (typeof inDirection === "string") {
      this._direction = CONST.TEXT_ANCHOR_POINTS[inDirection];
    } else {
      this._direction = inDirection;
    }
    return this;
  }
  anchor(inAnchor) {
    if (!(typeof inAnchor === "string" || is_real_number(inAnchor))) {
      throw this.sequence._customError(
        this,
        "direction",
        "inAnchor must be of type string (CONST.TEXT_ANCHOR_POINTS) or number"
      );
    }
    if (typeof inAnchor === "string" && CONST.TEXT_ANCHOR_POINTS[inAnchor] === void 0 || is_real_number(inAnchor) && !Object.values(CONST.TEXT_ANCHOR_POINTS).includes(inAnchor)) {
      throw this.sequence._customError(
        this,
        "direction",
        `${inAnchor} does not exist in CONST.TEXT_ANCHOR_POINTS!`
      );
    } else if (typeof inAnchor === "string") {
      this._anchor = CONST.TEXT_ANCHOR_POINTS[inAnchor];
    } else {
      this._anchor = inAnchor;
    }
    return this;
  }
  jitter(inJitter) {
    if (!(is_real_number(inJitter) && inJitter >= 0 && inJitter <= 1)) {
      throw this.sequence._customError(
        this,
        "jitter",
        "inJitter must be of type number between 0 and 1"
      );
    }
    this._jitter = inJitter;
    return this;
  }
  async preRun() {
    const oldSource = this._source;
    const crosshairSource = this.sequence?.crosshairs?.[this._source];
    if (typeof this._source === "string" && crosshairSource) {
      this._source = crosshairSource.uuid;
    }
    if (this._name && oldSource !== this._source) {
      this.sequence.nameOffsetMap[this._name].source = this._getSourceObject();
    }
  }
  async run() {
    const data = await this._sanitizeTextData();
    if (Hooks.call("preCreateScrollingText", data) === false) return;
    const push2 = !(data?.users?.length === 1 && data?.users?.includes(game.userId)) && !this.sequence.local && !this.sequence.remote;
    const duration = SequencerFoundryReplicator.playScrollingText(data, push2);
    await new Promise(
      (resolve) => setTimeout(resolve, this._currentWaitTime + duration)
    );
  }
  _getSourceObject() {
    if (!this._source || typeof this._source !== "object") return this._source;
    if (this._source instanceof CrosshairsPlaceable || this._source instanceof CrosshairsDocument) {
      const doc = this._source?.document ?? this._source;
      return doc.getOrientation().source;
    }
    return get_object_identifier(this._source) ?? get_object_canvas_data(this._source);
  }
  async _sanitizeTextData() {
    if (this._deserializedData) {
      return this._deserializedData;
    }
    return {
      sceneId: game.user.viewedScene,
      seed: this._seed,
      sequenceId: this.sequence.id,
      creatorUserId: game.userId,
      users: this._users ? Array.from(this._users) : false,
      moduleName: this.sequence.moduleName,
      source: this._getSourceObject(),
      offset: this._offset?.source ?? false,
      randomOffset: this._randomOffset?.source ?? false,
      content: this._text?.text ?? "",
      nameOffsetMap: this.sequence.nameOffsetMap,
      local: this.sequence.local,
      remote: this.sequence.remote,
      options: {
        anchor: this._anchor,
        direction: this._direction,
        duration: this._duration,
        distance: this._distance,
        jitter: this._jitter,
        ...this._text
      }
    };
  }
  async _serialize() {
    const data = await super._serialize();
    const sectionData = await this._sanitizeTextData();
    return {
      ...data,
      type: "scrollingText",
      sectionData
    };
  }
  async _deserialize(data) {
    this._deserializedData = data.sectionData;
    return super._deserialize(data);
  }
}
class CanvasPanSection extends Section {
  constructor(inSequence, target, duration, scale2) {
    super(inSequence);
    this._deserializedData = null;
    this._source = null;
    this._duration = duration ?? 250;
    this._speed = null;
    this._scale = scale2 ?? 1;
    this._lockView = null;
    this._shake = null;
    this._seed = get_hash(foundry.utils.randomID());
    if (target) {
      this.atLocation(target);
    }
  }
  static niceName = "Canvas Pan";
  /**
   * @private
   */
  _applyTraits() {
    Object.assign(this.constructor.prototype, traits.users);
    Object.assign(this.constructor.prototype, traits.location);
    Object.assign(this.constructor.prototype, traits.offset);
  }
  /**
   * Sets the speed (pixels per frame) of the canvas pan
   *
   * @param {number} inSpeed
   * @returns this
   */
  speed(inSpeed) {
    if (!is_real_number(inSpeed))
      throw this.sequence._customError(
        this,
        "speed",
        "inSpeed must be of type number"
      );
    if (inSpeed < 0) {
      throw this.sequence._customError(
        this,
        "speed",
        "inSpeed must be greater or equal to 0"
      );
    }
    this._speed = inSpeed;
    return this;
  }
  /**
   * Sets the zoom level of the canvas pan
   *
   * @param {number} inScale
   * @returns this
   */
  scale(inScale) {
    if (!is_real_number(inScale))
      throw this.sequence._customError(
        this,
        "scale",
        "inScale must be of type number"
      );
    if (inScale <= 0) {
      throw this.sequence._customError(
        this,
        "scale",
        "inScale must be greater than 0"
      );
    }
    this._scale = inScale;
    return this;
  }
  /**
   * Locks the canvas at the given location for the given duration
   *
   * @param {number} inDuration
   * @returns this
   */
  lockView(inDuration) {
    if (!is_real_number(inDuration))
      throw this.sequence._customError(
        this,
        "lockView",
        "inDuration must be of type number"
      );
    if (inDuration < 0) {
      throw this.sequence._customError(
        this,
        "lockView",
        "inDuration must be greater or equal to 0"
      );
    }
    this._lockView = inDuration;
    return this;
  }
  /**
   * Shakes the canvas
   *
   * @param {number} duration
   * @param {number} strength
   * @param {number} frequency
   * @param {number} fadeInDuration
   * @param {number} fadeOutDuration
   * @param {boolean} rotation
   * @returns this
   */
  shake({
    duration = 250,
    strength = 20,
    frequency = 10,
    fadeInDuration = 0,
    fadeOutDuration = 200,
    rotation: rotation2 = true
  } = {}) {
    if (!is_real_number(duration)) {
      throw this.sequence._customError(
        this,
        "shake",
        "duration must be of type number"
      );
    }
    if (!is_real_number(strength)) {
      throw this.sequence._customError(
        this,
        "shake",
        "strength must be of type number"
      );
    }
    if (!is_real_number(strength)) {
      throw this.sequence._customError(
        this,
        "shake",
        "frequency must be of type number"
      );
    }
    if (!is_real_number(fadeInDuration)) {
      throw this.sequence._customError(
        this,
        "shake",
        "fadeInDuration must be of type number"
      );
    }
    if (!is_real_number(fadeOutDuration)) {
      throw this.sequence._customError(
        this,
        "shake",
        "fadeOutDuration must be of type number"
      );
    }
    if (typeof rotation2 !== "boolean") {
      throw this.sequence._customError(
        this,
        "shake",
        "rotation must be of type boolean"
      );
    }
    this._shake = {
      duration,
      strength,
      frequency,
      fadeInDuration,
      fadeOutDuration,
      rotation: rotation2
    };
    return this;
  }
  async preRun() {
    const oldSource = this._source;
    const crosshairSource = this.sequence?.crosshairs?.[this._source];
    if (typeof this._source === "string" && crosshairSource) {
      this._source = crosshairSource.uuid;
    }
    if (this._name && oldSource !== this._source) {
      this.sequence.nameOffsetMap[this._name].source = this._getSourceObject();
    }
  }
  async run() {
    const data = await this._sanitizeData();
    if (Hooks.call("preCanvasPan", data) === false) return;
    const push2 = !(data?.users?.length === 1 && data?.users?.includes(game.userId)) && !this.sequence.local && !this.sequence.remote;
    const duration = SequencerFoundryReplicator.panCanvas(data, push2);
    await new Promise(
      (resolve) => setTimeout(resolve, this._currentWaitTime + duration)
    );
  }
  _getSourceObject() {
    if (!this._source || typeof this._source !== "object") return this._source;
    if (this._source instanceof CrosshairsPlaceable || this._source instanceof CrosshairsDocument) {
      const doc = this._source?.document ?? this._source;
      return doc.getOrientation().source;
    }
    return get_object_identifier(this._source) ?? get_object_canvas_data(this._source, { uuid: false });
  }
  async _sanitizeData() {
    if (this._deserializedData) {
      return this._deserializedData;
    }
    return {
      sceneId: game.user.viewedScene,
      seed: this._seed,
      sequenceId: this.sequence.id,
      creatorUserId: game.userId,
      users: this._users ? Array.from(this._users) : false,
      moduleName: this.sequence.moduleName,
      source: this._getSourceObject(),
      offset: this._offset?.source ?? false,
      randomOffset: this._randomOffset?.source ?? false,
      duration: this._duration,
      speed: this._speed,
      scale: this._scale,
      lockView: this._lockView,
      shake: this._shake,
      nameOffsetMap: this.sequence.nameOffsetMap,
      local: this.sequence.local,
      remote: this.sequence.remote
    };
  }
  async _serialize() {
    const data = await super._serialize();
    const sectionData = await this._sanitizeData();
    return {
      ...data,
      type: "canvasPan",
      sectionData
    };
  }
  async _deserialize(data) {
    this._deserializedData = data.sectionData;
    return super._deserialize(data);
  }
}
class WaitSection extends Section {
  constructor(inSequence, msMin, msMax) {
    super(inSequence);
    this._waitUntilFinished = true;
    this._waitDuration = random_int_between(msMin, Math.max(msMin, msMax));
  }
  static niceName = "Wait";
  /**
   * @returns {Promise<void>}
   */
  async run() {
    debug("Running wait");
    await new Promise(async (resolve) => {
      setTimeout(resolve, this._waitDuration);
    });
  }
  /**
   * @returns {Promise}
   * @private
   */
  async _execute() {
    await this.run();
  }
  async _serialize() {
    const data = await super._serialize();
    return {
      ...data,
      type: "wait",
      sectionData: {
        waitDuration: this._waitDuration
      }
    };
  }
  async _deserialize(data) {
    this._waitDuration = data.sectionData.waitDuration;
    return super._deserialize(data);
  }
}
class CrosshairSection extends Section {
  constructor(inSequence, inName = "") {
    super(inSequence);
    this._name = inName;
    this._type = CONST.MEASURED_TEMPLATE_TYPES.CIRCLE;
    this._distance = canvas.grid.distance / 2;
    this._angle = 53.13;
    this._width = canvas.grid.distance;
    this._direction = 0;
    this._borderColor = null;
    this._fillColor = null;
    this._persist = false;
    this._config = CrosshairsDocument.defaultConfig;
    this._waitUntilFinished = true;
    this._callbacks = {};
  }
  static niceName = "Crosshair";
  /**
   * Sets the type of MeasurableTemplate to create, see CONST.MEASURED_TEMPLATE_TYPES
   */
  type(inType = "") {
    if (typeof inType !== "string") {
      throw this.sequence._customError(this, "type", "inType must be of type string");
    }
    if (!Object.values(CONST.MEASURED_TEMPLATE_TYPES).includes(inType)) {
      throw this.sequence._customError(this, "type", `inType must be one of valid types: ${Object.values(CONST.MEASURED_TEMPLATE_TYPES).join(" ")} (see CONST.MEASURED_TEMPLATE_TYPES)`);
    }
    this._type = inType;
    return this;
  }
  /**
   * Sets a custom label to be set as a part of this crosshair
   */
  label(inText, inOptions = {}) {
    if (typeof inOptions !== "object") {
      throw this.sequence._customError(this, "label", "inOptions must be of type object");
    }
    inOptions = foundry.utils.mergeObject({
      dx: CrosshairsDocument.defaultConfig.label.dx,
      dy: CrosshairsDocument.defaultConfig.label.dy
    }, inOptions);
    if (typeof inText !== "string") {
      throw this.sequence._customError(this, "label", "inText must be of type string");
    }
    if (!is_real_number(inOptions.dx)) {
      throw this.sequence._customError(this, "label", "inOptions.dx must be of type number");
    }
    if (!is_real_number(inOptions.dy)) {
      throw this.sequence._customError(this, "label", "inOptions.dy must be of type number");
    }
    this._config["label"]["text"] = inText;
    this._config["label"]["dx"] = inOptions.dx;
    this._config["label"]["dy"] = inOptions.dy;
    return this;
  }
  /**
   * Sets how the position of the crosshair should snap on the grid
   */
  snapPosition(inSnap) {
    if (!is_real_number(inSnap)) {
      throw this.sequence._customError(this, "snapPosition", "inSnap must be of type number, see CONST.GRID_SNAPPING_MODES");
    }
    this._config["snap"]["position"] = inSnap;
    return this;
  }
  /**
   * Sets the distance for the crosshair - or radius for a circular crosshair
   */
  distance(inDistance, inOptions = {}) {
    if (typeof inOptions !== "object") {
      throw this.sequence._customError(this, "distance", "inOptions must be of type object");
    }
    inOptions = foundry.utils.mergeObject({
      min: CrosshairsDocument.defaultConfig.distanceMin,
      max: CrosshairsDocument.defaultConfig.distanceMax
    }, inOptions);
    if (!is_real_number(inDistance)) {
      throw this.sequence._customError(this, "distance", "inDistance must be of type number");
    }
    if (inOptions.min !== null) {
      if (!is_real_number(inOptions.min)) {
        throw this.sequence._customError(this, "distance", "inOptions.min must be of type number");
      }
      if (inOptions.min <= 0) {
        throw this.sequence._customError(this, "distance", "inOptions.min must be a non-zero number");
      }
    } else {
      inOptions.min = inDistance;
    }
    if (inOptions.max !== null) {
      if (!is_real_number(inOptions.max)) {
        throw this.sequence._customError(this, "distance", "inOptions.max must be of type number");
      }
      if (inOptions.max <= 0) {
        throw this.sequence._customError(this, "distance", "inOptions.max must be a non-zero number");
      }
    } else {
      inOptions.max = inDistance;
    }
    if (inOptions.min > inOptions.max) {
      throw this.sequence._customError(this, "distance", "inOptions.min cannot be larger than inOptions.max");
    }
    this._distance = inDistance;
    this._config["distanceMin"] = inOptions.min;
    this._config["distanceMax"] = inOptions.max;
    return this;
  }
  /**
   * Configures the angle of the crosshair - mostly used for the width of cone crosshairs
   */
  angle(inAngle) {
    if (!is_real_number(inAngle)) {
      throw this.sequence._customError(this, "angle", "inAngle must be of type number");
    }
    this._angle = inAngle;
    return this;
  }
  /**
   * Configures the width for the crosshair - used for ray crosshairs
   */
  width(inWidth) {
    if (!is_real_number(inWidth)) {
      throw this.sequence._customError(this, "width", "inWidth must be of type number");
    }
    this._width = inWidth;
    return this;
  }
  /**
   * Configures the width for the crosshair - used for ray crosshairs
   */
  direction(inDirection) {
    if (!is_real_number(inDirection)) {
      throw this.sequence._customError(this, "direction", "inDirection must be of type number");
    }
    this._direction = inDirection;
    return this;
  }
  /**
   * Configures the  increments the direction should snap along
   */
  snapDirection(inSnapDirection) {
    if (!is_real_number(inSnapDirection)) {
      throw this.sequence._customError(this, "snapDirection", "inDirectionSnap must be of type number");
    }
    this._config["snap"]["direction"] = inSnapDirection;
    return this;
  }
  /**
   * Toggles whether the crosshair can be manually rotated
   */
  lockManualRotation(inBool = true) {
    if (typeof inBool !== "boolean") {
      throw this.sequence._customError(this, "preventUserRotation", "inBool must be of type number");
    }
    this._config["lockManualRotation"] = inBool;
    return this;
  }
  /**
   * Toggles whether the crosshair's end position can be dragged
   * @param inBool
   */
  lockDrag(inBool = true) {
    if (typeof inBool !== "boolean") {
      throw this.sequence._customError(this, "lockDrag", "inBool must be of type number");
    }
    this._config["lockDrag"] = inBool;
    return this;
  }
  /**
   * Configures the custom icon to be used on the crosshair
   */
  icon(inTexture, inOptions = {}) {
    if (typeof inOptions !== "object") {
      throw this.sequence._customError(this, "icon", "inOptions must be of type object");
    }
    inOptions = foundry.utils.mergeObject({ borderVisible: CrosshairsDocument.defaultConfig.icon.borderVisible }, inOptions);
    if (typeof inTexture !== "string") {
      throw this.sequence._customError(this, "icon", "inTexture must be of type string");
    }
    if (typeof inOptions.borderVisible !== "boolean") {
      throw this.sequence._customError(this, "icon", "inOptions.borderVisible must be of type boolean");
    }
    this._config["icon"]["texture"] = inTexture;
    this._config["icon"]["borderVisible"] = inOptions.borderVisible;
    return this;
  }
  /**
   * Sets the border color of the crosshair
   */
  borderColor(inColor, inOptions = {}) {
    if (typeof inOptions !== "object") {
      throw this.sequence._customError(this, "borderColor", "inOptions must be of type object");
    }
    inOptions = foundry.utils.mergeObject({ alpha: CrosshairsDocument.defaultConfig.borderAlpha }, inOptions);
    const { alpha } = inOptions;
    if (!is_real_number(alpha)) {
      throw this.sequence._customError(this, "borderAlpha", "inOptions.alpha must be of type number");
    }
    this._borderColor = parseColor(inColor).hexadecimal;
    this._config["borderAlpha"] = normalize_alpha_number(alpha);
    return this;
  }
  /**
   * Sets the fill color of the crosshair
   */
  fillColor(inColor, inOptions = {}) {
    if (typeof inOptions !== "object") {
      throw this.sequence._customError(this, "fillColor", "inOptions must be of type object");
    }
    inOptions = foundry.utils.mergeObject({ alpha: CrosshairsDocument.defaultConfig.fillAlpha }, inOptions);
    const { alpha } = inOptions;
    if (!is_real_number(alpha)) {
      throw this.sequence._customError(this, "fillColor", "inOptions.alpha must be of type number");
    }
    this._config["fillAlpha"] = normalize_alpha_number(alpha);
    this._fillColor = parseColor(inColor).hexadecimal;
    return this;
  }
  /**
   * Sets the texture used by the crosshair
   */
  texture(inTexture, inOptions = {}) {
    if (typeof inOptions !== "object") {
      throw this.sequence._customError(this, "texture", "inOptions must be of type object");
    }
    inOptions = foundry.utils.mergeObject(
      {
        alpha: CrosshairsDocument.defaultConfig.textureAlpha,
        scale: CrosshairsDocument.defaultConfig.textureScale
      },
      inOptions
    );
    const { alpha, scale: scale2 } = inOptions;
    if (!is_real_number(alpha)) {
      throw this.sequence._customError(this, "texture", "inOptions.alpha must be of type number");
    }
    if (!is_real_number(scale2)) {
      throw this.sequence._customError(this, "texture", "inOptions.scale must be of type number");
    }
    if (typeof inTexture !== "string") {
      throw this.sequence._customError(this, "texture", "inTexture must be of type string");
    }
    this._texture = inTexture;
    this._config["textureAlpha"] = normalize_alpha_number(alpha);
    this._config["textureScale"] = scale2;
    return this;
  }
  /**
   * Configures the source location of the crosshair, usually to limit it around the target placeable object, or to
   * cause it to be limited within a certain range of the placeable object
   */
  location(inLocation, inOptions = {}) {
    if (!inLocation || typeof inLocation !== "object") {
      throw this.sequence._customError(this, "location", "inLocation is invalid, and must be of type of placeable object, or document");
    }
    if (typeof inOptions !== "object") {
      throw this.sequence._customError(this, "location", "inOptions must be of type object");
    }
    inOptions = foundry.utils.mergeObject({
      limitMinRange: CrosshairsDocument.defaultConfig.location.limitMinRange,
      limitMaxRange: CrosshairsDocument.defaultConfig.location.limitMaxRange,
      showRange: CrosshairsDocument.defaultConfig.location.showRange,
      lockToEdge: CrosshairsDocument.defaultConfig.location.lockToEdge,
      lockToEdgeDirection: CrosshairsDocument.defaultConfig.location.lockToEdgeDirection,
      offset: CrosshairsDocument.defaultConfig.location.offset,
      wallBehavior: CrosshairsDocument.defaultConfig.location.wallBehavior,
      displayRangePoly: CrosshairsDocument.defaultConfig.location.displayRangePoly,
      rangePolyFillColor: CrosshairsDocument.defaultConfig.location.rangePolyFillColor,
      rangePolyLineColor: CrosshairsDocument.defaultConfig.location.rangePolyLineColor,
      rangePolyFillAlpha: CrosshairsDocument.defaultConfig.location.rangePolyFillAlpha,
      rangePolyLineAlpha: CrosshairsDocument.defaultConfig.location.rangePolyLineAlpha
    }, inOptions);
    inLocation = this._validateLocation(inLocation);
    if (typeof inOptions.wallBehavior !== "string") {
      throw this.sequence._customError(this, "location", "inOptions.wallBehavior must be of type string");
    }
    if (inLocation === void 0) {
      throw this.sequence._customError(this, "location", "could not find position of given object");
    }
    if (inOptions.limitMinRange && !is_real_number(inOptions.limitMinRange)) {
      throw this.sequence._customError(this, "location", "inOptions.limitMinRange must be of type number");
    }
    if (inOptions.limitMaxRange && !is_real_number(inOptions.limitMaxRange)) {
      throw this.sequence._customError(this, "location", "inOptions.limitMaxRange must be of type number");
    }
    if (typeof inOptions.lockToEdge !== "boolean") {
      throw this.sequence._customError(this, "location", "inOptions.lockToEdge must be of type boolean");
    }
    if (typeof inOptions.showRange !== "boolean") {
      throw this.sequence._customError(this, "location", "inOptions.showRange must be of type boolean");
    }
    if (typeof inOptions.lockToEdgeDirection !== "boolean") {
      throw this.sequence._customError(this, "location", "inOptions.lockToEdgeDirection must be of type boolean");
    }
    if (typeof inOptions.offset !== "object") {
      throw this.sequence._customError(this, "location", "inOptions.offset must be of type of object");
    }
    if (!is_real_number(inOptions.offset.x)) {
      throw this.sequence._customError(this, "location", "inOptions.offset.x must be of type of number");
    }
    if (!is_real_number(inOptions.offset.y)) {
      throw this.sequence._customError(this, "location", "inOptions.offset.y must be of type of number");
    }
    if (typeof inOptions.displayRangePoly !== "boolean") {
      throw this.sequence._customError(this, "location", "inOptions.displayRangePoly must be of type boolean");
    }
    if (inOptions.rangePolyFillColor && !is_real_number(inOptions.rangePolyFillColor)) {
      throw this.sequence._customError(this, "location", "inOptions.rangePolyFillColor must be of type number (color, eg 0xFF0000)");
    }
    if (inOptions.rangePolyLineColor && !is_real_number(inOptions.rangePolyLineColor)) {
      throw this.sequence._customError(this, "location", "inOptions.rangePolyLineColor must be of type number (color, eg 0xFF0000)");
    }
    if (inOptions.rangePolyFillAlpha && !is_real_number(inOptions.rangePolyFillAlpha)) {
      throw this.sequence._customError(this, "location", "inOptions.rangePolyFillAlpha must be of type number");
    }
    if (inOptions.rangePolyLineAlpha && !is_real_number(inOptions.rangePolyLineAlpha)) {
      throw this.sequence._customError(this, "location", "inOptions.rangePolyLineAlpha must be of type number");
    }
    this._config["location"] = {
      obj: inLocation?.object ?? inLocation,
      limitMinRange: inOptions.limitMinRange,
      limitMaxRange: inOptions.limitMaxRange,
      lockToEdge: inOptions.lockToEdge,
      lockToEdgeDirection: inOptions.lockToEdgeDirection,
      showRange: inOptions.showRange,
      offset: inOptions.offset,
      wallBehavior: inOptions.wallBehavior,
      displayRangePoly: inOptions.displayRangePoly,
      rangePolyFillColor: inOptions.rangePolyFillColor,
      rangePolyLineColor: inOptions.rangePolyLineColor,
      rangePolyFillAlpha: inOptions.rangePolyFillAlpha,
      rangePolyLineAlpha: inOptions.rangePolyLineAlpha
    };
    return this;
  }
  /**
   * Causes the crosshair to spawn a measurable template identical to the crosshair
   */
  persist(inBool = true) {
    if (typeof inBool !== "boolean") {
      throw this.sequence._customError(this, "persist", "inBool must be of type boolean");
    }
    this._persist = inBool;
    return this;
  }
  /**
   * Toggles whether this crosshair should highlight the grid
   */
  gridHighlight(inBool = true) {
    if (typeof inBool !== "boolean") {
      throw this.sequence._customError(this, "gridHighlight", "inBool must be of type boolean");
    }
    this._config["gridHighlight"] = inBool;
    return this;
  }
  /**
   * Adds a callback function for certain crosshair events
   */
  callback(inString, inCallback) {
    if (typeof inString !== "string" || !Object.values(CONSTANTS.CALLBACKS).some((str) => str === inString)) {
      throw this.sequence._customError(this, "callback", "inString must be of type string and one of Sequencer.Crosshair.CALLBACKS");
    }
    if (!is_function(inCallback)) {
      throw this.sequence._customError(this, "callback", "inCallback must be of type Function");
    }
    this._callbacks[inString] = inCallback;
    return this;
  }
  async run() {
    const reticle = new CrosshairsDocument({
      t: this._type,
      distance: this._distance,
      width: this._width,
      direction: this._direction,
      angle: this._angle,
      borderColor: this._borderColor,
      texture: this._texture,
      fillColor: this._fillColor
    }, {
      parent: canvas.scene
    }, this._config, this._callbacks);
    return reticle.show().then(async (result) => {
      if (!result) return;
      if (this._persist) {
        const [template] = await canvas.scene.createEmbeddedDocuments("MeasuredTemplate", [
          reticle.toObject()
        ]);
        this.sequence.crosshairs[this._name] = template;
      }
      if (this._name) {
        this.sequence.nameOffsetMap ||= {};
        this.sequence.nameOffsetMap[this._name] = {
          seed: `${this._name}-${foundry.utils.randomID()}`,
          twister: {},
          source: this.sequence.crosshairs[this._name]?.uuid ?? reticle.getOrientation().source,
          target: this.sequence.crosshairs[this._name]?.uuid ?? reticle.getOrientation().target
        };
      }
    }).catch((err) => {
      this.sequence._abort();
    });
  }
  /**
   * @private
   */
  _applyTraits() {
    Object.assign(this.constructor.prototype, traits.name);
  }
}
let Sequence$1 = class Sequence2 {
  constructor(options = {
    moduleName: "Sequencer",
    softFail: false
  }, softFail = false) {
    this.id = foundry.utils.randomID();
    this.moduleName = typeof options === "string" ? options : options?.moduleName ?? "Sequencer";
    this.softFail = options?.softFail ?? softFail;
    this.sections = [];
    this.nameOffsetMap = false;
    this.crosshairs = {};
    this.effectIndex = 0;
    this.sectionToCreate = void 0;
    this.remote = false;
    this.local = false;
    this._status = writable(CONSTANTS.STATUS.READY);
    return sequence_proxy_wrap(this);
  }
  /**
   * Plays all of this sequence's sections
   *
   * @returns {Promise}
   */
  async play({ remote = false, preload = false, local = false } = {}) {
    this.remote = remote;
    this.local = local;
    if (remote) {
      const data = await this.toJSON();
      sequencerSocket.executeForOthers(
        SOCKET_HANDLERS.RUN_SEQUENCE_LOCALLY,
        data
      );
      return new Sequence2().fromJSON(data).play({ local: true });
    }
    Hooks.callAll("createSequencerSequence", this);
    debug("Initializing sections");
    const initializationPromises = this.sections.map((section) => section._initialize());
    await Promise.allSettled(initializationPromises);
    if (preload) {
      const preloadFiles = this.sections.filter((section) => !!section._file).map((section) => section._file);
      if (local) {
        await Sequencer.Preloader.preload(preloadFiles);
      } else {
        await Sequencer.Preloader.preloadForClients(preloadFiles);
      }
    }
    SequenceManager.RunningSequences.add(this.id, this);
    this.effectIndex = 0;
    debug("Playing sections");
    this.status = CONSTANTS.STATUS.RUNNING;
    const promises = [];
    for (let section of this.sections) {
      if (section instanceof EffectSection) this.effectIndex++;
      if (section.shouldWaitUntilFinished) {
        promises.push(await section._execute());
      } else {
        promises.push(section._execute());
      }
      if (this.status === CONSTANTS.STATUS.ABORTED) {
        continue;
      }
      if (!section._isLastSection) {
        await new Promise((resolve) => setTimeout(resolve, 3));
      }
    }
    return Promise.allSettled(promises).then(() => {
      Hooks.callAll("endedSequencerSequence");
      debug("Finished playing sections");
      this.status = CONSTANTS.STATUS.COMPLETE;
    });
  }
  /**
   * Creates a section that will run a function.
   *
   * @param {function} inFunc
   * @returns {Sequence} this
   */
  thenDo(inFunc) {
    const func = section_proxy_wrap(new FunctionSection(this, inFunc));
    this.sections.push(func);
    return func;
  }
  /**
   * Creates a section that will run a macro based on a name or a direct reference to a macro.
   *
   * @param {string|Macro} inMacro
   * @param {object} args
   * @returns {Sequence} this
   */
  macro(inMacro, ...args) {
    let macro;
    let compendium = false;
    if (typeof inMacro === "string") {
      if (inMacro.startsWith("Compendium")) {
        let packArray = inMacro.split(".");
        let pack = game.packs.get(`${packArray[1]}.${packArray[2]}`);
        if (!pack) {
          if (this.softFail) {
            return this;
          }
          throw custom_error(
            this.moduleName,
            `macro - Compendium '${packArray[1]}.${packArray[2]}' was not found`
          );
        }
        macro = packArray;
        compendium = pack;
      } else {
        macro = game.macros.getName(inMacro);
        if (!macro) {
          if (this.softFail) {
            return this;
          }
          throw custom_error(
            this.moduleName,
            `macro - Macro '${inMacro}' was not found`
          );
        }
      }
    } else if (inMacro instanceof Macro) {
      macro = inMacro;
    } else {
      throw custom_error(
        this.moduleName,
        `macro - inMacro must be of instance string or Macro`
      );
    }
    args = args.length ? args?.[0] : {};
    if (typeof args !== "object") {
      throw custom_error(
        this.moduleName,
        `macro - Secondary argument must be an object`
      );
    }
    const func = section_proxy_wrap(
      new FunctionSection(
        this,
        async () => {
          if (compendium) {
            const macroIndex = (await compendium.getIndex()).find((i) => {
              if (macro[3] === "Macro") {
                return macro[4] === i._id;
              }
              return i.name === macro[3];
            });
            const macroData = macroIndex ? await compendium.getDocument(macroIndex?._id) : false;
            if (!macroData) {
              if (this.softFail) {
                return;
              }
              throw custom_error(
                this.moduleName,
                `macro - Macro '${macro[3]}' was not found in compendium '${macro[1]}.${macro[2]}'`
              );
            }
            macro = new Macro(macroData);
            macro.ownership.default = CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER;
          }
          await macro.execute(args);
        },
        true
      )
    );
    this.sections.push(func);
    return this;
  }
  /**
   * Creates an effect section. Until you call .then(), .effect(), .sound(), or .wait(), you'll be working on the Effect section.
   *
   * @param {string} [inFile] inFile
   * @returns {Section}
   */
  effect(inFile = "") {
    const effect2 = section_proxy_wrap(new EffectSection(this, inFile));
    this.sections.push(effect2);
    return effect2;
  }
  /**
   * Creates a sound section. Until you call .then(), .effect(), .sound(), or .wait(), you'll be working on the Sound section.
   *
   * @param {string} [inFile] inFile
   * @returns {Section}
   */
  sound(inFile = "") {
    const sound = section_proxy_wrap(new SoundSection(this, inFile));
    this.sections.push(sound);
    return sound;
  }
  /**
   * Creates an animation. Until you call .then(), .effect(), .sound(), or .wait(), you'll be working on the Animation section.
   *
   * @param {Token|Tile} [inTarget=false] inTarget
   * @returns {AnimationSection}
   */
  animation(inTarget) {
    const animation2 = section_proxy_wrap(
      new AnimationSection(this, inTarget)
    );
    this.sections.push(animation2);
    return animation2;
  }
  /**
   * Creates a scrolling text. Until you call .then(), .effect(), .sound(), or .wait(), you'll be working on the Scrolling Text section.
   *
   * @param {Object|String|Boolean} [inTarget=false] inTarget
   * @param {String|Boolean} [inText=false] inText
   * @param {Object} [inTextOptions={}] inTextOptions
   * @returns {AnimationSection}
   */
  scrollingText(inTarget = false, inText = false, inTextOptions = {}) {
    const scrolling = section_proxy_wrap(
      new ScrollingTextSection(this, inTarget, inText, inTextOptions)
    );
    this.sections.push(scrolling);
    return scrolling;
  }
  /**
   * Pans the canvas text. Until you call any other sections you'll be working on the Canvas Pan section.
   *
   * @param {Object|String|Boolean} [inTarget=false] inTarget
   * @param {Boolean|Number} inDuration
   * @param {Boolean|Number} inSpeed
   * @returns {AnimationSection}
   */
  canvasPan(inTarget = false, inDuration = null, inSpeed = null) {
    const panning = section_proxy_wrap(
      new CanvasPanSection(this, inTarget, inDuration ?? void 0)
    );
    if (inSpeed !== null) panning.speed(inSpeed);
    this.sections.push(panning);
    return panning;
  }
  /**
   * Creates a crosshair section. Until you call other Sequence methods, you will be working on the Crosshair section.
   *
   * @param {string} [inName] inName
   * @returns {Section}
   */
  crosshair(inName = "") {
    const crosshair = section_proxy_wrap(new CrosshairSection(this, inName));
    this.sections.push(crosshair);
    return crosshair;
  }
  /**
   * Adds a location to the sequence and ties it to a string so that it may be used later
   * @param {string} inName
   * @param {object/PlaceableObject/Document} inLocation
   * @returns {Sequence} this
   */
  addNamedLocation(inName, inLocation) {
    if (typeof inName !== "string")
      throw custom_error(this.moduleName, `addNamedLocation - inName must be of type string`);
    if (!(typeof inLocation === "object" || inLocation instanceof foundry.canvas.placeables.PlaceableObject || inLocation instanceof foundry.abstract.Document))
      throw custom_error(this.moduleName, `addNamedLocation - inLocation must be of type object, PlaceableObject, or Document`);
    if (inLocation instanceof CrosshairsPlaceable) inLocation = inLocation.document;
    this.nameOffsetMap ||= {};
    const sourceLocation = inLocation?.source ?? inLocation?.cachedPosition?.source ?? (inLocation instanceof CrosshairsDocument ? inLocation.getOrientation().source : get_object_canvas_data(inLocation, { uuid: false }));
    const targetLocation = inLocation?.target ?? inLocation?.cachedPosition?.target ?? (inLocation instanceof CrosshairsDocument ? inLocation.getOrientation().target ?? sourceLocation : get_object_canvas_data(inLocation, { measure: true, uuid: false }));
    this.nameOffsetMap[inName] = {
      seed: `${inName}-${foundry.utils.randomID()}`,
      source: sourceLocation,
      target: targetLocation
    };
    return this;
  }
  /**
   * Causes the sequence to wait after the last section for as many milliseconds as you pass to this method. If given
   * a second number, a random wait time between the two given numbers will be generated.
   *
   * @param {number} [msMin=1] minMs
   * @param {number} [msMax=1] maxMs
   * @returns {Sequence} this
   */
  wait(msMin = 1, msMax = 1) {
    if (msMin < 1)
      throw custom_error(
        this.moduleName,
        `wait - Wait ms cannot be less than 1`
      );
    if (msMax < 1)
      throw custom_error(
        this.moduleName,
        `wait - Max wait ms cannot be less than 1`
      );
    const section = section_proxy_wrap(new WaitSection(this, msMin, msMax));
    this.sections.push(section);
    return this;
  }
  /**
   * Applies a preset to the sequence
   *
   * @param {string} presetName
   * @param {*} args
   * @returns {Sequence|FunctionSection|EffectSection|AnimationSection|SoundSection}
   */
  preset(presetName, ...args) {
    if (typeof presetName !== "string") {
      throw this._customError(this, "name", `inName must be of type string`);
    }
    const preset = SequencerPresets.get(presetName);
    if (!preset) {
      custom_warning(
        "Sequencer",
        `preset | Could not find preset with name "${presetName}"`
      );
      return this;
    }
    const lastSection = this.sections[this.sections.length - 1] || this;
    return preset(lastSection, ...args);
  }
  /**
   * Adds the sections from a given Sequence to this Sequence
   *
   * @param {Sequence|FunctionSection|EffectSection|AnimationSection|SoundSection} inSequence
   * @returns {Sequence} this
   */
  addSequence(inSequence) {
    if (inSequence instanceof Section) inSequence = inSequence.sequence;
    if (!(inSequence instanceof Sequence2)) {
      throw custom_error(
        this.moduleName,
        `addSequence - could not find the sequence from the given parameter`
      );
    }
    const newSections = inSequence.sections.map((section) => {
      const newSection = Object.assign(
        Object.create(Object.getPrototypeOf(section)),
        section
      );
      newSection.sequence = this;
      return newSection;
    });
    this.sections = this.sections.concat(newSections);
    return this;
  }
  async toJSON() {
    const data = {
      options: { moduleName: this.moduleName, softFail: this.softFail },
      sections: []
    };
    for (const section of this.sections) {
      const sectionData = await section._serialize();
      if (!sectionData.type) {
        throw new Error(
          `Sequencer | toJson | ${section.constructor.name} does not support serialization!`
        );
      }
      data.sections.push(sectionData);
    }
    return data;
  }
  fromJSON(data) {
    this.moduleName = data.options.moduleName;
    this.softFail = data.options.softFail;
    this.local = true;
    for (const section of data.sections) {
      this[section.type]()._deserialize(section);
    }
    return this;
  }
  _createCustomSection(...args) {
    const func = section_proxy_wrap(
      new this.sectionToCreate(this, ...args)
    );
    this.sectionToCreate = void 0;
    this.sections.push(func);
    return func;
  }
  _showWarning(self, func, warning, notify = false) {
    custom_warning(
      this.moduleName,
      `${self.constructor.name.replace("Section", "")} | ${func} - ${warning}`,
      notify
    );
  }
  _customError(self, func, error) {
    return custom_error(
      this.moduleName,
      `${self.constructor.name.replace("Section", "")} | ${func} - ${error}`
    );
  }
  set status(inStatus) {
    this._status.update((currentStatus) => {
      if (currentStatus === CONSTANTS.STATUS.READY || currentStatus === CONSTANTS.STATUS.RUNNING) {
        return inStatus;
      }
      return currentStatus;
    });
  }
  get status() {
    return get$1(this._status);
  }
  get statusStore() {
    return this._status;
  }
  _abort() {
    this.status = CONSTANTS.STATUS.ABORTED;
    for (const section of this.sections) {
      section._abortSection();
    }
  }
};
class SequencerSectionManager {
  #sections = {};
  get externalSections() {
    return this.#sections;
  }
  /**
   * Registers a class by a name that will then be available through the Sequencer
   *
   * @param {String}      inModuleName
   * @param {String}      inMethodName
   * @param {Class}       inClass
   * @param {Boolean}     overwrite
   * @returns {Boolean}               Whether the registration succeeded
   */
  registerSection(inModuleName, inMethodName, inClass, overwrite = false) {
    if (!(inClass.prototype instanceof Section)) {
      throw custom_error(
        inModuleName,
        `inClass must be instance of Sequencer.BaseSection`
      );
    }
    let coreMethods = Object.getOwnPropertyNames(Sequence.prototype).filter(
      (method) => {
        return !method.startsWith("_") && method !== "constructor";
      }
    );
    if (coreMethods.includes(inMethodName)) {
      throw custom_error(
        inModuleName,
        `${inMethodName} is an existing protected method of the Sequence class - please register with another method name!`
      );
    }
    if (this.#sections[inMethodName] && !overwrite) {
      throw custom_error(
        inModuleName,
        `${inMethodName} is already a registered Section with the class ${this.#sections[inMethodName].constructor.name}`
      );
    }
    coreMethods = coreMethods.concat(Object.keys(this.#sections));
    const clashingMethods = Object.getOwnPropertyNames(
      inClass.prototype
    ).filter((method) => coreMethods.includes(method));
    if (clashingMethods.length) {
      let errors = clashingMethods.join(", ");
      throw custom_error(
        inModuleName,
        `${inMethodName} cannot contain the following methods: ${errors}<br>These methods are existing methods on the Sequence or from already registered Sections. Please rename these methods to avoid conflicts.`
      );
    }
    debug(
      `SectionManager | Successfully registered ${inMethodName} with Sequencer!`
    );
    this.#sections[inMethodName] = inClass;
    return true;
  }
}
const sectionManager = new SequencerSectionManager();
let libWrapper = void 0;
const TGT_SPLIT_RE = new RegExp(
  `([^.[]+|\\[('([^'\\\\]|\\\\.)+?'|"([^"\\\\]|\\\\.)+?")\\])`,
  "g"
);
const TGT_CLEANUP_RE = new RegExp(`(^\\['|'\\]$|^\\["|"\\]$)`, "g");
Hooks.once("init", () => {
  if (globalThis.libWrapper && !(globalThis.libWrapper.is_fallback ?? true)) {
    libWrapper = globalThis.libWrapper;
    return;
  }
  libWrapper = class {
    static get is_fallback() {
      return true;
    }
    static get WRAPPER() {
      return "WRAPPER";
    }
    static get MIXED() {
      return "MIXED";
    }
    static get OVERRIDE() {
      return "OVERRIDE";
    }
    static register(package_id, target, fn, type = "MIXED", { chain = void 0, bind = [] } = {}) {
      const is_setter = target.endsWith("#set");
      target = !is_setter ? target : target.slice(0, -4);
      const split = target.match(TGT_SPLIT_RE).map((x) => x.replace(/\\(.)/g, "$1").replace(TGT_CLEANUP_RE, ""));
      const root_nm = split.splice(0, 1)[0];
      let obj, fn_name;
      if (split.length == 0) {
        obj = globalThis;
        fn_name = root_nm;
      } else {
        const _eval = eval;
        fn_name = split.pop();
        obj = split.reduce(
          (x, y) => x[y],
          globalThis[root_nm] ?? _eval(root_nm)
        );
      }
      let iObj = obj;
      let descriptor = null;
      while (iObj) {
        descriptor = Object.getOwnPropertyDescriptor(iObj, fn_name);
        if (descriptor) break;
        iObj = Object.getPrototypeOf(iObj);
      }
      if (!descriptor || descriptor?.configurable === false)
        throw new Error(
          `libWrapper Shim: '${target}' does not exist, could not be found, or has a non-configurable descriptor.`
        );
      let original = null;
      const wrapper = chain ?? (type.toUpperCase?.() != "OVERRIDE" && type != 3) ? function(...args) {
        return fn.call(this, original.bind(this), ...bind, ...args);
      } : function(...args) {
        return fn.call(this, ...bind, ...args);
      };
      if (!is_setter) {
        if (descriptor.value) {
          original = descriptor.value;
          descriptor.value = wrapper;
        } else {
          original = descriptor.get;
          descriptor.get = wrapper;
        }
      } else {
        if (!descriptor.set)
          throw new Error(
            `libWrapper Shim: '${target}' does not have a setter`
          );
        original = descriptor.set;
        descriptor.set = wrapper;
      }
      descriptor.configurable = true;
      Object.defineProperty(obj, fn_name, descriptor);
    }
  };
});
function registerLibwrappers() {
  libWrapper.register(CONSTANTS.MODULE_NAME, "PIXI.BaseImageResource.prototype.upload", PIXIUPLOAD);
}
function PIXIUPLOAD(wrapped, ...args) {
  let baseTexture = args[1];
  if (baseTexture.sequencer_patched || !game.settings.get(CONSTANTS.MODULE_NAME, "enable-global-fix-pixi")) {
    return wrapped(...args);
  }
  let source2 = args[3];
  source2 = source2 || this.source;
  const isVideo = !!source2.videoWidth;
  if (isVideo) {
    baseTexture.alphaMode = PIXI.ALPHA_MODES.PREMULTIPLIED_ALPHA;
    baseTexture.sequencer_patched = true;
  }
  return wrapped(...args);
}
async function runMigrations() {
  const sortedMigrations = Object.entries(migrations).sort((a, b) => {
    if (foundry.utils.isNewerVersion(b[0], a[0])) return -1;
    if (foundry.utils.isNewerVersion(a[0], b[0])) return 1;
    return 0;
  });
  for (const [version, migration] of sortedMigrations) {
    try {
      await migration(version);
    } catch (err) {
      custom_warning(
        "Sequencer",
        `Something went wrong when migrating to version ${version}. Please check the console for the error!`,
        true
      );
      console.error(err);
    }
  }
}
function getSequencerEffectTokens(version, tokenFilter = false) {
  return Array.from(game.scenes).map((scene) => [
    scene,
    Array.from(scene.tokens).filter((token) => token.isOwner).filter((token, index2) => {
      if (tokenFilter) {
        return tokenFilter(token, index2);
      }
      const effects = foundry.utils.getProperty(token, CONSTANTS.EFFECTS_FLAG) ?? [];
      const effectsOutOfDate = effects.filter(
        (e) => foundry.utils.isNewerVersion(version, e[1].flagVersion)
      );
      return effectsOutOfDate.length;
    })
  ]).filter(([_, tokens]) => tokens.length);
}
function getSequencerEffectActors(version, actorFilter = false) {
  return Array.from(game.actors).filter((actor) => actor.isOwner).filter((actor, index2) => {
    if (actorFilter) {
      return actorFilter(actor, index2);
    }
    const effects = foundry.utils.getProperty(actor, CONSTANTS.EFFECTS_FLAG) ?? [];
    const effectsOutOfDate = effects.filter(
      (e) => foundry.utils.isNewerVersion(version, e[1].flagVersion)
    );
    return effectsOutOfDate.length;
  });
}
const migrations = {
  "3.0.0": async (version) => {
    const actorsToUpdate = getSequencerEffectActors(version, (actor) => {
      const effects = foundry.utils.getProperty(actor, "prototypeToken." + CONSTANTS.EFFECTS_FLAG) ?? [];
      const effectsOutOfDate = effects.filter(
        (e) => foundry.utils.isNewerVersion(version, e[1].flagVersion)
      );
      return effectsOutOfDate.length;
    });
    const actorUpdateArray = actorsToUpdate.map((actor) => {
      const effectsToMoveFromTokenPrototype = foundry.utils.getProperty(
        actor.prototypeToken,
        CONSTANTS.EFFECTS_FLAG
      ).filter(([_, effect2]) => {
        return effect2.persistOptions?.persistTokenPrototype;
      }).map(([id, effect2]) => {
        effect2.flagVersion = version;
        return [id, effect2];
      });
      const effectsToKeepOnTokenPrototype = foundry.utils.getProperty(
        actor.prototypeToken,
        CONSTANTS.EFFECTS_FLAG
      ).filter(([_, effect2]) => {
        return !effect2.persistOptions?.persistTokenPrototype;
      }).map(([id, effect2]) => {
        effect2.flagVersion = version;
        return [id, effect2];
      });
      return {
        _id: actor.id,
        [CONSTANTS.EFFECTS_FLAG]: effectsToMoveFromTokenPrototype,
        ["prototypeToken." + CONSTANTS.EFFECTS_FLAG]: effectsToKeepOnTokenPrototype
      };
    });
    const tokensOnScenes = getSequencerEffectTokens(version, (t) => {
      let actor;
      try {
        actor = t.actor;
      } catch (err) {
        return false;
      }
      const effects = foundry.utils.getProperty(t, CONSTANTS.EFFECTS_FLAG) ?? [];
      const prototypeTokenEffects = foundry.utils.getProperty(actor, "prototypeToken." + CONSTANTS.EFFECTS_FLAG) ?? [];
      const effectsOutOfDate = effects.filter(
        (e) => foundry.utils.isNewerVersion(version, e[1].flagVersion)
      );
      const prototypeEffectsOutOfDate = prototypeTokenEffects.filter(
        (e) => foundry.utils.isNewerVersion(version, e[1].flagVersion)
      );
      return t.actorLink && (effectsOutOfDate.length || prototypeEffectsOutOfDate.length);
    });
    for (const [scene, tokens] of tokensOnScenes) {
      const updates = [];
      for (const token of tokens) {
        const effectsToKeepOnToken = foundry.utils.getProperty(token, CONSTANTS.EFFECTS_FLAG).filter(([_, effect2]) => {
          return !effect2.persistOptions?.persistTokenPrototype;
        }).map(([id, effect2]) => {
          effect2.flagVersion = version;
          return [id, effect2];
        });
        updates.push({
          _id: token.id,
          [CONSTANTS.EFFECTS_FLAG]: effectsToKeepOnToken
        });
      }
      if (updates.length) {
        debug(
          `Sequencer | Updated ${updates.length} tokens' effects on scene ${scene.id} to version ${version}`
        );
        await scene.updateEmbeddedDocuments("Token", updates);
      }
    }
    if (actorUpdateArray.length) {
      debug(
        `Sequencer | Updated ${actorUpdateArray.length} actors' effects to version ${version}`
      );
      await Actor.updateDocuments(actorUpdateArray);
    }
  },
  "3.4.4": async (version) => {
    const actorsToUpdate = getSequencerEffectActors(version, (actor) => {
      const effects = foundry.utils.getProperty(actor, "prototypeToken." + CONSTANTS.EFFECTS_FLAG) ?? [];
      const effectsOutOfDate = effects.filter(
        (e) => foundry.utils.isNewerVersion(version, e[1].flagVersion)
      );
      return effectsOutOfDate.length;
    });
    const actorUpdateArray = actorsToUpdate.map((actor) => {
      const actorEffects = foundry.utils.getProperty(actor, CONSTANTS.EFFECTS_FLAG) ?? [];
      const prototypeTokenEffects = foundry.utils.getProperty(actor, "prototypeToken." + CONSTANTS.EFFECTS_FLAG) ?? [];
      let update2 = {
        _id: actor.id,
        [CONSTANTS.EFFECTS_FLAG]: actorEffects.concat(prototypeTokenEffects)
      };
      forceDeletionKeyWrapper(update2, "prototypeToken." + CONSTANTS.REMOVE_EFFECTS_FLAG);
      return update2;
    });
    if (actorUpdateArray.length) {
      debug(
        `Sequencer | Updated ${actorUpdateArray.length} actors' effects to version ${version}`
      );
      await Actor.updateDocuments(actorUpdateArray);
    }
  },
  "4.0.0": async (version) => {
    let actorsToUpdate = getSequencerEffectActors(version);
    let journalUpdate = {};
    const actorUpdateArray = actorsToUpdate.map((actor) => {
      journalUpdate[actor.uuid.replaceAll(".", "-")] = foundry.utils.getProperty(actor, CONSTANTS.EFFECTS_FLAG) ?? [];
      let update2 = {
        _id: actor.id
      };
      forceDeletionKeyWrapper(update2, CONSTANTS.REMOVE_EFFECTS_FLAG);
      return update2;
    });
    let tokensOnScenes = getSequencerEffectTokens(version);
    for (const [scene, tokens] of tokensOnScenes) {
      const updates = [];
      for (const token of tokens) {
        journalUpdate[token.uuid.replaceAll(".", "-")] = foundry.utils.getProperty(token, CONSTANTS.EFFECTS_FLAG);
        let update2 = {
          _id: token.id
        };
        forceDeletionKeyWrapper(update2, CONSTANTS.REMOVE_EFFECTS_FLAG);
        updates.push(update2);
      }
      if (updates.length) {
        debug(
          `Sequencer | Updated ${updates.length} tokens' effects on scene ${scene.id} to version ${version}`
        );
        await scene.updateEmbeddedDocuments("Token", updates);
      }
    }
    if (actorUpdateArray.length) {
      debug(
        `Sequencer | Updated ${actorUpdateArray.length} actors' effects to version ${version}`
      );
      await Actor.updateDocuments(actorUpdateArray);
    }
    if (Object.keys(journalUpdate).length === 0) return;
    const journal = await createJournalDatabase();
    await journal.update({
      [CONSTANTS.EFFECTS_FLAG]: journalUpdate
    });
  }
};
class TokenCrosshairsDocument extends CrosshairsDocument {
  /**
   * @inheritdoc
   */
  constructor(tokenPlaceable, context = {}, crosshair = {}, callbacks = {}) {
    const tokenDoc = tokenPlaceable.document;
    const { distance, size } = context.parent.grid;
    const rayBounds = new foundry.canvas.geometry.Ray(
      { x: 0, y: 0 },
      { x: tokenDoc.width * distance, y: tokenDoc.height * distance }
    );
    const initial = {
      t: "rect",
      "icon.display": false,
      direction: Math.toDegrees(rayBounds.angle),
      distance: rayBounds.distance
    };
    const defaultCrosshair = foundry.utils.mergeObject(
      TokenCrosshairsDocument.defaultConfig,
      {
        distanceMinMax: {
          min: rayBounds.distance,
          max: rayBounds.distance
        },
        location: {
          offset: {
            x: tokenDoc.width * size / -2,
            y: tokenDoc.height * size / -2
          }
        },
        token: tokenPlaceable
      }
    );
    super(initial, context, foundry.utils.mergeObject(defaultCrosshair, crosshair), callbacks);
  }
  static get defaultConfig() {
    return foundry.utils.mergeObject(super.defaultConfig, {
      gridHighlight: false,
      snap: {
        position: CONST.GRID_SNAPPING_MODES.VERTEX
      },
      label: {
        display: false
      },
      lockDrag: true,
      alpha: 0.5
    });
  }
  static get placeableClass() {
    return TokenCrosshair;
  }
}
class TokenCrosshair extends CrosshairsPlaceable {
  #clonedToken = false;
  #tokenRotation = 0;
  /**
   * @override
   */
  _refreshTemplate() {
    this.template.clear();
    this.#clonedToken.document.x = this.x;
    this.#clonedToken.document.y = this.y;
    this.#clonedToken.document.rotation = this.#tokenRotation;
    this.#clonedToken.document.alpha = this.crosshair.alpha;
    this.#clonedToken.renderFlags.set({ refreshPosition: true, refreshRotation: true, refreshMesh: true });
    this.#clonedToken.initializeSources();
  }
  /**
   * @override
   */
  async draw() {
    this.#clonedToken = this.crosshair.token.clone();
    this.#tokenRotation = this.#clonedToken.document.rotation;
    this.#clonedToken.visible = false;
    this.layer.preview.addChild(this.#clonedToken);
    await this.#clonedToken.draw().then((c) => c.visible = true);
    return super.draw();
  }
  _destroy(options = {}) {
    super._destroy(options);
    this.#clonedToken?.destroy();
  }
  updateDirection(evt) {
    if (this.crosshair.lockManualRotation) return;
    const scrollDelta = Math.sign(evt.deltaY);
    let delta = this.crosshair.snap.direction ? this.crosshair.snap.direction * scrollDelta : scrollDelta * 10;
    if (delta < 0) delta += 360;
    if (delta > 360) delta -= 360;
    this.#tokenRotation += delta;
  }
  _onConfirm(evt) {
    evt.preventDefault();
    canvas.mouseInteractionManager.cancel(evt);
    if (this.isDrag) {
      this.isDrag = false;
      canvas.mouseInteractionManager.reset({
        interactionData: true,
        state: false
      });
      return;
    }
    this.document.updateSource({
      direction: this.#tokenRotation
    });
    return super._onConfirm(evt);
  }
}
class Crosshair {
  static PLACEMENT_RESTRICTIONS = CONSTANTS.PLACEMENT_RESTRICTIONS;
  static CALLBACKS = CONSTANTS.CALLBACKS;
  /**
   * Show a configurable crosshair
   *
   * @param {CrosshairData/Object} config
   * @param {CrosshairCallbackData/Object} callbacks
   * @returns {Promise}
   */
  static show(config = {}, callbacks = {}) {
    const reticle = new CrosshairsDocument({
      t: config.t ?? CONST.MEASURED_TEMPLATE_TYPES.CIRCLE,
      distance: config?.distance ?? canvas.grid.distance / 2,
      width: config?.width ?? canvas.grid.distance,
      borderColor: config.borderColor ?? "#000000",
      fillColor: config.fillColor ?? game.user.color,
      texture: config.texture,
      angle: config.angle ?? 53.13,
      // Default for Foundry, it seems?
      direction: config.direction ?? 0
    }, {
      parent: canvas.scene
    }, foundry.utils.mergeObject(CrosshairsDocument.defaultConfig, config), callbacks);
    return reticle.show();
  }
  /**
   * Show a configurable crosshair based a PlaceableObject
   *
   * @param {PlaceableObject} obj
   * @param {CrosshairData/Object} config
   * @param {CrosshairCallbackData/Object} callbacks
   * @returns {Promise}
   */
  static showToken(obj, config = {}, callbacks = {}) {
    const placeable = obj instanceof foundry.canvas.placeables.PlaceableObject ? obj : obj.object;
    const controlled = placeable.controlled;
    placeable.release();
    const reticle = new TokenCrosshairsDocument(placeable, {
      parent: canvas.scene
    }, config, callbacks);
    return reticle.show().finally(() => {
      if (controlled) placeable.control();
    });
  }
  /**
   * @param {CrosshairsDocument} crosshair
   * @param {String/Array<string>} types
   * @param {(object: PlaceableObject, crosshair: CrosshairsDocument, shape: PIXI.Graphics) => boolean} filterMethod
   * @returns {Array<Document>/Record<String, Array<Document>>}
   */
  static collect(crosshair, types = "Token", filterMethod = this.containsCenter) {
    const typesArray = Array.isArray(types) ? types : [types];
    const crosshairDoc = crosshair instanceof CrosshairsPlaceable ? crosshair.document : crosshair;
    const shape = crosshairDoc.getCrosshairShape();
    shape.x = crosshair.x;
    shape.y = crosshair.y;
    const result = typesArray.reduce((acc, embeddedName) => {
      const collection = crosshairDoc.parent.getEmbeddedCollection(embeddedName);
      acc[embeddedName] = collection.filter((document2) => {
        return filterMethod(document2.object, crosshairDoc, shape);
      });
      return acc;
    }, {});
    return Array.isArray(types) ? result : result[types];
  }
  static containsCenter(placeable, crosshair, shape) {
    return shape.contains(placeable.center.x, placeable.center.y);
  }
}
var root$1 = /* @__PURE__ */ from_html(`<div class="standard-form"><section><!></section> <footer class="form-footer"><button type="submit"><i class="fas fa-check"></i> Okay</button></footer></div>`);
function Svelte_dialog($$anchor, $$props) {
  push($$props, true);
  var div = root$1();
  var section = child(div);
  var node = child(section);
  component(node, () => $$props.component, ($$anchor2, $$component) => {
    $$component($$anchor2, {});
  });
  var footer = sibling(section, 2);
  var button = child(footer);
  delegated("click", button, () => $$props.foundryApp.close());
  append($$anchor, div);
  pop();
}
delegate(["click"]);
class SvelteDialog extends SvelteApplicationMixin(foundry.applications.api.ApplicationV2) {
  static DEFAULT_OPTIONS = {
    position: {
      width: 500,
      height: "auto"
    },
    window: {
      title: "Sequencer",
      resizable: false
    }
  };
  root = Svelte_dialog;
}
function _getDefaults() {
  return {
    async: false,
    breaks: false,
    extensions: null,
    gfm: true,
    hooks: null,
    pedantic: false,
    renderer: null,
    silent: false,
    tokenizer: null,
    walkTokens: null
  };
}
let _defaults = _getDefaults();
function changeDefaults(newDefaults) {
  _defaults = newDefaults;
}
const escapeTest = /[&<>"']/;
const escapeReplace = new RegExp(escapeTest.source, "g");
const escapeTestNoEncode = /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/;
const escapeReplaceNoEncode = new RegExp(escapeTestNoEncode.source, "g");
const escapeReplacements = {
  "&": "&amp;",
  "<": "&lt;",
  ">": "&gt;",
  '"': "&quot;",
  "'": "&#39;"
};
const getEscapeReplacement = (ch) => escapeReplacements[ch];
function escape$1(html2, encode) {
  if (encode) {
    if (escapeTest.test(html2)) {
      return html2.replace(escapeReplace, getEscapeReplacement);
    }
  } else {
    if (escapeTestNoEncode.test(html2)) {
      return html2.replace(escapeReplaceNoEncode, getEscapeReplacement);
    }
  }
  return html2;
}
const caret = /(^|[^\[])\^/g;
function edit(regex, opt) {
  let source2 = typeof regex === "string" ? regex : regex.source;
  opt = opt || "";
  const obj = {
    replace: (name2, val) => {
      let valSource = typeof val === "string" ? val : val.source;
      valSource = valSource.replace(caret, "$1");
      source2 = source2.replace(name2, valSource);
      return obj;
    },
    getRegex: () => {
      return new RegExp(source2, opt);
    }
  };
  return obj;
}
function cleanUrl(href) {
  try {
    href = encodeURI(href).replace(/%25/g, "%");
  } catch {
    return null;
  }
  return href;
}
const noopTest = { exec: () => null };
function splitCells(tableRow, count) {
  const row = tableRow.replace(/\|/g, (match, offset2, str) => {
    let escaped = false;
    let curr = offset2;
    while (--curr >= 0 && str[curr] === "\\")
      escaped = !escaped;
    if (escaped) {
      return "|";
    } else {
      return " |";
    }
  }), cells = row.split(/ \|/);
  let i = 0;
  if (!cells[0].trim()) {
    cells.shift();
  }
  if (cells.length > 0 && !cells[cells.length - 1].trim()) {
    cells.pop();
  }
  if (count) {
    if (cells.length > count) {
      cells.splice(count);
    } else {
      while (cells.length < count)
        cells.push("");
    }
  }
  for (; i < cells.length; i++) {
    cells[i] = cells[i].trim().replace(/\\\|/g, "|");
  }
  return cells;
}
function rtrim(str, c, invert) {
  const l = str.length;
  if (l === 0) {
    return "";
  }
  let suffLen = 0;
  while (suffLen < l) {
    const currChar = str.charAt(l - suffLen - 1);
    if (currChar === c && true) {
      suffLen++;
    } else {
      break;
    }
  }
  return str.slice(0, l - suffLen);
}
function findClosingBracket(str, b) {
  if (str.indexOf(b[1]) === -1) {
    return -1;
  }
  let level = 0;
  for (let i = 0; i < str.length; i++) {
    if (str[i] === "\\") {
      i++;
    } else if (str[i] === b[0]) {
      level++;
    } else if (str[i] === b[1]) {
      level--;
      if (level < 0) {
        return i;
      }
    }
  }
  return -1;
}
function outputLink(cap, link2, raw, lexer) {
  const href = link2.href;
  const title = link2.title ? escape$1(link2.title) : null;
  const text2 = cap[1].replace(/\\([\[\]])/g, "$1");
  if (cap[0].charAt(0) !== "!") {
    lexer.state.inLink = true;
    const token = {
      type: "link",
      raw,
      href,
      title,
      text: text2,
      tokens: lexer.inlineTokens(text2)
    };
    lexer.state.inLink = false;
    return token;
  }
  return {
    type: "image",
    raw,
    href,
    title,
    text: escape$1(text2)
  };
}
function indentCodeCompensation(raw, text2) {
  const matchIndentToCode = raw.match(/^(\s+)(?:```)/);
  if (matchIndentToCode === null) {
    return text2;
  }
  const indentToCode = matchIndentToCode[1];
  return text2.split("\n").map((node) => {
    const matchIndentInNode = node.match(/^\s+/);
    if (matchIndentInNode === null) {
      return node;
    }
    const [indentInNode] = matchIndentInNode;
    if (indentInNode.length >= indentToCode.length) {
      return node.slice(indentToCode.length);
    }
    return node;
  }).join("\n");
}
class _Tokenizer {
  options;
  rules;
  // set by the lexer
  lexer;
  // set by the lexer
  constructor(options) {
    this.options = options || _defaults;
  }
  space(src) {
    const cap = this.rules.block.newline.exec(src);
    if (cap && cap[0].length > 0) {
      return {
        type: "space",
        raw: cap[0]
      };
    }
  }
  code(src) {
    const cap = this.rules.block.code.exec(src);
    if (cap) {
      const text2 = cap[0].replace(/^(?: {1,4}| {0,3}\t)/gm, "");
      return {
        type: "code",
        raw: cap[0],
        codeBlockStyle: "indented",
        text: !this.options.pedantic ? rtrim(text2, "\n") : text2
      };
    }
  }
  fences(src) {
    const cap = this.rules.block.fences.exec(src);
    if (cap) {
      const raw = cap[0];
      const text2 = indentCodeCompensation(raw, cap[3] || "");
      return {
        type: "code",
        raw,
        lang: cap[2] ? cap[2].trim().replace(this.rules.inline.anyPunctuation, "$1") : cap[2],
        text: text2
      };
    }
  }
  heading(src) {
    const cap = this.rules.block.heading.exec(src);
    if (cap) {
      let text2 = cap[2].trim();
      if (/#$/.test(text2)) {
        const trimmed = rtrim(text2, "#");
        if (this.options.pedantic) {
          text2 = trimmed.trim();
        } else if (!trimmed || / $/.test(trimmed)) {
          text2 = trimmed.trim();
        }
      }
      return {
        type: "heading",
        raw: cap[0],
        depth: cap[1].length,
        text: text2,
        tokens: this.lexer.inline(text2)
      };
    }
  }
  hr(src) {
    const cap = this.rules.block.hr.exec(src);
    if (cap) {
      return {
        type: "hr",
        raw: rtrim(cap[0], "\n")
      };
    }
  }
  blockquote(src) {
    const cap = this.rules.block.blockquote.exec(src);
    if (cap) {
      let lines = rtrim(cap[0], "\n").split("\n");
      let raw = "";
      let text2 = "";
      const tokens = [];
      while (lines.length > 0) {
        let inBlockquote = false;
        const currentLines = [];
        let i;
        for (i = 0; i < lines.length; i++) {
          if (/^ {0,3}>/.test(lines[i])) {
            currentLines.push(lines[i]);
            inBlockquote = true;
          } else if (!inBlockquote) {
            currentLines.push(lines[i]);
          } else {
            break;
          }
        }
        lines = lines.slice(i);
        const currentRaw = currentLines.join("\n");
        const currentText = currentRaw.replace(/\n {0,3}((?:=+|-+) *)(?=\n|$)/g, "\n    $1").replace(/^ {0,3}>[ \t]?/gm, "");
        raw = raw ? `${raw}
${currentRaw}` : currentRaw;
        text2 = text2 ? `${text2}
${currentText}` : currentText;
        const top = this.lexer.state.top;
        this.lexer.state.top = true;
        this.lexer.blockTokens(currentText, tokens, true);
        this.lexer.state.top = top;
        if (lines.length === 0) {
          break;
        }
        const lastToken = tokens[tokens.length - 1];
        if (lastToken?.type === "code") {
          break;
        } else if (lastToken?.type === "blockquote") {
          const oldToken = lastToken;
          const newText = oldToken.raw + "\n" + lines.join("\n");
          const newToken = this.blockquote(newText);
          tokens[tokens.length - 1] = newToken;
          raw = raw.substring(0, raw.length - oldToken.raw.length) + newToken.raw;
          text2 = text2.substring(0, text2.length - oldToken.text.length) + newToken.text;
          break;
        } else if (lastToken?.type === "list") {
          const oldToken = lastToken;
          const newText = oldToken.raw + "\n" + lines.join("\n");
          const newToken = this.list(newText);
          tokens[tokens.length - 1] = newToken;
          raw = raw.substring(0, raw.length - lastToken.raw.length) + newToken.raw;
          text2 = text2.substring(0, text2.length - oldToken.raw.length) + newToken.raw;
          lines = newText.substring(tokens[tokens.length - 1].raw.length).split("\n");
          continue;
        }
      }
      return {
        type: "blockquote",
        raw,
        tokens,
        text: text2
      };
    }
  }
  list(src) {
    let cap = this.rules.block.list.exec(src);
    if (cap) {
      let bull = cap[1].trim();
      const isordered = bull.length > 1;
      const list2 = {
        type: "list",
        raw: "",
        ordered: isordered,
        start: isordered ? +bull.slice(0, -1) : "",
        loose: false,
        items: []
      };
      bull = isordered ? `\\d{1,9}\\${bull.slice(-1)}` : `\\${bull}`;
      if (this.options.pedantic) {
        bull = isordered ? bull : "[*+-]";
      }
      const itemRegex = new RegExp(`^( {0,3}${bull})((?:[	 ][^\\n]*)?(?:\\n|$))`);
      let endsWithBlankLine = false;
      while (src) {
        let endEarly = false;
        let raw = "";
        let itemContents = "";
        if (!(cap = itemRegex.exec(src))) {
          break;
        }
        if (this.rules.block.hr.test(src)) {
          break;
        }
        raw = cap[0];
        src = src.substring(raw.length);
        let line = cap[2].split("\n", 1)[0].replace(/^\t+/, (t) => " ".repeat(3 * t.length));
        let nextLine = src.split("\n", 1)[0];
        let blankLine = !line.trim();
        let indent = 0;
        if (this.options.pedantic) {
          indent = 2;
          itemContents = line.trimStart();
        } else if (blankLine) {
          indent = cap[1].length + 1;
        } else {
          indent = cap[2].search(/[^ ]/);
          indent = indent > 4 ? 1 : indent;
          itemContents = line.slice(indent);
          indent += cap[1].length;
        }
        if (blankLine && /^[ \t]*$/.test(nextLine)) {
          raw += nextLine + "\n";
          src = src.substring(nextLine.length + 1);
          endEarly = true;
        }
        if (!endEarly) {
          const nextBulletRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ 	][^\\n]*)?(?:\\n|$))`);
          const hrRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`);
          const fencesBeginRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:\`\`\`|~~~)`);
          const headingBeginRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}#`);
          const htmlBeginRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}<(?:[a-z].*>|!--)`, "i");
          while (src) {
            const rawLine = src.split("\n", 1)[0];
            let nextLineWithoutTabs;
            nextLine = rawLine;
            if (this.options.pedantic) {
              nextLine = nextLine.replace(/^ {1,4}(?=( {4})*[^ ])/g, "  ");
              nextLineWithoutTabs = nextLine;
            } else {
              nextLineWithoutTabs = nextLine.replace(/\t/g, "    ");
            }
            if (fencesBeginRegex.test(nextLine)) {
              break;
            }
            if (headingBeginRegex.test(nextLine)) {
              break;
            }
            if (htmlBeginRegex.test(nextLine)) {
              break;
            }
            if (nextBulletRegex.test(nextLine)) {
              break;
            }
            if (hrRegex.test(nextLine)) {
              break;
            }
            if (nextLineWithoutTabs.search(/[^ ]/) >= indent || !nextLine.trim()) {
              itemContents += "\n" + nextLineWithoutTabs.slice(indent);
            } else {
              if (blankLine) {
                break;
              }
              if (line.replace(/\t/g, "    ").search(/[^ ]/) >= 4) {
                break;
              }
              if (fencesBeginRegex.test(line)) {
                break;
              }
              if (headingBeginRegex.test(line)) {
                break;
              }
              if (hrRegex.test(line)) {
                break;
              }
              itemContents += "\n" + nextLine;
            }
            if (!blankLine && !nextLine.trim()) {
              blankLine = true;
            }
            raw += rawLine + "\n";
            src = src.substring(rawLine.length + 1);
            line = nextLineWithoutTabs.slice(indent);
          }
        }
        if (!list2.loose) {
          if (endsWithBlankLine) {
            list2.loose = true;
          } else if (/\n[ \t]*\n[ \t]*$/.test(raw)) {
            endsWithBlankLine = true;
          }
        }
        let istask = null;
        let ischecked;
        if (this.options.gfm) {
          istask = /^\[[ xX]\] /.exec(itemContents);
          if (istask) {
            ischecked = istask[0] !== "[ ] ";
            itemContents = itemContents.replace(/^\[[ xX]\] +/, "");
          }
        }
        list2.items.push({
          type: "list_item",
          raw,
          task: !!istask,
          checked: ischecked,
          loose: false,
          text: itemContents,
          tokens: []
        });
        list2.raw += raw;
      }
      list2.items[list2.items.length - 1].raw = list2.items[list2.items.length - 1].raw.trimEnd();
      list2.items[list2.items.length - 1].text = list2.items[list2.items.length - 1].text.trimEnd();
      list2.raw = list2.raw.trimEnd();
      for (let i = 0; i < list2.items.length; i++) {
        this.lexer.state.top = false;
        list2.items[i].tokens = this.lexer.blockTokens(list2.items[i].text, []);
        if (!list2.loose) {
          const spacers = list2.items[i].tokens.filter((t) => t.type === "space");
          const hasMultipleLineBreaks = spacers.length > 0 && spacers.some((t) => /\n.*\n/.test(t.raw));
          list2.loose = hasMultipleLineBreaks;
        }
      }
      if (list2.loose) {
        for (let i = 0; i < list2.items.length; i++) {
          list2.items[i].loose = true;
        }
      }
      return list2;
    }
  }
  html(src) {
    const cap = this.rules.block.html.exec(src);
    if (cap) {
      const token = {
        type: "html",
        block: true,
        raw: cap[0],
        pre: cap[1] === "pre" || cap[1] === "script" || cap[1] === "style",
        text: cap[0]
      };
      return token;
    }
  }
  def(src) {
    const cap = this.rules.block.def.exec(src);
    if (cap) {
      const tag2 = cap[1].toLowerCase().replace(/\s+/g, " ");
      const href = cap[2] ? cap[2].replace(/^<(.*)>$/, "$1").replace(this.rules.inline.anyPunctuation, "$1") : "";
      const title = cap[3] ? cap[3].substring(1, cap[3].length - 1).replace(this.rules.inline.anyPunctuation, "$1") : cap[3];
      return {
        type: "def",
        tag: tag2,
        raw: cap[0],
        href,
        title
      };
    }
  }
  table(src) {
    const cap = this.rules.block.table.exec(src);
    if (!cap) {
      return;
    }
    if (!/[:|]/.test(cap[2])) {
      return;
    }
    const headers = splitCells(cap[1]);
    const aligns = cap[2].replace(/^\||\| *$/g, "").split("|");
    const rows = cap[3] && cap[3].trim() ? cap[3].replace(/\n[ \t]*$/, "").split("\n") : [];
    const item = {
      type: "table",
      raw: cap[0],
      header: [],
      align: [],
      rows: []
    };
    if (headers.length !== aligns.length) {
      return;
    }
    for (const align2 of aligns) {
      if (/^ *-+: *$/.test(align2)) {
        item.align.push("right");
      } else if (/^ *:-+: *$/.test(align2)) {
        item.align.push("center");
      } else if (/^ *:-+ *$/.test(align2)) {
        item.align.push("left");
      } else {
        item.align.push(null);
      }
    }
    for (let i = 0; i < headers.length; i++) {
      item.header.push({
        text: headers[i],
        tokens: this.lexer.inline(headers[i]),
        header: true,
        align: item.align[i]
      });
    }
    for (const row of rows) {
      item.rows.push(splitCells(row, item.header.length).map((cell, i) => {
        return {
          text: cell,
          tokens: this.lexer.inline(cell),
          header: false,
          align: item.align[i]
        };
      }));
    }
    return item;
  }
  lheading(src) {
    const cap = this.rules.block.lheading.exec(src);
    if (cap) {
      return {
        type: "heading",
        raw: cap[0],
        depth: cap[2].charAt(0) === "=" ? 1 : 2,
        text: cap[1],
        tokens: this.lexer.inline(cap[1])
      };
    }
  }
  paragraph(src) {
    const cap = this.rules.block.paragraph.exec(src);
    if (cap) {
      const text2 = cap[1].charAt(cap[1].length - 1) === "\n" ? cap[1].slice(0, -1) : cap[1];
      return {
        type: "paragraph",
        raw: cap[0],
        text: text2,
        tokens: this.lexer.inline(text2)
      };
    }
  }
  text(src) {
    const cap = this.rules.block.text.exec(src);
    if (cap) {
      return {
        type: "text",
        raw: cap[0],
        text: cap[0],
        tokens: this.lexer.inline(cap[0])
      };
    }
  }
  escape(src) {
    const cap = this.rules.inline.escape.exec(src);
    if (cap) {
      return {
        type: "escape",
        raw: cap[0],
        text: escape$1(cap[1])
      };
    }
  }
  tag(src) {
    const cap = this.rules.inline.tag.exec(src);
    if (cap) {
      if (!this.lexer.state.inLink && /^<a /i.test(cap[0])) {
        this.lexer.state.inLink = true;
      } else if (this.lexer.state.inLink && /^<\/a>/i.test(cap[0])) {
        this.lexer.state.inLink = false;
      }
      if (!this.lexer.state.inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
        this.lexer.state.inRawBlock = true;
      } else if (this.lexer.state.inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
        this.lexer.state.inRawBlock = false;
      }
      return {
        type: "html",
        raw: cap[0],
        inLink: this.lexer.state.inLink,
        inRawBlock: this.lexer.state.inRawBlock,
        block: false,
        text: cap[0]
      };
    }
  }
  link(src) {
    const cap = this.rules.inline.link.exec(src);
    if (cap) {
      const trimmedUrl = cap[2].trim();
      if (!this.options.pedantic && /^</.test(trimmedUrl)) {
        if (!/>$/.test(trimmedUrl)) {
          return;
        }
        const rtrimSlash = rtrim(trimmedUrl.slice(0, -1), "\\");
        if ((trimmedUrl.length - rtrimSlash.length) % 2 === 0) {
          return;
        }
      } else {
        const lastParenIndex = findClosingBracket(cap[2], "()");
        if (lastParenIndex > -1) {
          const start = cap[0].indexOf("!") === 0 ? 5 : 4;
          const linkLen = start + cap[1].length + lastParenIndex;
          cap[2] = cap[2].substring(0, lastParenIndex);
          cap[0] = cap[0].substring(0, linkLen).trim();
          cap[3] = "";
        }
      }
      let href = cap[2];
      let title = "";
      if (this.options.pedantic) {
        const link2 = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href);
        if (link2) {
          href = link2[1];
          title = link2[3];
        }
      } else {
        title = cap[3] ? cap[3].slice(1, -1) : "";
      }
      href = href.trim();
      if (/^</.test(href)) {
        if (this.options.pedantic && !/>$/.test(trimmedUrl)) {
          href = href.slice(1);
        } else {
          href = href.slice(1, -1);
        }
      }
      return outputLink(cap, {
        href: href ? href.replace(this.rules.inline.anyPunctuation, "$1") : href,
        title: title ? title.replace(this.rules.inline.anyPunctuation, "$1") : title
      }, cap[0], this.lexer);
    }
  }
  reflink(src, links) {
    let cap;
    if ((cap = this.rules.inline.reflink.exec(src)) || (cap = this.rules.inline.nolink.exec(src))) {
      const linkString = (cap[2] || cap[1]).replace(/\s+/g, " ");
      const link2 = links[linkString.toLowerCase()];
      if (!link2) {
        const text2 = cap[0].charAt(0);
        return {
          type: "text",
          raw: text2,
          text: text2
        };
      }
      return outputLink(cap, link2, cap[0], this.lexer);
    }
  }
  emStrong(src, maskedSrc, prevChar = "") {
    let match = this.rules.inline.emStrongLDelim.exec(src);
    if (!match)
      return;
    if (match[3] && prevChar.match(/[\p{L}\p{N}]/u))
      return;
    const nextChar = match[1] || match[2] || "";
    if (!nextChar || !prevChar || this.rules.inline.punctuation.exec(prevChar)) {
      const lLength = [...match[0]].length - 1;
      let rDelim, rLength, delimTotal = lLength, midDelimTotal = 0;
      const endReg = match[0][0] === "*" ? this.rules.inline.emStrongRDelimAst : this.rules.inline.emStrongRDelimUnd;
      endReg.lastIndex = 0;
      maskedSrc = maskedSrc.slice(-1 * src.length + lLength);
      while ((match = endReg.exec(maskedSrc)) != null) {
        rDelim = match[1] || match[2] || match[3] || match[4] || match[5] || match[6];
        if (!rDelim)
          continue;
        rLength = [...rDelim].length;
        if (match[3] || match[4]) {
          delimTotal += rLength;
          continue;
        } else if (match[5] || match[6]) {
          if (lLength % 3 && !((lLength + rLength) % 3)) {
            midDelimTotal += rLength;
            continue;
          }
        }
        delimTotal -= rLength;
        if (delimTotal > 0)
          continue;
        rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal);
        const lastCharLength = [...match[0]][0].length;
        const raw = src.slice(0, lLength + match.index + lastCharLength + rLength);
        if (Math.min(lLength, rLength) % 2) {
          const text3 = raw.slice(1, -1);
          return {
            type: "em",
            raw,
            text: text3,
            tokens: this.lexer.inlineTokens(text3)
          };
        }
        const text2 = raw.slice(2, -2);
        return {
          type: "strong",
          raw,
          text: text2,
          tokens: this.lexer.inlineTokens(text2)
        };
      }
    }
  }
  codespan(src) {
    const cap = this.rules.inline.code.exec(src);
    if (cap) {
      let text2 = cap[2].replace(/\n/g, " ");
      const hasNonSpaceChars = /[^ ]/.test(text2);
      const hasSpaceCharsOnBothEnds = /^ /.test(text2) && / $/.test(text2);
      if (hasNonSpaceChars && hasSpaceCharsOnBothEnds) {
        text2 = text2.substring(1, text2.length - 1);
      }
      text2 = escape$1(text2, true);
      return {
        type: "codespan",
        raw: cap[0],
        text: text2
      };
    }
  }
  br(src) {
    const cap = this.rules.inline.br.exec(src);
    if (cap) {
      return {
        type: "br",
        raw: cap[0]
      };
    }
  }
  del(src) {
    const cap = this.rules.inline.del.exec(src);
    if (cap) {
      return {
        type: "del",
        raw: cap[0],
        text: cap[2],
        tokens: this.lexer.inlineTokens(cap[2])
      };
    }
  }
  autolink(src) {
    const cap = this.rules.inline.autolink.exec(src);
    if (cap) {
      let text2, href;
      if (cap[2] === "@") {
        text2 = escape$1(cap[1]);
        href = "mailto:" + text2;
      } else {
        text2 = escape$1(cap[1]);
        href = text2;
      }
      return {
        type: "link",
        raw: cap[0],
        text: text2,
        href,
        tokens: [
          {
            type: "text",
            raw: text2,
            text: text2
          }
        ]
      };
    }
  }
  url(src) {
    let cap;
    if (cap = this.rules.inline.url.exec(src)) {
      let text2, href;
      if (cap[2] === "@") {
        text2 = escape$1(cap[0]);
        href = "mailto:" + text2;
      } else {
        let prevCapZero;
        do {
          prevCapZero = cap[0];
          cap[0] = this.rules.inline._backpedal.exec(cap[0])?.[0] ?? "";
        } while (prevCapZero !== cap[0]);
        text2 = escape$1(cap[0]);
        if (cap[1] === "www.") {
          href = "http://" + cap[0];
        } else {
          href = cap[0];
        }
      }
      return {
        type: "link",
        raw: cap[0],
        text: text2,
        href,
        tokens: [
          {
            type: "text",
            raw: text2,
            text: text2
          }
        ]
      };
    }
  }
  inlineText(src) {
    const cap = this.rules.inline.text.exec(src);
    if (cap) {
      let text2;
      if (this.lexer.state.inRawBlock) {
        text2 = cap[0];
      } else {
        text2 = escape$1(cap[0]);
      }
      return {
        type: "text",
        raw: cap[0],
        text: text2
      };
    }
  }
}
const newline = /^(?:[ \t]*(?:\n|$))+/;
const blockCode = /^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/;
const fences = /^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/;
const hr = /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/;
const heading = /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/;
const bullet = /(?:[*+-]|\d{1,9}[.)])/;
const lheading = edit(/^(?!bull |blockCode|fences|blockquote|heading|html)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html))+?)\n {0,3}(=+|-+) *(?:\n+|$)/).replace(/bull/g, bullet).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}/).replace(/html/g, / {0,3}<[^\n>]+>\n/).getRegex();
const _paragraph = /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/;
const blockText = /^[^\n]+/;
const _blockLabel = /(?!\s*\])(?:\\.|[^\[\]\\])+/;
const def = edit(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label", _blockLabel).replace("title", /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex();
const list = edit(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g, bullet).getRegex();
const _tag = "address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul";
const _comment = /<!--(?:-?>|[\s\S]*?(?:-->|$))/;
const html = edit("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|<![A-Z][\\s\\S]*?(?:>\\n*|$)|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ 	]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ 	]*)+\\n|$)|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ 	]*)+\\n|$))", "i").replace("comment", _comment).replace("tag", _tag).replace("attribute", / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex();
const paragraph = edit(_paragraph).replace("hr", hr).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("|table", "").replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", _tag).getRegex();
const blockquote = edit(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph", paragraph).getRegex();
const blockNormal = {
  blockquote,
  code: blockCode,
  def,
  fences,
  heading,
  hr,
  html,
  lheading,
  list,
  newline,
  paragraph,
  table: noopTest,
  text: blockText
};
const gfmTable = edit("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr", hr).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("blockquote", " {0,3}>").replace("code", "(?: {4}| {0,3}	)[^\\n]").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", _tag).getRegex();
const blockGfm = {
  ...blockNormal,
  table: gfmTable,
  paragraph: edit(_paragraph).replace("hr", hr).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("table", gfmTable).replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", _tag).getRegex()
};
const blockPedantic = {
  ...blockNormal,
  html: edit(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)|<tag(?:"[^"]*"|'[^']*'|\\s[^'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment", _comment).replace(/tag/g, "(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),
  def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,
  heading: /^(#{1,6})(.*)(?:\n+|$)/,
  fences: noopTest,
  // fences not supported
  lheading: /^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,
  paragraph: edit(_paragraph).replace("hr", hr).replace("heading", " *#{1,6} *[^\n]").replace("lheading", lheading).replace("|table", "").replace("blockquote", " {0,3}>").replace("|fences", "").replace("|list", "").replace("|html", "").replace("|tag", "").getRegex()
};
const escape = /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/;
const inlineCode = /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/;
const br = /^( {2,}|\\)\n(?!\s*$)/;
const inlineText = /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/;
const _punctuation = "\\p{P}\\p{S}";
const punctuation = edit(/^((?![*_])[\spunctuation])/, "u").replace(/punctuation/g, _punctuation).getRegex();
const blockSkip = /\[[^[\]]*?\]\((?:\\.|[^\\\(\)]|\((?:\\.|[^\\\(\)])*\))*\)|`[^`]*?`|<[^<>]*?>/g;
const emStrongLDelim = edit(/^(?:\*+(?:((?!\*)[punct])|[^\s*]))|^_+(?:((?!_)[punct])|([^\s_]))/, "u").replace(/punct/g, _punctuation).getRegex();
const emStrongRDelimAst = edit("^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)[punct](\\*+)(?=[\\s]|$)|[^punct\\s](\\*+)(?!\\*)(?=[punct\\s]|$)|(?!\\*)[punct\\s](\\*+)(?=[^punct\\s])|[\\s](\\*+)(?!\\*)(?=[punct])|(?!\\*)[punct](\\*+)(?!\\*)(?=[punct])|[^punct\\s](\\*+)(?=[^punct\\s])", "gu").replace(/punct/g, _punctuation).getRegex();
const emStrongRDelimUnd = edit("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)[punct](_+)(?=[\\s]|$)|[^punct\\s](_+)(?!_)(?=[punct\\s]|$)|(?!_)[punct\\s](_+)(?=[^punct\\s])|[\\s](_+)(?!_)(?=[punct])|(?!_)[punct](_+)(?!_)(?=[punct])", "gu").replace(/punct/g, _punctuation).getRegex();
const anyPunctuation = edit(/\\([punct])/, "gu").replace(/punct/g, _punctuation).getRegex();
const autolink = edit(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme", /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email", /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex();
const _inlineComment = edit(_comment).replace("(?:-->|$)", "-->").getRegex();
const tag = edit("^comment|^</[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^<![a-zA-Z]+\\s[\\s\\S]*?>|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>").replace("comment", _inlineComment).replace("attribute", /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex();
const _inlineLabel = /(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/;
const link = edit(/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/).replace("label", _inlineLabel).replace("href", /<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/).replace("title", /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex();
const reflink = edit(/^!?\[(label)\]\[(ref)\]/).replace("label", _inlineLabel).replace("ref", _blockLabel).getRegex();
const nolink = edit(/^!?\[(ref)\](?:\[\])?/).replace("ref", _blockLabel).getRegex();
const reflinkSearch = edit("reflink|nolink(?!\\()", "g").replace("reflink", reflink).replace("nolink", nolink).getRegex();
const inlineNormal = {
  _backpedal: noopTest,
  // only used for GFM url
  anyPunctuation,
  autolink,
  blockSkip,
  br,
  code: inlineCode,
  del: noopTest,
  emStrongLDelim,
  emStrongRDelimAst,
  emStrongRDelimUnd,
  escape,
  link,
  nolink,
  punctuation,
  reflink,
  reflinkSearch,
  tag,
  text: inlineText,
  url: noopTest
};
const inlinePedantic = {
  ...inlineNormal,
  link: edit(/^!?\[(label)\]\((.*?)\)/).replace("label", _inlineLabel).getRegex(),
  reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label", _inlineLabel).getRegex()
};
const inlineGfm = {
  ...inlineNormal,
  escape: edit(escape).replace("])", "~|])").getRegex(),
  url: edit(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/, "i").replace("email", /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),
  _backpedal: /(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,
  del: /^(~~?)(?=[^\s~])((?:\\.|[^\\])*?(?:\\.|[^\s~\\]))\1(?=[^~]|$)/,
  text: /^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/
};
const inlineBreaks = {
  ...inlineGfm,
  br: edit(br).replace("{2,}", "*").getRegex(),
  text: edit(inlineGfm.text).replace("\\b_", "\\b_| {2,}\\n").replace(/\{2,\}/g, "*").getRegex()
};
const block = {
  normal: blockNormal,
  gfm: blockGfm,
  pedantic: blockPedantic
};
const inline = {
  normal: inlineNormal,
  gfm: inlineGfm,
  breaks: inlineBreaks,
  pedantic: inlinePedantic
};
class _Lexer {
  tokens;
  options;
  state;
  tokenizer;
  inlineQueue;
  constructor(options) {
    this.tokens = [];
    this.tokens.links = /* @__PURE__ */ Object.create(null);
    this.options = options || _defaults;
    this.options.tokenizer = this.options.tokenizer || new _Tokenizer();
    this.tokenizer = this.options.tokenizer;
    this.tokenizer.options = this.options;
    this.tokenizer.lexer = this;
    this.inlineQueue = [];
    this.state = {
      inLink: false,
      inRawBlock: false,
      top: true
    };
    const rules = {
      block: block.normal,
      inline: inline.normal
    };
    if (this.options.pedantic) {
      rules.block = block.pedantic;
      rules.inline = inline.pedantic;
    } else if (this.options.gfm) {
      rules.block = block.gfm;
      if (this.options.breaks) {
        rules.inline = inline.breaks;
      } else {
        rules.inline = inline.gfm;
      }
    }
    this.tokenizer.rules = rules;
  }
  /**
   * Expose Rules
   */
  static get rules() {
    return {
      block,
      inline
    };
  }
  /**
   * Static Lex Method
   */
  static lex(src, options) {
    const lexer = new _Lexer(options);
    return lexer.lex(src);
  }
  /**
   * Static Lex Inline Method
   */
  static lexInline(src, options) {
    const lexer = new _Lexer(options);
    return lexer.inlineTokens(src);
  }
  /**
   * Preprocessing
   */
  lex(src) {
    src = src.replace(/\r\n|\r/g, "\n");
    this.blockTokens(src, this.tokens);
    for (let i = 0; i < this.inlineQueue.length; i++) {
      const next = this.inlineQueue[i];
      this.inlineTokens(next.src, next.tokens);
    }
    this.inlineQueue = [];
    return this.tokens;
  }
  blockTokens(src, tokens = [], lastParagraphClipped = false) {
    if (this.options.pedantic) {
      src = src.replace(/\t/g, "    ").replace(/^ +$/gm, "");
    }
    let token;
    let lastToken;
    let cutSrc;
    while (src) {
      if (this.options.extensions && this.options.extensions.block && this.options.extensions.block.some((extTokenizer) => {
        if (token = extTokenizer.call({ lexer: this }, src, tokens)) {
          src = src.substring(token.raw.length);
          tokens.push(token);
          return true;
        }
        return false;
      })) {
        continue;
      }
      if (token = this.tokenizer.space(src)) {
        src = src.substring(token.raw.length);
        if (token.raw.length === 1 && tokens.length > 0) {
          tokens[tokens.length - 1].raw += "\n";
        } else {
          tokens.push(token);
        }
        continue;
      }
      if (token = this.tokenizer.code(src)) {
        src = src.substring(token.raw.length);
        lastToken = tokens[tokens.length - 1];
        if (lastToken && (lastToken.type === "paragraph" || lastToken.type === "text")) {
          lastToken.raw += "\n" + token.raw;
          lastToken.text += "\n" + token.text;
          this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text;
        } else {
          tokens.push(token);
        }
        continue;
      }
      if (token = this.tokenizer.fences(src)) {
        src = src.substring(token.raw.length);
        tokens.push(token);
        continue;
      }
      if (token = this.tokenizer.heading(src)) {
        src = src.substring(token.raw.length);
        tokens.push(token);
        continue;
      }
      if (token = this.tokenizer.hr(src)) {
        src = src.substring(token.raw.length);
        tokens.push(token);
        continue;
      }
      if (token = this.tokenizer.blockquote(src)) {
        src = src.substring(token.raw.length);
        tokens.push(token);
        continue;
      }
      if (token = this.tokenizer.list(src)) {
        src = src.substring(token.raw.length);
        tokens.push(token);
        continue;
      }
      if (token = this.tokenizer.html(src)) {
        src = src.substring(token.raw.length);
        tokens.push(token);
        continue;
      }
      if (token = this.tokenizer.def(src)) {
        src = src.substring(token.raw.length);
        lastToken = tokens[tokens.length - 1];
        if (lastToken && (lastToken.type === "paragraph" || lastToken.type === "text")) {
          lastToken.raw += "\n" + token.raw;
          lastToken.text += "\n" + token.raw;
          this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text;
        } else if (!this.tokens.links[token.tag]) {
          this.tokens.links[token.tag] = {
            href: token.href,
            title: token.title
          };
        }
        continue;
      }
      if (token = this.tokenizer.table(src)) {
        src = src.substring(token.raw.length);
        tokens.push(token);
        continue;
      }
      if (token = this.tokenizer.lheading(src)) {
        src = src.substring(token.raw.length);
        tokens.push(token);
        continue;
      }
      cutSrc = src;
      if (this.options.extensions && this.options.extensions.startBlock) {
        let startIndex = Infinity;
        const tempSrc = src.slice(1);
        let tempStart;
        this.options.extensions.startBlock.forEach((getStartIndex) => {
          tempStart = getStartIndex.call({ lexer: this }, tempSrc);
          if (typeof tempStart === "number" && tempStart >= 0) {
            startIndex = Math.min(startIndex, tempStart);
          }
        });
        if (startIndex < Infinity && startIndex >= 0) {
          cutSrc = src.substring(0, startIndex + 1);
        }
      }
      if (this.state.top && (token = this.tokenizer.paragraph(cutSrc))) {
        lastToken = tokens[tokens.length - 1];
        if (lastParagraphClipped && lastToken?.type === "paragraph") {
          lastToken.raw += "\n" + token.raw;
          lastToken.text += "\n" + token.text;
          this.inlineQueue.pop();
          this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text;
        } else {
          tokens.push(token);
        }
        lastParagraphClipped = cutSrc.length !== src.length;
        src = src.substring(token.raw.length);
        continue;
      }
      if (token = this.tokenizer.text(src)) {
        src = src.substring(token.raw.length);
        lastToken = tokens[tokens.length - 1];
        if (lastToken && lastToken.type === "text") {
          lastToken.raw += "\n" + token.raw;
          lastToken.text += "\n" + token.text;
          this.inlineQueue.pop();
          this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text;
        } else {
          tokens.push(token);
        }
        continue;
      }
      if (src) {
        const errMsg = "Infinite loop on byte: " + src.charCodeAt(0);
        if (this.options.silent) {
          console.error(errMsg);
          break;
        } else {
          throw new Error(errMsg);
        }
      }
    }
    this.state.top = true;
    return tokens;
  }
  inline(src, tokens = []) {
    this.inlineQueue.push({ src, tokens });
    return tokens;
  }
  /**
   * Lexing/Compiling
   */
  inlineTokens(src, tokens = []) {
    let token, lastToken, cutSrc;
    let maskedSrc = src;
    let match;
    let keepPrevChar, prevChar;
    if (this.tokens.links) {
      const links = Object.keys(this.tokens.links);
      if (links.length > 0) {
        while ((match = this.tokenizer.rules.inline.reflinkSearch.exec(maskedSrc)) != null) {
          if (links.includes(match[0].slice(match[0].lastIndexOf("[") + 1, -1))) {
            maskedSrc = maskedSrc.slice(0, match.index) + "[" + "a".repeat(match[0].length - 2) + "]" + maskedSrc.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex);
          }
        }
      }
    }
    while ((match = this.tokenizer.rules.inline.blockSkip.exec(maskedSrc)) != null) {
      maskedSrc = maskedSrc.slice(0, match.index) + "[" + "a".repeat(match[0].length - 2) + "]" + maskedSrc.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);
    }
    while ((match = this.tokenizer.rules.inline.anyPunctuation.exec(maskedSrc)) != null) {
      maskedSrc = maskedSrc.slice(0, match.index) + "++" + maskedSrc.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);
    }
    while (src) {
      if (!keepPrevChar) {
        prevChar = "";
      }
      keepPrevChar = false;
      if (this.options.extensions && this.options.extensions.inline && this.options.extensions.inline.some((extTokenizer) => {
        if (token = extTokenizer.call({ lexer: this }, src, tokens)) {
          src = src.substring(token.raw.length);
          tokens.push(token);
          return true;
        }
        return false;
      })) {
        continue;
      }
      if (token = this.tokenizer.escape(src)) {
        src = src.substring(token.raw.length);
        tokens.push(token);
        continue;
      }
      if (token = this.tokenizer.tag(src)) {
        src = src.substring(token.raw.length);
        lastToken = tokens[tokens.length - 1];
        if (lastToken && token.type === "text" && lastToken.type === "text") {
          lastToken.raw += token.raw;
          lastToken.text += token.text;
        } else {
          tokens.push(token);
        }
        continue;
      }
      if (token = this.tokenizer.link(src)) {
        src = src.substring(token.raw.length);
        tokens.push(token);
        continue;
      }
      if (token = this.tokenizer.reflink(src, this.tokens.links)) {
        src = src.substring(token.raw.length);
        lastToken = tokens[tokens.length - 1];
        if (lastToken && token.type === "text" && lastToken.type === "text") {
          lastToken.raw += token.raw;
          lastToken.text += token.text;
        } else {
          tokens.push(token);
        }
        continue;
      }
      if (token = this.tokenizer.emStrong(src, maskedSrc, prevChar)) {
        src = src.substring(token.raw.length);
        tokens.push(token);
        continue;
      }
      if (token = this.tokenizer.codespan(src)) {
        src = src.substring(token.raw.length);
        tokens.push(token);
        continue;
      }
      if (token = this.tokenizer.br(src)) {
        src = src.substring(token.raw.length);
        tokens.push(token);
        continue;
      }
      if (token = this.tokenizer.del(src)) {
        src = src.substring(token.raw.length);
        tokens.push(token);
        continue;
      }
      if (token = this.tokenizer.autolink(src)) {
        src = src.substring(token.raw.length);
        tokens.push(token);
        continue;
      }
      if (!this.state.inLink && (token = this.tokenizer.url(src))) {
        src = src.substring(token.raw.length);
        tokens.push(token);
        continue;
      }
      cutSrc = src;
      if (this.options.extensions && this.options.extensions.startInline) {
        let startIndex = Infinity;
        const tempSrc = src.slice(1);
        let tempStart;
        this.options.extensions.startInline.forEach((getStartIndex) => {
          tempStart = getStartIndex.call({ lexer: this }, tempSrc);
          if (typeof tempStart === "number" && tempStart >= 0) {
            startIndex = Math.min(startIndex, tempStart);
          }
        });
        if (startIndex < Infinity && startIndex >= 0) {
          cutSrc = src.substring(0, startIndex + 1);
        }
      }
      if (token = this.tokenizer.inlineText(cutSrc)) {
        src = src.substring(token.raw.length);
        if (token.raw.slice(-1) !== "_") {
          prevChar = token.raw.slice(-1);
        }
        keepPrevChar = true;
        lastToken = tokens[tokens.length - 1];
        if (lastToken && lastToken.type === "text") {
          lastToken.raw += token.raw;
          lastToken.text += token.text;
        } else {
          tokens.push(token);
        }
        continue;
      }
      if (src) {
        const errMsg = "Infinite loop on byte: " + src.charCodeAt(0);
        if (this.options.silent) {
          console.error(errMsg);
          break;
        } else {
          throw new Error(errMsg);
        }
      }
    }
    return tokens;
  }
}
class _Renderer {
  options;
  parser;
  // set by the parser
  constructor(options) {
    this.options = options || _defaults;
  }
  space(token) {
    return "";
  }
  code({ text: text2, lang, escaped }) {
    const langString = (lang || "").match(/^\S*/)?.[0];
    const code = text2.replace(/\n$/, "") + "\n";
    if (!langString) {
      return "<pre><code>" + (escaped ? code : escape$1(code, true)) + "</code></pre>\n";
    }
    return '<pre><code class="language-' + escape$1(langString) + '">' + (escaped ? code : escape$1(code, true)) + "</code></pre>\n";
  }
  blockquote({ tokens }) {
    const body = this.parser.parse(tokens);
    return `<blockquote>
${body}</blockquote>
`;
  }
  html({ text: text2 }) {
    return text2;
  }
  heading({ tokens, depth }) {
    return `<h${depth}>${this.parser.parseInline(tokens)}</h${depth}>
`;
  }
  hr(token) {
    return "<hr>\n";
  }
  list(token) {
    const ordered = token.ordered;
    const start = token.start;
    let body = "";
    for (let j = 0; j < token.items.length; j++) {
      const item = token.items[j];
      body += this.listitem(item);
    }
    const type = ordered ? "ol" : "ul";
    const startAttr = ordered && start !== 1 ? ' start="' + start + '"' : "";
    return "<" + type + startAttr + ">\n" + body + "</" + type + ">\n";
  }
  listitem(item) {
    let itemBody = "";
    if (item.task) {
      const checkbox = this.checkbox({ checked: !!item.checked });
      if (item.loose) {
        if (item.tokens.length > 0 && item.tokens[0].type === "paragraph") {
          item.tokens[0].text = checkbox + " " + item.tokens[0].text;
          if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === "text") {
            item.tokens[0].tokens[0].text = checkbox + " " + item.tokens[0].tokens[0].text;
          }
        } else {
          item.tokens.unshift({
            type: "text",
            raw: checkbox + " ",
            text: checkbox + " "
          });
        }
      } else {
        itemBody += checkbox + " ";
      }
    }
    itemBody += this.parser.parse(item.tokens, !!item.loose);
    return `<li>${itemBody}</li>
`;
  }
  checkbox({ checked }) {
    return "<input " + (checked ? 'checked="" ' : "") + 'disabled="" type="checkbox">';
  }
  paragraph({ tokens }) {
    return `<p>${this.parser.parseInline(tokens)}</p>
`;
  }
  table(token) {
    let header = "";
    let cell = "";
    for (let j = 0; j < token.header.length; j++) {
      cell += this.tablecell(token.header[j]);
    }
    header += this.tablerow({ text: cell });
    let body = "";
    for (let j = 0; j < token.rows.length; j++) {
      const row = token.rows[j];
      cell = "";
      for (let k = 0; k < row.length; k++) {
        cell += this.tablecell(row[k]);
      }
      body += this.tablerow({ text: cell });
    }
    if (body)
      body = `<tbody>${body}</tbody>`;
    return "<table>\n<thead>\n" + header + "</thead>\n" + body + "</table>\n";
  }
  tablerow({ text: text2 }) {
    return `<tr>
${text2}</tr>
`;
  }
  tablecell(token) {
    const content = this.parser.parseInline(token.tokens);
    const type = token.header ? "th" : "td";
    const tag2 = token.align ? `<${type} align="${token.align}">` : `<${type}>`;
    return tag2 + content + `</${type}>
`;
  }
  /**
   * span level renderer
   */
  strong({ tokens }) {
    return `<strong>${this.parser.parseInline(tokens)}</strong>`;
  }
  em({ tokens }) {
    return `<em>${this.parser.parseInline(tokens)}</em>`;
  }
  codespan({ text: text2 }) {
    return `<code>${text2}</code>`;
  }
  br(token) {
    return "<br>";
  }
  del({ tokens }) {
    return `<del>${this.parser.parseInline(tokens)}</del>`;
  }
  link({ href, title, tokens }) {
    const text2 = this.parser.parseInline(tokens);
    const cleanHref = cleanUrl(href);
    if (cleanHref === null) {
      return text2;
    }
    href = cleanHref;
    let out = '<a href="' + href + '"';
    if (title) {
      out += ' title="' + title + '"';
    }
    out += ">" + text2 + "</a>";
    return out;
  }
  image({ href, title, text: text2 }) {
    const cleanHref = cleanUrl(href);
    if (cleanHref === null) {
      return text2;
    }
    href = cleanHref;
    let out = `<img src="${href}" alt="${text2}"`;
    if (title) {
      out += ` title="${title}"`;
    }
    out += ">";
    return out;
  }
  text(token) {
    return "tokens" in token && token.tokens ? this.parser.parseInline(token.tokens) : token.text;
  }
}
class _TextRenderer {
  // no need for block level renderers
  strong({ text: text2 }) {
    return text2;
  }
  em({ text: text2 }) {
    return text2;
  }
  codespan({ text: text2 }) {
    return text2;
  }
  del({ text: text2 }) {
    return text2;
  }
  html({ text: text2 }) {
    return text2;
  }
  text({ text: text2 }) {
    return text2;
  }
  link({ text: text2 }) {
    return "" + text2;
  }
  image({ text: text2 }) {
    return "" + text2;
  }
  br() {
    return "";
  }
}
class _Parser {
  options;
  renderer;
  textRenderer;
  constructor(options) {
    this.options = options || _defaults;
    this.options.renderer = this.options.renderer || new _Renderer();
    this.renderer = this.options.renderer;
    this.renderer.options = this.options;
    this.renderer.parser = this;
    this.textRenderer = new _TextRenderer();
  }
  /**
   * Static Parse Method
   */
  static parse(tokens, options) {
    const parser = new _Parser(options);
    return parser.parse(tokens);
  }
  /**
   * Static Parse Inline Method
   */
  static parseInline(tokens, options) {
    const parser = new _Parser(options);
    return parser.parseInline(tokens);
  }
  /**
   * Parse Loop
   */
  parse(tokens, top = true) {
    let out = "";
    for (let i = 0; i < tokens.length; i++) {
      const anyToken = tokens[i];
      if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[anyToken.type]) {
        const genericToken = anyToken;
        const ret = this.options.extensions.renderers[genericToken.type].call({ parser: this }, genericToken);
        if (ret !== false || !["space", "hr", "heading", "code", "table", "blockquote", "list", "html", "paragraph", "text"].includes(genericToken.type)) {
          out += ret || "";
          continue;
        }
      }
      const token = anyToken;
      switch (token.type) {
        case "space": {
          out += this.renderer.space(token);
          continue;
        }
        case "hr": {
          out += this.renderer.hr(token);
          continue;
        }
        case "heading": {
          out += this.renderer.heading(token);
          continue;
        }
        case "code": {
          out += this.renderer.code(token);
          continue;
        }
        case "table": {
          out += this.renderer.table(token);
          continue;
        }
        case "blockquote": {
          out += this.renderer.blockquote(token);
          continue;
        }
        case "list": {
          out += this.renderer.list(token);
          continue;
        }
        case "html": {
          out += this.renderer.html(token);
          continue;
        }
        case "paragraph": {
          out += this.renderer.paragraph(token);
          continue;
        }
        case "text": {
          let textToken = token;
          let body = this.renderer.text(textToken);
          while (i + 1 < tokens.length && tokens[i + 1].type === "text") {
            textToken = tokens[++i];
            body += "\n" + this.renderer.text(textToken);
          }
          if (top) {
            out += this.renderer.paragraph({
              type: "paragraph",
              raw: body,
              text: body,
              tokens: [{ type: "text", raw: body, text: body }]
            });
          } else {
            out += body;
          }
          continue;
        }
        default: {
          const errMsg = 'Token with "' + token.type + '" type was not found.';
          if (this.options.silent) {
            console.error(errMsg);
            return "";
          } else {
            throw new Error(errMsg);
          }
        }
      }
    }
    return out;
  }
  /**
   * Parse Inline Tokens
   */
  parseInline(tokens, renderer) {
    renderer = renderer || this.renderer;
    let out = "";
    for (let i = 0; i < tokens.length; i++) {
      const anyToken = tokens[i];
      if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[anyToken.type]) {
        const ret = this.options.extensions.renderers[anyToken.type].call({ parser: this }, anyToken);
        if (ret !== false || !["escape", "html", "link", "image", "strong", "em", "codespan", "br", "del", "text"].includes(anyToken.type)) {
          out += ret || "";
          continue;
        }
      }
      const token = anyToken;
      switch (token.type) {
        case "escape": {
          out += renderer.text(token);
          break;
        }
        case "html": {
          out += renderer.html(token);
          break;
        }
        case "link": {
          out += renderer.link(token);
          break;
        }
        case "image": {
          out += renderer.image(token);
          break;
        }
        case "strong": {
          out += renderer.strong(token);
          break;
        }
        case "em": {
          out += renderer.em(token);
          break;
        }
        case "codespan": {
          out += renderer.codespan(token);
          break;
        }
        case "br": {
          out += renderer.br(token);
          break;
        }
        case "del": {
          out += renderer.del(token);
          break;
        }
        case "text": {
          out += renderer.text(token);
          break;
        }
        default: {
          const errMsg = 'Token with "' + token.type + '" type was not found.';
          if (this.options.silent) {
            console.error(errMsg);
            return "";
          } else {
            throw new Error(errMsg);
          }
        }
      }
    }
    return out;
  }
}
class _Hooks {
  options;
  block;
  constructor(options) {
    this.options = options || _defaults;
  }
  static passThroughHooks = /* @__PURE__ */ new Set([
    "preprocess",
    "postprocess",
    "processAllTokens"
  ]);
  /**
   * Process markdown before marked
   */
  preprocess(markdown) {
    return markdown;
  }
  /**
   * Process HTML after marked is finished
   */
  postprocess(html2) {
    return html2;
  }
  /**
   * Process all tokens before walk tokens
   */
  processAllTokens(tokens) {
    return tokens;
  }
  /**
   * Provide function to tokenize markdown
   */
  provideLexer() {
    return this.block ? _Lexer.lex : _Lexer.lexInline;
  }
  /**
   * Provide function to parse tokens
   */
  provideParser() {
    return this.block ? _Parser.parse : _Parser.parseInline;
  }
}
class Marked {
  defaults = _getDefaults();
  options = this.setOptions;
  parse = this.parseMarkdown(true);
  parseInline = this.parseMarkdown(false);
  Parser = _Parser;
  Renderer = _Renderer;
  TextRenderer = _TextRenderer;
  Lexer = _Lexer;
  Tokenizer = _Tokenizer;
  Hooks = _Hooks;
  constructor(...args) {
    this.use(...args);
  }
  /**
   * Run callback for every token
   */
  walkTokens(tokens, callback) {
    let values = [];
    for (const token of tokens) {
      values = values.concat(callback.call(this, token));
      switch (token.type) {
        case "table": {
          const tableToken = token;
          for (const cell of tableToken.header) {
            values = values.concat(this.walkTokens(cell.tokens, callback));
          }
          for (const row of tableToken.rows) {
            for (const cell of row) {
              values = values.concat(this.walkTokens(cell.tokens, callback));
            }
          }
          break;
        }
        case "list": {
          const listToken = token;
          values = values.concat(this.walkTokens(listToken.items, callback));
          break;
        }
        default: {
          const genericToken = token;
          if (this.defaults.extensions?.childTokens?.[genericToken.type]) {
            this.defaults.extensions.childTokens[genericToken.type].forEach((childTokens) => {
              const tokens2 = genericToken[childTokens].flat(Infinity);
              values = values.concat(this.walkTokens(tokens2, callback));
            });
          } else if (genericToken.tokens) {
            values = values.concat(this.walkTokens(genericToken.tokens, callback));
          }
        }
      }
    }
    return values;
  }
  use(...args) {
    const extensions = this.defaults.extensions || { renderers: {}, childTokens: {} };
    args.forEach((pack) => {
      const opts = { ...pack };
      opts.async = this.defaults.async || opts.async || false;
      if (pack.extensions) {
        pack.extensions.forEach((ext) => {
          if (!ext.name) {
            throw new Error("extension name required");
          }
          if ("renderer" in ext) {
            const prevRenderer = extensions.renderers[ext.name];
            if (prevRenderer) {
              extensions.renderers[ext.name] = function(...args2) {
                let ret = ext.renderer.apply(this, args2);
                if (ret === false) {
                  ret = prevRenderer.apply(this, args2);
                }
                return ret;
              };
            } else {
              extensions.renderers[ext.name] = ext.renderer;
            }
          }
          if ("tokenizer" in ext) {
            if (!ext.level || ext.level !== "block" && ext.level !== "inline") {
              throw new Error("extension level must be 'block' or 'inline'");
            }
            const extLevel = extensions[ext.level];
            if (extLevel) {
              extLevel.unshift(ext.tokenizer);
            } else {
              extensions[ext.level] = [ext.tokenizer];
            }
            if (ext.start) {
              if (ext.level === "block") {
                if (extensions.startBlock) {
                  extensions.startBlock.push(ext.start);
                } else {
                  extensions.startBlock = [ext.start];
                }
              } else if (ext.level === "inline") {
                if (extensions.startInline) {
                  extensions.startInline.push(ext.start);
                } else {
                  extensions.startInline = [ext.start];
                }
              }
            }
          }
          if ("childTokens" in ext && ext.childTokens) {
            extensions.childTokens[ext.name] = ext.childTokens;
          }
        });
        opts.extensions = extensions;
      }
      if (pack.renderer) {
        const renderer = this.defaults.renderer || new _Renderer(this.defaults);
        for (const prop2 in pack.renderer) {
          if (!(prop2 in renderer)) {
            throw new Error(`renderer '${prop2}' does not exist`);
          }
          if (["options", "parser"].includes(prop2)) {
            continue;
          }
          const rendererProp = prop2;
          const rendererFunc = pack.renderer[rendererProp];
          const prevRenderer = renderer[rendererProp];
          renderer[rendererProp] = (...args2) => {
            let ret = rendererFunc.apply(renderer, args2);
            if (ret === false) {
              ret = prevRenderer.apply(renderer, args2);
            }
            return ret || "";
          };
        }
        opts.renderer = renderer;
      }
      if (pack.tokenizer) {
        const tokenizer = this.defaults.tokenizer || new _Tokenizer(this.defaults);
        for (const prop2 in pack.tokenizer) {
          if (!(prop2 in tokenizer)) {
            throw new Error(`tokenizer '${prop2}' does not exist`);
          }
          if (["options", "rules", "lexer"].includes(prop2)) {
            continue;
          }
          const tokenizerProp = prop2;
          const tokenizerFunc = pack.tokenizer[tokenizerProp];
          const prevTokenizer = tokenizer[tokenizerProp];
          tokenizer[tokenizerProp] = (...args2) => {
            let ret = tokenizerFunc.apply(tokenizer, args2);
            if (ret === false) {
              ret = prevTokenizer.apply(tokenizer, args2);
            }
            return ret;
          };
        }
        opts.tokenizer = tokenizer;
      }
      if (pack.hooks) {
        const hooks = this.defaults.hooks || new _Hooks();
        for (const prop2 in pack.hooks) {
          if (!(prop2 in hooks)) {
            throw new Error(`hook '${prop2}' does not exist`);
          }
          if (["options", "block"].includes(prop2)) {
            continue;
          }
          const hooksProp = prop2;
          const hooksFunc = pack.hooks[hooksProp];
          const prevHook = hooks[hooksProp];
          if (_Hooks.passThroughHooks.has(prop2)) {
            hooks[hooksProp] = (arg) => {
              if (this.defaults.async) {
                return Promise.resolve(hooksFunc.call(hooks, arg)).then((ret2) => {
                  return prevHook.call(hooks, ret2);
                });
              }
              const ret = hooksFunc.call(hooks, arg);
              return prevHook.call(hooks, ret);
            };
          } else {
            hooks[hooksProp] = (...args2) => {
              let ret = hooksFunc.apply(hooks, args2);
              if (ret === false) {
                ret = prevHook.apply(hooks, args2);
              }
              return ret;
            };
          }
        }
        opts.hooks = hooks;
      }
      if (pack.walkTokens) {
        const walkTokens = this.defaults.walkTokens;
        const packWalktokens = pack.walkTokens;
        opts.walkTokens = function(token) {
          let values = [];
          values.push(packWalktokens.call(this, token));
          if (walkTokens) {
            values = values.concat(walkTokens.call(this, token));
          }
          return values;
        };
      }
      this.defaults = { ...this.defaults, ...opts };
    });
    return this;
  }
  setOptions(opt) {
    this.defaults = { ...this.defaults, ...opt };
    return this;
  }
  lexer(src, options) {
    return _Lexer.lex(src, options ?? this.defaults);
  }
  parser(tokens, options) {
    return _Parser.parse(tokens, options ?? this.defaults);
  }
  parseMarkdown(blockType) {
    const parse = (src, options) => {
      const origOpt = { ...options };
      const opt = { ...this.defaults, ...origOpt };
      const throwError = this.onError(!!opt.silent, !!opt.async);
      if (this.defaults.async === true && origOpt.async === false) {
        return throwError(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));
      }
      if (typeof src === "undefined" || src === null) {
        return throwError(new Error("marked(): input parameter is undefined or null"));
      }
      if (typeof src !== "string") {
        return throwError(new Error("marked(): input parameter is of type " + Object.prototype.toString.call(src) + ", string expected"));
      }
      if (opt.hooks) {
        opt.hooks.options = opt;
        opt.hooks.block = blockType;
      }
      const lexer = opt.hooks ? opt.hooks.provideLexer() : blockType ? _Lexer.lex : _Lexer.lexInline;
      const parser = opt.hooks ? opt.hooks.provideParser() : blockType ? _Parser.parse : _Parser.parseInline;
      if (opt.async) {
        return Promise.resolve(opt.hooks ? opt.hooks.preprocess(src) : src).then((src2) => lexer(src2, opt)).then((tokens) => opt.hooks ? opt.hooks.processAllTokens(tokens) : tokens).then((tokens) => opt.walkTokens ? Promise.all(this.walkTokens(tokens, opt.walkTokens)).then(() => tokens) : tokens).then((tokens) => parser(tokens, opt)).then((html2) => opt.hooks ? opt.hooks.postprocess(html2) : html2).catch(throwError);
      }
      try {
        if (opt.hooks) {
          src = opt.hooks.preprocess(src);
        }
        let tokens = lexer(src, opt);
        if (opt.hooks) {
          tokens = opt.hooks.processAllTokens(tokens);
        }
        if (opt.walkTokens) {
          this.walkTokens(tokens, opt.walkTokens);
        }
        let html2 = parser(tokens, opt);
        if (opt.hooks) {
          html2 = opt.hooks.postprocess(html2);
        }
        return html2;
      } catch (e) {
        return throwError(e);
      }
    };
    return parse;
  }
  onError(silent, async) {
    return (e) => {
      e.message += "\nPlease report this to https://github.com/markedjs/marked.";
      if (silent) {
        const msg = "<p>An error occurred:</p><pre>" + escape$1(e.message + "", true) + "</pre>";
        if (async) {
          return Promise.resolve(msg);
        }
        return msg;
      }
      if (async) {
        return Promise.reject(e);
      }
      throw e;
    };
  }
}
const markedInstance = new Marked();
function marked(src, opt) {
  return markedInstance.parse(src, opt);
}
marked.options = marked.setOptions = function(options) {
  markedInstance.setOptions(options);
  marked.defaults = markedInstance.defaults;
  changeDefaults(marked.defaults);
  return marked;
};
marked.getDefaults = _getDefaults;
marked.defaults = _defaults;
marked.use = function(...args) {
  markedInstance.use(...args);
  marked.defaults = markedInstance.defaults;
  changeDefaults(marked.defaults);
  return marked;
};
marked.walkTokens = function(tokens, callback) {
  return markedInstance.walkTokens(tokens, callback);
};
marked.parseInline = markedInstance.parseInline;
marked.Parser = _Parser;
marked.parser = _Parser.parse;
marked.Renderer = _Renderer;
marked.TextRenderer = _TextRenderer;
marked.Lexer = _Lexer;
marked.lexer = _Lexer.lex;
marked.Tokenizer = _Tokenizer;
marked.Hooks = _Hooks;
marked.parse = marked;
marked.options;
marked.setOptions;
marked.use;
marked.walkTokens;
marked.parseInline;
_Parser.parse;
_Lexer.lex;
const changelogText = '## Changelog\n\n# Version 4.2.3\n- *Sequencer* - Adjusted module typing (thank you MrVauxs on github!)\n- *Effects* - Fixed position calculation on effects and sounds with tiles on v14 (thank you aljames-arctic on github!)\n- *Effects* - Fixed custom PF2e template shapes not working with `.scaleToObject()`\n- *Effects* - Fixed `.loopOptions()` with `loops` set not limiting the number of loops when combined with a `.duration()` longer than the effect file\'s own duration\n\n# Version 4.2.2\n- *Effects* - Fixed `.copySprite()` not copying the sprites of tokens with scale on them\n- *Effects* - Fixed `.mask()` not working on Tokens or Tiles\n\n# Version 4.2.1\n- *Effects* - Fixed effects not appearing on scenes that only have the default scene level on Foundry v14\n\n# Version 4.2.0\n- *Sequencer* - Fixed the Sequencer Manager not scrolling when the list of running effects and sounds was taller than the window\n- *Effects* - Greatly improved lookup speed of effects that needed to be deleted across scenes\n- *Effects* - Added `.onLevels()` to limit an effect to specific scene levels on Foundry v14\n- *Effects* - Added support for `.elevation([bottom, top])` to give an effect a vertical elevation range that stays visible on every scene level it reaches on Foundry v14\n- *Effects* - Added a `topInclusive` option to `.elevation([bottom, top])` that mirrors the same property on Foundry Regions, including the top boundary in the range so the effect counts as part of the level above\n- *Effects* - Fixed `.zIndex()` not working with `.screenSpace()` effects\n- *Effects* - Fixed effects showing across every scene level on Foundry v14\n- *Effects* - Fixed `.copySprite()` no longer animating when used on animated webm tokens, and ignoring the token\'s Scale setting and copying at the grid footprint size instead of the visible size\n- *Effects* - Fixed `.filter("ColorMatrix", ...)` breaking when any of `hue`, `brightness`, `contrast`, or `saturate` was given `null` or `undefined`\n- *Effects* - Fixed attached persistent effects not coming along when copy/pasting a token, drawing, tile, template, or region\n- *Effects* - Tweaked `.blendMode()` to create a warning when using `"overlay"`, `"soft-light"`, `"hard-light"`, `"color-dodge"`, `"color-burn"`, `"darken"`, `"lighten"`, `"difference"`, `"exclusion"`, `"hue"`, `"saturation"`, `"color"`, or `"luminosity"`; as these modes are not available in Foundry\'s renderer\n- *Sounds* - Added `.elevation()` to override or offset a positional sound\'s Z, used by the level-distance attenuation\n- *Sounds* - Added `.onLevels()` to limit a sound to specific scene levels on Foundry v14\n- *Sounds* - Fixed sounds played at a position not attenuating across scene levels on Foundry v14\n- *Sounds* - Fixed attached persistent sounds not coming along when copy/pasting a token, drawing, tile, template, or region\n\n# Version 4.1.0\n- *Sequencer* - Added Czech localization, and updated Polish localization (thank you, Lethrendis and Lioheart on github!)\n- *Effects* - Added `.blendMode()` to effect sections, accepting either a string name (e.g. `"multiply"`, `"screen"`, `"soft-light"`) or the `PIXI.BLEND_MODES` const\n- *Effects* - Added `.constrainedByWalls()` for clipping effects to wall-bounded line of sight, independent of the Walled Templates module\n- *Effects* - Added `Sequencer.Helpers.computeWallPolygon()` and extended `.mask()` to accept raw `PIXI.Polygon`, `PIXI.Circle`, or `PIXI.Rectangle` shapes\n- *Effects* - Fixed effects attached to or targeting a Region rendering below tiles that should sit beneath them\n- *Effects* - Fixed `.mask()` rejecting Regions even though Regions were already supported by the effect mask renderer\n- *Effects* - Fixed .atLocation(tile) placing effects at the tile\'s top-left instead of centering them on the tile (thanks aljames-arctic on GitHub)\n\n# Version 4.0.2\n- *Sequencer* - Fixed the Effect Player\'s Alt hotkey leaving attach mode and stretch-to-attach stuck on after release\n- *Sequencer* - Fixed `TypeError: Failed to fetch` (CORS) when previewing a remote asset (e.g. S3) in the Sequencer Database before playing it on the canvas\n- *Sequencer* - Fixed `Sequencer.Presets.get()` freezing Foundry when given a dotted preset name whose fallbacks were not registered\n- *Sequencer* - Fixed `.thenDo()` silently accepting non-function input and failing later mid-sequence with an unhelpful error\n- *Sequencer* - Fixed `Sequencer.Database.registerEntries` with `override: true` not unmarking a module\'s previous private state\n- *Sequencer* - Fixed declining the overwrite prompt while saving an Effect Player preset throwing a `ReferenceError`\n- *Sequencer* - Fixed the Sequencer Database spritesheet preview leaking object URLs and failing to release frames when switching entries\n- *Sequencer* - Fixed deprecation warnings by using `foundry.applications.apps.FilePicker`, `foundry.applications.api.DialogV2`, `foundry.canvas.containers.PreciseText`, `foundry.canvas.geometry.Ray`, and `ui.notifications` progress bars instead of their deprecated globals\n- *Sequencer* - Fixed `.forUsers()` throwing when given `User` objects instead of user IDs, even though both are documented to work\n- *Effects* - Fixed forced-index database keys ending in two or more digits (e.g. `effect.12`) playing the wrong frame\n- *Effects* - Fixed `.zIndex()` having no effect on `.screenSpaceAboveUI()` effects\n- *Effects* - Fixed sequences erroring out for users that did not have permission to play effects, or were excluded by `.forUsers()`\n- *Effects* - Fixed sequences getting stuck waiting forever when an effect failed to load or was ended before it had finished setting up\n- *Effects* - Fixed `.moveTowards()` with `cacheLocation: true` throwing instead of caching the target\'s position\n- *Effects* - Fixed clicking overlapping effects in the Sequencer Manager not consistently selecting the visually top-most one\n- *Effects* - Fixed effect assets being loaded twice and leaking the duplicate when the same file was spawned in rapid succession\n- *Effects* - Fixed memory leak in spritesheet generation\n- *Effects* - Fixed effect file cache where the most recently used files were evicted first instead of the least recently used\n- *Sounds* - Fixed `.forUsers()` being ignored, causing sounds to play for every connected user instead of only the listed users\n- *Sounds* - Fixed persistent sound data being saved into the wrong storage on world load, causing sounds to not migrate correctly between Sequencer versions\n- *Sounds* - Fixed the `endedSequencerSound` hook never firing for persistent sounds when they end\n- *Animations* - Fixed `.repeats()` on animation sections only running once and `_abort()` not interrupting a mid-flight animation\n- *Crosshairs* - Fixed `.borderColor()` always throwing when called without an `alpha` option\n- *Crosshairs* - Fixed `.repeats()` on crosshair sections only running once and `_abort()` not interrupting a pending placement\n- *Crosshairs* - Fixed the crosshair clobbering other modules\' canvas wheel handlers on activation and teardown\n- *Crosshairs* - Fixed cancelling a crosshair with `.persist()` still creating a template on the scene at the cursor position\n- *Canvas Pan* - Fixed `.canvasPan()` ignoring its `duration` and `speed` arguments when called positionally\n- *Canvas Pan* - Fixed `.canvasPan().speed()` being ignored by the pan animation and mistiming any `.lockView()` or `.shake()` set on the same section\n\n# Version 4.0.1\n- *Sequencer* - Reverted `Sequencer.Database.entryExists` to accept partial segment matches (e.g. `"attack"` matching `"attack1"`), which was unintentionally broken in 4.0.0. A console warning is now logged when a partial segment match occurs; this behavior will be tightened in a future version\n- *Sequencer* - Added Portuguese localization (Thank you Kharmans on GitHub!)\n- *Effects* - Fixed `.mask()` not working on Foundry v13 due to the mask filter\'s fragment shader not being passed to the PIXI filter constructor\n- *Effects* - Fixed non-persistent effects not disappearing from the Sequencer Manager after finishing playback\n\n# Version 4.0.0\n- *Sequencer* - Updated to support FoundryVTT v14\n- *Sequencer* - Removed support for FoundryVTT v12\n- *Sequencer* - Removed the socketlib dependency; Sequencer now manages its own data internally\n- *Sequencer* - Introduced a new internal UI foundation with proper dark mode support\n- *Sequencer* - Fixed `preloadForClients` blocking indefinitely when a client is tabbed away or disconnects mid-preload\n- *Effects* - Reworked internal effect storage to use a centralized hidden journal, reducing token and actor update overhead and improving performance in effect-heavy games\n- *Effects* - Removed deprecated methods, warnings are now errors\n- *Effects* - Due to the changes to the storage of effect data, setting `local: true` on the Sequence\'s `play()` method will no longer make the effect entirely local, and still store it in the DB without anyone else seeing it\n- *Effects* - Effects now visually "punch through" region highlights (matches how Foundry\'s own tokens behave with always displayed regions)\n- *Effects* - `.sortLayer()`, `.zIndex()`, `.belowTokens()`, and `.belowTiles()` now work as documented on Foundry v13 & v14\n- *Effects* - `.aboveLighting()` now actually anchors the effect above all lighting elements within the interface layer, but not its interface elements\n- *Effects* - Fixed `.copySprite()` only capturing the subject texture on dynamic-ring tokens; the ring is now included via render-to-texture\n- *Sounds* - Added `.persist()`, which causes the sound to persist on the scene, very cool!\n- *Sounds* - Added `.attachTo()`, which attaches the sound to the target (only supports `bindVisibility` and `bindElevation` at this moment)\n- *Sounds* - Added `.panSound()` which causes the sound to pan left and right when the token you have selected moves to the right or left of the sound\n  - You can control the distance from which it starts panning with `innerEaseDistance`, which is how many grid units away from the source that the ease starts. Use `outerEaseDistance` to control where the sound will be fully panning - if `innerEaseDirstance` is set to 10, and `outerEaseDistance` is set to 30, within 10 grid units the sound will not be panning, between 10 and 30 it will slowly blend to a panning sound, and beyond 30 it will be fully panning\n- *Sounds* - Added `.extraEndDuration()`, which adds additional ending duration to persisted sounds\n- *Sounds* - Added `.loopOptions()`, which controls the looping options of the sounds\n- *Sounds* - Added `.toLocation()`, which will evaluate the target location, but still play at the `.atLocation()` position - this is useful when the sound has multiple files for different ranges\n- *Sounds* - Added `.moveTowards()`, which will cause the sound to move towards the target location - best **not** used with `.toLocation()`\n- *Sounds* - Added `.globalSound()`, which prevents any `.atLocation()` sound from only playing on the canvas, and instead always plays globally.\n- *Sounds* - Added support for the database range-finding implementation typically found in effects; sounds can be defined with 5ft, 15ft, 30ft, 60ft, and 90ft files, and using the above methods will select the correct sound file to play\n- *Animations* - Fixed chained light animations reading the origin position before the previous animation\'s document update had committed, causing the second animation to start from the wrong location\n\nSequencer now has a Patreon if you wish to support its development! Join us now at:\nhttps://www.patreon.com/cw/fantasycomputerworks\n\n# Version 3.6.11\n- *Sequencer* - Fixed `remote` on `.play()` not forcing local on the executing user\'s machine\n- *Sequencer* - Added a one-time dialog promoting our patreon\n- *Effects* - Fixed `.copySprite()` using the wrong texture with `.file()` being set\n\n# Version 3.6.10\n- *Effects* - Fixed `.copySprite()` not copying dynamic token rings (thank you ChasarooniZ on github!)\n- *Effects* - Fixed `.rotateTowards()` with `attach: true` not working properly (thank you RobinCodesStuff on github!)\n- *Effects* - Upgraded `.from()` into a loud deprecation - please use `.copySprite()` instead \n- *Effects* - Upgraded `.attachTo()` with `followRotation` into a loud deprecation - please use `bindRotation` instead \n- *Effects* - Upgraded `.noLoop()` into a loud deprecation - please use `.loopOptions({ loops: 1 })` instead\n- *Scrolling Text* - Fixed inputting `"CENTER"` to `.anchor()` not working\n- *Effects* - Fixed `.screenSpace()` on isometric scenes \n\n# Version 3.6.9\n- *Animations* - Fixed animations not working at all\n\n# Version 3.6.8\n- *Crosshairs* - Fix right click drag, when mouse up happens after holding cursor still (Thanks dmrickey on Github!)\n- *Crosshairs* - Fixed some issues with limit max range on gridless scenes\n- *Sequencer* - Updated all Sequencer types (Thanks LukeAbby on Github!)\n- *Sequencer* - Made crosshair data types optional (Thanks MrVauxs on Github!)\n- *Animations* - Removed token support for `ease` from the following methods due to Foundry removing support for custom easing of token movement:\n  - `.moveTowards()`\n  - `.rotateTowards()`\n  - `.rotateIn()`\n  - `.rotateOut()`\n  - `.fadeIn()`\n  - `.fadeOut()`\n\n# Version 3.6.7\n- *Effects* - Fixed persistent effects not actually being persistent\n- *Effects* - Fixed `.shape()`\'s `fillColor` not liking `#000000` and overriding it to white\n\n# Version 3.6.2\n- *Crosshairs* - Added `.texture()` (Thank you david aka claudekennilol on Discord!)\n- *Crosshairs* - Added `alpha` as an option to `.border()` and `.fillColor()` (Thank you david aka claudekennilol on Discord!)\n- *Sounds* - Fixed sounds with `.atLocation()` not working together with crosshairs \n\n# Version 3.6.1\n- *Sequencer* - Fixed some lingering issues in Foundry v13\n\n## Version 3.6.0\n- *Sequencer* - Updated Sequencer for Foundry v13 while still remaining compatible with v12\n\n## Version 3.5.4\n- *Effects* - Fixed minor issue with isometric & walled templates plugin implementation\n\n## Version 3.5.3\n- *Sequencer* - Fixed overly aggressive welcome chat message\n- *Sequencer* - Sequencer will now fully prevent from launching if socketlib is not properly set up or installed \n\n## Version 3.5.2\n- *Effects* - Fixed `complete` effect versions not playing correctly without `.persist()`\n- *Effects* - Fixed isometric support with better plugin implementation\n\n## Version 3.5.1\n- *Effects* - Fixed issue where effects would get "stuck" on scenes after being played through Automated Animations\n- *Effects* - Fixed issue where the effect manager would break if opened with an effect that had pre-defined ranges in its `.file()` method \n- *Effects* - Fixed .webm\'s with sounds would not play its sound if played as an effect \n- *Crosshairs* - Fixed snap position resolution being undefined by default, causing it to not snap if not defined \n- *Crosshairs* - Elaborated on some crosshairs function documentation\n\n## Version 3.5.0\n- *Sequencer* - Updated TJS implementation to latest versions\n- *Sequencer* - Updated Italian and Polish localization (thank you GregoryWarn and Lioheart on Github!)\n- *Effects* - Improved the sprite sheet generator\'s premultiplied alpha handling (Thanks Codas!)\n- *Effects* - Fixed issues with effects trying to play before the canvas had been fully initialized (Thanks Codas!)\n\n## Version 3.4.9\n- *Sequencer* - Added tooltips to Sequencer database viewer buttons\n- *Animations* - Fixed animation sections with tint throwing error due to missing import\n- *Crosshairs* - Fixed resolution 0 resulting in Foundry crashing (due to internal Foundry handling of 0 resolution)\n- *Effects* - Fixed `binary/octet-stream` not being considered a valid content type\n- *Effects* - Fixed screenspace effects scaling based on the grid-size of the current scene (thank you Vauxs!)\n- *Sounds* - Fixed once-off location-based sounds crashing Foundry  (thank you Vauxs!)\n\n# Version 3.4.8\n- *Sequencer* - Added Italian localization (thanks GregoryWarn!)\n- *Sequencer* - Added `override` to `Sequencer.Database.registerEntries` (thanks MrVauxs)\n- *Effects* - Fixed `.tint()` not working in v12 (thanks MrVauxs)\n- *Effects* - Added `.sortLayer()` to typing (thanks MrVauxs)\n- *Sounds* - Fixed sound breaking because of missing offset function implementation (thanks MrVauxs)\n- *Crosshairs* - Added grid-snapping resolution to the snap property (thanks Spappz)\n\n# Version 3.4.7\n- *Effects* - Fixed `.stretchTo(target, { attachTo: true })` with range-finding effects not properly preloading its different files\n- *Effects* - Fixed `.playbackRate()` not working well with `.fadeOut()` and similar `out` animations\n- *Effects* - Fixed `.stretchTo`\'s `tiling` option not working on scenes with non-100px grid sizes (thanks Codas!)\n- *Sequencer* - Updated Polish localization (thanks Lioheart!)\n\n# Version 3.4.6\n- *Effects* - Added `effect` as a valid `.animateProperty()` and `.loopProperty()` target\n- *Effects* - Added `sourceOffset` and `targetOffset` to `.animateProperty()` and `.loopProperty()` property target\n  - This is used as such: `.animateProperty("effect", "targetOffset.x", { from: 0, to: 200, duration: 5000 })`\n- *Effects* - Deprecated `.from()` in favor for `.copySprite()` which is a more descriptive method name\n- *Effects* - Fixed `anchor` not being optional for `.shape()` (Thanks Codas!)\n- *Effects* - Fixed `scale()` for tiling textures using `stretchTo()` overly stretching the effect (Thanks Codas!)\n- *Effects* - Fixed some issues with caching of generated spritesheets (Thanks Codas!)\n- *Effects* - Disabled spriteshet generation for animated tiling textures - too many visual defects at the moment (Thanks Codas!)\n- *Sequencer* - Fixed audio controls in the Sequencer Database viewer (Thanks Vauxs!)\n- *Sequencer* - Added audio filtering in the Sequencer Database viewer (Thanks Vauxs!)\n- *Crosshairs* - Fixed issue with `updateCrosshair` causing label text to break the canvas\n\n# Version 3.4.5\n- *Effects* - Added `anchor` as an optional argument to `.shape()` (see docs)\n- *Effects* - Fixed `.shape()` not working if no `.file()` was used with `.screenSpace()` and `.screenSpaceScale({ fitX: true, fitY: true })`\n- *Effects* - Fixed calculation of dimensions for canvas tiles (thanks Codas!)\n- *Effects* - Fixed spritesheets generated by just-in-time conversion not having the correct framerate (thanks Codas!)\n- *Effects* - Fixed `.stretchTo()` not having the correct scale after using `.scale()`\n- *Crosshairs* - Fixed `Sequencer.Crosshair.collect()` being broken due to missing import \n\n# Version 3.4.4\n- *Canvas Pan* - Added support for named locations with in-sequence crosshairs\n- *Scrolling Text* - Added support for named locations with in-sequence crosshairs\n- *Effects* - Fixed effects that should have been invisible to users being visible when utilizing `.attachTo()` with `bindVisibility` set to `true` (Thanks Codas!)\n- *Effects* - Fixed `.file()` with specific range-based effects not correctly using the default grid template data (Thanks Codas!)\n- *Effects* - Fixed `.aboveLighting()` being above lighting but not over certain elements\n- *Effects* - Fixed some effects sticking around on tokens even after being deleted\n- *Effects* - Added missing `anchor` documentation to the `.text()` method\n\n# Version 3.4.3\n- *Sequencer* - Fixed slight performance issue with the Sequencer layer querying mouse position even when not active (thanks Codas!)\n- *Effects* - Fixed `.shape()` and `.text()` not working on effects without an underlying sprite (thanks Codas!)\n- *Effects* - Fixed `.xray()` not working (thanks Codas!)\n- *Effects* - Fixed `.screenSpace()` not working due to upgrades to `.animateProperty()` messing with sprite width and such\n- *Crosshairs* - Fixed `displayRangePoly` not working due to missing import\n\n# Version 3.4.2\n- *Effects* - Fixed canvas breaking after playing some types of effects\n\n# Version 3.4.1\n- *Effects* - Fixed effects with internal loops not playing & looping correctly (thanks Codas!)\n- *Effects* - Tweaked just-in-time spritesheet generation to not scale resulting spritesheets as much (thanks Codas!) \n\n# Version 3.4.0\n- *Sequencer* - Added support for custom FPS for flipbooks through an `_fps` database tag\n- *Sequencer* - Added support for spritesheet type effects through linking the spritesheet `.json` manifest in the database\n- *Sequencer* - Added a just-in-time compiler to transform webm video-based effects to gpu-optimized spritesheets\n  - This greatly improves performance when many instances of the same persisted effects are playing at once\n  - Persisted effects are automatically transformed to spritesheets in background threads. Once the compiled spritesheets are available, video effects are seamlessly replaced with spritesheets, greatly reducing the overhead for video decoding for those effects. Depending on size of the effect, spritesheet generation is expected to take anywhere from 5 seconds to 90 seconds. The size of generated Spritesheet textures is limited to 8192x8192px to maximize compatibility and limit memory usage\n  - Generated spritesheets are cached client-side in a semi-persistent, file-based browser cache\n  - Spritesheet generation for video files is only supported in trusted contexts (meaning https or hosted and connected locally on the same machine). This is a hard limitation by the browser vendors and not expected to be lifted in the future\n- *Sequencer* - Tweaked `Sequencer.Presets.get()` to support fall-backs to lower-complexity named entries\n  - If you have added a preset named `spell.ranged` and attempt to get a non-existent `spell.ranged.fire`, it will return the `spell.ranged` entry as a fallback\n  - You can enable the previous behavior by passing `true` as a secondary argument, which causes the method to require an exact match\n- *Crosshairs* - Added `displayRangePoly` to `.location()` and the `Sequencer.Crosshair.show()` API\n- *Crosshairs* - Fixed some minor crosshair issues surrounding utilizing them for effects\n- *Crosshairs* - Fixed `Sequencer.Crosshair.collect()` not working correctly with some crosshair shapes\n- *Effects* - Fixed flipbook animations framerate being tied to canvas FPS\n- *Effects* - Fixed offset from `.rotateTowards()` applying to the source location instead of the target\n- *Effects* - Fixed effects with added text and `.rotateTowards()` having their anchor point moved to an unexpected location\n- *Effects* - Fixed persisted `.stretchTo()` effects not correctly looping the effect in certain cases\n- *Effects* - Fixed `.loopOptions()` not working correctly for persisted effects when combined with `.timeRange()`\n- *Effects* - Fixed `.scaleOut()` end value being multiplied by a potentially set `.scaleIn()` value.\n  - `scaleIn(0.25).scaleOut(2)` now scales the effect in from 0.25 to 1. Plays the animation at 1x size and then scales the animation out to 2x default animation size. Previously, the animation would scale out to 0.5x the default effect size.\n- *Effects* - Fixed `attachTo()` not working on in-flight crosshairs\n\nBig thanks to Codas for his incredible work on the just-in-time compiler, general optimizations, and many fixes! Please support him at the following link: <https://ko-fi.com/Codas>\n\n# Version 3.3.8\n- *Effects* - Fixed `screenSpace` being a required property instead of optional for `.animateProperty()` and `.loopProperty()`\n\n# Version 3.3.7\n- *Sequencer* - Fully removed custom PIXI filters in favor of dedicated Sequencer implementation\n- *Crosshairs* - Added `.width()` for ray-type crosshairs\n- *Effects* - Added `screenspace` (boolean) parameter to `.animateProperty()` and `.loopProperty()`\n  - This makes the values used in these methods for position, width, or height to take the screen-size into account, meaning an `.animateProperty()` with the x position going from 0.0 to 1.0 will have the effect move from the left side of the screen to the right\n- *Effects* - Fixed `.screenSpace()` effects not supporting animating their width when `.screenSpaceScale({ fitX: true, fitY: true })` was set\n- *Effects* - Fixed `.screenSpaceAboveUI()` not correctly setting its renderable status when effects were played on its layer  (thank you Codas!)\n- *Effects* - Fixed `.shape()` not being affected by `.fadeIn()`, `.fadeOut()`, or `.opacity()`  (thank you Codas!)\n\n# Version 3.3.6\n- *Sequencer* - Reverted PIXI filters plugin to more compatible version\n- *Sounds* - Fixed `.startTime()` and `.endTime()` not working\n\n# Version 3.3.5\n- *Effects* - Fixed `.screenSpaceAboveUI()` not correctly falling back to the below UI screenspace layer if the above UI layer is disabled\n- *Animations* - Fixed named sequence-crosshairs not working as a target position\n- *Sequencer* - Fixed minor Foundry v12 deprecation warnings\n- *Sequencer* - Updated PIXI filters\n\n# Version 3.3.4\n- *Crosshairs* - Fixed `.crosshair()`\'s `.persist()` option not working with subsequent `.effect()` sections\n- *Crosshairs* - Fixed typos on `.gridHighlight()` (thanks Vauxs!)\n- *Crosshairs* - Fixed crosshair documentation and typing (thanks Vauxs and Spappz on github!)\n- *Crosshairs* - Fixed `.crosshair()`\'s default size being smaller than the grid\n- *Effects* - Removed `.attachTo()` warning when using `.attachTo()` with a named location (specifically `.persist()`ing crosshairs)\n- *Effects* - Fixed `.name()` not passing mirrorX/Y, rotation, and random rotation to the subsequent effects\n- *Sounds* - Added `.audioChannel()` to sounds, which controls which output they are played through (thank you Oxy949 on github!)\n\n# Version 3.3.3\n- *Crosshairs* - Added `Sequencer.Crosshair.CALLBACKS.STOP_COLLIDING` callback\n- *Crosshairs* - Added `updateCrosshair` method to crosshairs\n- *Crosshairs* - Fixed invalid placement positions being cached and then used even if the crosshair was placed somewhere else\n- *Effects* - Fixed `.attachTo()`\'s `align` not playing well with `.stretchTo()`\n- *Effects* - Fixed `.persist(true, { persistTokenPrototype: true })` not storing more than one persistent effect on the token\n\n# Version 3.3.2\n- *Sequencer* - Fixed `.thenDo()` typing (thank you Vauxs!)\n- *Sequencer* - Tweaked `.addNamedLocation()` to better handle nonconforming inputs\n- *Effects* - Fixed `.screenSpaceAboveUI()` not working with recent optimizations (thanks you Codas!)\n- *Effects* - Fixed `.text()` not utilizing the `PreciseText` Foundry class\n\n# Version 3.3.1\n- *Sequencer* - Added `.addNamedLocation()` which allows you to store and cache locations as a string for later use in the sequence\n- *Effects* - Fixed `.tint()` and `.fadeIn()`/`.fadeOut()` not working properly (thank you Codas!)\n- *Effects* - Fixed `.from()` breaking antialiasing on token textures\n- *Animations* - Fixed using named locations and named crosshairs not working in animations\n\n# Version 3.3.0\n- *Crosshairs* - Added crosshairs! It can be used with `Sequencer.Crosshair.show()` (like warpgate) or with `.crosshair()` on a sequence. See the documentation ([here](https://fantasycomputer.works/FoundryVTT-Sequencer/#/crosshair) and [here](https://fantasycomputer.works/FoundryVTT-Sequencer/#/api/crosshair)) for more info.\n- *Sequencer* - Fixed issues with preloading arrays of files\n- *Sequencer* - Added polish localization (thank you Lioheart!)\n- *Effects* - Greatly improved effect efficiency by improving batching capabilities (thank you Codas, you\'re a legend)\n\nUnfortunately, Sequencer is now a V12 only module.\n\n## Version 3.2.17\n- *Sequencer* - Updated types (Thanks Vauxs on github!)\n- *Effects* - Fixed some issues with the vision masking shader relating to the batch batching optimizations (Thanks Codas on github!)\n- *Effects* - Fixed above UI layer not working with recent render batching optimizations (Thanks Codas on github!)\n\n## Version 3.2.16\n- *Effects* - Major improvements to the way that effects are rendered to better support render batching (thank you Codas, you damn legend!)\n- *Effects* - Tweaked flag manager to better handle effects with broken IDs\n- *Animations* - Fixed `.teleportTo()` triggering regions that react upon movement (thanks Michael on github!)\n- *Sounds* - Fixed sounds not playing on the right channel in v12\n- *Sounds* - Fixed sounds not working with `.fadeInAudio()` and `.fadeOutAudio()`\n- *Sounds* - Fixed sounds with `.atLocation()` and `randomOffset` in v12 not synchronizing the exact location between clients \n\n## Version 3.2.15\n- *Effects* - Fixed non-`.xray()` effects showing in the fog of war\n- *Sounds* - Removed stray debugger\n\n## Version 3.2.14 Hotfix\n- *Sounds* - Fixed sounds again (thank you, Vauxs!)\n\n## Version 3.2.13\n- *Sequencer* - Tweaks and fixes to documentation (thank you, Vauxs!)\n- *Effects* - Fixed templates getting deleted shortly after being used for an effect would cause a Foundry crash\n- *Effects* - Fixed effects sticking around after documents that they were attached to had been deleted\n- *Sounds* - Fixed sounds not working properly in certain macros\n\n## Version 3.2.12\n- *Sequencer* - Further updates to typing information (thank you, Vauxs!)\n- *Sequencer* - Tweaked `preload` optional argument on `Sequence#play` to batch preload files (thank you, Codas!)\n- *Sequencer* - Fixed `local` optional argument on `Sequence#play` to actually only play effects and sounds locally\n- *Sequencer* - Tweaked Sequencer Manager to be more robust and less error prone when invalid effects are active\n- *Effects* - Added `absolute` optional argument to `.animateProperty()` and `.loopProperty()`\n- *Effects* - Fixed some effects not playing in the correct location, which fixes effect position issues with the Isometric module\n- *Sounds* - Fixed sounds not playing in v11 (thank you, Vauxs!)\n- *Canvas Pan* - Fixed initiating a shake with a frequency or duration of 0 would cause the canvas to freeze\n\n## Version 3.2.11\n- *Sequencer* - Updated `Sequence#play` to have two additional secondary parameters:\n  - `preload: boolean` - causes any section with `.file()` entries to preload all files that would be played\n  - `local: boolean` - causes sections to be played only locally to the user executing the sequence\n- *Sequencer* - Improved typing information (thank you, Vauxs!)\n- *Effects* - Fixed non-ASCII text breaking some effects played with Automated Animations due to its usage with the Effect Manager\n- *Effects* - Fixed the alpha of tokens and tiles not being respected with `attachTo()`\n- *Effects* - Fixed `.template()` on effects not overwriting database templates\n- *Effects* - Fixed `CanvasEffect#addAnimatedProperties` causing errors when used twice in a row as it would try to incorrectly serialize entire PIXI sprites\n\n## Version 3.2.10\n- *Sequencer* - Fixed `{ name }` filters on `Sequencer.EffectManager` and `Sequencer.SoundManager` methods not working with regex-like strings\n- *Effects* - Tweaked and improved the animation engine to better handle stacked animations on the same property\n- *Effects* - Fixed `.persist()`ing effects with internal loop assets not finishing the ending portion when the effect was ended\n- *Effects* - Fixed one shot effects replaying the first frame before disappearing\n- *Effects* - Fixed `.aboveInterface()` effects blocking mouse interactions\n- *Effects* - Fixed some looping inconsistencies with `.loopOptions()`\n- *Effects* - Fixed being unable to snap effects to the grid when moved with the Sequencer Player\n\n## Version 3.2.9\n- *Effects* - Fixed effects with `.attachTo()` sometimes throwing errors and causing the scene to freeze\n- *Effects* - Fixed `.mask()`ed effects not following measured templates correctly\n\n## Version 3.2.8\n- *Sequencer* - Fixed Sequencer Manager throwing A DIFFERENT error when trying to see active\n- *Effects* - Fixed `.persist()` effects not looping when first created (but would loop if enough time had passed & user had refreshed - very odd)\n\n## Version 3.2.7 Hotfix\n- *Effects* - Fixed `.persist()` effects not looping\n\n## Version 3.2.6\n- *Sequencer* - Fixed Sequencer Manager throwing an error when trying to see active Sequences\n- *Effects* - Added `.sortLayer()` to be able to more directly control which layer the effect lands on (only in Foundry v12)\n  - This also fixes `.belowTokens()` being below tiles\n  - Thank you Codas on GitHub!\n- *Effects* - Added `.loopOptions()` to allow users to control how an effect loops\n- *Effects* - Deprecated `.noLoop()` in favor of the above, will be elevated to loud deprecation in a future major version\n\n## Version 3.2.5\n- *Effects* - Updated documentation to include `.volume()`, `.fadeInAudio()`, and `.fadeOutAudio()`\n- *Effects* - Fixed some issues with effect scaling when using `.loopProperty()` and `.animateProperty()`\n- *Sounds* - Fixed `Sequencer.SoundManager.endAllSounds()` not ending all sounds\n- *Sounds* - Fixed `Sequencer.SoundManager.endSounds()` sometimes not correctly ending the right sounds\n- *Sounds* - Fixed calling `.sound()` would break sequences in both Foundry v11 and v12\n\n## Version 3.2.4\n- *Sequencer* - Fixed `.preset()` not working when called directly on a sequence (thanks MrVauxs!)\n- *Effects* - Added deprecation warning to `.file()` instead of an error when giving it a second boolean parameter\n- *Effects* - Fixes to the isometric module - may still not be 100%, but it shouldn\'t error anymore\n- *Sounds* - Fixed sounds not playing on v11 (again!)\n\n## Version 3.2.3 Hotfix\n- *Effects* - Fixed issue with effects not playing their full duration\n\n## Version 3.2.2\n- *Sequencer* - ACTUALLY fixed welcome message being posted multiple times in bigger worlds (will also get rid of duplicates)\n- *Effects* - Fixed `.animateProperty()` and `.loopProperty()` not working when used on `alphaFilter`\n- *Effects* - Fixed effects that were attached to temporary templates (like warpgate\'s crosshairs) not disappearing after the crosshair had been placed\n- *Effects* - Fixed issue with `complete`-loop type effects not playing correctly\n\n## Version 3.2.1\n- *Sequencer* - Fixed welcome message being posted multiple times on The Forge - my apologies for this\n- *Sequencer* - Fixed `Sequencer.Preloader` not being properly set up (thanks Codas on github!)\n- *Effects* - Added `antialiasing` optional argument to `.file()` - expects `PIXI.SCALEMODES.LINEAR` or `PIXI.SCALEMODES.NEAREST`\n- *Effects* - Fixed `.moveTowards()` not working\n- *Effects* - Fixed `.animateProperty()` and `.loopProperty()` not properly animating effects\n- *Effects* - Fixed `.timeRange()` and `.endTime()` not working properly\n- *Sounds* - Fixed `.atLocation()` incorrectly throwing errors when on Foundry v12 (thanks Codas on github!)\n- *Sounds* - Fixed sounds not working on Foundry v11\n\n## Version 3.2.0\n- *Sequencer* - Added support for FoundryVTT v12 while remaining backwards compatible with v11\n- *Sequencer* - Added startup chat message with links to relevant external resources\n- *Sequencer* - Added support for the [Isometric module](https://foundryvtt.com/packages/grape_juice-isometrics) (thanks grape_juice for their assistance with this integration!) \n- *Sequencer* - Added `Sequencer.SoundManager` which is a sound interface that mirrors `Sequencer.EffectManager`\n- *Effects* - Greatly improved responsiveness of attached effects actually following their targets more accurately\n- *Effects* - Removed deprecated methods `.offset()` and `.randomOffset()` as those should now be done with the relevant location-based secondary parameters\n- *Effects* - Added `.syncGroup()` which allows you to synchronize the playback of multiple effects in the same scene\n- *Effects* - Tweaked `.scaleToObject()` to cache its target\'s scale when first created, unless paired with `.attachTo()` and `bindScale` (see below)\n- *Effects* - Added `bindScale` (defaults to `true`) to `.attachTo()`, that if combined with `.scaleToObject()` it will always scale with the object\n- *Effects* - Fixed `.tint()` not being applied when used with `.attachTo()` and `.stretchTo()` with `{ attachTo: true }`\n- *Effects* - Tweaked `.attachTo()`\'s `followRotation` to be named `bindRotation` (will remain backwards compatible until 3.3.0 before becoming deprecated)\n- *Sounds* - Added support for the following methods (see the [`.sound()` documentation](https://fantasycomputer.works/FoundryVTT-Sequencer/#/api/sound) for more info):\n  - `.name()`\n  - `.origin()`\n  - Below only in Foundry v12:\n    - `.atLocation()`\n    - `.radius()`\n    - `.constrainedByWalls()`\n    - `.distanceEasing()`\n    - `.alwaysForGMs()`\n    - `.baseEffect()`\n    - `.muffledEffect()`\n\n## Version 3.1.4\n- *Effects* - Added better support for the Walled Templates module\n- *Effects* - Fixed effects becoming invisible when using both `offset` and `local` with just a source location and no target\n- *Effects* - Fixed `.shape()`s `isMask` property not working when the shape type was set to `polygon`\n- *Sounds* - Fixed "End All Sounds" button not working in the Sequencer Manager\n\n## Version 3.1.3\n- *Sequencer* - Removed stray debugger, whoops\n- *Animations* - Fixed `.rotateTowards()` being off by a few degrees\n- *Effects* - Added `.temporary()`, which causes an effect to not set any flags on any objects, which means a refresh will make the effect disappear\n- *Effects* - Fixed `.zIndex()` not working with `.screenSpace()`\n- *Effects* - Fixed `offset`\'s `local` modifier not applying correctly when using `.stretchTo()` and the like\n\n## Version 3.1.2\n- *Sequencer* - Fixed Sequencer\'s ready hooks sometimes not firing when canvas was fully ready\n- *Effects* - Improved persistent effects behavior when pasted across scenes \n\n## Version 3.1.1\n- *Effects* - Fixed `.shape()` with `mask: true` not masking its parent\n- *Effects* - Hopefully fixed issues surrounding effects sometimes not being cleared properly\n- *Effects* - Fixed effects in v11 sometimes ending up in the top left corner after switching scenes\n\n## Version 3.1.0\n- *Sequencer* - Fixed a bug in the database when copying some file paths would not work properly\n- *Effects* - Added `.aboveInterface()`\n\n## Version 3.0.14\n- *Sequencer* - Improved Database-to-Database binding when considering custom metadata\n- *Animations* - Fixed cases where if a newly created token had `.animation()`s applied to them would sometimes fail due to `Token#mesh` not being initialized (Thanks LukeAbby & TMinz)\n- *Effects* - Vastly improved `.mask()` performance and behavior all thanks to dev7355608!\n- *Effects* - Fixed a rare case where range-finding effects would fail to determine the right video file to play\n\n## Version 3.0.13\n- *Effects* - Added `.randomSpriteRotation()`\n- *Effects* - Fixed a bug with cached locations and `.mask()`\n- *Effects* - Fixed database-to-database mappings\n\n## Version 3.0.12\n- *Sequencer* - Fixed console error caused by the clean-up of old expired effects (Thanks LukeAbby on Github!)\n- *Sequencer* - Added `fullyQualified` secondary parameter to `Sequencer.Database.getPathsUnder()` (Thanks LukeAbby on Github!)\n- *Animations* - Added support for elevation in `.teleportTo()`\n- *Canvas Pan* - Fixed `.canvasPan()` not working other clients than the executing client\n- *Effects* - Slightly adjusted how `.missed()` interacts with `.stretchTo()` when two tokens are right next to each other\n- *Effects* - Fixed `.playbackRate()` causing effects to have the incorrect amount of playback time\n- *Effects* - Fixed `.rotateTowards()` not working with `.text()`\n\n## Version 3.0.11\n- *Sequencer* - Made PIXI fix settings default to being turned off\n- *Sequencer* - Fixed strange interaction with `.preset()` not working when certain functions were called in the preset\n- *Sequencer* - In Foundry v11, `.macro()` now requires the secondary parameter to be an object, as the behavior of Advanced Macros have been partially integrated in v11\n- *Effects* - Fixed some persistent effects not being deleted from scenes after their attached sources were deleted\n- *Effects* - Fixed effects targeting drawings not properly using the drawing position and dimensions\n- *Effects* - Fixed `.playbackRate()` not working well with internal loops and `.persist()`\n\n## Version 3.0.10\n- *Sequencer* - Fixed Effect Player not working in Foundry v11\n- *Sequencer* - Fixed private modules showing up in the Database Viewer\n\n## Version 3.0.9\n- *Canvas Pan* - Added `.shake()` which can be used to add camera shake to the canvas\n- *Effects* - Fixed glow filter not working in v11\n- *Effects* - Fixed `.belowTokens()` being below tiles as well\n\n## Version 3.0.8\n- *Effects* - Fixed `stretchTo()` effects sometimes not being visible\n\n## Version 3.0.7\n- *Effects* - Added `requiresLineOfSight` and `hideLineOfSight` to the secondary arguments of `.stretchTo()`\n  - This requires `attachTo` to be true in `stretchTo()`\n  - `requiresLineOfSight` causes the effect to immediately end if the line of sight between the source and target is broken\n  - `hideLineOfSight` modifies the above behavior to temporarily hide the effect until the line of sight is unbroken\n- *Effects* - Fixed the "External Effect Opacity" setting, which causes effects playing for other players to show up as faint for GMs to let them know that players has effects playing for them, but would not work if the opacity was set to 0\n- *Effects* - Improved dual-attached effects performance\n\n## Version 3.0.6\n- *Sequencer* - Fixed issue with Sequencer trying to make users migrate tokens and effects they do not own \n- *Effects* - Fixed effects not updating reliably when the target\'s opacity and/or hidden status changes\n\n## Version 3.0.5\n- *Effects* - Fixed support for google bucket files (it meant to be `video/x-webm` instead of `video/webm-x`)\n- *Effects* - Fixed dual attached effects disappearing once going beyond the range of the initial effect\n\n## Version 3.0.4\n- *Effects* - Added support for file buckets that contain `video/webm-x` files (mostly Google Buckets)\n- *Effects* - Fixed `.scaleToObject()` taking into account token scale when it should not (unless `considerTokenScale` is set to `true`)\n\n## Version 3.0.3\n- *Sequencer* - Fixed edge case with `.addSequence()` that would cause a sequence to ignore `softFail`\n- *Effects & Sounds* - Removed secondary boolean argument for `.file()` as it is covered by the Sequence-wide soft fail\n\n### Version 3.0.2\n- *Sequencer* - Added backwards compatibility to the old format of `new Sequence(moduleName, softFail)`\n- *Sequencer* - Added `remote` support for `.wait()`\n- *Sequencer* - Added `moduleName` and `softFail` support for `remote`\n- *Animations* - Fixed `.snapToGrid()` sometimes not working well on hex scenes \n- *Effects* - Fixed `.xray()` causing effects to disappear\n- *Sounds* - Fixed `softFail` not working properly\n\n### Version 3.0.1\n- *Effects* - Fixed `cacheLocation` not working on effect locations\n- *Effects* - Fixed dual attached effects not working\n\n### Version 3.0.0\n- *Sequencer* - Updated Sequencer Database Viewer:\n  - Improved UI and added nested tree view\n  - Added ctrl modifier to buttons that copy paths, which adds quotes around the copied paths\n- *Sequencer* - Updated Sequencer Effect Player:\n  - Improved UI based on the design of MatthijsKok on github - thanks a lot for the inspiration!\n- *Sequencer* - Reworked the Sequencer Effect Manager to the Sequencer Manager:\n  - Added the ability to stop running sounds\n  - Added a Sequence view where you can see the sequences as they are running, and stop the entire execution or their individual sections\n- *Sequencer* - Added `.scrollingText()` which allows playing scrolling text on the canvas for users\n- *Sequencer* - Added `.canvasPan()` which allows panning the canvas for connected users\n- *Sequencer* - Added `.toJSON()` and `.fromJSON()` to Sequences to be able to be serialized and deserialized; only sequences with effects, sounds, and scrolling texts can be serialized\n- *Sequencer* - Added options to `.play()`, which may contain an object; currently supports `{ remote: true/false }` which will serialize the sequence (see above), and send it to each client for local playback, instead of the person running the sequence sending data to clients as it is being executed\n- *Sequencer* - Added database support for `_timestamps` metadata on effect files, which will trigger the `sequencerEffectTimestamp` hook when effects reach the point of the timestamps for that file\n- *Sequencer* - Added support for flipbook-type effects through a `_flipbook` database tag\n- *Animations* - Improved playback of movement, fade in/out, and rotation animations on tokens\n- *Effects* - Added `CanvasEffect#addAnimatedProperties`, which will allow you to easily add animations to properties of existing effects\n- *Effects* - Improved screenspace above UI effect performance by not rendering the extra canvas when not in use\n- *Effects* - Fixed screenspace effects being affected by the vision mask\n- *Effects* - Fixed `.stretchTo()` effects would be visible when not in vision\n- *Effects* - Fixed `.fadeOut()` and `.scaleOut()` not working at all\n- *Effects* - Reworked how effects are replicated on linked tokens when `.persist()`\'s `persistPrototypeToken` is enabled, improving performance\n\n### Version 2.414\n- *Sequencer* - Included missing CSS file\n\n### Version 2.413\n- *Sequencer* - Added support for database paths that resolve to other database paths\n- *Sequencer* - Isolated Sequencer\'s styling so that it doesn\'t leak out into other modules or systems\n- *Effects* - Fixed `.loopProperty()` not respecting `loops: 0`\n- *Effects* - Fixed `.animateProperty()` not keeping track of relative values when animating the same property multiple times\n- *Effects* - Fixed named screenspace effects without a specific location not playing and throwing errors\n- *Sounds* - Fixed `softFail` not allowing sounds to softly fail\n\n### Version 2.412\n- *Sequencer* - Added setting to hide/show the Sequencer buttons in the left sidebar when in the token controls\n- *Animation* - Fixed `.moveTowards()` going into infinite loop if the source and targets are on top of each other\n- *Effects* - Fixed `.shape()` taking grid size into account multiple times\n- *Effects* - Fixed `.volume()`, `.fadeInAudio()`, and `.fadeOutAudio()` not working on webms with embedded audio \n\n### Version 2.411\n- *Sequencer* - Fixed infinite recursion when using `.waitUntilFinished()` in the middle of a sequence\n- *Sequencer* - Undid some minor issues in the database viewer\n\n### Version 2.410\n- *Sequencer* - Added support for playing sounds in the Sequencer Database (thank you ZotyDev for the pull request!)\n- *Sequencer* - Calling methods on the sequence that it does not have will be attempted to be cast to the last section\n- *Sequencer* - You can now provide `false` as an argument to `.waitUntilFinished()`, which will negate its call\n- *Effects* - Fixed animations on `alphaFilter`\'s `alpha` not working\n\n### Version 2.49\n- We don\'t talk about this version\n\n### Version 2.4.8\n- *Effects* - Fixed rectangle measurable templates would be off by 45 degrees\n- *Effects* - Fixed tokens with locked rotation would cause attached effects to be rotated anyway\n- *Effects* - Fixed `.tint()` not applying to `.stretchTo()` effects\n\n### Version 2.4.7\n- *Effects* - Actually fixed shapes\n\n### Version 2.4.6\n- *Effects* - Fixed effects with only shapes would not play properly\n- *Effects* - Fixed error when users tried to play effects even when `softFail` was set to `true`\n\n### Version 2.4.5\n- *Effects* - Fixed effects playing on the same scene as the user\'s current scene, even if the target of the effect was on another scene\n- *Effects* - Fixed `.from()` not working with the new `softFail` sequence parameter\n- *Effects* - Fixed being able to pass non-valid parameters to `.atLocation()` and similar functions without errors\n\n### Version 2.4.4\n- *Sequencer* - Tweaked the arguments to `new Sequence("moduleName")` to `new Sequence(inOptions)` - it now takes a single object that can contain:\n  - `moduleName` <string> - The name of the module that is creating this sequence - this is for other users to know which module used Sequencer\n  - `softFail` <boolean> - Setting this to `true` causes any failures to find files for effects, sounds, or macros to softly fail, rather than halt the entire sequence\n- *Effects* - Tweaked `.shape()`s parent to be the `spriteContainer` rather than the `sprite`, so that animations to the sprite doesn\'t affect the shapes\n- *Effects* - Fixed `.shape()` not considering their offset with `isMask` enabled  \n\n### Version 2.4.3\n- *Sequencer* - Switched `Disable Pixi Fix` to `Enable Pixi Fix` to make it more consistent with other settings\n- *Sequencer* - Added `Enable Global Pixi Fix` which fixes the alpha on animated tiles if enabled (use with caution) \n- *Effects* - Fixed `.atLocation()` and `.persist()` throwing errors and thus failing to persist the effect on the scene\n- *Effects* - Slight tweaks to visibility logic of effects to be more consistent\n- *Effects* - Tweaked `.attachTo()`\'s `align` and `edge` to not consider token scale when determining the edge of the token\n\n### Version 2.4.2\n- *Sequencer* - Added `Sequencer.Presets` which allows you to create and save reusable bits of sequences\n- *Sequencer* - Added `.preset()` which allows you to use the aforementioned presets\n- *Sequencer* - Added optional `considerTokenScale` to the optional `options` parameter to `.scaleToObject()`, you can set it to `true` in order for the visual effect to also consider the token scale\n- *Sequencer* - Added support for persistent visual effects on "fake" tokens created by Multilevel Tokens (only supports effects that are applied to the prototype token) \n- *Sequencer* - Slightly improved the speed of document updates when visual effects are first applied\n- *Sequencer* - Added a throttled console warning when the Photosensitive Mode is enabled and a client is trying to play effects (only warns once every 10 seconds when effects are played)\n- *Effects* - Fixed `.scaleToObject()` always taking token scale into account, which it shouldn\'t do by default\n\n### Version 2.4.1\n- *Effects* - Fixed minor typo in `.mask()`\n\n### Version 2.4.0\n- *Sequencer* - Added `Seqencer.EffectManager` to the autocomplete types\n- *Sequencer* - Fixed minor issue with the Effect Manager sometimes trying to load non-existent effect data\n- *Effects* - Added `.shape()`, which allows you to create simple shapes on the canvas\n  - See: <https://fantasycomputer.works/FoundryVTT-Sequencer/#/api/effect?id=shape>\n- *Effects* - Fixed `.attachTo()` would attempt to apply flags on temporary templates such as warpgate crosshairs\n\n### Version 2.3.21\n- *Sequencer* - Fixed interaction with the `Advanced Macros` module past version 1.19.2 (Thanks MrVaux!)\n- *Sequencer* - Fixed issue with `Sequencer.Helpers.shuffle_array` not handling complex arrays very well\n- *Effects* - Fixed `.from()` not taking token scale into account\n\n### Version 2.3.20\n- *Sounds* - Fixed sounds not working\n\n### Version 2.3.19\n- *Sequencer* - Created a new wiki for Sequencer:\n  - <https://fantasycomputer.works/FoundryVTT-Sequencer/> \n- *Sequencer* - Added full support for Sequencer typings in the ***Monaco Macro Editor*** (thanks to laquasicinque for your initial work!)\n- *Sequencer* - Changed all settings to use Foundry v10\'s `requiresReload` instead of reloading the app\n- *Sequencer* - Removed compendium of sample macros, in favor of the new wiki\n- *Effects* - Added further support for webm\'s loaded through S3 buckets (files with type `application/octet-stream` now supported)\n- *Sounds* - Sounds\' `.file()` now has a secondary parameter to allow a false-y primary input to soft fail, instead of halting the entire Sequence\n\n### Version 2.3.18\n- *Sequencer* - Added support for Foundry\'s photosensitive setting, which disables all effects without impacting other functionality \n- *Sequencer* - Fixed incompatibility with the Foundry team\'s ***A House Divided*** adventure, the scenes should no longer appear to have a dark overlay\n- *Sequencer* - Updated `.macro()` to work with the latest version of the ***Advanced Macros*** module\n\n### Version 2.3.17\n- *Effects* - Fixed `.tieToDocuments()` not working for embedded documents on unlinked tokens\n\n### Version 2.3.16\n- *Effects* - Adjusted approach when ending effects when using `.tieToDocuments()`\n\n### Version 2.3.15\n- *Sequencer* - Fixed registering similar named modules in the database would cause the second to not register properly \n\n### Version 2.3.14\n- *Effects* - ACTUALLY fixed `.tieToDocuments()` (send help)\n\n### Version 2.3.13\n- *Effects* - Fixed deeper issue with `.tieToDocuments()` as it was not recognizing actors or items as parents in respect to UUIDs\n\n### Version 2.3.12\n- *Effects* - Fixed `.missed()` and `.stretchTo()`\'s `randomOffset` having weird interactions when `.name()` was used to play effects at target locations\n- *Effects* - Fixed `.tieToDocuments()` throwing errors and not removing effects when the tied documents were deleted \n\n### Version 2.3.11\n- *Sequencer* - Added `Sequencer.Database.inverseFlattenedEntries` which is a map object with the key being the file path and the value being the database path for that file\n- *Effects* - Added `bindElevation` (default `true`) as a secondary argument to `.attachTo()` which can be used to make effects not follow the target\'s elevation\n- *Effects* - Made `.elevation()` be relative to the target of the effect by default, you can pass a secondary object with `absolute: true` to make it absolutely elevated on the scene\n- *Effects* - Improved internal logic when trying to play effects on clients who have disabled them - previously it had a chance to throw an error when clients with effects disabled would run sequences that included effects (as they would not know the duration of the effect)\n\n### Version 2.3.10\n- *Effects* - Fixed race condition when deleting multiple attached effects in a row would leave some lingering effects\n\n### Version 2.3.9\n- *Effects* - Fixed effects sticking around after deleting the document they were attached to\n- *Effects* - Fixed error when deleting documents relating to named effects\n- *Effects* - Fixed setting the position of screenspace effects would not work\n\n### Version 2.3.8\n- *Sequencer* - Removed double declaration of socketlib hook\n- *Effects* - Fixed error if no scenes has been created yet\n- *Effects* - Fixed screenspace effects not working without setting its location\n- *Effects* - Fixed `.animateProperty()` not working very well with `width` and `height`\n- *Effects* - Improved the way effects interacts with Foundry hooks (it is more efficient)\n\n### Version 2.3.7\n- *Effects* - Fixed `.rotateTowards()`\'s `rotationOffset` parameter not working properly\n\n### Version 2.3.6\n- *Effects* - Fixed multiple `.attachTo()` and `.strechTo()` with `attach: true` sometimes causing crashes due to overloading Foundry\'s tick function\n- *Effects* - Fixed ColorMatrix `hue` and `.animateProperty()` not working well together\n- *Effects* - Fixed `.atLocation()` with `randomOffset: true` would cause weird effects with `.stretchTo()`\n\n### Version 2.3.5\n- *Sequencer* - Removed Ouija Board example macros, this is now a separate module made by md-mention2reply, check it out!\n    - <https://foundryvtt.com/packages/ouija-board-for-sequencer>\n- *Sequencer* - Updated module manifest to be more V10 compatible\n- *Effects* - Fixed ColorMatrix `hue` property to be able to be animated with `animateProperty()` and `.loopProperty()`\n- *Effects* - Fixed `.mask()` not working with tiles or measurable templates\n- *Effects* - Fixed mirror and random mirror X/Y not actually flipping the effect\n\n### Version 2.3.4\n- *Animations* - Fixed `.rotateTowards()` throwing update error\n\n### Version 2.3.3\n- *Effects* - Fixed code to remove deprecation warning when both `.mask()` and `.persist()` was used\n- *Effects* - Fixed rare issue where the temporary template layer would not be initialized and would cause Sequencer to error and stop working \n- *Effects* - Fixed Sequencer Effects Player deprecation warnings\n- *Effects* - Added warning to console for players trying to play effects when they do not have permission to do so\n  - I realize warnings is not desired in most cases, but this has been an ongoing point of support, so to preserve my own sanity, this is just how it is now.\n\n### Version 2.3.2 (Both Foundry V9 and V10)\n- *Animation* - Fixed `.rotateTowards()` throwing errors\n- *Effects* - Fixed `.loopProperty()` with property `scale` would incorrectly scale effect\n\n### Version 2.3.1\n- *Effects* - Fixed `.from()` and `.mask()` throwing errors about missing files\n\n### Version 2.3.0 (V10 only)\n- *Sequencer* - Added Spanish localization (thanks to Git-GoR!)\n- *Effects* - Re-enabled `scale.x` and `scale.y` on `.animateProperty()` and `.loopProperty()` \n- *Effects* - Deprecated `.belowTokens()` and `.belowTiles()` in favor of `.elevation()` due to fundamental changes in Foundry\'s V10 update. These methods will be removed in a future update\n- *Effects* - Fully removed deprecated methods: `.addPostOverride()`, `.reachTowards()`, `.gridSize()`, `.startPoint()`, `.endPoint()`\n\n### Version 2.2.4\n- *Effects* - Fixed `.rotateTowards()`\'s `rotationOffset` parameter not working properly\n- \n### Version 2.2.3\n- Re-released 2.2.2, git pulled a fast one and the changes in that version never got out\n\n### Version 2.2.2\n- *Animation* - Fixed `.rotateTowards()` throwing errors (again)\n- *Effects* - Fixed `.atLocation()` with `randomOffset: true` would cause weird effects with `.stretchTo()`\n- *Effects* - Fixed error that would sometimes pop up during startup if the template layer has not been initialized\n\n### Version 2.2.1\n- *Animation* - Fixed `.rotateTowards()` throwing errors\n- *Effects* - Fixed `.loopProperty()` with property `scale` would incorrectly scale effect\n- \n### Version 2.2.0 (last V9 update, except maybe some bug fixes)\n**Additions:**\n- *Effects* - Added `.tieToDocuments()` which allows you to tie an effect to Foundry documents - such as Active Effects or Tokens. When these are deleted, the effect is automatically ended.\n- *Effects* - Added secondary `offset` parameter to `.atLocation()`, `.attachTo()`, `.rotateTowards()`, `.from()`, and `.stretchTo()` which can be used to offset the location of the source or target\n  - Note: This means that `.offset()` is becoming deprecated - it will remain for a few versions with a silent warning\n- *Effects* - Added `.spriteScale()` which can be used to scale the sprite of the effect separately from `.scale()`\n\n**Tweaks:**\n- *Animations* - Renamed `.rotateTowards()`\'s secondary parameter\'s `offset` property to be more accurately named `rotationOffset`\n- *Effects* - Upgraded `.animateProperty()` and `.loopProperty()` to be additive, which means two animations can now target the same property on the same effect\n- *Effects* - Renamed `.rotateTowards()`\'s secondary parameter\'s `offset` property to be more accurately named `rotationOffset`\n\n**Fixes:**\n- *Sequencer* - Fixed issue where copying the file path of a Database entry that has multiple ranges would always copy the file path for the middle-most range\n- *Sequencer* - Rewrote the database traversal method to be more robust and carry metadata down to lower children\n- *Effects* - Fixed long-running issue with lag and performance impact from Sequencer on some computers - the cause was the `.screenSpace()` layers, which have now been reworked. A setting to disable the Above UI Screenspace effects layer has been added to further support impacted individuals.\n- *Effects* - Fixed `randomOffset` secondary option on `.attachTo()` not working\n- *Effects* - Fixed `.scaleToObject()` and `.scale()` not playing nicely together\n- *Effects* - Fixed loop markers not properly working\n\n### Version 2.1.14\n- *Sequencer* - Removed PIXI fix for Foundry .webm tiles to apply premultiplied alpha, native Foundry behavior is now active\n- *Effects* - Fixed fatal canvas errors when `persistTokenPrototype` was active and masked to the target of the effect\n- *Effects* - Added warning when using `persistTokenPrototype` with masks _other_ than masks applied to the source target\n- *Effects* - Added support for `.file()` to override `.from()`\'s file while keeping the other settings intact\n- *Effects* - Improved robustness of placeable object document retrieval\n\n### Version 2.1.13\n- *Sequencer* - Removed stray `console.log`\n- *Sequencer* - Fixed Ouija board macro error, slightly improved effect positioning\n- *Effects* - Added `.spriteRotation()` which allows you set the rotation of the effect in place - this differs from `.rotate()` in the sense that this is applied only locally to the sprite, after any other offsets or transformations\n\n### Version 2.1.12\n- *Effects* - Fixed `.strechTo()` with parameter `attachTo: true` resulting in no stretching\n\n### Version 2.1.11\n- *Effects* - Fixed effects attached to temporary templates causing errors in core Foundry code\n- *Effects* - Effects attached to temporary objects (like warpgate cursors) are now propagated to other clients (call `.locally()` to make it only appear for the creator)\n\n### Version 2.1.10\n- *Sequencer* - Fixed Sequencer Effect Manager not accepting Foundry documents as object references when filtering for effects\n- *Sequencer* - Fixed Sequencer Effect Player showing private database entries\n- *Effects* - Fixed attached effects not showing up for non-GMs\n\n### Version 2.1.9 \n- *Sequencer* - Unlocked keybinds so that users may configure their own keybinds for Sequencer\'s layers\n- *Sequencer* - Added support for `minDelay` and `maxDelay` on `.waitUntilFinished()`, so you can now have a random wait delay between sections\n- *Effects* - Added `fromEnd` to `.animateProperty()` which causes the animation to play at the end of the effect\'s duration\n- *Effects* - Added `gridUnits` support to `.animateProperty()` and `.loopProperty()` when using `position.x` or `position.y` as the animated target\n- *Effects* - Added `gridUnits` as a secondary option to both `.offset()` and `.spriteOffset()`\n- *Effects* - Fixed persistent prototype token effects not applying on every instance of its token\n- *Effects* - Fixed `.playbackRate()` only adjusting effect duration, and not the actual playback rate\n\n### Version 2.1.8\n- *Sequencer* - Added setting to allow clients to disable Sequencer\'s PIXI alpha fix for base textures\n\n### Version 2.1.7\n- *Effects* - Fixed string normalization\n\n### Version 2.1.6\n- *Sequencer* - Updated compendium of Sequencer macro samples\n- *Effects* - Fixed finding effects by name with accents in them silently failing\n- *Effects* - Fixed `.attachTo()` not following the target\'s rotation\n\n### Version 2.1.5\n- *Effects* - Fixed switching scenes would sometimes break effects\n\n### Version 2.1.4\n- *Effects* - Fixed issue with hovering over persistent effects attached to objects sometimes causing Foundry\'s layers to crash\n- *Effects* - Fixed `.animateProperty()` and `.loopProperty()` applying grid-size ratio on animated scales\n\n### Version 2.1.3\n- *Effects* - Fixed attached effects disappearing\n- *Effects* - Fixed `.randomOffset()` not randomly offsetting effects (still deprecated, see 2.1.0 release notes)\n\n### Version 2.1.2 Hotfix\n- *Effects* - Fixed effects sometimes not becoming visible\n\n### Version 2.1.1\n- *Sequencer* - Fixed canvas layer bug that caused performance issues for some users\n- *Sequencer* - Fixed missing default template causing some effects to not play properly\n- Added `.aboveLighting()`, which causes the effect to always be visible, regardless of sight, fog of war, or walls.\n  - Note that if an effect is attached to an object via `.attachTo()`, you may need to disable `bindVisibilty` if the object is hidden\n- *Effects* - Fixed `.from()`, it now uses the object\'s image when the effect plays, rather than when the Sequence was first created\n- *Effects* - Fixed highlight box when hovering over effects in the Effect Manager UI not taking effect rotation into account\n- *Effects* - Fixed effects sometimes not fully following its attached object \n\n### Version 2.1.0\n**Additions:**\n- *Sequencer* - Added support for the Effect Manager to be able to manipulate effects on other scenes, which means you can now end effects on other scenes than the one you\'re on via the API\n- *Sequencer* - Added secondary options parameter to `Sequencer.Database.getEntry`, where `softFail: true` will cause the method to not throw errors when an entry was not found. \n- *Sequencer* - Added `Sequencer.EffectManager.getEffectPositionByName` which will allow you retrieve an effect\'s position by name, in real time\n- *Effects* - Added `.mask()`, which can now clip-mask effects to only show them within tokens, templates, tiles, or drawings - this supports the [Walled Templates module](https://foundryvtt.com/packages/walledtemplates)!\n- *Effects* - Added a secondary options parameter to `.persist()`, which can accept `persistTokenPrototype: true` to persist the effect on the token\'s prototype data, useful for active effect-based VFX\n- *Effects* - Added vision masking - now token vision affects how much of an effect they can see\n- *Effects* - Added `.xray()` which can be used to turn off vision masking on individual effects \n- *Effects* - Added support in the Sequencer Database for internal effect loops, see the [documentation for more information](https://github.com/fantasycalendar/FoundryVTT-Sequencer/wiki/How-to:-Sequencer-Database#Internal-loops)\n- *Effects* - Added `edge` option to `.attachTo()`, which can be set to `inner`, `on`, or `outer` to align the effect on the attached object\'s edge when used with `align` \n- *Effects* - Added `.screenSpaceAboveUI()`, which causes `.screenSpace()` effects to play above _all_ UI elements in Foundry (use with caution)\n- *Effects* - Added options parameter to `.scaleToObject()`, which can be passed `uniform: true` to cause the scaling to always be uniform (it picks the largest dimension of the object)\n- *Macros* - Added the ability to reference compendiums when creating `.macro()`s in sequences\n\n**Fixes:**\n- *Sequencer* - As `SequencerDatabase` was deprecated in 2.0.0 in favor of `Sequencer.Database`, the former has now been removed\n- *Sequencer* - Adjusted Database methods with more validation so that searching with empty strings won\'t throw hard to read errors\n- *Sequencer* - Removed bogus Effect Player warning about permissions that no longer reflects what Sequencer does\n- *Sequencer* - Fixed some issues when copying and playing effects through the Database Viewer\n- *Effects* - Fixed effects being invisible to players if the effect was created out of sight (thanks @dev7355608!)\n- *Effects* - Fixed `align` on `.attachTo()` not working as expected when an effect\'s scale or size was set\n- *Effects* - Fixed blur filter not taking given properties into account\n- *Effects* - The following functions now have loud deprecation warnings:\n    - `.addPostOverride()`\n    - `.reachTowards()`\n    - `.gridSize()`\n    - `.startPoint()`\n    - `.endPoint()`\n- *Effects* - Deprecated `.randomOffset()` in favor of adding `randomOffset` as a secondary argument on `.atLocation()`, `.stretchTo()`, `.rotateTowards()`, and `.attachTo()`\n\n### Version 2.0.16\n- *Sequencer* - Added japanese localization (thanks to the illustrious Brother Sharp#6921!)\n\n### Version 2.0.15\n- *Effects* - Fixed errors relating to tiling textures\n- *Effects* - Fixed drifting effect animations when FPS dropped\n- *Effects* - Improved performance relating to always updating the position of the sprites, even when attached objects weren\'t moving\n- *Effects* - Hopefully fixed some memory leaks relating to assets not being deleted properly \n- *Effects* - Fixed attached effects\' rotations being funky\n- *Effects* - Fixed effects attached to temporary objects (such as the warpgate crosshair) would not be properly removed from the effect manager \n\n### Version 2.0.14\n- *Database* - Fixed the database sometimes getting confused by paths that have `ft` in them without being range-finding\n- *Effects* - Fixed double-attached effects sometimes resulting in the sprite freezing (or in rare cases, browser crashes), though this type of effect is still expensive!\n- *Effects* - Fixed `rotateTowards` with `attachTo` enabled not respecting actual target position end position\n- *Effects* - Fixed `.rotation()` with `.loopProperty()` on the `spriteContainer`\'s `rotation` causing rotational strangeness\n- *Effects* - Fixed `.repeat()` with partial database path not picking random images for each repetition\n- *Sounds* - Fixed error when playing sounds\n\n### Version 2.0.13\n- *Effects* - Fixed flipped tiles and measurable templates (with negative width or height) causing effects to not play on the correct location\n- *Effects* - Fixed `.rotateTowards()` not following the rotation of attached objects\n- *Sounds* - Fixed `.fadeInAudio()` and `.fadeOutAudio()` being broken\n\n### Version 2.0.12\n- *Effects* - Fixed effects with only `.text()` and no `.file()` not working properly\n- *Effects* - Fixed `.text()` combined with `.screenSpace()` would not be scaled properly \n\n### Version 2.0.11\n- *Effects* - Fixed `.extraEndDuration()` not working properly when `.waitUntilFinished()` was provided a negative number\n- *Effects* - Fixed `.noLoop()` effects sometimes not reaching their proper end time when `.endTime()`, `.endTimePerc()` or `.timeRange()` was used\n\n### Version 2.0.10\n- *Sequencer* - Fixed error in `Database.validateEntries()`\n- *Sequencer* - Updated `pre` hooks to cancel the action if any function return `false`\n- *Sequencer* - Updated Rope and Chain macros in compendium\n- *Effects* - Added `.tilingTexture()` - this will replace the `tiling` parameter on `.stretchTo()` in the long term\n- *Effects* - Deleting the object an effect is attached to will now actually trigger the effect\'s `.fadeOut()`, `.scaleOut()` etc\n\n### Version 2.0.9 Hotfix\n- *Effects* - Fixed nasty issue with rotation on effects\n- *Effects* - Made all effects have an assumed internal grid size of 100\n\n### Version 2.0.8\n- *Sequencer* - Added rope and chain sample macros to the Sequencer macro compendium\n- *Sequencer* - Removed non-functional Chain Lightning macro from macro collection\n- *Sequencer* - Added warnings to Preloader when it is given invalid parameters\n- *Effects* - Added `tiling` as an option to `.stretchTo()`\n- *Effects* - Fixed `.randomRotation()` not working with `.attachTo()`\n- *Effects* - Fixed issue with effects with a defined width and height were still being scaled by the scene-effect grid size difference\n\n### Version 2.0.7\n- *Sequencer* - Fixed broken macro in the Ouija example\n- *Animations* - Fixed various animation methods not resulting accurate movement or teleportation\n- *Effects* - Fixed `.randomRotation()` not working\n\n### Version 2.0.6\n- *Sequencer* - Improved intelligence of webm cache \n- *Effects* - Added `.private()` method to hide effects in the effect manager - DO NOT USE IF YOU DO NOT KNOW WHAT YOU ARE DOING\n- *Effects* - Fixed issue where ending effects by name would cause other effects without a name to get _ended\n- *Effects* - Fixed issue where filtering for effects with the Effect Manager would cause it to split the given name on each whitespace \n\n### Version 2.0.5 Hotfix Hotfix\n- *Effects* - Hotfix for the hotfix. It\'s just hotfixes all the way down, man.\n\n### Version 2.0.4 Hotfix\n- *Effects* - Fixed some modules causing Sequencer to complain \n- *Effects* - Actually fixed copying objects would not copy the effects on it\n\n### Version 2.0.3\n- *Effects* - Fixed some issues regarding file paths on ForgeVTT, though they did most of the legwork on their side\n- *Effects* - Fixed issue where `.scaleToObject()` would fail to scale to the object properly\n- *Effects* - Fixed Effect Manager not finding effects to end when it was only provided an object\n- *Effects* - Fixed copying tokens and other objects with ongoing effects would not properly play it for everyone\n- *Effects* - Fixed screenspace effects would sometimes not play properly \n\n### Version 2.0.2\n- *Sequencer* - Fixed the preloader throwing error about recursion\n- *Effects* - Fixed rotational animations not working properly\n- *Effects* - Fixed the update interface not allowing attribute paths like core Foundry does\n- *Effects* - Fixed effects lingering for other users after their attached objects were deleted\n- *Effects* - Fixed issues with using `.from()` on tiles\n\n### Version 2.0.1\n- *Sequencer* - Fixed preloader throwing error about missing functions\n- *Sequencer* - Fixed Effect Manager complaining if trying to filter effects by name while some effects didn\'t have a valid name\n- *Effects* - Added the ability to use Effects as elements to play other Effects on\n- *Effects* - Added cache-busting for when the Sequencer .webm cache would get larger than 1GB\n- *Effects* - Fixed persistent effects attached to WarpGate crosshairs throwing errors\n- *Effects* - Fixed `cacheLocation` throwing errors regarding missing function\n- *Effects* - Fixed `endedSequencerEffect` being called too late for users to be able to use its parameters\n- *Effects* - Fixed error when deleting the object an effect was attached to through both `.attachTo()` and `.stretchTo()` \n\n### Version 2.0.0\n**Breaking changes:**\n- *Sequencer* - Sequencer now requires the `socketlib` module\n- *Sequencer* - All existing persistent effects created using 1.X.X Sequencer will be updated to the 2.0.0 system, but it\'s nigh impossible to catch all the edge cases, so please report any strangeness!\n- *Effects* - Removed support for audio methods on effects (hardly used and caused a whole host of problems)\n- *Effects* - Deprecated `.reachTowards()` and renamed it to `.stretchTo()`. The deprecated method will be removed in 2.1.0.\n- *Effects* - Deprecated `.addPostOverride()`, please use `.addOverride()` instead. The deprecated method will be removed in 2.1.0.\n- *Effects* - Deprecated `.gridSize()`, `.startPoint()`, and `.endPoint()` in favor for `.template({ gridSize, startPoint, endPoint })`. The deprecated methods will be removed in 2.1.0.\n- *Effects* - Removed deprecated method `.JB2A()`\n\n**Tweaks:**\n- *Sounds & Effects* - Tweaked `.forUsers()` to also accept player names (case-sensitive) instead of just IDs\n- *Effects* - Tweaked attached effects\' layer handling - effects can now be attached but exist below _all_ Tokens, for example\n- *Effects* - Tweaked `.filter()` to allow being called multiple times, which now layers the filters in the order they were created\n\n**Additions:**\n- *Sequencer* - Added selection tool to the Effect Layer - select, move, reattach, and delete effects on the canvas!\n- *Sequencer* - Added `updateEffects` to the Effect Manager\'s API\n- *Sequencer* - Added `updateSequencerEffect` hook\n- *Sequencer* - Added support to `.macro()`s to be able to supply additional arguments (requires the Advanced Macros module)\n- *Sequencer* - Added wildcard support when filtering for named effects in the Effect Manager\'s API (such as `getEffects`, `endEffects`, etc)\n- *Sequencer* - Added support to filter for `source` and `target` in the Effect Manager\'s API (such as `getEffects`, `endEffects`, etc)\n- *Sequencer* - Added "private" boolean flag to `Sequencer.Database.registerEntries()` which causes the entries to not be visible in the Database Viewer and Effect Player\n- *Sequencer* - Added Setting to be able to hide Sequencer\'s tools on the toolbar\n- *Sequencer* - Added checkbox to Database Viewer to show all ranges of a single effect, which is by default set to false\n- *Sequencer* - Added `Sequencer.Helpers`, a library of useful methods - check them out on the wiki: https://github.com/fantasycalendar/FoundryVTT-Sequencer/wiki/Sequencer-Helper-Functions\n- *Animations* - Added `.hide()` and `.show()` to hide or show the animated object\n- *Effects* - Added more secondary options parameters to `.stretchTo()`, which accepts:\n    - At long last, this can now `attachTo` (boolean) to the given target. Combine with `.attachTo()` to link an effect between two tokens!\n    - `onlyX` (boolean), if set to true, this will cause stretchTo to only stretch the X axis of the sprite towards the target (keeping Y at 1.0, or your given scale)\n- *Effects* - Added support to `.file()` for an object map containing the feet range and filepath key-value pair. Check out the file wiki entry to understand what this means: https://github.com/fantasycalendar/FoundryVTT-Sequencer/wiki/Effects#file\n- *Effects* - Added secondary options parameter to `.attachTo()`, which accepts:\n    - `align` (string, default "center"), accepts `top-left`, `center`, `left`, `bottom-right`, etc. Read the wiki: https://github.com/fantasycalendar/FoundryVTT-Sequencer/wiki/Effects#attach-to\n    - `bindVisibility` (boolean, default true), if set to false, the effect will not be hidden if the attached object is hidden\n    - `bindAlpha` (boolean, default true), if set to false, the effect\'s alpha will be independent of the attached object\n    - `followRotation` (boolean, default true), if set to false, the effect will not follow the rotation of the attached object\n- *Effects* - Added options to `.size()` which allows for `{ gridUnits: true }` - this makes the size given to the method scale to the scene\'s grid, instead of setting the exact width and height\n- *Effects* - Added the same option as above to `.animateProperty()` and `.loopProperty()`, which only works if you animate the `width` or `height`\n\n**Fixes:**\n- *Sequencer* - Fixed module permissions settings being slightly wonky\n- *Sequencer* - Fixed number inputs not throwing errors on `NaN` values\n- *Animations* - Fixed users not being able to teleport or move tokens they do not own\n- *Animations* - Fixed `.moveSpeed()` not affecting the duration of the animation\n- *Animations* - Fixed `.delay()` not being respected\n- *Effects* - Fixed memory leak where effect textures were not properly destroyed\n- *Effects* - Adjusted `.origin()` to be able to accept a `Document` object to infer the UUID from\n- *Effects* - Fixed `.from()` not taking mirror x/y into account on tokens\n- *Effects* - Tokens with effects attached to them can now be _ended by anyone who can update the token (owners of the token, GMs, etc)\n- *Effects* - Increased default resolution of `.text()` to 10 (should increase quality)\n- *Effects* - Fixed `.screenSpace()` effects still being affected by grid size normalization\n\n### Version 1.3.5\n- *Sequencer* - Fixed Permissions being broken in the latest Foundry update, and moved Sequencer specific permissions into module settings instead\n- *Effects* - <img src="images/siren.gif" width="18px" height="18px" alt="Siren"> Breaking change <img src="images/siren.gif" width="18px" height="18px" alt="Siren"> - Fixed issue where setting the `.size()` of an effect and then scaling it would result in unexpected behavior. As a result, `.scaleIn()` and `.scaleOut()` now function as _multipliers_ to the existing scale on the effect\n\n### Version 1.3.4\n- *Sequencer* - Added popup warning the first time a GM opens the Effect Player to tell them about the custom Permissions\n- *Sequencer* - Added button to open Foundry\'s Permissions directly from the Effect Player how-to page\n\n### Version 1.3.3\n- *Sounds* - Fixed `.fadeInAudio()` and `.fadeOutAudio()` being broken\n\n### Version 1.3.2\n- *Sequencer* - Minor backend updates to flag handling\n- *Effects* - Fixed static images failing to load in v9\n- *Effects* - Fixed effects attached to tokens that were copied to another scene would not play\n- *Effects* - Suppressed recent deprecation warnings until the next release\n- *Effects* - Reverted some code that would break persisting effects\n\n### Version 1.3.1\n- *Sequencer* - Fixed minor spelling issue\n\n### Version 1.3.0\n- *Sequencer* - Sequencer is now v9 ready!\n- *Sequencer* - Improved search accuracy functionality on the Effect Player\n- *Animations* - Added `relativeToCenter` option to `.teleportTo()` and `.moveTowards()`, which will offset the location relative to the object\'s center, effectively centering the animated object on the location - use with `.snapToGrid()` for reliable snapping!\n- *Animations* - Fixed `.fadeOut()`, `.fadeOutAudio()`, and `.rotateOut()` not correctly setting the duration of the animation, causing `.waitUntilFinished()` to not actually wait for the animation to finish\n- *Effects* - Deprecated `.JB2A()` as the recommended workflow is now to use Database paths\n\n### Version 1.2.12 Hotfix\n- *Effects* - Fixed `.animateProperty()` and `.loopProperty()` applying animations that were already complete\n\n### Version 1.2.11\n- *Sequencer* - Added French localization (thanks to Elfenduil)\n- *Sequencer* - Fixed error with `Sequencer.Database.validateEntries()` throwing an error\n- *Effects* - Improved handling of the `Glow` filter when used with `.fadeIn()` and `.fadeOut()`\n    - Due to this change, it is now recommended that if you use `.animateProperty()` or `.loopProperty()` on the `sprite`\'s `alpha` property to instead use it on the `alphaFilter`\'s `alpha` property\n- *Macros* - Updated the Misty Step macro to be more generic and not specifically _require_ MidiQOL\n\n### Version 1.2.10\n- *Sequencer* - Fixed misspelled permission which caused players to not be able to see the toolbar buttons\n- *Sequencer* - Added `End All Effects` button to the Effect Manager\n\n### Version 1.2.9\n- *Sequencer* - Removed error from the Effect Manager when no effects were removed\n- *Effects* - Fixed `.randomOffset()` on tiles would result in pretty crazy behavior\n\n### Version 1.2.8\n- *Sequencer* - Added sidebar tool permissions, you can now hide them from players\n- *Effects* - Added `.origin()` which provides a way to tag an effect with a string you can then search for with the Effect Manager\n- *Effects* - Added support for using both `.reachTowards()` and `.scale()` and will now scale the effect whilst keeping the range finding correct\n\n### Version 1.2.7 Hotfix\n- *Sounds* - ACTUALLY Fixed sounds being broken\n\n### Version 1.2.6 Hotfix\n- *Sounds* - Fixed sounds being broken\n\n### Version 1.2.5\n- *Sequencer* - Made hooks `createSequencerEffect` and `endedSequencerEffect` instead supply the CanvasEffect itself, rather than its data\n- *Effects* - Fixed bug that caused effects to linger for other clients after having been _ended\n\n### Version 1.2.4\n- *Sequencer* - Fixed error caused by preload option on Sequencer Effect Player\n- *Sequencer* - Fixed error when pressing ESC in the Sequencer Layer\n- *Sequencer* - Fixed permissions not being loaded properly\n- *Effects & Animations* - Added `.tint()` which allows you to tint effects, tokens, and tiles\n\n### Version 1.2.3\n- *Sequencer* - Added granular permissions - check it out in Configure Settings -> Open Permissions Configuration\n- *Sequencer* - Added localization support\n- *Sequencer* - Fixed the Sequencer Player throwing an error if the layer was active while switching scene\n\n### Version 1.2.2\n- *Sequencer* - Added Stretch or Move checkbox to Sequencer Player\n- *Sequencer* - Added Move Speed input to Sequencer Player\n\n### Version 1.2.1\n- *Sequencer* - Fixed file picker being broken\n\n### Version 1.2.0\n- *Sequencer* - Added the [Sequencer Effect Player](https://github.com/fantasycalendar/FoundryVTT-Sequencer/wiki/Sequencer-Effect-Player)!\n- *Sequencer* - Refactored the Sequencer Animation Engine, which fixes some animation funkiness\n- *Sequencer* - Fixed the Sequencer Preloader sometimes not succeeding and getting stuck\n- *Animations* - Removed the `.snapToSquare()` method, use `.snapToGrid()` instead\n- *Effects* - Added `.spriteOffset()` which allows you to add an offset to the effect\'s sprite\'s location\n- *Effects* - Added optional boolean parameters to `.randomizeMirrorX()` and `.randomizeMirrorY()`\n\n### Version 1.1.5\n- *Effects* - Fixed bug that caused all effects to stay transparent after playing an effect for specific user\n- *Effects* - Fixed bug with `.missed()` and `.reachTowards()` failing to play any effect\n- *Effects* - Fixed all effects sharing users\n\n### Version 1.1.4\n- *Sequencer* - Fixed error in the Sequencer Preloader when pre-loading files from Database paths\n- *Effects* - Fixed bug that caused persistent effects to end when a client connected after it was created\n\n### Version 1.1.3\n- *Sequencer* - Added `sequencerEffectManagerReady` hook which is called when every effect has been set up on the scene that\'s currently loaded\n- *Sequencer* - Added `validateEntries` method to the Sequencer Database, which is helpful for module creators to validate their asset collections to the database\n- *Sequencer* - Added `getPathsUnder` method to the Sequencer Database, which retrieves valid collections under a certain database path\n- *Sequencer* - Minor speed improvements to how the database retrieves files\n- *Sequencer* - Removed the requirement for a user to be trusted to use the Database Viewer\n- *Sequencer* - Fixed `Sequencer.EffectManager.endEffects` not throwing error when incorrect or incomplete parameters were given, and instead _ended all effects (whoops)\n- *Effects* - Made user-created effects that were made to be displayed only for other users also show up for GMs, though saturated and with 50% opacity. This is to ensure no player-to-player abuse would occur\n- *Effects* - Fixed scaled tokens causing effects to not play on the correct location\n- *Effects* - Fixed temporary effects attached to warpgate cursors no longer stays around for longer than they should\n\n### Version 1.1.2\n- *Sequencer* - Removed compatibility warning regarding Perfect Vision as the module was updated to support Sequencer\n- *Sequencer* - Added warning when trying to register database collections under a module name containing dots (as it uses dot-notated paths)\n\n### Version 1.1.1\n- *Sequencer* - Removed Hyperspace sample from compendiums, as it was getting too big\n- *Sequencer* - Added compatibility warning if user has Perfect Vision installed\n- *Sequencer* - Added warning for Hyperspace assets that are going to be removed in a future update, and instead put into a separate module:\n    - https://foundryvtt.com/packages/nrsap by Nachtrose#9287 on Discord\n- *Sequencer* - Prepared Sequencer for v9, it _should_ be compatible to test\n- *Effects* - Added `.text()` which allows you to create text snippets on the canvas\n- *Effects* - Added `.from()` which creates an effect based on the given object, effectively copying the object as an effect\n- *Effects* - Added support for `.attachTo()` for temporary measured templates before they have been created, for use with WarpGate\n- *Effects* - Removed warning when `.attachTo()` and `.atLocation()` are used on the same effect - `.attachTo()` always wins out\n\n### Version 1.1.0\n- *Sequencer* - Added hooks:\n    - `createSequencerSequence`\n    - `endedSequencerSequence`\n    - Effects:\n        - `preCreateSequencerEffect` - Provides the effect\'s data\n        - `createSequencerEffect` - Provides the effect\'s data\n        - `endedSequencerEffect` - Provides the effect\'s data\n    - Sounds:\n        - `preCreateSequencerSound` - Provides the sound\'s data\n        - `createSequencerSound` - Provides the sound\'s data\n        - `endedSequencerSound` - Provides the sound\'s data\n- *Sequencer* - Hook for `sequencer.ready` is becoming deprecated in favor for `sequencerReady`\n- *Sequencer* - Vastly improved the speed of the Database Viewer (thanks to Naito#1235 on discord!)\n- *Effects* - Added screen space layer for UI effects!\n    - Added `.screenSpace()` which causes the effect to be played on the screen rather than in the game canvas\n    - Added `.screenSpaceAnchor()` which causes the effect to anchor itself to a side on the screen space layer\n    - Added `.screenSpacePosition()`, pretty straightforward what this does, sets the position of the effect in screen space\n    - Added `.screenSpaceScale()` which can help you stretch and fit the effect to the screen, even on different screen sizes\n- *Effects* - Added `.spriteAnchor()` which controls the effect\'s core anchor point within its container (defaults to 0.5 on X and Y)\n- *Effects* - Added support on `.atLocation()` for a secondary options object, which currently accepts:\n    - `cacheLocation: boolean` - causes the given object\'s location to be cached immediately rather than retrieved during the Sequence\'s runtime\n- *Effects* - Added `.snapToGrid()` which snaps the effect to the given location\'s closest grid section\n- *Effects* - Added `.scaleToObject()` which scales the effect to the bounds of the object, with an optional scalar on top of that\n- *Effects* - Added `.zeroSpriteRotation()` which causes an effect\'s sprite to remain un-rotated when its container rotates in animations\n- *Effects* - Tweaked `.size()` to also accept only one of height or width, the other will be automatically resized to keep the effect\'s ratio\n- *Effects* - Fixed `.persist()`ing effects with an end duration that doesn\'t loop would not properly stop at its end duration\n- *Effects* - Improved look of transparent .webm files\n- *Animations* - Renamed `.snapToSquare()` method to `.snapToGrid()` - the old method will be fully removed in 1.2.0\n- *Foundry* - Added libwrapper patch for .webm transparency not playing correctly in Foundry\n- *Sequencer* - Updated some sample macros\n- *Sequencer* - The `SequencerDatabase` accessor has been removed, and is now accessible with `Sequencer.Database`\n- *Sequencer* - The `SequencerDatabaseViewer` accessor has been removed, and is now accessible with `Sequencer.DatabaseViewer`\n- *Sequencer* - The `SequencerPreloader` accessor is deprecated, and is now accessible with `Sequencer.Preloader`\n\n### Version 1.0.3\n- *Sequencer* - Added animated space backgrounds (thanks to Keirsti on the Foundry VTT discord server)\n- *Sequencer* - Fixed Hyperspace macro placing the hyperspace intro and out incorrectly\n\n### Version 1.0.2 Hotfix\n- *Sequencer* - Changed Effect Viewer icon to something less controversial\n\n### Version 1.0.1\n- *Sequencer* - Renamed `.sequence()` method on Sequences to `.addSequence()` due to internal code conflicts\n- *Effects* - Added `.filter()` - was technically added in 1.0.0, but was left undocumented\n- *Effects* - Fixed `.size()` being scaled to account for grid size differences - it should now set the exact width/height in pixels\n\n### Version 1.0.0\n- *Sequencer* - Added recent Sequencer tools to the menu in the top left - you can disable these in the module settings\n- *Sequencer* - Added `Sequencer.EffectManager` to manage persistent effects - [read more here](https://github.com/fantasycalendar/FoundryVTT-Sequencer/wiki/Sequencer-Effect-Manager)\n- *Sequencer* - Added the ability for you to implement your own Sequencer functions - [read more here](https://github.com/fantasycalendar/FoundryVTT-Sequencer/wiki/Sequencer-Section-Manager)\n- *Sequencer* - `SequencerDatabase` is deprecated, and is now accessible with `Sequencer.Database` - 1.1.0 will remove the old path entirely\n- *Sequencer* - `SequencerDatabaseViewer` is deprecated, and is now accessible with `Sequencer.DatabaseViewer` - 1.1.0 will remove the old path entirely\n- *Sequencer* - `SequencerPreloader` is deprecated, and is now accessible with `Sequencer.Preloader` - 1.1.0 will remove the old path entirely\n- *Sequencer* - Fixed Documents sometimes not properly resolving to their PlaceableObject\n- *Sequencer* - Fixed settings not being client side - whoops\n- *Effects* - Added `.attachTo()` which causes the effect to be attached to a given object\n- *Effects* - Added `.persist()` which causes the effect to become permanent on the canvas until removed\n- *Effects* - Added `.extraEndDuration()` which allows `.persist()`-ed effects to stick around for a bit longer instead of end immediately\n- *Effects* - Tweaked `.missed()` to hit an area only facing the origin of the effect, if it had an origin and target\n- *Effects & Sounds* - Added support for wildcard paths, like `modules/jb2a_patreon/Library/1st_Level/Bardic_Inspiration/BardicInspiration_01_*_400x400.webm`\n\n### Version 0.6.12\n- *Sequencer* - Fixed an issue where the preloader would sometimes fail to preload\n- *Effects* - Fixed effects not playing on hex grids\n\n### Version 0.6.11 Hotfix\n- *Effects* - Fixed range-finding effects sometimes not picking the right distance\n\n### Version 0.6.10\n- *Sequencer* - Improved the search speed of the Database Viewer\n- *Sequencer* - Fixed previewing static images through the Database Viewer\n- *Sequencer* - Fixed bugs that caused the Database to sometimes fail registering new files\n- *Effects* - Fixed issue where `.delay()` would incorrectly contribute towards the effect\'s `.waitUntilFinished()` duration\n\n### Version 0.6.9 Hotfix\n- *Sequencer* - Fixed the database sometimes failing to get the correct file\n\n### Version 0.6.8 Hotfix\n- *Sequencer* - Fixed database not correctly finding range-based effects\n\n### Version 0.6.7\n- *Effects & Sounds* - Fixed `.locally()` and `.forUsers()` sometimes erroneously remembering users between different effects & sounds\n- *Effects* - Fixed `.scaleIn()` would not take a custom `.size()` into account\n- *Effects* - Fixed static images sometimes not playing due to duration being set to 0ms\n\n### Version 0.6.6\n- *Sequencer* - Added support for preloading files based on database paths\n- *Effects & Sounds* - Added `.locally()` and `.forUsers()`, which allow you to control which users will have the effect and sounds played for them\n- *Effects* - Improved positional handling of Tiles and TileDocuments\n\n### Version 0.6.5\n- *Sequencer* - Updated Sequencer Database Viewer layout to be more user friendly\n- *Effects* - Fixed bug with templates and raw positions not being respected\n\n### Version 0.6.4\n- *Sequencer* - Added Sequencer Database Viewer to the module settings, it allows you to preview effects and copy any files registered with Sequencer by other modules\n- *Sequencer* - Added client-side settings for users to be able to turn off effects and sounds being played locally\n- *Effects & Sounds* - Fixed effects and sounds playing on scenes they did not originate from\n- *Effects* - Added `.size()`, which sets the width and height of the effect in pixels\n- *Effects* - Added `rotate` option to `.moveTowards()`, which defaults to true. If set to false, the effect will not rotate towards the end location.\n- *Effects* - Fixed duration of effects using `.moveTowards()` not being calculated correctly\n- *Effects* - Fixed static image effects\' durations also not being calculated correctly\n\n### Version 0.6.3 Hotfix\n- *Effects* - Fixed effects failing to play static images\n\n### Version 0.6.2\n- *Sequencer* - Further small fixes to how the database registers files\n\n### Version 0.6.1\n- *Sequencer* - Removed the need for `.playIf()` to have to be given a function or a boolean\n- *Sequencer* - Fixed issues with the database when files were listed in arrays\n\n### Version 0.6.0\n**Breaking:**\n- *Effects* - <img src="images/siren.gif" width="12px" height="12px" alt="Siren"> `.JB2A()` has been altered to set the gridsize to 200, as it was previously set to 100 - this will halve the size all JB2A on-the-spot effects, sorry! <img src="images/siren.gif" width="12px" height="12px" alt="Siren">\n\n**Additions:**\n- *Sequencer* - Added `SequencerPreloader` - you can now preload images, effects, and sounds for your players, read more on the [docs](https://github.com/fantasycalendar/FoundryVTT-Sequencer/wiki/Sequencer-Preloader)\n- *Sequencer* - Added support for templates and time ranges in database structure, more info on the [docs](https://github.com/fantasycalendar/FoundryVTT-Sequencer/wiki/How-to:-Sequencer-Database)\n- *Effects* - Added support for static images such as webp, pngs, jpgs, etc\n- *Effects & Sounds* - Added `.startTime()`, `.startTimePerc()`, `.endTime()`, `.endTimePerc()`, and `.timeRange()`, more info on the [docs](https://github.com/fantasycalendar/FoundryVTT-Sequencer/wiki/Effects#start-time)\n- *Sounds* - Added `.addOverride()`, `.baseFolder()`, and `.setMustache()` support to sounds\n\n**Updates & Fixes:**\n- *Sequencer* - `.play()` now reliably resolves its promise at the end of the entire sequence\n- *Effects* - *Vastly* improved effect caching and loading speed of effects and sounds\n- *Effects* - Improved object position handling slightly when providing non-foundry class objects to `.atLocation()` and alike\n- *Effects* - Generally improved compatibility with `TokenDocument` and Foundry D&D 5E documents when getting their positions\n\n### Version 0.5.9\n- *Effects* - Added `.addPostOverride()` as an alternative to `.addOverride()`, which executes at the end of the effect data sanitation\n- *Effects* - Fixed `.gridSize()`, `.startPoint()`, and `.endPoint()` not being respected and being overridden by internal logic\n\n### Version 0.5.8 Hotfix\n- *Macros* - Fixed macros throwing error when playing sequence\n\n### Version 0.5.7\n- *Effects* - Fixed templates sometimes not being found\n- *Effects* - Re-added backwards compatibility with old macros that still use `data.distance` in overrides\n\n### Version 0.5.6\n- *Sequencer* - Added alpha version of the Sequencer Database Viewer\n- *Effects* - Added `.randomOffset()` which can add a random offset similar to `.missed()`, but *within* the bounds of the target token / tile / other. Check out the docs!\n- *Effects* - Fixed `.waitUntilFinished()` not being respected\n- *Effects* - Fixed `.offset()` throwing an error about a missing function\n- *Effects* - Fixed `.repeats()` throwing an error, because the entire function went missing in the last update >.>\n\n### Version 0.5.5 Hotfix\n- *Effects* - Fixed effects sometimes not playing\n\n### Version 0.5.4 Hotfix\n- *Effects* - Fixed melee attacks not picking the right JB2A template\n\n### Version 0.5.3\n- *Sequencer* - Added sound to the Hyperspeed Sample thanks to the wonderful AurelTristen over at [HellScape Tabletop Assets](https://www.patreon.com/HellScapeAssets) (even though they\'re not specifically focused on sound effects)\n- *Effects* - Fixed major issue with JB2A templates, causing effects to pick the wrong ranged attacks & other shenanigans\n- *Effects* - Fixed effect scale inconsistencies across scenes with different grid sizes\n\n### Version 0.5.2\n- *Sequencer* - <img src="images/siren.gif" width="12px" height="12px" alt="Siren"> Removed support for Foundry Version 0.7.x <img src="images/siren.gif" width="12px" height="12px" alt="Siren">\n- *Sequencer* - Refactored animations into a dedicated animation engine\n- *Effects* - Added support for static image effects (.jpeg, .png, etc)\n- *Effects* - Fixed grid size sometimes not being taken into account when playing effects with `.reachTowards()`\n- *Sounds* - Vastly improved and fixed sound implementation, big thanks to ghost#2000!\n- *Sounds* - <img src="images/siren.gif" width="12px" height="12px" alt="Siren"> Removed support for `.fadeIn()` and `.fadeOut()` in Sounds <img src="images/siren.gif" width="12px" height="12px" alt="Siren">\n\n### Version 0.5.1\n- *Sequencer* - Added two sample scenes with macros and accompanying art:\n    - An animated Oujia board made by md-mention2reply\n    - A Star Wars inspired hyperspeed scene-switching scene, effects, and macro\n- *Sequencer* - Removed Token Ease as dependency until we can solve its conflicts with other modules\n- *Effects* - Fixed effects not auto-centering on tokens\n- *Effects* - Fixed effects not finding the proper location when a previous effect\'s `.name()` was given\n- *Animations* - Fixed `.rotate()`, `.opacity()`, and `.volume()`, now they work even without having to use their respective in/out functions\n\n### Version 0.5.0\n- *Sequencer* - Module now depends on [Token Ease](https://github.com/fantasycalendar/FoundryVTT-TokenEase)\n- *Sequencer* - Added the Sequencer Database to help content creators! Read more on the [database documentation](https://github.com/fantasycalendar/FoundryVTT-Sequencer/wiki/Sequencer-Database) how to use it!\n- *Effects & Sounds* - Added support for database collections to the `.file()` method - more info can be found in the [docs](https://github.com/fantasycalendar/FoundryVTT-Sequencer/wiki/Effects#file)\n- *Animations, Effects & Sounds* - Added the following functions:\n    - `.audioVolume()`\n    - `.fadeInAudio()`\n    - `.fadeOutAudio()`\n- *Effects* - Added support for delays on these methods (e.g. a delay of -500 means it will finish 500ms before the end of the duration):\n    - `.rotateOut()`\n    - `.fadeOut()`\n    - `.scaleOut()`\n    - `.fadeOutAudio()`\n- *Animations* - Fixed `.rotateTowards()` to properly rotate towards the target without having to add an offset to properly line them up\n- *Effects* - Made effects more intelligent when determining locations when given partial object data with `id` collections\n- *Effects* - Fixed issues surrounding delays and fades\n- *Sounds* - <img src="images/siren.gif" width="12px" height="12px" alt="Siren"> `.fadeIn()` and `.fadeOut()` will become deprecated in a future version, please switch to `.fadeInAudio()` and `.fadeOutAudio()` <img src="images/siren.gif" width="12px" height="12px" alt="Siren">\n\n### Version 0.4.6 Hotfix\n- *Effects* - Fixed effects not playing on tokens on 0.7.10 and below\n\n### Version 0.4.5\n- *Effects* - Added `.offset()` so that you can offset the effect - an optional parameter allows you to offset in local or canvas space\n- *Animations* - Added `.snapToSquare()`, which causes the given object to be snapped to the square it is moving or teleported towards\n- *Animations* - Fixed `.rotateIn()` and `.rotateOut()` not properly calculating rotation\n- *Animations* - Adjusted `.rotateTowards()` to instead consider the target position as the rotation origin, rather than the object\'s current position\n\n### Version 0.4.4\n- *Animations* - Added `.animation()` section - animate tokens and tiles! Check out the [documentation](https://github.com/fantasycalendar/FoundryVTT-Sequencer/wiki/Animations) how to use it!\n- *Effects* - Added official support for tiles in `.atLocation()`, `.moveTowards()`, etc\n- *Effects* - Tweaked how effects get locations when dealing with raw template data\n- *Sequencer* - Added `.sequence()` so you can combine multiple sequences into one\n- *Sequencer* - Updated all sample macros to 0.8.x conventions\n\n### Version 0.4.3 Minor Fixes\n- *Effects* - Removed error catch in `.file()` when providing it with something else than string or array\n- *Effects* - Fixed `.belowTokens()` and `.belowTiles()` throwing errors if no boolean was provided\n\n### Version 0.4.2 Hotfix\n- *Effects* - Added `.rotate()` which adds an offset to the effect\'s rotation\n- *Effects* - Fixed `.moveTowards()` not respecting given easing\n\n### Version 0.4.1\n- *Sequencer* - <img src="images/siren.gif" width="12px" height="12px" alt="Siren"> **Breaking Changes**: Removed deprecated `.then()` method <img src="images/siren.gif" width="12px" height="12px" alt="Siren">\n- *Sequencer* - Tweaked `.play()` to now return a promise\n- *Sequencer* - Reworked module class structure\n- *Sequencer* - Added debug setting\n\n### Version 0.4.0\n- *Sequencer* - Renamed `.then()` to `.thenDo()` due to JavaScript reasons — <img src="images/siren.gif" width="12px" height="12px" alt="Siren"> `.then()` will be removed in 0.4.1 <img src="images/siren.gif" width="12px" height="12px" alt="Siren">\n- *Sequencer* - Removed the requirement to pass `true` as a second argument to `.then()` (now `.thenDo()`) if the function was async, it will now wait for it to finish if it is an `async function`\n- *Effects* - Added `.mirrorX()` and `.mirrorY()` to mirror the effect on that axis\n- *Effects* - Improved `.JB2A()` to better handle melee weapon attacks\n- *Effects* - Tweaked `.belowTiles()` and `.belowTokens()` to accept an optional boolean parameter whether the effect should play behind the respective element\n- *Effects* - Tweaked effects to assume that .webms have a base 100px internal grid for size consistency\n\n### Version 0.3.13 Hotfix\n- *Effects* - Fixed ANOTHER bug with `.belowTiles()` sometimes not playing below tiles\n\n### Version 0.3.12\n- *Effects* - Added `.opacity()` which controls the alpha of the effect being played\n- *Effects* - Fixed bug with `.belowTiles()` sometimes not playing below tiles\n\n### Version 0.3.11\n- *Effects* - Added `.belowTiles()` to play effects below tiles\n- *Effects* - Implemented better order handling - the effects created first will always be on top, each subsequent effect will be played below the previous\n- *Effects* - Added `.zIndex()` for you to have direct control over the order of effects\n- *Effects & Sounds* - Added `.duration()` which can override the duration of an effect or sound\n- *Effects & Sounds* - Tweaked `.waitUntilFinished()` to accept a single number parameter as a delay or to end the effect or sound earlier - read more in the [documentation](https://github.com/fantasycalendar/FoundryVTT-Sequencer/wiki#wait-until-finished)\n- *Sounds* - Added support for `.fadeIn()` and `.fadeOut()` - easing sadly doesn\'t work for sounds yet\n\n### Version 0.3.10\n- *Sequencer* - Added macro pack containing examples of Sequencer usages\n- *Effects* - Added the following animated functions:\n    - `.scaleIn()`\n    - `.scaleOut()`\n    - `.rotateIn()`\n    - `.rotateOut()`\n    - All of these can utilize any of the easings listed here: https://easings.net/\n    - Read the [documentation](https://github.com/fantasycalendar/FoundryVTT-Sequencer/wiki/Effects#scale-in) how to use these\n- *Effects* - Added better error reporting when something goes wrong in the sequence\n- *Effects* - Fixed bug with scale sometimes overriding `.reachTowards()`\n\n### Version 0.3.9\n- *Effects* - Added `.belowTokens()` so you can now play effects, well, below tokens\n- *Effects* - Fixed effects not replicating properly (AGAIN)\n- *Effects* - Fixed effects not being able to use `.name()`d effects if they didn\'t miss - now any effect can be named and be used in future effects\n\n### Version 0.3.8 Hotfix\n- *Effects* - Fixed effects that were supposed to be once-off instead looping\n\n### Version 0.3.7\n- *Effects* - Added `.moveTowards()` and `.moveSpeed()` for missile-like behavior\n- *Effects* - Tweaked the way the effects layer is applied to the canvas\' layers\n- *Effects* - Fixed major issue with the way effects that were using `.missed()` and `.name()` were cached\n- *Sequencer* - Removed stray debug code\n\n### Version 0.3.6\n- *Effects* - Added `.fadeIn()` and `.fadeOut()` - you can now make your effects look slightly nicer!\n- *Effects* - Added support for cone and line templates with `.reachTowards()` and `.rotateTowards()` - it now reaches towards the end point of the template\n- *Effects* - Added `.name()` to effects - this will cause the effect\'s position to be stored and can then be used with `.atLocation()`, `.reachTowards()`, and `.rotateTowards()` to refer to previous effects\' locations\n    - Example: naming an impact effect with `.name("hit_location")` and making it miss with `.missed()`, and then have a subsequent effect use `.rotateTowards("hit_location")` to rotate towards the previous effect\'s calculated location\n- *Effects* - Fixed `.scale()` bug that caused it to not properly set the scale and then cause an error upon calling `.play()`\n- *Effects* - Removed `.moves()` for future implementation\n- *Sequencer* - Tweaked `.async()` and `.waitUntilFinished()` handling\n    - They now act the same on effect and sounds that only play once, but if it `.repeats()`, `.async()` causes the effect or sound to wait between each repetition, `.waitUntilFinished()` causes the sequencer to wait until the effect or sound has finished executing all of its repetitions, which may or may not wait for each effect or sound to play with `.async()`\n- *Sequencer* - Calling `.play()` now returns the sequence\n- *Sequencer* - Removed `FXMaster` dependency and implemented a custom canvas layer and effects class\n\n### Version 0.3.5 Hotfix\n- *Sequencer* - Fixed `.wait()` breaking due to the `.async()` and `.waitUntilFinished()` swap\n\n### Version 0.3.4 Hotfix\n- *Effects* - Fixed issue that caused the wrong scale to be applied when using `.reachTowards()`\n\n### Version 0.3.3\n- *Effects* - Added `.playIf()` ([docs](https://github.com/fantasycalendar/FoundryVTT-Sequencer/wiki#play-if)); this allows you to completely ignore playing an effect or sound, depending on a boolean or a function\n- *Sounds* - Added support for `.async()` and `.waitUntilFinished()` for sounds - requires both to be `true` due to code weirdness, I\'ll be refactoring this in the future\n- *Effects* - Refactored `.scale()` when it was provided with a minimum and maximum value, it now randomizes the scale of the effect when executed instead of when the method was called\n- *Effects & Sounds* - Refactored `.file()` for both effects and sounds so that providing an array of files no longer immediately picks one from the array, but randomly picks a file each time the section is executed\n- *Effects & Sounds* - Refactored how `.delay()` interacted with `.repeats()`, which should result in more consistent behavior\n- *Sequencer* - Swapped the functionality of `.async()` and `.waitUntilFinished()`, and clarified in the docs\n- *Sequencer* - Added support for random range within a `.wait()` block (like, `.wait(500, 1000)` etc)\n\n### Version 0.3.2 - 0.8.x ready!\n* *Effects* - Added `.playbackRate()` to effects, you can now speed up the play rate of your effects\n* *Sequencer* - Tweaked internal handling of `.async()` together with `.waitUntilFinished()` improved\n* *Sequencer* - Tweaked to use `ready` instead of `init` to load module\n\n### Version 0.3.1\n- *Effects* - Refactored `.randomizeMirror()` into `.randomizeMirrorX()` and `.randomizeMirrorY()`\n- *Effects* - Refactored scaling algorithm for `.reachTowards()`\n- *Sequencer* - Added support for random `.wait()` interval\n\n### Version 0.3.0\n- *Effects* - Refactored `.aimTowards()` into `.rotateTowards()` and `.reachTowards()`\n- *Effects* - Refactored how `.missed()` chooses the location to hit and now takes token size into account\n- *Effects* - Added `.JB2A()` to automatically set the effect to handle their sprites in the best way possible\n- *Effects* - Added `.randomizeMirror()` to randomly mirror sprites on the Y axis\n- *Effects* - Added Mustache support in file names\n\n### Version 0.2.0\n- *Sequencer* - Added support for executing macros\n- *Sequencer* - Added support for playing sounds\n- *Sequencer* - Wrapped classes in proxies to simplify fluid interface (gets rid of `.done()` on effects and sounds)\n\n### Version 0.1.0\n- First implementation\n';
var root_2 = /* @__PURE__ */ from_html(`<div class="sequencer-changelog-status sequencer-changelog-error"><p> </p> <p><a target="_blank" rel="noopener noreferrer">View on GitHub</a></p></div>`);
var root_3 = /* @__PURE__ */ from_html(`<div class="sequencer-changelog-status">No changelog entries found.</div>`);
var root_6 = /* @__PURE__ */ from_html(`<span class="sequencer-changelog-latest-badge">Latest</span>`);
var root_5 = /* @__PURE__ */ from_html(`<details class="sequencer-changelog-entry"><summary><span class="sequencer-changelog-version"> </span> <!> <i class="fas fa-chevron-down sequencer-changelog-chevron"></i></summary> <div class="sequencer-changelog-body"></div></details>`);
var root_4 = /* @__PURE__ */ from_html(`<div class="sequencer-changelog-entries"></div>`);
var root = /* @__PURE__ */ from_html(`<div class="sequencer-changelog"><header class="sequencer-changelog-header"><a class="sequencer-changelog-header-link" target="_blank" rel="noopener noreferrer" title="View the changelog on GitHub"><i class="fab fa-github"></i> View on GitHub</a> <a class="sequencer-changelog-header-link sequencer-changelog-header-patreon" target="_blank" rel="noopener noreferrer" title="Support Sequencer development on Patreon"><i class="fab fa-patreon"></i> Support Sequencer</a></header> <div class="sequencer-changelog-content"><!></div></div>`);
function Changelog_dialog($$anchor, $$props) {
  push($$props, true);
  const GITHUB_CHANGELOG_URL = "https://github.com/fantasycalendar/FoundryVTT-Sequencer/blob/master/changelog.md";
  const PATREON_URL2 = "https://patreon.com/cw/fantasycomputerworks";
  function parseChangelog(raw) {
    const firstHeader = raw.search(/^# Version /m);
    const body = firstHeader >= 0 ? raw.slice(firstHeader) : raw;
    const chunks = body.split(/^(?=# Version )/m).filter((c) => c.trim().length);
    return chunks.map((chunk) => {
      const headerMatch = chunk.match(/^# Version\s+(.+?)\s*$/m);
      const version = headerMatch ? headerMatch[1].trim() : "Unknown";
      const content = chunk.replace(/^# Version.*$/m, "").trim();
      return { version, html: marked.parse(content) };
    });
  }
  let error = /* @__PURE__ */ state(null);
  let entries = /* @__PURE__ */ state(proxy([]));
  try {
    set(entries, parseChangelog(changelogText), true);
  } catch (e) {
    set(error, e?.message ?? String(e), true);
  }
  var div = root();
  var header = child(div);
  var a = child(header);
  set_attribute(a, "href", GITHUB_CHANGELOG_URL);
  var a_1 = sibling(a, 2);
  set_attribute(a_1, "href", PATREON_URL2);
  var div_1 = sibling(header, 2);
  var node = child(div_1);
  {
    var consequent_1 = ($$anchor2) => {
      var div_3 = root_2();
      var p = child(div_3);
      var text2 = child(p);
      var p_1 = sibling(p, 2);
      var a_2 = child(p_1);
      set_attribute(a_2, "href", GITHUB_CHANGELOG_URL);
      template_effect(() => set_text(text2, `Failed to load changelog: ${get(error) ?? ""}`));
      append($$anchor2, div_3);
    };
    var consequent_2 = ($$anchor2) => {
      var div_4 = root_3();
      append($$anchor2, div_4);
    };
    var alternate = ($$anchor2) => {
      var div_5 = root_4();
      each(div_5, 23, () => get(entries), (entry) => entry.version, ($$anchor3, entry, index2) => {
        var details = root_5();
        var summary = child(details);
        var span = child(summary);
        var text_1 = child(span);
        var node_1 = sibling(span, 2);
        {
          var consequent_3 = ($$anchor4) => {
            var span_1 = root_6();
            append($$anchor4, span_1);
          };
          if_block(node_1, ($$render) => {
            if (get(index2) === 0) $$render(consequent_3);
          });
        }
        var div_6 = sibling(summary, 2);
        html$1(div_6, () => get(entry).html, true);
        template_effect(() => {
          details.open = get(index2) === 0;
          set_text(text_1, `v${get(entry).version ?? ""}`);
        });
        append($$anchor3, details);
      });
      append($$anchor2, div_5);
    };
    if_block(node, ($$render) => {
      if (get(error)) $$render(consequent_1, 1);
      else if (get(entries).length === 0) $$render(consequent_2, 2);
      else $$render(alternate, -1);
    });
  }
  append($$anchor, div);
  pop();
}
const PATREON_URL = "https://patreon.com/cw/fantasycomputerworks";
const DISCORD_URL = "https://discord.gg/qFHQUwBZAz";
function showChangelog() {
  return SvelteDialog.show({
    component: Changelog_dialog,
    classes: ["sequencer-changelog-dialog"],
    window: { title: "Sequencer Changelog", icon: "fas fa-clipboard-list", resizable: true },
    position: { width: 720, height: 600 }
  });
}
let moduleValid = false;
let moduleReady = false;
let canvasReady = false;
let lastSceneId = null;
Hooks.once("init", async function() {
  moduleValid = true;
  CONSTANTS.IS_V14 = foundry.utils.isNewerVersion(game.version, "14");
  initializeModule();
  registerSocket();
  flagManager.setup();
});
Hooks.once("ready", async function() {
  for (const [name2, func] of Object.entries(easeFunctions)) {
    if (!foundry.canvas.animation.CanvasAnimation[name2]) {
      foundry.canvas.animation.CanvasAnimation[name2] = func;
    }
  }
  if (game.user.isGM) {
    await runMigrations();
    await migrateSettings();
    await PlayerSettings.migrateOldPresets();
    const database = await createJournalDatabase();
    flagManager._databaseId = database?.id ?? null;
  }
  SequencerFoundryReplicator.registerHooks();
  InteractionManager.initialize();
});
Hooks.on("canvasTearDown", () => {
  canvasReady = false;
  SequencerEffectManager.tearDownPersistentEffects();
  SequencerSoundManager.tearDownPersistentSounds();
});
const setupModule = foundry.utils.debounce(() => {
  if (!moduleValid) return;
  if (!moduleReady) {
    moduleReady = true;
    debug("Ready to go!");
    Hooks.callAll("sequencer.ready");
    Hooks.callAll("sequencerReady");
  }
  if (!canvasReady && game.canvas?.ready) {
    canvasReady = true;
    SequencerEffectManager.initializePersistentEffects();
    SequencerSoundManager.initializePersistentSounds();
  }
}, 25);
Hooks.on("canvasReady", () => {
  const currentSceneId = canvas.scene?.id ?? null;
  const isSceneSwitch = currentSceneId !== lastSceneId;
  lastSceneId = currentSceneId;
  setTimeout(setupModule, isSceneSwitch ? 450 : 100);
});
Hooks.on("refreshToken", setupModule);
Hooks.on("refreshDrawing", setupModule);
Hooks.on("refreshTile", setupModule);
Hooks.on("refreshMeasuredTemplate", setupModule);
function initializeModule() {
  window.Sequence = Sequence$1;
  window.Sequencer = {
    Player: EffectPlayer,
    Presets: SequencerPresets,
    Database: SequencerDatabase,
    DatabaseViewer: DatabaseViewerApp,
    Preloader: SequencerPreloader,
    EffectManager: SequencerEffectManager,
    SoundManager: SequencerSoundManager,
    SectionManager: sectionManager,
    registerEase,
    BaseSection: Section,
    CONSTANTS: {
      EASE
    },
    Helpers: {
      wait,
      clamp,
      interpolate,
      random_float_between,
      random_int_between,
      shuffle_array,
      random_array_element,
      random_object_element,
      make_array_unique,
      computeWallPolygon
    },
    Crosshair,
    showChangelog
  };
  registerSettings();
  registerLayers();
  registerHotkeys();
  registerLibwrappers();
  registerBatchShader();
  SequencerEffectManager.setup();
  SequencerSoundManager.setup();
  PasteManager.setup();
  PluginsManager.initialize();
  Hooks.on("renderChatMessageHTML", (_message, html2) => {
    const links = html2.querySelectorAll('[data-action="sequencer-show-changelog"]');
    links.forEach((el) => {
      if (el.dataset.sequencerWired === "1") return;
      el.dataset.sequencerWired = "1";
      el.addEventListener("click", (event2) => {
        event2.preventDefault();
        event2.stopPropagation();
        showChangelog();
      });
      el.addEventListener("keydown", (event2) => {
        if (event2.key === "Enter" || event2.key === " ") {
          event2.preventDefault();
          event2.stopPropagation();
          showChangelog();
        }
      });
    });
  });
}
Hooks.once("ready", async () => {
  displaySequencerUpdateNotice();
});
function getMajorMinor(version) {
  if (typeof version !== "string") return null;
  const match = version.match(/^(\d+)\.(\d+)/);
  if (!match) return null;
  return `${match[1]}.${match[2]}`;
}
async function displaySequencerUpdateNotice() {
  if (!game.user.isGM) return;
  const currentVersion = game.modules.get(CONSTANTS.MODULE_NAME)?.version;
  if (!currentVersion) return;
  const lastShown = game.settings.get(CONSTANTS.MODULE_NAME, "update-notice-shown-version") ?? "0.0.0";
  const currentMM = getMajorMinor(currentVersion);
  const lastMM = getMajorMinor(lastShown);
  if (!currentMM) return;
  if (lastMM && !foundry.utils.isNewerVersion(currentMM, lastMM)) {
    if (lastShown !== currentVersion) {
      await game.settings.set(CONSTANTS.MODULE_NAME, "update-notice-shown-version", currentVersion);
    }
    return;
  }
  await game.settings.set(CONSTANTS.MODULE_NAME, "update-notice-shown-version", currentVersion);
  const oldMessageIds = game.messages.filter((message) => {
    return message.content.includes("sequencer-welcome");
  }).map((message) => message.id);
  if (oldMessageIds.length) {
    await ChatMessage.deleteDocuments(oldMessageIds);
  }
  const gmIds = game.users.filter((u) => u.isGM).map((u) => u.id);
  await ChatMessage.create({
    whisper: gmIds,
    content: `
<div class="sequencer-welcome">
<img class="sequencer-welcome-image" src="modules/sequencer/images/sequencer.png"/>
<div class="sequencer-welcome-divider"></div>
<p class="sequencer-welcome-title">Sequencer updated to v${currentVersion}</p>
<div class="sequencer-welcome-divider"></div>
<div class="sequencer-welcome-links">
<span class="sequencer-welcome-link" role="button" tabindex="0" data-action="sequencer-show-changelog"><i class="fas fa-clipboard-list"></i> View changelog</span>
<a target="_blank" rel="noopener noreferrer" href="${DISCORD_URL}"><i class="fab fa-discord"></i> Discord</a>
<a target="_blank" rel="noopener noreferrer" href="${PATREON_URL}"><i class="fab fa-patreon"></i> Support on Patreon</a>
</div>
</div>`
  });
}
Hooks.once("monaco-editor.ready", registerTypes);
//# sourceMappingURL=sequencer.js.map
