{"version":3,"file":"main.js","mappings":";;;;;AAAA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA,aAAa,YAAY,IAAI,UAAU,IAAI,IAAI;AAC/C;AACA;AACA;AACA,0CAA0C;AAC1C,UAAU;AACV,8BAA8B;AAC9B;AACA;AACA;AACA;AACA,yCAAyC;AACzC,UAAU;AACV,6BAA6B;AAC7B;AACA;AACA;AACA;AACA,yCAAyC;AACzC,UAAU;AACV,6BAA6B;AAC7B;AACA;AACA;AACA;AACA,0CAA0C;AAC1C,UAAU;AACV,8BAA8B;AAC9B;AACA;AACA;AACA;AACA;AACA,GAAG;;AAEH;AACA;AACA,GAAG;;AAEH;AACA;AACA,GAAG;;AAEH;AACA;AACA,GAAG;;AAEH;AACA;AACA,GAAG;AACH;;AAEA,kDAAe,MAAM,EAAC;;;ACzFtB;AACA;AACA;;AAEuC;AACR;;AAE/B;;AAEA;;AAEA;AACA;AACA,yEAAyE,wBAAwB,IAAI,eAAe;AACpH;;;AAGA;AACA;AACA;AACA,YAAY,mBAAmB,GAAG,aAAa,IAAI,kCAAkC;AACrF,YAAY,mBAAmB,IAAI,kCAAkC;AACrE;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;;;AAGA;AACA;AACA,gEAAgE,cAAc;AAC9E;AACA;AACA;AACA;;AAEA;AACA,cAAc,QAAQ;AACtB,cAAc,QAAQ;AACtB,cAAc,QAAQ;AACtB;AACA,2DAA2D;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,cAAc,QAAQ;AACtB,cAAc,QAAQ;AACtB,eAAe,SAAS;AACxB;AACA;AACA;AACA;AACA;;AAEA,sBAAsB,kBAAkB;AACxC;AACA;AACA,+BAA+B,cAAc,GAAG,SAAS;AACzD;AACA;AACA,4EAA4E,cAAc,KAAK,2BAA2B;;AAE1H,UAAU;AACV,gCAAgC,iBAAiB,cAAc,mBAAmB;AAClF;AACA,YAAY,iBAAY,iCAAiC,wBAAwB,KAAK,mBAAmB;AACzG;AACA;AACA;AACA,MAAM;AACN;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA,cAAc,OAAO,GAAG,YAAY,GAAG,SAAS;AAChD;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,gDAAgD,OAAO,GAAG,YAAY,GAAG,SAAS;AAClF,UAAU;AACV;AACA,2BAA2B,WAAW,GAAG,qCAAqC,GAAG,YAAY,GAAG,SAAS;AACzG,UAAU;AACV,UAAU,iBAAY;AACtB;AACA;AACA,MAAM;AACN,yEAAyE,cAAc,kBAAkB,SAAS;AAClH;AACA;AACA;AACA;;AAEA,2DAAe,eAAe,EAAC;;;AChKoB;AAClB;;AAEjC;AACA;AACA;;AAEe;;AAEf;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,mCAAmC;;AAEnC;AACA;AACA;;AAEA,oBAAoB,iBAAiB;AACrC,2DAA2D;AAC3D;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR,eAAe;AACf;AACA;AACA;;AAEA;AACA,aAAa;AACb;;AAEA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW;AACX;AACA,SAAS;AACT;AACA;AACA,UAAU;AACV;AACA;AACA,OAAO;AACP,KAAK;AACL;;AAEA;AACA;AACA,aAAa,GAAG;AAChB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;;AAEA;AACA;AACA,aAAa,GAAG;AAChB,aAAa,GAAG;AAChB;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;;AAEA;AACA;AACA,aAAa,QAAQ;AACrB;AACA;AACA;AACA;AACA;AACA,uBAAuB,YAAY;AACnC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa,uBAAuB,GAAG,QAAQ;AAC/C;AACA;AACA;AACA;AACA;AACA,QAAQ,iBAAY;AACpB;AACA;AACA;AACA,QAAQ,iBAAY;AACpB;AACA;AACA;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA,4CAA4C,iBAAiB;;AAE7D,oBAAoB,0BAAqB;;AAEzC,IAAI,iBAAY,cAAc,SAAS,KAAK,kCAAkC;;AAE9E;AACA,uFAAuF,wBAAwB,IAAI,eAAe;;AAElI;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,qBAAqB;AAC7C;AACA;AACA;AACA,OAAO;AACP,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW;AACX;AACA,OAAO;AACP;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU,iBAAY;AACtB;AACA,SAAS;AACT,QAAQ;AACR;AACA;AACA,KAAK;AACL;;AAEA;AACA;;AAEA,oBAAoB,kBAAkB;AACtC;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC,eAAe;AACnD,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,mCAAmC,2BAA2B;AAC9D;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA,oBAAoB,oBAAoB;AACxC;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO;AACP;AACA;;AAEA;;;;;AC3WA;AACA;AACA;AACA;AACA;AACA;AACA,icAAic,+BAA+B;AAChe;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEO;;AAEP;AACA;AACA;;AAEO;AACP;AACA;;AAEA,kCAAkC;AAClC;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,MAAM;AACN;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,IAAI;;AAEJ;AACA;;AAEA;AACA;AACA;AACA;;;AC3FA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;;AAEA,4BAA4B,oBAAoB;AAChD,uCAAuC,eAAe;AACtD,mCAAmC,eAAe;AAClD,uCAAuC,eAAe;;AAEtD,gDAAe,SAAS,EAAC;;;AClGe;;AAEzB;;AAEf,gBAAgB,0DAA0D,IAAI;AAC9E;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA,wBAAwB,gCAAgC;AACxD;;AAEA;;;ACtBwC;AACT;;AAE/B;AACA;AACA;AACA,oBAAoB,KAAK;AACzB;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,kBAAkB,MAAM;;AAExB;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;;AAGA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA,GAAG;;AAEH;AACA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,qCAAqC;AACrC;AACA,sCAAsC;AACtC;AACA,iCAAiC;AACjC;AACA,WAAW;AACX;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA,yBAAyB,uBAAuB;AAChD,iCAAiC,0BAA0B;AAC3D;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,yCAAyC,YAAY,0BAA0B,iCAAiC;AAChH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEO;AACP;AACA;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA,KAAK;;AAEL;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA,IAAI;AACJ;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA,KAAK;AACL;AACA;;AAEA;;AAEA;AACA;;;AChMwC;AACH;;AAE9B;;AAEP;AACA;AACA;AACA;AACA;AACA,4DAA4D,2BAA2B;AACvF;AACA;AACA;AACA;;AAEA;AACA,oBAAoB,UAAU;AAC9B,yCAAyC,SAAS;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA,4CAA4C,2BAA2B;AACvE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,sCAAsC,2BAA2B;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA,gBAAgB,iBAAiB;AACjC;AACA;;AAEA,oDAAoD,2BAA2B;AAC/E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,+DAA+D,2BAA2B;AAC1F;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA,mCAAmC,mBAAmB;AACtD;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA,0BAA0B;AAC1B;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,mBAAmB,iBAAiB;AACpC;AACA,8CAA8C,2BAA2B;AACzE;AACA;AACA,8BAA8B,2BAA2B;AACzD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,IAAI,gBAAgB;AAC3B;;AAEA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA,sBAAsB,cAAc;AACpC;AACA;AACA,uBAAuB,MAAM;AAC7B;AACA;AACA,KAAK;;;AAGL;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,KAAK;;AAEL;;AAEA;AACA,mDAAmD,2BAA2B;AAC9E;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;;AChVA;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mDAAmD;;AAEnD;;AAEA,cAAc;AACd,sEAAsE;;AAEtE,iBAAiB,4CAA4C,GAAG;AAChE;AACA,wBAAwB;;AAExB;AACA,0BAA0B,cAAc;AACxC;AACA,4BAA4B;;AAE5B,2CAA2C;AAC3C;AACA,oCAAoC;AACpC;AACA,wCAAwC;AACxC;AACA,wCAAwC;AACxC;;AAEA,qBAAqB;;AAErB,0BAA0B;AAC1B,2BAA2B;;AAE3B;AACA;AACA;AACA;AACA,yBAAyB;AACzB,uCAAuC;AACvC;AACA,sCAAsC;AACtC;AACA,0CAA0C;AAC1C;AACA,0CAA0C;AAC1C;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB,uCAAuC;AACvC;AACA,sCAAsC;AACtC;AACA,0CAA0C;AAC1C;AACA,0CAA0C;AAC1C;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,uBAAuB,iDAAiD,GAAG;AAC3E;AACA,uBAAuB,kDAAkD,GAAG;AAC5E;AACA;AACA;AACA;AACA,kCAAkC,2CAA2C,GAAG;AAChF;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI;;AAEJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mDAAmD;;AAEnD;;AAEA,cAAc;AACd,sEAAsE;;AAEtE,iBAAiB,4CAA4C,GAAG;AAChE;AACA,wBAAwB;;AAExB;AACA,0BAA0B,cAAc;AACxC;AACA,4BAA4B;;AAE5B,2CAA2C;;AAE3C,qBAAqB;;AAErB,0BAA0B;AAC1B,2BAA2B;;AAE3B;AACA,oCAAoC;AACpC;AACA,wCAAwC;AACxC;AACA,wCAAwC;AACxC;;AAEA;AACA;AACA;AACA;AACA,yBAAyB;AACzB,uCAAuC;;AAEvC;AACA;AACA;;AAEA;AACA,sCAAsC;AACtC;AACA,0CAA0C;AAC1C;AACA,0CAA0C;AAC1C;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB,uCAAuC;;AAEvC;AACA;AACA;;AAEA;AACA,sCAAsC;AACtC;AACA,0CAA0C;AAC1C;AACA,0CAA0C;AAC1C;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,uBAAuB,iDAAiD,GAAG;AAC3E;AACA,uBAAuB,kDAAkD,GAAG;AAC5E;AACA;AACA;AACA;AACA;AACA,uBAAuB,2CAA2C,GAAG;AACrE;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI;;AAEJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA,YAAY,QAAQ,QAAQ,YAAY,OAAO,KAAK,QAAQ,KAAK,SAAS,KAAK;AAC/E,YAAY,KAAK;AACjB,YAAY,KAAK;AACjB,YAAY,KAAK;AACjB,YAAY,YAAY;AACxB,YAAY,SAAS;AACrB,aAAa,QAAQ,OAAO,YAAY,OAAO,KAAK,QAAQ,KAAK,SAAS,QAAQ;AAClF;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,YAAY,QAAQ,OAAO,YAAY,OAAO,KAAK,QAAQ,KAAK,SAAS,QAAQ;AACjF,YAAY,KAAK;AACjB,aAAa,QAAQ,OAAO,YAAY,OAAO,KAAK,QAAQ,KAAK,SAAS,QAAQ;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,cAAc,YAAY;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,OAAO;AACrB;AACA;;AAEA;AACA;AACA;;AAEA;AACA,iBAAiB,cAAc;AAC/B,mBAAmB,cAAc;AACjC;AACA,qBAAqB;AACrB;AACA,yCAAyC;AACzC;AACA;AACA,sBAAsB,SAAS;AAC/B;AACA;AACA;AACA,yCAAyC;AACzC;AACA;AACA,sBAAsB,SAAS;AAC/B;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA,YAAY,QAAQ,OAAO,YAAY,OAAO,KAAK,QAAQ,KAAK,SAAS,QAAQ;AACjF,YAAY,KAAK;AACjB,YAAY,YAAY;AACxB,aAAa,OAAO;AACpB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,gBAAgB,SAAS;AACzB;AACA;AACA;;AAEA;AACA,eAAe,YAAY;AAC3B,iBAAiB,YAAY;AAC7B;AACA,mCAAmC;AACnC,kBAAkB;AAClB,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,mBAAmB,cAAc;AACjC;;AAEA;AACA,mBAAmB,cAAc;AACjC;;AAEA;AACA,mBAAmB,cAAc;;AAEjC;AACA,mBAAmB,cAAc;AACjC;;AAEA;AACA;AACA;AACA;AACA;AACA,0BAA0B;;AAE1B;AACA;AACA,cAAc,SAAS;AACvB,mBAAmB;AACnB,iBAAiB;AACjB,oBAAoB;AACpB,eAAe;AACf,qBAAqB;AACrB;AACA,uCAAuC;AACvC;AACA;AACA,oBAAoB,SAAS;AAC7B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uCAAuC;AACvC;AACA;AACA,oBAAoB,SAAS;AAC7B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA,YAAY,QAAQ,OAAO,YAAY,OAAO,KAAK,QAAQ,KAAK,SAAS,QAAQ;AACjF,YAAY,KAAK;AACjB,YAAY,YAAY;AACxB,aAAa,QAAQ,OAAO,YAAY,OAAO,KAAK,QAAQ,KAAK,SAAS,QAAQ;AAClF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,cAAc,YAAY;AAC1B;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,OAAO;AACrB;AACA;;AAEA;AACA;AACA;;AAEA;AACA,cAAc,SAAS;AACvB,mBAAmB;AACnB;AACA,eAAe;AACf,qBAAqB;AACrB;AACA,uCAAuC;AACvC;AACA;AACA,oBAAoB,SAAS;AAC7B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,uCAAuC;AACvC;AACA;AACA,oBAAoB,SAAS;AAC7B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA,YAAY,QAAQ,OAAO,YAAY,OAAO,KAAK,QAAQ,KAAK,SAAS,QAAQ;AACjF,aAAa,QAAQ,cAAc,YAAY,OAAO,KAAK,QAAQ,KAAK,SAAS,QAAQ;AACzF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,eAAe,YAAY;AAC3B,iBAAiB,YAAY;AAC7B;AACA,mCAAmC;AACnC,kBAAkB;AAClB,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,mBAAmB,cAAc;AACjC;;AAEA;AACA,mBAAmB,cAAc;AACjC;AACA;;AAEA;AACA,mBAAmB,cAAc;;AAEjC;AACA,mBAAmB,cAAc;AACjC;AACA;;AAEA;AACA;AACA;AACA;AACA,cAAc,kBAAkB;AAChC;AACA;;AAEA;AACA,YAAY,QAAQ,OAAO,YAAY,OAAO,KAAK,QAAQ,KAAK;AAChE,aAAa,OAAO;AACpB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,cAAc,QAAQ;AACtB,gBAAgB,QAAQ;AACxB;AACA,mCAAmC;AACnC,kBAAkB;AAClB,kBAAkB;AAClB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,cAAc,OAAO;;AAErB,cAAc,OAAO;;AAErB;AACA,cAAc,OAAO;;AAErB;AACA,cAAc,OAAO;;AAErB;AACA;;AAEA;AACA,YAAY,QAAQ,OAAO,YAAY,OAAO,KAAK,QAAQ,KAAK,SAAS,QAAQ;AACjF,aAAa,QAAQ,cAAc,YAAY,OAAO,KAAK,QAAQ,KAAK,SAAS,QAAQ;AACzF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sCAAsC;;AAEtC;AACA,iBAAiB,cAAc;AAC/B,mBAAmB,cAAc;AACjC;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,cAAc,0BAA0B;AACxC;AACA;;AAEA;AACA;AACA,YAAY,QAAQ,OAAO,YAAY,OAAO,KAAK,QAAQ,KAAK,SAAS,QAAQ;AACjF,aAAa,OAAO,WAAW,OAAO,SAAS,MAAM,QAAQ,KAAK;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,cAAc,WAAW;AACzB,gBAAgB,WAAW;AAC3B;AACA;AACA,qBAAqB,QAAQ;AAC7B;AACA;AACA;AACA,6BAA6B;AAC7B,qBAAqB;;AAErB;AACA,iCAAiC;AACjC,2CAA2C;AAC3C;AACA;AACA,yDAAyD;AACzD;AACA,0BAA0B,OAAO;AACjC;;AAEA;AACA,qCAAqC;AACrC,yBAAyB;;AAEzB;AACA;AACA;AACA,oCAAoC;AACpC;AACA;AACA,+BAA+B;AAC/B;AACA;AACA,wCAAwC;AACxC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA,gBAAgB;AAChB;AACA;AACA,uBAAuB,wCAAwC;AAC/D;AACA,mCAAmC;AACnC;;AAEA;AACA;AACA,uBAAuB,kCAAkC,GAAG;AAC5D,8BAA8B,uCAAuC,GAAG;AACxE;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA,YAAY,OAAO,WAAW,OAAO,SAAS,MAAM,QAAQ,KAAK;AACjE,YAAY,OAAO;AACnB,YAAY,KAAK;AACjB,aAAa,OAAO,WAAW,OAAO,SAAS,MAAM,QAAQ,KAAK,QAAQ,KAAK;AAC/E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,cAAc,iBAAiB;AAC/B;AACA;AACA;;AAEA;AACA;AACA;AACA,kBAAkB,SAAS;AAC3B,yBAAyB,gCAAgC;AACzD;AACA;AACA;AACA;AACA;AACA;AACA,OAAO;AACP;AACA;;AAEA,wBAAwB;AACxB,eAAe,yBAAyB,GAAG;;AAE3C;AACA;AACA;AACA;AACA;AACA;;AAEA,mBAAmB;AACnB,wBAAwB;;AAExB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,oBAAoB;AACpB;AACA;AACA;;AAEA;AACA;AACA,wBAAwB;AACxB,qBAAqB,8BAA8B,GAAG;AACtD,qBAAqB,6BAA6B,GAAG;AACrD;AACA,MAAM;;AAEN;AACA;AACA;AACA;AACA,KAAK,GAAG;AACR,gBAAgB,SAAS;AACzB,uBAAuB,0CAA0C,GAAG;AACpE;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,uDAAe,SAAS,EAAC;;;AClgCzB;AACkD;AACV;AACH;;AAE9B;;AAEP;AACA;AACA;AACA;AACA;AACA,kDAAkD,2BAA2B;AAC7E;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,4DAA4D,2BAA2B;AACvF;AACA;;AAEA;AACA,oBAAoB,UAAU;AAC9B,yCAAyC,SAAS;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,4DAA4D,2BAA2B;AACvF,qBAAqB,iBAAiB;AACtC,wDAAwD,2BAA2B;AACnF;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA,mCAAmC,mBAAmB;AACtD;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA,KAAK;AACL;;AAEA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,qBAAqB,iBAAiB;AACtC;AACA,gDAAgD,2BAA2B;AAC3E;AACA;AACA,gCAAgC,2BAA2B;AAC3D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA,MAAM;AACN;AACA,MAAM;AACN;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA,sBAAsB,oBAAoB;AAC1C;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,6EAA6E,sBAAsB;AACnG;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA,gBAAgB,0BAAmB;;AAEnC,+BAA+B,oCAA6B;;AAE5D;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA,mCAAmC,iCAA0B;;AAE7D;;AAEA;AACA,gBAAgB,SAAS;AACzB;AACA,qBAAqB;AACrB,2BAA2B;AAC3B;AACA;AACA;AACA,qCAAqC;AACrC,QAAQ;AACR,iCAAiC;AACjC;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA,aAAa,8BAAuB;AACpC,SAAS,iCAA0B;;AAEnC;;AAEA;AACA;AACA;AACA;AACA,oBAAoB,eAAe;AACnC;AACA;AACA;AACA,sBAAsB,eAAe;AACrC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,eAAe;AACnC;AACA;AACA;AACA,sBAAsB,eAAe;AACrC;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,0BAA0B,kBAAkB;AAC5C,4BAA4B,kBAAkB;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,QAAQ;AACzB,iDAAiD;AACjD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,QAAQ;AACzB;AACA,kBAAkB,SAAS;AAC3B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;;ACniBqC;AACa;AACV;AACa;AAChB;AACA;AACE;AACM;;AAE9B;;AAEf;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL,8BAA8B,iCAAiC;AAC/D,kCAAkC,+BAA+B;AACjE;AACA,kCAAkC,iBAAiB;AACnD;AACA;;AAEA;AACA,kBAAkB,iBAAiB;AACnC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,IAAI,IAAI;AACR;AACA;AACA,cAAc,kBAAkB;AAChC;AACA;AACA,kBAAkB,iBAAiB;AACnC;AACA,mBAAmB,iBAAiB;AACpC;AACA,oBAAoB,iBAAiB;;AAErC;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA,oBAAoB;AACpB;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kCAAkC,+BAA+B;AACjE;AACA;;AAEA;AACA;AACA;;AAEA;AACA,8BAA8B,iCAAiC;AAC/D;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA,cAAc,iBAAiB;AAC/B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;;AAEL;AACA,sBAAsB,iBAAiB;AACvC,sBAAsB,iBAAiB;AACvC,uBAAuB,iBAAiB;AACxC,wBAAwB,iBAAiB;;AAEzC;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,oCAAoC,iBAAiB;AACrD,6CAA6C,iBAAiB;AAC9D,iDAAiD,iBAAiB;AAClE;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;;AAEA;AACA,WAAW,gCAAgC;AAC3C;;AAEA;AACA;;AAEA;AACA;AACA,aAAa,wBAAwB;AACrC,MAAM;AACN,aAAa,0BAA0B;AACvC;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;;AAEA;AACA,2BAA2B,MAAM;AACjC;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;;AAEA;AACA,2BAA2B,UAAU;AACrC;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,wBAAwB,uBAAuB;AAC/C,8BAA8B,gCAAgC;AAC9D;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA,wBAAwB,uBAAuB;AAC/C,8BAA8B,gCAAgC;AAC9D;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,sBAAsB;AACvC,kBAAkB,sBAAsB;AACxC;AACA;AACA,KAAK,4EAA4E,sBAAsB,EAAE,sBAAsB;;AAE/H;AACA;AACA;AACA,KAAK;AACL,8CAA8C,sBAAsB,MAAM,sBAAsB;;AAEhG;AACA,+CAA+C,mBAAmB;AAClE;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA,MAAM;AACN;AACA,qBAAqB,YAAY;AACjC;AACA;AACA;AACA,sBAAsB,mBAAmB;AACzC;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,OAAO;AACP;;AAEA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,sCAAsC,mBAAmB;AACzD;AACA;AACA;AACA,UAAU,eAAe;AACzB;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA,sBAAsB,iBAAiB;AACvC;;AAEA,qBAAqB,qGAAqG,IAAI;AAC9H;AACA;AACA;AACA;AACA;;AAEA,mCAAmC,mBAAmB;AACtD;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,8BAA8B,uEAAuE;AACrG;AACA;AACA;AACA;;AAEA;AACA;AACA,aAAa,OAAO;AACpB;AACA;AACA;AACA;AACA,mDAAmD,2BAA2B;AAC9E;AACA;AACA;AACA,kBAAkB,iBAAiB;AACnC;;AAEA,qBAAqB,6BAA6B,IAAI;AACtD;AACA;AACA;;AAEA,8BAA8B,oCAAoC;AAClE;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,aAAa,QAAQ;AACrB,aAAa,QAAQ;AACrB;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA,iDAAiD,OAAO;AACxD,+BAA+B,KAAK;AACpC;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM,iBAAY,gDAAgD,cAAc;AAChF,KAAK;AACL;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,mCAAmC,oBAAoB;AACvD;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,yBAAyB,iBAAiB;AAC1C,0DAA0D,2BAA2B;AACrF;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,iCAAiC;AACjC;AACA;AACA,oBAAoB,iBAAiB;AACrC;AACA,uEAAuE;AACvE,+EAA+E;AAC/E,+EAA+E;AAC/E;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA,oBAAoB,iBAAiB;AACrC;AACA;AACA,4EAA4E;AAC5E;AACA,oFAAoF;AACpF;AACA,oFAAoF;AACpF;;AAEA;AACA;;AAEA;AACA,uBAAuB,iBAAiB;AACxC,sDAAsD,2BAA2B;AACjF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA,qBAAqB,iBAAiB;AACtC,gDAAgD,2BAA2B;AAC3E;AACA,uEAAuE,qBAAqB;AAC5F;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,qBAAqB,iBAAiB;AACtC;AACA;AACA;AACA,KAAK;AACL;AACA,iBAAiB,iBAAiB;AAClC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,KAAK;AACL;AACA;;;ACtsBwC;;AAEzB;;AAEf;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;;AAGA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,wDAAwD,UAAU,0BAA0B;AAC5F,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,6DAA6D,UAAU,0BAA0B;AACjG,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,oEAAoE,UAAU,0BAA0B;AACxG,KAAK;;AAEL;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA,uCAAuC,qBAAqB;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW;AACX,SAAS;AACT,OAAO;AACP;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA,2DAA2D,UAAU,0BAA0B;AAC/F,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,gEAAgE,UAAU,0BAA0B;AACpG,QAAQ;AACR,8DAA8D,UAAU,0BAA0B;AAClG;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,wDAAwD,UAAU,0BAA0B;AAC5F,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,yDAAyD,UAAU,0BAA0B;AAC7F,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,gEAAgE,UAAU,0BAA0B;AACpG,KAAK;;AAEL;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,KAAK;;AAEL;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA,0BAA0B,uBAAuB;AACjD;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,8BAA8B,uBAAuB;AACrD,QAAQ;AACR,iEAAiE,UAAU,+CAA+C;AAC1H;AACA,KAAK;;AAEL;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,oBAAoB,wBAAwB;AAC5C,SAAS;AACT;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,oBAAoB,wBAAwB;AAC5C,SAAS;AACT;AACA,KAAK;;AAEL;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,mDAAmD;AACvE,SAAS;AACT;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,oBAAoB,qCAAqC;AACzD,SAAS;AACT;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB,wBAAwB;AAC9C,WAAW;AACX;AACA,QAAQ;AACR;AACA;AACA;AACA,sBAAsB,wBAAwB;AAC9C,WAAW;AACX;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,oBAAoB,yCAAyC;AAC7D,SAAS;AACT;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,oBAAoB,2CAA2C;AAC/D,SAAS;AACT;AACA,KAAK;AACL;AACA;;AAEA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA,qCAAqC,gCAAgC;;AAErE;AACA;AACA;AACA;AACA;AACA;AACA,2DAA2D,QAAQ;AACnE,uCAAuC,mBAAmB;AAC1D,KAAK;;AAEL;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA,wCAAwC,mCAAmC;;AAE3E;AACA;AACA;AACA;AACA;AACA;AACA,8DAA8D,QAAQ;AACtE,0CAA0C,wBAAwB;AAClE,KAAK;AACL;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA,sCAAsC,iCAAiC;;AAEvE;AACA;AACA;AACA;AACA;AACA;AACA,4DAA4D,QAAQ;AACpE,wCAAwC,mBAAmB;AAC3D,KAAK;AACL;;AAEA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,6CAA6C,kCAAkC;;AAE/E;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mEAAmE,QAAQ;AAC3E,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA,sEAAsE,QAAQ;AAC9E,+CAA+C,mBAAmB;AAClE,KAAK;AACL;AACA;AACA;;AAEA;;AAEA;;AAEA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,sEAAsE,QAAQ;AAC9E,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sBAAsB,wBAAwB;AAC9C,WAAW;AACX;AACA,QAAQ;AACR;AACA;AACA;AACA,sBAAsB,wBAAwB;AAC9C,WAAW;AACX;AACA;AACA,KAAK;;AAEL;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,4EAA4E,UAAU,0BAA0B;AAChH,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,+DAA+D,UAAU,0BAA0B;AACnG,KAAK;;;AAGL;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,mDAAmD;AACvE,SAAS;AACT;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,oBAAoB,qCAAqC;AACzD,SAAS;AACT;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,4EAA4E,UAAU;AACtF;;AAEA;AACA;AACA;AACA;AACA,sBAAsB,+CAA+C;AACrE,WAAW;AACX;AACA,OAAO;;AAEP;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,MAAM;AACN;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,MAAM;AACN;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,uCAAuC,uBAAuB;AAC9D;AACA;AACA;AACA,0CAA0C,sBAAsB;AAChE;AACA;AACA;AACA,wCAAwC,oBAAoB;AAC5D;AACA;AACA;AACA,+CAA+C,2BAA2B;AAC1E;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;;ACnjC+B;AACI;AACE;AACG;AACD;AACF;;AAEtB;AACf;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA,yBAAyB,cAAc;AACvC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA,aAAa,uBAAuB;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,8CAA8C,mBAAmB;AACjE;AACA;AACA,0BAA0B,iBAAiB;AAC3C;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,aAAa,WAAW,YAAY;AACpC,YAAY;AACZ;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,aAAa,IAAI;AACjB;AACA;AACA;AACA;;AAEA;AACA;AACA,aAAa,IAAI;AACjB;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,aAAa,OAAO;AACpB;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,aAAa,OAAO;AACpB;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,aAAa,OAAO;AACpB;AACA;AACA;AACA,4BAA4B,oBAAoB;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oCAAoC,cAAc;;AAElD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN,4BAA4B,oBAAoB;AAChD;AACA;AACA;AACA,QAAQ;AACR,+CAA+C,cAAc;AAC7D,8BAA8B,KAAK;AACnC;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,aAAa,OAAO;AACpB;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA,MAAM;AACN,4BAA4B,oBAAoB;AAChD;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA,oBAAoB,6BAA6B;AACjD;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,KAAK;;AAEL;AACA;;AAEA;AACA,oBAAoB,+BAA+B;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA,6BAA6B,mBAAmB,IAAI;AACpD;AACA;AACA,0BAA0B,OAAO;AACjC;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,OAAO;AACP;AACA;AACA;AACA,OAAO;AACP;AACA;AACA;AACA,OAAO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO;AACP;AACA;AACA,OAAO;AACP;AACA;AACA,OAAO;AACP;AACA;AACA,OAAO;AACP;AACA;AACA;AACA;AACA,OAAO;AACP;AACA;AACA,OAAO;AACP;AACA;AACA,OAAO;AACP;AACA;AACA,OAAO;AACP;AACA;AACA,OAAO;AACP;AACA;AACA,OAAO;AACP;AACA;AACA;AACA,OAAO;AACP;AACA;AACA;AACA,OAAO;AACP;AACA;AACA,OAAO;AACP;AACA;AACA,OAAO;AACP;AACA;AACA;AACA,OAAO;AACP;AACA;AACA,OAAO;AACP;AACA;AACA,OAAO;AACP;AACA;AACA;AACA,OAAO;AACP;AACA;AACA;AACA,OAAO;AACP;AACA;AACA,OAAO;AACP;AACA;AACA;AACA,OAAO;AACP;AACA;AACA;AACA,OAAO;AACP;AACA;AACA;AACA,OAAO;AACP;AACA;AACA;AACA,OAAO;AACP;AACA;AACA;AACA,OAAO;AACP;AACA;AACA;AACA,OAAO;;AAEP,MAAM;AACN,MAAM,iBAAY;AAClB,MAAM,iBAAY;AAClB;AACA;AACA;;AAEA,oBAAoB,kCAAkC;AACtD;AACA;AACA;AACA;;AAEA,oCAAoC,kBAAkB;AACtD;;AAEA,kBAAkB,iDAAiD,IAAI;AACvE;AACA,IAAI,iBAAY;AAChB,6BAA6B,MAAM;AACnC;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;;AAEA,kBAAkB,eAAe;AACjC,2BAA2B,kBAAkB;AAC7C;;AAEA,uBAAuB;AACvB,iBAAiB,kBAAkB,qEAAqE,IAAI;AAC5G;AACA,mBAAmB,cAAc,iDAAiD;AAClF;AACA;;AAEA,IAAI,iBAAY,uBAAuB,OAAO;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,kBAAkB,eAAe;;AAEjC;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,2BAA2B,kBAAkB;AAC7C;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,aAAa,QAAQ;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,aAAa,QAAQ;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,gCAAgC,gDAAgD;AAChF;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAGA;AACA;AACA,aAAa,SAAS;AACtB;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;AACA,aAAa,SAAS;AACtB;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA,IAAI,iBAAY,6BAA6B,OAAO,GAAG,GAAG;AAC1D;AACA;AACA;AACA,UAAU,iBAAY,gBAAgB,KAAK;AAC3C;AACA,UAAU;AACV,UAAU,iBAAY,gBAAgB,KAAK;AAC3C;AACA;AACA,OAAO;AACP;AACA;;AAEA;AACA;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA;AACA,IAAI,iBAAY,mBAAmB,IAAI;AACvC;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA,IAAI,iBAAY,oCAAoC,IAAI,iBAAiB,YAAY;AACrF;AACA;AACA;;AAEA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA;AACA;;AAEA;AACA,IAAI,iBAAY,2BAA2B,GAAG;AAC9C;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,IAAI,iBAAY,0BAA0B,GAAG;AAC7C;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,MAAM,iBAAY,8BAA8B,uBAAuB;AACvE;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA,MAAM,iBAAY;AAClB;AACA,QAAQ,iBAAY,2BAA2B,SAAS;AACxD;AACA,OAAO;AACP;AACA,QAAQ,iBAAY,mCAAmC,SAAS;AAChE;AACA,OAAO;AACP;;AAEA;AACA,6CAA6C,YAAY;AACzD;AACA;AACA,uBAAuB,cAAc,iEAAiE;AACtG;AACA;AACA,QAAQ,iBAAY,kBAAkB,UAAU,MAAM,OAAO;;AAE7D;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AC59BmD;AACZ;;AAEvC,QAAQ,4CAA4C;;AAEpD;;AAEA;;AAEA;AACA;AACA;AACA,gBAAgB,0BAAqB;AACrC;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN,MAAM,gBAAW,sCAAsC,gBAAgB;AACvE,2DAA2D,gBAAgB;AAC3E;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA,uBAAuB;AACvB;AACA;AACA,KAAK;;AAEL;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAgB,0BAAqB;AACrC;AACA;AACA;AACA;AACA;AACA,iBAAiB,oBAAoB;AACrC;AACA;AACA,gBAAgB,qBAAqB,GAAG,sBAAsB,IAAI,UAAU;AAC5E,gBAAgB,qBAAqB,IAAI,UAAU;AACnD;AACA,OAAO;AACP,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAmB;AACnB,SAAS;AACT,qBAAqB,kBAAkB;AACvC;AACA;AACA;AACA;AACA;AACA,gEAAgE,IAAI;AACpE;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA,wDAAe,YAAY,EAAC;;;ACnLW;AACC;AACW;;AAEnD,QAAQ,aAAa,kEAA4B,sDAAG;AACpD,MAAM,8BAAO;;;AAGN,qCAAqC,iDAA0B,CAAC,oCAAa;;AAEpF;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO;AACP;AACA;AACA;AACA;AACA;AACA,OAAO;AACP;;AAEA,aAAa;AACb;;AAEA;AACA;AACA;AACA;AACA,0BAA0B,+BAA0B;;AAEpD,2BAA2B,8BAAO;AAClC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,8BAA8B,2BAAsB;AACpD;AACA;AACA;AACA,SAAS;;AAET,kDAAkD,iBAAiB;AACnE,OAAO;AACP,KAAK;AACL;;AAEA;;;AAGA;AACA;AACA,YAAY,uCAAuC;AACnD,aAAa,aAAa;AAC1B,aAAa,iBAAiB;AAC9B,aAAa,kBAAkB;AAC/B,eAAe;AACf;AACA;AACA;;AAEA;AACA,uCAAuC,aAAa;AACpD;AACA;AACA;AACA;AACA,eAAe,2BAA2B;AAC1C;AACA,uBAAuB,+BAA0B,CAAC,0BAAqB;AACvE,OAAO;AACP;;AAEA;AACA;AACA;AACA,uDAAuD,WAAW;AAClE,YAAY,iBAAiB;AAC7B;AACA,QAAQ,iBAAY;AACpB;AACA;AACA,SAAS;AACT;AACA,MAAM;AACN;AACA;AACA,6CAA6C,WAAW;AACxD,YAAY,iBAAiB;AAC7B;AACA,QAAQ,iBAAY;AACpB;AACA;AACA,SAAS;AACT;AACA,MAAM;AACN;AACA;AACA;AACA;AACA,MAAM,iBAAY;AAClB;AACA;AACA;AACA;AACA,OAAO;AACP;AACA;;AAEA;AACA;;;ACrKqC;AACE;AACV;AAC4B;AACN;AACX;AACmC;;AAE3E,QAAQ,aAAa,qDAA4B,yCAAG;;AAErC,wBAAwB,oCAA0B,CAAC,uBAAa;;AAE/E;AACA,0BAA0B,mBAAmB;AAC7C;AACA,IAAI,iBAAY;;AAEhB;AACA;AACA,wBAAwB,QAAQ;AAChC,8DAA8D,QAAQ,GAAG,EAAE,kBAAkB,EAAE,IAAI,EAAE;AACrG,gCAAgC,kDAAkD,EAAE,GAAG,GAAG,EAAE;AAC5F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO;AACP,KAAK;AACL;AACA;;AAEA;AACA,0BAA0B,mBAAmB;AAC7C;AACA,IAAI,iBAAY;;AAEhB,oBAAoB,SAAS;AAC7B,iFAAiF,EAAE;AACnF,qCAAqC,kDAAkD,EAAE,EAAE;AAC3F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA,uEAAuE,mBAAmB;AAC1F;AACA;AACA;;AAEA;AACA,IAAI,iBAAY,+CAA+C,cAAc;;AAE7E,gBAAgB,0BAAqB;AACrC,8EAA8E,oBAAoB;;AAElG;AACA;;AAEA;AACA,uCAAuC,mBAAmB;AAC1D,4CAA4C,mBAAmB;AAC/D;AACA,oDAAoD,mBAAmB;AACvE;AACA,wCAAwC,mBAAmB;AAC3D;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO;AACP;AACA;AACA;AACA;AACA,OAAO;AACP;AACA;AACA;AACA;AACA;AACA;AACA,OAAO;AACP;AACA,aAAa,0BAA0B;AACvC;AACA;AACA,OAAO;AACP;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO;AACP;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO;AACP;;AAEA;AACA;;AAEA;AACA;AACA,iCAAiC,OAAO;AACxC;AACA;AACA;AACA,aAAa,iBAAiB;AAC9B;AACA;AACA;;AAEA;AACA,2BAA2B,2BAAsB;AACjD;AACA,wBAAwB,iBAAiB;AACzC;AACA,oDAAoD,KAAK;AACzD,OAAO;;AAEP;AACA;AACA,sBAAsB,0BAA0B;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,4CAA4C,mBAAmB;AAC/D,IAAI,iBAAY,0BAA0B,cAAc;AACxD,gBAAgB,0BAAqB;AACrC;AACA,6DAA6D,oBAAoB;AACjF;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA,4CAA4C,mBAAmB;AAC/D,IAAI,iBAAY,0BAA0B,cAAc;AACxD,gBAAgB,0BAAqB;AACrC;AACA,6DAA6D,oBAAoB;AACjF;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,0CAA0C,mBAAmB;;AAE7D;AACA;AACA;AACA;AACA;AACA,OAAO;AACP;;AAEA,+BAA+B,sBAAsB;AACrD;AACA;AACA;AACA;AACA;AACA,OAAO;AACP;;AAEA,kCAAkC,0BAA0B;AAC5D;AACA;AACA;AACA;AACA;AACA,OAAO;AACP;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,MAAM;AACN;AACA,MAAM;AACN;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,qBAAqB,WAAW;AAChC;AACA;AACA;AACA;AACA;AACA;AACA,2CAA2C,mBAAmB;AAC9D;AACA;AACA,UAAU,YAAY;AACtB;AACA,2CAA2C,mBAAmB;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,mBAAmB;AAC7D,qCAAqC,mBAAmB;AACxD;AACA;AACA;AACA,yCAAyC,mBAAmB;AAC5D,sCAAsC,mBAAmB;AACzD;AACA,0CAA0C,mBAAmB;AAC7D;AACA;AACA,yCAAyC,mBAAmB;AAC5D;AACA;AACA;AACA;AACA,6BAA6B,uBAAuB,SAAS,gBAAgB,GAAG,iBAAiB;AACjG,4BAA4B,uBAAuB,QAAQ,gBAAgB,GAAG,iBAAiB;AAC/F;AACA;;AAEA;AACA;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;;AAEA;;AAEA;AACA;AACA;AACA,0CAA0C,mBAAmB;AAC7D,4BAA4B,iBAAiB;;AAE7C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,QAAQ,cAAc;AACtB,mDAAmD,eAAe;AAClE;AACA,OAAO;AACP,KAAK;;AAEL;AACA;;AAEA;AACA;AACA;AACA,QAAQ,aAAa;AACrB,WAAW,0BAA0B;AACrC;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,4BAA4B,cAAc;;AAE1C;AACA;AACA,yBAAyB,0BAAqB;AAC9C;AACA;AACA,aAAa,mBAAmB,GAAG,UAAU,WAAW,iBAAiB;AACzE,uDAAuD,iBAAiB;AACxE;AACA,eAAe,gCAAgC,GAAG,iBAAiB;;AAEnE;AACA;AACA;AACA,OAAO;;AAEP;AACA;AACA;;AAEA;AACA;AACA,cAAc,UAAU,GAAG,OAAO,EAAE,QAAQ,GAAG,iBAAiB;AAChE;;AAEA;AACA;AACA;AACA,6BAA6B,qBAAqB;AAClD,MAAM,iBAAY,qBAAqB,SAAS;AAChD;AACA;AACA;;AAEA;AACA;AACA;AACA,6BAA6B,qBAAqB;AAClD,MAAM,iBAAY,sBAAsB,SAAS;AACjD;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,kDAAkD,KAAK;AACvD;AACA;AACA;AACA;;AAEA;;AAEA;AACA,mEAAmE;AACnE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,cAAc;AACtC,4FAA4F,mBAAmB;AAC/G,MAAM,iBAAY;AAClB,wBAAwB,IAAI;AAC5B,uCAAuC,kBAAkB;;AAEzD;AACA;AACA,MAAM;AACN;AACA,oFAAoF,KAAK;AACzF;AACA;AACA;AACA,UAAU;AACV;AACA;AACA,QAAQ;AACR;AACA;AACA;;AAEA;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,2BAA2B,iBAAY,eAAe,8DAA8D;AACpH,wBAAwB,aAAa;AACrC;AACA;AACA;AACA,2BAA2B,iBAAY,gBAAgB,+DAA+D;AACtH,wBAAwB,aAAa;AACrC;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,yDAAyD,WAAW;AACpE,sDAAsD,WAAW;AACjE;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA,0CAA0C,WAAW;AACrD;AACA,SAAS;AACT,oDAAoD,YAAY;AAChE;AACA,SAAS;;AAET;AACA;AACA,UAAU;AACV;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wDAAwD,WAAW;AACnE,sDAAsD,WAAW;AACjE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,sBAAsB;AACvD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wBAAwB,8CAA8C;AACtE,WAAW;AACX,oCAAoC,0BAA0B;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,UAAU;AACV;AACA;AACA;AACA;AACA;AACA,QAAQ,iBAAY;AACpB;AACA;AACA;AACA,SAAS;AACT;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA,0BAA0B,YAAY;AACtC,kCAAkC,eAAe;AACjD;AACA;AACA;AACA;AACA;AACA,kDAAkD,iBAAiB;AACnE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,qCAAqC;AACzD;AACA,iBAAiB,kDAAkD;AACnE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAa;AACb,WAAW;AACX;AACA,SAAS;AACT;AACA;AACA,8BAA8B,cAAc;AAC5C,sCAAsC,eAAe;AACrD,YAAY;AACZ,YAAY,iBAAY;AACxB;AACA;AACA;AACA;AACA;AACA;AACA,sEAAsE,eAAe;AACrF;AACA;AACA;AACA,uEAAuE,+BAA+B;AACtG;AACA;AACA;AACA,+CAA+C,mBAAmB;AAClE,6BAA6B,qBAAqB;AAClD;AACA;AACA;AACA;AACA,gCAAgC,cAAc,iDAAiD,eAAe;AAC9G;AACA,SAAS;AACT;AACA;AACA;AACA,8BAA8B,sBAAsB;AACpD,2BAA2B,aAAa;AACxC;AACA;AACA;AACA;AACA;;AAEA;AACA,0BAA0B,mBAAmB;AAC7C,iCAAiC,0BAA0B;AAC3D;AACA,0BAA0B,mBAAmB;AAC7C;AACA;AACA;;AAEA;AACA;AACA,MAAM,iBAAY;AAClB;AACA;AACA;AACA,MAAM,iBAAY;AAClB;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,MAAM,iBAAY;AAClB,wBAAwB,cAAc;AACtC,MAAM,iBAAY;;AAElB;AACA;AACA;AACA;AACA;AACA,sCAAsC,8BAA8B;AACpE;AACA,MAAM;AACN;AACA,QAAQ,iBAAY,oCAAoC,OAAO;AAC/D,QAAQ;AACR,0FAA0F,sCAAsC;AAChI;AACA,QAAQ,iBAAY;AACpB;AACA;AACA;AACA;;AAEA;AACA,wCAAwC,mBAAmB;AAC3D;AACA;AACA,mCAAmC,mBAAmB;AACtD,MAAM;AACN;AACA;AACA;AACA,uCAAuC,mBAAmB;AAC1D;AACA;;AAEA;AACA;AACA,uCAAuC,mBAAmB;AAC1D;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,+BAA+B,mBAAmB;AAClD;AACA;AACA;AACA,iCAAiC,mBAAmB;AACpD;AACA;AACA,iCAAiC,mBAAmB;AACpD;AACA;AACA;AACA,iCAAiC,mBAAmB;AACpD;AACA;AACA;;AAEA;AACA;AACA,wCAAwC,mBAAmB;AAC3D;;AAEA;AACA;AACA,4BAA4B,mBAAmB;AAC/C,4BAA4B,mBAAmB;AAC/C;AACA;;AAEA;AACA,WAAW,kBAAkB,mBAAmB,qBAAqB,GAAG,SAAS;AACjF;AACA;AACA;;AAEA;;AAEA;AACA,sBAAsB,0BAAqB;AAC3C;AACA,0BAA0B,cAAc;AACxC,wCAAwC,wFAAwF;AAChI,QAAQ;AACR,+FAA+F,wBAAwB;AACvH;AACA;AACA;AACA;;AAEA;AACA,8CAA8C,mBAAmB;AACjE;;AAEA;AACA,WAAW,kBAAkB,mBAAmB,qBAAqB,GAAG,SAAS;AACjF;AACA;AACA;;AAEA;AACA,sBAAsB,0BAAqB;AAC3C;AACA,0BAA0B,cAAc;AACxC,wCAAwC,8EAA8E;AACtH,QAAQ;AACR,8FAA8F,uBAAuB;AACrH;AACA;AACA;AACA;;AAEA;AACA;AACA,8CAA8C,mBAAmB;AACjE,4CAA4C,mBAAmB;AAC/D;;AAEA;AACA,sBAAsB,0BAAqB;AAC3C;AACA,0BAA0B,cAAc;AACxC,wCAAwC,8FAA8F;AACtI,QAAQ;AACR,iGAAiG,0BAA0B;AAC3H;AACA;AACA;AACA;;AAEA;AACA,0CAA0C,mBAAmB;AAC7D;AACA,IAAI,kBAAkB;AACtB;;AAEA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;;AAEL;AACA;AACA,4BAA4B,kDAAkD,GAAG,oDAAoD;AACrI,uBAAuB,IAAI,yBAAyB,mBAAmB;AACvE;AACA,MAAM;AACN,uBAAuB,IAAI,yBAAyB,mBAAmB;AACvE;AACA;;AAEA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA,GAAG;AACH;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH,CAAC;;;ACj9BuC;AACD;;AAEvC,QAAQ,aAAa,wDAA4B,4CAAG;;AAErC,2BAA2B,uCAA0B,CAAC,0BAAa;;AAElF;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO;AACP,sBAAsB,cAAc,WAAW,oBAAoB;AACnE;AACA;AACA,OAAO;AACP,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,MAAM,iBAAY,eAAe,OAAO;AACxC;AACA;AACA;AACA,YAAY,SAAS,UAAU,gBAAgB,sCAAsC,GAAG;AACxF;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA,0DAA0D,yBAAyB;AACnF;AACA;AACA,iFAAiF,4BAA4B;AAC7G,SAAS;AACT;;AAEA;AACA,MAAM;AACN;AACA;AACA,wEAAwE,yBAAyB;AACjG;AACA,MAAM,iBAAY;AAClB;AACA;AACA;;AAEA;AACA,kDAAkD,KAAK;AACvD;AACA,yBAAyB,kCAAkC;AAC3D,6BAA6B,oDAAoD,GAAG,kCAAkC,OAAO,KAAK;AAClI;;AAEA;AACA;AACA,6BAA6B,oDAAoD,GAAG,KAAK;AACzF;AACA;;;ACnHmD;;AAEnD;AACA;AACA;;AAEA,MAAM,mBAAO;;AAEb,0BAA0B,mBAAO;AACjC,0BAA0B;AAC1B;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA,oBAAoB,0BAAqB;AACzC,WAAW,mBAAO,uDAAuD,wBAAwB;AACjG;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,YAAY,mBAAmB,GAAG,aAAa,IAAI,WAAW;AAC9D,YAAY,mBAAmB,IAAI,WAAW;AAC9C;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0B,aAAa;AACvC,SAAS;AACT;AACA;AACA,KAAK;AACL;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,CAAC;;AAED,uDAAe,WAAW,EAAC;;;AC/GqB;AACV;AACC;;AAEvC,QAAQ,aAAa,oDAA4B,wCAAG;;AAEpD,gCAAgC,mCAA0B,CAAC,sBAAa;;AAExE;AACA;AACA,2BAA2B,mBAAmB,CAAC;AAC/C,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA,oCAAoC,mBAAmB,CAAC;AACxD,oCAAoC,mBAAmB,CAAC;AACxD,OAAO;AACP;AACA;;AAEA;AACA;AACA,sBAAsB,mBAAmB;AACzC;AACA;;AAEA,+BAA+B,mCAA0B,CAAC,sBAAa;;AAEvE;AACA;AACA,2BAA2B,mBAAmB,CAAC;AAC/C,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA,oCAAoC,mBAAmB,CAAC;AACxD,oCAAoC,mBAAmB,CAAC;AACxD,OAAO;AACP;AACA;;AAEA;AACA;AACA,sBAAsB,mBAAmB;AACzC;AACA;;AAEA,4BAA4B,mCAA0B,CAAC,sBAAa;;AAEpE;AACA;AACA,2BAA2B,mBAAmB,CAAC;AAC/C,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA,gBAAgB,mBAAmB,CAAC;AACpC,KAAK;AACL;;AAEA;AACA;AACA,iCAAiC,mBAAmB,CAAC;AACrD;;AAEA;AACA;AACA;AACA;AACA,2CAA2C,mBAAmB,CAAC;AAC/D;AACA,uCAAuC,mBAAmB,CAAC;AAC3D,6CAA6C,mBAAmB,CAAC;AACjE,SAAS;AACT;AACA,uCAAuC,mBAAmB,CAAC;AAC3D,6CAA6C,mBAAmB,CAAC;AACjE,SAAS;AACT;AACA,uCAAuC,mBAAmB,CAAC;AAC3D,6CAA6C,mBAAmB,CAAC;AACjE,SAAS;AACT;AACA,uCAAuC,mBAAmB,CAAC;AAC3D,6CAA6C,mBAAmB,CAAC;AACjE,SAAS;AACT;AACA,uCAAuC,mBAAmB,CAAC;AAC3D,6CAA6C,mBAAmB,CAAC;AACjE,SAAS;AACT,OAAO;AACP;AACA;;AAEA;AACA;AACA;AACA;AACA,gCAAgC,mBAAmB;AACnD,gCAAgC,mBAAmB;AACnD,gCAAgC,mBAAmB;AACnD,gCAAgC,mBAAmB;AACnD,gCAAgC,mBAAmB,wDAAwD,mBAAmB,CAAC;AAC/H,gCAAgC,mBAAmB,8CAA8C,mBAAmB,CAAC;AACrH,gCAAgC,mBAAmB;AACnD;AACA;AACA;AACA,gCAAgC,mBAAmB;AACnD,gCAAgC,mBAAmB;AACnD,gCAAgC,mBAAmB;AACnD,gCAAgC,mBAAmB;AACnD,gCAAgC,mBAAmB,wDAAwD,mBAAmB,CAAC;AAC/H,gCAAgC,mBAAmB,8CAA8C,mBAAmB,CAAC;AACrH,gCAAgC,mBAAmB;AACnD;AACA;AACA;AACA,gCAAgC,mBAAmB;AACnD,gCAAgC,mBAAmB;AACnD,gCAAgC,mBAAmB;AACnD,gCAAgC,mBAAmB;AACnD,gCAAgC,mBAAmB,wDAAwD,mBAAmB,CAAC;AAC/H,gCAAgC,mBAAmB,8CAA8C,mBAAmB,CAAC;AACrH,gCAAgC,mBAAmB;AACnD;AACA;AACA;AACA,gCAAgC,mBAAmB;AACnD,gCAAgC,mBAAmB;AACnD,gCAAgC,mBAAmB;AACnD,gCAAgC,mBAAmB;AACnD,gCAAgC,mBAAmB;AACnD,gCAAgC,mBAAmB,8CAA8C,mBAAmB,CAAC;AACrH,gCAAgC,mBAAmB;AACnD;AACA;AACA;AACA,gCAAgC,mBAAmB;AACnD,gCAAgC,mBAAmB;AACnD,gCAAgC,mBAAmB;AACnD,gCAAgC,mBAAmB;AACnD,gCAAgC,mBAAmB;AACnD,gCAAgC,mBAAmB;AACnD,gCAAgC,mBAAmB;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;;AAEO;;AAEP,yBAAyB,mBAAmB;AAC5C;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C;AACA;AACA;AACA;AACA,GAAG;;AAEH;AACA,6BAA6B,mBAAmB;AAChD,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC;AACA;AACA;AACA,GAAG;;AAEH,6BAA6B,mBAAmB;AAChD,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC;AACA;AACA;AACA,GAAG;;AAEH,6BAA6B,mBAAmB;AAChD,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC;AACA;AACA;AACA,GAAG;;AAEH;AACA,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA;AACA,GAAG;;;AAGH;AACA,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA,eAAe,8BAA8B;AAC7C;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH;;AAEA,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC,UAAU,oBAAe;AACzB,uBAAuB,cAAc,CAAC;AACtC;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC,UAAU,oBAAe;AACzB,uBAAuB,cAAc,CAAC;AACtC;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC,UAAU,oBAAe;AACzB,uBAAuB,cAAc,CAAC;AACtC;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC,UAAU,oBAAe;AACzB,uBAAuB,cAAc,CAAC;AACtC;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH;AACA,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;;AAGH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH;;AAEA,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA,UAAU,oBAAe;AACzB,uBAAuB,cAAc,CAAC;AACtC,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH;AACA,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA,UAAU,oBAAe;AACzB,uBAAuB,cAAc,CAAC;AACtC,GAAG;;AAEH;;AAEA,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;;AAGH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH,yBAAyB,mBAAmB;AAC5C,aAAa,mBAAmB,CAAC;AACjC,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,GAAG;;AAEH,EAAE,iBAAY;AACd;;;ACpoBiD;AACO;AACpB;AACE;AACC;AACgB;AAChB;AACU;;AAE1C;AACP,EAAE,gBAAgB;AAClB;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW,GAAG;AACd,WAAW,GAAG;AACd;AACA;AACA;AACA,gDAAgD,mBAAmB,CAAC;AACpE,0BAA0B,mBAAmB;AAC7C;;AAEA;AACA;AACA,GAAG;;AAEH,EAAE,iBAAY;AACd,wBAAwB,SAAS;AACjC,qBAAqB,aAAa;;AAElC;;AAEA,wDAAwD,oBAAoB,IAAI;AAChF,oCAAoC,mBAAmB;AACvD;AACA;;AAEA,wBAAwB,mBAAmB;AAC3C;AACA,IAAI;AACJ;AACA;AACA;AACA;AACA,qCAAqC,mBAAmB;AACxD;AACA;AACA;;AAEA;;;AAGA;AACA,wBAAwB,mBAAmB;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,EAAE,iBAAY;AACd,qBAAqB,YAAY;;AAEjC;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,8DAA8D,+CAA+C;AAC7G;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,kFAAkF,0DAA0D;AAC5I;AACA;AACA,iBAAiB,gBAAgB,GAAG,UAAU,WAAW,YAAY;AACrE;AACA;;AAEA,EAAE,iBAAY;AACd;AACA;AACA;AACA;AACA;AACA,IAAI;AACJ;AACA;AACA;AACA;AACA;AACA,KAAK;AACL,wEAAwE,+BAA+B;AACvG;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,MAAM;AACN;AACA;AACA,IAAI;AACJ;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA,qCAAqC,mBAAmB;AACxD;;AAEA;AACA;;AAEA;AACA,kCAAkC,mBAAmB;AACrD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,UAAU;AACtC;;AAEA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,cAAc;AAClC;;AAEA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBAAoB,cAAc;AAClC;;AAEA;;AAEA;;;AAGA;AACA;AACA;AACA,IAAI;AACJ;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA,IAAI;AACJ;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;;AAEO,4CAA4C;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,EAAE,iBAAY,0BAA0B,WAAW;AACnD;AACA;AACA,GAAG;AACH,wBAAwB,SAAS;;AAEjC;AACA,8EAA8E,iDAAiD;AAC/H,YAAY,eAAe;AAC3B;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,wBAAwB,IAAI,8BAA8B,mBAAmB;AAC7E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,6CAA6C,mBAAmB;AAChE,uCAAuC,mBAAmB;AAC1D,yCAAyC,mBAAmB;AAC5D,wCAAwC,mBAAmB;;AAE3D;AACA,IAAI,+BAA0B,CAAC,0BAAqB;AACpD,IAAI,+BAA0B,CAAC,0BAAqB;AACpD,IAAI,+BAA0B,CAAC,0BAAqB;AACpD,IAAI,+BAA0B,CAAC,0BAAqB;AACpD;;AAEA,oEAAoE,mBAAmB;;AAEvF;;;AAGA;AACA;AACA;AACA;AACA;AACA,yBAAyB,mBAAmB;AAC5C;AACA;AACA;;AAEA,sCAAsC,mBAAmB;AACzD;AACA;;AAEA;AACA;AACA;AACA,WAAW,mBAAmB;AAC9B;AACA,GAAG;AACH;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,yBAAyB,mBAAmB;AAC5C;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc;AACd;AACA,QAAQ,iBAAY,yCAAyC,oBAAoB;AACjF;AACA,OAAO;AACP,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,uDAAuD,cAAc;AACrE;;AAEA;AACA,yDAAyD,mBAAmB;AAC5E,uDAAuD,mBAAmB;AAC1E;AACA;AACA;;AAEA;AACA;AACA;AACA,qDAAqD;;AAErD;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAAS;AAChB,KAAK;AACL,GAAG;AACH;;AAEA;AACA;AACA;AACA;AACA;AACA,yBAAyB,mBAAmB;AAC5C;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,mBAAmB,mBAAmB;AACtC;;AAEA;AACA;AACA;AACA,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA,yBAAyB,YAAY;AACrC,sBAAsB,aAAa;AACnC;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA,GAAG;AACH;;AAEA;AACA;;AAEA;AACA,aAAa,mBAAmB,CAAC;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ,iBAAY,aAAa,UAAU;AAC3C;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,KAAK;AACL,GAAG;AACH;;AAEA;AACA;AACA;AACA;AACA;AACA,yBAAyB,mBAAmB;AAC5C;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ,iBAAY,eAAe,SAAS;AAC5C;AACA;AACA,KAAK;AACL;AACA;AACA;AACA,8BAA8B,mBAAmB;AACjD,KAAK;AACL,GAAG;AACH;;AAEO;AACP,EAAE,gBAAW;AACb;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,CAAC;;AAED;AACA;AACA,CAAC;;AAED;AACA;;AAEA;AACA;AACA;AACA;AACA,CAAC;;AAED;AACA;AACA,CAAC;;;ACjjBwC;;AAEzC;AACA,iBAAiB,IAAI;AACrB,oBAAoB,KAAK","sources":["webpack://tokenizer/./src/libs/logger.js","webpack://tokenizer/./src/libs/DirectoryPicker.js","webpack://tokenizer/./src/libs/Utils.js","webpack://tokenizer/./src/libs/MarchingSquares.js","webpack://tokenizer/./src/constants.js","webpack://tokenizer/./src/libs/Color.js","webpack://tokenizer/./src/libs/RayMask.js","webpack://tokenizer/./src/tokenizer/Masker.js","webpack://tokenizer/./vendor/MagicWand.js","webpack://tokenizer/./src/tokenizer/MagicLasso.js","webpack://tokenizer/./src/tokenizer/Layer.js","webpack://tokenizer/./src/tokenizer/Control.js","webpack://tokenizer/./src/tokenizer/View.js","webpack://tokenizer/./src/libs/ImageBrowser.js","webpack://tokenizer/./src/libs/TokenizerSaveLocations.js","webpack://tokenizer/./src/tokenizer/Tokenizer.js","webpack://tokenizer/./src/tokenizer/AutoTokenize.js","webpack://tokenizer/./src/libs/ImagePicker.js","webpack://tokenizer/./src/settings.js","webpack://tokenizer/./src/hooks.js","webpack://tokenizer/./src/index.js"],"sourcesContent":["const logger = {\n  _showMessage: (logLevel, data) => {\n    if (!logLevel || !data || typeof logLevel !== \"string\") {\n      return false;\n    }\n\n    const setting = game.settings.get(\"vtta-tokenizer\", \"log-level\");\n    const logLevels = [\"DEBUG\", \"INFO\", \"WARN\", \"ERR\", \"OFF\"];\n    const logLevelIndex = logLevels.indexOf(logLevel.toUpperCase());\n    if (\n      setting == \"OFF\"\n      || logLevelIndex === -1\n      || logLevelIndex < logLevels.indexOf(setting)\n    ) {\n      return false;\n    }\n    return true;\n  },\n  log: (logLevel, ...data) => {\n    if (!logger._showMessage(logLevel, data)) {\n      return;\n    }\n\n    logLevel = logLevel.toUpperCase();\n\n    const LOG_PREFIX = \"Tokenizer\";\n    let msg\n      = \"No logging message provided.  Please see the payload for more information.\";\n    let payload = data.slice();\n    if (data[0] && typeof (data[0] == \"string\")) {\n      msg = data[0];\n      if (data.length > 1) {\n        payload = data.slice(1);\n      } else {\n        payload = null;\n      }\n    }\n    msg = `${LOG_PREFIX} | ${logLevel} > ${msg}`;\n    switch (logLevel) {\n      case \"DEBUG\":\n        if (payload) {\n          console.debug(msg, ...payload); // eslint-disable-line no-console\n        } else {\n          console.debug(msg); // eslint-disable-line no-console\n        }\n        break;\n      case \"INFO\":\n        if (payload) {\n          console.info(msg, ...payload); // eslint-disable-line no-console\n        } else {\n          console.info(msg); // eslint-disable-line no-console\n        }\n        break;\n      case \"WARN\":\n        if (payload) {\n          console.warn(msg, ...payload); // eslint-disable-line no-console\n        } else {\n          console.warn(msg); // eslint-disable-line no-console\n        }\n        break;\n      case \"ERR\":\n        if (payload) {\n          console.error(msg, ...payload); // eslint-disable-line no-console\n        } else {\n          console.error(msg); // eslint-disable-line no-console\n        }\n        break;\n      default:\n        break;\n    }\n  },\n\n  debug: (...data) => {\n    logger.log(\"DEBUG\", ...data);\n  },\n\n  info: (...data) => {\n    logger.log(\"INFO\", ...data);\n  },\n\n  warn: (...data) => {\n    logger.log(\"WARN\", ...data);\n  },\n\n  error: (...data) => {\n    logger.log(\"ERR\", ...data);\n  },\n};\n\nexport default logger;\n","/**\n * Game Settings: Directory\n */\n\nimport logger from \"../libs/logger.js\";\nimport Utils from \"./Utils.js\";\n\nconst FPClass = foundry.applications.apps.FilePicker.implementation;\n\nclass DirectoryPicker extends FPClass {\n\n  static async uploadToPath(path, file) {\n    const options = DirectoryPicker.parse(path);\n    return FPClass.upload(options.activeSource, options.current, file, { bucket: options.bucket }, { notify: false });\n  }\n\n\n  // formats the data into a string for saving it as a GameSetting\n  static format(value) {\n    return value.bucket !== null\n      ? `[${value.activeSource}:${value.bucket}] ${value.path ?? value.current ?? \"\"}`\n      : `[${value.activeSource}] ${value.path ?? value.current ?? \"\"}`;\n  }\n\n  // parses the string back to something the FilePicker can understand as an option\n  static parse(inStr) {\n    const str = inStr ?? '';\n    let matches = str.match(/\\[(.+)\\]\\s*(.+)?/u);\n\n    if (matches) {\n      let [, source, current = ''] = matches;\n      current = current.trim();\n      const [s3, bucket] = source.split(\":\");\n      if (bucket !== undefined) {\n        return {\n          activeSource: s3,\n          bucket: bucket,\n          current: current,\n          fullPath: inStr,\n        };\n      } else {\n        return {\n          activeSource: s3,\n          bucket: null,\n          current: current,\n          fullPath: inStr,\n        };\n      }\n    }\n    // failsave, try it at least\n    return {\n      activeSource: \"data\",\n      bucket: null,\n      current: str,\n    };\n  }\n\n  static extractUrl(str) {\n    let options = DirectoryPicker.parse(str);\n    if (options.activeSource === \"data\" || options.activeSource === \"public\") {\n      return undefined;\n    } else {\n      return options.current;\n    }\n  }\n\n\n  static async forgeCreateDirectory(target) {\n    if (!target) return;\n    const response = await ForgeAPI.call('assets/new-folder', { path: target });\n    if (!response || response.error) {\n      throw new Error(response ? response.error : \"Unknown error while creating directory.\");\n    }\n  }\n\n  /**\n   * @param  {string} source\n   * @param  {string} target\n   * @param  {object} options={}\n   */\n  static async createDirectory(source, target, options = {}) {\n    if (!target) {\n      throw new Error(\"No directory name provided\");\n    }\n    if (typeof ForgeVTT !== \"undefined\" && ForgeVTT?.usingTheForge) {\n      return DirectoryPicker.forgeCreateDirectory(target);\n    }\n    return FPClass.createDirectory(source, target, options);\n  }\n\n  /**\n   * Verifies server path exists, and if it doesn't creates it.\n   *\n   * @param  {object} parsedPath - output from DirectoryPicker,parse\n   * @param  {string} targetPath - if set will check this path, else check parsedPath.current\n   * @returns {boolean} - true if verfied, false if unable to create/verify\n   */\n  static async verifyPath(parsedPath, targetPath = null) {\n    try {\n      const paths = (targetPath) ? targetPath.split(\"/\") : parsedPath.current.split(\"/\");\n      let currentSource = paths[0];\n\n      for (let i = 0; i < paths.length; i += 1) {\n        try {\n          if (currentSource !== paths[i]) {\n            currentSource = `${currentSource}/${paths[i]}`;\n          }\n          // eslint-disable-next-line no-await-in-loop\n          await DirectoryPicker.createDirectory(parsedPath.activeSource, `${currentSource}`, { bucket: parsedPath.bucket });\n\n        } catch (err) {\n          const errMessage = `${(err?.message ?? Utils.isString(err) ? err : err)}`.replace(/^Error: /, \"\").trim();\n          if (!errMessage.startsWith(\"EEXIST\") && !errMessage.startsWith(\"The S3 key\")) {\n            logger.error(`Error trying to verify path [${parsedPath.activeSource}], ${parsedPath.current}`, err);\n          }\n        }\n      }\n    } catch (err) {\n      return false;\n    }\n\n    return true;\n  }\n\n  static async getForgeUrl(directoryPath, filename) {\n    const dir = DirectoryPicker.parse(directoryPath);\n    const prefix = ForgeVTT.ASSETS_LIBRARY_URL_PREFIX + await ForgeAPI.getUserId();\n    return `${prefix}/${dir.current}/${filename}`;\n    \n  }\n\n  static async getFileUrl(directoryPath, filename) {\n    let uri;\n    try {\n      if (typeof ForgeVTT !== \"undefined\" && ForgeVTT?.usingTheForge) {\n        uri = await DirectoryPicker.getForgeUrl(directoryPath, filename);\n        return uri;\n      } else {\n        const dir = DirectoryPicker.parse(directoryPath);\n        if (dir.activeSource == \"data\") {\n          // Local on-server file system\n          uri = dir.current + \"/\" + filename;\n        } else if (dir.activeSource == \"forgevtt\") {\n          const status = ForgeAPI.lastStatus || (await ForgeAPI.status());\n          const userId = status.user;\n          uri = `https://assets.forge-vtt.com/${userId}/${dir.current}/${filename}`;\n        } else if (dir.activeSource == \"s3\") {\n          // S3 Bucket\n          uri = `https://${dir.bucket}.${game.data.files.s3.endpoint.hostname}/${dir.current}/${filename}`;\n        } else {\n          logger.error(\"Tokenizer cannot handle files stored in that location\", dir);\n        }\n      }\n    } catch (exception) {\n      throw new Error(`Unable to determine file URL for directoryPath \"${directoryPath}\" and filename \"${filename}\"`);\n    }\n    return encodeURI(uri);\n  }\n}\n\nexport default DirectoryPicker;\n","import DirectoryPicker from \"./DirectoryPicker.js\";\nimport logger from \"./logger.js\";\n\nconst SKIPPING_WORDS = [\n  \"the\", \"of\", \"at\", \"it\", \"a\",\n];\n\nexport default class Utils {\n\n  static isObject(obj) {\n    return typeof obj === 'object' && !Array.isArray(obj) && obj !== null;\n  }\n\n  static isString(str) {\n    return typeof str === 'string' || str instanceof String;\n  }\n\n  static htmlToDoc (text) {\n    const parser = new DOMParser();\n    return parser.parseFromString(text, \"text/html\");\n  }\n\n  static endsWithAny(suffixes, string) {\n    return suffixes.some((suffix) => {\n        return string.endsWith(suffix);\n    });\n  }\n\n  static dirPath(path) {\n    return path.split(\"/\").slice(0, -1).join(\"/\");\n  }\n\n  static generateUUID() {\n    // I generate the UID from two parts here\n    // to ensure the random number provide enough bits.\n    var firstPart = (Math.random() * 46656) || 0;\n    var secondPart = (Math.random() * 46656) || 0;\n    firstPart = (\"000\" + firstPart.toString(36)).slice(-3);\n    secondPart = (\"000\" + secondPart.toString(36)).slice(-3);\n    return firstPart + secondPart;\n  }\n\n  static U2A(str) {\n    let reserved = \"\";\n    const code = str.match(/&#(d+);/g);\n\n    if (code === null) {\n      return str;\n    }\n\n    for (var i = 0; i < code.length; i++) {\n      reserved += String.fromCharCode(code[i].replace(/[&#;]/g, \"\"));\n    }\n\n    return reserved;\n  }\n\n  static getElementPosition(obj) {\n    let curleft = 0,\n      curtop = 0;\n    if (obj.offsetParent) {\n      do {\n        curleft += obj.offsetLeft;\n        curtop += obj.offsetTop;\n      } while ((obj = obj.offsetParent));\n      return { x: curleft, y: curtop };\n    }\n    return undefined;\n  }\n\n  static getRelativeCoords(event) {\n    return { x: event.offsetX || event.layerX, y: event.offsetY || event.layerY };\n  }\n\n  static getCanvasCords(canvas, event) {\n    const elementRelativeX = event.offsetX;\n    const elementRelativeY = event.offsetY;\n    const canvasRelativeX = elementRelativeX * canvas.width / canvas.clientWidth;\n    const canvasRelativeY = elementRelativeY * canvas.height / canvas.clientHeight;\n    return { x: canvasRelativeX, y: canvasRelativeY };\n  }\n\n  static upload() {\n    let fileInput = document.createElement(\"input\");\n    fileInput.type = \"file\";\n    fileInput.click();\n\n    return new Promise((resolve, reject) => {\n      fileInput.addEventListener(\"change\", (event) => {\n        // setup the FileReader\n        let file = event.target.files[0];\n        let reader = new FileReader();\n        reader.addEventListener(\"load\", () => {\n          let img = document.createElement(\"img\");\n          img.addEventListener(\"load\", () => {\n            resolve(img);\n          });\n          img.src = reader.result;\n        });\n        if (file) {\n          reader.readAsDataURL(file);\n        } else {\n          reject(\"No input file given\");\n        }\n      });\n    });\n  }\n\n  /**\n   * Should the image use the proxy?\n   * @param {*} url\n   */\n  static async useProxy(url) {\n    if (\n      url.toLowerCase().startsWith(\"https://www.dndbeyond.com/\")\n      || url.toLowerCase().startsWith(\"https://dndbeyond.com/\")\n      || url.toLowerCase().startsWith(\"https://media-waterdeep.cursecdn.com/\")\n      || url.toLowerCase().startsWith(\"https://images.dndbeyond.com\")\n    ) {\n      return true;\n    } else if (\n      await game.settings.get(\"vtta-tokenizer\", \"force-proxy\")\n      && url.toLowerCase().match(\"^https?://\")\n    ) {\n      return true;\n    } else {\n      return false;\n    }\n  }\n\n  /**\n   * Converts url to proxied url\n   * @param {*} url\n   * @param {*} proxy\n   */\n  static proxiedUrl(url, proxy) {\n    if (proxy.match(\"%URL%\")) {\n      return proxy.replace(\"%URL%\", encodeURIComponent(url));\n    } else {\n      return proxy + url;\n    }\n  }\n\n  /**\n   * Downloads an image from a given URL\n   * @param {String} url URL of the image that we try to download\n   */\n  static async download(url) {\n    if (!url || url.trim() === \"\") url = \"icons/mystery-man.png\";\n    const proxy = await game.settings.get(\"vtta-tokenizer\", \"proxy\");\n    const useProxy = await Utils.useProxy(url);\n    const dateTag = `${+new Date()}`;\n    const forge = (typeof ForgeVTT !== \"undefined\" && ForgeVTT?.usingTheForge);\n    return new Promise((resolve, reject) => {\n      const proxyImg = useProxy ? Utils.proxiedUrl(url, proxy) : url;\n      // we remove existing data tag and add a new one\n      // this forces chrome to reload the image rather than using the cached value\n      // this can cause problems dues to https://stackoverflow.com/questions/12648809/cors-policy-on-cached-image\n      // an exception for using moulinette on the forge because of _reasons_\n      const imgSrc = forge && proxyImg.startsWith(\"moulinette\")\n        ? proxyImg\n        : `${proxyImg.split(\"?\")[0]}?${dateTag}`;\n      let img = new Image();\n      // cross origin needed for images from other domains\n      // an empty value here defaults to anonymous\n      img.crossOrigin = \"\";\n      img.onerror = function(event) {\n        logger.error(\"Download listener error\", event);\n        reject(event);\n      };\n      img.onload = function() {\n        logger.debug(\"Loading image:\", img);\n        resolve(img);\n      };\n      // add image source after adding handlers\n      img.src = imgSrc;\n    });\n  }\n\n  static async uploadToFoundry(data, directoryPath, fileName) {\n    // create new file from the response\n    let file = new File([data], fileName, { type: data.type });\n\n    const options = DirectoryPicker.parse(directoryPath);\n\n    logger.debug(`Uploading ${fileName}`, { directoryPath, fileName, options });\n\n    const FPClass = foundry.applications.apps.FilePicker.implementation;\n    const result = await FPClass.upload(options.activeSource, options.current, file, { bucket: options.bucket }, { notify: false });\n\n    return result.path;\n  }\n\n  static rgbToHex(r, g, b) {\n    if (r > 255 || g > 255 || b > 255) throw new Error('Invalid color component');\n    // eslint-disable-next-line no-bitwise\n    return ((r << 16) | (g << 8) | b).toString(16);\n  }\n\n  static getHash(str, algo = \"SHA-256\") {\n    let strBuf = new TextEncoder(\"utf-8\").encode(str);\n\n    if (window.isSecureContext) {\n      return crypto.subtle.digest(algo, strBuf).then((hash) => {\n        // window.hash = hash;\n        // here hash is an arrayBuffer,\n        // so we'll convert it to its hex version\n        let result = \"\";\n        const view = new DataView(hash);\n        for (let i = 0; i < hash.byteLength; i += 4) {\n          result += (\"00000000\" + view.getUint32(i).toString(16)).slice(-8);\n        }\n        return result;\n      });\n    } else {\n      return new Promise((resolve) => {\n        resolve(\n          str.split(\"\").reduce((a, b) => {\n            // eslint-disable-next-line no-bitwise\n            a = (a << 5) - a + b.charCodeAt(0);\n            // eslint-disable-next-line no-bitwise\n            return a & a;\n          }, 0),\n        );\n      });\n    }\n  }\n\n  static async makeSlug(name) {\n    const toReplace\n      = \"а,б,в,г,д,е,ё,ж,з,и,й,к,л,м,н,о,п,р,с,т,у,ф,х,ц,ч,ш,щ,ъ,ы,ь,э,ю,я\".split(\n        \",\",\n      );\n    const replacers\n      = \"a,b,v,g,d,e,yo,zh,z,i,y,k,l,m,n,o,p,r,s,t,u,f,kh,c,ch,sh,sch,_,y,_,e,yu,ya\".split(\n        \",\",\n      );\n    const replaceDict = Object.fromEntries(\n      toReplace.map((_, i) => [toReplace[i], replacers[i]]),\n    );\n    const unicodeString = name\n      .toLowerCase()\n      .split(\"\")\n      .map((x) => (Object.prototype.hasOwnProperty.call(replaceDict, x) ? replaceDict[x] : x))\n      .join(\"\")\n      .replace(/[^\\w.]/gi, \"_\")\n      .replace(/__+/g, \"_\");\n    let asciiString = Utils.U2A(unicodeString);\n    return new Promise((resolve) => {\n      if (asciiString.length < 2) {\n        Utils.getHash(name).then((hash) => {\n          logger.debug(\"Tokenizer is having to use a hashed file name.\");\n          resolve(hash);\n        });\n      } else {\n        resolve(asciiString);\n      }\n    });\n  }\n\n  static titleString (text) {\n    const words = text.trim().split(\" \");\n\n    for (let i = 0; i < words.length; i++) {\n      if (words[i][0] && (i == 0 || !SKIPPING_WORDS.includes(words[i]))) {\n        words[i] = words[i][0].toUpperCase() + words[i].substr(1);\n      }\n    }\n\n    return words.join(\" \");\n  }\n\n  static extractImage(event, view) {\n    const evData = event?.clipboardData || event?.dataTransfer;\n\n    if (!evData.items) return;\n\n    for (const item of evData.items) {\n      if (item.type.startsWith('image')) {\n        const blob = item.getAsFile();\n        const img = new Image();\n        img.crossOrigin = \"Anonymous\";\n        img.addEventListener(\"load\", () => {\n          view.addImageLayer(img, { type: \"image\" });\n        });\n        const reader = new FileReader();\n        reader.onload = function(ev) {\n          img.src = ev.target.result;\n        }; \n        reader.readAsDataURL(blob);\n      }\n    }\n  }\n\n  static cloneCanvas(sourceCanvas) {\n    const cloneCanvas = document.createElement(\"canvas\");\n    cloneCanvas.width = sourceCanvas.width;\n    cloneCanvas.height = sourceCanvas.height;\n    cloneCanvas.getContext(\"2d\", { willReadFrequently: false }).drawImage(sourceCanvas, 0, 0);\n    return cloneCanvas;\n  }\n\n  static versionCompare (v1, v2, options) {\n    const lexicographical = options && options.lexicographical;\n    const zeroExtend = options && options.zeroExtend;\n    let v1parts = v1.split(\".\");\n    let v2parts = v2.split(\".\");\n\n    function isValidPart(x) {\n      return (lexicographical ? /^\\d+[A-Za-z]*$/ : /^\\d+$/).test(x);\n    }\n\n    if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {\n      return NaN;\n    }\n\n    if (zeroExtend) {\n      while (v1parts.length < v2parts.length) v1parts.push(\"0\");\n      while (v2parts.length < v1parts.length) v2parts.push(\"0\");\n    }\n\n    if (!lexicographical) {\n      v1parts = v1parts.map(Number);\n      v2parts = v2parts.map(Number);\n    }\n\n    for (var i = 0; i < v1parts.length; ++i) {\n      if (v2parts.length == i) {\n        return 1;\n      }\n\n      if (v1parts[i] > v2parts[i]) {\n        return 1;\n      }\n      if (v1parts[i] < v2parts[i]) {\n        return -1;\n      }\n    }\n\n    if (v1parts.length != v2parts.length) {\n      return -1;\n    }\n\n    return 0;\n  }\n\n  static throttle(cb, delay) {\n    let wait = false;\n  \n    return (...args) => {\n      if (wait) {\n          return;\n      }\n  \n      // eslint-disable-next-line callback-return\n      cb(...args);\n      wait = true;\n      setTimeout(() => {\n        wait = false;\n      }, delay);\n    };\n  }\n\n}\n\n\n","/**\n * Copyright (c) 2012-2014, Michael Bostock All rights reserved.\n *  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n *  Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n *  Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\n *  The name Michael Bostock may not be used to endorse or promote products derived from this software without specific prior written permission.\n *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n * Computes a contour for a given input grid function using the <a\n * href=\"http://en.wikipedia.org/wiki/Marching_squares\">marching\n * squares</a> algorithm. Returns the contour polygon as an array of points.\n *\n * @param grid a two-input function(x, y) that returns true for values\n * inside the contour and false for values outside the contour.\n * @param start an optional starting point [x, y] on the grid.\n * @returns polygon [[x1, y1], [x2, y2], …]\n */\n\nexport const geom = {};\n\n// lookup tables for marching directions\nconst d3GeomContourDx = [1, 0, 1, 1, -1, 0, -1, 1, 0, 0, 0, 0, -1, 0, -1, NaN];\nconst d3GeomContourDy = [0, -1, 0, 0, 0, -1, 0, 0, 1, -1, 1, 1, 0, -1, 0, NaN];\n\nexport function dGeomContourStart(grid) {\n  let x = 0,\n    y = 0;\n\n  // search for a starting point; begin at origin\n  // and proceed along outward-expanding diagonals\n  while (!grid(x, y)) {\n    if (x === 0) {\n      x = y + 1;\n      y = 0;\n    } else {\n      x -= 1;\n      y += 1;\n    }\n  }\n\n  return [x, y];\n}\n\ngeom.contour = function(grid, start) {\n  let s = start || dGeomContourStart(grid), // starting point\n    c = [], // contour polygon\n    x = s[0], // current x position\n    y = s[1], // current y position\n    dx = 0, // next x direction\n    dy = 0, // next y direction\n    pdx = NaN, // previous x direction\n    pdy = NaN, // previous y direction\n    i = 0;\n\n  do {\n    // determine marching squares index\n    i = 0;\n    if (grid(x - 1, y - 1)) i += 1;\n    if (grid(x, y - 1)) i += 2;\n    if (grid(x - 1, y)) i += 4;\n    if (grid(x, y)) i += 8;\n\n    // determine next direction\n    if (i === 6) {\n      dx = pdy === -1 ? -1 : 1;\n      dy = 0;\n    } else if (i === 9) {\n      dx = 0;\n      dy = pdx === 1 ? -1 : 1;\n    } else {\n      dx = d3GeomContourDx[i];\n      dy = d3GeomContourDy[i];\n    }\n\n    // update contour polygon\n    if (dx != pdx && dy != pdy) {\n      c.push([x, y]);\n      pdx = dx;\n      pdy = dy;\n    }\n\n    x += dx;\n    y += dy;\n  } while (s[0] != x || s[1] != y);\n\n  return c;\n};\n\n/**\n * End of Block for\n * Copyright (c) 2012-2014, Michael Bostock All rights reserved.\n */\n","const CONSTANTS = {\n  MODULE_ID: \"vtta-tokenizer\",\n  MODULE_NAME: \"Tokenizer\",\n  BLEND_MODES: {\n    SOURCE_OVER: \"source-over\",\n    SOURCE_IN: \"source-in\",\n    SOURCE_OUT: \"source-out\",\n    SOURCE_ATOP: \"source-atop\",\n    DESTINATION_OVER: \"destination-over\",\n    DESTINATION_IN: \"destination-in\",\n    DESTINATION_OUT: \"destination-out\",\n    DESTINATION_ATOP: \"destination-atop\",\n    LIGHTER: \"lighter\",\n    COPY: \"copy\",\n    XOR: \"xor\",\n    MULTIPLY: \"multiply\",\n    SCREEN: \"screen\",\n    OVERLAY: \"overlay\",\n    DARKEN: \"darken\",\n    LIGHTEN: \"lighten\",\n    COLOR_DODGE: \"color-dodge\",\n    COLOR_BURN: \"color-burn\",\n    HARD_LIGHT: \"hard-light\",\n    SOFT_LIGHT: \"soft-light\",\n    DIFFERENCE: \"difference\",\n    EXCLUSION: \"exclusion\",\n    HUE: \"hue\",\n    SATURATION: \"saturation\",\n    COLOR: \"color\",\n    LUMINOSITY: \"luminosity\",\n  },\n  TO_RADIANS: Math.PI / 180,\n  TRANSPARENCY_THRESHOLD: 254,\n  MASK_DENSITY: 400,\n  COLOR: {\n    OPAQUE_THRESHOLD: 254,\n    TRANSPARENCY_THRESHOLD: 0,\n  },\n  MASK: {\n    SAMPLE_SIZE: 5,\n    MINIMUM_ALPHA: 255,\n  },\n  BAD_DIRS: [\"[data]\", \"[data] \", \"\", null],\n  NUMBERS: {\n    ACTIVE: [\n      \"⓿\",\n      \"❶\",\n      \"❷\",\n      \"❸\",\n      \"❹\",\n      \"❺\",\n      \"❻\",\n      \"❼\",\n      \"❽\",\n      \"❾\",\n      \"❿\",\n      \"⓫\",\n      \"⓬\",\n      \"⓭\",\n      \"⓮\",\n      \"⓯\",\n      \"⓰\",\n      \"⓱\",\n      \"⓲\",\n      \"⓳\",\n      \"⓴\",\n    ],\n    INACTIVE: [\n      \"⓪\",\n      \"①\",\n      \"②\",\n      \"③\",\n      \"④\",\n      \"⑤\",\n      \"⑥\",\n      \"⑦\",\n      \"⑧\",\n      \"⑨\",\n      \"⑩\",\n      \"⑪\",\n      \"⑫\",\n      \"⑬\",\n      \"⑭\",\n      \"⑮\",\n      \"⑯\",\n      \"⑰\",\n      \"⑱\",\n      \"⑲\",\n      \"⑳\",\n    ],\n  },\n};\n\nCONSTANTS.PATH = `modules/${CONSTANTS.MODULE_ID}/`;\nCONSTANTS.SHADOWDARK_FRAME = `[data] ${CONSTANTS.PATH}img/shadowdark-frame.png`.replace(/^\\/|\\/$/g, \"\");\nCONSTANTS.DEFAULT_MASK = `[data] ${CONSTANTS.PATH}img/dynamic-ring-circle-mask.webp`.replace(/^\\/|\\/$/g, \"\");\nCONSTANTS.DEFAULT_TOP_MASK = `[data] ${CONSTANTS.PATH}img/dynamic-ring-top-mask.webp`.replace(/^\\/|\\/$/g, \"\");\n\nexport default CONSTANTS;\n","import CONSTANTS from \"../constants.js\";\n\nexport default class Color {\n\n  constructor({ red = 0, green = 0, blue = 0, alpha = 1, tolerance = 50 } = {}) {\n    this.red = red;\n    this.green = green;\n    this.blue = blue;\n    this.alpha = alpha;\n    this.tolerance = tolerance;\n  }\n\n  isNeighborColor(color) {\n    return Math.abs(this.red - color.red) <= this.tolerance\n      && Math.abs(this.green - color.green) <= this.tolerance\n      && Math.abs(this.blue - color.blue) <= this.tolerance;\n  }\n\n  isOpaque() {\n    return this.alpha > CONSTANTS.COLOR.OPAQUE_THRESHOLD;\n  }\n\n}\n","import CONSTANTS from \"../constants.js\";\nimport Color from \"./Color.js\";\n\nfunction getEnrichedPixel(imageData, point) {\n  const index = point.x + (point.y * imageData.height);\n  const baseIndex = index * 4;\n  const color = new Color(\n    {\n      red: imageData.data[baseIndex],\n      green: imageData.data[baseIndex + 1],\n      blue: imageData.data[baseIndex + 2],\n      alpha: imageData.data[baseIndex + 3],\n    },\n  );\n  return {\n    x: point.x,\n    y: point.y,\n    color,\n  };\n}\n\n// attempts a Bresenhams line algorithm\nfunction getPixelLine(startPoint, endPoint) {\n  const pixels = [];\n  const dx = Math.abs(Math.floor(endPoint.x) - Math.floor(startPoint.x));\n  const dy = Math.abs(Math.floor(endPoint.y) - Math.floor(startPoint.y));\n  const sx = startPoint.x < endPoint.x ? 1 : -1;\n  const sy = startPoint.y < endPoint.y ? 1 : -1;\n  let err = dx - dy;\n  let x = Math.floor(startPoint.x);\n  let y = Math.floor(startPoint.y);\n  let process = true;\n\n  while (process) {\n    pixels.push({ x, y });\n\n    if (x === Math.floor(endPoint.x) && y === Math.floor(endPoint.y)) {\n      process = false;\n      break;\n    } else {\n      const e2 = 2 * err;\n      if (e2 >= -dy) {\n        err -= dy;\n        x += sx;\n      }\n      if (e2 <= dx) {\n        err += dx;\n        y += sy;\n      }\n    }\n  }\n\n  return pixels;\n}\n\n\nfunction findEdgeOnRay(imageData, startPoint, endPoint) {\n  // get all pixels on ray\n  const rayPixels = getPixelLine(startPoint, endPoint);\n  // find colors and alpha of ray pixels\n  const enrichedPixels = rayPixels.map((point) => getEnrichedPixel(imageData, point));\n\n  let start = null;\n  let end = null;\n  let edgePixel = null;\n\n  // check to see if we find an edge\n  enrichedPixels.forEach((pixel, index) => {\n    if (pixel.color.isOpaque()) {\n      if (start === null) {\n        start = index;\n      } else {\n        end = index;\n      }\n      edgePixel = !edgePixel || edgePixel.color.alpha < pixel.color.alpha\n        ? pixel\n        : edgePixel;\n    } else if (end !== null) {\n      // reset\n      start = null;\n      end = null;\n    }\n  });\n\n  return edgePixel;\n}\n\n\nfunction createRay(imageData, startPoint, endPoint, findEdge = false) {\n  return {\n    startPoint,\n    endPoint,\n    edgePixel: findEdge ? findEdgeOnRay(imageData, startPoint, endPoint) : null,\n    processed: findEdge,\n  };\n}\n\n// get the co-ords of an edge on the canvas(mask)\n// 0,0 is top left\nfunction getCanvasEdge(mask, startNum) {\n  let pos = startNum;\n  if (pos < mask.width - 1) return { x: pos, y: 0 };\n  pos -= mask.width - 1;\n  if (pos < mask.height - 1) return { x: mask.width - 1, y: pos };\n  pos -= mask.height - 1;\n  if (pos < mask.width) return { x: mask.width - 1 - pos, y: mask.height - 1 };\n  pos -= mask.width - 1;\n  return { x: 0, y: mask.height - 1 - pos };\n}\n\nfunction createRays(mask, maskImageData) {\n  const maskCentre = {\n    x: mask.width / 2,\n    y: mask.height / 2,\n  };\n  \n  const edgePoints = (2 * mask.width) + (2 * (mask.height - 2));\n\n  const rays = [];\n\n  // first loop through all rays and process at sample size\n  for (let rayIndex = 0; edgePoints > rayIndex; rayIndex++) {\n    const sampleRay = rayIndex % CONSTANTS.MASK.SAMPLE_SIZE === 0;\n    const ray = createRay(\n      maskImageData, \n      maskCentre,\n      getCanvasEdge(mask, rayIndex),\n      sampleRay,\n    );\n\n    // if we didn't find an edge pixel, lets step back over sample size\n    if (sampleRay && !ray.edgePixel) {\n      for (let stepIndex = rayIndex - 1; stepIndex < CONSTANTS.MASK.SAMPLE_SIZE && (rayIndex - stepIndex) >= 0; stepIndex++) {\n        const stepRay = createRay(\n          maskImageData, \n          maskCentre,\n          getCanvasEdge(mask, stepIndex),\n          sampleRay,\n        );\n        if (stepRay.edgePixel) {\n          rays.edgePixel = stepRay.edgePixel;\n          break;\n        }\n      }\n    }\n    rays.push(ray);\n  }\n  return rays;\n}\n\nexport function generateRayMask(maskCanvas) {\n  const maskImageData = maskCanvas\n    .getContext(\"2d\", {\n      willReadFrequently: false,\n    })\n    .getImageData(0, 0, maskCanvas.width, maskCanvas.height);\n\n  const mask = document.createElement(\"canvas\");\n  mask.width = maskCanvas.width;\n  mask.height = maskCanvas.height;\n\n  const rays = createRays(mask, maskImageData);\n\n  const context = mask.getContext(\"2d\", {\n      willReadFrequently: false,\n    });\n\n  const edgePoints = rays\n    .filter((ray) => ray.edgePixel)\n    .map((ray) => ray.edgePixel);\n\n  context.fillStyle = \"black\";\n\n  // unable to calculate suitable radius, so just fill the whole mask\n  if (edgePoints.length < 2) {\n    context.rect(0, 0, mask.width, mask.height);\n    context.fill();\n  } else {\n    context.beginPath();\n    edgePoints.forEach((edgePoint, index) => {\n      if (index === 0) {\n        context.moveTo(edgePoint.x, edgePoint.y);\n      } else {\n        context.lineTo(edgePoint.x, edgePoint.y);\n      }\n    });\n    context.closePath();\n    context.fill();\n\n  }\n\n  return mask;\n}\n","import CONSTANTS from \"../constants.js\";\nimport Utils from \"../libs/Utils.js\";\n\nexport class Masker {\n\n  #drawChequeredBackground(width = 7) {\n    this.chequeredSource = document.createElement(\"canvas\");\n    this.chequeredSource.width = this.width;\n    this.chequeredSource.height = this.height;\n    \n    const context = this.chequeredSource.getContext(\"2d\", { willReadFrequently: false });\n    const fillStyle = context.fillStyle;\n    const alpha = context.globalAlpha;\n    const columns = Math.ceil(this.chequeredSource.width / width);\n    const rows = Math.ceil(this.chequeredSource.height / width);\n\n    context.fillStyle = \"rgb(212, 163, 19)\";\n    for (let i = 0; i < rows; ++i) {\n      for (let j = 0, col = columns / 2; j < col; ++j) {\n        context.rect(\n          (2 * j * width) + (i % 2 ? 0 : width),\n          i * width,\n          width,\n          width,\n        );\n      }\n    }\n    context.fill();\n\n    context.fillStyle = fillStyle;\n    context.globalAlpha = alpha;\n\n    this.chequeredSource.getContext(\"2d\", { willReadFrequently: false })\n      .drawImage(\n        this.layer.preview,\n        0,\n        0,\n        this.layer.preview.width,\n        this.layer.preview.height,\n        this.yOffset,\n        this.xOffset,\n        this.scaledWidth,\n        this.scaledHeight,\n      );\n  }\n\n  #drawGreyScaleBackground() {\n    this.greyscale = document.createElement(\"canvas\");\n    this.greyscale.width = this.width;\n    this.greyscale.height = this.height;\n    this.greyscale.filter = \"grayscale()\";\n    this.greyscale.getContext(\"2d\", { willReadFrequently: false })\n      .drawImage(\n        this.layer.preview,\n        0,\n        0,\n        this.layer.preview.width,\n        this.layer.preview.height,\n        this.yOffset,\n        this.xOffset,\n        this.scaledWidth,\n        this.scaledHeight,\n      );\n  }\n\n  #createBaseCanvas() {\n    this.canvas = document.createElement(\"canvas\");\n    this.canvas.width = this.width;\n    this.canvas.height = this.height;\n  }\n\n  #createMaskCanvas() {\n    this.mask = Utils.cloneCanvas(this.canvas);\n    this.mask.width = this.width;\n    this.mask.height = this.height;\n\n    this.maskContext = this.mask.getContext(\"2d\", { willReadFrequently: false });\n    this.maskContext.resetTransform();\n    this.maskContext.clearRect(0, 0, this.width, this.height);\n    this.maskContext.drawImage(\n      this.layer.renderedMask,\n      0,\n      0,\n      this.layer.renderedMask.width,\n      this.layer.renderedMask.height,\n      this.yOffset,\n      this.xOffset,\n      this.scaledWidth,\n      this.scaledHeight,\n    );\n\n    this.maskContext.lineJoin = \"round\";\n    this.maskContext.lineCap = \"round\";\n  }\n\n  #createMaskedSourceCanvas() {\n    this.maskedSource = document.createElement(\"canvas\");\n    this.maskedSource.width = this.width;\n    this.maskedSource.height = this.height;\n    const maskedContext = this.maskedSource.getContext(\"2d\", { willReadFrequently: false });\n    // add the mask\n    maskedContext.drawImage(this.mask, 0, 0);\n    maskedContext.globalCompositeOperation = \"source-in\";\n    // now the chequered layer\n    maskedContext.drawImage(this.chequeredSource, 0, 0);\n  }\n\n  constructor(layer) {\n    this.container = null;\n    this.layer = layer;\n\n    this.height = Math.min(1000, layer.preview.height, layer.preview.width);\n    this.width = Math.min(1000, layer.preview.height, layer.preview.width);\n\n    const crop = game.settings.get(CONSTANTS.MODULE_ID, \"default-crop-image\");\n    // if we crop the image we scale to the smallest dimension of the image\n    // otherwise we scale to the largest dimension of the image\n    const direction = crop ? layer.preview.height > layer.preview.width : layer.preview.height < layer.preview.width;\n\n    this.scaledWidth = !direction\n      ? this.height * (layer.preview.height / layer.preview.width)\n      : this.width;\n    this.scaledHeight = direction\n      ? this.width * (layer.preview.height / layer.preview.width)\n      : this.height;\n\n    // offset the canvas for the scaled image\n    this.yOffset = (this.width - this.scaledWidth) / 2;\n    this.xOffset = (this.height - this.scaledHeight) / 2;\n\n    // create base canvases\n    this.#createBaseCanvas();\n    this.#createMaskCanvas();\n\n    // create background images\n    this.#drawGreyScaleBackground();\n    this.#drawChequeredBackground();\n\n    this.brushSize = 20;\n    this.maskChanged = false;\n    this.currentPoint = { x: 0, y: 0 };\n    this.previousPoint = null;\n    this.mouseDown = false;\n  }\n\n  async display(callback, nestedCallback) {\n    const html = await foundry.applications.handlebars.renderTemplate(\"modules/vtta-tokenizer/templates/mask-editor.hbs\");\n    const template = document.createElement(\"template\");\n    template.innerHTML = html.trim();\n    this.container = template.content.firstChild;\n    this.container.append(this.canvas);\n    document.body.appendChild(this.container);\n    this.#activateListeners(callback, nestedCallback);\n  }\n\n  getMousePointer(event) {\n    const rect = this.canvas.getBoundingClientRect();\n    return {\n      x: event.clientX - rect.left,\n      y: event.clientY - rect.top,\n    };\n  }\n\n  distanceBetweenLastAndCurrent() {\n    return Math.sqrt(((this.currentPoint.x - this.previousPoint.x) ** 2) + ((this.currentPoint.y - this.previousPoint.y) ** 2));\n  }\n\n  angleBetweenLastAndCurrent() {\n    return Math.atan2(this.currentPoint.x - this.previousPoint.x, this.currentPoint.y - this.previousPoint.y);\n  }\n\n  // eslint-disable-next-line consistent-return\n  #saveAndCleanup(action, callback, nestedCallback) {\n    window.cancelAnimationFrame(this.cancelAnimationFrame);\n    window.removeEventListener(\"keyup\", this.onKeyUp);\n    this.container.remove();\n    delete this.canvas;\n\n    if (action === \"ok\" && this.maskChanged) {\n      const mask = Utils.cloneCanvas(this.layer.renderedMask);\n      // rescale the mask back up for the appropriate layer canvas size\n      const context = mask.getContext(\"2d\", { willReadFrequently: false });\n      context.resetTransform();\n      context.clearRect(0, 0, this.layer.preview.width, this.layer.preview.height);\n      mask.getContext(\"2d\", { willReadFrequently: false }).drawImage(\n        this.mask,\n        this.yOffset,\n        this.xOffset,\n        this.scaledWidth,\n        this.scaledHeight,\n        0,\n        0,\n        this.layer.preview.width,\n        this.layer.preview.height,\n      );\n      return callback(mask, nestedCallback);\n    }\n  }\n\n  clickButton(event, callback, nestedCallback) {\n    event.preventDefault();\n    const action = event.data?.action ?? event.target?.dataset?.action;\n\n    if (action) {\n      this.#saveAndCleanup(action, callback, nestedCallback);\n    }\n  }\n\n  drawArc(point, remove) {\n    this.maskContext.globalCompositeOperation = remove\n      ? \"destination-out\"\n      : \"destination-over\";\n\n    this.maskContext.fillStyle = \"black\";\n    this.maskContext.beginPath();\n\n    this.maskContext.arc(\n      point.x / this.ratio,\n      point.y / this.ratio,\n      this.brushSize / this.ratio,\n      0,\n      2 * Math.PI,\n    );\n    this.maskContext.fill();\n    this.maskChanged = true;\n  }\n\n  #activateListeners(callback, nestedCallback) {\n    let rect = this.canvas.getBoundingClientRect();\n    this.ratio = rect.width / this.canvas.width;\n\n    this.canvas.addEventListener(\"wheel\", (event) => {\n      event.preventDefault();\n      if (event.wheelDelta < 0) {\n          if (this.brushSize > 50) this.brushSize -= 7;\n          else if (this.brushSize > 25) this.brushSize -= 4;\n          else if (this.brushSize > 10) this.brushSize -= 2;\n          else this.brushSize--;\n          if (this.brushSize <= 1) this.brushSize = 1;\n      } else {\n          if (this.brushSize > 50) this.brushSize += 7;\n          else if (this.brushSize > 25) this.brushSize += 4;\n          else if (this.brushSize > 10) this.brushSize += 2;\n          else this.brushSize++;\n          if (this.brushSize >= 100) this.brushSize = 100;\n      }\n      }, { passive: false },\n    );\n\n    this.canvas.addEventListener(\"mouseup\", () => {\n      this.mouseDown = false;\n    });\n\n    this.canvas.addEventListener(\"mousedown\", (event) => {\n      event.preventDefault();\n      this.mouseDown = [0, 2].includes(event.button);\n      if (this.mouseDown) {\n        this.previousPoint = this.getMousePointer(event);\n        this.drawArc(this.previousPoint, event.shiftKey || event.buttons === 2);\n      }\n    });\n\n    this.canvas.addEventListener(\"mousemove\", (event) => {\n      this.currentPoint = this.getMousePointer(event);\n      if (!this.mouseDown) return;\n\n      const distanceBetween = (point1, point2) => {\n        return Math.sqrt(((point2.x - point1.x) ** 2) + ((point2.y - point1.y) ** 2));\n      };\n      const angleBetween = (point1, point2) => {\n        return Math.atan2(point2.x - point1.x, point2.y - point1.y);\n      };\n\n      const distance = distanceBetween(this.previousPoint, this.currentPoint);\n      const angle = angleBetween(this.previousPoint, this.currentPoint);\n\n      for (var i = 0; i < distance; i += 1) {\n        const x = this.previousPoint.x + (Math.sin(angle) * i);\n        const y = this.previousPoint.y + (Math.cos(angle) * i);\n        this.drawArc({ x, y }, event.shiftKey || event.buttons === 2);\n        this.previousPoint = this.currentPoint;\n      }\n    });\n\n\n    // eslint-disable-next-line consistent-return\n    this.onKeyUp = (event) => {\n      const action = event.keyCode === 13\n        ? \"ok\"\n        : event.keyCode === 27\n          ? \"cancel\"\n          : null;\n\n      if (action) {\n        this.#saveAndCleanup(action, callback, nestedCallback);\n      }\n    };\n    window.addEventListener(\"keyup\", this.onKeyUp);\n\n    const wrapper = document.getElementById(\"mask-editor-buttons\");\n    wrapper.addEventListener(\"click\", (event) => {\n      if (event.target.nodeName === 'BUTTON') {\n        this.clickButton(event, callback, nestedCallback);\n      }\n    });\n\n  }\n\n  draw() {\n    const context = this.canvas.getContext(\"2d\", { willReadFrequently: false });\n    context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n\n    // add a grey version to canvas\n    context.globalAlpha = 0.25;\n    context.drawImage(this.greyscale, 0, 0);\n    context.globalAlpha = 1;\n\n    // now the masked version\n    this.#createMaskedSourceCanvas();\n    context.drawImage(this.maskedSource, 0, 0);\n\n    // add brush\n    context.fillStyle = \"black\";\n    context.beginPath();\n    context.arc(\n      this.currentPoint.x / this.ratio,\n      this.currentPoint.y / this.ratio,\n      this.brushSize / this.ratio,\n      0,\n      2 * Math.PI,\n    );\n    context.fill();\n\n    // begin frame animation for duration of canvas\n    this.cancelAnimationFrame = window.requestAnimationFrame(this.draw.bind(this));\n  }\n}\n","/* eslint-disable max-depth */\n/* eslint-disable no-continue */\n// Magic Wand from https://github.com/Tamersoul/magic-wand-js/\n\n// The MIT License (MIT)\n\n// Copyright (c) 2014, Ryasnoy Paul (ryasnoypaul@gmail.com)\n\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\n// eslint-disable-next-line complexity\nfunction floodFillWithoutBorders(image, px, py, colorThreshold, mask) {\n  let c,\n    x,\n    newY,\n    el,\n    xr,\n    xl,\n    dy,\n    dyl,\n    dyr,\n    checkY,\n    data = image.data,\n    w = image.width,\n    h = image.height,\n    bytes = image.bytes, // number of bytes in the color\n    maxX = -1,\n    minX = w + 1,\n    maxY = -1,\n    minY = h + 1,\n    i = (py * w) + px, // start point index in the mask data\n    result = new Uint8Array(w * h), // result mask\n    visited = new Uint8Array(mask ? mask : w * h); // mask of visited points\n\n  if (visited[i] === 1) return null;\n\n  i *= bytes; // start point index in the image data\n  let sampleColor = [data[i], data[i + 1], data[i + 2], data[i + 3]]; // start point color (sample)\n\n  let stack = [{ y: py, left: px - 1, right: px + 1, dir: 1 }]; // first scanning line\n  do {\n    el = stack.shift(); // get line for scanning\n\n    checkY = false;\n    for (x = el.left + 1; x < el.right; x++) {\n      dy = el.y * w;\n      i = (dy + x) * bytes; // point index in the image data\n\n      if (visited[dy + x] === 1) continue; // check whether the point has been visited\n      // compare the color of the sample\n      c = data[i] - sampleColor[0]; // check by red\n      if (c > colorThreshold || c < -colorThreshold) continue;\n      c = data[i + 1] - sampleColor[1]; // check by green\n      if (c > colorThreshold || c < -colorThreshold) continue;\n      c = data[i + 2] - sampleColor[2]; // check by blue\n      if (c > colorThreshold || c < -colorThreshold) continue;\n\n      checkY = true; // if the color of the new point(x,y) is similar to the sample color need to check minmax for Y\n\n      result[dy + x] = 1; // mark a new point in mask\n      visited[dy + x] = 1; // mark a new point as visited\n\n      xl = x - 1;\n      // walk to left side starting with the left neighbor\n      while (xl > -1) {\n        dyl = dy + xl;\n        i = dyl * bytes; // point index in the image data\n        if (visited[dyl] === 1) break; // check whether the point has been visited\n        // compare the color of the sample\n        c = data[i] - sampleColor[0]; // check by red\n        if (c > colorThreshold || c < -colorThreshold) break;\n        c = data[i + 1] - sampleColor[1]; // check by green\n        if (c > colorThreshold || c < -colorThreshold) break;\n        c = data[i + 2] - sampleColor[2]; // check by blue\n        if (c > colorThreshold || c < -colorThreshold) break;\n\n        result[dyl] = 1;\n        visited[dyl] = 1;\n\n        xl--;\n      }\n      xr = x + 1;\n      // walk to right side starting with the right neighbor\n      while (xr < w) {\n        dyr = dy + xr;\n        i = dyr * bytes; // index point in the image data\n        if (visited[dyr] === 1) break; // check whether the point has been visited\n        // compare the color of the sample\n        c = data[i] - sampleColor[0]; // check by red\n        if (c > colorThreshold || c < -colorThreshold) break;\n        c = data[i + 1] - sampleColor[1]; // check by green\n        if (c > colorThreshold || c < -colorThreshold) break;\n        c = data[i + 2] - sampleColor[2]; // check by blue\n        if (c > colorThreshold || c < -colorThreshold) break;\n\n        result[dyr] = 1;\n        visited[dyr] = 1;\n\n        xr++;\n      }\n\n      // check minmax for X\n      if (xl < minX) minX = xl + 1;\n      if (xr > maxX) maxX = xr - 1;\n\n      newY = el.y - el.dir;\n      if (newY >= 0 && newY < h) {\n        // add two scanning lines in the opposite direction (y - dir) if necessary\n        if (xl < el.left)\n          stack.push({ y: newY, left: xl, right: el.left, dir: -el.dir }); // from \"new left\" to \"current left\"\n        if (el.right < xr)\n          stack.push({ y: newY, left: el.right, right: xr, dir: -el.dir }); // from \"current right\" to \"new right\"\n      }\n      newY = el.y + el.dir;\n      if (newY >= 0 && newY < h) {\n        // add the scanning line in the direction (y + dir) if necessary\n        if (xl < xr) stack.push({ y: newY, left: xl, right: xr, dir: el.dir }); // from \"new left\" to \"new right\"\n      }\n    }\n    // check minmax for Y if necessary\n    if (checkY) {\n      if (el.y < minY) minY = el.y;\n      if (el.y > maxY) maxY = el.y;\n    }\n  } while (stack.length > 0);\n\n  return {\n    data: result,\n    width: image.width,\n    height: image.height,\n    bounds: {\n      minX: minX,\n      minY: minY,\n      maxX: maxX,\n      maxY: maxY,\n    },\n  };\n}\n\n// eslint-disable-next-line complexity\nfunction floodFillWithBorders(image, px, py, colorThreshold, mask) {\n  let c,\n    x,\n    newY,\n    el,\n    xr,\n    xl,\n    dy,\n    dyl,\n    dyr,\n    checkY,\n    data = image.data,\n    w = image.width,\n    h = image.height,\n    bytes = image.bytes, // number of bytes in the color\n    maxX = -1,\n    minX = w + 1,\n    maxY = -1,\n    minY = h + 1,\n    i = (py * w) + px, // start point index in the mask data\n    result = new Uint8Array(w * h), // result mask\n    visited = new Uint8Array(mask ? mask : w * h); // mask of visited points\n\n  if (visited[i] === 1) return null;\n\n  i *= bytes; // start point index in the image data\n  let sampleColor = [data[i], data[i + 1], data[i + 2], data[i + 3]]; // start point color (sample)\n\n  let stack = [{ y: py, left: px - 1, right: px + 1, dir: 1 }]; // first scanning line\n  do {\n    el = stack.shift(); // get line for scanning\n\n    checkY = false;\n    for (x = el.left + 1; x < el.right; x++) {\n      dy = el.y * w;\n      i = (dy + x) * bytes; // point index in the image data\n\n      if (visited[dy + x] === 1) continue; // check whether the point has been visited\n\n      checkY = true; // if the color of the new point(x,y) is similar to the sample color need to check minmax for Y\n\n      result[dy + x] = 1; // mark a new point in mask\n      visited[dy + x] = 1; // mark a new point as visited\n\n      // compare the color of the sample\n      c = data[i] - sampleColor[0]; // check by red\n      if (c > colorThreshold || c < -colorThreshold) continue;\n      c = data[i + 1] - sampleColor[1]; // check by green\n      if (c > colorThreshold || c < -colorThreshold) continue;\n      c = data[i + 2] - sampleColor[2]; // check by blue\n      if (c > colorThreshold || c < -colorThreshold) continue;\n\n      xl = x - 1;\n      // walk to left side starting with the left neighbor\n      while (xl > -1) {\n        dyl = dy + xl;\n        i = dyl * bytes; // point index in the image data\n        if (visited[dyl] === 1) break; // check whether the point has been visited\n\n        result[dyl] = 1;\n        visited[dyl] = 1;\n        xl--;\n\n        // compare the color of the sample\n        c = data[i] - sampleColor[0]; // check by red\n        if (c > colorThreshold || c < -colorThreshold) break;\n        c = data[i + 1] - sampleColor[1]; // check by green\n        if (c > colorThreshold || c < -colorThreshold) break;\n        c = data[i + 2] - sampleColor[2]; // check by blue\n        if (c > colorThreshold || c < -colorThreshold) break;\n      }\n      xr = x + 1;\n      // walk to right side starting with the right neighbor\n      while (xr < w) {\n        dyr = dy + xr;\n        i = dyr * bytes; // index point in the image data\n        if (visited[dyr] === 1) break; // check whether the point has been visited\n\n        result[dyr] = 1;\n        visited[dyr] = 1;\n        xr++;\n\n        // compare the color of the sample\n        c = data[i] - sampleColor[0]; // check by red\n        if (c > colorThreshold || c < -colorThreshold) break;\n        c = data[i + 1] - sampleColor[1]; // check by green\n        if (c > colorThreshold || c < -colorThreshold) break;\n        c = data[i + 2] - sampleColor[2]; // check by blue\n        if (c > colorThreshold || c < -colorThreshold) break;\n      }\n\n      // check minmax for X\n      if (xl < minX) minX = xl + 1;\n      if (xr > maxX) maxX = xr - 1;\n\n      newY = el.y - el.dir;\n      if (newY >= 0 && newY < h) {\n        // add two scanning lines in the opposite direction (y - dir) if necessary\n        if (xl < el.left)\n          stack.push({ y: newY, left: xl, right: el.left, dir: -el.dir }); // from \"new left\" to \"current left\"\n        if (el.right < xr)\n          stack.push({ y: newY, left: el.right, right: xr, dir: -el.dir }); // from \"current right\" to \"new right\"\n      }\n      newY = el.y + el.dir;\n      if (newY >= 0 && newY < h) {\n        // add the scanning line in the direction (y + dir) if necessary\n        if (xl < xr)\n          stack.push({ y: newY, left: xl, right: xr, dir: el.dir }); // from \"new left\" to \"new right\"\n      }\n    }\n    // check minmax for Y if necessary\n    if (checkY) {\n      if (el.y < minY) minY = el.y;\n      if (el.y > maxY) maxY = el.y;\n    }\n  } while (stack.length > 0);\n\n  return {\n    data: result,\n    width: image.width,\n    height: image.height,\n    bounds: {\n      minX: minX,\n      minY: minY,\n      maxX: maxX,\n      maxY: maxY,\n    },\n  };\n}\n\n// Create a binary mask on the image by color threshold\n//  Algorithm: Scanline flood fill (http://en.wikipedia.org/wiki/Flood_fill)\n//  @param {Object} image: {Uint8Array} data, {int} width, {int} height, {int} bytes\n//  @param {int} x of start pixel\n//  @param {int} y of start pixel\n//  @param {int} color threshold\n//  @param {Uint8Array} mask of visited points (optional)\n//  @param {boolean} [includeBorders=false] indicate whether to include borders pixels\n//  @return {Object} mask: {Uint8Array} data, {int} width, {int} height, {Object} bounds\n//\nfunction floodFill(image, px, py, colorThreshold, mask, includeBorders) {\n  return includeBorders\n    ? floodFillWithBorders(image, px, py, colorThreshold, mask)\n    : floodFillWithoutBorders(image, px, py, colorThreshold, mask);\n}\n\n// Apply the gauss-blur filter to binary mask\n//  Algorithms: http://blog.ivank.net/fastest-gaussian-blur.html\n//  http://www.librow.com/articles/article-9\n//  http://elynxsdk.free.fr/ext-docs/Blur/Fast_box_blur.pdf\n//  @param {Object} mask: {Uint8Array} data, {int} width, {int} height, {Object} bounds\n//  @param {int} blur radius\n//  @return {Object} mask: {Uint8Array} data, {int} width, {int} height, {Object} bounds\n//\nfunction gaussBlur(mask, radius) {\n  let i,\n    k,\n    k1,\n    x,\n    y,\n    val,\n    start,\n    end,\n    n = (radius * 2) + 1, // size of the pattern for radius-neighbors (from -r to +r with the center point)\n    s2 = radius * radius,\n    wg = new Float32Array(n), // weights\n    total = 0, // sum of weights(used for normalization)\n    w = mask.width,\n    h = mask.height,\n    data = mask.data,\n    minX = mask.bounds.minX,\n    maxX = mask.bounds.maxX,\n    minY = mask.bounds.minY,\n    maxY = mask.bounds.maxY;\n\n  // calc gauss weights\n  for (i = 0; i < radius; i++) {\n    var dsq = (radius - i) * (radius - i);\n    var ww = Math.exp(-dsq / (2.0 * s2)) / (2 * Math.PI * s2);\n    wg[radius + i] = wg[radius - i] = ww;\n    total += 2 * ww;\n  }\n  // normalization weights\n  for (i = 0; i < n; i++) {\n    wg[i] /= total;\n  }\n\n  let result = new Uint8Array(w * h), // result mask\n    endX = radius + w,\n    endY = radius + h;\n\n  // walk through all source points for blur\n  for (y = minY; y < maxY + 1; y++)\n    for (x = minX; x < maxX + 1; x++) {\n      val = 0;\n      k = y * w + x; // index of the point\n      start = radius - x > 0 ? radius - x : 0;\n      end = endX - x < n ? endX - x : n; // Math.min((((w - 1) - x) + radius) + 1, n);\n      k1 = k - radius;\n      // walk through x-neighbors\n      for (i = start; i < end; i++) {\n        val += data[k1 + i] * wg[i];\n      }\n      start = radius - y > 0 ? radius - y : 0;\n      end = endY - y < n ? endY - y : n; // Math.min((((h - 1) - y) + radius) + 1, n);\n      k1 = k - (radius * w);\n      // walk through y-neighbors\n      for (i = start; i < end; i++) {\n        val += data[k1 + (i * w)] * wg[i];\n      }\n      result[k] = val > 0.5 ? 1 : 0;\n    }\n\n  return {\n    data: result,\n    width: w,\n    height: h,\n    bounds: {\n      minX: minX,\n      minY: minY,\n      maxX: maxX,\n      maxY: maxY,\n    },\n  };\n}\n\n// Create a border index array of boundary points of the mask with radius-neighbors\n//  @param {Object} mask: {Uint8Array} data, {int} width, {int} height, {Object} bounds\n//  @param {int} blur radius\n//  @param {Uint8Array} visited: mask of visited points (optional)\n//  @return {Array} border index array of boundary points with radius-neighbors (only points need for blur)\n//\n// eslint-disable-next-line complexity\nfunction createBorderForBlur(mask, radius, visited) {\n  let x,\n    i,\n    j,\n    y,\n    k,\n    k1,\n    k2,\n    w = mask.width,\n    h = mask.height,\n    data = mask.data,\n    visitedData = new Uint8Array(data),\n    minX = mask.bounds.minX,\n    maxX = mask.bounds.maxX,\n    minY = mask.bounds.minY,\n    maxY = mask.bounds.maxY,\n    len = w * h,\n    temp = new Uint8Array(len), // auxiliary array to check uniqueness\n    border = [], // only border points\n    x0 = Math.max(minX, 1),\n    x1 = Math.min(maxX, w - 2),\n    y0 = Math.max(minY, 1),\n    y1 = Math.min(maxY, h - 2);\n\n  if (visited && visited.length > 0) {\n    // copy visited points (only \"black\")\n    for (k = 0; k < len; k++) {\n      if (visited[k] === 1) visitedData[k] = 1;\n    }\n  }\n\n  // walk through inner values except points on the boundary of the image\n  for (y = y0; y < y1 + 1; y++)\n    for (x = x0; x < x1 + 1; x++) {\n      k = (y * w) + x;\n      if (data[k] === 0) continue; // \"white\" point isn't the border\n      k1 = k + w; // y + 1\n      k2 = k - w; // y - 1\n      // check if any neighbor with a \"white\" color\n      if (\n        visitedData[k + 1] === 0\n        || visitedData[k - 1] === 0\n        || visitedData[k1] === 0\n        || visitedData[k1 + 1] === 0\n        || visitedData[k1 - 1] === 0\n        || visitedData[k2] === 0\n        || visitedData[k2 + 1] === 0\n        || visitedData[k2 - 1] === 0\n      ) {\n        // if (visitedData[k + 1] + visitedData[k - 1] +\n        //    visitedData[k1] + visitedData[k1 + 1] + visitedData[k1 - 1] +\n        //    visitedData[k2] + visitedData[k2 + 1] + visitedData[k2 - 1] == 8) continue;\n        border.push(k);\n      }\n    }\n\n  // walk through points on the boundary of the image if necessary\n  // if the \"black\" point is adjacent to the boundary of the image, it is a border point\n  if (minX == 0)\n    for (y = minY; y < maxY + 1; y++)\n      if (data[y * w] === 1) border.push(y * w);\n\n  if (maxX == w - 1)\n    for (y = minY; y < maxY + 1; y++)\n      if (data[(y * w) + maxX] === 1) border.push((y * w) + maxX);\n\n  if (minY == 0)\n    for (x = minX; x < maxX + 1; x++) if (data[x] === 1) border.push(x);\n\n  if (maxY == h - 1)\n    for (x = minX; x < maxX + 1; x++)\n      if (data[(maxY * w) + x] === 1) border.push((maxY * w) + x);\n\n  let result = [], // border points with radius-neighbors\n    start,\n    end,\n    endX = radius + w,\n    endY = radius + h,\n    n = (radius * 2) + 1; // size of the pattern for radius-neighbors (from -r to +r with the center point)\n\n  len = border.length;\n  // walk through radius-neighbors of border points and add them to the result array\n  for (j = 0; j < len; j++) {\n    k = border[j]; // index of the border point\n    temp[k] = 1; // mark border point\n    result.push(k); // save the border point\n    x = k % w; // calc x by index\n    y = (k - x) / w; // calc y by index\n    start = radius - x > 0 ? radius - x : 0;\n    end = endX - x < n ? endX - x : n; // Math.min((((w - 1) - x) + radius) + 1, n);\n    k1 = k - radius;\n    // walk through x-neighbors\n    for (i = start; i < end; i++) {\n      k2 = k1 + i;\n      if (temp[k2] === 0) {\n        // check the uniqueness\n        temp[k2] = 1;\n        result.push(k2);\n      }\n    }\n    start = radius - y > 0 ? radius - y : 0;\n    end = endY - y < n ? endY - y : n; // Math.min((((h - 1) - y) + radius) + 1, n);\n    k1 = k - (radius * w);\n    // walk through y-neighbors\n    for (i = start; i < end; i++) {\n      k2 = k1 + (i * w);\n      if (temp[k2] === 0) {\n        // check the uniqueness\n        temp[k2] = 1;\n        result.push(k2);\n      }\n    }\n  }\n\n  return result;\n}\n\n// Apply the gauss-blur filter ONLY to border points with radius-neighbors\n//  Algorithms: http://blog.ivank.net/fastest-gaussian-blur.html\n//  http://www.librow.com/articles/article-9\n//  http://elynxsdk.free.fr/ext-docs/Blur/Fast_box_blur.pdf\n//  @param {Object} mask: {Uint8Array} data, {int} width, {int} height, {Object} bounds\n//  @param {int} blur radius\n//  @param {Uint8Array} visited: mask of visited points (optional)\n//  @return {Object} mask: {Uint8Array} data, {int} width, {int} height, {Object} bounds\n//\nfunction gaussBlurOnlyBorder(mask, radius, visited) {\n  let border = createBorderForBlur(mask, radius, visited), // get border points with radius-neighbors\n    ww,\n    dsq,\n    i,\n    j,\n    k,\n    k1,\n    x,\n    y,\n    val,\n    start,\n    end,\n    n = (radius * 2) + 1, // size of the pattern for radius-neighbors (from -r to +r with center point)\n    s2 = 2 * radius * radius,\n    wg = new Float32Array(n), // weights\n    total = 0, // sum of weights(used for normalization)\n    w = mask.width,\n    h = mask.height,\n    data = mask.data,\n    minX = mask.bounds.minX,\n    maxX = mask.bounds.maxX,\n    minY = mask.bounds.minY,\n    maxY = mask.bounds.maxY,\n    len = border.length;\n\n  // calc gauss weights\n  for (i = 0; i < radius; i++) {\n    dsq = (radius - i) * (radius - i);\n    ww = Math.exp(-dsq / s2) / Math.PI;\n    wg[radius + i] = wg[radius - i] = ww;\n    total += 2 * ww;\n  }\n  // normalization weights\n  for (i = 0; i < n; i++) {\n    wg[i] /= total;\n  }\n\n  let result = new Uint8Array(data), // copy the source mask\n    endX = radius + w,\n    endY = radius + h;\n\n  // walk through all border points for blur\n  for (i = 0; i < len; i++) {\n    k = border[i]; // index of the border point\n    val = 0;\n    x = k % w; // calc x by index\n    y = (k - x) / w; // calc y by index\n    start = radius - x > 0 ? radius - x : 0;\n    end = endX - x < n ? endX - x : n; // Math.min((((w - 1) - x) + radius) + 1, n);\n    k1 = k - radius;\n    // walk through x-neighbors\n    for (j = start; j < end; j++) {\n      val += data[k1 + j] * wg[j];\n    }\n    if (val > 0.5) {\n      result[k] = 1;\n      // check minmax\n      if (x < minX) minX = x;\n      if (x > maxX) maxX = x;\n      if (y < minY) minY = y;\n      if (y > maxY) maxY = y;\n      continue;\n    }\n    start = radius - y > 0 ? radius - y : 0;\n    end = endY - y < n ? endY - y : n; // Math.min((((h - 1) - y) + radius) + 1, n);\n    k1 = k - (radius * w);\n    // walk through y-neighbors\n    for (j = start; j < end; j++) {\n      val += data[k1 + (j * w)] * wg[j];\n    }\n    if (val > 0.5) {\n      result[k] = 1;\n      // check minmax\n      if (x < minX) minX = x;\n      if (x > maxX) maxX = x;\n      if (y < minY) minY = y;\n      if (y > maxY) maxY = y;\n    } else {\n      result[k] = 0;\n    }\n  }\n\n  return {\n    data: result,\n    width: w,\n    height: h,\n    bounds: {\n      minX: minX,\n      minY: minY,\n      maxX: maxX,\n      maxY: maxY,\n    },\n  };\n}\n\n// Create a border mask (only boundary points)\n//  @param {Object} mask: {Uint8Array} data, {int} width, {int} height, {Object} bounds\n//  @return {Object} border mask: {Uint8Array} data, {int} width, {int} height, {Object} offset\n//\n// eslint-disable-next-line complexity\nfunction createBorderMask(mask) {\n  let x,\n    y,\n    k,\n    k1,\n    k2,\n    w = mask.width,\n    h = mask.height,\n    data = mask.data,\n    minX = mask.bounds.minX,\n    maxX = mask.bounds.maxX,\n    minY = mask.bounds.minY,\n    maxY = mask.bounds.maxY,\n    rw = maxX - minX + 1, // bounds size\n    rh = maxY - minY + 1,\n    result = new Uint8Array(rw * rh), // reduced mask (bounds size)\n    x0 = Math.max(minX, 1),\n    x1 = Math.min(maxX, w - 2),\n    y0 = Math.max(minY, 1),\n    y1 = Math.min(maxY, h - 2);\n\n  // walk through inner values except points on the boundary of the image\n  for (y = y0; y < y1 + 1; y++)\n    for (x = x0; x < x1 + 1; x++) {\n      k = (y * w) + x;\n      if (data[k] === 0) continue; // \"white\" point isn't the border\n      k1 = k + w; // y + 1\n      k2 = k - w; // y - 1\n      // check if any neighbor with a \"white\" color\n      if (\n        data[k + 1] === 0\n        || data[k - 1] === 0\n        || data[k1] === 0\n        || data[k1 + 1] === 0\n        || data[k1 - 1] === 0\n        || data[k2] === 0\n        || data[k2 + 1] === 0\n        || data[k2 - 1] === 0\n      ) {\n        // if (data[k + 1] + data[k - 1] +\n        //    data[k1] + data[k1 + 1] + data[k1 - 1] +\n        //    data[k2] + data[k2 + 1] + data[k2 - 1] == 8) continue;\n        result[((y - minY) * rw) + (x - minX)] = 1;\n      }\n    }\n\n  // walk through points on the boundary of the image if necessary\n  // if the \"black\" point is adjacent to the boundary of the image, it is a border point\n  if (minX == 0)\n    for (y = minY; y < maxY + 1; y++)\n      if (data[y * w] === 1) result[(y - minY) * rw] = 1;\n\n  if (maxX == w - 1)\n    for (y = minY; y < maxY + 1; y++)\n      if (data[(y * w) + maxX] === 1)\n        result[((y - minY) * rw) + (maxX - minX)] = 1;\n\n  if (minY == 0)\n    for (x = minX; x < maxX + 1; x++) if (data[x] === 1) result[x - minX] = 1;\n\n  if (maxY == h - 1)\n    for (x = minX; x < maxX + 1; x++)\n      if (data[(maxY * w) + x] === 1)\n        result[((maxY - minY) * rw) + (x - minX)] = 1;\n\n  return {\n    data: result,\n    width: rw,\n    height: rh,\n    offset: { x: minX, y: minY },\n  };\n}\n\n// Create a border index array of boundary points of the mask\n//  @param {Object} mask: {Uint8Array} data, {int} width, {int} height\n//  @return {Array} border index array boundary points of the mask\n//\nfunction getBorderIndices(mask) {\n  let x,\n    y,\n    k,\n    k1,\n    k2,\n    w = mask.width,\n    h = mask.height,\n    data = mask.data,\n    border = [], // only border points\n    x1 = w - 1,\n    y1 = h - 1;\n\n  // walk through inner values except points on the boundary of the image\n  for (y = 1; y < y1; y++)\n    for (x = 1; x < x1; x++) {\n      k = (y * w) + x;\n      if (data[k] === 0) continue; // \"white\" point isn't the border\n      k1 = k + w; // y + 1\n      k2 = k - w; // y - 1\n      // check if any neighbor with a \"white\" color\n      if (\n        data[k + 1] === 0\n        || data[k - 1] === 0\n        || data[k1] === 0\n        || data[k1 + 1] === 0\n        || data[k1 - 1] === 0\n        || data[k2] === 0\n        || data[k2 + 1] === 0\n        || data[k2 - 1] === 0\n      ) {\n        // if (data[k + 1] + data[k - 1] +\n        //    data[k1] + data[k1 + 1] + data[k1 - 1] +\n        //    data[k2] + data[k2 + 1] + data[k2 - 1] == 8) continue;\n        border.push(k);\n      }\n    }\n\n  // walk through points on the boundary of the image if necessary\n  // if the \"black\" point is adjacent to the boundary of the image, it is a border point\n  for (y = 0; y < h; y++) if (data[y * w] === 1) border.push(y * w);\n\n  for (x = 0; x < w; x++) if (data[x] === 1) border.push(x);\n\n  k = w - 1;\n  for (y = 0; y < h; y++) if (data[y * w + k] === 1) border.push((y * w) + k);\n\n  k = (h - 1) * w;\n  for (x = 0; x < w; x++) if (data[k + x] === 1) border.push(k + x);\n\n  return border;\n}\n\n// Create a compressed mask with a \"white\" border (1px border with zero values) for the contour tracing\n//  @param {Object} mask: {Uint8Array} data, {int} width, {int} height, {Object} bounds\n//  @return {Object} border mask: {Uint8Array} data, {int} width, {int} height, {Object} offset\n//\nfunction prepareMask(mask) {\n  let x,\n    y,\n    w = mask.width,\n    data = mask.data,\n    minX = mask.bounds.minX,\n    maxX = mask.bounds.maxX,\n    minY = mask.bounds.minY,\n    maxY = mask.bounds.maxY,\n    rw = maxX - minX + 3, // bounds size +1 px on each side (a \"white\" border)\n    rh = maxY - minY + 3,\n    result = new Uint8Array(rw * rh); // reduced mask (bounds size)\n\n  // walk through inner values and copy only \"black\" points to the result mask\n  for (y = minY; y < maxY + 1; y++)\n    for (x = minX; x < maxX + 1; x++) {\n      if (data[(y * w) + x] === 1)\n        result[((y - minY + 1) * rw) + (x - minX + 1)] = 1;\n    }\n\n  return {\n    data: result,\n    width: rw,\n    height: rh,\n    offset: { x: minX - 1, y: minY - 1 },\n  };\n}\n\n// Create a contour array for the binary mask\n//  Algorithm: http://www.sciencedirect.com/science/article/pii/S1077314203001401\n//  @param {Object} mask: {Uint8Array} data, {int} width, {int} height, {Object} bounds\n//  @return {Array} contours: {Array} points, {bool} inner, {int} label\n//\nfunction traceContours(mask) {\n  let m = prepareMask(mask),\n    contours = [],\n    label = 0,\n    w = m.width,\n    w2 = w * 2,\n    h = m.height,\n    src = m.data,\n    dx = m.offset.x,\n    dy = m.offset.y,\n    dest = new Uint8Array(src), // label matrix\n    i,\n    j,\n    x,\n    y,\n    k,\n    k1,\n    c,\n    inner,\n    dir,\n    first,\n    second,\n    current,\n    previous,\n    next,\n    d;\n\n  // all [dx,dy] pairs (array index is the direction)\n  // 5 6 7\n  // 4 X 0\n  // 3 2 1\n  let directions = [\n    [1, 0],\n    [1, 1],\n    [0, 1],\n    [-1, 1],\n    [-1, 0],\n    [-1, -1],\n    [0, -1],\n    [1, -1],\n  ];\n\n  for (y = 1; y < h - 1; y++)\n    for (x = 1; x < w - 1; x++) {\n      k = (y * w) + x;\n      if (src[k] === 1) {\n        for (i = -w; i < w2; i += w2) {\n          // k - w: outer tracing (y - 1), k + w: inner tracing (y + 1)\n          if (src[k + i] === 0 && dest[k + i] === 0) {\n            // need contour tracing\n            inner = i === w; // is inner contour tracing ?\n            label++; // label for the next contour\n\n            c = [];\n            dir = inner ? 2 : 6; // start direction\n            current = previous = first = { x: x, y: y };\n            second = null;\n            while (true) {\n              dest[(current.y * w) + current.x] = label; // mark label for the current point\n              // bypass all the neighbors around the current point in a clockwise\n              for (j = 0; j < 8; j++) {\n                dir = (dir + 1) % 8;\n\n                // get the next point by new direction\n                d = directions[dir]; // index as direction\n                next = { x: current.x + d[0], y: current.y + d[1] };\n\n                k1 = (next.y * w) + next.x;\n                if (src[k1] === 1) {\n                  // black boundary pixel\n                  dest[k1] = label; // mark a label\n                  break;\n                }\n                dest[k1] = -1; // mark a white boundary pixel\n                next = null;\n              }\n              if (next === null) break; // no neighbours (one-point contour)\n              current = next;\n              if (second) {\n                if (\n                  previous.x === first.x\n                  && previous.y === first.y\n                  && current.x === second.x\n                  && current.y === second.y\n                ) {\n                  break; // creating the contour completed when returned to original position\n                }\n              } else {\n                second = next;\n              }\n              c.push({ x: previous.x + dx, y: previous.y + dy });\n              previous = current;\n              dir = (dir + 4) % 8; // next dir (symmetrically to the current direction)\n            }\n\n            // eslint-disable-next-line max-depth\n            if (next != null) {\n              c.push({ x: first.x + dx, y: first.y + dy }); // close the contour\n              contours.push({ inner: inner, label: label, points: c }); // add contour to the list\n            }\n          }\n        }\n      }\n    }\n\n  return contours;\n}\n\n// Simplify contours\n//  Algorithms: http://psimpl.sourceforge.net/douglas-peucker.html\n//  http://neerc.ifmo.ru/wiki/index.php?title=%D0%A3%D0%BF%D1%80%D0%BE%D1%89%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BF%D0%BE%D0%BB%D0%B8%D0%B3%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B9_%D1%86%D0%B5%D0%BF%D0%B8\n//  @param {Array} contours: {Array} points, {bool} inner, {int} label\n//  @param {float} simplify tolerant\n//  @param {int} simplify count: min number of points when the contour is simplified\n//  @return {Array} contours: {Array} points, {bool} inner, {int} label, {int} initialCount\n//\nfunction simplifyContours(contours, simplifyTolerant, simplifyCount) {\n  let lenContours = contours.length,\n    result = [],\n    i,\n    j,\n    k,\n    c,\n    points,\n    len,\n    resPoints,\n    lst,\n    stack,\n    ids,\n    maxd,\n    maxi,\n    dist,\n    r1,\n    r2,\n    r12,\n    dx,\n    dy,\n    pi,\n    pf,\n    pl;\n\n  // walk through all contours\n  for (j = 0; j < lenContours; j++) {\n    c = contours[j];\n    points = c.points;\n    len = c.points.length;\n\n    if (len < simplifyCount) {\n      // contour isn't simplified\n      resPoints = [];\n      for (k = 0; k < len; k++) {\n        resPoints.push({ x: points[k].x, y: points[k].y });\n      }\n      result.push({\n        inner: c.inner,\n        label: c.label,\n        points: resPoints,\n        initialCount: len,\n      });\n      continue;\n    }\n\n    lst = [0, len - 1]; // always add first and last points\n    stack = [{ first: 0, last: len - 1 }]; // first processed edge\n\n    do {\n      ids = stack.shift();\n      if (ids.last <= ids.first + 1) {\n        // no intermediate points\n        continue;\n      }\n\n      maxd = -1.0; // max distance from point to current edge\n      maxi = ids.first; // index of maximally distant point\n\n      for (\n        i = ids.first + 1;\n        i < ids.last;\n        i++ // bypass intermediate points in edge\n      ) {\n        // calc the distance from current point to edge\n        pi = points[i];\n        pf = points[ids.first];\n        pl = points[ids.last];\n        dx = pi.x - pf.x;\n        dy = pi.y - pf.y;\n        r1 = Math.sqrt((dx * dx) + (dy * dy));\n        dx = pi.x - pl.x;\n        dy = pi.y - pl.y;\n        r2 = Math.sqrt((dx * dx) + (dy * dy));\n        dx = pf.x - pl.x;\n        dy = pf.y - pl.y;\n        r12 = Math.sqrt((dx * dx) + (dy * dy));\n        if (r1 >= Math.sqrt((r2 * r2) + (r12 * r12))) dist = r2;\n        else if (r2 >= Math.sqrt((r1 * r1) + (r12 * r12))) dist = r1;\n        else\n          dist = Math.abs(\n            ((dy * pi.x) - (dx * pi.y) + (pf.x * pl.y) - (pl.x * pf.y)) / r12\n          );\n\n        if (dist > maxd) {\n          maxi = i; // save the index of maximally distant point\n          maxd = dist;\n        }\n      }\n\n      if (maxd > simplifyTolerant) {\n        // if the max \"deviation\" is larger than allowed then...\n        lst.push(maxi); // add index to the simplified list\n        stack.push({ first: ids.first, last: maxi }); // add the left part for processing\n        stack.push({ first: maxi, last: ids.last }); // add the right part for processing\n      }\n    } while (stack.length > 0);\n\n    resPoints = [];\n    len = lst.length;\n    lst.sort((a, b) => {\n      return a - b;\n    }); // restore index order\n    for (k = 0; k < len; k++) {\n      resPoints.push({ x: points[lst[k]].x, y: points[lst[k]].y }); // add result points to the correct order\n    }\n    result.push({\n      inner: c.inner,\n      label: c.label,\n      points: resPoints,\n      initialCount: c.points.length,\n    });\n  }\n\n  return result;\n}\n\nconst MagicWand = {\n  floodFill: floodFill.bind(),\n  gaussBlur: gaussBlur.bind(),\n  gaussBlurOnlyBorder: gaussBlurOnlyBorder.bind(),\n  createBorderMask: createBorderMask.bind(),\n  getBorderIndices: getBorderIndices.bind(),\n  traceContours: traceContours.bind(),\n  simplifyContours: simplifyContours.bind(),\n};\n\nexport default MagicWand;\n","/* eslint-disable no-continue */\nimport MagicWand from \"../../vendor/MagicWand.js\";\nimport CONSTANTS from \"../constants.js\";\nimport Utils from \"../libs/Utils.js\";\n\nexport class MagicLasso {\n\n  #createBaseCanvas() {\n    this.canvas = document.createElement(\"canvas\");\n    this.canvas.id = \"base-canvas\";\n    this.canvas.width = this.width;\n    this.canvas.height = this.height;\n    this.context = this.canvas.getContext(\"2d\", { willReadFrequently: false });\n    this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n  }\n\n  #drawChequeredBackground(width = 7) {\n    this.chequeredSource = document.createElement(\"canvas\");\n    this.chequeredSource.id = \"chequered-canvas\";\n    this.chequeredSource.width = this.width;\n    this.chequeredSource.height = this.height;\n    \n    const context = this.chequeredSource.getContext(\"2d\", { willReadFrequently: false });\n    const columns = Math.ceil(this.chequeredSource.width / width);\n    const rows = Math.ceil(this.chequeredSource.height / width);\n\n    context.fillStyle = \"rgb(212, 163, 19)\";\n    for (let i = 0; i < rows; ++i) {\n      for (let j = 0, col = columns / 2; j < col; ++j) {\n        context.rect(\n          (2 * j * width) + (i % 2 ? 0 : width),\n          i * width,\n          width,\n          width,\n        );\n      }\n    }\n    context.fill();\n  }\n\n  #drawLayerCanvas() {\n    this.layerCanvas = document.createElement(\"canvas\");\n    this.layerCanvas.id = \"layer-canvas\";\n    this.layerCanvas.width = this.width;\n    this.layerCanvas.height = this.height;\n    this.layerContext = this.layerCanvas.getContext(\"2d\", { willReadFrequently: false });\n    const original = Utils.cloneCanvas(this.layer.source);\n    const originalContext = original.getContext(\"2d\", { willReadFrequently: false });\n    this.layer.applyTransformations(originalContext, this.layer.source, false);\n    let source = this.layer.source;\n    if (this.layer.filters.length > 0) {\n      for (const filter of this.layer.filters) {\n        source = filter(original);\n      }\n    }\n\n    this.layerContext\n      .drawImage(\n        source,\n        0,\n        0,\n        this.layer.source.width,\n        this.layer.source.height,\n        this.yOffset,\n        this.xOffset,\n        this.scaledWidth,\n        this.scaledHeight,\n      );\n  }\n\n  #setData() {\n    this.data = this.layerContext.getImageData(\n      0,\n      0,\n      this.canvas.width,\n      this.canvas.height,\n    );\n  }\n\n  #colorPicker() {\n    // a nicer looking proxy for the color picker\n    this.colorSelector = document.getElementById(\"magic-lasso-color-selector\");\n    this.colorSelectorProxy = document.getElementById(\"magic-lasso-color-selector-proxy\");\n\n    this.colorSelectorProxy.addEventListener(\"click\", () => {\n      this.colorSelector.click();\n    });\n\n    // listen to the color Selector onChange Event to update the layer's background color\n    this.colorSelector.addEventListener('change', (event) => {\n      this.colorSelectorProxy.style.backgroundColor = event.target.value;\n      this.colorSelectorProxy.classList.remove('transparent');\n      const button = document.getElementById(\"lasso-fill\");\n      button.disabled = false;\n      this.fillColor = event.target.value;\n    });\n  }\n\n  constructor(layer) {\n    this.container = null;\n    this.layer = layer;\n\n    this.height = Math.min(1000, layer.source.height, layer.source.width);\n    this.width = Math.min(1000, layer.source.height, layer.source.width);\n\n    const crop = game.settings.get(CONSTANTS.MODULE_ID, \"default-crop-image\");\n    // if we crop the image we scale to the smallest dimension of the image\n    // otherwise we scale to the largest dimension of the image\n    const direction = crop ? layer.source.height > layer.source.width : layer.source.height < layer.source.width;\n\n    this.scaledWidth = !direction\n      ? this.height * (layer.source.height / layer.source.width)\n      : this.width;\n    this.scaledHeight = direction\n      ? this.width * (layer.source.height / layer.source.width)\n      : this.height;\n\n    // offset the canvas for the scaled image\n    this.yOffset = (this.width - this.scaledWidth) / 2;\n    this.xOffset = (this.height - this.scaledHeight) / 2;\n\n    this.data = null;\n    this.mask = null;\n    this.oldMask = null;\n    this.context = null;\n\n    // create base canvases\n    this.#drawChequeredBackground();\n    this.#drawLayerCanvas();\n    this.#createBaseCanvas();\n    this.#setData();\n\n    // magic laso vars\n    this.colorThreshold = 15;\n    this.blurRadius = 5;\n    this.simplifyCount = 30;\n    this.hatchLength = 4;\n    this.hatchOffset = 0;\n    this.fillColor = \"f59042\";\n\n    this.cacheInd = null;\n    this.downPoint = null;\n    this.allowDraw = false;\n    this.addMode = false;\n    this.currentThreshold = this.colorThreshold;\n\n    this.canvasChanged = false;\n\n  }\n\n  async display(callback, nestedCallback) {\n    const html = await foundry.applications.handlebars.renderTemplate(\"modules/vtta-tokenizer/templates/magic-lasso.hbs\");\n    const template = document.createElement(\"template\");\n    template.innerHTML = html.trim();\n    this.container = template.content.firstChild;\n    this.wrapper = document.createElement('div');\n    this.wrapper.classList.add('magic-lasso-wrapper');\n    this.wrapper.appendChild(this.chequeredSource);\n    this.wrapper.appendChild(this.layerCanvas);\n    this.wrapper.appendChild(this.canvas);\n    this.container.append(this.wrapper);\n    document.body.appendChild(this.container);\n    this.#activateListeners(callback, nestedCallback);\n\n    this.#colorPicker();\n    this.showThreshold();\n    this.showBlur();\n    this.mask = null;\n\n    this.interval = setInterval(() => {\n      this.hatchTick();\n    }, 300);\n  }\n\n  #activateListeners(callback, nestedCallback) {\n\n    this.callbacks = {\n      callback,\n      nestedCallback,\n    };\n\n    window.addEventListener(\"keyup\", this.onKeyUp.bind(this));\n    window.addEventListener(\"keydown\", this.onKeyDown.bind(this));\n\n    const wrapper = document.getElementById(\"magic-lasso-buttons\");\n    wrapper.addEventListener(\"click\", (event) => {\n      if (event.target.nodeName === 'BUTTON') {\n        this.clickButton(event);\n      }\n    });\n\n    const blurRadius = document.getElementById(\"tokenizer-blur-radius\");\n    blurRadius.addEventListener(\"onchange\", (event) => {\n      this.onRadiusChange(event);\n    });\n\n    this.canvas.addEventListener(\"mouseup\", this.onMouseUp.bind(this));\n    this.canvas.addEventListener(\"mousedown\", this.onMouseDown.bind(this));\n    this.canvas.addEventListener(\"mousemove\", this.onMouseMove.bind(this));\n\n  }\n\n  // eslint-disable-next-line consistent-return\n  #saveAndCleanup(action) {\n    clearInterval(this.interval);\n    window.removeEventListener(\"keyup\", this.onKeyUp);\n    window.removeEventListener(\"keydown\", this.onKeyDown);\n    this.container.remove();\n\n    if (action === \"ok\" && this.canvasChanged) {\n      const source = Utils.cloneCanvas(this.layer.source);\n      // rescale the mask back up for the appropriate layer canvas size\n      const context = source.getContext(\"2d\", { willReadFrequently: false });\n      context.resetTransform();\n      context.clearRect(0, 0, source.width, source.height);\n      source.getContext(\"2d\", { willReadFrequently: false }).drawImage(\n        this.layerCanvas,\n        this.yOffset,\n        this.xOffset,\n        this.scaledWidth,\n        this.scaledHeight,\n        0,\n        0,\n        this.layer.source.width,\n        this.layer.source.height,\n      );\n      return this.callbacks.callback(source, this.callbacks.nestedCallback);\n    }\n  }\n\n  clickButton(event) {\n    event.preventDefault();\n    const action = event.data?.action ?? event.target?.dataset?.action;\n\n    if (action === \"ok\" || action === \"cancel\") {\n      this.#saveAndCleanup(action);\n    } else if (action === \"fill\") {\n      this.fill(1);\n    } else if (action === \"delete\") {\n      this.fill(0);\n    }\n  }\n\n  hatchTick() {\n    this.hatchOffset = (this.hatchOffset + 1) % (this.hatchLength * 2);\n    this.drawBorder(true);\n  }\n\n  onRadiusChange(event) {\n    this.blurRadius = event.target.value;\n    this.showBlur();\n  }\n\n  #getMousePointer(event) {\n    const realPoint = Utils.getCanvasCords(this.layerCanvas, event);\n    return {\n      x: Math.floor(realPoint.x),\n      y: Math.floor(realPoint.y),\n    };\n  }\n\n  onMouseDown(event) {\n    if (event.button == 0) {\n      this.allowDraw = true;\n      this.addMode = event.ctrlKey;\n      this.downPoint = this.#getMousePointer(event);\n      this.drawMask(this.downPoint.x, this.downPoint.y);\n    } else {\n      this.allowDraw = false;\n      this.addMode = false;\n      this.oldMask = null;\n    }\n  }\n\n  onMouseMove(event) {\n    if (this.allowDraw) {\n      const p = this.#getMousePointer(event);\n      if (p.x != this.downPoint.x || p.y != this.downPoint.y) {\n        let dx = p.x - this.downPoint.x,\n          dy = p.y - this.downPoint.y,\n          len = Math.sqrt((dx * dx) + (dy * dy)),\n          adx = Math.abs(dx),\n          ady = Math.abs(dy),\n          sign = adx > ady ? dx / adx : dy / ady;\n        sign = sign < 0 ? sign / 5 : sign / 3;\n        let threshold = Math.min(\n          Math.max(this.colorThreshold + Math.floor(sign * len), 1),\n          255,\n        );\n        if (threshold != this.currentThreshold) {\n          this.currentThreshold = threshold;\n          this.drawMask(this.downPoint.x, this.downPoint.y);\n        }\n      }\n    }\n  }\n\n  onMouseUp() {\n    this.allowDraw = false;\n    this.addMode = false;\n    this.oldMask = null;\n    this.currentThreshold = this.colorThreshold;\n  }\n\n  onKeyDown(event) {\n    if (event.keyCode == 17) this.canvas.classList.add(\"add-mode\");\n  }\n\n  // eslint-disable-next-line consistent-return\n  onKeyUp(event) {\n    switch (event.keyCode) {\n      // ctrl\n      case 17:\n        this.canvas.classList.remove(\"add-mode\");\n        break;\n      // f\n      case 70:\n        this.fill(1);\n        break;\n      // delete\n      case 68:\n      case 46:\n      case 8: \n        this.fill(0);\n        break;\n      // enter\n      case 13:\n        this.#saveAndCleanup(\"ok\");\n        break;\n      // escape\n      case 27:\n        this.#saveAndCleanup(\"cancel\");\n        break;\n      // no default\n    }\n  }\n\n  showThreshold() {\n    document.getElementById(\"tokenizer-threshold\").innerHTML = `Threshold: ${this.currentThreshold}`;\n  }\n\n  showBlur() {\n    document.getElementById(\"tokenizer-blur-radius\").value = this.blurRadius;\n  }\n\n  drawMask(x, y) {\n    if (!this.data) return;\n\n    this.showThreshold();\n\n    let image = {\n      data: this.data.data,\n      width: this.canvas.width,\n      height: this.canvas.height,\n      bytes: 4,\n    };\n\n    if (this.addMode && !this.oldMask) {\n      this.oldMask = this.mask;\n    }\n\n    let old = this.oldMask ? this.oldMask.data : null;\n    this.mask = MagicWand.floodFill(image, x, y, this.currentThreshold, old, true);\n\n    if (this.mask) this.mask = MagicWand.gaussBlurOnlyBorder(this.mask, this.blurRadius, old);\n\n    if (this.addMode && this.oldMask) {\n      this.mask = this.mask ? MagicLasso.concatMasks(this.mask, this.oldMask) : this.oldMask;\n    }\n\n    this.drawBorder();\n  }\n\n  drawBorder(noBorder) {\n    if (!this.mask) return;\n\n    let x,\n      y,\n      i,\n      j,\n      k,\n      width = this.canvas.width,\n      height = this.canvas.height;\n\n    let imageData = this.layerContext.createImageData(width, height);\n\n    if (!noBorder) this.cacheInd = MagicWand.getBorderIndices(this.mask);\n\n    this.context.clearRect(0, 0, width, height);\n\n    const len = this.cacheInd.length;\n    for (j = 0; j < len; j++) {\n      i = this.cacheInd[j];\n      x = i % width; // calc x by index\n      y = (i - x) / width; // calc y by index\n      k = ((y * width) + x) * 4;\n      if ((x + y + this.hatchOffset) % (this.hatchLength * 2) < this.hatchLength) {\n        // detect hatch color\n        imageData.data[k + 3] = 255; // black, change only alpha\n      } else {\n        imageData.data[k] = 255; // white\n        imageData.data[k + 1] = 255;\n        imageData.data[k + 2] = 255;\n        imageData.data[k + 3] = 255;\n      }\n    }\n\n    this.context.putImageData(imageData, 0, 0);\n  }\n\n  trace() {\n    let cs = MagicWand.traceContours(this.mask);\n    cs = MagicWand.simplifyContours(cs, this.simplifyTolerant, this.simplifyCount);\n\n    this.mask = null;\n\n    // draw contours\n    this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n    // inner\n    this.context.beginPath();\n    for (let i = 0; i < cs.length; i++) {\n      if (!cs[i].inner) continue;\n      let ps = cs[i].points;\n      this.context.moveTo(ps[0].x, ps[0].y);\n      for (let j = 1; j < ps.length; j++) {\n        this.context.lineTo(ps[j].x, ps[j].y);\n      }\n    }\n    this.context.strokeStyle = \"red\";\n    this.context.stroke();\n    // outer\n    this.context.beginPath();\n    for (let i = 0; i < cs.length; i++) {\n      if (cs[i].inner) continue;\n      let ps = cs[i].points;\n      this.context.moveTo(ps[0].x, ps[0].y);\n      for (let j = 1; j < ps.length; j++) {\n        this.context.lineTo(ps[j].x, ps[j].y);\n      }\n    }\n    this.context.strokeStyle = \"blue\";\n    this.context.stroke();\n  }\n\n  static hexToRgb(hex, alpha) {\n    const int = parseInt(hex.replace(/^#/, \"\"), 16);\n    // eslint-disable-next-line no-bitwise\n    const r = (int >> 16) & 255;\n    // eslint-disable-next-line no-bitwise\n    const g = (int >> 8) & 255;\n    // eslint-disable-next-line no-bitwise\n    const b = int & 255;\n  \n    return [r, g, b, Math.round(alpha * 255)];\n  }\n\n  fill(alpha = 0.35) {\n    if (!this.mask) return;\n\n    const fullAlpha = (alpha === 0);\n    const rgba = MagicLasso.hexToRgb(this.fillColor, alpha);\n\n    let x,\n      y,\n      data = this.mask.data,\n      bounds = this.mask.bounds,\n      maskW = this.mask.width,\n      width = this.canvas.width,\n      height = this.canvas.height,\n      imgData = this.layerContext.getImageData(0, 0, width, height);\n\n    for (y = bounds.minY; y <= bounds.maxY; y++) {\n      for (x = bounds.minX; x <= bounds.maxX; x++) {\n        if (data[(y * maskW) + x] == 0) continue;\n        const k = ((y * width) + x) * 4;\n        imgData.data[k] = fullAlpha ? 0 : rgba[0];\n        imgData.data[k + 1] = fullAlpha ? 0 : rgba[1];\n        imgData.data[k + 2] = fullAlpha ? 0 : rgba[2];\n        imgData.data[k + 3] = fullAlpha ? 0 : rgba[3];\n      }\n    }\n\n    this.mask = null;\n  \n    this.context.clearRect(0, 0, width, height);\n    this.layerContext.putImageData(imgData, 0, 0);\n    this.canvasChanged = true;\n  }\n\n  static concatMasks(mask, old) {\n    let data1 = old.data,\n      data2 = mask.data,\n      w1 = old.width,\n      w2 = mask.width,\n      b1 = old.bounds,\n      b2 = mask.bounds,\n      b = {\n        // bounds for new mask\n        minX: Math.min(b1.minX, b2.minX),\n        minY: Math.min(b1.minY, b2.minY),\n        maxX: Math.max(b1.maxX, b2.maxX),\n        maxY: Math.max(b1.maxY, b2.maxY),\n      },\n      w = old.width, // size for new mask\n      h = old.height,\n      i,\n      j,\n      k,\n      k1,\n      k2,\n      len;\n  \n    let result = new Uint8Array(w * h);\n  \n    // copy all old mask\n    len = b1.maxX - b1.minX + 1;\n    i = (b1.minY * w) + b1.minX;\n    k1 = (b1.minY * w1) + b1.minX;\n    k2 = (b1.maxY * w1) + b1.minX + 1;\n    // walk through rows (Y)\n    for (k = k1; k < k2; k += w1) {\n      result.set(data1.subarray(k, k + len), i); // copy row\n      i += w;\n    }\n  \n    // copy new mask (only \"black\" pixels)\n    len = b2.maxX - b2.minX + 1;\n    i = (b2.minY * w) + b2.minX;\n    k1 = (b2.minY * w2) + b2.minX;\n    k2 = (b2.maxY * w2) + b2.minX + 1;\n    // walk through rows (Y)\n    for (k = k1; k < k2; k += w2) {\n      // walk through cols (X)\n      for (j = 0; j < len; j++) {\n        if (data2[k + j] === 1) result[i + j] = 1;\n      }\n      i += w;\n    }\n  \n    return {\n      data: result,\n      width: w,\n      height: h,\n      bounds: b,\n    };\n  }\n\n}\n","import Utils from '../libs/Utils.js';\nimport { geom } from '../libs/MarchingSquares.js';\nimport CONSTANTS from '../constants.js';\nimport { generateRayMask } from '../libs/RayMask.js';\nimport { Masker } from './Masker.js';\nimport Color from '../libs/Color.js';\nimport logger from '../libs/logger.js';\nimport { MagicLasso } from './MagicLasso.js';\n\nexport default class Layer {\n\n  resetMasks() {\n    this.customMaskLayers = false;\n    this.appliedMaskIds.clear();\n    this.view.layers.forEach((l) => {\n      if (l.providesMask && this.view.isOriginLayerHigher(l.id, this.id)) {\n        this.appliedMaskIds.add(l.id);\n      }\n    });\n    this.compositeOperation = CONSTANTS.BLEND_MODES.SOURCE_OVER;\n    this.maskCompositeOperation = CONSTANTS.BLEND_MODES.SOURCE_IN;\n    this.customMask = false;\n    this.mask = this.sourceMask ? Utils.cloneCanvas(this.sourceMask) : null;\n    this.redraw();\n  }\n\n  reset() {\n    this.source = Utils.cloneCanvas(this.original);\n    this.alphaPixelColors.clear();\n    this.resetMasks();\n    this.scale = this.width / Math.max(this.source.width, this.source.height);\n    this.rotation = 0;\n    this.position.x = Math.floor((this.width / 2) - ((this.source.width * this.scale) / 2));\n    this.position.y = Math.floor((this.height / 2) - ((this.source.height * this.scale) / 2));\n    this.mask = null;\n    this.filters = [];\n    this.redraw();\n    if (this.providesMask) this.createMask();\n    this.recalculateMask();\n  }\n\n  constructor({\n    type, view, canvas, tintColor, tintLayer, img = null, color = null, maskFromImage = false, visible = true,\n    filters = [], contrast, brightness,\n  } = {}) {\n    this.type = type ?? null;\n    this.view = view;\n    this.id = Utils.generateUUID();\n    this.canvas = canvas;\n    // keep a copy of the source to work transforms from\n    this.source = Utils.cloneCanvas(this.canvas);\n    // canvas referencing to the source (image) that will be displayed on the view canvas\n    this.preview = Utils.cloneCanvas(this.canvas);\n    // for reset purposes\n    this.original = Utils.cloneCanvas(this.canvas);\n\n    // the current position of the source image on the view canvas\n    this.position = {\n      x: 0,\n      y: 0,\n    };\n\n    // the current scale, will be calculated once an image is loaded into the view canvas\n    this.scale = 1;\n\n    // the current degree of rotation\n    this.rotation = 0;\n\n    // mirror\n    this.center = { x: this.canvas.width / 2, y: this.canvas.height / 2 };\n    this.mirror = 1;\n    this.flipped = false;\n\n    // the image drawn on the source, kept for rotations\n    if (img) {\n      this.img = img;\n      this.sourceImg = img.src;\n    }\n\n    // active layers allow mouse events to be followed (scale/translate)\n    this.active = false;\n\n    // source mask is the mask generated by the source image, and mask can be another mask\n    // from another layer\n    this.providesMask = false;\n    this.renderedMask = document.createElement('canvas');\n    this.renderedMask.width = this.source.width;\n    this.renderedMask.height = this.source.height;\n    this.mask = null;\n    this.sourceMask = null;\n    this.maskCompositeOperation = CONSTANTS.BLEND_MODES.SOURCE_IN;\n    this.customMask = false;\n    this.maskFromImage = maskFromImage;\n\n    // mask ids to apply to this layer\n    this.appliedMaskIds = new Set();\n    this.customMaskLayers = false;\n\n    this.alpha = 1.0;\n    this.compositeOperation = CONSTANTS.BLEND_MODES.SOURCE_OVER;\n    this.visible = visible;\n\n    // initialize with color\n    this.previousColor = null;\n    this.color = color;\n    this.colorLayer = color !== null;\n\n    // extra alpha pixels\n    this.previousAlphaPixelColors = null;\n    this.alphaPixelColors = new Set();\n\n    // tint the layer?\n    this.tintLayer = tintLayer;\n    this.tintColor = tintColor;\n    // this.tintColor = \"#f59042\";\n\n    this.contrast = contrast ?? 0;\n    this.brightness = brightness ?? 0;\n    this.lineArtBlurSize = 25;\n\n    this.filters = filters;\n  }\n\n  static fromLayer(layer) {\n    const newLayer = new Layer({\n      type: layer.type == \"original\" ? \"clone\" : layer.type,\n      view: layer.view,\n      canvas: Utils.cloneCanvas(layer.canvas),\n      img: layer.img,\n      tintColor: layer.tintColor,\n      tintLayer: layer.tintLayer,\n      maskFromImage: layer.maskFromImage,\n      visible: layer.visible,\n      color: layer.color,\n      filters: foundry.utils.deepClone(layer.filters),\n      contrast: layer.contrast,\n      brightness: layer.brightness,\n    });\n\n    newLayer.colorLayer = layer.colorLayer;\n    newLayer.source = Utils.cloneCanvas(layer.source);\n    newLayer.canvas = Utils.cloneCanvas(layer.canvas);\n    newLayer.preview = Utils.cloneCanvas(layer.preview);\n    newLayer.original = Utils.cloneCanvas(layer.original);\n\n    newLayer.providesMask = layer.providesMask;\n\n    newLayer.scale = layer.scale;\n    newLayer.rotation = layer.rotation;\n    newLayer.position = foundry.utils.deepClone(layer.position);\n    newLayer.center = layer.center;\n    newLayer.mirror = layer.mirror;\n    newLayer.flipped = layer.flipped;\n    newLayer.visible = layer.visible;\n    newLayer.alpha = layer.alpha;\n    newLayer.lineArtBlurSize = layer.lineArtBlurSize;\n\n    if (layer.mask) newLayer.mask = Utils.cloneCanvas(layer.mask);\n    if (layer.sourceMask) layer.sourceMask = Utils.cloneCanvas(layer.sourceMask);\n    if (layer.renderedMask) layer.renderedMask = Utils.cloneCanvas(layer.renderedMask);\n    newLayer.customMask = layer.customMask;\n    newLayer.customMaskLayers = layer.customMaskLayers;\n    newLayer.appliedMaskIds = new Set(layer.appliedMaskIds);\n\n    newLayer.compositeOperation = layer.compositeOperation;\n    newLayer.maskCompositeOperation = layer.maskCompositeOperation;\n    newLayer.maskFromImage = layer.maskFromImage;\n\n    newLayer.alphaPixelColors = new Set(layer.alphaPixelColors);\n    if (layer.previousAlphaPixelColors) newLayer.previousAlphaPixelColors = new Set(layer.previousAlphaPixelColors);\n\n    newLayer.redraw();\n    return newLayer;\n\n  }\n\n  clone() {\n    const newLayer = Layer.fromLayer(this);\n    return newLayer;\n  }\n\n  static isTransparent(pixels, x, y) {\n    return CONSTANTS.TRANSPARENCY_THRESHOLD < pixels.data[(((y * pixels.width) + x) * 4) + 3];\n  }\n\n  getLayerLabel(active = false) {\n    const index = this.view.layers.findIndex((layer) => layer.id === this.id);\n\n    if (index === -1) return \"?\";\n    if (active) {\n      return CONSTANTS.NUMBERS.ACTIVE[index];\n    } else {\n      return CONSTANTS.NUMBERS.INACTIVE[index];\n    }\n  }\n\n  applyCustomMask(mask, callback) {\n    this.customMask = true;\n    this.mask = mask;\n    const maskContext = this.renderedMask.getContext('2d', {\n      willReadFrequently: false,\n    });\n    maskContext.resetTransform();\n    maskContext.clearRect(0, 0, this.canvas.width, this.canvas.height);\n    maskContext.drawImage(this.mask, 0, 0, this.canvas.width, this.canvas.height);\n    callback(true);\n  }\n\n  editMask(callback) {\n    const maskEditor = new Masker(this);\n    maskEditor.display(this.applyCustomMask.bind(this), callback).then(() => {\n      maskEditor.draw();\n    });\n  }\n\n  applyMagicLasso(canvas, callback) {\n    this.source = canvas;\n    callback(true);\n  }\n\n  magicLasso(callback) {\n    const magicLasso = new MagicLasso(this);\n    magicLasso.display(this.applyMagicLasso.bind(this), callback);\n  }\n\n  /**\n   * Activates the event listeners on the view canvas for scaling and translating\n   */\n  activate() {\n    this.active = true;\n  }\n\n  /**\n   * Deactivates the event listeners on the view canvas for scaling and translating (color picking is always active)\n   */\n  deactivate() {\n    this.active = false;\n  }\n\n  isCompletelyTransparent() {\n    const pixels = this.source.getContext('2d').getImageData(0, 0, this.source.width, this.source.height).data;\n    for (let index = 0; index < pixels.length; index += 4) {\n      if (pixels[index + 3] > CONSTANTS.TRANSPARENCY_THRESHOLD) {\n        return false;\n      }\n    }\n\n    return true;\n  }\n\n  isCompletelyOpaque() {\n    const pixels = this.source.getContext('2d').getImageData(0, 0, this.source.width, this.source.height).data;\n    for (let index = 0; index < pixels.length; index += 4) {\n      if (pixels[index + 3] < CONSTANTS.TRANSPARENCY_THRESHOLD) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  /**\n   * Creates a mask using the marching squares algorithm by walking the edges of the non-transparent pixels to find a contour.\n   * Works naturally best for token images which have a circular ring-shape. The algorithm walks the contour and fills the inner regions with black, too\n   * The mask is not active on creating, it is controlled by\n   *\n   * this.applyMask(mask | null), see above\n   */\n  createOriginalMask() {\n    // create intermediate canvas\n    const temp = document.createElement('canvas');\n    // create a canvas that has at least a 1px transparent border all around\n    // so the marching squares algorithm won't run endlessly\n    temp.width = CONSTANTS.MASK_DENSITY + 2;\n    temp.height = CONSTANTS.MASK_DENSITY + 2;\n    temp.getContext('2d', {\n      willReadFrequently: false,\n    }).drawImage(this.canvas, 1, 1, this.canvas.width, this.canvas.height, 1, 1, CONSTANTS.MASK_DENSITY, CONSTANTS.MASK_DENSITY);\n\n    // get the pixel data from the source image\n    let context = temp.getContext('2d', {\n      willReadFrequently: false,\n    });\n    const pixels = context.getImageData(0, 0, CONSTANTS.MASK_DENSITY + 2, CONSTANTS.MASK_DENSITY + 2);\n\n    // re-use the intermediate canvas\n    const defaultFillColor = game.settings.get(CONSTANTS.MODULE_ID, \"default-color\");\n    if (defaultFillColor !== \"\") context.fillStyle = defaultFillColor;\n    context.strokeStyle = '#000000AA';\n    context.lineWidth = 1;\n    context.fillStyle = \"black\";\n\n    // the mask is totally transparent\n    if (this.isCompletelyTransparent()) {\n      context.clearRect(0, 0, temp.width, temp.height);\n    } else if (this.isCompletelyOpaque()) {\n      context.clearRect(0, 0, temp.width, temp.height);\n      context.fillRect(0, 0, temp.width, temp.height);\n      context.fill();\n    } else {\n      // process the pixel data\n      const points = geom.contour((x, y) => Layer.isTransparent(pixels, x, y));\n      context.clearRect(0, 0, temp.width, temp.height);\n      context.beginPath();\n      context.moveTo(points[0][0], points[0][4]);\n      for (let i = 1; i < points.length; i++) {\n        const point = points[i];\n        context.lineTo(point[0], point[1]);\n      }\n      context.closePath();\n      context.fill();\n    }\n\n    return temp;\n  }\n\n  createMaskFromImage() {\n    const mask = document.createElement(\"canvas\");\n    mask.width = this.canvas.width;\n    mask.height = this.canvas.height;\n\n    mask\n      .getContext('2d', {\n        willReadFrequently: false,\n      })\n      .drawImage(this.canvas, 0, 0, this.canvas.width, this.canvas.height);\n\n    const context = mask.getContext(\"2d\", {\n      willReadFrequently: false,\n    });\n    context.fillStyle = \"black\";\n    context.globalCompositeOperation = \"source-in\";\n    // context.globalCompositeOperation = \"destination-over\";\n    context.fillRect(0, 0, mask.width, mask.height);\n\n    return mask;\n  }\n\n  createMask() {\n    if (!this.renderedMask) {\n      this.renderedMask = document.createElement('canvas');\n      this.renderedMask.width = this.source.width;\n      this.renderedMask.height = this.source.height;\n    }\n    const rayMask = game.settings.get(CONSTANTS.MODULE_ID, \"default-algorithm\");\n    this.mask = this.maskFromImage\n      ? this.createMaskFromImage()\n      : rayMask\n        ? generateRayMask(this.canvas)\n        : this.createOriginalMask();\n    const maskContext = this.renderedMask.getContext('2d', {\n      willReadFrequently: false,\n    });\n    maskContext.resetTransform();\n    maskContext.drawImage(this.mask, 0, 0, this.canvas.width, this.canvas.height);\n\n    this.sourceMask = Utils.cloneCanvas(this.mask);\n  }\n\n  static fromImage({ view, img, canvasHeight, canvasWidth, tintColor, tintLayer, maskFromImage, visible, type = \"image\" } = {}) {\n    const height = Math.max(1000, canvasHeight, img.naturalHeight, img.naturalWidth);\n    const width = Math.max(1000, canvasWidth, img.naturalHeight, img.naturalWidth);\n    const canvas = document.createElement(\"canvas\");\n    canvas.width = width;\n    canvas.height = height;\n\n    const crop = game.settings.get(CONSTANTS.MODULE_ID, \"default-crop-image\");\n    // if we crop the image we scale to the smallest dimension of the image\n    // otherwise we scale to the largest dimension of the image\n    const direction = crop ? img.naturalHeight > img.naturalWidth : img.naturalHeight < img.naturalWidth;\n\n    const scaledWidth = !direction\n      ? height * (img.width / img.height)\n      : width;\n    const scaledHeight = direction\n      ? width * (img.height / img.width)\n      : height;\n\n    // offset the canvas for the scaled image\n    const yOffset = (width - scaledWidth) / 2;\n    const xOffset = (height - scaledHeight) / 2;\n\n    const context = canvas.getContext(\"2d\", {\n      willReadFrequently: false,\n    });\n    context.drawImage(\n        img,\n        0,\n        0,\n        img.naturalWidth,\n        img.naturalHeight,\n        yOffset,\n        xOffset,\n        scaledWidth,\n        scaledHeight,\n      );\n\n    const layer = new Layer({ type, view, canvas, img, tintColor, tintLayer, maskFromImage, visible });\n    // layer.createMask();\n    layer.redraw();\n    return layer;\n  }\n\n  /**\n   * Sets the background color for this layer. It will be masked, too\n   * @param {color} hexColorString\n   */\n  setColor(hexColorString = null) {\n    if (!this.colorLayer) return;\n    this.color = hexColorString;\n    const context = this.canvas.getContext(\"2d\", { willReadFrequently: false });\n    context.fillStyle = hexColorString;\n    context.rect(0, 0, this.width, this.height);\n    context.fill();\n    this.source = Utils.cloneCanvas(this.canvas);\n  }\n\n  static fromColor({ view, color, width, height } = {}) {\n    const canvas = document.createElement(\"canvas\");\n    canvas.width = width;\n    canvas.height = height;\n\n    const layer = new Layer({ type: \"color\", view, canvas, color });\n    layer.setColor(color);\n    return layer;\n  }\n\n  saveColor() {\n    this.previousColor = this.color;\n  }\n\n  restoreColor() {\n    this.setColor(this.previousColor);\n  }\n\n  saveAlphas() {\n    this.previousAlphaPixelColors = new Set(this.alphaPixelColors);\n  }\n\n  restoreAlphas() {\n    this.alphaPixelColors = new Set(this.previousAlphaPixelColors);\n  }\n\n  /**\n   * Gets the width of the view canvas\n   */\n  get width() {\n    return this.canvas.width;\n  }\n\n  /**\n   * Gets the height of the view canvas\n   */\n  get height() {\n    return this.canvas.height;\n  }\n\n  /**\n   * Translates the source on the view canvas\n   * @param {Number} dx translation on the x-axis\n   * @param {Number} dy translation on the y-axis\n   */\n  translate(dx, dy) {\n    this.position.x -= dx;\n    this.position.y -= dy;\n    // this.redraw();\n  }\n\n  centre() {\n    this.position.x = Math.floor((this.width / 2) - ((this.source.width * this.scale) / 2));\n    this.position.y = Math.floor((this.height / 2) - ((this.source.height * this.scale) / 2));\n    // this.redraw();\n  }\n\n  rotate(degree) {\n    this.rotation += degree * 2;\n  }\n\n  flip() {\n    this.mirror *= -1;\n    this.flipped = !this.flipped;\n    this.redraw();\n  }\n\n  scaleByPercent(percentage) {\n    const newWidth = this.original.width * (percentage / 100);\n    const newHeight = this.original.width * (percentage / 100);\n\n    const xOffset = (this.original.width - newWidth) / 2;\n    const yOffset = (this.original.width - newHeight) / 2;\n\n    this.scale = (percentage / 100);\n    this.position.x = xOffset;\n    this.position.y = yOffset;\n  }\n\n  applyTransparentPixels(context) {\n    if (this.alphaPixelColors.size === 0) return;\n\n    let imageData = context.getImageData(0, 0, this.canvas.width, this.canvas.height);\n    this.alphaPixelColors.forEach((color) => {\n      // iterate over all pixels\n      let count = 0;\n      for (let i = 0, n = imageData.data.length; i < n; i += 4) {\n        const pixelColor = new Color({\n          red: imageData.data[i],\n          blue: imageData.data[i + 1],\n          green: imageData.data[i + 2],\n          alpha: imageData.data[i + 3],\n        });\n        if (color.isNeighborColor(pixelColor)) {\n          count++;\n          imageData.data[i] = 0;\n          imageData.data[i + 1] = 0;\n          imageData.data[i + 2] = 0;\n          imageData.data[i + 3] = 0;\n        }\n      }\n      logger.debug(\"Applying the following color transparency\", { color, count });\n    });\n    context.putImageData(imageData, 0, 0);\n  }\n\n  addTransparentColour(color) {\n    this.alphaPixelColors.add(color);\n  }\n\n  applyTransformations(context, alpha = true) {\n    context.resetTransform();\n    context.clearRect(0, 0, this.source.width, this.source.height);\n    context.translate(this.center.x, this.center.y);\n    context.scale(this.mirror * 1, 1);\n    context.rotate(this.rotation * CONSTANTS.TO_RADIANS);\n    context.translate(-this.center.x, -this.center.y);\n    if (alpha) context.globalAlpha = this.alpha;\n  }\n\n  recalculateMask() {\n    if (this.mask && this.renderedMask && !this.customMask) {\n      const context = this.renderedMask.getContext('2d');\n      this.applyTransformations(context, false);\n      context.drawImage(\n        this.mask,\n        this.position.x,\n        this.position.y,\n        this.source.width * this.scale,\n        this.source.height * this.scale,\n      );\n    }\n  }\n\n  getFilteredCanvas(filters = \"\") {\n    const filterCanvas = Utils.cloneCanvas(this.source);\n    const filterContext = filterCanvas.getContext(\"2d\", { willReadFrequently: false });\n    filterCanvas.width = this.source.width;\n    filterCanvas.height = this.source.height;\n    filterContext.filter = filters;\n    filterContext.drawImage(this.source, 0, 0);\n    return filterCanvas;\n  }\n\n  adjustBrightness(context) {\n    // Get pixel data from the canvas\n    const imageData = context.getImageData(0, 0, context.canvas.width, context.canvas.height);\n    const data = imageData.data; // Uint8ClampedArray [R, G, B, A, R, G, B, A, ...]\n \n    // Loop through each pixel (4 values per pixel: R, G, B, A)\n    for (let i = 0; i < data.length; i += 4) {\n      // Adjust R, G, B (skip A)\n      data[i] = Math.max(0, Math.min(255, data[i] + this.brightness)); // Red\n      data[i + 1] = Math.max(0, Math.min(255, data[i + 1] + this.brightness)); // Green\n      data[i + 2] = Math.max(0, Math.min(255, data[i + 2] + this.brightness)); // Blue\n      // Alpha (data[i+3]) remains unchanged\n    }\n \n    // Put modified data back on canvas\n    context.putImageData(imageData, 0, 0);\n  }\n\n  adjustContrast(context) {\n    const imageData = context.getImageData(0, 0, context.canvas.width, context.canvas.height);\n    const data = imageData.data;\n    const factor = (259 * (this.contrast + 255)) / (255 * (259 - this.contrast));\n\n    for (let i = 0; i < data.length; i += 4) {\n      // Apply contrast formula and clamp\n      // eslint-disable-next-line no-mixed-operators\n      data[i] = Math.max(0, Math.min(255, factor * (data[i] - 128) + 128)); // Red\n      // eslint-disable-next-line no-mixed-operators\n      data[i + 1] = Math.max(0, Math.min(255, factor * (data[i + 1] - 128) + 128)); // Green\n      // eslint-disable-next-line no-mixed-operators\n      data[i + 2] = Math.max(0, Math.min(255, factor * (data[i + 2] - 128) + 128)); // Blue\n    }\n\n    context.putImageData(imageData, 0, 0);\n  }\n\n  applyTint(context) {\n    const tintCanvas = Utils.cloneCanvas(this.source);\n    const tintContext = tintCanvas.getContext(\"2d\", { willReadFrequently: false });\n    tintCanvas.width = this.source.width;\n    tintCanvas.height = this.source.height;\n    this.applyTransformations(tintContext, false);\n    tintContext.drawImage(this.source, 0, 0);\n    tintContext.globalCompositeOperation = 'source-atop';\n    tintContext.fillStyle = this.tintColor;\n    tintContext.alpha = 0.5;\n    tintContext.fillRect(0, 0, this.source.width, this.source.height);  \n    tintContext.globalCompositeOperation = 'source-over';\n\n    context.globalCompositeOperation = 'color';\n    context.drawImage(tintCanvas, 0, 0);\n    context.globalCompositeOperation = 'source-over';\n  }\n\n  lineArtEffect(canvas) {\n    const original = Utils.cloneCanvas(canvas);\n    const context = original.getContext(\"2d\", { willReadFrequently: false });\n    const bnw = this.getFilteredCanvas(\"grayscale(1)\");\n    const blur = this.getFilteredCanvas(`grayscale(1) invert(1) blur(${this.lineArtBlurSize}px)`);\n    context.drawImage(bnw, 0, 0, this.canvas.width, this.canvas.height);\n    context.globalCompositeOperation = 'color-dodge';\n    context.drawImage(blur, 0, 0, this.canvas.width, this.canvas.height);\n    context.globalCompositeOperation = 'source-over';\n    return original;\n  }\n\n  /**\n   * Refreshes the view canvas with the background color and/or the source image\n   */\n  redraw() {\n    // we take the original image and apply our scaling transformations\n    const original = Utils.cloneCanvas(this.source);\n    // apply transformations to original\n    const originalContext = original.getContext(\"2d\", {\n      willReadFrequently: false,\n    });\n    this.applyTransformations(originalContext, false);\n    let source = Utils.cloneCanvas(this.source);\n    if (this.filters.length > 0) {\n      for (const filter of this.filters) {\n        source = filter(original);\n      }\n    }\n    const sourceContext = source.getContext(\"2d\", {\n      willReadFrequently: false,\n    });\n    this.adjustBrightness(sourceContext);\n    this.adjustContrast(sourceContext);\n    originalContext.drawImage(source, 0, 0);\n    if (this.tintLayer) this.applyTint(originalContext);\n    originalContext.resetTransform();\n\n    // place the computed layer on the view canvas\n\n    const preview = this.preview.getContext(\"2d\", {\n      willReadFrequently: false,\n    });\n    const context = this.canvas.getContext(\"2d\", {\n      willReadFrequently: false,\n    });\n    [context, preview].forEach((cContext) => {\n      cContext.globalCompositeOperation = this.compositeOperation;\n      cContext.clearRect(0, 0, this.source.width, this.source.height);\n      cContext.resetTransform();\n    });\n\n    const maskIds = this.customMaskLayers ? this.appliedMaskIds : this.view.maskIds;\n    for (const maskId of maskIds) {\n      const maskLayer = this.view.getMaskLayer(maskId);\n      // we apply the mask if the layer is below a masking layer if not using custom masking layers\n      if (maskLayer\n        && (this.customMaskLayers || (!this.customMaskLayers && this.view.isOriginLayerHigher(maskId, this.id)))\n      ) {\n        context.drawImage(\n          maskLayer.renderedMask,\n          0,\n          0,\n          maskLayer.width,\n          maskLayer.height,\n          0,\n          0,\n          this.canvas.width,\n          this.canvas.height,\n        );\n        context.globalCompositeOperation = this.maskCompositeOperation;\n      }\n    }\n\n    [context, preview].forEach((cContext) => {\n      cContext.translate(0, 0);\n\n      if (this.colorLayer) {\n        cContext.fillStyle = this.color;\n        cContext.rect(0, 0, this.width, this.height);\n        cContext.fill();\n      } else {\n        // apply computed image and scale\n        cContext.drawImage(\n          original,\n          this.position.x,\n          this.position.y,\n          this.source.width * this.scale,\n          this.source.height * this.scale,\n        );\n        this.applyTransparentPixels(cContext);\n      }\n\n      cContext.resetTransform();\n    });\n  }\n}\n","import CONSTANTS from \"../constants.js\";\n\nexport default class Control {\n\n  toggleVisibleDiv(button, element) {\n    // this.tokenizer.activeLayerSelectorElement currently selected selector\n    // this.tokenizer.lastControlButtonClicked the last clicked selector button\n\n    // cases\n    // selector button clicked, show selector\n    // selector button clicked, dismiss selector\n    // - clicked back on button after selector dismissed by button click\n    // clicked inside selector, nothing\n    // clicked outside selector, dismiss selector\n    // - clicked back on button after selector dismissed by outside click\n    // click on another button, dismiss selector, show new selector\n\n    element.classList.toggle(\"show\");\n    this.tokenizer.lastControlButtonClicked = button;\n    this.tokenizer.activeLayerSelectorElement = element.classList.contains(\"show\")\n      ? element\n      : null;\n  }\n\n\n  view = document.createElement('div');\n\n  idNumber = document.createElement(\"div\");\n\n  maskControl = document.createElement('button');\n\n  maskManagementSection = document.createElement('div');\n\n  maskLayerSelector = document.createElement(\"div\");\n\n  positionManagementSection = document.createElement('div');\n\n  visibleControl = document.createElement('button');\n\n  activeControl = document.createElement('button');\n\n  deleteSection = document.createElement('div');\n\n  colorManagementSection = document.createElement('div');\n\n  colorSelector = document.createElement('input');\n\n  colorSelectorProxy = document.createElement('div');\n\n  clearColor = document.createElement('button');\n\n  getColor = document.createElement('button');\n\n  moveManagementSection = document.createElement('div');\n\n  moveUpControl = document.createElement('button');\n\n  moveDownControl = document.createElement('button');\n\n  adjustmentManagementSection = document.createElement('div');\n\n  colorSelectionManagementSection = document.createElement('div');\n\n  colorBoxSpan = document.createElement('div');\n\n  getAlpha = document.createElement('button');\n\n  colorTintSelectorProxy = document.createElement('div');\n\n  clearColorTint = document.createElement('button');\n\n  tintControlsDiv = document.createElement('div');\n\n  alphaSelectorProxy = document.createElement('div');\n\n  deleteControl = document.createElement('button');\n\n  opacitySliderControl = document.createElement('input');\n\n  opacityLabel = document.createElement('div');\n\n  brightnessSliderControl = document.createElement('input');\n\n  brightnessLabel = document.createElement('div');\n\n  contrastSliderControl = document.createElement('input');\n\n  contrastLabel = document.createElement('div');\n\n  lineArtBlurSizeControl = document.createElement('input');\n\n  lineArtBlurSizeLabel = document.createElement('div');\n\n  lineArtEnableButton = document.createElement('input');\n\n  constructor(tokenizer, layer) {\n    this.tokenizer = tokenizer;\n    this.layer = layer;\n\n    this.view.setAttribute('data-layer', this.layer.id);\n    this.view.classList.add('view-layer-control');\n\n    const idSection = document.createElement(\"div\");\n    idSection.name = \"layer-id-num\";\n    idSection.title = game.i18n.localize(\"vtta-tokenizer.label.LayerNumber\");\n    idSection.classList.add(\"section\", \"number\");\n    this.idNumber.innerHTML = this.layer.getLayerLabel();\n\n    let previewSection = document.createElement('div');\n    previewSection.name = 'preview';\n    previewSection.classList.add('section');\n\n    let previewMaskSection = document.createElement('div');\n    previewMaskSection.name = 'previewMask';\n    previewMaskSection.classList.add('section');\n\n    this.#configureColorManagement();\n    this.#configureMaskManagementSection();\n    this.#configureTranslationControls();\n    this.#configureAdjustmentSection();\n    this.#configureMagicLassoSection();\n    this.#configureMovementSection();\n    this.#configureDeletionSection();\n\n    // push all elements to the control's view\n    this.view.appendChild(idSection);\n    idSection.appendChild(this.idNumber);\n    this.view.appendChild(previewSection);\n    previewSection.appendChild(this.layer.preview);\n    this.view.appendChild(previewMaskSection);\n    previewMaskSection.appendChild(this.layer.renderedMask);\n    this.view.appendChild(this.maskManagementSection);\n    if (this.layer.colorLayer) {\n      this.view.appendChild(this.colorManagementSection);\n      this.colorManagementSection.appendChild(this.visibleControl);\n      this.colorManagementSection.appendChild(this.colorSelector);\n      this.colorManagementSection.appendChild(this.colorSelectorProxy);\n      this.colorManagementSection.appendChild(this.clearColor);\n      this.colorManagementSection.appendChild(this.getColor);\n      this.colorManagementSection.appendChild(this.adjustmentManagementSection);\n      this.maskControl.disabled = true;\n    } else {\n      this.view.appendChild(this.positionManagementSection);\n      this.positionManagementSection.appendChild(this.visibleControl);\n      this.positionManagementSection.appendChild(this.activeControl);\n      this.positionManagementSection.appendChild(this.colorSelectionManagementSection);\n      this.positionManagementSection.appendChild(this.adjustmentManagementSection);\n      this.positionManagementSection.appendChild(this.layerMovementControl);\n    }\n    this.view.appendChild(this.moveManagementSection);\n    this.view.appendChild(this.deleteSection);\n  }\n\n  #configureMaskManagementSection() {\n    this.maskManagementSection.name = 'mask-management';\n    this.maskManagementSection.classList.add('section');\n    let maskManagementTitle = document.createElement('span');\n    maskManagementTitle.innerHTML = 'Masks';\n    this.maskManagementSection.appendChild(maskManagementTitle);\n\n    // Set the basic mask of this layer\n    this.maskControl.classList.add('mask-control', 'popup-button');\n    this.maskControl.title = game.i18n.localize(\"vtta-tokenizer.label.ToggleBasicMask\");\n    let maskButtonText = document.createElement('i');\n    maskButtonText.classList.add('fas', 'fa-mask');\n    this.maskControl.appendChild(maskButtonText);\n\n    // send a mask event when clicked\n    this.maskControl.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      this.view.dispatchEvent(new CustomEvent('mask', { detail: { layerId: this.layer.id } }));\n    });\n\n    // Set the mask of this layer\n    let maskEditControl = document.createElement('button');\n    maskEditControl.classList.add('mask-control', 'popup-button');\n    // maskEditControl.disabled = true;\n    maskEditControl.title = game.i18n.localize(\"vtta-tokenizer.label.EditMask\");\n    let maskEditButtonText = document.createElement('i');\n    maskEditButtonText.classList.add('fas', 'fa-pencil');\n    maskEditControl.appendChild(maskEditButtonText);\n\n    // send a mask event when clicked\n    maskEditControl.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      this.view.dispatchEvent(new CustomEvent('edit-mask', { detail: { layerId: this.layer.id } }));\n    });\n\n    // Set the mask of this layer\n    let maskResetControl = document.createElement('button');\n    maskResetControl.classList.add('popup-button');\n    maskResetControl.title = game.i18n.localize(\"vtta-tokenizer.label.ResetMasks\");\n    let maskResetButtonText = document.createElement('i');\n    maskResetButtonText.classList.add('fas', 'fa-compress-arrows-alt');\n    maskResetControl.appendChild(maskResetButtonText);\n\n    maskResetControl.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      this.view.dispatchEvent(new CustomEvent('reset-mask-layer', { detail: { layerId: this.layer.id } }));\n    });\n\n    let masksControl = document.createElement('button');\n    masksControl.classList.add('blend-control');\n    masksControl.title = game.i18n.localize(\"vtta-tokenizer.label.AdvancedMaskApplication\");\n\n    let masksButtonText = document.createElement('i');\n    masksButtonText.classList.add('fas', 'fa-masks-theater');\n    masksControl.appendChild(masksButtonText);\n\n    let maskSelectorSpan = document.createElement('div');\n    maskSelectorSpan.classList.add('popup');\n\n    this.maskLayerSelector.classList.add(\"popup-selector\");\n\n    this.#addSelectLayerMasks();\n    let basicMaskControls = document.createElement('div');\n    basicMaskControls.classList.add('basic-control');\n    basicMaskControls.appendChild(this.maskControl);\n    basicMaskControls.appendChild(maskEditControl);\n    basicMaskControls.appendChild(maskResetControl);\n    \n    maskSelectorSpan.appendChild(basicMaskControls);\n    maskSelectorSpan.appendChild(this.maskLayerSelector);\n\n    let blendControlImage = document.createElement('select');\n    blendControlImage.classList.add('blend-control-image');\n    let blendControlMask = document.createElement('select');\n    blendControlMask.classList.add('blend-control-mask');\n\n    [blendControlMask, blendControlImage].forEach((blendControlElement) => {\n      blendControlElement.classList.add('blend-control-selector');\n      for (const mode of Object.values(CONSTANTS.BLEND_MODES)) {\n        const option = document.createElement('option');\n        option.value = mode;\n        option.innerHTML = mode;\n        if ((blendControlElement.classList.contains(\"blend-control-image\") && mode === this.layer.compositeOperation)\n          || (blendControlElement.classList.contains(\"blend-control-mask\") && mode === this.layer.maskCompositeOperation)) {\n          option.selected = true;\n        }\n        blendControlElement.append(option);\n      }\n  \n      blendControlElement.addEventListener('change', (event) => {\n        event.preventDefault();\n        this.view.dispatchEvent(new CustomEvent('blend', {\n          detail: {\n            layerId: this.layer.id,\n            image: blendControlElement.classList.contains(\"blend-control-image\"),\n            mask: blendControlElement.classList.contains(\"blend-control-mask\"),\n            blendMode: event.target.value,\n          },\n        }));\n      });\n  \n    });\n\n    let blendImageDiv = document.createElement('div');\n    let blendImageText = document.createElement('i');\n    blendImageText.title = game.i18n.localize(\"vtta-tokenizer.label.ImageBlendMode\");\n    blendImageText.classList.add('fas', 'fa-image');\n    blendImageDiv.appendChild(blendImageText);\n    blendImageDiv.appendChild(blendControlImage);\n    maskSelectorSpan.appendChild(blendImageDiv);\n\n    let blendMaskDiv = document.createElement('div');\n    let blendMaskText = document.createElement('i');\n    blendMaskText.title = game.i18n.localize(\"vtta-tokenizer.label.MaskBlendMode\");\n    blendMaskText.classList.add('fas', 'fa-mask');\n    blendMaskDiv.appendChild(blendMaskText);\n    blendMaskDiv.appendChild(blendControlMask);\n    maskSelectorSpan.appendChild(blendMaskDiv);\n\n    // send an activate event when clicked\n    masksControl.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      this.toggleVisibleDiv(masksControl, maskSelectorSpan);\n    }, true);\n\n    // blend mode controls\n    let blendManagementSection = document.createElement('div');\n    blendManagementSection.appendChild(masksControl);\n    blendManagementSection.appendChild(maskSelectorSpan);\n    this.maskManagementSection.appendChild(blendManagementSection);\n  }\n\n  #configureTranslationControls() {\n    // position management section\n    this.positionManagementSection.name = 'position-management';\n    this.positionManagementSection.classList.add('section');\n    let positionManagementTitle = document.createElement('span');\n    positionManagementTitle.innerHTML = game.i18n.localize(\"vtta-tokenizer.label.Transform\");\n    this.positionManagementSection.appendChild(positionManagementTitle);\n\n    // is this layer visible?\n    this.visibleControl.classList.add('visible-layer');\n    this.visibleControl.title = game.i18n.localize(\"vtta-tokenizer.label.VisibleLayer\");\n\n    let visibleButtonText = document.createElement('i');\n    visibleButtonText.classList.add('fas', 'fa-eye');\n    this.visibleControl.appendChild(visibleButtonText);\n\n    // send a mask event when clicked\n    this.visibleControl.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      this.view.dispatchEvent(new CustomEvent('visible', { detail: { layerId: this.layer.id } }));\n    });\n\n    // Makes the layer active for translating/ scaling\n    this.activeControl.title = game.i18n.localize(\"vtta-tokenizer.label.EnableDisableTransformation\");\n    this.activeControl.classList.add('mask-control');\n    let activeButtonText = document.createElement('i');\n    activeButtonText.classList.add('fas', 'fa-lock');\n    this.activeControl.appendChild(activeButtonText);\n\n    // send an activate event when clicked\n    this.activeControl.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      if (this.activeControl.classList.contains('active')) {\n        this.view.dispatchEvent(new CustomEvent('deactivate', { detail: { layerId: this.layer.id } }));\n      } else {\n        this.view.dispatchEvent(new CustomEvent('activate', { detail: { layerId: this.layer.id } }));\n      }\n    });\n\n    // Makes flips the layer\n    let flipControl = document.createElement('button');\n    flipControl.title = game.i18n.localize(\"vtta-tokenizer.label.FlipLayer\");\n    flipControl.classList.add('flip-control', 'popup-button');\n    let flipButtonText = document.createElement('i');\n    flipButtonText.classList.add('fas', 'fa-people-arrows');\n    flipControl.appendChild(flipButtonText);\n\n    // send an activate event when clicked\n    flipControl.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      this.view.dispatchEvent(new CustomEvent('flip', { detail: { layerId: this.layer.id } }));\n    });\n\n    // resets the layer on the view\n    let resetControl = document.createElement('button');\n    resetControl.classList.add('reset-control', 'popup-button');\n    resetControl.title = game.i18n.localize(\"vtta-tokenizer.label.ResetLayer\");\n    let resetButtonText = document.createElement('i');\n    resetButtonText.classList.add('fas', 'fa-compress-arrows-alt');\n    resetControl.appendChild(resetButtonText);\n\n    // send an activate event when clicked\n    resetControl.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      this.view.dispatchEvent(new CustomEvent('reset', { detail: { layerId: this.layer.id } }));\n    });\n\n    // Centres the layer\n    let centreLayerControl = document.createElement('button');\n    centreLayerControl.title = game.i18n.localize(\"vtta-tokenizer.label.CentreLayer\");\n    centreLayerControl.classList.add('centre-control', 'popup-button');\n    let centreLayerText = document.createElement('i');\n    centreLayerText.classList.add('fas', 'fa-crosshairs');\n    centreLayerControl.appendChild(centreLayerText);\n\n    // send an activate event when clicked\n    centreLayerControl.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      this.view.dispatchEvent(new CustomEvent('centre-layer', { detail: { layerId: this.layer.id } }));\n    });\n\n    // Layer movement selector\n    let layerMovementSelectorDiv = document.createElement('div');\n    layerMovementSelectorDiv.classList.add('popup');\n\n    // Layer movement controls\n    let layerMovementControl = document.createElement('button');\n    layerMovementControl.title = game.i18n.localize(\"vtta-tokenizer.label.LayerMovementControls\");\n    layerMovementControl.classList.add('layer-movement-control');\n    let layerMovementText = document.createElement('i');\n    layerMovementText.classList.add('fas', 'fa-toolbox');\n    layerMovementControl.appendChild(layerMovementText);\n\n    layerMovementControl.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      this.toggleVisibleDiv(this.layerMovementControl, layerMovementSelectorDiv);\n    }, true);\n\n    let buttonDiv = document.createElement(\"div\");\n    buttonDiv.classList.add(\"popup-selector\");\n\n    buttonDiv.appendChild(flipControl);\n    buttonDiv.appendChild(centreLayerControl);\n    buttonDiv.appendChild(resetControl);\n\n    layerMovementSelectorDiv.appendChild(buttonDiv);\n    layerMovementSelectorDiv.appendChild(document.createElement('hr'));\n\n    let scaleDiv = document.createElement('div');\n    scaleDiv.classList.add(\"popup-selector\");\n\n    let scaleInput = document.createElement('input');\n    scaleInput.type = \"text\";\n    scaleInput.value = `${this.layer.scale * 100}`;\n    scaleInput.classList.add('scale-input', 'popup-input');\n\n    let scaleControl = document.createElement('button');\n    scaleControl.title = game.i18n.localize(\"vtta-tokenizer.label.ScaleButton\");\n    scaleControl.classList.add('scale-control', 'popup-button');\n    let scaleText = document.createElement('i');\n    scaleText.classList.add('fas', 'fa-compress');\n    scaleControl.appendChild(scaleText);\n\n    scaleControl.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      const percentage = parseFloat(scaleInput.value);\n      if (isNaN(percentage)) {\n        scaleInput.value = `${this.layer.scale * 100}`;\n      } else {\n        this.view.dispatchEvent(new CustomEvent('scale-layer', { detail: { layerId: this.layer.id, percent: percentage } }));\n      }\n    });\n\n    scaleDiv.appendChild(scaleInput);\n    scaleDiv.appendChild(scaleControl);\n\n    layerMovementSelectorDiv.appendChild(scaleDiv);\n\n    let wrapperDiv = document.createElement(\"div\");\n    wrapperDiv.appendChild(layerMovementControl);\n    wrapperDiv.appendChild(layerMovementSelectorDiv);\n    \n    this.layerMovementControl = wrapperDiv;\n  }\n\n  #configureDeletionSection() {\n    this.deleteSection.name = 'delete-management';\n    this.deleteSection.classList.add('section');\n\n    // duplicate\n    let duplicateControl = document.createElement('button');\n    duplicateControl.classList.add('duplicate-control');\n    duplicateControl.title = game.i18n.localize(\"vtta-tokenizer.label.CloneLayer\");\n    let duplicateButtonText = document.createElement('i');\n    duplicateButtonText.classList.add('fas', 'fa-clone');\n    duplicateControl.appendChild(duplicateButtonText);\n\n    duplicateControl.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      this.view.dispatchEvent(\n        new CustomEvent('duplicate', {\n          detail: { layerId: this.layer.id },\n        }),\n      );\n    });\n\n    // delete\n    this.deleteControl.classList.add('delete-control');\n    this.deleteControl.title = game.i18n.localize(\"vtta-tokenizer.label.DeleteLayer\");\n    let deleteButtonText = document.createElement('i');\n    deleteButtonText.classList.add('fas', 'fa-trash-alt');\n    this.deleteControl.appendChild(deleteButtonText);\n\n    this.deleteControl.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      this.view.dispatchEvent(\n        new CustomEvent('delete', {\n          detail: { layerId: this.layer.id },\n        }),\n      );\n    });\n\n    this.deleteSection.appendChild(duplicateControl);\n    this.deleteSection.appendChild(this.deleteControl);\n  }\n\n  #configureColorManagement() {\n    this.colorManagementSection.name = 'color-management';\n    this.colorManagementSection.classList.add('section');\n    let colorManagementTitle = document.createElement('span');\n    colorManagementTitle.innerHTML = game.i18n.localize(\"vtta-tokenizer.label.Color\");\n    this.colorManagementSection.appendChild(colorManagementTitle);\n\n    // the color picker element, which is hidden\n    this.colorSelector.type = 'color';\n    this.colorSelector.value = '#00000FF';\n\n    // a nicer looking proxy for the color picker\n    this.colorSelectorProxy.title = game.i18n.localize(\"vtta-tokenizer.label.EditTint\");\n    this.colorSelectorProxy.classList.add('color-picker', 'transparent');\n    this.colorSelectorProxy.addEventListener(\"click\", () => {\n      this.colorSelector.click();\n    });\n\n    // listen to the color Selector onChange Event to update the layer's background color\n    this.colorSelector.addEventListener('change', (event) => {\n      this.colorSelectorProxy.style.backgroundColor = event.target.value;\n      this.colorSelectorProxy.classList.remove('transparent');\n      this.view.dispatchEvent(\n        new CustomEvent('color', {\n          detail: { layerId: this.layer.id, color: event.target.value },\n        }),\n      );\n    });\n\n    // ability to clear the color of the layer\n    this.clearColor.disabled = true;\n    this.clearColor.classList.add('danger');\n    this.clearColor.title = game.i18n.localize(\"vtta-tokenizer.label.ClearTint\");\n    let clearButtonText = document.createElement('i');\n    clearButtonText.classList.add('fas', 'fa-minus-circle');\n    this.clearColor.appendChild(clearButtonText);\n\n    this.clearColor.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      this.view.dispatchEvent(\n        new CustomEvent('color', {\n          detail: { layerId: this.layer.id, color: null },\n        }),\n      );\n    });\n\n    // get color from canvas\n    this.getColor.title = game.i18n.localize(\"vtta-tokenizer.label.PickTint\");\n    let colorButtonText = document.createElement('i');\n    colorButtonText.classList.add('fas', 'fa-eye-dropper');\n    this.getColor.appendChild(colorButtonText);\n\n    // dispatch the request for color picking\n    this.getColor.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      if (this.getColor.classList.contains('active')) {\n        this.getColor.classList.remove('active');\n        this.view.dispatchEvent(\n          new CustomEvent('pick-color-end', {\n            detail: { layerId: this.layer.id },\n          }),\n        );\n      } else {\n        this.getColor.classList.add('active');\n        this.view.dispatchEvent(\n          new CustomEvent('pick-color-start', {\n            detail: { layerId: this.layer.id },\n          }),\n        );\n      }\n    });\n  }\n\n  #configureMovementSection() {\n    this.moveManagementSection.classList.add('move-control');\n    this.moveManagementSection.name = 'move-management';\n    this.moveManagementSection.classList.add('section');\n\n    // moving up\n    this.moveUpControl.classList.add('move-control', 'move-up');\n    this.moveUpControl.title = game.i18n.localize(\"vtta-tokenizer.label.MoveLayerUp\");\n    let moveUpButtonText = document.createElement('i');\n    moveUpButtonText.classList.add('fas', 'fa-caret-up');\n    this.moveUpControl.appendChild(moveUpButtonText);\n\n    // moving up event dispatcher\n    this.moveUpControl.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      this.view.dispatchEvent(\n        new CustomEvent('move', {\n          detail: { layerId: this.layer.id, direction: 'up' },\n        }),\n      );\n    });\n\n    // moving down\n    this.moveDownControl.classList.add('move-control', 'move-down');\n    this.moveDownControl.title = game.i18n.localize(\"vtta-tokenizer.label.MoveLayerDown\");\n    let moveDownButtonText = document.createElement('i');\n    moveDownButtonText.classList.add('fas', 'fa-caret-down');\n    this.moveDownControl.appendChild(moveDownButtonText);\n\n    // moving down event dispatcher\n    this.moveDownControl.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      this.view.dispatchEvent(\n        new CustomEvent('move', {\n          detail: { layerId: this.layer.id, direction: 'down' },\n        }),\n      );\n    });\n    this.moveManagementSection.appendChild(this.moveUpControl);\n    this.moveManagementSection.appendChild(this.moveDownControl);\n\n  }\n\n  #configureAdjustmentSection() {\n    let adjustmentSelectionControl = document.createElement('button');\n    adjustmentSelectionControl.classList.add('adjustment-control');\n    adjustmentSelectionControl.title = game.i18n.localize(\"vtta-tokenizer.label.Adjustments\");\n\n    let adjustmentsButtonText = document.createElement('i');\n    adjustmentsButtonText.classList.add('fas', 'fa-hammer-brush');\n    adjustmentSelectionControl.appendChild(adjustmentsButtonText);\n    this.adjustmentManagementSection.appendChild(adjustmentSelectionControl);\n\n    let adjustmentControlsSpan = document.createElement('div');\n    adjustmentControlsSpan.classList.add('popup');\n\n    // send an activate event when clicked\n    adjustmentSelectionControl.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      this.toggleVisibleDiv(adjustmentSelectionControl, adjustmentControlsSpan);\n    }, true);\n\n    // opacity controls\n    let opacitySliderSpan = document.createElement('div');\n    opacitySliderSpan.classList.add('basic-control');\n    let opacityIcon = document.createElement('i');\n    opacityIcon.classList.add('fa-duotone', 'fa-solid', 'fa-face-dotted');\n    opacityIcon.title = game.i18n.localize(\"vtta-tokenizer.label.Opacity\");\n    opacitySliderSpan.appendChild(opacityIcon);\n\n    this.opacitySliderControl.type = 'range';\n    this.opacitySliderControl.min = 0;\n    this.opacitySliderControl.max = 100;\n    this.opacitySliderControl.value = (this.layer.alpha ?? 1) * 100;\n    this.opacitySliderControl.title = game.i18n.localize(\"vtta-tokenizer.label.Opacity\");\n    this.opacitySliderControl.name = \"opacity\";\n\n    this.opacityLabel.innerHTML = `${this.opacitySliderControl.value}%`;\n\n    this.opacitySliderControl.addEventListener('input', (event) => {\n      event.preventDefault();\n      const detail = {\n        layerId: this.layer.id,\n        opacity: event.target.value,\n      };\n      this.view.dispatchEvent(new CustomEvent('opacity', { detail }));\n      this.opacityLabel.innerHTML = `${event.target.value}%`;\n    });\n\n    opacitySliderSpan.appendChild(this.opacitySliderControl);\n    opacitySliderSpan.appendChild(this.opacityLabel);\n\n    adjustmentControlsSpan.appendChild(opacitySliderSpan);\n    adjustmentControlsSpan.appendChild(document.createElement('hr'));\n\n    // brightness controls\n    let brightnessSliderSpan = document.createElement('div');\n    brightnessSliderSpan.classList.add('basic-control');\n    let brightnessIcon = document.createElement('i');\n    brightnessIcon.classList.add('fa-solid', 'fa-brightness');\n    brightnessIcon.title = game.i18n.localize(\"vtta-tokenizer.label.Brightness\");\n    brightnessSliderSpan.appendChild(brightnessIcon);\n\n    this.brightnessSliderControl.type = 'range';\n    this.brightnessSliderControl.min = -100;\n    this.brightnessSliderControl.max = 100;\n    this.brightnessSliderControl.value = this.layer.brightness ?? 0;\n    this.brightnessSliderControl.title = game.i18n.localize(\"vtta-tokenizer.label.Brightness\");\n    this.brightnessSliderControl.name = \"brightness\";\n\n    this.brightnessLabel.innerHTML = `${this.brightnessSliderControl.value}%`;\n\n    this.brightnessSliderControl.addEventListener('input', (event) => {\n      event.preventDefault();\n      const detail = {\n        layerId: this.layer.id,\n        brightness: event.target.value,\n      };\n      this.view.dispatchEvent(new CustomEvent('brightness', { detail }));\n      this.brightnessLabel.innerHTML = `${event.target.value ?? 0}%`;\n    });\n    brightnessSliderSpan.appendChild(this.brightnessSliderControl);\n    brightnessSliderSpan.appendChild(this.brightnessLabel);\n\n    adjustmentControlsSpan.appendChild(brightnessSliderSpan);\n    adjustmentControlsSpan.appendChild(document.createElement('hr'));\n\n    // contrast controls\n    let contrastSliderSpan = document.createElement('div');\n    contrastSliderSpan.classList.add('basic-control');\n    let contrastIcon = document.createElement('i');\n    contrastIcon.classList.add('fa-solid', 'fa-circle-half-stroke');\n    contrastIcon.title = game.i18n.localize(\"vtta-tokenizer.label.Contrast\");\n    contrastSliderSpan.appendChild(contrastIcon);\n\n    this.contrastSliderControl.type = 'range';\n    this.contrastSliderControl.min = -100;\n    this.contrastSliderControl.max = 100;\n    this.contrastSliderControl.value = this.layer.contrast ?? 0;\n    this.contrastSliderControl.title = game.i18n.localize(\"vtta-tokenizer.label.Contrast\");\n    this.contrastSliderControl.name = \"contrast\";\n\n    this.contrastLabel.innerHTML = `${this.contrastSliderControl.value}%`;\n\n    this.contrastSliderControl.addEventListener('input', (event) => {\n      event.preventDefault();\n      const detail = {\n        layerId: this.layer.id,\n        contrast: event.target.value,\n      };\n      this.view.dispatchEvent(new CustomEvent('contrast', { detail }));\n      this.contrastLabel.innerHTML = `${event.target.value}%`;\n    });\n    contrastSliderSpan.appendChild(this.contrastSliderControl);\n\n    contrastSliderSpan.appendChild(this.contrastLabel);\n\n    adjustmentControlsSpan.appendChild(contrastSliderSpan);\n    adjustmentControlsSpan.appendChild(document.createElement('hr'));\n\n    // line art brush size\n    let lineArtBlurSizeSpan = document.createElement('div');\n    lineArtBlurSizeSpan.classList.add('basic-control');\n    let lineArtBlurSizeIcon = document.createElement('i');\n    lineArtBlurSizeIcon.classList.add('fa-solid', 'fa-paint-brush');\n    lineArtBlurSizeIcon.title = game.i18n.localize(\"vtta-tokenizer.label.LineArtBlurSize\");\n    lineArtBlurSizeSpan.appendChild(lineArtBlurSizeIcon);\n\n    let lineArtEnabled = this.layer.filters.some((f) => f.name.includes(\"lineArtEffect\"));\n\n    this.lineArtBlurSizeControl.type = 'range';\n    this.lineArtBlurSizeControl.min = 1;\n    this.lineArtBlurSizeControl.max = 50;\n    this.lineArtBlurSizeControl.value = this.layer.lineArtBlurSize ?? 25;\n    this.lineArtBlurSizeControl.title = game.i18n.localize(\"vtta-tokenizer.label.LineArtBlurSize\");\n    this.lineArtBlurSizeControl.name = \"line-art-blur-size\";\n    this.lineArtBlurSizeControl.disabled = !lineArtEnabled;\n\n    this.lineArtBlurSizeLabel.innerHTML = `${this.lineArtBlurSizeControl.value}px`;\n\n    this.lineArtEnableButton.type = 'checkbox';\n    this.lineArtEnableButton.checked = lineArtEnabled;\n    this.lineArtEnableButton.title = game.i18n.localize(\"vtta-tokenizer.label.EnableLineArt\");\n\n    this.lineArtEnableButton.addEventListener('input', (event) => {\n      event.preventDefault();\n      const detail = {\n        layerId: this.layer.id,\n        enabled: event.target.checked,\n      };\n      this.lineArtBlurSizeControl.disabled = !event.target.checked;\n      this.view.dispatchEvent(new CustomEvent('line-art-toggle', { detail }));\n    });\n\n    this.lineArtBlurSizeControl.addEventListener('input', (event) => {\n      event.preventDefault();\n      const detail = {\n        layerId: this.layer.id,\n        size: event.target.value,\n      };\n      this.view.dispatchEvent(new CustomEvent('line-art-blur-size', { detail }));\n      this.lineArtBlurSizeLabel.innerHTML = `${event.target.value}px`;\n    });\n    lineArtBlurSizeSpan.appendChild(this.lineArtBlurSizeControl);\n    lineArtBlurSizeSpan.appendChild(this.lineArtBlurSizeLabel);\n    lineArtBlurSizeSpan.appendChild(this.lineArtEnableButton);\n\n    adjustmentControlsSpan.appendChild(lineArtBlurSizeSpan);\n\n    this.adjustmentManagementSection.appendChild(adjustmentControlsSpan);\n\n    // add section for adjusting brightness\n\n    // add section for adjusting contrast\n  }\n\n  #configureMagicLassoSection() {\n    let colorSelectionControl = document.createElement('button');\n    colorSelectionControl.classList.add('color-selection-control');\n    colorSelectionControl.title = game.i18n.localize(\"vtta-tokenizer.label.ColorChangeControl\");\n\n    let buttonText = document.createElement('i');\n    buttonText.classList.add('fa-thin', 'fa-eye-dropper', 'fa-regular');\n    colorSelectionControl.appendChild(buttonText);\n    this.colorSelectionManagementSection.appendChild(colorSelectionControl);\n\n    this.colorBoxSpan.classList.add('popup');\n\n    // send an activate event when clicked\n    colorSelectionControl.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      this.toggleVisibleDiv(colorSelectionControl, this.colorBoxSpan);\n    }, true);\n\n    let colorThresholdSliderControl = document.createElement('input');\n    colorThresholdSliderControl.type = 'range';\n    colorThresholdSliderControl.min = 0;\n    colorThresholdSliderControl.max = 150;\n    colorThresholdSliderControl.value = 15;\n    colorThresholdSliderControl.title = game.i18n.localize(\"vtta-tokenizer.label.ColorThreshold\");\n    colorThresholdSliderControl.name = \"color-threshold\";\n\n    colorThresholdSliderControl.addEventListener('input', (event) => {\n      event.preventDefault();\n      const detail = {\n        layerId: this.layer.id,\n        tolerance: event.target.value,\n      };\n      this.view.dispatchEvent(new CustomEvent('transparency-level', { detail }));\n    });\n\n    // get color from canvas\n    this.getAlpha.classList.add('popup-button');\n    this.getAlpha.title = game.i18n.localize(\"vtta-tokenizer.label.PickAlpha\");\n    let alphaButtonText = document.createElement('i');\n    alphaButtonText.classList.add('fa-thin', 'fa-eye-dropper', 'fa-regular');\n    this.getAlpha.appendChild(alphaButtonText);\n\n    // dispatch the request for color picking\n    this.getAlpha.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      if (this.getAlpha.classList.contains('active')) {\n        this.getAlpha.classList.remove('active');\n        this.view.dispatchEvent(\n          new CustomEvent('pick-alpha-end', {\n            detail: { layerId: this.layer.id },\n          }),\n        );\n      } else {\n        this.getAlpha.classList.add('active');\n        this.view.dispatchEvent(\n          new CustomEvent('pick-alpha-start', {\n            detail: { layerId: this.layer.id },\n          }),\n        );\n      }\n    });\n\n    this.alphaSelectorProxy.classList.add('color-picker', 'transparent');\n\n    let transparencyResetControl = document.createElement('button');\n    transparencyResetControl.classList.add('popup-button');\n    transparencyResetControl.title = game.i18n.localize(\"vtta-tokenizer.label.ResetColorTransparency\");\n    let resetButtonText = document.createElement('i');\n    resetButtonText.classList.add('fas', 'fa-compress-arrows-alt');\n    transparencyResetControl.appendChild(resetButtonText);\n\n    transparencyResetControl.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      this.view.dispatchEvent(new CustomEvent('reset-transparency-level', { detail: { layerId: this.layer.id } }));\n    });\n\n    // get color from canvas\n    let magicLassoControl = document.createElement('button');\n    magicLassoControl.classList.add('popup-button');\n    magicLassoControl.title = game.i18n.localize(\"vtta-tokenizer.label.MagicLasso\");\n    let magicLassoButtonText = document.createElement('i');\n    magicLassoButtonText.classList.add('fa-thin', 'fa-lasso-sparkles', 'fa-regular');\n    magicLassoControl.appendChild(magicLassoButtonText);\n\n    magicLassoControl.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      this.view.dispatchEvent(new CustomEvent('magic-lasso', { detail: { layerId: this.layer.id } }));\n    });\n\n\n    let lassoControls = document.createElement('div');\n    lassoControls.classList.add('basic-control');\n    lassoControls.appendChild(magicLassoControl);\n\n    this.#tintControls();\n\n    let transparencyControls = document.createElement('div');\n    transparencyControls.classList.add('basic-control');\n    transparencyControls.appendChild(this.alphaSelectorProxy);\n    transparencyControls.appendChild(this.getAlpha);\n    transparencyControls.appendChild(transparencyResetControl);\n\n    this.colorBoxSpan.appendChild(lassoControls);\n    this.colorBoxSpan.appendChild(document.createElement('hr'));\n    this.colorBoxSpan.appendChild(this.tintControlsDiv);\n    this.colorBoxSpan.appendChild(document.createElement('hr'));\n    this.colorBoxSpan.appendChild(transparencyControls);\n    this.colorBoxSpan.appendChild(colorThresholdSliderControl);\n    this.colorSelectionManagementSection.appendChild(this.colorBoxSpan);\n  }\n\n  #tintControls() {\n    // the color picker element, which is hidden\n    let colorTintSelector = document.createElement('input');\n    colorTintSelector.type = 'color';\n    colorTintSelector.value = '#00000FF';\n\n    // a nicer looking proxy for the color picker\n    this.colorTintSelectorProxy.title = game.i18n.localize(\"vtta-tokenizer.label.EditLayerTint\");\n    this.colorTintSelectorProxy.classList.add('color-picker', 'transparent');\n    this.colorTintSelectorProxy.addEventListener(\"click\", () => {\n      colorTintSelector.click();\n    });\n\n    // listen to the color Selector onChange Event to update the layer's background color\n    colorTintSelector.addEventListener('change', (event) => {\n      this.colorTintSelectorProxy.style.backgroundColor = event.target.value;\n      this.colorTintSelectorProxy.classList.remove('transparent');\n      this.view.dispatchEvent(\n        new CustomEvent('color-tint', {\n          detail: { layerId: this.layer.id, color: event.target.value },\n        }),\n      );\n    });\n\n    // ability to clear the color of the layer\n    this.clearColorTint.disabled = true;\n    this.clearColorTint.classList.add('danger', 'popup-button');\n    this.clearColorTint.title = game.i18n.localize(\"vtta-tokenizer.label.ClearTint\");\n    let clearButtonText = document.createElement('i');\n    clearButtonText.classList.add('fas', 'fa-minus-circle');\n    this.clearColorTint.appendChild(clearButtonText);\n\n    this.clearColorTint.addEventListener(\"click\", (event) => {\n      event.preventDefault();\n      this.view.dispatchEvent(\n        new CustomEvent('color-tint', {\n          detail: { layerId: this.layer.id, color: null },\n        }),\n      );\n    });\n\n    this.tintControlsDiv.classList.add('basic-control');\n    this.tintControlsDiv.appendChild(colorTintSelector);\n    this.tintControlsDiv.appendChild(this.colorTintSelectorProxy);\n    this.tintControlsDiv.appendChild(this.clearColorTint);\n  }\n\n  #addSelectLayerMasks() {\n    this.maskLayerSelector.innerHTML = \"\";\n    this.layer.view.layers.forEach((layer) => {\n      const layerIdDiv = document.createElement(\"div\");\n      const active = this.layer.appliedMaskIds.has(layer.id);\n      const layerNum = this.layer.view.layers.findIndex((l) => l.id === layer.id);\n\n      const button = document.createElement('button');\n      button.classList.add('popup-choice');\n      if (active) button.classList.add('active');\n      button.title = game.i18n.format(\"vtta-tokenizer.label.ToggleLayer\", { layerNum });\n      button.innerHTML = layer.getLayerLabel(active);\n\n      button.addEventListener(\"click\", (event) => {\n        event.preventDefault();\n        this.view.dispatchEvent(\n          new CustomEvent('mask-layer', {\n            detail: { layerId: this.layer.id, maskLayerId: layer.id },\n          }),\n        );\n      });\n\n      layerIdDiv.appendChild(button);\n      this.maskLayerSelector.appendChild(layerIdDiv);\n    });\n  }\n\n  refresh() {\n    this.idNumber.innerHTML = this.layer.getLayerLabel();\n    // is this layer providing the mask for the view?\n    if (this.layer.customMaskLayers) {\n      this.maskControl.classList.remove('active');\n      this.maskControl.disabled = true;\n    } else if (this.layer.providesMask) {\n      this.maskControl.classList.add('active');\n      // this.maskEditControl.disabled = false;\n    } else {\n      this.maskControl.classList.remove('active');\n      // this.maskEditControl.disabled = true;\n    }\n\n    this.maskLayerSelector.innerHTML = \"\";\n    this.#addSelectLayerMasks();\n\n    // is this layer visible\n    if (this.layer.visible) {\n      this.visibleControl.classList.add('active');\n      this.visibleControl.firstChild.classList.remove('fa-eye-slash');\n      this.visibleControl.firstChild.classList.add('fa-eye');\n    } else {\n      this.visibleControl.classList.remove('active');\n      this.visibleControl.firstChild.classList.remove('fa-eye');\n      this.visibleControl.firstChild.classList.add('fa-eye-slash');\n    }\n\n    // is this layer active?\n    if (this.layer.active) {\n      this.activeControl.classList.add('active');\n      this.activeControl.firstChild.classList.remove('fa-lock');\n      this.activeControl.firstChild.classList.add('fa-lock-open');\n    } else {\n      this.activeControl.classList.remove('active');\n      this.activeControl.firstChild.classList.remove('fa-lock-open');\n      this.activeControl.firstChild.classList.add('fa-lock');\n    }\n\n    // is this layer's color currently transparent / null\n    if (this.layer.color === null) {\n      this.colorSelectorProxy.classList.add('transparent');\n      this.clearColor.disabled = true;\n    } else {\n      this.colorSelectorProxy.classList.remove('transparent');\n      this.colorSelectorProxy.style.backgroundColor = this.layer.color;\n      this.clearColor.disabled = false;\n    }\n\n    if (this.layer.tintColor === null) {\n      this.colorTintSelectorProxy.classList.add('transparent');\n      this.clearColorTint.disabled = true;\n    } else {\n      this.colorTintSelectorProxy.classList.remove('transparent');\n      this.colorTintSelectorProxy.style.backgroundColor = this.layer.tintColor;\n      this.clearColorTint.disabled = false;\n    }\n\n    if (this.layer.view.isAlphaPicking) {\n      this.alphaSelectorProxy.classList.remove('transparent');\n      this.alphaSelectorProxy.style.backgroundColor = this.layer.view.alphaColorHex;\n    } else {\n      this.alphaSelectorProxy.classList.add('transparent');\n    }\n\n    // first child?\n    this.enableMoveUp();\n    this.enableMoveDown();\n    if (this.view.parentElement.firstChild.getAttribute('data-layer') === this.layer.id) {\n      this.disableMoveUp();\n    }\n    // last child?\n    if (this.view.parentElement.lastChild.getAttribute('data-layer') === this.layer.id) {\n      this.disableMoveDown();\n    }\n\n    if (this.opacitySliderControl.value !== this.layer.opacity) {\n      this.opacitySliderControl.value = this.layer.alpha * 100;\n      this.opacityLabel.innerHTML = `${this.layer.alpha * 100}%`;\n    }\n    if (this.brightnessSliderControl.value !== this.layer.brightness) {\n      this.brightnessSliderControl.value = this.layer.brightness;\n      this.brightnessLabel.innerHTML = `${this.layer.brightness}%`;\n    }\n    if (this.contrastSliderControl.value !== this.layer.contrast) {\n      this.contrastSliderControl.value = this.layer.contrast;\n      this.contrastLabel.innerHTML = `${this.layer.contrast}%`;\n    }\n    if (this.lineArtBlurSizeControl.value !== this.layer.lineArtBlurSize) {\n      this.lineArtBlurSizeControl.value = this.layer.lineArtBlurSize;\n      this.lineArtBlurSizeLabel.innerHTML = `${this.layer.lineArtBlurSize}px`;\n    }\n    let lineArtEnabled = this.layer.filters.some((f) => f.name.includes(\"lineArtEffect\"));\n    this.lineArtEnableButton.checked = lineArtEnabled;\n    this.lineArtBlurSizeControl.disabled = !lineArtEnabled;\n\n  }\n\n  startColorPicking() {\n    this.getColor.classList.add('active');\n  }\n\n  endColorPicking() {\n    this.getColor.classList.remove('active');\n  }\n\n  startAlphaPicking() {\n    this.getAlpha.classList.add('active');\n  }\n\n  endAlphaPicking() {\n    this.getAlpha.classList.remove('active');\n    this.colorBoxSpan.classList.toggle(\"show\");\n  }\n\n  enableMoveUp() {\n    this.moveUpControl.disabled = false;\n  }\n\n  disableMoveUp() {\n    this.moveUpControl.disabled = true;\n  }\n\n  enableMoveDown() {\n    this.moveDownControl.disabled = false;\n  }\n\n  disableMoveDown() {\n    this.moveDownControl.disabled = true;\n  }\n}\n","import Layer from './Layer.js';\nimport Control from './Control.js';\nimport Utils from '../libs/Utils.js';\nimport CONSTANTS from '../constants.js';\nimport logger from '../libs/logger.js';\nimport Color from '../libs/Color.js';\n\nexport default class View {\n  constructor(tokenizer, dimension, element) {\n    this.tokenizer = tokenizer;\n    // the canvas where the resulting image is rendered to\n    this.canvas = document.createElement('canvas');\n    this.canvas.width = dimension;\n    this.canvas.height = dimension;\n    this.canvas.style.width = dimension;\n    this.canvas.style.height = dimension;\n\n    // keep track of all layers\n    this.layers = [];\n\n    // keep track of all controls;\n    this.controls = [];\n\n    this.menu = null;\n\n    // there is one mask that is active for every layer\n    this.maskIds = new Set();\n\n    // the currently selected layer for translation/scaling\n    this.activeLayer = null;\n\n    // the user wants to retrieve a color from the view's layers\n    this.isColorPicking = false;\n    this.colorPickingForLayer = null;\n    this.colorPickingLayerId = null;\n\n    // alpha picking\n    this.isAlphaPicking = false;\n    this.alphaPickingForLayer = null;\n    this.alphaPickingLayerId = null;\n    this.alphaColor = null;\n    this.alphaTolerance = 50;\n\n    // The working stage for the View\n    this.stage = document.createElement('div');\n    this.stage.name = 'view';\n\n    if (element.id === \"tokenizer-token\") {\n      this.stage.setAttribute(\"id\", \"token-canvas\");\n      this.type = \"token\";\n    } else if (element.id === \"tokenizer-avatar\") {\n      this.stage.setAttribute(\"id\", \"avatar-canvas\");\n      this.type = \"avatar\";\n    }\n\n    // The controls area for the View\n    this.controlsArea = document.createElement('div');\n    this.controlsArea.name = 'view-controls';\n\n    // The menu bar for the View\n    this.menu = document.createElement('div');\n    this.menu.name = 'view-menu';\n\n    // add them both to the designated View element as child nodes\n    element.appendChild(this.stage);\n    this.stage.appendChild(this.canvas);\n    element.appendChild(this.controlsArea);\n    element.appendChild(this.menu);\n\n    const moveFunction = Utils.throttle(this.onMouseMove.bind(this), 15);\n    // add event listeners for translation/scaling of the active layer\n    this.canvas.addEventListener('mousedown', this.onMouseDown.bind(this));\n    this.canvas.addEventListener('mouseup', this.onMouseUp.bind(this));\n    this.canvas.addEventListener('mousemove', moveFunction);\n    // this.canvas.addEventListener('mousemove', this.onMouseMove.bind(this));\n    this.canvas.addEventListener('wheel', this.onWheel.bind(this), {\n      passive: false,\n    });\n  }\n\n  /**\n   * Returns the current canvas in one of three types\n   * @param {canvas | blob | Image} type Defines the return type: canvas, blob or Image\n   */\n  get(type = 'canvas') {\n    switch (type.toLowerCase()) {\n      case 'img':\n        return new Promise((resolve, reject) => {\n          let img = document.createElement('img');\n          img.onload = () => {\n            resolve(img);\n          };\n          img.onerror = (error) => {\n            reject(error);\n          };\n          img.src = this.canvas.toDataURL();\n        });\n      case 'blob': {\n        const imageFormat = game.settings.get(CONSTANTS.MODULE_ID, \"image-save-type\");\n        return new Promise((resolve, reject) => {\n          try {\n            const clone = Utils.cloneCanvas(this.canvas);\n            const context = clone.getContext('2d');\n            context.clearRect(0, 0, this.width, this.height);\n\n            context.drawImage(\n              this.canvas,\n              0,\n              0,\n              this.canvas.width,\n              this.canvas.height,\n              0,\n              0,\n              this.width,\n              this.height,\n            );\n\n            this.canvas.toBlob((blob) => {\n              resolve(blob);\n            }, `image/${imageFormat}`);\n          } catch (error) {\n            reject(error);\n          }\n        });\n      }\n      default:\n        return Promise.resolve(this.canvas);\n    }\n  }\n\n  /**\n   * Get this mask from the layer id or default view\n   * @param {id} Number\n   */\n  getMaskLayer(id) {\n    return this.layers.find((layer) => layer.id === id);\n  }\n\n    /**\n   * Get this mask from the layer id or default view\n   * @param {id} Number\n   */\n  getMaskLayers() {\n    return this.layers.find((layer) => this.maskIds.has(layer.id));\n  }\n\n  initializeMenu() {\n    let newImageSection = document.createElement('div');\n    newImageSection.name = 'color-management';\n    newImageSection.classList.add('section');\n    var title = document.createElement('span');\n    title.innerHTML = game.i18n.localize(\"vtta-tokenizer.label.NewImage\");\n    newImageSection.appendChild(title);\n\n    // Set the mask of this layer\n    this.maskControl = document.createElement('button');\n    this.maskControl.classList.add('menu-button');\n    var buttonText = document.createElement('i');\n    buttonText.classList.add('fas', 'fa-globe');\n    this.maskControl.appendChild(buttonText);\n  }\n\n  /**\n   * Enables dragging\n   * @param {Event} event\n   */\n  onMouseDown(event) {\n    if (this.isColorPicking) {\n      this.endColorPicking(false);\n    } else if (this.isAlphaPicking) {\n      this.endAlphaPicking(false);\n    }\n\n    if (this.activeLayer === null) return;\n    this.redraw(true);\n    this.isDragging = true;\n    this.lastPosition = {\n      x: event.clientX,\n      y: event.clientY,\n    };\n  }\n\n  /**\n   * Disables dragging\n   * @param {Event} event\n   */\n  // eslint-disable-next-line no-unused-vars\n  onMouseUp(event) {\n    this.redraw(true);\n    if (this.activeLayer === null) return;\n    this.isDragging = false;\n  }\n\n  /**\n   * Enables color picking on the current view canvas and (if a drag event is registered) translation\n   * of the source image on the view canvas\n   * @param {Event} event\n   */\n  onMouseMove(event) {\n    if (this.isColorPicking) {\n      const eventLocation = Utils.getCanvasCords(this.canvas, event);\n      // Get the data of the pixel according to the mouse pointer location\n      const pixelData = this.canvas.getContext('2d').getImageData(eventLocation.x, eventLocation.y, 1, 1).data;\n      // If transparency on the pixel , array = [0,0,0,0]\n      if (pixelData[0] == 0 && pixelData[1] == 0 && pixelData[2] == 0 && pixelData[3] == 0) {\n        // Do something if the pixel is transparent\n      }\n      // Convert it to HEX if you want using the rgbToHex method.\n      const hex = '#' + ('000000' + Utils.rgbToHex(pixelData[0], pixelData[1], pixelData[2])).slice(-6);\n\n      // update the layer\n      // setting the color\n      this.colorPickingForLayer.setColor(hex);\n      // refreshing the control\n      let control = this.controls.find((control) => control.layer.id === this.colorPickingForLayer.id);\n      control.refresh();\n      this.redraw();\n    } else if (this.isAlphaPicking) {\n      const eventLocation = Utils.getCanvasCords(this.canvas, event);\n      const pixelData = this.canvas.getContext('2d').getImageData(eventLocation.x, eventLocation.y, 1, 1).data;\n      if (pixelData[0] == 0 && pixelData[1] == 0 && pixelData[2] == 0 && pixelData[3] == 0) {\n        // Do nothing if the pixel is transparent\n      } else {\n        this.alphaColorHex = '#' + ('000000' + Utils.rgbToHex(pixelData[0], pixelData[1], pixelData[2])).slice(-6);\n        this.alphaColor = new Color({\n          red: pixelData[0],\n          green: pixelData[1],\n          blue: pixelData[2],\n          alpha: pixelData[3],\n          tolerance: this.alphaTolerance,\n        });\n        const control = this.controls.find((control) => control.layer.id === this.alphaPickingForLayer.id);\n        control.refresh();\n        this.redraw();\n      }\n    }\n\n    if (this.activeLayer === null) return;\n    if (!this.isDragging) return;\n\n    const delta = {\n      x: this.lastPosition.x - event.clientX,\n      y: this.lastPosition.y - event.clientY,\n    };\n\n    if (this.activeLayer.source !== null) {\n      this.activeLayer.translate(delta.x, delta.y);\n    }\n    this.activeLayer.redraw();\n    this.redraw(true);\n    this.lastPosition = {\n      x: event.clientX,\n      y: event.clientY,\n    };\n  }\n\n  /**\n   * Scales the source image on mouse wheel events\n   * @param {Event} event\n   */\n  onWheel(event) {\n    if (this.activeLayer === null) return;\n    event.preventDefault();\n\n    if (event.shiftKey) {\n      const degree = event.deltaY / 100;\n\n      this.activeLayer.rotate(degree);\n      this.activeLayer.redraw();\n      this.redraw();\n    } else {\n      const eventLocation = Utils.getCanvasCords(this.canvas, event);\n      if (this.activeLayer.source !== null) {\n        const scaleDirection = event.deltaY / 100;\n        const factor = 1 - (scaleDirection * 0.05);\n        const dx = (eventLocation.x - this.activeLayer.position.x) * (factor - 1),\n          dy = (eventLocation.y - this.activeLayer.position.y) * (factor - 1);\n\n        this.activeLayer.scale *= factor;\n        this.activeLayer.translate(dx, dy);\n        this.activeLayer.redraw();\n        this.redraw(true);\n      }\n    }\n  }\n\n  get width() {\n    return this.canvas.width;\n  }\n\n  get height() {\n    return this.canvas.height;\n  }\n\n  removeAllLayers() {\n    const ids = this.layers.map((l) => l.id);\n    ids.forEach((id) => {\n      this.removeImageLayer(id);\n    });\n  }\n\n  removeImageLayer(layerId) {\n    let index = 0;\n    for (index = 0; index <= this.layers.length; index++) {\n      if (this.layers[index].id === layerId) {\n        break;\n      }\n    }\n\n    // if this layer provided the mask, remove that mask, too\n    if (this.layers[index].providesMask) {\n      this.maskIds.delete(index);\n    }\n\n    // remove any masks applied to other layers\n    this.layers.forEach((l) => {\n      l.appliedMaskIds.delete(layerId);\n    });\n\n    // delete this from the array\n    this.layers.splice(index, 1);\n\n    // now for the controls\n    for (index = 0; index <= this.controls.length; index++) {\n      if (this.controls[index].layer.id === layerId) {\n        break;\n      }\n    }\n    // remove the control first\n    let control = this.controls.find((control) => control.layer.id === layerId);\n    control.view.remove();\n\n    this.controls.splice(index, 1);\n    this.refreshControls();\n    this.redraw(true);\n  }\n\n  refreshControls() {\n    this.controls.forEach((control) => control.refresh());\n  }\n\n  #addLayerControls(layer, { masked, activate } = {}) {\n    // add the control at the top of the control array\n    try {\n      const control = new Control(this.tokenizer, layer, this.layers.length - 1);\n      this.controls.unshift(control);\n\n      // add the control at the top of the control area, too\n      this.controlsArea.insertBefore(control.view, this.controlsArea.firstChild);\n      this.refreshControls();\n\n      // Setup all listeners for this control\n      control.view.addEventListener('color', (event) => {\n        this.setColor(event.detail.layerId, event.detail.color);\n        this.refreshControls();\n      });\n      control.view.addEventListener('color-tint', (event) => {\n        this.setLayerColorTint(event.detail.layerId, event.detail.color);\n        this.refreshControls();\n      });\n      control.view.addEventListener('mask', (event) => {\n        this.activateMask(event.detail.layerId);\n        this.refreshControls();\n      });\n      // if a default mask is applied, trigger the calculation of the mask, too\n      if (masked) {\n        this.activateMask(layer.id);\n        this.refreshControls();\n      }\n      control.view.addEventListener('activate', (event) => {\n        this.activateLayer(event.detail.layerId);\n        this.refreshControls();\n      });\n      if (activate) {\n        this.activateLayer(layer.id);\n        this.refreshControls();\n      }\n      control.view.addEventListener('deactivate', () => {\n        this.deactivateLayers();\n        this.refreshControls();\n      });\n      control.view.addEventListener('center', (event) => {\n        this.centerLayer(event.detail.layerId);\n      });\n      control.view.addEventListener('reset', (event) => {\n        this.resetLayer(event.detail.layerId);\n      });\n      control.view.addEventListener('flip', (event) => {\n        this.mirrorLayer(event.detail.layerId);\n      });\n      control.view.addEventListener('move', (event) => {\n        // move the control in sync\n        this.moveLayer(event.detail.layerId, event.detail.direction);\n        this.refreshControls();\n      });\n      control.view.addEventListener('pick-color-start', (event) => {\n        this.startColorPicking(event.detail.layerId);\n      });\n      control.view.addEventListener('pick-color-end', () => {\n        this.endColorPicking(true);\n      });\n      control.view.addEventListener('pick-alpha-start', (event) => {\n        this.startAlphaPicking(event.detail.layerId);\n      });\n      control.view.addEventListener('pick-alpha-end', () => {\n        this.endAlphaPicking(true);\n      });\n      control.view.addEventListener('delete', (event) => {\n        this.removeImageLayer(event.detail.layerId);\n      });\n      control.view.addEventListener('opacity', (event) => {\n        this.setOpacity(event.detail.layerId, event.detail.opacity);\n        this.refreshControls();\n      });\n      control.view.addEventListener('visible', (event) => {\n        this.setLayerVisibility(event.detail.layerId);\n        this.refreshControls();\n      });\n      control.view.addEventListener('blend', (event) => {\n        this.setBlendMode(event.detail.layerId, event.detail.blendMode, event.detail.mask);\n      });\n      control.view.addEventListener('edit-mask', async (event) => {\n        this.editMask(event.detail.layerId);\n      });\n      control.view.addEventListener('mask-layer', async (event) => {\n        this.customMaskLayerToggle(event.detail.layerId, event.detail.maskLayerId);\n        this.refreshControls();\n      });\n      control.view.addEventListener('transparency-level', (event) => {\n        this.alphaTolerance = event.detail.tolerance;\n      });\n      control.view.addEventListener('reset-transparency-level', (event) => {\n        this.resetTransparencyLevel(event.detail.layerId);\n      });\n      control.view.addEventListener('reset-mask-layer', (event) => {\n        this.resetCustomMaskLayers(event.detail.layerId);\n        this.refreshControls();\n      });\n      control.view.addEventListener('duplicate', (event) => {\n        this.cloneLayer(event.detail.layerId);\n        this.refreshControls();\n      });\n      control.view.addEventListener('magic-lasso', async (event) => {\n        this.magicLasso(event.detail.layerId);\n      });\n      control.view.addEventListener('centre-layer', (event) => {\n        this.centreLayer(event.detail.layerId);\n        this.refreshControls();\n      });\n      control.view.addEventListener('scale-layer', (event) => {\n        this.scaleLayer(event.detail.layerId, event.detail.percent);\n        this.refreshControls();\n      });\n      control.view.addEventListener('line-art-blur-size', (event) => {\n        this.setLineArtBlurSize(event.detail.layerId, event.detail.size);\n        this.refreshControls();\n      });\n      control.view.addEventListener('line-art-toggle', (event) => {\n        this.setLineArtMode(event.detail.layerId, event.detail.enabled);\n        this.refreshControls();\n      });\n      control.view.addEventListener('contrast', (event) => {\n        this.setContrast(event.detail.layerId, event.detail.contrast);\n        this.refreshControls();\n      });\n      control.view.addEventListener('brightness', (event) => {\n        this.setBrightness(event.detail.layerId, event.detail.brightness);\n        this.refreshControls();\n      });\n\n    } catch (e) {\n      logger.error(e);\n      logger.error(e.stack);\n      throw e;\n    }\n  }\n\n  addLayer(layer, { masked = false, activate = false }) {\n    // add the new layer on top\n    this.layers.unshift(layer);\n    this.calculateAllDefaultMaskLayers();\n    this.redraw(true);\n\n    this.#addLayerControls(layer, { masked, activate });\n  }\n\n  addColorLayer({ masked = false, activate = false, color = null } = {},\n  ) {\n    logger.debug(`adding color layer with options`, {\n      imgSrc: `colorLayer: ${color}`,\n      masked,\n      color,\n      activate,\n    });\n\n    const imgOptions = {\n      view: this,\n      color,\n      width: this.width,\n      height: this.height,\n    };\n\n    const layer = Layer.fromColor(imgOptions);\n    this.addLayer(layer, { masked, activate });\n  }\n\n  addImageLayer(img, { masked = false, activate = false, tintColor = null, tintLayer = false,\n    position = { x: null, y: null }, scale = null, maskFromImage = false, visible = true, type = null } = {},\n  ) {\n    const imgSrc = Utils.isString(img.src) && !img.src.startsWith(\"data:image/png;base64\")\n      ? img.src\n      : \"blob-data\";\n\n    logger.debug(`adding image layer ${imgSrc}`, {\n      imgSrc,\n      masked,\n      activate,\n      tintColor,\n      tintLayer,\n      position,\n      scale,\n      maskFromImage,\n      visible,\n    });\n\n    const imgOptions = {\n      view: this,\n      img,\n      canvasHeight: this.width,\n      canvasWidth: this.height,\n      tintColor,\n      tintLayer,\n      maskFromImage,\n      visible,\n      type,\n    };\n\n    const layer = Layer.fromImage(imgOptions);\n\n    if (masked) {\n      layer.createMask();\n      layer.redraw();\n    }\n\n    if (scale) layer.scale = scale;\n    if (position.x && position.y) {\n      const upScaledX = layer.canvas.width * (position.x / this.width);\n      const upScaledY = layer.canvas.height * (position.y / this.height);\n      layer.translate(upScaledX, upScaledY);\n      if (!scale) {\n        const newScaleFactor = (layer.canvas.width - (Math.abs(upScaledX) * 2)) / layer.canvas.width;\n        layer.scale *= newScaleFactor;\n      }\n    }\n\n    this.addLayer(layer, { masked, activate });\n  }\n\n  setLayerColorTint(id, color) {\n    const layer = this.layers.find((layer) => layer.id === id);\n    if (layer) {\n      if (color) {\n        layer.tintColor = color;\n        layer.tintLayer = true;\n      } else {\n        layer.tintColor = null;\n        layer.tintLayer = false;\n      }\n      this.redraw(true);\n    }\n  }\n\n  /**\n   * Starts color picking for a given layer\n   * @param {String} id The layer that is getting the picked color as a background color\n   */\n  startColorPicking(id) {\n    const layer = this.layers.find((layer) => layer.id === id);\n    layer.saveColor();\n    // move the control in sync\n    this.isColorPicking = true;\n    this.colorPickingForLayer = layer;\n    this.canvas.classList.add('isColorPicking');\n  }\n\n    /**\n   * Starts alpha picking for a given layer\n   * @param {String} id The layer that is getting the picked color as a alpha color\n   */\n  startAlphaPicking(id) {\n    const layer = this.layers.find((layer) => layer.id === id);\n    layer.saveAlphas();\n    // move the control in sync\n    this.isAlphaPicking = true;\n    this.alphaPickingForLayer = layer;\n    this.canvas.classList.add('isColorPicking');\n  }\n\n  resetTransparencyLevel(id) {\n    const layer = this.layers.find((layer) => layer.id === id);\n    if (layer) {\n      layer.alphaPixelColors.clear();\n      this.redraw(true);\n    }\n  }\n\n  resetCustomMaskLayers(id) {\n    const layer = this.layers.find((layer) => layer.id === id);\n    if (layer) {\n      layer.resetMasks();\n      this.redraw(true);\n    }\n  }\n\n  cloneLayer(id) {\n    const layer = this.layers.find((layer) => layer.id === id);\n    if (layer) {\n      const newLayer = layer.clone();\n      this.addLayer(newLayer, { masked: newLayer.providesMask, activate: false });\n      this.redraw(true);\n    }\n  }\n\n  centreLayer(id) {\n    const layer = this.layers.find((layer) => layer.id === id);\n    if (layer) {\n      layer.centre();\n      this.redraw(true);\n    }\n  }\n\n  scaleLayer(id, percent) {\n    const layer = this.layers.find((layer) => layer.id === id);\n    if (layer) {\n      layer.scaleByPercent(percent);\n      this.redraw(true);\n    }\n  }\n\n\n  /**\n   * Ends a color picking state\n   * @param {boolean} reset If the user aborted the color picking, we will reset to the original color\n   */\n  endColorPicking(reset = false) {\n    this.canvas.classList.remove('isColorPicking');\n    // move the control in sync\n    this.isColorPicking = false;\n\n    // update the layer\n    if (reset) {\n      // setting the color\n      this.colorPickingForLayer.restoreColor();\n      this.redraw(true);\n    }\n\n    // refreshing the control\n    const control = this.controls.find((control) => control.layer.id === this.colorPickingForLayer.id);\n    control.endColorPicking();\n\n    this.colorPickingForLayer = null;\n\n    control.refresh();\n    this.redraw(true);\n  }\n\n  /**\n   * Ends a color picking state\n   * @param {boolean} reset If the user aborted the color picking, we will reset to the original color\n   */\n  endAlphaPicking(reset = false) {\n    this.canvas.classList.remove('isColorPicking');\n    // move the control in sync\n    this.isAlphaPicking = false;\n\n    // update the layer\n    if (reset) {\n      // setting the color\n      this.alphaPickingForLayer.restoreAlphas();\n      this.redraw(true);\n    } else {\n      this.alphaPickingForLayer.addTransparentColour(this.alphaColor);\n    }\n\n    // refreshing the control\n    const control = this.controls.find((control) => control.layer.id === this.alphaPickingForLayer.id);\n    control.endAlphaPicking();\n\n    this.alphaPickingForLayer = null;\n\n    control.refresh();\n    this.redraw();\n  }\n\n  isOriginLayerHigher(originId, targetId) {\n    if (!originId || !targetId) return undefined;\n    const originIndex = this.layers.findIndex((layer) => layer.id === originId);\n    const targetIndex = this.layers.findIndex((layer) => layer.id === targetId);\n    return targetIndex > originIndex;\n  }\n\n  moveLayer(id, direction) {\n    // get the index in the layers-layer for this layer;\n    const sourceId = this.layers.findIndex((layer) => layer.id === id);\n    // check for validity\n    const targetId = sourceId == -1 \n      ? -1\n      : (direction === 'up') \n        ? sourceId - 1 \n        : sourceId + 1;\n    // check if a valid targetID was derived\n    if (this.layers[targetId] !== undefined) {\n      // swap the elements\n      [this.layers[sourceId], this.layers[targetId]] = [this.layers[targetId], this.layers[sourceId]];\n      // swap the corresponding controls, too\n      const sourceControl = this.controlsArea.children[sourceId];\n      const targetControl = this.controlsArea.children[targetId];\n\n      // swap the elements and enable/disable move controls if they are at the bottom or top\n      if (direction === 'up') {\n        this.controlsArea.insertBefore(sourceControl, targetControl);\n        if (targetId === 0) {\n          this.controls[targetId].disableMoveDown();\n          this.controls[sourceId].enableMoveDown();\n        }\n        if (targetId === this.layers.length - 1) {\n          this.controls[targetId].disableMoveUp();\n          this.controls[sourceId].enableMoveUp();\n        }\n      } else {\n        this.controlsArea.insertBefore(sourceControl, targetControl.nextSibling);\n      }\n      this.calculateDefaultAppliedMaskLayers(this.layers[targetId].id);\n      this.calculateDefaultAppliedMaskLayers(this.layers[sourceId].id);\n    }\n    this.redraw(true);\n  }\n\n  centerLayer(id) {\n    this.resetLayer(id);\n  }\n\n  resetLayer(id) {\n    const layer = this.layers.find((layer) => layer.id === id);\n    if (layer !== null) {\n      layer.reset();\n      this.redraw(true);\n    }\n  }\n\n  setBlendMode(id, blendMode, isMask) {\n    const layer = this.layers.find((layer) => layer.id === id);\n    if (layer !== null) {\n      if (isMask) layer.maskCompositeOperation = blendMode;\n      else layer.compositeOperation = blendMode;\n      this.redraw(true);\n    }\n  }\n\n  mirrorLayer(id) {\n    const layer = this.layers.find((layer) => layer.id === id);\n    if (layer !== null) {\n      layer.flip();\n      this.redraw();\n    }\n  }\n\n  setOpacity(id, opacity) {\n    const layer = this.layers.find((layer) => layer.id === id);\n    if (layer !== null) {\n      layer.alpha = parseInt(opacity) / 100;\n      layer.redraw();\n      this.redraw();\n    }\n  }\n\n  setLineArtBlurSize(id, size) {\n    const layer = this.layers.find((layer) => layer.id === id);\n    if (layer !== null) {\n      layer.lineArtBlurSize = parseInt(size);\n      layer.redraw();\n      this.redraw(true);\n    }\n  }\n\n  setLineArtMode(id, enabled) {\n    const layer = this.layers.find((layer) => layer.id === id);\n    if (layer !== null) {\n      const existing = layer.filters.some((f) => f.name.includes(\"lineArtEffect\"));\n      if (enabled && !existing) {\n        layer.filters.push(layer.lineArtEffect.bind(layer));\n      } else if (!enabled && existing) {\n        layer.filters = layer.filters.filter((f) => !f.name.includes(\"lineArtEffect\"));\n      }\n      layer.redraw();\n      this.redraw(true);\n    }\n  }\n\n  setContrast(id, contrast) {\n    const layer = this.layers.find((layer) => layer.id === id);\n    if (layer !== null) {\n      layer.contrast = parseInt(contrast);\n      layer.redraw();\n      this.redraw(true);\n    }\n  }\n\n  setBrightness(id, brightness) {\n    const layer = this.layers.find((layer) => layer.id === id);\n    if (layer !== null) {\n      layer.brightness = parseInt(brightness);\n      layer.redraw();\n      this.redraw(true);\n    }\n  }\n\n  setLayerVisibility(id) {\n    const layer = this.layers.find((layer) => layer.id === id);\n    if (layer !== null) {\n      layer.visible = !layer.visible;\n      this.redraw();\n    }\n  }\n\n  /**\n   * Activates a layer for translation/scaling\n   * @param Number | null id of the layer that should activate it's mask, if null: Activate the lowest layer with id = 0\n   */\n  activateLayer(id = 0) {\n    // set all layers to inactive\n    this.layers.forEach((layer) => (layer.active = false));\n    this.activeLayer = this.layers.find((layer) => layer.id === id);\n    // activate the layer with given id\n    if (this.activeLayer !== null) {\n      this.activeLayer.active = true;\n    }\n    this.redraw();\n  }\n\n  /**\n   * Deactives all layers (can only be one active at a time...)\n   */\n  deactivateLayers() {\n    this.activeLayer = null;\n    this.layers.forEach((layer) => (layer.active = false));\n    this.redraw();\n  }\n\n  calculateDefaultAppliedMaskLayers(id) {\n    const layer = this.layers.find((l) => l.id === id);\n    const index = this.layers.findIndex((l) => l.id === id);\n\n    logger.debug(`Adding mask ids to layer ${index} (${id})`, layer);\n    if (layer && !layer.customMaskLayers) {\n      this.layers.forEach((l) => {\n        if (l.providesMask && this.isOriginLayerHigher(l.id, id)) {\n          logger.debug(`Applying id ${l.id}`);\n          layer.appliedMaskIds.add(l.id);\n        } else {\n          logger.debug(`Deleting id ${l.id}`);\n          layer.appliedMaskIds.delete(l.id);\n        }\n      });\n    }\n  }\n\n  calculateAllDefaultMaskLayers() {\n    this.layers.forEach((layer) => {\n      this.calculateDefaultAppliedMaskLayers(layer.id);\n    });\n  }\n\n  /**\n   * Activates the mask with the given id\n   * @param Number | null id of the layer that should activate it's mask, if null: Activate the lowest layer with id = 0\n   */\n  activateMask(id = 0) {\n    logger.debug(`Toggling layer ${id} active mask`);\n    // reset existing mask provision\n    const layer = this.layers.find((layer) => layer.id === id);\n\n    if (layer) {\n      // check if this layer currently provides the mask\n      if (layer.providesMask === true) {\n        layer.providesMask = false;\n        this.maskIds.delete(id);\n      } else {\n        layer.createMask();\n        layer.redraw();\n        this.maskIds.add(id);\n        layer.providesMask = true;\n      }\n\n      this.calculateAllDefaultMaskLayers();\n    }\n\n    this.redraw(true);\n    return true;\n  }\n\n  customMaskLayerToggle(id, maskLayerId) {\n    logger.debug(`Toggling custom mask layers for ${id} layer and mask ${maskLayerId}`);\n    const layer = this.layers.find((l) => l.id === id);\n    if (layer) {\n      layer.customMaskLayers = true;\n\n      if (layer.appliedMaskIds.has(maskLayerId)) {\n        layer.appliedMaskIds.delete(maskLayerId);\n      } else {\n        layer.appliedMaskIds.add(maskLayerId);\n      }\n    }\n    this.redraw(true);\n  }\n\n  editMask(id) {\n    logger.debug(`Editing mask for layer ${id}`);\n    const layer = this.layers.find((layer) => layer.id === id);\n    if (layer) {\n      layer.editMask(this.redraw.bind(this));\n      this.deactivateLayers();\n      this.refreshControls();\n    }\n  }\n\n  magicLasso(id) {\n    logger.debug(`Magic Lasso for layer ${id}`);\n    const layer = this.layers.find((layer) => layer.id === id);\n    if (layer) {\n      layer.magicLasso(this.redraw.bind(this));\n      this.deactivateLayers();\n      this.refreshControls();\n    }\n  }\n\n  // eslint-disable-next-line default-param-last\n  setColor(id = 0, hexColorString) {\n    const layer = this.layers.find((layer) => layer.id === id);\n    if (layer !== null) {\n      logger.debug('Setting color for layer', { layer, hexColorString });\n      layer.setColor(hexColorString);\n      this.redraw(true);\n    }\n  }\n\n  redraw(full = false) {\n    const context = this.canvas.getContext('2d');\n    context.clearRect(0, 0, this.width, this.height);\n\n    if (full) {\n      logger.debug(\"Full redraw triggered\");\n      this.layers.forEach((layer) => {\n        logger.debug(`Recalculating mask for ${layer.id}`, layer);\n        layer.recalculateMask();\n      });\n      this.layers.forEach((layer) => {\n        logger.debug(`Recalculating visual layer for ${layer.id}`, layer);\n        layer.redraw();\n      });\n    }\n\n    // loop through each layer, and apply the layer to the canvas\n    for (let index = this.layers.length - 1; index >= 0; index--) {\n      const layer = this.layers[index];\n      if (layer.visible) {\n        const imgSrc = Utils.isString(layer.sourceImg) && !layer.sourceImg.startsWith(\"data:image/png;base64\")\n          ? layer.sourceImg\n          : \"blob-data\";\n        logger.debug(`Drawing layer ${layer.id} for ${imgSrc}`);\n\n        context.globalCompositeOperation = layer.compositeOperation;\n        context.globalAlpha = layer.alpha;\n\n        context.drawImage(\n          layer.canvas,\n          0,\n          0,\n          layer.canvas.width,\n          layer.canvas.height,\n          0,\n          0,\n          this.width,\n          this.height,\n        );\n      }\n    }\n  }\n}\n","import DirectoryPicker from \"./DirectoryPicker.js\";\nimport logger from \"../libs/logger.js\";\n\nconst { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;\n\nclass ImageBrowser extends HandlebarsApplicationMixin(ApplicationV2) {\n\n  static MAX_ASSETS = 100;\n\n  static async getFileUrl (foundryFilePath, encode = true) {\n    let uri;\n    try {\n      let dir = DirectoryPicker.parse(foundryFilePath);\n      if (dir.activeSource == \"data\" || dir.current.startsWith(\"https://\")) {\n        // Local on-server file system\n        uri = dir.current;\n      } else if (dir.activeSource == \"forgevtt\") {\n        const status = ForgeAPI.lastStatus || await ForgeAPI.status();\n        const userId = status.user;\n        uri = \"https://assets.forge-vtt.com/\" + userId + \"/\" + dir.current;\n      } else {\n        // S3 Bucket\n        uri\n          = game.data.files.s3.endpoint.protocol\n          + \"//\"\n          + dir.bucket\n          + \".\"\n          + game.data.files.s3.endpoint.hostname\n          + \"/\"\n          + dir.current;\n      }\n    } catch (exception) {\n      logger.warn(`Unable to determine file URL for '${foundryFilePath}'`);\n      throw new Error(`Unable to determine file URL for '${foundryFilePath}'`);\n    }\n    if (encode) {\n      return encodeURI(uri);\n    } else {\n      return uri;\n    }\n  }\n\n  constructor(assets, options) {\n    super();\n    this.assets = assets;\n    this.type = options.type || \"image\";\n    this.callback = options.callback;\n    this.assetInc = 0;\n    this.ignoreScroll = false;\n  }\n\n  static PARTS = {\n    header: {\n      template: \"modules/vtta-tokenizer/templates/image-browser/header.hbs\",\n    },\n    form: {\n      template: \"modules/vtta-tokenizer/templates/image-browser/form.hbs\",\n    },\n  };\n\n  static DEFAULT_OPTIONS = {\n    id: \"tokenizer-image-browser\",\n    classes: [\"standard-form\", \"imagebrowser\", 'themed', 'theme-light'],\n    actions: {\n      selectImage: ImageBrowser.selectImage,\n      openFilePicker: ImageBrowser.openFilePicker,\n    },\n    position: {\n      width: 902,\n      height: \"auto\",\n    },\n    tag: \"form\",\n    window: {\n      title: \"Image Browser\",\n      resizable: true,\n      minimizable: false,\n      subtitle: \"\",\n    },\n    form: {\n      handler: ImageBrowser.formHandler,\n      submitOnChange: false,\n      closeOnSubmit: false,\n    },\n  };\n\n  async _prepareContext() {\n    let idx = 0;\n    const assets = await Promise.all(this.assets.map(async (asset) => {\n      const uri = await ImageBrowser.getFileUrl(asset.key, false);\n      const result = { uri, label: asset.label, idx };\n      idx++;\n      return result;\n    }));\n\n    const canBrowse = game.user && game.user.can(\"FILES_BROWSE\");\n\n    return {\n      canBrowse,\n      assets,\n    };\n  }\n\n  _onRender() {\n    this.bringToTop();\n    const listEl = this.element.querySelector(\".list\");\n    if (listEl) {\n      listEl.addEventListener(\"scroll\", this._onScroll.bind(this));\n    }\n  }\n\n  static async selectImage(event, target) {\n    event.preventDefault();\n    const idx = target.dataset.idx;\n    this.callback(this.assets[idx].key);\n    this.close();\n  }\n\n  static async openFilePicker(event) {\n    event.preventDefault();\n    const directoryPath = game.settings.get(\"vtta-tokenizer\", \"frame-directory\");\n    const usePath = directoryPath === \"\"\n      ? \"[data] modules/vtta-tokenizer/img\"\n      : directoryPath;\n    const dir = DirectoryPicker.parse(usePath);\n    new foundry.applications.apps.FilePicker.implementation({\n      type: 'image',\n      displayMode: 'tiles',\n      source: dir.activeSource,\n      current: dir.current,\n      options: { bucket: dir.bucket },\n      callback: (imagePath, fPicker) => {\n        const formattedPath = fPicker.result.bucket\n          ? `[${fPicker.activeSource}:${fPicker.result.bucket}] ${imagePath}`\n          : `[${fPicker.activeSource}] ${imagePath}`;\n        this.callback(formattedPath);\n      },\n    }).render();\n    this.close();\n  }\n\n  async _onScroll(event) {\n    if (this.ignoreScroll) return;\n    const el = event.currentTarget;\n    const bottom = el.scrollHeight - el.scrollTop;\n    const height = el.clientHeight;\n    if (!this.assets) return;\n    if (bottom - 20 < height) {\n      this.ignoreScroll = true;\n      if (\n        this.assetInc * ImageBrowser.MAX_ASSETS\n        < this.assets.length\n      ) {\n        this.assetInc++;\n        const listEl = this.element.querySelector(\".list\");\n        const newAssets = this.assets.slice(\n          this.assetInc * ImageBrowser.MAX_ASSETS,\n          (this.assetInc + 1) * ImageBrowser.MAX_ASSETS,\n        );\n        const resolvedAssets = await Promise.all(newAssets.map(async (asset) => {\n          const uri = await ImageBrowser.getFileUrl(asset.key, false);\n          const idx = this.assets.indexOf(asset);\n          return { uri, label: asset.label, idx };\n        }));\n        for (const { uri, label, idx } of resolvedAssets) {\n          const div = document.createElement(\"div\");\n          div.className = \"imageresult draggable\";\n          div.title = label;\n          div.dataset.action = \"selectImage\";\n          div.dataset.idx = idx;\n          div.innerHTML = `<img width=\"100\" height=\"100\" src=\"${uri}\"/>`;\n          listEl.appendChild(div);\n        }\n      }\n      this.ignoreScroll = false;\n    }\n  }\n\n}\n\nexport default ImageBrowser;\n","import logger from \"../libs/logger.js\";\nimport CONSTANTS from \"../constants.js\";\nimport DirectoryPicker from \"./DirectoryPicker.js\";\n\nconst { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;\nconst FPClass = foundry.applications.apps.FilePicker.implementation;\n\n\nexport class TokenizerSaveLocations extends HandlebarsApplicationMixin(ApplicationV2) {\n\n  constructor(tokenizer) {\n    super();\n    this.tokenizer = tokenizer;\n    this.data = [];\n  }\n\n  static PARTS = {\n    form: {\n      template: \"modules/vtta-tokenizer/templates/save-locations/form.hbs\",\n    },\n    footer: {\n      template: \"modules/vtta-tokenizer/templates/save-locations/footer.hbs\",\n    },\n  };\n\n    /** @inheritDoc */\n  static DEFAULT_OPTIONS = {\n    id: \"tokenizer-save-locations\",\n    classes: [\"standard-form\"],\n    actions: {\n      selectDirectory: TokenizerSaveLocations.selectDirectory,\n    },\n    position: {\n      width: \"500\",\n      height: \"auto\",\n    },\n    window: {\n      resizable: false,\n      minimizable: false,\n      subtitle: \"\",\n    },\n    tag: \"form\",\n    form: {\n      submitOnChange: false,\n      handler: TokenizerSaveLocations.formHandler,\n      closeOnSubmit: false,\n    },\n  };\n\n  // eslint-disable-next-line class-methods-use-this\n  get title() {\n    return game.i18n.localize(\"vtta-tokenizer.label.save-locations\");\n  }\n\n  async _prepareContext() {\n    this.data = [\n      {\n        key: \"avatar\",\n        name: game.i18n.localize(\"vtta-tokenizer.label.avatar\"),\n        directoryPath: this.tokenizer.avatarUploadDirectory,\n        fileName: this.tokenizer.avatarFileName,\n      },\n      {\n        key: \"token\",\n        name: game.i18n.localize(\"vtta-tokenizer.label.token\"),\n        directoryPath: this.tokenizer.tokenUploadDirectory,\n        fileName: this.tokenizer.tokenFileName,\n      },\n    ];\n\n    return { type: this.data };\n  }\n\n  static async selectDirectory(event, target) {\n    const targetDirSetting = target.dataset.target;\n    const currentDir = this.tokenizer[targetDirSetting];\n    // const parsedDir = FileHelper.parseDirectory(currentDir);\n    const current = await DirectoryPicker.getFileUrl(currentDir, \"\");\n\n    const filePicker = new FPClass({\n      type: \"folder\",\n      current: current,\n      // source: parsedDir.activeSource,\n      // activeSource: parsedDir.activeSource,\n      // bucket: parsedDir.bucket,\n      callback: async (path, picker) => {\n        const activeSource = picker.activeSource;\n        const bucket = activeSource === \"s3\" && picker.sources.s3?.bucket && picker.sources.s3.bucket !== \"\"\n          ? picker.sources.s3.bucket\n          : null;\n\n        const formattedPath = DirectoryPicker.format({\n          activeSource,\n          bucket,\n          path,\n        });\n\n        this.element.querySelector(`input[name='${targetDirSetting}']`).value = formattedPath;\n      },\n    });\n    filePicker.render();\n\n  }\n\n\n  /**\n   * Process form submission for the sheet\n   * @this {DDBLocationSetup}                      The handler is called with the application as its bound scope\n   * @param {SubmitEvent} event                   The originating form submission event\n   * @param {HTMLFormElement} form                The form element that was submitted\n   * @param {FormDataExtended} formData           Processed data for the submitted form\n   * @returns {Promise<void>}\n   */\n  static async formHandler(event, form, formData) {\n    const directoryStatus = [];\n\n    for (const dataType of this.data) {\n      const value = formData.object[`${dataType.key}UploadDirectory`];\n      // eslint-disable-next-line no-await-in-loop\n      directoryStatus.push({\n        key: dataType.key,\n        value: dataType.value,\n        isBad: CONSTANTS.BAD_DIRS.includes(value),\n        // eslint-disable-next-line no-await-in-loop\n        isValid: await DirectoryPicker.verifyPath(DirectoryPicker.parse(value)),\n      });\n    }\n\n    if (directoryStatus.some((dir) => dir.isBad)) {\n      for (const data of directoryStatus.filter((dir) => dir.isBad)) {\n        ui.notifications.error(\n          `Please set the image upload directory for ${data.name} to something other than the root.`,\n          { permanent: true },\n        );\n        logger.error(\"Error setting Image directory\", {\n          directoryStatus,\n          data,\n        });\n      }\n    } else if (directoryStatus.some((dir) => !dir.isValid)) {\n      for (const data of directoryStatus.filter((dir) => !dir.isValid)) {\n        ui.notifications.error(\n          `Directory Validation Failed for ${data.name} please check it exists and can be written to.`,\n          { permanent: true },\n        );\n        logger.error(\"Error validating Image directory\", {\n          directoryStatus,\n          data,\n        });\n      }\n    } else {\n      this.tokenizer.avatarUploadDirectory = formData.object[\"avatarUploadDirectory\"];\n      this.tokenizer.tokenUploadDirectory = formData.object[\"tokenUploadDirectory\"];\n      this.tokenizer.avatarFileName = formData.object[\"avatarFileName\"];\n      this.tokenizer.tokenFileName = formData.object[\"tokenFileName\"];\n      logger.debug(\"Changed tokenizer save paths to...\", {\n        avatarUploadDirectory: this.tokenizer.avatarUploadDirectory,\n        tokenUploadDirectory: this.tokenizer.tokenUploadDirectory,\n        avatarFileName: this.tokenizer.avatarFileName,\n        tokenFileName: this.tokenizer.tokenFileName,\n      });\n      this.close();\n    }\n\n  }\n}\n","import Utils from \"../libs/Utils.js\";\nimport logger from \"../libs/logger.js\";\nimport View from \"./View.js\";\nimport DirectoryPicker from \"../libs/DirectoryPicker.js\";\nimport ImageBrowser from \"../libs/ImageBrowser.js\";\nimport CONSTANTS from \"../constants.js\";\nimport { TokenizerSaveLocations } from \"../libs/TokenizerSaveLocations.js\";\n\nconst { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;\n\nexport default class Tokenizer extends HandlebarsApplicationMixin(ApplicationV2) {\n\n  getOMFGFrames() {\n    if (game.settings.get(CONSTANTS.MODULE_ID, \"disable-omfg-frames\")) return [];\n    if (this.omfgFrames.length > 0) return this.omfgFrames;\n    logger.debug(`Checking for OMFG Token Frames files in...`);\n\n    [\"normal\", \"desaturated\"].forEach((version) => {\n      [\"v2\", \"v3\", \"v4\", \"v7\", \"v12\"].forEach((v) => {\n        for (let i = 1; i <= 8; i++) {\n          const fileName = `modules/vtta-tokenizer/img/omfg/${version}/${v}/OMFG_Tokenizer_${v}_0${i}.png`;\n          const label = `OMFG ${game.i18n.localize(\"vtta-tokenizer.label.Frame\")} ${v} 0${i}`;\n          const obj = {\n            key: fileName,\n            label,\n            selected: false,\n          };\n          if (!this.frames.some((frame) => frame.key === fileName)) {\n            this.omfgFrames.push(obj);\n          }\n        }\n      });\n    });\n    return this.omfgFrames;\n  }\n\n  async getTheGreatNachoFrames() {\n    if (game.settings.get(CONSTANTS.MODULE_ID, \"disable-thegreatnacho-frames\")) return [];\n    if (this.theGreatNachoFrames.length > 0) return this.theGreatNachoFrames;\n    logger.debug(`Checking for GreatNacho Token Frames.`);\n\n    for (let i = 1; i <= 20; i++) {\n      const fileName = `modules/vtta-tokenizer/img/thegreatnacho/theGreatNacho-${i}.webp`;\n      const label = `TheGreatNacho ${game.i18n.localize(\"vtta-tokenizer.label.Frame\")} ${i}`;\n      const obj = {\n        key: fileName,\n        label,\n        selected: false,\n      };\n      if (!this.frames.some((frame) => frame.key === fileName)) {\n        this.theGreatNachoFrames.push(obj);\n      }\n    }\n\n    return this.theGreatNachoFrames;\n  }\n\n  async getJColsonFrames() {\n    if (!game.modules.get(\"token-frames\")?.active || game.settings.get(CONSTANTS.MODULE_ID, \"disable-jcolson-frames\")) {\n      return [];\n    }\n    if (this.jColsonFrames.length > 0) return this.jColsonFrames;\n\n    const directoryPath = \"[data] modules/token-frames/token_frames\";\n    logger.debug(`Checking for JColson Token Frames files in ${directoryPath}...`);\n\n    const dir = DirectoryPicker.parse(directoryPath);\n    this.jColsonFrames = await this.getDirectoryImageData(dir.activeSource, { bucket: dir.bucket }, dir.current);\n\n    return this.jColsonFrames;\n  }\n\n  static getDefaultFrames() {\n    const npcFrame = game.settings.get(CONSTANTS.MODULE_ID, \"default-frame-npc\");\n    const otherNPCFrame = game.settings.get(CONSTANTS.MODULE_ID, \"default-frame-neutral\");\n    const npcDiff = npcFrame !== otherNPCFrame;\n    const setPlayerDefaultFrame = game.settings.get(CONSTANTS.MODULE_ID, \"default-frame-pc\").replace(/^\\/|\\/$/g, \"\");\n    const setNPCDefaultFrame = npcFrame.replace(/^\\/|\\/$/g, \"\");\n    const tintFrame = game.settings.get(CONSTANTS.MODULE_ID, \"default-frame-tint\");\n    const setTintFrame = tintFrame.replace(/^\\/|\\/$/g, \"\");\n\n    const defaultFrames = [\n      {\n        key: setTintFrame,\n        label: game.i18n.localize(\"vtta-tokenizer.default-frame-tint.name\"),\n        selected: false,\n      },\n      {\n        key: setPlayerDefaultFrame,\n        label: game.i18n.localize(\"vtta-tokenizer.default-frame-pc.name\"),\n        selected: false,\n      },\n      {\n        key: setNPCDefaultFrame,\n        label: npcDiff\n          ? game.i18n.localize(\"vtta-tokenizer.default-frame-npc.hostile\")\n          : game.i18n.localize(\"vtta-tokenizer.default-frame-npc.neutral\"),\n        selected: true,\n      },\n      {\n        key: CONSTANTS.SHADOWDARK_FRAME,\n        label: game.i18n.localize(\"vtta-tokenizer.shadowdark-frame.name\"),\n        selected: false,\n      },\n    ];\n\n    const foundryDefaultPCFrame = game.settings.settings.get(\"vtta-tokenizer.default-frame-pc\").default.replace(/^\\/|\\/$/g, \"\");\n    const foundryDefaultNPCFrame = game.settings.settings.get(\"vtta-tokenizer.default-frame-npc\").default.replace(/^\\/|\\/$/g, \"\");\n\n    if (foundryDefaultPCFrame !== setPlayerDefaultFrame) {\n      defaultFrames.push({\n        key: foundryDefaultPCFrame,\n        label: game.i18n.localize(\"vtta-tokenizer.default-frame-pc.foundry\"),\n        selected: false,\n      });\n    }\n    if (foundryDefaultNPCFrame !== setNPCDefaultFrame) {\n      defaultFrames.push({\n        key: foundryDefaultNPCFrame,\n        label: npcDiff\n          ? game.i18n.localize(\"vtta-tokenizer.default-frame-npc.foundry-hostile\")\n          : game.i18n.localize(\"vtta-tokenizer.default-frame-npc.foundry-neutral\"),\n        selected: false,\n      });\n    }\n\n    if (npcDiff) {\n      defaultFrames.push({\n        key: otherNPCFrame.replace(/^\\/|\\/$/g, \"\"),\n        label: game.i18n.localize(\"vtta-tokenizer.default-frame-npc.other\"),\n        selected: false,\n      });\n    }\n\n    return defaultFrames;\n  }\n\n  static generateImageData(file, prefix = \"\", selected = false) {\n    const labelSplit = file.split(\"/\").pop().trim();\n    const regex = new RegExp(`^${prefix}-`);\n    const label = labelSplit.replace(regex, \"\").replace(/[-_]/g, \" \");\n    return {\n      key: file,\n      label: Utils.titleString(label).split(\".\")[0],\n      selected,\n    };\n  }\n\n  async getDirectoryImageData(activeSource, options, path, type = \"frame\") {\n    const fileList = await DirectoryPicker.browse(activeSource, path, options);\n    const folderImages = fileList.files\n      .filter((file) => Utils.endsWithAny([\"png\", \"jpg\", \"jpeg\", \"gif\", \"webp\", \"webm\", \"bmp\"], file))\n      .map((file) => {\n        return Tokenizer.generateImageData(file, `${type}-`);\n      });\n\n    let dirImages = [];\n    if (fileList.dirs.length > 0) {\n      for (let i = 0; i < fileList.dirs.length; i++) {\n        const dir = fileList.dirs[i];\n        // eslint-disable-next-line no-await-in-loop\n        const subDirImages = await this.getDirectoryImageData(activeSource, options, dir);\n        dirImages.push(...subDirImages);\n      }\n    }\n    const result = folderImages.concat(dirImages);\n    return result;\n  }\n\n  async getFrames() {\n    const directoryPath = game.settings.get(CONSTANTS.MODULE_ID, \"frame-directory\");\n    logger.debug(`Checking for files in ${directoryPath}...`);\n    const dir = DirectoryPicker.parse(directoryPath);\n    const folderFrames = (directoryPath && directoryPath.trim() !== \"\" && directoryPath.trim() !== \"[data]\")\n      ? await this.getDirectoryImageData(dir.activeSource, { bucket: dir.bucket }, dir.current)\n      : [];\n\n    this.getOMFGFrames();\n    this.getTheGreatNachoFrames();\n    await this.getJColsonFrames();\n\n    const frames = this.defaultFrames.concat(folderFrames, this.customFrames, this.omfgFrames, this.theGreatNachoFrames, this.jColsonFrames);\n\n    this.frames = frames;\n    return this.frames;\n  }\n\n  async getMasks() {\n    const directoryPath = game.settings.get(CONSTANTS.MODULE_ID, \"masks-directory\");\n    logger.debug(`Checking for files in ${directoryPath}...`);\n    const dir = DirectoryPicker.parse(directoryPath);\n    const folderMasks = (directoryPath && directoryPath.trim() !== \"\" && directoryPath.trim() !== \"[data]\")\n      ? await this.getDirectoryImageData(dir.activeSource, { bucket: dir.bucket }, dir.current)\n      : [];\n\n    const masks = this.defaultMasks.concat(folderMasks, this.customMasks);\n\n    this.masks = masks;\n    return this.masks;\n  }\n\n  async handleFrameSelection(framePath) {\n    const frameInList = this.frames.some((frame) => frame.key === framePath);\n    if (!frameInList) {\n      const frame = Tokenizer.generateImageData(framePath, \"frame-\");\n      this.frames.push(frame);\n      this.customFrames.push(frame);\n      game.settings.set(\"vtta-tokenizer\", \"custom-frames\", this.customFrames);\n    }\n    this._setTokenFrame(framePath, true);\n  }\n\n  static getDefaultMasks() {\n    const defaultMask = game.settings.get(CONSTANTS.MODULE_ID, \"default-mask-layer\").replace(/^\\/|\\/$/g, \"\");\n\n    const defaultMasks = [\n      {\n        key: defaultMask,\n        label: game.i18n.localize(\"vtta-tokenizer.default-mask.name\"),\n        selected: true,\n      },\n    ];\n\n    const foundryDefaultMask = CONSTANTS.DEFAULT_MASK;\n    if (defaultMask !== foundryDefaultMask) {\n      defaultMasks.push({\n        key: foundryDefaultMask,\n        label: game.i18n.localize(\"vtta-tokenizer.dynamic-mask.foundry\"),\n        selected: false,\n      });\n    }\n\n    const foundryDefaultTopMask = CONSTANTS.DEFAULT_TOP_MASK;\n    if (defaultMask !== foundryDefaultTopMask) {\n      defaultMasks.push({\n        key: foundryDefaultTopMask,\n        label: game.i18n.localize(\"vtta-tokenizer.dynamic-top-mask.foundry\"),\n        selected: false,\n      });\n    }\n\n    return defaultMasks;\n  }\n\n  async handleMaskSelection(maskPath) {\n    const maskInList = this.masks.some((mask) => mask.key === maskPath);\n    if (!maskInList) {\n      const mask = Tokenizer.generateImageData(maskPath, \"mask-\");\n      this.masks.push(mask);\n      this.customMasks.push(mask);\n      game.settings.set(\"vtta-tokenizer\", \"custom-masks\", this.customMasks);\n    }\n    this._setTokenMask(maskPath, true);\n  }\n\n  getBaseUploadDirectory() {\n    if (this.tokenType === \"character\") {\n      return game.settings.get(\"vtta-tokenizer\", \"image-upload-directory\");\n    } else if (this.tokenType === \"npc\") {\n      return game.settings.get(\"vtta-tokenizer\", \"npc-image-upload-directory\");\n    } else {\n      return game.settings.get(\"vtta-tokenizer\", \"image-upload-directory\");\n    }\n  }\n\n  //  Options include\n  //  name: name to use as part of filename identifier\n  //  type: pc, npc\n  //  disposition: token disposition = -1, 0, 1\n  //  avatarFilename: current avatar image - defaults to null/mystery man\n  //  tokenFilename: current tokenImage - defaults to null/mystery man\n  //  targetFolder: folder to target, otherwise uses defaults, wildcard use folder derived from wildcard path\n  //  isWildCard: is wildcard token?\n  //  tokenOffset: { position: {x:0, y:0} }\n  //  forceDynamicRing: true - force dynamic ring on token\n  //  any other items needed in callback function, options will be passed to callback, with filenames updated to new references\n  //\n  constructor(options, callback) {\n    super();\n    this.tokenOptions = options;\n    this.defaultOffset = game.settings.get(CONSTANTS.MODULE_ID, \"default-token-offset\");\n    this.tokenOffset = options.tokenOffset\n      ? options.tokenOffset\n      : { position: { x: this.defaultOffset, y: this.defaultOffset } };\n    this.callback = callback;\n    this.modifyAvatar = !game.settings.get(CONSTANTS.MODULE_ID, \"token-only-toggle\");\n    this.modifyToken = true;\n    // frames\n    this.defaultFrames = Tokenizer.getDefaultFrames();\n    this.frames = [];\n    this.omfgFrames = [];\n    this.theGreatNachoFrames = [];\n    this.jColsonFrames = [];\n    this.customFrames = game.settings.get(CONSTANTS.MODULE_ID, \"custom-frames\");\n    this.addMask = game.settings.get(CONSTANTS.MODULE_ID, \"add-mask-default\");\n    // masks\n    this.defaultMasks = Tokenizer.getDefaultMasks();\n    this.masks = [];\n    this.customMasks = game.settings.get(CONSTANTS.MODULE_ID, \"custom-masks\");\n    this.addFrame = game.settings.get(CONSTANTS.MODULE_ID, \"add-frame-default\");\n    // colors\n    this.defaultColor = game.settings.get(CONSTANTS.MODULE_ID, \"default-color\");\n    this.tokenType = this.tokenOptions.type === \"pc\" ? \"pc\" : \"npc\";\n    this.nameSuffix = this.tokenOptions.nameSuffix ? this.tokenOptions.nameSuffix : \"\";\n    this.imageFormat = game.settings.get(CONSTANTS.MODULE_ID, \"image-save-type\");\n    // add some default file names, these will likely be changed\n    this.wildCardPath = undefined;\n    this.avatarUploadDirectory = this.getOverRidePath(false) || this.getBaseUploadDirectory();\n    this.tokenUploadDirectory = this.getOverRidePath(true) || this.getBaseUploadDirectory();\n    this.avatarFileName = `${this.tokenOptions.name}.Avatar${this.nameSuffix}.${this.imageFormat}`;\n    this.tokenFileName = `${this.tokenOptions.name}.Token${this.nameSuffix}.${this.imageFormat}`;\n    this.activeLayerSelectorElement = null;\n  }\n\n  static PARTS = {\n    form: {\n      template: \"modules/vtta-tokenizer/templates/tokenizer.hbs\",\n    },\n  };\n\n  static DEFAULT_OPTIONS = {\n    id: \"tokenizer-control\",\n    classes: [\"tokenizer\", \"themed\", \"theme-light\"],\n    tag: \"form\",\n    form: {\n      handler: Tokenizer.formHandler,\n      submitOnChange: false,\n      closeOnSubmit: true,\n    },\n    actions: {\n      filePickerThumbs: Tokenizer.filePickerThumbs,\n      boxButton: Tokenizer.boxButton,\n      menuButton: Tokenizer.menuButton,\n      invisibleButton: Tokenizer.invisibleButton,\n      chooseImage: this.#onChooseImage,\n    },\n    position: {\n      width: \"auto\",\n      height: \"auto\",\n    },\n    window: {\n      title: \"Tokenizer\",\n    },\n  };\n\n  /* -------------------------------------------- */\n\n  async _prepareContext() {\n    const frames = await this.getFrames();\n    const masks = await this.getMasks();\n    const pasteTarget = game.settings.get(CONSTANTS.MODULE_ID, \"paste-target\") ?? \"token\";\n    const pasteTargetName = Utils.titleString(pasteTarget);\n\n    return {\n      options: this.tokenOptions,\n      canUpload: game.user && game.user.can(\"FILES_UPLOAD\"),\n      canBrowse: game.user && game.user.can(\"FILES_BROWSE\"),\n      tokenVariantsEnabled: game.user && game.user.can(\"FILES_BROWSE\") && game.modules.get(\"token-variants\")?.active,\n      frames,\n      masks,\n      pasteTarget,\n      pasteTargetName,\n      modifyAvatar: this.modifyAvatar,\n    };\n  }\n\n  static async #onChooseImage(event, button) {\n    const target = button.dataset.target;\n    let current = \"\";\n    let view;\n    if (target === \"foundryAvatar\") {\n      view = this.Avatar;\n      current = this.tokenOptions.avatarFilename || \"\";\n    } else if (target === \"foundryToken\") {\n      view = this.Token;\n      current = this.tokenOptions.tokenFilename || \"\";\n    }\n\n    const fp = new foundry.applications.apps.FilePicker.implementation({\n      type: \"image\",\n      current: current,\n      callback: (path) => {\n        Utils.download(path)\n          .then((img) => view.addImageLayer(img, { type: \"image\" }))\n          .catch((error) => ui.notifications.error(error));\n      },\n    });\n\n    fp.render(true);\n  }\n\n  getWildCardPath() {\n    if (!this.tokenOptions.isWildCard) return undefined;\n    this.wildCardPath = this.tokenOptions.tokenFilename\n      ? Utils.dirPath(this.tokenOptions.tokenFilename)\n      : `${this.tokenUploadDirectory}`;\n    return this.wildCardPath;\n  }\n\n  getOverRidePath(isToken) {\n    let path;\n    if (isToken && this.tokenOptions.isWildCard) {\n      path = this.getWildCardPath();\n    }\n    if (!path) {\n      path = this.tokenOptions.targetFolder\n        ? this.tokenOptions.targetFolder\n        : undefined;\n    }\n    return path;\n  }\n\n  async _getFilename(suffix = \"Avatar\", postfix = \"\") {\n    const actorName = await Utils.makeSlug(this.tokenOptions.name);\n\n    if (suffix === \"Token\" && this.tokenOptions.isWildCard) {\n      // for wildcards we respect the current path of the existing/provided tokenpath\n      const dirOptions = DirectoryPicker.parse(this.wildCardPath);\n      const tokenWildcard = this.tokenOptions.tokenFilename.indexOf(\"*\") === -1\n        // set it to a wildcard we can actually use\n        ? `${dirOptions.current}/${actorName}.Token-*.${this.imageFormat}`\n        : this.tokenOptions.tokenFilename.endsWith(`.${this.imageFormat}`)\n          ? this.tokenOptions.tokenFilename\n          : `${this.tokenOptions.tokenFilename}.${this.imageFormat}`;\n\n      const FPClass = foundry.applications.apps.FilePicker.implementation;\n      const browser = await FPClass.browse(dirOptions.activeSource, tokenWildcard, {\n        wildcard: true,\n      });\n\n      const newCount = browser.files.length + 1;\n      const num = newCount.toString().padStart(3, \"0\");\n      const targetFilename = tokenWildcard.replace(/\\*/g, num).split(\"/\").pop();\n\n      return targetFilename;\n    }\n    return `${actorName}.${suffix}${postfix}.${this.imageFormat}`;\n  }\n\n  async updateToken(dataBlob) {\n    if (this.modifyToken) {\n      this.tokenOptions.tokenUploadDirectory = this.tokenUploadDirectory;\n      const filePath = await Utils.uploadToFoundry(dataBlob, this.tokenUploadDirectory, this.tokenFileName);\n      logger.debug(`Created token at ${filePath}`);\n      this.tokenOptions.tokenFilename = filePath;\n    }\n  }\n\n  async updateAvatar(dataBlob) {\n    if (this.modifyAvatar) {\n      this.tokenOptions.avatarUploadDirectory = this.avatarUploadDirectory;\n      const filePath = await Utils.uploadToFoundry(dataBlob, this.avatarUploadDirectory, this.avatarFileName);\n      logger.debug(`Created avatar at ${filePath}`);\n      this.tokenOptions.avatarFilename = filePath;\n    }\n  }\n\n  static async formHandler() {\n    // upload token and avatar\n    const dataResults = await Promise.all([this.Avatar.get(\"blob\"), this.Token.get(\"blob\")]);\n    await this.updateAvatar(dataResults[0]);\n    await this.updateToken(dataResults[1]);\n    this.callback(this.tokenOptions);\n  }\n\n  closeQuickLayerSelector(type) {\n    const menu = document.getElementById(`quick-${type}-menu`);\n    menu.classList.remove(\"show\");\n    this.lastControlButtonClicked = null;\n    this.activeLayerSelectorElement = null;\n  }\n\n  /* -------------------------------------------- */\n\n  async _initAvatar(inputUrl) {\n    const url = inputUrl ?? CONST.DEFAULT_TOKEN ?? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';\n    const avatarView = document.querySelector(\".avatar > .view\");\n    if (this.Avatar) {\n      this.Avatar.canvas.remove();\n      this.Avatar.stage.remove();\n      this.Avatar.controlsArea.remove();\n      this.Avatar.menu.remove();\n    }\n    this.Avatar = null;\n    try {\n      const img = await Utils.download(url);\n      const MAX_DIMENSION = Math.max(img.naturalHeight, img.naturalWidth, game.settings.get(CONSTANTS.MODULE_ID, \"portrait-size\"));\n      logger.debug(\"Setting Avatar dimensions to \" + MAX_DIMENSION + \"x\" + MAX_DIMENSION);\n      this.Avatar = new View(this, MAX_DIMENSION, avatarView);\n      this.Avatar.addImageLayer(img, { type: \"original\" });\n\n      // Setting the height of the form to the desired auto height\n      this.element.style.height = \"auto\";\n    } catch (error) {\n      if (inputUrl) {\n        const error = game.i18n.format(\"vtta-tokenizer.notification.failedInput\", { url });\n        ui.notifications.error(error);\n        if (inputUrl !== this.tokenOptions.avatarFilename) {\n          await this._initAvatar(this.tokenOptions.avatarFilename);\n        } else {\n          await this._initAvatar();\n        }\n      } else {\n        ui.notifications.error(game.i18n.localize(\"vtta-tokenizer.notification.failedFallback\"));\n      }\n    }\n\n    this.element.querySelectorAll(\"#avatar-options :is(input, select, textarea, button)\").forEach((el) => {\n      el.disabled = !this.modifyAvatar;\n    });\n    this.element.querySelectorAll(\"#tokenizer-avatar :is(input, select, textarea, button)\").forEach((el) => {\n      el.disabled = !this.modifyAvatar;\n    });\n    this.element.querySelectorAll(\"#token-options :is(input, select, textarea, button)\").forEach((el) => {\n      el.disabled = !this.modifyToken;\n    });\n    this.element.querySelectorAll(\"#tokenizer-token :is(input, select, textarea, button)\").forEach((el) => {\n      el.disabled = !this.modifyToken;\n    });\n  }\n\n  _onRender() {\n    this.loadImages();\n  }\n\n  static async filePickerThumbs(event, target) {\n    event.preventDefault();\n    switch (target.dataset.type) {\n      case \"mask\": {\n        const picker = new ImageBrowser(this.masks, { type: \"image\", callback: this.handleMaskSelection.bind(this) });\n        picker.render({ force: true });\n        break;\n      }\n      case \"frame\": {\n        const picker = new ImageBrowser(this.frames, { type: \"image\", callback: this.handleFrameSelection.bind(this) });\n        picker.render({ force: true });\n        break;\n      }\n      // no default\n    }\n  }\n\n  static async invisibleButton(event) {\n    event.preventDefault();\n  }\n\n  static async boxButton(event, target) {\n    event.preventDefault();\n    const targetName = target.dataset.target;\n    const isAvatar = targetName === \"avatar\";\n    const view = isAvatar ? this.Avatar : this.Token;\n\n    switch (target.dataset.type) {\n      case \"modify-toggle\": {\n        const button = document.getElementById(`modify-${targetName}`);\n        const fas = document.getElementById(`modify-${targetName}-fas`);\n        const newState = isAvatar\n          ? !this.modifyAvatar\n          : !this.modifyToken;\n\n        fas.classList.toggle(\"fa-regular\");\n        fas.classList.toggle(\"fas\");\n        fas.classList.toggle(\"fa-square\");\n        fas.classList.toggle(\"fa-square-check\");\n\n        this.element.querySelectorAll(`#${targetName}-options :is(input, select, textarea, button)`).forEach((el) => {\n          el.disabled = !newState;\n        });\n        this.element.querySelectorAll(`#tokenizer-${targetName} :is(input, select, textarea, button)`).forEach((el) => {\n          el.disabled = !newState;\n        });\n\n        if (isAvatar) {\n          this.modifyAvatar = newState;\n        } else {\n          this.modifyToken = newState;\n        }\n\n        button.classList.toggle('deselected');\n        fas.classList.toggle('deselected');\n        break;\n      }\n      case \"paste-toggle\": {\n        const avatarButton = document.getElementById(`paste-avatar`);\n        const avatarFas = document.getElementById(`paste-avatar-fas`);\n        const tokenButton = document.getElementById(`paste-token`);\n        const tokenFas = document.getElementById(`paste-token-fas`);\n        game.settings.set(\"vtta-tokenizer\", \"paste-target\", targetName);\n\n        avatarButton.classList.toggle('deselected');\n        avatarFas.classList.toggle(\"fa-circle\");\n        avatarFas.classList.toggle(\"fa-circle-dot\");\n        tokenButton.classList.toggle('deselected');\n        tokenFas.classList.toggle(\"fa-circle\");\n        tokenFas.classList.toggle(\"fa-circle-dot\");\n        break;\n      }\n      case \"quick-preset\": {\n        // pop up selection box\n        const button = document.getElementById(`quick-${targetName}`);\n        const menu = document.getElementById(`quick-${targetName}-menu`);\n        menu.classList.toggle(\"show\");\n        this.lastControlButtonClicked = button;\n        this.activeLayerSelectorElement = menu.classList.contains(\"show\")\n          ? menu\n          : null;\n        break;\n      }\n      case \"quick-token-dynamic\": {\n        this.closeQuickLayerSelector(targetName);\n        const frameIds = view.layers.filter((l) => [\"mask\", \"frame\"].includes(l.type)).map((l) => l.id);\n        for (const id of frameIds) {\n          view.removeImageLayer(id);\n        }\n        await this._setTokenMask(CONSTANTS.DEFAULT_MASK, true);\n        this.tokenOptions.forceDynamicRing = true;\n        break;\n      }\n      case \"quick-avatar-lineart\":\n      case \"quick-token-lineart\": {\n        this.closeQuickLayerSelector(targetName);\n        const removalTypes = isAvatar ? [\"mask\", \"frame\"] : [\"mask\", \"frame\", \"original\"];\n        const frameIds = view.layers.filter((l) => removalTypes.includes(l.type)).map((l) => l.id);\n        for (const id of frameIds) {\n          view.removeImageLayer(id);\n        }\n        if (!isAvatar) {\n          const img = await this.Avatar.get(\"img\");\n          view.addImageLayer(img, {\n            activate: true,\n            type: \"image\",\n            position: { x: this.defaultOffset, y: this.defaultOffset },\n          });\n          await this._setTokenFrame(CONSTANTS.SHADOWDARK_FRAME, true);\n        }\n        view.layers.filter((l) => [\"image\", \"original\"].includes(l.type)).forEach((l) => {\n          l.contrast = 40;\n          l.brightness = -30;\n          l.lineArtBlurSize = 25;\n          l.filters.push(l.lineArtEffect.bind(l));\n        });\n        view.refreshControls();\n        view.redraw(true);\n        break;\n      }\n      case \"quick-avatar-reset\":\n      case \"quick-token-reset\": {\n        this.closeQuickLayerSelector(targetName);\n        view.removeAllLayers();\n        if (isAvatar) {\n          await this._initAvatar();\n        } else {\n          this._loadTokenImageToTokenView();\n        }\n        break;\n      }\n      default:\n        logger.debug(\"Unhandled box-button click:\", {\n          event,\n          target,\n          type: target.dataset?.type,\n        });\n    }\n  }\n\n  static async menuButton(event, target) {\n    event.preventDefault();\n    const view = target.dataset.target === \"avatar\" ? this.Avatar : this.Token;\n\n    switch (target.dataset.type) {\n      case \"upload\": {\n        const img = await Utils.upload();\n        view.addImageLayer(img, { type: \"image\" });\n        break;\n      }\n      case \"download-token\": {\n        const filename = this.tokenFileName;\n        const blob = await this.Token.get(\"blob\");\n        const file = new File([blob], filename, { type: blob.type });\n        let a = document.createElement(\"a\");\n        a.href = URL.createObjectURL(file);\n        a.download = filename;\n        a.click();\n        break;\n      }\n      case \"download\": {\n        const result = await foundry.applications.api.DialogV2.prompt({\n          window: { title: \"Download from the internet\" },\n          content: `\n            <p>${game.i18n.localize(\"vtta-tokenizer.download.url\")}.</p>\n            <div class=\"form-group\">\n              <label>URL</label>\n              <input id=\"tokenizerurl\" type=\"text\" name=\"tokenizerurl\" placeholder=\"https://\" data-dtype=\"String\">\n            </div>`,\n          ok: {\n            label: game.i18n.localize(\"vtta-tokenizer.label.OK\"),\n            callback: (event, button) => {\n              return button.form.elements.tokenizerurl.value;\n            },\n          },\n          rejectClose: false,\n        });\n        if (result) {\n          try {\n            const img = await Utils.download(result);\n            view.addImageLayer(img, { type: \"image\" });\n          } catch (error) {\n            logger.error(\"Error fetching image\", error);\n            ui.notifications.error(error);\n          }\n        }\n        break;\n      }\n      case \"token\": {\n        this.Token.get(\"img\").then((img) => view.addImageLayer(img, { type: \"image\" }));\n        break;\n      }\n      case \"avatar\": {\n        this.Avatar.get(\"img\").then((img) => view.addImageLayer(img, { activate: true, type: \"image\" }));\n        break;\n      }\n      case \"color\": {\n        const defaultColor = game.settings.get(CONSTANTS.MODULE_ID, \"default-color\");\n        view.addColorLayer({ color: defaultColor });\n        break;\n      }\n      case \"tokenVariants\": {\n        game.modules.get('token-variants').api.showArtSelect(this.tokenOptions.name, {\n          callback: (imgSrc) => Utils.download(imgSrc).then((img) => view.addImageLayer(img, { type: \"image\" })),\n          searchType: target.dataset.target === \"avatar\" ? \"Portrait\" : \"Token\",\n        });\n        break;\n      }\n      case \"locations\": {\n        const locations = new TokenizerSaveLocations(this);\n        locations.render({ force: true });\n        break;\n      }\n      // no default\n    }\n  }\n\n  async _addBaseTokenLayers() {\n    if (game.settings.get(CONSTANTS.MODULE_ID, \"default-color-layer\")) {\n      this.Token.addColorLayer({ color: this.defaultColor });\n    }\n    if (game.settings.get(CONSTANTS.MODULE_ID, \"enable-default-texture-layer\")) {\n      await this._addTokenTexture();\n    }\n  }\n\n  async _addHigherTokenLayers() {\n    if (this.addFrame) {\n      logger.debug(\"Loading default token frame\");\n      await this._setTokenFrame();\n    }\n    if (this.addMask) {\n      logger.debug(\"Loading default token mask\");\n      await this._setTokenMask();\n    }\n  }\n\n  async _initWildCardToken() {\n    await this._addBaseTokenLayers();\n    await this._addHigherTokenLayers();\n  }\n\n  async _initToken(src) {\n    let imgSrc = src ?? CONST.DEFAULT_TOKEN;\n    try {\n      logger.debug(\"Initializing Token, trying to download\", imgSrc);\n      const img = await Utils.download(imgSrc);\n      logger.debug(\"Got image\", img);\n\n      await this._addBaseTokenLayers();\n      // if we add a frame by default offset the token image\n      const options = this.addFrame || this.addMask\n        ? this.tokenOffset\n        : {};\n      this.Token.addImageLayer(img, { ...options, type: \"original\" });\n      await this._addHigherTokenLayers();\n    } catch (error) {\n      if (!src || src === CONST.DEFAULT_TOKEN) {\n        logger.error(`Failed to load fallback token: \"${imgSrc}\"`);\n      } else {\n        const errorMessage = game.i18n.format(\"vtta-tokenizer.notification.failedLoad\", { imgSrc, default: CONST.DEFAULT_TOKEN });\n        ui.notifications.error(errorMessage);\n        logger.error(\"Failed to init image\", errorMessage);\n        await this._initToken();\n      }\n    }\n  }\n\n  #getNPCFrame() {\n    const tintFrame = game.settings.get(CONSTANTS.MODULE_ID, \"frame-tint\");\n    let npcFrame;\n    if (tintFrame) {\n      npcFrame = game.settings.get(CONSTANTS.MODULE_ID, \"default-frame-tint\");\n    } else {\n      switch (parseInt(this.tokenOptions.disposition)) {\n        case 0:\n        case 1: {\n          npcFrame = game.settings.get(CONSTANTS.MODULE_ID, \"default-frame-neutral\");\n          break;\n        }\n\n        case -1:\n        default: {\n          npcFrame = game.settings.get(CONSTANTS.MODULE_ID, \"default-frame-npc\");\n          break;\n        }\n      }\n    }\n    return npcFrame;\n  }\n\n  #getTintColor() {\n    if (this.tokenType === \"pc\") {\n      return game.settings.get(CONSTANTS.MODULE_ID, \"default-frame-tint-pc\");\n    }\n    switch (parseInt(this.tokenOptions.disposition)) {\n      case 0: {\n        return game.settings.get(CONSTANTS.MODULE_ID, \"default-frame-tint-neutral\");\n      }\n      case 1: {\n        return game.settings.get(CONSTANTS.MODULE_ID, \"default-frame-tint-friendly\");\n      }\n      case -1:\n      default: {\n        return game.settings.get(CONSTANTS.MODULE_ID, \"default-frame-tint-hostile\");\n      }\n    }\n  }\n\n  async _setTokenFrame(fileName, fullPath = false) {\n    // load the default frame, if there is one set\n    const tintFrame = game.settings.get(CONSTANTS.MODULE_ID, \"frame-tint\");\n    const npcFrame = this.#getNPCFrame();\n\n    const frameTypePath = this.tokenType === \"pc\"\n      ? tintFrame\n        ? game.settings.get(CONSTANTS.MODULE_ID, \"default-frame-tint\")\n        : game.settings.get(CONSTANTS.MODULE_ID, \"default-frame-pc\")\n      : npcFrame;\n    const isDefault = fileName != npcFrame.replace(/^\\/|\\/$/g, \"\");\n\n    const framePath = fileName && !isDefault\n      ? `${game.settings.get(CONSTANTS.MODULE_ID, \"frame-directory\")}/${fileName}`\n      : fileName && isDefault\n        ? fileName.replace(/^\\/|\\/$/g, \"\")\n        : frameTypePath.replace(/^\\/|\\/$/g, \"\");\n\n    const tintColor = this.#getTintColor();\n\n    if (framePath && framePath.trim() !== \"\") {\n      const options = DirectoryPicker.parse(fullPath ? fileName : framePath);\n      try {\n        const img = await Utils.download(options.current);\n        this.Token.addImageLayer(img, { masked: true, onTop: true, tintColor, tintLayer: tintFrame && !fileName, type: \"frame\" });\n      } catch (error) {\n        const errorMessage = game.i18n.format(\"vtta-tokenizer.notification.failedLoadFrame\", { frame: options.current });\n        ui.notifications.error(errorMessage);\n      }\n    }\n  }\n\n  async _setTokenMask(fileName, fullPath = false) {\n    const defaultMaskPath = game.settings.get(CONSTANTS.MODULE_ID, \"default-mask-layer\");\n    const isDefault = fileName != defaultMaskPath.replace(/^\\/|\\/$/g, \"\");\n\n    const maskPath = fileName && !isDefault\n      ? `${game.settings.get(CONSTANTS.MODULE_ID, \"masks-directory\")}/${fileName}`\n      : fileName && isDefault\n        ? fileName.replace(/^\\/|\\/$/g, \"\")\n        : defaultMaskPath.replace(/^\\/|\\/$/g, \"\");\n\n    if (maskPath && maskPath.trim() !== \"\") {\n      const options = DirectoryPicker.parse(fullPath ? fileName : maskPath);\n      try {\n        const img = await Utils.download(options.current);\n        this.Token.addImageLayer(img, { masked: true, onTop: true, maskFromImage: true, visible: false, type: \"mask\" });\n      } catch (error) {\n        const errorMessage = game.i18n.format(\"vtta-tokenizer.notification.failedLoadMask\", { mask: options.current });\n        ui.notifications.error(errorMessage);\n      }\n    }\n  }\n\n  async _addTokenTexture(fileName, fullPath = false) {\n    // load the default frame, if there is one set\n    const tintLayerColour = game.settings.get(CONSTANTS.MODULE_ID, \"default-texture-layer-tint\");\n    const tintLayerPath = game.settings.get(CONSTANTS.MODULE_ID, \"default-texture-layer\");\n    const tintColor = tintLayerColour.trim() !== \"\" ? tintLayerColour : undefined;\n\n    if (tintLayerPath && tintLayerPath.trim() !== \"\") {\n      const options = DirectoryPicker.parse(fullPath ? fileName : tintLayerPath.replace(/^\\/|\\/$/g, \"\"));\n      try {\n        const img = await Utils.download(options.current);\n        this.Token.addImageLayer(img, { masked: true, onTop: true, tintColor, tintLayer: tintLayerPath && tintColor, type: \"texture\" });\n      } catch (error) {\n        const errorMessage = game.i18n.format(\"vtta-tokenizer.notification.failedLoadTexture\", { texture: options.current });\n        ui.notifications.error(errorMessage);\n      }\n    }\n  }\n\n  pasteImage(event) {\n    const pasteTarget = game.settings.get(CONSTANTS.MODULE_ID, \"paste-target\");\n    const view = pasteTarget === \"token\" ? this.Token : this.Avatar;\n    Utils.extractImage(event, view);\n  }\n\n  _loadTokenImageToTokenView() {\n    if (this.tokenOptions.isWildCard) {\n      this._initWildCardToken();\n    } else {\n      this._initToken(this.tokenOptions.tokenFilename);\n    }\n  }\n\n  loadImages() {\n    let tokenView = document.querySelector(\".token > .view\");\n\n    // get the target filename for the avatar\n    this._getFilename(\"Avatar\", this.nameSuffix).then((targetFilename) => {\n      const avatarInput = this.element.querySelector('input[name=\"targetAvatarFilename\"]');\n      if (avatarInput) avatarInput.value = targetFilename;\n      this.avatarFileName = targetFilename;\n    });\n    // get the target filename for the token\n    this._getFilename(\"Token\", this.nameSuffix).then((targetFilename) => {\n      const filenameSpan = this.element.querySelector('span[name=\"targetFilename\"]');\n      if (filenameSpan) filenameSpan.textContent = targetFilename;\n      const tokenInput = this.element.querySelector('input[name=\"targetTokenFilename\"]');\n      if (tokenInput) tokenInput.value = targetFilename;\n      this.tokenFileName = targetFilename;\n    });\n\n    if (this.tokenOptions.isWildCard) {\n      const header = document.getElementById(\"tokenizer-token-header\");\n      header.innerText = `${game.i18n.localize(\"vtta-tokenizer.label.token\")} (${game.i18n.localize(\"vtta-tokenizer.label.Wildcard\")})`;\n      this.Token = new View(this, game.settings.get(CONSTANTS.MODULE_ID, \"token-size\"), tokenView);\n      this._loadTokenImageToTokenView();\n    } else {\n      this.Token = new View(this, game.settings.get(CONSTANTS.MODULE_ID, \"token-size\"), tokenView);\n      this._loadTokenImageToTokenView();\n    }\n\n    this._initAvatar(this.tokenOptions.avatarFilename);\n  }\n\n}\n\nHooks.on(\"renderTokenizer\", (app) => {\n  window.addEventListener(\"paste\", async (e) => {\n    game.canvas.layers.forEach((layer) => {\n      layer._copy = [];\n    });\n    e.stopPropagation();\n    app.pasteImage(e);\n  });\n  window.addEventListener(\"drop\", async (e) => {\n    e.stopPropagation();\n    app.pasteImage(e);\n  });\n  app.element.addEventListener(\"mousedown\", async (e) => {\n    // this handles clearing if the selector pop ups when a non popup is clicked\n    if (!app.activeLayerSelectorElement) return;\n    if (!app.activeLayerSelectorElement.contains(e.target)\n     && !app.lastControlButtonClicked.contains(e.target)\n    ) {\n      e.preventDefault();\n      app.activeLayerSelectorElement.classList.remove(\"show\");\n      app.activeLayerSelectorElement = null;\n      app.lastControlButtonClicked = null;\n    }\n  }, false);\n});\n","import { autoToken } from \"../hooks.js\";\nimport logger from \"../libs/logger.js\";\n\nconst { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;\n\nexport default class AutoTokenize extends HandlebarsApplicationMixin(ApplicationV2) {\n\n  constructor(pack) {\n    super();\n    this.pack = pack;\n    this.packName = pack.metadata.label;\n    this.defaultFrame = game.settings.get(\"vtta-tokenizer\", \"default-frame-npc\");\n  }\n\n  static PARTS = {\n    form: {\n      template: \"modules/vtta-tokenizer/templates/auto.hbs\",\n    },\n  };\n\n  static DEFAULT_OPTIONS = {\n    id: \"auto-tokenize\",\n    classes: [\"tokenizer\"],\n    actions: {\n      startTokenize: AutoTokenize.startTokenize,\n    },\n    position: {\n      width: 350,\n    },\n    window: {\n      title: \"Auto Tokenize\",\n    },\n  };\n\n  // eslint-disable-next-line class-methods-use-this\n  async _prepareContext() {\n    const data = {\n      packName: this.packName,\n      length: this.pack.index.size,\n    };\n    return {\n      data,\n      cssClass: \"tokenizer-window\",\n    };\n  }\n\n  static _renderCompleteDialog(title, content) {\n    foundry.applications.api.DialogV2.prompt({\n      window: {\n        title,\n        classes: [\"dialog\", \"auto-complete\"],\n      },\n      content: `<h1>${content.title}</h1><h3>${content.description}</h3>`,\n      ok: {\n        label: \"OK\",\n      },\n    });\n  }\n\n  async tokenizePack() {\n    let currentCount = 1;\n    const tokenIndex = this.pack.index.filter((i) => i.name !== \"#[CF_tempEntity]\");\n    const totalCount = tokenIndex.length;\n    for (const i of tokenIndex) {\n      this._updateProgress(totalCount, currentCount, \"token\", i.name);\n      logger.debug(`Tokenizing ${i.name}`);\n      // eslint-disable-next-line no-await-in-loop\n      const actor = await this.pack.getDocument(i._id);\n      // eslint-disable-next-line no-await-in-loop\n      await autoToken(actor, { nameSuffix: `.${this.pack.metadata.name.toLowerCase()}` });\n      currentCount++;\n    }\n  }\n\n  static async startTokenize() {\n    try {\n      const progressEl = this.element.querySelector(\".import-progress\");\n      const overlayEl = this.element.querySelector(\".tokenizer-overlay\");\n      progressEl.classList.toggle(\"import-hidden\");\n      overlayEl.classList.toggle(\"import-invalid\");\n\n      await this.tokenizePack();\n\n      overlayEl.classList.toggle(\"import-invalid\");\n\n      AutoTokenize._renderCompleteDialog(\n        game.i18n.format(\"vtta-tokenizer.auto.success\", { packName: this.packName }),\n        {\n          title: this.packName,\n          description: game.i18n.format(\"vtta-tokenizer.auto.success-content\", { size: this.pack.index.size }),\n        },\n      );\n\n      this.close();\n    } catch (err) {\n      const overlayEl = this.element.querySelector(\".tokenizer-overlay\");\n      overlayEl.classList.toggle(\"import-invalid\");\n      const errorText = game.i18n.format(\"vtta-tokenizer.auto.error\", { packName: this.packName });\n      ui.notifications.error(errorText);\n      logger.error(errorText, err);\n      this.close();\n    }\n  }\n\n  _updateProgress(total, count, type, note = \"\") {\n    const localizedType = `vtta-tokenizer.label.${type}`;\n    const bar = this.element.querySelector(\".import-progress-bar\");\n    bar.style.width = `${Math.trunc((count / total) * 100)}%`;\n    bar.innerHTML = `<span>${game.i18n.localize(\"vtta-tokenizer.label.Working\")} (${game.i18n.localize(localizedType)})... ${note}</span>`;\n  }\n\n  _progressNote(note) {\n    const bar = this.element.querySelector(\".import-progress-bar\");\n    bar.innerHTML = `<span>${game.i18n.localize(\"vtta-tokenizer.label.Working\")} (${note})...</span>`;\n  }\n}\n","import DirectoryPicker from \"./DirectoryPicker.js\";\n\n/**\n * Game Settings: ImagePicker\n */\n\nconst FPClass = foundry.applications.apps.FilePicker.implementation;\n\nclass ImagePicker extends FPClass {\n  constructor(options = {}) {\n    super(options);\n  }\n\n  _onSubmit(event) {\n    event.preventDefault();\n    const path = event.target.file.value;\n    const activeSource = this.activeSource;\n    const bucket = event.target.bucket ? event.target.bucket.value : null;\n    this.field.value = ImagePicker.format({\n      activeSource,\n      bucket,\n      path,\n    });\n    this.close();\n  }\n\n  static async uploadToPath(path, file) {\n    const options = DirectoryPicker.parse(path);\n    return FPClass.upload(options.activeSource, options.current, file, { bucket: options.bucket });\n  }\n\n  // returns the type \"Img\" for rendering the SettingsConfig\n  static Img(val) {\n    return val === null ? '' : String(val);\n  }\n\n  // formats the data into a string for saving it as a GameSetting\n  static format(value) {\n    return value.bucket !== null\n      ? `[${value.activeSource}:${value.bucket}] ${value.path}`\n      : `[${value.activeSource}] ${value.path}`;\n  }\n\n  // parses the string back to something the FilePicker can understand as an option\n  static parse(inStr) {\n    const str = inStr ?? '';\n    let matches = str.match(/\\[(.+)\\]\\s*(.+)?/u);\n    if (matches) {\n      let [, source, current = ''] = matches;\n      current = current.trim();\n      const [s3, bucket] = source.split(\":\");\n      if (bucket !== undefined) {\n        return {\n          activeSource: s3,\n          bucket: bucket,\n          current: current,\n        };\n      } else {\n        return {\n          activeSource: s3,\n          bucket: null,\n          current: current,\n        };\n      }\n    }\n    // failsave, try it at least\n    return {\n      activeSource: \"data\",\n      bucket: null,\n      current: str,\n    };\n  }\n\n  // Adds a FilePicker-Simulator-Button next to the input fields\n  static processHtml(html) {\n    const root = html instanceof HTMLElement ? html : html[0] ?? html;\n    const inputs = root.querySelectorAll(`input[data-dtype=\"Img\"]`);\n    inputs.forEach((element) => {\n      if (!element.nextElementSibling) {\n        let picker = new ImagePicker({\n          field: element,\n          ...ImagePicker.parse(element.value),\n        });\n        let pickerButton = document.createElement(\"button\");\n        pickerButton.type = \"button\";\n        pickerButton.className = \"file-picker\";\n        pickerButton.title = \"Pick image\";\n        pickerButton.innerHTML = '<i class=\"fas fa-file-import fa-fw\"></i>';\n        pickerButton.addEventListener(\"click\", () => {\n          picker.render({ force: true });\n        });\n        element.parentElement.appendChild(pickerButton);\n      }\n    });\n  }\n\n\n  /** @override */\n  _onRender() {\n    const footerButton = this.element.querySelector(\"footer button\");\n    if (footerButton) {\n      footerButton.textContent = \"Select Image\";\n    }\n  }\n}\n\nHooks.on(\"renderSettingsConfig\", (app, html) => {\n  const root = html instanceof HTMLElement ? html : html[0] ?? html;\n  ImagePicker.processHtml(root);\n});\n\nexport default ImagePicker;\n","import ImagePicker from \"./libs/ImagePicker.js\";\nimport logger from \"./libs/logger.js\";\nimport CONSTANTS from \"./constants.js\";\n\nconst { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;\n\nclass ResetCustomFrames extends HandlebarsApplicationMixin(ApplicationV2) {\n\n  static PARTS = {\n    form: {\n      template: `modules/${CONSTANTS.MODULE_ID}/templates/cleanup.hbs`,\n    },\n  };\n\n  static DEFAULT_OPTIONS = {\n    id: \"cleanup-custom-frames\",\n    tag: \"form\",\n    form: {\n      handler: ResetCustomFrames.formHandler,\n      submitOnChange: false,\n      closeOnSubmit: true,\n    },\n    window: {\n      title: \"Reset Custom Frames\",\n    },\n  };\n\n  // eslint-disable-next-line class-methods-use-this\n  async _prepareContext() {\n    return {\n      labels: {\n        name: game.i18n.localize(`${CONSTANTS.MODULE_ID}.reset-custom-frames.name`),\n        list: game.i18n.localize(`${CONSTANTS.MODULE_ID}.reset-custom-frames.list`),\n      },\n    };\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  static async formHandler() {\n    game.settings.set(CONSTANTS.MODULE_ID, \"custom-frames\", []);\n  }\n}\n\nclass ResetCustomMasks extends HandlebarsApplicationMixin(ApplicationV2) {\n\n  static PARTS = {\n    form: {\n      template: `modules/${CONSTANTS.MODULE_ID}/templates/cleanup.hbs`,\n    },\n  };\n\n  static DEFAULT_OPTIONS = {\n    id: \"cleanup-custom-masks\",\n    tag: \"form\",\n    form: {\n      handler: ResetCustomMasks.formHandler,\n      submitOnChange: false,\n      closeOnSubmit: true,\n    },\n    window: {\n      title: \"Reset Custom Masks\",\n    },\n  };\n\n  // eslint-disable-next-line class-methods-use-this\n  async _prepareContext() {\n    return {\n      labels: {\n        name: game.i18n.localize(`${CONSTANTS.MODULE_ID}.reset-custom-masks.name`),\n        list: game.i18n.localize(`${CONSTANTS.MODULE_ID}.reset-custom-masks.list`),\n      },\n    };\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  static async formHandler() {\n    game.settings.set(CONSTANTS.MODULE_ID, \"custom-masks\", []);\n  }\n}\n\nclass QuickSettings extends HandlebarsApplicationMixin(ApplicationV2) {\n\n  static PARTS = {\n    form: {\n      template: `modules/${CONSTANTS.MODULE_ID}/templates/quick-settings.hbs`,\n    },\n  };\n\n  static DEFAULT_OPTIONS = {\n    id: \"quick-settings\",\n    actions: {\n      applyPreset: QuickSettings.applyPreset,\n    },\n    window: {\n      title: `${CONSTANTS.MODULE_ID}.quick-settings.label`,\n    },\n  };\n\n  // eslint-disable-next-line class-methods-use-this\n  get title() {\n    return game.i18n.localize(`${CONSTANTS.MODULE_ID}.quick-settings.label`);\n  }\n\n  // eslint-disable-next-line class-methods-use-this\n  async _prepareContext() {\n    return {\n      labels: {\n        description: game.i18n.localize(`${CONSTANTS.MODULE_ID}.quick-settings.description`),\n        \"tokenring-colour\": {\n          label: game.i18n.localize(`${CONSTANTS.MODULE_ID}.quick-settings.tokenring-colour.label`),\n          description: game.i18n.localize(`${CONSTANTS.MODULE_ID}.quick-settings.tokenring-colour.description`),\n        },\n        \"tokenring-texture\": {\n          label: game.i18n.localize(`${CONSTANTS.MODULE_ID}.quick-settings.tokenring-texture.label`),\n          description: game.i18n.localize(`${CONSTANTS.MODULE_ID}.quick-settings.tokenring-texture.description`),\n        },\n        \"tokenring-transparent\": {\n          label: game.i18n.localize(`${CONSTANTS.MODULE_ID}.quick-settings.tokenring-transparent.label`),\n          description: game.i18n.localize(`${CONSTANTS.MODULE_ID}.quick-settings.tokenring-transparent.description`),\n        },\n        dynamicring: {\n          label: game.i18n.localize(`${CONSTANTS.MODULE_ID}.quick-settings.dynamicring.label`),\n          description: game.i18n.localize(`${CONSTANTS.MODULE_ID}.quick-settings.dynamicring.description`),\n        },\n        nothing: {\n          label: game.i18n.localize(`${CONSTANTS.MODULE_ID}.quick-settings.nothing.label`),\n          description: game.i18n.localize(`${CONSTANTS.MODULE_ID}.quick-settings.nothing.description`),\n        },\n      },\n    };\n  }\n\n  static async applyPreset(event, target) {\n    const preset = target.dataset.preset;\n    switch (preset) {\n      case \"tokenring-colour\": {\n        await game.settings.set(CONSTANTS.MODULE_ID, \"add-frame-default\", true);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"default-color-layer\", true);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"add-mask-default\", false);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"enable-default-texture-layer\", false);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"default-token-offset\", game.settings.settings.get(`${CONSTANTS.MODULE_ID}.default-token-offset`).default);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"token-size\", game.settings.settings.get(`${CONSTANTS.MODULE_ID}.token-size`).default);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"auto-apply-dynamic-token-ring\", false);\n        break;\n      }\n      case \"tokenring-texture\": {\n        await game.settings.set(CONSTANTS.MODULE_ID, \"add-frame-default\", true);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"default-color-layer\", false);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"add-mask-default\", false);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"enable-default-texture-layer\", true);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"default-token-offset\", game.settings.settings.get(`${CONSTANTS.MODULE_ID}.default-token-offset`).default);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"token-size\", game.settings.settings.get(`${CONSTANTS.MODULE_ID}.token-size`).default);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"auto-apply-dynamic-token-ring\", false);\n        break;\n      }\n      case \"tokenring-transparent\": {\n        await game.settings.set(CONSTANTS.MODULE_ID, \"add-frame-default\", true);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"default-color-layer\", false);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"add-mask-default\", false);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"enable-default-texture-layer\", false);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"default-token-offset\", game.settings.settings.get(`${CONSTANTS.MODULE_ID}.default-token-offset`).default);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"token-size\", game.settings.settings.get(`${CONSTANTS.MODULE_ID}.token-size`).default);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"auto-apply-dynamic-token-ring\", false);\n        break;\n      }\n      case \"dynamicring\": {\n        await game.settings.set(CONSTANTS.MODULE_ID, \"add-frame-default\", false);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"default-color-layer\", false);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"add-mask-default\", true);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"enable-default-texture-layer\", false);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"default-token-offset\", -65);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"token-size\", game.settings.settings.get(`${CONSTANTS.MODULE_ID}.token-size`).default);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"auto-apply-dynamic-token-ring\", true);\n        break;\n      }\n      case \"nothing\": {\n        await game.settings.set(CONSTANTS.MODULE_ID, \"add-frame-default\", false);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"default-color-layer\", false);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"add-mask-default\", false);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"enable-default-texture-layer\", false);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"frame-tint\", false);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"default-token-offset\", 0);\n        await game.settings.set(CONSTANTS.MODULE_ID, \"auto-apply-dynamic-token-ring\", false);\n        break;\n      }\n      // no default\n    }\n    foundry.utils.debounce(window.location.reload(), 100);\n  }\n}\n\nexport function registerSettings() {\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"custom-frames\", {\n    scope: \"client\",\n    config: false,\n    type: Array,\n    default: [],\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"custom-masks\", {\n    scope: \"client\",\n    config: false,\n    type: Array,\n    default: [],\n  });\n\n  // dialogues \n  game.settings.registerMenu(CONSTANTS.MODULE_ID, \"quick-settings\", {\n    name: `${CONSTANTS.MODULE_ID}.quick-settings.label`,\n    hint: `${CONSTANTS.MODULE_ID}.quick-settings.description`,\n    label: `${CONSTANTS.MODULE_ID}.quick-settings.label`,\n    scope: \"client\",\n    config: true,\n    type: QuickSettings,\n  });\n\n  game.settings.registerMenu(CONSTANTS.MODULE_ID, \"reset-custom-masks\", {\n    name: `${CONSTANTS.MODULE_ID}.reset-custom-masks.name`,\n    hint: `${CONSTANTS.MODULE_ID}.reset-custom-masks.hint`,\n    label: `${CONSTANTS.MODULE_ID}.reset-custom-masks.name`,\n    scope: \"client\",\n    config: true,\n    type: ResetCustomMasks,\n  });\n\n  game.settings.registerMenu(CONSTANTS.MODULE_ID, \"reset-custom-frames\", {\n    name: `${CONSTANTS.MODULE_ID}.reset-custom-frames.name`,\n    hint: `${CONSTANTS.MODULE_ID}.reset-custom-frames.hint`,\n    label: `${CONSTANTS.MODULE_ID}.reset-custom-frames.name`,\n    scope: \"client\",\n    config: true,\n    type: ResetCustomFrames,\n  });\n\n  // results upload\n  game.settings.register(CONSTANTS.MODULE_ID, \"image-upload-directory\", {\n    name: `${CONSTANTS.MODULE_ID}.image-upload-directory.name`,\n    hint: `${CONSTANTS.MODULE_ID}.image-upload-directory.hint`,\n    scope: \"world\",\n    config: true,\n    type: String,\n    filePicker: \"folder\",\n    default: \"[data] tokenizer/pc-images\",\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"npc-image-upload-directory\", {\n    name: `${CONSTANTS.MODULE_ID}.npc-image-upload-directory.name`,\n    hint: `${CONSTANTS.MODULE_ID}.npc-image-upload-directory.hint`,\n    scope: \"world\",\n    config: true,\n    type: String,\n    filePicker: \"folder\",\n    default: \"[data] tokenizer/npc-images\",\n  });\n\n\n  // some common token settings\n  game.settings.register(CONSTANTS.MODULE_ID, \"image-save-type\", {\n    name: `${CONSTANTS.MODULE_ID}.image-save-type.name`,\n    hint: `${CONSTANTS.MODULE_ID}.image-save-type.hint`,\n    scope: \"world\",\n    config: true,\n    default: \"webp\",\n    choices: { webp: \"*.webp\", png: \"*.png\" },\n    type: String,\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"token-size\", {\n    name: `${CONSTANTS.MODULE_ID}.token-size.name`,\n    hint: `${CONSTANTS.MODULE_ID}.token-size.hint`,\n    scope: \"player\",\n    config: true,\n    type: Number,\n    default: 400,\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"portrait-size\", {\n    name: `${CONSTANTS.MODULE_ID}.portrait-size.name`,\n    hint: `${CONSTANTS.MODULE_ID}.portrait-size.hint`,\n    scope: \"player\",\n    config: true,\n    type: Number,\n    default: 1000,\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"reset-scaling\", {\n    name: `${CONSTANTS.MODULE_ID}.reset-scaling.name`,\n    hint: `${CONSTANTS.MODULE_ID}.reset-scaling.hint`,\n    scope: \"player\",\n    config: true,\n    type: Boolean,\n    default: false,\n  });\n\n  // frames\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"add-frame-default\", {\n    name: `${CONSTANTS.MODULE_ID}.add-frame-default.name`,\n    hint: `${CONSTANTS.MODULE_ID}.add-frame-default.hint`,\n    scope: \"world\",\n    config: true,\n    type: Boolean,\n    default: true,\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"default-frame-pc\", {\n    name: `${CONSTANTS.MODULE_ID}.default-frame-pc.name`,\n    hint: `${CONSTANTS.MODULE_ID}.default-frame-pc.hint`,\n    type: ImagePicker.Img,\n    default: `[data] ${CONSTANTS.PATH}img/default-frame-pc.png`,\n    scope: \"world\",\n    config: true,\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"default-frame-npc\", {\n    name: `${CONSTANTS.MODULE_ID}.default-frame-npc.name`,\n    hint: `${CONSTANTS.MODULE_ID}.default-frame-npc.hint`,\n    type: ImagePicker.Img,\n    default: `[data] ${CONSTANTS.PATH}img/default-frame-npc.png`,\n    scope: \"world\",\n    config: true,\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"default-frame-neutral\", {\n    name: `${CONSTANTS.MODULE_ID}.default-frame-neutral.name`,\n    hint: `${CONSTANTS.MODULE_ID}.default-frame-neutral.hint`,\n    type: ImagePicker.Img,\n    default: `[data] ${CONSTANTS.PATH}img/default-frame-npc.png`,\n    scope: \"world\",\n    config: true,\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"frame-directory\", {\n    name: `${CONSTANTS.MODULE_ID}.frame-directory.name`,\n    hint: `${CONSTANTS.MODULE_ID}.frame-directory.hint`,\n    scope: \"world\",\n    config: true,\n    type: String,\n    filePicker: \"folder\",\n    default: \"[data] tokenizer/frames\",\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"frame-tint\", {\n    name: `${CONSTANTS.MODULE_ID}.frame-tint.name`,\n    hint: `${CONSTANTS.MODULE_ID}.frame-tint.hint`,\n    scope: \"world\",\n    config: true,\n    type: Boolean,\n    default: false,\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"default-frame-tint\", {\n    name: `${CONSTANTS.MODULE_ID}.default-frame-tint.name`,\n    hint: `${CONSTANTS.MODULE_ID}.default-frame-tint.hint`,\n    type: ImagePicker.Img,\n    default: `[data] ${CONSTANTS.PATH}img/plain-marble-frame-grey.png`,\n    scope: \"world\",\n    config: true,\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"default-frame-tint-pc\", {\n    name: `${CONSTANTS.MODULE_ID}.default-frame-tint.pc`,\n    scope: \"player\",\n    config: true,\n    type: String,\n    default: \"grey\",\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"default-frame-tint-friendly\", {\n    name: `${CONSTANTS.MODULE_ID}.default-frame-tint.friendly`,\n    scope: \"player\",\n    config: true,\n    type: String,\n    default: \"green\",\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"default-frame-tint-neutral\", {\n    name: `${CONSTANTS.MODULE_ID}.default-frame-tint.neutral`,\n    scope: \"player\",\n    config: true,\n    type: String,\n    default: \"blue\",\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"default-frame-tint-hostile\", {\n    name: `${CONSTANTS.MODULE_ID}.default-frame-tint.hostile`,\n    scope: \"player\",\n    config: true,\n    type: String,\n    default: \"red\",\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"auto-apply-dynamic-token-ring\", {\n    name: `${CONSTANTS.MODULE_ID}.auto-apply-dynamic-token-ring.name`,\n    hint: `${CONSTANTS.MODULE_ID}.auto-apply-dynamic-token-ring.hint`,\n    scope: \"player\",\n    config: true,\n    type: Boolean,\n    default: false,\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"force-disable-dynamic-token-ring\", {\n    name: `${CONSTANTS.MODULE_ID}.force-disable-dynamic-token-ring.name`,\n    hint: `${CONSTANTS.MODULE_ID}.force-disable-dynamic-token-ring.hint`,\n    scope: \"player\",\n    config: true,\n    type: Boolean,\n    default: false,\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"disable-omfg-frames\", {\n    name: `${CONSTANTS.MODULE_ID}.disable-omfg-frames.name`,\n    scope: \"player\",\n    config: true,\n    type: Boolean,\n    default: false,\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"disable-jcolson-frames\", {\n    name: `${CONSTANTS.MODULE_ID}.disable-jcolson-frames.name`,\n    scope: \"player\",\n    config: true,\n    type: Boolean,\n    default: false,\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"disable-thegreatnacho-frames\", {\n    name: `${CONSTANTS.MODULE_ID}.disable-thegreatnacho-frames.name`,\n    scope: \"player\",\n    config: true,\n    type: Boolean,\n    default: false,\n  });\n\n  // colour layer\n  game.settings.register(CONSTANTS.MODULE_ID, \"default-color-layer\", {\n    name: `${CONSTANTS.MODULE_ID}.default-color-layer.name`,\n    scope: \"player\",\n    config: true,\n    type: Boolean,\n    default: true,\n  });\n\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"default-color\", {\n    name: `${CONSTANTS.MODULE_ID}.default-color.name`,\n    hint: `${CONSTANTS.MODULE_ID}.default-color.hint`,\n    scope: \"player\",\n    config: true,\n    type: String,\n    default: \"white\",\n  });\n\n  // Texture Layer\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"enable-default-texture-layer\", {\n    name: `${CONSTANTS.MODULE_ID}.enable-default-texture-layer.name`,\n    hint: `${CONSTANTS.MODULE_ID}.enable-default-texture-layer.hint`,\n    scope: \"world\",\n    config: true,\n    type: Boolean,\n    default: false,\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"default-texture-layer\", {\n    name: `${CONSTANTS.MODULE_ID}.default-texture-layer.name`,\n    scope: \"world\",\n    config: true,\n    type: ImagePicker.Img,\n    default: `[data] ${CONSTANTS.PATH}img/grey-texture.webp`,\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"default-texture-layer-tint\", {\n    name: `${CONSTANTS.MODULE_ID}.default-texture-layer-tint.name`,\n    hint: `${CONSTANTS.MODULE_ID}.default-texture-layer-tint.hint`,\n    scope: \"player\",\n    config: true,\n    type: String,\n    default: \"\",\n  });\n\n  // MASKS\n  game.settings.register(CONSTANTS.MODULE_ID, \"add-mask-default\", {\n    name: `${CONSTANTS.MODULE_ID}.add-mask-default.name`,\n    hint: `${CONSTANTS.MODULE_ID}.add-mask-default.hint`,\n    scope: \"world\",\n    config: true,\n    type: Boolean,\n    default: false,\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"masks-directory\", {\n    name: `${CONSTANTS.MODULE_ID}.masks-directory.name`,\n    hint: `${CONSTANTS.MODULE_ID}.masks-directory.hint`,\n    scope: \"world\",\n    config: true,\n    type: String,\n    filePicker: \"folder\",\n    default: \"[data] tokenizer/masks\",\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"default-mask-layer\", {\n    name: `${CONSTANTS.MODULE_ID}.default-mask-layer.name`,\n    scope: \"world\",\n    config: true,\n    type: ImagePicker.Img,\n    default: `[data] ${CONSTANTS.PATH}img/dynamic-ring-circle-mask.webp`,\n  });\n\n  // MISC\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"title-link\", {\n    name: `${CONSTANTS.MODULE_ID}.title-link.name`,\n    hint: `${CONSTANTS.MODULE_ID}.title-link.hint`,\n    scope: \"world\",\n    config: true,\n    type: Boolean,\n    default: false,\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"disable-player\", {\n    name: `${CONSTANTS.MODULE_ID}.disable-player.name`,\n    hint: `${CONSTANTS.MODULE_ID}.disable-player.hint`,\n    scope: \"world\",\n    config: true,\n    type: Boolean,\n    default: true,\n  });\n\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"disable-avatar-click\", {\n    name: `${CONSTANTS.MODULE_ID}.disable-avatar-click.name`,\n    hint: `${CONSTANTS.MODULE_ID}.disable-avatar-click.hint`,\n    scope: \"world\",\n    config: true,\n    type: Boolean,\n    default: false,\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"disable-avatar-click-user\", {\n    name: `${CONSTANTS.MODULE_ID}.disable-avatar-click-user.name`,\n    hint: `${CONSTANTS.MODULE_ID}.disable-avatar-click-user.hint`,\n    scope: \"player\",\n    config: true,\n    type: String,\n    choices: {\n      global: \"Use global setting\",\n      tokenizer: \"Tokenizer\",\n      default: \"Default File Picker\",\n    },\n    default: \"global\",\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"proxy\", {\n    scope: \"world\",\n    config: false,\n    type: String,\n    default: \"https://images.ddb.mrprimate.co.uk/\",\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"force-proxy\", {\n    scope: \"world\",\n    config: false,\n    type: Boolean,\n    default: false,\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"paste-target\", {\n    scope: \"player\",\n    config: false,\n    type: String,\n    default: \"token\",\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"token-only-toggle\", {\n    name: `${CONSTANTS.MODULE_ID}.token-only-toggle.name`,\n    hint: `${CONSTANTS.MODULE_ID}.token-only-toggle.hint`,\n    scope: \"player\",\n    config: true,\n    type: Boolean,\n    default: true,\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"default-token-offset\", {\n    name: `${CONSTANTS.MODULE_ID}.default-token-offset.name`,\n    hint: `${CONSTANTS.MODULE_ID}.default-token-offset.hint`,\n    scope: \"player\",\n    config: true,\n    default: -35,\n    type: Number,\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"default-algorithm\", {\n    name: `${CONSTANTS.MODULE_ID}.default-algorithm.name`,\n    scope: \"player\",\n    config: true,\n    type: Boolean,\n    default: true,\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"default-crop-image\", {\n    name: `${CONSTANTS.MODULE_ID}.default-crop-image.name`,\n    hint: `${CONSTANTS.MODULE_ID}.default-crop-image.hint`,\n    scope: \"world\",\n    config: true,\n    type: Boolean,\n    default: false,\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"log-level\", {\n    name: `${CONSTANTS.MODULE_ID}.log-level.name`,\n    scope: \"world\",\n    config: true,\n    type: String,\n    choices: {\n      DEBUG: \"DEBUG\",\n      INFO: \"INFO\",\n      WARN: \"WARN\",\n      ERR: \"ERROR \",\n      OFF: \"OFF\",\n    },\n    default: \"INFO\",\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"actor-id-in-name\", {\n    name: `${CONSTANTS.MODULE_ID}.actor-id-in-name.name`,\n    hint: `${CONSTANTS.MODULE_ID}.actor-id-in-name.hint`,\n    scope: \"world\",\n    config: true,\n    type: Boolean,\n    default: false,\n  });\n\n  game.settings.register(CONSTANTS.MODULE_ID, \"check-for-wildcard-asterisk\", {\n    name: `${CONSTANTS.MODULE_ID}.check-for-wildcard-asterisk.name`,\n    hint: `${CONSTANTS.MODULE_ID}.check-for-wildcard-asterisk.hint`,\n    scope: \"world\",\n    config: true,\n    type: Boolean,\n    default: false,\n  });\n\n  logger.debug(\"Init complete\");\n}\n","import Tokenizer from \"./tokenizer/Tokenizer.js\";\nimport DirectoryPicker from \"./libs/DirectoryPicker.js\";\nimport Utils from \"./libs/Utils.js\";\nimport logger from \"./libs/logger.js\";\nimport View from \"./tokenizer/View.js\";\nimport AutoTokenize from \"./tokenizer/AutoTokenize.js\";\nimport CONSTANTS from \"./constants.js\";\nimport { registerSettings } from \"./settings.js\";\n\nexport function init() {\n  registerSettings();\n}\n\nfunction getAvatarKey() {\n  let dataEditField;\n  switch (game.system.id) {\n    case \"yzecoriolis\": {\n      if (foundry.utils.isNewerVersion(\"3.2.0\", game.system.version)) {\n        dataEditField = \"system.keyArt\";\n      } else {\n        dataEditField = \"img\";\n      }\n      break;\n    }\n    default:\n      dataEditField = \"img\";\n  }\n  return dataEditField;\n}\n\nfunction getAvatarPath(actor) {\n  const key = getAvatarKey();\n  return foundry.utils.getProperty(actor, key);\n}\n\n/**\n * Launch the tokenizer\n * Options include\n * name: name to use as part of filename identifier\n * type: pc, npc - defaults to pc\n * avatarFilename: current avatar image - defaults to null/mystery man\n * tokenFilename: current tokenImage - defaults to null/mystery man\n * isWildCard: is wildcard token?\n * any other items needed in callback function, options will be passed to callback, with filenames updated to new references\n * @param {*} options \n * @param {*} callback function to pass return object to \n */\nfunction launchTokenizer(options, callback) {\n  if (!game.user.can(\"FILES_UPLOAD\")) {\n    ui.notifications.warn(game.i18n.localize(`${CONSTANTS.MODULE_ID}.requires-upload-permission`));\n    if (game.settings.get(CONSTANTS.MODULE_ID, \"disable-player\")) return;\n  }\n\n  game.canvas.layers.forEach((layer) => {\n    layer._copy = [];\n  });\n\n  logger.debug(\"Tokenizer options\", options);\n  const tokenizer = new Tokenizer(options, callback);\n  tokenizer.render({ force: true });\n\n}\n\nfunction updateDynamicRingData(updateTokenData, path, { forceRing = false } = {}) {\n  const setRing = game.settings.get(CONSTANTS.MODULE_ID, \"auto-apply-dynamic-token-ring\")\n    || foundry.utils.getProperty(updateTokenData, \"ring.enabled\")\n    || forceRing;\n\n  if (game.settings.get(CONSTANTS.MODULE_ID, \"force-disable-dynamic-token-ring\") && !forceRing) {\n    foundry.utils.setProperty(updateTokenData, \"ring.enabled\", false);\n  } else {\n    foundry.utils.setProperty(updateTokenData, \"ring.enabled\", setRing);\n    if (setRing) {\n      foundry.utils.setProperty(updateTokenData, \"ring.subject.texture\", path); \n    }\n    if (setRing && game.settings.get(CONSTANTS.MODULE_ID, \"reset-scaling\")) {\n      foundry.utils.setProperty(updateTokenData, \"ring.subject.scale\", 1); \n    }\n  }\n\n}\n\n\nfunction adjustScaling(tokenData) {\n  if (game.settings.get(CONSTANTS.MODULE_ID, \"reset-scaling\") && tokenData.texture) {\n    // if the user has set the scaling to be reset, we do that\n    tokenData.texture.scaleX = 1;\n    tokenData.texture.scaleY = 1;\n    tokenData.texture.offsetY = 0;\n    tokenData.texture.offsetY = 0;\n    tokenData.texture.rotation = 0;\n    tokenData.texture.anchorX = 0.5;\n    tokenData.texture.anchorY = 0.5;\n    tokenData.texture.fit = \"contain\";\n  }\n}\n\nasync function updateActor(tokenizerResponse) {\n  logger.debug(\"Updating Actor, tokenizer data\", tokenizerResponse);\n  const dateTag = `${+new Date()}`;\n\n  // updating the avatar filename\n  const update = {};\n  const avatarKey = getAvatarKey();\n  update[avatarKey] = tokenizerResponse.avatarFilename.split(\"?\")[0] + \"?\" + dateTag;\n\n  if (!tokenizerResponse.isWildCard) {\n    // for non-wildcard tokens, we set the token img now\n    const tokenPath = tokenizerResponse.tokenFilename.split(\"?\")[0] + \"?\" + dateTag;\n    foundry.utils.setProperty(update, \"prototypeToken.texture.src\", tokenPath);\n    foundry.utils.setProperty(update, \"prototypeToken.randomImg\", false);\n    updateDynamicRingData(update.prototypeToken, tokenPath, { forceRing: tokenizerResponse.forceDynamicRing });\n    adjustScaling(update.prototypeToken);\n  } \n  // else if (tokenizerResponse.actor.prototypeToken.texture.src.indexOf(\"*\") === -1) {\n  //   // if it is a wildcard and it isn't set like one, we change that\n  //   const actorName = tokenizerResponse.actor.name.replace(/[^\\w.]/gi, \"_\").replace(/__+/g, \"\");\n  //   const options = DirectoryPicker.parse(tokenizerResponse.tokenUploadDirectory);\n\n  //   // set it to a wildcard we can actually use\n  //   const imageFormat = game.settings.get(CONSTANTS.MODULE_ID, \"image-save-type\");\n  //   const message = game.i18n.format(\"vtta-tokenizer.notification.wildcard\", { path: tokenizerResponse.actor.prototypeToken.texture.src });\n  //   ui.notifications.info(message);\n  //   update.token = {\n  //     img: `${options.current}/${actorName}.Token-*.${imageFormat}`,\n  //   };\n  // }\n\n  logger.debug(\"Updating with\", update);\n  await tokenizerResponse.actor.update(update);\n  if (!update.prototypeToken) return;\n  // if there is a scene token, lets update it\n  if (tokenizerResponse.token) {\n    tokenizerResponse.token.update(update.prototypeToken);\n  } else if (canvas.ready) {\n    const tokens = tokenizerResponse.actor.getActiveTokens(true);\n    const tokenUpdates = tokens.map((t) => {\n      const data = foundry.utils.deepClone(update.prototypeToken);\n      data._id = t.id;\n      return data;\n    });\n    await canvas.scene.updateEmbeddedDocuments(\"Token\", tokenUpdates, { diff: false, recursive: false });\n  }\n}\n\nfunction getActorType(actor) {\n  if ([\"character\", \"pc\"].includes(actor.type)) {\n    // forbidden lands support\n    if (foundry.utils.getProperty(actor, \"system.subtype.type\") === \"npc\") {\n      return \"npc\";\n    } else {\n      return \"pc\";\n    }\n  } else {\n    return \"npc\";\n  }\n  \n}\n\nfunction getWildCard(actor) {\n  const isWildCard = actor.prototypeToken.randomImg;\n  if (!isWildCard) return false;\n\n  const asterisk = game.settings.get(CONSTANTS.MODULE_ID, \"check-for-wildcard-asterisk\");\n  if (!asterisk) return isWildCard;\n\n  return actor.prototypeToken.texture.src.includes(\"*\");\n}\n\nfunction tokenizeActor(actor) {\n  const addId = game.settings.get(CONSTANTS.MODULE_ID, \"actor-id-in-name\");\n  const options = {\n    actor: actor,\n    name: actor.name,\n    type: getActorType(actor),\n    disposition: actor.prototypeToken.disposition,\n    avatarFilename: getAvatarPath(actor),\n    tokenFilename: actor.prototypeToken.texture.src,\n    isWildCard: getWildCard(actor),\n    nameSuffix: addId ? `.${actor._id}` : undefined,\n  };\n\n  launchTokenizer(options, updateActor);\n\n}\n\nfunction tokenizeSceneToken(doc) {\n  const options = {\n    actor: doc.actor,\n    token: doc.token,\n    name: doc.token.name,\n    type: getActorType(doc.actor),\n    disposition: doc.token.disposition,\n    avatarFilename: getAvatarPath(doc.actor),\n    tokenFilename: doc.token.texture.src,\n    nameSuffix: `.${doc.token._id}`,\n  };\n\n  launchTokenizer(options, updateActor);\n\n}\n\nfunction tokenizeSceneTokenV2(doc) {\n  const options = {\n    actor: doc,\n    token: doc.token,\n    name: doc.token.name,\n    type: getActorType(doc),\n    disposition: doc.token.disposition,\n    avatarFilename: getAvatarPath(doc),\n    tokenFilename: doc.token.texture.src,\n    nameSuffix: `.${doc.token._id}`,\n  };\n\n  launchTokenizer(options, updateActor);\n\n}\n\n\nfunction tokenizeDoc(doc) {\n  if (doc.token) {\n    tokenizeSceneToken(doc);\n  } else {  \n    tokenizeActor(doc);\n  }\n}\n\nfunction tokenizeDocV2() {\n  // eslint-disable-next-line no-invalid-this\n  const doc = this.document;\n\n  if (doc.token) {\n    // eslint-disable-next-line no-invalid-this\n    tokenizeSceneTokenV2(this.actor);\n  } else {\n    tokenizeActor(doc);\n  }\n}\n\nasync function updateSceneTokenImg(actor) {\n  const updates = await Promise.all(actor.getActiveTokens().map(async (t) => {\n    const newToken = await actor.getTokenDocument();\n    const tokenUpdate = {\n      _id: t.id,\n      \"texture\": newToken.texture,\n      \"ring\": newToken.ring,\n    };\n    return tokenUpdate;\n  }));\n  if (updates.length) canvas.scene.updateEmbeddedDocuments(\"Token\", updates);\n}\n\nexport async function autoToken(actor, options = {}) {\n  const defaultOptions = {\n    actor: actor,\n    name: actor.name,\n    type: getActorType(actor),\n    disposition: actor.token?.disposition ?? actor.prototypeToken.disposition,\n    avatarFilename: getAvatarPath(actor),\n    tokenFilename: actor.token?.texture?.src ?? actor.prototypeToken.texture.src,\n    isWildCard: getWildCard(actor),\n    auto: true,\n    updateActor: true,\n  };\n  const mergedOptions = foundry.utils.mergeObject(defaultOptions, options);\n  logger.debug(`Tokenizer options for ${actor.name}:`, {\n    mergedOptions,\n    actor,\n  });\n  const tokenizer = new Tokenizer(mergedOptions, updateActor);\n\n  // create mock elements to generate images in\n  const tokenizerHtml = `<div class=\"token\" id=\"tokenizer-token-parent\"><h1>${game.i18n.localize(\"vtta-tokenizer.label.token\")}</h1><div class=\"view\" id=\"tokenizer-token\"></div>`;\n  let doc = Utils.htmlToDoc(tokenizerHtml);\n  let tokenView = doc.querySelector(\".token > .view\");\n  \n  // get the target filename for the token\n  const nameSuffix = tokenizer.tokenOptions.nameSuffix ? tokenizer.tokenOptions.nameSuffix : \"\";\n  const targetFilename = await tokenizer._getFilename(\"Token\", nameSuffix);\n  tokenizer.tokenFileName = targetFilename;\n\n  // create a Token View\n  tokenizer.Token = new View(tokenizer, game.settings.get(CONSTANTS.MODULE_ID, \"token-size\"), tokenView);\n  // Add the actor image and frame to the token view\n  await tokenizer._initToken(tokenizer.tokenOptions.tokenFilename);\n  // upload result to foundry\n  const dataResult = await tokenizer.Token.get(\"blob\");\n  await tokenizer.updateToken(dataResult);\n  // update actor\n  if (mergedOptions.updateActor) {\n    await updateActor(tokenizer.tokenOptions);\n  }\n  return tokenizer.tokenOptions.tokenFilename;\n}\n\nfunction fixUploadLocation() {\n  // Set base character upload folder.\n  const characterUploads = game.settings.get(CONSTANTS.MODULE_ID, \"image-upload-directory\");\n  const npcUploads = game.settings.get(CONSTANTS.MODULE_ID, \"npc-image-upload-directory\");\n  const frameUploads = game.settings.get(CONSTANTS.MODULE_ID, \"frame-directory\");\n  const maskUploads = game.settings.get(CONSTANTS.MODULE_ID, \"masks-directory\");\n\n  if (game.user.isGM) {\n    DirectoryPicker.verifyPath(DirectoryPicker.parse(characterUploads));\n    DirectoryPicker.verifyPath(DirectoryPicker.parse(npcUploads));\n    DirectoryPicker.verifyPath(DirectoryPicker.parse(frameUploads));\n    DirectoryPicker.verifyPath(DirectoryPicker.parse(maskUploads));\n  }\n\n  if (characterUploads != \"\" && npcUploads == \"\") game.settings.set(CONSTANTS.MODULE_ID, \"npc-image-upload-directory\", characterUploads);\n\n}\n\n\nfunction getActorSheetHeaderButtons(app, buttons) {\n  if (\n    // don't enable if user can't upload\n    !game.user.can(\"FILES_UPLOAD\")\n    // and the player setting is disabled\n    && game.settings.get(CONSTANTS.MODULE_ID, \"disable-player\")\n  ) {\n    return;\n  }\n\n  const titleLink = game.settings.get(CONSTANTS.MODULE_ID, \"title-link\");\n  if (!titleLink) return;\n  const doc = (app.token) ? app : app.document;\n\n  buttons.unshift({\n    label: \"Tokenizer\",\n    icon: \"far fa-user-circle\",\n    class: CONSTANTS.MODULE_ID,\n    onclick: () => tokenizeDoc(doc),\n  });\n}\n\nfunction getActorSheetHeaderButtonsV2(config, buttons) {\n  if (!(config.document instanceof Actor)) return;\n  if (\n    // don't enable if user can't upload\n    !game.user.can(\"FILES_UPLOAD\")\n    // and the player setting is disabled\n    && game.settings.get(CONSTANTS.MODULE_ID, \"disable-player\")\n  ) {\n    return;\n  }\n\n  config.options.actions[\"tokenizer\"] = tokenizeDocV2;\n  buttons.push({\n    label: \"Tokenizer\",\n    icon: \"far fa-user-circle\",\n    action: \"tokenizer\",\n  });\n}\n\nfunction linkTidySheets() {\n  const api = game.modules.get('tidy5e-sheet')?.api;\n  if (!api) return;\n\n  api.config.actorPortrait.registerMenuCommands([\n    {\n      label: game.i18n.localize(\"vtta-tokenizer.module-name\"),\n      iconClass: \"fas fa-user-circle\",\n      tooltip: game.i18n.localize(\"vtta-tokenizer.label.open\"),\n      enabled: (params) => params.actor.type !== \"vehicle\",\n      execute: (params) => {\n        const token = foundry.utils.getProperty(params, \"context.options.token\");\n        const doc = token\n          ? { actor: params.actor, token }\n          : params.actor;\n        logger.debug(\"Calling Tokenizer via Tidy5e sheet\", { params, token, doc });\n        tokenizeDoc(doc);\n      },\n    },\n  ]);\n}\n\nfunction linkDefaultSheets() {\n  let sheetNames = Object.values(CONFIG.Actor.sheetClasses)\n    .reduce((arr, classes) => {\n      return arr.concat(Object.values(classes).map((c) => c.cls));\n    }, [])\n    .map((cls) => cls.name);\n\n  // register tokenizer on all character (npc and pc) sheets\n  sheetNames.forEach((sheetName) => {\n    Hooks.on(\"render\" + sheetName, (app, html, _data) => {\n      if (!game.user) return;\n      const dataEditField = getAvatarKey();\n      const key = html[0] instanceof HTMLDivElement || html[0] instanceof HTMLFormElement\n        ? html[0]\n        : html;\n\n      const element = key.querySelector(`[data-edit=\"${dataEditField}\"], [data-edit=\"prototypeToken.texture.src\"]`);\n      if (!element) return;\n\n      const doc = (app.token) ? app : app.document;\n      const disableAvatarClickGlobal = game.settings.get(CONSTANTS.MODULE_ID, \"disable-avatar-click\");\n      const disableAvatarClickUser = game.settings.get(CONSTANTS.MODULE_ID, \"disable-avatar-click-user\");\n      const disableAvatarClick = disableAvatarClickUser === \"global\"\n        ? disableAvatarClickGlobal\n        : disableAvatarClickUser === \"default\";\n\n      element.addEventListener('click', (event) => {\n        const launchTokenizer\n          = (!disableAvatarClick && !event.shiftKey) // avatar click not disabled, and not shift key\n          || (disableAvatarClick && event.shiftKey); // avatar click disabled, and shift key\n\n        if (launchTokenizer) {\n          event.preventDefault();\n          event.stopPropagation();\n          event.stopImmediatePropagation();\n          tokenizeDoc(doc);\n        }\n        // Otherwise, let it bubble/propagate normally to existing handlers\n      }, true); // 'true' = capture phase, runs before other handlers\n    });\n  });\n}\n\nfunction linkSheets() {\n  if (\n    // don't enable if user can't upload\n    !game.user.can(\"FILES_UPLOAD\")\n    // and the player setting is disabled\n    && game.settings.get(CONSTANTS.MODULE_ID, \"disable-player\")\n  ) {\n    return;\n  }\n\n  linkTidySheets();\n  linkDefaultSheets();\n}\n\nfunction exposeAPI() {\n  const API = {\n    launch: launchTokenizer,\n    launchTokenizer,\n    tokenizeActor,\n    tokenizeSceneToken,\n    tokenizeDoc,\n    updateSceneTokenImg,\n    autoToken,\n  };\n\n  window.Tokenizer = API;\n  game.modules.get(CONSTANTS.MODULE_ID).api = API;\n}\n\nfunction addCompendiumContextOptions(contextOptions) {\n  if (!game.user.isGM) return;\n  contextOptions.push({\n    name: `${CONSTANTS.MODULE_ID}.compendium.auto-tokenize`,\n    callback: (li) => {\n      const pack = li.dataset?.pack ?? li.getAttribute(\"data-pack\");\n      const compendium = game.packs.get(pack);\n      if (compendium) {\n        const auto = new AutoTokenize(compendium);\n        auto.render({ force: true });\n      }\n    },\n    condition: (li) => {\n      const pack = li.dataset?.pack ?? li.getAttribute(\"data-pack\");\n      const compendium = game.packs.get(pack);\n      if (!compendium) return false;\n      const isActor = compendium.metadata.type === \"Actor\";\n      return isActor;\n    },\n    icon: '<i class=\"fas fa-user-circle\"></i>',\n  });\n}\n\nfunction addUpdateSceneTokensContext(contextOptions) {\n  if (!game.user.isGM) return;\n\n  contextOptions.push({\n    name: `${CONSTANTS.MODULE_ID}.apply-prototype-to-scene`,\n    callback: (li) => {\n      const docId = li.dataset?.entryId\n        ?? li.dataset?.documentId\n        ?? li.dataset?.actorId\n        ?? li.dataset?.entityId\n        ?? li.getAttribute(\"data-entry-id\")\n        ?? li.getAttribute(\"data-document-id\")\n        ?? li.getAttribute(\"data-actor-id\")\n        ?? li.getAttribute(\"data-entity-id\");\n      if (docId) {\n        const doc = game.actors.get(docId);\n        logger.debug(`Updating ${doc.name} scene tokens for:`, doc);\n        updateSceneTokenImg(doc);\n      }\n    },\n    icon: '<i class=\"fas fa-user-circle\"></i>',\n    condition: () => {\n      return game.user.can(\"FILES_UPLOAD\");\n    },\n  });\n}\n\nfunction addTokenizerContentOption(contextOptions) {\n  if (\n    // don't enable if user can't upload\n    !game.user.can(\"FILES_UPLOAD\")\n    // and the player setting is disabled\n    && game.settings.get(CONSTANTS.MODULE_ID, \"disable-player\")\n  ) {\n    return;\n  }\n\n  contextOptions.push({\n    name: \"Tokenizer\",\n    callback: (li) => {\n      const docId = li.dataset?.entryId\n        ?? li.dataset?.documentId\n        ?? li.dataset?.actorId\n        ?? li.dataset?.entityId\n        ?? li.getAttribute(\"data-entry-id\")\n        ?? li.getAttribute(\"data-document-id\")\n        ?? li.getAttribute(\"data-actor-id\")\n        ?? li.getAttribute(\"data-entity-id\");\n      if (docId) {\n        const doc = game.actors.get(docId);\n        logger.debug(`Tokenizing ${doc.name}`);\n        tokenizeActor(doc);\n      }\n    },\n    icon: '<i class=\"fas fa-user-circle\"></i>',\n    condition: () => {\n      return game.user.can(\"FILES_UPLOAD\")\n        || !game.settings.get(CONSTANTS.MODULE_ID, \"disable-player\");\n    },\n  });\n}\n\nexport function ready() {\n  logger.info(\"Ready Hook Called\");\n  fixUploadLocation();\n  linkSheets();\n  exposeAPI();\n}\n\nHooks.on('getActorDirectoryEntryContext', (html, contextOptions) => {\n  addTokenizerContentOption(contextOptions);\n  addUpdateSceneTokensContext(contextOptions);\n});\n\nHooks.on(\"getCompendiumDirectoryEntryContext\", (html, contextOptions) => {\n  addCompendiumContextOptions(contextOptions);\n});\n\nHooks.on('getActorSheetHeaderButtons', getActorSheetHeaderButtons);\nHooks.on(\"getHeaderControlsApplicationV2\", getActorSheetHeaderButtonsV2);\n\n// v13 hooks for context menus\nHooks.once('getActorContextOptions', (html, contextOptions) => {\n  addTokenizerContentOption(contextOptions);\n  addUpdateSceneTokensContext(contextOptions);\n});\n\nHooks.once(\"getCompendiumContextOptions\", (html, contextOptions) => {\n  addCompendiumContextOptions(contextOptions);\n});\n","import { init, ready } from \"./hooks.js\";\n\n// registering the hooks\nHooks.on(\"init\", init);\nHooks.once(\"ready\", ready);\n"],"names":[],"sourceRoot":""}