{"version":3,"file":"product-detail-section.min.js","mappings":"wZAcA,MAAMA,0BAA0BC,YAC9B,6BAAkBC,GAChB,MAAO,CAAC,mBACV,CAyBQC,gBAAiB,EACjBC,aAAc,EAGdC,MAAgBC,WAAa,IAAM,KAAO,KAC1CC,cAAe,EACfC,aAAyB,GAKzBC,YAAc,EACdC,YAAc,EAEdC,mBAAqBC,KAAKC,YAAYC,KAAKF,MAC3CG,oBAAsBH,KAAKI,aAAaF,KAAKF,MAC7CK,6BAA+BL,KAAKM,wBAAwBJ,KAAKF,MACjEO,kBAAoBP,KAAKQ,WAAWN,KAAKF,MACzCS,oBAAsBT,KAAKU,eAAeR,KAAKF,MAC/CW,oBAAsBX,KAAKY,eAAeV,KAAKF,MAC/Ca,oBAAsBb,KAAKc,eAAeZ,KAAKF,MAC/Ce,uBAAyBf,KAAKgB,kBAAkBd,KAAKF,MACrDiB,uBAAyBjB,KAAKkB,kBAAkBhB,KAAKF,MAErDmB,iBAAmBnB,KAAKoB,YAAYlB,KAAKF,MACzCqB,gBAAkBrB,KAAKsB,WAAWpB,KAAKF,MACvCuB,gBAAkBvB,KAAKwB,WAAWtB,KAAKF,MAEvCyB,kBAAoBzB,KAAK0B,aAAaxB,KAAKF,MAC3C2B,iBAAmB3B,KAAK4B,YAAY1B,KAAKF,MACzC6B,iBAAmB7B,KAAK8B,YAAY5B,KAAKF,MAE1C,iBAAA+B,GACL/B,KAAKgC,uBAEL,MAAMC,EAAcjC,KAAKkC,WAAWC,cAAc,UACvC,MAAXF,GAAAA,EAAaG,iBAAiB,QAASpC,KAAKG,qBAE5C,MAAMkC,EAAarC,KAAKkC,WAAWC,cAAc,gBACvC,MAAVE,GAAAA,EAAYD,iBAAiB,QAASpC,KAAKO,mBACjC,MAAV8B,GAAAA,EAAYD,iBAAiB,UAAWpC,KAAKO,mBACnC,MAAV8B,GAAAA,EAAYD,iBAAiB,QAASpC,KAAKO,mBAE3C,MAAM+B,EAAatC,KAAKkC,WAAWC,cAAc,SACvC,MAAVG,GAAAA,EAAYF,iBAAiB,QAASpC,KAAKW,qBACjC,MAAV2B,GAAAA,EAAYF,iBAAiB,UAAWpC,KAAKW,qBACnC,MAAV2B,GAAAA,EAAYF,iBAAiB,QAASpC,KAAKW,qBAE3C,MAAM4B,EAAavC,KAAKkC,WAAWC,cAAc,SACvC,MAAVI,GAAAA,EAAYH,iBAAiB,QAASpC,KAAKa,qBACjC,MAAV0B,GAAAA,EAAYH,iBAAiB,UAAWpC,KAAKa,qBACnC,MAAV0B,GAAAA,EAAYH,iBAAiB,QAASpC,KAAKa,qBAEzBb,KAAKkC,WAAWC,cAAc,UACtCC,iBAAiB,QAASpC,KAAKD,oBAEX,oBAAnByC,iBACTA,eAAeJ,iBAAiB,SAAUpC,KAAKiB,wBAC/CuB,eAAeJ,iBAAiB,SAAUpC,KAAKiB,yBAGjDmB,iBAAiB,YAAapC,KAAKiB,wBAEnCjB,KAAKoC,iBAAiB,2BAA4BpC,KAAKe,wBACvDf,KAAKoC,iBAAiB,yBAA0BpC,KAAKS,qBAGrDT,KAAKyC,eAELzC,KAAK0C,QACP,CAEO,oBAAAC,GACL,MAAMV,EAAcjC,KAAKkC,WAAWC,cAAc,UACvC,MAAXF,GAAAA,EAAaW,oBAAoB,QAAS5C,KAAKG,qBAE/C,MAAMkC,EAAarC,KAAKkC,WAAWC,cAAc,gBACvC,MAAVE,GAAAA,EAAYO,oBAAoB,QAAS5C,KAAKO,mBACpC,MAAV8B,GAAAA,EAAYO,oBAAoB,UAAW5C,KAAKO,mBACtC,MAAV8B,GAAAA,EAAYO,oBAAoB,QAAS5C,KAAKO,mBAE9C,MAAM+B,EAAatC,KAAKkC,WAAWC,cAAc,SACvC,MAAVG,GAAAA,EAAYM,oBAAoB,QAAS5C,KAAKW,qBACpC,MAAV2B,GAAAA,EAAYM,oBAAoB,UAAW5C,KAAKW,qBACtC,MAAV2B,GAAAA,EAAYM,oBAAoB,QAAS5C,KAAKW,qBAE9C,MAAM4B,EAAavC,KAAKkC,WAAWC,cAAc,SACvC,MAAVI,GAAAA,EAAYK,oBAAoB,QAAS5C,KAAKa,qBACpC,MAAV0B,GAAAA,EAAYK,oBAAoB,UAAW5C,KAAKa,qBACtC,MAAV0B,GAAAA,EAAYK,oBAAoB,QAAS5C,KAAKa,qBAEhB,oBAAnB2B,iBACTA,eAAeI,oBAAoB,SAAU5C,KAAKiB,wBAClDuB,eAAeI,oBAAoB,SAAU5C,KAAKiB,yBAGpD2B,oBAAoB,YAAa5C,KAAKiB,wBAEtC,MAAM4B,EAAY7C,KAAKkC,WAAWC,cAAc,UACvC,MAATU,GAAAA,EAAWD,oBAAoB,QAAS5C,KAAKD,oBAI7C6C,oBAAoB,0BAA2B5C,KAAKK,8BAEpDL,KAAK4C,oBAAoB,2BAA4B5C,KAAKe,wBAC1Df,KAAK4C,oBAAoB,yBAA0B5C,KAAKS,oBAC1D,CAEO,wBAAAqC,CAAyBC,EAAcC,EAAkBC,GAC9DjD,KAAKgC,uBAEAhC,KAAKkD,aAAwB,qBAATH,GAA+BC,IAAaC,IAIpD,SAAbA,EACFjD,KAAKmD,SACiB,SAAbH,GACThD,KAAKoD,WAET,CAEQ,oBAAApB,GACDhC,KAAKkC,aACRlC,KAAKqD,aAAa,CAAEC,KAAM,SAC1BtD,KAAKkC,WAAWqB,UAAY,yyMAEhC,CAEQ,MAAAb,GAEN,MAAMc,EAA2BxD,KAAKH,YAAc,EAE/BG,KAAKkC,WAAWC,cAAc,kBACtCsB,YAAcD,EAAyBE,WAEzB1D,KAAKkC,WAAWC,cAAc,iBACtCsB,YAAczD,KAAKF,YAAY4D,WAElD,MAAMC,EAAU3D,KAAKkC,WAAWC,cAAc,YACvC,MAAPwB,GAAAA,EAASC,aAAa,aAAc,CAClC,QACAJ,EAAyBE,WACzB,KACA1D,KAAKF,YAAY4D,YACjBG,KAAK,MAGP,MAAMC,EAAS9D,KAAKkC,WAAW6B,iBAA8B,gBAC7D,GAAI/D,KAAKF,aAAe,EACtB,IAAK,MAAMkE,KAASF,EAClBE,EAAMC,QAAS,OAKjB,IAAK,MAAMD,KAASF,EAClBE,EAAMC,QAAS,CAGrB,CAEQ,iBAAA/C,CAAkBgD,GACxBlE,KAAKmE,oBACP,CAEQ,kBAAAA,GACN,MAAMC,EAAQpE,KAAKoE,MACnB,IAAIC,EAAW,CACbC,MAAO5E,WACP6E,OAAQC,YACRC,UAAW,EACXC,WAAY,EACZjF,MAAO,GAGqB,oBAAnB+C,iBACT6B,EAAW7B,gBAGb4B,EAAMO,YAAY,mBAAoBC,KAAKC,KAAKR,EAASC,OAAS,MAClEF,EAAMO,YAAY,oBAAqBC,KAAKC,KAAKR,EAASE,QAAU,MACpEH,EAAMO,YAAY,iBAAkBN,EAASI,UAAY,MACzDL,EAAMO,YAAY,kBAAmBN,EAASK,WAAa,MAC3DN,EAAMO,YAAY,mBAAoBN,EAAS5E,MAAMiE,WACvD,CAEQ,iBAAA1C,CAAkBkD,GACxBlE,KAAKyC,eACLzC,KAAK0C,QACP,CAMQ,YAAAD,GACN,MAAMqC,EAAW9E,KAAKmC,cAAc,oBACpC,GAAa,MAAR2C,IAAAA,EAAUC,QAAQC,WACrB,OAGFhF,KAAKF,YAAcmF,SAASH,EAASC,QAAQC,WAAY,IACzDhF,KAAKH,YAAcoF,SAASH,EAASC,QAAQG,MAAO,IAEpDlF,KAAKJ,aAAe,GACpB,MAAMuF,EAASL,EAASf,iBAAiB,yBACzC,IAAK,MAAOmB,EAAOE,KAAUD,EAAOE,UAC9BD,EAAME,UAAUC,SAAS,yBAC3BvF,KAAKJ,aAAa4F,KAAKN,EAG7B,CAEQ,cAAAxE,CAAe+E,GACrBzF,KAAKH,YAAc4F,EAAMC,OAAOR,MAChClF,KAAK0C,QACP,CAKQ,IAAAiD,CAAKT,GACX,MAAMJ,EAAW9E,KAAKmC,cAAc,oBAChC2C,IACFA,EAASC,QAAQa,GAAKV,EAAMxB,WAEhC,CAEQ,cAAA9C,CAAe6E,GACrB,GAAIA,aAAiBI,cAAe,CAClC,IAAI,CAAC,QAAS,SAASC,SAASL,EAAMM,MAGpC,OAGF,GALEN,EAAMO,iBAKW,YAAfP,EAAMQ,KACR,MAEJ,CAEAjG,KAAKkG,YAGL,MAAMC,EAAQnG,KAAKF,YACnB,IAAIsG,EAAiC,IAArBpG,KAAKH,YAAoBsG,EAAQ,EAAInG,KAAKH,YAAc,EACxE,KAAOG,KAAKJ,aAAakG,SAASM,KAChCA,EAA0B,IAAdA,EAAkBD,EAAQ,EAAIC,EAAY,EAElDA,IAAcpG,KAAKH,eAKzBG,KAAK2F,KAAKS,EACZ,CAEQ,cAAAtF,CAAe2E,GACrB,GAAIA,aAAiBI,cAAe,CAClC,IAAI,CAAC,QAAS,SAASC,SAASL,EAAMM,MAGpC,OAGF,GALEN,EAAMO,iBAKW,YAAfP,EAAMQ,KACR,MAEJ,CAEAjG,KAAKkG,YAGL,MAAMC,EAAQnG,KAAKF,YACnB,IAAIuG,EAAYrG,KAAKH,YAAc,IAAMsG,EAAQ,EAAInG,KAAKH,YAAc,EACxE,KAAOG,KAAKJ,aAAakG,SAASO,KAChCA,EAAYA,EAAY,IAAMF,EAAQ,EAAIE,EAAY,EAElDA,IAAcrG,KAAKH,eAKzBG,KAAK2F,KAAKU,EACZ,CAEQ,WAAApG,CAAYwF,GAAc,IAAAa,EAEhC,GAAItG,KAAKJ,aAAakG,SAAS9F,KAAKH,aAClC,OAMF,MAAM0G,EAASd,EAAMc,OACjBA,aAAkBlH,cACA,WAAnBkH,EAAOC,SAA0D,YAAd,OAApBF,EAAAC,EAAOE,oBAAa,EAApBH,EAAsBE,WAMpDD,aAAkBG,SAAWH,EAAOI,QAAQ,sBAC7CJ,EAAOI,QAAQ,0BAOlB3G,KAAK4G,aACP,CAMQ,WAAAA,GACN,GAAgC,SAA5B5G,KAAK+E,QAAQ8B,WACf,OAGF,MAAMpB,EAAQ,IAAIqB,MAAM,uBAAwB,CAAEC,SAAS,EAAMC,UAAU,IAC3EhH,KAAKiH,cAAcxB,EACrB,CAEQ,YAAArF,GACN,GAAgC,SAA5BJ,KAAK+E,QAAQ8B,WACf,OAGF,MAAMpB,EAAQ,IAAIqB,MAAM,wBAAyB,CAAEC,SAAS,EAAMC,UAAU,IAC5EhH,KAAKiH,cAAcxB,EACrB,CAEQ,MAAAtC,GACN,MAAMN,EAAY7C,KAAKkC,WAAWC,cAA2B,UAC7DU,EAAUT,iBAAiB,YAAapC,KAAKmB,kBAC7C0B,EAAUT,iBAAiB,UAAWpC,KAAKqB,iBAC3CwB,EAAUT,iBAAiB,aAAcpC,KAAKyB,kBAAmB,CAAEyF,SAAS,IAC5ErE,EAAUT,iBAAiB,WAAYpC,KAAK2B,iBAAkB,CAAEuF,SAAS,IAEzE9E,iBAAiB,0BAA2BpC,KAAKK,8BAEjDL,KAAK+E,QAAQoC,SAAW,OACxBnH,KAAKmE,qBAEL,MAAMW,EAAW9E,KAAKmC,cAAc,oBAChC2C,IACFA,EAASC,QAAQqC,eAAiB,QAGpC,MACMC,EAAYrH,KAAKkC,WAAW6B,iBADjB,8DAEjB,IAAK,MAAMuD,KAAeD,EACxBC,EAAYC,SAAW,EAINvH,KAAKkC,WAAWC,cAA2B,gBACnDqF,MAAM,CAAEC,cAAc,GACnC,CAMQ,uBAAAnH,CAAwB4D,GAC9BlE,KAAKI,cACP,CAEQ,QAAAgD,GACNpD,KAAKkG,YAEL,MACMmB,EAAYrH,KAAKkC,WAAW6B,iBADjB,8DAEjB,IAAK,MAAMuD,KAAeD,EACxBC,EAAYC,UAAY,SAGnBvH,KAAK+E,QAAQoC,SAEpB,MAAMrC,EAAW9E,KAAKmC,cAAc,oBAChC2C,UACKA,EAASC,QAAQqC,eAG1B,MAAMvE,EAAY7C,KAAKkC,WAAWC,cAA2B,UAC7DU,EAAUD,oBAAoB,YAAa5C,KAAKmB,kBAChD0B,EAAUD,oBAAoB,UAAW5C,KAAKqB,iBAC9CwB,EAAUD,oBAAoB,aAAc5C,KAAKyB,mBACjDoB,EAAUD,oBAAoB,WAAY5C,KAAK2B,kBAE/CiB,oBAAoB,0BAA2B5C,KAAKK,6BACtD,CAEQ,SAAA6F,GACN,MAAMwB,EAAa1H,KAAKmC,cAAc,oBACtC,IAAKuF,EACH,OAGF,MAAMC,EAAS3H,KAAKkC,WAAWC,cAAc,gBACvC,MAANwF,GAAAA,EAAQ/D,aAAa,eAAgB,gBAE9B5D,KAAK+E,QAAQ6C,KACpBF,EAAWtD,MAAMyD,UAAY,0BAC/B,CAEQ,UAAArH,CAAWiF,GACjB,GAAIA,aAAiBI,cAAe,CAClC,IAAI,CAAC,QAAS,SAASC,SAASL,EAAMM,MAGpC,OAGF,GALEN,EAAMO,iBAKW,YAAfP,EAAMQ,KACR,MAEJ,CAEA,MAAMyB,EAAa1H,KAAKmC,cAAc,oBACtC,IAAKuF,EACH,OAGF,GAA0B,SAAtB1H,KAAK+E,QAAQ6C,KAEf,YADA5H,KAAKkG,YAIP,MAAMyB,EAAS3H,KAAKkC,WAAWC,cAAc,gBACvC,MAANwF,GAAAA,EAAQ/D,aAAa,eAAgB,QAErC8D,EAAWtD,MAAMyD,UAAY,6BAA6B7H,KAAKP,SAC/DO,KAAK+E,QAAQ6C,KAAO,MACtB,CAEQ,WAAAxG,CAAYqE,GAClB,MAAMiC,EAAa1H,KAAKmC,cAAc,oBACtC,IAAKuF,EACH,OAGF,MAAMI,EAAUJ,EAAWtD,MAAMyD,UAAUE,MAAM,aAAe,GAEhE/H,KAAKgI,KAAOvC,EAAMwC,SAAWH,EAAQ,GAAK7C,SAAS6C,EAAQ,GAAI,IAAMJ,EAAWhD,YAChF1E,KAAKkI,KAAOzC,EAAM0C,SAAWL,EAAQ,GAAK7C,SAAS6C,EAAQ,GAAI,IAAMJ,EAAWjD,WAEtD,SAAtBzE,KAAK+E,QAAQ6C,OACf5H,KAAK+E,QAAQqD,SAAW,OACxBpI,KAAKR,aAAc,EACnB6I,SAASC,KAAKlG,iBAAiB,YAAapC,KAAKuB,iBAErD,CAEQ,UAAAD,CAAW4C,GACElE,KAAKmC,cAAc,sBAKjCnC,KAAKT,gBACRS,KAAKQ,aAGPR,KAAKT,gBAAiB,EACtBS,KAAKR,aAAc,SACZQ,KAAK+E,QAAQqD,SACpBC,SAASC,KAAK1F,oBAAoB,YAAa5C,KAAKuB,iBACtD,CAEQ,UAAAC,CAAWiE,GACjB,MAAMiC,EAAa1H,KAAKmC,cAAc,oBACtC,IAAKuF,EACH,OAIF,GADA1H,KAAKT,gBAAiB,GACjBS,KAAKR,YACR,OAGF,IAAI+I,EAAU9C,EAAMwC,QAAUjI,KAAKgI,KAC/BQ,EAAU/C,EAAM0C,QAAUnI,KAAKkI,KAEnC,MAAMrF,EAAY7C,KAAKkC,WAAWC,cAAc,WAC1CmC,MAAEA,EAAKC,OAAEA,GAAWmD,EAAWe,wBAE/BC,EAAc9D,KAAK+D,OAAOrE,EAAQzB,EAAU+F,aAAe,GAC3DC,EAAcjE,KAAK+D,OAAOpE,EAAS1B,EAAUiG,cAAgB,GAE7DP,GAAWG,GAAeH,IAA0B,EAAfG,IACzCH,EAAUA,EAAU,EAAIG,GAAe,EAAIA,GAGvCF,GAAWK,GAAeL,IAA0B,EAAfK,IACzCL,EAAUA,EAAU,EAAIK,GAAe,EAAIA,GAG7CnB,EAAWtD,MAAMyD,UAAY,aAAaU,OAAaC,cAAoBxI,KAAKP,QAClF,CAEQ,YAAAiC,CAAa+D,GACnBA,EAAMO,iBAEN,MAAM0B,EAAa1H,KAAKmC,cAAc,oBACtC,GAAKuF,EAAL,CAIA,GAA6B,IAAzBjC,EAAMsD,QAAQC,OAAc,CAC9B,MAAMlB,EAAUJ,EAAWtD,MAAMyD,UAAUE,MAAM,qBAAuB,GAElEkB,EAAInB,EAAQ,GAAK7C,SAAS6C,EAAQ,GAAI,IAAMJ,EAAWhD,WACvDwE,EAAIpB,EAAQ,GAAK7C,SAAS6C,EAAQ,GAAI,IAAMJ,EAAWjD,UAE7DzE,KAAKgI,KAAOvC,EAAMsD,QAAQ,GAAGI,MAAQF,EACrCjJ,KAAKkI,KAAOzC,EAAMsD,QAAQ,GAAGK,MAAQF,CACvC,CAE0B,SAAtBlJ,KAAK+E,QAAQ6C,OACf5H,KAAK+E,QAAQqD,SAAW,OACxBpI,KAAKR,aAAc,EACnB6I,SAASC,KAAKlG,iBAAiB,YAAapC,KAAK6B,kBAfnD,CAiBF,CAEQ,WAAAD,CAAY6D,GAClBA,EAAMO,iBAEahG,KAAKmC,cAAc,qBAKlCsD,EAAMsD,QAAQC,OAAS,IACpBhJ,KAAKT,gBACRS,KAAKQ,aAGPR,KAAKR,aAAc,EACnBQ,KAAKT,gBAAiB,EACtBS,KAAKL,cAAe,SAEbK,KAAK+E,QAAQqD,SACpBC,SAASC,KAAK1F,oBAAoB,YAAa5C,KAAK6B,kBAExD,CAEQ,WAAAC,CAAY2D,GAClB,MAAMiC,EAAa1H,KAAKmC,cAAc,oBACtC,GAAKuF,IAIL1H,KAAKT,gBAAiB,EACjBS,KAAKR,aAImB,IAAzBiG,EAAMsD,QAAQC,QAAc,CAC9B,GAAIhJ,KAAKL,aACP,OAGF,MAAMkD,EAAY7C,KAAKkC,WAAWC,cAAc,WAC1CmC,MAAEA,EAAKC,OAAEA,GAAWmD,EAAWe,wBACrC,IAAIF,EAAU9C,EAAMsD,QAAQ,GAAGI,MAAQnJ,KAAKgI,KACxCQ,EAAU/C,EAAMsD,QAAQ,GAAGK,MAAQpJ,KAAKkI,KAE5C,MAAMQ,EAAc9D,KAAK+D,OAAOrE,EAAQzB,EAAU+F,aAAe,GAC3DC,EAAcjE,KAAK+D,OAAOpE,EAAS1B,EAAUiG,cAAgB,GAE7DP,GAAWG,GAAeH,IAA0B,EAAfG,IACzCH,EAAUA,EAAU,EAAIG,GAAe,EAAIA,GAGvCF,GAAWK,GAAeL,IAA0B,EAAfK,IACzCL,EAAUA,EAAU,EAAIK,GAAe,EAAIA,GAG7CnB,EAAWtD,MAAMyD,UAAY,aAAaU,OAAaC,cAAoBxI,KAAKP,QAClF,CACF,EC5jBK,SAAS4J,EAAMC,EACpBrD,GACA,QAD2B,IAA3BA,IAAAA,EAA8B,oBACF,aAAxBoC,SAASkB,YAAuC,qBAATtD,GACjB,gBAAxBoC,SAASkB,WAA+B,CACxC,MAAMC,EAAQC,WAAWH,EAAU,GAEnC,MAD8B,IAAMI,aAAaF,EAEnD,CACEpH,iBAAiB6D,EAAMqD,GAEvB,MAD8B,IAAM1G,oBAAoBqD,EAAMqD,EAGlE,CCrBO,SAASK,EAAOC,EAAwBC,EAAsBC,GAKnE,IAAK,MAAMC,KAAUD,EAAS,CAC5B,MAAME,EAAeJ,EAAOzH,cAC1B,4BAA4B0H,MAAiBE,EAAOhH,WAClDiH,IACFA,EAAajF,QAAQkF,SAAWF,EAAOG,gBAE3C,CAIA,MAAMC,EAAmB,GACnBC,EAAgBR,EAAO7F,iBAAiB,iBAC9C,IAAK,MAAMiG,KAAgBI,EAAe,CAIxCD,EAFiBlF,SAAS+E,EAAajF,QAAQsF,SAAU,IAChC,GACTL,EAAajF,QAAQkF,QACvC,CAEA,MAAMK,EAAQH,EAAOtG,KAAK,OAC1B,OAAO+F,EAAOzH,cAA8B,+BAA+BmI,MAC7E,CCzEA,SAASC,EAAwB9E,GAC/B,KAAMA,EAAMc,kBAAkBiE,kBAC5B,OAGF,MAAMV,EAAUzB,SAASlG,cAA2B,yBACpD,IAAK2H,EAEH,YADAW,QAAQC,MAAM,kCAIhB,MAAMC,EAAQtC,SAASlG,cAAc,kBACrC,GAAKwI,EAKL,GAA2B,iBAAvBlF,EAAMc,OAAOqE,MAA0B,CACzCd,EAAQe,OAAQ,EAChBf,EAAQxE,UAAUwF,OAAO,aAEzBH,EAAMvI,iBAAiB,SAAU2I,GACjC,MAAMd,EACJ5B,SAASlG,cAAiC,uCAExC8H,IACFe,EAAsC,MAARf,OAAQ,EAARA,EAAUW,OACxCK,EAA2BxF,EAAMc,OAAQ0D,EAASW,OAEtD,KAAkC,aAAvBnF,EAAMc,OAAOqE,QACtBD,EAAM/H,oBAAoB,SAAUmI,GACpCC,EAA8B,IAC9BC,EAA2BxF,EAAMc,QACjCuD,EAAQxE,UAAU4F,IAAI,aACtBpB,EAAQe,OAAQ,QArBhBJ,QAAQC,MAAM,0BAuBlB,CAEA,SAASK,EAAqBtF,GAC5B,GAAIA,EAAMc,kBAAkB4E,kBAAmB,CAC7C,MACMC,EADW3F,EAAMc,OAAOuD,QAAQrE,EAAMc,OAAO8E,eACpBT,MAE/BI,EAA8BI,GAC9BE,EAAgCF,GAChCG,EAA+BH,GAC/BH,EAA2BxF,EAAMc,OAAQ6E,EAC3C,CACF,CAMA,SAASH,EAA2B1E,EAAqB6E,GAIvD,MAAM3F,EAAQ,IAAI+F,YAAoB,uBAAwB,CAC5DzE,SAAS,EACTC,UAAU,EACVtB,OAAQ,CACN+F,gBAAiBL,KAIrB7E,EAAOU,cAAcxB,EACvB,CAEA,SAAS6F,EAAgCF,GACvC,MAAMM,EAAgBrD,SAASlG,cAC7B,8BAA8BiJ,OAEhC,IAAKM,EAEH,YADAjB,QAAQkB,KAAK,4DAA6DP,GAI5E,MAAMQ,EAAUvD,SAASlG,cACvB,0DACF,IAAKyJ,EACH,OAGF,MAAMC,EAAYxD,SAASlG,cACzB,yFACF,IAAK0J,EACH,OAGF,MAAMC,EAAW7G,SAASyG,EAAc3G,QAAQgH,SAAU,IACpDC,EAAQ/G,SAAS2G,EAAQ7G,QAAQgH,SAAU,KAK5CF,EAAU9G,QAAQgH,UAAYD,EAAWE,IAC5CJ,EAAQ7G,QAAQgH,SAAWD,EAASpI,WACpCmI,EAAU9G,QAAQgH,SAAWC,EAAMtI,YAKjCuB,SAAS4G,EAAU9G,QAAQgH,SAAU,MAAQD,IAC/CD,EAAU9G,QAAQgH,SAAW,IAK/B,MAAME,EAAeJ,EAAU9G,QAAQgH,SAAW9G,SAAS4G,EAAU9G,QAAQgH,SAAU,IAAM,EAE7F,IAAIG,EAAU7D,SAASlG,cAA2B,mCAClD,GAAI2J,EAAWG,EAAc,CACtBC,IACHA,EAAU7D,SAAS8D,cAAc,QACjCD,EAAQ5G,UAAU4F,IAAI,gBACtBgB,EAAQ5G,UAAU4F,IAAI,iBACtBgB,EAAQ9H,MAAMgI,QAAU,SACxBP,EAAUQ,sBAAsB,WAAYH,IAG9C,MAAMI,EAAgBL,EAAeH,EAC/BS,EAAkB3H,KAAK4H,MAAOF,EAAgBL,EAAgB,KACpEC,EAAQ3I,UAAY,GAAGgJ,QACzB,MAAWL,GAETA,EAAQpB,QAEZ,CAEA,SAASS,EAA+BH,GACtC,MAAMM,EAAgBrD,SAASlG,cAC7B,8BAA8BiJ,OAEhC,IAAKM,EAEH,YADAjB,QAAQkB,KAAK,4DAA6DP,GAI5E,MAAMqB,EAAWpE,SAAStE,iBACxB,oCACsB,IAApB0I,EAASzD,QACXyB,QAAQkB,KAAK,wDAAyDP,GAGxE,IAAK,MAAMQ,KAAWa,EACpBb,EAAQ7G,QAAQgH,SAAWL,EAAc3G,QAAQgH,QAErD,CAsDA,SAASf,EAA8BI,GACrC,MAAMsB,EAAWrE,SAASlG,cACxB,uCACGuK,GAKLA,EAAS3H,QAAQqG,cAAgBA,EACjCsB,EAAS3H,QAAQ4H,OAASC,KAAKC,MAAMnJ,YALnC+G,QAAQkB,KAAK,wBAMjB,CAEA,SAASmB,EAAsBrH,GAAmB,IAAAsH,EAEhD,KAAItH,EAAMc,kBAAkBG,SAAqC,YAAd,OAAZqG,EAAAtH,EAAMc,aAAM,EAAZwG,EAAcvG,WAIjDf,EAAMuH,yBAAyBC,eAAgB,CACjD,MAAMC,EAAQzH,EAAMuH,cAAc7K,cAAgC,uBAC9D+K,IAAUA,EAAMC,UAClBD,EAAM1F,QACN0F,EAAME,QAEV,CACF,CCpMA,SAASC,EACP5H,GACA,MAAM6H,EAAgB7H,EAAMuH,cAC5B,KAAMM,aAAyBjO,aAC7B,OAGF,IAAIkO,EAAY9H,EAAMC,OAAO8H,GACzBC,EAAYhI,EAAMC,OAAOgI,oBAE7B,GAAIjI,EAAMC,OAAOiI,kBAAmB,CAClC,MAAM/D,EAAS0D,EAAcnL,cAC3B,0CAA0CsD,EAAMC,OAAOiI,uBACzD,IAAK/D,EACH,OAGF,MAAMgE,EAAUjE,EAAoBC,EAAQnE,EAAMC,OAAO4E,MAAO7E,EAAMC,OAAOmI,eAC7E,IAAKD,EACH,OAGFL,EAAY9H,EAAMC,OAAOiI,kBACzBF,EAAYxI,SAAS2I,EAAQ7I,QAAQyI,GAAI,IA6C7C,SAA4BF,EAA4BC,EAAmBE,GACzE,MAAMK,EAAkBR,EAAcvJ,iBACpC,4CAA4CwJ,OAC9C,IAAK,MAAMQ,KAAkBD,EAAiB,CAC5CC,EAAehJ,QAAQiJ,kBAAoBP,EAAU/J,WAChCqK,EAAepH,QAAQ,iBAC/B5B,QAAQ4H,OAASC,KAAKC,MAAMnJ,UAC3C,CAEA,MAAMuK,EAAiBX,EAAcnL,cACnC,4CAA4CoL,uCACjBE,OAEvBS,EAAaZ,EAAcnL,cAA4B,0BACvDgM,EAAsBb,EAAcnL,cACxC,qCACE8L,GAAkBC,GAAcC,IAClCD,EAAWnJ,QAAQgH,SAAWkC,EAAelJ,QAAQiH,MACrDmC,EAAoBpJ,QAAQgH,SAAWkC,EAAelJ,QAAQqJ,eAElE,CAhEIC,CAAmBf,EAAeC,EAAWE,EAC/C,EAqEF,SAA+BH,EAA4BG,GACzD,MAAMa,EAAWhB,EAAcvJ,iBAA8B,qBAC7D,IAAK,MAAMwK,KAAWD,EACpBE,EAAqBD,EAASd,EAElC,CAxEEgB,CAAsBnB,EAAeG,GAiHvC,SAAuCH,EAA4BC,EACjEE,GAIA,MAAMQ,EAAiBX,EAAcnL,cACnC,0CAA0CoL,uCACfE,OAC7B,IAAKQ,EACH,OAGF,MAAMjC,EAAQ/G,SAASgJ,EAAelJ,QAAQiH,MAAO,IACrD,IAAIoC,EAAiBnJ,SAASgJ,EAAelJ,QAAQqJ,eAAgB,MAChEM,OAAOC,UAAUP,IAAmBA,EAAiBpC,KACxDoC,EAAiBpC,GAGnB,MAAMsC,EAAWhB,EAAcvJ,iBAC7B,6CAA6CwJ,wCAC/C,IAAK,MAAMgB,KAAWD,EACpBC,EAAQxJ,QAAQiH,MAAQA,EAAMtI,WAC9B6K,EAAQxJ,QAAQqJ,eAAiBA,EAAe1K,WAGlD,MAAMkL,EAAStB,EAAcvJ,iBAC3B,2CAA2CwJ,wCAC7C,IAAK,MAAMsB,KAASD,EAClBC,EAAM9J,QAAQiH,MAAQA,EAAMtI,UAEhC,CA9IEoL,CAA8BxB,EAAeC,EAAWE,GAqJ1D,SAAmCH,EAA4BC,EAC7DE,GAGA,MAAMa,EAAWhB,EAAcvJ,iBAC7B,gDAAgDwJ,OAClD,IAAK,MAAMgB,KAAWD,EACpBS,EAAyBR,EAASd,EAEtC,CA7JEuB,CAA0B1B,EAAeC,EAAWE,GD+F/C,SAAuBA,GAI5B,MAAM9C,EAAQtC,SAAStE,iBAA8B,6CACrD,IAAK,MAAMkL,KAAQtE,EAAO,CACxB,MAAMiD,EAAUqB,EAAK9M,cACnB,qCAAqCsL,OACnCG,IACFqB,EAAKlK,QAAQgH,SAAW6B,EAAQ7I,QAAQiH,MAE5C,CAEA,MAAM/B,EAAW5B,SAASlG,cACxB,+DACF,IAAK8H,EACH,OAGF,MAAM2D,EAAUvF,SAASlG,cACvB,sDAAsD8H,EAASW,2CACpC6C,OAC7B,IAAKG,EACH,OAGF,MAAMhC,EAAUvD,SAASlG,cACvB,0DACF,IAAKyJ,EACH,OAGF,MAAMC,EAAYxD,SAASlG,cACzB,yFACG0J,IAOLD,EAAQ7G,QAAQgH,SAAW6B,EAAQ7I,QAAQiH,MAC3CH,EAAU9G,QAAQgH,SAAW6B,EAAQ7I,QAAQqJ,eAC7C9C,EAAgCrB,EAASW,OACzCW,EAA+BtB,EAASW,OAC1C,CC3IEsE,CAAyBzB,GAS3B,SAAyCH,EAA4BG,GAGnE,MAAMzB,EAAQsB,EAAcnL,cAC1B,8DACF,IAAK6J,EACH,OAGF,MAAMoC,EAAiBpC,EAAMvF,cAActE,cACzC,yCACF,IAAKiM,EACH,OAGF,MAAMH,EAAiBX,EAAcnL,cACnC,2DAA2DsL,OAC7D,IAAKQ,EACH,OAGFjC,EAAMjH,QAAQgH,SAAWkC,EAAelJ,QAAQiH,MAChDoC,EAAerJ,QAAQgH,SAAWkC,EAAelJ,QAAQqJ,cAC3D,CA/BEe,CAAgC7B,EAAeG,EACjD,CAqEA,SAASe,EAAqBD,EAAsBd,GAElD,MAAM2B,EAAeb,EAAQpM,cAA4B,wBACzD,IAAKiN,EACH,OAMF,IAAInB,EACJ,MAAMoB,EAAWd,EAAQ5H,QAAQ,cAC3B2I,EAAiBf,EAAQ5H,QAAQ,oBACjC4I,EAAsBhB,EAAQ5H,QAAQ,yBAY5C,GAXI0I,EACFpB,EAAiBoB,EAASlN,cACxB,0CAA0CsL,OACnC6B,EACTrB,EAAiBqB,EAAenN,cAC9B,0CAA0CsL,OACnC8B,IACTtB,EAAiBsB,EAAoBpN,cACnC,+CAA+CsL,SAG9CQ,EACH,OAGF,MAAMjC,EAAQ/G,SAASgJ,EAAelJ,QAAQiH,MAAO,IAC/CwD,EAAaC,WAAWlB,EAAQxJ,QAAQ2K,oBAC9CN,EAAarK,QAAQgH,SAAWnH,KAAK4H,MAAMR,EAAQA,EAAQwD,EAAa,KAAK9L,UAC/E,CAuDA,SAASqL,EAAyBR,EAChCd,GACA,MAAMQ,EAAiBM,EAAQpM,cAC7B,qCAAqCsL,OACvC,IAAKQ,EACH,OAIF,MAAMjC,EAAQ/G,SAASgJ,EAAelJ,QAAQiH,MAAO,IACrD,IAAI2D,EAAiB3D,EACrB,GAAIuC,EAAQxJ,QAAQ2K,mBAAoB,CACtC,MAAMF,EAAaC,WAAWlB,EAAQxJ,QAAQ2K,oBAC9CC,EAAiB/K,KAAK4H,OAAO,IAAMgD,GAAcxD,EAAQ,IAC3D,CAEAuC,EAAQxJ,QAAQ4K,eAAiBA,EAAejM,UAClD,CCtNA,SAASkM,EAAmBnK,GAC1B,KAAMA,EAAMuH,yBAAyB3N,aACnC,OAGF,MAAMwQ,EAAa,IAAI/I,MAAM,6BAA8B,CACzDC,SAAS,EACTC,UAAU,IAEZvB,EAAMuH,cAAc/F,cAAc4I,GAEpBxH,SAASlG,cAA4B,kBAC7C4C,QAAQ+K,KAAO,MACvB,CAQA,SAASC,EAAc7L,GACrB,MAAM8L,EAAU3H,SAASlG,cAAc,2BACnC6N,aAAmB3Q,cACrB2Q,EAAQnF,OAAQ,EAEpB,CAMA,SAASoF,EAAc/L,GACrB,MAAM8L,EAAU3H,SAASlG,cAAc,2BACnC6N,aAAmB3Q,cACrB2Q,EAAQnF,OAAQ,EAEpB,CCtCOqF,eAAeC,EAAKC,GACzB,OAAOA,EAAMD,MACf,CNsmBKE,eAAeC,IAAI,uBACtBD,eAAeE,OAAO,qBAAsBnR,mBOrlB9CiK,EAzCA,WAGE,MAAMmH,EAAanI,SAASlG,cAA2B,mCACvD,IAAKqO,EACH,OAGF,IAAKA,EAAWzL,QAAQ0L,YAAcD,EAAWzL,QAAQ2L,QACvD,OAGF,MAAMC,EAAiB1L,SAASuL,EAAWzL,QAAQ0L,UAAW,IACxDG,EAAe3L,SAASuL,EAAWzL,QAAQ2L,QAAS,IACpDG,EAAYjM,KAAK+D,MAAMiE,KAAKC,MAAQ,KAE1C,GAAIgE,EAAYF,GAAkBE,EAAYD,EAAc,CAC1D,IAAIE,GAAc,EACdC,EAAU1I,SAASlG,cAAc,kCAKhC4O,IACHA,EAAU1I,SAAS8D,cAAc,kCACjC2E,GAAc,GAGhBC,EAAQhM,QAAQ0L,UAAYD,EAAWzL,QAAQ0L,UAC/CM,EAAQhM,QAAQ2L,QAAUF,EAAWzL,QAAQ2L,QAEzCF,EAAWzL,QAAQiM,UACrBD,EAAQhM,QAAQiM,QAAUR,EAAWzL,QAAQiM,SAG3CF,GACFN,EAAWS,YAAYF,EAE3B,CACF,GJ0OA1H,EA5CA,WACE,MAAM6H,EAAiB7I,SAAStE,iBAC9B,4CACF,IAAK,MAAMoN,KAAWD,EAAgB,CACpC,MAAME,EAAQD,EAAQhP,cAAgC,uBAClDiP,GACFA,EAAMhP,iBAAiB,SAAUmI,GAInC4G,EAAQ/O,iBAAiB,QAAS0K,EACpC,CAKA,MAAMuE,EAAoBhJ,SAASlG,cACjC,wEAC8B,IAAAmP,EAAX,MAAjBD,GAAAA,EAAmBlE,UAC2C,OAAhEmE,EAAAjJ,SAASlG,cAAc,4CAAvBmP,EACIlP,iBAAiB,SAAU2I,IAIjC,MAAMwG,EAAoBlJ,SAASlG,cACjC,+DAGEoP,IACFjG,EAAgCiG,EAAkB3G,OAClDW,EAA+BgG,EAAkB3G,OAErD,GChBAvB,EAPA,WACE,MAAMiE,EAAgBjF,SAASlG,cAA2B,2BACtDmL,GACFA,EAAclL,iBAAiB,iCAAkCiL,EAErE,GCtLAhE,EAvDA,WACE,MAAMwF,EAAQxG,SAASlG,cAAc,kBACrC,IAAK0M,EACH,OAGFA,EAAMzM,iBAAiB,eAAgB2N,GACvClB,EAAMzM,iBAAiB,eAAgB6N,GAEvC,MAAMuB,EAAanJ,SAAStE,iBAC1B,iDACF,IAAK,MAAM0N,KAAUD,EACnBC,EAAOrP,iBAAiB,QAASwN,EAErC,GGXA,MAAM8B,EAAwB,IAAIC,QAElC,SAASC,EAAiBnE,GACxB,MAAMoE,EAAmBxJ,SAAStE,iBAChC,+CAEF,IAAI+N,GAAU,EACd,IAAK,MAAMC,KAAMF,EAAkB,CACjC,MAAMG,EAAUD,EAAGhN,QAAQkN,eAAiBxE,EAAU/J,WACtDqO,EAAGzM,UAAU4M,OAAO,mBAAoBF,GACpCA,IACFF,GAAU,EAEd,CAEA,IAAKA,EAAS,CAGZ,MAAMK,EAAa9J,SAAStE,iBAC1B,uDACF,IAAK,MAAMqO,KAAaD,EACtBC,EAAU9M,UAAU4F,IAAI,mBAE5B,CACF,CAEA,SAASmH,EAAiBC,GACpBA,EAAQnQ,cAAc,0BACxBmQ,EAAQvN,QAAQa,GAAK,IAEzB,CAMA,SAAS2M,EAAYzN,GACnB,IAAI0N,EAEFA,EADE1N,EAASQ,UAAUC,SAAS,8BACnB,6BAEA,8BAGb,MAAM1C,EAAYiC,EAAS6B,QAAQ,0BACnC,OAAgB,MAAT9D,OAAS,EAATA,EAAWV,cAA2BqQ,EAC/C,CAEA,SAASC,EAAkBlM,GACzB,OAAOA,aAAkBlH,cACvBkH,EAAOjB,UAAUC,SAAS,+BAC1BgB,EAAOjB,UAAUC,SAAS,6BAC9B,CAMA,SAASmN,EAAqBC,EAAczN,GAC1C,MAAM0N,EAAUD,EAAI5O,iBAAiB,kCACrC,IAAK,MAAO8O,EAAapB,KAAWmB,EAAQvN,UACtCwN,IAAgB3N,EAClBuM,EAAO7N,aAAa,eAAgB,QAEpC6N,EAAOqB,gBAAgB,eAG7B,CA+FA,SAASC,EAAmBT,EAAsBlN,EAAoBF,GAIpE,MAAMkL,EAAQhL,EAAMjD,cAAc,SAC9BiO,IAAU4C,QAAQC,aAEpB7C,EAAM8C,OAAQ,EAGd9C,EAAM+C,MAAO,EAGbhD,EAAKC,GAAOgD,MAAM3I,QAAQ4I,QAG5B,IAAIC,EAAe5B,EAAsBpB,IAAIgC,GACxCgB,IACHA,EAAe,IAAIC,IACnB7B,EAAsB8B,IAAIlB,EAASgB,IAGrCA,EAAapI,IAAIhG,GAIjB,MAAMuO,EAAcnB,EAAQvO,iBAAiB,yBAAyBiF,OAIhEtD,EAAiB,CACrBR,QACAwO,WAAYtD,EACZuD,eAAiBL,EAAaM,KAAOH,EAAe,IACpDI,WAAY5O,SAASqN,EAAQvN,QAAQwI,UAAW,KAG5CuG,EAAiB1O,EAAMjD,cAA2B,yCACtC,MAAd2R,GAAAA,EAAgB/O,QAAQkN,eAC1BvM,EAAOqO,WAAa9O,SAAS6O,EAAe/O,QAAQkN,aAAc,KAGpE,MAAMxM,EAAQ,IAAI+F,YAAoB,0BAA2B,CAC/DzE,SAAS,EACTC,UAAU,EACVtB,WAGF4M,EAAQrL,cAAcxB,EACxB,CAyCA,SAASuO,EAAmCpJ,GAE1C,OAAOA,aAAiB9D,OAAwB,mCAAf8D,EAAM3E,IACzC,CCpQA,SAASgO,EAAqBxO,GAC5B,MAAMiH,EAAWrE,SAASlG,cACxB,uCACF,IAAKuK,EAEH,YADAjC,QAAQkB,KAAK,yBAIf,MAAMP,EAAgB3F,EAAMC,OAAO+F,gBAC/BL,EACFsB,EAAS3H,QAAQqG,cAAgBA,SAE1BsB,EAAS3H,QAAQqG,cAG1BsB,EAAS3H,QAAQ4H,OAASC,KAAKC,MAAMnJ,UACvC,CAEA,SAASwQ,EAA8BzO,GACrC,MAAMuI,EAAoBvI,EAAMC,OAAOgI,oBAEjC6B,EAAsBlH,SAASlG,cAAc,yBAC9CoN,IAKLA,EAAoBxK,QAAQiJ,kBAAoBA,EAAkBtK,WACpE,CDuTAtB,iBAAiB,2BAnNjB,SAA2BqD,GACzB,MAAMX,EAAWW,EAAMc,OACvB,IAAKkM,EAAkB3N,GACrB,OAGF,MAAMqP,EAAU5B,EAAYzN,GACjB,MAAPqP,GAAAA,EAASpP,QAAQG,OAASiP,EAAQpP,QAAQG,QAAUJ,EAASC,QAAQG,QACvEJ,EAASC,QAAQa,GAAKuO,EAAQpP,QAAQG,OAGpCJ,EAASQ,UAAUC,SAAS,8BAC9BmN,EAAqB5N,EAAUG,SAASH,EAASC,QAAQG,MAAO,IAEpE,GAsMA9C,iBAAiB,8BA1QjB,SAA4BqD,GAC1B,IAAKgN,EAAkBhN,EAAMc,QAC3B,OAGF,MAAM4N,EAAU5B,EAAY9M,EAAMc,QAC9B4N,IACFA,EAAQpP,QAAQa,GAAKH,EAAMC,OAAOR,MAAMxB,YAGtC+B,EAAMc,OAAOjB,UAAUC,SAAS,8BAClCmN,EAAqBjN,EAAMc,OAAQd,EAAMC,OAAOR,MAEpD,GA8PA9C,iBAAiB,yBAxPjB,SAAwBqD,GACjBgN,EAAkBhN,EAAMc,UAIzBd,EAAMc,OAAOjB,UAAUC,SAAS,6BAClCE,EAAMc,OAAOxB,QAAQa,GAAKH,EAAMC,OAAOR,MAAMxB,WAUjD,SAAqB0B,GACnB,MAAMgL,EAAQhL,EAAMjD,cAAc,SAClC,IAAKiO,EACH,OAGEA,EAAMgE,OAERjE,EAAKC,GAAOgD,MAAM3I,QAAQ4I,OAE1BjD,EAAMiE,OAEV,CApBIC,CAAY7O,EAAMC,OAAON,OAE7B,GA+OAhD,iBAAiB,yBAzLjB,SAAwBqD,GAChBA,EAAMc,kBAAkBlH,aAI1BoG,EAAMc,OAAOjB,UAAUC,SAAS,+BAClCwN,EAAmBtN,EAAMc,OAAQd,EAAMC,OAAON,MAAOK,EAAMC,OAAOR,MAEtE,GAkLA9C,iBAAiB,wBAvMjB,SAAuBqD,GACrB,GAAMA,EAAMc,kBAAkBlH,aAI1BoG,EAAMc,OAAOjB,UAAUC,SAAS,8BAA+B,CACjE,MAAM6K,EAAQ3K,EAAMC,OAAON,MAAMjD,cAAc,SAC3CiO,IAAUA,EAAMgE,QAClBhE,EAAMiE,OAEV,CACF,GA8LAhL,EAnFA,WACE,MAAMiJ,EAAUjK,SAASlG,cAA2B,+BACpD,IAAKmQ,EACH,OAMF,IAAKZ,EAAsB6C,IAAIjC,IAAYA,EAAQvN,QAAQG,MAAO,CAChE,MAAMC,EAASmN,EAAQvO,iBAA8B,yBAC/CmB,EAAQD,SAASqN,EAAQvN,QAAQG,MAAO,IACxCE,EAAQD,EAAOD,GACjBE,GACF2N,EAAmBT,EAASlN,EAAOF,EAEvC,CAGA,MAAMyN,EAAMJ,EAAYD,GACjB,MAAHK,GAAAA,EAAK5N,QAAQG,OACfwN,EAAqBC,EAAK1N,SAAS0N,EAAI5N,QAAQG,MAAO,KAIxD,MAAMmI,EAAiC5H,KArEzC,SAA4B6M,EAC1B7M,GACA,IAAKiJ,OAAOC,UAAUlJ,EAAMC,OAAOgI,qBACjC,OAGF,MAAMH,EAAYtI,SAASqN,EAAQvN,QAAQwI,UAAW,IAMtD,GAAIA,IAAc9H,EAAMC,OAAOiI,kBAAmB,CAEhD,MAAM/D,EAASvB,SAASlG,cACtB,kEAAkEoL,OACpE,IAAK3D,EACH,OAGF,MAAMgE,EAAUjE,EAAoBC,EAAQnE,EAAMC,OAAO4E,MAAO7E,EAAMC,OAAOmI,eAM7E,YALID,IACFgE,EAAiB3M,SAAS2I,EAAQ7I,QAAQyI,GAAI,KAC9C6E,EAAiBC,IAIrB,CAMI/E,IAAc9H,EAAMC,OAAO8H,KAC7BoE,EAAiBnM,EAAMC,OAAOgI,qBAC9B2E,EAAiBC,GAErB,CAiCIkC,CAAmBlC,EAAS7M,IAG9B,IAAK,MAAMA,KAASgP,sBAAsBC,MACxC,GAAIV,EAAmCvO,GACrC,IACE4H,EAA8B5H,EAChC,CAAE,MAAOiF,GACPD,QAAQkB,KAAKjB,EACf,CAIJtI,iBAAiB,iCAAkCiL,EACrD,GCxQAhE,EARA,WACE,MAAMiE,EAAgBjF,SAASlG,cAA2B,2BACtDmL,IACFA,EAAclL,iBAAiB,uBAAwB6R,GACvD3G,EAAclL,iBAAiB,iCAAkC8R,GAErE,GCWA,MAAMS,4BAA4BtV,YAChC,6BAAkBC,GAChB,MAAO,CACL,2BAEJ,CA6BQsV,yBAAsD,GAEtDC,6BAA+B7U,KAAKuK,wBAAwBrK,KAAKF,MACjE8U,0BAA4B9U,KAAK+K,qBAAqB7K,KAAKF,MAE5D,iBAAA+B,GACL/B,KAAKgC,uBAEL,MAAM+S,EAAkB/U,KAAKkC,WAAW6B,iBAA8B,oBACtE,IAAK,MAAMgG,KAAUgL,EAAiB,CACpC,MAAM3D,EAAQrH,EAAO5H,cAAgC,uBACjDiP,GACFA,EAAMhP,iBAAiB,SAAUpC,KAAK6U,8BAIxC9K,EAAO3H,iBAAiB,QAAS4S,EACnC,CAE2BhV,KAAKkC,WAAWC,cACzC,wBACiBC,iBAAiB,SAAUpC,KAAK8U,2BAEnD9U,KAAKiV,aACLjV,KAAKkV,4BACLlV,KAAK0C,QACP,CAQQ,yBAAAwS,GACN,MAAM9J,EAAgBpL,KAAK+E,QAAQoQ,sBACnC,IAAK/J,EACH,OAGF,IAAK,MAAM6D,KAAQjP,KAAK4U,yBACtB,GAAI3F,EAAKzB,KAAOpC,EAAe,CAC7BpL,KAAKoV,oBAAsBnG,EAC3B,KACF,CAGF,IAAKjP,KAAKoV,oBACR,OAGYpV,KAAKkC,WAAWC,cAC5B,uDACIgL,SAAU,EAChBnN,KAAKqV,mBAAmB,eAC1B,CAMQ,kBAAAA,CAAmB/F,GACzB,MAAMxF,EAAU9J,KAAKkC,WAAWC,cAA2B,yBACrDmT,EAAetV,KAAKkC,WAAWC,cAAc,mBAC7CoT,EAAoBvV,KAAKkC,WAAWC,cAAc,2BAEjC,iBAAnBmN,GACFxF,EAAQe,OAAQ,EAChBf,EAAQxE,UAAUwF,OAAO,aACzBwK,EAAahQ,UAAUwF,OAAO,YAC9ByK,EAAkBjQ,UAAU4F,IAAI,cAEhCpB,EAAQxE,UAAU4F,IAAI,aACtBpB,EAAQe,OAAQ,EAChByK,EAAahQ,UAAU4F,IAAI,YAC3BqK,EAAkBjQ,UAAUwF,OAAO,YAEvC,CAEO,oBAAAnI,GACL,MAAMoS,EAAkB/U,KAAKkC,WAAW6B,iBAA8B,oBACtE,IAAK,MAAMgG,KAAUgL,EAAiB,CACpC,MAAM3D,EAAQrH,EAAO5H,cAAgC,uBACjDiP,GACFA,EAAMxO,oBAAoB,SAAU5C,KAAK6U,8BAG3C9K,EAAOnH,oBAAoB,QAASoS,EACtC,CAE2BhV,KAAKkC,WAAWC,cACzC,wBACiBS,oBAAoB,SAAU5C,KAAK8U,0BACxD,CAEO,wBAAAhS,CAAyBC,EAAcC,EAAkBC,GAC9DjD,KAAKgC,uBAEDgB,IAAaC,GAIbjD,KAAKkD,cACPlD,KAAKiV,aACLjV,KAAK0C,SAET,CAEQ,oBAAAV,GACDhC,KAAKkC,aACRlC,KAAKqD,aAAa,CAAEC,KAAM,SAC1BtD,KAAKkC,WAAWqB,UAAY,spGAEhC,CAEQ,UAAA0R,GACN,MAAMO,EAAW,GACXC,EAAkBzV,KAAK+D,iBAC3B,sCAEF,IAAK,MAAMkK,KAAkBwH,EAAiB,KAAAC,EAC5C,MAAM9H,EAA0B,CAC9B+H,UAAgD,SAArC1H,EAAelJ,QAAQ4Q,UAClCnI,GAAIS,EAAelJ,QAAQyI,GAC3BxB,MAAO/G,SAAqC,OAA7ByQ,EAACzH,EAAelJ,QAAQiH,OAAK0J,EAAI,IAAK,KAGvD,GAAIzH,EAAelJ,QAAQqJ,eAAgB,CACzC,MAAMA,EAAiBnJ,SAASgJ,EAAelJ,QAAQqJ,eAAgB,IAClEwH,MAAMxH,KACTR,EAAQQ,eAAiBA,EAE7B,CAEAoH,EAAShQ,KAAKoI,EAChB,CAEA,MAAMI,EAAoBhO,KAAK+E,QAAQiJ,kBACvChO,KAAK6V,qBAAkBC,EACvB,IAAK,MAAMlI,KAAW4H,EACpB,GAAI5H,EAAQJ,KAAOQ,EAAmB,CACpChO,KAAK6V,gBAAkBjI,EACvB,KACF,CAKF,MAAMmI,EAA0C,GAC1CC,EAAsBhW,KAAK+D,iBAC/B,2CAEF,IAAK,MAAMkS,KAAsBD,EAAqB,CACpD,MAAME,EAAuC,CAC3C1I,GAAIyI,EAAmBlR,QAAQyI,GAC/BzK,KAAMkT,EAAmBxS,YAAY0S,OACrCC,0BAA2B3G,WAAWwG,EAAmBlR,QAAQqR,2BACjEC,oBAAwE,SAAnDJ,EAAmBlR,QAAQsR,qBAG9CT,MAAMM,EAAYE,6BACpBF,EAAYE,0BAA4B,GAG1CL,EAAavQ,KAAK0Q,EACpB,CAEAlW,KAAK4U,yBAA2BmB,CAClC,CAEQ,MAAArT,GAAS,IAAA4T,EACStW,KAAKkC,WAAWC,cAAc,qBACtCsB,YAAczD,KAAK+E,QAAQwR,oBAEjBvW,KAAKkC,WAAWC,cAAc,uBACtCsB,YAAczD,KAAK+E,QAAQyR,sBAE7C,MAAMC,EAAqBzW,KAAKkC,WAAWC,cACzC,wBACFsU,EAAmBlT,UAAY,GAE/B,IAAImT,EAA8C,OAA3BJ,EAAGtW,KAAKoV,0BAAmB,EAAxBkB,EAA0B9I,IAE/CkJ,GAAuB1W,KAAK4U,yBAAyB5L,OAAS,IACjE0N,EAAsB1W,KAAK4U,yBAAyB,GAAGpH,IAGzD,IAAK,MAAMyB,KAAQjP,KAAK4U,yBAA0B,CAChD,MAAM7K,EAAS1B,SAAS8D,cAAc,UACtCpC,EAAOa,MAAQqE,EAAKzB,GACpBzD,EAAOtG,YAAcwL,EAAKlM,KACtBkM,EAAKzB,KAAOkJ,IACd3M,EAAOE,UAAW,GAGpBwM,EAAmBxF,YAAYlH,EACjC,CAEA/J,KAAK2W,cACP,CAEQ,YAAAA,GACN,IAAK3W,KAAK6V,gBACR,OAIF,MAAMe,EAAsB5W,KAAKmC,cAA4B,kCACvD0U,EAA+B7W,KAAKmC,cACxC,6CAEI2U,EAAe9W,KAAK6V,gBAAgB7J,MACpC+K,EAAwB/W,KAAK6V,gBAAgBzH,eAEnDwI,EAAoB7R,QAAQgH,SAAW+K,EAAapT,WAChDqT,GACFF,EAA6B9R,QAAQgH,SAAWgL,EAAsBrT,WACtEmT,EAA6B9R,QAAQiS,KAAO,SAE5CH,EAA6B9R,QAAQiS,KAAO,OAG9C,MAAMC,EAA2BjX,KAAKmC,cACpC,sCACI+U,EAAoClX,KAAKmC,cAC7C,iDAEF,IAAIiU,EACApW,KAAKoV,oBAEPgB,EAA4BpW,KAAKoV,oBAAoBgB,0BAC5CpW,KAAK4U,yBAAyB5L,OAAS,IAGhDoN,EAA4BpW,KAAK4U,yBAAyB,GAAGwB,2BAG3DA,EAA4B,GAC9Ba,EAAyBlS,QAAQgH,SA+EvC,SAAwBC,EAAemL,GACrC,MAAM5K,GAAuC,MAApB4K,EAAAA,EAAwB,GAAK,IACtD,OAAOvS,KAAK4H,MAAMR,GAAS,EAAIO,GACjC,CAlFkD6K,CAAepX,KAAK6V,gBAAgB7J,MAC9EoK,GAA2B1S,WAC7BwT,EAAkCnS,QAAQgH,SAAW/L,KAAK6V,gBAAgB7J,MAAMtI,WAChFwT,EAAkCnS,QAAQiS,KAAO,UAEjDC,EAAyBlS,QAAQgH,SAAW/L,KAAK6V,gBAAgB7J,MAAMtI,WACvEwT,EAAkCnS,QAAQiS,KAAO,OAErD,CAEQ,oBAAAjM,CAAqB7G,GAC3BlE,KAAKqX,0BACP,CAEQ,uBAAA9M,CAAwB9E,GACxBA,EAAMuH,yBAAyBxC,mBAIH,iBAA9B/E,EAAMuH,cAAcpC,MACtB5K,KAAKqV,mBAAmB,gBAExBrV,KAAKqV,mBAAmB,YAG1BrV,KAAKqX,2BACP,CAEQ,wBAAAA,GACN,MAAMjG,EAAQpR,KAAKkC,WAAWC,cAC5B,uDAEF,IAAImV,EAIJ,GAAIlG,GAASA,EAAMjE,QAAS,CAE1B,MAAMoE,EAAoBvR,KAAKkC,WAAWC,cACxC,uCACF,GAAIoP,EAAmB,CACrB,MAAMnG,EAAgBmG,EAAkB3G,MACxC,IAAK,MAAMqE,KAAQjP,KAAK4U,yBACtB,GAAI3F,EAAKzB,KAAOpC,EAAe,CAC7BkM,EAAmBrI,EACnB,KACF,CAEJ,CACF,CAGA,GAAIjP,KAAKoV,sBAAwBkC,EAAkB,KAAAC,EACjDvX,KAAKoV,oBAAsBkC,EAK3B,MAAM7R,EAAQ,IAAI+F,YAAoB,uBAAwB,CAC5DzE,SAAS,EACTC,UAAU,EACVtB,OAAQ,CACN+F,gBAAyC,OAA1B8L,EAAEvX,KAAKoV,0BAAmB,EAAxBmC,EAA0B/J,MAI/CxN,KAAKiH,cAAcxB,EACrB,CAEAzF,KAAK2W,cACP,EAoBF,SAAS3B,EAAwBvP,GAC/B,KAAIA,EAAMc,kBAAkBG,SAAoC,WAAzBjB,EAAMc,OAAOC,UAIhDf,EAAMuH,yBAAyBC,eAAgB,CACjD,MAAMC,EAAQzH,EAAMuH,cAAc7K,cAAgC,uBAC9D+K,IAAUA,EAAMC,UAClBD,EAAM1F,QACN0F,EAAME,QAEV,CACF,CCtaA,SAASoK,EAAkB/R,GACzB,GAAIA,EAAMuH,yBAAyBxC,iBAAkB,CACnD,MAAMiN,EAAQhS,EAAMuH,cAAcrG,QAAqB,UACnD8Q,GACFC,EAAiBD,EAErB,CACF,CAEA,SAASE,EAAgBlS,GACvB,GAAIA,EAAMuH,yBAAyB7B,kBAAmB,CACpD,MAAMsM,EAAQhS,EAAMuH,cAAcrG,QAAqB,UACnD8Q,GACFC,EAAiBD,EAErB,CACF,CAMA,SAASC,EAAiBD,GAExB,MAAMG,EAAYvP,SAASlG,cAAgC,iCAC3D,IAAKyV,EAEH,YADAnN,QAAQkB,KAAK,wEAIf,MAAMkM,EAAiBJ,EAAMtV,cAA2B,oBAElD2V,EAASL,EAAMtV,cAAiC,2BAChD6L,EAA0B,MAAN8J,OAAM,EAANA,EAAQlN,MAElC,IAAImN,EAAiC,MAAdF,OAAc,EAAdA,EAAgB1V,cACrC,4DAOF,GALI6L,IACF+J,EAAiC,MAAdF,OAAc,EAAdA,EAAgB1V,cACjC,8CAAgD6L,EAAoB,QAGnE+J,EAEH,YADAtN,QAAQkB,KAAK,yCAIf,MAAMoC,EAAiBgK,EAAiBpR,QAAQ,mBAChD,IAAIqR,EAAWD,EAAiBhT,QAAQiT,SACnCA,GACCjK,IACFiK,EAAWjK,EAAehJ,QAAQkT,WAItC,MAAMC,EAAeT,EAAMtV,cAAc,OACrC+V,GAAgBF,IAClBE,EAAaC,IAAMH,GAGrB,MAAMI,EAAmBX,EAAMtV,cAA2B,cAC1D,GAAIiW,EAAkB,CACpB,MAAMC,EAAYN,EAAiBhT,QAAQsT,UAC3CD,EAAiB3U,YAAc4U,GAAa,GAC5CD,EAAiBnU,QAAUoU,CAC7B,CAEA,MAAM9K,EAAYQ,EAAehJ,QAAQyI,GAGnCgF,EAAW,0DAA0DjF,MACrEiI,EAAWoC,EAAU7T,iBAAiByO,GAC5C,IAAK,MAAM5E,KAAW4H,EACpB5H,EAAQ9C,SAKV,GADiB+M,EAAe1V,cAAgC,0BACnDgL,QAAS,CAEpB,MAAMS,EAA0BmK,EAAiBO,WAAU,GAC3D1K,EAAQ7I,QAAQwT,QAAU,OAC1B3K,EAAQ7I,QAAQwI,UAAYA,EAExBK,EAAQ7I,QAAQsT,WAClBG,EAAmB5K,EAAS,qBAAsBA,EAAQ7I,QAAQsT,WAGhEzK,EAAQ7I,QAAQ0T,wBAClBD,EAAmB5K,EAAS,eAAgBA,EAAQ7I,QAAQ0T,wBAG9Db,EAAU3G,YAAYrD,EACxB,CAEAgK,EAAU7S,QAAQ4H,OAASC,KAAKC,MAAMnJ,UACxC,CAKA,SAAS8U,EAAmB5K,EAAyB8K,EAAa9N,GAEhE,IAAI+N,EAAkB/K,EAAQzL,cAC5B,8BAA8BuW,OAC3BC,IACHA,EAAkBtQ,SAAS8D,cAAc,oBACzCwM,EAAgB5T,QAAQ2T,IAAMA,EAC9B9K,EAAQqD,YAAY0H,IAGtBA,EAAgB5T,QAAQ6F,MAAQA,CAClC,CC9GA,SAASgO,EAA4BnT,GACnC,KAAMA,EAAMuH,yBAAyB3N,aACnC,OAGF,MAAMwQ,EAAa,IAAI/I,MAAM,6BAA8B,CACzDC,SAAS,EACTC,UAAU,IAEZvB,EAAMuH,cAAc/F,cAAc4I,GAElC,MAAMhB,EAAQxG,SAASlG,cAA4B,mCACnD,IAAK0M,EAEH,YADApE,QAAQkB,KAAK,4CAIf,MAAMkN,EAAehK,EAAM9K,iBAA8B,cACzD,IAAK,MAAM+U,KAAQD,EACbC,EAAK/T,QAAQyI,KAAO/H,EAAMuH,cAAcjI,QAAQgU,UAClDD,EAAK7U,QAAS,EAEd6U,EAAK7U,QAAS,EAIlB4K,EAAM9J,QAAQ+K,KAAO,MACvB,CAQA,SAASkJ,EAAc9U,GACrB,MAAM8L,EAAU3H,SAASlG,cAAc,2BACnC6N,aAAmB3Q,cACrB2Q,EAAQnF,OAAQ,EAEpB,CAMA,SAASoO,EAAc/U,GACrB,MAAM8L,EAAU3H,SAASlG,cAAc,2BACnC6N,aAAmB3Q,cACrB2Q,EAAQnF,OAAQ,EAEpB,CCtDA,SAASqO,EAAgBzT,GACvB,MAAMc,EAASd,EAAMuH,cAErB,KAAMzG,aAAkB4S,mBACtB,OAGF,IAAKC,OAAOC,QACV,OAGF,MAAMC,EAAY,IAAIxS,MAAM,sBAAuB,CAAEC,SAAS,IAC9DsS,QAAQE,SAAS,SAAU,IAAMhT,EAAOU,cAAcqS,GACxD,CHiaKjJ,eAAeC,IAAI,0BACtBD,eAAeE,OAAO,wBAAyBoE,qBC3SjDtL,EAjBA,WACE,MAAMmQ,EAASnR,SAAStE,iBAA8B,kCACtD,IAAK,MAAM0T,KAAS+B,EAAQ,CAC1B,MAAMC,EAAWhC,EAAMtV,cAAgC,0BACnDsX,GACFA,EAASrX,iBAAiB,SAAUoV,GAGtC,MAAMM,EAASL,EAAMtV,cAAiC,2BAClD2V,GACFA,EAAO1V,iBAAiB,SAAUuV,GAGpCD,EAAiBD,EACnB,CACF,GClDApO,EAxBA,WACE,MAAMwF,EAAQxG,SAASlG,cAAc,mCACrC,IAAK0M,EACH,OAGFA,EAAMzM,iBAAiB,eAAgB4W,GACvCnK,EAAMzM,iBAAiB,eAAgB6W,GAEvC,MAAMS,EAAsBrR,SAAStE,iBACnC,qDACF,IAAK,MAAM0N,KAAUiI,EAAqB,CACxC,MAAMZ,EAAOrH,EAAO9K,QAAqB,eACpCmS,GAKLrH,EAAO7N,aAAa,aAAc,uCAClC6N,EAAO1M,QAAQgU,UAAYD,EAAK/T,QAAQyI,GACxCiE,EAAOrP,iBAAiB,QAASwW,IAN/BnH,EAAO5G,OAAQ,CAOnB,CACF,GC7CAxB,EAlBA,WACE,MACMsQ,EADS,IAAIC,gBAAgBC,SAASC,QACjBxJ,IAAI,gBACzByJ,EAA4C,SAApB,MAAXJ,OAAW,EAAXA,EAAaK,eAC1BC,EAAeJ,SAASK,SAASpU,SAAS,eAC9C+T,SAASK,SAASpU,SAAS,YAE7B,GAAIiU,GAAcE,EAAc,CAC9B,MAAME,EAAS9R,SAASlG,cAAiC,kBACzD,IAAKgY,EACH,OAGFA,EAAO/V,MAAMgI,QAAU,QACvB+N,EAAO/X,iBAAiB,QAAS8W,EACnC,CACF","sources":["webpack://lange/./sections/product-detail/fullscreen-gallery/index.ts","webpack://lange/./lib/dom-ready.ts","webpack://lange/./lib/bundle-variant.ts","webpack://lange/./sections/product-detail/subscriptions.ts","webpack://lange/./sections/product-detail/variant-prices.ts","webpack://lange/./sections/product-detail/mbg-modal.ts","webpack://lange/./lib/video.ts","webpack://lange/./sections/product-detail/countdown-timer-override.ts","webpack://lange/./sections/product-detail/product-image-gallery.ts","webpack://lange/./sections/product-detail/subscriptions-v2.ts","webpack://lange/./sections/product-detail/selling-plan-selector/index.ts","webpack://lange/./sections/product-detail/addons.ts","webpack://lange/./sections/product-detail/selling-points.ts","webpack://lange/./sections/product-detail/b2s-promo.ts"],"sourcesContent":["import type { CustomElement } from '@integrabeauty/custom-elements';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * Wraps product carousel gallery to display it as full screen gallery. It also has the zoom in,\n * zoom out option. The gallery is a carousel-element inside this one, and we read the slide count,\n * the current slide, and which slides are videos off of it for the counter and the prev/next\n * arrows, which move it by setting its data-go. It requires product carousel to have high\n * resolution images so images do not look too blurry after zooming.\n *\n * A tap on a slide asks the modal manager to open the gallery in fullscreen mode, and the manager\n * sets `data-modal-state`, which the gallery expands and collapses off.\n */\nclass FullscreenGallery extends HTMLElement implements CustomElement {\n  public static get observedAttributes() {\n    return ['data-modal-state'];\n  }\n\n  declare readonly dataset: {\n    /**\n     * Present while someone drags a zoomed image. Reflected to CSS.\n     */\n    dragging?: 'true';\n\n    /**\n     * Present while the gallery is in fullscreen mode. Reflected to CSS.\n     */\n    expanded?: 'true';\n\n    /**\n     * Whether the gallery is in fullscreen mode, as \"open\". Anything else, or no value at all,\n     * means closed. The modal manager sets this when the gallery asks it to open or close.\n     */\n    modalState: string;\n\n    /**\n     * Present while the active image is zoomed in. Reflected to CSS.\n     */\n    zoom?: 'true';\n  };\n\n  private containerMoved = false;\n  private dragStarted = false;\n  private declare posX: number;\n  private declare posY: number;\n  private scale: number = innerWidth > 430 ? 1.75 : 2.25;\n  private scaleStarted = false;\n  private videoIndexes: number[] = [];\n\n  /**\n   * Current index of the carousel slide (0-based to match the carousel)\n   */\n  private slidesIndex = 0;\n  private slidesTotal = 0;\n\n  private onGalleryOpenBound = this.galleryOpen.bind(this);\n  private onGalleryCloseBound = this.requestClose.bind(this);\n  private onModalDismissRequestedBound = this.onModalDismissRequested.bind(this);\n  private onToggleZoomBound = this.toggleZoom.bind(this);\n  private onSlideVisibleBound = this.onSlideVisible.bind(this);\n  private onCarouselPrevBound = this.onCarouselPrev.bind(this);\n  private onCarouselNextBound = this.onCarouselNext.bind(this);\n  private onCarouselMountedBound = this.onCarouselMounted.bind(this);\n  private onViewportChangedBound = this.onViewportChanged.bind(this);\n\n  private onStartDragBound = this.onStartDrag.bind(this);\n  private onStopDragBound = this.onStopDrag.bind(this);\n  private onMoveDragBound = this.onMoveDrag.bind(this);\n\n  private onStartTouchBound = this.onStartTouch.bind(this);\n  private onStopTouchBound = this.onStopTouch.bind(this);\n  private onMoveTouchBound = this.onMoveTouch.bind(this);\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    const closeButton = this.shadowRoot.querySelector('#close');\n    closeButton?.addEventListener('click', this.onGalleryCloseBound);\n\n    const zoomButton = this.shadowRoot.querySelector('#toggle-zoom');\n    zoomButton?.addEventListener('click', this.onToggleZoomBound);\n    zoomButton?.addEventListener('keydown', this.onToggleZoomBound);\n    zoomButton?.addEventListener('keyup', this.onToggleZoomBound);\n\n    const prevButton = this.shadowRoot.querySelector('.prev');\n    prevButton?.addEventListener('click', this.onCarouselPrevBound);\n    prevButton?.addEventListener('keydown', this.onCarouselPrevBound);\n    prevButton?.addEventListener('keyup', this.onCarouselPrevBound);\n\n    const nextButton = this.shadowRoot.querySelector('.next');\n    nextButton?.addEventListener('click', this.onCarouselNextBound);\n    nextButton?.addEventListener('keydown', this.onCarouselNextBound);\n    nextButton?.addEventListener('keyup', this.onCarouselNextBound);\n\n    const container = this.shadowRoot.querySelector('.inner');\n    container.addEventListener('click', this.onGalleryOpenBound);\n\n    if (typeof visualViewport !== 'undefined') {\n      visualViewport.addEventListener('resize', this.onViewportChangedBound);\n      visualViewport.addEventListener('scroll', this.onViewportChangedBound);\n    }\n\n    addEventListener('scrollend', this.onViewportChangedBound);\n\n    this.addEventListener('carousel-element-mounted', this.onCarouselMountedBound);\n    this.addEventListener('carousel-slide-visible', this.onSlideVisibleBound);\n\n    // The carousel may have mounted before we connected, in which case we missed the event\n    this.readCarousel();\n\n    this.render();\n  }\n\n  public disconnectedCallback() {\n    const closeButton = this.shadowRoot.querySelector('#close');\n    closeButton?.removeEventListener('click', this.onGalleryCloseBound);\n\n    const zoomButton = this.shadowRoot.querySelector('#toggle-zoom');\n    zoomButton?.removeEventListener('click', this.onToggleZoomBound);\n    zoomButton?.removeEventListener('keydown', this.onToggleZoomBound);\n    zoomButton?.removeEventListener('keyup', this.onToggleZoomBound);\n\n    const prevButton = this.shadowRoot.querySelector('.prev');\n    prevButton?.removeEventListener('click', this.onCarouselPrevBound);\n    prevButton?.removeEventListener('keydown', this.onCarouselPrevBound);\n    prevButton?.removeEventListener('keyup', this.onCarouselPrevBound);\n\n    const nextButton = this.shadowRoot.querySelector('.next');\n    nextButton?.removeEventListener('click', this.onCarouselNextBound);\n    nextButton?.removeEventListener('keydown', this.onCarouselNextBound);\n    nextButton?.removeEventListener('keyup', this.onCarouselNextBound);\n\n    if (typeof visualViewport !== 'undefined') {\n      visualViewport.removeEventListener('resize', this.onViewportChangedBound);\n      visualViewport.removeEventListener('scroll', this.onViewportChangedBound);\n    }\n\n    removeEventListener('scrollend', this.onViewportChangedBound);\n\n    const container = this.shadowRoot.querySelector('.inner');\n    container?.removeEventListener('click', this.onGalleryOpenBound);\n\n    // The listener is only added while the gallery is open, but if we are removed while open the\n    // window would hold onto us and keep sending us dismiss requests from a page we left.\n    removeEventListener('modal-dismiss-requested', this.onModalDismissRequestedBound);\n\n    this.removeEventListener('carousel-element-mounted', this.onCarouselMountedBound);\n    this.removeEventListener('carousel-slide-visible', this.onSlideVisibleBound);\n  }\n\n  public attributeChangedCallback(name: string, oldValue: string, newValue: string) {\n    this.initializeShadowRoot();\n\n    if (!this.isConnected || name !== 'data-modal-state' || oldValue === newValue) {\n      return;\n    }\n\n    if (newValue === 'open') {\n      this.expand();\n    } else if (oldValue === 'open') {\n      this.collapse();\n    }\n  }\n\n  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open' });\n      this.shadowRoot.innerHTML = `<style>${styles}</style>${html}`;\n    }\n  }\n\n  private render() {\n    // The index is 0 based but visitors expect it to start at 1\n    const visualCurrentSlideNumber = this.slidesIndex + 1;\n\n    const currentSlide = this.shadowRoot.querySelector('.current-slide');\n    currentSlide.textContent = visualCurrentSlideNumber.toString();\n\n    const totalSlidesElement = this.shadowRoot.querySelector('.total-slides');\n    totalSlidesElement.textContent = this.slidesTotal.toString();\n\n    const counter = this.shadowRoot.querySelector('.counter');\n    counter?.setAttribute('aria-label', [\n      'Slide',\n      visualCurrentSlideNumber.toString(),\n      'of',\n      this.slidesTotal.toString()\n    ].join(' '));\n\n    // Decide whether to show or hide the arrows\n    const arrows = this.shadowRoot.querySelectorAll<HTMLElement>('.prev, .next');\n    if (this.slidesTotal <= 1) {\n      for (const arrow of arrows) {\n        arrow.hidden = true;\n      }\n    } else {\n      // The number of slides isn't expected to change, but the render method is always called the\n      // first time with this.slidesTotal === 0. After that, it gets updated with the actual value.\n      for (const arrow of arrows) {\n        arrow.hidden = false;\n      }\n    }\n  }\n\n  private onViewportChanged(_event: Event) {\n    this.updateViewportVars();\n  }\n\n  private updateViewportVars() {\n    const style = this.style;\n    let viewport = {\n      width: innerWidth,\n      height: innerHeight,\n      offsetTop: 0,\n      offsetLeft: 0,\n      scale: 1\n    };\n\n    if (typeof visualViewport !== 'undefined') {\n      viewport = visualViewport;\n    }\n\n    style.setProperty('--viewport-width', Math.ceil(viewport.width) + 'px');\n    style.setProperty('--viewport-height', Math.ceil(viewport.height) + 'px');\n    style.setProperty('--viewport-top', viewport.offsetTop + 'px');\n    style.setProperty('--viewport-left', viewport.offsetLeft + 'px');\n    style.setProperty('--viewport-scale', viewport.scale.toString());\n  }\n\n  private onCarouselMounted(_event: Event) {\n    this.readCarousel();\n    this.render();\n  }\n\n  /**\n   * Reads the slide count, the current slide, and the video slides off the carousel once it has\n   * mounted. Does nothing before then.\n   */\n  private readCarousel() {\n    const carousel = this.querySelector('carousel-element');\n    if (!carousel?.dataset.slideCount) {\n      return;\n    }\n\n    this.slidesTotal = parseInt(carousel.dataset.slideCount, 10);\n    this.slidesIndex = parseInt(carousel.dataset.index, 10);\n\n    this.videoIndexes = [];\n    const slides = carousel.querySelectorAll('[data-carousel-slide]');\n    for (const [index, slide] of slides.entries()) {\n      if (slide.classList.contains('product-single-media')) {\n        this.videoIndexes.push(index);\n      }\n    }\n  }\n\n  private onSlideVisible(event: GlobalEventHandlersEventMap['carousel-slide-visible']) {\n    this.slidesIndex = event.detail.index;\n    this.render();\n  }\n\n  /**\n   * Moves the carousel to the slide at the given index.\n   */\n  private goTo(index: number) {\n    const carousel = this.querySelector('carousel-element');\n    if (carousel) {\n      carousel.dataset.go = index.toString();\n    }\n  }\n\n  private onCarouselPrev(event?: Event) {\n    if (event instanceof KeyboardEvent) {\n      if (['Space', 'Enter'].includes(event.code)) {\n        event.preventDefault();\n      } else {\n        return;\n      }\n\n      if (event.type === 'keydown') {\n        return;\n      }\n    }\n\n    this.clearZoom();\n\n    // skip video slides as we do not want to display them in fullscreen mode\n    const total = this.slidesTotal;\n    let prevIndex = this.slidesIndex === 0 ? total - 1 : this.slidesIndex - 1;\n    while (this.videoIndexes.includes(prevIndex)) {\n      prevIndex = prevIndex === 0 ? total - 1 : prevIndex - 1;\n      // make sure we do not loop endlessly\n      if (prevIndex === this.slidesIndex) {\n        break;\n      }\n    }\n\n    this.goTo(prevIndex);\n  }\n\n  private onCarouselNext(event?: Event) {\n    if (event instanceof KeyboardEvent) {\n      if (['Space', 'Enter'].includes(event.code)) {\n        event.preventDefault();\n      } else {\n        return;\n      }\n\n      if (event.type === 'keydown') {\n        return;\n      }\n    }\n\n    this.clearZoom();\n\n    // skip video slides as we do not want to display them in fullscreen mode\n    const total = this.slidesTotal;\n    let nextIndex = this.slidesIndex + 1 === total ? 0 : this.slidesIndex + 1;\n    while (this.videoIndexes.includes(nextIndex)) {\n      nextIndex = nextIndex + 1 === total ? 0 : nextIndex + 1;\n      // make sure we do not loop endlessly\n      if (nextIndex === this.slidesIndex) {\n        break;\n      }\n    }\n\n    this.goTo(nextIndex);\n  }\n\n  private galleryOpen(event: Event) {\n    // prevent opening the video in full screen\n    if (this.videoIndexes.includes(this.slidesIndex)) {\n      return;\n    }\n\n    // The event handler is installed on an area that contains some clickable elements.\n    // Unfortunately the carousel does not use `stopPropagation()` so we receive the event even if\n    // it was a pagination click etc. Filter out such events for a better UX and accessibility.\n    const target = event.target;\n    if (target instanceof HTMLElement &&\n      (target.tagName === 'BUTTON' || target.parentElement?.tagName === 'BUTTON')) {\n      return;\n    }\n\n    // The carousel's own controls, like its drag bar, sit inside this element too, so only a tap on\n    // one of the slides opens the gallery\n    if (target instanceof Element && target.closest('carousel-element') &&\n      !target.closest('[data-carousel-slide]')) {\n      return;\n    }\n\n    // Nothing gets pushed onto the modal manager's focus stack here, since the gallery opens from a\n    // click on a slide rather than from a focusable opener. So when the gallery closes, the manager\n    // may move focus to an older opener still on the page, such as the header cart button.\n    this.requestOpen();\n  }\n\n  /**\n   * Asks the modal manager to open the gallery. The manager sets our data-modal-state, which\n   * expands it.\n   */\n  private requestOpen() {\n    if (this.dataset.modalState === 'open') {\n      return;\n    }\n\n    const event = new Event('modal-open-requested', { bubbles: true, composed: true });\n    this.dispatchEvent(event);\n  }\n\n  private requestClose() {\n    if (this.dataset.modalState !== 'open') {\n      return;\n    }\n\n    const event = new Event('modal-close-requested', { bubbles: true, composed: true });\n    this.dispatchEvent(event);\n  }\n\n  private expand() {\n    const container = this.shadowRoot.querySelector<HTMLElement>('.inner');\n    container.addEventListener('mousedown', this.onStartDragBound);\n    container.addEventListener('mouseup', this.onStopDragBound);\n    container.addEventListener('touchstart', this.onStartTouchBound, { passive: false });\n    container.addEventListener('touchend', this.onStopTouchBound, { passive: false });\n\n    addEventListener('modal-dismiss-requested', this.onModalDismissRequestedBound);\n\n    this.dataset.expanded = 'true';\n    this.updateViewportVars();\n\n    const carousel = this.querySelector('carousel-element');\n    if (carousel) {\n      carousel.dataset.controlsHidden = 'true';\n    }\n\n    const selector = '#toggle-zoom, .prev>[role=\"button\"], .next>[role=\"button\"]';\n    const focusable = this.shadowRoot.querySelectorAll<HTMLElement>(selector);\n    for (const focusableEl of focusable) {\n      focusableEl.tabIndex = 0;\n    }\n\n    // focus on the first active element\n    const zoomButton = this.shadowRoot.querySelector<HTMLElement>('#toggle-zoom');\n    zoomButton.focus({ focusVisible: true });\n  }\n\n  /**\n   * Closes the gallery on a dismiss request from the modal manager, which it sends for an escape\n   * key press or a backdrop click. This listener is only attached while the gallery is open.\n   */\n  private onModalDismissRequested(_event: WindowEventMap['modal-dismiss-requested']) {\n    this.requestClose();\n  }\n\n  private collapse() {\n    this.clearZoom();\n\n    const selector = '#toggle-zoom, .prev>[role=\"button\"], .next>[role=\"button\"]';\n    const focusable = this.shadowRoot.querySelectorAll<HTMLElement>(selector);\n    for (const focusableEl of focusable) {\n      focusableEl.tabIndex = -1;\n    }\n\n    delete this.dataset.expanded;\n\n    const carousel = this.querySelector('carousel-element');\n    if (carousel) {\n      delete carousel.dataset.controlsHidden;\n    }\n\n    const container = this.shadowRoot.querySelector<HTMLElement>('.inner');\n    container.removeEventListener('mousedown', this.onStartDragBound);\n    container.removeEventListener('mouseup', this.onStopDragBound);\n    container.removeEventListener('touchstart', this.onStartTouchBound);\n    container.removeEventListener('touchend', this.onStopTouchBound);\n\n    removeEventListener('modal-dismiss-requested', this.onModalDismissRequestedBound);\n  }\n\n  private clearZoom() {\n    const carouselEl = this.querySelector('carousel-element');\n    if (!carouselEl) {\n      return;\n    }\n\n    const zoomEl = this.shadowRoot.querySelector('#toggle-zoom');\n    zoomEl?.setAttribute('aria-checked', 'false');\n\n    delete this.dataset.zoom;\n    carouselEl.style.transform = 'translate(0, 0) scale(1)';\n  }\n\n  private toggleZoom(event?: Event) {\n    if (event instanceof KeyboardEvent) {\n      if (['Space', 'Enter'].includes(event.code)) {\n        event.preventDefault();\n      } else {\n        return;\n      }\n\n      if (event.type === 'keydown') {\n        return;\n      }\n    }\n\n    const carouselEl = this.querySelector('carousel-element');\n    if (!carouselEl) {\n      return;\n    }\n\n    if (this.dataset.zoom === 'true') {\n      this.clearZoom();\n      return;\n    }\n\n    const zoomEl = this.shadowRoot.querySelector('#toggle-zoom');\n    zoomEl?.setAttribute('aria-checked', 'true');\n\n    carouselEl.style.transform = `translate(0px, 0px) scale(${this.scale})`;\n    this.dataset.zoom = 'true';\n  }\n\n  private onStartDrag(event: MouseEvent) {\n    const carouselEl = this.querySelector('carousel-element');\n    if (!carouselEl) {\n      return;\n    }\n\n    const matches = carouselEl.style.transform.match(/(-?\\d+)/g) || [];\n\n    this.posX = event.clientX - (matches[0] ? parseInt(matches[0], 10) : carouselEl.offsetLeft);\n    this.posY = event.clientY - (matches[1] ? parseInt(matches[1], 10) : carouselEl.offsetTop);\n\n    if (this.dataset.zoom === 'true') {\n      this.dataset.dragging = 'true';\n      this.dragStarted = true;\n      document.body.addEventListener('mousemove', this.onMoveDragBound);\n    }\n  }\n\n  private onStopDrag(_event: MouseEvent) {\n    const carouselEl = this.querySelector('carousel-element');\n    if (!carouselEl) {\n      return;\n    }\n\n    if (!this.containerMoved) {\n      this.toggleZoom();\n    }\n\n    this.containerMoved = false;\n    this.dragStarted = false;\n    delete this.dataset.dragging;\n    document.body.removeEventListener('mousemove', this.onMoveDragBound);\n  }\n\n  private onMoveDrag(event: MouseEvent) {\n    const carouselEl = this.querySelector('carousel-element');\n    if (!carouselEl) {\n      return;\n    }\n\n    this.containerMoved = true;\n    if (!this.dragStarted) {\n      return;\n    }\n\n    let offsetX = event.clientX - this.posX;\n    let offsetY = event.clientY - this.posY;\n\n    const container = this.shadowRoot.querySelector('.inner');\n    const { width, height } = carouselEl.getBoundingClientRect();\n\n    const constraintX = Math.floor((width - container.clientWidth) / 2);\n    const constraintY = Math.floor((height - container.clientHeight) / 2);\n\n    if (!(offsetX <= constraintX && offsetX >= constraintX * -1)) {\n      offsetX = offsetX > 0 ? constraintX : -1 * constraintX;\n    }\n\n    if (!(offsetY <= constraintY && offsetY >= constraintY * -1)) {\n      offsetY = offsetY > 0 ? constraintY : -1 * constraintY;\n    }\n\n    carouselEl.style.transform = `translate(${offsetX}px,${offsetY}px) scale(${this.scale})`;\n  }\n\n  private onStartTouch(event: TouchEvent) {\n    event.preventDefault();\n\n    const carouselEl = this.querySelector('carousel-element');\n    if (!carouselEl) {\n      return;\n    }\n\n    if (event.touches.length === 1) {\n      const matches = carouselEl.style.transform.match(/(-?\\d+(\\.\\d+)?)/g) || [];\n\n      const x = matches[0] ? parseInt(matches[0], 10) : carouselEl.offsetLeft;\n      const y = matches[1] ? parseInt(matches[1], 10) : carouselEl.offsetTop;\n\n      this.posX = event.touches[0].pageX - x;\n      this.posY = event.touches[0].pageY - y;\n    }\n\n    if (this.dataset.zoom === 'true') {\n      this.dataset.dragging = 'true';\n      this.dragStarted = true;\n      document.body.addEventListener('touchmove', this.onMoveTouchBound);\n    }\n  }\n\n  private onStopTouch(event: TouchEvent) {\n    event.preventDefault();\n\n    const carouselEl = this.querySelector('carousel-element');\n    if (!carouselEl) {\n      return;\n    }\n\n    if (event.touches.length < 1) {\n      if (!this.containerMoved) {\n        this.toggleZoom();\n      }\n\n      this.dragStarted = false;\n      this.containerMoved = false;\n      this.scaleStarted = false;\n\n      delete this.dataset.dragging;\n      document.body.removeEventListener('touchmove', this.onMoveTouchBound);\n    }\n  }\n\n  private onMoveTouch(event: TouchEvent) {\n    const carouselEl = this.querySelector('carousel-element');\n    if (!carouselEl) {\n      return;\n    }\n\n    this.containerMoved = true;\n    if (!this.dragStarted) {\n      return;\n    }\n\n    if (event.touches.length === 1) {\n      if (this.scaleStarted) {\n        return;\n      }\n\n      const container = this.shadowRoot.querySelector('.inner');\n      const { width, height } = carouselEl.getBoundingClientRect();\n      let offsetX = event.touches[0].pageX - this.posX;\n      let offsetY = event.touches[0].pageY - this.posY;\n\n      const constraintX = Math.floor((width - container.clientWidth) / 2);\n      const constraintY = Math.floor((height - container.clientHeight) / 2);\n\n      if (!(offsetX <= constraintX && offsetX >= constraintX * -1)) {\n        offsetX = offsetX > 0 ? constraintX : -1 * constraintX;\n      }\n\n      if (!(offsetY <= constraintY && offsetY >= constraintY * -1)) {\n        offsetY = offsetY > 0 ? constraintY : -1 * constraintY;\n      }\n\n      carouselEl.style.transform = `translate(${offsetX}px,${offsetY}px) scale(${this.scale})`;\n    }\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'fullscreen-gallery': FullscreenGallery;\n  }\n}\n\nif (!customElements.get('fullscreen-gallery')) {\n  customElements.define('fullscreen-gallery', FullscreenGallery);\n}\n","/**\n * Releases the handle returned from the ready() function.\n *\n * This is idempotent. Additional calls are pointless but harmless.\n */\nexport type ReleaseHandle = () => void;\n\n/**\n * A subset of events that pertain to document ready state.\n */\ntype LoadingEventMap = Pick<DocumentEventMap, 'DOMContentLoaded' | 'load'>;\n\n/**\n * Calls the provided callback when the document is ready. This works both when the document is\n * already ready and when it is not, so callers do not have to care. This exists because it ensures\n * that each caller consistently expresses its dependency on document readiness, and to save a bit\n * of boilerplate.\n *\n * A document is considered ready when it has been parsed. This corresponds to the ready state of\n * 'interactive'. When the document has been fully loaded, the ready state is 'complete'. The\n * callback runs either way, so callers do not have to check which one they are in.\n *\n * Ready means the html is parsed and the DOM is there to query. It does not mean the page's other\n * scripts have run. Deferred scripts and module scripts run after the ready state turns\n * 'interactive' and before DOMContentLoaded fires, so a callback here can run before them. When a\n * caller needs some other script to have run already, this is the wrong tool, and it should wait on\n * whatever that script produces instead.\n *\n * That remedy has a catch of its own when the calling bundle is loaded with async. An async bundle\n * can execute after DOMContentLoaded has already fired, so a plain listener for that event never\n * runs and the work silently never happens. Nothing here can detect that, because by the time this\n * file executes the event is long gone. Catching it needs something registered at parse time,\n * before any bundle loads.\n *\n * There is a second trap right next to that one, and it applies only when waiting for\n * DOMContentLoaded. The ready state stays 'interactive' after DOMContentLoaded has fired, all the\n * way until 'complete', so the ready state cannot tell you whether DOMContentLoaded is still coming\n * or has already gone. Waiting for load is not exposed to this, because the ready state turns\n * 'complete' and load fires in the same task, so seeing 'complete' means load has fired too.\n *\n * When the document is already ready, the callback is invoked via setTimeout. This way, if the\n * callback throws, the error surfaces to the global error handler in a way that is similar to what\n * happens when an event listener throws.\n *\n * We might one day want to move this to an NPM library.\n *\n * Beware of a quirk here. setTimeout yields, and the busier the frontend, the longer it takes to\n * fire. Page load is exactly when the frontend is busiest. The timeout is a floor and not a\n * guarantee, so there is no real bound on how long that yield lasts. What this means is that using\n * this library can introduce more delay than you might expect. Compare code that runs on async load\n * and checks ready state and invokes immediately to code that runs after setTimeout. The former\n * runs in the same tick, the latter runs in \"some later tick\". That later tick can be surprisingly\n * much later. That can cause initial paint issues.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event\n * @see https://html.spec.whatwg.org/multipage/parsing.html#the-end\n */\nexport function ready(callback: (event?: Event) => void,\n  type: keyof LoadingEventMap = 'DOMContentLoaded') {\n  if (document.readyState === 'complete' || (type === 'DOMContentLoaded' &&\n    document.readyState === 'interactive')) {\n    const timer = setTimeout(callback, 0);\n    const handle: ReleaseHandle = () => clearTimeout(timer);\n    return handle;\n  } else {\n    addEventListener(type, callback);\n    const handle: ReleaseHandle = () => removeEventListener(type, callback);\n    return handle;\n  }\n}\n","type ProductElement = HTMLElementTagNameMap['product-element'];\ntype BundleOption = HTMLElementTagNameMap['bundle-option'];\ntype VariantElement = HTMLElementTagNameMap['variant-element'];\n\n/**\n * One of the inner product's option groups, cut down to the two fields we read. The option groups\n * the product variant option changed event carries already fit this shape, so a listener can pass\n * them straight in.\n */\ninterface SelectOption {\n  /**\n   * The name of the option\n   *\n   * @example \"Size\"\n   * @example \"Color\"\n   */\n  name: string;\n\n  /**\n   * The value the shopper chose for the option\n   *\n   * @example \"Black\"\n   */\n  selected_option: string;\n}\n\n/**\n * Records the chosen options of one product inside a bundle and returns the bundle variant that all\n * of the bundle's current choices add up to, or nothing when the bundle has no variant for that\n * combination.\n *\n * A bundle made with the Shopify Bundles app is its own product, and each of its variants stands\n * for one combination of the options of the products inside it. The page shows a picker for each\n * product inside the bundle rather than one for the bundle, so when a shopper changes one of those\n * pickers we call this to work out which bundle variant the pickers now add up to.\n *\n * Choices carry over from one call to the next, so the result matches the pickers on screen even\n * after a change that landed on a combination with no bundle variant. Recording the same choice\n * twice changes nothing, so every script reacting to the same picker change can call this and they\n * all get the same answer, whichever runs first.\n *\n * An option of the inner product that the bundle has no bundle option for is left out. Some bundles\n * leave an inner product's option out on purpose, such as the Switch It Up bundles, which come in a\n * bundle per color and so carry no color option.\n *\n * @param bundle The bundle's product element\n * @param productTitle The title of the inner product whose picker changed\n * @param options The inner product's option groups with their chosen values\n */\nexport function select(bundle: ProductElement, productTitle: string, options: SelectOption[]) {\n  // We keep the current choice on the bundle option elements rather than starting from the bundle's\n  // selected variant, so a change that lands on a combination with no bundle variant still counts\n  // when the next change builds on it. Each bundle option element is named by the inner product's\n  // title and the option's name.\n  for (const option of options) {\n    const bundleOption = bundle.querySelector<BundleOption>(\n      `bundle-option[data-name=\"${productTitle} (${option.name})\"]`);\n    if (bundleOption) {\n      bundleOption.dataset.selected = option.selected_option;\n    }\n  }\n\n  // The bundle variant titles join one value per bundle option, in the order of each bundle\n  // option's position, like \"Black / Oval 75mm\"\n  const values: string[] = [];\n  const bundleOptions = bundle.querySelectorAll('bundle-option');\n  for (const bundleOption of bundleOptions) {\n    // Positions count from 1, and the array counts from 0\n    const position = parseInt(bundleOption.dataset.position, 10);\n    const index = position - 1;\n    values[index] = bundleOption.dataset.selected;\n  }\n\n  const title = values.join(' / ');\n  return bundle.querySelector<VariantElement>(`variant-element[data-title=\"${title}\"]`);\n}\n","import { ready } from '../../lib/dom-ready.js';\n\nfunction onPurchaseOptionChanged(event: Event) {\n  if (!(event.target instanceof HTMLInputElement)) {\n    return;\n  }\n\n  const options = document.querySelector<HTMLElement>('.subscription-options');\n  if (!options) {\n    console.error('subscription-options not found');\n    return;\n  }\n\n  const plans = document.querySelector('.selling-plans');\n  if (!plans) {\n    console.error('selling-plans not found');\n    return;\n  }\n\n  if (event.target.value === 'subscription') {\n    options.inert = false;\n    options.classList.remove('collapsed');\n\n    plans.addEventListener('change', onSellingPlanChanged);\n    const selected =\n      document.querySelector<HTMLOptionElement>('select.selling-plans option:checked');\n\n    if (selected) {\n      updateSellingPlanIdOnAddToBag(selected?.value);\n      dispatchSellingPlanChanged(event.target, selected.value);\n    }\n  } else if (event.target.value === 'one-time') {\n    plans.removeEventListener('change', onSellingPlanChanged);\n    updateSellingPlanIdOnAddToBag('');\n    dispatchSellingPlanChanged(event.target);\n    options.classList.add('collapsed');\n    options.inert = true;\n  }\n}\n\nfunction onSellingPlanChanged(event: Event) {\n  if (event.target instanceof HTMLSelectElement) {\n    const selected = event.target.options[event.target.selectedIndex];\n    const sellingPlanId = selected.value;\n\n    updateSellingPlanIdOnAddToBag(sellingPlanId);\n    updateProductDiscountPercentage(sellingPlanId);\n    updateProductSellingPlanPrices(sellingPlanId);\n    dispatchSellingPlanChanged(event.target, sellingPlanId);\n  }\n}\n\n/**\n * Tells the page which selling plan the shopper picked, with the same event the newer subscription\n * pickers send. One-time purchase has no selling plan, so it goes out as undefined.\n */\nfunction dispatchSellingPlanChanged(target: EventTarget, sellingPlanId?: string) {\n  type SellingPlanChangedEvent = GlobalEventHandlersEventMap['selling-plan-changed'];\n  type Detail = SellingPlanChangedEvent['detail'];\n\n  const event = new CustomEvent<Detail>('selling-plan-changed', {\n    bubbles: true,\n    composed: true,\n    detail: {\n      selling_plan_id: sellingPlanId\n    }\n  });\n\n  target.dispatchEvent(event);\n}\n\nfunction updateProductDiscountPercentage(sellingPlanId: string) {\n  const sellingPlanEl = document.querySelector<HTMLElement>(\n    `subscription-plan[data-id=\"${sellingPlanId}\"]`);\n\n  if (!sellingPlanEl) {\n    console.warn('subscription-plan element not found for selling plan id: ', sellingPlanId);\n    return;\n  }\n\n  const priceEl = document.querySelector<HTMLElementTagNameMap['price-element']>(\n    '.subscription-wrapper price-element.subscription-price');\n  if (!priceEl) {\n    return;\n  }\n\n  const compareEl = document.querySelector<HTMLElementTagNameMap['price-element']>(\n    '.subscription-wrapper price-element.subscription-price + price-element.price-discount');\n  if (!compareEl) {\n    return;\n  }\n\n  const newPrice = parseInt(sellingPlanEl.dataset.subunits, 10);\n  const price = parseInt(priceEl.dataset.subunits, 10);\n\n  // If the subscription price is lower than the original price and the compare at price is null we\n  // must update the compare at price to be the original price so that the discount badge will show\n  // with the correct discount percentage.\n  if (!compareEl.dataset.subunits && newPrice < price) {\n    priceEl.dataset.subunits = newPrice.toString();\n    compareEl.dataset.subunits = price.toString();\n  }\n\n  // If new price matches the existing compare at price we zero out the compare at price subunit\n  // attributes to prevent showing a discount badge when there is no discount.\n  if (parseInt(compareEl.dataset.subunits, 10) === newPrice) {\n    compareEl.dataset.subunits = '';\n  }\n\n  // Extract final compare at price after potential adjustments above to ensure discount badge is\n  // calculated correctly based on the actual values that will be rendered in the price elements.\n  const comparePrice = compareEl.dataset.subunits ? parseInt(compareEl.dataset.subunits, 10) : 0;\n\n  let badgeEl = document.querySelector<HTMLElement>('span.subscription.discount-copy');\n  if (newPrice < comparePrice) {\n    if (!badgeEl) {\n      badgeEl = document.createElement('span');\n      badgeEl.classList.add('subscription');\n      badgeEl.classList.add('discount-copy');\n      badgeEl.style.display = 'inline';\n      compareEl.insertAdjacentElement('afterend', badgeEl);\n    }\n\n    const discountValue = comparePrice - newPrice;\n    const discountPercent = Math.round((discountValue / comparePrice) * 100);\n    badgeEl.innerHTML = `${discountPercent}% OFF`;\n  } else if (badgeEl) {\n    // If there is no discount but the badge exists, remove it.\n    badgeEl.remove();\n  }\n}\n\nfunction updateProductSellingPlanPrices(sellingPlanId: string) {\n  const sellingPlanEl = document.querySelector<HTMLElement>(\n    `subscription-plan[data-id=\"${sellingPlanId}\"]`);\n\n  if (!sellingPlanEl) {\n    console.warn('subscription-plan element not found for selling plan id: ', sellingPlanId);\n    return;\n  }\n\n  const priceEls = document.querySelectorAll<HTMLElementTagNameMap['price-element']>(\n    'price-element.subscription-price');\n  if (priceEls.length === 0) {\n    console.warn('price-element with class subscription-price not found', sellingPlanId);\n  }\n\n  for (const priceEl of priceEls) {\n    priceEl.dataset.subunits = sellingPlanEl.dataset.subunits;\n  }\n}\n\n/**\n * Shows the subscription prices for the given variant. The server works the plan prices out for the\n * variant selected on page load, and variants can have different prices, so each plan also carries\n * its price for every variant.\n */\nexport function renderVariant(variantId: number) {\n  type PlanElement = HTMLElementTagNameMap['subscription-plan'];\n  type VariantElement = HTMLElementTagNameMap['variant-element'];\n\n  const plans = document.querySelectorAll<PlanElement>('#product-detail-section subscription-plan');\n  for (const plan of plans) {\n    const variant = plan.querySelector<VariantElement>(\n      `:scope > variant-element[data-id=\"${variantId}\"]`);\n    if (variant) {\n      plan.dataset.subunits = variant.dataset.price;\n    }\n  }\n\n  const selected = document.querySelector<HTMLOptionElement>(\n    '#product-detail-section select.selling-plans option:checked');\n  if (!selected) {\n    return;\n  }\n\n  const variant = document.querySelector<VariantElement>(\n    `#product-detail-section subscription-plan[data-id=\"${selected.value}\"] >\n    variant-element[data-id=\"${variantId}\"]`);\n  if (!variant) {\n    return;\n  }\n\n  const priceEl = document.querySelector<HTMLElementTagNameMap['price-element']>(\n    '.subscription-wrapper price-element.subscription-price');\n  if (!priceEl) {\n    return;\n  }\n\n  const compareEl = document.querySelector<HTMLElementTagNameMap['price-element']>(\n    '.subscription-wrapper price-element.subscription-price + price-element.price-discount');\n  if (!compareEl) {\n    return;\n  }\n\n  // We start from what the server renders for a variant, the selected plan's price next to the\n  // variant's own compare at price, and let the plan updates work out the badge from there, the\n  // same as on page load.\n  priceEl.dataset.subunits = variant.dataset.price;\n  compareEl.dataset.subunits = variant.dataset.compareAtPrice;\n  updateProductDiscountPercentage(selected.value);\n  updateProductSellingPlanPrices(selected.value);\n}\n\nfunction updateSellingPlanIdOnAddToBag(sellingPlanId: string) {\n  const addToBag = document.querySelector<HTMLElementTagNameMap['add-to-cart']>(\n    '#product-detail-section add-to-cart');\n  if (!addToBag) {\n    console.warn('add-to-cart not found');\n    return;\n  }\n\n  addToBag.dataset.sellingPlanId = sellingPlanId;\n  addToBag.dataset.update = Date.now().toString();\n}\n\nfunction onPurchaseOptionClick(event: MouseEvent) {\n  // Ignore events from the selling plans dropdown\n  if (event.target instanceof Element && event.target?.tagName === 'SELECT') {\n    return;\n  }\n\n  if (event.currentTarget instanceof HTMLDivElement) {\n    const input = event.currentTarget.querySelector<HTMLInputElement>('input[type=\"radio\"]');\n    if (input && !input.checked) {\n      input.focus();\n      input.click();\n    }\n  }\n}\n\nfunction onReady() {\n  const optionWrappers = document.querySelectorAll<HTMLElement>(\n    '#product-detail-section .purchase-option');\n  for (const wrapper of optionWrappers) {\n    const radio = wrapper.querySelector<HTMLInputElement>('input[type=\"radio\"]');\n    if (radio) {\n      radio.addEventListener('change', onPurchaseOptionChanged);\n    }\n\n    // for better ux allow clicking anywhere in the wrapper to activate the radio button\n    wrapper.addEventListener('click', onPurchaseOptionClick);\n  }\n\n  // The page starts on the subscription when the url names one of its selling plans. Picking the\n  // subscription is what starts listening to the frequency dropdown, so start listening here too.\n\n  const subscriptionRadio = document.querySelector<HTMLInputElement>(\n    '#product-detail-section .purchase-option input[value=\"subscription\"]');\n  if (subscriptionRadio?.checked) {\n    document.querySelector('#product-detail-section .selling-plans')\n      ?.addEventListener('change', onSellingPlanChanged);\n  }\n\n  // get active subscription plan id\n  const activeSellingPlan = document.querySelector<HTMLOptionElement>(\n    '#product-detail-section select.selling-plans option:checked');\n\n  // update pricing and discount percents in case selling plan has additional discounts\n  if (activeSellingPlan) {\n    updateProductDiscountPercentage(activeSellingPlan.value);\n    updateProductSellingPlanPrices(activeSellingPlan.value);\n  }\n}\n\ntype ProductSubscriptionOptionChangedEvent = CustomEvent<Partial<{\n  selling_plan_id?: string;\n}>>;\n\ndeclare global {\n  interface WindowEventMap {\n    'product-subscription-option-changed': ProductSubscriptionOptionChangedEvent;\n  }\n}\n\nready(onReady);\n","import { select as selectBundleVariant } from '../../lib/bundle-variant.js';\nimport { ready } from '../../lib/dom-ready.js';\nimport { renderVariant as renderSubscriptionPrices } from './subscriptions.js';\n\n// The server renders the prices on the product page for the variant selected on page load, and\n// variants can have different prices, so when the shopper picks another variant we show that\n// variant's prices. We read them from the product price on the page, so every price matches the\n// regular price shown next to it.\n//\n// The discount messages show the price with the product's discount code. There are two kinds. One\n// is the snippet message in the price box and the subscription selector, and the other is the\n// product discount message element on the default product page, which can also render as the\n// discount modal.\n//\n// The installment message is the exception and reads the variant prices it carries itself. The\n// server works its amount out from the variant's own price, while an offer template can put the\n// offer's price in the product price, so reading the product price could change the amount on a\n// switch that keeps the same price.\n//\n// The subscription option's prices come from its selling plans, and the subscriptions script\n// already draws them when the shopper picks a plan, so we hand it the variant and it draws them\n// again. The gift with purchase section's subscription option shows the one-time price instead, so\n// there we copy the variant's prices from the one-time option.\n//\n// On a value set the picker belongs to a product inside the bundle, so its change carries that\n// product's variant rather than the bundle's. We work out which bundle variant the pickers add up\n// to, and then the bundle's regular price, its bundle total and its discount messages follow that\n// variant. The product price follows the shopper's own picker by itself, but not a bundle variant\n// worked out from another product's picker, so we point it at the bundle variant ourselves.\n\ntype ProductElement = HTMLElementTagNameMap['product-element'];\ntype VariantElement = HTMLElementTagNameMap['variant-element'];\n\nfunction onProductVariantOptionChanged(\n  event: GlobalEventHandlersEventMap['product-variant-option-changed']) {\n  const productDetail = event.currentTarget;\n  if (!(productDetail instanceof HTMLElement)) {\n    return;\n  }\n\n  let productId = event.detail.id;\n  let variantId = event.detail.selected_variant_id;\n\n  if (event.detail.bundle_product_id) {\n    const bundle = productDetail.querySelector<ProductElement>(\n      `add-to-cart > product-element[data-id=\"${event.detail.bundle_product_id}\"]`);\n    if (!bundle) {\n      return;\n    }\n\n    const variant = selectBundleVariant(bundle, event.detail.title, event.detail.option_groups);\n    if (!variant) {\n      return;\n    }\n\n    productId = event.detail.bundle_product_id;\n    variantId = parseInt(variant.dataset.id, 10);\n    renderBundlePrices(productDetail, productId, variantId);\n  }\n\n  renderSnippetMessages(productDetail, variantId);\n  renderProductDiscountMessages(productDetail, productId, variantId);\n  renderInstallmentMessages(productDetail, productId, variantId);\n  renderSubscriptionPrices(variantId);\n  renderSubscriptionOneTimePrices(productDetail, variantId);\n}\n\n/**\n * Gives the gift with purchase section's subscription option the given variant's price and compare\n * at price. That option shows the one-time price rather than a selling plan price, so we read both\n * from the one-time option's product price.\n */\nfunction renderSubscriptionOneTimePrices(productDetail: HTMLElement, variantId: number) {\n  type PriceElement = HTMLElementTagNameMap['price-element'];\n\n  const price = productDetail.querySelector<PriceElement>(\n    '.purchase-option price-element.subscription-one-time-price');\n  if (!price) {\n    return;\n  }\n\n  const compareAtPrice = price.parentElement.querySelector<PriceElement>(\n    ':scope > price-element.price-discount');\n  if (!compareAtPrice) {\n    return;\n  }\n\n  const variantElement = productDetail.querySelector<VariantElement>(\n    `.purchase-option product-price variant-element[data-id=\"${variantId}\"]`);\n  if (!variantElement) {\n    return;\n  }\n\n  price.dataset.subunits = variantElement.dataset.price;\n  compareAtPrice.dataset.subunits = variantElement.dataset.compareAtPrice;\n}\n\n/**\n * Points the bundle's product prices at the given bundle variant, and shows that variant's prices\n * in the bundle total.\n */\nfunction renderBundlePrices(productDetail: HTMLElement, productId: number, variantId: number) {\n  const productElements = productDetail.querySelectorAll<ProductElement>(\n    `product-price > product-element[data-id=\"${productId}\"]`);\n  for (const productElement of productElements) {\n    productElement.dataset.selectedVariantId = variantId.toString();\n    const productPrice = productElement.closest('product-price');\n    productPrice.dataset.update = Date.now().toString();\n  }\n\n  const variantElement = productDetail.querySelector<VariantElement>(\n    `product-price > product-element[data-id=\"${productId}\"] >\n    variant-element[data-id=\"${variantId}\"]`);\n  type PriceElement = HTMLElementTagNameMap['price-element'];\n  const totalPrice = productDetail.querySelector<PriceElement>('.bundle-pricing #price');\n  const totalCompareAtPrice = productDetail.querySelector<PriceElement>(\n    '.bundle-pricing #compare-at-price');\n  if (variantElement && totalPrice && totalCompareAtPrice) {\n    totalPrice.dataset.subunits = variantElement.dataset.price;\n    totalCompareAtPrice.dataset.subunits = variantElement.dataset.compareAtPrice;\n  }\n}\n\n/**\n * Shows the price with the code for the given variant in the snippet messages, the ones in the\n * price box, in the subscription selector, and in the one-time purchase option.\n */\nfunction renderSnippetMessages(productDetail: HTMLElement, variantId: number) {\n  const messages = productDetail.querySelectorAll<HTMLElement>('.discount-message');\n  for (const message of messages) {\n    renderSnippetMessage(message, variantId);\n  }\n}\n\nfunction renderSnippetMessage(message: HTMLElement, variantId: number) {\n  type PriceElement = HTMLElementTagNameMap['price-element'];\n  const priceElement = message.querySelector<PriceElement>('.price price-element');\n  if (!priceElement) {\n    return;\n  }\n\n  // We read the variant's data from the element holding the message. That is the product price in\n  // the price box and in the one-time purchase option, and the product slot in the subscription\n  // selector.\n  let variantElement: VariantElement;\n  const priceBox = message.closest('.price-box');\n  const purchaseOption = message.closest('.purchase-option');\n  const sellingPlanSelector = message.closest('selling-plan-selector');\n  if (priceBox) {\n    variantElement = priceBox.querySelector<VariantElement>(\n      `product-price variant-element[data-id=\"${variantId}\"]`);\n  } else if (purchaseOption) {\n    variantElement = purchaseOption.querySelector<VariantElement>(\n      `product-price variant-element[data-id=\"${variantId}\"]`);\n  } else if (sellingPlanSelector) {\n    variantElement = sellingPlanSelector.querySelector<VariantElement>(\n      `[slot=\"product\"] > variant-element[data-id=\"${variantId}\"]`);\n  }\n\n  if (!variantElement) {\n    return;\n  }\n\n  const price = parseInt(variantElement.dataset.price, 10);\n  const percentage = parseFloat(message.dataset.discountPercentage);\n  priceElement.dataset.subunits = Math.round(price - price * percentage / 100).toString();\n}\n\n/**\n * Gives the product discount messages and discount modals of the given product the prices of the\n * given variant. Only that product's messages follow, so an add-on in the section keeps its own. A\n * message whose price an offer template overrides keeps that price.\n */\nfunction renderProductDiscountMessages(productDetail: HTMLElement, productId: number,\n  variantId: number) {\n  type MessageElement = HTMLElementTagNameMap['product-discount-message'];\n  type ModalElement = HTMLElementTagNameMap['product-discount-modal'];\n\n  const variantElement = productDetail.querySelector<VariantElement>(\n    `product-price product-element[data-id=\"${productId}\"] >\n    variant-element[data-id=\"${variantId}\"]`);\n  if (!variantElement) {\n    return;\n  }\n\n  const price = parseInt(variantElement.dataset.price, 10);\n  let compareAtPrice = parseInt(variantElement.dataset.compareAtPrice, 10);\n  if (!Number.isInteger(compareAtPrice) || compareAtPrice < price) {\n    compareAtPrice = price;\n  }\n\n  const messages = productDetail.querySelectorAll<MessageElement>(\n    `product-discount-message[data-product-id=\"${productId}\"]:not([data-offer-price-override])`);\n  for (const message of messages) {\n    message.dataset.price = price.toString();\n    message.dataset.compareAtPrice = compareAtPrice.toString();\n  }\n\n  const modals = productDetail.querySelectorAll<ModalElement>(\n    `product-discount-modal[data-product-id=\"${productId}\"]:not([data-offer-price-override])`);\n  for (const modal of modals) {\n    modal.dataset.price = price.toString();\n  }\n}\n\n/**\n * Gives the installment messages of the given product the estimated total for the given variant,\n * the variant's price with the message's sale price percentage taken off when it has one. The\n * message hides itself when that total is under the installment threshold.\n */\nfunction renderInstallmentMessages(productDetail: HTMLElement, productId: number,\n  variantId: number) {\n  type MessageElement = HTMLElementTagNameMap['installment-payment-message'];\n\n  const messages = productDetail.querySelectorAll<MessageElement>(\n    `installment-payment-message[data-product-id=\"${productId}\"]`);\n  for (const message of messages) {\n    renderInstallmentMessage(message, variantId);\n  }\n}\n\nfunction renderInstallmentMessage(message: HTMLElementTagNameMap['installment-payment-message'],\n  variantId: number) {\n  const variantElement = message.querySelector<VariantElement>(\n    `:scope > variant-element[data-id=\"${variantId}\"]`);\n  if (!variantElement) {\n    return;\n  }\n\n  // The server works the starting amount out the same way, so keep the two in step\n  const price = parseInt(variantElement.dataset.price, 10);\n  let estimatedTotal = price;\n  if (message.dataset.discountPercentage) {\n    const percentage = parseFloat(message.dataset.discountPercentage);\n    estimatedTotal = Math.round((100 - percentage) * price / 100);\n  }\n\n  message.dataset.estimatedTotal = estimatedTotal.toString();\n}\n\nfunction onReady() {\n  const productDetail = document.querySelector<HTMLElement>('#product-detail-section');\n  if (productDetail) {\n    productDetail.addEventListener('product-variant-option-changed', onProductVariantOptionChanged);\n  }\n}\n\nready(onReady);\n","import { ready } from '../../lib/dom-ready.js';\n\n// The modal-element around the money back guarantee handles the close button, the dismiss gestures,\n// and telling the modal manager when it opens and closes. We open it, and inert the product detail\n// section while it's open.\n\ntype ModalElement = HTMLElementTagNameMap['modal-element'];\n\nfunction onReady() {\n  const modal = document.querySelector('#pdp-mbg-modal');\n  if (!modal) {\n    return;\n  }\n\n  modal.addEventListener('modal-opened', onModalOpened);\n  modal.addEventListener('modal-closed', onModalClosed);\n\n  const mbgButtons = document.querySelectorAll<HTMLButtonElement>(\n    '.product-details .money-back-guarantee button');\n  for (const button of mbgButtons) {\n    button.addEventListener('click', onMBGButtonClicked);\n  }\n}\n\nfunction onMBGButtonClicked(event: MouseEvent) {\n  if (!(event.currentTarget instanceof HTMLElement)) {\n    return;\n  }\n\n  const focusEvent = new Event('focus-stack-push-requested', {\n    bubbles: true,\n    composed: true\n  });\n  event.currentTarget.dispatchEvent(focusEvent);\n\n  const modal = document.querySelector<ModalElement>('#pdp-mbg-modal');\n  modal.dataset.open = 'true';\n}\n\n/**\n * The modal manager inerts every other section on the page, but it can't inert ours, since the\n * modal lives in it too. That leaves the buy box and the gallery tabbable behind the open modal, so\n * we inert them ourselves. The modal renders as a sibling of the product detail page rather than\n * inside it, so this doesn't reach the modal.\n */\nfunction onModalOpened(_event: HTMLElementEventMap['modal-opened']) {\n  const section = document.querySelector('#product-detail-section');\n  if (section instanceof HTMLElement) {\n    section.inert = true;\n  }\n}\n\n/**\n * Takes the page back out of inert. The modal manager puts focus back on the trigger only after\n * this runs, so the trigger is no longer inert by then.\n */\nfunction onModalClosed(_event: HTMLElementEventMap['modal-closed']) {\n  const section = document.querySelector('#product-detail-section');\n  if (section instanceof HTMLElement) {\n    section.inert = false;\n  }\n}\n\nready(onReady);\n","/**\n * Try to play a video, and always report the outcome as a promise.\n *\n * Do not inline this function. Playing a video is inconsistent across browsers in several ways, and\n * wrapping the call in an async function is what smooths all of them over at once.\n *\n * Some browsers have no `play` function on the element at all, so the call throws a TypeError. Some\n * have one that returns undefined instead of a promise, which is how `play` worked before the spec\n * gained a return value. Some return a promise that rejects later, which is the modern behavior and\n * is how a browser reports that it refused to autoplay. And some throw synchronously, right out of\n * the call, rather than rejecting.\n *\n * An async function turns all four into the same thing. Whatever happens inside it, the caller gets\n * a promise back, so a synchronous throw arrives as a rejection and a missing or non-promise `play`\n * still produces a promise that resolves. That means one `catch` on the call site covers every\n * failure mode, and the caller never has to check whether it got a promise before attaching a\n * handler.\n *\n * Nothing here is actionable, because a refusal is the browser enforcing its own autoplay policy.\n * Restrictive in-app browsers and low power mode refuse even after a user gesture, and interrupted\n * or unsupported sources reject too. The video simply stays paused, so callers should keep a debug\n * breadcrumb and never warn.\n */\nexport async function play(video: HTMLVideoElement) {\n  return video.play();\n}\n","import { ready } from '../../lib/dom-ready.js';\n\nfunction onReady() {\n  // We must query the DOM to determine if a flash sale countdown timer override exists. If none is\n  // found we have no need to continue further and short circuit.\n  const overrideEl = document.querySelector<HTMLElement>('#product-detail-countdown-timer');\n  if (!overrideEl) {\n    return;\n  }\n\n  if (!overrideEl.dataset.startTime || !overrideEl.dataset.endTime) {\n    return;\n  }\n\n  const flashStartTime = parseInt(overrideEl.dataset.startTime, 10);\n  const flashEndTime = parseInt(overrideEl.dataset.endTime, 10);\n  const timestamp = Math.floor(Date.now() / 1000);\n\n  if (timestamp > flashStartTime && timestamp < flashEndTime) {\n    let appendTimer = false;\n    let timerEl = document.querySelector('product-detail-countdown-timer');\n\n    // Since the global countdown timer is uniquely shown based on the global timer we must check if\n    // it exists and create the element to be inserted into the DOM after all dataset attributes\n    // have been updated to ensure proper rendering.\n    if (!timerEl) {\n      timerEl = document.createElement('product-detail-countdown-timer');\n      appendTimer = true;\n    }\n\n    timerEl.dataset.startTime = overrideEl.dataset.startTime;\n    timerEl.dataset.endTime = overrideEl.dataset.endTime;\n\n    if (overrideEl.dataset.heading) {\n      timerEl.dataset.heading = overrideEl.dataset.heading;\n    }\n\n    if (appendTimer) {\n      overrideEl.appendChild(timerEl);\n    }\n  }\n}\n\nready(onReady);\n","import { select as selectBundleVariant } from '../../lib/bundle-variant.js';\nimport { ready } from '../../lib/dom-ready.js';\nimport { play } from '../../lib/video.js';\n\n// The gallery and its thumbnail strip are two separate carousel-element elements, and neither one\n// knows about the other. This module is what keeps them pointed at the same picture. A move on\n// either carousel moves the other one to the same index through data-go. The carousel ignores a\n// move to the slide it is already on, so the echo coming back from the other carousel stops there.\n\n// Keep track of the slide indices that have been viewed in each gallery so we can calculate the\n// percentage of the gallery that has been viewed. A sale page can carry more than one gallery.\nconst viewedSlidesByGallery = new WeakMap<Element, Set<number>>();\n\nfunction showVariantImage(variantId: number) {\n  const allVariantImages = document.querySelectorAll<HTMLElement>(\n    '.product-image-gallery [data-variant-image]');\n\n  let matched = false;\n  for (const el of allVariantImages) {\n    const isMatch = el.dataset.variantImage === variantId.toString();\n    el.classList.toggle('selected-variant', isMatch);\n    if (isMatch) {\n      matched = true;\n    }\n  }\n\n  if (!matched) {\n    // Variant has no image — show the default (first variant image). The main gallery and the nav\n    // strip each have their own default, so both need it back or the nav thumbnail stays hidden.\n    const defaultEls = document.querySelectorAll(\n      '.product-image-gallery [data-variant-image-default]');\n    for (const defaultEl of defaultEls) {\n      defaultEl.classList.add('selected-variant');\n    }\n  }\n}\n\nfunction goToVariantSlide(gallery: HTMLElement) {\n  if (gallery.querySelector('[data-variant-slide]')) {\n    gallery.dataset.go = '0';\n  }\n}\n\n/**\n * Finds the thumbnail strip that goes with a gallery, or the gallery that goes with a strip. Both\n * sit inside the same product image gallery container.\n */\nfunction findPartner(carousel: Element) {\n  let selector: string;\n  if (carousel.classList.contains('product-image-gallery-main')) {\n    selector = '.product-image-gallery-nav';\n  } else {\n    selector = '.product-image-gallery-main';\n  }\n\n  const container = carousel.closest('.product-image-gallery');\n  return container?.querySelector<HTMLElement>(selector);\n}\n\nfunction isGalleryCarousel(target: EventTarget): target is HTMLElement {\n  return target instanceof HTMLElement && (\n    target.classList.contains('product-image-gallery-main') ||\n    target.classList.contains('product-image-gallery-nav'));\n}\n\n/**\n * Marks the thumbnail button for the slide at the given index as the current one, the way the\n * carousel's navigation option used to mark the thumbnail slide itself.\n */\nfunction markCurrentThumbnail(nav: Element, index: number) {\n  const buttons = nav.querySelectorAll('[data-carousel-slide] > button');\n  for (const [buttonIndex, button] of buttons.entries()) {\n    if (buttonIndex === index) {\n      button.setAttribute('aria-current', 'true');\n    } else {\n      button.removeAttribute('aria-current');\n    }\n  }\n}\n\nfunction onSlideMoveStarted(event: WindowEventMap['carousel-slide-move-started']) {\n  if (!isGalleryCarousel(event.target)) {\n    return;\n  }\n\n  const partner = findPartner(event.target);\n  if (partner) {\n    partner.dataset.go = event.detail.index.toString();\n  }\n\n  if (event.target.classList.contains('product-image-gallery-nav')) {\n    markCurrentThumbnail(event.target, event.detail.index);\n  }\n}\n\n/**\n * A click or Enter on a thumbnail moves the strip to it, and the strip's move carries the gallery\n * along with it.\n */\nfunction onSlideClicked(event: WindowEventMap['carousel-slide-clicked']) {\n  if (!isGalleryCarousel(event.target)) {\n    return;\n  }\n\n  if (event.target.classList.contains('product-image-gallery-nav')) {\n    event.target.dataset.go = event.detail.index.toString();\n  } else {\n    toggleVideo(event.detail.slide);\n  }\n}\n\n/**\n * The gallery's videos play muted on a loop as part of the page, with no browser controls drawn\n * over them, so a tap on one is how a visitor pauses it and plays it again.\n */\nfunction toggleVideo(slide: HTMLElement) {\n  const video = slide.querySelector('video');\n  if (!video) {\n    return;\n  }\n\n  if (video.paused) {\n    // A refusal here is not actionable, so keep a breadcrumb and never warn.\n    play(video).catch(console.debug);\n  } else {\n    video.pause();\n  }\n}\n\n/**\n * The two carousels mount on their own, so whichever one mounts second catches up to the other.\n * They both start on the first slide, so this only matters when the first one moved before the\n * second one was ready.\n */\nfunction onCarouselMounted(event: WindowEventMap['carousel-element-mounted']) {\n  const carousel = event.target;\n  if (!isGalleryCarousel(carousel)) {\n    return;\n  }\n\n  const partner = findPartner(carousel);\n  if (partner?.dataset.index && partner.dataset.index !== carousel.dataset.index) {\n    carousel.dataset.go = partner.dataset.index;\n  }\n\n  if (carousel.classList.contains('product-image-gallery-nav')) {\n    markCurrentThumbnail(carousel, parseInt(carousel.dataset.index, 10));\n  }\n}\n\nfunction onSlideHidden(event: WindowEventMap['carousel-slide-hidden']) {\n  if (!(event.target instanceof HTMLElement)) {\n    return;\n  }\n\n  if (event.target.classList.contains('product-image-gallery-main')) {\n    const video = event.detail.slide.querySelector('video');\n    if (video && !video.paused) {\n      video.pause();\n    }\n  }\n}\n\nfunction onSlideVisible(event: WindowEventMap['carousel-slide-visible']) {\n  if (!(event.target instanceof HTMLElement)) {\n    return;\n  }\n\n  if (event.target.classList.contains('product-image-gallery-main')) {\n    handleSlideVisible(event.target, event.detail.slide, event.detail.index);\n  }\n}\n\nfunction handleSlideVisible(gallery: HTMLElement, slide: HTMLElement, index: number) {\n  // This module expects that the Shopify global is correctly declared and initialized. We\n  // intentionally are not being defensive about its presence here.\n\n  const video = slide.querySelector('video');\n  if (video && !Shopify.designMode) {\n    // a11y requirement - 1.4.2 Audio Control – Level A\n    video.muted = true;\n    // we delay setting the loop attribute until the video is visible as a workaround for a11y\n    // checkers that incorrectly flag looping muted videos as violations\n    video.loop = true;\n\n    // A refusal here is not actionable, so keep a breadcrumb and never warn.\n    play(video).catch(console.debug);\n  }\n\n  let viewedSlides = viewedSlidesByGallery.get(gallery);\n  if (!viewedSlides) {\n    viewedSlides = new Set();\n    viewedSlidesByGallery.set(gallery, viewedSlides);\n  }\n\n  viewedSlides.add(index);\n\n  // The carousel reports the first slides in view during its mount, before it writes its slide\n  // count, so we count the slides ourselves\n  const totalSlides = gallery.querySelectorAll('[data-carousel-slide]').length;\n\n  type Detail = ProductCarouselViewedEvent['detail'];\n\n  const detail: Detail = {\n    index,\n    is_video: !!video,\n    percent_viewed: (viewedSlides.size / totalSlides) * 100,\n    product_id: parseInt(gallery.dataset.productId, 10)\n  };\n\n  const variantImageEl = slide.querySelector<HTMLElement>('.selected-variant[data-variant-image]');\n  if (variantImageEl?.dataset.variantImage) {\n    detail.variant_id = parseInt(variantImageEl.dataset.variantImage, 10);\n  }\n\n  const event = new CustomEvent<Detail>('product-carousel-viewed', {\n    bubbles: true,\n    composed: true,\n    detail\n  });\n\n  gallery.dispatchEvent(event);\n}\n\nfunction updateVariantImage(gallery: HTMLElement,\n  event: WindowEventMap['product-variant-option-changed']) {\n  if (!Number.isInteger(event.detail.selected_variant_id)) {\n    return;\n  }\n\n  const productId = parseInt(gallery.dataset.productId, 10);\n\n  // Because the Shopify Bundle App creates a new abstraction product, the emitted values from the\n  // event.detail.id property will not match the passed in productId for bundle product id\n  // configured using the app. We can test the passed in productId against\n  // event.detail.bundle_product_id to confirm this is a bundle.\n  if (productId === event.detail.bundle_product_id) {\n    type ProductElement = HTMLElementTagNameMap['product-element'];\n    const bundle = document.querySelector<ProductElement>(\n      `#product-detail-section add-to-cart > product-element[data-id=\"${productId}\"]`);\n    if (!bundle) {\n      return;\n    }\n\n    const variant = selectBundleVariant(bundle, event.detail.title, event.detail.option_groups);\n    if (variant) {\n      showVariantImage(parseInt(variant.dataset.id, 10));\n      goToVariantSlide(gallery);\n    }\n\n    return;\n  }\n\n  // Due to the fact sale pages could have more than one product detail section each with their own\n  // respective image galleries, we must incorporate a check to ensure the scoped gallery is for the\n  // same product which originated the variant changed event.\n\n  if (productId === event.detail.id) {\n    showVariantImage(event.detail.selected_variant_id);\n    goToVariantSlide(gallery);\n  }\n}\n\nfunction isProductVariantOptionChangedEvent(value: any):\n  value is WindowEventMap['product-variant-option-changed'] {\n  return value instanceof Event && value.type === 'product-variant-option-changed';\n}\n\nfunction onReady() {\n  const gallery = document.querySelector<HTMLElement>('.product-image-gallery-main');\n  if (!gallery) {\n    return;\n  }\n\n  // The gallery may have mounted before these listeners went on, in which case it already reported\n  // its first slide as visible and we never heard it. So when we have not seen any slide yet but\n  // the gallery is mounted, we handle the slide it is showing now.\n  if (!viewedSlidesByGallery.has(gallery) && gallery.dataset.index) {\n    const slides = gallery.querySelectorAll<HTMLElement>('[data-carousel-slide]');\n    const index = parseInt(gallery.dataset.index, 10);\n    const slide = slides[index];\n    if (slide) {\n      handleSlideVisible(gallery, slide, index);\n    }\n  }\n\n  // Same for the thumbnail strip, which marks its current thumbnail once it mounts\n  const nav = findPartner(gallery);\n  if (nav?.dataset.index) {\n    markCurrentThumbnail(nav, parseInt(nav.dataset.index, 10));\n  }\n\n  type ProductVariantOptionChangedEvent = WindowEventMap['product-variant-option-changed'];\n  const onProductVariantOptionChanged = (event: ProductVariantOptionChangedEvent) => {\n    updateVariantImage(gallery, event);\n  };\n\n  for (const event of product_image_gallery.queue) {\n    if (isProductVariantOptionChangedEvent(event)) {\n      try {\n        onProductVariantOptionChanged(event);\n      } catch (error) {\n        console.warn(error);\n      }\n    }\n  }\n\n  addEventListener('product-variant-option-changed', onProductVariantOptionChanged);\n}\n\ntype ProductCarouselViewedEvent = CustomEvent<{\n  index: number;\n  is_video: boolean;\n  percent_viewed: number;\n  product_id: number;\n  variant_id?: number;\n}>;\n\ndeclare global {\n  /**\n   * Holds the global state for the product image gallery. Created in the product image gallery\n   * snippet before this module loads so that events can be captured and replayed once the module\n   * initializes.\n   */\n  var product_image_gallery: {\n    /**\n     * A listener used by the snippet to capture events before this module loads.\n     *\n     * This is only declared for awareness purposes. Nothing should use this other than the snippet.\n     */\n    listener: (event: Event) => void;\n\n    /**\n     * Captures events that occurred before this module loaded.\n     */\n    queue: Event[];\n  };\n\n  interface WindowEventMap {\n    'product-carousel-viewed': ProductCarouselViewedEvent;\n  }\n}\n\n// These go on now rather than once the document is ready, because a carousel can mount and report\n// its first slides before a ready callback gets to run.\naddEventListener('carousel-element-mounted', onCarouselMounted);\naddEventListener('carousel-slide-move-started', onSlideMoveStarted);\naddEventListener('carousel-slide-clicked', onSlideClicked);\naddEventListener('carousel-slide-visible', onSlideVisible);\naddEventListener('carousel-slide-hidden', onSlideHidden);\n\nready(onReady);\n","import { ready } from '../../lib/dom-ready.js';\n\ntype SellingPlanChangedEvent = GlobalEventHandlersEventMap['selling-plan-changed'];\ntype ProductVariantOptionChangedEvent =\n  GlobalEventHandlersEventMap['product-variant-option-changed'];\n\nfunction onSellingPlanChanged(event: SellingPlanChangedEvent) {\n  const addToBag = document.querySelector<HTMLElementTagNameMap['add-to-cart']>(\n    '#product-detail-section add-to-cart');\n  if (!addToBag) {\n    console.warn('add-to-cart not found');\n    return;\n  }\n\n  const sellingPlanId = event.detail.selling_plan_id;\n  if (sellingPlanId) {\n    addToBag.dataset.sellingPlanId = sellingPlanId;\n  } else {\n    delete addToBag.dataset.sellingPlanId;\n  }\n\n  addToBag.dataset.update = Date.now().toString();\n}\n\nfunction onProductVariantOptionChanged(event: ProductVariantOptionChangedEvent) {\n  const selectedVariantId = event.detail.selected_variant_id;\n\n  const sellingPlanSelector = document.querySelector('selling-plan-selector');\n  if (!sellingPlanSelector) {\n    // not a subscription product, or using v1 of the subscription UI\n    return;\n  }\n\n  sellingPlanSelector.dataset.selectedVariantId = selectedVariantId.toString();\n}\n\nfunction onReady() {\n  const productDetail = document.querySelector<HTMLElement>('#product-detail-section');\n  if (productDetail) {\n    productDetail.addEventListener('selling-plan-changed', onSellingPlanChanged);\n    productDetail.addEventListener('product-variant-option-changed', onProductVariantOptionChanged);\n  }\n}\n\nready(onReady);\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport html from './index.html';\nimport styles from './index.scss';\n\ntype VariantElement = HTMLElementTagNameMap['variant-element'];\n\n/**\n * Product variants, e.g. different colors or sizes\n **/\ninterface ProductVariant {\n  /**\n   * Whether the variant is available or out of stock\n   */\n  available: boolean;\n\n  /**\n   * Variant price in cents\n   **/\n  compareAtPrice?: number;\n\n  /**\n   * The ID of the variant\n   */\n  id: string;\n\n  /**\n   * Variant price in cents\n   **/\n  price: number;\n}\n\ninterface SubscriptionSellingPlan {\n  id: string;\n  name: string;\n\n  /**\n   * The price adjustment percentage of the selling plan, e.g. 10 for a 10% discount or 0 when there\n   * is no discount\n   */\n  priceAdjustmentPercentage: number;\n\n  /**\n   * Whether the selling plan is a subscription plan\n   */\n  recurringDeliveries: boolean;\n}\n\n/**\n * The selling plan selector component is responsible for displaying the available selling plans\n * (e.g. one-time purchase and subscription options) for a product, allowing the user to select a\n * selling plan, and communicating the selected selling plan to the rest of the app via a custom\n * event. It handles multiple variants even when the variants have different prices.\n */\nclass SellingPlanSelector extends HTMLElement implements CustomElement {\n  public static get observedAttributes() {\n    return [\n      'data-selected-variant-id'\n    ];\n  }\n\n  declare readonly dataset: {\n    /**\n     * Id of the subscription selling plan to start on. The Liquid sets it only when ?selling_plan=\n     * in the url names one of this product's subscription plans, and without it the selector starts\n     * on one-time purchase.\n     */\n    selectedSellingPlanId: string;\n\n    /**\n     * Id of the currently selected variant. This is used to determine which selling plans to show.\n     * This may be updated by the product form when the user selects a different variant - we react\n     * to those changes and update the selling plans accordingly.\n     */\n    selectedVariantId: string;\n\n    /**\n     * Text on the subscription badge\n     */\n    subscriptionBadgeText: string;\n\n    /**\n     * Text on the subscription CTA button\n     */\n    subscriptionCtaText: string;\n  };\n\n  private declare selectedVariant?: ProductVariant;\n  private subscriptionSellingPlans: SubscriptionSellingPlan[] = [];\n  private declare selectedSellingPlan?: SubscriptionSellingPlan;\n  private onPurchaseOptionChangedBound = this.onPurchaseOptionChanged.bind(this);\n  private onSellingPlanChangedBound = this.onSellingPlanChanged.bind(this);\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    const purchaseOptions = this.shadowRoot.querySelectorAll<HTMLElement>('.purchase-option');\n    for (const option of purchaseOptions) {\n      const radio = option.querySelector<HTMLInputElement>('input[type=\"radio\"]');\n      if (radio) {\n        radio.addEventListener('change', this.onPurchaseOptionChangedBound);\n      }\n\n      // for better UX the entire div wrapper is clickable to activate the radio button\n      option.addEventListener('click', onPurchaseOptionClicked);\n    }\n\n    const sellingPlansSelect = this.shadowRoot.querySelector<HTMLSelectElement>(\n      'select.selling-plans');\n    sellingPlansSelect.addEventListener('change', this.onSellingPlanChangedBound);\n\n    this.gatherData();\n    this.selectStartingSellingPlan();\n    this.render();\n  }\n\n  /**\n   * Starts on the subscription plan the page asked for, when there is one, by drawing the selector\n   * with the subscription picked and that plan in the frequency dropdown. Nothing else needs to\n   * hear about it. The url already carries the plan, since the page loaded from it, Shopify's\n   * delivery badge reads it from there, and the add to bag gets the same plan from the Liquid.\n   */\n  private selectStartingSellingPlan() {\n    const sellingPlanId = this.dataset.selectedSellingPlanId;\n    if (!sellingPlanId) {\n      return;\n    }\n\n    for (const plan of this.subscriptionSellingPlans) {\n      if (plan.id === sellingPlanId) {\n        this.selectedSellingPlan = plan;\n        break;\n      }\n    }\n\n    if (!this.selectedSellingPlan) {\n      return;\n    }\n\n    const radio = this.shadowRoot.querySelector<HTMLInputElement>(\n      'input[name=\"purchase-option\"][value=\"subscription\"]');\n    radio.checked = true;\n    this.showPurchaseOption('subscription');\n  }\n\n  /**\n   * Opens the frequency dropdown and marks the subscription as picked, or closes it and marks one-\n   * time purchase as picked.\n   */\n  private showPurchaseOption(purchaseOption: 'one-time' | 'subscription') {\n    const options = this.shadowRoot.querySelector<HTMLElement>('.subscription-options');\n    const oneTimeLabel = this.shadowRoot.querySelector('label .one-time');\n    const subscriptionLabel = this.shadowRoot.querySelector('label .subscription-cta');\n\n    if (purchaseOption === 'subscription') {\n      options.inert = false;\n      options.classList.remove('collapsed');\n      oneTimeLabel.classList.remove('selected');\n      subscriptionLabel.classList.add('selected');\n    } else {\n      options.classList.add('collapsed');\n      options.inert = true;\n      oneTimeLabel.classList.add('selected');\n      subscriptionLabel.classList.remove('selected');\n    }\n  }\n\n  public disconnectedCallback() {\n    const purchaseOptions = this.shadowRoot.querySelectorAll<HTMLElement>('.purchase-option');\n    for (const option of purchaseOptions) {\n      const radio = option.querySelector<HTMLInputElement>('input[type=\"radio\"]');\n      if (radio) {\n        radio.removeEventListener('change', this.onPurchaseOptionChangedBound);\n      }\n\n      option.removeEventListener('click', onPurchaseOptionClicked);\n    }\n\n    const sellingPlansSelect = this.shadowRoot.querySelector<HTMLSelectElement>(\n      'select.selling-plans');\n    sellingPlansSelect.removeEventListener('change', this.onSellingPlanChangedBound);\n  }\n\n  public attributeChangedCallback(name: string, oldValue: string, newValue: string) {\n    this.initializeShadowRoot();\n\n    if (oldValue === newValue) {\n      return;\n    }\n\n    if (this.isConnected) {\n      this.gatherData();\n      this.render();\n    }\n  }\n\n  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open' });\n      this.shadowRoot.innerHTML = `<style>${styles}</style>${html}`;\n    }\n  }\n\n  private gatherData() {\n    const variants = [];\n    const variantElements = this.querySelectorAll<VariantElement>(\n      '[slot=\"product\"] > variant-element');\n\n    for (const variantElement of variantElements) {\n      const variant: ProductVariant = {\n        available: variantElement.dataset.available === 'true',\n        id: variantElement.dataset.id,\n        price: parseInt(variantElement.dataset.price ?? '0', 10)\n      };\n\n      if (variantElement.dataset.compareAtPrice) {\n        const compareAtPrice = parseInt(variantElement.dataset.compareAtPrice, 10);\n        if (!isNaN(compareAtPrice)) {\n          variant.compareAtPrice = compareAtPrice;\n        }\n      }\n\n      variants.push(variant);\n    }\n\n    const selectedVariantId = this.dataset.selectedVariantId;\n    this.selectedVariant = undefined;\n    for (const variant of variants) {\n      if (variant.id === selectedVariantId) {\n        this.selectedVariant = variant;\n        break;\n      }\n    }\n\n    type SubscriptionSellingPlanElement = HTMLElementTagNameMap['subscription-selling-plan'];\n\n    const sellingPlans: SubscriptionSellingPlan[] = [];\n    const sellingPlanElements = this.querySelectorAll<SubscriptionSellingPlanElement>(\n      'subscription-selling-plan[slot=\"plans\"]');\n\n    for (const sellingPlanElement of sellingPlanElements) {\n      const sellingPlan: SubscriptionSellingPlan = {\n        id: sellingPlanElement.dataset.id,\n        name: sellingPlanElement.textContent.trim(),\n        priceAdjustmentPercentage: parseFloat(sellingPlanElement.dataset.priceAdjustmentPercentage),\n        recurringDeliveries: sellingPlanElement.dataset.recurringDeliveries === 'true'\n      };\n\n      if (isNaN(sellingPlan.priceAdjustmentPercentage)) {\n        sellingPlan.priceAdjustmentPercentage = 0;\n      }\n\n      sellingPlans.push(sellingPlan);\n    }\n\n    this.subscriptionSellingPlans = sellingPlans;\n  }\n\n  private render() {\n    const subscriptionCta = this.shadowRoot.querySelector('.subscription-cta');\n    subscriptionCta.textContent = this.dataset.subscriptionCtaText;\n\n    const subscriptionBadge = this.shadowRoot.querySelector('.subscription-badge');\n    subscriptionBadge.textContent = this.dataset.subscriptionBadgeText;\n\n    const sellingPlansSelect = this.shadowRoot.querySelector<HTMLSelectElement>(\n      'select.selling-plans');\n    sellingPlansSelect.innerHTML = '';\n\n    let activeSellingPlanId = this.selectedSellingPlan?.id;\n    // if no plan has been selected yet, default to the first one\n    if (!activeSellingPlanId && this.subscriptionSellingPlans.length > 0) {\n      activeSellingPlanId = this.subscriptionSellingPlans[0].id;\n    }\n\n    for (const plan of this.subscriptionSellingPlans) {\n      const option = document.createElement('option');\n      option.value = plan.id;\n      option.textContent = plan.name;\n      if (plan.id === activeSellingPlanId) {\n        option.selected = true;\n      }\n\n      sellingPlansSelect.appendChild(option);\n    }\n\n    this.updatePrices();\n  }\n\n  private updatePrices() {\n    if (!this.selectedVariant) {\n      return;\n    }\n\n    type PriceElement = HTMLElementTagNameMap['price-element'];\n    const oneTimePriceElement = this.querySelector<PriceElement>('[slot=\"one-time-price\"] .price');\n    const oneTimeCompareAtPriceElement = this.querySelector<PriceElement>(\n      '[slot=\"one-time-price\"] .compare-at-price');\n\n    const oneTimePrice = this.selectedVariant.price;\n    const oneTimeCompareAtPrice = this.selectedVariant.compareAtPrice;\n\n    oneTimePriceElement.dataset.subunits = oneTimePrice.toString();\n    if (oneTimeCompareAtPrice) {\n      oneTimeCompareAtPriceElement.dataset.subunits = oneTimeCompareAtPrice.toString();\n      oneTimeCompareAtPriceElement.dataset.hide = 'false';\n    } else {\n      oneTimeCompareAtPriceElement.dataset.hide = 'true';\n    }\n\n    const subscriptionPriceElement = this.querySelector<PriceElement>(\n      '[slot=\"subscription-price\"] .price');\n    const subscriptionCompareAtPriceElement = this.querySelector<PriceElement>(\n      '[slot=\"subscription-price\"] .compare-at-price');\n\n    let priceAdjustmentPercentage;\n    if (this.selectedSellingPlan) {\n      // if the plan is selected - use its price adjustment percentage\n      priceAdjustmentPercentage = this.selectedSellingPlan.priceAdjustmentPercentage;\n    } else if (this.subscriptionSellingPlans.length > 0) {\n      // if a one-time plan is currently selected but there are subscription plans available,\n      // default to the first subscription plan's price adjustment percentage for price calculation\n      priceAdjustmentPercentage = this.subscriptionSellingPlans[0].priceAdjustmentPercentage;\n    }\n\n    if (priceAdjustmentPercentage > 0) {\n      subscriptionPriceElement.dataset.subunits = calculatePrice(this.selectedVariant.price,\n        priceAdjustmentPercentage).toString();\n      subscriptionCompareAtPriceElement.dataset.subunits = this.selectedVariant.price.toString();\n      subscriptionCompareAtPriceElement.dataset.hide = 'false';\n    } else {\n      subscriptionPriceElement.dataset.subunits = this.selectedVariant.price.toString();\n      subscriptionCompareAtPriceElement.dataset.hide = 'true';\n    }\n  }\n\n  private onSellingPlanChanged(_event: Event) {\n    this.updateCurrentSellingPlan();\n  }\n\n  private onPurchaseOptionChanged(event: Event) {\n    if (!(event.currentTarget instanceof HTMLInputElement)) {\n      return;\n    }\n\n    if (event.currentTarget.value === 'subscription') {\n      this.showPurchaseOption('subscription');\n    } else {\n      this.showPurchaseOption('one-time');\n    }\n\n    this.updateCurrentSellingPlan();\n  }\n\n  private updateCurrentSellingPlan() {\n    const radio = this.shadowRoot.querySelector<HTMLInputElement>(\n      'input[name=\"purchase-option\"][value=\"subscription\"]');\n\n    let subscriptionPlan: SubscriptionSellingPlan;\n    // if the subscription option is selected, the selling plan is determined by the dropdown;\n    // otherwise the selling plan is the \"one-time purchase\" option, which doesn't have a selling\n    // plan id and is represented by undefined\n    if (radio && radio.checked) {\n      // get active subscription plan id\n      const activeSellingPlan = this.shadowRoot.querySelector<HTMLOptionElement>(\n        'select.selling-plans option:checked');\n      if (activeSellingPlan) {\n        const sellingPlanId = activeSellingPlan.value;\n        for (const plan of this.subscriptionSellingPlans) {\n          if (plan.id === sellingPlanId) {\n            subscriptionPlan = plan;\n            break;\n          }\n        }\n      }\n    }\n\n    // dispatch the event only if the selling plan has changed\n    if (this.selectedSellingPlan !== subscriptionPlan) {\n      this.selectedSellingPlan = subscriptionPlan;\n\n      type SellingPlanChangedEvent = GlobalEventHandlersEventMap['selling-plan-changed'];\n      type Detail = SellingPlanChangedEvent['detail'];\n\n      const event = new CustomEvent<Detail>('selling-plan-changed', {\n        bubbles: true,\n        composed: true,\n        detail: {\n          selling_plan_id: this.selectedSellingPlan?.id\n        }\n      });\n\n      this.dispatchEvent(event);\n    }\n\n    this.updatePrices();\n  }\n}\n\n/**\n * Calculate the price after applying the discount percentage. For example, if the original price is\n * $24.00 (2400 cents) and the discount percentage is 10, the final price will be $21.60 (2160\n * cents). We round the result to avoid fractional cents, e.g. when the original price is $19.99 and\n * the discount percentage is 15, the final price will be $16.99 instead of $16.9915.\n */\nfunction calculatePrice(price: number, adjustmentPercentage?: number) {\n  const discountPercent = (adjustmentPercentage ?? 0) / 100;\n  return Math.round(price * (1 - discountPercent));\n}\n\n/**\n * For better UX we allow clicking anywhere in the purchase option wrapper to activate the radio\n * button, so we need to forward those click events to the radio button. However, we don't want to\n * trigger that when the user is interacting with the selling plan dropdown, so we ignore click\n * events coming from there.\n */\nfunction onPurchaseOptionClicked(event: MouseEvent) {\n  if (event.target instanceof Element && event.target.tagName === 'SELECT') {\n    return;\n  }\n\n  if (event.currentTarget instanceof HTMLDivElement) {\n    const input = event.currentTarget.querySelector<HTMLInputElement>('input[type=\"radio\"]');\n    if (input && !input.checked) {\n      input.focus();\n      input.click();\n    }\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'selling-plan-selector': SellingPlanSelector;\n  }\n}\n\nif (!customElements.get('selling-plan-selector')) {\n  customElements.define('selling-plan-selector', SellingPlanSelector);\n}\n","import { ready } from '../../lib/dom-ready.js';\n\ntype AddToCartElement = HTMLElementTagNameMap['add-to-cart'];\ntype VariantElement = HTMLElementTagNameMap['variant-element'];\n\nfunction onCheckboxChanged(event: Event) {\n  if (event.currentTarget instanceof HTMLInputElement) {\n    const addon = event.currentTarget.closest<HTMLElement>('.addon');\n    if (addon) {\n      onVariantChanged(addon);\n    }\n  }\n}\n\nfunction onSelectChanged(event: Event) {\n  if (event.currentTarget instanceof HTMLSelectElement) {\n    const addon = event.currentTarget.closest<HTMLElement>('.addon');\n    if (addon) {\n      onVariantChanged(addon);\n    }\n  }\n}\n\n/**\n * Refresh the addon card contents after any of the possible state changes (checkbox, variant\n * select, etc)\n */\nfunction onVariantChanged(addon: HTMLElement) {\n  // Find the main add to cart button\n  const addToCart = document.querySelector<AddToCartElement>('add-to-cart[data-main=\"true\"]');\n  if (!addToCart) {\n    console.warn('Could not find main add to cart element to add addon variant data to');\n    return;\n  }\n\n  const addonContainer = addon.querySelector<HTMLElement>('.addon-container');\n\n  const select = addon.querySelector<HTMLSelectElement>('select.variant-selector');\n  const selectedVariantId = select?.value;\n\n  let addonVariantData = addonContainer?.querySelector<VariantElement>(\n    'product-element > variant-element[data-available=\"true\"]');\n\n  if (selectedVariantId) {\n    addonVariantData = addonContainer?.querySelector<VariantElement>(\n      'product-element > variant-element[data-id=\"' + selectedVariantId + '\"]');\n  }\n\n  if (!addonVariantData) {\n    console.warn('Could not find variant data for addon');\n    return;\n  }\n\n  const productElement = addonVariantData.closest('product-element');\n  let imageSrc = addonVariantData.dataset.imageSrc;\n  if (!imageSrc) {\n    if (productElement) {\n      imageSrc = productElement.dataset.mainImage;\n    }\n  }\n\n  const productImage = addon.querySelector('img');\n  if (productImage && imageSrc) {\n    productImage.src = imageSrc;\n  }\n\n  const backorderMessage = addon.querySelector<HTMLElement>('.backorder');\n  if (backorderMessage) {\n    const backorder = addonVariantData.dataset.backorder;\n    backorderMessage.textContent = backorder || '';\n    backorderMessage.hidden = !backorder;\n  }\n\n  const productId = productElement.dataset.id;\n\n  // remove any existing addon data from the add to cart element (any variants)\n  const selector = `variant-element[data-is-addon=\"true\"][data-product-id=\"${productId}\"]`;\n  const variants = addToCart.querySelectorAll(selector);\n  for (const variant of variants) {\n    variant.remove();\n  }\n\n  // if the checkbox is checked - add the current variant to the add to cart element\n  const checkbox = addonContainer.querySelector<HTMLInputElement>('input[type=\"checkbox\"]');\n  if (checkbox.checked) {\n    // Add the addon variant data to the add to cart element\n    const variant = <VariantElement>addonVariantData.cloneNode(true);\n    variant.dataset.isAddon = 'true';\n    variant.dataset.productId = productId;\n\n    if (variant.dataset.backorder) {\n      setCustomAttribute(variant, '_backorder_message', variant.dataset.backorder);\n    }\n\n    if (variant.dataset.backorderSellingPlanId) {\n      setCustomAttribute(variant, 'selling_plan', variant.dataset.backorderSellingPlanId);\n    }\n\n    addToCart.appendChild(variant);\n  }\n\n  addToCart.dataset.update = Date.now().toString();\n}\n\n/**\n * Sets a custom attribute on a variant element\n */\nfunction setCustomAttribute(variant: VariantElement, key: string, value: string) {\n  type CustomAttribute = HTMLElementTagNameMap['custom-attribute'];\n  let customAttribute = variant.querySelector<CustomAttribute>(\n    `custom-attribute[data-key=\"${key}\"]`);\n  if (!customAttribute) {\n    customAttribute = document.createElement('custom-attribute');\n    customAttribute.dataset.key = key;\n    variant.appendChild(customAttribute);\n  }\n\n  customAttribute.dataset.value = value;\n}\n\nfunction onReady() {\n  const addons = document.querySelectorAll<HTMLElement>('#product-detail-section .addon');\n  for (const addon of addons) {\n    const checkbox = addon.querySelector<HTMLInputElement>('input[type=\"checkbox\"]');\n    if (checkbox) {\n      checkbox.addEventListener('change', onCheckboxChanged);\n    }\n\n    const select = addon.querySelector<HTMLSelectElement>('select.variant-selector');\n    if (select) {\n      select.addEventListener('change', onSelectChanged);\n    }\n\n    onVariantChanged(addon);\n  }\n}\n\nready(onReady);\n","import { ready } from '../../lib/dom-ready.js';\n\n// The modal-element around the selling point disclaimers handles the close button, the dismiss\n// gestures, and telling the modal manager when it opens and closes. We pick which disclaimer shows,\n// open it, and inert the product detail section while it's open.\n\ntype ModalElement = HTMLElementTagNameMap['modal-element'];\n\nfunction onSellingPointButtonClicked(event: MouseEvent) {\n  if (!(event.currentTarget instanceof HTMLElement)) {\n    return;\n  }\n\n  const focusEvent = new Event('focus-stack-push-requested', {\n    bubbles: true,\n    composed: true\n  });\n  event.currentTarget.dispatchEvent(focusEvent);\n\n  const modal = document.querySelector<ModalElement>('#selling-point-disclaimer-modal');\n  if (!modal) {\n    console.warn('Selling point disclaimer modal not found');\n    return;\n  }\n\n  const contentItems = modal.querySelectorAll<HTMLElement>('.content p');\n  for (const item of contentItems) {\n    if (item.dataset.id === event.currentTarget.dataset.contentId) {\n      item.hidden = false;\n    } else {\n      item.hidden = true;\n    }\n  }\n\n  modal.dataset.open = 'true';\n}\n\n/**\n * The modal manager inerts every other section on the page, but it can't inert ours, since the\n * modal lives in it too. That leaves the buy box and the gallery tabbable behind the open modal, so\n * we inert them ourselves. The modal renders as a sibling of the product detail page rather than\n * inside it, so this doesn't reach the modal.\n */\nfunction onModalOpened(_event: HTMLElementEventMap['modal-opened']) {\n  const section = document.querySelector('#product-detail-section');\n  if (section instanceof HTMLElement) {\n    section.inert = true;\n  }\n}\n\n/**\n * Takes the page back out of inert. The modal manager puts focus back on the trigger only after\n * this runs, so the trigger is no longer inert by then.\n */\nfunction onModalClosed(_event: HTMLElementEventMap['modal-closed']) {\n  const section = document.querySelector('#product-detail-section');\n  if (section instanceof HTMLElement) {\n    section.inert = false;\n  }\n}\n\nfunction onReady() {\n  const modal = document.querySelector('#selling-point-disclaimer-modal');\n  if (!modal) {\n    return;\n  }\n\n  modal.addEventListener('modal-opened', onModalOpened);\n  modal.addEventListener('modal-closed', onModalClosed);\n\n  const sellingPointButtons = document.querySelectorAll<HTMLButtonElement>(\n    '#product-detail-section .why-love-it .body button');\n  for (const button of sellingPointButtons) {\n    const item = button.closest<HTMLElement>('li[data-id]');\n    if (!item) {\n      button.inert = true;\n      continue;\n    }\n\n    button.setAttribute('aria-label', 'Open a window with more information');\n    button.dataset.contentId = item.dataset.id;\n    button.addEventListener('click', onSellingPointButtonClicked);\n  }\n}\n\nready(onReady);\n","import { ready } from '../../lib/dom-ready.js';\nimport type {} from '@integrabeauty/klaviyo-types';\n\n// B2S is the back to school promotion\n\nfunction onB2sCtaClicked(event: MouseEvent) {\n  const target = event.currentTarget;\n\n  if (!(target instanceof HTMLButtonElement)) {\n    return;\n  }\n\n  if (!window.klaviyo) {\n    return;\n  }\n\n  const openEvent = new Event('klaviyo-form-opened', { bubbles: true });\n  klaviyo.openForm('T4k3yf', () => target.dispatchEvent(openEvent));\n}\n\nfunction onReady() {\n  const params = new URLSearchParams(location.search);\n  const utmCampaign = params.get('utm_campaign');\n  const isB2sPromo = utmCampaign?.toLowerCase() === 'b2s';\n  const isB2sProduct = location.pathname.includes('axia-dryer') ||\n    location.pathname.includes('dorm-kit');\n\n  if (isB2sPromo && isB2sProduct) {\n    const b2sCta = document.querySelector<HTMLButtonElement>('.b2s-promo-cta');\n    if (!b2sCta) {\n      return;\n    }\n\n    b2sCta.style.display = 'block';\n    b2sCta.addEventListener('click', onB2sCtaClicked);\n  }\n}\n\nready(onReady);\n"],"names":["FullscreenGallery","HTMLElement","observedAttributes","containerMoved","dragStarted","scale","innerWidth","scaleStarted","videoIndexes","slidesIndex","slidesTotal","onGalleryOpenBound","this","galleryOpen","bind","onGalleryCloseBound","requestClose","onModalDismissRequestedBound","onModalDismissRequested","onToggleZoomBound","toggleZoom","onSlideVisibleBound","onSlideVisible","onCarouselPrevBound","onCarouselPrev","onCarouselNextBound","onCarouselNext","onCarouselMountedBound","onCarouselMounted","onViewportChangedBound","onViewportChanged","onStartDragBound","onStartDrag","onStopDragBound","onStopDrag","onMoveDragBound","onMoveDrag","onStartTouchBound","onStartTouch","onStopTouchBound","onStopTouch","onMoveTouchBound","onMoveTouch","connectedCallback","initializeShadowRoot","closeButton","shadowRoot","querySelector","addEventListener","zoomButton","prevButton","nextButton","visualViewport","readCarousel","render","disconnectedCallback","removeEventListener","container","attributeChangedCallback","name","oldValue","newValue","isConnected","expand","collapse","attachShadow","mode","innerHTML","visualCurrentSlideNumber","textContent","toString","counter","setAttribute","join","arrows","querySelectorAll","arrow","hidden","_event","updateViewportVars","style","viewport","width","height","innerHeight","offsetTop","offsetLeft","setProperty","Math","ceil","carousel","dataset","slideCount","parseInt","index","slides","slide","entries","classList","contains","push","event","detail","goTo","go","KeyboardEvent","includes","code","preventDefault","type","clearZoom","total","prevIndex","nextIndex","_target$parentElement","target","tagName","parentElement","Element","closest","requestOpen","modalState","Event","bubbles","composed","dispatchEvent","passive","expanded","controlsHidden","focusable","focusableEl","tabIndex","focus","focusVisible","carouselEl","zoomEl","zoom","transform","matches","match","posX","clientX","posY","clientY","dragging","document","body","offsetX","offsetY","getBoundingClientRect","constraintX","floor","clientWidth","constraintY","clientHeight","touches","length","x","y","pageX","pageY","ready","callback","readyState","timer","setTimeout","clearTimeout","bundle_variant_select","bundle","productTitle","options","option","bundleOption","selected","selected_option","values","bundleOptions","position","title","onPurchaseOptionChanged","HTMLInputElement","console","error","plans","value","inert","remove","onSellingPlanChanged","updateSellingPlanIdOnAddToBag","dispatchSellingPlanChanged","add","HTMLSelectElement","sellingPlanId","selectedIndex","updateProductDiscountPercentage","updateProductSellingPlanPrices","CustomEvent","selling_plan_id","sellingPlanEl","warn","priceEl","compareEl","newPrice","subunits","price","comparePrice","badgeEl","createElement","display","insertAdjacentElement","discountValue","discountPercent","round","priceEls","addToBag","update","Date","now","onPurchaseOptionClick","_event$target","currentTarget","HTMLDivElement","input","checked","click","onProductVariantOptionChanged","productDetail","productId","id","variantId","selected_variant_id","bundle_product_id","variant","option_groups","productElements","productElement","selectedVariantId","variantElement","totalPrice","totalCompareAtPrice","compareAtPrice","renderBundlePrices","messages","message","renderSnippetMessage","renderSnippetMessages","Number","isInteger","modals","modal","renderProductDiscountMessages","renderInstallmentMessage","renderInstallmentMessages","plan","renderVariant","renderSubscriptionOneTimePrices","priceElement","priceBox","purchaseOption","sellingPlanSelector","percentage","parseFloat","discountPercentage","estimatedTotal","onMBGButtonClicked","focusEvent","open","onModalOpened","section","onModalClosed","async","play","video","customElements","get","define","overrideEl","startTime","endTime","flashStartTime","flashEndTime","timestamp","appendTimer","timerEl","heading","appendChild","optionWrappers","wrapper","radio","subscriptionRadio","_document$querySelect","activeSellingPlan","mbgButtons","button","viewedSlidesByGallery","WeakMap","showVariantImage","allVariantImages","matched","el","isMatch","variantImage","toggle","defaultEls","defaultEl","goToVariantSlide","gallery","findPartner","selector","isGalleryCarousel","markCurrentThumbnail","nav","buttons","buttonIndex","removeAttribute","handleSlideVisible","Shopify","designMode","muted","loop","catch","debug","viewedSlides","Set","set","totalSlides","is_video","percent_viewed","size","product_id","variantImageEl","variant_id","isProductVariantOptionChangedEvent","subscriptions_v2_onSellingPlanChanged","subscriptions_v2_onProductVariantOptionChanged","partner","paused","pause","toggleVideo","has","updateVariantImage","product_image_gallery","queue","SellingPlanSelector","subscriptionSellingPlans","onPurchaseOptionChangedBound","onSellingPlanChangedBound","purchaseOptions","onPurchaseOptionClicked","gatherData","selectStartingSellingPlan","selectedSellingPlanId","selectedSellingPlan","showPurchaseOption","oneTimeLabel","subscriptionLabel","variants","variantElements","_variantElement$datas","available","isNaN","selectedVariant","undefined","sellingPlans","sellingPlanElements","sellingPlanElement","sellingPlan","trim","priceAdjustmentPercentage","recurringDeliveries","_this$selectedSelling","subscriptionCtaText","subscriptionBadgeText","sellingPlansSelect","activeSellingPlanId","updatePrices","oneTimePriceElement","oneTimeCompareAtPriceElement","oneTimePrice","oneTimeCompareAtPrice","hide","subscriptionPriceElement","subscriptionCompareAtPriceElement","adjustmentPercentage","calculatePrice","updateCurrentSellingPlan","subscriptionPlan","_this$selectedSelling2","onCheckboxChanged","addon","onVariantChanged","onSelectChanged","addToCart","addonContainer","select","addonVariantData","imageSrc","mainImage","productImage","src","backorderMessage","backorder","cloneNode","isAddon","setCustomAttribute","backorderSellingPlanId","key","customAttribute","onSellingPointButtonClicked","contentItems","item","contentId","selling_points_onModalOpened","selling_points_onModalClosed","onB2sCtaClicked","HTMLButtonElement","window","klaviyo","openEvent","openForm","addons","checkbox","sellingPointButtons","utmCampaign","URLSearchParams","location","search","isB2sPromo","toLowerCase","isB2sProduct","pathname","b2sCta"],"sourceRoot":""}